Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
07780fa10c | ||
|
|
a6b7f0a9d4 | ||
|
|
5b72142aa3 | ||
|
|
476d2fee09 | ||
|
|
b95c559564 | ||
|
|
cfdba71f90 | ||
|
|
8ac90c693d | ||
|
|
74546578ac | ||
|
|
88c6c17767 | ||
|
|
f5223d754a | ||
|
|
278f7d8381 | ||
|
|
2dddc58754 | ||
|
|
d454e610f1 | ||
|
|
4d06069138 | ||
|
|
3f0a5d9e5d | ||
|
|
85bda72038 | ||
|
|
1c7df2958a | ||
|
|
302d412530 | ||
|
|
ca2e54c361 | ||
|
|
b9706e456a | ||
|
|
e1478eefd4 | ||
|
|
3a2452baf6 | ||
|
|
82c9a88aa2 | ||
|
|
7ef007bbf5 | ||
|
|
d32fc495c8 | ||
|
|
d3635eed77 | ||
|
|
a16b48d8f3 | ||
|
|
6ad9dac71a | ||
|
|
a921f77258 | ||
|
|
8253d17529 | ||
|
|
d3be3d8912 | ||
|
|
a6e0226189 | ||
|
|
f3dbebd467 | ||
|
|
b7b9322e33 | ||
|
|
5e4ab7f307 | ||
|
|
2b159b0c75 | ||
|
|
984ba8a377 | ||
|
|
01be916c37 | ||
|
|
c1d98ba59c | ||
|
|
a1823af745 | ||
|
|
4a01d23025 | ||
|
|
27632b118f | ||
|
|
0e54de63cc | ||
|
|
c0c5906d07 | ||
|
|
f6f4ee708a | ||
|
|
8f54f83285 | ||
|
|
e393b4aaa6 | ||
|
|
8822aa4ece | ||
|
|
3e83897752 | ||
|
|
1d7cdfae5d | ||
|
|
0e71b7c01d | ||
|
|
3c8bff70df | ||
|
|
6de0c06ada | ||
|
|
4ede0ac923 | ||
|
|
fa3f1b277f | ||
|
|
5ade5cca03 | ||
|
|
99a5b4aed3 | ||
|
|
07026bc7a9 | ||
|
|
0ed9694199 | ||
|
|
098be788ae | ||
|
|
c6609bd248 | ||
|
|
cadc3869b3 | ||
|
|
4669da6074 | ||
|
|
439c9143af | ||
|
|
b9340ada3e | ||
|
|
88e2761b18 | ||
|
|
02516361c7 | ||
|
|
31ca8f9dbf | ||
|
|
a657e25a81 | ||
|
|
e355e50c0d | ||
|
|
af8f590646 | ||
|
|
c3ecbc9d48 | ||
|
|
a09986baa2 | ||
|
|
e9ca0ffad1 | ||
|
|
aed6fd3fcc | ||
|
|
e77b2ae288 | ||
|
|
828e5ba0b8 | ||
|
|
0eee97b988 | ||
|
|
358342aa4f | ||
|
|
6b08f97910 | ||
|
|
045edcca2f | ||
|
|
b4050276a9 | ||
|
|
6639353568 | ||
|
|
928a693491 | ||
|
|
f08148cabb | ||
|
|
d1ca38e106 | ||
|
|
15bdf6cd2d | ||
|
|
4037c763c2 | ||
|
|
d4fb739981 | ||
|
|
4bd6de5995 | ||
|
|
61b0d0ba9e | ||
|
|
56bfe15eca | ||
|
|
c10d26cf02 | ||
|
|
695babf7ba | ||
|
|
ecf1887e5b | ||
|
|
c6e8f54fe7 | ||
|
|
a4062bd15c | ||
|
|
4d352d6eb1 | ||
|
|
737b0e0504 | ||
|
|
16e5032528 | ||
|
|
26bd1b5787 | ||
|
|
13f3ff15bd | ||
|
|
e88e67f02c | ||
|
|
9c7b27acf0 | ||
|
|
aad5b4dd7e | ||
|
|
c12a52d20e | ||
|
|
0c59de40e7 | ||
|
|
a897a7711c | ||
|
|
fb8167c6f1 | ||
|
|
0482d0716f | ||
|
|
f183cb491f | ||
|
|
80770cc96e | ||
|
|
21d061fa44 | ||
|
|
7d27df0907 | ||
|
|
768e2a31fa | ||
|
|
487392b99c | ||
|
|
50af75972a | ||
|
|
88518ba278 | ||
|
|
a83359da7d | ||
|
|
9c4b4ca35f | ||
|
|
2e31087818 | ||
|
|
11e520e212 | ||
|
|
3254870899 | ||
|
|
51210045f7 | ||
|
|
9e6ae4422e | ||
|
|
ce431dd73f | ||
|
|
934ec2467d | ||
|
|
ff4d0dae70 | ||
|
|
bac3d65ede | ||
|
|
ecb180c0c5 | ||
|
|
f01d91459b | ||
|
|
3e07f03c87 | ||
|
|
28e0b14220 | ||
|
|
f82ef55c67 | ||
|
|
53ba2f0aff | ||
|
|
45b36b378d | ||
|
|
41a5c6f523 | ||
|
|
4f1d87f893 | ||
|
|
66c92fe075 | ||
|
|
ebc5dcb4bb | ||
|
|
afff92c248 | ||
|
|
ead3518b4b | ||
|
|
c38dcb1d73 | ||
|
|
00fe47ba62 | ||
|
|
b7d49cdeb7 | ||
|
|
495a35a6a6 | ||
|
|
9f13d28508 | ||
|
|
de4cc0ad54 | ||
|
|
7f4c527fc5 | ||
|
|
8a305ee189 | ||
|
|
718e93bd23 | ||
|
|
254be128b0 | ||
|
|
2c49cd1e0a | ||
|
|
ec167554c5 | ||
|
|
f24f38be9c | ||
|
|
0f89f8562f | ||
|
|
8eb52538de | ||
|
|
90629bc6d3 | ||
|
|
c6fea4939e | ||
|
|
0597162189 | ||
|
|
74e7d383c9 | ||
|
|
051320e73c | ||
|
|
6e04ccf677 | ||
|
|
436dc01f52 | ||
|
|
a6bb02aca1 | ||
|
|
ec5008c702 | ||
|
|
3453972ac4 | ||
|
|
7c37cd98dd | ||
|
|
a1eb940c51 | ||
|
|
bd6359dcb1 | ||
|
|
cc75b084d8 | ||
|
|
bd9314939b | ||
|
|
2298927331 | ||
|
|
b3b93b28df | ||
|
|
d68c1a8187 | ||
|
|
06a525456d | ||
|
|
9d19676ef1 | ||
|
|
540b55a35c | ||
|
|
2f435cfcce | ||
|
|
18f678a3c8 | ||
|
|
cc06a2d8ab | ||
|
|
1dd2cdf7a9 | ||
|
|
5944f069b6 | ||
|
|
aade206f22 | ||
|
|
22c3582699 | ||
|
|
2948deb210 | ||
|
|
db9139a0cd | ||
|
|
bbcbb5f7b8 | ||
|
|
1a872f3895 | ||
|
|
095d7e54c3 | ||
|
|
b2a484d5f0 | ||
|
|
8472272afe | ||
|
|
648632d5ac | ||
|
|
ea3456756d | ||
|
|
0672a0858f | ||
|
|
a1381fe0cf | ||
|
|
3baf8058ed | ||
|
|
daf92d8e08 | ||
|
|
c91b2c2907 | ||
|
|
6e0aea233b | ||
|
|
abd138026b | ||
|
|
3b9847d5d9 | ||
|
|
c4ec25bb14 | ||
|
|
0109bf0ac7 | ||
|
|
4482d307bf | ||
|
|
5aa2a7a14c | ||
|
|
984cad5b58 | ||
|
|
de8c1d262f | ||
|
|
2d964bdc95 | ||
|
|
91044ac0fa | ||
|
|
98aeb674c4 | ||
|
|
ea11c5eedc | ||
|
|
c146ee142d | ||
|
|
88d115aab1 | ||
|
|
fce954192c | ||
|
|
aab86c38b7 | ||
|
|
adc038d8d1 | ||
|
|
cfa9b4f638 | ||
|
|
e6fd970920 | ||
|
|
ad0a5477f9 | ||
|
|
2b617633ae | ||
|
|
7776e48a62 | ||
|
|
6c0075ab82 | ||
|
|
48534b1e30 | ||
|
|
8b6c5bdae4 | ||
|
|
b8abb22833 | ||
|
|
ae0a4fef50 | ||
|
|
4dcec8265d | ||
|
|
8360024e06 | ||
|
|
7a7867ae16 | ||
|
|
43f8d073db | ||
|
|
2e4d56efd6 | ||
|
|
6bc199a8bc | ||
|
|
ad42f27e03 | ||
|
|
c75cadf007 | ||
|
|
b080224072 | ||
|
|
537868008f | ||
|
|
63a9f879fb | ||
|
|
1664fce9d1 | ||
|
|
c2cf825cdf | ||
|
|
38a79b7f59 | ||
|
|
010766fe9e | ||
|
|
c09dfe6b5a | ||
|
|
d177bb8a47 | ||
|
|
a26dd1b1d7 | ||
|
|
34de08d649 | ||
|
|
74d0b3b51e | ||
|
|
6b33f4215b | ||
|
|
cc43782d87 | ||
|
|
c2eb900dd3 | ||
|
|
915a72ad18 | ||
|
|
320a8ad15b | ||
|
|
cf08b8b1aa | ||
|
|
001322751f | ||
|
|
0b3dd35624 | ||
|
|
cdf3e4b98e | ||
|
|
0d10479e8a | ||
|
|
357d0e918a | ||
|
|
3123418022 | ||
|
|
dcc2d16408 | ||
|
|
e9081c6607 | ||
|
|
439e57ca56 | ||
|
|
18ddf50b4c | ||
|
|
6ebac90cb0 | ||
|
|
5e0231ec5e | ||
|
|
c25bfafcb2 | ||
|
|
6e4f025d9c | ||
|
|
07ea8a2613 | ||
|
|
f9a0ba82a0 | ||
|
|
4c0fe7b006 | ||
|
|
cddef7cae5 | ||
|
|
197f4165a3 | ||
|
|
4a2affabb5 | ||
|
|
211e527d3f | ||
|
|
09a6c2eda0 | ||
|
|
71ec252298 | ||
|
|
9f10b21c9c | ||
|
|
62d823850c | ||
|
|
c48eefa82d | ||
|
|
65eff683dd | ||
|
|
b1ec2b788a | ||
|
|
f42e275884 | ||
|
|
8ce6d3863e | ||
|
|
f11314ed3e | ||
|
|
e080f00354 | ||
|
|
b00f0aee65 | ||
|
|
7b6364696c | ||
|
|
5dfd104728 | ||
|
|
88cb07203a | ||
|
|
bc7539c4ad | ||
|
|
0474227d6e | ||
|
|
04bbb14f59 | ||
|
|
f9f6e8ed26 | ||
|
|
9c2e9bab14 | ||
|
|
4539bde5f5 | ||
|
|
44db686ef3 | ||
|
|
221ef4adca | ||
|
|
c1d4e34ed0 | ||
|
|
4903c2b23c | ||
|
|
cd9b7bfcda | ||
|
|
c8ca295cda | ||
|
|
4e09889cab | ||
|
|
a66543f98e | ||
|
|
0705319f25 | ||
|
|
b153e6a0d2 | ||
|
|
ef892fd4ed | ||
|
|
024eb46c7d | ||
|
|
ecdd7aa7ce | ||
|
|
b09b556796 | ||
|
|
2d6a916b2d | ||
|
|
011fb55e5f | ||
|
|
ac4e87418a | ||
|
|
42ff61f97c | ||
|
|
08059412b5 | ||
|
|
f9615c34dc | ||
|
|
703b8dfd11 | ||
|
|
b86e6412fa | ||
|
|
aa1c1cc9be | ||
|
|
4b5fb2785e | ||
|
|
4eaeb0f30c | ||
|
|
9c096526b8 | ||
|
|
eee77d49d1 | ||
|
|
d2d1d2b43a | ||
|
|
4874605add | ||
|
|
922f465ecb | ||
|
|
c80bf7e996 | ||
|
|
dd505a8e17 | ||
|
|
08e9db900a | ||
|
|
86bfc91ac9 | ||
|
|
2eeb754454 | ||
|
|
2a10182cf6 | ||
|
|
115162fbfd | ||
|
|
82adc85562 | ||
|
|
05a7024b88 | ||
|
|
62e8028591 | ||
|
|
2bc5fd30c0 | ||
|
|
35671135bd | ||
|
|
a57335738d | ||
|
|
459bb08828 | ||
|
|
3ad9d8e785 | ||
|
|
52552b59fb | ||
|
|
f8e2af32cd | ||
|
|
66cd178308 | ||
|
|
be2dfda78e | ||
|
|
0009b05635 | ||
|
|
f2e38ae6c5 | ||
|
|
b83f4a00ed | ||
|
|
e2c8dba981 | ||
|
|
6584df274e | ||
|
|
7c65b6e3bb | ||
|
|
268d355cae | ||
|
|
b84ef98de8 | ||
|
|
29674b7780 | ||
|
|
9cfc104fc7 | ||
|
|
824329d4c9 | ||
|
|
604309fb53 | ||
|
|
73f1a04d5f | ||
|
|
b76e2a87cf | ||
|
|
b6eec08247 | ||
|
|
03563e847e | ||
|
|
4c159a9af6 | ||
|
|
bc61838aaa | ||
|
|
8c9937b0be | ||
|
|
bdc9eae000 | ||
|
|
95cbc8940e | ||
|
|
f29716387f | ||
|
|
30b9d33276 | ||
|
|
532a6b69d9 | ||
|
|
f2ef5e880a | ||
|
|
dd74bb1774 | ||
|
|
adf56d316d | ||
|
|
cb9af20a2e | ||
|
|
cb01e3f87b | ||
|
|
590c1dde1d | ||
|
|
be3f88656b | ||
|
|
246f394731 | ||
|
|
27367ab292 | ||
|
|
f5d8fc5de2 | ||
|
|
7cdf273c88 | ||
|
|
20620f0002 | ||
|
|
8d79e33ba1 | ||
|
|
7453c7a5dd | ||
|
|
ce4857f5dc | ||
|
|
71af1f3120 | ||
|
|
e65a08471f | ||
|
|
b96330d529 | ||
|
|
f17d50cc50 | ||
|
|
436f8ef7c8 | ||
|
|
65f435f042 | ||
|
|
561c8d012c | ||
|
|
bb4c0f5247 | ||
|
|
31a964ba9b | ||
|
|
b717399364 | ||
|
|
b79bb32782 | ||
|
|
6ff7419981 | ||
|
|
b228bbb9fb | ||
|
|
752baa4bd6 | ||
|
|
6cbde89eeb | ||
|
|
bceabe32a2 | ||
|
|
c5361801c9 | ||
|
|
3283bc89fc | ||
|
|
26519333c0 | ||
|
|
639cde0f67 | ||
|
|
6a9f9f66dc | ||
|
|
639e0e82c9 | ||
|
|
6f65512471 | ||
|
|
4b0a6e1433 | ||
|
|
d5b82cac45 | ||
|
|
cb6db91b09 | ||
|
|
6a02e2dc17 | ||
|
|
f4ad97b0c9 | ||
|
|
b377207faf | ||
|
|
24ebb7cc7c | ||
|
|
b5518db03c | ||
|
|
2aa4e6a83a | ||
|
|
7c655cc3ba | ||
|
|
d7a1bdd7a8 | ||
|
|
75cda01167 | ||
|
|
4f0f3cf98d | ||
|
|
d9637ce620 | ||
|
|
e014140134 | ||
|
|
c8ae128e59 | ||
|
|
12d848162d | ||
|
|
b12096bd6c | ||
|
|
644485d4ae | ||
|
|
1430982166 | ||
|
|
2a83aad1d6 | ||
|
|
4a7157959a | ||
|
|
e52abafc48 | ||
|
|
b064bcce49 | ||
|
|
0f9d9c77a3 | ||
|
|
33c81add8d | ||
|
|
ee531b8023 | ||
|
|
497a104db7 | ||
|
|
5e761a62b0 | ||
|
|
b442567406 | ||
|
|
5dd8584fb6 | ||
|
|
4bd18737b7 | ||
|
|
ba8926a6ea | ||
|
|
09cd9e091a | ||
|
|
17d53af2a2 | ||
|
|
2d4d3577ba | ||
|
|
546f9592f9 | ||
|
|
06f3793f05 | ||
|
|
f396c60402 | ||
|
|
ad036499eb | ||
|
|
38c6eb28e8 | ||
|
|
11e2515f70 | ||
|
|
c79e036c08 | ||
|
|
7a19c2f147 | ||
|
|
4cee647e73 | ||
|
|
7ea3299dc6 | ||
|
|
5707581ba3 | ||
|
|
264d5e4828 | ||
|
|
9311294d9c | ||
|
|
2bfc86e7b2 | ||
|
|
36ee8bacb0 | ||
|
|
1bb1108b91 | ||
|
|
a77201bf77 | ||
|
|
a3d842bf4a | ||
|
|
43eb70193b | ||
|
|
1228dbddb9 | ||
|
|
a401d0d6ea | ||
|
|
c00b00d623 | ||
|
|
7bbc9f1b82 | ||
|
|
6252871e93 | ||
|
|
15a92f9c92 | ||
|
|
9386c01845 | ||
|
|
6d1e33b558 | ||
|
|
ed3c50f43e | ||
|
|
9ac62e8005 | ||
|
|
034da376c4 | ||
|
|
10fbc75997 | ||
|
|
98d76e1099 | ||
|
|
f2a1b3829d | ||
|
|
e47c4a78f0 | ||
|
|
f9f6649f90 | ||
|
|
a5c9898884 | ||
|
|
839eb28310 | ||
|
|
e2ec2b436d | ||
|
|
07b79568dd | ||
|
|
1d3c80ab49 | ||
|
|
38013d2f41 | ||
|
|
65c48707f6 | ||
|
|
415e550f86 | ||
|
|
034d8933c0 | ||
|
|
cf24bb0e89 | ||
|
|
ae725e333a | ||
|
|
dde244160b | ||
|
|
fdd006022d | ||
|
|
1e01a31939 | ||
|
|
4117ef0c50 | ||
|
|
e3c19d6414 | ||
|
|
b1e6bc2929 | ||
|
|
feee4f0494 | ||
|
|
34ab86124b | ||
|
|
f37eb4b77d | ||
|
|
5232a9af6b | ||
|
|
90795e5446 | ||
|
|
c393b23b31 | ||
|
|
6bebce0b5a | ||
|
|
71da517e88 | ||
|
|
faa7bad8d8 | ||
|
|
710c13ced9 | ||
|
|
6bd8da905e | ||
|
|
839f534350 | ||
|
|
955fbca44a | ||
|
|
18c26e9fbb | ||
|
|
0a4b3a04ba | ||
|
|
78f2d82984 | ||
|
|
f8fa37c895 | ||
|
|
b54efbde69 | ||
|
|
c2a869491f | ||
|
|
a750ef8033 | ||
|
|
30fef10afa | ||
|
|
2e9c1ee046 | ||
|
|
81c49e6005 | ||
|
|
11ccada740 | ||
|
|
be9d64b2cc | ||
|
|
b2c9802aa2 | ||
|
|
c6f356a167 | ||
|
|
5fbceb0f78 | ||
|
|
3c6211f9f2 | ||
|
|
b9502ee1cd | ||
|
|
4f97b3ac85 | ||
|
|
e4828ad30a | ||
|
|
20dab1f8fe | ||
|
|
eae45ebc94 | ||
|
|
ea50b3d5cc | ||
|
|
3297b93edb | ||
|
|
6d0e867ad0 | ||
|
|
1cea9c1c87 | ||
|
|
3320a9e7d4 | ||
|
|
9b90a938f5 | ||
|
|
c0ece16881 | ||
|
|
95a4ca8e3e | ||
|
|
c6d0149ba6 | ||
|
|
598efe5d1a | ||
|
|
cab4e37ef8 | ||
|
|
0dff2e66c6 | ||
|
|
6680a8a054 | ||
|
|
3679065258 | ||
|
|
482045aed8 | ||
|
|
610a90c727 | ||
|
|
31f736b37e | ||
|
|
d220379ad3 | ||
|
|
4649f1158a | ||
|
|
952e9de265 | ||
|
|
ef1b8d6237 | ||
|
|
2970df4505 | ||
|
|
87f793f0e0 | ||
|
|
de17a44f35 | ||
|
|
52ce4df18e | ||
|
|
1a590b3ffa | ||
|
|
e249a7ef0c | ||
|
|
83412e4514 | ||
|
|
1a59734cdb | ||
|
|
7f3dc6f202 | ||
|
|
f1ed5bf838 | ||
|
|
256a801d16 | ||
|
|
c416cf8da5 | ||
|
|
efffbe6de1 | ||
|
|
a64435a9a5 | ||
|
|
c2fba38c46 | ||
|
|
d2ba647f68 | ||
|
|
a6aad6dc49 | ||
|
|
7455a699c4 | ||
|
|
0872623ca5 | ||
|
|
75e812a061 | ||
|
|
4cc7b01940 | ||
|
|
4d05775e11 | ||
|
|
a2ff7e5981 | ||
|
|
d5bfc61a73 | ||
|
|
7ec17ab2ef | ||
|
|
cda98a6375 | ||
|
|
03ac917567 | ||
|
|
3b7a754b0a | ||
|
|
c01c6bcb91 | ||
|
|
5d598a1015 | ||
|
|
adafb79e34 | ||
|
|
e47360f8dc | ||
|
|
6035b54277 | ||
|
|
a238130613 | ||
|
|
998be42a52 | ||
|
|
bafa3f4f4e | ||
|
|
14944da396 | ||
|
|
a3974c2aeb | ||
|
|
6570b0ba1a | ||
|
|
13ec517f94 | ||
|
|
2a2b2da097 | ||
|
|
43419815f4 | ||
|
|
b5256f7b71 | ||
|
|
0caa9dd652 | ||
|
|
dd7271aaa2 | ||
|
|
133c16f989 | ||
|
|
e631fb0d69 | ||
|
|
5e1e8ec543 | ||
|
|
432c1904da | ||
|
|
8a691814e2 | ||
|
|
553c8b734d | ||
|
|
3767a680e8 | ||
|
|
e27b320ede | ||
|
|
90afa7db93 | ||
|
|
efd4fc4704 | ||
|
|
d770532b08 | ||
|
|
31b303a419 | ||
|
|
9578798ec0 | ||
|
|
8061ea4079 | ||
|
|
a83b58cd27 | ||
|
|
8eeb6dd836 | ||
|
|
bd7e5d6921 | ||
|
|
7c1e80ca89 | ||
|
|
9aae1fa3c9 | ||
|
|
5bf440d558 | ||
|
|
c391092ea6 | ||
|
|
d97c385b4e | ||
|
|
1d3ca061ad | ||
|
|
d50975a185 | ||
|
|
6261ce0865 | ||
|
|
b4682bbc8c | ||
|
|
d9a32e563c | ||
|
|
b0e98f99d0 | ||
|
|
6684ae8316 | ||
|
|
9b4a5d1cb4 | ||
|
|
9f79e5be54 | ||
|
|
aa44b66a31 | ||
|
|
3a367595d7 | ||
|
|
e5c256daa1 | ||
|
|
f12ec3b295 | ||
|
|
9f0399f27e | ||
|
|
cc72f7d3ee | ||
|
|
c098aefed9 | ||
|
|
6a103aef27 | ||
|
|
80df5a7906 | ||
|
|
28561237ab | ||
|
|
b3a29bec1b | ||
|
|
b055c9a4a2 | ||
|
|
799160c5c2 | ||
|
|
3daceac668 | ||
|
|
29ebeeaff5 | ||
|
|
257d88f48e | ||
|
|
4611e8825a | ||
|
|
00e4729533 | ||
|
|
517a2e0e0a | ||
|
|
bb8c7c599c | ||
|
|
0d0fc39236 | ||
|
|
e468399616 | ||
|
|
b2b293a8ee | ||
|
|
2f885a7906 | ||
|
|
7985e43e68 | ||
|
|
f84b933d7c | ||
|
|
4672e9ec1f | ||
|
|
a5ddba03f7 | ||
|
|
302a5a4efc | ||
|
|
3b29e8e6f2 | ||
|
|
a1dff70ba2 | ||
|
|
9336cc4121 | ||
|
|
12671be260 | ||
|
|
d2ad5ddb01 | ||
|
|
73df093e48 | ||
|
|
9a4adccec6 | ||
|
|
abea21245d | ||
|
|
f90390974a | ||
|
|
0b4f1b5af0 | ||
|
|
9933a4a368 | ||
|
|
c3f2150b5b | ||
|
|
46566e2159 | ||
|
|
1d8d098450 | ||
|
|
1038cbd1a3 | ||
|
|
21a2cdd579 | ||
|
|
f598ae297b | ||
|
|
4b0fe502f0 | ||
|
|
d9b9ce6bcd | ||
|
|
4fffa6fa43 | ||
|
|
c0cd9dffc2 | ||
|
|
f91d7bb7e4 | ||
|
|
3e6f31c8e2 | ||
|
|
19b53cdeb1 | ||
|
|
c6cc10169f | ||
|
|
987d89632d | ||
|
|
8804f88c93 | ||
|
|
3c654bc036 | ||
|
|
8ddbb3d8af | ||
|
|
422e9b58cd | ||
|
|
64a017c972 | ||
|
|
e4295a2da9 | ||
|
|
216a0fe4fc | ||
|
|
604f5c599a | ||
|
|
9aa4313d37 | ||
|
|
969dc4e527 | ||
|
|
bbd0b15792 | ||
|
|
99b7fbbc71 | ||
|
|
486eb740b5 | ||
|
|
c6a73b8a75 | ||
|
|
262f8915a8 | ||
|
|
b623e914c9 | ||
|
|
70bfcd033b | ||
|
|
80770d39e5 | ||
|
|
beb15fa32c | ||
|
|
1bec425510 | ||
|
|
0adf7626d0 | ||
|
|
9c17fe259c | ||
|
|
ecab4b4716 | ||
|
|
c3e6669d18 | ||
|
|
7660ad761a | ||
|
|
d010000583 | ||
|
|
419f2ff71b | ||
|
|
4ebcd355f6 | ||
|
|
ec872d7b45 | ||
|
|
9801e00ba6 | ||
|
|
79452d3a9a | ||
|
|
0ee22af32d | ||
|
|
13f37edb64 | ||
|
|
75d9e95b15 | ||
|
|
d59594c6b0 | ||
|
|
4a6b72c649 | ||
|
|
20a8488a15 | ||
|
|
be8260ae79 | ||
|
|
9c31de9cb9 | ||
|
|
c1e8c9c537 | ||
|
|
c8ff2de3b3 | ||
|
|
24d1c73012 | ||
|
|
24a2e43547 | ||
|
|
2816899543 | ||
|
|
394d2add68 | ||
|
|
a9d212c888 | ||
|
|
c1b1276aaf | ||
|
|
370212609c | ||
|
|
159bad54b0 | ||
|
|
1cc7ca18ba | ||
|
|
281937cf82 | ||
|
|
d11b6e8075 | ||
|
|
4e22922256 | ||
|
|
8f01f6e85c | ||
|
|
27f477a075 | ||
|
|
82ac69be4e | ||
|
|
0f357cdf7c | ||
|
|
7709c6d0bd | ||
|
|
ef3e478506 | ||
|
|
93eef26d0b | ||
|
|
14d78cf08c | ||
|
|
872d88acb6 | ||
|
|
e96a63f550 | ||
|
|
7a5b69f850 | ||
|
|
a71d2a1682 | ||
|
|
f0f0e6f36d | ||
|
|
6ecdf69b88 | ||
|
|
58de14ff65 | ||
|
|
4d304146f3 | ||
|
|
e9520215a0 | ||
|
|
a2b192c472 | ||
|
|
52575915d0 | ||
|
|
0ecc0ee02e | ||
|
|
8101dea338 | ||
|
|
373f8a4463 | ||
|
|
8846274c42 | ||
|
|
c2356ebcde | ||
|
|
33812f7666 | ||
|
|
f58e3bf2a9 | ||
|
|
5473995305 | ||
|
|
1796582032 | ||
|
|
4214a09392 | ||
|
|
e833ab672d | ||
|
|
257b1a5353 | ||
|
|
39e29fd40b | ||
|
|
ec0c5f0375 | ||
|
|
b45ac75735 | ||
|
|
ac53d7d47b | ||
|
|
c7c669fd11 | ||
|
|
fdc7e15192 | ||
|
|
ba266ef8d3 | ||
|
|
f434fad3be | ||
|
|
bab758ebf9 | ||
|
|
800f4663b6 | ||
|
|
78ba69ef9f | ||
|
|
b1b62afa90 | ||
|
|
6b90ef7078 | ||
|
|
593e3d3593 | ||
|
|
2ed4b45c24 | ||
|
|
716f50c4e4 | ||
|
|
47ba9748b4 | ||
|
|
077ad4208f | ||
|
|
c19edec364 | ||
|
|
0c2aa494ae | ||
|
|
73e0a6bb0a | ||
|
|
abf1db0045 | ||
|
|
2f94d727ea | ||
|
|
b40b78ece0 | ||
|
|
3ade796e8d | ||
|
|
7c2e808924 | ||
|
|
7d2227c5a4 | ||
|
|
8a517d5c7c | ||
|
|
cb6176fd8a | ||
|
|
b93ce1cea5 | ||
|
|
3be28523c9 | ||
|
|
592335092c | ||
|
|
6c4e9fccd3 | ||
|
|
6903b8d458 | ||
|
|
274265f001 | ||
|
|
ceac3c3d83 | ||
|
|
48658f011f | ||
|
|
4ebb13fa94 | ||
|
|
5580b7a522 | ||
|
|
bda8cff7c1 | ||
|
|
ff9a6b91a4 | ||
|
|
86fc37a968 | ||
|
|
37ae169323 | ||
|
|
1ce0df4e94 | ||
|
|
db5ba61915 | ||
|
|
31006002f3 | ||
|
|
db120fb9be | ||
|
|
ea6e171a7f | ||
|
|
246cb76e50 | ||
|
|
ecf4a82b99 | ||
|
|
357f67255e | ||
|
|
7015ab8717 | ||
|
|
67195bc9f3 | ||
|
|
ce18509aa7 | ||
|
|
cbaa92dcb2 | ||
|
|
f09d87c112 | ||
|
|
7a8bba4d95 | ||
|
|
e6e414a017 | ||
|
|
81df06b3f4 | ||
|
|
5401037743 | ||
|
|
9d9768ee6f | ||
|
|
3043217b2c | ||
|
|
afcb84d42c | ||
|
|
d5aad8f8d2 | ||
|
|
c46febd7df | ||
|
|
f880da4827 | ||
|
|
85c9cf43f7 | ||
|
|
ce90d2389b | ||
|
|
d8cfbc4e08 | ||
|
|
88fad251aa | ||
|
|
1a1d8b7c74 | ||
|
|
a4ef96690e | ||
|
|
f28ad350bd | ||
|
|
0afc3db383 | ||
|
|
ca1cce3232 | ||
|
|
f9228eeb46 | ||
|
|
0c077b8bb1 | ||
|
|
0e9991f7d4 | ||
|
|
41482aff01 | ||
|
|
791fc9d6b1 | ||
|
|
776ccac00a | ||
|
|
1539009362 | ||
|
|
9542b77a34 | ||
|
|
b709cdeab5 | ||
|
|
e2f936fde3 | ||
|
|
a4ca389f2e | ||
|
|
c973808b20 | ||
|
|
e12dbb4605 | ||
|
|
c341399498 | ||
|
|
d1ba4ab7f4 | ||
|
|
85d4b1f137 | ||
|
|
b9792f8f96 | ||
|
|
e0cb9cf24d | ||
|
|
4e41d4d451 | ||
|
|
059cc65b55 | ||
|
|
d434b50dfc | ||
|
|
96f69583e7 | ||
|
|
ce25f58639 | ||
|
|
367b73e6e3 | ||
|
|
98a7d3fc99 | ||
|
|
6fd0f121a0 | ||
|
|
68d6c262d1 | ||
|
|
1d9ef76080 | ||
|
|
b51b1adfcd | ||
|
|
b1cb0c298d | ||
|
|
d7835eabfb | ||
|
|
ab902b45aa | ||
|
|
ed14561afd | ||
|
|
86d2db5cc2 | ||
|
|
700851cdd5 | ||
|
|
c3e1f5a6c6 | ||
|
|
9bf18b1761 | ||
|
|
1374e4c4de | ||
|
|
0ef57a102f | ||
|
|
1c7954a43d | ||
|
|
be1dc8ef96 | ||
|
|
18fc6ccc03 | ||
|
|
7ec46983aa | ||
|
|
eddcf93942 | ||
|
|
42807f7b8a | ||
|
|
0b521272ae | ||
|
|
ecd93bbfe4 | ||
|
|
a6f07fbaac | ||
|
|
f87876c1c3 | ||
|
|
ec3de1a072 | ||
|
|
c1d2c159f0 | ||
|
|
9f86e2ba43 | ||
|
|
d0be36dc9b | ||
|
|
af604a1477 | ||
|
|
c981cff450 | ||
|
|
ab1033c26c | ||
|
|
01706df7c8 | ||
|
|
827a7c1cf1 | ||
|
|
d14d9933b4 | ||
|
|
af946893f0 | ||
|
|
245ae5e987 | ||
|
|
29f3d711c6 | ||
|
|
3909936cc8 | ||
|
|
0602c91c54 | ||
|
|
a7bdb02a2b | ||
|
|
2cfaf5a119 | ||
|
|
20a48f05b3 | ||
|
|
4e70fb7f9d | ||
|
|
1f438dbf71 | ||
|
|
619d0e2bdd | ||
|
|
ec420c66d4 | ||
|
|
ac1cf633c7 | ||
|
|
6f25d2170d | ||
|
|
295a2ced32 | ||
|
|
cbb2ad018d | ||
|
|
7e15eabe65 | ||
|
|
eb9b5b535f | ||
|
|
a13351a9eb | ||
|
|
74fdf6beec | ||
|
|
cd7c88e5a5 | ||
|
|
a22b88ed89 | ||
|
|
5eb99fe5ef | ||
|
|
85e465cfa4 | ||
|
|
e101dde62e | ||
|
|
1980ffea30 | ||
|
|
0935e0a13d | ||
|
|
b2b68119fc | ||
|
|
4420057d4f | ||
|
|
bd2b8df7c5 | ||
|
|
068bbe0837 | ||
|
|
7e0e1fbaad | ||
|
|
9723476392 | ||
|
|
9dee2f2230 | ||
|
|
175f38414e | ||
|
|
8bbf8231d2 | ||
|
|
2fbbb802bd | ||
|
|
cb65a80dce | ||
|
|
3a9bcbcf6b | ||
|
|
e38018ab1e | ||
|
|
044c6474b2 | ||
|
|
5f42a4d95e | ||
|
|
6d3fd6da07 | ||
|
|
34e7996f2f | ||
|
|
f2d7a28258 | ||
|
|
ba6f8da1e4 | ||
|
|
0a5e79891d | ||
|
|
901bf63cb8 | ||
|
|
350b8efb0b | ||
|
|
2f69b4d190 | ||
|
|
57a15a4e96 | ||
|
|
13042002d7 | ||
|
|
8ac745b4e5 | ||
|
|
8a4f038ec4 | ||
|
|
890522f6f4 | ||
|
|
c8aa032051 | ||
|
|
9830c146d0 | ||
|
|
00c9164fdb | ||
|
|
a31e7118b4 | ||
|
|
2058ce3f3a | ||
|
|
61e3bf4a20 | ||
|
|
ea5bae711b | ||
|
|
44a2c5fdff | ||
|
|
3b43960779 | ||
|
|
89f27ec99a | ||
|
|
82cf4358ce | ||
|
|
0f9b117744 | ||
|
|
c420c21219 | ||
|
|
3bae4b0d93 | ||
|
|
1432bd7a81 | ||
|
|
bdcf930e25 | ||
|
|
2e42d74c59 | ||
|
|
bbf26fc3e8 | ||
|
|
6dcc42cdeb | ||
|
|
385b0aa07f | ||
|
|
f2b1ee3398 | ||
|
|
2a2c60201f | ||
|
|
53749156a7 | ||
|
|
ee12b34334 | ||
|
|
b5a4aefe8b | ||
|
|
c2d5cab1b8 | ||
|
|
f25af8f7d6 | ||
|
|
33648b083c | ||
|
|
eb79711e0a | ||
|
|
03ce6052cc | ||
|
|
7992bf4177 | ||
|
|
ddb390d5ec | ||
|
|
1500b1dc00 | ||
|
|
9263d3e82c | ||
|
|
02b7fd53ed | ||
|
|
3cd199e852 | ||
|
|
77bc22baf5 | ||
|
|
f57988761c | ||
|
|
7987d09438 | ||
|
|
262352f8bb | ||
|
|
e8c56a8e8e | ||
|
|
312691376a | ||
|
|
a9611c782b | ||
|
|
bcc70b36fd | ||
|
|
9ee3b1bfbd | ||
|
|
e80a83b30b | ||
|
|
d80e72dfcd | ||
|
|
d01c26e618 | ||
|
|
cab2e11ca9 | ||
|
|
b48994b1b3 | ||
|
|
fb48acddb2 | ||
|
|
7d627f7af4 | ||
|
|
f458491d88 | ||
|
|
de802f9d76 | ||
|
|
804e6ba586 | ||
|
|
e230eeb486 |
No files matched your search
+15
-9
@@ -25,6 +25,7 @@ _testmain.go
|
||||
*.prof
|
||||
profile.cov
|
||||
coverage.html
|
||||
.pkg
|
||||
|
||||
# Emacs backup files
|
||||
*~
|
||||
@@ -40,25 +41,30 @@ prog/weaveproxy/weaveproxy
|
||||
prog/sigproxy/sigproxy
|
||||
prog/weavewait/weavewait
|
||||
prog/weavewait/weavewait_noop
|
||||
prog/netcheck/netcheck
|
||||
prog/docker_tls_args/docker_tls_args
|
||||
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
|
||||
.weaver.uptodate
|
||||
.weavedns.uptodate
|
||||
.weaveexec.uptodate
|
||||
weave.tar.gz
|
||||
.*.uptodate
|
||||
weave*.tar.gz
|
||||
prog/weaveexec/weave
|
||||
prog/weaveexec/weavehosts
|
||||
prog/weaveexec/weaveproxy
|
||||
prog/weaveexec/weavewait
|
||||
prog/weaveexec/weavewait_noop
|
||||
prog/weaveexec/weavewait_nomcast
|
||||
prog/weaveexec/sigproxy
|
||||
prog/weaveexec/netcheck
|
||||
prog/weaveexec/docker_tls_args
|
||||
prog/weaveexec/docker*.tgz
|
||||
prog/weaveexec/weaveutil
|
||||
prog/weaveexec/docker*
|
||||
Vagrantfile.local
|
||||
test/tls/tls
|
||||
test/tls/*.pem
|
||||
test/coverage
|
||||
test/coverage.*
|
||||
*qemu-*-static
|
||||
+123
@@ -1,3 +1,126 @@
|
||||
[submodule "tools"]
|
||||
path = tools
|
||||
url = https://github.com/weaveworks/build-tools
|
||||
[submodule "vendor/github.com/vishvananda/netlink"]
|
||||
path = vendor/github.com/vishvananda/netlink
|
||||
url = https://github.com/weaveworks/netlink.git
|
||||
[submodule "vendor/github.com/gorilla/mux"]
|
||||
path = vendor/github.com/gorilla/mux
|
||||
url = https://github.com/gorilla/mux.git
|
||||
[submodule "vendor/github.com/gorilla/context"]
|
||||
path = vendor/github.com/gorilla/context
|
||||
url = https://github.com/gorilla/context.git
|
||||
[submodule "vendor/github.com/google/gopacket"]
|
||||
path = vendor/github.com/google/gopacket
|
||||
url = https://github.com/google/gopacket.git
|
||||
[submodule "vendor/github.com/weaveworks/go-odp"]
|
||||
path = vendor/github.com/weaveworks/go-odp
|
||||
url = https://github.com/weaveworks/go-odp.git
|
||||
[submodule "vendor/github.com/opencontainers/runc"]
|
||||
path = vendor/github.com/opencontainers/runc
|
||||
url = https://github.com/opencontainers/runc.git
|
||||
[submodule "vendor/github.com/fsouza/go-dockerclient"]
|
||||
path = vendor/github.com/fsouza/go-dockerclient
|
||||
url = https://github.com/fsouza/go-dockerclient.git
|
||||
[submodule "vendor/github.com/docker/libnetwork"]
|
||||
path = vendor/github.com/docker/libnetwork
|
||||
url = https://github.com/docker/libnetwork.git
|
||||
[submodule "vendor/github.com/docker/docker"]
|
||||
path = vendor/github.com/docker/docker
|
||||
url = https://github.com/docker/docker.git
|
||||
[submodule "vendor/github.com/Sirupsen/logrus"]
|
||||
path = vendor/github.com/Sirupsen/logrus
|
||||
url = https://github.com/Sirupsen/logrus.git
|
||||
[submodule "vendor/github.com/andybalholm/go-bit"]
|
||||
path = vendor/github.com/andybalholm/go-bit
|
||||
url = https://github.com/andybalholm/go-bit.git
|
||||
[submodule "vendor/github.com/miekg/dns"]
|
||||
path = vendor/github.com/miekg/dns
|
||||
url = https://github.com/miekg/dns.git
|
||||
[submodule "vendor/github.com/stretchr/testify"]
|
||||
path = vendor/github.com/stretchr/testify
|
||||
url = https://github.com/stretchr/testify.git
|
||||
[submodule "vendor/golang.org/x/crypto"]
|
||||
path = vendor/golang.org/x/crypto
|
||||
url = https://github.com/golang/crypto.git
|
||||
[submodule "vendor/github.com/docker/machine"]
|
||||
path = vendor/github.com/docker/machine
|
||||
url = https://github.com/docker/machine.git
|
||||
[submodule "vendor/github.com/weaveworks/mesh"]
|
||||
path = vendor/github.com/weaveworks/mesh
|
||||
url = https://github.com/weaveworks/mesh
|
||||
[submodule "vendor/github.com/vishvananda/netns"]
|
||||
path = vendor/github.com/vishvananda/netns
|
||||
url = https://github.com/vishvananda/netns.git
|
||||
[submodule "vendor/github.com/boltdb/bolt"]
|
||||
path = vendor/github.com/boltdb/bolt
|
||||
url = https://github.com/boltdb/bolt
|
||||
[submodule "vendor/github.com/weaveworks/go-checkpoint"]
|
||||
path = vendor/github.com/weaveworks/go-checkpoint
|
||||
url = https://github.com/weaveworks/go-checkpoint
|
||||
[submodule "vendor/github.com/hashicorp/go-cleanhttp"]
|
||||
path = vendor/github.com/hashicorp/go-cleanhttp
|
||||
url = http://github.com/hashicorp/go-cleanhttp
|
||||
[submodule "vendor/github.com/appc/cni"]
|
||||
path = vendor/github.com/appc/cni
|
||||
url = https://github.com/appc/cni
|
||||
[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
|
||||
@@ -0,0 +1,8 @@
|
||||
# IMPORTANT: Please only use GLOBs to ignore files and directories.
|
||||
|
||||
# Do not lint any bash script in Weave Net, in order to
|
||||
# avoid breaking the build on make lint:
|
||||
*.sh
|
||||
bin/*
|
||||
prog/weaveexec/symlink
|
||||
weave
|
||||
-28
@@ -1,28 +0,0 @@
|
||||
language: go
|
||||
sudo: false
|
||||
go:
|
||||
- 1.4
|
||||
|
||||
env:
|
||||
- PATH="${PATH}:${HOME}/.local/bin"
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libpcap-dev
|
||||
|
||||
install:
|
||||
- go clean -i net
|
||||
- go install -tags netgo std
|
||||
- go get github.com/golang/lint/golint
|
||||
- pip install --user --upgrade gsutil
|
||||
- bin/setup-ci-secrets $encrypted_5ba036e89377_key $encrypted_5ba036e89377_iv
|
||||
- make travis
|
||||
- go get ./...
|
||||
|
||||
script:
|
||||
- make tests
|
||||
|
||||
after_success:
|
||||
- go get github.com/mattn/goveralls
|
||||
- test "${TRAVIS_PULL_REQUEST}" = "false" && site/build-and-publish.sh
|
||||
+340
@@ -1,3 +1,343 @@
|
||||
## 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
|
||||
|
||||
- A new [Container Network Interface](https://github.com/appc/cni#cni---the-container-network-interface) plugin.
|
||||
- This release is much more robust against unscheduled restarts,
|
||||
because it persists key data to disk.
|
||||
- New configuration options that are useful when you create or
|
||||
auto-scale larger networks.
|
||||
- Weave now periodically checks for updates (can be disabled)
|
||||
|
||||
Plus many bug fixes and minor enhancements. More details below and in
|
||||
the [change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.5.0).
|
||||
|
||||
## Release 1.4.6
|
||||
|
||||
Fixes some issues encountered by our users.
|
||||
|
||||
* Restarting a peer could leave stale entries in WeaveDNS
|
||||
#1867/#2023/#2081
|
||||
* Weave proxy occasionally failing to attach any new containers
|
||||
#2016/#2049
|
||||
|
||||
Other fixes:
|
||||
|
||||
* Resolved a crash when a restarting peer re-connected to a peer that
|
||||
had not received the latest IPAM data #2083/#2092
|
||||
* Make IP address space available immediately after a dead peer is
|
||||
removed #2068
|
||||
|
||||
## Release 1.4.5
|
||||
|
||||
Higher performance for multicast and broadcast traffic when using
|
||||
Weave's Fast Datapath
|
||||
|
||||
* The flow rule to deliver broadcast and multicast packets in-kernel
|
||||
was not created correctly, hence every such packet caused a
|
||||
context-switch to the software router #2003/#2008
|
||||
|
||||
Other fixes:
|
||||
|
||||
* Remove DNS entries for containers that are being restarted by Docker
|
||||
but are not live yet #1977/#1989
|
||||
* Don't let one failing allocation attempt prevent others from
|
||||
succeeding; they could be in different subnets which are more
|
||||
available #1996/#2001
|
||||
* Don't complain on second router launch on kernels that lack support
|
||||
for Fast Datapath #1929/#1983
|
||||
* Fix build broken by change in libnetwork IPAM API #1984/#1985
|
||||
|
||||
## Release 1.4.4
|
||||
|
||||
Fixing a rather serious issue that slipped through our preparations
|
||||
for Docker 1.10:
|
||||
|
||||
* Restarting Docker or rebooting your machine while the Weave Net
|
||||
plugin is running causes Docker 1.10.0 to fail to start #1959/#1963
|
||||
|
||||
Also one other small fix:
|
||||
|
||||
* Avoid a hang when trying to use plugin and proxy at the same time
|
||||
#1956/#1961
|
||||
|
||||
## Release 1.4.3
|
||||
|
||||
Preparing for Docker 1.10, plus some bug-fixes.
|
||||
|
||||
* Avoid hang in Docker v1.10 on `docker volume ls` after `weave stop`,
|
||||
`weave stop-plugin` or `weave reset` #1934/#1936
|
||||
* Fix "unexpected EOF" from Docker 1.10 on `docker exec` with Weave
|
||||
proxy `--rewrite-inspect` enabled #1911/#1917
|
||||
* Avoid losing DNS entries and potentially double-allocating IP
|
||||
Addresses allocated via plugin, on router restart; also extend
|
||||
`weave ps` to show IP addresses allocated via plugin #1745/#1921
|
||||
* Stop creating lots of copies of `weavewait` program in Docker
|
||||
volumes #1757/#1935
|
||||
* Prevent container starting prematurely when proxy in
|
||||
`--no-multicast-route` mode #1942/#1943
|
||||
* Log error message from plugin rather than crashing when weave not
|
||||
running #1906/#1918
|
||||
* Warn, don't error, if unable to remove plugin network in 'weave
|
||||
stop', to avoid breaking the usual upgrade or config change process
|
||||
#1900/#1919
|
||||
* Don't crash if network conditions suggest only very small packets
|
||||
will get through #1905/#1926
|
||||
* Cope with unexpected errors during route traversal when starting
|
||||
container via proxy #1909/#1910/#1932
|
||||
|
||||
## Release 1.4.2
|
||||
|
||||
Bug-fixes and minor improvements.
|
||||
|
||||
* A race condition in weavewait that would occasionally hang
|
||||
containers at startup #1882/#1884
|
||||
* Having the plugin auto-restart prevents successful `weave launch` on
|
||||
reboot #1869
|
||||
* Work round weave router failure on CoreOS 4.3 caused by kernel bug
|
||||
#1854
|
||||
* `weave launch` would exit with error code on docker <1.9 #1851
|
||||
* Running `eval $(weave env)` multiple times would break `eval $(weave
|
||||
env --restore)` #1824/#1825
|
||||
* Don't complain in `weave stop` about "Plugin is not running" when
|
||||
plugin is not enabled #1840/#1841
|
||||
* `weave --local launch` would fail if utility program
|
||||
`docker_tls_args` could not be found #1844
|
||||
* Improved error reporting when TLS arg detection fails #1843
|
||||
* Improve error reporting when docker isn't running #1845
|
||||
* Add `--trusted-subnets` usage to `weave` script #1842
|
||||
* weave run can hang under rare combinations of options #1858
|
||||
|
||||
## Release 1.4.1
|
||||
|
||||
This is a bug-fix release to cover a few issues that came up since the
|
||||
release of 1.4.0.
|
||||
|
||||
* Weave would fail to launch when `$DOCKER_HOST` was set to a TCP socket secured with TLS #1820/#1822
|
||||
* Weave would fail to stop when run against Docker pre-1.9 #1815/#1817
|
||||
* Issue a warning instead of failing on `--with-dns` option to proxy, which was removed #1810/#1812
|
||||
* Make `weave version` show the plugin version #1797/#1813
|
||||
* Make `weave launch` show when the a container is restarting #1778/#1814
|
||||
* Make `weave launch` fail if the plugin is running, for consistency with router and proxy. #1818/#1819
|
||||
|
||||
More details in the [change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.4.1).
|
||||
|
||||
## Release 1.4.0
|
||||
|
||||
**Highlights**
|
||||
|
||||
* The Docker Network plugin can now operate without a cluster store, so it is now run by default.
|
||||
* You can now use the fast datapath over trusted links and Weave encryption over untrusted links, in the same network.
|
||||
|
||||
Plus many bug fixes and minor enhancements. More details below and in
|
||||
the [change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.4.0).
|
||||
|
||||
## Release 1.3.1
|
||||
|
||||
**Highlights**
|
||||
|
||||
* The minimum Docker version has been increased to 1.6 due to the
|
||||
[upcoming deprecation of Dockerhub access](https://blog.docker.com/2015/10/docker-hub-deprecation-1-5/)
|
||||
for old clients. From December 7th onwards previous versions of the
|
||||
`weave` script will fail to pull down images from the hub; if you
|
||||
are unable to upgrade to 1.3.1 immediately you can work around this
|
||||
by running `weave --local setup` in conjunction with a compatible
|
||||
Docker client installation
|
||||
* Docker networking plugin now works with older kernels and allows you
|
||||
to configure the MTU
|
||||
|
||||
More details in the
|
||||
[change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.3.1).
|
||||
|
||||
## Release 1.3.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
|
||||
@@ -176,7 +176,7 @@
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2014-2015 Weaveworks Ltd.
|
||||
Copyright 2014-2016 Weaveworks Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,40 +1,137 @@
|
||||
PUBLISH=publish_weave publish_weaveexec
|
||||
|
||||
.DEFAULT: all
|
||||
.PHONY: all update tests lint publish $(PUBLISH) clean clean-bin prerequisites build travis 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
|
||||
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
|
||||
NETCHECK_EXE=prog/netcheck/netcheck
|
||||
DOCKERTLSARGS_EXE=prog/docker_tls_args/docker_tls_args
|
||||
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) $(NETCHECK_EXE) $(DOCKERTLSARGS_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)
|
||||
|
||||
WEAVER_UPTODATE=.weaver.uptodate
|
||||
WEAVEEXEC_UPTODATE=.weaveexec.uptodate
|
||||
# These stamp files are used to mark the current state of the build; whether an image has been built or not
|
||||
BUILD_UPTODATE=.build.uptodate
|
||||
WEAVER_UPTODATE=.weaver$(ARCH_EXT).uptodate
|
||||
WEAVEEXEC_UPTODATE=.weaveexec$(ARCH_EXT).uptodate
|
||||
PLUGIN_UPTODATE=.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)
|
||||
IMAGES_UPTODATE=$(WEAVER_UPTODATE) $(WEAVEEXEC_UPTODATE) $(PLUGIN_UPTODATE) $(WEAVEKUBE_UPTODATE) $(WEAVENPC_UPTODATE)
|
||||
|
||||
WEAVER_IMAGE=$(DOCKERHUB_USER)/weave
|
||||
WEAVEEXEC_IMAGE=$(DOCKERHUB_USER)/weaveexec
|
||||
# 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)
|
||||
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.3.1
|
||||
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."; \
|
||||
@@ -43,106 +140,222 @@ NETGO_CHECK=@strings $@ | grep cgo_stub\\\.go >/dev/null || { \
|
||||
echo " sudo go install -tags netgo std"; \
|
||||
false; \
|
||||
}
|
||||
BUILD_FLAGS=-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) $(RUNNER_EXE)
|
||||
all: $(WEAVE_EXPORT)
|
||||
testrunner: $(RUNNER_EXE) $(TEST_TLS_EXE)
|
||||
|
||||
travis: $(EXES)
|
||||
$(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 net/*.go plugin/net/*.go plugin/ipam/*.go db/*.go
|
||||
$(SIGPROXY_EXE): prog/sigproxy/*.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: tools/.git
|
||||
lint: tools/.git
|
||||
|
||||
update:
|
||||
go get -u -f -v -tags netgo $(addprefix ./,$(dir $(EXES)))
|
||||
ifeq ($(BUILD_IN_CONTAINER),true)
|
||||
|
||||
$(WEAVER_EXE) $(WEAVEPROXY_EXE): common/*.go common/*/*.go net/*.go
|
||||
# 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=$(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)
|
||||
$(eval COVERAGE_MODULES := $(shell (go list ./$(@D); go list -f '{{join .Deps "\n"}}' ./$(@D) | grep "^$(PACKAGE_BASE)/") | paste -s -d,))
|
||||
go get -t -tags netgo ./$(@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
|
||||
go get -tags netgo ./$(@D)
|
||||
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
||||
endif
|
||||
$(NETGO_CHECK)
|
||||
|
||||
$(NETCHECK_EXE): common/*.go common/*/*.go net/*.go
|
||||
go get -tags netgo ./$(@D)
|
||||
$(WEAVEUTIL_EXE) $(KUBEPEERS_EXE) $(WEAVENPC_EXE):
|
||||
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
||||
$(NETGO_CHECK)
|
||||
|
||||
$(WEAVER_EXE): router/*.go mesh/*.go ipam/*.go ipam/*/*.go nameserver/*.go prog/weaver/*.go
|
||||
$(WEAVEPROXY_EXE): proxy/*.go prog/weaveproxy/main.go
|
||||
$(NETCHECK_EXE): prog/netcheck/netcheck.go
|
||||
$(WEAVEWAIT_EXE):
|
||||
go build $(BUILD_FLAGS) -tags "netgo iface mcast" -o $@ ./$(@D)
|
||||
$(NETGO_CHECK)
|
||||
|
||||
# Sigproxy and weavewait need separate rules as they fail the netgo check in
|
||||
$(WEAVEWAIT_NOMCAST_EXE):
|
||||
go build $(BUILD_FLAGS) -tags "netgo iface" -o $@ ./$(@D)
|
||||
$(NETGO_CHECK)
|
||||
|
||||
# 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): prog/sigproxy/main.go
|
||||
$(WEAVEWAIT_EXE): prog/weavewait/*.go net/*.go
|
||||
$(DOCKERTLSARGS_EXE): prog/docker_tls_args/*.go
|
||||
|
||||
$(WEAVEWAIT_EXE) $(SIGPROXY_EXE) $(DOCKERTLSARGS_EXE):
|
||||
go get -tags netgo ./$(@D)
|
||||
$(SIGPROXY_EXE) $(TEST_TLS_EXE) $(WEAVEWAIT_NOOP_EXE) $(RUNNER_EXE) $(MANIFEST_TOOL_EXE):
|
||||
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
||||
|
||||
$(WEAVEWAIT_NOOP_EXE): prog/weavewait/*.go
|
||||
go get -tags netgo ./$(@D)
|
||||
go build $(BUILD_FLAGS) -tags noop -o $@ ./$(@D)
|
||||
tests:
|
||||
./tools/test -no-go-get -netgo -timeout 8m
|
||||
|
||||
$(WEAVER_UPTODATE): prog/weaver/Dockerfile $(WEAVER_EXE)
|
||||
$(SUDO) docker build -t $(WEAVER_IMAGE) prog/weaver
|
||||
lint:
|
||||
./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 $@
|
||||
|
||||
$(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(NETCHECK_EXE) $(DOCKERTLSARGS_EXE)
|
||||
# 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.$(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
|
||||
cp $(WEAVEWAIT_EXE) prog/weaveexec/weavewait
|
||||
cp $(WEAVEWAIT_NOOP_EXE) prog/weaveexec/weavewait_noop
|
||||
cp $(NETCHECK_EXE) prog/weaveexec/netcheck
|
||||
cp $(DOCKERTLSARGS_EXE) prog/weaveexec/docker_tls_args
|
||||
cp $(DOCKER_DISTRIB) prog/weaveexec/docker.tgz
|
||||
$(SUDO) docker build -t $(WEAVEEXEC_IMAGE) prog/weaveexec
|
||||
cp $(WEAVEWAIT_NOMCAST_EXE) prog/weaveexec/weavewait_nomcast
|
||||
cp $(WEAVEUTIL_EXE) prog/weaveexec/weaveutil
|
||||
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 $@
|
||||
|
||||
$(WEAVE_EXPORT): $(IMAGES_UPTODATE)
|
||||
$(SUDO) docker save $(addsuffix :latest,$(IMAGES)) | gzip > $@
|
||||
$(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) $(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
|
||||
|
||||
tests: tools/.git
|
||||
tools/test
|
||||
|
||||
lint: tools/.git
|
||||
tools/lint -nocomment -notestpackage .
|
||||
|
||||
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 tag -f $(DOCKERHUB_USER)/$* $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
|
||||
$(SUDO) docker push $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
|
||||
ifneq ($(UPDATE_LATEST),false)
|
||||
$(SUDO) docker push $(DOCKERHUB_USER)/$*:latest
|
||||
# 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 rmi $(IMAGES)
|
||||
go clean -r $(addprefix ./,$(dir $(EXES)))
|
||||
rm -f $(EXES) $(IMAGES_UPTODATE) $(WEAVE_EXPORT)
|
||||
-$(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
|
||||
$(SUDO) go install -tags netgo std
|
||||
$(MAKE)
|
||||
|
||||
run-smoketests: all
|
||||
run-smoketests: all testrunner
|
||||
cd test && ./setup.sh && ./run_all.sh
|
||||
@@ -1,3 +1,3 @@
|
||||
Weave
|
||||
Copyright 2014-2015 Weaveworks Ltd.
|
||||
Copyright 2014-2016 Weaveworks Ltd.
|
||||
This product includes software developed at Weaveworks Ltd.
|
||||
@@ -1,174 +1,27 @@
|
||||
# Weave - weaving containers into applications
|
||||
# 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 develops Weave -
|
||||
the most productive way for developers to connect, observe and control
|
||||
Docker containers. To learn about our products, including getting
|
||||
started tutorials, visit our [website](http://weave.works) and
|
||||
[documentation](http://docs.weave.works) or continue to read about
|
||||
some of the more technical aspects of Weave in this readme.
|
||||
[Weaveworks](https://www.weave.works) is the company that delivers the most productive way for developers to connect, observe and control
|
||||
Docker containers.
|
||||
|
||||
# Weave
|
||||
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.
|
||||
|
||||
Weave creates a virtual network that connects Docker containers
|
||||
deployed across multiple hosts and enables their automatic discovery.
|
||||
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.
|
||||
|
||||
Applications use the network just as if the containers were all
|
||||
plugged into the same network switch, with no need to configure port
|
||||
mappings, links, etc. Services provided by application containers on
|
||||
the weave network can be made accessible to the outside world,
|
||||
regardless of where those containers are running. Similarly, existing
|
||||
internal systems can be exposed to application containers irrespective
|
||||
of their location.
|
||||
# Weave Net
|
||||
|
||||

|
||||
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.
|
||||
|
||||
Weave can traverse firewalls and operate in partially connected
|
||||
networks. Traffic can be encrypted, allowing hosts to be connected
|
||||
across an untrusted network.
|
||||
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.
|
||||
|
||||
With weave you can easily construct applications consisting of
|
||||
multiple containers, running anywhere.
|
||||
|
||||
Weave works alongside Docker's existing (single host) networking
|
||||
capabilities, so these can continue to be used by containers.
|
||||
|
||||
## Installation
|
||||
|
||||
Ensure you are running Linux (kernel 3.8 or later) and have Docker
|
||||
(version 1.3.1 or later) installed. Then install weave with
|
||||
|
||||
sudo 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, as shown in [the Docker Machine
|
||||
documentation](https://docs.docker.com/installation/mac/#from-your-shell).
|
||||
Then you can launch weave directly from the OSX host.
|
||||
|
||||
CoreOS users see [here](https://github.com/fintanr/weave-gs/blob/master/coreos-simple/user-data) for an example of installing weave using cloud-config.
|
||||
|
||||
Amazon ECS users see
|
||||
[here](https://github.com/weaveworks/guides/blob/master/aws-ecs/LATESTAMIs.md)
|
||||
for the latest Weave AMIs and
|
||||
[here](http://weave.works/guides/service-discovery-with-weave-aws-ecs.html) to get started with Weave on ECS.
|
||||
|
||||
Weave respects the environment variable `DOCKER_HOST`, so you can run
|
||||
it locally to control a weave network on a remote host.
|
||||
|
||||
## 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 we 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. The second line configures our environment
|
||||
so that containers launched via the docker command line are
|
||||
automatically attached to the weave network. Finally, we run our
|
||||
application container.
|
||||
|
||||
That's it! If our application consists of more than one container on
|
||||
this host we simply launch them with `docker run` as appropriate.
|
||||
|
||||
Next we 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 we tell our weave that it should peer with the weave on
|
||||
`$HOST1` (specified as the IP address or hostname, and optional
|
||||
`:port`, by which `$HOST2` can reach it). NB: if there is a firewall
|
||||
between `$HOST1` and `$HOST2`, you must permit traffic to the weave
|
||||
control port (TCP 6783) and data ports (UDP 6783/6784).
|
||||
|
||||
Note that we could instead have told the weave on `$HOST1` to connect to
|
||||
`$HOST2`, or told both about each other. Order does not matter here;
|
||||
weave automatically (re)connects to peers when they become
|
||||
available. Also, we can tell weave to connect to multiple peers by
|
||||
supplying multiple addresses, separated by spaces. And we can
|
||||
[add peers dynamically](http://docs.weave.works/weave/latest_release/features.html#dynamic-topologies).
|
||||
|
||||
Weave must be started once per host. The relevant container images are
|
||||
pulled down on demand, but if you wish you can preload them by running
|
||||
`weave setup` - this is particularly useful for automated deployments,
|
||||
and ensures that there are no delays during later operations.
|
||||
|
||||
Now that we've got everything set up, let's see whether our containers
|
||||
can talk to each other...
|
||||
|
||||
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 we have it, two containers on separate hosts happily talking
|
||||
to each other.
|
||||
|
||||
## Find out more
|
||||
|
||||
* [Documentation homepage](http://docs.weave.works/weave/latest_release/)
|
||||
* [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? You can
|
||||
[File an issue](https://github.com/weaveworks/weave/issues). And
|
||||
there is a [Weave Users Google
|
||||
Group](https://groups.google.com/a/weave.works/forum/#!forum/weave-users),
|
||||
which you can post to by email at weave-users@weave.works. Or you can
|
||||
contact Weaveworks directly at help@weave.works. When reporting a
|
||||
bug, please include which version of weave you are running, as shown
|
||||
by `weave version`.
|
||||
|
||||
Follow on Twitter:
|
||||
[@weaveworks](https://twitter.com/weaveworks).
|
||||
|
||||
Read the Weave blog:
|
||||
[Weaveblog](http://weaveblog.com/).
|
||||
|
||||
IRC:
|
||||
[#weavenetwork](https://botbot.me/freenode/weavenetwork/)
|
||||
* [Install Weave Net](https://www.weave.works/docs/net/latest/installing-weave/)
|
||||
* [Find out more about Weave Net](https://www.weave.works/products/weave-net/)
|
||||
Vendored
+7
-3
@@ -5,7 +5,7 @@ require './vagrant-common.rb'
|
||||
vm_ip = "172.16.0.3" # arbitrary private IP
|
||||
|
||||
pkgs = %w(
|
||||
docker-engine
|
||||
docker-engine=1.10.2-0~wily
|
||||
aufs-tools
|
||||
build-essential
|
||||
ethtool
|
||||
@@ -14,15 +14,16 @@ pkgs = %w(
|
||||
git
|
||||
mercurial
|
||||
bc
|
||||
jq
|
||||
)
|
||||
|
||||
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||
|
||||
config.vm.box = "ubuntu/ubuntu-15.04-amd64"
|
||||
config.vm.box_url = "https://cloud-images.ubuntu.com/vagrant/vivid/current/vivid-server-cloudimg-amd64-vagrant-disk1.box"
|
||||
config.vm.box = "ubuntu/wily64"
|
||||
|
||||
config.vm.network "private_network", ip: vm_ip
|
||||
config.vm.provider :virtualbox do |vb|
|
||||
vb.memory = 2048
|
||||
vb.customize ["modifyvm", :id, "--natdnshostresolver1", "off"]
|
||||
vb.customize ["modifyvm", :id, "--natdnsproxy1", "off"]
|
||||
end
|
||||
@@ -30,6 +31,9 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||
config.vm.synced_folder ".", "/vagrant", disabled: true
|
||||
config.vm.synced_folder ".", "/home/vagrant/src/github.com/weaveworks/weave"
|
||||
|
||||
# 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"
|
||||
|
||||
install_build_deps config.vm, pkgs
|
||||
install_go_toochain config.vm
|
||||
tweak_user_env config.vm
|
||||
|
||||
+22
@@ -0,0 +1,22 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
func (client *Client) DNSDomain() (string, error) {
|
||||
return client.httpVerb("GET", "/domain", nil)
|
||||
}
|
||||
|
||||
func (client *Client) RegisterWithDNS(ID string, fqdn string, ip string) error {
|
||||
data := url.Values{}
|
||||
data.Add("fqdn", fqdn)
|
||||
_, err := client.httpVerb("PUT", fmt.Sprintf("/name/%s/%s", ID, ip), data)
|
||||
return err
|
||||
}
|
||||
|
||||
func (client *Client) DeregisterWithDNS(ID string, ip string) error {
|
||||
_, err := client.httpVerb("DELETE", fmt.Sprintf("/name/%s/%s", ID, ip), nil)
|
||||
return err
|
||||
}
|
||||
+72
@@ -0,0 +1,72 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"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 {
|
||||
return nil, err
|
||||
}
|
||||
return parseIP(ip)
|
||||
}
|
||||
|
||||
// returns an IP for the ID given, allocating a fresh one if necessary
|
||||
func (client *Client) AllocateIP(ID string) (*net.IPNet, error) {
|
||||
return client.ipamOp(ID, "POST")
|
||||
}
|
||||
|
||||
func (client *Client) AllocateIPInSubnet(ID string, subnet *net.IPNet) (*net.IPNet, error) {
|
||||
ip, err := client.httpVerb("POST", fmt.Sprintf("/ip/%s/%s", ID, subnet), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return parseIP(ip)
|
||||
}
|
||||
|
||||
// returns an IP for the ID given, or nil if one has not been
|
||||
// allocated
|
||||
func (client *Client) LookupIP(ID string) (*net.IPNet, error) {
|
||||
return client.ipamOp(ID, "GET")
|
||||
}
|
||||
|
||||
// Claim a specific IP on behalf of the ID
|
||||
func (client *Client) ClaimIP(ID string, cidr *net.IPNet) error {
|
||||
_, err := client.httpVerb("PUT", fmt.Sprintf("/ip/%s/%s", ID, cidr), nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// release all IPs owned by an ID
|
||||
func (client *Client) ReleaseIPsFor(ID string) error {
|
||||
_, err := client.httpVerb("DELETE", fmt.Sprintf("/ip/%s", ID), nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (client *Client) DefaultSubnet() (*net.IPNet, error) {
|
||||
cidr, err := client.httpVerb("GET", fmt.Sprintf("/ipinfo/defaultsubnet"), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, ipnet, err := net.ParseCIDR(cidr)
|
||||
return ipnet, err
|
||||
}
|
||||
|
||||
func parseIP(body string) (*net.IPNet, error) {
|
||||
ip, ipnet, err := net.ParseCIDR(string(body))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ipnet.IP = ip
|
||||
return ipnet, nil
|
||||
}
|
||||
@@ -0,0 +1,82 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
WeaveHTTPHost = "127.0.0.1"
|
||||
WeaveHTTPPort = 6784
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
baseURL string
|
||||
log Logger
|
||||
}
|
||||
|
||||
func (client *Client) httpVerb(verb string, url string, values url.Values) (string, error) {
|
||||
url = client.baseURL + url
|
||||
client.log.Debugf("weave %s to %s with %v", verb, url, values)
|
||||
var body io.Reader
|
||||
if values != nil {
|
||||
body = strings.NewReader(values.Encode())
|
||||
}
|
||||
req, err := http.NewRequest(verb, url, body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if values != nil {
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
rbody, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusNoContent {
|
||||
return string(rbody), nil
|
||||
}
|
||||
return "", errors.New(resp.Status + ": " + string(rbody))
|
||||
}
|
||||
|
||||
func NewClient(addr string, log Logger) *Client {
|
||||
host := WeaveHTTPHost
|
||||
port := fmt.Sprintf("%d", WeaveHTTPPort)
|
||||
switch parts := strings.Split(addr, ":"); len(parts) {
|
||||
case 0:
|
||||
case 1:
|
||||
if parts[0] != "" {
|
||||
host = parts[0]
|
||||
}
|
||||
case 2:
|
||||
if parts[0] != "" {
|
||||
host = parts[0]
|
||||
}
|
||||
if parts[1] != "" {
|
||||
port = parts[1]
|
||||
}
|
||||
default:
|
||||
return &Client{baseURL: fmt.Sprintf("http://%s", addr), log: log}
|
||||
}
|
||||
return &Client{baseURL: fmt.Sprintf("http://%s:%s", host, port), log: log}
|
||||
}
|
||||
|
||||
func (client *Client) Connect(remote string) error {
|
||||
_, err := client.httpVerb("POST", "/connect", url.Values{"peer": {remote}})
|
||||
return err
|
||||
}
|
||||
|
||||
type Logger interface {
|
||||
Infof(string, ...interface{})
|
||||
Debugf(string, ...interface{})
|
||||
}
|
||||
Executable
+46
@@ -0,0 +1,46 @@
|
||||
#!/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
|
||||
[ -z "$SECRET_PASSWORD" ] || bin/setup-circleci-secrets "$SECRET_PASSWORD"
|
||||
cd $SRCDIR; git submodule update --init
|
||||
if [ "$CIRCLE_NODE_INDEX" = "0" -a -n "$SECRET_PASSWORD" ] ; then
|
||||
cd $SRCDIR/test; ./gce.sh make_template
|
||||
fi
|
||||
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
+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
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
source "$STATE"
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ] ; then
|
||||
cd $SRCDIR/test
|
||||
[ -z "$SECRET_PASSWORD" ] || (./gce.sh setup && eval $(./gce.sh hosts) && ./setup.sh)
|
||||
fi
|
||||
Executable
+14
@@ -0,0 +1,14 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
source "$STATE"
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ] ; then
|
||||
[ -n "$SECRET_PASSWORD" ] || { echo "Cannot run smoke tests: no secret key"; exit 1; }
|
||||
cd $SRCDIR/test
|
||||
eval $(./gce.sh hosts)
|
||||
export COVERAGE=true
|
||||
export WEAVE_NET_SANITY_CHECKS_FILES="$CIRCLE_ARTIFACTS/weave_net_sanity_check_*.log"
|
||||
./run_all.sh
|
||||
fi
|
||||
Executable
+11
@@ -0,0 +1,11 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
source "$STATE"
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ] ; then
|
||||
cd $SRCDIR/test
|
||||
[ -z "$SECRET_PASSWORD" ] || ./gce.sh destroy
|
||||
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
|
||||
+28
-14
@@ -14,7 +14,8 @@ set -e
|
||||
START=${FIRST_WEAVE:-1}
|
||||
COUNT=${NUM_WEAVES:-28}
|
||||
FINISH=$((START+COUNT-1))
|
||||
PORT=6783
|
||||
|
||||
SUBNET=192.168.7
|
||||
|
||||
weavedir=$(dirname $0)/..
|
||||
|
||||
@@ -26,29 +27,42 @@ random_mac() {
|
||||
od -txC -An -N6 /dev/urandom | ( read a b c d e f && printf "%02x:$b:$c:$d:$e:$f" $((0x$a & ~1 | 2)) )
|
||||
}
|
||||
|
||||
weave() {
|
||||
i=$1
|
||||
shift
|
||||
WEAVE_CONTAINER_NAME=weave$i WEAVE_HTTP_ADDR=$SUBNET.$i:6784 $weavedir/weave "$@"
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
launch)
|
||||
shift 1
|
||||
echo $(date -R) launching
|
||||
ip link show multiweave >/dev/null 2>&1 || sudo ip link add name multiweave type bridge
|
||||
for i in $(seq $START $FINISH); do
|
||||
WEAVE_CONTAINER_NAME=weave$i WEAVE_PORT=$((5000+i)) $weavedir/weave launch-router --iface '' --name $(random_mac) --nickname weave$i --port $PORT --no-dns "$@"
|
||||
IP=$SUBNET.$i
|
||||
sudo ip addr add $IP/24 dev multiweave
|
||||
weave $i launch-router --datapath '' --iface '' --no-dns --host $IP \
|
||||
--name $(random_mac) --nickname weave$i "$@"
|
||||
done
|
||||
if [ $START -ne 1 ] ; then
|
||||
status=$(docker inspect --format='{{.State.Running}} {{.NetworkSettings.IPAddress}}' weave$((START-1)))
|
||||
PREV="${status#true }"
|
||||
fi
|
||||
echo $(date -R) connecting
|
||||
[ $START -gt 1 ] || START=2
|
||||
for i in $(seq $START $FINISH); do
|
||||
if [ -n "$PREV" ] ; then
|
||||
echo connecting weave$i to $PREV
|
||||
WEAVE_CONTAINER_NAME=weave$i $weavedir/weave connect $PREV
|
||||
fi
|
||||
status=$(docker inspect --format='{{.State.Running}} {{.NetworkSettings.IPAddress}}' weave$i)
|
||||
PREV="${status#true }"
|
||||
TO=$SUBNET.$((i-1))
|
||||
echo connecting weave$i to $TO
|
||||
weave $i connect $TO
|
||||
done
|
||||
echo $(date -R) done
|
||||
;;
|
||||
stop)
|
||||
for i in $(seq $START $FINISH); do
|
||||
for i in $(seq $FINISH -1 $START); do
|
||||
IP=$SUBNET.$i
|
||||
echo Stopping weave$i
|
||||
WEAVE_CONTAINER_NAME=weave$i $weavedir/weave stop-router
|
||||
weave $i stop-router
|
||||
sudo ip addr del $IP/24 dev multiweave || true
|
||||
done
|
||||
[ -z "$(ip -4 -o addr show multiweave)" ] && sudo ip link del multiweave
|
||||
;;
|
||||
*)
|
||||
weave "$@"
|
||||
;;
|
||||
esac
|
||||
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
|
||||
+60
-4
@@ -157,6 +157,28 @@ draft() {
|
||||
--name "weave" \
|
||||
--file "./weave"
|
||||
|
||||
github-release release $RELEASE_ARGS \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave-kube \
|
||||
--tag $LATEST_TAG \
|
||||
--name "$RELEASE_NAME $VERSION" \
|
||||
--description "$RELEASE_DESCRIPTION"
|
||||
|
||||
sed -i -e s/:latest/:$VERSION/ -e "/imagePullPolicy: Always/d" ./prog/weave-kube/weave-daemonset.yaml
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave-kube \
|
||||
--tag $LATEST_TAG \
|
||||
--name "weave-daemonset.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset.yaml"
|
||||
|
||||
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 +187,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 +226,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 +258,40 @@ publish() {
|
||||
--name "weave" \
|
||||
--file "./weave"
|
||||
|
||||
github-release publish \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave-kube \
|
||||
--tag $LATEST_TAG
|
||||
|
||||
if github-release info --user $GITHUB_USER --repo weave-kube \
|
||||
--tag latest_release >/dev/null 2>&1; then
|
||||
github-release delete \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave-kube \
|
||||
--tag latest_release
|
||||
fi
|
||||
|
||||
github-release release \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave-kube \
|
||||
--tag latest_release \
|
||||
--name "$RELEASE_NAME latest ($VERSION)" \
|
||||
--description "[Release Notes](https://github.com/$GITHUB_USER/weave/releases/$LATEST_TAG)"
|
||||
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave-kube \
|
||||
--tag latest_release \
|
||||
--name "weave-daemonset.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset.yaml"
|
||||
|
||||
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
|
||||
+72
-17
@@ -1,24 +1,79 @@
|
||||
FROM ubuntu
|
||||
FROM golang:1.7.4
|
||||
|
||||
RUN apt-get -y update && apt-get -y install --no-install-recommends ca-certificates apt-transport-https
|
||||
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9
|
||||
RUN echo deb https://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list
|
||||
RUN apt-get -y update && apt-get -y install --no-install-recommends build-essential git lxc-docker-1.3.1 mercurial libpcap-dev curl make pkg-config gcc bison flex python-requests
|
||||
# Support Raspberry Pi 2 and newer
|
||||
ENV GOARM 7
|
||||
|
||||
# When doing a build in a container, "apt-get update" happens twice,
|
||||
# which can be a very significant overhead for incremental builds.
|
||||
# And it's unnecessary, because if any of the prerequisites do change,
|
||||
# the right thing to do is to rebuild the container image. Rather
|
||||
# than suppressing the "apt-get update" commands explicitly, we clear
|
||||
# out sources.list so that 'apt-get update' doesn't do anything.
|
||||
RUN echo >/etc/apt/sources.list
|
||||
# The names of the architectures that we're fetching cross-compilers for from the Ubuntu packages
|
||||
ENV DEB_CROSSPLATFORMS \
|
||||
armhf \
|
||||
arm64
|
||||
|
||||
ENV GO_VERSION 1.4.2
|
||||
RUN curl -sSL https://golang.org/dl/go${GO_VERSION}.linux-amd64.tar.gz | tar -C /usr/local -xz
|
||||
ENV PATH /usr/local/go/bin:$PATH
|
||||
# The go platforms we are supporting to cross-compile
|
||||
ENV GO_CROSSPLATFORMS \
|
||||
linux/arm \
|
||||
linux/arm64
|
||||
|
||||
# So that sudo does not trample on PATH
|
||||
RUN sed -ie '/secure_path/d' /etc/sudoers
|
||||
# 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 \
|
||||
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
|
||||
|
||||
# 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"]
|
||||
+22
-36
@@ -1,45 +1,31 @@
|
||||
#!/bin/sh
|
||||
set -e
|
||||
|
||||
GOPATH=/home/go
|
||||
export GOPATH
|
||||
set -eu
|
||||
|
||||
WEAVE_SRC=$GOPATH/src/github.com/weaveworks/weave
|
||||
|
||||
if [ $# -eq 0 -o "$1" = "tests" ] ; then
|
||||
# No arguments. Expect that the weave repo will be bind-mounted
|
||||
# into $GOPATH
|
||||
if ! [ -e $WEAVE_SRC ] ; then
|
||||
cat 2>&1 <<EOF
|
||||
No container arguments supplied, and nothing at ${WEAVE_SRC}. Please
|
||||
either bind-mount the golang workspace containing weave with the
|
||||
if ! [ -e $WEAVE_SRC ] ; then
|
||||
cat 2>&1 <<EOF
|
||||
Nothing at ${WEAVE_SRC}. Please bind-mount the weave repo with the
|
||||
docker run -v option, e.g.:
|
||||
|
||||
$ docker run -v <host gopath>:${GOPATH} \\
|
||||
-v /var/run/docker.sock:/var/run/docker.sock weaveworks/weave-build
|
||||
|
||||
Or supply git clone arguments to retrieve it, e.g.:
|
||||
|
||||
$ docker run -v /var/run/docker.sock:/var/run/docker.sock \\
|
||||
weaveworks/weave-build https://github.com/weaveworks/weave.git
|
||||
$ cd weave
|
||||
$ docker run -v $(pwd):/go/src/github.com/weaveworks/weave \\
|
||||
weaveworks/weave-build
|
||||
EOF
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# If we run make directly, any files created on the bind mount
|
||||
# will have awkward ownership. So we switch to a user with the
|
||||
# same user and group IDs as source directory. We have to set a
|
||||
# few things up so that sudo works without complaining later on.
|
||||
uid=$(stat --format="%u" $WEAVE_SRC)
|
||||
gid=$(stat --format="%g" $WEAVE_SRC)
|
||||
echo "weave:x:$uid:$gid::$WEAVE_SRC:/bin/sh" >>/etc/passwd
|
||||
echo "weave:*:::::::" >>/etc/shadow
|
||||
echo "weave ALL=(ALL) NOPASSWD: ALL" >>/etc/sudoers
|
||||
|
||||
su weave -c "PATH=$PATH make -C $WEAVE_SRC build"
|
||||
else
|
||||
# There are arguments to pass to git-clone
|
||||
mkdir -p ${WEAVE_SRC%/*}
|
||||
git clone "$@" $WEAVE_SRC
|
||||
make -C $WEAVE_SRC build
|
||||
fi
|
||||
|
||||
# If we run make directly, any files created on the bind mount
|
||||
# will have awkward ownership. So we switch to a user with the
|
||||
# same user and group IDs as source directory. We have to set a
|
||||
# few things up so that sudo works without complaining later on.
|
||||
uid=$(stat --format="%u" $WEAVE_SRC)
|
||||
gid=$(stat --format="%g" $WEAVE_SRC)
|
||||
echo "weave:x:$uid:$gid::$WEAVE_SRC:/bin/sh" >>/etc/passwd
|
||||
echo "weave:*:::::::" >>/etc/shadow
|
||||
echo "weave ALL=(ALL) NOPASSWD: ALL" >>/etc/sudoers
|
||||
|
||||
# Allow full write access to the Go folders for the user Weave
|
||||
chown -R $uid:$gid /usr/local/go
|
||||
|
||||
su weave -c "PATH=$PATH make -C $WEAVE_SRC BUILD_IN_CONTAINER=false $*"
|
||||
@@ -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
|
||||
+24
-39
@@ -2,72 +2,57 @@ 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
|
||||
WEAVE_TEST: $HOME/docker/weave-test.tar
|
||||
STATE: /home/ubuntu/state.env
|
||||
|
||||
dependencies:
|
||||
cache_directories:
|
||||
- "~/docker"
|
||||
override:
|
||||
- if [[ -e "$WEAVE_TEST" ]]; then
|
||||
docker load -i $WEAVE_TEST;
|
||||
else
|
||||
docker build -t weaveworks/weave-build build;
|
||||
docker build -t weaveworks/weave-test test/image;
|
||||
mkdir -p $(dirname "$WEAVE_TEST");
|
||||
docker save weaveworks/weave-test >$WEAVE_TEST;
|
||||
fi
|
||||
post:
|
||||
- sudo apt-get update && sudo apt-get install bc jq && pip install requests
|
||||
- curl https://sdk.cloud.google.com | bash
|
||||
- bin/setup-circleci-secrets "$SECRET_PASSWORD"
|
||||
- mkdir -p $(dirname $SRCDIR) && cp -r $(pwd)/ $SRCDIR
|
||||
- cd $SRCDIR/test; ./gce.sh make_template:
|
||||
parallel: false
|
||||
- bin/circle-dependencies-post
|
||||
|
||||
test:
|
||||
pre:
|
||||
- "$SRCDIR/bin/circle-test-pre":
|
||||
parallel: true
|
||||
override:
|
||||
- cd $SRCDIR; make lint:
|
||||
- "$SRCDIR/bin/circle-test-unit":
|
||||
parallel: true
|
||||
- docker run -v /home/ubuntu:/home/go -e COVERDIR=test/coverage -e SLOW=true -e CIRCLECI -e CIRCLE_BUILD_NUM -e CIRCLE_NODE_TOTAL -e CIRCLE_NODE_INDEX weaveworks/weave-test:
|
||||
parallel: true
|
||||
- docker run -v /var/run/docker.sock:/run/docker.sock -v /home/ubuntu:/home/go -e COVERAGE=true weaveworks/weave-build:
|
||||
parallel: true
|
||||
- cd $SRCDIR/test; ./gce.sh setup && 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
|
||||
- cd $SRCDIR; make clean-bin:
|
||||
parallel: true
|
||||
- docker run -v /var/run/docker.sock:/run/docker.sock -v /home/ubuntu:/home/go weaveworks/weave-build:
|
||||
parallel: true
|
||||
post:
|
||||
- cd $SRCDIR/test; ./gce.sh destroy:
|
||||
- "$SRCDIR/bin/circle-test-teardown":
|
||||
parallel: true
|
||||
|
||||
teardown:
|
||||
pre:
|
||||
- test "$CIRCLE_NODE_INDEX" != "0" || (cd $SRCDIR/test; ./gen_coverage_reports.sh):
|
||||
parallel: true
|
||||
- test "$CIRCLE_NODE_INDEX" != "0" || (go get github.com/mattn/goveralls && goveralls -repotoken $COVERALLS_REPO_TOKEN -coverprofile=$SRCDIR/test/profile.cov -service=circleci || true):
|
||||
parallel: true
|
||||
- test "$CIRCLE_NODE_INDEX" != "0" || (cd $SRCDIR/test; cp coverage.* $CIRCLE_ARTIFACTS):
|
||||
- "$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 publish
|
||||
- "$SRCDIR/bin/circle-deploy-master"
|
||||
issues:
|
||||
branch: /.*/
|
||||
owner: weaveworks
|
||||
commands:
|
||||
- "$SRCDIR/bin/circle-deploy-issues"
|
||||
+162
-24
@@ -1,82 +1,220 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"github.com/fsouza/go-dockerclient"
|
||||
. "github.com/weaveworks/weave/common"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
docker "github.com/fsouza/go-dockerclient"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
const (
|
||||
InitialInterval = 1 * time.Second
|
||||
MaxInterval = 20 * time.Second
|
||||
)
|
||||
|
||||
// An observer for container events
|
||||
type ContainerObserver interface {
|
||||
ContainerStarted(ident string)
|
||||
ContainerDied(ident string)
|
||||
ContainerDestroyed(ident string)
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
*docker.Client
|
||||
}
|
||||
|
||||
type syncPair struct {
|
||||
stop chan struct{}
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
type pendingStarts map[string]*syncPair
|
||||
|
||||
// NewClient creates a new Docker client and checks we can talk to Docker
|
||||
func NewClient(apiPath string) (*Client, error) {
|
||||
if apiPath != "" && !strings.Contains(apiPath, "://") {
|
||||
apiPath = "tcp://" + apiPath
|
||||
}
|
||||
dc, err := docker.NewClient(apiPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := &Client{dc}
|
||||
|
||||
return client, client.checkWorking(apiPath)
|
||||
return client, client.checkWorking()
|
||||
}
|
||||
|
||||
func NewVersionedClient(apiPath string, apiVersionString string) (*Client, error) {
|
||||
if !strings.Contains(apiPath, "://") {
|
||||
apiPath = "tcp://" + apiPath
|
||||
}
|
||||
dc, err := docker.NewVersionedClient(apiPath, apiVersionString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := &Client{dc}
|
||||
|
||||
return client, client.checkWorking(apiPath)
|
||||
return client, client.checkWorking()
|
||||
}
|
||||
|
||||
func (c *Client) checkWorking(apiPath string) error {
|
||||
func NewVersionedClientFromEnv(apiVersionString string) (*Client, error) {
|
||||
dc, err := docker.NewVersionedClientFromEnv(apiVersionString)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := &Client{dc}
|
||||
|
||||
return client, client.checkWorking()
|
||||
}
|
||||
|
||||
func (c *Client) checkWorking() error {
|
||||
_, err := c.Version()
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Client) Info() string {
|
||||
env, err := c.Version()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Sprintf("Docker API error: %s", err)
|
||||
}
|
||||
Log.Infof("[docker] Using Docker API on %s: %v", apiPath, env)
|
||||
return nil
|
||||
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 {
|
||||
events := make(chan *docker.APIEvents)
|
||||
if err := c.AddEventListener(events); err != nil {
|
||||
Log.Errorf("[docker] Unable to add listener to Docker API: %s", err)
|
||||
return err
|
||||
}
|
||||
|
||||
go func() {
|
||||
for event := range events {
|
||||
switch event.Status {
|
||||
case "start":
|
||||
id := event.ID
|
||||
ob.ContainerStarted(id)
|
||||
case "die":
|
||||
id := event.ID
|
||||
ob.ContainerDied(id)
|
||||
pending := make(pendingStarts)
|
||||
retryInterval := InitialInterval
|
||||
for {
|
||||
events := make(chan *docker.APIEvents)
|
||||
if err := c.AddEventListener(events); err != nil {
|
||||
c.errorf("Unable to add listener to Docker API: %s - retrying in %ds", err, retryInterval/time.Second)
|
||||
} else {
|
||||
start := time.Now()
|
||||
for event := range events {
|
||||
switch event.Status {
|
||||
case "start":
|
||||
pending.finish(event.ID)
|
||||
pending.start(event.ID, c, ob)
|
||||
case "die":
|
||||
pending.finish(event.ID)
|
||||
ob.ContainerDied(event.ID)
|
||||
case "destroy":
|
||||
pending.finish(event.ID)
|
||||
ob.ContainerDestroyed(event.ID)
|
||||
}
|
||||
}
|
||||
if time.Since(start) > retryInterval {
|
||||
retryInterval = InitialInterval
|
||||
}
|
||||
c.errorf("Event listener channel closed - retrying subscription in %ds", retryInterval/time.Second)
|
||||
}
|
||||
time.Sleep(retryInterval)
|
||||
retryInterval = retryInterval * 3 / 2
|
||||
if retryInterval > MaxInterval {
|
||||
retryInterval = MaxInterval
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Docker sends a 'start' event before it has attempted to start the
|
||||
// container. Delay notifying the observer until the container has a
|
||||
// pid, or we are told to stop when a 'die' event arrives.
|
||||
//
|
||||
// Note we always deliver the event, even if the container seems to
|
||||
// have gone away.
|
||||
func (pending pendingStarts) start(id string, c *Client, ob ContainerObserver) {
|
||||
sync := syncPair{make(chan struct{}), make(chan struct{})}
|
||||
pending[id] = &sync
|
||||
go func() {
|
||||
defer close(sync.done)
|
||||
defer ob.ContainerStarted(id)
|
||||
for {
|
||||
if container, err := c.InspectContainer(id); err != nil || container.State.Pid != 0 {
|
||||
return
|
||||
}
|
||||
select {
|
||||
case <-sync.stop:
|
||||
return
|
||||
case <-time.After(200 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (pending pendingStarts) finish(id string) {
|
||||
if sync, found := pending[id]; found {
|
||||
close(sync.stop)
|
||||
<-sync.done
|
||||
delete(pending, id)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Client) AllContainerIDs() ([]string, error) {
|
||||
all, err := c.ListContainers(docker.ListContainersOptions{All: true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ids []string
|
||||
for _, c := range all {
|
||||
ids = append(ids, c.ID)
|
||||
}
|
||||
return ids, nil
|
||||
}
|
||||
|
||||
// IsContainerNotRunning returns true if we have checked with Docker that the ID is not running
|
||||
func (c *Client) IsContainerNotRunning(idStr string) bool {
|
||||
container, err := c.InspectContainer(idStr)
|
||||
if err == nil {
|
||||
return !container.State.Running
|
||||
return !container.State.Running || container.State.Restarting
|
||||
}
|
||||
if _, notThere := err.(*docker.NoSuchContainer); notThere {
|
||||
return true
|
||||
}
|
||||
Log.Errorf("[docker] Could not check container status: %s", err)
|
||||
c.errorf("Could not check container status: %s", err)
|
||||
return false
|
||||
}
|
||||
|
||||
// This is intended to find an IP address that we can reach the container on;
|
||||
// if it is on the Docker bridge network then that address; if on the host network
|
||||
// then localhost
|
||||
func (c *Client) GetContainerIP(nameOrID string) (string, error) {
|
||||
info, err := c.InspectContainer(nameOrID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if info.NetworkSettings.Networks != nil {
|
||||
if bridgeNetwork, ok := info.NetworkSettings.Networks["bridge"]; ok {
|
||||
return bridgeNetwork.IPAddress, nil
|
||||
} else if _, ok := info.NetworkSettings.Networks["host"]; ok {
|
||||
return "127.0.0.1", nil
|
||||
}
|
||||
} else if info.HostConfig.NetworkMode == "host" {
|
||||
return "127.0.0.1", nil
|
||||
}
|
||||
if info.NetworkSettings.IPAddress == "" {
|
||||
return "", errors.New("No IP address found for container " + nameOrID)
|
||||
}
|
||||
return info.NetworkSettings.IPAddress, nil
|
||||
}
|
||||
|
||||
// logging
|
||||
|
||||
func (c *Client) errorf(fmt string, args ...interface{}) {
|
||||
common.Log.Errorf("[docker] "+fmt, args...)
|
||||
}
|
||||
@@ -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}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package common
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
@@ -64,3 +65,23 @@ func CheckWarn(e error) {
|
||||
Log.Warnln(e)
|
||||
}
|
||||
}
|
||||
|
||||
// Adaptor type to go from Logrus to something expecting a standard library log.Logger
|
||||
type logLogger struct {
|
||||
logger *logrus.Logger
|
||||
}
|
||||
|
||||
// io.Writer interface
|
||||
func (l logLogger) Write(p []byte) (n int, err error) {
|
||||
ln := len(p)
|
||||
// Strip a trailing newline, because logrus formatter adds one
|
||||
if ln > 0 && p[ln-1] == '\n' {
|
||||
p = p[:ln-1]
|
||||
}
|
||||
l.logger.Info(string(p))
|
||||
return ln, nil
|
||||
}
|
||||
|
||||
func LogLogger() *log.Logger {
|
||||
return log.New(logLogger{Log}, "", 0)
|
||||
}
|
||||
@@ -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))
|
||||
}
|
||||
+15
-17
@@ -10,49 +10,50 @@ import (
|
||||
|
||||
// ODP admin functionality
|
||||
|
||||
func CreateDatapath(dpname string) (err error, supported bool) {
|
||||
func CreateDatapath(dpname string) (supported bool, err error) {
|
||||
dpif, err := odp.NewDpif()
|
||||
if err != nil {
|
||||
if odp.IsKernelLacksODPError(err) {
|
||||
return nil, false
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return err, true
|
||||
return true, err
|
||||
}
|
||||
|
||||
defer dpif.Close()
|
||||
|
||||
dp, err := dpif.CreateDatapath(dpname)
|
||||
if err != nil && !odp.IsDatapathNameAlreadyExistsError(err) {
|
||||
return err, true
|
||||
if err != nil {
|
||||
if !odp.IsDatapathNameAlreadyExistsError(err) {
|
||||
return true, err
|
||||
}
|
||||
if dp, err = dpif.LookupDatapath(dpname); err != nil {
|
||||
return true, err
|
||||
}
|
||||
}
|
||||
|
||||
// Pick an ephemeral port number to use in probing for vxlan
|
||||
// support.
|
||||
udpconn, err := net.ListenUDP("udp4", nil)
|
||||
if err != nil {
|
||||
return err, true
|
||||
return true, err
|
||||
}
|
||||
defer udpconn.Close()
|
||||
|
||||
// we leave the UDP socket open, so creating a vxlan vport on
|
||||
// the same port number should fail. But that's fine: It's
|
||||
// still sufficient to probe for support.
|
||||
portno := uint16(udpconn.LocalAddr().(*net.UDPAddr).Port)
|
||||
vpid, err := dp.CreateVport(odp.NewVxlanVportSpec(
|
||||
fmt.Sprintf("vxlan-%d", portno), portno))
|
||||
vpid, err := dp.CreateVport(odp.NewVxlanVportSpec(fmt.Sprintf("vxlan-%d", portno), portno))
|
||||
if nlerr, ok := err.(odp.NetlinkError); ok {
|
||||
if syscall.Errno(nlerr) == syscall.EAFNOSUPPORT {
|
||||
dp.Delete()
|
||||
return fmt.Errorf("kernel does not have Open vSwitch VXLAN support"), false
|
||||
return false, fmt.Errorf("kernel does not have Open vSwitch VXLAN support")
|
||||
}
|
||||
}
|
||||
|
||||
if err == nil {
|
||||
dp.DeleteVport(vpid)
|
||||
}
|
||||
|
||||
udpconn.Close()
|
||||
return nil, true
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func DeleteDatapath(dpname string) error {
|
||||
@@ -60,7 +61,6 @@ func DeleteDatapath(dpname string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer dpif.Close()
|
||||
|
||||
dp, err := dpif.LookupDatapath(dpname)
|
||||
@@ -68,7 +68,6 @@ func DeleteDatapath(dpname string) error {
|
||||
if odp.IsNoSuchDatapathError(err) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -80,7 +79,6 @@ func AddDatapathInterface(dpname string, ifname string) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer dpif.Close()
|
||||
|
||||
dp, err := dpif.LookupDatapath(dpname)
|
||||
|
||||
@@ -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
-3
@@ -1,8 +1,6 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
import "strings"
|
||||
|
||||
// Assert test is true, panic otherwise
|
||||
func Assert(test bool) {
|
||||
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
type DB interface {
|
||||
Load(string, interface{}) (bool, error)
|
||||
Save(string, interface{}) error
|
||||
}
|
||||
|
||||
type BoltDB struct {
|
||||
db *bolt.DB
|
||||
}
|
||||
|
||||
var (
|
||||
versionIdent = []byte("version")
|
||||
persistenceVersion = []byte{1, 0} // major.minor
|
||||
topBucket = []byte("top")
|
||||
)
|
||||
|
||||
const (
|
||||
NameIdent = "peername"
|
||||
FileName = "data.db"
|
||||
)
|
||||
|
||||
func 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)
|
||||
}
|
||||
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
|
||||
}
|
||||
if err := top.Put(versionIdent, persistenceVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if checkVersion := top.Get(versionIdent); checkVersion != nil {
|
||||
if checkVersion[0] != persistenceVersion[0] {
|
||||
return fmt.Errorf("mismatched version %x", checkVersion)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (d *BoltDB) Load(ident string, data interface{}) (bool, error) {
|
||||
found := true
|
||||
err := d.db.View(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(topBucket)
|
||||
v := b.Get([]byte(ident))
|
||||
if v == nil {
|
||||
found = false
|
||||
return nil
|
||||
}
|
||||
reader := bytes.NewReader(v)
|
||||
decoder := gob.NewDecoder(reader)
|
||||
err := decoder.Decode(data)
|
||||
return err
|
||||
})
|
||||
return found, err
|
||||
}
|
||||
|
||||
func (d *BoltDB) Save(ident string, data interface{}) error {
|
||||
return d.db.Update(func(tx *bolt.Tx) error {
|
||||
b := tx.Bucket(topBucket)
|
||||
buf := new(bytes.Buffer)
|
||||
enc := gob.NewEncoder(buf)
|
||||
if err := enc.Encode(data); err != nil {
|
||||
return err
|
||||
}
|
||||
return b.Put([]byte(ident), buf.Bytes())
|
||||
})
|
||||
}
|
||||
|
||||
func (d *BoltDB) Close() error {
|
||||
return d.db.Close()
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
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
|
||||
32byte key and 4byte salt. This combo provides the following security
|
||||
properties:
|
||||
|
||||
* Data confidentiality.
|
||||
* Data origin authentication.
|
||||
* Integrity.
|
||||
* Anti-replay service.
|
||||
* Limited traffic flow confidentiality.
|
||||
|
||||
## Notation
|
||||
|
||||
* `SAin`: IPsec security association for inbound connections.
|
||||
* `SAout`: IPsec security association for outbound connections.
|
||||
* `SPout`: IPsec security policy for outbound connections. Used to match
|
||||
outbound flows to SAout.
|
||||
|
||||
## SA Key Derivation
|
||||
|
||||
For each connection direction, a different AES-GCM key and salt is used.
|
||||
The pairs are derived with HKDF ([RFC 5869][hkdf]):
|
||||
|
||||
```
|
||||
SAin[KeyAndSalt] = HKDF(sha256, ikm=sessionKey, salt=nonceIn, info=localPeerName)
|
||||
SAout[KeyAndSalt] = HKDF(sha256, ikm=sessionKey, salt=nonceOut, info=remotePeerName)
|
||||
```
|
||||
|
||||
Where:
|
||||
|
||||
* the mutual `sessionKey` is derived by the [Mesh][mesh] library during
|
||||
the control plane connection establishment between the local peer and
|
||||
the remote peer.
|
||||
* `nonceIn` and `nonceOut` are randomly generated 32byte nonces which
|
||||
are exchanged over the encrypted control plane channel.
|
||||
|
||||
## SPI
|
||||
|
||||
A directional secure connection between two peers is identified with SPI.
|
||||
|
||||
The kernel requires the pair of SPI and dst IP to be unique among security
|
||||
associations. Thus, to avoid collisions, we generate outbound SPI on a remote
|
||||
peer.
|
||||
|
||||
## Connection Establishment
|
||||
|
||||
```
|
||||
Peer A Peer B
|
||||
-----------------------------------------------------------------------------------------------------------------
|
||||
|
||||
fastdp.fwd.Confirm():
|
||||
install iptables blocking rules, fastdp.fwd.Confirm():
|
||||
nonce_BA = rand(), install iptables blocking rules,
|
||||
{key,salt}_BA = hkdf(sessionKey, nonce_BA, A), nonce_AB = rand(),
|
||||
spi_BA = allocspi(), {key,salt}_AB = hkdf(sessionKey, nonce_AB, B),
|
||||
create SA_BA(B<-A, spi_BA, key_BA, salt_BA), spi_AB = allocspi(),
|
||||
send InitSARemote(spi_BA, nonce_BA). --> create SA_AB(A<-B, spi_AB, key_AB, salt_AB),
|
||||
<-- send InitSARemote(spi_AB, nonce_AB).
|
||||
|
||||
|
||||
--> recv InitSARemote(spi_BA, nonce_BA):
|
||||
recv InitSARemote(spi_AB, nonce_AB): <-- {key,salt}_BA = hkdf(sessionKey, nonce_BA, A),
|
||||
{key,salt}_AB = hkdf(sessionKey, nonce_AB, B), create SA_BA(B<-A, spi_BA, key_BA, salt_BA),
|
||||
create SA_AB(A<-B, spi_AB, key_AB, salt_AB), create SP_BA(B<-A, spi_BA),
|
||||
create SP_AB(A<-B, spi_AB), install marking rule.
|
||||
install marking rule.
|
||||
```
|
||||
|
||||
# Implementation Details
|
||||
|
||||
## XFRM
|
||||
|
||||
The implementation is based on the kernel IP packet transformation framework
|
||||
called XFRM. Unfortunately, docs are barely existing and the complexity of
|
||||
the framework is high. The best resource I found is Chapter 10 in
|
||||
"Linux Kernel Networking: Implementation and Theory" by Rami Rosen.
|
||||
|
||||
## iptables Rules
|
||||
|
||||
The kernel VXLAN driver does not set a dst port of a tunnel in the ip flow
|
||||
descriptor, thus XFRM policy lookup cannot match a policy (SPout) which includes
|
||||
the port. This makes impossible to encrypt only tunneled traffic between
|
||||
peers. To work around, we mark such outgoing packets with iptables and set
|
||||
the same mark in the policy selector (funnily enough, `iptables_mangle` module
|
||||
eventually sets the missing dst port in the flow descriptor). The challenge
|
||||
here is to pick a mark that it would not interfere with other networking
|
||||
applications before OUTPUT'ing a packet. For example, Kubernetes by default
|
||||
uses 1<<14 and 1<<15 marks and we choose 1<<17 (0x20000). Additionally,
|
||||
such workaround brings the requirement for at least the 4.2 kernel.
|
||||
|
||||
The marking rules are the following:
|
||||
|
||||
```
|
||||
iptables -t mangle -A OUTPUT -j WEAVE-IPSEC-OUT
|
||||
iptables -t mangle -A WEAVE-IPSEC-OUT -s ${LOCAL_PEER_IP} -d ${REMOTE_PEER_IP} \
|
||||
-p udp --dport ${TUNNEL_PORT} -j WEAVE-IPSEC-OUT-MARK
|
||||
iptables -t mangle -A WEAVE-IPSEC-OUT-MARK --set-xmark ${MARK} -j MARK
|
||||
```
|
||||
|
||||
As Linux does not implement [IP Security Levels][ipseclevel], we install
|
||||
additional iptables rules to prevent from accidentally sending unencrypted
|
||||
traffic between peers which have previously established the secure connection.
|
||||
|
||||
For inbound traffic, we mark each ESP packet with the mark and drop non-marked
|
||||
tunnel traffic:
|
||||
|
||||
```
|
||||
iptables -t mangle -A INPUT -j WEAVE-IPSEC-IN
|
||||
iptables -t mangle -A WEAVE-IPSEC-IN -s ${REMOTE_PEER_IP} -d ${LOCAL_PEER_IP} \
|
||||
-m esp --espspi ${SPIin} -j WEAVE-IPSEC-IN-MARK
|
||||
iptables -t mangle -A WEAVE-IPSEC-IN-MARK --set-xmark ${MARK} -j MARK
|
||||
|
||||
iptables -t filter -A INPUT -j WEAVE-IPSEC-IN
|
||||
iptables -t filter -A WEAVE-IPSEC-IN -s ${REMOTE_PEER_IP} -d ${LOCAL_PEER_IP} \
|
||||
-p udp --dport ${TUNNEL_PORT} -m mark ! --mark ${MARK} -j DROP
|
||||
```
|
||||
|
||||
For outbound traffic, we drop marked traffic which does not match any SPout:
|
||||
|
||||
```
|
||||
iptables -t filter -A OUTPUT ! -p esp -m policy --dir out --pol none \
|
||||
-m mark --mark ${MARK} -j DROP
|
||||
```
|
||||
|
||||
## ESN
|
||||
|
||||
To prevent from cycling SeqNo which makes replay attacks possible, we use
|
||||
64-bit extended sequence numbers known as [ESN](esn).
|
||||
|
||||
## MTU
|
||||
|
||||
In addition to the VXLAN overhead, the MTU calculation should take into account
|
||||
the ESP overhead which is 34-37 bytes (encrypted Payload is 4 bytes aligned) and
|
||||
consists of:
|
||||
|
||||
* 4 bytes (SPI).
|
||||
* 4 bytes (Sequence Number).
|
||||
* 8 bytes (ESP IV).
|
||||
* 1 byte (Pad Length).
|
||||
* 1 byte (NextHeader).
|
||||
* 16 bytes (ICV).
|
||||
* 0-3 bytes (Padding).
|
||||
|
||||
|
||||
[esp]: https://tools.ietf.org/html/rfc2406
|
||||
[aesgcm]: https://tools.ietf.org/html/rfc4106
|
||||
[hkdf]: https://tools.ietf.org/html/rfc5869
|
||||
[esn]: https://tools.ietf.org/html/rfc4304
|
||||
[ipseclevel]: https://tools.ietf.org/html/draft-mcdonald-simple-ipsec-api-01)
|
||||
[mesh]: https://github.com/weaveworks/mesh
|
||||
+170
@@ -0,0 +1,170 @@
|
||||
# Overlay Method Selection
|
||||
|
||||
Weave automatically selects the best working overlay implementation
|
||||
when connecting to a new peer. It does this by initiating both
|
||||
forwarders in parallel, eventually settling on the most preferred
|
||||
established forwarder at which point the other forwarder is shut down,
|
||||
assuming it had not failed already. In the event that the remaining
|
||||
forwarder fails at a later time (e.g. because of a heartbeat timeout)
|
||||
the control plane TCP connection will recycle allowing the selection
|
||||
algorithm to run again. By default fast datapath is preferred over
|
||||
Sleeve.
|
||||
|
||||
# Local Bridging
|
||||
|
||||
Unlike the weave bridge netdev used by Sleeve, the OVS datapath has no
|
||||
inherent bridging capability. Consequently fastdp implements an
|
||||
ethernet bridge in addition to the vxlan overlay, maintaining its own
|
||||
forwarding database by learning MAC addresses and dispatching
|
||||
broadcast, unicast & multicast traffic accordingly.
|
||||
|
||||
# Short Peer IDs
|
||||
|
||||
The router relies on having access to the source and destination peer
|
||||
when deciding how to forward packets. When the Sleeve overlay is in
|
||||
use this information is conveyed directly within the encapsulation by
|
||||
including the names of the peers in question, a solution not
|
||||
accommodated directly by the vxlan wire format. Fortunately vxlan does
|
||||
have a twenty four bit segment ID field in the header we can use to
|
||||
encode this data - the challenge is to identify peers uniquely with a
|
||||
twelve bit identifier instead of the seventeen bytes used by Sleeve.
|
||||
In practice this is achieved by peers adopting a random 'short ID' and
|
||||
resolving ownership collisions of same via the existing gossip
|
||||
mechanism.
|
||||
|
||||
# Heartbeats via vxlan
|
||||
|
||||
Heartbeats are implemented by sending an encapsulated ethernet frame
|
||||
with source and destination MAC addresses of `00:00:00:00:00:00` and a
|
||||
payload consisting of the connection UID and total ethernet frame
|
||||
length. The vxlan vport miss handler on the receiving side detects the
|
||||
all-zero addresses and acknowledges the heartbeat via the TCP control
|
||||
channel after validating the connection UID and frame length against
|
||||
their expected values.
|
||||
|
||||
# PMTU Discovery
|
||||
|
||||
The length of the heartbeat ethernet frame is set to the MTU of the
|
||||
datapath interface. In the event the vxlan packet is dropped or
|
||||
truncated, the heartbeat will not be acknowledged; this lack of
|
||||
acknowledgement will cause the peers to fall back to the Sleeve
|
||||
overlay, which has a more sophisticated dynamic mechanism for coping
|
||||
with low path MTUs.
|
||||
|
||||
To avoid triggering this fallback in typical deployments, the datapath
|
||||
interface is statically configured with an MTU of 1410 bytes allowing
|
||||
it to work with most underlay network provider MTUs, including GCE at
|
||||
1460 bytes (the fifty byte difference accommodates the vxlan overhead).
|
||||
This value can be overridden by setting `WEAVE_MTU` at launch if
|
||||
necessary.
|
||||
|
||||
# Virtual Ports
|
||||
|
||||
There are three kinds of virtual port associated with the weave
|
||||
datapath:
|
||||
|
||||
* `internal` - one of these is created automatically, named after the
|
||||
datapath. It corresponds to the network interface of the same name
|
||||
that appears on the host when the datapath is created; it is the
|
||||
ingress/egress port for `weave expose`
|
||||
* `netdev` - one for each application container, corresponding to the
|
||||
host end of the veth pair
|
||||
* `vxlan` - one for each UDP port on which we are listening for vxlan
|
||||
encapsulated packets. Typically there is only one of these, but
|
||||
there may be more in a network in which peers do not all use the same
|
||||
port configuration
|
||||
|
||||
# Misses and Flow Creation
|
||||
|
||||
As mentioned above, the datapath has no inherent behaviour - any
|
||||
ingressing packet which does not match a flow is passed to a userspace
|
||||
miss handler. The miss handler is then responsible for a) instructing
|
||||
the datapath to take actions for the packet that caused the miss (for
|
||||
example by copying it to one or more ports) and optionally b)
|
||||
installing flow rules which will allow the datapath to act on similar
|
||||
packets in future without invoking the miss handler.
|
||||
|
||||
At a high level the fast datapath implementation can be viewed as a
|
||||
set of miss handlers that determine what actions the OVS datapath
|
||||
should take based on router topology information, together with some
|
||||
additional machinery that manages the expiry of resulting flows when
|
||||
that state changes.
|
||||
|
||||
When a miss handler is invoked, it has two pieces of context: a byte
|
||||
array containing the packet that triggered the miss, and a set of
|
||||
'flow keys' that have been extracted from the packet by the kernel
|
||||
module. The following flow keys are of interest to weave:
|
||||
|
||||
* `InPort` - the identifier of the ingress virtual port
|
||||
* `Ethernet` - the source and destination MAC addresses of the
|
||||
ethernet frame
|
||||
* `Tunnel` - tunnel identifier (see section on short peer IDs above),
|
||||
and source/dest IPv4 addresses. Only present for ingress via a vxlan
|
||||
vport
|
||||
|
||||
Crucially, the router must use this information alone to determine
|
||||
which actions to take - this allows the specification of a flow which
|
||||
matches these keys and instructs the kernel to take action
|
||||
automatically in future without further userspace involvement.
|
||||
|
||||
Two actions are of interest:
|
||||
|
||||
* `Output` - output the packet to the specified vport
|
||||
* `SetTunnel` - update the effective vxlan tunnel parameters. Only
|
||||
required prior to output from a vxlan vport
|
||||
|
||||
It is possible to have multiple actions in a flow, so the router can
|
||||
for example create a single rule that copies broadcast traffic to all
|
||||
'local' vports (save the ingress vport obviously) as well as to vxlan
|
||||
vports for onward peers as dictated by the routing topology.
|
||||
|
||||
Every flow specified by the router has the following characteristics:
|
||||
|
||||
* An `InPort` key matching the ingress vport
|
||||
* A `Tunnel` key if the ingress vport is of type vxlan
|
||||
* An `Ethernet` key matching the source and destination MAC
|
||||
* A list of `Output` (for internal and netdev vports) and
|
||||
`SetTunnel`+`Output` (for vxlan vports) actions
|
||||
|
||||
Under certain circumstances the miss handler will instruct the
|
||||
datapath to execute a set of actions for a packet without creating a
|
||||
corresponding flow:
|
||||
|
||||
* When broadcasting packets to discover the path to unknown
|
||||
destination MACs. This condition is transient in the overwhelming
|
||||
majority of cases and so the benefit of creating a flow is
|
||||
outweighed by the need to invalidate it once the MAC is learned
|
||||
* When the packet also needs to be forwarded to one or more peers via
|
||||
Sleeve. In this case the router needs to handle all subsequent
|
||||
matching packets as there is no OVS flow action to do it without
|
||||
userspace involvement
|
||||
|
||||
# Flow Invalidation
|
||||
|
||||
Once a flow has been created for a particular combination of keys, the
|
||||
miss handler will never again be invoked for matching packets. It is
|
||||
therefore extremely important that we detect events which invalidate
|
||||
existing flow actions:
|
||||
|
||||
* Addition of netdev vports to the datapath
|
||||
* Route invalidation (topology change)
|
||||
* Short peer ID collision
|
||||
|
||||
The response in each case is the same - delete all flows from the
|
||||
datapath, allowing them to be recreated taking into account the
|
||||
updated state.
|
||||
|
||||
In addition to these event based invalidations there is an expiry
|
||||
process that executes every five minutes. This process enumerates all
|
||||
flows in the datapath, removing any which have not been used since the
|
||||
last check; this cleans up:
|
||||
|
||||
* Flows referring to netdev vports which have been removed
|
||||
* Flows created by forwarders which have been stopped
|
||||
* Flows related to MAC addresses which have not communicated recently
|
||||
|
||||
Flows keyed on tunnel IPv4 address do not need to be cleared when a
|
||||
peer appears to change IP address due to NAT; this will cause a miss
|
||||
resulting in a new flow, and the old flow will expire naturally via
|
||||
the timer mechanism.
|
||||
|
||||
@@ -210,11 +210,3 @@ reached the peer that does the `rmpeer`, then we get an inconsistent
|
||||
ring. We could make it safe, up to partitions, by inquiring of every
|
||||
live peer what its view of the dead peer is, and making sure we use
|
||||
the latest info.)
|
||||
|
||||
## Limitations
|
||||
|
||||
Nothing is persisted. If one peer restarts it should receive gossip
|
||||
from other peers showing what it used to own, and in that case it can
|
||||
recover quite well. If, however, there are no peers left alive, or
|
||||
this peer cannot establish network communication with those that are,
|
||||
then it cannot recover.
|
||||
+13
-3
@@ -6,7 +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`
|
||||
|
||||
## Release Types
|
||||
|
||||
@@ -63,9 +62,10 @@ This has the following effects:
|
||||
## Draft Phase
|
||||
### Push Version Tag Upstream
|
||||
|
||||
First you must push your version tag upstream, so that an associated
|
||||
GitHub release may be created:
|
||||
First you must push your branch and version tag upstream, so that an
|
||||
associated GitHub release may be created:
|
||||
|
||||
git push git@github.com:weaveworks/weave
|
||||
git push git@github.com:weaveworks/weave $TAG
|
||||
|
||||
N.B. if you're testing the release process, push to your fork
|
||||
@@ -128,6 +128,16 @@ Finally, for **Mainline** releases only:
|
||||
|
||||
* Images tagged `latest` are updated on DockerHub
|
||||
|
||||
### Finish up
|
||||
|
||||
* If not on master, merge branch into master and push to GitHub.
|
||||
* Close the [milestone](https://github.com/weaveworks/weave/milestones) in GitHub and create the next milestone
|
||||
* Update the `#weavenetwork` topic heading on freenode (requires 'chanops' permission)
|
||||
* For a mainline release vX.Y.0, create a release branch X.Y from the
|
||||
tag 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
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ The model is that each host has a service that is notified of
|
||||
hostnames and weave addresses for containers on the host. Like IPAM,
|
||||
this service is embedded within the router. It binds to
|
||||
the host bridge to answer DNS queries from local containers; for
|
||||
anything it can't answer, it uses the infomation in the host's
|
||||
anything it can't answer, it uses the information in the host's
|
||||
/etc/resolv.conf to query an 'fallback' server.
|
||||
|
||||
The service is comprised of a DNS server, which answers all DNS queries
|
||||
@@ -33,7 +33,7 @@ are re-broadcast around the cluster.
|
||||
The DNS server also listens to the Docker event stream, and removes
|
||||
entries for containers when they die. Entries removed in this way are
|
||||
tombstoned, and the tombstone lazily broadcast around the cluster.
|
||||
After a short timeout the tombstones are independantly removed from
|
||||
After a short timeout the tombstones are independently removed from
|
||||
each host.
|
||||
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
# 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
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
+19
-10
@@ -3,6 +3,7 @@ package ipam
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/weaveworks/weave/api"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
@@ -14,7 +15,8 @@ type allocateResult struct {
|
||||
type allocate struct {
|
||||
resultChan chan<- allocateResult
|
||||
ident string
|
||||
r address.Range // Range we are trying to allocate within
|
||||
r address.CIDR // Subnet we are trying to allocate within
|
||||
isContainer bool
|
||||
hasBeenCancelled func() bool
|
||||
}
|
||||
|
||||
@@ -25,29 +27,36 @@ func (g *allocate) Try(alloc *Allocator) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
if addr, found := alloc.lookupOwned(g.ident, g.r); found {
|
||||
g.resultChan <- allocateResult{addr, nil}
|
||||
if addrs := alloc.ownedInRange(g.ident, g.r.Range()); len(addrs) > 0 {
|
||||
// If we had heard that this container died, resurrect it
|
||||
delete(alloc.dead, g.ident) // delete is no-op if key not in map
|
||||
g.resultChan <- allocateResult{addrs[0].Addr, nil}
|
||||
return true
|
||||
}
|
||||
|
||||
if !alloc.universe.Overlaps(g.r) {
|
||||
g.resultChan <- allocateResult{0, fmt.Errorf("range %s out of bounds: %s", g.r, alloc.universe)}
|
||||
if !alloc.universe.Range().Overlaps(g.r.Range()) {
|
||||
g.resultChan <- allocateResult{err: fmt.Errorf("range %s out of bounds: %s", g.r, alloc.universe)}
|
||||
return true
|
||||
}
|
||||
|
||||
alloc.establishRing()
|
||||
|
||||
if ok, addr := alloc.space.Allocate(g.r); ok {
|
||||
if ok, addr := alloc.space.Allocate(g.r.HostRange()); ok {
|
||||
// If caller hasn't supplied a unique ID, file it under the IP address
|
||||
// which lets the caller then release the address using DELETE /ip/address
|
||||
if g.ident == api.NoContainerID {
|
||||
g.ident = addr.String()
|
||||
}
|
||||
alloc.debugln("Allocated", addr, "for", g.ident, "in", g.r)
|
||||
alloc.addOwned(g.ident, addr)
|
||||
alloc.addOwned(g.ident, address.MakeCIDR(g.r, addr), g.isContainer)
|
||||
g.resultChan <- allocateResult{addr, nil}
|
||||
return true
|
||||
}
|
||||
|
||||
// out of space
|
||||
donors := alloc.ring.ChoosePeersToAskForSpace(g.r.Start, g.r.End)
|
||||
donors := alloc.ring.ChoosePeersToAskForSpace(g.r.Addr, g.r.Range().End)
|
||||
for _, donor := range donors {
|
||||
if err := alloc.sendSpaceRequest(donor, g.r); err != nil {
|
||||
if err := alloc.sendSpaceRequest(donor, g.r.Range()); err != nil {
|
||||
alloc.debugln("Problem asking peer", donor, "for space:", err)
|
||||
} else {
|
||||
alloc.debugln("Decided to ask peer", donor, "for space in range", g.r)
|
||||
@@ -59,7 +68,7 @@ func (g *allocate) Try(alloc *Allocator) bool {
|
||||
}
|
||||
|
||||
func (g *allocate) Cancel() {
|
||||
g.resultChan <- allocateResult{0, &errorCancelled{"Allocate", g.ident}}
|
||||
g.resultChan <- allocateResult{err: &errorCancelled{"Allocate", g.ident}}
|
||||
}
|
||||
|
||||
func (g *allocate) ForContainer(ident string) bool {
|
||||
|
||||
+507
-185
@@ -7,11 +7,14 @@ import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/weaveworks/mesh"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/db"
|
||||
"github.com/weaveworks/weave/ipam/paxos"
|
||||
"github.com/weaveworks/weave/ipam/ring"
|
||||
"github.com/weaveworks/weave/ipam/space"
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
"github.com/weaveworks/weave/ipam/tracker"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
@@ -21,8 +24,9 @@ const (
|
||||
msgRingUpdate
|
||||
msgSpaceRequestDenied
|
||||
|
||||
paxosInterval = time.Second * 5
|
||||
MinSubnetSize = 4 // first and last addresses are excluded, so 2 would be too small
|
||||
tickInterval = time.Second * 5
|
||||
MinSubnetSize = 4 // first and last addresses are excluded, so 2 would be too small
|
||||
containerDiedTimeout = time.Second * 30
|
||||
)
|
||||
|
||||
// operation represents something which Allocator wants to do, but
|
||||
@@ -38,52 +42,135 @@ type operation interface {
|
||||
ForContainer(ident string) bool
|
||||
}
|
||||
|
||||
// This type is persisted hence all fields exported
|
||||
type ownedData struct {
|
||||
IsContainer bool
|
||||
Cidrs []address.CIDR
|
||||
}
|
||||
|
||||
// Allocator brings together Ring and space.Set, and does the
|
||||
// necessary plumbing. Runs as a single-threaded Actor, so no locks
|
||||
// are used around data structures.
|
||||
type Allocator struct {
|
||||
actionChan chan<- func()
|
||||
ourName mesh.PeerName
|
||||
universe address.Range // superset of all ranges
|
||||
ring *ring.Ring // information on ranges owned by all peers
|
||||
space space.Space // more detail on ranges owned by us
|
||||
owned map[string][]address.Address // who owns what addresses, indexed by container-ID
|
||||
nicknames map[mesh.PeerName]string // so we can map nicknames for rmpeer
|
||||
pendingAllocates []operation // held until we get some free space
|
||||
pendingClaims []operation // held until we know who owns the space
|
||||
gossip mesh.Gossip // our link to the outside world for sending messages
|
||||
paxos *paxos.Node
|
||||
paxosTicker *time.Ticker
|
||||
shuttingDown bool // to avoid doing any requests while trying to shut down
|
||||
isKnownPeer func(mesh.PeerName) bool
|
||||
now func() time.Time
|
||||
actionChan chan<- func()
|
||||
stopChan chan<- struct{}
|
||||
ourName mesh.PeerName
|
||||
seed []mesh.PeerName // optional user supplied ring seed
|
||||
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
|
||||
nicknames map[mesh.PeerName]string // so we can map nicknames for rmpeer
|
||||
pendingAllocates []operation // held until we get some free space
|
||||
pendingClaims []operation // held until we know who owns the space
|
||||
pendingPrimes []operation // held while our ring is empty
|
||||
dead map[string]time.Time // containers we heard were dead, and when
|
||||
db db.DB // persistence
|
||||
gossip mesh.Gossip // our link to the outside world for sending messages
|
||||
paxos paxos.Participant
|
||||
awaitingConsensus bool
|
||||
ticker *time.Ticker
|
||||
shuttingDown bool // to avoid doing any requests while trying to shut down
|
||||
isKnownPeer func(mesh.PeerName) bool
|
||||
quorum func() uint
|
||||
now func() time.Time
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
OurName mesh.PeerName
|
||||
OurUID mesh.PeerUID
|
||||
OurNickname string
|
||||
Seed []mesh.PeerName
|
||||
Universe address.CIDR
|
||||
IsObserver bool
|
||||
Quorum func() uint
|
||||
Db db.DB
|
||||
IsKnownPeer func(name mesh.PeerName) bool
|
||||
Tracker tracker.LocalRangeTracker
|
||||
}
|
||||
|
||||
// NewAllocator creates and initialises a new Allocator
|
||||
func NewAllocator(ourName mesh.PeerName, ourUID mesh.PeerUID, ourNickname string, universe address.Range, quorum uint, isKnownPeer func(name mesh.PeerName) bool) *Allocator {
|
||||
return &Allocator{
|
||||
ourName: ourName,
|
||||
universe: universe,
|
||||
ring: ring.New(universe.Start, universe.End, ourName),
|
||||
owned: make(map[string][]address.Address),
|
||||
paxos: paxos.NewNode(ourName, ourUID, quorum),
|
||||
nicknames: map[mesh.PeerName]string{ourName: ourNickname},
|
||||
isKnownPeer: isKnownPeer,
|
||||
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)
|
||||
}
|
||||
|
||||
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.Range().Start, config.Universe.Range().End, config.OurName, onUpdate),
|
||||
owned: make(map[string]ownedData),
|
||||
db: config.Db,
|
||||
paxos: participant,
|
||||
nicknames: map[mesh.PeerName]string{config.OurName: config.OurNickname},
|
||||
isKnownPeer: config.IsKnownPeer,
|
||||
quorum: config.Quorum,
|
||||
dead: make(map[string]time.Time),
|
||||
now: time.Now,
|
||||
}
|
||||
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() {
|
||||
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")
|
||||
}
|
||||
actionChan := make(chan func(), mesh.ChannelSize)
|
||||
stopChan := make(chan struct{})
|
||||
alloc.actionChan = actionChan
|
||||
go alloc.actorLoop(actionChan)
|
||||
alloc.stopChan = stopChan
|
||||
alloc.ticker = time.NewTicker(tickInterval)
|
||||
go alloc.actorLoop(actionChan, stopChan)
|
||||
}
|
||||
|
||||
// Stop makes the actor routine exit, for test purposes ONLY because any
|
||||
// calls after this is processed will hang. Async.
|
||||
func (alloc *Allocator) Stop() {
|
||||
alloc.actionChan <- nil
|
||||
select {
|
||||
case alloc.stopChan <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// Operation life cycle
|
||||
@@ -140,35 +227,31 @@ func (alloc *Allocator) cancelOpsFor(ops *[]operation, ident string) bool {
|
||||
return found
|
||||
}
|
||||
|
||||
// Try all pending operations
|
||||
func (alloc *Allocator) tryPendingOps() {
|
||||
// The slightly different semantics requires us to operate on 'claims' and
|
||||
// 'allocates' separately:
|
||||
// Claims must be tried before Allocates
|
||||
for i := 0; i < len(alloc.pendingClaims); {
|
||||
op := alloc.pendingClaims[i]
|
||||
// Try all operations in a queue
|
||||
func (alloc *Allocator) tryOps(ops *[]operation) {
|
||||
for i := 0; i < len(*ops); {
|
||||
op := (*ops)[i]
|
||||
if !op.Try(alloc) {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
alloc.pendingClaims = append(alloc.pendingClaims[:i], alloc.pendingClaims[i+1:]...)
|
||||
*ops = append((*ops)[:i], (*ops)[i+1:]...)
|
||||
}
|
||||
}
|
||||
|
||||
// When the first Allocate fails, bail - no need to
|
||||
// send too many begs for space.
|
||||
for i := 0; i < len(alloc.pendingAllocates); {
|
||||
op := alloc.pendingAllocates[i]
|
||||
if !op.Try(alloc) {
|
||||
break
|
||||
}
|
||||
alloc.pendingAllocates = append(alloc.pendingAllocates[:i], alloc.pendingAllocates[i+1:]...)
|
||||
}
|
||||
// Try all pending operations
|
||||
func (alloc *Allocator) tryPendingOps() {
|
||||
// Unblock pending primes first
|
||||
alloc.tryOps(&alloc.pendingPrimes)
|
||||
// Process existing claims before servicing new allocations
|
||||
alloc.tryOps(&alloc.pendingClaims)
|
||||
alloc.tryOps(&alloc.pendingAllocates)
|
||||
}
|
||||
|
||||
func (alloc *Allocator) spaceRequestDenied(sender mesh.PeerName, r address.Range) {
|
||||
for i := 0; i < len(alloc.pendingClaims); {
|
||||
claim := alloc.pendingClaims[i].(*claim)
|
||||
if r.Contains(claim.addr) {
|
||||
if r.Contains(claim.cidr.Addr) {
|
||||
claim.deniedBy(alloc, sender)
|
||||
alloc.pendingClaims = append(alloc.pendingClaims[:i], alloc.pendingClaims[i+1:]...)
|
||||
continue
|
||||
@@ -188,87 +271,135 @@ func (e *errorCancelled) Error() string {
|
||||
|
||||
// Actor client API
|
||||
|
||||
// Prime (Sync) - wait for consensus
|
||||
func (alloc *Allocator) Prime() {
|
||||
resultChan := make(chan struct{})
|
||||
op := &prime{resultChan: resultChan}
|
||||
alloc.doOperation(op, &alloc.pendingPrimes)
|
||||
<-resultChan
|
||||
}
|
||||
|
||||
// Allocate (Sync) - get new IP address for container with given name in range
|
||||
// if there isn't any space in that range we block indefinitely
|
||||
func (alloc *Allocator) Allocate(ident string, r address.Range, hasBeenCancelled func() bool) (address.Address, error) {
|
||||
func (alloc *Allocator) Allocate(ident string, r address.CIDR, isContainer bool, hasBeenCancelled func() bool) (address.Address, error) {
|
||||
resultChan := make(chan allocateResult)
|
||||
op := &allocate{resultChan: resultChan, ident: ident, r: r, hasBeenCancelled: hasBeenCancelled}
|
||||
op := &allocate{
|
||||
resultChan: resultChan,
|
||||
ident: ident,
|
||||
r: r,
|
||||
isContainer: isContainer,
|
||||
hasBeenCancelled: hasBeenCancelled,
|
||||
}
|
||||
alloc.doOperation(op, &alloc.pendingAllocates)
|
||||
result := <-resultChan
|
||||
return result.addr, result.err
|
||||
}
|
||||
|
||||
// Lookup (Sync) - get existing IP address for container with given name in range
|
||||
func (alloc *Allocator) Lookup(ident string, r address.Range) (address.Address, error) {
|
||||
resultChan := make(chan allocateResult)
|
||||
// Lookup (Sync) - get existing IP addresses for container with given name in range
|
||||
func (alloc *Allocator) Lookup(ident string, r address.Range) ([]address.CIDR, error) {
|
||||
resultChan := make(chan []address.CIDR)
|
||||
alloc.actionChan <- func() {
|
||||
if addr, found := alloc.lookupOwned(ident, r); found {
|
||||
resultChan <- allocateResult{addr: addr}
|
||||
return
|
||||
}
|
||||
resultChan <- allocateResult{err: fmt.Errorf("lookup: no address found for %s in range %s", ident, r)}
|
||||
resultChan <- alloc.ownedInRange(ident, r)
|
||||
}
|
||||
result := <-resultChan
|
||||
return result.addr, result.err
|
||||
return <-resultChan, nil
|
||||
}
|
||||
|
||||
// Claim an address that we think we should own (Sync)
|
||||
func (alloc *Allocator) Claim(ident string, addr address.Address, 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, ident: ident, addr: addr, noErrorOnUnknown: noErrorOnUnknown}
|
||||
op := &claim{
|
||||
resultChan: resultChan,
|
||||
ident: ident,
|
||||
cidr: cidr,
|
||||
isContainer: isContainer,
|
||||
noErrorOnUnknown: noErrorOnUnknown,
|
||||
hasBeenCancelled: hasBeenCancelled,
|
||||
}
|
||||
alloc.doOperation(op, &alloc.pendingClaims)
|
||||
return <-resultChan
|
||||
}
|
||||
|
||||
// ContainerDied is provided to satisfy the updater interface; does a 'Delete' underneath. Sync.
|
||||
// ContainerDied called from the updater interface. Async.
|
||||
func (alloc *Allocator) ContainerDied(ident string) {
|
||||
if err := alloc.Delete(ident); err == nil {
|
||||
alloc.debugln("Container", ident, "died; released addresses")
|
||||
alloc.actionChan <- func() {
|
||||
if alloc.hasOwnedByContainer(ident) {
|
||||
alloc.debugln("Container", ident, "died; noting to remove later")
|
||||
alloc.dead[ident] = alloc.now()
|
||||
}
|
||||
// Also remove any pending ops
|
||||
alloc.cancelOpsFor(&alloc.pendingAllocates, ident)
|
||||
alloc.cancelOpsFor(&alloc.pendingClaims, ident)
|
||||
}
|
||||
}
|
||||
|
||||
func (alloc *Allocator) ContainerStarted(ident string) {}
|
||||
// ContainerDestroyed called from the updater interface. Async.
|
||||
func (alloc *Allocator) ContainerDestroyed(ident string) {
|
||||
alloc.actionChan <- func() {
|
||||
if alloc.hasOwnedByContainer(ident) {
|
||||
alloc.debugln("Container", ident, "destroyed; removing addresses")
|
||||
alloc.delete(ident)
|
||||
delete(alloc.dead, ident)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (alloc *Allocator) removeDeadContainers() {
|
||||
cutoff := alloc.now().Add(-containerDiedTimeout)
|
||||
for ident, timeOfDeath := range alloc.dead {
|
||||
if timeOfDeath.Before(cutoff) {
|
||||
if err := alloc.delete(ident); err == nil {
|
||||
alloc.debugln("Removed addresses for container", ident)
|
||||
}
|
||||
delete(alloc.dead, ident)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (alloc *Allocator) ContainerStarted(ident string) {
|
||||
alloc.actionChan <- func() {
|
||||
delete(alloc.dead, ident) // delete is no-op if key not in map
|
||||
}
|
||||
}
|
||||
|
||||
func (alloc *Allocator) PruneOwned(ids []string) {
|
||||
idmap := make(map[string]struct{}, len(ids))
|
||||
for _, id := range ids {
|
||||
idmap[id] = struct{}{}
|
||||
}
|
||||
alloc.actionChan <- func() {
|
||||
alloc.pruneOwned(idmap)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete (Sync) - release all IP addresses for container with given name
|
||||
func (alloc *Allocator) Delete(ident string) error {
|
||||
errChan := make(chan error)
|
||||
alloc.actionChan <- func() {
|
||||
addrs, found := alloc.owned[ident]
|
||||
for _, addr := range addrs {
|
||||
alloc.space.Free(addr)
|
||||
}
|
||||
delete(alloc.owned, ident)
|
||||
|
||||
// Also remove any pending ops
|
||||
found = alloc.cancelOpsFor(&alloc.pendingAllocates, ident) || found
|
||||
found = alloc.cancelOpsFor(&alloc.pendingClaims, ident) || found
|
||||
|
||||
if !found {
|
||||
errChan <- fmt.Errorf("Delete: no addresses for %s", ident)
|
||||
return
|
||||
}
|
||||
errChan <- nil
|
||||
errChan <- alloc.delete(ident)
|
||||
}
|
||||
return <-errChan
|
||||
}
|
||||
|
||||
func (alloc *Allocator) delete(ident string) error {
|
||||
cidrs := alloc.removeAllOwned(ident)
|
||||
if len(cidrs) == 0 {
|
||||
return fmt.Errorf("Delete: no addresses for %s", ident)
|
||||
}
|
||||
for _, cidr := range cidrs {
|
||||
alloc.space.Free(cidr.Addr)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Free (Sync) - release single IP address for container
|
||||
func (alloc *Allocator) Free(ident string, addrToFree address.Address) error {
|
||||
errChan := make(chan error)
|
||||
alloc.actionChan <- func() {
|
||||
addrs := alloc.owned[ident]
|
||||
for i, ownedAddr := range addrs {
|
||||
if ownedAddr == addrToFree {
|
||||
alloc.debugln("Freed", addrToFree, "for", ident)
|
||||
if len(addrs) == 1 {
|
||||
delete(alloc.owned, ident)
|
||||
} else {
|
||||
alloc.owned[ident] = append(addrs[:i], addrs[i+1:]...)
|
||||
}
|
||||
alloc.space.Free(addrToFree)
|
||||
errChan <- nil
|
||||
return
|
||||
}
|
||||
if alloc.removeOwned(ident, addrToFree) {
|
||||
alloc.debugln("Freed", addrToFree, "for", ident)
|
||||
alloc.space.Free(addrToFree)
|
||||
errChan <- nil
|
||||
return
|
||||
}
|
||||
|
||||
errChan <- fmt.Errorf("Free: address %s not found for %s", addrToFree, ident)
|
||||
@@ -299,7 +430,7 @@ func (alloc *Allocator) pickPeerForTransfer() mesh.PeerName {
|
||||
if heir := alloc.ring.PickPeerForTransfer(t); heir != mesh.UnknownPeerName {
|
||||
return heir
|
||||
}
|
||||
// finally, disappeared peers that that passively participated in IPAM
|
||||
// finally, disappeared peers that passively participated in IPAM
|
||||
return alloc.pickPeerFromNicknames(t)
|
||||
}
|
||||
|
||||
@@ -311,37 +442,53 @@ func (alloc *Allocator) Shutdown() {
|
||||
alloc.shuttingDown = true
|
||||
alloc.cancelOps(&alloc.pendingClaims)
|
||||
alloc.cancelOps(&alloc.pendingAllocates)
|
||||
if heir := alloc.pickPeerForTransfer(); heir != mesh.UnknownPeerName {
|
||||
alloc.ring.Transfer(alloc.ourName, heir)
|
||||
alloc.space.Clear()
|
||||
alloc.cancelOps(&alloc.pendingPrimes)
|
||||
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{}{}
|
||||
}
|
||||
<-doneChan
|
||||
}
|
||||
|
||||
// AdminTakeoverRanges (Sync) - take over the ranges owned by a given peer.
|
||||
// Only done on adminstrator command.
|
||||
func (alloc *Allocator) AdminTakeoverRanges(peerNameOrNickname string) error {
|
||||
resultChan := make(chan error)
|
||||
// AdminTakeoverRanges (Sync) - take over the ranges owned by a given
|
||||
// peer, and return how much space was transferred in the process.
|
||||
// Only done on administrator command.
|
||||
func (alloc *Allocator) AdminTakeoverRanges(peerNameOrNickname string) address.Count {
|
||||
resultChan := make(chan address.Count)
|
||||
alloc.actionChan <- func() {
|
||||
peername, err := alloc.lookupPeername(peerNameOrNickname)
|
||||
if err != nil {
|
||||
resultChan <- fmt.Errorf("Cannot find peer '%s'", peerNameOrNickname)
|
||||
alloc.warnf("attempt to take over range from unknown peer '%s'", peerNameOrNickname)
|
||||
resultChan <- address.Count(0)
|
||||
return
|
||||
}
|
||||
|
||||
alloc.debugln("AdminTakeoverRanges:", peername)
|
||||
if peername == alloc.ourName {
|
||||
resultChan <- fmt.Errorf("Cannot take over ranges from yourself!")
|
||||
alloc.warnf("attempt to take over range from ourself")
|
||||
resultChan <- address.Count(0)
|
||||
return
|
||||
}
|
||||
|
||||
newRanges, err := alloc.ring.Transfer(peername, alloc.ourName)
|
||||
alloc.space.AddRanges(newRanges)
|
||||
resultChan <- err
|
||||
newRanges := alloc.ring.Transfer(peername, alloc.ourName)
|
||||
|
||||
if len(newRanges) == 0 {
|
||||
resultChan <- address.Count(0)
|
||||
return
|
||||
}
|
||||
|
||||
before := alloc.space.NumFreeAddresses()
|
||||
alloc.ringUpdated()
|
||||
after := alloc.space.NumFreeAddresses()
|
||||
|
||||
alloc.gossip.GossipBroadcast(alloc.Gossip())
|
||||
|
||||
resultChan <- after - before
|
||||
}
|
||||
return <-resultChan
|
||||
}
|
||||
@@ -382,6 +529,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)
|
||||
@@ -394,9 +557,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
|
||||
@@ -425,7 +590,7 @@ func (alloc *Allocator) OnGossipBroadcast(sender mesh.PeerName, msg []byte) (mes
|
||||
|
||||
type gossipState struct {
|
||||
// We send a timstamp along with the information to be
|
||||
// gossipped in order to detect skewed clocks
|
||||
// gossipped for backwards-compatibility (previously to detect skewed clocks)
|
||||
Now int64
|
||||
Nicknames map[mesh.PeerName]string
|
||||
|
||||
@@ -440,6 +605,9 @@ func (alloc *Allocator) encode() []byte {
|
||||
}
|
||||
|
||||
// We're only interested in Paxos until we have a Ring.
|
||||
// Non-electing participants (e.g. observers) return
|
||||
// a nil gossip state in order to provoke a unicast ring
|
||||
// update from electing peers who have reached consensus.
|
||||
if alloc.ring.Empty() {
|
||||
data.Paxos = alloc.paxos.GossipState()
|
||||
} else {
|
||||
@@ -478,8 +646,8 @@ type ipamGossipData struct {
|
||||
alloc *Allocator
|
||||
}
|
||||
|
||||
func (d *ipamGossipData) Merge(other mesh.GossipData) {
|
||||
// no-op
|
||||
func (d *ipamGossipData) Merge(other mesh.GossipData) mesh.GossipData {
|
||||
return d // no-op
|
||||
}
|
||||
|
||||
func (d *ipamGossipData) Encode() [][]byte {
|
||||
@@ -499,21 +667,22 @@ func (alloc *Allocator) SetInterfaces(gossip mesh.Gossip) {
|
||||
|
||||
// ACTOR server
|
||||
|
||||
func (alloc *Allocator) actorLoop(actionChan <-chan func()) {
|
||||
func (alloc *Allocator) actorLoop(actionChan <-chan func(), stopChan <-chan struct{}) {
|
||||
defer alloc.ticker.Stop()
|
||||
for {
|
||||
var tickChan <-chan time.Time
|
||||
if alloc.paxosTicker != nil {
|
||||
tickChan = alloc.paxosTicker.C
|
||||
}
|
||||
|
||||
select {
|
||||
case action := <-actionChan:
|
||||
if action == nil {
|
||||
return
|
||||
}
|
||||
action()
|
||||
case <-tickChan:
|
||||
alloc.propose()
|
||||
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.assertInvariants()
|
||||
@@ -525,39 +694,35 @@ func (alloc *Allocator) actorLoop(actionChan <-chan func()) {
|
||||
|
||||
// Ensure we are making progress towards an established ring
|
||||
func (alloc *Allocator) establishRing() {
|
||||
if !alloc.ring.Empty() || alloc.paxosTicker != nil {
|
||||
if !alloc.ring.Empty() || alloc.awaitingConsensus {
|
||||
return
|
||||
}
|
||||
|
||||
alloc.awaitingConsensus = true
|
||||
alloc.paxos.SetQuorum(alloc.quorum())
|
||||
alloc.propose()
|
||||
if ok, cons := alloc.paxos.Consensus(); ok {
|
||||
// If the quorum was 1, then proposing immediately
|
||||
// leads to consensus
|
||||
alloc.createRing(cons.Value)
|
||||
} else {
|
||||
// re-try until we get consensus
|
||||
alloc.paxosTicker = time.NewTicker(paxosInterval)
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
// When we have a ring, we don't need paxos any more
|
||||
if alloc.paxos != nil {
|
||||
if alloc.awaitingConsensus {
|
||||
alloc.awaitingConsensus = false
|
||||
alloc.paxos = nil
|
||||
|
||||
if alloc.paxosTicker != nil {
|
||||
alloc.paxosTicker.Stop()
|
||||
alloc.paxosTicker = nil
|
||||
}
|
||||
}
|
||||
|
||||
alloc.persistRing()
|
||||
alloc.space.UpdateRanges(alloc.ring.OwnedRanges())
|
||||
alloc.tryPendingOps()
|
||||
}
|
||||
@@ -626,27 +791,27 @@ 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
|
||||
}
|
||||
|
||||
deltat := time.Unix(data.Now, 0).Sub(alloc.now())
|
||||
if deltat > time.Hour || -deltat > time.Hour {
|
||||
return fmt.Errorf("clock skew of %v detected, ignoring update", deltat)
|
||||
}
|
||||
|
||||
// Merge nicknames
|
||||
for peer, nickname := range data.Nicknames {
|
||||
alloc.nicknames[peer] = nickname
|
||||
}
|
||||
|
||||
// only one of Ring and Paxos should be present. And we
|
||||
// shouldn't get updates for a empty Ring. But tolerate
|
||||
// them just in case.
|
||||
if data.Ring != nil {
|
||||
switch err = alloc.ring.Merge(*data.Ring); err {
|
||||
switch {
|
||||
// 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:
|
||||
updated, err := alloc.ring.Merge(*data.Ring)
|
||||
switch err {
|
||||
case nil:
|
||||
if updated {
|
||||
alloc.pruneNicknames()
|
||||
alloc.ringUpdated()
|
||||
}
|
||||
case ring.ErrDifferentSeeds:
|
||||
return fmt.Errorf("IP allocation was seeded by different peers (received: %v, ours: %v)",
|
||||
alloc.annotatePeernames(data.Ring.Seeds), alloc.annotatePeernames(alloc.ring.Seeds))
|
||||
@@ -654,31 +819,44 @@ func (alloc *Allocator) update(sender mesh.PeerName, msg []byte) error {
|
||||
return fmt.Errorf("Incompatible IP allocation ranges (received: %s, ours: %s)",
|
||||
data.Ring.Range().AsCIDRString(), alloc.ring.Range().AsCIDRString())
|
||||
default:
|
||||
if err == nil && !alloc.ring.Empty() {
|
||||
alloc.pruneNicknames()
|
||||
alloc.ringUpdated()
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if data.Paxos != nil {
|
||||
if alloc.ring.Empty() {
|
||||
if alloc.paxos.Update(data.Paxos) {
|
||||
if alloc.paxos.Think() {
|
||||
// If something important changed, broadcast
|
||||
alloc.gossip.GossipBroadcast(alloc.Gossip())
|
||||
}
|
||||
|
||||
if ok, cons := alloc.paxos.Consensus(); ok {
|
||||
alloc.createRing(cons.Value)
|
||||
}
|
||||
}
|
||||
} else if sender != mesh.UnknownPeerName {
|
||||
// Sender is trying to initialize a ring, but we have one
|
||||
// already - send it straight back
|
||||
// If we reach this point we know the sender is either an elector
|
||||
// broadcasting a paxos proposal to form a ring or a non-elector
|
||||
// broadcasting a ring request. If we have a ring already we can just send
|
||||
// it back regardless.
|
||||
case !alloc.ring.Empty():
|
||||
if sender != mesh.UnknownPeerName {
|
||||
alloc.sendRingUpdate(sender)
|
||||
}
|
||||
|
||||
// Otherwise, we need to react according to whether or not we received a
|
||||
// paxos proposal.
|
||||
case data.Paxos != nil:
|
||||
// Process the proposal (this is a no-op if we're an observer)
|
||||
if alloc.paxos.Update(data.Paxos) {
|
||||
if alloc.paxos.Think() {
|
||||
// If something important changed, broadcast
|
||||
alloc.gossip.GossipBroadcast(alloc.Gossip())
|
||||
}
|
||||
|
||||
if ok, cons := alloc.paxos.Consensus(); ok {
|
||||
alloc.createRing(cons.Value)
|
||||
}
|
||||
}
|
||||
|
||||
// No paxos proposal present, so sender is a non-elector. We don't have a
|
||||
// ring to send, so attempt to establish one on their behalf. NB we only do
|
||||
// this:
|
||||
//
|
||||
// * On an explicit broadcast request triggered by a remote allocation attempt
|
||||
// (if we did so on periodic gossip we would force consensus unnecessarily)
|
||||
// * If we are an elector (to avoid a broadcast storm of ring request messages)
|
||||
default:
|
||||
if alloc.paxos.IsElector() && sender != mesh.UnknownPeerName {
|
||||
alloc.establishRing()
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -686,13 +864,12 @@ func (alloc *Allocator) update(sender mesh.PeerName, msg []byte) error {
|
||||
|
||||
func (alloc *Allocator) donateSpace(r address.Range, to mesh.PeerName) {
|
||||
// No matter what we do, we'll send a unicast gossip
|
||||
// of our ring back to tha chap who asked for space.
|
||||
// of our ring back to the chap who asked for space.
|
||||
// This serves to both tell him of any space we might
|
||||
// have given him, or tell him where he might find some
|
||||
// more.
|
||||
defer alloc.sendRingUpdate(to)
|
||||
|
||||
alloc.debugln("Peer", to, "asked me for space")
|
||||
chunk, ok := alloc.space.Donate(r)
|
||||
if !ok {
|
||||
free := alloc.space.NumFreeAddressesInRange(r)
|
||||
@@ -705,7 +882,7 @@ func (alloc *Allocator) donateSpace(r address.Range, to mesh.PeerName) {
|
||||
}
|
||||
alloc.debugln("Giving range", chunk, "to", to)
|
||||
alloc.ring.GrantRangeToHost(chunk.Start, chunk.End, to)
|
||||
alloc.sendRingUpdate(to)
|
||||
alloc.persistRing()
|
||||
}
|
||||
|
||||
func (alloc *Allocator) assertInvariants() {
|
||||
@@ -731,33 +908,145 @@ func (alloc *Allocator) reportFreeSpace() {
|
||||
return
|
||||
}
|
||||
|
||||
freespace := make(map[address.Address]address.Offset)
|
||||
freespace := make(map[address.Address]address.Count)
|
||||
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"
|
||||
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(db.NameIdent, alloc.ourName); err != nil {
|
||||
alloc.fatalf("Error persisting ring data: %s", err)
|
||||
return
|
||||
}
|
||||
if err := alloc.db.Save(ringIdent, alloc.ring); err != nil {
|
||||
alloc.fatalf("Error persisting ring data: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 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(db.NameIdent, &checkPeerName)
|
||||
if err != nil {
|
||||
alloc.fatalf("Error loading persisted peer name: %s", err)
|
||||
}
|
||||
var persistedRing *ring.Ring
|
||||
ringFound, err := alloc.db.Load(ringIdent, &persistedRing)
|
||||
if err != nil {
|
||||
alloc.fatalf("Error loading persisted IPAM data: %s", err)
|
||||
}
|
||||
var persistedOwned map[string]ownedData
|
||||
ownedFound, err := alloc.db.Load(ownedIdent, &persistedOwned)
|
||||
if err != nil {
|
||||
alloc.fatalf("Error loading persisted address data: %s", err)
|
||||
}
|
||||
|
||||
overwritePersisted := func(fmt string, args ...interface{}) {
|
||||
alloc.infof(fmt, args...)
|
||||
alloc.persistRing()
|
||||
alloc.persistOwned()
|
||||
}
|
||||
|
||||
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() {
|
||||
if err := alloc.db.Save(ownedIdent, alloc.owned); err != nil {
|
||||
alloc.fatalf("Error persisting address data: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Owned addresses
|
||||
|
||||
// NB: addr must not be owned by ident already
|
||||
func (alloc *Allocator) addOwned(ident string, addr address.Address) {
|
||||
alloc.owned[ident] = append(alloc.owned[ident], addr)
|
||||
func (alloc *Allocator) hasOwnedByContainer(ident string) bool {
|
||||
d, b := alloc.owned[ident]
|
||||
return b && d.IsContainer
|
||||
}
|
||||
|
||||
func (alloc *Allocator) lookupOwned(ident string, r address.Range) (address.Address, bool) {
|
||||
for _, addr := range alloc.owned[ident] {
|
||||
if r.Contains(addr) {
|
||||
return addr, true
|
||||
// NB: addr must not be owned by ident already
|
||||
func (alloc *Allocator) addOwned(ident string, cidr address.CIDR, isContainer bool) {
|
||||
d := alloc.owned[ident]
|
||||
d.IsContainer = isContainer
|
||||
d.Cidrs = append(d.Cidrs, cidr)
|
||||
alloc.owned[ident] = d
|
||||
alloc.persistOwned()
|
||||
}
|
||||
|
||||
func (alloc *Allocator) removeAllOwned(ident string) []address.CIDR {
|
||||
a := alloc.owned[ident]
|
||||
delete(alloc.owned, ident)
|
||||
alloc.persistOwned()
|
||||
return a.Cidrs
|
||||
}
|
||||
|
||||
func (alloc *Allocator) removeOwned(ident string, addrToFree address.Address) bool {
|
||||
d := alloc.owned[ident]
|
||||
for i, ownedCidr := range d.Cidrs {
|
||||
if ownedCidr.Addr == addrToFree {
|
||||
if len(d.Cidrs) == 1 {
|
||||
delete(alloc.owned, ident)
|
||||
} else {
|
||||
d.Cidrs = append(d.Cidrs[:i], d.Cidrs[i+1:]...)
|
||||
alloc.owned[ident] = d
|
||||
}
|
||||
alloc.persistOwned()
|
||||
return true
|
||||
}
|
||||
}
|
||||
return 0, false
|
||||
return false
|
||||
}
|
||||
|
||||
func (alloc *Allocator) ownedInRange(ident string, r address.Range) []address.CIDR {
|
||||
var c []address.CIDR
|
||||
for _, cidr := range alloc.owned[ident].Cidrs {
|
||||
if r.Contains(cidr.Addr) {
|
||||
c = append(c, cidr)
|
||||
}
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (alloc *Allocator) findOwner(addr address.Address) string {
|
||||
for ident, addrs := range alloc.owned {
|
||||
for _, candidate := range addrs {
|
||||
if candidate == addr {
|
||||
for ident, d := range alloc.owned {
|
||||
for _, candidate := range d.Cidrs {
|
||||
if candidate.Addr == addr {
|
||||
return ident
|
||||
}
|
||||
}
|
||||
@@ -765,14 +1054,47 @@ func (alloc *Allocator) findOwner(addr address.Address) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// For each ID in the 'owned' map, remove the entry if it isn't in the map
|
||||
func (alloc *Allocator) pruneOwned(ids map[string]struct{}) {
|
||||
changed := false
|
||||
for ident, d := range alloc.owned {
|
||||
if !d.IsContainer {
|
||||
continue
|
||||
}
|
||||
if _, found := ids[ident]; !found {
|
||||
for _, cidr := range d.Cidrs {
|
||||
alloc.space.Free(cidr.Addr)
|
||||
}
|
||||
alloc.debugf("Deleting old entry %s: %v", ident, d.Cidrs)
|
||||
delete(alloc.owned, ident)
|
||||
changed = true
|
||||
}
|
||||
}
|
||||
if changed {
|
||||
alloc.persistOwned()
|
||||
}
|
||||
}
|
||||
|
||||
// Logging
|
||||
|
||||
func (alloc *Allocator) fatalf(fmt string, args ...interface{}) {
|
||||
alloc.logf(common.Log.Fatalf, fmt, args...)
|
||||
}
|
||||
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{}) {
|
||||
common.Log.Infof("[allocator %s] "+fmt, append([]interface{}{alloc.ourName}, args...)...)
|
||||
alloc.logf(common.Log.Infof, fmt, args...)
|
||||
}
|
||||
func (alloc *Allocator) debugf(fmt string, args ...interface{}) {
|
||||
alloc.logf(common.Log.Debugf, fmt, args...)
|
||||
}
|
||||
func (alloc *Allocator) logf(f func(string, ...interface{}), fmt string, args ...interface{}) {
|
||||
f("[allocator %s] "+fmt, append([]interface{}{alloc.ourName}, args...)...)
|
||||
}
|
||||
func (alloc *Allocator) debugln(args ...interface{}) {
|
||||
common.Log.Debugln(append([]interface{}{fmt.Sprintf("[allocator %s]:", alloc.ourName)}, args...)...)
|
||||
}
|
||||
func (alloc *Allocator) debugf(fmt string, args ...interface{}) {
|
||||
common.Log.Debugf("[allocator %s] "+fmt, append([]interface{}{alloc.ourName}, args...)...)
|
||||
}
|
||||
+160
-90
@@ -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"
|
||||
@@ -37,40 +45,54 @@ func TestAllocFree(t *testing.T) {
|
||||
|
||||
alloc, subnet := makeAllocatorWithMockGossip(t, "01:00:00:01:00:00", universe, 1)
|
||||
defer alloc.Stop()
|
||||
_, cidr1, _ := address.ParseCIDR(subnet1)
|
||||
_, cidr2, _ := address.ParseCIDR(subnet2)
|
||||
cidr1, _ := address.ParseCIDR(subnet1)
|
||||
cidr2, _ := address.ParseCIDR(subnet2)
|
||||
|
||||
alloc.claimRingForTesting()
|
||||
addr1, err := alloc.Allocate(container1, cidr1.HostRange(), returnFalse)
|
||||
addr1, err := alloc.SimplyAllocate(container1, cidr1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, testAddr1, addr1.String(), "address")
|
||||
|
||||
addr2, err := alloc.Allocate(container1, cidr2.HostRange(), returnFalse)
|
||||
addr2, err := alloc.SimplyAllocate(container1, cidr2)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, testAddr2, addr2.String(), "address")
|
||||
|
||||
addrs, err := alloc.Lookup(container1, subnet.Range())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, []address.CIDR{address.MakeCIDR(cidr1, addr1), address.MakeCIDR(cidr2, addr2)}, addrs)
|
||||
|
||||
// Ask for another address for a different container and check it's different
|
||||
addr1b, _ := alloc.Allocate(container2, cidr1.HostRange(), 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.HostRange(), returnFalse)
|
||||
addr1a, _ := alloc.SimplyAllocate(container1, cidr1)
|
||||
require.Equal(t, testAddr1, addr1a.String(), "address")
|
||||
addr2a, _ := alloc.Allocate(container1, cidr2.HostRange(), 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.HostRange(), returnFalse)
|
||||
addr3, _ := alloc.SimplyAllocate(container3, cidr1)
|
||||
require.Equal(t, testAddr1, addr3.String(), "address")
|
||||
addr4, _ := alloc.Allocate(container3, cidr2.HostRange(), returnFalse)
|
||||
addr4, _ := alloc.SimplyAllocate(container3, cidr2)
|
||||
require.Equal(t, testAddr2, addr4.String(), "address")
|
||||
|
||||
alloc.ContainerDied(container2)
|
||||
|
||||
// Resurrect
|
||||
addr1c, err := alloc.SimplyAllocate(container2, cidr1)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, addr1b, addr1c, "address")
|
||||
|
||||
alloc.ContainerDied(container3)
|
||||
require.Equal(t, address.Offset(spaceSize), alloc.NumFreeAddresses(subnet))
|
||||
alloc.Encode() // sync up
|
||||
// Move the clock forward and clear out the dead container
|
||||
alloc.actionChan <- func() { alloc.now = func() time.Time { return time.Now().Add(containerDiedTimeout * 2) } }
|
||||
alloc.actionChan <- func() { alloc.removeDeadContainers() }
|
||||
require.Equal(t, address.Count(spaceSize+1), alloc.NumFreeAddresses(subnet.Range()))
|
||||
}
|
||||
|
||||
func TestBootstrap(t *testing.T) {
|
||||
@@ -95,7 +117,7 @@ func TestBootstrap(t *testing.T) {
|
||||
ExpectBroadcastMessage(alloc1, nil) // alloc1 will try to form consensus
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
alloc1.Allocate("somecontainer", subnet, returnFalse)
|
||||
alloc1.Allocate("somecontainer", subnet, true, returnFalse)
|
||||
done <- true
|
||||
}()
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
@@ -103,6 +125,7 @@ func TestBootstrap(t *testing.T) {
|
||||
|
||||
CheckAllExpectedMessagesSent(alloc1, alloc2)
|
||||
|
||||
// Check that tryPendingOps doesn't cause anything to happen here
|
||||
alloc1.actionChan <- func() { alloc1.tryPendingOps() }
|
||||
AssertNothingSent(t, done)
|
||||
|
||||
@@ -115,14 +138,24 @@ func TestBootstrap(t *testing.T) {
|
||||
ExpectBroadcastMessage(alloc1, nil)
|
||||
alloc1.OnGossipBroadcast(alloc2.ourName, alloc2.Encode())
|
||||
|
||||
// both nodes will get consensus now so initialize the ring
|
||||
ExpectBroadcastMessage(alloc2, nil)
|
||||
CheckAllExpectedMessagesSent(alloc1, alloc2)
|
||||
|
||||
// at this point, both nodes have seen each other, but don't have a valid proposal
|
||||
// so there is another exchange of messages
|
||||
ExpectBroadcastMessage(alloc2, nil)
|
||||
alloc2.OnGossipBroadcast(alloc1.ourName, alloc1.Encode())
|
||||
|
||||
// we have consensus now so alloc1 will initialize the ring
|
||||
ExpectBroadcastMessage(alloc1, nil)
|
||||
alloc1.OnGossipBroadcast(alloc2.ourName, alloc2.Encode())
|
||||
|
||||
CheckAllExpectedMessagesSent(alloc1, alloc2)
|
||||
|
||||
// alloc2 receives the ring and does not reply
|
||||
alloc2.OnGossipBroadcast(alloc1.ourName, alloc1.Encode())
|
||||
|
||||
CheckAllExpectedMessagesSent(alloc1, alloc2)
|
||||
|
||||
alloc1.OnGossipBroadcast(alloc2.ourName, alloc2.Encode())
|
||||
// now alloc1 should have space
|
||||
|
||||
AssertSent(t, done)
|
||||
@@ -135,42 +168,42 @@ func TestAllocatorClaim(t *testing.T) {
|
||||
container1 = "abcdef"
|
||||
container3 = "b01df00d"
|
||||
universe = "10.0.3.0/24"
|
||||
testAddr1 = "10.0.3.2"
|
||||
testAddr2 = "10.0.4.2"
|
||||
testAddr1 = "10.0.3.2/24"
|
||||
testAddr2 = "10.0.4.2/24"
|
||||
)
|
||||
|
||||
allocs, _, subnet := makeNetworkOfAllocators(2, universe)
|
||||
allocs, router, subnet := makeNetworkOfAllocators(2, universe)
|
||||
defer stopNetworkOfAllocators(allocs, router)
|
||||
alloc := allocs[1]
|
||||
defer alloc.Stop()
|
||||
addr1, _ := address.ParseIP(testAddr1)
|
||||
addr1, _ := address.ParseCIDR(testAddr1)
|
||||
|
||||
// First claim should trigger "dunno, I'm going to wait"
|
||||
err := alloc.Claim(container3, addr1, true)
|
||||
err := alloc.SimplyClaim(container3, addr1)
|
||||
require.NoError(t, err)
|
||||
|
||||
// Do one allocate to ensure paxos is all done
|
||||
alloc.Allocate("unused", subnet, returnFalse)
|
||||
alloc.Prime()
|
||||
// Do an allocate on the other peer, which we will try to claim later
|
||||
addrx, err := allocs[0].Allocate(container1, subnet, returnFalse)
|
||||
addrx, err := allocs[0].Allocate(container1, subnet, true, returnFalse)
|
||||
router.Flush()
|
||||
|
||||
// Now try the claim again
|
||||
err = alloc.Claim(container3, addr1, 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, returnFalse)
|
||||
require.Equal(t, testAddr1, addr3.String(), "address")
|
||||
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)
|
||||
err = alloc.SimplyClaim(container3, addr1)
|
||||
require.NoError(t, err)
|
||||
// claim for a different container should fail
|
||||
err = alloc.Claim(container1, addr1, true)
|
||||
err = alloc.SimplyClaim(container1, addr1)
|
||||
require.Error(t, err)
|
||||
// claiming the address allocated on the other peer should fail
|
||||
err = alloc.Claim(container1, addrx, true)
|
||||
err = alloc.SimplyClaim(container1, address.MakeCIDR(subnet, addrx))
|
||||
require.Error(t, err, "claiming address allocated on other peer should fail")
|
||||
// Check an address outside of our universe
|
||||
addr2, _ := address.ParseIP(testAddr2)
|
||||
err = alloc.Claim(container1, addr2, true)
|
||||
addr2, _ := address.ParseCIDR(testAddr2)
|
||||
err = alloc.SimplyClaim(container1, addr2)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -187,16 +220,13 @@ 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)
|
||||
alloc1, subnet := makeAllocator("01:00:00:02:00:00", cidr, 2)
|
||||
alloc1.SetInterfaces(router.Connect(alloc1.ourName, alloc1))
|
||||
|
||||
alloc2, _ := makeAllocator("02:00:00:02:00:00", CIDR, 2)
|
||||
alloc2, _ := makeAllocator("02:00:00:02:00:00", cidr, 2)
|
||||
alloc2.SetInterfaces(router.Connect(alloc2.ourName, alloc2))
|
||||
alloc1.claimRingForTesting(alloc1, alloc2)
|
||||
alloc2.claimRingForTesting(alloc1, alloc2)
|
||||
@@ -208,9 +238,9 @@ func TestCancel(t *testing.T) {
|
||||
alloc1.OnGossipBroadcast(alloc2.ourName, alloc2.Encode())
|
||||
|
||||
// Get some IPs, so each allocator has some space
|
||||
res1, _ := alloc1.Allocate("foo", subnet, returnFalse)
|
||||
res1, _ := alloc1.Allocate("foo", subnet, true, returnFalse)
|
||||
common.Log.Debugf("res1 = %s", res1.String())
|
||||
res2, _ := alloc2.Allocate("bar", subnet, returnFalse)
|
||||
res2, _ := alloc2.Allocate("bar", subnet, true, returnFalse)
|
||||
common.Log.Debugf("res2 = %s", res2.String())
|
||||
if res1 == res2 {
|
||||
require.FailNow(t, "Error: got same ips!")
|
||||
@@ -221,13 +251,13 @@ func TestCancel(t *testing.T) {
|
||||
unpause := alloc2.pause()
|
||||
|
||||
// Use up all the IPs that alloc1 owns, so the allocation after this will prompt a request to alloc2
|
||||
for i := 0; alloc1.NumFreeAddresses(subnet) > 0; i++ {
|
||||
alloc1.Allocate(fmt.Sprintf("tmp%d", i), subnet, returnFalse)
|
||||
for i := 0; alloc1.NumFreeAddresses(subnet.HostRange()) > 0; i++ {
|
||||
alloc1.Allocate(fmt.Sprintf("tmp%d", i), subnet, true, returnFalse)
|
||||
}
|
||||
cancelChan := make(chan bool, 1)
|
||||
doneChan := make(chan bool)
|
||||
go func() {
|
||||
_, ok := alloc1.Allocate("baz", subnet,
|
||||
_, ok := alloc1.Allocate("baz", subnet, true,
|
||||
func() bool {
|
||||
select {
|
||||
case <-cancelChan:
|
||||
@@ -251,18 +281,18 @@ 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"
|
||||
)
|
||||
|
||||
router := gossip.NewTestRouter(0.0)
|
||||
alloc1, subnet := makeAllocator("01:00:00:02:00:00", CIDR, 2)
|
||||
alloc1, subnet := makeAllocator("01:00:00:02:00:00", cidr, 2)
|
||||
alloc1.SetInterfaces(router.Connect(alloc1.ourName, alloc1))
|
||||
alloc1.Start()
|
||||
|
||||
doneChan := make(chan bool)
|
||||
f := func() {
|
||||
_, ok := alloc1.Allocate(container1, subnet, returnFalse)
|
||||
_, ok := alloc1.Allocate(container1, subnet, true, returnFalse)
|
||||
doneChan <- ok == nil
|
||||
}
|
||||
|
||||
@@ -293,62 +323,80 @@ func TestGossipShutdown(t *testing.T) {
|
||||
defer alloc.Stop()
|
||||
|
||||
alloc.claimRingForTesting()
|
||||
alloc.Allocate(container1, subnet, returnFalse)
|
||||
alloc.SimplyAllocate(container1, subnet)
|
||||
|
||||
alloc.Shutdown()
|
||||
|
||||
_, err := alloc.Allocate(container2, subnet, 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)
|
||||
alloc1 := allocs[0]
|
||||
alloc2 := allocs[1]
|
||||
alloc3 := allocs[2] // This will be 'master' and get the first range
|
||||
defer stopNetworkOfAllocators(allocs, router)
|
||||
alloc0 := allocs[0]
|
||||
alloc1 := allocs[1]
|
||||
alloc2 := allocs[2]
|
||||
|
||||
_, err := alloc2.Allocate("foo", subnet, returnFalse)
|
||||
_, err := alloc1.Allocate("foo", subnet, true, returnFalse)
|
||||
require.True(t, err == nil, "Failed to get address")
|
||||
|
||||
_, err = alloc3.Allocate("bar", subnet, returnFalse)
|
||||
_, err = alloc2.Allocate("bar", subnet, true, returnFalse)
|
||||
require.True(t, err == nil, "Failed to get address")
|
||||
|
||||
// simulation of periodic gossip
|
||||
alloc2.gossip.GossipBroadcast(alloc2.Gossip())
|
||||
router.Flush()
|
||||
alloc2.gossip.GossipBroadcast(alloc3.Gossip())
|
||||
alloc1.gossip.GossipBroadcast(alloc1.Gossip())
|
||||
router.Flush()
|
||||
|
||||
free1 := alloc1.NumFreeAddresses(subnet.Range())
|
||||
free2 := alloc2.NumFreeAddresses(subnet.Range())
|
||||
|
||||
router.RemovePeer(alloc1.ourName)
|
||||
router.RemovePeer(alloc2.ourName)
|
||||
router.RemovePeer(alloc3.ourName)
|
||||
alloc2.Stop()
|
||||
alloc3.Stop()
|
||||
router.Flush()
|
||||
require.NoError(t, alloc1.AdminTakeoverRanges(alloc2.ourName.String()))
|
||||
require.NoError(t, alloc1.AdminTakeoverRanges(alloc3.ourName.String()))
|
||||
router.Flush()
|
||||
|
||||
require.Equal(t, address.Offset(1022), alloc1.NumFreeAddresses(subnet))
|
||||
|
||||
_, err = alloc1.Allocate("foo", subnet, returnFalse)
|
||||
require.True(t, err == nil, "Failed to get address")
|
||||
alloc1.Stop()
|
||||
alloc2.Stop()
|
||||
router.Flush()
|
||||
|
||||
require.Equal(t, free1+1, alloc0.AdminTakeoverRanges(alloc1.ourName.String()))
|
||||
require.Equal(t, free2+1, alloc0.AdminTakeoverRanges(alloc2.ourName.String()))
|
||||
router.Flush()
|
||||
|
||||
require.Equal(t, address.Count(1024), alloc0.NumFreeAddresses(subnet.Range()))
|
||||
|
||||
_, err = alloc0.Allocate("foo", subnet, true, returnFalse)
|
||||
require.True(t, err == nil, "Failed to get address")
|
||||
alloc0.Stop()
|
||||
}
|
||||
|
||||
func TestFakeRouterSimple(t *testing.T) {
|
||||
const (
|
||||
cidr = "10.0.1.7/22"
|
||||
)
|
||||
allocs, _, subnet := makeNetworkOfAllocators(2, cidr)
|
||||
defer stopNetworkOfAllocators(allocs)
|
||||
const cidr = "10.0.4.0/22"
|
||||
allocs, router, subnet := makeNetworkOfAllocators(2, cidr)
|
||||
defer stopNetworkOfAllocators(allocs, router)
|
||||
|
||||
alloc1 := allocs[0]
|
||||
//alloc2 := allocs[1]
|
||||
|
||||
_, err := alloc1.Allocate("foo", subnet, returnFalse)
|
||||
_, err := alloc1.Allocate("foo", subnet, true, returnFalse)
|
||||
require.NoError(t, err, "Failed to get address")
|
||||
}
|
||||
|
||||
@@ -359,10 +407,10 @@ func TestAllocatorFuzz(t *testing.T) {
|
||||
nodes = 5
|
||||
maxAddresses = 1000
|
||||
concurrency = 5
|
||||
cidr = "10.0.1.7/22"
|
||||
cidr = "10.0.4.0/22"
|
||||
)
|
||||
allocs, _, subnet := makeNetworkOfAllocators(nodes, cidr)
|
||||
defer stopNetworkOfAllocators(allocs)
|
||||
allocs, router, subnet := makeNetworkOfAllocators(nodes, cidr)
|
||||
defer stopNetworkOfAllocators(allocs, router)
|
||||
|
||||
// Test state
|
||||
// For each IP issued we store the allocator
|
||||
@@ -429,7 +477,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, returnFalse)
|
||||
addr, err := alloc.SimplyAllocate(name, subnet)
|
||||
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Could not allocate addr"))
|
||||
@@ -486,7 +534,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, 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))
|
||||
@@ -506,8 +554,8 @@ func TestAllocatorFuzz(t *testing.T) {
|
||||
allocIndex := rand.Int31n(nodes)
|
||||
addressIndex := rand.Int31n(int32(subnet.Size()))
|
||||
alloc := allocs[allocIndex]
|
||||
addr := address.Add(subnet.Start, address.Offset(addressIndex))
|
||||
err := alloc.Claim(name, addr, true)
|
||||
addr := address.Add(subnet.Addr, address.Offset(addressIndex))
|
||||
err := alloc.SimplyClaim(name, address.MakeCIDR(subnet, addr))
|
||||
if err == nil {
|
||||
noteAllocation(allocIndex, name, addr)
|
||||
}
|
||||
@@ -563,14 +611,36 @@ func TestAllocatorFuzz(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestGossipSkew(t *testing.T) {
|
||||
alloc1, _ := makeAllocatorWithMockGossip(t, "01:00:00:01:00:00", "10.0.1.0/22", 2)
|
||||
defer alloc1.Stop()
|
||||
alloc2, _ := makeAllocatorWithMockGossip(t, "02:00:00:02:00:00", "10.0.1.0/22", 2)
|
||||
alloc2.now = func() time.Time { return time.Now().Add(time.Hour * 2) }
|
||||
defer alloc2.Stop()
|
||||
func TestSpaceRequest(t *testing.T) {
|
||||
const (
|
||||
container1 = "cont-1"
|
||||
container2 = "cont-2"
|
||||
universe = "10.32.0.0/12"
|
||||
)
|
||||
allocs, router, subnet := makeNetworkOfAllocators(1, universe)
|
||||
defer stopNetworkOfAllocators(allocs, router)
|
||||
alloc1 := allocs[0]
|
||||
|
||||
if _, err := alloc1.OnGossipBroadcast(alloc2.ourName, alloc2.Encode()); err == nil {
|
||||
t.Fail()
|
||||
}
|
||||
addr, err := alloc1.Allocate(container1, subnet, true, returnFalse)
|
||||
require.Nil(t, err, "")
|
||||
// free it again so the donation splits the range neatly
|
||||
err = alloc1.Free(container1, addr)
|
||||
require.Nil(t, err, "")
|
||||
require.Equal(t, cidrRanges(universe), alloc1.OwnedRanges(), "")
|
||||
|
||||
// Start a new peer
|
||||
alloc2, _ := makeAllocator("02:00:00:02:00:00", universe, 2)
|
||||
alloc2.SetInterfaces(router.Connect(alloc2.ourName, alloc2))
|
||||
alloc2.Start()
|
||||
defer alloc2.Stop()
|
||||
alloc2.Allocate(container2, subnet, true, returnFalse)
|
||||
|
||||
// Test whether the universe has been split into two equal halves (GH #2009)
|
||||
require.Equal(t, cidrRanges("10.32.0.0/13"), alloc1.OwnedRanges(), "")
|
||||
require.Equal(t, cidrRanges("10.40.0.0/13"), alloc2.OwnedRanges(), "")
|
||||
}
|
||||
|
||||
func cidrRanges(s string) []address.Range {
|
||||
c, _ := address.ParseCIDR(s)
|
||||
return []address.Range{c.Range()}
|
||||
}
|
||||
+46
-19
@@ -3,16 +3,20 @@ package ipam
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/weaveworks/mesh"
|
||||
|
||||
"github.com/weaveworks/weave/api"
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
type claim struct {
|
||||
resultChan chan<- error
|
||||
ident string
|
||||
addr address.Address
|
||||
cidr address.CIDR
|
||||
isContainer bool
|
||||
noErrorOnUnknown bool
|
||||
hasBeenCancelled func() bool
|
||||
}
|
||||
|
||||
// Send an error (or nil for success) back to caller listening on resultChan
|
||||
@@ -31,33 +35,41 @@ 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 !alloc.ring.Contains(c.addr) {
|
||||
if 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("Ignored address %s claimed by %s - not in our universe", c.addr, c.ident)
|
||||
alloc.infof("Address %s claimed by %s - not in our range", c.cidr, c.ident)
|
||||
alloc.addOwned(c.ident, c.cidr, c.isContainer)
|
||||
c.sendResult(nil)
|
||||
return true
|
||||
}
|
||||
|
||||
alloc.establishRing()
|
||||
|
||||
switch owner := alloc.ring.Owner(c.addr); owner {
|
||||
// If we had heard that this container died, resurrect it
|
||||
delete(alloc.dead, c.ident) // (delete is no-op if key not in map)
|
||||
|
||||
switch owner := alloc.ring.Owner(c.cidr.Addr); owner {
|
||||
case alloc.ourName:
|
||||
// success
|
||||
case mesh.UnknownPeerName:
|
||||
// If our ring doesn't know, it must be empty.
|
||||
alloc.infof("Claim %s for %s: is in the range %s, but the allocator is not initialized yet; will try later.",
|
||||
c.cidr, c.ident, alloc.universe)
|
||||
if c.noErrorOnUnknown {
|
||||
alloc.infof("Claim %s for %s: address allocator still initializing; will try later.", c.addr, c.ident)
|
||||
c.sendResult(nil)
|
||||
} else {
|
||||
c.sendResult(fmt.Errorf("%s is in the range %s, but the allocator is not initialized yet", c.addr, alloc.universe.AsCIDRString()))
|
||||
}
|
||||
return false
|
||||
default:
|
||||
alloc.debugf("requesting address %s from other peer %s", c.addr, owner)
|
||||
err := alloc.sendSpaceRequest(owner, address.NewRange(c.addr, 1))
|
||||
alloc.debugf("requesting address %s from other peer %s", c.cidr, owner)
|
||||
err := alloc.sendSpaceRequest(owner, address.NewRange(c.cidr.Addr, 1))
|
||||
if err != nil { // can't speak to owner right now
|
||||
if c.noErrorOnUnknown {
|
||||
alloc.infof("Claim %s for %s: %s; will try later.", c.addr, c.ident, err)
|
||||
alloc.infof("Claim %s for %s: %s; will try later.", c.cidr, c.ident, err)
|
||||
c.sendResult(nil)
|
||||
} else { // just tell the user they can't do this.
|
||||
c.deniedBy(alloc, owner)
|
||||
@@ -67,21 +79,36 @@ 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.addr); existingIdent {
|
||||
case "":
|
||||
if err := alloc.space.Claim(c.addr); err == nil {
|
||||
alloc.debugln("Claimed", c.addr, "for", c.ident)
|
||||
alloc.addOwned(c.ident, c.addr)
|
||||
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 == api.NoContainerID {
|
||||
alloc.addOwned(c.cidr.Addr.String(), c.cidr, c.isContainer)
|
||||
} else {
|
||||
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 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.addr.String(), existingIdent))
|
||||
c.sendResult(fmt.Errorf("address %s is already owned by %s", c.cidr.String(), existingIdent))
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -91,7 +118,7 @@ func (c *claim) deniedBy(alloc *Allocator, owner mesh.PeerName) {
|
||||
if found {
|
||||
name = " (" + name + ")"
|
||||
}
|
||||
c.sendResult(fmt.Errorf("address %s is owned by other peer %s%s", c.addr.String(), owner, name))
|
||||
c.sendResult(fmt.Errorf("address %s is owned by other peer %s%s", c.cidr.String(), owner, name))
|
||||
}
|
||||
|
||||
func (c *claim) Cancel() {
|
||||
|
||||
+81
-48
@@ -16,87 +16,120 @@ func badRequest(w http.ResponseWriter, err error) {
|
||||
common.Log.Warningln("[allocator]:", err.Error())
|
||||
}
|
||||
|
||||
func parseCIDR(w http.ResponseWriter, cidrStr string) (address.CIDR, bool) {
|
||||
subnetAddr, cidr, err := address.ParseCIDR(cidrStr)
|
||||
func parseCIDR(w http.ResponseWriter, cidrStr string, net bool) (address.CIDR, bool) {
|
||||
var cidr address.CIDR
|
||||
var err error
|
||||
if net {
|
||||
cidr, err = ParseCIDRSubnet(cidrStr)
|
||||
} else {
|
||||
cidr, err = address.ParseCIDR(cidrStr)
|
||||
}
|
||||
if err != nil {
|
||||
badRequest(w, err)
|
||||
return address.CIDR{}, false
|
||||
}
|
||||
if cidr.Start != subnetAddr {
|
||||
badRequest(w, fmt.Errorf("Invalid subnet %s - bits after network prefix are not all zero", cidrStr))
|
||||
return address.CIDR{}, false
|
||||
}
|
||||
return cidr, true
|
||||
}
|
||||
|
||||
func writeAddresses(w http.ResponseWriter, cidrs []address.CIDR) {
|
||||
for i, cidr := range cidrs {
|
||||
fmt.Fprint(w, cidr)
|
||||
if i < len(cidrs)-1 {
|
||||
w.Write([]byte{' '})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func hasBeenCancelled(dockerCli *docker.Client, closedChan <-chan bool, ident string, checkAlive bool) func() bool {
|
||||
return func() bool {
|
||||
select {
|
||||
case <-closedChan:
|
||||
return true
|
||||
default:
|
||||
res := checkAlive && dockerCli != nil && dockerCli.IsContainerNotRunning(ident)
|
||||
checkAlive = false // we check only once; if the container dies later we learn about that through events
|
||||
return res
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func cancellationErr(w http.ResponseWriter, err error) bool {
|
||||
if _, ok := err.(*errorCancelled); ok {
|
||||
common.Log.Infoln("[allocator]:", err.Error())
|
||||
fmt.Fprint(w, "cancelled")
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (alloc *Allocator) handleHTTPAllocate(dockerCli *docker.Client, w http.ResponseWriter, ident string, checkAlive bool, subnet address.CIDR) {
|
||||
closedChan := w.(http.CloseNotifier).CloseNotify()
|
||||
addr, err := alloc.Allocate(ident, subnet.HostRange(),
|
||||
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
|
||||
}
|
||||
})
|
||||
addr, err := alloc.Allocate(ident, subnet, checkAlive,
|
||||
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)
|
||||
}
|
||||
|
||||
// HandleHTTP wires up ipams HTTP endpoints to the provided mux.
|
||||
func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CIDR, dockerCli *docker.Client) {
|
||||
router.Methods("PUT").Path("/ip/{id}/{ip}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
ident := vars["id"]
|
||||
ipStr := vars["ip"]
|
||||
noErrorOnUnknown := r.FormValue("noErrorOnUnknown") == "true"
|
||||
if ip, err := address.ParseIP(ipStr); err != nil {
|
||||
badRequest(w, err)
|
||||
return
|
||||
} else if err := alloc.Claim(ident, ip, noErrorOnUnknown); err != nil {
|
||||
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
|
||||
}
|
||||
return
|
||||
}
|
||||
w.WriteHeader(204)
|
||||
}
|
||||
|
||||
w.WriteHeader(204)
|
||||
// HandleHTTP wires up ipams HTTP endpoints to the provided mux.
|
||||
func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CIDR, tracker string, dockerCli *docker.Client) {
|
||||
router.Methods("GET").Path("/ipinfo/defaultsubnet").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "%s", defaultSubnet)
|
||||
})
|
||||
|
||||
router.Methods("PUT").Path("/ip/{id}/{ip}/{prefixlen}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
if cidr, ok := parseCIDR(w, vars["ip"]+"/"+vars["prefixlen"], false); ok {
|
||||
ident := vars["id"]
|
||||
checkAlive := r.FormValue("check-alive") == "true"
|
||||
noErrorOnUnknown := r.FormValue("noErrorOnUnknown") == "true"
|
||||
alloc.handleHTTPClaim(dockerCli, w, ident, cidr, checkAlive, noErrorOnUnknown)
|
||||
}
|
||||
})
|
||||
|
||||
router.Methods("GET").Path("/ring").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
alloc.Prime()
|
||||
})
|
||||
|
||||
router.Methods("GET").Path("/ip/{id}/{ip}/{prefixlen}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
if subnet, ok := parseCIDR(w, vars["ip"]+"/"+vars["prefixlen"]); ok {
|
||||
addr, err := alloc.Lookup(vars["id"], subnet.HostRange())
|
||||
if subnet, ok := parseCIDR(w, vars["ip"]+"/"+vars["prefixlen"], true); ok {
|
||||
cidrs, err := alloc.Lookup(vars["id"], subnet.HostRange())
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, "%s/%d", addr, subnet.PrefixLen)
|
||||
writeAddresses(w, cidrs)
|
||||
}
|
||||
})
|
||||
|
||||
router.Methods("GET").Path("/ip/{id}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
addr, err := alloc.Lookup(mux.Vars(r)["id"], defaultSubnet.HostRange())
|
||||
addrs, err := alloc.Lookup(mux.Vars(r)["id"], defaultSubnet.HostRange())
|
||||
if err != nil {
|
||||
http.NotFound(w, r)
|
||||
return
|
||||
}
|
||||
fmt.Fprintf(w, "%s/%d", addr, defaultSubnet.PrefixLen)
|
||||
writeAddresses(w, addrs)
|
||||
})
|
||||
|
||||
router.Methods("POST").Path("/ip/{id}/{ip}/{prefixlen}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
if subnet, ok := parseCIDR(w, vars["ip"]+"/"+vars["prefixlen"]); ok {
|
||||
if subnet, ok := parseCIDR(w, vars["ip"]+"/"+vars["prefixlen"], true); ok {
|
||||
alloc.handleHTTPAllocate(dockerCli, w, vars["id"], r.FormValue("check-alive") == "true", subnet)
|
||||
}
|
||||
})
|
||||
@@ -138,11 +171,11 @@ func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CID
|
||||
|
||||
router.Methods("DELETE").Path("/peer/{id}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ident := mux.Vars(r)["id"]
|
||||
if err := alloc.AdminTakeoverRanges(ident); err != nil {
|
||||
badRequest(w, err)
|
||||
return
|
||||
}
|
||||
transferred := alloc.AdminTakeoverRanges(ident)
|
||||
fmt.Fprintf(w, "%d IPs taken over from %s\n", transferred, ident)
|
||||
})
|
||||
|
||||
w.WriteHeader(204)
|
||||
router.Methods("GET").Path("/ipinfo/tracker").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, tracker)
|
||||
})
|
||||
}
|
||||
+14
-25
@@ -14,7 +14,6 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
func HTTPPost(t *testing.T, url string) string {
|
||||
@@ -44,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 {
|
||||
@@ -81,7 +80,7 @@ func TestHttp(t *testing.T) {
|
||||
)
|
||||
|
||||
alloc, _ := makeAllocatorWithMockGossip(t, "08:00:27:01:c3:9a", universe, 1)
|
||||
_, cidr, _ := address.ParseCIDR(universe)
|
||||
cidr, _ := address.ParseCIDR(universe)
|
||||
port := listenHTTP(alloc, cidr)
|
||||
alloc.claimRingForTesting()
|
||||
|
||||
@@ -123,7 +122,7 @@ func TestBadHttp(t *testing.T) {
|
||||
|
||||
alloc, _ := makeAllocatorWithMockGossip(t, "08:00:27:01:c3:9a", testCIDR1, 1)
|
||||
defer alloc.Stop()
|
||||
_, cidr, _ := address.ParseCIDR(testCIDR1)
|
||||
cidr, _ := address.ParseCIDR(testCIDR1)
|
||||
port := listenHTTP(alloc, cidr)
|
||||
|
||||
alloc.claimRingForTesting()
|
||||
@@ -145,40 +144,30 @@ func TestBadHttp(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestHTTPCancel(t *testing.T) {
|
||||
wt.RunWithTimeout(t, 2*time.Second, func() {
|
||||
impTestHTTPCancel(t)
|
||||
})
|
||||
}
|
||||
|
||||
func impTestHTTPCancel(t *testing.T) {
|
||||
var (
|
||||
containerID = "deadbeef"
|
||||
testCIDR1 = "10.0.3.0/29"
|
||||
)
|
||||
|
||||
alloc, _ := makeAllocatorWithMockGossip(t, "08:00:27:01:c3:9a", testCIDR1, 1)
|
||||
// Say quorum=2, so the allocate won't go ahead
|
||||
alloc, _ := makeAllocatorWithMockGossip(t, "08:00:27:01:c3:9a", testCIDR1, 2)
|
||||
defer alloc.Stop()
|
||||
alloc.claimRingForTesting()
|
||||
_, cidr, _ := address.ParseCIDR(testCIDR1)
|
||||
ExpectBroadcastMessage(alloc, nil) // trying to form consensus
|
||||
cidr, _ := address.ParseCIDR(testCIDR1)
|
||||
port := listenHTTP(alloc, cidr)
|
||||
|
||||
// Stop the alloc so nothing actually works
|
||||
unpause := alloc.pause()
|
||||
|
||||
// Ask the http server for a new address
|
||||
done := make(chan *http.Response)
|
||||
req, _ := http.NewRequest("POST", allocURL(port, testCIDR1, containerID), nil)
|
||||
// On another goroutine, wait for a bit then cancel the request
|
||||
go func() {
|
||||
res, _ := http.DefaultClient.Do(req)
|
||||
done <- res
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
common.Log.Debug("Cancelling allocate")
|
||||
http.DefaultTransport.(*http.Transport).CancelRequest(req)
|
||||
}()
|
||||
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
fmt.Println("Cancelling allocate")
|
||||
http.DefaultTransport.(*http.Transport).CancelRequest(req)
|
||||
unpause()
|
||||
res := <-done
|
||||
res, _ := http.DefaultClient.Do(req)
|
||||
if res != nil {
|
||||
require.FailNow(t, "Error: Allocate returned non-nil")
|
||||
body, _ := ioutil.ReadAll(res.Body)
|
||||
require.FailNow(t, "Error: Allocate returned non-nil", string(body))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
package paxos
|
||||
|
||||
type Participant interface {
|
||||
GossipState() GossipState
|
||||
Update(from GossipState) bool
|
||||
SetQuorum(uint)
|
||||
Propose()
|
||||
Think() bool
|
||||
Consensus() (bool, AcceptedValue)
|
||||
IsElector() bool
|
||||
}
|
||||
|
||||
type Observer struct {
|
||||
}
|
||||
|
||||
func NewObserver() Participant {
|
||||
return &Observer{}
|
||||
}
|
||||
|
||||
func (observer *Observer) GossipState() GossipState {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (observer *Observer) Update(from GossipState) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (observer *Observer) Propose() {
|
||||
}
|
||||
|
||||
func (observer *Observer) SetQuorum(uint) {
|
||||
}
|
||||
|
||||
func (observer *Observer) Think() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (observer *Observer) Consensus() (bool, AcceptedValue) {
|
||||
return false, AcceptedValue{}
|
||||
}
|
||||
|
||||
func (observer *Observer) IsElector() bool {
|
||||
return false
|
||||
}
|
||||
+17
-5
@@ -1,7 +1,7 @@
|
||||
package paxos
|
||||
|
||||
import (
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
"github.com/weaveworks/mesh"
|
||||
)
|
||||
|
||||
// The node identifier. The use of the UID here is important: Paxos
|
||||
@@ -80,6 +80,10 @@ func NewNode(name mesh.PeerName, uid mesh.PeerUID, quorum uint) *Node {
|
||||
}
|
||||
}
|
||||
|
||||
func (node *Node) SetQuorum(quorum uint) {
|
||||
node.quorum = quorum
|
||||
}
|
||||
|
||||
func (node *Node) GossipState() GossipState {
|
||||
return node.knows
|
||||
}
|
||||
@@ -124,6 +128,9 @@ func max(a uint, b uint) uint {
|
||||
// simply a matter of gossipping a new proposal that supersedes all
|
||||
// others.
|
||||
func (node *Node) Propose() {
|
||||
if node.quorum == 0 {
|
||||
panic("Paxos node.Propose() called with no quorum set")
|
||||
}
|
||||
// Find the highest round number around
|
||||
round := uint(0)
|
||||
|
||||
@@ -234,6 +241,9 @@ func (node *Node) pickValue() Value {
|
||||
|
||||
// Has a consensus been reached, based on the known claims of other nodes?
|
||||
func (node *Node) Consensus() (bool, AcceptedValue) {
|
||||
if node.quorum == 0 {
|
||||
return false, AcceptedValue{}
|
||||
}
|
||||
counts := map[ProposalID]uint{}
|
||||
|
||||
for _, claims := range node.knows {
|
||||
@@ -250,14 +260,16 @@ func (node *Node) Consensus() (bool, AcceptedValue) {
|
||||
return false, AcceptedValue{}
|
||||
}
|
||||
|
||||
func (node *Node) IsElector() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type Status struct {
|
||||
Elector bool
|
||||
KnownNodes int
|
||||
Quorum uint
|
||||
}
|
||||
|
||||
func NewStatus(node *Node) *Status {
|
||||
if node == nil {
|
||||
return nil
|
||||
}
|
||||
return &Status{len(node.knows), node.quorum}
|
||||
return &Status{true, len(node.knows), node.quorum}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
"github.com/weaveworks/mesh"
|
||||
)
|
||||
|
||||
type TestNode struct {
|
||||
@@ -300,14 +300,14 @@ func (m *Model) validate() {
|
||||
for i := range m.nodes {
|
||||
ok, val := m.nodes[i].Consensus()
|
||||
if !ok {
|
||||
//m.dump()
|
||||
m.dump()
|
||||
m.t.Fatal("Node doesn't know about consensus")
|
||||
}
|
||||
|
||||
firstConsensus := m.nodes[i].firstConsensus
|
||||
if firstConsensus.Origin.valid() &&
|
||||
firstConsensus.Origin != val.Origin {
|
||||
//m.dump()
|
||||
m.dump()
|
||||
m.t.Fatal("Consensus mismatch")
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
package ipam
|
||||
|
||||
type prime struct {
|
||||
resultChan chan<- struct{}
|
||||
}
|
||||
|
||||
func (c *prime) Try(alloc *Allocator) bool {
|
||||
if !alloc.ring.Empty() {
|
||||
close(c.resultChan)
|
||||
return true
|
||||
}
|
||||
|
||||
alloc.establishRing()
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *prime) Cancel() {
|
||||
close(c.resultChan)
|
||||
}
|
||||
|
||||
func (c *prime) ForContainer(ident string) bool {
|
||||
return false
|
||||
}
|
||||
+4
-3
@@ -3,8 +3,9 @@ package ring
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/weaveworks/mesh"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
@@ -13,7 +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
|
||||
Free address.Count // Number of free IPs in this range
|
||||
}
|
||||
|
||||
func (e *entry) Equal(e2 *entry) bool {
|
||||
@@ -21,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)))
|
||||
}
|
||||
+175
-107
@@ -12,17 +12,21 @@ import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/weaveworks/mesh"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
"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() {
|
||||
@@ -37,42 +41,52 @@ 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!")
|
||||
ErrNewerVersion = errors.New("Received new version for entry I own!")
|
||||
ErrInvalidEntry = errors.New("Received invalid state update!")
|
||||
ErrEntryInMyRange = errors.New("Received new entry in my range!")
|
||||
ErrNotFound = errors.New("No entries for peer found")
|
||||
ErrDifferentSeeds = errors.New("Received ring was seeded differently from ours")
|
||||
ErrDifferentRange = errors.New("Received range differs from ours")
|
||||
)
|
||||
|
||||
func errInconsistentEntry(mine, theirs *entry) error {
|
||||
return fmt.Errorf("Inconsistent entries for %s: owned by %s but incoming message says %s", mine.Token, mine.Peer, theirs.Peer)
|
||||
}
|
||||
func errEntryInMyRange(theirs *entry) error {
|
||||
return fmt.Errorf("Peer %s says it owns the IP range from %s, which I think I own", theirs.Peer, theirs.Token)
|
||||
}
|
||||
func errNewerVersion(mine, theirs *entry) error {
|
||||
return fmt.Errorf("Received update for IP range I own at %s v%d: incoming message says owner %s v%d", mine.Token, mine.Version, theirs.Peer, theirs.Version)
|
||||
}
|
||||
|
||||
func (r *Ring) checkInvariants() error {
|
||||
if !sort.IsSorted(r.Entries) {
|
||||
return r.checkEntries(r.Entries)
|
||||
}
|
||||
|
||||
func (r *Ring) checkEntries(entries entries) error {
|
||||
if !sort.IsSorted(entries) {
|
||||
return ErrNotSorted
|
||||
}
|
||||
|
||||
// Check no token appears twice
|
||||
// We know it's sorted...
|
||||
for i := 1; i < len(r.Entries); i++ {
|
||||
if r.Entries[i-1].Token == r.Entries[i].Token {
|
||||
for i := 1; i < len(entries); i++ {
|
||||
if entries[i-1].Token == entries[i].Token {
|
||||
return ErrTokenRepeated
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.Entries) == 0 {
|
||||
if len(entries) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check tokens are in range
|
||||
if r.Entries.entry(0).Token < r.Start {
|
||||
if entries.entry(0).Token < r.Start {
|
||||
return ErrTokenOutOfRange
|
||||
}
|
||||
if r.Entries.entry(-1).Token >= r.End {
|
||||
if entries.entry(-1).Token >= r.End {
|
||||
return ErrTokenOutOfRange
|
||||
}
|
||||
|
||||
// Check all the freespaces are in range
|
||||
for i, entry := range r.Entries {
|
||||
next := r.Entries.entry(i + 1)
|
||||
for i, entry := range entries {
|
||||
next := entries.entry(i + 1)
|
||||
distance := r.distance(entry.Token, next.Token)
|
||||
|
||||
if entry.Free > distance {
|
||||
@@ -83,27 +97,46 @@ func (r *Ring) checkInvariants() 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)
|
||||
@@ -115,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 -----------------
|
||||
|
||||
@@ -178,43 +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.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)
|
||||
@@ -222,56 +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
|
||||
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) {
|
||||
common.Log.Debugf("Error merging entries at %s - %v != %v", mine.Token, mine, theirs)
|
||||
return ErrInvalidEntry
|
||||
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
|
||||
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
|
||||
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 len(r.Seeds) == 0 {
|
||||
r.Seeds = gossip.Seeds
|
||||
}
|
||||
r.Entries = result
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
// Empty returns true if the ring has no entries
|
||||
@@ -304,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})
|
||||
}
|
||||
@@ -316,40 +375,57 @@ func (r *Ring) OwnedRanges() (result []address.Range) {
|
||||
return r.splitRangesOverZero(result)
|
||||
}
|
||||
|
||||
// For printing status
|
||||
type RangeInfo struct {
|
||||
Peer mesh.PeerName
|
||||
address.Range
|
||||
Version uint32
|
||||
}
|
||||
|
||||
func (r *Ring) AllRangeInfo() (result []RangeInfo) {
|
||||
for i, entry := range r.Entries {
|
||||
nextEntry := r.Entries.entry(i + 1)
|
||||
ranges := []address.Range{{Start: entry.Token, End: nextEntry.Token}}
|
||||
ranges = r.splitRangesOverZero(ranges)
|
||||
for _, r := range ranges {
|
||||
result = append(result, RangeInfo{entry.Peer, r, entry.Version})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ClaimForPeers claims the entire ring for the array of peers passed
|
||||
// in. Only works for empty rings.
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
if e, found := r.Entries.get(pos); found {
|
||||
e.update(peer, share)
|
||||
} else {
|
||||
r.Entries.insert(entry{Token: pos, Peer: peer, Free: share})
|
||||
}
|
||||
|
||||
pos += address.Address(share)
|
||||
}
|
||||
|
||||
common.Assert(pos == r.End)
|
||||
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]
|
||||
@@ -371,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.Offset) {
|
||||
// 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
|
||||
@@ -402,15 +477,17 @@ func (r *Ring) ReportFree(freespace map[address.Address]address.Offset) {
|
||||
// Check we're not reporting more space than the range
|
||||
entry, next := entries.entry(i), entries.entry(i+1)
|
||||
maxSize := r.distance(entry.Token, next.Token)
|
||||
common.Assert(free <= maxSize)
|
||||
common.Assert(free <= address.Count(maxSize))
|
||||
|
||||
if entries[i].Free == free {
|
||||
return
|
||||
continue
|
||||
}
|
||||
|
||||
entries[i].Free = free
|
||||
entries[i].Version++
|
||||
updated = true
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type weightedPeer struct {
|
||||
@@ -427,10 +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 {
|
||||
var (
|
||||
sum address.Offset
|
||||
totalSpacePerPeer = make(map[mesh.PeerName]address.Offset) // Compute total free space per peer
|
||||
)
|
||||
totalSpacePerPeer := make(map[mesh.PeerName]address.Count)
|
||||
|
||||
// iterate through tokens
|
||||
for i, entry := range r.Entries {
|
||||
@@ -452,7 +526,6 @@ func (r *Ring) ChoosePeersToAskForSpace(start, end address.Address) []mesh.PeerN
|
||||
}
|
||||
|
||||
totalSpacePerPeer[entry.Peer] += entry.Free
|
||||
sum += entry.Free
|
||||
}
|
||||
|
||||
// Compute weighted random numbers, then sort.
|
||||
@@ -481,28 +554,23 @@ func (r *Ring) PickPeerForTransfer(isValid func(mesh.PeerName) bool) mesh.PeerNa
|
||||
|
||||
// Transfer will mark all entries associated with 'from' peer as owned by 'to' peer
|
||||
// and return ranges indicating the new space we picked up
|
||||
func (r *Ring) Transfer(from, to mesh.PeerName) ([]address.Range, error) {
|
||||
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
|
||||
found := false
|
||||
|
||||
for i, entry := range r.Entries {
|
||||
if entry.Peer == from {
|
||||
found = true
|
||||
entry.Peer = to
|
||||
entry.Version++
|
||||
newRanges = append(newRanges, address.Range{Start: entry.Token, End: r.Entries.entry(i + 1).Token})
|
||||
}
|
||||
}
|
||||
|
||||
if !found {
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
return r.splitRangesOverZero(newRanges), nil
|
||||
return r.splitRangesOverZero(newRanges)
|
||||
}
|
||||
|
||||
// Contains returns true if addr is in this ring
|
||||
|
||||
+160
-113
@@ -8,8 +8,9 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/weaveworks/mesh"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
@@ -18,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")
|
||||
@@ -29,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}}
|
||||
@@ -41,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")
|
||||
|
||||
@@ -50,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() {
|
||||
@@ -59,17 +70,23 @@ func TestInsert(t *testing.T) {
|
||||
|
||||
ring.Entries.entry(0).Free = 0
|
||||
ring.Entries.insert(entry{Token: dot245, Peer: peer1name})
|
||||
ring2 := New(start, end, peer1name)
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer1name, Free: 0}, {Token: dot245, Peer: peer1name}}
|
||||
require.Equal(t, ring2, ring)
|
||||
check := []RangeInfo{
|
||||
{Peer: peer1name, Range: address.Range{Start: start, End: dot245}},
|
||||
{Peer: peer1name, Range: address.Range{Start: dot245, End: end}},
|
||||
}
|
||||
require.Equal(t, check, ring.AllRangeInfo())
|
||||
|
||||
ring.Entries.insert(entry{Token: dot10, Peer: peer1name})
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer1name, Free: 0}, {Token: dot10, Peer: peer1name}, {Token: dot245, Peer: peer1name}}
|
||||
require.Equal(t, ring2, ring)
|
||||
check2 := []RangeInfo{
|
||||
{Peer: peer1name, Range: address.Range{Start: start, End: dot10}},
|
||||
{Peer: peer1name, Range: address.Range{Start: dot10, End: dot245}},
|
||||
{Peer: peer1name, Range: address.Range{Start: dot245, End: end}},
|
||||
}
|
||||
require.Equal(t, check2, ring.AllRangeInfo())
|
||||
}
|
||||
|
||||
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
|
||||
@@ -111,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},
|
||||
@@ -146,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()
|
||||
|
||||
@@ -169,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)
|
||||
|
||||
@@ -193,47 +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}}
|
||||
require.True(t, ring1.Merge(*ring2) == ErrNewerVersion, "Expected ErrNewerVersion")
|
||||
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.True(t, ring1.Merge(*ring2) == ErrInvalidEntry, "Expected ErrInvalidEntry")
|
||||
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.True(t, ring1.Merge(*ring2) == ErrEntryInMyRange, "Expected ErrEntryInMyRange")
|
||||
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)
|
||||
@@ -244,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
|
||||
@@ -320,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)
|
||||
@@ -330,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)
|
||||
@@ -342,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 {
|
||||
@@ -358,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)
|
||||
|
||||
@@ -366,7 +384,7 @@ func TestFindFree(t *testing.T) {
|
||||
assertPeersWithSpace(t, ring1, start, end, 0)
|
||||
|
||||
// We shouldn't return outselves
|
||||
ring1.ReportFree(map[address.Address]address.Offset{start: 10})
|
||||
ring1.ReportFree(map[address.Address]address.Count{start: 10})
|
||||
assertPeersWithSpace(t, ring1, start, end, 0)
|
||||
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 1},
|
||||
@@ -394,14 +412,14 @@ 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.Offset)
|
||||
freespace := make(map[address.Address]address.Count)
|
||||
for _, r := range ring2.OwnedRanges() {
|
||||
freespace[r.Start] = 0
|
||||
}
|
||||
@@ -409,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")
|
||||
|
||||
@@ -418,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())
|
||||
@@ -457,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}}
|
||||
@@ -472,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")
|
||||
|
||||
@@ -505,6 +523,36 @@ func TestOwner(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func makePeerName(i int) mesh.PeerName {
|
||||
if i >= 10000 {
|
||||
panic("makePeerName: invalid value")
|
||||
}
|
||||
peer, _ := mesh.PeerNameFromString(fmt.Sprintf("%02d:%02d:00:00:00:ff", i/100, i%100))
|
||||
return peer
|
||||
}
|
||||
|
||||
func makePeers(numPeers int) []mesh.PeerName {
|
||||
peers := make([]mesh.PeerName, numPeers)
|
||||
for i := 0; i < numPeers; i++ {
|
||||
peers[i] = makePeerName(i)
|
||||
}
|
||||
return peers
|
||||
}
|
||||
|
||||
func TestClaimForPeers(t *testing.T) {
|
||||
const numPeers = 12
|
||||
// Different end to usual so we get a number of addresses that a)
|
||||
// is smaller than the max number of peers, and b) is divisible by
|
||||
// some number of peers. This maximises coverage of edge cases.
|
||||
end := dot8
|
||||
peers := makePeers(numPeers)
|
||||
// Test for a range of peer counts
|
||||
for i := 0; i < numPeers; i++ {
|
||||
ring := NewRing(start, end, peers[0])
|
||||
ring.ClaimForPeers(peers[:i+1])
|
||||
}
|
||||
}
|
||||
|
||||
type addressSlice []address.Address
|
||||
|
||||
func (s addressSlice) Len() int { return len(s) }
|
||||
@@ -519,8 +567,7 @@ func TestFuzzRing(t *testing.T) {
|
||||
|
||||
peers := make([]mesh.PeerName, numPeers)
|
||||
for i := 0; i < numPeers; i++ {
|
||||
peer, _ := mesh.PeerNameFromString(fmt.Sprintf("%02d:00:00:00:02:00", i))
|
||||
peers[i] = peer
|
||||
peers[i] = makePeerName(i)
|
||||
}
|
||||
|
||||
// Make a valid, random ring
|
||||
@@ -539,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})
|
||||
@@ -556,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()
|
||||
@@ -573,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})
|
||||
@@ -589,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()
|
||||
@@ -607,11 +654,11 @@ func TestFuzzRingHard(t *testing.T) {
|
||||
)
|
||||
|
||||
addPeer := func() {
|
||||
peer, _ := mesh.PeerNameFromString(fmt.Sprintf("%02d:%02d:00:00:00:00", nextPeerID/10, nextPeerID%10))
|
||||
peer := makePeerName(nextPeerID)
|
||||
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++ {
|
||||
@@ -650,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)
|
||||
@@ -664,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()
|
||||
}
|
||||
}
|
||||
@@ -693,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()
|
||||
@@ -712,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()
|
||||
}
|
||||
|
||||
|
||||
+31
-9
@@ -31,8 +31,8 @@ func (s *Space) Add(start address.Address, size address.Offset) {
|
||||
|
||||
// Clear removes all spaces from this space set. Used during node shutdown.
|
||||
func (s *Space) Clear() {
|
||||
s.free = s.free[:0]
|
||||
s.ours = s.ours[:0]
|
||||
s.free = s.free[:0]
|
||||
}
|
||||
|
||||
// Walk down the free list calling f() on the in-range portions, until
|
||||
@@ -88,8 +88,24 @@ func (s *Space) Claim(addr address.Address) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Space) NumFreeAddressesInRange(r address.Range) address.Offset {
|
||||
res := address.Offset(0)
|
||||
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 {
|
||||
res += address.Length(s.free[i+1], s.free[i])
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (s *Space) NumFreeAddressesInRange(r address.Range) address.Count {
|
||||
res := address.Count(0)
|
||||
s.walkFree(r, func(chunk address.Range) bool {
|
||||
res += chunk.Size()
|
||||
return false
|
||||
@@ -111,11 +127,14 @@ func (s *Space) Free(addr address.Address) error {
|
||||
}
|
||||
|
||||
func (s *Space) biggestFreeRange(r address.Range) (biggest address.Range) {
|
||||
biggestSize := address.Offset(0)
|
||||
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
|
||||
})
|
||||
@@ -129,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, 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)
|
||||
|
||||
+56
-62
@@ -19,16 +19,6 @@ func ip(s string) address.Address {
|
||||
return addr
|
||||
}
|
||||
|
||||
// Helper function to avoid 'NumFreeAddressesInRange(start, end)'
|
||||
// dozens of times in tests
|
||||
func (s *Space) NumFreeAddresses() address.Offset {
|
||||
res := address.Offset(0)
|
||||
for i := 0; i < len(s.free); i += 2 {
|
||||
res += address.Subtract(s.free[i+1], s.free[i])
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func TestLowlevel(t *testing.T) {
|
||||
a := []address.Address{}
|
||||
a = add(a, 100, 200)
|
||||
@@ -61,24 +51,24 @@ func TestLowlevel(t *testing.T) {
|
||||
require.Equal(t, []address.Address{}, a)
|
||||
|
||||
s := New()
|
||||
require.Equal(t, address.Offset(0), s.NumFreeAddresses())
|
||||
require.Equal(t, address.Count(0), s.NumFreeAddresses())
|
||||
ok, got := s.Allocate(address.NewRange(0, 1000))
|
||||
require.False(t, ok, "allocate in empty space should fail")
|
||||
|
||||
s.Add(100, 100)
|
||||
require.Equal(t, address.Offset(100), s.NumFreeAddresses())
|
||||
require.Equal(t, address.Count(100), s.NumFreeAddresses())
|
||||
ok, got = s.Allocate(address.NewRange(0, 1000))
|
||||
require.True(t, ok && got == 100, "allocate")
|
||||
require.Equal(t, address.Offset(99), s.NumFreeAddresses())
|
||||
require.Equal(t, address.Count(99), s.NumFreeAddresses())
|
||||
require.NoError(t, s.Claim(150))
|
||||
require.Equal(t, address.Offset(98), s.NumFreeAddresses())
|
||||
require.Equal(t, address.Count(98), s.NumFreeAddresses())
|
||||
require.NoError(t, s.Free(100))
|
||||
require.Equal(t, address.Offset(99), s.NumFreeAddresses())
|
||||
require.Equal(t, address.Count(99), s.NumFreeAddresses())
|
||||
wt.AssertErrorInterface(t, (*error)(nil), s.Free(0), "free not allocated")
|
||||
wt.AssertErrorInterface(t, (*error)(nil), s.Free(100), "double free")
|
||||
|
||||
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()
|
||||
@@ -88,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")
|
||||
}
|
||||
@@ -107,19 +97,22 @@ func TestSpaceAllocate(t *testing.T) {
|
||||
)
|
||||
|
||||
space1 := makeSpace(start, size)
|
||||
require.Equal(t, address.Offset(20), space1.NumFreeAddresses())
|
||||
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.Offset(19), space1.NumFreeAddresses())
|
||||
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.Offset(18), space1.NumFreeAddresses())
|
||||
require.Equal(t, address.Offset(13), space1.NumFreeAddressesInRange(address.Range{Start: ip(testAddr1), End: ip(testAddrx)}))
|
||||
require.Equal(t, address.Offset(18), space1.NumFreeAddressesInRange(address.Range{Start: ip(testAddr1), End: ip(testAddry)}))
|
||||
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()
|
||||
|
||||
space1.Free(addr2)
|
||||
@@ -134,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")
|
||||
}
|
||||
@@ -155,48 +149,48 @@ func TestSpaceFree(t *testing.T) {
|
||||
// Check we are full
|
||||
ok, _ := space.Allocate(entireRange)
|
||||
require.True(t, !ok, "Should have failed to get address")
|
||||
r, ok = space.Donate(entireRange)
|
||||
r, _ = space.Donate(entireRange)
|
||||
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.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.30
|
||||
r = space.biggestFreeRange(entireRange)
|
||||
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.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.Offset(4), space.NumFreeAddresses())
|
||||
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.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.Equal(t, address.Offset(7), space.NumFreeAddresses())
|
||||
|
||||
// Now get the biggest free space; should be 3.21
|
||||
r = space.biggestFreeRange(entireRange)
|
||||
require.True(t, r.Start == ip("10.0.3.10") && r.Size() == 4, "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 (
|
||||
@@ -209,9 +203,9 @@ 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, address.Offset(size/2), numGivenUp)
|
||||
require.Equal(t, address.Offset(size/2), ps1.NumFreeAddresses())
|
||||
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())
|
||||
|
||||
// Now check we can give the rest up.
|
||||
count := 0 // count to avoid infinite loop
|
||||
@@ -222,8 +216,8 @@ func TestDonateSimple(t *testing.T) {
|
||||
}
|
||||
numGivenUp += r.Size()
|
||||
}
|
||||
require.Equal(t, address.Offset(0), ps1.NumFreeAddresses())
|
||||
require.Equal(t, address.Offset(size), numGivenUp)
|
||||
require.Equal(t, address.Count(0), ps1.NumFreeAddresses())
|
||||
require.Equal(t, address.Count(size), numGivenUp)
|
||||
}
|
||||
|
||||
func TestDonateHard(t *testing.T) {
|
||||
@@ -240,7 +234,7 @@ func TestDonateHard(t *testing.T) {
|
||||
require.True(t, ok, "Failed to get IP!")
|
||||
}
|
||||
|
||||
require.Equal(t, address.Offset(0), spaceset.NumFreeAddresses())
|
||||
require.Equal(t, address.Count(0), spaceset.NumFreeAddresses())
|
||||
|
||||
// Now free all but the last address
|
||||
// this will force us to split the free list
|
||||
@@ -251,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.Offset(24), newRange.Size())
|
||||
require.Equal(t, address.Offset(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)
|
||||
}
|
||||
+32
-11
@@ -1,6 +1,7 @@
|
||||
package ipam
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/weaveworks/weave/ipam/paxos"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
@@ -8,6 +9,8 @@ import (
|
||||
type Status struct {
|
||||
Paxos *paxos.Status
|
||||
Range string
|
||||
RangeNumIPs int
|
||||
ActiveIPs int
|
||||
DefaultSubnet string
|
||||
Entries []EntryStatus
|
||||
PendingClaims []ClaimStatus
|
||||
@@ -15,14 +18,17 @@ type Status struct {
|
||||
}
|
||||
|
||||
type EntryStatus struct {
|
||||
Token string
|
||||
Peer string
|
||||
Version uint32
|
||||
Token string
|
||||
Size uint32
|
||||
Peer string
|
||||
Nickname string
|
||||
IsKnownPeer bool
|
||||
Version uint32
|
||||
}
|
||||
|
||||
type ClaimStatus struct {
|
||||
Ident string
|
||||
Address address.Address
|
||||
Ident string
|
||||
CIDR address.CIDR
|
||||
}
|
||||
|
||||
func NewStatus(allocator *Allocator, defaultSubnet address.CIDR) *Status {
|
||||
@@ -31,8 +37,14 @@ func NewStatus(allocator *Allocator, defaultSubnet address.CIDR) *Status {
|
||||
}
|
||||
|
||||
var paxosStatus *paxos.Status
|
||||
if allocator.paxosTicker != nil {
|
||||
paxosStatus = paxos.NewStatus(allocator.paxos)
|
||||
if allocator.awaitingConsensus && allocator.paxos != nil {
|
||||
if allocator.paxos.IsElector() {
|
||||
if node, ok := allocator.paxos.(*paxos.Node); ok {
|
||||
paxosStatus = paxos.NewStatus(node)
|
||||
}
|
||||
} else {
|
||||
paxosStatus = &paxos.Status{Elector: false}
|
||||
}
|
||||
}
|
||||
|
||||
resultChan := make(chan *Status)
|
||||
@@ -40,6 +52,8 @@ func NewStatus(allocator *Allocator, defaultSubnet address.CIDR) *Status {
|
||||
resultChan <- &Status{
|
||||
paxosStatus,
|
||||
allocator.universe.String(),
|
||||
int(allocator.universe.Size()),
|
||||
int(allocator.space.NumOwnedAddresses()),
|
||||
defaultSubnet.String(),
|
||||
newEntryStatusSlice(allocator),
|
||||
newClaimStatusSlice(allocator),
|
||||
@@ -56,8 +70,15 @@ func newEntryStatusSlice(allocator *Allocator) []EntryStatus {
|
||||
return slice
|
||||
}
|
||||
|
||||
for _, entry := range allocator.ring.Entries {
|
||||
slice = append(slice, EntryStatus{entry.Token.String(), entry.Peer.String(), entry.Version})
|
||||
for _, r := range allocator.ring.AllRangeInfo() {
|
||||
slice = append(slice, EntryStatus{
|
||||
Token: r.Start.String(),
|
||||
Size: uint32(r.Size()),
|
||||
Peer: r.Peer.String(),
|
||||
Nickname: allocator.nicknames[r.Peer],
|
||||
IsKnownPeer: allocator.isKnownPeer(r.Peer),
|
||||
Version: r.Version,
|
||||
})
|
||||
}
|
||||
|
||||
return slice
|
||||
@@ -67,7 +88,7 @@ func newClaimStatusSlice(allocator *Allocator) []ClaimStatus {
|
||||
var slice []ClaimStatus
|
||||
for _, op := range allocator.pendingClaims {
|
||||
claim := op.(*claim)
|
||||
slice = append(slice, ClaimStatus{claim.ident, claim.addr})
|
||||
slice = append(slice, ClaimStatus{claim.ident, claim.cidr})
|
||||
}
|
||||
return slice
|
||||
}
|
||||
@@ -76,7 +97,7 @@ func newAllocateIdentSlice(allocator *Allocator) []string {
|
||||
var slice []string
|
||||
for _, op := range allocator.pendingAllocates {
|
||||
allocate := op.(*allocate)
|
||||
slice = append(slice, allocate.ident)
|
||||
slice = append(slice, fmt.Sprintf("%s %s", allocate.ident, allocate.r.String()))
|
||||
}
|
||||
return slice
|
||||
}
|
||||
+37
-15
@@ -7,8 +7,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/weaveworks/mesh"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/testing/gossip"
|
||||
)
|
||||
@@ -48,7 +49,7 @@ func (m *mockGossipComms) String() string {
|
||||
// that the contents of messages are never re-ordered. Which, for instance,
|
||||
// requires they are not based off iterating through a map.
|
||||
|
||||
func (m *mockGossipComms) GossipBroadcast(update mesh.GossipData) error {
|
||||
func (m *mockGossipComms) GossipBroadcast(update mesh.GossipData) {
|
||||
m.Lock()
|
||||
defer m.Unlock()
|
||||
buf := []byte{}
|
||||
@@ -62,7 +63,6 @@ func (m *mockGossipComms) GossipBroadcast(update mesh.GossipData) error {
|
||||
// Swallow this message
|
||||
m.messages = m.messages[1:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func equalByteBuffer(a, b []byte) bool {
|
||||
@@ -123,24 +123,35 @@ func CheckAllExpectedMessagesSent(allocs ...*Allocator) {
|
||||
}
|
||||
}
|
||||
|
||||
func makeAllocator(name string, cidrStr string, quorum uint) (*Allocator, address.Range) {
|
||||
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) {
|
||||
peername, err := mesh.PeerNameFromString(name)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, cidr, err := address.ParseCIDR(cidrStr)
|
||||
cidr, err := address.ParseCIDR(cidrStr)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
alloc := NewAllocator(peername, mesh.PeerUID(rand.Int63()),
|
||||
"nick-"+name, cidr.Range(), quorum, func(mesh.PeerName) bool { return true })
|
||||
|
||||
return alloc, cidr.HostRange()
|
||||
return NewAllocator(Config{
|
||||
OurName: peername,
|
||||
OurUID: mesh.PeerUID(rand.Int63()),
|
||||
OurNickname: "nick-" + name,
|
||||
Universe: cidr,
|
||||
IsObserver: quorum == 0,
|
||||
Quorum: func() uint { return quorum },
|
||||
Db: new(mockDB),
|
||||
IsKnownPeer: func(mesh.PeerName) bool { return true },
|
||||
}), cidr
|
||||
}
|
||||
|
||||
func makeAllocatorWithMockGossip(t *testing.T, name string, universeCIDR string, quorum uint) (*Allocator, address.Range) {
|
||||
func makeAllocatorWithMockGossip(t *testing.T, name string, universeCIDR string, quorum uint) (*Allocator, address.CIDR) {
|
||||
alloc, subnet := makeAllocator(name, universeCIDR, quorum)
|
||||
gossip := &mockGossipComms{T: t, name: name}
|
||||
alloc.SetInterfaces(gossip)
|
||||
@@ -157,14 +168,22 @@ func (alloc *Allocator) claimRingForTesting(allocs ...*Allocator) {
|
||||
alloc.space.AddRanges(alloc.ring.OwnedRanges())
|
||||
}
|
||||
|
||||
func (alloc *Allocator) NumFreeAddresses(r address.Range) address.Offset {
|
||||
resultChan := make(chan address.Offset)
|
||||
func (alloc *Allocator) NumFreeAddresses(r address.Range) address.Count {
|
||||
resultChan := make(chan address.Count)
|
||||
alloc.actionChan <- func() {
|
||||
resultChan <- alloc.space.NumFreeAddressesInRange(r)
|
||||
}
|
||||
return <-resultChan
|
||||
}
|
||||
|
||||
func (alloc *Allocator) OwnedRanges() (result []address.Range) {
|
||||
resultChan := make(chan []address.Range)
|
||||
alloc.actionChan <- func() {
|
||||
resultChan <- alloc.ring.OwnedRanges()
|
||||
}
|
||||
return <-resultChan
|
||||
}
|
||||
|
||||
// Check whether or not something was sent on a channel
|
||||
func AssertSent(t *testing.T, ch <-chan bool) {
|
||||
timeout := time.After(10 * time.Second)
|
||||
@@ -194,10 +213,10 @@ func AssertNothingSentErr(t *testing.T, ch <-chan error) {
|
||||
}
|
||||
}
|
||||
|
||||
func makeNetworkOfAllocators(size int, cidr string) ([]*Allocator, *gossip.TestRouter, address.Range) {
|
||||
func makeNetworkOfAllocators(size int, cidr string) ([]*Allocator, *gossip.TestRouter, address.CIDR) {
|
||||
gossipRouter := gossip.NewTestRouter(0.0)
|
||||
allocs := make([]*Allocator, size)
|
||||
var subnet address.Range
|
||||
var subnet address.CIDR
|
||||
|
||||
for i := 0; i < size; i++ {
|
||||
var alloc *Allocator
|
||||
@@ -213,7 +232,10 @@ func makeNetworkOfAllocators(size int, cidr string) ([]*Allocator, *gossip.TestR
|
||||
return allocs, gossipRouter, subnet
|
||||
}
|
||||
|
||||
func stopNetworkOfAllocators(allocs []*Allocator) {
|
||||
func stopNetworkOfAllocators(allocs []*Allocator, gossipRouter *gossip.TestRouter) {
|
||||
// NB: We must stop the router first since ipam gossip makes
|
||||
// synchronous calls into the allocator.
|
||||
gossipRouter.Stop()
|
||||
for _, alloc := range allocs {
|
||||
alloc.Stop()
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -1,437 +0,0 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Connection interface {
|
||||
Local() *Peer
|
||||
Remote() *Peer
|
||||
RemoteTCPAddr() string
|
||||
Outbound() bool
|
||||
Established() bool
|
||||
BreakTie(Connection) ConnectionTieBreak
|
||||
Shutdown(error)
|
||||
Log(args ...interface{})
|
||||
}
|
||||
|
||||
type ConnectionTieBreak int
|
||||
|
||||
const (
|
||||
TieBreakWon ConnectionTieBreak = iota
|
||||
TieBreakLost
|
||||
TieBreakTied
|
||||
)
|
||||
|
||||
var ErrConnectToSelf = fmt.Errorf("Cannot connect to ourself")
|
||||
|
||||
type RemoteConnection struct {
|
||||
local *Peer
|
||||
remote *Peer
|
||||
remoteTCPAddr string
|
||||
outbound bool
|
||||
established bool
|
||||
}
|
||||
|
||||
type LocalConnection struct {
|
||||
sync.RWMutex
|
||||
RemoteConnection
|
||||
TCPConn *net.TCPConn
|
||||
version byte
|
||||
tcpSender TCPSender
|
||||
SessionKey *[32]byte
|
||||
heartbeatTCP *time.Ticker
|
||||
Router *Router
|
||||
uid uint64
|
||||
actionChan chan<- ConnectionAction
|
||||
finished <-chan struct{} // closed to signal that actorLoop has finished
|
||||
OverlayConn OverlayConnection
|
||||
}
|
||||
|
||||
type ConnectionAction func() error
|
||||
|
||||
func NewRemoteConnection(from, to *Peer, tcpAddr string, outbound bool, established bool) *RemoteConnection {
|
||||
return &RemoteConnection{
|
||||
local: from,
|
||||
remote: to,
|
||||
remoteTCPAddr: tcpAddr,
|
||||
outbound: outbound,
|
||||
established: established,
|
||||
}
|
||||
}
|
||||
|
||||
func (conn *RemoteConnection) Local() *Peer { return conn.local }
|
||||
func (conn *RemoteConnection) Remote() *Peer { return conn.remote }
|
||||
func (conn *RemoteConnection) RemoteTCPAddr() string { return conn.remoteTCPAddr }
|
||||
func (conn *RemoteConnection) Outbound() bool { return conn.outbound }
|
||||
func (conn *RemoteConnection) Established() bool { return conn.established }
|
||||
func (conn *RemoteConnection) BreakTie(Connection) ConnectionTieBreak { return TieBreakTied }
|
||||
func (conn *RemoteConnection) Shutdown(error) {}
|
||||
|
||||
func (conn *RemoteConnection) Log(args ...interface{}) {
|
||||
log.Println(append(append([]interface{}{}, fmt.Sprintf("->[%s|%s]:", conn.remoteTCPAddr, conn.remote)), args...)...)
|
||||
}
|
||||
|
||||
func (conn *RemoteConnection) ErrorLog(args ...interface{}) {
|
||||
log.Errorln(append(append([]interface{}{}, fmt.Sprintf("->[%s|%s]:", conn.remoteTCPAddr, conn.remote)), args...)...)
|
||||
}
|
||||
|
||||
// Does not return anything. If the connection is successful, it will
|
||||
// end up in the local peer's connections map.
|
||||
func StartLocalConnection(connRemote *RemoteConnection, tcpConn *net.TCPConn, router *Router, acceptNewPeer bool) {
|
||||
if connRemote.local != router.Ourself.Peer {
|
||||
log.Fatal("Attempt to create local connection from a peer which is not ourself")
|
||||
}
|
||||
// NB, we're taking a copy of connRemote here.
|
||||
actionChan := make(chan ConnectionAction, ChannelSize)
|
||||
finished := make(chan struct{})
|
||||
conn := &LocalConnection{
|
||||
RemoteConnection: *connRemote,
|
||||
Router: router,
|
||||
TCPConn: tcpConn,
|
||||
uid: randUint64(),
|
||||
actionChan: actionChan,
|
||||
finished: finished}
|
||||
go conn.run(actionChan, finished, acceptNewPeer)
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) BreakTie(dupConn Connection) ConnectionTieBreak {
|
||||
dupConnLocal := dupConn.(*LocalConnection)
|
||||
// conn.uid is used as the tie breaker here, in the knowledge that
|
||||
// both sides will make the same decision.
|
||||
if conn.uid < dupConnLocal.uid {
|
||||
return TieBreakWon
|
||||
} else if dupConnLocal.uid < conn.uid {
|
||||
return TieBreakLost
|
||||
} else {
|
||||
return TieBreakTied
|
||||
}
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) Established() bool {
|
||||
return conn.established
|
||||
}
|
||||
|
||||
// Send directly, not via the Actor. If it goes via the Actor we can
|
||||
// get a deadlock where LocalConnection is blocked talking to
|
||||
// LocalPeer and LocalPeer is blocked trying send a ProtocolMsg via
|
||||
// LocalConnection, and the channels are full in both directions so
|
||||
// nothing can proceed.
|
||||
func (conn *LocalConnection) SendProtocolMsg(m ProtocolMsg) {
|
||||
if err := conn.sendProtocolMsg(m); err != nil {
|
||||
conn.Shutdown(err)
|
||||
}
|
||||
}
|
||||
|
||||
// ACTOR methods
|
||||
|
||||
// NB: The conn.* fields are only written by the connection actor
|
||||
// process, which is the caller of the ConnectionAction funs. Hence we
|
||||
// do not need locks for reading, and only need write locks for fields
|
||||
// read by other processes.
|
||||
|
||||
// Async
|
||||
func (conn *LocalConnection) Shutdown(err error) {
|
||||
// err should always be a real error, even if only io.EOF
|
||||
if err == nil {
|
||||
panic("nil error")
|
||||
}
|
||||
|
||||
// Run on its own goroutine in case the channel is backed up
|
||||
go func() { conn.sendAction(func() error { return err }) }()
|
||||
}
|
||||
|
||||
// Send an actor request to the actorLoop, but don't block if
|
||||
// actorLoop has exited - see http://blog.golang.org/pipelines for
|
||||
// pattern
|
||||
func (conn *LocalConnection) sendAction(action ConnectionAction) {
|
||||
select {
|
||||
case conn.actionChan <- action:
|
||||
case <-conn.finished:
|
||||
}
|
||||
}
|
||||
|
||||
// ACTOR server
|
||||
|
||||
func (conn *LocalConnection) run(actionChan <-chan ConnectionAction, finished chan<- struct{}, acceptNewPeer bool) {
|
||||
var err error // important to use this var and not create another one with 'err :='
|
||||
defer func() { conn.shutdown(err) }()
|
||||
defer close(finished)
|
||||
|
||||
conn.TCPConn.SetLinger(0)
|
||||
intro, err := ProtocolIntroParams{
|
||||
MinVersion: conn.Router.ProtocolMinVersion,
|
||||
MaxVersion: ProtocolMaxVersion,
|
||||
Features: conn.makeFeatures(),
|
||||
Conn: conn.TCPConn,
|
||||
Password: conn.Router.Password,
|
||||
Outbound: conn.outbound,
|
||||
}.DoIntro()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
conn.SessionKey = intro.SessionKey
|
||||
conn.tcpSender = intro.Sender
|
||||
conn.version = intro.Version
|
||||
|
||||
remote, err := conn.parseFeatures(intro.Features)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err = conn.registerRemote(remote, acceptNewPeer); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
conn.Log("connection ready; using protocol version", conn.version)
|
||||
|
||||
params := OverlayConnectionParams{
|
||||
RemotePeer: conn.remote,
|
||||
LocalAddr: conn.TCPConn.LocalAddr().(*net.TCPAddr),
|
||||
RemoteAddr: conn.TCPConn.RemoteAddr().(*net.TCPAddr),
|
||||
Outbound: conn.outbound,
|
||||
ConnUID: conn.uid,
|
||||
SessionKey: conn.SessionKey,
|
||||
SendControlMessage: conn.sendOverlayControlMessage,
|
||||
Features: intro.Features,
|
||||
}
|
||||
if conn.OverlayConn, err = conn.Router.Overlay.PrepareConnection(params); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// As soon as we do AddConnection, the new connection becomes
|
||||
// visible to the packet routing logic. So AddConnection must
|
||||
// come after PrepareConnection
|
||||
if err = conn.Router.Ourself.AddConnection(conn); err != nil {
|
||||
return
|
||||
}
|
||||
conn.Router.ConnectionMaker.ConnectionCreated(conn)
|
||||
|
||||
// OverlayConnection confirmation comes after AddConnection,
|
||||
// because only after that completes do we know the connection is
|
||||
// valid: in particular that it is not a duplicate connection to
|
||||
// the same peer. Overlay communication on a duplicate connection
|
||||
// can cause problems such as tripping up overlay crypto at the
|
||||
// other end due to data being decoded by the other connection. It
|
||||
// is also generally wasteful to engage in any interaction with
|
||||
// the remote on a connection that turns out to be invalid.
|
||||
conn.OverlayConn.Confirm()
|
||||
|
||||
// receiveTCP must follow also AddConnection. In the absence
|
||||
// of any indirect connectivity to the remote peer, the first
|
||||
// we hear about it (and any peers reachable from it) is
|
||||
// through topology gossip it sends us on the connection. We
|
||||
// must ensure that the connection has been added to Ourself
|
||||
// prior to processing any such gossip, otherwise we risk
|
||||
// immediately gc'ing part of that newly received portion of
|
||||
// the topology (though not the remote peer itself, since that
|
||||
// will have a positive ref count), leaving behind dangling
|
||||
// references to peers. Hence we must invoke AddConnection,
|
||||
// which is *synchronous*, first.
|
||||
conn.heartbeatTCP = time.NewTicker(TCPHeartbeat)
|
||||
go conn.receiveTCP(intro.Receiver)
|
||||
|
||||
// AddConnection must precede actorLoop. More precisely, it
|
||||
// must precede shutdown, since that invokes DeleteConnection
|
||||
// and is invoked on termination of this entire
|
||||
// function. Essentially this boils down to a prohibition on
|
||||
// running AddConnection in a separate goroutine, at least not
|
||||
// without some synchronisation. Which in turn requires the
|
||||
// launching of the receiveTCP goroutine to precede actorLoop.
|
||||
err = conn.actorLoop(actionChan)
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) makeFeatures() map[string]string {
|
||||
features := map[string]string{
|
||||
"PeerNameFlavour": PeerNameFlavour,
|
||||
"Name": conn.local.Name.String(),
|
||||
"NickName": conn.local.NickName,
|
||||
"ShortID": fmt.Sprint(conn.local.ShortID),
|
||||
"UID": fmt.Sprint(conn.local.UID),
|
||||
"ConnID": fmt.Sprint(conn.uid),
|
||||
}
|
||||
conn.Router.Overlay.AddFeaturesTo(features)
|
||||
return features
|
||||
}
|
||||
|
||||
type features map[string]string
|
||||
|
||||
func (features features) MustHave(keys []string) error {
|
||||
for _, key := range keys {
|
||||
if _, ok := features[key]; !ok {
|
||||
return fmt.Errorf("Field %s is missing", key)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (features features) Get(key string) string {
|
||||
return features[key]
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) parseFeatures(features features) (*Peer, error) {
|
||||
if err := features.MustHave([]string{"PeerNameFlavour", "Name", "NickName", "UID", "ConnID"}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
remotePeerNameFlavour := features.Get("PeerNameFlavour")
|
||||
if remotePeerNameFlavour != PeerNameFlavour {
|
||||
return nil, fmt.Errorf("Peer name flavour mismatch (ours: '%s', theirs: '%s')", PeerNameFlavour, remotePeerNameFlavour)
|
||||
}
|
||||
|
||||
name, err := PeerNameFromString(features.Get("Name"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
nickName := features.Get("NickName")
|
||||
|
||||
var shortID uint64
|
||||
var hasShortID bool
|
||||
if shortIDStr, present := features["ShortID"]; present {
|
||||
hasShortID = true
|
||||
shortID, err = strconv.ParseUint(shortIDStr, 10,
|
||||
PeerShortIDBits)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
uid, err := ParsePeerUID(features.Get("UID"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
remoteConnID, err := strconv.ParseUint(features.Get("ConnID"), 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
conn.uid ^= remoteConnID
|
||||
peer := NewPeer(name, nickName, uid, 0, PeerShortID(shortID))
|
||||
peer.HasShortID = hasShortID
|
||||
return peer, nil
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) registerRemote(remote *Peer, acceptNewPeer bool) error {
|
||||
if acceptNewPeer {
|
||||
conn.remote = conn.Router.Peers.FetchWithDefault(remote)
|
||||
} else {
|
||||
conn.remote = conn.Router.Peers.FetchAndAddRef(remote.Name)
|
||||
if conn.remote == nil {
|
||||
return fmt.Errorf("Found unknown remote name: %s at %s", remote.Name, conn.remoteTCPAddr)
|
||||
}
|
||||
}
|
||||
|
||||
if conn.remote == conn.local {
|
||||
return ErrConnectToSelf
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) actorLoop(actionChan <-chan ConnectionAction) (err error) {
|
||||
fwdErrorChan := conn.OverlayConn.ErrorChannel()
|
||||
fwdEstablishedChan := conn.OverlayConn.EstablishedChannel()
|
||||
|
||||
for err == nil {
|
||||
select {
|
||||
case action := <-actionChan:
|
||||
err = action()
|
||||
|
||||
case <-conn.heartbeatTCP.C:
|
||||
err = conn.sendSimpleProtocolMsg(ProtocolHeartbeat)
|
||||
|
||||
case <-fwdEstablishedChan:
|
||||
conn.established = true
|
||||
fwdEstablishedChan = nil
|
||||
conn.Router.Ourself.ConnectionEstablished(conn)
|
||||
|
||||
case err = <-fwdErrorChan:
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) shutdown(err error) {
|
||||
if conn.remote == nil {
|
||||
log.Errorf("->[%s] connection shutting down due to error during handshake: %v", conn.remoteTCPAddr, err)
|
||||
} else {
|
||||
conn.ErrorLog("connection shutting down due to error:", err)
|
||||
}
|
||||
|
||||
if conn.TCPConn != nil {
|
||||
checkWarn(conn.TCPConn.Close())
|
||||
}
|
||||
|
||||
if conn.remote != nil {
|
||||
conn.Router.Peers.Dereference(conn.remote)
|
||||
conn.Router.Ourself.DeleteConnection(conn)
|
||||
}
|
||||
|
||||
if conn.heartbeatTCP != nil {
|
||||
conn.heartbeatTCP.Stop()
|
||||
}
|
||||
|
||||
if conn.OverlayConn != nil {
|
||||
conn.OverlayConn.Stop()
|
||||
}
|
||||
|
||||
conn.Router.ConnectionMaker.ConnectionTerminated(conn, err)
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) sendOverlayControlMessage(tag byte, msg []byte) error {
|
||||
return conn.sendProtocolMsg(ProtocolMsg{ProtocolTag(tag), msg})
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
func (conn *LocalConnection) sendSimpleProtocolMsg(tag ProtocolTag) error {
|
||||
return conn.sendProtocolMsg(ProtocolMsg{tag: tag})
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) sendProtocolMsg(m ProtocolMsg) error {
|
||||
return conn.tcpSender.Send(append([]byte{byte(m.tag)}, m.msg...))
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) receiveTCP(receiver TCPReceiver) {
|
||||
var err error
|
||||
for {
|
||||
conn.extendReadDeadline()
|
||||
|
||||
var msg []byte
|
||||
if msg, err = receiver.Receive(); err != nil {
|
||||
break
|
||||
}
|
||||
if len(msg) < 1 {
|
||||
conn.Log("ignoring blank msg")
|
||||
continue
|
||||
}
|
||||
if err = conn.handleProtocolMsg(ProtocolTag(msg[0]), msg[1:]); err != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
conn.Shutdown(err)
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) handleProtocolMsg(tag ProtocolTag, payload []byte) error {
|
||||
switch tag {
|
||||
case ProtocolHeartbeat:
|
||||
case ProtocolReserved1, ProtocolReserved2, ProtocolReserved3, ProtocolOverlayControlMsg:
|
||||
conn.OverlayConn.ControlMessage(byte(tag), payload)
|
||||
case ProtocolGossipUnicast, ProtocolGossipBroadcast, ProtocolGossip:
|
||||
return conn.Router.handleGossip(tag, payload)
|
||||
default:
|
||||
conn.Log("ignoring unknown protocol tag:", tag)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (conn *LocalConnection) extendReadDeadline() {
|
||||
conn.TCPConn.SetReadDeadline(time.Now().Add(TCPHeartbeat * 2))
|
||||
}
|
||||
@@ -1,312 +0,0 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
InitialInterval = 2 * time.Second
|
||||
MaxInterval = 6 * time.Minute
|
||||
ResetAfter = 1 * time.Minute
|
||||
)
|
||||
|
||||
type peerAddrs map[string]*net.TCPAddr
|
||||
|
||||
type ConnectionMaker struct {
|
||||
ourself *LocalPeer
|
||||
peers *Peers
|
||||
port int
|
||||
discovery bool
|
||||
targets map[string]*Target
|
||||
connections map[Connection]struct{}
|
||||
directPeers peerAddrs
|
||||
actionChan chan<- ConnectionMakerAction
|
||||
}
|
||||
|
||||
type TargetState int
|
||||
|
||||
const (
|
||||
TargetWaiting TargetState = iota // we are waiting to connect there
|
||||
TargetAttempting // we are attempting to connect there
|
||||
TargetConnected // we are connected to there
|
||||
)
|
||||
|
||||
// Information about an address where we may find a peer
|
||||
type Target struct {
|
||||
state TargetState
|
||||
lastError error // reason for disconnection last time
|
||||
tryAfter time.Time // next time to try this address
|
||||
tryInterval time.Duration // retry delay on next failure
|
||||
}
|
||||
|
||||
type ConnectionMakerAction func() bool
|
||||
|
||||
func NewConnectionMaker(ourself *LocalPeer, peers *Peers, port int, discovery bool) *ConnectionMaker {
|
||||
actionChan := make(chan ConnectionMakerAction, ChannelSize)
|
||||
cm := &ConnectionMaker{
|
||||
ourself: ourself,
|
||||
peers: peers,
|
||||
port: port,
|
||||
discovery: discovery,
|
||||
directPeers: peerAddrs{},
|
||||
targets: make(map[string]*Target),
|
||||
connections: make(map[Connection]struct{}),
|
||||
actionChan: actionChan}
|
||||
go cm.queryLoop(actionChan)
|
||||
return cm
|
||||
}
|
||||
|
||||
func (cm *ConnectionMaker) InitiateConnections(peers []string, replace bool) []error {
|
||||
errors := []error{}
|
||||
addrs := peerAddrs{}
|
||||
for _, peer := range peers {
|
||||
host, port, err := net.SplitHostPort(peer)
|
||||
if err != nil {
|
||||
host = peer
|
||||
port = "0" // we use that as an indication that "no port was supplied"
|
||||
}
|
||||
if addr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%s", host, port)); err != nil {
|
||||
errors = append(errors, err)
|
||||
} else {
|
||||
addrs[peer] = addr
|
||||
}
|
||||
}
|
||||
cm.actionChan <- func() bool {
|
||||
if replace {
|
||||
cm.directPeers = peerAddrs{}
|
||||
}
|
||||
for peer, addr := range addrs {
|
||||
cm.directPeers[peer] = addr
|
||||
// curtail any existing reconnect interval
|
||||
if target, found := cm.targets[addr.String()]; found {
|
||||
target.nextTryNow()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
func (cm *ConnectionMaker) ForgetConnections(peers []string) {
|
||||
cm.actionChan <- func() bool {
|
||||
for _, peer := range peers {
|
||||
delete(cm.directPeers, peer)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *ConnectionMaker) ConnectionAborted(address string, err error) {
|
||||
cm.actionChan <- func() bool {
|
||||
target := cm.targets[address]
|
||||
target.state = TargetWaiting
|
||||
target.lastError = err
|
||||
target.nextTryLater()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *ConnectionMaker) ConnectionCreated(conn Connection) {
|
||||
cm.actionChan <- func() bool {
|
||||
cm.connections[conn] = void
|
||||
if conn.Outbound() {
|
||||
target := cm.targets[conn.RemoteTCPAddr()]
|
||||
target.state = TargetConnected
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *ConnectionMaker) ConnectionTerminated(conn Connection, err error) {
|
||||
cm.actionChan <- func() bool {
|
||||
delete(cm.connections, conn)
|
||||
if conn.Outbound() {
|
||||
target := cm.targets[conn.RemoteTCPAddr()]
|
||||
target.state = TargetWaiting
|
||||
target.lastError = err
|
||||
switch {
|
||||
case err == ErrConnectToSelf:
|
||||
target.nextTryNever()
|
||||
case time.Now().After(target.tryAfter.Add(ResetAfter)):
|
||||
target.nextTryNow()
|
||||
default:
|
||||
target.nextTryLater()
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *ConnectionMaker) Refresh() {
|
||||
cm.actionChan <- func() bool { return true }
|
||||
}
|
||||
|
||||
func (cm *ConnectionMaker) queryLoop(actionChan <-chan ConnectionMakerAction) {
|
||||
timer := time.NewTimer(MaxDuration)
|
||||
run := func() { timer.Reset(cm.checkStateAndAttemptConnections()) }
|
||||
for {
|
||||
select {
|
||||
case action := <-actionChan:
|
||||
if action() {
|
||||
run()
|
||||
}
|
||||
case <-timer.C:
|
||||
run()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cm *ConnectionMaker) checkStateAndAttemptConnections() time.Duration {
|
||||
var (
|
||||
validTarget = make(map[string]struct{})
|
||||
directTarget = make(map[string]struct{})
|
||||
)
|
||||
ourConnectedPeers, ourConnectedTargets, ourInboundIPs := cm.ourConnections()
|
||||
|
||||
addTarget := func(address string) {
|
||||
if _, connected := ourConnectedTargets[address]; connected {
|
||||
return
|
||||
}
|
||||
validTarget[address] = void
|
||||
if _, found := cm.targets[address]; found {
|
||||
return
|
||||
}
|
||||
target := &Target{state: TargetWaiting}
|
||||
target.nextTryNow()
|
||||
cm.targets[address] = target
|
||||
}
|
||||
|
||||
// Add direct targets that are not connected
|
||||
for _, addr := range cm.directPeers {
|
||||
completeAddr := *addr
|
||||
attempt := true
|
||||
if completeAddr.Port == 0 {
|
||||
completeAddr.Port = cm.port
|
||||
// If a peer was specified w/o a port, then we do not
|
||||
// attempt to connect to it if we have any inbound
|
||||
// connections from that IP.
|
||||
if _, connected := ourInboundIPs[completeAddr.IP.String()]; connected {
|
||||
attempt = false
|
||||
}
|
||||
}
|
||||
address := completeAddr.String()
|
||||
directTarget[address] = void
|
||||
if attempt {
|
||||
addTarget(address)
|
||||
}
|
||||
}
|
||||
|
||||
// Add targets for peers that someone else is connected to, but we
|
||||
// aren't
|
||||
if cm.discovery {
|
||||
cm.addPeerTargets(ourConnectedPeers, addTarget)
|
||||
}
|
||||
|
||||
return cm.connectToTargets(validTarget, directTarget)
|
||||
}
|
||||
|
||||
func (cm *ConnectionMaker) ourConnections() (PeerNameSet, map[string]struct{}, map[string]struct{}) {
|
||||
var (
|
||||
ourConnectedPeers = make(PeerNameSet)
|
||||
ourConnectedTargets = make(map[string]struct{})
|
||||
ourInboundIPs = make(map[string]struct{})
|
||||
)
|
||||
for conn := range cm.connections {
|
||||
address := conn.RemoteTCPAddr()
|
||||
ourConnectedPeers[conn.Remote().Name] = void
|
||||
ourConnectedTargets[address] = void
|
||||
if conn.Outbound() {
|
||||
continue
|
||||
}
|
||||
if ip, _, err := net.SplitHostPort(address); err == nil { // should always succeed
|
||||
ourInboundIPs[ip] = void
|
||||
}
|
||||
}
|
||||
return ourConnectedPeers, ourConnectedTargets, ourInboundIPs
|
||||
}
|
||||
|
||||
func (cm *ConnectionMaker) addPeerTargets(ourConnectedPeers PeerNameSet, addTarget func(string)) {
|
||||
cm.peers.ForEach(func(peer *Peer) {
|
||||
if peer == cm.ourself.Peer {
|
||||
return
|
||||
}
|
||||
// Modifying peer.connections requires a write lock on Peers,
|
||||
// and since we are holding a read lock (due to the ForEach),
|
||||
// access without locking the peer is safe.
|
||||
for otherPeer, conn := range peer.connections {
|
||||
if otherPeer == cm.ourself.Name {
|
||||
continue
|
||||
}
|
||||
if _, connected := ourConnectedPeers[otherPeer]; connected {
|
||||
continue
|
||||
}
|
||||
address := conn.RemoteTCPAddr()
|
||||
if conn.Outbound() {
|
||||
addTarget(address)
|
||||
} else if ip, _, err := net.SplitHostPort(address); err == nil {
|
||||
// There is no point connecting to the (likely
|
||||
// ephemeral) remote port of an inbound connection
|
||||
// that some peer has. Let's try to connect on the
|
||||
// weave port instead.
|
||||
addTarget(fmt.Sprintf("%s:%d", ip, cm.port))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func (cm *ConnectionMaker) connectToTargets(validTarget map[string]struct{}, directTarget map[string]struct{}) time.Duration {
|
||||
now := time.Now() // make sure we catch items just added
|
||||
after := MaxDuration
|
||||
for address, target := range cm.targets {
|
||||
if target.state != TargetWaiting {
|
||||
continue
|
||||
}
|
||||
if _, valid := validTarget[address]; !valid {
|
||||
delete(cm.targets, address)
|
||||
continue
|
||||
}
|
||||
if target.tryAfter.IsZero() {
|
||||
continue
|
||||
}
|
||||
switch duration := target.tryAfter.Sub(now); {
|
||||
case duration <= 0:
|
||||
target.state = TargetAttempting
|
||||
_, isCmdLineTarget := directTarget[address]
|
||||
go cm.attemptConnection(address, isCmdLineTarget)
|
||||
case duration < after:
|
||||
after = duration
|
||||
}
|
||||
}
|
||||
return after
|
||||
}
|
||||
|
||||
func (cm *ConnectionMaker) attemptConnection(address string, acceptNewPeer bool) {
|
||||
log.Printf("->[%s] attempting connection", address)
|
||||
if err := cm.ourself.CreateConnection(address, acceptNewPeer); err != nil {
|
||||
log.Errorf("->[%s] error during connection attempt: %v", address, err)
|
||||
cm.ConnectionAborted(address, err)
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Target) nextTryNever() {
|
||||
t.tryAfter = time.Time{}
|
||||
t.tryInterval = MaxInterval
|
||||
}
|
||||
|
||||
func (t *Target) nextTryNow() {
|
||||
t.tryAfter = time.Now()
|
||||
t.tryInterval = InitialInterval
|
||||
}
|
||||
|
||||
// The delay at the nth retry is a random value in the range
|
||||
// [i-i/2,i+i/2], where i = InitialInterval * 1.5^(n-1).
|
||||
func (t *Target) nextTryLater() {
|
||||
t.tryAfter = time.Now().Add(t.tryInterval/2 + time.Duration(rand.Int63n(int64(t.tryInterval))))
|
||||
t.tryInterval = t.tryInterval * 3 / 2
|
||||
if t.tryInterval > MaxInterval {
|
||||
t.tryInterval = MaxInterval
|
||||
}
|
||||
}
|
||||
-171
@@ -1,171 +0,0 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type GossipData interface {
|
||||
Encode() [][]byte
|
||||
Merge(GossipData)
|
||||
}
|
||||
|
||||
type Gossip interface {
|
||||
// specific message from one peer to another
|
||||
// intermediate peers relay it using unicast topology.
|
||||
GossipUnicast(dstPeerName PeerName, msg []byte) error
|
||||
// send gossip to every peer, relayed using broadcast topology.
|
||||
GossipBroadcast(update GossipData) error
|
||||
}
|
||||
|
||||
type Gossiper interface {
|
||||
OnGossipUnicast(sender PeerName, msg []byte) error
|
||||
// merge received data into state and return a representation of
|
||||
// the received data, for further propagation
|
||||
OnGossipBroadcast(sender PeerName, update []byte) (GossipData, error)
|
||||
// return state of everything we know; gets called periodically
|
||||
Gossip() GossipData
|
||||
// merge received data into state and return "everything new I've
|
||||
// just learnt", or nil if nothing in the received data was new
|
||||
OnGossip(update []byte) (GossipData, error)
|
||||
}
|
||||
|
||||
// Accumulates GossipData that needs to be sent to one destination,
|
||||
// and sends it when possible.
|
||||
type GossipSender struct {
|
||||
send func(GossipData)
|
||||
cell chan GossipData
|
||||
// for testing
|
||||
sent bool
|
||||
flushch chan chan bool
|
||||
}
|
||||
|
||||
func NewGossipSender(send func(GossipData)) *GossipSender {
|
||||
cell := make(chan GossipData, 1)
|
||||
flushch := make(chan chan bool)
|
||||
sender := &GossipSender{
|
||||
send: send,
|
||||
cell: cell,
|
||||
flushch: flushch,
|
||||
}
|
||||
go sender.run()
|
||||
return sender
|
||||
}
|
||||
|
||||
func (sender *GossipSender) run() {
|
||||
for {
|
||||
select {
|
||||
case pending := <-sender.cell:
|
||||
if pending == nil { // receive zero value when chan is closed
|
||||
return
|
||||
}
|
||||
sender.send(pending)
|
||||
sender.sent = true
|
||||
case ch := <-sender.flushch:
|
||||
// send anything pending, then reply back whether we sent
|
||||
// anything since previous flush
|
||||
select {
|
||||
case pending := <-sender.cell:
|
||||
sender.send(pending)
|
||||
sender.sent = true
|
||||
default:
|
||||
}
|
||||
ch <- sender.sent
|
||||
sender.sent = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sender *GossipSender) Send(data GossipData) {
|
||||
// NB: this must not be invoked concurrently
|
||||
select {
|
||||
case pending := <-sender.cell:
|
||||
pending.Merge(data)
|
||||
sender.cell <- pending
|
||||
default:
|
||||
sender.cell <- data
|
||||
}
|
||||
}
|
||||
|
||||
func (sender *GossipSender) Stop() {
|
||||
close(sender.cell)
|
||||
}
|
||||
|
||||
type GossipChannels map[string]*GossipChannel
|
||||
|
||||
func (router *Router) NewGossip(channelName string, g Gossiper) Gossip {
|
||||
channel := NewGossipChannel(channelName, router.Ourself, router.Routes, g)
|
||||
router.gossipLock.Lock()
|
||||
defer router.gossipLock.Unlock()
|
||||
if _, found := router.gossipChannels[channelName]; found {
|
||||
checkFatal(fmt.Errorf("[gossip] duplicate channel %s", channelName))
|
||||
}
|
||||
router.gossipChannels[channelName] = channel
|
||||
return channel
|
||||
}
|
||||
|
||||
func (router *Router) gossipChannel(channelName string) *GossipChannel {
|
||||
router.gossipLock.RLock()
|
||||
channel, found := router.gossipChannels[channelName]
|
||||
router.gossipLock.RUnlock()
|
||||
if found {
|
||||
return channel
|
||||
}
|
||||
router.gossipLock.Lock()
|
||||
defer router.gossipLock.Unlock()
|
||||
if channel, found = router.gossipChannels[channelName]; found {
|
||||
return channel
|
||||
}
|
||||
channel = NewGossipChannel(channelName, router.Ourself, router.Routes, &surrogateGossiper)
|
||||
channel.log("created surrogate channel")
|
||||
router.gossipChannels[channelName] = channel
|
||||
return channel
|
||||
}
|
||||
|
||||
func (router *Router) gossipChannelSet() map[*GossipChannel]struct{} {
|
||||
channels := make(map[*GossipChannel]struct{})
|
||||
router.gossipLock.RLock()
|
||||
defer router.gossipLock.RUnlock()
|
||||
for _, channel := range router.gossipChannels {
|
||||
channels[channel] = void
|
||||
}
|
||||
return channels
|
||||
}
|
||||
|
||||
func (router *Router) SendAllGossip() {
|
||||
for channel := range router.gossipChannelSet() {
|
||||
if gossip := channel.gossiper.Gossip(); gossip != nil {
|
||||
channel.Send(router.Ourself.Name, gossip)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (router *Router) SendAllGossipDown(conn Connection) {
|
||||
for channel := range router.gossipChannelSet() {
|
||||
if gossip := channel.gossiper.Gossip(); gossip != nil {
|
||||
channel.SendDown(conn, channel.gossiper.Gossip())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// for testing
|
||||
|
||||
func (router *Router) sendPendingGossip() bool {
|
||||
sentSomething := false
|
||||
for channel := range router.gossipChannelSet() {
|
||||
channel.Lock()
|
||||
for _, sender := range channel.senders {
|
||||
sentSomething = sender.flush() || sentSomething
|
||||
}
|
||||
for _, sender := range channel.broadcasters {
|
||||
sentSomething = sender.flush() || sentSomething
|
||||
}
|
||||
channel.Unlock()
|
||||
}
|
||||
return sentSomething
|
||||
}
|
||||
|
||||
func (sender *GossipSender) flush() bool {
|
||||
ch := make(chan bool)
|
||||
sender.flushch <- ch
|
||||
return <-ch
|
||||
}
|
||||
@@ -1,250 +0,0 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type GossipChannel struct {
|
||||
sync.Mutex
|
||||
name string
|
||||
ourself *LocalPeer
|
||||
routes *Routes
|
||||
gossiper Gossiper
|
||||
senders connectionSenders
|
||||
broadcasters peerSenders
|
||||
}
|
||||
|
||||
type connectionSenders map[Connection]*GossipSender
|
||||
type peerSenders map[PeerName]*GossipSender
|
||||
|
||||
func NewGossipChannel(channelName string, ourself *LocalPeer, routes *Routes, g Gossiper) *GossipChannel {
|
||||
return &GossipChannel{
|
||||
name: channelName,
|
||||
ourself: ourself,
|
||||
routes: routes,
|
||||
gossiper: g,
|
||||
senders: make(connectionSenders),
|
||||
broadcasters: make(peerSenders)}
|
||||
}
|
||||
|
||||
func (router *Router) handleGossip(tag ProtocolTag, payload []byte) error {
|
||||
decoder := gob.NewDecoder(bytes.NewReader(payload))
|
||||
var channelName string
|
||||
if err := decoder.Decode(&channelName); err != nil {
|
||||
return err
|
||||
}
|
||||
channel := router.gossipChannel(channelName)
|
||||
var srcName PeerName
|
||||
if err := decoder.Decode(&srcName); err != nil {
|
||||
return err
|
||||
}
|
||||
switch tag {
|
||||
case ProtocolGossipUnicast:
|
||||
return channel.deliverUnicast(srcName, payload, decoder)
|
||||
case ProtocolGossipBroadcast:
|
||||
return channel.deliverBroadcast(srcName, payload, decoder)
|
||||
case ProtocolGossip:
|
||||
return channel.deliver(srcName, payload, decoder)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *GossipChannel) deliverUnicast(srcName PeerName, origPayload []byte, dec *gob.Decoder) error {
|
||||
var destName PeerName
|
||||
if err := dec.Decode(&destName); err != nil {
|
||||
return err
|
||||
}
|
||||
if c.ourself.Name != destName {
|
||||
if err := c.relayUnicast(destName, origPayload); err != nil {
|
||||
// just log errors from relayUnicast; a problem between us and destination
|
||||
// is not enough reason to break the connection from the source
|
||||
c.log(err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
var payload []byte
|
||||
if err := dec.Decode(&payload); err != nil {
|
||||
return err
|
||||
}
|
||||
return c.gossiper.OnGossipUnicast(srcName, payload)
|
||||
}
|
||||
|
||||
func (c *GossipChannel) deliverBroadcast(srcName PeerName, _ []byte, dec *gob.Decoder) error {
|
||||
var payload []byte
|
||||
if err := dec.Decode(&payload); err != nil {
|
||||
return err
|
||||
}
|
||||
data, err := c.gossiper.OnGossipBroadcast(srcName, payload)
|
||||
if err != nil || data == nil {
|
||||
return err
|
||||
}
|
||||
return c.relayBroadcast(srcName, data)
|
||||
}
|
||||
|
||||
func (c *GossipChannel) deliver(srcName PeerName, _ []byte, dec *gob.Decoder) error {
|
||||
var payload []byte
|
||||
if err := dec.Decode(&payload); err != nil {
|
||||
return err
|
||||
}
|
||||
if data, err := c.gossiper.OnGossip(payload); err != nil {
|
||||
return err
|
||||
} else if data != nil {
|
||||
c.Send(srcName, data)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *GossipChannel) Send(srcName PeerName, data GossipData) {
|
||||
// do this outside the lock below so we avoid lock nesting
|
||||
c.routes.EnsureRecalculated()
|
||||
selectedConnections := make(ConnectionSet)
|
||||
for name := range c.routes.RandomNeighbours(srcName) {
|
||||
if conn, found := c.ourself.ConnectionTo(name); found {
|
||||
selectedConnections[conn] = void
|
||||
}
|
||||
}
|
||||
if len(selectedConnections) == 0 {
|
||||
return
|
||||
}
|
||||
connections := c.ourself.Connections()
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
c.gcSenders(connections)
|
||||
for conn := range selectedConnections {
|
||||
c.sendDown(conn, data)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *GossipChannel) SendDown(conn Connection, data GossipData) {
|
||||
connections := c.ourself.Connections()
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
c.gcSenders(connections)
|
||||
c.sendDown(conn, data)
|
||||
}
|
||||
|
||||
// GC - randomly (courtesy of go's map iterator) pick some existing
|
||||
// senders and stop&remove them if the associated connection is no
|
||||
// longer active. We stop as soon as we encounter a valid entry; the
|
||||
// idea being that when there is little or no garbage then this
|
||||
// executes close to O(1)[1], whereas when there is lots of garbage we
|
||||
// remove it quickly.
|
||||
//
|
||||
// [1] TODO Unfortunately, due to the desire to avoid nested locks,
|
||||
// instead of simply invoking LocalPeer.ConnectionTo(name), we pass in
|
||||
// the result of LocalPeer.Connections(). That is O(n_our_connections)
|
||||
// at best.
|
||||
func (c *GossipChannel) gcSenders(connections ConnectionSet) {
|
||||
for conn, sender := range c.senders {
|
||||
if _, found := connections[conn]; !found {
|
||||
delete(c.senders, conn)
|
||||
sender.Stop()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We have seen a couple of failures which suggest a >128GB slice was encountered.
|
||||
// 100MB should be enough for anyone.
|
||||
const maxFeasibleMessageLen = 100 * 1024 * 1024
|
||||
|
||||
func (c *GossipChannel) sendDown(conn Connection, data GossipData) {
|
||||
sender, found := c.senders[conn]
|
||||
if !found {
|
||||
sender = NewGossipSender(func(pending GossipData) {
|
||||
for _, msg := range pending.Encode() {
|
||||
if len(msg) > maxFeasibleMessageLen {
|
||||
panic(fmt.Sprintf("Gossip message too large: len=%d bytes; on channel '%s' from %+v", len(msg), c.name, pending))
|
||||
}
|
||||
protocolMsg := ProtocolMsg{ProtocolGossip, GobEncode(c.name, c.ourself.Name, msg)}
|
||||
conn.(ProtocolSender).SendProtocolMsg(protocolMsg)
|
||||
}
|
||||
})
|
||||
c.senders[conn] = sender
|
||||
}
|
||||
sender.Send(data)
|
||||
}
|
||||
|
||||
func (c *GossipChannel) GossipUnicast(dstPeerName PeerName, msg []byte) error {
|
||||
return c.relayUnicast(dstPeerName, GobEncode(c.name, c.ourself.Name, dstPeerName, msg))
|
||||
}
|
||||
|
||||
func (c *GossipChannel) GossipBroadcast(update GossipData) error {
|
||||
return c.relayBroadcast(c.ourself.Name, update)
|
||||
}
|
||||
|
||||
func (c *GossipChannel) relayUnicast(dstPeerName PeerName, buf []byte) (err error) {
|
||||
if relayPeerName, found := c.routes.UnicastAll(dstPeerName); !found {
|
||||
err = fmt.Errorf("unknown relay destination: %s", dstPeerName)
|
||||
} else if conn, found := c.ourself.ConnectionTo(relayPeerName); !found {
|
||||
err = fmt.Errorf("unable to find connection to relay peer %s", relayPeerName)
|
||||
} else {
|
||||
conn.(ProtocolSender).SendProtocolMsg(ProtocolMsg{ProtocolGossipUnicast, buf})
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *GossipChannel) relayBroadcast(srcName PeerName, update GossipData) error {
|
||||
names := c.routes.PeerNames() // do this outside the lock so they don't nest
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
// GC - randomly (courtesy of go's map iterator) pick some
|
||||
// existing broadcasters and stop&remove them if their source peer
|
||||
// is unknown. We stop as soon as we encounter a valid entry; the
|
||||
// idea being that when there is little or no garbage then this
|
||||
// executes close to O(1)[1], whereas when there is lots of
|
||||
// garbage we remove it quickly.
|
||||
//
|
||||
// [1] TODO Unfortunately, due to the desire to avoid nested
|
||||
// locks, instead of simply invoking Peers.Fetch(name) below, we
|
||||
// have that Peers.Names() invocation above. That is O(n_peers) at
|
||||
// best.
|
||||
for name, broadcaster := range c.broadcasters {
|
||||
if _, found := names[name]; !found {
|
||||
delete(c.broadcasters, name)
|
||||
broadcaster.Stop()
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
broadcaster, found := c.broadcasters[srcName]
|
||||
if !found {
|
||||
broadcaster = NewGossipSender(func(pending GossipData) { c.sendBroadcast(srcName, pending) })
|
||||
c.broadcasters[srcName] = broadcaster
|
||||
}
|
||||
broadcaster.Send(update)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *GossipChannel) sendBroadcast(srcName PeerName, update GossipData) {
|
||||
c.routes.EnsureRecalculated()
|
||||
nextHops := c.routes.BroadcastAll(srcName)
|
||||
if len(nextHops) == 0 {
|
||||
return
|
||||
}
|
||||
connections := c.ourself.ConnectionsTo(nextHops)
|
||||
for _, msg := range update.Encode() {
|
||||
protocolMsg := ProtocolMsg{ProtocolGossipBroadcast, GobEncode(c.name, srcName, msg)}
|
||||
// FIXME a single blocked connection can stall us
|
||||
for _, conn := range connections {
|
||||
conn.(ProtocolSender).SendProtocolMsg(protocolMsg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *GossipChannel) log(args ...interface{}) {
|
||||
log.Println(append(append([]interface{}{}, "[gossip "+c.name+"]:"), args...)...)
|
||||
}
|
||||
|
||||
func GobEncode(items ...interface{}) []byte {
|
||||
buf := new(bytes.Buffer)
|
||||
enc := gob.NewEncoder(buf)
|
||||
for _, i := range items {
|
||||
checkFatal(enc.Encode(i))
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
@@ -1,259 +0,0 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
)
|
||||
|
||||
// TODO test gossip unicast; atm we only test topology gossip and
|
||||
// surrogates, neither of which employ unicast.
|
||||
|
||||
type mockChannelConnection struct {
|
||||
RemoteConnection
|
||||
dest *Router
|
||||
}
|
||||
|
||||
func NewTestRouter(name string) *Router {
|
||||
peerName, _ := PeerNameFromString(name)
|
||||
router := NewRouter(Config{}, peerName, "nick", nil)
|
||||
router.Start()
|
||||
return router
|
||||
}
|
||||
|
||||
func (conn *mockChannelConnection) SendProtocolMsg(protocolMsg ProtocolMsg) {
|
||||
if err := conn.dest.handleGossip(protocolMsg.tag, protocolMsg.msg); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func sendPendingGossip(routers ...*Router) {
|
||||
// Loop until all routers report they didn't send anything
|
||||
for sentSomething := true; sentSomething; {
|
||||
sentSomething = false
|
||||
for _, router := range routers {
|
||||
sentSomething = router.sendPendingGossip() || sentSomething
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (router *Router) AddTestChannelConnection(r *Router) {
|
||||
fromPeer := NewPeerFrom(router.Ourself.Peer)
|
||||
toPeer := NewPeerFrom(r.Ourself.Peer)
|
||||
|
||||
r.Peers.FetchWithDefault(fromPeer) // Has side-effect of incrementing refcount
|
||||
router.Peers.FetchWithDefault(toPeer) //
|
||||
|
||||
conn := &mockChannelConnection{RemoteConnection{router.Ourself.Peer, toPeer, "", false, true}, r}
|
||||
router.Ourself.handleAddConnection(conn)
|
||||
router.Ourself.handleConnectionEstablished(conn)
|
||||
}
|
||||
|
||||
func (router *Router) DeleteTestChannelConnection(r *Router) {
|
||||
fromName := router.Ourself.Peer.Name
|
||||
toName := r.Ourself.Peer.Name
|
||||
|
||||
fromPeer := r.Peers.Fetch(fromName)
|
||||
toPeer := router.Peers.Fetch(toName)
|
||||
|
||||
r.Peers.Dereference(fromPeer)
|
||||
router.Peers.Dereference(toPeer)
|
||||
|
||||
conn, _ := router.Ourself.ConnectionTo(toName)
|
||||
router.Ourself.handleDeleteConnection(conn)
|
||||
}
|
||||
|
||||
func TestGossipTopology(t *testing.T) {
|
||||
wt.RunWithTimeout(t, 5*time.Second, func() {
|
||||
implTestGossipTopology(t)
|
||||
})
|
||||
}
|
||||
|
||||
// Create a Peer representing the receiver router, with connections to
|
||||
// the routers supplied as arguments, carrying across all UID and
|
||||
// version information.
|
||||
func (router *Router) tp(routers ...*Router) *Peer {
|
||||
peer := NewPeerFrom(router.Ourself.Peer)
|
||||
connections := make(map[PeerName]Connection)
|
||||
for _, r := range routers {
|
||||
p := NewPeerFrom(r.Ourself.Peer)
|
||||
connections[r.Ourself.Peer.Name] = newMockConnection(peer, p)
|
||||
}
|
||||
peer.Version = router.Ourself.Peer.Version
|
||||
peer.connections = connections
|
||||
return peer
|
||||
}
|
||||
|
||||
// Check that the topology of router matches the peers and all of their connections
|
||||
func checkTopology(t *testing.T, router *Router, wantedPeers ...*Peer) {
|
||||
router.Peers.RLock()
|
||||
checkTopologyPeers(t, true, router.Peers.allPeers(), wantedPeers...)
|
||||
router.Peers.RUnlock()
|
||||
}
|
||||
|
||||
func implTestGossipTopology(t *testing.T) {
|
||||
// Create some peers that will talk to each other
|
||||
r1 := NewTestRouter("01:00:00:01:00:00")
|
||||
r2 := NewTestRouter("02:00:00:02:00:00")
|
||||
r3 := NewTestRouter("03:00:00:03:00:00")
|
||||
|
||||
// Check state when they have no connections
|
||||
checkTopology(t, r1, r1.tp())
|
||||
checkTopology(t, r2, r2.tp())
|
||||
|
||||
// Now try adding some connections
|
||||
r1.AddTestChannelConnection(r2)
|
||||
sendPendingGossip(r1, r2)
|
||||
checkTopology(t, r1, r1.tp(r2), r2.tp())
|
||||
checkTopology(t, r2, r1.tp(r2), r2.tp())
|
||||
r2.AddTestChannelConnection(r1)
|
||||
sendPendingGossip(r1, r2)
|
||||
checkTopology(t, r1, r1.tp(r2), r2.tp(r1))
|
||||
checkTopology(t, r2, r1.tp(r2), r2.tp(r1))
|
||||
|
||||
// Currently, the connection from 2 to 3 is one-way only
|
||||
r2.AddTestChannelConnection(r3)
|
||||
sendPendingGossip(r1, r2, r3)
|
||||
checkTopology(t, r1, r1.tp(r2), r2.tp(r1, r3), r3.tp())
|
||||
checkTopology(t, r2, r1.tp(r2), r2.tp(r1, r3), r3.tp())
|
||||
// When r2 gossiped to r3, 1 was unreachable from r3 so it got removed from the
|
||||
// list of peers, but remains referenced in the connection from 1 to 3.
|
||||
checkTopology(t, r3, r2.tp(r1, r3), r3.tp())
|
||||
|
||||
// Add a connection from 3 to 1 and now r1 is reachable.
|
||||
r3.AddTestChannelConnection(r1)
|
||||
sendPendingGossip(r1, r2, r3)
|
||||
checkTopology(t, r1, r1.tp(r2), r2.tp(r1, r3), r3.tp(r1))
|
||||
checkTopology(t, r2, r1.tp(r2), r2.tp(r1, r3), r3.tp(r1))
|
||||
checkTopology(t, r3, r1.tp(), r2.tp(r1, r3), r3.tp(r1))
|
||||
|
||||
r1.AddTestChannelConnection(r3)
|
||||
sendPendingGossip(r1, r2, r3)
|
||||
checkTopology(t, r1, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1))
|
||||
checkTopology(t, r2, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1))
|
||||
checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1, r3), r3.tp(r1))
|
||||
|
||||
// Drop the connection from 2 to 3
|
||||
r2.DeleteTestChannelConnection(r3)
|
||||
sendPendingGossip(r1, r2, r3)
|
||||
checkTopology(t, r1, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1))
|
||||
checkTopology(t, r2, r1.tp(r2, r3), r2.tp(r1))
|
||||
checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1))
|
||||
|
||||
// Drop the connection from 1 to 3
|
||||
r1.DeleteTestChannelConnection(r3)
|
||||
sendPendingGossip(r1, r2, r3)
|
||||
checkTopology(t, r1, r1.tp(r2), r2.tp(r1), r3.tp(r1))
|
||||
|
||||
checkTopology(t, r1, r1.tp(r2), r2.tp(r1), r3.tp(r1))
|
||||
checkTopology(t, r2, r1.tp(r2), r2.tp(r1))
|
||||
// r3 still thinks r1 has a connection to it
|
||||
checkTopology(t, r3, r1.tp(r2, r3), r2.tp(r1), r3.tp(r1))
|
||||
|
||||
// On a timer, r3 will gossip to r1
|
||||
r3.SendAllGossip()
|
||||
sendPendingGossip(r1, r2, r3)
|
||||
checkTopology(t, r1, r1.tp(r2), r2.tp(r1), r3.tp(r1))
|
||||
}
|
||||
|
||||
func TestGossipSurrogate(t *testing.T) {
|
||||
// create the topology r1 <-> r2 <-> r3
|
||||
r1 := NewTestRouter("01:00:00:01:00:00")
|
||||
r2 := NewTestRouter("02:00:00:02:00:00")
|
||||
r3 := NewTestRouter("03:00:00:03:00:00")
|
||||
r1.AddTestChannelConnection(r2)
|
||||
r2.AddTestChannelConnection(r1)
|
||||
r3.AddTestChannelConnection(r2)
|
||||
r2.AddTestChannelConnection(r3)
|
||||
sendPendingGossip(r1, r2, r3)
|
||||
checkTopology(t, r1, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2))
|
||||
checkTopology(t, r2, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2))
|
||||
checkTopology(t, r3, r1.tp(r2), r2.tp(r1, r3), r3.tp(r2))
|
||||
|
||||
// create a gossiper at either end, but not the middle
|
||||
g1 := newTestGossiper()
|
||||
g3 := newTestGossiper()
|
||||
s1 := r1.NewGossip("Test", g1)
|
||||
s3 := r3.NewGossip("Test", g3)
|
||||
|
||||
// broadcast a message from each end, check it reaches the other
|
||||
broadcast(s1, 1)
|
||||
broadcast(s3, 2)
|
||||
sendPendingGossip(r1, r2, r3)
|
||||
g1.checkHas(t, 2)
|
||||
g3.checkHas(t, 1)
|
||||
|
||||
// check that each end gets their message back through periodic
|
||||
// gossip
|
||||
r1.SendAllGossip()
|
||||
r3.SendAllGossip()
|
||||
sendPendingGossip(r1, r2, r3)
|
||||
g1.checkHas(t, 1, 2)
|
||||
g3.checkHas(t, 1, 2)
|
||||
}
|
||||
|
||||
type testGossiper struct {
|
||||
sync.RWMutex
|
||||
state map[byte]struct{}
|
||||
}
|
||||
|
||||
func newTestGossiper() *testGossiper {
|
||||
return &testGossiper{state: make(map[byte]struct{})}
|
||||
}
|
||||
|
||||
func (g *testGossiper) OnGossipUnicast(sender PeerName, msg []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *testGossiper) OnGossipBroadcast(_ PeerName, update []byte) (GossipData, error) {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
for _, v := range update {
|
||||
g.state[v] = void
|
||||
}
|
||||
return NewSurrogateGossipData(update), nil
|
||||
}
|
||||
|
||||
func (g *testGossiper) Gossip() GossipData {
|
||||
g.RLock()
|
||||
defer g.RUnlock()
|
||||
state := make([]byte, len(g.state))
|
||||
for v := range g.state {
|
||||
state = append(state, v)
|
||||
}
|
||||
return NewSurrogateGossipData(state)
|
||||
}
|
||||
|
||||
func (g *testGossiper) OnGossip(update []byte) (GossipData, error) {
|
||||
g.Lock()
|
||||
defer g.Unlock()
|
||||
var delta []byte
|
||||
for _, v := range update {
|
||||
if _, found := g.state[v]; !found {
|
||||
delta = append(delta, v)
|
||||
g.state[v] = void
|
||||
}
|
||||
}
|
||||
if len(delta) > 0 {
|
||||
return NewSurrogateGossipData(delta), nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (g *testGossiper) checkHas(t *testing.T, vs ...byte) {
|
||||
g.RLock()
|
||||
defer g.RUnlock()
|
||||
for _, v := range vs {
|
||||
if _, found := g.state[v]; !found {
|
||||
require.FailNow(t, fmt.Sprintf("%d is missing", v))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func broadcast(s Gossip, v byte) {
|
||||
s.GossipBroadcast(NewSurrogateGossipData([]byte{v}))
|
||||
}
|
||||
@@ -1,261 +0,0 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type LocalPeer struct {
|
||||
sync.RWMutex
|
||||
*Peer
|
||||
router *Router
|
||||
actionChan chan<- LocalPeerAction
|
||||
}
|
||||
|
||||
type LocalPeerAction func()
|
||||
|
||||
func NewLocalPeer(name PeerName, nickName string, router *Router) *LocalPeer {
|
||||
actionChan := make(chan LocalPeerAction, ChannelSize)
|
||||
peer := &LocalPeer{
|
||||
Peer: NewPeer(name, nickName, randomPeerUID(), 0, randomPeerShortID()),
|
||||
router: router,
|
||||
actionChan: actionChan,
|
||||
}
|
||||
go peer.actorLoop(actionChan)
|
||||
return peer
|
||||
}
|
||||
|
||||
func (peer *LocalPeer) Connections() ConnectionSet {
|
||||
connections := make(ConnectionSet)
|
||||
peer.RLock()
|
||||
defer peer.RUnlock()
|
||||
for _, conn := range peer.connections {
|
||||
connections[conn] = void
|
||||
}
|
||||
return connections
|
||||
}
|
||||
|
||||
func (peer *LocalPeer) ConnectionTo(name PeerName) (Connection, bool) {
|
||||
peer.RLock()
|
||||
defer peer.RUnlock()
|
||||
conn, found := peer.connections[name]
|
||||
return conn, found // yes, you really can't inline that. FFS.
|
||||
}
|
||||
|
||||
func (peer *LocalPeer) ConnectionsTo(names []PeerName) []Connection {
|
||||
conns := make([]Connection, 0, len(names))
|
||||
peer.RLock()
|
||||
defer peer.RUnlock()
|
||||
for _, name := range names {
|
||||
conn, found := peer.connections[name]
|
||||
// Again, !found could just be due to a race.
|
||||
if found {
|
||||
conns = append(conns, conn)
|
||||
}
|
||||
}
|
||||
return conns
|
||||
}
|
||||
|
||||
func (peer *LocalPeer) CreateConnection(peerAddr string, acceptNewPeer bool) error {
|
||||
if err := peer.checkConnectionLimit(); err != nil {
|
||||
return err
|
||||
}
|
||||
tcpAddr, err := net.ResolveTCPAddr("tcp4", peerAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tcpConn, err := net.DialTCP("tcp4", nil, tcpAddr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
connRemote := NewRemoteConnection(peer.Peer, nil, tcpConn.RemoteAddr().String(), true, false)
|
||||
StartLocalConnection(connRemote, tcpConn, peer.router, acceptNewPeer)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ACTOR client API
|
||||
|
||||
// Sync.
|
||||
func (peer *LocalPeer) AddConnection(conn *LocalConnection) error {
|
||||
resultChan := make(chan error)
|
||||
peer.actionChan <- func() {
|
||||
resultChan <- peer.handleAddConnection(conn)
|
||||
}
|
||||
return <-resultChan
|
||||
}
|
||||
|
||||
// Async.
|
||||
func (peer *LocalPeer) ConnectionEstablished(conn *LocalConnection) {
|
||||
peer.actionChan <- func() {
|
||||
peer.handleConnectionEstablished(conn)
|
||||
}
|
||||
}
|
||||
|
||||
// Sync.
|
||||
func (peer *LocalPeer) DeleteConnection(conn *LocalConnection) {
|
||||
resultChan := make(chan interface{})
|
||||
peer.actionChan <- func() {
|
||||
peer.handleDeleteConnection(conn)
|
||||
resultChan <- nil
|
||||
}
|
||||
<-resultChan
|
||||
}
|
||||
|
||||
// ACTOR server
|
||||
|
||||
func (peer *LocalPeer) actorLoop(actionChan <-chan LocalPeerAction) {
|
||||
gossipTimer := time.Tick(GossipInterval)
|
||||
for {
|
||||
select {
|
||||
case action := <-actionChan:
|
||||
action()
|
||||
case <-gossipTimer:
|
||||
peer.router.SendAllGossip()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (peer *LocalPeer) handleAddConnection(conn Connection) error {
|
||||
if peer.Peer != conn.Local() {
|
||||
log.Fatal("Attempt made to add connection to peer where peer is not the source of connection")
|
||||
}
|
||||
if conn.Remote() == nil {
|
||||
log.Fatal("Attempt made to add connection to peer with unknown remote peer")
|
||||
}
|
||||
toName := conn.Remote().Name
|
||||
dupErr := fmt.Errorf("Multiple connections to %s added to %s", conn.Remote(), peer.String())
|
||||
// deliberately non symmetrical
|
||||
if dupConn, found := peer.connections[toName]; found {
|
||||
if dupConn == conn {
|
||||
return nil
|
||||
}
|
||||
switch conn.BreakTie(dupConn) {
|
||||
case TieBreakWon:
|
||||
dupConn.Shutdown(dupErr)
|
||||
peer.handleDeleteConnection(dupConn)
|
||||
case TieBreakLost:
|
||||
return dupErr
|
||||
case TieBreakTied:
|
||||
// oh good grief. Sod it, just kill both of them.
|
||||
dupConn.Shutdown(dupErr)
|
||||
peer.handleDeleteConnection(dupConn)
|
||||
return dupErr
|
||||
}
|
||||
}
|
||||
if err := peer.checkConnectionLimit(); err != nil {
|
||||
return err
|
||||
}
|
||||
_, isConnectedPeer := peer.router.Routes.Unicast(toName)
|
||||
peer.addConnection(conn)
|
||||
if isConnectedPeer {
|
||||
conn.Log("connection added")
|
||||
} else {
|
||||
conn.Log("connection added (new peer)")
|
||||
peer.router.SendAllGossipDown(conn)
|
||||
}
|
||||
|
||||
peer.router.Routes.Recalculate()
|
||||
peer.broadcastPeerUpdate(conn.Remote())
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (peer *LocalPeer) handleConnectionEstablished(conn Connection) {
|
||||
if peer.Peer != conn.Local() {
|
||||
log.Fatal("Peer informed of active connection where peer is not the source of connection")
|
||||
}
|
||||
if dupConn, found := peer.connections[conn.Remote().Name]; !found || conn != dupConn {
|
||||
conn.Shutdown(fmt.Errorf("Cannot set unknown connection active"))
|
||||
return
|
||||
}
|
||||
peer.connectionEstablished(conn)
|
||||
conn.Log("connection fully established")
|
||||
|
||||
peer.router.Routes.Recalculate()
|
||||
peer.broadcastPeerUpdate()
|
||||
}
|
||||
|
||||
func (peer *LocalPeer) handleDeleteConnection(conn Connection) {
|
||||
if peer.Peer != conn.Local() {
|
||||
log.Fatal("Attempt made to delete connection from peer where peer is not the source of connection")
|
||||
}
|
||||
if conn.Remote() == nil {
|
||||
log.Fatal("Attempt made to delete connection to peer with unknown remote peer")
|
||||
}
|
||||
toName := conn.Remote().Name
|
||||
if connFound, found := peer.connections[toName]; !found || connFound != conn {
|
||||
return
|
||||
}
|
||||
peer.deleteConnection(conn)
|
||||
conn.Log("connection deleted")
|
||||
// Must do garbage collection first to ensure we don't send out an
|
||||
// update with unreachable peers (can cause looping)
|
||||
peer.router.Peers.GarbageCollect()
|
||||
peer.router.Routes.Recalculate()
|
||||
peer.broadcastPeerUpdate()
|
||||
}
|
||||
|
||||
// helpers
|
||||
|
||||
func (peer *LocalPeer) broadcastPeerUpdate(peers ...*Peer) {
|
||||
// Some tests run without a router. This should be fixed so
|
||||
// that the relevant part of Router can be easily run in the
|
||||
// context of a test, but that will involve significant
|
||||
// reworking of tests.
|
||||
if peer.router != nil {
|
||||
peer.router.BroadcastTopologyUpdate(append(peers, peer.Peer))
|
||||
}
|
||||
}
|
||||
|
||||
func (peer *LocalPeer) checkConnectionLimit() error {
|
||||
limit := peer.router.ConnLimit
|
||||
if 0 != limit && peer.connectionCount() >= limit {
|
||||
return fmt.Errorf("Connection limit reached (%v)", limit)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (peer *LocalPeer) addConnection(conn Connection) {
|
||||
peer.Lock()
|
||||
defer peer.Unlock()
|
||||
peer.connections[conn.Remote().Name] = conn
|
||||
peer.Version++
|
||||
}
|
||||
|
||||
func (peer *LocalPeer) deleteConnection(conn Connection) {
|
||||
peer.Lock()
|
||||
defer peer.Unlock()
|
||||
delete(peer.connections, conn.Remote().Name)
|
||||
peer.Version++
|
||||
}
|
||||
|
||||
func (peer *LocalPeer) connectionEstablished(conn Connection) {
|
||||
peer.Lock()
|
||||
defer peer.Unlock()
|
||||
peer.Version++
|
||||
}
|
||||
|
||||
func (peer *LocalPeer) connectionCount() int {
|
||||
peer.RLock()
|
||||
defer peer.RUnlock()
|
||||
return len(peer.connections)
|
||||
}
|
||||
|
||||
func (peer *LocalPeer) setShortID(shortID PeerShortID) {
|
||||
peer.Lock()
|
||||
defer peer.Unlock()
|
||||
peer.ShortID = shortID
|
||||
peer.Version++
|
||||
}
|
||||
|
||||
func (peer *LocalPeer) setVersionBeyond(version uint64) bool {
|
||||
peer.Lock()
|
||||
defer peer.Unlock()
|
||||
if version >= peer.Version {
|
||||
peer.Version = version + 1
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
// No mocks are tested by this file.
|
||||
//
|
||||
// It supplies some mock implementations to other unit tests, and is
|
||||
// named "...test.go" so it is only compiled under `go test`.
|
||||
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// Add to peers a connection from peers.ourself to p
|
||||
func (peers *Peers) AddTestConnection(p *Peer) {
|
||||
summary := p.PeerSummary
|
||||
summary.Version = 0
|
||||
toPeer := NewPeerFromSummary(summary)
|
||||
peers.FetchWithDefault(toPeer) // Has side-effect of incrementing refcount
|
||||
conn := newMockConnection(peers.ourself.Peer, toPeer)
|
||||
peers.ourself.addConnection(conn)
|
||||
peers.ourself.connectionEstablished(conn)
|
||||
}
|
||||
|
||||
// Add to peers a connection from p1 to p2
|
||||
func (peers *Peers) AddTestRemoteConnection(p1, p2 *Peer) {
|
||||
fromPeer := NewPeerFrom(p1)
|
||||
fromPeer = peers.FetchWithDefault(fromPeer)
|
||||
toPeer := NewPeerFrom(p2)
|
||||
toPeer = peers.FetchWithDefault(toPeer)
|
||||
peers.ourself.addConnection(&RemoteConnection{fromPeer, toPeer, "", false, false})
|
||||
}
|
||||
|
||||
func (peers *Peers) DeleteTestConnection(p *Peer) {
|
||||
toName := p.Name
|
||||
toPeer := peers.Fetch(toName)
|
||||
peers.Dereference(toPeer)
|
||||
conn, _ := peers.ourself.ConnectionTo(toName)
|
||||
peers.ourself.deleteConnection(conn)
|
||||
}
|
||||
|
||||
// mockConnection used in testing is very similar to a
|
||||
// RemoteConnection, without the RemoteTCPAddr(). We are making it a
|
||||
// separate type in order to distinguish what is created by the test
|
||||
// from what is created by the real code.
|
||||
func newMockConnection(from, to *Peer) Connection {
|
||||
type mockConnection struct{ RemoteConnection }
|
||||
return &mockConnection{RemoteConnection{from, to, "", false, false}}
|
||||
}
|
||||
|
||||
func checkEqualConns(t *testing.T, ourName PeerName, got, wanted map[PeerName]Connection) {
|
||||
checkConns := make(PeerNameSet)
|
||||
for _, conn := range wanted {
|
||||
checkConns[conn.Remote().Name] = void
|
||||
}
|
||||
for _, conn := range got {
|
||||
remoteName := conn.Remote().Name
|
||||
if _, found := checkConns[remoteName]; found {
|
||||
delete(checkConns, remoteName)
|
||||
} else {
|
||||
require.FailNow(t, fmt.Sprintf("Unexpected connection from %s to %s", ourName, remoteName))
|
||||
}
|
||||
}
|
||||
if len(checkConns) > 0 {
|
||||
require.FailNow(t, fmt.Sprintf("Expected connections not found: from %s to %v", ourName, checkConns))
|
||||
}
|
||||
}
|
||||
|
||||
// Get all the peers from a Peers in a slice
|
||||
func (peers *Peers) allPeers() []*Peer {
|
||||
var res []*Peer
|
||||
for _, peer := range peers.byName {
|
||||
res = append(res, peer)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (peers *Peers) allPeersExcept(excludeName PeerName) []*Peer {
|
||||
res := peers.allPeers()
|
||||
for i, peer := range res {
|
||||
if peer.Name == excludeName {
|
||||
return append(res[:i], res[i+1:]...)
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Check that the peers slice matches the wanted peers
|
||||
func checkPeerArray(t *testing.T, peers []*Peer, wantedPeers ...*Peer) {
|
||||
checkTopologyPeers(t, false, peers, wantedPeers...)
|
||||
}
|
||||
|
||||
// Check that the peers slice matches the wanted peers and optionally
|
||||
// all of their connections
|
||||
func checkTopologyPeers(t *testing.T, checkConns bool, peers []*Peer, wantedPeers ...*Peer) {
|
||||
check := make(map[PeerName]*Peer)
|
||||
for _, peer := range wantedPeers {
|
||||
check[peer.Name] = peer
|
||||
}
|
||||
for _, peer := range peers {
|
||||
name := peer.Name
|
||||
if wantedPeer, found := check[name]; found {
|
||||
if checkConns {
|
||||
checkEqualConns(t, name, peer.connections, wantedPeer.connections)
|
||||
}
|
||||
delete(check, name)
|
||||
} else {
|
||||
require.FailNow(t, fmt.Sprintf("Unexpected peer: %s", name))
|
||||
}
|
||||
}
|
||||
if len(check) > 0 {
|
||||
require.FailNow(t, fmt.Sprintf("Expected peers not found: %v", check))
|
||||
}
|
||||
}
|
||||
-116
@@ -1,116 +0,0 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
type Overlay interface {
|
||||
// Enhance a features map with overlay-related features
|
||||
AddFeaturesTo(map[string]string)
|
||||
|
||||
// Prepare on overlay connection. The connection should remain
|
||||
// passive until it has been Confirm()ed.
|
||||
PrepareConnection(OverlayConnectionParams) (OverlayConnection, error)
|
||||
|
||||
// Obtain diagnostic information specific to the overlay
|
||||
Diagnostics() interface{}
|
||||
}
|
||||
|
||||
type OverlayConnectionParams struct {
|
||||
RemotePeer *Peer
|
||||
|
||||
// The local address of the corresponding TCP connection. Used to
|
||||
// derive the local IP address for sending. May differ for
|
||||
// different overlay connections.
|
||||
LocalAddr *net.TCPAddr
|
||||
|
||||
// The remote address of the corresponding TCP connection. Used to
|
||||
// determine the address to send to, but only if the TCP
|
||||
// connection is outbound. Otherwise the Overlay needs to discover
|
||||
// it (e.g. from incoming datagrams).
|
||||
RemoteAddr *net.TCPAddr
|
||||
|
||||
// Is the corresponding TCP connection outbound?
|
||||
Outbound bool
|
||||
|
||||
// Unique identifier for this connection
|
||||
ConnUID uint64
|
||||
|
||||
// Session key, if connection is encrypted; nil otherwise.
|
||||
//
|
||||
// NB: overlay connections must take care not to use nonces which
|
||||
// may collide with those of the main connection. These nonces are
|
||||
// 192 bits, with the top most bit unspecified, the next bit set
|
||||
// to 1, followed by 126 zero bits, and a message sequence number
|
||||
// in the lowest 64 bits.
|
||||
SessionKey *[32]byte
|
||||
|
||||
// Function to send a control message to the counterpart
|
||||
// overlay connection.
|
||||
SendControlMessage func(tag byte, msg []byte) error
|
||||
|
||||
// Features passed at connection initiation
|
||||
Features map[string]string
|
||||
}
|
||||
|
||||
// All of the machinery to manage overlay connectivity to a particular
|
||||
// peer
|
||||
type OverlayConnection interface {
|
||||
// Confirm that the connection is really wanted, and so the
|
||||
// Overlay should begin heartbeats etc. to verify the operation of
|
||||
// the overlay connection.
|
||||
Confirm()
|
||||
|
||||
// A channel indicating that the overlay connection is
|
||||
// established, i.e. its operation has been confirmed.
|
||||
EstablishedChannel() <-chan struct{}
|
||||
|
||||
// A channel indicating an error from the overlay connection. The
|
||||
// overlay connection is not expected to be operational after the
|
||||
// first error, so the channel only needs to buffer a single
|
||||
// error.
|
||||
ErrorChannel() <-chan error
|
||||
|
||||
Stop()
|
||||
|
||||
// Handle a message from the peer. 'tag' exists for
|
||||
// compatibility, and should always be
|
||||
// ProtocolOverlayControlMessage for non-sleeve overlays.
|
||||
ControlMessage(tag byte, msg []byte)
|
||||
|
||||
// User facing overlay name
|
||||
DisplayName() string
|
||||
}
|
||||
|
||||
type NullOverlay struct{}
|
||||
|
||||
func (NullOverlay) AddFeaturesTo(map[string]string) {
|
||||
}
|
||||
|
||||
func (NullOverlay) PrepareConnection(OverlayConnectionParams) (OverlayConnection, error) {
|
||||
return NullOverlay{}, nil
|
||||
}
|
||||
|
||||
func (NullOverlay) Diagnostics() interface{} {
|
||||
return nil
|
||||
}
|
||||
func (NullOverlay) Confirm() {
|
||||
}
|
||||
|
||||
func (NullOverlay) EstablishedChannel() <-chan struct{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (NullOverlay) ErrorChannel() <-chan error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (NullOverlay) Stop() {
|
||||
}
|
||||
|
||||
func (NullOverlay) ControlMessage(byte, []byte) {
|
||||
}
|
||||
|
||||
func (NullOverlay) DisplayName() string {
|
||||
return "null"
|
||||
}
|
||||
-173
@@ -1,173 +0,0 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func randBytes(n int) []byte {
|
||||
buf := make([]byte, n)
|
||||
_, err := rand.Read(buf)
|
||||
checkFatal(err)
|
||||
return buf
|
||||
}
|
||||
|
||||
func randUint64() (r uint64) {
|
||||
return binary.LittleEndian.Uint64(randBytes(8))
|
||||
}
|
||||
|
||||
func randUint16() (r uint16) {
|
||||
return binary.LittleEndian.Uint16(randBytes(2))
|
||||
}
|
||||
|
||||
type PeerUID uint64
|
||||
|
||||
func randomPeerUID() PeerUID {
|
||||
return PeerUID(randUint64())
|
||||
}
|
||||
|
||||
func ParsePeerUID(s string) (PeerUID, error) {
|
||||
uid, err := strconv.ParseUint(s, 10, 64)
|
||||
return PeerUID(uid), err
|
||||
}
|
||||
|
||||
// Short IDs exist for the sake of fast datapath. They are 12 bits,
|
||||
// randomly assigned, but we detect and recover from collisions. This
|
||||
// does limit us to 4096 peers, but that should be sufficient for a
|
||||
// while.
|
||||
type PeerShortID uint16
|
||||
|
||||
const PeerShortIDBits = 12
|
||||
|
||||
func randomPeerShortID() PeerShortID {
|
||||
return PeerShortID(randUint16() & (1<<PeerShortIDBits - 1))
|
||||
}
|
||||
|
||||
type PeerSummary struct {
|
||||
NameByte []byte
|
||||
NickName string
|
||||
UID PeerUID
|
||||
Version uint64
|
||||
ShortID PeerShortID
|
||||
HasShortID bool
|
||||
}
|
||||
|
||||
type Peer struct {
|
||||
Name PeerName
|
||||
PeerSummary
|
||||
localRefCount uint64 // maintained by Peers
|
||||
connections map[PeerName]Connection
|
||||
}
|
||||
|
||||
type ListOfPeers []*Peer
|
||||
|
||||
func (lop ListOfPeers) Len() int {
|
||||
return len(lop)
|
||||
}
|
||||
func (lop ListOfPeers) Swap(i, j int) {
|
||||
lop[i], lop[j] = lop[j], lop[i]
|
||||
}
|
||||
func (lop ListOfPeers) Less(i, j int) bool {
|
||||
return lop[i].Name < lop[j].Name
|
||||
}
|
||||
|
||||
type ConnectionSet map[Connection]struct{}
|
||||
|
||||
func NewPeerFromSummary(summary PeerSummary) *Peer {
|
||||
return &Peer{
|
||||
Name: PeerNameFromBin(summary.NameByte),
|
||||
PeerSummary: summary,
|
||||
connections: make(map[PeerName]Connection),
|
||||
}
|
||||
}
|
||||
|
||||
func NewPeer(name PeerName, nickName string, uid PeerUID, version uint64, shortID PeerShortID) *Peer {
|
||||
return NewPeerFromSummary(PeerSummary{
|
||||
NameByte: name.Bin(),
|
||||
NickName: nickName,
|
||||
UID: uid,
|
||||
Version: version,
|
||||
ShortID: shortID,
|
||||
HasShortID: true,
|
||||
})
|
||||
}
|
||||
|
||||
func NewPeerFrom(peer *Peer) *Peer {
|
||||
return NewPeerFromSummary(peer.PeerSummary)
|
||||
}
|
||||
|
||||
func (peer *Peer) String() string {
|
||||
return fmt.Sprint(peer.Name, "(", peer.NickName, ")")
|
||||
}
|
||||
|
||||
// Calculate the routing table from this peer to all peers reachable
|
||||
// from it, returning a "next hop" map of PeerNameX -> PeerNameY,
|
||||
// which says "in order to send a message to X, the peer should send
|
||||
// the message to its neighbour Y".
|
||||
//
|
||||
// Because currently we do not have weightings on the connections
|
||||
// between peers, there is no need to use a minimum spanning tree
|
||||
// algorithm. Instead we employ the simpler and cheaper breadth-first
|
||||
// widening. The computation is deterministic, which ensures that when
|
||||
// it is performed on the same data by different peers, they get the
|
||||
// same result. This is important since otherwise we risk message loss
|
||||
// or routing cycles.
|
||||
//
|
||||
// When the 'establishedAndSymmetric' flag is set, only connections
|
||||
// that are marked as 'established' and are symmetric (i.e. where both
|
||||
// sides indicate they have a connection to the other) are considered.
|
||||
//
|
||||
// When a non-nil stopAt peer is supplied, the widening stops when it
|
||||
// reaches that peer. The boolean return indicates whether that has
|
||||
// happened.
|
||||
//
|
||||
// NB: This function should generally be invoked while holding a read
|
||||
// lock on Peers and LocalPeer.
|
||||
func (peer *Peer) Routes(stopAt *Peer, establishedAndSymmetric bool) (bool, map[PeerName]PeerName) {
|
||||
routes := make(unicastRoutes)
|
||||
routes[peer.Name] = UnknownPeerName
|
||||
nextWorklist := []*Peer{peer}
|
||||
for len(nextWorklist) > 0 {
|
||||
worklist := nextWorklist
|
||||
sort.Sort(ListOfPeers(worklist))
|
||||
nextWorklist = []*Peer{}
|
||||
for _, curPeer := range worklist {
|
||||
if curPeer == stopAt {
|
||||
return true, routes
|
||||
}
|
||||
curPeer.ForEachConnectedPeer(establishedAndSymmetric, routes,
|
||||
func(remotePeer *Peer) {
|
||||
nextWorklist = append(nextWorklist, remotePeer)
|
||||
remoteName := remotePeer.Name
|
||||
// We now know how to get to remoteName: the same
|
||||
// way we get to curPeer. Except, if curPeer is
|
||||
// the starting peer in which case we know we can
|
||||
// reach remoteName directly.
|
||||
if curPeer == peer {
|
||||
routes[remoteName] = remoteName
|
||||
} else {
|
||||
routes[remoteName] = routes[curPeer.Name]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
return false, routes
|
||||
}
|
||||
|
||||
func (peer *Peer) ForEachConnectedPeer(establishedAndSymmetric bool, exclude map[PeerName]PeerName, f func(*Peer)) {
|
||||
for remoteName, conn := range peer.connections {
|
||||
if establishedAndSymmetric && !conn.Established() {
|
||||
continue
|
||||
}
|
||||
if _, found := exclude[remoteName]; found {
|
||||
continue
|
||||
}
|
||||
remotePeer := conn.Remote()
|
||||
if remoteConn, found := remotePeer.connections[peer.Name]; !establishedAndSymmetric || (found && remoteConn.Established()) {
|
||||
f(remotePeer)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
// +build peer_name_hash
|
||||
|
||||
// Let peer names be sha256 hashes...of anything (as long as it's
|
||||
// unique).
|
||||
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
)
|
||||
|
||||
type PeerName string
|
||||
|
||||
const (
|
||||
PeerNameFlavour = "hash"
|
||||
NameSize = sha256.Size >> 1
|
||||
UnknownPeerName = PeerName("")
|
||||
)
|
||||
|
||||
func PeerNameFromUserInput(userInput string) (PeerName, error) {
|
||||
// fixed-length identity
|
||||
nameByteAry := sha256.Sum256([]byte(userInput))
|
||||
return PeerNameFromBin(nameByteAry[:NameSize]), nil
|
||||
}
|
||||
|
||||
func PeerNameFromString(nameStr string) (PeerName, error) {
|
||||
if _, err := hex.DecodeString(nameStr); err != nil {
|
||||
return UnknownPeerName, err
|
||||
}
|
||||
return PeerName(nameStr), nil
|
||||
}
|
||||
|
||||
func PeerNameFromBin(nameByte []byte) PeerName {
|
||||
return PeerName(hex.EncodeToString(nameByte))
|
||||
}
|
||||
|
||||
func (name PeerName) Bin() []byte {
|
||||
res, err := hex.DecodeString(string(name))
|
||||
checkFatal(err)
|
||||
return res
|
||||
}
|
||||
|
||||
func (name PeerName) String() string {
|
||||
return string(name)
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
// The !peer_name_alternative effectively makes this the default,
|
||||
// i.e. to choose an alternative, run "go build -tags
|
||||
// 'peer_name_alternative peer_name_hash'"
|
||||
|
||||
// +build peer_name_mac !peer_name_alternative
|
||||
|
||||
// Let peer names be MACs...
|
||||
//
|
||||
// MACs need to be unique across our network, or bad things will
|
||||
// happen anyway. So they make pretty good candidates for peer
|
||||
// names. And doing so is pretty efficient both computationally and
|
||||
// network overhead wise.
|
||||
//
|
||||
// Note that we do not mandate *what* MAC should be used as the peer
|
||||
// name. In particular it doesn't actually have to be the MAC of, say,
|
||||
// the network interface the peer is sniffing on.
|
||||
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"net"
|
||||
)
|
||||
|
||||
// PeerName is used as a map key. Since net.HardwareAddr isn't
|
||||
// suitable for that - it's a slice, and slices can't be map keys - we
|
||||
// convert that to/from uint64.
|
||||
type PeerName uint64
|
||||
|
||||
const (
|
||||
PeerNameFlavour = "mac"
|
||||
NameSize = 6
|
||||
UnknownPeerName = PeerName(0)
|
||||
)
|
||||
|
||||
func PeerNameFromUserInput(userInput string) (PeerName, error) {
|
||||
res, err := PeerNameFromString(userInput)
|
||||
return res, err
|
||||
}
|
||||
|
||||
func PeerNameFromString(nameStr string) (PeerName, error) {
|
||||
mac, err := net.ParseMAC(nameStr)
|
||||
if err != nil {
|
||||
return UnknownPeerName, err
|
||||
}
|
||||
return PeerName(macint(mac)), nil
|
||||
}
|
||||
|
||||
func PeerNameFromBin(nameByte []byte) PeerName {
|
||||
return PeerName(macint(net.HardwareAddr(nameByte)))
|
||||
}
|
||||
|
||||
func (name PeerName) Bin() []byte {
|
||||
return intmac(uint64(name))
|
||||
}
|
||||
|
||||
func (name PeerName) String() string {
|
||||
return intmac(uint64(name)).String()
|
||||
}
|
||||
|
||||
func macint(mac net.HardwareAddr) (r uint64) {
|
||||
for _, b := range mac {
|
||||
r <<= 8
|
||||
r |= uint64(b)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func intmac(key uint64) (r net.HardwareAddr) {
|
||||
r = make([]byte, 6)
|
||||
for i := 5; i >= 0; i-- {
|
||||
r[i] = byte(key)
|
||||
key >>= 8
|
||||
}
|
||||
return
|
||||
}
|
||||
-538
@@ -1,538 +0,0 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var void = struct{}{}
|
||||
|
||||
type Peers struct {
|
||||
sync.RWMutex
|
||||
ourself *LocalPeer
|
||||
byName map[PeerName]*Peer
|
||||
byShortID map[PeerShortID]ShortIDPeers
|
||||
onGC []func(*Peer)
|
||||
|
||||
// Called when the mapping from short ids to peers changes
|
||||
onInvalidateShortIDs []func()
|
||||
}
|
||||
|
||||
type ShortIDPeers struct {
|
||||
// If we know about a single peer with the short id, this is
|
||||
// that peer. If there is a collision, this is the peer with
|
||||
// the lowest Name.
|
||||
peer *Peer
|
||||
|
||||
// In case of a collision, this holds the other peers.
|
||||
others []*Peer
|
||||
}
|
||||
|
||||
type UnknownPeerError struct {
|
||||
Name PeerName
|
||||
}
|
||||
|
||||
func (upe UnknownPeerError) Error() string {
|
||||
return fmt.Sprint("Reference to unknown peer ", upe.Name)
|
||||
}
|
||||
|
||||
type PeerNameSet map[PeerName]struct{}
|
||||
|
||||
type ConnectionSummary struct {
|
||||
NameByte []byte
|
||||
RemoteTCPAddr string
|
||||
Outbound bool
|
||||
Established bool
|
||||
}
|
||||
|
||||
// Pending notifications due to changes to Peers that need to be sent
|
||||
// out once the Peers is unlocked.
|
||||
type PeersPendingNotifications struct {
|
||||
// Peers that have been GCed
|
||||
removed []*Peer
|
||||
|
||||
// The mapping from shorts ids to peers changed
|
||||
invalidateShortIDs bool
|
||||
|
||||
// The local short ID needs reassigning due to a collision
|
||||
reassignLocalShortID bool
|
||||
|
||||
// The local peer was modified
|
||||
localPeerModified bool
|
||||
}
|
||||
|
||||
func NewPeers(ourself *LocalPeer) *Peers {
|
||||
peers := &Peers{
|
||||
ourself: ourself,
|
||||
byName: make(map[PeerName]*Peer),
|
||||
byShortID: make(map[PeerShortID]ShortIDPeers),
|
||||
}
|
||||
peers.FetchWithDefault(ourself.Peer)
|
||||
return peers
|
||||
}
|
||||
|
||||
func (peers *Peers) OnGC(callback func(*Peer)) {
|
||||
peers.Lock()
|
||||
defer peers.Unlock()
|
||||
|
||||
// Although the array underlying peers.onGC might be accessed
|
||||
// without holding the lock in unlockAndNotify, we don't
|
||||
// support removing callbacks, so a simple append here is
|
||||
// safe.
|
||||
peers.onGC = append(peers.onGC, callback)
|
||||
}
|
||||
|
||||
func (peers *Peers) OnInvalidateShortIDs(callback func()) {
|
||||
peers.Lock()
|
||||
defer peers.Unlock()
|
||||
|
||||
// Safe, as in OnGC
|
||||
peers.onInvalidateShortIDs = append(peers.onInvalidateShortIDs, callback)
|
||||
}
|
||||
|
||||
func (peers *Peers) unlockAndNotify(pending *PeersPendingNotifications) {
|
||||
broadcastLocalPeer := (pending.reassignLocalShortID && peers.reassignLocalShortID(pending)) ||
|
||||
pending.localPeerModified
|
||||
onGC := peers.onGC
|
||||
onInvalidateShortIDs := peers.onInvalidateShortIDs
|
||||
peers.Unlock()
|
||||
|
||||
if pending.removed != nil {
|
||||
for _, callback := range onGC {
|
||||
for _, peer := range pending.removed {
|
||||
callback(peer)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if pending.invalidateShortIDs {
|
||||
for _, callback := range onInvalidateShortIDs {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
if broadcastLocalPeer {
|
||||
peers.ourself.broadcastPeerUpdate()
|
||||
}
|
||||
}
|
||||
|
||||
func (peers *Peers) addByShortID(peer *Peer, pending *PeersPendingNotifications) {
|
||||
if !peer.HasShortID {
|
||||
return
|
||||
}
|
||||
|
||||
entry, ok := peers.byShortID[peer.ShortID]
|
||||
if !ok {
|
||||
entry = ShortIDPeers{peer: peer}
|
||||
} else if entry.peer == nil {
|
||||
// This short ID is free, but was used in the past.
|
||||
// Because we are reusing it, it's an invalidation
|
||||
// event.
|
||||
entry.peer = peer
|
||||
pending.invalidateShortIDs = true
|
||||
} else if peer.Name < entry.peer.Name {
|
||||
// Short ID collision, this peer becomes the principal
|
||||
// peer for the short ID, bumping the previous one
|
||||
// into others.
|
||||
|
||||
if entry.peer == peers.ourself.Peer {
|
||||
// The bumped peer is peers.ourself, so we
|
||||
// need to look for a new short id
|
||||
pending.reassignLocalShortID = true
|
||||
}
|
||||
|
||||
entry.others = append(entry.others, entry.peer)
|
||||
entry.peer = peer
|
||||
pending.invalidateShortIDs = true
|
||||
} else {
|
||||
// Short ID collision, this peer is secondary
|
||||
entry.others = append(entry.others, peer)
|
||||
}
|
||||
|
||||
peers.byShortID[peer.ShortID] = entry
|
||||
}
|
||||
|
||||
func (peers *Peers) deleteByShortID(peer *Peer, pending *PeersPendingNotifications) {
|
||||
if !peer.HasShortID {
|
||||
return
|
||||
}
|
||||
|
||||
entry := peers.byShortID[peer.ShortID]
|
||||
var otherIndex int
|
||||
|
||||
if peer != entry.peer {
|
||||
// peer is secondary, find its index in others
|
||||
otherIndex = -1
|
||||
|
||||
for i, other := range entry.others {
|
||||
if peer == other {
|
||||
otherIndex = i
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if otherIndex < 0 {
|
||||
return
|
||||
}
|
||||
} else if len(entry.others) != 0 {
|
||||
// need to find the peer with the lowest name to
|
||||
// become the new principal one
|
||||
otherIndex = 0
|
||||
minName := entry.others[0].Name
|
||||
|
||||
for i := 1; i < len(entry.others); i++ {
|
||||
otherName := entry.others[i].Name
|
||||
if otherName < minName {
|
||||
minName = otherName
|
||||
otherIndex = i
|
||||
}
|
||||
}
|
||||
|
||||
entry.peer = entry.others[otherIndex]
|
||||
pending.invalidateShortIDs = true
|
||||
} else {
|
||||
// This is the last peer with the short id. We clear
|
||||
// the entry, don't delete it, in order to detect when
|
||||
// it gets re-used.
|
||||
peers.byShortID[peer.ShortID] = ShortIDPeers{}
|
||||
return
|
||||
}
|
||||
|
||||
entry.others[otherIndex] = entry.others[len(entry.others)-1]
|
||||
entry.others = entry.others[:len(entry.others)-1]
|
||||
peers.byShortID[peer.ShortID] = entry
|
||||
}
|
||||
|
||||
func (peers *Peers) reassignLocalShortID(pending *PeersPendingNotifications) bool {
|
||||
newShortID, ok := peers.chooseShortID()
|
||||
if ok {
|
||||
peers.setLocalShortID(newShortID, pending)
|
||||
return true
|
||||
}
|
||||
|
||||
// Otherwise we'll try again later on in garbageColleect
|
||||
return false
|
||||
}
|
||||
|
||||
func (peers *Peers) setLocalShortID(newShortID PeerShortID, pending *PeersPendingNotifications) {
|
||||
peers.deleteByShortID(peers.ourself.Peer, pending)
|
||||
peers.ourself.setShortID(newShortID)
|
||||
peers.addByShortID(peers.ourself.Peer, pending)
|
||||
}
|
||||
|
||||
// Choose an available short id at random
|
||||
func (peers *Peers) chooseShortID() (PeerShortID, bool) {
|
||||
rng := rand.New(rand.NewSource(int64(randUint64())))
|
||||
|
||||
// First, just try picking some short ids at random, and
|
||||
// seeing if they are available:
|
||||
for i := 0; i < 10; i++ {
|
||||
shortID := PeerShortID(rng.Intn(1 << PeerShortIDBits))
|
||||
if peers.byShortID[shortID].peer == nil {
|
||||
return shortID, true
|
||||
}
|
||||
}
|
||||
|
||||
// Looks like most short ids are used. So count the number of
|
||||
// unused ones, and pick one at random.
|
||||
available := int(1 << PeerShortIDBits)
|
||||
for _, entry := range peers.byShortID {
|
||||
if entry.peer != nil {
|
||||
available--
|
||||
}
|
||||
}
|
||||
|
||||
if available == 0 {
|
||||
// All short ids are used.
|
||||
return 0, false
|
||||
}
|
||||
|
||||
n := rng.Intn(available)
|
||||
var i PeerShortID
|
||||
for {
|
||||
if peers.byShortID[i].peer == nil {
|
||||
if n == 0 {
|
||||
return PeerShortID(i), true
|
||||
}
|
||||
|
||||
n--
|
||||
}
|
||||
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
func (peers *Peers) FetchWithDefault(peer *Peer) *Peer {
|
||||
peers.Lock()
|
||||
var pending PeersPendingNotifications
|
||||
defer peers.unlockAndNotify(&pending)
|
||||
|
||||
if existingPeer, found := peers.byName[peer.Name]; found {
|
||||
existingPeer.localRefCount++
|
||||
return existingPeer
|
||||
}
|
||||
|
||||
peers.byName[peer.Name] = peer
|
||||
peers.addByShortID(peer, &pending)
|
||||
peer.localRefCount++
|
||||
return peer
|
||||
}
|
||||
|
||||
func (peers *Peers) Fetch(name PeerName) *Peer {
|
||||
peers.RLock()
|
||||
defer peers.RUnlock()
|
||||
return peers.byName[name]
|
||||
}
|
||||
|
||||
func (peers *Peers) FetchAndAddRef(name PeerName) *Peer {
|
||||
peers.Lock()
|
||||
defer peers.Unlock()
|
||||
peer := peers.byName[name]
|
||||
if peer != nil {
|
||||
peer.localRefCount++
|
||||
}
|
||||
return peer
|
||||
}
|
||||
|
||||
func (peers *Peers) FetchByShortID(shortID PeerShortID) *Peer {
|
||||
peers.RLock()
|
||||
defer peers.RUnlock()
|
||||
return peers.byShortID[shortID].peer
|
||||
}
|
||||
|
||||
func (peers *Peers) Dereference(peer *Peer) {
|
||||
peers.Lock()
|
||||
defer peers.Unlock()
|
||||
peer.localRefCount--
|
||||
}
|
||||
|
||||
func (peers *Peers) ForEach(fun func(*Peer)) {
|
||||
peers.RLock()
|
||||
defer peers.RUnlock()
|
||||
for _, peer := range peers.byName {
|
||||
fun(peer)
|
||||
}
|
||||
}
|
||||
|
||||
// Merge an incoming update with our own topology.
|
||||
//
|
||||
// We add peers hitherto unknown to us, and update peers for which the
|
||||
// update contains a more recent version than known to us. The return
|
||||
// value is a) a representation of the received update, and b) an
|
||||
// "improved" update containing just these new/updated elements.
|
||||
func (peers *Peers) ApplyUpdate(update []byte) (PeerNameSet, PeerNameSet, error) {
|
||||
peers.Lock()
|
||||
var pending PeersPendingNotifications
|
||||
defer peers.unlockAndNotify(&pending)
|
||||
|
||||
newPeers, decodedUpdate, decodedConns, err := peers.decodeUpdate(update)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
// By this point, we know the update doesn't refer to any peers we
|
||||
// have no knowledge of. We can now apply the update. Start by
|
||||
// adding in any new peers.
|
||||
for name, newPeer := range newPeers {
|
||||
peers.byName[name] = newPeer
|
||||
peers.addByShortID(newPeer, &pending)
|
||||
}
|
||||
|
||||
// Now apply the updates
|
||||
newUpdate := peers.applyUpdate(decodedUpdate, decodedConns, &pending)
|
||||
peers.garbageCollect(&pending)
|
||||
for _, peerRemoved := range pending.removed {
|
||||
delete(newUpdate, peerRemoved.Name)
|
||||
}
|
||||
|
||||
updateNames := make(PeerNameSet)
|
||||
for _, peer := range decodedUpdate {
|
||||
updateNames[peer.Name] = void
|
||||
}
|
||||
|
||||
return updateNames, newUpdate, nil
|
||||
}
|
||||
|
||||
func (peers *Peers) Names() PeerNameSet {
|
||||
peers.RLock()
|
||||
defer peers.RUnlock()
|
||||
|
||||
names := make(PeerNameSet)
|
||||
for name := range peers.byName {
|
||||
names[name] = void
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func (peers *Peers) EncodePeers(names PeerNameSet) []byte {
|
||||
buf := new(bytes.Buffer)
|
||||
enc := gob.NewEncoder(buf)
|
||||
peers.RLock()
|
||||
defer peers.RUnlock()
|
||||
for name := range names {
|
||||
if peer, found := peers.byName[name]; found {
|
||||
if peer == peers.ourself.Peer {
|
||||
peers.ourself.Encode(enc)
|
||||
} else {
|
||||
peer.Encode(enc)
|
||||
}
|
||||
}
|
||||
}
|
||||
return buf.Bytes()
|
||||
}
|
||||
|
||||
func (peers *Peers) GarbageCollect() {
|
||||
peers.Lock()
|
||||
var pending PeersPendingNotifications
|
||||
defer peers.unlockAndNotify(&pending)
|
||||
|
||||
peers.garbageCollect(&pending)
|
||||
}
|
||||
|
||||
func (peers *Peers) garbageCollect(pending *PeersPendingNotifications) {
|
||||
peers.ourself.RLock()
|
||||
_, reached := peers.ourself.Routes(nil, false)
|
||||
peers.ourself.RUnlock()
|
||||
|
||||
for name, peer := range peers.byName {
|
||||
if _, found := reached[peer.Name]; !found && peer.localRefCount == 0 {
|
||||
delete(peers.byName, name)
|
||||
peers.deleteByShortID(peer, pending)
|
||||
pending.removed = append(pending.removed, peer)
|
||||
}
|
||||
}
|
||||
|
||||
if peers.byShortID[peers.ourself.ShortID].peer != peers.ourself.Peer {
|
||||
// The local peer doesn't own its short id. Garbage
|
||||
// collection might have freed some up, so try to
|
||||
// reassign.
|
||||
pending.reassignLocalShortID = true
|
||||
}
|
||||
}
|
||||
|
||||
func (peers *Peers) decodeUpdate(update []byte) (newPeers map[PeerName]*Peer, decodedUpdate []*Peer, decodedConns [][]ConnectionSummary, err error) {
|
||||
newPeers = make(map[PeerName]*Peer)
|
||||
decodedUpdate = []*Peer{}
|
||||
decodedConns = [][]ConnectionSummary{}
|
||||
|
||||
decoder := gob.NewDecoder(bytes.NewReader(update))
|
||||
|
||||
for {
|
||||
peerSummary, connSummaries, decErr := decodePeer(decoder)
|
||||
if decErr == io.EOF {
|
||||
break
|
||||
} else if decErr != nil {
|
||||
err = decErr
|
||||
return
|
||||
}
|
||||
newPeer := NewPeerFromSummary(peerSummary)
|
||||
decodedUpdate = append(decodedUpdate, newPeer)
|
||||
decodedConns = append(decodedConns, connSummaries)
|
||||
if _, found := peers.byName[newPeer.Name]; !found {
|
||||
newPeers[newPeer.Name] = newPeer
|
||||
}
|
||||
}
|
||||
|
||||
for _, connSummaries := range decodedConns {
|
||||
for _, connSummary := range connSummaries {
|
||||
remoteName := PeerNameFromBin(connSummary.NameByte)
|
||||
if _, found := newPeers[remoteName]; found {
|
||||
continue
|
||||
}
|
||||
if _, found := peers.byName[remoteName]; found {
|
||||
continue
|
||||
}
|
||||
// Update refers to a peer which we have no knowledge
|
||||
// of. Thus we can't apply the update. Abort.
|
||||
err = UnknownPeerError{remoteName}
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (peers *Peers) applyUpdate(decodedUpdate []*Peer, decodedConns [][]ConnectionSummary, pending *PeersPendingNotifications) PeerNameSet {
|
||||
newUpdate := make(PeerNameSet)
|
||||
for idx, newPeer := range decodedUpdate {
|
||||
connSummaries := decodedConns[idx]
|
||||
name := newPeer.Name
|
||||
// guaranteed to find peer in the peers.byName
|
||||
switch peer := peers.byName[name]; peer {
|
||||
case peers.ourself.Peer:
|
||||
if newPeer.UID != peer.UID {
|
||||
// The update contains information about an old
|
||||
// incarnation of ourselves. We increase our version
|
||||
// number beyond that which we received, so our
|
||||
// information supersedes the old one when it is
|
||||
// received by other peers.
|
||||
pending.localPeerModified = peers.ourself.setVersionBeyond(newPeer.Version)
|
||||
}
|
||||
case newPeer:
|
||||
peer.connections = makeConnsMap(peer, connSummaries, peers.byName)
|
||||
newUpdate[name] = void
|
||||
default: // existing peer
|
||||
if newPeer.Version < peer.Version ||
|
||||
(newPeer.Version == peer.Version && newPeer.UID <= peer.UID) {
|
||||
continue
|
||||
}
|
||||
peer.Version = newPeer.Version
|
||||
peer.UID = newPeer.UID
|
||||
peer.connections = makeConnsMap(peer, connSummaries, peers.byName)
|
||||
|
||||
if newPeer.ShortID != peer.ShortID {
|
||||
peers.deleteByShortID(peer, pending)
|
||||
peer.ShortID = newPeer.ShortID
|
||||
peers.addByShortID(peer, pending)
|
||||
}
|
||||
newUpdate[name] = void
|
||||
}
|
||||
}
|
||||
return newUpdate
|
||||
}
|
||||
|
||||
func (peer *Peer) Encode(enc *gob.Encoder) {
|
||||
checkFatal(enc.Encode(peer.PeerSummary))
|
||||
|
||||
connSummaries := []ConnectionSummary{}
|
||||
for _, conn := range peer.connections {
|
||||
connSummaries = append(connSummaries, ConnectionSummary{
|
||||
conn.Remote().NameByte,
|
||||
conn.RemoteTCPAddr(),
|
||||
conn.Outbound(),
|
||||
conn.Established(),
|
||||
})
|
||||
}
|
||||
|
||||
checkFatal(enc.Encode(connSummaries))
|
||||
}
|
||||
|
||||
func (peer *LocalPeer) Encode(enc *gob.Encoder) {
|
||||
peer.RLock()
|
||||
defer peer.RUnlock()
|
||||
peer.Peer.Encode(enc)
|
||||
}
|
||||
|
||||
func decodePeer(dec *gob.Decoder) (peerSummary PeerSummary, connSummaries []ConnectionSummary, err error) {
|
||||
if err = dec.Decode(&peerSummary); err != nil {
|
||||
return
|
||||
}
|
||||
if err = dec.Decode(&connSummaries); err != nil {
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func makeConnsMap(peer *Peer, connSummaries []ConnectionSummary, byName map[PeerName]*Peer) map[PeerName]Connection {
|
||||
conns := make(map[PeerName]Connection)
|
||||
for _, connSummary := range connSummaries {
|
||||
name := PeerNameFromBin(connSummary.NameByte)
|
||||
remotePeer := byName[name]
|
||||
conn := NewRemoteConnection(peer, remotePeer, connSummary.RemoteTCPAddr, connSummary.Outbound, connSummary.Established)
|
||||
conns[name] = conn
|
||||
}
|
||||
return conns
|
||||
}
|
||||
@@ -1,376 +0,0 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// TODO we should also test:
|
||||
//
|
||||
// - applying an incremental update, including the case where that
|
||||
// leads to an UnknownPeerError
|
||||
//
|
||||
// - the "improved update" calculation
|
||||
//
|
||||
// - non-gc of peers that are only referenced locally
|
||||
|
||||
func newNode(name PeerName) (*Peer, *Peers) {
|
||||
peer := NewLocalPeer(name, "", nil)
|
||||
peers := NewPeers(peer)
|
||||
return peer.Peer, peers
|
||||
}
|
||||
|
||||
// Check that ApplyUpdate copies the whole topology from peers
|
||||
func checkApplyUpdate(t *testing.T, peers *Peers) {
|
||||
dummyName, _ := PeerNameFromString("99:00:00:01:00:00")
|
||||
// We need a new node outside of the network, with a connection
|
||||
// into it.
|
||||
_, testBedPeers := newNode(dummyName)
|
||||
testBedPeers.AddTestConnection(peers.ourself.Peer)
|
||||
testBedPeers.ApplyUpdate(peers.EncodePeers(peers.Names()))
|
||||
|
||||
checkTopologyPeers(t, true, testBedPeers.allPeersExcept(dummyName), peers.allPeers()...)
|
||||
}
|
||||
|
||||
func TestPeersEncoding(t *testing.T) {
|
||||
const numNodes = 20
|
||||
const numIters = 1000
|
||||
var peer [numNodes]*Peer
|
||||
var ps [numNodes]*Peers
|
||||
for i := 0; i < numNodes; i++ {
|
||||
name, _ := PeerNameFromString(fmt.Sprintf("%02d:00:00:01:00:00", i))
|
||||
peer[i], ps[i] = newNode(name)
|
||||
}
|
||||
|
||||
var conns []struct{ from, to int }
|
||||
for i := 0; i < numIters; i++ {
|
||||
oper := rand.Intn(2)
|
||||
switch oper {
|
||||
case 0:
|
||||
from, to := rand.Intn(numNodes), rand.Intn(numNodes)
|
||||
if from != to {
|
||||
if _, found := peer[from].connections[peer[to].Name]; !found {
|
||||
ps[from].AddTestConnection(peer[to])
|
||||
conns = append(conns, struct{ from, to int }{from, to})
|
||||
checkApplyUpdate(t, ps[from])
|
||||
}
|
||||
}
|
||||
case 1:
|
||||
if len(conns) > 0 {
|
||||
n := rand.Intn(len(conns))
|
||||
c := conns[n]
|
||||
ps[c.from].DeleteTestConnection(peer[c.to])
|
||||
ps[c.from].GarbageCollect()
|
||||
checkApplyUpdate(t, ps[c.from])
|
||||
conns = append(conns[:n], conns[n+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func garbageCollect(peers *Peers) []*Peer {
|
||||
var removed []*Peer
|
||||
peers.OnGC(func(peer *Peer) { removed = append(removed, peer) })
|
||||
peers.GarbageCollect()
|
||||
return removed
|
||||
}
|
||||
|
||||
func TestPeersGarbageCollection(t *testing.T) {
|
||||
const (
|
||||
peer1NameString = "01:00:00:01:00:00"
|
||||
peer2NameString = "02:00:00:02:00:00"
|
||||
peer3NameString = "03:00:00:03:00:00"
|
||||
)
|
||||
var (
|
||||
peer1Name, _ = PeerNameFromString(peer1NameString)
|
||||
peer2Name, _ = PeerNameFromString(peer2NameString)
|
||||
peer3Name, _ = PeerNameFromString(peer3NameString)
|
||||
)
|
||||
|
||||
// Create some peers with some connections to each other
|
||||
p1, ps1 := newNode(peer1Name)
|
||||
p2, ps2 := newNode(peer2Name)
|
||||
p3, ps3 := newNode(peer3Name)
|
||||
ps1.AddTestConnection(p2)
|
||||
ps2.AddTestRemoteConnection(p1, p2)
|
||||
ps2.AddTestConnection(p1)
|
||||
ps2.AddTestConnection(p3)
|
||||
ps3.AddTestConnection(p1)
|
||||
ps1.AddTestConnection(p3)
|
||||
ps2.AddTestRemoteConnection(p1, p3)
|
||||
ps2.AddTestRemoteConnection(p3, p1)
|
||||
|
||||
// Every peer is referenced, so nothing should be dropped
|
||||
require.Empty(t, garbageCollect(ps1), "peers removed")
|
||||
require.Empty(t, garbageCollect(ps2), "peers removed")
|
||||
require.Empty(t, garbageCollect(ps3), "peers removed")
|
||||
|
||||
// Drop the connection from 2 to 3, and 3 isn't garbage-collected
|
||||
// because 1 has a connection to 3
|
||||
ps2.DeleteTestConnection(p3)
|
||||
require.Empty(t, garbageCollect(ps2), "peers removed")
|
||||
|
||||
// Drop the connection from 1 to 3, and 3 will get removed by
|
||||
// garbage-collection
|
||||
ps1.DeleteTestConnection(p3)
|
||||
checkPeerArray(t, garbageCollect(ps1), p3)
|
||||
}
|
||||
|
||||
func TestShortIDCollisions(t *testing.T) {
|
||||
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
_, peers := newNode(PeerName(1 << PeerShortIDBits))
|
||||
|
||||
// Make enough peers that short id collisions are
|
||||
// overwhelmingly likely
|
||||
ps := make([]*Peer, 1<<PeerShortIDBits)
|
||||
for i := 0; i < 1<<PeerShortIDBits; i++ {
|
||||
ps[i] = NewPeer(PeerName(i), "", PeerUID(i), 0,
|
||||
PeerShortID(rng.Intn(1<<PeerShortIDBits)))
|
||||
}
|
||||
|
||||
shuffle := func() {
|
||||
for i := range ps {
|
||||
j := rng.Intn(i + 1)
|
||||
ps[i], ps[j] = ps[j], ps[i]
|
||||
}
|
||||
}
|
||||
|
||||
// Fill peers
|
||||
shuffle()
|
||||
var pending PeersPendingNotifications
|
||||
for _, p := range ps {
|
||||
peers.addByShortID(p, &pending)
|
||||
}
|
||||
|
||||
// Check invariants
|
||||
counts := make([]int, 1<<PeerShortIDBits)
|
||||
saw := func(p *Peer) {
|
||||
if p != peers.ourself.Peer {
|
||||
counts[p.UID]++
|
||||
}
|
||||
}
|
||||
|
||||
for shortID, entry := range peers.byShortID {
|
||||
if entry.peer == nil {
|
||||
// no principal peer for this short id, so
|
||||
// others must be empty
|
||||
require.Empty(t, entry.others)
|
||||
continue
|
||||
}
|
||||
|
||||
require.Equal(t, shortID, entry.peer.ShortID)
|
||||
saw(entry.peer)
|
||||
|
||||
for _, p := range entry.others {
|
||||
saw(p)
|
||||
require.Equal(t, shortID, p.ShortID)
|
||||
|
||||
// the principal peer should have the lowest name
|
||||
require.True(t, p.Name > entry.peer.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Check that every peer was seen
|
||||
for _, n := range counts {
|
||||
require.Equal(t, 1, n)
|
||||
}
|
||||
|
||||
// Delete all the peers
|
||||
shuffle()
|
||||
for _, p := range ps {
|
||||
peers.deleteByShortID(p, &pending)
|
||||
}
|
||||
|
||||
for _, entry := range peers.byShortID {
|
||||
if entry.peer != peers.ourself.Peer {
|
||||
require.Nil(t, entry.peer)
|
||||
}
|
||||
|
||||
require.Empty(t, entry.others)
|
||||
}
|
||||
}
|
||||
|
||||
// Test the easy case of short id reassignment, when few short ids are taken
|
||||
func TestShortIDReassignmentEasy(t *testing.T) {
|
||||
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
_, peers := newNode(PeerName(0))
|
||||
|
||||
for i := 1; i <= 10; i++ {
|
||||
peers.FetchWithDefault(NewPeer(PeerName(i), "", PeerUID(i), 0,
|
||||
PeerShortID(rng.Intn(1<<PeerShortIDBits))))
|
||||
}
|
||||
|
||||
checkShortIDReassignment(t, peers)
|
||||
}
|
||||
|
||||
// Test the hard case of short id reassignment, when most short ids are taken
|
||||
func TestShortIDReassignmentHard(t *testing.T) {
|
||||
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
_, peers := newNode(PeerName(1 << PeerShortIDBits))
|
||||
|
||||
// Take all short ids
|
||||
ps := make([]*Peer, 1<<PeerShortIDBits)
|
||||
var pending PeersPendingNotifications
|
||||
for i := 0; i < 1<<PeerShortIDBits; i++ {
|
||||
ps[i] = NewPeer(PeerName(i), "", PeerUID(i), 0,
|
||||
PeerShortID(i))
|
||||
peers.addByShortID(ps[i], &pending)
|
||||
}
|
||||
|
||||
// As all short ids are taken, an attempted reassigment won't
|
||||
// do anything
|
||||
oldShortID := peers.ourself.ShortID
|
||||
require.False(t, peers.reassignLocalShortID(&pending))
|
||||
require.Equal(t, oldShortID, peers.ourself.ShortID)
|
||||
|
||||
// Free up a few ids
|
||||
for i := 0; i < 10; i++ {
|
||||
x := rng.Intn(len(ps))
|
||||
if ps[x] != nil {
|
||||
peers.deleteByShortID(ps[x], &pending)
|
||||
ps[x] = nil
|
||||
}
|
||||
}
|
||||
|
||||
checkShortIDReassignment(t, peers)
|
||||
}
|
||||
|
||||
func checkShortIDReassignment(t *testing.T, peers *Peers) {
|
||||
oldShortID := peers.ourself.ShortID
|
||||
peers.reassignLocalShortID(&PeersPendingNotifications{})
|
||||
require.NotEqual(t, oldShortID, peers.ourself.ShortID)
|
||||
require.Equal(t, peers.ourself.Peer, peers.byShortID[peers.ourself.ShortID].peer)
|
||||
}
|
||||
|
||||
func TestShortIDInvalidation(t *testing.T) {
|
||||
_, peers := newNode(PeerName(1 << PeerShortIDBits))
|
||||
|
||||
// need to use a short id that is not the local peer's
|
||||
shortID := peers.ourself.ShortID + 1
|
||||
|
||||
var pending PeersPendingNotifications
|
||||
|
||||
requireInvalidateShortIDs := func(expect bool) {
|
||||
require.Equal(t, expect, pending.invalidateShortIDs)
|
||||
pending.invalidateShortIDs = false
|
||||
}
|
||||
|
||||
// The use of a fresh short id does not cause invalidation
|
||||
a := NewPeer(PeerName(1), "", PeerUID(1), 0, shortID)
|
||||
peers.addByShortID(a, &pending)
|
||||
requireInvalidateShortIDs(false)
|
||||
|
||||
// An addition which does not change the mapping
|
||||
b := NewPeer(PeerName(2), "", PeerUID(2), 0, shortID)
|
||||
peers.addByShortID(b, &pending)
|
||||
requireInvalidateShortIDs(false)
|
||||
|
||||
// An addition which does change the mapping
|
||||
c := NewPeer(PeerName(0), "", PeerUID(0), 0, shortID)
|
||||
peers.addByShortID(c, &pending)
|
||||
requireInvalidateShortIDs(true)
|
||||
|
||||
// A deletion which does not change the mapping
|
||||
peers.deleteByShortID(b, &pending)
|
||||
requireInvalidateShortIDs(false)
|
||||
|
||||
// A deletion which does change the mapping
|
||||
peers.deleteByShortID(c, &pending)
|
||||
requireInvalidateShortIDs(true)
|
||||
|
||||
// Deleting the last peer with a short id does not cause invalidation
|
||||
peers.deleteByShortID(a, &pending)
|
||||
requireInvalidateShortIDs(false)
|
||||
|
||||
// .. but subsequent reuse of that short id does cause invalidation
|
||||
peers.addByShortID(a, &pending)
|
||||
requireInvalidateShortIDs(true)
|
||||
}
|
||||
|
||||
func TestShortIDPropagation(t *testing.T) {
|
||||
_, peers1 := newNode(PeerName(1))
|
||||
_, peers2 := newNode(PeerName(2))
|
||||
|
||||
peers1.AddTestConnection(peers2.ourself.Peer)
|
||||
peers1.ApplyUpdate(peers2.EncodePeers(peers2.Names()))
|
||||
peers12 := peers1.Fetch(PeerName(2))
|
||||
old := peers12.PeerSummary
|
||||
|
||||
require.True(t,
|
||||
peers2.reassignLocalShortID(&PeersPendingNotifications{}))
|
||||
peers1.ApplyUpdate(peers2.EncodePeers(peers2.Names()))
|
||||
require.NotEqual(t, old.Version, peers12.Version)
|
||||
require.NotEqual(t, old.ShortID, peers12.ShortID)
|
||||
}
|
||||
|
||||
func TestShortIDCollision(t *testing.T) {
|
||||
// Create 3 peers
|
||||
_, peers1 := newNode(PeerName(1))
|
||||
_, peers2 := newNode(PeerName(2))
|
||||
_, peers3 := newNode(PeerName(3))
|
||||
|
||||
var pending PeersPendingNotifications
|
||||
peers1.setLocalShortID(1, &pending)
|
||||
peers2.setLocalShortID(2, &pending)
|
||||
peers3.setLocalShortID(3, &pending)
|
||||
|
||||
peers2.AddTestConnection(peers1.ourself.Peer)
|
||||
peers3.AddTestConnection(peers2.ourself.Peer)
|
||||
|
||||
// Propogate from 1 to 2 to 3
|
||||
peers2.ApplyUpdate(peers1.EncodePeers(peers1.Names()))
|
||||
peers3.ApplyUpdate(peers2.EncodePeers(peers2.Names()))
|
||||
|
||||
// Force the short id of peer 1 to collide with peer 2. Peer
|
||||
// 1 has the lowest name, so it gets to keep the short id
|
||||
peers1.setLocalShortID(2, &pending)
|
||||
|
||||
oldShortID := peers2.ourself.ShortID
|
||||
_, updated, _ := peers2.ApplyUpdate(peers1.EncodePeers(peers1.Names()))
|
||||
|
||||
// peer 2 should have noticed the collision and resolved it
|
||||
require.NotEqual(t, oldShortID, peers2.ourself.ShortID)
|
||||
|
||||
// The Peers do not have a Router, so broadcastPeerUpdate does
|
||||
// nothing in the context of this test. So we fake what it
|
||||
// would do.
|
||||
updated[PeerName(2)] = void
|
||||
|
||||
// the update from peer 2 should include its short id change
|
||||
peers3.ApplyUpdate(peers2.EncodePeers(updated))
|
||||
require.Equal(t, peers2.ourself.ShortID,
|
||||
peers3.Fetch(PeerName(2)).ShortID)
|
||||
}
|
||||
|
||||
// Test the case where all short ids are taken, but then some peers go
|
||||
// away, so the local peer reassigns
|
||||
func TestDeferredShortIDReassignment(t *testing.T) {
|
||||
rng := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
_, us := newNode(PeerName(1 << PeerShortIDBits))
|
||||
|
||||
// Connect us to other peers occupying all short ids
|
||||
others := make([]*Peers, 1<<PeerShortIDBits)
|
||||
var pending PeersPendingNotifications
|
||||
for i := range others {
|
||||
_, others[i] = newNode(PeerName(i))
|
||||
others[i].setLocalShortID(PeerShortID(i), &pending)
|
||||
us.AddTestConnection(others[i].ourself.Peer)
|
||||
}
|
||||
|
||||
// Check that, as expected, the local peer does not own its
|
||||
// short id
|
||||
require.NotEqual(t, us.ourself.Peer,
|
||||
us.byShortID[us.ourself.ShortID].peer)
|
||||
|
||||
// Disconnect one peer, and we should now be able to claim its
|
||||
// short id
|
||||
other := others[rng.Intn(1<<PeerShortIDBits)]
|
||||
us.DeleteTestConnection(other.ourself.Peer)
|
||||
us.GarbageCollect()
|
||||
|
||||
require.Equal(t, us.ourself.Peer, us.byShortID[us.ourself.ShortID].peer)
|
||||
}
|
||||
@@ -1,348 +0,0 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/gob"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
Protocol = "weave"
|
||||
ProtocolMinVersion = 1
|
||||
ProtocolMaxVersion = 2
|
||||
)
|
||||
|
||||
var (
|
||||
ProtocolBytes = []byte(Protocol)
|
||||
|
||||
HeaderTimeout = 10 * time.Second
|
||||
|
||||
ProtocolV1Features = []string{
|
||||
"ConnID",
|
||||
"Name",
|
||||
"NickName",
|
||||
"PeerNameFlavour",
|
||||
"UID",
|
||||
}
|
||||
|
||||
ErrExpectedCrypto = fmt.Errorf("Password specified, but peer requested an unencrypted connection")
|
||||
ErrExpectedNoCrypto = fmt.Errorf("No password specificed, but peer requested an encrypted connection")
|
||||
)
|
||||
|
||||
// We don't need the full net.TCPConn to do the protocol intro. This
|
||||
// interface contains just the parts we do need, to support testing
|
||||
type ProtocolIntroConn interface {
|
||||
// io.Reader
|
||||
Read(b []byte) (n int, err error)
|
||||
|
||||
// io.Writer
|
||||
Write(b []byte) (n int, err error)
|
||||
|
||||
// net.Conn's deadline methods
|
||||
SetDeadline(t time.Time) error
|
||||
SetReadDeadline(t time.Time) error
|
||||
SetWriteDeadline(t time.Time) error
|
||||
}
|
||||
|
||||
type ProtocolIntroParams struct {
|
||||
MinVersion byte
|
||||
MaxVersion byte
|
||||
Features map[string]string
|
||||
Conn ProtocolIntroConn
|
||||
Password []byte
|
||||
Outbound bool
|
||||
}
|
||||
|
||||
type ProtocolIntroResults struct {
|
||||
Features map[string]string
|
||||
Receiver TCPReceiver
|
||||
Sender TCPSender
|
||||
SessionKey *[32]byte
|
||||
Version byte
|
||||
}
|
||||
|
||||
func (params ProtocolIntroParams) DoIntro() (res ProtocolIntroResults, err error) {
|
||||
if err = params.Conn.SetDeadline(time.Now().Add(HeaderTimeout)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if res.Version, err = params.exchangeProtocolHeader(); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var pubKey, privKey *[32]byte
|
||||
if params.Password != nil {
|
||||
if pubKey, privKey, err = GenerateKeyPair(); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if err = params.Conn.SetWriteDeadline(time.Time{}); err != nil {
|
||||
return
|
||||
}
|
||||
if err = params.Conn.SetReadDeadline(time.Now().Add(TCPHeartbeat * 2)); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
switch res.Version {
|
||||
case 1:
|
||||
err = res.doIntroV1(params, pubKey, privKey)
|
||||
case 2:
|
||||
err = res.doIntroV2(params, pubKey, privKey)
|
||||
default:
|
||||
panic("unhandled protocol version")
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (params ProtocolIntroParams) exchangeProtocolHeader() (byte, error) {
|
||||
// Write in a separate goroutine to avoid the possibility of
|
||||
// deadlock. The result channel is of size 1 so that the
|
||||
// goroutine does not linger even if we encounter an error on
|
||||
// the read side.
|
||||
sendHeader := append(ProtocolBytes, params.MinVersion, params.MaxVersion)
|
||||
writeDone := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := params.Conn.Write(sendHeader)
|
||||
writeDone <- err
|
||||
}()
|
||||
|
||||
header := make([]byte, len(ProtocolBytes)+2)
|
||||
if n, err := io.ReadFull(params.Conn, header); err != nil && n == 0 {
|
||||
return 0, fmt.Errorf("failed to receive remote protocol header: %s", err)
|
||||
} else if err != nil {
|
||||
return 0, fmt.Errorf("received incomplete remote protocol header (%d octets instead of %d): %v; error: %s",
|
||||
n, len(header), header[:n], err)
|
||||
}
|
||||
|
||||
if !bytes.Equal(ProtocolBytes, header[:len(ProtocolBytes)]) {
|
||||
return 0, fmt.Errorf("remote protocol header not recognised: %v", header[:len(ProtocolBytes)])
|
||||
}
|
||||
|
||||
theirMinVersion := header[len(ProtocolBytes)]
|
||||
minVersion := theirMinVersion
|
||||
if params.MinVersion > minVersion {
|
||||
minVersion = params.MinVersion
|
||||
}
|
||||
|
||||
theirMaxVersion := header[len(ProtocolBytes)+1]
|
||||
maxVersion := theirMaxVersion
|
||||
if maxVersion > params.MaxVersion {
|
||||
maxVersion = params.MaxVersion
|
||||
}
|
||||
|
||||
if minVersion > maxVersion {
|
||||
return 0, fmt.Errorf("remote version range [%d,%d] is incompatible with ours [%d,%d]",
|
||||
theirMinVersion, theirMaxVersion,
|
||||
params.MinVersion, params.MaxVersion)
|
||||
}
|
||||
|
||||
if err := <-writeDone; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return maxVersion, nil
|
||||
}
|
||||
|
||||
// The V1 procotol consists of the protocol identification/version
|
||||
// header, followed by a stream of gobified values. The first value
|
||||
// is the encoded features map (never encrypted). The subsequent
|
||||
// values are the messages on the connection (encrypted for an
|
||||
// encrypted connection). For an encrypted connection, the public key
|
||||
// is passed in the "PublicKey" feature as a string of hex digits.
|
||||
func (res *ProtocolIntroResults) doIntroV1(params ProtocolIntroParams, pubKey, privKey *[32]byte) error {
|
||||
features := filterV1Features(params.Features)
|
||||
if pubKey != nil {
|
||||
features["PublicKey"] = hex.EncodeToString(pubKey[:])
|
||||
}
|
||||
|
||||
enc := gob.NewEncoder(params.Conn)
|
||||
dec := gob.NewDecoder(params.Conn)
|
||||
|
||||
// Encode in a separate goroutine to avoid the possibility of
|
||||
// deadlock. The result channel is of size 1 so that the
|
||||
// goroutine does not linger even if we encounter an error on
|
||||
// the read side.
|
||||
encodeDone := make(chan error, 1)
|
||||
go func() {
|
||||
encodeDone <- enc.Encode(features)
|
||||
}()
|
||||
|
||||
if err := dec.Decode(&res.Features); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := <-encodeDone; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res.Sender = NewGobTCPSender(enc)
|
||||
res.Receiver = NewGobTCPReceiver(dec)
|
||||
|
||||
if pubKey == nil {
|
||||
if _, present := res.Features["PublicKey"]; present {
|
||||
return ErrExpectedNoCrypto
|
||||
}
|
||||
} else {
|
||||
remotePubKeyStr, ok := res.Features["PublicKey"]
|
||||
if !ok {
|
||||
return ErrExpectedCrypto
|
||||
}
|
||||
|
||||
remotePubKey, err := hex.DecodeString(remotePubKeyStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res.setupCrypto(params, remotePubKey, privKey)
|
||||
}
|
||||
|
||||
res.Features = filterV1Features(res.Features)
|
||||
return nil
|
||||
}
|
||||
|
||||
// In the V1 protocol, the intro fields are sent unencrypted. So we
|
||||
// restrict them to an established subset of fields that are assumed
|
||||
// to be safe.
|
||||
func filterV1Features(intro map[string]string) map[string]string {
|
||||
safe := make(map[string]string)
|
||||
for _, k := range ProtocolV1Features {
|
||||
if val, ok := intro[k]; ok {
|
||||
safe[k] = val
|
||||
}
|
||||
}
|
||||
|
||||
return safe
|
||||
}
|
||||
|
||||
// The V2 procotol consists of the protocol identification/version
|
||||
// header, followed by:
|
||||
//
|
||||
// - A single "encryption flag" byte: 0 for no encryption, 1 for
|
||||
// encryption.
|
||||
//
|
||||
// - When the connection is encrypted, 32 bytes follow containing the
|
||||
// public key.
|
||||
//
|
||||
// - Then a stream of length-prefixed messages, which are encrypted
|
||||
// for an encrypted connection.
|
||||
//
|
||||
// The first message contains the encoded features map (so in contrast
|
||||
// to V1, it will be encrypted on an encrypted connection).
|
||||
func (res *ProtocolIntroResults) doIntroV2(params ProtocolIntroParams, pubKey, privKey *[32]byte) error {
|
||||
// Public key exchange
|
||||
var wbuf []byte
|
||||
if pubKey == nil {
|
||||
wbuf = []byte{0}
|
||||
} else {
|
||||
wbuf = make([]byte, 1+len(*pubKey))
|
||||
wbuf[0] = 1
|
||||
copy(wbuf[1:], (*pubKey)[:])
|
||||
}
|
||||
|
||||
// Write in a separate goroutine to avoid the possibility of
|
||||
// deadlock. The result channel is of size 1 so that the
|
||||
// goroutine does not linger even if we encounter an error on
|
||||
// the read side.
|
||||
writeDone := make(chan error, 1)
|
||||
go func() {
|
||||
_, err := params.Conn.Write(wbuf)
|
||||
writeDone <- err
|
||||
}()
|
||||
|
||||
rbuf := make([]byte, 1)
|
||||
if _, err := io.ReadFull(params.Conn, rbuf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch rbuf[0] {
|
||||
case 0:
|
||||
if pubKey != nil {
|
||||
return ErrExpectedCrypto
|
||||
}
|
||||
|
||||
res.Sender = NewLengthPrefixTCPSender(params.Conn)
|
||||
res.Receiver = NewLengthPrefixTCPReceiver(params.Conn)
|
||||
|
||||
case 1:
|
||||
if pubKey == nil {
|
||||
return ErrExpectedNoCrypto
|
||||
}
|
||||
|
||||
rbuf = make([]byte, len(pubKey))
|
||||
if _, err := io.ReadFull(params.Conn, rbuf); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
res.Sender = NewLengthPrefixTCPSender(params.Conn)
|
||||
res.Receiver = NewLengthPrefixTCPReceiver(params.Conn)
|
||||
res.setupCrypto(params, rbuf, privKey)
|
||||
|
||||
default:
|
||||
return fmt.Errorf("Bad encryption flag %d", rbuf[0])
|
||||
}
|
||||
|
||||
if err := <-writeDone; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Features exchange
|
||||
go func() {
|
||||
buf := new(bytes.Buffer)
|
||||
if err := gob.NewEncoder(buf).Encode(¶ms.Features); err != nil {
|
||||
writeDone <- err
|
||||
return
|
||||
}
|
||||
|
||||
writeDone <- res.Sender.Send(buf.Bytes())
|
||||
}()
|
||||
|
||||
rbuf, err := res.Receiver.Receive()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := gob.NewDecoder(bytes.NewReader(rbuf)).Decode(&res.Features); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := <-writeDone; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (res *ProtocolIntroResults) setupCrypto(params ProtocolIntroParams, remotePubKey []byte, privKey *[32]byte) {
|
||||
var remotePubKeyArr [32]byte
|
||||
copy(remotePubKeyArr[:], remotePubKey)
|
||||
res.SessionKey = FormSessionKey(&remotePubKeyArr, privKey, params.Password)
|
||||
res.Sender = NewEncryptedTCPSender(res.Sender, res.SessionKey, params.Outbound)
|
||||
res.Receiver = NewEncryptedTCPReceiver(res.Receiver, res.SessionKey, params.Outbound)
|
||||
}
|
||||
|
||||
type ProtocolTag byte
|
||||
|
||||
const (
|
||||
ProtocolHeartbeat = iota
|
||||
ProtocolReserved1 // legacy overlay control messages
|
||||
ProtocolReserved2 //
|
||||
ProtocolReserved3 //
|
||||
ProtocolGossip
|
||||
ProtocolGossipUnicast
|
||||
ProtocolGossipBroadcast
|
||||
ProtocolOverlayControlMsg
|
||||
)
|
||||
|
||||
type ProtocolMsg struct {
|
||||
tag ProtocolTag
|
||||
msg []byte
|
||||
}
|
||||
|
||||
type ProtocolSender interface {
|
||||
SendProtocolMsg(m ProtocolMsg)
|
||||
}
|
||||
@@ -1,180 +0,0 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"encoding/binary"
|
||||
"encoding/gob"
|
||||
"fmt"
|
||||
"io"
|
||||
"sync"
|
||||
|
||||
"golang.org/x/crypto/nacl/box"
|
||||
"golang.org/x/crypto/nacl/secretbox"
|
||||
)
|
||||
|
||||
const MaxTCPMsgSize = 10 * 1024 * 1024
|
||||
|
||||
func GenerateKeyPair() (publicKey, privateKey *[32]byte, err error) {
|
||||
return box.GenerateKey(rand.Reader)
|
||||
}
|
||||
|
||||
func FormSessionKey(remotePublicKey, localPrivateKey *[32]byte, secretKey []byte) *[32]byte {
|
||||
var sharedKey [32]byte
|
||||
box.Precompute(&sharedKey, remotePublicKey, localPrivateKey)
|
||||
sharedKeySlice := sharedKey[:]
|
||||
sharedKeySlice = append(sharedKeySlice, secretKey...)
|
||||
sessionKey := sha256.Sum256(sharedKeySlice)
|
||||
return &sessionKey
|
||||
}
|
||||
|
||||
// TCP Senders/Receivers
|
||||
|
||||
// The lowest 64 bits of the nonce contain the message sequence
|
||||
// number. The top most bit indicates the connection polarity at the
|
||||
// sender - '1' for outbound; the next indicates protocol type - '1'
|
||||
// for TCP. The remaining 126 bits are zero. The polarity is needed so
|
||||
// that the two ends of a connection do not use the same nonces; the
|
||||
// protocol type so that the TCP connection nonces are distinct from
|
||||
// nonces used by overlay connections, if they share the session key.
|
||||
// This is a requirement of the NaCl Security Model; see
|
||||
// http://nacl.cr.yp.to/box.html.
|
||||
type TCPCryptoState struct {
|
||||
sessionKey *[32]byte
|
||||
nonce [24]byte
|
||||
seqNo uint64
|
||||
}
|
||||
|
||||
func NewTCPCryptoState(sessionKey *[32]byte, outbound bool) *TCPCryptoState {
|
||||
s := &TCPCryptoState{sessionKey: sessionKey}
|
||||
if outbound {
|
||||
s.nonce[0] |= (1 << 7)
|
||||
}
|
||||
s.nonce[0] |= (1 << 6)
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *TCPCryptoState) advance() {
|
||||
s.seqNo++
|
||||
binary.BigEndian.PutUint64(s.nonce[16:24], s.seqNo)
|
||||
}
|
||||
|
||||
type TCPSender interface {
|
||||
Send([]byte) error
|
||||
}
|
||||
|
||||
type GobTCPSender struct {
|
||||
encoder *gob.Encoder
|
||||
}
|
||||
|
||||
type LengthPrefixTCPSender struct {
|
||||
writer io.Writer
|
||||
}
|
||||
|
||||
type EncryptedTCPSender struct {
|
||||
sync.RWMutex
|
||||
sender TCPSender
|
||||
state *TCPCryptoState
|
||||
}
|
||||
|
||||
func NewGobTCPSender(encoder *gob.Encoder) *GobTCPSender {
|
||||
return &GobTCPSender{encoder: encoder}
|
||||
}
|
||||
|
||||
func (sender *GobTCPSender) Send(msg []byte) error {
|
||||
return sender.encoder.Encode(msg)
|
||||
}
|
||||
|
||||
func NewLengthPrefixTCPSender(writer io.Writer) *LengthPrefixTCPSender {
|
||||
return &LengthPrefixTCPSender{writer: writer}
|
||||
}
|
||||
|
||||
func (sender *LengthPrefixTCPSender) Send(msg []byte) error {
|
||||
l := len(msg)
|
||||
if l > MaxTCPMsgSize {
|
||||
return fmt.Errorf("outgoing message exceeds maximum size: %d > %d", l, MaxTCPMsgSize)
|
||||
}
|
||||
// We copy the message so we can send it in a single Write
|
||||
// operation, thus making this thread-safe without locking.
|
||||
prefixedMsg := make([]byte, 4+l)
|
||||
binary.BigEndian.PutUint32(prefixedMsg, uint32(l))
|
||||
copy(prefixedMsg[4:], msg)
|
||||
_, err := sender.writer.Write(prefixedMsg)
|
||||
return err
|
||||
}
|
||||
|
||||
func NewEncryptedTCPSender(sender TCPSender, sessionKey *[32]byte, outbound bool) *EncryptedTCPSender {
|
||||
return &EncryptedTCPSender{sender: sender, state: NewTCPCryptoState(sessionKey, outbound)}
|
||||
}
|
||||
|
||||
func (sender *EncryptedTCPSender) Send(msg []byte) error {
|
||||
sender.Lock()
|
||||
defer sender.Unlock()
|
||||
encodedMsg := secretbox.Seal(nil, msg, &sender.state.nonce, sender.state.sessionKey)
|
||||
sender.state.advance()
|
||||
return sender.sender.Send(encodedMsg)
|
||||
}
|
||||
|
||||
type TCPReceiver interface {
|
||||
Receive() ([]byte, error)
|
||||
}
|
||||
|
||||
type GobTCPReceiver struct {
|
||||
decoder *gob.Decoder
|
||||
}
|
||||
|
||||
type LengthPrefixTCPReceiver struct {
|
||||
reader io.Reader
|
||||
}
|
||||
|
||||
type EncryptedTCPReceiver struct {
|
||||
receiver TCPReceiver
|
||||
state *TCPCryptoState
|
||||
}
|
||||
|
||||
func NewGobTCPReceiver(decoder *gob.Decoder) *GobTCPReceiver {
|
||||
return &GobTCPReceiver{decoder: decoder}
|
||||
}
|
||||
|
||||
func (receiver *GobTCPReceiver) Receive() ([]byte, error) {
|
||||
var msg []byte
|
||||
err := receiver.decoder.Decode(&msg)
|
||||
return msg, err
|
||||
}
|
||||
|
||||
func NewLengthPrefixTCPReceiver(reader io.Reader) *LengthPrefixTCPReceiver {
|
||||
return &LengthPrefixTCPReceiver{reader: reader}
|
||||
}
|
||||
|
||||
func (receiver *LengthPrefixTCPReceiver) Receive() ([]byte, error) {
|
||||
lenPrefix := make([]byte, 4)
|
||||
if _, err := io.ReadFull(receiver.reader, lenPrefix); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
l := binary.BigEndian.Uint32(lenPrefix)
|
||||
if l > MaxTCPMsgSize {
|
||||
return nil, fmt.Errorf("incoming message exceeds maximum size: %d > %d", l, MaxTCPMsgSize)
|
||||
}
|
||||
msg := make([]byte, l)
|
||||
_, err := io.ReadFull(receiver.reader, msg)
|
||||
return msg, err
|
||||
}
|
||||
|
||||
func NewEncryptedTCPReceiver(receiver TCPReceiver, sessionKey *[32]byte, outbound bool) *EncryptedTCPReceiver {
|
||||
return &EncryptedTCPReceiver{receiver: receiver, state: NewTCPCryptoState(sessionKey, !outbound)}
|
||||
}
|
||||
|
||||
func (receiver *EncryptedTCPReceiver) Receive() ([]byte, error) {
|
||||
msg, err := receiver.receiver.Receive()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
decodedMsg, success := secretbox.Open(nil, msg, &receiver.state.nonce, receiver.state.sessionKey)
|
||||
if !success {
|
||||
return nil, fmt.Errorf("Unable to decrypt TCP msg")
|
||||
}
|
||||
|
||||
receiver.state.advance()
|
||||
return decodedMsg, nil
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"io"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type testConn struct {
|
||||
io.Writer
|
||||
io.Reader
|
||||
}
|
||||
|
||||
func (testConn) SetDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (testConn) SetReadDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (testConn) SetWriteDeadline(t time.Time) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func connPair() (ProtocolIntroConn, ProtocolIntroConn) {
|
||||
a := testConn{}
|
||||
b := testConn{}
|
||||
a.Reader, b.Writer = io.Pipe()
|
||||
b.Reader, a.Writer = io.Pipe()
|
||||
return &a, &b
|
||||
}
|
||||
|
||||
func doIntro(t *testing.T, params ProtocolIntroParams) <-chan ProtocolIntroResults {
|
||||
ch := make(chan ProtocolIntroResults, 1)
|
||||
go func() {
|
||||
res, err := params.DoIntro()
|
||||
require.Nil(t, err)
|
||||
ch <- res
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
func doProtocolIntro(t *testing.T, aver, bver byte, password []byte) byte {
|
||||
aconn, bconn := connPair()
|
||||
aresch := doIntro(t, ProtocolIntroParams{
|
||||
MinVersion: ProtocolMinVersion,
|
||||
MaxVersion: aver,
|
||||
Features: map[string]string{"Name": "A"},
|
||||
Conn: aconn,
|
||||
Outbound: true,
|
||||
Password: password,
|
||||
})
|
||||
bresch := doIntro(t, ProtocolIntroParams{
|
||||
MinVersion: ProtocolMinVersion,
|
||||
MaxVersion: bver,
|
||||
Features: map[string]string{"Name": "B"},
|
||||
Conn: bconn,
|
||||
Outbound: false,
|
||||
Password: password,
|
||||
})
|
||||
ares := <-aresch
|
||||
bres := <-bresch
|
||||
|
||||
// Check that features were conveyed
|
||||
require.Equal(t, "B", ares.Features["Name"])
|
||||
require.Equal(t, "A", bres.Features["Name"])
|
||||
|
||||
// Check that Senders and Receivers work
|
||||
go func() {
|
||||
require.Nil(t, ares.Sender.Send([]byte("Hello from A")))
|
||||
require.Nil(t, bres.Sender.Send([]byte("Hello from B")))
|
||||
}()
|
||||
|
||||
data, err := bres.Receiver.Receive()
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "Hello from A", string(data))
|
||||
|
||||
data, err = ares.Receiver.Receive()
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, "Hello from B", string(data))
|
||||
|
||||
require.Equal(t, ares.Version, bres.Version)
|
||||
return ares.Version
|
||||
}
|
||||
|
||||
func TestProtocolIntro(t *testing.T) {
|
||||
require.Equal(t, 2, int(doProtocolIntro(t, 2, 2, nil)))
|
||||
require.Equal(t, 2, int(doProtocolIntro(t, 2, 2, []byte("sekr1t"))))
|
||||
require.Equal(t, 1, int(doProtocolIntro(t, 1, 2, nil)))
|
||||
require.Equal(t, 1, int(doProtocolIntro(t, 1, 2, []byte("pa55"))))
|
||||
require.Equal(t, 1, int(doProtocolIntro(t, 2, 1, nil)))
|
||||
require.Equal(t, 1, int(doProtocolIntro(t, 2, 1, []byte("w0rd"))))
|
||||
}
|
||||
-189
@@ -1,189 +0,0 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
)
|
||||
|
||||
const (
|
||||
Port = 6783
|
||||
ChannelSize = 16
|
||||
TCPHeartbeat = 30 * time.Second
|
||||
GossipInterval = 30 * time.Second
|
||||
MaxDuration = time.Duration(math.MaxInt64)
|
||||
|
||||
acceptMaxTokens = 100 // [1]
|
||||
acceptTokenDelay = 100 * time.Millisecond // [2]
|
||||
)
|
||||
|
||||
// [1] capacity of token bucket for rate limiting accepts
|
||||
|
||||
// [2] control rate at which new tokens are added to the bucket
|
||||
|
||||
var (
|
||||
log = common.Log
|
||||
checkFatal = common.CheckFatal
|
||||
checkWarn = common.CheckWarn
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Port int
|
||||
ProtocolMinVersion byte
|
||||
Password []byte
|
||||
ConnLimit int
|
||||
PeerDiscovery bool
|
||||
}
|
||||
|
||||
type Router struct {
|
||||
Config
|
||||
Overlay Overlay
|
||||
Ourself *LocalPeer
|
||||
Peers *Peers
|
||||
Routes *Routes
|
||||
ConnectionMaker *ConnectionMaker
|
||||
gossipLock sync.RWMutex
|
||||
gossipChannels GossipChannels
|
||||
TopologyGossip Gossip
|
||||
acceptLimiter *TokenBucket
|
||||
}
|
||||
|
||||
func NewRouter(config Config, name PeerName, nickName string, overlay Overlay) *Router {
|
||||
router := &Router{Config: config, gossipChannels: make(GossipChannels)}
|
||||
|
||||
if overlay == nil {
|
||||
overlay = NullOverlay{}
|
||||
}
|
||||
|
||||
router.Overlay = overlay
|
||||
router.Ourself = NewLocalPeer(name, nickName, router)
|
||||
router.Peers = NewPeers(router.Ourself)
|
||||
router.Peers.OnGC(func(peer *Peer) {
|
||||
log.Println("Removed unreachable peer", peer)
|
||||
})
|
||||
router.Routes = NewRoutes(router.Ourself, router.Peers)
|
||||
router.ConnectionMaker = NewConnectionMaker(router.Ourself, router.Peers, router.Port, router.PeerDiscovery)
|
||||
router.TopologyGossip = router.NewGossip("topology", router)
|
||||
router.acceptLimiter = NewTokenBucket(acceptMaxTokens, acceptTokenDelay)
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
// Start listening for TCP connections. This is separate from
|
||||
// NewRouter so that gossipers can register before we start forming
|
||||
// connections.
|
||||
func (router *Router) Start() {
|
||||
router.listenTCP(router.Port)
|
||||
}
|
||||
|
||||
func (router *Router) Stop() error {
|
||||
// TODO: perform graceful shutdown...
|
||||
return nil
|
||||
}
|
||||
|
||||
func (router *Router) UsingPassword() bool {
|
||||
return router.Password != nil
|
||||
}
|
||||
|
||||
func (router *Router) listenTCP(localPort int) {
|
||||
localAddr, err := net.ResolveTCPAddr("tcp4", fmt.Sprint(":", localPort))
|
||||
checkFatal(err)
|
||||
ln, err := net.ListenTCP("tcp4", localAddr)
|
||||
checkFatal(err)
|
||||
go func() {
|
||||
defer ln.Close()
|
||||
for {
|
||||
tcpConn, err := ln.AcceptTCP()
|
||||
if err != nil {
|
||||
log.Errorln(err)
|
||||
continue
|
||||
}
|
||||
router.acceptTCP(tcpConn)
|
||||
router.acceptLimiter.Wait()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (router *Router) acceptTCP(tcpConn *net.TCPConn) {
|
||||
remoteAddrStr := tcpConn.RemoteAddr().String()
|
||||
log.Printf("->[%s] connection accepted", remoteAddrStr)
|
||||
connRemote := NewRemoteConnection(router.Ourself.Peer, nil, remoteAddrStr, false, false)
|
||||
StartLocalConnection(connRemote, tcpConn, router, true)
|
||||
}
|
||||
|
||||
// Gossiper methods - the Router is the topology Gossiper
|
||||
|
||||
type TopologyGossipData struct {
|
||||
peers *Peers
|
||||
update PeerNameSet
|
||||
}
|
||||
|
||||
func (router *Router) BroadcastTopologyUpdate(update []*Peer) {
|
||||
names := make(PeerNameSet)
|
||||
for _, p := range update {
|
||||
names[p.Name] = void
|
||||
}
|
||||
|
||||
router.TopologyGossip.GossipBroadcast(&TopologyGossipData{
|
||||
peers: router.Peers,
|
||||
update: names,
|
||||
})
|
||||
}
|
||||
|
||||
func (d *TopologyGossipData) Merge(other GossipData) {
|
||||
for name := range other.(*TopologyGossipData).update {
|
||||
d.update[name] = void
|
||||
}
|
||||
}
|
||||
|
||||
func (d *TopologyGossipData) Encode() [][]byte {
|
||||
return [][]byte{d.peers.EncodePeers(d.update)}
|
||||
}
|
||||
|
||||
func (router *Router) OnGossipUnicast(sender PeerName, msg []byte) error {
|
||||
return fmt.Errorf("unexpected topology gossip unicast: %v", msg)
|
||||
}
|
||||
|
||||
func (router *Router) OnGossipBroadcast(_ PeerName, update []byte) (GossipData, error) {
|
||||
origUpdate, _, err := router.applyTopologyUpdate(update)
|
||||
if err != nil || len(origUpdate) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
return &TopologyGossipData{peers: router.Peers, update: origUpdate}, nil
|
||||
}
|
||||
|
||||
func (router *Router) Gossip() GossipData {
|
||||
return &TopologyGossipData{peers: router.Peers, update: router.Peers.Names()}
|
||||
}
|
||||
|
||||
func (router *Router) OnGossip(update []byte) (GossipData, error) {
|
||||
_, newUpdate, err := router.applyTopologyUpdate(update)
|
||||
if err != nil || len(newUpdate) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
return &TopologyGossipData{peers: router.Peers, update: newUpdate}, nil
|
||||
}
|
||||
|
||||
func (router *Router) applyTopologyUpdate(update []byte) (PeerNameSet, PeerNameSet, error) {
|
||||
origUpdate, newUpdate, err := router.Peers.ApplyUpdate(update)
|
||||
if _, ok := err.(UnknownPeerError); err != nil && ok {
|
||||
// That update contained a reference to a peer which wasn't
|
||||
// itself included in the update, and we didn't know about
|
||||
// already. We ignore this; eventually we should receive an
|
||||
// update containing a complete topology.
|
||||
log.Println("Topology gossip:", err)
|
||||
return nil, nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if len(newUpdate) > 0 {
|
||||
router.ConnectionMaker.Refresh()
|
||||
router.Routes.Recalculate()
|
||||
}
|
||||
return origUpdate, newUpdate, nil
|
||||
}
|
||||
-272
@@ -1,272 +0,0 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type unicastRoutes map[PeerName]PeerName
|
||||
type broadcastRoutes map[PeerName][]PeerName
|
||||
|
||||
type Routes struct {
|
||||
sync.RWMutex
|
||||
ourself *LocalPeer
|
||||
peers *Peers
|
||||
onChange []func()
|
||||
unicast unicastRoutes
|
||||
unicastAll unicastRoutes // [1]
|
||||
broadcast broadcastRoutes
|
||||
broadcastAll broadcastRoutes // [1]
|
||||
recalculate chan<- *struct{}
|
||||
wait chan<- chan struct{}
|
||||
// [1] based on *all* connections, not just established &
|
||||
// symmetric ones
|
||||
}
|
||||
|
||||
func NewRoutes(ourself *LocalPeer, peers *Peers) *Routes {
|
||||
recalculate := make(chan *struct{}, 1)
|
||||
wait := make(chan chan struct{})
|
||||
routes := &Routes{
|
||||
ourself: ourself,
|
||||
peers: peers,
|
||||
unicast: make(unicastRoutes),
|
||||
unicastAll: make(unicastRoutes),
|
||||
broadcast: make(broadcastRoutes),
|
||||
broadcastAll: make(broadcastRoutes),
|
||||
recalculate: recalculate,
|
||||
wait: wait}
|
||||
routes.unicast[ourself.Name] = UnknownPeerName
|
||||
routes.unicastAll[ourself.Name] = UnknownPeerName
|
||||
routes.broadcast[ourself.Name] = []PeerName{}
|
||||
routes.broadcastAll[ourself.Name] = []PeerName{}
|
||||
go routes.run(recalculate, wait)
|
||||
return routes
|
||||
}
|
||||
|
||||
func (routes *Routes) OnChange(callback func()) {
|
||||
routes.Lock()
|
||||
defer routes.Unlock()
|
||||
routes.onChange = append(routes.onChange, callback)
|
||||
}
|
||||
|
||||
func (routes *Routes) PeerNames() PeerNameSet {
|
||||
return routes.peers.Names()
|
||||
}
|
||||
|
||||
func (routes *Routes) Unicast(name PeerName) (PeerName, bool) {
|
||||
routes.RLock()
|
||||
defer routes.RUnlock()
|
||||
hop, found := routes.unicast[name]
|
||||
return hop, found
|
||||
}
|
||||
|
||||
func (routes *Routes) UnicastAll(name PeerName) (PeerName, bool) {
|
||||
routes.RLock()
|
||||
defer routes.RUnlock()
|
||||
hop, found := routes.unicastAll[name]
|
||||
return hop, found
|
||||
}
|
||||
|
||||
func (routes *Routes) Broadcast(name PeerName) []PeerName {
|
||||
routes.RLock()
|
||||
defer routes.RUnlock()
|
||||
return routes.broadcast[name]
|
||||
}
|
||||
|
||||
func (routes *Routes) BroadcastAll(name PeerName) []PeerName {
|
||||
routes.RLock()
|
||||
defer routes.RUnlock()
|
||||
return routes.broadcastAll[name]
|
||||
}
|
||||
|
||||
// Choose min(log2(n_peers), n_neighbouring_peers) neighbours, with a
|
||||
// random distribution that is topology-sensitive, favouring
|
||||
// neighbours at the end of "bottleneck links". We determine the
|
||||
// latter based on the unicast routing table. If a neighbour appears
|
||||
// as the value more frequently than others - meaning that we reach a
|
||||
// higher proportion of peers via that neighbour than other neighbours
|
||||
// - then it is chosen with a higher probability.
|
||||
//
|
||||
// Note that we choose log2(n_peers) *neighbours*, not
|
||||
// peers. Consequently, on sparsely connected peers this function
|
||||
// returns a higher proportion of neighbours than elsewhere. In
|
||||
// extremis, on peers with fewer than log2(n_peers) neighbours, all
|
||||
// neighbours are returned.
|
||||
func (routes *Routes) RandomNeighbours(except PeerName) PeerNameSet {
|
||||
res := make(PeerNameSet)
|
||||
routes.RLock()
|
||||
defer routes.RUnlock()
|
||||
count := int(math.Log2(float64(len(routes.unicastAll))))
|
||||
// depends on go's random map iteration
|
||||
for _, dst := range routes.unicastAll {
|
||||
if dst != UnknownPeerName && dst != except {
|
||||
res[dst] = void
|
||||
if len(res) >= count {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Request recalculation of the routing table. This is async but can
|
||||
// effectively be made synchronous with a subsequent call to
|
||||
// EnsureRecalculated.
|
||||
func (routes *Routes) Recalculate() {
|
||||
// The use of a 1-capacity channel in combination with the
|
||||
// non-blocking send is an optimisation that results in multiple
|
||||
// requests being coalesced.
|
||||
select {
|
||||
case routes.recalculate <- nil:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// Wait for any preceding Recalculate requests to be processed.
|
||||
func (routes *Routes) EnsureRecalculated() {
|
||||
done := make(chan struct{})
|
||||
routes.wait <- done
|
||||
<-done
|
||||
}
|
||||
|
||||
func (routes *Routes) run(recalculate <-chan *struct{}, wait <-chan chan struct{}) {
|
||||
for {
|
||||
select {
|
||||
case <-recalculate:
|
||||
routes.calculate()
|
||||
case done := <-wait:
|
||||
select {
|
||||
case <-recalculate:
|
||||
routes.calculate()
|
||||
default:
|
||||
}
|
||||
close(done)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (routes *Routes) calculate() {
|
||||
routes.peers.RLock()
|
||||
routes.ourself.RLock()
|
||||
var (
|
||||
oldUnicast = routes.unicast
|
||||
oldBroadcast = routes.broadcast
|
||||
unicast = routes.calculateUnicast(true)
|
||||
unicastAll = routes.calculateUnicast(false)
|
||||
broadcast = routes.calculateBroadcast(true)
|
||||
broadcastAll = routes.calculateBroadcast(false)
|
||||
)
|
||||
routes.ourself.RUnlock()
|
||||
routes.peers.RUnlock()
|
||||
|
||||
routes.Lock()
|
||||
routes.unicast = unicast
|
||||
routes.unicastAll = unicastAll
|
||||
routes.broadcast = broadcast
|
||||
routes.broadcastAll = broadcastAll
|
||||
onChange := routes.onChange
|
||||
routes.Unlock()
|
||||
|
||||
if !unicast.equals(oldUnicast) || !broadcast.equals(oldBroadcast) {
|
||||
for _, callback := range onChange {
|
||||
callback()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate all the routes for the question: if *we* want to send a
|
||||
// packet to Peer X, what is the next hop?
|
||||
//
|
||||
// When we sniff a packet, we determine the destination peer
|
||||
// ourself. Consequently, we can relay the packet via any
|
||||
// arbitrary peers - the intermediate peers do not have to have
|
||||
// any knowledge of the MAC address at all. Thus there's no need
|
||||
// to exchange knowledge of MAC addresses, nor any constraints on
|
||||
// the routes that we construct.
|
||||
func (routes *Routes) calculateUnicast(establishedAndSymmetric bool) unicastRoutes {
|
||||
_, unicast := routes.ourself.Routes(nil, establishedAndSymmetric)
|
||||
return unicast
|
||||
}
|
||||
|
||||
// Calculate all the routes for the question: if we receive a
|
||||
// broadcast originally from Peer X, which peers should we pass the
|
||||
// frames on to?
|
||||
//
|
||||
// When the topology is stable, and thus all peers perform route
|
||||
// calculations based on the same data, the algorithm ensures that
|
||||
// broadcasts reach every peer exactly once.
|
||||
//
|
||||
// This is largely due to properties of the Peer.Routes algorithm. In
|
||||
// particular:
|
||||
//
|
||||
// ForAll X,Y,Z in Peers.
|
||||
// X.Routes(Y) <= X.Routes(Z) \/
|
||||
// X.Routes(Z) <= X.Routes(Y)
|
||||
// ForAll X,Y,Z in Peers.
|
||||
// Y =/= Z /\ X.Routes(Y) <= X.Routes(Z) =>
|
||||
// X.Routes(Y) u [P | Y.HasSymmetricConnectionTo(P)] <= X.Routes(Z)
|
||||
// where <= is the subset relationship on keys of the returned map.
|
||||
func (routes *Routes) calculateBroadcast(establishedAndSymmetric bool) broadcastRoutes {
|
||||
broadcast := make(broadcastRoutes)
|
||||
for _, peer := range routes.peers.byName {
|
||||
hops := []PeerName{}
|
||||
if found, reached := peer.Routes(routes.ourself.Peer, establishedAndSymmetric); found {
|
||||
routes.ourself.ForEachConnectedPeer(establishedAndSymmetric, reached,
|
||||
func(remotePeer *Peer) { hops = append(hops, remotePeer.Name) })
|
||||
}
|
||||
broadcast[peer.Name] = hops
|
||||
}
|
||||
return broadcast
|
||||
}
|
||||
|
||||
func (a unicastRoutes) equals(b unicastRoutes) bool {
|
||||
for key, aval := range a {
|
||||
if bval, ok := b[key]; !ok || bval != aval {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
for key := range b {
|
||||
if _, ok := a[key]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (a broadcastRoutes) equals(b broadcastRoutes) bool {
|
||||
set := make(map[PeerName]struct{})
|
||||
|
||||
for key, aval := range a {
|
||||
bval, ok := b[key]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, peer := range aval {
|
||||
set[peer] = struct{}{}
|
||||
}
|
||||
|
||||
for _, peer := range bval {
|
||||
if _, ok := set[peer]; !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
delete(set, peer)
|
||||
}
|
||||
|
||||
if len(set) != 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
for key := range b {
|
||||
if _, ok := a[key]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
-193
@@ -1,193 +0,0 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Status struct {
|
||||
Protocol string
|
||||
ProtocolMinVersion int
|
||||
ProtocolMaxVersion int
|
||||
Encryption bool
|
||||
PeerDiscovery bool
|
||||
Name string
|
||||
NickName string
|
||||
Port int
|
||||
Peers []PeerStatus
|
||||
UnicastRoutes []UnicastRouteStatus
|
||||
BroadcastRoutes []BroadcastRouteStatus
|
||||
Connections []LocalConnectionStatus
|
||||
Targets []string
|
||||
OverlayDiagnostics interface{}
|
||||
}
|
||||
|
||||
type PeerStatus struct {
|
||||
Name string
|
||||
NickName string
|
||||
UID PeerUID
|
||||
ShortID PeerShortID
|
||||
Version uint64
|
||||
Connections []ConnectionStatus
|
||||
}
|
||||
|
||||
type ConnectionStatus struct {
|
||||
Name string
|
||||
NickName string
|
||||
Address string
|
||||
Outbound bool
|
||||
Established bool
|
||||
}
|
||||
|
||||
type UnicastRouteStatus struct {
|
||||
Dest, Via string
|
||||
}
|
||||
|
||||
type BroadcastRouteStatus struct {
|
||||
Source string
|
||||
Via []string
|
||||
}
|
||||
|
||||
type LocalConnectionStatus struct {
|
||||
Address string
|
||||
Outbound bool
|
||||
State string
|
||||
Info string
|
||||
}
|
||||
|
||||
func NewStatus(router *Router) *Status {
|
||||
return &Status{
|
||||
Protocol,
|
||||
ProtocolMinVersion,
|
||||
ProtocolMaxVersion,
|
||||
router.UsingPassword(),
|
||||
router.PeerDiscovery,
|
||||
router.Ourself.Name.String(),
|
||||
router.Ourself.NickName,
|
||||
router.Port,
|
||||
NewPeerStatusSlice(router.Peers),
|
||||
NewUnicastRouteStatusSlice(router.Routes),
|
||||
NewBroadcastRouteStatusSlice(router.Routes),
|
||||
NewLocalConnectionStatusSlice(router.ConnectionMaker),
|
||||
NewTargetSlice(router.ConnectionMaker),
|
||||
router.Overlay.Diagnostics()}
|
||||
}
|
||||
|
||||
func NewPeerStatusSlice(peers *Peers) []PeerStatus {
|
||||
var slice []PeerStatus
|
||||
|
||||
peers.ForEach(func(peer *Peer) {
|
||||
var connections []ConnectionStatus
|
||||
if peer == peers.ourself.Peer {
|
||||
for conn := range peers.ourself.Connections() {
|
||||
connections = append(connections, newConnectionStatus(conn))
|
||||
}
|
||||
} else {
|
||||
// Modifying peer.connections requires a write lock on
|
||||
// Peers, and since we are holding a read lock (due to the
|
||||
// ForEach), access without locking the peer is safe.
|
||||
for _, conn := range peer.connections {
|
||||
connections = append(connections, newConnectionStatus(conn))
|
||||
}
|
||||
}
|
||||
slice = append(slice, PeerStatus{
|
||||
peer.Name.String(),
|
||||
peer.NickName,
|
||||
peer.UID,
|
||||
peer.ShortID,
|
||||
peer.Version,
|
||||
connections})
|
||||
})
|
||||
|
||||
return slice
|
||||
}
|
||||
|
||||
func newConnectionStatus(c Connection) ConnectionStatus {
|
||||
return ConnectionStatus{
|
||||
c.Remote().Name.String(),
|
||||
c.Remote().NickName,
|
||||
c.RemoteTCPAddr(),
|
||||
c.Outbound(),
|
||||
c.Established()}
|
||||
}
|
||||
|
||||
func NewUnicastRouteStatusSlice(routes *Routes) []UnicastRouteStatus {
|
||||
routes.RLock()
|
||||
defer routes.RUnlock()
|
||||
|
||||
var slice []UnicastRouteStatus
|
||||
for dest, via := range routes.unicast {
|
||||
slice = append(slice, UnicastRouteStatus{dest.String(), via.String()})
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
func NewBroadcastRouteStatusSlice(routes *Routes) []BroadcastRouteStatus {
|
||||
routes.RLock()
|
||||
defer routes.RUnlock()
|
||||
|
||||
var slice []BroadcastRouteStatus
|
||||
for source, via := range routes.broadcast {
|
||||
var hops []string
|
||||
for _, hop := range via {
|
||||
hops = append(hops, hop.String())
|
||||
}
|
||||
slice = append(slice, BroadcastRouteStatus{source.String(), hops})
|
||||
}
|
||||
return slice
|
||||
}
|
||||
|
||||
func NewLocalConnectionStatusSlice(cm *ConnectionMaker) []LocalConnectionStatus {
|
||||
resultChan := make(chan []LocalConnectionStatus, 0)
|
||||
cm.actionChan <- func() bool {
|
||||
var slice []LocalConnectionStatus
|
||||
for conn := range cm.connections {
|
||||
state := "pending"
|
||||
if conn.Established() {
|
||||
state = "established"
|
||||
}
|
||||
lc, _ := conn.(*LocalConnection)
|
||||
info := fmt.Sprintf("%-6v %v", lc.OverlayConn.DisplayName(), conn.Remote())
|
||||
slice = append(slice, LocalConnectionStatus{conn.RemoteTCPAddr(), conn.Outbound(), state, info})
|
||||
}
|
||||
for address, target := range cm.targets {
|
||||
add := func(state, info string) {
|
||||
slice = append(slice, LocalConnectionStatus{address, true, state, info})
|
||||
}
|
||||
switch target.state {
|
||||
case TargetWaiting:
|
||||
until := "never"
|
||||
if !target.tryAfter.IsZero() {
|
||||
until = target.tryAfter.String()
|
||||
}
|
||||
if target.lastError == nil { // shouldn't happen
|
||||
add("waiting", "until: "+until)
|
||||
} else {
|
||||
add("failed", target.lastError.Error()+", retry: "+until)
|
||||
}
|
||||
case TargetAttempting:
|
||||
if target.lastError == nil {
|
||||
add("connecting", "")
|
||||
} else {
|
||||
add("retrying", target.lastError.Error())
|
||||
}
|
||||
case TargetConnected:
|
||||
}
|
||||
}
|
||||
resultChan <- slice
|
||||
return false
|
||||
}
|
||||
return <-resultChan
|
||||
}
|
||||
|
||||
func NewTargetSlice(cm *ConnectionMaker) []string {
|
||||
resultChan := make(chan []string, 0)
|
||||
cm.actionChan <- func() bool {
|
||||
var slice []string
|
||||
for peer := range cm.directPeers {
|
||||
slice = append(slice, peer)
|
||||
}
|
||||
resultChan <- slice
|
||||
return false
|
||||
}
|
||||
return <-resultChan
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package mesh
|
||||
|
||||
type SurrogateGossipData struct {
|
||||
messages [][]byte
|
||||
}
|
||||
|
||||
func NewSurrogateGossipData(msg []byte) *SurrogateGossipData {
|
||||
return &SurrogateGossipData{messages: [][]byte{msg}}
|
||||
}
|
||||
|
||||
func (d *SurrogateGossipData) Encode() [][]byte {
|
||||
return d.messages
|
||||
}
|
||||
|
||||
func (d *SurrogateGossipData) Merge(other GossipData) {
|
||||
d.messages = append(d.messages, other.(*SurrogateGossipData).messages...)
|
||||
}
|
||||
|
||||
// SurrogateGossiper ignores unicasts and relays broadcasts and gossips.
|
||||
|
||||
type SurrogateGossiper struct{}
|
||||
|
||||
func (*SurrogateGossiper) OnGossipUnicast(sender PeerName, msg []byte) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*SurrogateGossiper) OnGossipBroadcast(_ PeerName, update []byte) (GossipData, error) {
|
||||
return NewSurrogateGossipData(update), nil
|
||||
}
|
||||
|
||||
func (*SurrogateGossiper) Gossip() GossipData {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (*SurrogateGossiper) OnGossip(update []byte) (GossipData, error) {
|
||||
return NewSurrogateGossipData(update), nil
|
||||
}
|
||||
|
||||
var (
|
||||
surrogateGossiper SurrogateGossiper
|
||||
)
|
||||
@@ -1,42 +0,0 @@
|
||||
package mesh
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type TokenBucket struct {
|
||||
capacity int64 // Maximum capacity of bucket
|
||||
tokenInterval time.Duration // Token replenishment rate
|
||||
refillDuration time.Duration // Time to refill from empty
|
||||
earliestUnspentToken time.Time
|
||||
}
|
||||
|
||||
func NewTokenBucket(capacity int64, tokenInterval time.Duration) *TokenBucket {
|
||||
tb := TokenBucket{
|
||||
capacity: capacity,
|
||||
tokenInterval: tokenInterval,
|
||||
refillDuration: tokenInterval * time.Duration(capacity)}
|
||||
|
||||
tb.earliestUnspentToken = tb.capacityToken()
|
||||
|
||||
return &tb
|
||||
}
|
||||
|
||||
func (tb *TokenBucket) Wait() {
|
||||
// If earliest unspent token is in the future, sleep until then
|
||||
time.Sleep(tb.earliestUnspentToken.Sub(time.Now()))
|
||||
|
||||
// Alternatively, enforce bucket capacity if necessary
|
||||
capacityToken := tb.capacityToken()
|
||||
if tb.earliestUnspentToken.Before(capacityToken) {
|
||||
tb.earliestUnspentToken = capacityToken
|
||||
}
|
||||
|
||||
// 'Remove' a token from the bucket
|
||||
tb.earliestUnspentToken = tb.earliestUnspentToken.Add(tb.tokenInterval)
|
||||
}
|
||||
|
||||
// Determine the historic token timestamp representing a full bucket
|
||||
func (tb *TokenBucket) capacityToken() time.Time {
|
||||
return time.Now().Add(-tb.refillDuration).Truncate(tb.tokenInterval)
|
||||
}
|
||||
+76
-23
@@ -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,
|
||||
@@ -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
|
||||
|
||||
+10
-5
@@ -11,21 +11,26 @@ import (
|
||||
"github.com/miekg/dns"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
"github.com/weaveworks/mesh"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
type mockUpstream struct {
|
||||
config *dns.ClientConfig
|
||||
}
|
||||
|
||||
func (mu *mockUpstream) Config() (*dns.ClientConfig, error) {
|
||||
return mu.config, nil
|
||||
}
|
||||
|
||||
func startServer(t *testing.T, upstream *dns.ClientConfig) (*DNSServer, *Nameserver, int, int) {
|
||||
peername, err := mesh.PeerNameFromString("00:00:00:02:00:00")
|
||||
require.Nil(t, err)
|
||||
nameserver := New(peername, "", func(mesh.PeerName) bool { return true })
|
||||
dnsserver, err := NewDNSServer(nameserver, "weave.local.", "0.0.0.0:0", "", 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
|
||||
}
|
||||
|
||||
+40
-12
@@ -8,7 +8,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
"github.com/weaveworks/mesh"
|
||||
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
@@ -93,6 +94,9 @@ func (e1 *Entry) merge(e2 *Entry) bool {
|
||||
e1.Version = e2.Version
|
||||
e1.Tombstone = e2.Tombstone
|
||||
return true
|
||||
} else if e2.Version == e1.Version && e2.Tombstone > e1.Tombstone {
|
||||
e1.Tombstone = e2.Tombstone
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -105,9 +109,18 @@ func (e1 *Entry) addLowercase() {
|
||||
e1.lHostname = strings.ToLower(e1.Hostname)
|
||||
}
|
||||
|
||||
func (e1 *Entry) tombstone() bool {
|
||||
if e1.Tombstone > 0 {
|
||||
return false
|
||||
}
|
||||
e1.Tombstone = now()
|
||||
e1.Version++
|
||||
return true
|
||||
}
|
||||
|
||||
func check(es SortableEntries) error {
|
||||
if !sort.IsSorted(es) {
|
||||
return fmt.Errorf("Not sorted!")
|
||||
return fmt.Errorf("Not sorted")
|
||||
}
|
||||
for i := 1; i < es.Len(); i++ {
|
||||
if es.Get(i).equal(es.Get(i - 1)) {
|
||||
@@ -181,9 +194,7 @@ func (es *Entries) tombstone(ourname mesh.PeerName, f func(*Entry) bool) Entries
|
||||
|
||||
tombstoned := Entries{}
|
||||
for i, e := range *es {
|
||||
if f(&e) && e.Origin == ourname {
|
||||
e.Version++
|
||||
e.Tombstone = now()
|
||||
if f(&e) && e.Origin == ourname && e.tombstone() {
|
||||
(*es)[i] = e
|
||||
tombstoned = append(tombstoned, e)
|
||||
}
|
||||
@@ -191,6 +202,7 @@ func (es *Entries) tombstone(ourname mesh.PeerName, f func(*Entry) bool) Entries
|
||||
return tombstoned
|
||||
}
|
||||
|
||||
// note f() may only modify entries such that they remain in order defined by less()
|
||||
func (es *Entries) filter(f func(*Entry) bool) {
|
||||
defer es.checkAndPanic().checkAndPanic()
|
||||
|
||||
@@ -205,6 +217,16 @@ func (es *Entries) filter(f func(*Entry) bool) {
|
||||
*es = (*es)[:i]
|
||||
}
|
||||
|
||||
func (es Entries) findEqual(e *Entry) (*Entry, bool) {
|
||||
i := sort.Search(len(es), func(i int) bool {
|
||||
return !es[i].insensitiveLess(e)
|
||||
})
|
||||
if i < len(es) && es[i].equal(*e) {
|
||||
return &es[i], true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func (es Entries) lookup(hostname string) Entries {
|
||||
es.checkAndPanic()
|
||||
|
||||
@@ -245,12 +267,14 @@ type GossipData struct {
|
||||
Entries
|
||||
}
|
||||
|
||||
func (g *GossipData) Merge(o mesh.GossipData) {
|
||||
func (g *GossipData) Merge(o mesh.GossipData) mesh.GossipData {
|
||||
other := o.(*GossipData)
|
||||
g.Entries.merge(other.Entries)
|
||||
if g.Timestamp < other.Timestamp {
|
||||
g.Timestamp = other.Timestamp
|
||||
gossip := g.copy()
|
||||
gossip.Entries.merge(other.Entries)
|
||||
if gossip.Timestamp < other.Timestamp {
|
||||
gossip.Timestamp = other.Timestamp
|
||||
}
|
||||
return gossip
|
||||
}
|
||||
|
||||
func (g *GossipData) Decode(msg []byte) error {
|
||||
@@ -264,9 +288,7 @@ func (g *GossipData) Decode(msg []byte) error {
|
||||
}
|
||||
|
||||
func (g *GossipData) Encode() [][]byte {
|
||||
// Make a copy so we can sort: all outgoing data is sent in case-sensitive order
|
||||
g2 := GossipData{Timestamp: g.Timestamp, Entries: make(Entries, len(g.Entries))}
|
||||
copy(g2.Entries, g.Entries)
|
||||
g2 := g.copy()
|
||||
sort.Sort(CaseSensitive(g2.Entries))
|
||||
buf := &bytes.Buffer{}
|
||||
if err := gob.NewEncoder(buf).Encode(g2); err != nil {
|
||||
@@ -274,3 +296,9 @@ func (g *GossipData) Encode() [][]byte {
|
||||
}
|
||||
return [][]byte{buf.Bytes()}
|
||||
}
|
||||
|
||||
func (g *GossipData) copy() *GossipData {
|
||||
g2 := &GossipData{Timestamp: g.Timestamp, Entries: make(Entries, len(g.Entries))}
|
||||
copy(g2.Entries, g.Entries)
|
||||
return g2
|
||||
}
|
||||
@@ -4,8 +4,8 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/weaveworks/mesh"
|
||||
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
@@ -86,6 +86,21 @@ func TestTombstone(t *testing.T) {
|
||||
Entry{Hostname: "B", Version: 1, Tombstone: 1234},
|
||||
})
|
||||
require.Equal(t, expected, es)
|
||||
|
||||
// Now try a merge including two entries which differ only in tombstone
|
||||
e2 := make(Entries, len(es))
|
||||
copy(e2, es)
|
||||
e2[1].Tombstone = 5555
|
||||
|
||||
diff := es.merge(e2)
|
||||
|
||||
expected2 := l(Entries{
|
||||
Entry{Hostname: "A"},
|
||||
Entry{Hostname: "B", Version: 1, Tombstone: 5555},
|
||||
})
|
||||
require.Equal(t, expected2, es)
|
||||
expectedDiff := l(Entries{Entry{Hostname: "B", Version: 1, Tombstone: 5555}})
|
||||
require.Equal(t, expectedDiff, diff)
|
||||
}
|
||||
|
||||
func TestDelete(t *testing.T) {
|
||||
@@ -117,7 +132,7 @@ func TestGossipDataMerge(t *testing.T) {
|
||||
g1 := GossipData{Entries: makeEntries("AcDf")}
|
||||
g2 := GossipData{Entries: makeEntries("BEf")}
|
||||
|
||||
g1.Merge(&g2)
|
||||
g3 := g1.Merge(&g2).(*GossipData)
|
||||
|
||||
require.Equal(t, GossipData{Entries: makeEntries("ABcDEf")}, g1)
|
||||
require.Equal(t, GossipData{Entries: makeEntries("ABcDEf")}, *g3)
|
||||
}
|
||||
+3
-11
@@ -40,16 +40,11 @@ func (n *Nameserver) HandleHTTP(router *mux.Router, dockerCli *docker.Client) {
|
||||
return
|
||||
}
|
||||
|
||||
if err := n.AddEntry(hostname, container, n.ourName, ip); err != nil {
|
||||
n.badRequest(w, fmt.Errorf("Unable to add entry: %v", err))
|
||||
return
|
||||
}
|
||||
n.AddEntry(hostname, container, n.ourName, ip)
|
||||
|
||||
if r.FormValue("check-alive") == "true" && dockerCli != nil && dockerCli.IsContainerNotRunning(container) {
|
||||
n.infof("container '%s' is not running: removing", container)
|
||||
if err := n.Delete(hostname, container, ipStr, ip); err != nil {
|
||||
n.infof("failed to remove: %v", err)
|
||||
}
|
||||
n.Delete(hostname, container, ipStr, ip)
|
||||
}
|
||||
|
||||
w.WriteHeader(204)
|
||||
@@ -79,10 +74,7 @@ func (n *Nameserver) HandleHTTP(router *mux.Router, dockerCli *docker.Client) {
|
||||
ipStr = "*"
|
||||
}
|
||||
|
||||
if err := n.Delete(hostname, container, ipStr, ip); err != nil {
|
||||
n.badRequest(w, fmt.Errorf("Unable to delete entries: %v", err))
|
||||
return
|
||||
}
|
||||
n.Delete(hostname, container, ipStr, ip)
|
||||
w.WriteHeader(204)
|
||||
}
|
||||
router.Methods("DELETE").Path("/name/{container}/{ip}").HandlerFunc(deleteHandler)
|
||||
|
||||
+58
-27
@@ -6,9 +6,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/miekg/dns"
|
||||
"github.com/weaveworks/mesh"
|
||||
|
||||
. "github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
@@ -73,22 +73,22 @@ func (n *Nameserver) Stop() {
|
||||
n.quit <- struct{}{}
|
||||
}
|
||||
|
||||
func (n *Nameserver) broadcastEntries(es ...Entry) error {
|
||||
if n.gossip != nil {
|
||||
return n.gossip.GossipBroadcast(&GossipData{
|
||||
Entries: Entries(es),
|
||||
Timestamp: now(),
|
||||
})
|
||||
func (n *Nameserver) broadcastEntries(es ...Entry) {
|
||||
if n.gossip == nil || len(es) == 0 {
|
||||
return
|
||||
}
|
||||
return nil
|
||||
n.gossip.GossipBroadcast(&GossipData{
|
||||
Entries: Entries(es),
|
||||
Timestamp: now(),
|
||||
})
|
||||
}
|
||||
|
||||
func (n *Nameserver) AddEntry(hostname, containerid string, origin mesh.PeerName, addr address.Address) error {
|
||||
n.infof("adding entry %s -> %s", hostname, addr.String())
|
||||
func (n *Nameserver) AddEntry(hostname, containerid string, origin mesh.PeerName, addr address.Address) {
|
||||
n.Lock()
|
||||
n.infof("adding entry for %s: %s -> %s", containerid, hostname, addr.String())
|
||||
entry := n.entries.add(hostname, containerid, origin, addr)
|
||||
n.Unlock()
|
||||
return n.broadcastEntries(entry)
|
||||
n.broadcastEntries(entry)
|
||||
}
|
||||
|
||||
func (n *Nameserver) Lookup(hostname string) []address.Address {
|
||||
@@ -121,7 +121,8 @@ func (n *Nameserver) ReverseLookup(ip address.Address) (string, error) {
|
||||
return match.Hostname, nil
|
||||
}
|
||||
|
||||
func (n *Nameserver) ContainerStarted(ident string) {}
|
||||
func (n *Nameserver) ContainerStarted(ident string) {}
|
||||
func (n *Nameserver) ContainerDestroyed(ident string) {}
|
||||
|
||||
func (n *Nameserver) ContainerDied(ident string) {
|
||||
n.Lock()
|
||||
@@ -133,11 +134,7 @@ func (n *Nameserver) ContainerDied(ident string) {
|
||||
return false
|
||||
})
|
||||
n.Unlock()
|
||||
if len(entries) > 0 {
|
||||
if err := n.broadcastEntries(entries...); err != nil {
|
||||
n.errorf("failed to broadcast container %s death: %v", ident, err)
|
||||
}
|
||||
}
|
||||
n.broadcastEntries(entries...)
|
||||
}
|
||||
|
||||
func (n *Nameserver) PeerGone(peer mesh.PeerName) {
|
||||
@@ -149,7 +146,7 @@ func (n *Nameserver) PeerGone(peer mesh.PeerName) {
|
||||
})
|
||||
}
|
||||
|
||||
func (n *Nameserver) Delete(hostname, containerid, ipStr string, ip address.Address) error {
|
||||
func (n *Nameserver) Delete(hostname, containerid, ipStr string, ip address.Address) {
|
||||
n.Lock()
|
||||
n.infof("tombstoning hostname=%s, container=%s, ip=%s", hostname, containerid, ipStr)
|
||||
entries := n.entries.tombstone(n.ourName, func(e *Entry) bool {
|
||||
@@ -169,7 +166,7 @@ func (n *Nameserver) Delete(hostname, containerid, ipStr string, ip address.Addr
|
||||
return true
|
||||
})
|
||||
n.Unlock()
|
||||
return n.broadcastEntries(entries...)
|
||||
n.broadcastEntries(entries...)
|
||||
}
|
||||
|
||||
func (n *Nameserver) deleteTombstones() {
|
||||
@@ -202,17 +199,46 @@ 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)
|
||||
}
|
||||
|
||||
n.Lock()
|
||||
defer n.Unlock()
|
||||
|
||||
// Filter to remove entries from unknown peers, done before we take
|
||||
// the nameserver lock, so we don't have to worry what isKnownPeer locks.
|
||||
gossip.Entries.filter(func(e *Entry) bool {
|
||||
return n.isKnownPeer(e.Origin)
|
||||
})
|
||||
|
||||
var overriddenEntries []Entry
|
||||
n.Lock()
|
||||
|
||||
// Check entries claiming to originate from us against our current data
|
||||
gossip.Entries.filter(func(e *Entry) bool {
|
||||
if e.Origin == n.ourName {
|
||||
if ourEntry, ok := n.entries.findEqual(e); ok {
|
||||
if ourEntry.Version < e.Version ||
|
||||
(ourEntry.Version == e.Version && ourEntry.Tombstone != e.Tombstone) {
|
||||
// Take our version of the data, but make the version higher than the incoming
|
||||
nextVersion := e.Version + 1
|
||||
*e = *ourEntry
|
||||
e.Version = nextVersion
|
||||
overriddenEntries = append(overriddenEntries, *e)
|
||||
}
|
||||
} else { // We have no entry matching the one that came in with us as Origin
|
||||
if e.tombstone() {
|
||||
overriddenEntries = append(overriddenEntries, *e)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
newEntries := n.entries.merge(gossip.Entries)
|
||||
n.Unlock() // unlock before attempting to broadcast
|
||||
|
||||
// Note that all overriddenEntries have been merged into our entries, either
|
||||
// because we forced the version higher or because they were missing before.
|
||||
n.broadcastEntries(overriddenEntries...)
|
||||
|
||||
if len(newEntries) > 0 {
|
||||
return &GossipData{Entries: newEntries, Timestamp: now()}, &gossip, nil
|
||||
}
|
||||
@@ -233,12 +259,17 @@ func (n *Nameserver) OnGossipBroadcast(_ mesh.PeerName, msg []byte) (mesh.Gossip
|
||||
return entries, err
|
||||
}
|
||||
|
||||
// Logging
|
||||
|
||||
func (n *Nameserver) infof(fmt string, args ...interface{}) {
|
||||
Log.Infof("[nameserver %s] "+fmt, append([]interface{}{n.ourName}, args...)...)
|
||||
n.logf(common.Log.Infof, fmt, args...)
|
||||
}
|
||||
func (n *Nameserver) debugf(fmt string, args ...interface{}) {
|
||||
Log.Debugf("[nameserver %s] "+fmt, append([]interface{}{n.ourName}, args...)...)
|
||||
n.logf(common.Log.Debugf, fmt, args...)
|
||||
}
|
||||
func (n *Nameserver) errorf(fmt string, args ...interface{}) {
|
||||
Log.Errorf("[nameserver %s] "+fmt, append([]interface{}{n.ourName}, args...)...)
|
||||
n.logf(common.Log.Errorf, fmt, args...)
|
||||
}
|
||||
func (n *Nameserver) logf(f func(string, ...interface{}), fmt string, args ...interface{}) {
|
||||
f("[nameserver %s] "+fmt, append([]interface{}{n.ourName}, args...)...)
|
||||
}
|
||||
@@ -10,10 +10,9 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/weaveworks/mesh"
|
||||
|
||||
"github.com/weaveworks/weave/mesh"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
wt "github.com/weaveworks/weave/testing"
|
||||
"github.com/weaveworks/weave/testing/gossip"
|
||||
)
|
||||
|
||||
@@ -76,12 +75,6 @@ func (m mapping) Addrs() []address.Address {
|
||||
}
|
||||
|
||||
func TestNameservers(t *testing.T) {
|
||||
wt.RunWithTimeout(t, 2*time.Minute, func() {
|
||||
testNameservers(t)
|
||||
})
|
||||
}
|
||||
|
||||
func testNameservers(t *testing.T) {
|
||||
//common.SetLogLevel("debug")
|
||||
|
||||
lookupTimeout := 10 // ms
|
||||
@@ -124,7 +117,7 @@ func testNameservers(t *testing.T) {
|
||||
mapping := mapping{hostname, []pair{{nameserver.ourName, addr}}}
|
||||
mappings = append(mappings, mapping)
|
||||
|
||||
require.Nil(t, nameserver.AddEntry(hostname, "", nameserver.ourName, addr))
|
||||
nameserver.AddEntry(hostname, "", nameserver.ourName, addr)
|
||||
check(nameserver, mapping)
|
||||
}
|
||||
|
||||
@@ -139,7 +132,7 @@ func testNameservers(t *testing.T) {
|
||||
mapping.addrs = append(mapping.addrs, pair{nameserver.ourName, addr})
|
||||
mappings[i] = mapping
|
||||
|
||||
require.Nil(t, nameserver.AddEntry(mapping.hostname, "", nameserver.ourName, addr))
|
||||
nameserver.AddEntry(mapping.hostname, "", nameserver.ourName, addr)
|
||||
check(nameserver, mapping)
|
||||
}
|
||||
|
||||
@@ -164,7 +157,7 @@ func testNameservers(t *testing.T) {
|
||||
mappings[i] = mapping
|
||||
nameserver := nameserversByName[pair.origin]
|
||||
|
||||
require.Nil(t, nameserver.Delete(mapping.hostname, "*", pair.addr.String(), pair.addr))
|
||||
nameserver.Delete(mapping.hostname, "*", pair.addr.String(), pair.addr)
|
||||
check(nameserver, mapping)
|
||||
}
|
||||
|
||||
@@ -230,15 +223,13 @@ func TestContainerAndPeerDeath(t *testing.T) {
|
||||
require.Nil(t, err)
|
||||
nameserver := makeNameserver(peername)
|
||||
|
||||
err = nameserver.AddEntry("hostname", "containerid", peername, address.Address(0))
|
||||
require.Nil(t, err)
|
||||
nameserver.AddEntry("hostname", "containerid", peername, address.Address(0))
|
||||
require.Equal(t, []address.Address{0}, nameserver.Lookup("hostname"))
|
||||
|
||||
nameserver.ContainerDied("containerid")
|
||||
require.Equal(t, []address.Address{}, nameserver.Lookup("hostname"))
|
||||
|
||||
err = nameserver.AddEntry("hostname", "containerid", peername, address.Address(0))
|
||||
require.Nil(t, err)
|
||||
nameserver.AddEntry("hostname", "containerid", peername, address.Address(0))
|
||||
require.Equal(t, []address.Address{0}, nameserver.Lookup("hostname"))
|
||||
|
||||
nameserver.PeerGone(peername)
|
||||
@@ -254,15 +245,13 @@ func TestTombstoneDeletion(t *testing.T) {
|
||||
require.Nil(t, err)
|
||||
nameserver := makeNameserver(peername)
|
||||
|
||||
err = nameserver.AddEntry("hostname", "containerid", peername, address.Address(0))
|
||||
require.Nil(t, err)
|
||||
nameserver.AddEntry("hostname", "containerid", peername, address.Address(0))
|
||||
require.Equal(t, []address.Address{0}, nameserver.Lookup("hostname"))
|
||||
|
||||
nameserver.deleteTombstones()
|
||||
require.Equal(t, []address.Address{0}, nameserver.Lookup("hostname"))
|
||||
|
||||
err = nameserver.Delete("hostname", "containerid", "", address.Address(0))
|
||||
require.Nil(t, err)
|
||||
nameserver.Delete("hostname", "containerid", "", address.Address(0))
|
||||
require.Equal(t, []address.Address{}, nameserver.Lookup("hostname"))
|
||||
require.Equal(t, l(Entries{Entry{
|
||||
ContainerID: "containerid",
|
||||
|
||||
Loaded 100 of 403 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user