Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
af3fc82b77 | ||
|
|
3bb7036cfd | ||
|
|
d802eb485b | ||
|
|
b4dd9c2490 | ||
|
|
04ab198e95 | ||
|
|
c345700963 | ||
|
|
b21afcb78f | ||
|
|
7f65530b26 | ||
|
|
e230eeb486 | ||
|
|
b1b03f3a97 | ||
|
|
36ef6d8da2 | ||
|
|
28bb0dd3b0 | ||
|
|
79f8a8f909 | ||
|
|
bfe2513540 | ||
|
|
2536aef737 | ||
|
|
0661ccdea9 | ||
|
|
29f7015e58 | ||
|
|
7dc1b50d3b | ||
|
|
682133a56a | ||
|
|
76db732f1b | ||
|
|
64aa2b5cc3 | ||
|
|
1eed5fd271 | ||
|
|
dd7ffa4501 | ||
|
|
5e79da0c9c | ||
|
|
65fe753a58 | ||
|
|
17c835e672 | ||
|
|
fa5bcd0457 | ||
|
|
499d6312b7 | ||
|
|
20d5903696 | ||
|
|
f039221ec9 | ||
|
|
dc9f8762ca | ||
|
|
e5c69801a7 | ||
|
|
ab458368de | ||
|
|
8eb680fcc9 | ||
|
|
71b05972f4 | ||
|
|
7deeb8dba5 | ||
|
|
daad190188 | ||
|
|
7cc0052451 | ||
|
|
18f8e29af8 | ||
|
|
d78f6614cc | ||
|
|
53ac43202a | ||
|
|
bfb050c9cc | ||
|
|
97622b961c | ||
|
|
b7e7c11a57 | ||
|
|
59e11e4e8a | ||
|
|
39b0357517 | ||
|
|
878a12002c | ||
|
|
d3d2863d52 | ||
|
|
f3f23b43b5 | ||
|
|
006262ed8f | ||
|
|
519aef86fa | ||
|
|
8bd77c0d47 | ||
|
|
7a5c910cd5 | ||
|
|
4e34f2001c | ||
|
|
54b7e2c874 | ||
|
|
19f89f7122 | ||
|
|
9f25d72a4a | ||
|
|
3e99b71e0f | ||
|
|
fb3b690c5f | ||
|
|
18fe553d23 | ||
|
|
e7ff6a2191 | ||
|
|
ad15e5a234 | ||
|
|
094c7aa438 | ||
|
|
472ed5a7d3 | ||
|
|
f284300870 | ||
|
|
ab5f4410b6 | ||
|
|
a400a3dd09 | ||
|
|
c4855e8123 | ||
|
|
3abb657777 | ||
|
|
4dd63d8f72 | ||
|
|
6c0bdec2c9 | ||
|
|
d6dd1f269a | ||
|
|
2bd23f3709 | ||
|
|
79dc464b83 | ||
|
|
3dbd5b11d8 | ||
|
|
549fd5fe29 | ||
|
|
0ab5c65185 | ||
|
|
5f4753f881 | ||
|
|
e03a45f8c5 | ||
|
|
67c2d53e59 | ||
|
|
7b01dd9794 | ||
|
|
04e50ff02c | ||
|
|
e57ccdc858 | ||
|
|
a73a65b48a | ||
|
|
781c805e10 | ||
|
|
7c4af88911 | ||
|
|
0aca173e96 | ||
|
|
60db57a41c | ||
|
|
df68f7dcb9 | ||
|
|
e95e3cc29d | ||
|
|
67e9310cc1 | ||
|
|
94984df047 | ||
|
|
861bf953bf | ||
|
|
06eaac8690 | ||
|
|
6b9e8816d1 | ||
|
|
cf9f715a28 | ||
|
|
08e043fea7 | ||
|
|
54ba1009d3 | ||
|
|
4a6a46fd7b | ||
|
|
0eb7414f72 | ||
|
|
c02691a70c | ||
|
|
8742957f75 | ||
|
|
ace372cfc6 | ||
|
|
27c5f5f49f | ||
|
|
ec5a1a3977 | ||
|
|
abc653c87d | ||
|
|
30a128c00b | ||
|
|
77c7963bde | ||
|
|
b80c1343c3 | ||
|
|
e5c5fbcea3 | ||
|
|
fc7d04ce73 | ||
|
|
35f57b3ed2 | ||
|
|
ed9ade05ea | ||
|
|
f485b729bc | ||
|
|
64f7953423 | ||
|
|
fc1d837501 | ||
|
|
6a238818da | ||
|
|
9800c43bfa | ||
|
|
b3cda39b0b | ||
|
|
7e1b7a2fcd | ||
|
|
ccae8d42fd | ||
|
|
38f78286c7 | ||
|
|
c3a53a6ec5 | ||
|
|
2bbe6a2ad8 | ||
|
|
f64f63f675 | ||
|
|
f0fb44f6d4 | ||
|
|
fa972615e6 | ||
|
|
f7cb2ef25d | ||
|
|
949f8bb2e7 | ||
|
|
5eaff802d4 | ||
|
|
b37d428726 | ||
|
|
a6336149f8 | ||
|
|
12f5ec40b5 | ||
|
|
6a672cf8bc | ||
|
|
036b7d9f66 | ||
|
|
ca6bce3820 | ||
|
|
9b4f2297b0 | ||
|
|
355c4bc58e | ||
|
|
a31b2876d0 | ||
|
|
b697b6e57b | ||
|
|
4792fe6391 | ||
|
|
7408f72de2 | ||
|
|
203a3cfdd1 | ||
|
|
3a029686e3 | ||
|
|
9fa7b3956d | ||
|
|
b888ace508 | ||
|
|
2e54efe34d | ||
|
|
94d7f33a03 | ||
|
|
11598295cf | ||
|
|
383c3769aa | ||
|
|
caccba18e9 | ||
|
|
95ce00e551 | ||
|
|
85dad58cfe | ||
|
|
acdc70f72a | ||
|
|
bc9db72212 | ||
|
|
7905f81d7e | ||
|
|
2ef9cc6a1c | ||
|
|
2357b69a5f | ||
|
|
98b47f866b | ||
|
|
d2dfb0d15d | ||
|
|
63339c026d | ||
|
|
40922b88f7 | ||
|
|
674c617c16 | ||
|
|
720862dafb | ||
|
|
5120b4b468 | ||
|
|
c5f5d4543e | ||
|
|
baf92da9f2 | ||
|
|
526c9c3d73 | ||
|
|
119d87b795 | ||
|
|
17b030cca1 | ||
|
|
81c9b3f327 | ||
|
|
50198a2159 | ||
|
|
20c8b1eaf6 | ||
|
|
faf94b033d | ||
|
|
342b3d3d08 | ||
|
|
26d43f193e | ||
|
|
f04ae91c82 | ||
|
|
bb5515113f | ||
|
|
c09d84efc6 | ||
|
|
2a99a2a597 | ||
|
|
08f6dcf08d | ||
|
|
284d78c6b2 | ||
|
|
b8b5fb9eae | ||
|
|
697ee62d9a | ||
|
|
20ac0edda8 | ||
|
|
a1bbb06e2f | ||
|
|
a87cc5c3b1 | ||
|
|
5ff740cddc | ||
|
|
db9229f485 | ||
|
|
ace6c01b00 | ||
|
|
2f4d460c04 | ||
|
|
d5401ae3cb | ||
|
|
3482a7f1f1 | ||
|
|
23762e0639 | ||
|
|
50cc15af3b | ||
|
|
a0f0367e60 | ||
|
|
3f08ddefe9 | ||
|
|
debe656f9f | ||
|
|
4350b6402f | ||
|
|
5d2892f6fc | ||
|
|
91f27ddd66 | ||
|
|
6a674d0d67 | ||
|
|
0d165487b8 | ||
|
|
bacc5836ea | ||
|
|
230931a2c9 | ||
|
|
61d289f414 | ||
|
|
ed9577150b | ||
|
|
80319e7087 | ||
|
|
9ac655d774 | ||
|
|
9b17470129 | ||
|
|
5c114fc6fe | ||
|
|
efb12f41b2 | ||
|
|
813edcfe0a | ||
|
|
e7f91b7529 | ||
|
|
5e8a64508e | ||
|
|
21ebb7a585 | ||
|
|
34606e0f9f | ||
|
|
5f62f9edc7 | ||
|
|
207ee8667b | ||
|
|
5984ed921e | ||
|
|
aac5fb8c24 | ||
|
|
8cfc8b3132 | ||
|
|
0b5ebd7b1d | ||
|
|
35141a4aef | ||
|
|
e702234fdc | ||
|
|
05dbe23563 | ||
|
|
fd07f59d16 | ||
|
|
b36ffe1c9d | ||
|
|
1f1451e666 | ||
|
|
1000839309 | ||
|
|
ed4ed5fdcd | ||
|
|
eaf1f7cc13 | ||
|
|
ddfc8c2fa4 | ||
|
|
91afa7f7f4 | ||
|
|
da49e71eb8 | ||
|
|
5b8ce8ebae | ||
|
|
827bcd898e | ||
|
|
54a4add40f | ||
|
|
0093eb1220 | ||
|
|
b56ff7678c | ||
|
|
431197042d | ||
|
|
295876e1d1 | ||
|
|
e8f975e44f | ||
|
|
bc026ab49a | ||
|
|
f56457271d | ||
|
|
bc5250c899 | ||
|
|
971511e1c5 | ||
|
|
51317dca70 | ||
|
|
91dda08165 | ||
|
|
67bfa5bc90 | ||
|
|
0841f4a27d | ||
|
|
c6edf8421f | ||
|
|
97fb90c37a | ||
|
|
1d00bdd2a4 | ||
|
|
0ff01871f3 | ||
|
|
e00a675e6e | ||
|
|
d5e63b875b | ||
|
|
a31ccef99b | ||
|
|
375fd9140b | ||
|
|
fbb2c213a9 | ||
|
|
d323de3c21 | ||
|
|
18872cc8ae | ||
|
|
33085e3798 | ||
|
|
e42c811797 | ||
|
|
6b321e4d02 | ||
|
|
e6b17c701d | ||
|
|
60bda7ca25 | ||
|
|
b37d2ec6d1 | ||
|
|
751ccd02f6 | ||
|
|
431d04bd40 | ||
|
|
e756f07cad | ||
|
|
b9e025b50e | ||
|
|
7a83a71a02 | ||
|
|
8aaf4cfaf0 | ||
|
|
8a62d13cee | ||
|
|
5a45471c68 | ||
|
|
6dc21bf433 | ||
|
|
1da24c68ca | ||
|
|
3d3b7a5672 | ||
|
|
f221a6d953 | ||
|
|
bcf71c0fee | ||
|
|
a689317696 | ||
|
|
14a5c12404 | ||
|
|
35b2968b60 | ||
|
|
7e0ae6112c | ||
|
|
9ad1ccf19f | ||
|
|
5b7599e1b0 | ||
|
|
19c44b83d2 | ||
|
|
41691957d2 | ||
|
|
e3182624fd | ||
|
|
b8b3e6f9cd | ||
|
|
520c7286c4 | ||
|
|
d547829c5f | ||
|
|
49459cfc9b | ||
|
|
827ba446ab | ||
|
|
e5316221bb | ||
|
|
357174e351 | ||
|
|
5fc2f3236e | ||
|
|
a3484cce43 | ||
|
|
cdd107b06b | ||
|
|
6619b584bc | ||
|
|
c24dc6609d | ||
|
|
b5e7e9fc5c | ||
|
|
58dec7334b | ||
|
|
725f99ea4c | ||
|
|
4b43cac093 | ||
|
|
0159e26631 | ||
|
|
19cb1ae8e1 | ||
|
|
f8e06f9dbc | ||
|
|
e68ca761a9 | ||
|
|
fc6f3993bd | ||
|
|
f8b074f696 | ||
|
|
66f14099ed | ||
|
|
f3dc681f61 | ||
|
|
62f62e1fd5 | ||
|
|
f3360e61e1 | ||
|
|
5208dcd67e | ||
|
|
6f7d4a7a01 | ||
|
|
958a3bbd99 | ||
|
|
d5c85a3d53 | ||
|
|
5e7b527af2 | ||
|
|
ddbd33a8e3 | ||
|
|
a7dc368191 | ||
|
|
5b1d0c55a0 | ||
|
|
58515ca36e | ||
|
|
2d753ebf1a | ||
|
|
50cd716220 | ||
|
|
47c97e8191 | ||
|
|
d704ba175f | ||
|
|
c1036c5e4f | ||
|
|
356d06915b | ||
|
|
2b4fd738da | ||
|
|
f9de6df426 | ||
|
|
8ee409d9bd | ||
|
|
0cf3de4541 | ||
|
|
3a79be7426 | ||
|
|
30589bb580 | ||
|
|
e07b18bb18 | ||
|
|
b31eb3db82 | ||
|
|
da2f98fa8f | ||
|
|
7ac532a1d0 | ||
|
|
b98ab70517 | ||
|
|
0bdc84d42a | ||
|
|
7d7c5ce63e | ||
|
|
547c9e7f5a | ||
|
|
3e4d8a3ffe | ||
|
|
74c3b0d079 | ||
|
|
846aa98a9c | ||
|
|
17b7789531 | ||
|
|
b86d9bf1ab | ||
|
|
6e06ce2af1 | ||
|
|
dc275d7009 | ||
|
|
a67b9f566a | ||
|
|
9344289262 | ||
|
|
6713282a43 | ||
|
|
f834626f00 | ||
|
|
0a475bd4cc | ||
|
|
0820c484d6 | ||
|
|
dbf39403d3 | ||
|
|
14dde9ec32 | ||
|
|
3c266dce8c | ||
|
|
f7be34f5bb | ||
|
|
64b47ca3c7 | ||
|
|
e39974eb07 | ||
|
|
76895c76a1 | ||
|
|
98e8ef4e11 | ||
|
|
2ffe9b53f7 | ||
|
|
405c2889f0 | ||
|
|
40cf392504 | ||
|
|
42baa03985 | ||
|
|
016f20b2d4 | ||
|
|
d5faf31d43 | ||
|
|
12324e3550 | ||
|
|
ff67499562 | ||
|
|
2ba5abc2bb | ||
|
|
3d4a8aaad1 | ||
|
|
4ded1f6193 | ||
|
|
940a0a67a5 | ||
|
|
be304365a0 | ||
|
|
fc00767cf8 | ||
|
|
2163b39268 | ||
|
|
aa61cbd574 | ||
|
|
2a89d53d7d | ||
|
|
f7ce6a1fdb | ||
|
|
20b35f3139 | ||
|
|
46d454730f | ||
|
|
c2ad4c5633 | ||
|
|
6e0795253e | ||
|
|
9956d422f2 | ||
|
|
57e78e2bec | ||
|
|
5aa58e6043 | ||
|
|
5860492a38 | ||
|
|
e74b6cbcaf | ||
|
|
1e2344341d | ||
|
|
b712ba1e04 | ||
|
|
9ae6435317 | ||
|
|
81e8ba223f |
No files matched your search
+15
-8
@@ -25,6 +25,7 @@ _testmain.go
|
|||||||
*.prof
|
*.prof
|
||||||
profile.cov
|
profile.cov
|
||||||
coverage.html
|
coverage.html
|
||||||
|
.pkg
|
||||||
|
|
||||||
# Emacs backup files
|
# Emacs backup files
|
||||||
*~
|
*~
|
||||||
@@ -39,24 +40,30 @@ prog/weavehosts/weavehosts
|
|||||||
prog/weaveproxy/weaveproxy
|
prog/weaveproxy/weaveproxy
|
||||||
prog/sigproxy/sigproxy
|
prog/sigproxy/sigproxy
|
||||||
prog/weavewait/weavewait
|
prog/weavewait/weavewait
|
||||||
prog/netcheck/netcheck
|
prog/weavewait/weavewait_noop
|
||||||
|
prog/weavewait/weavewait_nomcast
|
||||||
|
prog/weaveutil/weaveutil
|
||||||
|
prog/*/Dockerfile.weaveworks
|
||||||
|
prog/kube-peers/kube-peers
|
||||||
|
prog/weave-kube/kube-peers
|
||||||
|
prog/weave-npc/weave-npc
|
||||||
|
prog/plugin/plugin
|
||||||
testing/cover/cover
|
testing/cover/cover
|
||||||
testing/runner/runner
|
testing/runner/runner
|
||||||
tools/bin
|
|
||||||
tools/build
|
|
||||||
releases
|
releases
|
||||||
.weaver.uptodate
|
.*.uptodate
|
||||||
.weavedns.uptodate
|
weave.tar.gz
|
||||||
.weaveexec.uptodate
|
|
||||||
weave.tar
|
|
||||||
prog/weaveexec/weave
|
prog/weaveexec/weave
|
||||||
prog/weaveexec/weavehosts
|
prog/weaveexec/weavehosts
|
||||||
prog/weaveexec/weaveproxy
|
prog/weaveexec/weaveproxy
|
||||||
prog/weaveexec/weavewait
|
prog/weaveexec/weavewait
|
||||||
|
prog/weaveexec/weavewait_noop
|
||||||
|
prog/weaveexec/weavewait_nomcast
|
||||||
prog/weaveexec/sigproxy
|
prog/weaveexec/sigproxy
|
||||||
prog/weaveexec/netcheck
|
prog/weaveexec/weaveutil
|
||||||
prog/weaveexec/docker*.tgz
|
prog/weaveexec/docker*.tgz
|
||||||
Vagrantfile.local
|
Vagrantfile.local
|
||||||
|
test/tls/tls
|
||||||
test/tls/*.pem
|
test/tls/*.pem
|
||||||
test/coverage
|
test/coverage
|
||||||
test/coverage.*
|
test/coverage.*
|
||||||
+120
@@ -0,0 +1,120 @@
|
|||||||
|
[submodule "tools"]
|
||||||
|
path = tools
|
||||||
|
url = https://github.com/weaveworks/build-tools
|
||||||
|
[submodule "vendor/github.com/vishvananda/netlink"]
|
||||||
|
path = vendor/github.com/vishvananda/netlink
|
||||||
|
url = https://github.com/vishvananda/netlink.git
|
||||||
|
[submodule "vendor/github.com/gorilla/mux"]
|
||||||
|
path = vendor/github.com/gorilla/mux
|
||||||
|
url = https://github.com/gorilla/mux.git
|
||||||
|
[submodule "vendor/github.com/gorilla/context"]
|
||||||
|
path = vendor/github.com/gorilla/context
|
||||||
|
url = https://github.com/gorilla/context.git
|
||||||
|
[submodule "vendor/github.com/google/gopacket"]
|
||||||
|
path = vendor/github.com/google/gopacket
|
||||||
|
url = https://github.com/google/gopacket.git
|
||||||
|
[submodule "vendor/github.com/weaveworks/go-odp"]
|
||||||
|
path = vendor/github.com/weaveworks/go-odp
|
||||||
|
url = https://github.com/weaveworks/go-odp.git
|
||||||
|
[submodule "vendor/github.com/opencontainers/runc"]
|
||||||
|
path = vendor/github.com/opencontainers/runc
|
||||||
|
url = https://github.com/opencontainers/runc.git
|
||||||
|
[submodule "vendor/github.com/fsouza/go-dockerclient"]
|
||||||
|
path = vendor/github.com/fsouza/go-dockerclient
|
||||||
|
url = https://github.com/fsouza/go-dockerclient.git
|
||||||
|
[submodule "vendor/github.com/docker/libnetwork"]
|
||||||
|
path = vendor/github.com/docker/libnetwork
|
||||||
|
url = https://github.com/docker/libnetwork.git
|
||||||
|
[submodule "vendor/github.com/docker/docker"]
|
||||||
|
path = vendor/github.com/docker/docker
|
||||||
|
url = https://github.com/docker/docker.git
|
||||||
|
[submodule "vendor/github.com/Sirupsen/logrus"]
|
||||||
|
path = vendor/github.com/Sirupsen/logrus
|
||||||
|
url = https://github.com/Sirupsen/logrus.git
|
||||||
|
[submodule "vendor/github.com/andybalholm/go-bit"]
|
||||||
|
path = vendor/github.com/andybalholm/go-bit
|
||||||
|
url = https://github.com/andybalholm/go-bit.git
|
||||||
|
[submodule "vendor/github.com/miekg/dns"]
|
||||||
|
path = vendor/github.com/miekg/dns
|
||||||
|
url = https://github.com/miekg/dns.git
|
||||||
|
[submodule "vendor/github.com/stretchr/testify"]
|
||||||
|
path = vendor/github.com/stretchr/testify
|
||||||
|
url = https://github.com/stretchr/testify.git
|
||||||
|
[submodule "vendor/golang.org/x/crypto"]
|
||||||
|
path = vendor/golang.org/x/crypto
|
||||||
|
url = https://github.com/golang/crypto.git
|
||||||
|
[submodule "vendor/github.com/docker/machine"]
|
||||||
|
path = vendor/github.com/docker/machine
|
||||||
|
url = https://github.com/docker/machine.git
|
||||||
|
[submodule "vendor/github.com/weaveworks/mesh"]
|
||||||
|
path = vendor/github.com/weaveworks/mesh
|
||||||
|
url = https://github.com/weaveworks/mesh
|
||||||
|
[submodule "vendor/github.com/vishvananda/netns"]
|
||||||
|
path = vendor/github.com/vishvananda/netns
|
||||||
|
url = https://github.com/vishvananda/netns.git
|
||||||
|
[submodule "vendor/github.com/boltdb/bolt"]
|
||||||
|
path = vendor/github.com/boltdb/bolt
|
||||||
|
url = https://github.com/boltdb/bolt
|
||||||
|
[submodule "vendor/github.com/weaveworks/go-checkpoint"]
|
||||||
|
path = vendor/github.com/weaveworks/go-checkpoint
|
||||||
|
url = https://github.com/weaveworks/go-checkpoint
|
||||||
|
[submodule "vendor/github.com/hashicorp/go-cleanhttp"]
|
||||||
|
path = vendor/github.com/hashicorp/go-cleanhttp
|
||||||
|
url = http://github.com/hashicorp/go-cleanhttp
|
||||||
|
[submodule "vendor/github.com/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/weaveworks/docker"]
|
||||||
|
path = vendor/github.com/weaveworks/docker
|
||||||
|
url = https://github.com/weaveworks/docker
|
||||||
|
[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
|
||||||
-29
@@ -1,29 +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:
|
|
||||||
- bin/lint .
|
|
||||||
- make tests
|
|
||||||
|
|
||||||
after_success:
|
|
||||||
- go get github.com/mattn/goveralls
|
|
||||||
- test "${TRAVIS_PULL_REQUEST}" = "false" && site/build-and-publish.sh
|
|
||||||
+319
@@ -1,3 +1,322 @@
|
|||||||
|
## 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**
|
||||||
|
|
||||||
|
This release includes a Docker Plugin, so you have the option to use
|
||||||
|
Weave Net that way. More details in the
|
||||||
|
[change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.3.0).
|
||||||
|
|
||||||
|
## Release 1.2.1
|
||||||
|
|
||||||
|
This release contains a number of bug fixes and minor
|
||||||
|
enhancements. More details in the
|
||||||
|
[change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.2.1).
|
||||||
|
|
||||||
|
The release is fully compatible with 1.2.0 versions, so existing
|
||||||
|
clusters can be upgraded incrementally.
|
||||||
|
|
||||||
|
## Release 1.2.0
|
||||||
|
|
||||||
|
**Highlights**
|
||||||
|
|
||||||
|
This release introduces the
|
||||||
|
[Fast Data Path](http://docs.weave.works/weave/master/head/features.html#fast-data-path),
|
||||||
|
which allows Weave networks to operate at near wire-level speeds. This
|
||||||
|
new feature is enabled by default.
|
||||||
|
|
||||||
|
Other highlights:
|
||||||
|
|
||||||
|
- auto-configuration of TLS for the Weave Docker API proxy, making it
|
||||||
|
easier to run Weave on Macs and in conjunction with tools like
|
||||||
|
Docker Swarm
|
||||||
|
- support for restart policies on application containers and weave
|
||||||
|
infrastructure containers
|
||||||
|
- better compatibility with recent and future Docker versions
|
||||||
|
|
||||||
|
More details in the
|
||||||
|
[change log](https://github.com/weaveworks/weave/issues?q=milestone%3A1.2.0).
|
||||||
|
|
||||||
## Release 1.1.2
|
## Release 1.1.2
|
||||||
|
|
||||||
This release contains a small number of bug fixes. More details in the
|
This release contains a small number of bug fixes. More details in the
|
||||||
|
|||||||
@@ -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
|
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");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
PUBLISH=publish_weave publish_weaveexec
|
PUBLISH=publish_weave publish_weaveexec publish_plugin publish_weave-kube publish_weave-npc
|
||||||
|
|
||||||
.DEFAULT: all
|
.DEFAULT: all
|
||||||
.PHONY: all update tests publish $(PUBLISH) clean prerequisites build travis run-smoketests
|
.PHONY: all exes testrunner update tests lint publish $(PUBLISH) clean clean-bin prerequisites build run-smoketests
|
||||||
|
|
||||||
# If you can use docker without being root, you can do "make SUDO="
|
# 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
|
DOCKERHUB_USER=weaveworks
|
||||||
WEAVE_VERSION=git-$(shell git rev-parse --short=12 HEAD)
|
WEAVE_VERSION=git-$(shell git rev-parse --short=12 HEAD)
|
||||||
@@ -12,27 +16,41 @@ WEAVE_VERSION=git-$(shell git rev-parse --short=12 HEAD)
|
|||||||
WEAVER_EXE=prog/weaver/weaver
|
WEAVER_EXE=prog/weaver/weaver
|
||||||
WEAVEPROXY_EXE=prog/weaveproxy/weaveproxy
|
WEAVEPROXY_EXE=prog/weaveproxy/weaveproxy
|
||||||
SIGPROXY_EXE=prog/sigproxy/sigproxy
|
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_EXE=prog/weavewait/weavewait
|
||||||
WEAVEHOSTS_EXE=prog/weavehosts/weavehosts
|
WEAVEWAIT_NOOP_EXE=prog/weavewait/weavewait_noop
|
||||||
NETCHECK_EXE=prog/netcheck/netcheck
|
WEAVEWAIT_NOMCAST_EXE=prog/weavewait/weavewait_nomcast
|
||||||
COVER_EXE=testing/cover/cover
|
WEAVEUTIL_EXE=prog/weaveutil/weaveutil
|
||||||
RUNNER_EXE=testing/runner/runner
|
PLUGIN_EXE=prog/plugin/plugin
|
||||||
|
RUNNER_EXE=tools/runner/runner
|
||||||
|
TEST_TLS_EXE=test/tls/tls
|
||||||
|
|
||||||
EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEHOSTS_EXE) $(NETCHECK_EXE) $(COVER_EXE) $(RUNNER_EXE)
|
EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(KUBEPEERS_EXE) $(WEAVENPC_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(WEAVEUTIL_EXE) $(PLUGIN_EXE) $(TEST_TLS_EXE)
|
||||||
|
|
||||||
|
BUILD_UPTODATE=.build.uptodate
|
||||||
WEAVER_UPTODATE=.weaver.uptodate
|
WEAVER_UPTODATE=.weaver.uptodate
|
||||||
WEAVEEXEC_UPTODATE=.weaveexec.uptodate
|
WEAVEEXEC_UPTODATE=.weaveexec.uptodate
|
||||||
|
PLUGIN_UPTODATE=.plugin.uptodate
|
||||||
|
WEAVEKUBE_UPTODATE=.weavekube.uptodate
|
||||||
|
WEAVENPC_UPTODATE=.weavenpc.uptodate
|
||||||
|
WEAVEDB_UPTODATE=.weavedb.uptodate
|
||||||
|
|
||||||
IMAGES_UPTODATE=$(WEAVER_UPTODATE) $(WEAVEEXEC_UPTODATE)
|
IMAGES_UPTODATE=$(WEAVER_UPTODATE) $(WEAVEEXEC_UPTODATE) $(PLUGIN_UPTODATE) $(WEAVEKUBE_UPTODATE) $(WEAVENPC_UPTODATE) $(WEAVEDB_UPTODATE)
|
||||||
|
|
||||||
WEAVER_IMAGE=$(DOCKERHUB_USER)/weave
|
WEAVER_IMAGE=$(DOCKERHUB_USER)/weave
|
||||||
WEAVEEXEC_IMAGE=$(DOCKERHUB_USER)/weaveexec
|
WEAVEEXEC_IMAGE=$(DOCKERHUB_USER)/weaveexec
|
||||||
|
PLUGIN_IMAGE=$(DOCKERHUB_USER)/plugin
|
||||||
|
WEAVEKUBE_IMAGE=$(DOCKERHUB_USER)/weave-kube
|
||||||
|
WEAVENPC_IMAGE=$(DOCKERHUB_USER)/weave-npc
|
||||||
|
BUILD_IMAGE=$(DOCKERHUB_USER)/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
|
WEAVE_EXPORT=weave.tar.gz
|
||||||
|
|
||||||
WEAVEEXEC_DOCKER_VERSION=1.3.1
|
WEAVEEXEC_DOCKER_VERSION=1.6.2
|
||||||
DOCKER_DISTRIB=prog/weaveexec/docker-$(WEAVEEXEC_DOCKER_VERSION).tgz
|
DOCKER_DISTRIB=prog/weaveexec/docker-$(WEAVEEXEC_DOCKER_VERSION).tgz
|
||||||
DOCKER_DISTRIB_URL=https://get.docker.com/builds/Linux/x86_64/docker-$(WEAVEEXEC_DOCKER_VERSION).tgz
|
DOCKER_DISTRIB_URL=https://get.docker.com/builds/Linux/x86_64/docker-$(WEAVEEXEC_DOCKER_VERSION).tgz
|
||||||
NETGO_CHECK=@strings $@ | grep cgo_stub\\\.go >/dev/null || { \
|
NETGO_CHECK=@strings $@ | grep cgo_stub\\\.go >/dev/null || { \
|
||||||
@@ -43,90 +61,153 @@ NETGO_CHECK=@strings $@ | grep cgo_stub\\\.go >/dev/null || { \
|
|||||||
echo " sudo go install -tags netgo std"; \
|
echo " sudo go install -tags netgo std"; \
|
||||||
false; \
|
false; \
|
||||||
}
|
}
|
||||||
BUILD_FLAGS=-ldflags "-extldflags \"-static\" -X main.version $(WEAVE_VERSION)" -tags netgo
|
BUILD_FLAGS=-i -ldflags "-linkmode external -extldflags -static -X main.version=$(WEAVE_VERSION)" -tags netgo
|
||||||
|
|
||||||
all: $(WEAVE_EXPORT) $(COVER_EXE) $(RUNNER_EXE)
|
PACKAGE_BASE=$(shell go list -e ./)
|
||||||
|
|
||||||
travis: $(EXES)
|
all: $(WEAVE_EXPORT)
|
||||||
|
testrunner: $(RUNNER_EXE) $(TEST_TLS_EXE)
|
||||||
|
|
||||||
update:
|
$(WEAVER_EXE) $(WEAVEPROXY_EXE) $(WEAVEUTIL_EXE): common/*.go common/*/*.go net/*.go net/*/*.go
|
||||||
go get -u -f -v -tags netgo $(addprefix ./,$(dir $(EXES)))
|
$(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
|
||||||
|
$(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
|
||||||
|
$(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
|
||||||
|
|
||||||
$(WEAVER_EXE) $(WEAVEPROXY_EXE): common/*.go common/*/*.go net/*.go
|
ifeq ($(BUILD_IN_CONTAINER),true)
|
||||||
|
|
||||||
|
exes $(EXES) tests lint: $(BUILD_UPTODATE)
|
||||||
|
git submodule update --init
|
||||||
|
@mkdir -p $(shell pwd)/.pkg
|
||||||
|
$(SUDO) docker run $(RM) $(RUN_FLAGS) \
|
||||||
|
-v $(shell pwd):/go/src/github.com/weaveworks/weave \
|
||||||
|
-v $(shell pwd)/.pkg:/go/pkg \
|
||||||
|
-e GOARCH -e GOOS -e CIRCLECI -e CIRCLE_BUILD_NUM -e CIRCLE_NODE_TOTAL -e CIRCLE_NODE_INDEX -e COVERDIR -e SLOW \
|
||||||
|
$(BUILD_IMAGE) COVERAGE=$(COVERAGE) WEAVE_VERSION=$(WEAVE_VERSION) $@
|
||||||
|
|
||||||
|
else
|
||||||
|
|
||||||
|
exes: $(EXES)
|
||||||
|
|
||||||
|
$(WEAVER_EXE) $(WEAVEPROXY_EXE) $(PLUGIN_EXE):
|
||||||
ifeq ($(COVERAGE),true)
|
ifeq ($(COVERAGE),true)
|
||||||
$(eval COVERAGE_MODULES := $(shell (go list ./$(@D); go list -f '{{join .Deps "\n"}}' ./$(@D) | grep "weaveworks") | paste -s -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 get -t -tags netgo ./$(@D)
|
|
||||||
go test -c -o ./$@ $(BUILD_FLAGS) -v -covermode=atomic -coverpkg $(COVERAGE_MODULES) ./$(@D)/
|
go test -c -o ./$@ $(BUILD_FLAGS) -v -covermode=atomic -coverpkg $(COVERAGE_MODULES) ./$(@D)/
|
||||||
else
|
else
|
||||||
go get -tags netgo ./$(@D)
|
|
||||||
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
||||||
endif
|
endif
|
||||||
$(NETGO_CHECK)
|
$(NETGO_CHECK)
|
||||||
|
|
||||||
$(NETCHECK_EXE): common/*.go common/*/*.go net/*.go
|
$(WEAVEUTIL_EXE) $(KUBEPEERS_EXE) $(WEAVENPC_EXE):
|
||||||
go get -tags netgo ./$(@D)
|
|
||||||
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
||||||
$(NETGO_CHECK)
|
$(NETGO_CHECK)
|
||||||
|
|
||||||
$(WEAVER_EXE): router/*.go ipam/*.go ipam/*/*.go nameserver/*.go prog/weaver/*.go
|
$(WEAVEWAIT_EXE):
|
||||||
$(WEAVEPROXY_EXE): proxy/*.go prog/weaveproxy/main.go
|
go build $(BUILD_FLAGS) -tags "netgo iface mcast" -o $@ ./$(@D)
|
||||||
$(NETCHECK_EXE): prog/netcheck/netcheck.go
|
$(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
|
# the main build stanza due to not importing net package
|
||||||
$(SIGPROXY_EXE): prog/sigproxy/main.go
|
$(SIGPROXY_EXE) $(TEST_TLS_EXE) $(WEAVEWAIT_NOOP_EXE):
|
||||||
$(WEAVEWAIT_EXE): prog/weavewait/main.go net/*.go
|
|
||||||
$(WEAVEHOSTS_EXE): prog/weavehosts/weavehosts.go
|
|
||||||
$(COVER_EXE): testing/cover/cover.go
|
|
||||||
$(RUNNER_EXE): testing/runner/runner.go
|
|
||||||
|
|
||||||
$(WEAVEWAIT_EXE) $(SIGPROXY_EXE) $(WEAVEHOSTS_EXE) $(COVER_EXE) $(RUNNER_EXE):
|
|
||||||
go get -tags netgo ./$(@D)
|
|
||||||
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
||||||
|
|
||||||
$(WEAVER_UPTODATE): prog/weaver/Dockerfile $(WEAVER_EXE)
|
tests:
|
||||||
$(SUDO) docker build -t $(WEAVER_IMAGE) prog/weaver
|
./tools/test -no-go-get
|
||||||
|
|
||||||
|
lint:
|
||||||
|
./tools/lint -nocomment -notestpackage .
|
||||||
|
|
||||||
|
endif
|
||||||
|
|
||||||
|
$(BUILD_UPTODATE): build/*
|
||||||
|
$(SUDO) docker build -t $(BUILD_IMAGE) build/
|
||||||
touch $@
|
touch $@
|
||||||
|
|
||||||
$(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEHOSTS_EXE) $(NETCHECK_EXE)
|
%/Dockerfile.$(DOCKERHUB_USER): %/Dockerfile.template
|
||||||
|
sed -e "s/DOCKERHUB_USER/$(DOCKERHUB_USER)/" $^ > $@
|
||||||
|
|
||||||
|
$(WEAVER_UPTODATE): prog/weaver/Dockerfile.$(DOCKERHUB_USER) $(WEAVER_EXE) $(WEAVEEXEC_UPTODATE)
|
||||||
|
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build -f prog/weaver/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVER_IMAGE) prog/weaver
|
||||||
|
touch $@
|
||||||
|
|
||||||
|
$(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile prog/weaveexec/symlink $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(WEAVEUTIL_EXE)
|
||||||
cp weave prog/weaveexec/weave
|
cp weave prog/weaveexec/weave
|
||||||
cp $(SIGPROXY_EXE) prog/weaveexec/sigproxy
|
cp $(SIGPROXY_EXE) prog/weaveexec/sigproxy
|
||||||
cp $(WEAVEPROXY_EXE) prog/weaveexec/weaveproxy
|
cp $(WEAVEPROXY_EXE) prog/weaveexec/weaveproxy
|
||||||
cp $(WEAVEWAIT_EXE) prog/weaveexec/weavewait
|
cp $(WEAVEWAIT_EXE) prog/weaveexec/weavewait
|
||||||
cp $(WEAVEHOSTS_EXE) prog/weaveexec/weavehosts
|
cp $(WEAVEWAIT_NOOP_EXE) prog/weaveexec/weavewait_noop
|
||||||
cp $(NETCHECK_EXE) prog/weaveexec/netcheck
|
cp $(WEAVEWAIT_NOMCAST_EXE) prog/weaveexec/weavewait_nomcast
|
||||||
|
cp $(WEAVEUTIL_EXE) prog/weaveexec/weaveutil
|
||||||
cp $(DOCKER_DISTRIB) prog/weaveexec/docker.tgz
|
cp $(DOCKER_DISTRIB) prog/weaveexec/docker.tgz
|
||||||
$(SUDO) docker build -t $(WEAVEEXEC_IMAGE) prog/weaveexec
|
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build -t $(WEAVEEXEC_IMAGE) prog/weaveexec
|
||||||
|
touch $@
|
||||||
|
|
||||||
|
$(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 $(WEAVENPC_EXE) prog/weave-npc/ulogd.conf
|
||||||
|
$(SUDO) docker build -f prog/weave-npc/Dockerfile -t $(WEAVENPC_IMAGE) prog/weave-npc
|
||||||
|
touch $@
|
||||||
|
|
||||||
|
$(WEAVEDB_UPTODATE): prog/weavedb/Dockerfile
|
||||||
|
$(SUDO) docker build -t $(WEAVEDB_IMAGE) prog/weavedb
|
||||||
touch $@
|
touch $@
|
||||||
|
|
||||||
$(WEAVE_EXPORT): $(IMAGES_UPTODATE)
|
$(WEAVE_EXPORT): $(IMAGES_UPTODATE)
|
||||||
$(SUDO) docker save $(addsuffix :latest,$(IMAGES)) > $@
|
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker save $(addsuffix :latest,$(IMAGES)) | gzip > $@
|
||||||
|
|
||||||
$(DOCKER_DISTRIB):
|
$(DOCKER_DISTRIB):
|
||||||
curl -o $(DOCKER_DISTRIB) $(DOCKER_DISTRIB_URL)
|
curl -o $(DOCKER_DISTRIB) $(DOCKER_DISTRIB_URL)
|
||||||
|
|
||||||
tests: $(COVER_EXE)
|
tools/.git:
|
||||||
@test/units.sh
|
git submodule update --init
|
||||||
|
|
||||||
|
$(RUNNER_EXE): tools/.git
|
||||||
|
GO15VENDOREXPERIMENT=1 make -C tools/runner
|
||||||
|
|
||||||
$(PUBLISH): publish_%: $(IMAGES_UPTODATE)
|
$(PUBLISH): publish_%: $(IMAGES_UPTODATE)
|
||||||
$(SUDO) docker tag -f $(DOCKERHUB_USER)/$* $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
|
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker tag $(DOCKERHUB_USER)/$* $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
|
||||||
$(SUDO) docker push $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
|
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
|
||||||
ifneq ($(UPDATE_LATEST),false)
|
ifneq ($(UPDATE_LATEST),false)
|
||||||
$(SUDO) docker push $(DOCKERHUB_USER)/$*:latest
|
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/$*:latest
|
||||||
endif
|
endif
|
||||||
|
|
||||||
publish: $(PUBLISH)
|
publish: $(PUBLISH)
|
||||||
|
ifeq ($(PUBLISH_WEAVEDB),true)
|
||||||
|
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker push $(DOCKERHUB_USER)/weavedb:latest
|
||||||
|
endif
|
||||||
|
|
||||||
clean:
|
clean-bin:
|
||||||
-$(SUDO) docker rmi $(IMAGES)
|
-$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker rmi $(IMAGES)
|
||||||
go clean -r ./...
|
rm -rf $(EXES) $(IMAGES_UPTODATE) $(WEAVE_EXPORT) .pkg
|
||||||
rm -f $(EXES) $(IMAGES_UPTODATE) $(WEAVE_EXPORT)
|
|
||||||
rm -rf test/tls/*.pem test/coverage.* test/coverage
|
clean: clean-bin
|
||||||
|
-$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker rmi $(BUILD_IMAGE)
|
||||||
|
rm -rf test/tls/*.pem test/coverage.* test/coverage $(BUILD_UPTODATE)
|
||||||
|
|
||||||
build:
|
build:
|
||||||
$(SUDO) go clean -i net
|
$(SUDO) go clean -i net
|
||||||
$(SUDO) go install -tags netgo std
|
$(SUDO) go install -tags netgo std
|
||||||
$(MAKE)
|
$(MAKE)
|
||||||
|
|
||||||
run-smoketests: all
|
run-smoketests: all testrunner
|
||||||
cd test && ./setup.sh && ./run_all.sh
|
cd test && ./setup.sh && ./run_all.sh
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
Weave
|
Weave
|
||||||
Copyright 2014-2015 Weaveworks Ltd.
|
Copyright 2014-2016 Weaveworks Ltd.
|
||||||
This product includes software developed at Weaveworks Ltd.
|
This product includes software developed at Weaveworks Ltd.
|
||||||
@@ -1,163 +1,25 @@
|
|||||||
# 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
|
# About Weaveworks
|
||||||
|
|
||||||
[Weaveworks](http://weave.works) is the company that develops Weave -
|
[Weaveworks](https://www.weave.works) is the company that delivers the most productive way for developers to connect, observe and control
|
||||||
the most productive way for developers to connect, observe and control
|
Docker containers.
|
||||||
Docker containers. To learn about our products, including getting
|
|
||||||
started tutorials, visit our [website](http://weave.works), read about
|
|
||||||
the
|
|
||||||
[1.0 release of Weave](http://blog.weave.works/2015/06/18/weave-1-0-is-out-and-it-is-good/),
|
|
||||||
visit our [documentation](http://docs.weave.works) or continue to read
|
|
||||||
about some of the more technical aspects of Weave in this readme.
|
|
||||||
|
|
||||||
# Weave
|
This repository contains [Weave Net](https://www.weave.works/products/weave-net/), the first product developed by Weaveworks, and with nearly 5 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
|
Other products from Weaveworks include [Weave Scope](https://www.weave.works/products/weave-scope/), a powerful container monitoring tool that automatically maps Docker containers and their interactions, and [Weave Flux](https://www.weave.works/products/weave-flux/), a microservice router that automates the access of containers as services.
|
||||||
deployed across multiple hosts and enables their automatic discovery.
|
|
||||||
|
|
||||||

|
# Weave Net
|
||||||
|
|
||||||
Applications use the network just as if the containers were all
|
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.
|
||||||
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.
|
|
||||||
|
|
||||||

|
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.
|
||||||
|
|
||||||
Weave can traverse firewalls and operate in partially connected
|
* [Install Weave Net](https://www.weave.works/docs/net/latest/installing-weave/)
|
||||||
networks. Traffic can be encrypted, allowing hosts to be connected
|
* [Find out more about Weave Net](https://www.weave.works/products/weave-net/)
|
||||||
across an untrusted network.
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
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/BF9YJSPQUcY" 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 open the weave port (6783 by
|
|
||||||
default; this can be overriden by setting `WEAVE_PORT`) for TCP and
|
|
||||||
UDP.
|
|
||||||
|
|
||||||
Note that we could instead have told the weave on `$HOST1` to connect to
|
|
||||||
`$HOST2`, or told both about each other. Order does not matter here;
|
|
||||||
weave automatically (re)connects to peers when they become
|
|
||||||
available. Also, we can tell weave to connect to multiple peers by
|
|
||||||
supplying multiple addresses, separated by spaces. And we can
|
|
||||||
[add peers dynamically](http://docs.weave.works/weave/latest_release/features.html#dynamic-topologies).
|
|
||||||
|
|
||||||
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?
|
|
||||||
[File an issue](https://github.com/weaveworks/weave/issues), or email
|
|
||||||
help@weave.works. When reporting a bug, please include which version of
|
|
||||||
weave you are running, as shown by `weave version`.
|
|
||||||
|
|
||||||
Follow weave on Twitter:
|
|
||||||
[@weaveworks](https://twitter.com/weaveworks).
|
|
||||||
|
|
||||||
Read the Weave blog:
|
|
||||||
[Weaveblog](http://weaveblog.com/).
|
|
||||||
|
|
||||||
IRC:
|
|
||||||
[#weavenetwork](https://botbot.me/freenode/weavenetwork/)
|
|
||||||
Vendored
+6
-3
@@ -5,7 +5,7 @@ require './vagrant-common.rb'
|
|||||||
vm_ip = "172.16.0.3" # arbitrary private IP
|
vm_ip = "172.16.0.3" # arbitrary private IP
|
||||||
|
|
||||||
pkgs = %w(
|
pkgs = %w(
|
||||||
docker-engine
|
docker-engine=1.10.2-0~wily
|
||||||
aufs-tools
|
aufs-tools
|
||||||
build-essential
|
build-essential
|
||||||
ethtool
|
ethtool
|
||||||
@@ -18,11 +18,11 @@ pkgs = %w(
|
|||||||
|
|
||||||
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
||||||
|
|
||||||
config.vm.box = "ubuntu/ubuntu-15.04-amd64"
|
config.vm.box = "ubuntu/wily64"
|
||||||
config.vm.box_url = "https://cloud-images.ubuntu.com/vagrant/vivid/current/vivid-server-cloudimg-amd64-vagrant-disk1.box"
|
|
||||||
|
|
||||||
config.vm.network "private_network", ip: vm_ip
|
config.vm.network "private_network", ip: vm_ip
|
||||||
config.vm.provider :virtualbox do |vb|
|
config.vm.provider :virtualbox do |vb|
|
||||||
|
vb.memory = 2048
|
||||||
vb.customize ["modifyvm", :id, "--natdnshostresolver1", "off"]
|
vb.customize ["modifyvm", :id, "--natdnshostresolver1", "off"]
|
||||||
vb.customize ["modifyvm", :id, "--natdnsproxy1", "off"]
|
vb.customize ["modifyvm", :id, "--natdnsproxy1", "off"]
|
||||||
end
|
end
|
||||||
@@ -30,6 +30,9 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
|
|||||||
config.vm.synced_folder ".", "/vagrant", disabled: true
|
config.vm.synced_folder ".", "/vagrant", disabled: true
|
||||||
config.vm.synced_folder ".", "/home/vagrant/src/github.com/weaveworks/weave"
|
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_build_deps config.vm, pkgs
|
||||||
install_go_toochain config.vm
|
install_go_toochain config.vm
|
||||||
tweak_user_env 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
|
||||||
|
}
|
||||||
+63
@@ -0,0 +1,63 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
)
|
||||||
|
|
||||||
|
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
+40
@@ -0,0 +1,40 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "TEST_AND_PUBLISH=1" > "$STATE"
|
||||||
|
|
||||||
|
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
|
||||||
|
bin/setup-circleci-secrets "$SECRET_PASSWORD"
|
||||||
|
cd $SRCDIR; git submodule update --init
|
||||||
|
if [ "$CIRCLE_NODE_INDEX" = "0" ] ; 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
|
||||||
|
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
+16
@@ -0,0 +1,16 @@
|
|||||||
|
#!/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
|
||||||
|
make publish
|
||||||
|
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
+14
@@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
source "$STATE"
|
||||||
|
|
||||||
|
if [ -n "$TEST_AND_PUBLISH" ] ; then
|
||||||
|
cd $SRCDIR
|
||||||
|
make testrunner
|
||||||
|
make COVERAGE=true RM= all
|
||||||
|
|
||||||
|
cd $SRCDIR/test
|
||||||
|
./gce.sh setup && eval $(./gce.sh hosts) && ./setup.sh
|
||||||
|
fi
|
||||||
Executable
+12
@@ -0,0 +1,12 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
set -e
|
||||||
|
|
||||||
|
source "$STATE"
|
||||||
|
|
||||||
|
if [ -n "$TEST_AND_PUBLISH" ] ; then
|
||||||
|
cd $SRCDIR/test
|
||||||
|
eval $(./gce.sh hosts)
|
||||||
|
export COVERAGE=true
|
||||||
|
./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
|
||||||
|
./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 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
|
||||||
@@ -1,86 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# This scipt lints go files for common errors.
|
|
||||||
#
|
|
||||||
# Its runs gofmt and go vet, and optionally golint and
|
|
||||||
# gocyclo, if they are installed.
|
|
||||||
#
|
|
||||||
# With no arguments, it lints the current files staged
|
|
||||||
# for git commit. Or you can pass it explicit filenames
|
|
||||||
# (or directories) and it will lint them.
|
|
||||||
#
|
|
||||||
# To use this script automatically, run:
|
|
||||||
# ln -s ../../bin/lint .git/hooks/pre-commit
|
|
||||||
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
function lint_go {
|
|
||||||
filename="$1"
|
|
||||||
result=0
|
|
||||||
|
|
||||||
if [ -n "$(gofmt -l "${filename}")" ]; then
|
|
||||||
result=1
|
|
||||||
echo "${filename}: run gofmt -w ${filename}!"
|
|
||||||
fi
|
|
||||||
|
|
||||||
go tool vet "${filename}" || result=$?
|
|
||||||
|
|
||||||
# golint is completely optional. If you don't like it
|
|
||||||
# don't have it installed.
|
|
||||||
if type golint >/dev/null 2>&1; then
|
|
||||||
# golint doesn't set an exit code it seems
|
|
||||||
lintoutput=$(golint "${filename}" | grep -vE 'comment|dot imports')
|
|
||||||
if [ "$lintoutput" != "" ]; then
|
|
||||||
result=1
|
|
||||||
echo "$lintoutput"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
# gocyclo is completely optional. If you don't like it
|
|
||||||
# don't have it installed. Also never blocks a commit,
|
|
||||||
# it just warns.
|
|
||||||
if type gocyclo >/dev/null 2>&1; then
|
|
||||||
gocyclo -over 25 "${filename}" | while read line; do
|
|
||||||
echo "${filename}": higher than 25 cyclomatic complexity - "${line}"
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
return $result
|
|
||||||
}
|
|
||||||
|
|
||||||
function lint {
|
|
||||||
filename="$1"
|
|
||||||
ext="${filename##*\.}"
|
|
||||||
|
|
||||||
# Don't lint this script
|
|
||||||
if [ "${filename}" = "bin/lint" ]; then
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Don't lint files that don't exist
|
|
||||||
if ! [ -a "${filename}" ]; then
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
case "$ext" in
|
|
||||||
go) lint_go "${filename}"
|
|
||||||
;;
|
|
||||||
esac
|
|
||||||
}
|
|
||||||
|
|
||||||
function lint_files {
|
|
||||||
lint_result=0
|
|
||||||
while read filename; do
|
|
||||||
lint "${filename}" || lint_result=1
|
|
||||||
done
|
|
||||||
exit $lint_result
|
|
||||||
}
|
|
||||||
|
|
||||||
function list_files {
|
|
||||||
if [ $# -gt 0 ]; then
|
|
||||||
find "$@" -type f
|
|
||||||
else
|
|
||||||
git diff --cached --name-only
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
list_files "$@" | lint_files
|
|
||||||
+28
-14
@@ -14,7 +14,8 @@ set -e
|
|||||||
START=${FIRST_WEAVE:-1}
|
START=${FIRST_WEAVE:-1}
|
||||||
COUNT=${NUM_WEAVES:-28}
|
COUNT=${NUM_WEAVES:-28}
|
||||||
FINISH=$((START+COUNT-1))
|
FINISH=$((START+COUNT-1))
|
||||||
PORT=6783
|
|
||||||
|
SUBNET=192.168.7
|
||||||
|
|
||||||
weavedir=$(dirname $0)/..
|
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)) )
|
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
|
case "$1" in
|
||||||
launch)
|
launch)
|
||||||
shift 1
|
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
|
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
|
done
|
||||||
if [ $START -ne 1 ] ; then
|
echo $(date -R) connecting
|
||||||
status=$(docker inspect --format='{{.State.Running}} {{.NetworkSettings.IPAddress}}' weave$((START-1)))
|
[ $START -gt 1 ] || START=2
|
||||||
PREV="${status#true }"
|
|
||||||
fi
|
|
||||||
for i in $(seq $START $FINISH); do
|
for i in $(seq $START $FINISH); do
|
||||||
if [ -n "$PREV" ] ; then
|
TO=$SUBNET.$((i-1))
|
||||||
echo connecting weave$i to $PREV
|
echo connecting weave$i to $TO
|
||||||
WEAVE_CONTAINER_NAME=weave$i $weavedir/weave connect $PREV
|
weave $i connect $TO
|
||||||
fi
|
|
||||||
status=$(docker inspect --format='{{.State.Running}} {{.NetworkSettings.IPAddress}}' weave$i)
|
|
||||||
PREV="${status#true }"
|
|
||||||
done
|
done
|
||||||
|
echo $(date -R) done
|
||||||
;;
|
;;
|
||||||
stop)
|
stop)
|
||||||
for i in $(seq $START $FINISH); do
|
for i in $(seq $FINISH -1 $START); do
|
||||||
|
IP=$SUBNET.$i
|
||||||
echo Stopping weave$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
|
done
|
||||||
|
[ -z "$(ip -4 -o addr show multiweave)" ] && sudo ip link del multiweave
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
weave "$@"
|
||||||
;;
|
;;
|
||||||
esac
|
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" \
|
--name "weave" \
|
||||||
--file "./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 "** Draft $TYPE $RELEASE_NAME $VERSION created at"
|
||||||
echo -e "\thttps://github.com/$GITHUB_USER/weave/releases/$LATEST_TAG"
|
echo -e "\thttps://github.com/$GITHUB_USER/weave/releases/$LATEST_TAG"
|
||||||
}
|
}
|
||||||
@@ -165,14 +187,14 @@ publish() {
|
|||||||
setup
|
setup
|
||||||
cd $PWD/$RELEASE_DIR
|
cd $PWD/$RELEASE_DIR
|
||||||
|
|
||||||
UPDATE_LATEST=false
|
IS_MAINLINE=false
|
||||||
if [ "$RELEASE_TYPE" = 'MAINLINE' ] ; then
|
if [ "$RELEASE_TYPE" = 'MAINLINE' ] ; then
|
||||||
UPDATE_LATEST=true
|
IS_MAINLINE=true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "$RELEASE_TYPE" = 'PRERELEASE' ] ; then
|
if [ "$RELEASE_TYPE" = 'PRERELEASE' ] ; then
|
||||||
echo "== Tagging and pushing images on docker hub as user $DOCKERHUB_USER"
|
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 "** Docker images tagged and pushed"
|
||||||
|
|
||||||
echo "== Publishing pre-release on GitHub"
|
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 '** 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"
|
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 "** Docker images tagged and pushed"
|
||||||
|
|
||||||
echo "== Publishing release on GitHub"
|
echo "== Publishing release on GitHub"
|
||||||
@@ -236,6 +258,40 @@ publish() {
|
|||||||
--name "weave" \
|
--name "weave" \
|
||||||
--file "./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 "** 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_TAG"
|
||||||
echo -e "\thttps://github.com/$GITHUB_USER/weave/releases/latest_release"
|
echo -e "\thttps://github.com/$GITHUB_USER/weave/releases/latest_release"
|
||||||
|
|||||||
+6
-22
@@ -1,24 +1,8 @@
|
|||||||
FROM ubuntu
|
FROM golang:1.5.2
|
||||||
|
ENV GO15VENDOREXPERIMENT 1
|
||||||
RUN apt-get -y update && apt-get -y install --no-install-recommends ca-certificates apt-transport-https
|
RUN apt-get update && apt-get install -y libpcap-dev python-requests time file
|
||||||
RUN apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 36A1D7869245C8950F966E92D8576A8BA88D21E9
|
RUN go get github.com/fzipp/gocyclo github.com/client9/misspell/cmd/misspell
|
||||||
RUN echo deb https://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list
|
RUN go clean -i net && go install -tags netgo std
|
||||||
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
|
RUN go install -race -tags netgo std
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
# So that sudo does not trample on PATH
|
|
||||||
RUN sed -ie '/secure_path/d' /etc/sudoers
|
|
||||||
|
|
||||||
COPY build.sh /
|
COPY build.sh /
|
||||||
ENTRYPOINT ["sh", "/build.sh"]
|
ENTRYPOINT ["sh", "/build.sh"]
|
||||||
+19
-40
@@ -1,49 +1,28 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
set -e
|
|
||||||
|
|
||||||
GOPATH=/home/go
|
set -eu
|
||||||
export GOPATH
|
|
||||||
|
|
||||||
WEAVE_SRC=$GOPATH/src/github.com/weaveworks/weave
|
WEAVE_SRC=$GOPATH/src/github.com/weaveworks/weave
|
||||||
|
|
||||||
if [ $# -eq 0 -o "$1" = "tests" ] ; then
|
if ! [ -e $WEAVE_SRC ] ; then
|
||||||
# No arguments. Expect that the weave repo will be bind-mounted
|
cat 2>&1 <<EOF
|
||||||
# into $GOPATH
|
Nothing at ${WEAVE_SRC}. Please bind-mount the weave repo with the
|
||||||
if ! [ -e $WEAVE_SRC ] ; then
|
|
||||||
cat 2>&1 <<EOF
|
|
||||||
No container arguments supplied, and nothing at ${WEAVE_SRC}. Please
|
|
||||||
either bind-mount the golang workspace containing weave with the
|
|
||||||
docker run -v option, e.g.:
|
docker run -v option, e.g.:
|
||||||
|
|
||||||
$ docker run -v <host gopath>:${GOPATH} \\
|
$ cd weave
|
||||||
-v /var/run/docker.sock:/var/run/docker.sock weaveworks/weave-build
|
$ docker run -v $(pwd):/go/src/github.com/weaveworks/weave \\
|
||||||
|
weaveworks/weave-build
|
||||||
Or supply git clone arguments to retrieve it, e.g.:
|
|
||||||
|
|
||||||
$ docker run -v /var/run/docker.sock:/var/run/docker.sock \\
|
|
||||||
weaveworks/weave-build https://github.com/weaveworks/weave.git
|
|
||||||
EOF
|
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
|
|
||||||
|
|
||||||
if [ "$1" = "tests" ] ; then
|
|
||||||
su weave -c "PATH=$PATH make -C $WEAVE_SRC tests"
|
|
||||||
else
|
|
||||||
su weave -c "PATH=$PATH make -C $WEAVE_SRC build"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
# There are arguments to pass to git-clone
|
|
||||||
mkdir -p ${WEAVE_SRC%/*}
|
|
||||||
git clone "$@" $WEAVE_SRC
|
|
||||||
make -C $WEAVE_SRC build
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# If we run make directly, any files created on the bind mount
|
||||||
|
# will have awkward ownership. So we switch to a user with the
|
||||||
|
# same user and group IDs as source directory. We have to set a
|
||||||
|
# few things up so that sudo works without complaining later on.
|
||||||
|
uid=$(stat --format="%u" $WEAVE_SRC)
|
||||||
|
gid=$(stat --format="%g" $WEAVE_SRC)
|
||||||
|
echo "weave:x:$uid:$gid::$WEAVE_SRC:/bin/sh" >>/etc/passwd
|
||||||
|
echo "weave:*:::::::" >>/etc/shadow
|
||||||
|
echo "weave ALL=(ALL) NOPASSWD: ALL" >>/etc/sudoers
|
||||||
|
|
||||||
|
su weave -c "PATH=$PATH make -C $WEAVE_SRC BUILD_IN_CONTAINER=false $*"
|
||||||
+27
-31
@@ -10,52 +10,48 @@ machine:
|
|||||||
environment:
|
environment:
|
||||||
GOPATH: /home/ubuntu:$GOPATH
|
GOPATH: /home/ubuntu:$GOPATH
|
||||||
SRCDIR: /home/ubuntu/src/github.com/weaveworks/weave
|
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
|
CLOUDSDK_CORE_DISABLE_PROMPTS: 1
|
||||||
WEAVE_BUILD: $HOME/docker/weave-build.tar
|
STATE: /home/ubuntu/state.env
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
cache_directories:
|
cache_directories:
|
||||||
- "~/docker"
|
- "~/docker"
|
||||||
override:
|
|
||||||
- if [[ -e "$WEAVE_BUILD" ]]; then
|
|
||||||
docker load -i $WEAVE_BUILD;
|
|
||||||
else
|
|
||||||
docker build -t weaveworks/weave-build build;
|
|
||||||
mkdir -p $(dirname "$WEAVE_BUILD");
|
|
||||||
docker save weaveworks/weave-build >$WEAVE_BUILD;
|
|
||||||
fi
|
|
||||||
post:
|
post:
|
||||||
- sudo apt-get update
|
- bin/circle-dependencies-post
|
||||||
- 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
|
|
||||||
|
|
||||||
test:
|
test:
|
||||||
|
pre:
|
||||||
|
- "$SRCDIR/bin/circle-test-pre":
|
||||||
|
parallel: true
|
||||||
override:
|
override:
|
||||||
- docker run -v /var/run/docker.sock:/run/docker.sock -v /home/ubuntu:/home/go -e COVERDIR=test/coverage -e SLOW=true -e CIRCLECI -e CIRCLE_BUILD_NUM -e CIRCLE_NODE_TOTAL -e CIRCLE_NODE_INDEX weaveworks/weave-build tests:
|
- "$SRCDIR/bin/circle-test-unit":
|
||||||
parallel: true
|
parallel: true
|
||||||
- docker run -v /var/run/docker.sock:/run/docker.sock -v /home/ubuntu:/home/go -e COVERAGE=true weaveworks/weave-build:
|
- "$SRCDIR/bin/circle-test-smoke":
|
||||||
parallel: true
|
|
||||||
- cd $SRCDIR/test; ./gce.sh setup:
|
|
||||||
parallel: true
|
|
||||||
- cd $SRCDIR/test; eval $(./gce.sh hosts); ./setup.sh:
|
|
||||||
parallel: true
|
|
||||||
- cd $SRCDIR/test; eval $(./gce.sh hosts); export COVERAGE=true; ./run_all.sh:
|
|
||||||
parallel: true
|
parallel: true
|
||||||
timeout: 300
|
timeout: 300
|
||||||
post:
|
post:
|
||||||
- cd $SRCDIR/test; ./gce.sh destroy:
|
- "$SRCDIR/bin/circle-test-teardown":
|
||||||
parallel: true
|
parallel: true
|
||||||
|
|
||||||
teardown:
|
teardown:
|
||||||
pre:
|
pre:
|
||||||
- test "$CIRCLE_NODE_INDEX" != "0" || (cd $SRCDIR/test; ./gen_coverage_reports.sh):
|
- "$SRCDIR/bin/circle-teardown-pre":
|
||||||
parallel: true
|
|
||||||
- test "$CIRCLE_NODE_INDEX" != "0" || (go get github.com/mattn/goveralls && goveralls -repotoken $COVERALLS_REPO_TOKEN -coverprofile=$SRCDIR/test/profile.cov -service=circleci || true):
|
|
||||||
parallel: true
|
|
||||||
- test "$CIRCLE_NODE_INDEX" != "0" || (cd $SRCDIR/test; cp coverage.* $CIRCLE_ARTIFACTS):
|
|
||||||
parallel: true
|
parallel: true
|
||||||
|
|
||||||
|
deployment:
|
||||||
|
release:
|
||||||
|
branch: /[0-9]+\.[0-9]+/
|
||||||
|
owner: weaveworks
|
||||||
|
commands:
|
||||||
|
- "$SRCDIR/bin/circle-deploy-release"
|
||||||
|
master:
|
||||||
|
branch: master
|
||||||
|
owner: weaveworks
|
||||||
|
commands:
|
||||||
|
- "$SRCDIR/bin/circle-deploy-master"
|
||||||
|
issues:
|
||||||
|
branch: /.*/
|
||||||
|
owner: weaveworks
|
||||||
|
commands:
|
||||||
|
- "$SRCDIR/bin/circle-deploy-issues"
|
||||||
+174
-18
@@ -1,64 +1,220 @@
|
|||||||
package docker
|
package docker
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/fsouza/go-dockerclient"
|
"errors"
|
||||||
. "github.com/weaveworks/weave/common"
|
"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
|
// An observer for container events
|
||||||
type ContainerObserver interface {
|
type ContainerObserver interface {
|
||||||
|
ContainerStarted(ident string)
|
||||||
ContainerDied(ident string)
|
ContainerDied(ident string)
|
||||||
|
ContainerDestroyed(ident string)
|
||||||
}
|
}
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
*docker.Client
|
*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
|
// NewClient creates a new Docker client and checks we can talk to Docker
|
||||||
func NewClient(apiPath string) (*Client, error) {
|
func NewClient(apiPath string) (*Client, error) {
|
||||||
|
if apiPath != "" && !strings.Contains(apiPath, "://") {
|
||||||
|
apiPath = "tcp://" + apiPath
|
||||||
|
}
|
||||||
dc, err := docker.NewClient(apiPath)
|
dc, err := docker.NewClient(apiPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
client := &Client{dc}
|
client := &Client{dc}
|
||||||
|
|
||||||
env, err := client.Version()
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
Log.Infof("[docker] Using Docker API on %s: %v", apiPath, env)
|
client := &Client{dc}
|
||||||
return client, nil
|
|
||||||
|
return client, client.checkWorking()
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewVersionedClientFromEnv(apiVersionString string) (*Client, error) {
|
||||||
|
dc, err := docker.NewVersionedClientFromEnv(apiVersionString)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
client := &Client{dc}
|
||||||
|
|
||||||
|
return client, client.checkWorking()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) checkWorking() error {
|
||||||
|
_, err := c.Version()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) Info() string {
|
||||||
|
env, err := c.Version()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("Docker API error: %s", err)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("Docker API on %s: %v", c.Endpoint(), env)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) DockerVersion() string {
|
||||||
|
if env, err := c.Version(); err == nil {
|
||||||
|
if v, found := env.Map()["Version"]; found {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "unknown"
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddObserver adds an observer for docker events
|
// AddObserver adds an observer for docker events
|
||||||
func (c *Client) AddObserver(ob ContainerObserver) error {
|
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() {
|
go func() {
|
||||||
for event := range events {
|
pending := make(pendingStarts)
|
||||||
switch event.Status {
|
retryInterval := InitialInterval
|
||||||
case "die":
|
for {
|
||||||
id := event.ID
|
events := make(chan *docker.APIEvents)
|
||||||
ob.ContainerDied(id)
|
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
|
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
|
// IsContainerNotRunning returns true if we have checked with Docker that the ID is not running
|
||||||
func (c *Client) IsContainerNotRunning(idStr string) bool {
|
func (c *Client) IsContainerNotRunning(idStr string) bool {
|
||||||
container, err := c.InspectContainer(idStr)
|
container, err := c.InspectContainer(idStr)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return !container.State.Running
|
return !container.State.Running || container.State.Restarting
|
||||||
}
|
}
|
||||||
if _, notThere := err.(*docker.NoSuchContainer); notThere {
|
if _, notThere := err.(*docker.NoSuchContainer); notThere {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
Log.Errorf("[docker] Could not check container status: %s", err)
|
c.errorf("Could not check container status: %s", err)
|
||||||
return false
|
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 (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
@@ -52,3 +53,35 @@ func SetLogLevel(levelname string) {
|
|||||||
}
|
}
|
||||||
Log.Level = level
|
Log.Level = level
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CheckFatal(e error) {
|
||||||
|
if e != nil {
|
||||||
|
Log.Fatal(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func CheckWarn(e error) {
|
||||||
|
if e != nil {
|
||||||
|
Log.Warnln(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adaptor type to go from Logrus to something expecting a standard library log.Logger
|
||||||
|
type logLogger struct {
|
||||||
|
logger *logrus.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// io.Writer interface
|
||||||
|
func (l logLogger) Write(p []byte) (n int, err error) {
|
||||||
|
ln := len(p)
|
||||||
|
// Strip a trailing newline, because logrus formatter adds one
|
||||||
|
if ln > 0 && p[ln-1] == '\n' {
|
||||||
|
p = p[:ln-1]
|
||||||
|
}
|
||||||
|
l.logger.Info(string(p))
|
||||||
|
return ln, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LogLogger() *log.Logger {
|
||||||
|
return log.New(logLogger{Log}, "", 0)
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@ package mflagext
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/docker/docker/pkg/mflag"
|
"github.com/weaveworks/docker/pkg/mflag"
|
||||||
)
|
)
|
||||||
|
|
||||||
type listOpts struct {
|
type listOpts struct {
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package odp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/weaveworks/go-odp/odp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ODP admin functionality
|
||||||
|
|
||||||
|
func CreateDatapath(dpname string) (supported bool, err error) {
|
||||||
|
dpif, err := odp.NewDpif()
|
||||||
|
if err != nil {
|
||||||
|
if odp.IsKernelLacksODPError(err) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
defer dpif.Close()
|
||||||
|
|
||||||
|
dp, err := dpif.CreateDatapath(dpname)
|
||||||
|
if err != nil && !odp.IsDatapathNameAlreadyExistsError(err) {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pick an ephemeral port number to use in probing for vxlan
|
||||||
|
// support.
|
||||||
|
udpconn, err := net.ListenUDP("udp4", nil)
|
||||||
|
if err != nil {
|
||||||
|
return true, err
|
||||||
|
}
|
||||||
|
defer udpconn.Close()
|
||||||
|
|
||||||
|
// we leave the UDP socket open, so creating a vxlan vport on
|
||||||
|
// the same port number should fail. But that's fine: It's
|
||||||
|
// still sufficient to probe for support.
|
||||||
|
portno := uint16(udpconn.LocalAddr().(*net.UDPAddr).Port)
|
||||||
|
vpid, err := dp.CreateVport(odp.NewVxlanVportSpec(fmt.Sprintf("vxlan-%d", portno), portno))
|
||||||
|
if nlerr, ok := err.(odp.NetlinkError); ok {
|
||||||
|
if syscall.Errno(nlerr) == syscall.EAFNOSUPPORT {
|
||||||
|
dp.Delete()
|
||||||
|
return false, fmt.Errorf("kernel does not have Open vSwitch VXLAN support")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
dp.DeleteVport(vpid)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteDatapath(dpname string) error {
|
||||||
|
dpif, err := odp.NewDpif()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer dpif.Close()
|
||||||
|
|
||||||
|
dp, err := dpif.LookupDatapath(dpname)
|
||||||
|
if err != nil {
|
||||||
|
if odp.IsNoSuchDatapathError(err) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dp.Delete()
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddDatapathInterface(dpname string, ifname string) error {
|
||||||
|
dpif, err := odp.NewDpif()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer dpif.Close()
|
||||||
|
|
||||||
|
dp, err := dpif.LookupDatapath(dpname)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = dp.CreateVport(odp.NewNetdevVportSpec(ifname))
|
||||||
|
return err
|
||||||
|
}
|
||||||
@@ -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
-1
@@ -14,7 +14,7 @@ type SignalReceiver interface {
|
|||||||
|
|
||||||
func SignalHandlerLoop(ss ...SignalReceiver) {
|
func SignalHandlerLoop(ss ...SignalReceiver) {
|
||||||
sigs := make(chan os.Signal, 1)
|
sigs := make(chan os.Signal, 1)
|
||||||
signal.Notify(sigs, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGUSR1, syscall.SIGTERM)
|
signal.Notify(sigs, syscall.SIGINT, syscall.SIGQUIT, syscall.SIGTERM)
|
||||||
buf := make([]byte, 1<<20)
|
buf := make([]byte, 1<<20)
|
||||||
for {
|
for {
|
||||||
switch <-sigs {
|
switch <-sigs {
|
||||||
|
|||||||
+1
-3
@@ -1,8 +1,6 @@
|
|||||||
package common
|
package common
|
||||||
|
|
||||||
import (
|
import "strings"
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// Assert test is true, panic otherwise
|
// Assert test is true, panic otherwise
|
||||||
func Assert(test bool) {
|
func Assert(test bool) {
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package db
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/gob"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"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")
|
||||||
|
)
|
||||||
|
|
||||||
|
func NewBoltDB(dbPathname string) (*BoltDB, error) {
|
||||||
|
db, err := bolt.Open(dbPathname, 0660, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("[boltDB] Unable to open %s: %s", dbPathname, err)
|
||||||
|
}
|
||||||
|
// check the persistence version
|
||||||
|
err = db.Update(func(tx *bolt.Tx) error {
|
||||||
|
if top := tx.Bucket(topBucket); top == nil {
|
||||||
|
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("[boltDB] Cannot use persistence file %s - version %x", dbPathname, checkVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
return &BoltDB{db: db}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
+13
-13
@@ -66,23 +66,23 @@ Router:
|
|||||||
simply responds to traffic received from the remote peer via TCP
|
simply responds to traffic received from the remote peer via TCP
|
||||||
5. We register this connection with the local peer
|
5. We register this connection with the local peer
|
||||||
6a. If we initiated this connection then we now start sending fast
|
6a. If we initiated this connection then we now start sending fast
|
||||||
heartbeats to the remote peer so that the remote peer can determine what
|
heartbeats to the remote peer so that the remote peer can
|
||||||
address/port it should use to send UDP back to us. To do this, we
|
determine what address/port it should use to send UDP back to
|
||||||
spawn off two further threads which are the forwarder
|
us. To do this, we spawn off a "forwarder" thread to send
|
||||||
loops. These receive frames which are to be sent to the remote
|
heartbeats, monitor incoming heartbeats, and some other auxiliary
|
||||||
peer. One of them sends frames without the DF flag set. To do this
|
duties. It also consumes frames to be encapsulated and send via
|
||||||
it just sends the packets out of the UDP Listener socket. The
|
UDP from two channels, for DF and non-DF cases. In the non-DF
|
||||||
other needs to send frames with the DF flag set and needs its own
|
case, it can just send the packets out of the UDP Listener
|
||||||
socket so that it can do PMTU discovery easily. To do this, it
|
socket. In the DF case, it needs its own socket so that it can do
|
||||||
uses a Raw IP socket (IP has no ports, so there's no collision
|
PMTU discovery easily. To do this, it uses a Raw IP socket (IP has
|
||||||
issue with the UDP Listener socket) and so it must add UDP headers
|
no ports, so there's no collision issue with the UDP Listener
|
||||||
itself.
|
socket) and so it must add UDP headers itself.
|
||||||
6b. If we did not initiate this connection then the UDP Listener
|
6b. If we did not initiate this connection then the UDP Listener
|
||||||
should start receiving fast heartbeats from the remote peer. From
|
should start receiving fast heartbeats from the remote peer. From
|
||||||
those it should be able to identify the local connection via the
|
those it should be able to identify the local connection via the
|
||||||
local peer. It will tell the local connection (communicating to
|
local peer. It will tell the local connection (communicating to
|
||||||
the actor thread) about the UDP address of the remote peer. The
|
the actor thread) about the UDP address of the remote peer. The
|
||||||
local connection will then start its forwarder threads as
|
local connection will then start its forwarder thread as
|
||||||
described in 6a, and start sending fast heartbeats. We send to the
|
described in 6a, and start sending fast heartbeats. We send to the
|
||||||
remote peer via TCP a ConnectionEstablished message. The remote
|
remote peer via TCP a ConnectionEstablished message. The remote
|
||||||
peer receives this (on the TCP receiver process), tells the
|
peer receives this (on the TCP receiver process), tells the
|
||||||
|
|||||||
+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.
|
||||||
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 43 KiB |
@@ -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
|
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
|
live peer what its view of the dead peer is, and making sure we use
|
||||||
the latest info.)
|
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.
|
|
||||||
+22
-3
@@ -6,7 +6,6 @@
|
|||||||
* Create a [github token for
|
* Create a [github token for
|
||||||
github-release](https://help.github.com/articles/creating-an-access-token-for-command-line-use/);
|
github-release](https://help.github.com/articles/creating-an-access-token-for-command-line-use/);
|
||||||
set and export `$GITHUB_TOKEN` with this value
|
set and export `$GITHUB_TOKEN` with this value
|
||||||
* Update all dependencies with `make update`
|
|
||||||
|
|
||||||
## Release Types
|
## Release Types
|
||||||
|
|
||||||
@@ -63,9 +62,10 @@ This has the following effects:
|
|||||||
## Draft Phase
|
## Draft Phase
|
||||||
### Push Version Tag Upstream
|
### Push Version Tag Upstream
|
||||||
|
|
||||||
First you must push your version tag upstream, so that an associated
|
First you must push your branch and version tag upstream, so that an
|
||||||
GitHub release may be created:
|
associated GitHub release may be created:
|
||||||
|
|
||||||
|
git push git@github.com:weaveworks/weave
|
||||||
git push git@github.com:weaveworks/weave $TAG
|
git push git@github.com:weaveworks/weave $TAG
|
||||||
|
|
||||||
N.B. if you're testing the release process, push to your fork
|
N.B. if you're testing the release process, push to your fork
|
||||||
@@ -107,6 +107,15 @@ the build script will complain otherwise.
|
|||||||
N.B. if you're testing the release process, push to your fork
|
N.B. if you're testing the release process, push to your fork
|
||||||
instead!
|
instead!
|
||||||
|
|
||||||
|
While the git.io link for weave-kube is still pointing to the
|
||||||
|
weave-kube repository, we also need to update the tag there:
|
||||||
|
|
||||||
|
cd ../weave-kube
|
||||||
|
git tag -a -m "Release $TAG" $TAG
|
||||||
|
git tag -af -m "Release $TAG" latest_release $TAG
|
||||||
|
git push -f git@github.com:weaveworks/weave latest_release
|
||||||
|
|
||||||
|
|
||||||
### Publish Release & Distributable Artefacts
|
### Publish Release & Distributable Artefacts
|
||||||
|
|
||||||
You can now publish the release and upload the remaining
|
You can now publish the release and upload the remaining
|
||||||
@@ -128,6 +137,16 @@ Finally, for **Mainline** releases only:
|
|||||||
|
|
||||||
* Images tagged `latest` are updated on DockerHub
|
* 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
|
## 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,
|
hostnames and weave addresses for containers on the host. Like IPAM,
|
||||||
this service is embedded within the router. It binds to
|
this service is embedded within the router. It binds to
|
||||||
the host bridge to answer DNS queries from local containers; for
|
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.
|
/etc/resolv.conf to query an 'fallback' server.
|
||||||
|
|
||||||
The service is comprised of a DNS server, which answers all DNS queries
|
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
|
The DNS server also listens to the Docker event stream, and removes
|
||||||
entries for containers when they die. Entries removed in this way are
|
entries for containers when they die. Entries removed in this way are
|
||||||
tombstoned, and the tombstone lazily broadcast around the cluster.
|
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.
|
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
|
||||||
+18
-10
@@ -14,7 +14,8 @@ type allocateResult struct {
|
|||||||
type allocate struct {
|
type allocate struct {
|
||||||
resultChan chan<- allocateResult
|
resultChan chan<- allocateResult
|
||||||
ident string
|
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
|
hasBeenCancelled func() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -25,29 +26,36 @@ func (g *allocate) Try(alloc *Allocator) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if addr, found := alloc.lookupOwned(g.ident, g.r); found {
|
if addrs := alloc.ownedInRange(g.ident, g.r.Range()); len(addrs) > 0 {
|
||||||
g.resultChan <- allocateResult{addr, nil}
|
// 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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
if !alloc.universe.Overlaps(g.r) {
|
if !alloc.universe.Range().Overlaps(g.r.Range()) {
|
||||||
g.resultChan <- allocateResult{0, fmt.Errorf("range %s out of bounds: %s", g.r, alloc.universe)}
|
g.resultChan <- allocateResult{err: fmt.Errorf("range %s out of bounds: %s", g.r, alloc.universe)}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
alloc.establishRing()
|
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 == "_" {
|
||||||
|
g.ident = addr.String()
|
||||||
|
}
|
||||||
alloc.debugln("Allocated", addr, "for", g.ident, "in", g.r)
|
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}
|
g.resultChan <- allocateResult{addr, nil}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// out of space
|
// 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 {
|
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)
|
alloc.debugln("Problem asking peer", donor, "for space:", err)
|
||||||
} else {
|
} else {
|
||||||
alloc.debugln("Decided to ask peer", donor, "for space in range", g.r)
|
alloc.debugln("Decided to ask peer", donor, "for space in range", g.r)
|
||||||
@@ -59,7 +67,7 @@ func (g *allocate) Try(alloc *Allocator) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (g *allocate) Cancel() {
|
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 {
|
func (g *allocate) ForContainer(ident string) bool {
|
||||||
|
|||||||
+582
-225
File diff suppressed because it is too large.
Load diff
+170
-90
@@ -20,6 +20,16 @@ const (
|
|||||||
testStart3 = "10.0.3.0"
|
testStart3 = "10.0.3.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func returnFalse() bool { return false }
|
||||||
|
|
||||||
|
func (alloc *Allocator) SimplyAllocate(ident string, cidr address.CIDR) (address.Address, error) {
|
||||||
|
return alloc.Allocate(ident, cidr, true, returnFalse)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (alloc *Allocator) SimplyClaim(ident string, cidr address.CIDR) error {
|
||||||
|
return alloc.Claim(ident, cidr, true, true, returnFalse)
|
||||||
|
}
|
||||||
|
|
||||||
func TestAllocFree(t *testing.T) {
|
func TestAllocFree(t *testing.T) {
|
||||||
const (
|
const (
|
||||||
container1 = "abcdef"
|
container1 = "abcdef"
|
||||||
@@ -35,40 +45,54 @@ func TestAllocFree(t *testing.T) {
|
|||||||
|
|
||||||
alloc, subnet := makeAllocatorWithMockGossip(t, "01:00:00:01:00:00", universe, 1)
|
alloc, subnet := makeAllocatorWithMockGossip(t, "01:00:00:01:00:00", universe, 1)
|
||||||
defer alloc.Stop()
|
defer alloc.Stop()
|
||||||
_, cidr1, _ := address.ParseCIDR(subnet1)
|
cidr1, _ := address.ParseCIDR(subnet1)
|
||||||
_, cidr2, _ := address.ParseCIDR(subnet2)
|
cidr2, _ := address.ParseCIDR(subnet2)
|
||||||
|
|
||||||
alloc.claimRingForTesting()
|
alloc.claimRingForTesting()
|
||||||
addr1, err := alloc.Allocate(container1, cidr1.HostRange(), nil)
|
addr1, err := alloc.SimplyAllocate(container1, cidr1)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, testAddr1, addr1.String(), "address")
|
require.Equal(t, testAddr1, addr1.String(), "address")
|
||||||
|
|
||||||
addr2, err := alloc.Allocate(container1, cidr2.HostRange(), nil)
|
addr2, err := alloc.SimplyAllocate(container1, cidr2)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, testAddr2, addr2.String(), "address")
|
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
|
// Ask for another address for a different container and check it's different
|
||||||
addr1b, _ := alloc.Allocate(container2, cidr1.HostRange(), nil)
|
addr1b, _ := alloc.SimplyAllocate(container2, cidr1)
|
||||||
if addr1b.String() == testAddr1 {
|
if addr1b.String() == testAddr1 {
|
||||||
t.Fatalf("Expected different address but got %s", addr1b.String())
|
t.Fatalf("Expected different address but got %s", addr1b.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ask for the first container again and we should get the same addresses again
|
// Ask for the first container again and we should get the same addresses again
|
||||||
addr1a, _ := alloc.Allocate(container1, cidr1.HostRange(), nil)
|
addr1a, _ := alloc.SimplyAllocate(container1, cidr1)
|
||||||
require.Equal(t, testAddr1, addr1a.String(), "address")
|
require.Equal(t, testAddr1, addr1a.String(), "address")
|
||||||
addr2a, _ := alloc.Allocate(container1, cidr2.HostRange(), nil)
|
addr2a, _ := alloc.SimplyAllocate(container1, cidr2)
|
||||||
require.Equal(t, testAddr2, addr2a.String(), "address")
|
require.Equal(t, testAddr2, addr2a.String(), "address")
|
||||||
|
|
||||||
// Now delete the first container, and we should get its addresses back
|
// Now delete the first container, and we should get its addresses back
|
||||||
require.NoError(t, alloc.Delete(container1))
|
require.NoError(t, alloc.Delete(container1))
|
||||||
addr3, _ := alloc.Allocate(container3, cidr1.HostRange(), nil)
|
addr3, _ := alloc.SimplyAllocate(container3, cidr1)
|
||||||
require.Equal(t, testAddr1, addr3.String(), "address")
|
require.Equal(t, testAddr1, addr3.String(), "address")
|
||||||
addr4, _ := alloc.Allocate(container3, cidr2.HostRange(), nil)
|
addr4, _ := alloc.SimplyAllocate(container3, cidr2)
|
||||||
require.Equal(t, testAddr2, addr4.String(), "address")
|
require.Equal(t, testAddr2, addr4.String(), "address")
|
||||||
|
|
||||||
alloc.ContainerDied(container2)
|
alloc.ContainerDied(container2)
|
||||||
|
|
||||||
|
// Resurrect
|
||||||
|
addr1c, err := alloc.SimplyAllocate(container2, cidr1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, addr1b, addr1c, "address")
|
||||||
|
|
||||||
alloc.ContainerDied(container3)
|
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) {
|
func TestBootstrap(t *testing.T) {
|
||||||
@@ -93,7 +117,7 @@ func TestBootstrap(t *testing.T) {
|
|||||||
ExpectBroadcastMessage(alloc1, nil) // alloc1 will try to form consensus
|
ExpectBroadcastMessage(alloc1, nil) // alloc1 will try to form consensus
|
||||||
done := make(chan bool)
|
done := make(chan bool)
|
||||||
go func() {
|
go func() {
|
||||||
alloc1.Allocate("somecontainer", subnet, nil)
|
alloc1.Allocate("somecontainer", subnet, true, returnFalse)
|
||||||
done <- true
|
done <- true
|
||||||
}()
|
}()
|
||||||
time.Sleep(100 * time.Millisecond)
|
time.Sleep(100 * time.Millisecond)
|
||||||
@@ -101,6 +125,7 @@ func TestBootstrap(t *testing.T) {
|
|||||||
|
|
||||||
CheckAllExpectedMessagesSent(alloc1, alloc2)
|
CheckAllExpectedMessagesSent(alloc1, alloc2)
|
||||||
|
|
||||||
|
// Check that tryPendingOps doesn't cause anything to happen here
|
||||||
alloc1.actionChan <- func() { alloc1.tryPendingOps() }
|
alloc1.actionChan <- func() { alloc1.tryPendingOps() }
|
||||||
AssertNothingSent(t, done)
|
AssertNothingSent(t, done)
|
||||||
|
|
||||||
@@ -113,14 +138,24 @@ func TestBootstrap(t *testing.T) {
|
|||||||
ExpectBroadcastMessage(alloc1, nil)
|
ExpectBroadcastMessage(alloc1, nil)
|
||||||
alloc1.OnGossipBroadcast(alloc2.ourName, alloc2.Encode())
|
alloc1.OnGossipBroadcast(alloc2.ourName, alloc2.Encode())
|
||||||
|
|
||||||
// both nodes will get consensus now so initialize the ring
|
CheckAllExpectedMessagesSent(alloc1, alloc2)
|
||||||
ExpectBroadcastMessage(alloc2, nil)
|
|
||||||
|
// 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)
|
ExpectBroadcastMessage(alloc2, nil)
|
||||||
alloc2.OnGossipBroadcast(alloc1.ourName, alloc1.Encode())
|
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)
|
CheckAllExpectedMessagesSent(alloc1, alloc2)
|
||||||
|
|
||||||
alloc1.OnGossipBroadcast(alloc2.ourName, alloc2.Encode())
|
|
||||||
// now alloc1 should have space
|
// now alloc1 should have space
|
||||||
|
|
||||||
AssertSent(t, done)
|
AssertSent(t, done)
|
||||||
@@ -133,42 +168,42 @@ func TestAllocatorClaim(t *testing.T) {
|
|||||||
container1 = "abcdef"
|
container1 = "abcdef"
|
||||||
container3 = "b01df00d"
|
container3 = "b01df00d"
|
||||||
universe = "10.0.3.0/24"
|
universe = "10.0.3.0/24"
|
||||||
testAddr1 = "10.0.3.2"
|
testAddr1 = "10.0.3.2/24"
|
||||||
testAddr2 = "10.0.4.2"
|
testAddr2 = "10.0.4.2/24"
|
||||||
)
|
)
|
||||||
|
|
||||||
allocs, _, subnet := makeNetworkOfAllocators(2, universe)
|
allocs, router, subnet := makeNetworkOfAllocators(2, universe)
|
||||||
|
defer stopNetworkOfAllocators(allocs, router)
|
||||||
alloc := allocs[1]
|
alloc := allocs[1]
|
||||||
defer alloc.Stop()
|
addr1, _ := address.ParseCIDR(testAddr1)
|
||||||
addr1, _ := address.ParseIP(testAddr1)
|
|
||||||
|
|
||||||
// First claim should trigger "dunno, I'm going to wait"
|
// 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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
// Do one allocate to ensure paxos is all done
|
alloc.Prime()
|
||||||
alloc.Allocate("unused", subnet, nil)
|
|
||||||
// Do an allocate on the other peer, which we will try to claim later
|
// Do an allocate on the other peer, which we will try to claim later
|
||||||
addrx, err := allocs[0].Allocate(container1, subnet, nil)
|
addrx, err := allocs[0].Allocate(container1, subnet, true, returnFalse)
|
||||||
|
router.Flush()
|
||||||
|
|
||||||
// Now try the claim again
|
// Now try the claim again
|
||||||
err = alloc.Claim(container3, addr1, true)
|
err = alloc.SimplyClaim(container3, addr1)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// Check we get this address back if we try an allocate
|
// Check we get this address back if we try an allocate
|
||||||
addr3, _ := alloc.Allocate(container3, subnet, nil)
|
addr3, _ := alloc.SimplyAllocate(container3, subnet)
|
||||||
require.Equal(t, testAddr1, addr3.String(), "address")
|
require.Equal(t, testAddr1, address.MakeCIDR(subnet, addr3).String(), "address")
|
||||||
// one more claim should still work
|
// one more claim should still work
|
||||||
err = alloc.Claim(container3, addr1, true)
|
err = alloc.SimplyClaim(container3, addr1)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
// claim for a different container should fail
|
// claim for a different container should fail
|
||||||
err = alloc.Claim(container1, addr1, true)
|
err = alloc.SimplyClaim(container1, addr1)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
// claiming the address allocated on the other peer should fail
|
// 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")
|
require.Error(t, err, "claiming address allocated on other peer should fail")
|
||||||
// Check an address outside of our universe
|
// Check an address outside of our universe
|
||||||
addr2, _ := address.ParseIP(testAddr2)
|
addr2, _ := address.ParseCIDR(testAddr2)
|
||||||
err = alloc.Claim(container1, addr2, true)
|
err = alloc.SimplyClaim(container1, addr2)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -185,16 +220,13 @@ func (alloc *Allocator) pause() func() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestCancel(t *testing.T) {
|
func TestCancel(t *testing.T) {
|
||||||
const (
|
const cidr = "10.0.4.0/26"
|
||||||
CIDR = "10.0.1.7/26"
|
|
||||||
)
|
|
||||||
|
|
||||||
router := gossip.NewTestRouter(0.0)
|
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.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))
|
alloc2.SetInterfaces(router.Connect(alloc2.ourName, alloc2))
|
||||||
alloc1.claimRingForTesting(alloc1, alloc2)
|
alloc1.claimRingForTesting(alloc1, alloc2)
|
||||||
alloc2.claimRingForTesting(alloc1, alloc2)
|
alloc2.claimRingForTesting(alloc1, alloc2)
|
||||||
@@ -206,9 +238,9 @@ func TestCancel(t *testing.T) {
|
|||||||
alloc1.OnGossipBroadcast(alloc2.ourName, alloc2.Encode())
|
alloc1.OnGossipBroadcast(alloc2.ourName, alloc2.Encode())
|
||||||
|
|
||||||
// Get some IPs, so each allocator has some space
|
// Get some IPs, so each allocator has some space
|
||||||
res1, _ := alloc1.Allocate("foo", subnet, nil)
|
res1, _ := alloc1.Allocate("foo", subnet, true, returnFalse)
|
||||||
common.Log.Debugf("res1 = %s", res1.String())
|
common.Log.Debugf("res1 = %s", res1.String())
|
||||||
res2, _ := alloc2.Allocate("bar", subnet, nil)
|
res2, _ := alloc2.Allocate("bar", subnet, true, returnFalse)
|
||||||
common.Log.Debugf("res2 = %s", res2.String())
|
common.Log.Debugf("res2 = %s", res2.String())
|
||||||
if res1 == res2 {
|
if res1 == res2 {
|
||||||
require.FailNow(t, "Error: got same ips!")
|
require.FailNow(t, "Error: got same ips!")
|
||||||
@@ -219,13 +251,21 @@ func TestCancel(t *testing.T) {
|
|||||||
unpause := alloc2.pause()
|
unpause := alloc2.pause()
|
||||||
|
|
||||||
// Use up all the IPs that alloc1 owns, so the allocation after this will prompt a request to alloc2
|
// 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++ {
|
for i := 0; alloc1.NumFreeAddresses(subnet.HostRange()) > 0; i++ {
|
||||||
alloc1.Allocate(fmt.Sprintf("tmp%d", i), subnet, nil)
|
alloc1.Allocate(fmt.Sprintf("tmp%d", i), subnet, true, returnFalse)
|
||||||
}
|
}
|
||||||
cancelChan := make(chan bool, 1)
|
cancelChan := make(chan bool, 1)
|
||||||
doneChan := make(chan bool)
|
doneChan := make(chan bool)
|
||||||
go func() {
|
go func() {
|
||||||
_, ok := alloc1.Allocate("baz", subnet, cancelChan)
|
_, ok := alloc1.Allocate("baz", subnet, true,
|
||||||
|
func() bool {
|
||||||
|
select {
|
||||||
|
case <-cancelChan:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
})
|
||||||
doneChan <- ok == nil
|
doneChan <- ok == nil
|
||||||
}()
|
}()
|
||||||
|
|
||||||
@@ -241,18 +281,18 @@ func TestCancel(t *testing.T) {
|
|||||||
|
|
||||||
func TestCancelOnDied(t *testing.T) {
|
func TestCancelOnDied(t *testing.T) {
|
||||||
const (
|
const (
|
||||||
CIDR = "10.0.1.7/26"
|
cidr = "10.0.4.0/26"
|
||||||
container1 = "abcdef"
|
container1 = "abcdef"
|
||||||
)
|
)
|
||||||
|
|
||||||
router := gossip.NewTestRouter(0.0)
|
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.SetInterfaces(router.Connect(alloc1.ourName, alloc1))
|
||||||
alloc1.Start()
|
alloc1.Start()
|
||||||
|
|
||||||
doneChan := make(chan bool)
|
doneChan := make(chan bool)
|
||||||
f := func() {
|
f := func() {
|
||||||
_, ok := alloc1.Allocate(container1, subnet, nil)
|
_, ok := alloc1.Allocate(container1, subnet, true, returnFalse)
|
||||||
doneChan <- ok == nil
|
doneChan <- ok == nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -283,62 +323,80 @@ func TestGossipShutdown(t *testing.T) {
|
|||||||
defer alloc.Stop()
|
defer alloc.Stop()
|
||||||
|
|
||||||
alloc.claimRingForTesting()
|
alloc.claimRingForTesting()
|
||||||
alloc.Allocate(container1, subnet, nil)
|
alloc.SimplyAllocate(container1, subnet)
|
||||||
|
|
||||||
alloc.Shutdown()
|
alloc.Shutdown()
|
||||||
|
|
||||||
_, err := alloc.Allocate(container2, subnet, nil) // 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")
|
require.False(t, err == nil, "no address")
|
||||||
|
|
||||||
CheckAllExpectedMessagesSent(alloc)
|
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) {
|
func TestTransfer(t *testing.T) {
|
||||||
const (
|
const cidr = "10.0.4.0/22"
|
||||||
cidr = "10.0.1.7/22"
|
|
||||||
)
|
|
||||||
allocs, router, subnet := makeNetworkOfAllocators(3, cidr)
|
allocs, router, subnet := makeNetworkOfAllocators(3, cidr)
|
||||||
alloc1 := allocs[0]
|
defer stopNetworkOfAllocators(allocs, router)
|
||||||
alloc2 := allocs[1]
|
alloc0 := allocs[0]
|
||||||
alloc3 := allocs[2] // This will be 'master' and get the first range
|
alloc1 := allocs[1]
|
||||||
|
alloc2 := allocs[2]
|
||||||
|
|
||||||
_, err := alloc2.Allocate("foo", subnet, nil)
|
_, err := alloc1.Allocate("foo", subnet, true, returnFalse)
|
||||||
require.True(t, err == nil, "Failed to get address")
|
require.True(t, err == nil, "Failed to get address")
|
||||||
|
|
||||||
_, err = alloc3.Allocate("bar", subnet, nil)
|
_, err = alloc2.Allocate("bar", subnet, true, returnFalse)
|
||||||
require.True(t, err == nil, "Failed to get address")
|
require.True(t, err == nil, "Failed to get address")
|
||||||
|
|
||||||
|
// simulation of periodic gossip
|
||||||
alloc2.gossip.GossipBroadcast(alloc2.Gossip())
|
alloc2.gossip.GossipBroadcast(alloc2.Gossip())
|
||||||
router.Flush()
|
router.Flush()
|
||||||
alloc2.gossip.GossipBroadcast(alloc3.Gossip())
|
alloc1.gossip.GossipBroadcast(alloc1.Gossip())
|
||||||
router.Flush()
|
router.Flush()
|
||||||
|
|
||||||
|
free1 := alloc1.NumFreeAddresses(subnet.Range())
|
||||||
|
free2 := alloc2.NumFreeAddresses(subnet.Range())
|
||||||
|
|
||||||
|
router.RemovePeer(alloc1.ourName)
|
||||||
router.RemovePeer(alloc2.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, nil)
|
|
||||||
require.True(t, err == nil, "Failed to get address")
|
|
||||||
alloc1.Stop()
|
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) {
|
func TestFakeRouterSimple(t *testing.T) {
|
||||||
const (
|
const cidr = "10.0.4.0/22"
|
||||||
cidr = "10.0.1.7/22"
|
allocs, router, subnet := makeNetworkOfAllocators(2, cidr)
|
||||||
)
|
defer stopNetworkOfAllocators(allocs, router)
|
||||||
allocs, _, subnet := makeNetworkOfAllocators(2, cidr)
|
|
||||||
defer stopNetworkOfAllocators(allocs)
|
|
||||||
|
|
||||||
alloc1 := allocs[0]
|
alloc1 := allocs[0]
|
||||||
//alloc2 := allocs[1]
|
//alloc2 := allocs[1]
|
||||||
|
|
||||||
_, err := alloc1.Allocate("foo", subnet, nil)
|
_, err := alloc1.Allocate("foo", subnet, true, returnFalse)
|
||||||
require.NoError(t, err, "Failed to get address")
|
require.NoError(t, err, "Failed to get address")
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -349,10 +407,10 @@ func TestAllocatorFuzz(t *testing.T) {
|
|||||||
nodes = 5
|
nodes = 5
|
||||||
maxAddresses = 1000
|
maxAddresses = 1000
|
||||||
concurrency = 5
|
concurrency = 5
|
||||||
cidr = "10.0.1.7/22"
|
cidr = "10.0.4.0/22"
|
||||||
)
|
)
|
||||||
allocs, _, subnet := makeNetworkOfAllocators(nodes, cidr)
|
allocs, router, subnet := makeNetworkOfAllocators(nodes, cidr)
|
||||||
defer stopNetworkOfAllocators(allocs)
|
defer stopNetworkOfAllocators(allocs, router)
|
||||||
|
|
||||||
// Test state
|
// Test state
|
||||||
// For each IP issued we store the allocator
|
// For each IP issued we store the allocator
|
||||||
@@ -419,7 +477,7 @@ func TestAllocatorFuzz(t *testing.T) {
|
|||||||
allocIndex := rand.Int31n(nodes)
|
allocIndex := rand.Int31n(nodes)
|
||||||
alloc := allocs[allocIndex]
|
alloc := allocs[allocIndex]
|
||||||
//common.Log.Infof("Allocate: asking allocator %d", allocIndex)
|
//common.Log.Infof("Allocate: asking allocator %d", allocIndex)
|
||||||
addr, err := alloc.Allocate(name, subnet, nil)
|
addr, err := alloc.SimplyAllocate(name, subnet)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("Could not allocate addr"))
|
panic(fmt.Sprintf("Could not allocate addr"))
|
||||||
@@ -476,7 +534,7 @@ func TestAllocatorFuzz(t *testing.T) {
|
|||||||
|
|
||||||
//common.Log.Infof("Asking for %s (%s) on allocator %d again", res.name, addr, res.alloc)
|
//common.Log.Infof("Asking for %s (%s) on allocator %d again", res.name, addr, res.alloc)
|
||||||
|
|
||||||
newAddr, _ := alloc.Allocate(res.name, subnet, nil)
|
newAddr, _ := alloc.SimplyAllocate(res.name, subnet)
|
||||||
oldAddr, _ := address.ParseIP(addr)
|
oldAddr, _ := address.ParseIP(addr)
|
||||||
if newAddr != oldAddr {
|
if newAddr != oldAddr {
|
||||||
panic(fmt.Sprintf("Got different address for repeat request for %s: %s != %s", res.name, newAddr, oldAddr))
|
panic(fmt.Sprintf("Got different address for repeat request for %s: %s != %s", res.name, newAddr, oldAddr))
|
||||||
@@ -496,8 +554,8 @@ func TestAllocatorFuzz(t *testing.T) {
|
|||||||
allocIndex := rand.Int31n(nodes)
|
allocIndex := rand.Int31n(nodes)
|
||||||
addressIndex := rand.Int31n(int32(subnet.Size()))
|
addressIndex := rand.Int31n(int32(subnet.Size()))
|
||||||
alloc := allocs[allocIndex]
|
alloc := allocs[allocIndex]
|
||||||
addr := address.Add(subnet.Start, address.Offset(addressIndex))
|
addr := address.Add(subnet.Addr, address.Offset(addressIndex))
|
||||||
err := alloc.Claim(name, addr, true)
|
err := alloc.SimplyClaim(name, address.MakeCIDR(subnet, addr))
|
||||||
if err == nil {
|
if err == nil {
|
||||||
noteAllocation(allocIndex, name, addr)
|
noteAllocation(allocIndex, name, addr)
|
||||||
}
|
}
|
||||||
@@ -553,14 +611,36 @@ func TestAllocatorFuzz(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGossipSkew(t *testing.T) {
|
func TestSpaceRequest(t *testing.T) {
|
||||||
alloc1, _ := makeAllocatorWithMockGossip(t, "01:00:00:01:00:00", "10.0.1.0/22", 2)
|
const (
|
||||||
defer alloc1.Stop()
|
container1 = "cont-1"
|
||||||
alloc2, _ := makeAllocatorWithMockGossip(t, "02:00:00:02:00:00", "10.0.1.0/22", 2)
|
container2 = "cont-2"
|
||||||
alloc2.now = func() time.Time { return time.Now().Add(time.Hour * 2) }
|
universe = "10.32.0.0/12"
|
||||||
defer alloc2.Stop()
|
)
|
||||||
|
allocs, router, subnet := makeNetworkOfAllocators(1, universe)
|
||||||
|
defer stopNetworkOfAllocators(allocs, router)
|
||||||
|
alloc1 := allocs[0]
|
||||||
|
|
||||||
if _, err := alloc1.OnGossipBroadcast(alloc2.ourName, alloc2.Encode()); err == nil {
|
addr, err := alloc1.Allocate(container1, subnet, true, returnFalse)
|
||||||
t.Fail()
|
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()}
|
||||||
}
|
}
|
||||||
+37
-19
@@ -3,16 +3,19 @@ package ipam
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/weaveworks/mesh"
|
||||||
|
|
||||||
"github.com/weaveworks/weave/common"
|
"github.com/weaveworks/weave/common"
|
||||||
"github.com/weaveworks/weave/net/address"
|
"github.com/weaveworks/weave/net/address"
|
||||||
"github.com/weaveworks/weave/router"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type claim struct {
|
type claim struct {
|
||||||
resultChan chan<- error
|
resultChan chan<- error
|
||||||
ident string
|
ident string
|
||||||
addr address.Address
|
cidr address.CIDR
|
||||||
|
isContainer bool
|
||||||
noErrorOnUnknown bool
|
noErrorOnUnknown bool
|
||||||
|
hasBeenCancelled func() bool
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send an error (or nil for success) back to caller listening on resultChan
|
// Send an error (or nil for success) back to caller listening on resultChan
|
||||||
@@ -31,33 +34,41 @@ func (c *claim) sendResult(result error) {
|
|||||||
|
|
||||||
// Try returns true for success (or failure), false if we need to try again later
|
// Try returns true for success (or failure), false if we need to try again later
|
||||||
func (c *claim) Try(alloc *Allocator) bool {
|
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
|
// 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)
|
c.sendResult(nil)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
alloc.establishRing()
|
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:
|
case alloc.ourName:
|
||||||
// success
|
// success
|
||||||
case router.UnknownPeerName:
|
case mesh.UnknownPeerName:
|
||||||
// If our ring doesn't know, it must be empty.
|
// 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 {
|
if c.noErrorOnUnknown {
|
||||||
alloc.infof("Claim %s for %s: address allocator still initializing; will try later.", c.addr, c.ident)
|
|
||||||
c.sendResult(nil)
|
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
|
return false
|
||||||
default:
|
default:
|
||||||
alloc.debugf("requesting address %s from other peer %s", c.addr, owner)
|
alloc.debugf("requesting address %s from other peer %s", c.cidr, owner)
|
||||||
err := alloc.sendSpaceRequest(owner, address.NewRange(c.addr, 1))
|
err := alloc.sendSpaceRequest(owner, address.NewRange(c.cidr.Addr, 1))
|
||||||
if err != nil { // can't speak to owner right now
|
if err != nil { // can't speak to owner right now
|
||||||
if c.noErrorOnUnknown {
|
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)
|
c.sendResult(nil)
|
||||||
} else { // just tell the user they can't do this.
|
} else { // just tell the user they can't do this.
|
||||||
c.deniedBy(alloc, owner)
|
c.deniedBy(alloc, owner)
|
||||||
@@ -66,32 +77,39 @@ func (c *claim) Try(alloc *Allocator) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.ident == "_" { // Special "I don't have a unique ID" identifier
|
||||||
|
c.ident = c.cidr.Addr.String()
|
||||||
|
}
|
||||||
// We are the owner, check we haven't given it to another container
|
// We are the owner, check we haven't given it to another container
|
||||||
switch existingIdent := alloc.findOwner(c.addr); existingIdent {
|
switch existingIdent := alloc.findOwner(c.cidr.Addr); existingIdent {
|
||||||
case "":
|
case "":
|
||||||
if err := alloc.space.Claim(c.addr); err == nil {
|
if err := alloc.space.Claim(c.cidr.Addr); err == nil {
|
||||||
alloc.debugln("Claimed", c.addr, "for", c.ident)
|
alloc.debugln("Claimed", c.cidr, "for", c.ident)
|
||||||
alloc.addOwned(c.ident, c.addr)
|
alloc.addOwned(c.ident, c.cidr, c.isContainer)
|
||||||
c.sendResult(nil)
|
c.sendResult(nil)
|
||||||
} else {
|
} else {
|
||||||
c.sendResult(err)
|
c.sendResult(err)
|
||||||
}
|
}
|
||||||
case c.ident:
|
case c.ident:
|
||||||
// same identifier is claiming same address; that's OK
|
// same identifier is claiming same address; that's OK
|
||||||
|
alloc.debugln("Re-Claimed", c.cidr, "for", c.ident)
|
||||||
c.sendResult(nil)
|
c.sendResult(nil)
|
||||||
|
case c.cidr.Addr.String():
|
||||||
|
// Address already allocated via "_" name
|
||||||
|
c.sendResult(fmt.Errorf("address %s already in use", c.cidr))
|
||||||
default:
|
default:
|
||||||
// Addr already owned by container on this machine
|
// 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
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *claim) deniedBy(alloc *Allocator, owner router.PeerName) {
|
func (c *claim) deniedBy(alloc *Allocator, owner mesh.PeerName) {
|
||||||
name, found := alloc.nicknames[owner]
|
name, found := alloc.nicknames[owner]
|
||||||
if found {
|
if found {
|
||||||
name = " (" + name + ")"
|
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() {
|
func (c *claim) Cancel() {
|
||||||
|
|||||||
+83
-46
@@ -16,83 +16,120 @@ func badRequest(w http.ResponseWriter, err error) {
|
|||||||
common.Log.Warningln("[allocator]:", err.Error())
|
common.Log.Warningln("[allocator]:", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseCIDR(w http.ResponseWriter, cidrStr string) (address.CIDR, bool) {
|
func parseCIDR(w http.ResponseWriter, cidrStr string, net bool) (address.CIDR, bool) {
|
||||||
subnetAddr, cidr, err := address.ParseCIDR(cidrStr)
|
var cidr address.CIDR
|
||||||
|
var err error
|
||||||
|
if net {
|
||||||
|
cidr, err = ParseCIDRSubnet(cidrStr)
|
||||||
|
} else {
|
||||||
|
cidr, err = address.ParseCIDR(cidrStr)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
badRequest(w, err)
|
badRequest(w, err)
|
||||||
return address.CIDR{}, false
|
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
|
return cidr, true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (alloc *Allocator) handleHTTPAllocate(dockerCli *docker.Client, w http.ResponseWriter, ident string, checkAlive bool, subnet address.CIDR) {
|
func writeAddresses(w http.ResponseWriter, cidrs []address.CIDR) {
|
||||||
closedChan := w.(http.CloseNotifier).CloseNotify()
|
for i, cidr := range cidrs {
|
||||||
addr, err := alloc.Allocate(ident, subnet.HostRange(), closedChan)
|
fmt.Fprint(w, cidr)
|
||||||
if err != nil {
|
if i < len(cidrs)-1 {
|
||||||
if _, ok := err.(*errorCancelled); ok { // cancellation is not really an error
|
w.Write([]byte{' '})
|
||||||
common.Log.Infoln("[allocator]:", err.Error())
|
|
||||||
fmt.Fprint(w, "cancelled")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
badRequest(w, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if checkAlive && dockerCli != nil && dockerCli.IsContainerNotRunning(ident) {
|
|
||||||
common.Log.Infof("[allocator] '%s' is not running: freeing %s", ident, addr)
|
|
||||||
alloc.Free(ident, addr)
|
|
||||||
fmt.Fprint(w, "cancelled")
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasBeenCancelled(dockerCli *docker.Client, closedChan <-chan bool, ident string, checkAlive bool) func() bool {
|
||||||
|
return func() bool {
|
||||||
|
select {
|
||||||
|
case <-closedChan:
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
res := checkAlive && dockerCli != nil && dockerCli.IsContainerNotRunning(ident)
|
||||||
|
checkAlive = false // we check only once; if the container dies later we learn about that through events
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func cancellationErr(w http.ResponseWriter, err error) bool {
|
||||||
|
if _, ok := err.(*errorCancelled); ok {
|
||||||
|
common.Log.Infoln("[allocator]:", err.Error())
|
||||||
|
fmt.Fprint(w, "cancelled")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (alloc *Allocator) handleHTTPAllocate(dockerCli *docker.Client, w http.ResponseWriter, ident string, checkAlive bool, subnet address.CIDR) {
|
||||||
|
addr, err := alloc.Allocate(ident, subnet, checkAlive,
|
||||||
|
hasBeenCancelled(dockerCli, w.(http.CloseNotifier).CloseNotify(), ident, checkAlive))
|
||||||
|
if err != nil {
|
||||||
|
if !cancellationErr(w, err) {
|
||||||
|
badRequest(w, err)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
fmt.Fprintf(w, "%s/%d", addr, subnet.PrefixLen)
|
fmt.Fprintf(w, "%s/%d", addr, subnet.PrefixLen)
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandleHTTP wires up ipams HTTP endpoints to the provided mux.
|
func (alloc *Allocator) handleHTTPClaim(dockerCli *docker.Client, w http.ResponseWriter, ident string, cidr address.CIDR, checkAlive, noErrorOnUnknown bool) {
|
||||||
func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CIDR, dockerCli *docker.Client) {
|
err := alloc.Claim(ident, cidr, checkAlive, noErrorOnUnknown,
|
||||||
router.Methods("PUT").Path("/ip/{id}/{ip}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
hasBeenCancelled(dockerCli, w.(http.CloseNotifier).CloseNotify(), ident, checkAlive))
|
||||||
vars := mux.Vars(r)
|
if err != nil {
|
||||||
ident := vars["id"]
|
if !cancellationErr(w, err) {
|
||||||
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 {
|
|
||||||
badRequest(w, fmt.Errorf("Unable to claim: %s", 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) {
|
router.Methods("GET").Path("/ip/{id}/{ip}/{prefixlen}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
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 {
|
||||||
addr, err := alloc.Lookup(vars["id"], subnet.HostRange())
|
cidrs, err := alloc.Lookup(vars["id"], subnet.HostRange())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
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) {
|
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 {
|
if err != nil {
|
||||||
http.NotFound(w, r)
|
http.NotFound(w, r)
|
||||||
return
|
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) {
|
router.Methods("POST").Path("/ip/{id}/{ip}/{prefixlen}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
vars := mux.Vars(r)
|
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)
|
alloc.handleHTTPAllocate(dockerCli, w, vars["id"], r.FormValue("check-alive") == "true", subnet)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -134,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) {
|
router.Methods("DELETE").Path("/peer/{id}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
ident := mux.Vars(r)["id"]
|
ident := mux.Vars(r)["id"]
|
||||||
if err := alloc.AdminTakeoverRanges(ident); err != nil {
|
transferred := alloc.AdminTakeoverRanges(ident)
|
||||||
badRequest(w, err)
|
fmt.Fprintf(w, "%d IPs taken over from %s\n", transferred, ident)
|
||||||
return
|
})
|
||||||
}
|
|
||||||
|
|
||||||
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/stretchr/testify/require"
|
||||||
"github.com/weaveworks/weave/common"
|
"github.com/weaveworks/weave/common"
|
||||||
"github.com/weaveworks/weave/net/address"
|
"github.com/weaveworks/weave/net/address"
|
||||||
wt "github.com/weaveworks/weave/testing"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func HTTPPost(t *testing.T, url string) string {
|
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) {
|
router.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {
|
||||||
io.WriteString(w, fmt.Sprintln(alloc))
|
io.WriteString(w, fmt.Sprintln(alloc))
|
||||||
})
|
})
|
||||||
alloc.HandleHTTP(router, subnet, nil)
|
alloc.HandleHTTP(router, subnet, "", nil)
|
||||||
|
|
||||||
httpListener, err := net.Listen("tcp", ":0")
|
httpListener, err := net.Listen("tcp", ":0")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -81,7 +80,7 @@ func TestHttp(t *testing.T) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
alloc, _ := makeAllocatorWithMockGossip(t, "08:00:27:01:c3:9a", universe, 1)
|
alloc, _ := makeAllocatorWithMockGossip(t, "08:00:27:01:c3:9a", universe, 1)
|
||||||
_, cidr, _ := address.ParseCIDR(universe)
|
cidr, _ := address.ParseCIDR(universe)
|
||||||
port := listenHTTP(alloc, cidr)
|
port := listenHTTP(alloc, cidr)
|
||||||
alloc.claimRingForTesting()
|
alloc.claimRingForTesting()
|
||||||
|
|
||||||
@@ -123,7 +122,7 @@ func TestBadHttp(t *testing.T) {
|
|||||||
|
|
||||||
alloc, _ := makeAllocatorWithMockGossip(t, "08:00:27:01:c3:9a", testCIDR1, 1)
|
alloc, _ := makeAllocatorWithMockGossip(t, "08:00:27:01:c3:9a", testCIDR1, 1)
|
||||||
defer alloc.Stop()
|
defer alloc.Stop()
|
||||||
_, cidr, _ := address.ParseCIDR(testCIDR1)
|
cidr, _ := address.ParseCIDR(testCIDR1)
|
||||||
port := listenHTTP(alloc, cidr)
|
port := listenHTTP(alloc, cidr)
|
||||||
|
|
||||||
alloc.claimRingForTesting()
|
alloc.claimRingForTesting()
|
||||||
@@ -145,40 +144,30 @@ func TestBadHttp(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestHTTPCancel(t *testing.T) {
|
func TestHTTPCancel(t *testing.T) {
|
||||||
wt.RunWithTimeout(t, 2*time.Second, func() {
|
|
||||||
impTestHTTPCancel(t)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func impTestHTTPCancel(t *testing.T) {
|
|
||||||
var (
|
var (
|
||||||
containerID = "deadbeef"
|
containerID = "deadbeef"
|
||||||
testCIDR1 = "10.0.3.0/29"
|
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()
|
defer alloc.Stop()
|
||||||
alloc.claimRingForTesting()
|
ExpectBroadcastMessage(alloc, nil) // trying to form consensus
|
||||||
_, cidr, _ := address.ParseCIDR(testCIDR1)
|
cidr, _ := address.ParseCIDR(testCIDR1)
|
||||||
port := listenHTTP(alloc, cidr)
|
port := listenHTTP(alloc, cidr)
|
||||||
|
|
||||||
// Stop the alloc so nothing actually works
|
|
||||||
unpause := alloc.pause()
|
|
||||||
|
|
||||||
// Ask the http server for a new address
|
// Ask the http server for a new address
|
||||||
done := make(chan *http.Response)
|
|
||||||
req, _ := http.NewRequest("POST", allocURL(port, testCIDR1, containerID), nil)
|
req, _ := http.NewRequest("POST", allocURL(port, testCIDR1, containerID), nil)
|
||||||
|
// On another goroutine, wait for a bit then cancel the request
|
||||||
go func() {
|
go func() {
|
||||||
res, _ := http.DefaultClient.Do(req)
|
time.Sleep(100 * time.Millisecond)
|
||||||
done <- res
|
common.Log.Debug("Cancelling allocate")
|
||||||
|
http.DefaultTransport.(*http.Transport).CancelRequest(req)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
time.Sleep(100 * time.Millisecond)
|
res, _ := http.DefaultClient.Do(req)
|
||||||
fmt.Println("Cancelling allocate")
|
|
||||||
http.DefaultTransport.(*http.Transport).CancelRequest(req)
|
|
||||||
unpause()
|
|
||||||
res := <-done
|
|
||||||
if res != nil {
|
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
|
||||||
|
}
|
||||||
+22
-10
@@ -1,7 +1,7 @@
|
|||||||
package paxos
|
package paxos
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/weaveworks/weave/router"
|
"github.com/weaveworks/mesh"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The node identifier. The use of the UID here is important: Paxos
|
// The node identifier. The use of the UID here is important: Paxos
|
||||||
@@ -9,8 +9,8 @@ import (
|
|||||||
// node does not restart and lose its Paxos state but claim to have
|
// node does not restart and lose its Paxos state but claim to have
|
||||||
// the same ID.
|
// the same ID.
|
||||||
type NodeID struct {
|
type NodeID struct {
|
||||||
Name router.PeerName
|
Name mesh.PeerName
|
||||||
UID router.PeerUID
|
UID mesh.PeerUID
|
||||||
}
|
}
|
||||||
|
|
||||||
// note all fields exported in structs so we can Gob them
|
// note all fields exported in structs so we can Gob them
|
||||||
@@ -39,7 +39,7 @@ func (a ProposalID) valid() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// For seeding IPAM, the value we want consensus on is a set of peer names
|
// For seeding IPAM, the value we want consensus on is a set of peer names
|
||||||
type Value []router.PeerName
|
type Value []mesh.PeerName
|
||||||
|
|
||||||
// An AcceptedValue is a Value plus the proposal which originated that
|
// An AcceptedValue is a Value plus the proposal which originated that
|
||||||
// Value. The origin is not essential, but makes comparing
|
// Value. The origin is not essential, but makes comparing
|
||||||
@@ -72,7 +72,7 @@ type Node struct {
|
|||||||
knows GossipState
|
knows GossipState
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNode(name router.PeerName, uid router.PeerUID, quorum uint) *Node {
|
func NewNode(name mesh.PeerName, uid mesh.PeerUID, quorum uint) *Node {
|
||||||
return &Node{
|
return &Node{
|
||||||
id: NodeID{name, uid},
|
id: NodeID{name, uid},
|
||||||
quorum: quorum,
|
quorum: quorum,
|
||||||
@@ -80,6 +80,10 @@ func NewNode(name router.PeerName, uid router.PeerUID, quorum uint) *Node {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (node *Node) SetQuorum(quorum uint) {
|
||||||
|
node.quorum = quorum
|
||||||
|
}
|
||||||
|
|
||||||
func (node *Node) GossipState() GossipState {
|
func (node *Node) GossipState() GossipState {
|
||||||
return node.knows
|
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
|
// simply a matter of gossipping a new proposal that supersedes all
|
||||||
// others.
|
// others.
|
||||||
func (node *Node) Propose() {
|
func (node *Node) Propose() {
|
||||||
|
if node.quorum == 0 {
|
||||||
|
panic("Paxos node.Propose() called with no quorum set")
|
||||||
|
}
|
||||||
// Find the highest round number around
|
// Find the highest round number around
|
||||||
round := uint(0)
|
round := uint(0)
|
||||||
|
|
||||||
@@ -223,7 +230,7 @@ func (node *Node) Think() bool {
|
|||||||
// about. This is not necessarily all peer names, but it is at least
|
// about. This is not necessarily all peer names, but it is at least
|
||||||
// a quorum, and so good enough for seeding the ring.
|
// a quorum, and so good enough for seeding the ring.
|
||||||
func (node *Node) pickValue() Value {
|
func (node *Node) pickValue() Value {
|
||||||
val := make([]router.PeerName, len(node.knows))
|
val := make([]mesh.PeerName, len(node.knows))
|
||||||
i := 0
|
i := 0
|
||||||
for id := range node.knows {
|
for id := range node.knows {
|
||||||
val[i] = id.Name
|
val[i] = id.Name
|
||||||
@@ -234,6 +241,9 @@ func (node *Node) pickValue() Value {
|
|||||||
|
|
||||||
// Has a consensus been reached, based on the known claims of other nodes?
|
// Has a consensus been reached, based on the known claims of other nodes?
|
||||||
func (node *Node) Consensus() (bool, AcceptedValue) {
|
func (node *Node) Consensus() (bool, AcceptedValue) {
|
||||||
|
if node.quorum == 0 {
|
||||||
|
return false, AcceptedValue{}
|
||||||
|
}
|
||||||
counts := map[ProposalID]uint{}
|
counts := map[ProposalID]uint{}
|
||||||
|
|
||||||
for _, claims := range node.knows {
|
for _, claims := range node.knows {
|
||||||
@@ -250,14 +260,16 @@ func (node *Node) Consensus() (bool, AcceptedValue) {
|
|||||||
return false, AcceptedValue{}
|
return false, AcceptedValue{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (node *Node) IsElector() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
type Status struct {
|
type Status struct {
|
||||||
|
Elector bool
|
||||||
KnownNodes int
|
KnownNodes int
|
||||||
Quorum uint
|
Quorum uint
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStatus(node *Node) *Status {
|
func NewStatus(node *Node) *Status {
|
||||||
if node == nil {
|
return &Status{true, len(node.knows), node.quorum}
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return &Status{len(node.knows), node.quorum}
|
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/weaveworks/weave/router"
|
"github.com/weaveworks/mesh"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TestNode struct {
|
type TestNode struct {
|
||||||
@@ -97,8 +97,8 @@ func makeRandomModel(params *TestParams, r *rand.Rand, t *testing.T) *Model {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for i := range m.nodes {
|
for i := range m.nodes {
|
||||||
m.nodes[i].Node = NewNode(router.PeerName(i/2+1),
|
m.nodes[i].Node = NewNode(mesh.PeerName(i/2+1),
|
||||||
router.PeerUID(r.Int63()), m.quorum)
|
mesh.PeerUID(r.Int63()), m.quorum)
|
||||||
m.nodes[i].Propose()
|
m.nodes[i].Propose()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,8 +172,8 @@ func (m *Model) isolateNode(node *TestNode) {
|
|||||||
|
|
||||||
// Restart a node
|
// Restart a node
|
||||||
func (m *Model) restart(node *TestNode) {
|
func (m *Model) restart(node *TestNode) {
|
||||||
node.Node = NewNode(router.PeerName(m.nextID),
|
node.Node = NewNode(mesh.PeerName(m.nextID),
|
||||||
router.PeerUID(m.r.Int63()), m.quorum)
|
mesh.PeerUID(m.r.Int63()), m.quorum)
|
||||||
m.nextID++
|
m.nextID++
|
||||||
node.Propose()
|
node.Propose()
|
||||||
|
|
||||||
@@ -300,14 +300,14 @@ func (m *Model) validate() {
|
|||||||
for i := range m.nodes {
|
for i := range m.nodes {
|
||||||
ok, val := m.nodes[i].Consensus()
|
ok, val := m.nodes[i].Consensus()
|
||||||
if !ok {
|
if !ok {
|
||||||
//m.dump()
|
m.dump()
|
||||||
m.t.Fatal("Node doesn't know about consensus")
|
m.t.Fatal("Node doesn't know about consensus")
|
||||||
}
|
}
|
||||||
|
|
||||||
firstConsensus := m.nodes[i].firstConsensus
|
firstConsensus := m.nodes[i].firstConsensus
|
||||||
if firstConsensus.Origin.valid() &&
|
if firstConsensus.Origin.valid() &&
|
||||||
firstConsensus.Origin != val.Origin {
|
firstConsensus.Origin != val.Origin {
|
||||||
//m.dump()
|
m.dump()
|
||||||
m.t.Fatal("Consensus mismatch")
|
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
|
||||||
|
}
|
||||||
+5
-4
@@ -3,17 +3,18 @@ package ring
|
|||||||
import (
|
import (
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
|
"github.com/weaveworks/mesh"
|
||||||
|
|
||||||
"github.com/weaveworks/weave/common"
|
"github.com/weaveworks/weave/common"
|
||||||
"github.com/weaveworks/weave/net/address"
|
"github.com/weaveworks/weave/net/address"
|
||||||
"github.com/weaveworks/weave/router"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Entry represents entries around the ring
|
// Entry represents entries around the ring
|
||||||
type entry struct {
|
type entry struct {
|
||||||
Token address.Address // The start of this range
|
Token address.Address // The start of this range
|
||||||
Peer router.PeerName // Who owns this range
|
Peer mesh.PeerName // Who owns this range
|
||||||
Version uint32 // Version of 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 {
|
func (e *entry) Equal(e2 *entry) bool {
|
||||||
@@ -21,7 +22,7 @@ func (e *entry) Equal(e2 *entry) bool {
|
|||||||
e.Version == e2.Version
|
e.Version == e2.Version
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *entry) update(peername router.PeerName, free address.Offset) {
|
func (e *entry) update(peername mesh.PeerName, free address.Count) {
|
||||||
e.Peer = peername
|
e.Peer = peername
|
||||||
e.Version++
|
e.Version++
|
||||||
e.Free = free
|
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)))
|
|
||||||
}
|
|
||||||
+205
-119
@@ -12,16 +12,21 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/weaveworks/mesh"
|
||||||
|
|
||||||
"github.com/weaveworks/weave/common"
|
"github.com/weaveworks/weave/common"
|
||||||
"github.com/weaveworks/weave/net/address"
|
"github.com/weaveworks/weave/net/address"
|
||||||
"github.com/weaveworks/weave/router"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type OnUpdate func(prev, curr []address.Range, local bool)
|
||||||
|
|
||||||
// Ring represents the ring itself
|
// Ring represents the ring itself
|
||||||
type Ring struct {
|
type Ring struct {
|
||||||
Start, End address.Address // [min, max) tokens in this ring. Due to wrapping, min == max (effectively)
|
Start, End address.Address // [min, max) tokens in this ring. Due to wrapping, min == max (effectively)
|
||||||
Peer router.PeerName // name of peer owning this ring instance
|
Peer mesh.PeerName // name of peer owning this ring instance
|
||||||
Entries entries // list of entries sorted by token
|
Entries entries // list of entries sorted by token
|
||||||
|
Seeds []mesh.PeerName // peers with which the ring was seeded
|
||||||
|
onUpdate OnUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Ring) assertInvariants() {
|
func (r *Ring) assertInvariants() {
|
||||||
@@ -36,41 +41,52 @@ var (
|
|||||||
ErrNotSorted = errors.New("Ring not sorted")
|
ErrNotSorted = errors.New("Ring not sorted")
|
||||||
ErrTokenRepeated = errors.New("Token appears twice in ring")
|
ErrTokenRepeated = errors.New("Token appears twice in ring")
|
||||||
ErrTokenOutOfRange = errors.New("Token is out of range")
|
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!")
|
ErrDifferentRange = errors.New("Received range differs from ours!")
|
||||||
ErrNewerVersion = errors.New("Received new version for entry I own!")
|
|
||||||
ErrInvalidEntry = errors.New("Received invalid state update!")
|
|
||||||
ErrEntryInMyRange = errors.New("Received new entry in my range!")
|
|
||||||
ErrNotFound = errors.New("No entries for peer found")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func 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 {
|
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
|
return ErrNotSorted
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check no token appears twice
|
// Check no token appears twice
|
||||||
// We know it's sorted...
|
// We know it's sorted...
|
||||||
for i := 1; i < len(r.Entries); i++ {
|
for i := 1; i < len(entries); i++ {
|
||||||
if r.Entries[i-1].Token == r.Entries[i].Token {
|
if entries[i-1].Token == entries[i].Token {
|
||||||
return ErrTokenRepeated
|
return ErrTokenRepeated
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(r.Entries) == 0 {
|
if len(entries) == 0 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check tokens are in range
|
// Check tokens are in range
|
||||||
if r.Entries.entry(0).Token < r.Start {
|
if entries.entry(0).Token < r.Start {
|
||||||
return ErrTokenOutOfRange
|
return ErrTokenOutOfRange
|
||||||
}
|
}
|
||||||
if r.Entries.entry(-1).Token >= r.End {
|
if entries.entry(-1).Token >= r.End {
|
||||||
return ErrTokenOutOfRange
|
return ErrTokenOutOfRange
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check all the freespaces are in range
|
// Check all the freespaces are in range
|
||||||
for i, entry := range r.Entries {
|
for i, entry := range entries {
|
||||||
next := r.Entries.entry(i + 1)
|
next := entries.entry(i + 1)
|
||||||
distance := r.distance(entry.Token, next.Token)
|
distance := r.distance(entry.Token, next.Token)
|
||||||
|
|
||||||
if entry.Free > distance {
|
if entry.Free > distance {
|
||||||
@@ -81,27 +97,46 @@ func (r *Ring) checkInvariants() error {
|
|||||||
return nil
|
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.
|
// New creates an empty ring belonging to peer.
|
||||||
func New(start, end address.Address, peer router.PeerName) *Ring {
|
func New(start, end address.Address, peer mesh.PeerName, f OnUpdate) *Ring {
|
||||||
common.Assert(start < end)
|
common.Assert(start < end)
|
||||||
|
|
||||||
ring := &Ring{Start: start, End: end, Peer: peer, Entries: make([]*entry, 0)}
|
ring := &Ring{Start: start, End: end, Peer: peer, Entries: make([]*entry, 0), onUpdate: f}
|
||||||
ring.updateExportedVariables()
|
|
||||||
return ring
|
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 {
|
func (r *Ring) Range() address.Range {
|
||||||
return address.Range{Start: r.Start, End: r.End}
|
return address.Range{Start: r.Start, End: r.End}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the distance between two tokens on this ring, dealing
|
// Returns the distance between two tokens on this ring, dealing
|
||||||
// with ranges which cross the origin
|
// 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 {
|
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)
|
// GrantRangeToHost modifies the ring such that range [start, end)
|
||||||
@@ -109,12 +144,12 @@ func (r *Ring) distance(start, end address.Address) address.Offset {
|
|||||||
// Preconditions:
|
// Preconditions:
|
||||||
// - start < end
|
// - start < end
|
||||||
// - [start, end) must be owned by the calling peer
|
// - [start, end) must be owned by the calling peer
|
||||||
func (r *Ring) GrantRangeToHost(start, end address.Address, peer router.PeerName) {
|
func (r *Ring) GrantRangeToHost(start, end address.Address, peer mesh.PeerName) {
|
||||||
//fmt.Printf("%s GrantRangeToHost [%v,%v) -> %s\n", r.Peer, start, end, peer)
|
//fmt.Printf("%s GrantRangeToHost [%v,%v) -> %s\n", r.Peer, start, end, peer)
|
||||||
|
|
||||||
r.assertInvariants()
|
r.assertInvariants()
|
||||||
|
defer r.trackUpdates()()
|
||||||
defer r.assertInvariants()
|
defer r.assertInvariants()
|
||||||
defer r.updateExportedVariables()
|
|
||||||
|
|
||||||
// ----------------- Start of Checks -----------------
|
// ----------------- Start of Checks -----------------
|
||||||
|
|
||||||
@@ -176,32 +211,69 @@ func (r *Ring) GrantRangeToHost(start, end address.Address, peer router.PeerName
|
|||||||
r.Entries.insert(entry{Token: end, Peer: r.Peer, Free: endFree})
|
r.Entries.insert(entry{Token: end, Peer: r.Peer, Free: endFree})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge the given ring into this ring and return any new ranges added
|
// Merge the given ring into this ring and indicate whether this ring
|
||||||
func (r *Ring) Merge(gossip Ring) error {
|
// got updated as a result.
|
||||||
|
func (r *Ring) Merge(gossip Ring) (bool, error) {
|
||||||
r.assertInvariants()
|
r.assertInvariants()
|
||||||
defer r.assertInvariants()
|
defer r.trackUpdates()()
|
||||||
defer r.updateExportedVariables()
|
|
||||||
|
|
||||||
// Don't panic when checking the gossiped in ring.
|
// Don't panic when checking the gossiped in ring.
|
||||||
// In this case just return any error found.
|
// In this case just return any error found.
|
||||||
if err := gossip.checkInvariants(); err != nil {
|
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 false, ErrDifferentSeeds
|
||||||
|
}
|
||||||
|
for i, seed := range gossip.Seeds {
|
||||||
|
if seed != r.Seeds[i] {
|
||||||
|
return false, ErrDifferentSeeds
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.Start != gossip.Start || r.End != gossip.End {
|
if r.Start != gossip.Start || r.End != gossip.End {
|
||||||
return ErrDifferentRange
|
return false, ErrDifferentRange
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now merge their ring with yours, in a temporary ring.
|
result, updated, err := r.Entries.merge(gossip.Entries, r.Peer)
|
||||||
var result entries
|
|
||||||
addToResult := func(e entry) { result = append(result, &e) }
|
|
||||||
|
|
||||||
|
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 mine, theirs *entry
|
||||||
var previousOwner *router.PeerName
|
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
|
var i, j int
|
||||||
for i < len(r.Entries) && j < len(gossip.Entries) {
|
for i < len(es) && j < len(other) {
|
||||||
mine, theirs = r.Entries[i], gossip.Entries[j]
|
mine, theirs = es[i], other[j]
|
||||||
switch {
|
switch {
|
||||||
case mine.Token < theirs.Token:
|
case mine.Token < theirs.Token:
|
||||||
addToResult(*mine)
|
addToResult(*mine)
|
||||||
@@ -209,53 +281,52 @@ func (r *Ring) Merge(gossip Ring) error {
|
|||||||
i++
|
i++
|
||||||
case mine.Token > theirs.Token:
|
case mine.Token > theirs.Token:
|
||||||
// insert, checking that a range owned by us hasn't been split
|
// insert, checking that a range owned by us hasn't been split
|
||||||
if previousOwner != nil && *previousOwner == r.Peer && theirs.Peer != r.Peer {
|
if previousOwner != nil && *previousOwner == ourPeer && theirs.Peer != ourPeer {
|
||||||
return ErrEntryInMyRange
|
err = errEntryInMyRange(theirs)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
addToResult(*theirs)
|
addTheirs(*theirs)
|
||||||
previousOwner = nil
|
|
||||||
j++
|
j++
|
||||||
case mine.Token == theirs.Token:
|
case mine.Token == theirs.Token:
|
||||||
// merge
|
// merge
|
||||||
switch {
|
switch {
|
||||||
case mine.Version >= theirs.Version:
|
case mine.Version >= theirs.Version:
|
||||||
if mine.Version == theirs.Version && !mine.Equal(theirs) {
|
if mine.Version == theirs.Version && !mine.Equal(theirs) {
|
||||||
common.Log.Debugf("Error merging entries at %s - %v != %v", mine.Token, mine, theirs)
|
err = errInconsistentEntry(mine, theirs)
|
||||||
return ErrInvalidEntry
|
return
|
||||||
}
|
}
|
||||||
addToResult(*mine)
|
addToResult(*mine)
|
||||||
previousOwner = &mine.Peer
|
previousOwner = &mine.Peer
|
||||||
case mine.Version < theirs.Version:
|
case mine.Version < theirs.Version:
|
||||||
if mine.Peer == r.Peer { // We shouldn't receive updates to our own tokens
|
if mine.Peer == ourPeer { // We shouldn't receive updates to our own tokens
|
||||||
return ErrNewerVersion
|
err = errNewerVersion(mine, theirs)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
addToResult(*theirs)
|
addTheirs(*theirs)
|
||||||
previousOwner = nil
|
|
||||||
}
|
}
|
||||||
i++
|
i++
|
||||||
j++
|
j++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// At this point, either i is at the end of r or j is at the end
|
// At this point, either i is at the end of es or j is at the end
|
||||||
// of gossip, so copy over the remaining entries.
|
// of other, so copy over the remaining entries.
|
||||||
|
|
||||||
for ; i < len(r.Entries); i++ {
|
for ; i < len(es); i++ {
|
||||||
mine = r.Entries[i]
|
mine = es[i]
|
||||||
addToResult(*mine)
|
addToResult(*mine)
|
||||||
}
|
}
|
||||||
|
|
||||||
for ; j < len(gossip.Entries); j++ {
|
for ; j < len(other); j++ {
|
||||||
theirs = gossip.Entries[j]
|
theirs = other[j]
|
||||||
if previousOwner != nil && *previousOwner == r.Peer && theirs.Peer != r.Peer {
|
if previousOwner != nil && *previousOwner == ourPeer && theirs.Peer != ourPeer {
|
||||||
return ErrEntryInMyRange
|
err = errEntryInMyRange(theirs)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
addToResult(*theirs)
|
addTheirs(*theirs)
|
||||||
previousOwner = nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Entries = result
|
return
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty returns true if the ring has no entries
|
// Empty returns true if the ring has no entries
|
||||||
@@ -288,10 +359,14 @@ func (r *Ring) splitRangesOverZero(ranges []address.Range) []address.Range {
|
|||||||
// ranges are owned by this peer. Will split ranges which
|
// ranges are owned by this peer. Will split ranges which
|
||||||
// span 0 in the ring.
|
// span 0 in the ring.
|
||||||
func (r *Ring) OwnedRanges() (result []address.Range) {
|
func (r *Ring) OwnedRanges() (result []address.Range) {
|
||||||
|
return r.OwnedRangesOfPeer(r.Peer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *Ring) OwnedRangesOfPeer(peer mesh.PeerName) (result []address.Range) {
|
||||||
r.assertInvariants()
|
r.assertInvariants()
|
||||||
|
|
||||||
for i, entry := range r.Entries {
|
for i, entry := range r.Entries {
|
||||||
if entry.Peer == r.Peer {
|
if entry.Peer == peer {
|
||||||
nextEntry := r.Entries.entry(i + 1)
|
nextEntry := r.Entries.entry(i + 1)
|
||||||
result = append(result, address.Range{Start: entry.Token, End: nextEntry.Token})
|
result = append(result, address.Range{Start: entry.Token, End: nextEntry.Token})
|
||||||
}
|
}
|
||||||
@@ -300,39 +375,58 @@ func (r *Ring) OwnedRanges() (result []address.Range) {
|
|||||||
return r.splitRangesOverZero(result)
|
return r.splitRangesOverZero(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClaimForPeers claims the entire ring for the array of peers passed
|
// For printing status
|
||||||
// in. Only works for empty rings.
|
type RangeInfo struct {
|
||||||
func (r *Ring) ClaimForPeers(peers []router.PeerName) {
|
Peer mesh.PeerName
|
||||||
common.Assert(r.Empty())
|
address.Range
|
||||||
defer r.assertInvariants()
|
Version uint32
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Ring) FprintWithNicknames(w io.Writer, m map[router.PeerName]string) {
|
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. Each claimed range is CIDR-aligned.
|
||||||
|
func (r *Ring) ClaimForPeers(peers []mesh.PeerName) {
|
||||||
|
common.Assert(r.Empty())
|
||||||
|
|
||||||
|
defer r.trackUpdates()()
|
||||||
|
defer r.assertInvariants()
|
||||||
|
defer func() {
|
||||||
|
e := r.Entries[len(r.Entries)-1]
|
||||||
|
common.Assert(address.Add(e.Token, address.Offset(e.Free)) == r.End)
|
||||||
|
}()
|
||||||
|
|
||||||
|
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 {
|
for _, entry := range r.Entries {
|
||||||
nickname, found := m[entry.Peer]
|
nickname, found := m[entry.Peer]
|
||||||
if found {
|
if found {
|
||||||
@@ -347,17 +441,16 @@ func (r *Ring) FprintWithNicknames(w io.Writer, m map[router.PeerName]string) {
|
|||||||
func (r *Ring) String() string {
|
func (r *Ring) String() string {
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
fmt.Fprintf(&buffer, "Ring [%s, %s)", r.Start, r.End)
|
fmt.Fprintf(&buffer, "Ring [%s, %s)", r.Start, r.End)
|
||||||
r.FprintWithNicknames(&buffer, make(map[router.PeerName]string))
|
r.FprintWithNicknames(&buffer, make(map[mesh.PeerName]string))
|
||||||
return buffer.String()
|
return buffer.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReportFree is used by the allocator to tell the ring how many free
|
// ReportFree is used by the allocator to tell the ring how many free
|
||||||
// ips are in a given range, so that ChoosePeersToAskForSpace can make
|
// ips are in a given range, so that ChoosePeersToAskForSpace can make
|
||||||
// more intelligent decisions.
|
// more intelligent decisions. Returns true if any changes made.
|
||||||
func (r *Ring) ReportFree(freespace map[address.Address]address.Offset) {
|
func (r *Ring) ReportFree(freespace map[address.Address]address.Count) (updated bool) {
|
||||||
r.assertInvariants()
|
r.assertInvariants()
|
||||||
defer r.assertInvariants()
|
defer r.assertInvariants()
|
||||||
defer r.updateExportedVariables()
|
|
||||||
|
|
||||||
common.Assert(!r.Empty())
|
common.Assert(!r.Empty())
|
||||||
entries := r.Entries
|
entries := r.Entries
|
||||||
@@ -384,20 +477,22 @@ func (r *Ring) ReportFree(freespace map[address.Address]address.Offset) {
|
|||||||
// Check we're not reporting more space than the range
|
// Check we're not reporting more space than the range
|
||||||
entry, next := entries.entry(i), entries.entry(i+1)
|
entry, next := entries.entry(i), entries.entry(i+1)
|
||||||
maxSize := r.distance(entry.Token, next.Token)
|
maxSize := r.distance(entry.Token, next.Token)
|
||||||
common.Assert(free <= maxSize)
|
common.Assert(free <= address.Count(maxSize))
|
||||||
|
|
||||||
if entries[i].Free == free {
|
if entries[i].Free == free {
|
||||||
return
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
entries[i].Free = free
|
entries[i].Free = free
|
||||||
entries[i].Version++
|
entries[i].Version++
|
||||||
|
updated = true
|
||||||
}
|
}
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
type weightedPeer struct {
|
type weightedPeer struct {
|
||||||
weight float64
|
weight float64
|
||||||
peername router.PeerName
|
peername mesh.PeerName
|
||||||
}
|
}
|
||||||
type weightedPeers []weightedPeer
|
type weightedPeers []weightedPeer
|
||||||
|
|
||||||
@@ -408,11 +503,8 @@ 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
|
// ChoosePeersToAskForSpace returns all peers we can ask for space in
|
||||||
// the range [start, end), in weighted-random order. Assumes start<end.
|
// the range [start, end), in weighted-random order. Assumes start<end.
|
||||||
func (r *Ring) ChoosePeersToAskForSpace(start, end address.Address) []router.PeerName {
|
func (r *Ring) ChoosePeersToAskForSpace(start, end address.Address) []mesh.PeerName {
|
||||||
var (
|
totalSpacePerPeer := make(map[mesh.PeerName]address.Count)
|
||||||
sum address.Offset
|
|
||||||
totalSpacePerPeer = make(map[router.PeerName]address.Offset) // Compute total free space per peer
|
|
||||||
)
|
|
||||||
|
|
||||||
// iterate through tokens
|
// iterate through tokens
|
||||||
for i, entry := range r.Entries {
|
for i, entry := range r.Entries {
|
||||||
@@ -434,7 +526,6 @@ func (r *Ring) ChoosePeersToAskForSpace(start, end address.Address) []router.Pee
|
|||||||
}
|
}
|
||||||
|
|
||||||
totalSpacePerPeer[entry.Peer] += entry.Free
|
totalSpacePerPeer[entry.Peer] += entry.Free
|
||||||
sum += entry.Free
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Compute weighted random numbers, then sort.
|
// Compute weighted random numbers, then sort.
|
||||||
@@ -445,46 +536,41 @@ func (r *Ring) ChoosePeersToAskForSpace(start, end address.Address) []router.Pee
|
|||||||
ws = append(ws, weightedPeer{weight: float64(space) * rand.Float64(), peername: peername})
|
ws = append(ws, weightedPeer{weight: float64(space) * rand.Float64(), peername: peername})
|
||||||
}
|
}
|
||||||
sort.Sort(ws)
|
sort.Sort(ws)
|
||||||
result := make([]router.PeerName, len(ws))
|
result := make([]mesh.PeerName, len(ws))
|
||||||
for i, wp := range ws {
|
for i, wp := range ws {
|
||||||
result[i] = wp.peername
|
result[i] = wp.peername
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Ring) PickPeerForTransfer() router.PeerName {
|
func (r *Ring) PickPeerForTransfer(isValid func(mesh.PeerName) bool) mesh.PeerName {
|
||||||
for _, entry := range r.Entries {
|
for _, entry := range r.Entries {
|
||||||
if entry.Peer != r.Peer {
|
if entry.Peer != r.Peer && isValid(entry.Peer) {
|
||||||
return entry.Peer
|
return entry.Peer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return router.UnknownPeerName
|
return mesh.UnknownPeerName
|
||||||
}
|
}
|
||||||
|
|
||||||
// Transfer will mark all entries associated with 'from' peer as owned by 'to' peer
|
// Transfer will mark all entries associated with 'from' peer as owned by 'to' peer
|
||||||
// and return ranges indicating the new space we picked up
|
// and return ranges indicating the new space we picked up
|
||||||
func (r *Ring) Transfer(from, to router.PeerName) ([]address.Range, error) {
|
func (r *Ring) Transfer(from, to mesh.PeerName) []address.Range {
|
||||||
r.assertInvariants()
|
r.assertInvariants()
|
||||||
|
defer r.trackUpdates()()
|
||||||
|
defer r.trackUpdatesOfPeer(from)()
|
||||||
defer r.assertInvariants()
|
defer r.assertInvariants()
|
||||||
defer r.updateExportedVariables()
|
|
||||||
|
|
||||||
var newRanges []address.Range
|
var newRanges []address.Range
|
||||||
found := false
|
|
||||||
|
|
||||||
for i, entry := range r.Entries {
|
for i, entry := range r.Entries {
|
||||||
if entry.Peer == from {
|
if entry.Peer == from {
|
||||||
found = true
|
|
||||||
entry.Peer = to
|
entry.Peer = to
|
||||||
entry.Version++
|
entry.Version++
|
||||||
newRanges = append(newRanges, address.Range{Start: entry.Token, End: r.Entries.entry(i + 1).Token})
|
newRanges = append(newRanges, address.Range{Start: entry.Token, End: r.Entries.entry(i + 1).Token})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !found {
|
return r.splitRangesOverZero(newRanges)
|
||||||
return nil, ErrNotFound
|
|
||||||
}
|
|
||||||
|
|
||||||
return r.splitRangesOverZero(newRanges), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Contains returns true if addr is in this ring
|
// Contains returns true if addr is in this ring
|
||||||
@@ -493,13 +579,13 @@ func (r *Ring) Contains(addr address.Address) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Owner returns the peername which owns the range containing addr
|
// Owner returns the peername which owns the range containing addr
|
||||||
func (r *Ring) Owner(token address.Address) router.PeerName {
|
func (r *Ring) Owner(token address.Address) mesh.PeerName {
|
||||||
common.Assert(r.Start <= token && token < r.End)
|
common.Assert(r.Start <= token && token < r.End)
|
||||||
|
|
||||||
r.assertInvariants()
|
r.assertInvariants()
|
||||||
// There can be no owners on an empty ring
|
// There can be no owners on an empty ring
|
||||||
if r.Empty() {
|
if r.Empty() {
|
||||||
return router.UnknownPeerName
|
return mesh.UnknownPeerName
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for the right-most entry, less than or equal to token
|
// Look for the right-most entry, less than or equal to token
|
||||||
@@ -512,8 +598,8 @@ func (r *Ring) Owner(token address.Address) router.PeerName {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the set of PeerNames mentioned in the ring
|
// Get the set of PeerNames mentioned in the ring
|
||||||
func (r *Ring) PeerNames() map[router.PeerName]struct{} {
|
func (r *Ring) PeerNames() map[mesh.PeerName]struct{} {
|
||||||
res := make(map[router.PeerName]struct{})
|
res := make(map[mesh.PeerName]struct{})
|
||||||
|
|
||||||
for _, entry := range r.Entries {
|
for _, entry := range r.Entries {
|
||||||
res[entry.Peer] = struct{}{}
|
res[entry.Peer] = struct{}{}
|
||||||
|
|||||||
+168
-121
@@ -8,17 +8,19 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/weaveworks/mesh"
|
||||||
|
|
||||||
"github.com/weaveworks/weave/common"
|
"github.com/weaveworks/weave/common"
|
||||||
"github.com/weaveworks/weave/net/address"
|
"github.com/weaveworks/weave/net/address"
|
||||||
"github.com/weaveworks/weave/router"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
peer1name, _ = router.PeerNameFromString("01:00:00:00:02:00")
|
peer1name, _ = mesh.PeerNameFromString("01:00:00:00:02:00")
|
||||||
peer2name, _ = router.PeerNameFromString("02:00:00:00:02:00")
|
peer2name, _ = mesh.PeerNameFromString("02:00:00:00:02:00")
|
||||||
peer3name, _ = router.PeerNameFromString("03: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")
|
dot10, dot245 = ParseIP("10.0.0.10"), ParseIP("10.0.0.245")
|
||||||
dot250 = ParseIP("10.0.0.250")
|
dot250 = ParseIP("10.0.0.250")
|
||||||
middle = ParseIP("10.0.0.128")
|
middle = ParseIP("10.0.0.128")
|
||||||
@@ -29,8 +31,17 @@ func ParseIP(s string) address.Address {
|
|||||||
return addr
|
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) {
|
func TestInvariants(t *testing.T) {
|
||||||
ring := New(start, end, peer1name)
|
ring := NewRing(start, end, peer1name)
|
||||||
|
|
||||||
// Check ring is sorted
|
// Check ring is sorted
|
||||||
ring.Entries = []*entry{{Token: dot245, Peer: peer1name}, {Token: dot10, Peer: peer2name}}
|
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")
|
require.True(t, ring.checkInvariants() == ErrTokenRepeated, "Expected error")
|
||||||
|
|
||||||
// Check tokens are in bounds
|
// Check tokens are in bounds
|
||||||
ring = New(dot10, dot245, peer1name)
|
ring = NewRing(dot10, dot245, peer1name)
|
||||||
ring.Entries = []*entry{{Token: start, Peer: peer1name}}
|
ring.Entries = []*entry{{Token: start, Peer: peer1name}}
|
||||||
require.True(t, ring.checkInvariants() == ErrTokenOutOfRange, "Expected error")
|
require.True(t, ring.checkInvariants() == ErrTokenOutOfRange, "Expected error")
|
||||||
|
|
||||||
@@ -50,7 +61,7 @@ func TestInvariants(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestInsert(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}}
|
ring.Entries = []*entry{{Token: start, Peer: peer1name, Free: 255}}
|
||||||
|
|
||||||
require.Panics(t, func() {
|
require.Panics(t, func() {
|
||||||
@@ -59,17 +70,23 @@ func TestInsert(t *testing.T) {
|
|||||||
|
|
||||||
ring.Entries.entry(0).Free = 0
|
ring.Entries.entry(0).Free = 0
|
||||||
ring.Entries.insert(entry{Token: dot245, Peer: peer1name})
|
ring.Entries.insert(entry{Token: dot245, Peer: peer1name})
|
||||||
ring2 := New(start, end, peer1name)
|
check := []RangeInfo{
|
||||||
ring2.Entries = []*entry{{Token: start, Peer: peer1name, Free: 0}, {Token: dot245, Peer: peer1name}}
|
{Peer: peer1name, Range: address.Range{Start: start, End: dot245}},
|
||||||
require.Equal(t, ring2, ring)
|
{Peer: peer1name, Range: address.Range{Start: dot245, End: end}},
|
||||||
|
}
|
||||||
|
require.Equal(t, check, ring.AllRangeInfo())
|
||||||
|
|
||||||
ring.Entries.insert(entry{Token: dot10, Peer: peer1name})
|
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}}
|
check2 := []RangeInfo{
|
||||||
require.Equal(t, ring2, ring)
|
{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) {
|
func TestBetween(t *testing.T) {
|
||||||
ring1 := New(start, end, peer1name)
|
ring1 := NewRing(start, end, peer1name)
|
||||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 255}}
|
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 255}}
|
||||||
|
|
||||||
// First off, in a ring where everything is owned by the peer
|
// 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) {
|
func TestGrantSimple(t *testing.T) {
|
||||||
ring1 := New(start, end, peer1name)
|
ring1 := NewRing(start, end, peer1name)
|
||||||
ring2 := New(start, end, peer2name)
|
ring2 := NewRing(start, end, peer2name)
|
||||||
|
|
||||||
// Claim everything for peer1
|
// Claim everything for peer1
|
||||||
ring1.ClaimItAll()
|
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
|
// Now grant everything to peer2
|
||||||
ring1.GrantRangeToHost(start, end, peer2name)
|
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)
|
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||||
|
|
||||||
// Now spint back to peer 1
|
// Now spint back to peer 1
|
||||||
ring2.GrantRangeToHost(dot10, end, peer1name)
|
ring2.GrantRangeToHost(dot10, end, peer1name)
|
||||||
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 10, Version: 2},
|
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)
|
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||||
|
|
||||||
// And spint back to peer 2 again
|
// And spint back to peer 2 again
|
||||||
ring1.GrantRangeToHost(dot245, end, peer2name)
|
ring1.GrantRangeToHost(dot245, end, peer2name)
|
||||||
require.Equal(t, entries{{Token: start, Peer: peer2name, Free: 10, Version: 2},
|
require.Equal(t, entries{{Token: start, Peer: peer2name, Free: 10, Version: 2},
|
||||||
{Token: dot10, Peer: peer1name, Free: 235, Version: 1},
|
{Token: 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
|
// Grant range spanning a live token
|
||||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 10, Version: 2},
|
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) {
|
func TestGrantSplit(t *testing.T) {
|
||||||
ring1 := New(start, end, peer1name)
|
ring1 := NewRing(start, end, peer1name)
|
||||||
ring2 := New(start, end, peer2name)
|
ring2 := NewRing(start, end, peer2name)
|
||||||
|
|
||||||
// Claim everything for peer1
|
// Claim everything for peer1
|
||||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 255}}
|
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 256}}
|
||||||
ring2.Merge(*ring1)
|
merge(ring2, ring1)
|
||||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||||
|
|
||||||
// Now grant a split range to peer2
|
// Now grant a split range to peer2
|
||||||
ring1.GrantRangeToHost(dot10, dot245, peer2name)
|
ring1.GrantRangeToHost(dot10, dot245, peer2name)
|
||||||
require.Equal(t, entries{{Token: start, Peer: peer1name, Free: 10, Version: 1},
|
require.Equal(t, entries{{Token: start, Peer: peer1name, Free: 10, Version: 1},
|
||||||
{Token: dot10, Peer: peer2name, Free: 235},
|
{Token: dot10, Peer: peer2name, Free: 235},
|
||||||
{Token: dot245, Peer: peer1name, Free: 10}}, ring1.Entries)
|
{Token: dot245, Peer: peer1name, Free: 11}}, ring1.Entries)
|
||||||
|
|
||||||
ring1.assertInvariants()
|
ring1.assertInvariants()
|
||||||
|
|
||||||
@@ -169,22 +186,22 @@ func TestGrantSplit(t *testing.T) {
|
|||||||
require.Equal(t, entries{{Token: start, Peer: peer1name, Free: 10, Version: 2},
|
require.Equal(t, entries{{Token: start, Peer: peer1name, Free: 10, Version: 2},
|
||||||
{Token: dot10, Peer: peer2name, Free: 118, Version: 1},
|
{Token: dot10, Peer: peer2name, Free: 118, Version: 1},
|
||||||
{Token: middle, Peer: peer2name, Free: 117, 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()
|
ring1.assertInvariants()
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMergeSimple(t *testing.T) {
|
func TestMergeSimple(t *testing.T) {
|
||||||
ring1 := New(start, end, peer1name)
|
ring1 := NewRing(start, end, peer1name)
|
||||||
ring2 := New(start, end, peer2name)
|
ring2 := NewRing(start, end, peer2name)
|
||||||
|
|
||||||
// Claim everything for peer1
|
// Claim everything for peer1
|
||||||
ring1.ClaimItAll()
|
ring1.ClaimItAll()
|
||||||
ring1.GrantRangeToHost(middle, end, peer2name)
|
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},
|
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)
|
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||||
|
|
||||||
@@ -193,47 +210,48 @@ func TestMergeSimple(t *testing.T) {
|
|||||||
ring1.GrantRangeToHost(start, middle, peer2name)
|
ring1.GrantRangeToHost(start, middle, peer2name)
|
||||||
ring2.GrantRangeToHost(middle, end, peer1name)
|
ring2.GrantRangeToHost(middle, end, peer1name)
|
||||||
|
|
||||||
require.NoError(t, ring2.Merge(*ring1))
|
require.NoError(t, merge(ring2, ring1))
|
||||||
require.NoError(t, ring1.Merge(*ring2))
|
require.NoError(t, merge(ring1, ring2))
|
||||||
|
|
||||||
require.Equal(t, entries{{Token: start, Peer: peer2name, Free: 128, Version: 2},
|
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)
|
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMergeErrors(t *testing.T) {
|
func TestMergeErrors(t *testing.T) {
|
||||||
// Cannot Merge in an invalid ring
|
// Cannot Merge in an invalid ring
|
||||||
ring1 := New(start, end, peer1name)
|
ring1 := NewRing(start, end, peer1name)
|
||||||
ring2 := New(start, end, peer2name)
|
ring2 := NewRing(start, end, peer2name)
|
||||||
ring2.Entries = []*entry{{Token: middle, Peer: peer2name}, {Token: start, Peer: 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
|
// Should Merge two rings for different ranges
|
||||||
ring2 = New(start, middle, peer2name)
|
ring2 = NewRing(start, middle, peer2name)
|
||||||
ring2.Entries = []*entry{}
|
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
|
// 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}}
|
ring1.Entries = []*entry{{Token: start, Peer: peer1name}}
|
||||||
ring2.Entries = []*entry{{Token: start, Peer: peer1name, Version: 1}}
|
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
|
// Cannot Merge two entries with same version but different hosts
|
||||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name}}
|
ring1.Entries = []*entry{{Token: start, Peer: peer1name}}
|
||||||
ring2.Entries = []*entry{{Token: start, Peer: peer2name}}
|
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
|
// Cannot Merge an entry into a range I own
|
||||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name}}
|
ring1.Entries = []*entry{{Token: start, Peer: peer1name}}
|
||||||
ring2.Entries = []*entry{{Token: middle, Peer: peer2name}}
|
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) {
|
func TestMergeMore(t *testing.T) {
|
||||||
ring1 := New(start, end, peer1name)
|
ring1 := NewRing(start, end, peer1name)
|
||||||
ring2 := New(start, end, peer2name)
|
ring2 := NewRing(start, end, peer2name)
|
||||||
|
|
||||||
assertRing := func(ring *Ring, entries entries) {
|
assertRing := func(ring *Ring, entries entries) {
|
||||||
require.Equal(t, entries, ring.Entries)
|
require.Equal(t, entries, ring.Entries)
|
||||||
@@ -244,74 +262,74 @@ func TestMergeMore(t *testing.T) {
|
|||||||
|
|
||||||
// Claim everything for peer1
|
// Claim everything for peer1
|
||||||
ring1.ClaimItAll()
|
ring1.ClaimItAll()
|
||||||
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 255}})
|
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 256}})
|
||||||
assertRing(ring2, []*entry{})
|
assertRing(ring2, []*entry{})
|
||||||
|
|
||||||
// Check the Merge sends it to the other ring
|
// Check the Merge sends it to the other ring
|
||||||
require.NoError(t, ring2.Merge(*ring1))
|
require.NoError(t, merge(ring2, ring1))
|
||||||
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 255}})
|
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 256}})
|
||||||
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 255}})
|
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 256}})
|
||||||
|
|
||||||
// Give everything to peer2
|
// Give everything to peer2
|
||||||
ring1.GrantRangeToHost(start, end, peer2name)
|
ring1.GrantRangeToHost(start, end, peer2name)
|
||||||
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 255, Version: 1}})
|
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 256, Version: 1}})
|
||||||
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 255}})
|
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 256}})
|
||||||
|
|
||||||
require.NoError(t, ring2.Merge(*ring1))
|
require.NoError(t, merge(ring2, ring1))
|
||||||
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 255, Version: 1}})
|
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 256, Version: 1}})
|
||||||
assertRing(ring2, []*entry{{Token: start, Peer: peer2name, Free: 255, Version: 1}})
|
assertRing(ring2, []*entry{{Token: start, Peer: peer2name, Free: 256, Version: 1}})
|
||||||
|
|
||||||
// And carve off some space
|
// And carve off some space
|
||||||
ring2.GrantRangeToHost(middle, end, peer1name)
|
ring2.GrantRangeToHost(middle, end, peer1name)
|
||||||
assertRing(ring2, []*entry{{Token: start, Peer: peer2name, Free: 128, Version: 2},
|
assertRing(ring2, []*entry{{Token: start, Peer: peer2name, Free: 128, Version: 2},
|
||||||
{Token: middle, Peer: peer1name, Free: 127}})
|
{Token: middle, Peer: peer1name, Free: 128}})
|
||||||
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 255, Version: 1}})
|
assertRing(ring1, []*entry{{Token: start, Peer: peer2name, Free: 256, Version: 1}})
|
||||||
|
|
||||||
// And Merge back
|
// 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},
|
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},
|
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
|
// 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},
|
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},
|
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) {
|
func TestMergeSplit(t *testing.T) {
|
||||||
ring1 := New(start, end, peer1name)
|
ring1 := NewRing(start, end, peer1name)
|
||||||
ring2 := New(start, end, peer2name)
|
ring2 := NewRing(start, end, peer2name)
|
||||||
|
|
||||||
// Claim everything for peer2
|
// Claim everything for peer2
|
||||||
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 255}}
|
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 256}}
|
||||||
require.NoError(t, ring2.Merge(*ring1))
|
require.NoError(t, merge(ring2, ring1))
|
||||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||||
|
|
||||||
// Now grant a split range to peer1
|
// Now grant a split range to peer1
|
||||||
ring2.GrantRangeToHost(dot10, dot245, peer1name)
|
ring2.GrantRangeToHost(dot10, dot245, peer1name)
|
||||||
require.Equal(t, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
|
require.Equal(t, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
|
||||||
{Token: dot10, Peer: peer1name, Free: 235},
|
{Token: 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},
|
require.Equal(t, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
|
||||||
{Token: dot10, Peer: peer1name, Free: 235},
|
{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)
|
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMergeSplit2(t *testing.T) {
|
func TestMergeSplit2(t *testing.T) {
|
||||||
ring1 := New(start, end, peer1name)
|
ring1 := NewRing(start, end, peer1name)
|
||||||
ring2 := New(start, end, peer2name)
|
ring2 := NewRing(start, end, peer2name)
|
||||||
|
|
||||||
// Claim everything for peer2
|
// Claim everything for peer2
|
||||||
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 250}, {Token: dot250, Peer: peer2name, Free: 5}}
|
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)
|
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||||
|
|
||||||
// Now grant a split range to peer1
|
// Now grant a split range to peer1
|
||||||
@@ -320,7 +338,7 @@ func TestMergeSplit2(t *testing.T) {
|
|||||||
{Token: dot10, Peer: peer1name, Free: 235},
|
{Token: dot10, Peer: peer1name, Free: 235},
|
||||||
{Token: dot245, Peer: peer2name, Free: 5}, {Token: dot250, Peer: peer2name, Free: 5}}, ring2.Entries)
|
{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},
|
require.Equal(t, entries{{Token: start, Peer: peer2name, Free: 10, Version: 1},
|
||||||
{Token: dot10, Peer: peer1name, Free: 235},
|
{Token: dot10, Peer: peer1name, Free: 235},
|
||||||
{Token: dot245, Peer: peer2name, Free: 5}, {Token: dot250, Peer: peer2name, Free: 5}}, ring1.Entries)
|
{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
|
// A simple test, very similar to above, but using the marshalling to byte[]s
|
||||||
func TestGossip(t *testing.T) {
|
func TestGossip(t *testing.T) {
|
||||||
ring1 := New(start, end, peer1name)
|
ring1 := NewRing(start, end, peer1name)
|
||||||
ring2 := New(start, end, peer2name)
|
ring2 := NewRing(start, end, peer2name)
|
||||||
|
|
||||||
assertRing := func(ring *Ring, entries entries) {
|
assertRing := func(ring *Ring, entries entries) {
|
||||||
require.Equal(t, entries, ring.Entries)
|
require.Equal(t, entries, ring.Entries)
|
||||||
@@ -342,23 +360,23 @@ func TestGossip(t *testing.T) {
|
|||||||
|
|
||||||
// Claim everything for peer1
|
// Claim everything for peer1
|
||||||
ring1.ClaimItAll()
|
ring1.ClaimItAll()
|
||||||
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 255}})
|
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 256}})
|
||||||
assertRing(ring2, []*entry{})
|
assertRing(ring2, []*entry{})
|
||||||
|
|
||||||
// Check the Merge sends it to the other ring
|
// Check the Merge sends it to the other ring
|
||||||
require.NoError(t, ring2.Merge(*ring1))
|
require.NoError(t, merge(ring2, ring1))
|
||||||
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 255}})
|
assertRing(ring1, []*entry{{Token: start, Peer: peer1name, Free: 256}})
|
||||||
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 255}})
|
assertRing(ring2, []*entry{{Token: start, Peer: peer1name, Free: 256}})
|
||||||
}
|
}
|
||||||
|
|
||||||
func assertPeersWithSpace(t *testing.T, ring *Ring, start, end address.Address, expected int) []router.PeerName {
|
func assertPeersWithSpace(t *testing.T, ring *Ring, start, end address.Address, expected int) []mesh.PeerName {
|
||||||
peers := ring.ChoosePeersToAskForSpace(start, end)
|
peers := ring.ChoosePeersToAskForSpace(start, end)
|
||||||
require.Equal(t, expected, len(peers))
|
require.Equal(t, expected, len(peers))
|
||||||
return peers
|
return peers
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFindFree(t *testing.T) {
|
func TestFindFree(t *testing.T) {
|
||||||
ring1 := New(start, end, peer1name)
|
ring1 := NewRing(start, end, peer1name)
|
||||||
|
|
||||||
assertPeersWithSpace(t, ring1, start, end, 0)
|
assertPeersWithSpace(t, ring1, start, end, 0)
|
||||||
|
|
||||||
@@ -366,7 +384,7 @@ func TestFindFree(t *testing.T) {
|
|||||||
assertPeersWithSpace(t, ring1, start, end, 0)
|
assertPeersWithSpace(t, ring1, start, end, 0)
|
||||||
|
|
||||||
// We shouldn't return outselves
|
// 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)
|
assertPeersWithSpace(t, ring1, start, end, 0)
|
||||||
|
|
||||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 1},
|
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 1},
|
||||||
@@ -375,7 +393,7 @@ func TestFindFree(t *testing.T) {
|
|||||||
ring1.assertInvariants()
|
ring1.assertInvariants()
|
||||||
|
|
||||||
// We should return others
|
// We should return others
|
||||||
var peers []router.PeerName
|
var peers []mesh.PeerName
|
||||||
|
|
||||||
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 1}}
|
ring1.Entries = []*entry{{Token: start, Peer: peer2name, Free: 1}}
|
||||||
peers = assertPeersWithSpace(t, ring1, start, end, 1)
|
peers = assertPeersWithSpace(t, ring1, start, end, 1)
|
||||||
@@ -394,14 +412,14 @@ func TestFindFree(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestReportFree(t *testing.T) {
|
func TestReportFree(t *testing.T) {
|
||||||
ring1 := New(start, end, peer1name)
|
ring1 := NewRing(start, end, peer1name)
|
||||||
ring2 := New(start, end, peer2name)
|
ring2 := NewRing(start, end, peer2name)
|
||||||
|
|
||||||
ring1.ClaimItAll()
|
ring1.ClaimItAll()
|
||||||
ring1.GrantRangeToHost(middle, end, peer2name)
|
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() {
|
for _, r := range ring2.OwnedRanges() {
|
||||||
freespace[r.Start] = 0
|
freespace[r.Start] = 0
|
||||||
}
|
}
|
||||||
@@ -409,7 +427,7 @@ func TestReportFree(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMisc(t *testing.T) {
|
func TestMisc(t *testing.T) {
|
||||||
ring := New(start, end, peer1name)
|
ring := NewRing(start, end, peer1name)
|
||||||
|
|
||||||
require.True(t, ring.Empty(), "empty")
|
require.True(t, ring.Empty(), "empty")
|
||||||
|
|
||||||
@@ -418,38 +436,38 @@ func TestMisc(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestEmptyGossip(t *testing.T) {
|
func TestEmptyGossip(t *testing.T) {
|
||||||
ring1 := New(start, end, peer1name)
|
ring1 := NewRing(start, end, peer1name)
|
||||||
ring2 := New(start, end, peer2name)
|
ring2 := NewRing(start, end, peer2name)
|
||||||
|
|
||||||
ring1.ClaimItAll()
|
ring1.ClaimItAll()
|
||||||
// This used to panic, and it shouldn't
|
// 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) {
|
func TestMergeOldMessage(t *testing.T) {
|
||||||
ring1 := New(start, end, peer1name)
|
ring1 := NewRing(start, end, peer1name)
|
||||||
ring2 := New(start, end, peer2name)
|
ring2 := NewRing(start, end, peer2name)
|
||||||
|
|
||||||
ring1.ClaimItAll()
|
ring1.ClaimItAll()
|
||||||
require.NoError(t, ring2.Merge(*ring1))
|
require.NoError(t, merge(ring2, ring1))
|
||||||
|
|
||||||
ring1.GrantRangeToHost(middle, end, peer1name)
|
ring1.GrantRangeToHost(middle, end, peer1name)
|
||||||
require.NoError(t, ring1.Merge(*ring2))
|
require.NoError(t, merge(ring1, ring2))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSplitRangeAtBeginning(t *testing.T) {
|
func TestSplitRangeAtBeginning(t *testing.T) {
|
||||||
ring1 := New(start, end, peer1name)
|
ring1 := NewRing(start, end, peer1name)
|
||||||
ring2 := New(start, end, peer2name)
|
ring2 := NewRing(start, end, peer2name)
|
||||||
|
|
||||||
ring1.ClaimItAll()
|
ring1.ClaimItAll()
|
||||||
require.NoError(t, ring2.Merge(*ring1))
|
require.NoError(t, merge(ring2, ring1))
|
||||||
|
|
||||||
ring1.GrantRangeToHost(start, middle, peer2name)
|
ring1.GrantRangeToHost(start, middle, peer2name)
|
||||||
require.NoError(t, ring2.Merge(*ring1))
|
require.NoError(t, merge(ring2, ring1))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOwnedRange(t *testing.T) {
|
func TestOwnedRange(t *testing.T) {
|
||||||
ring1 := New(start, end, peer1name)
|
ring1 := NewRing(start, end, peer1name)
|
||||||
ring1.ClaimItAll()
|
ring1.ClaimItAll()
|
||||||
|
|
||||||
require.Equal(t, []address.Range{{Start: start, End: end}}, ring1.OwnedRanges())
|
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)
|
ring1.GrantRangeToHost(middle, end, peer2name)
|
||||||
require.Equal(t, []address.Range{{Start: start, End: middle}}, ring1.OwnedRanges())
|
require.Equal(t, []address.Range{{Start: start, End: middle}}, ring1.OwnedRanges())
|
||||||
|
|
||||||
ring2 := New(start, end, peer2name)
|
ring2 := NewRing(start, end, peer2name)
|
||||||
ring2.Merge(*ring1)
|
merge(ring2, ring1)
|
||||||
require.Equal(t, []address.Range{{Start: middle, End: end}}, ring2.OwnedRanges())
|
require.Equal(t, []address.Range{{Start: middle, End: end}}, ring2.OwnedRanges())
|
||||||
|
|
||||||
ring2.Entries = []*entry{{Token: middle, Peer: peer2name}}
|
ring2.Entries = []*entry{{Token: middle, Peer: peer2name}}
|
||||||
@@ -472,7 +490,7 @@ func TestOwnedRange(t *testing.T) {
|
|||||||
|
|
||||||
func TestTransfer(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
|
// 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.ClaimItAll()
|
||||||
ring1.GrantRangeToHost(middle, end, peer2name)
|
ring1.GrantRangeToHost(middle, end, peer2name)
|
||||||
ring1.Transfer(peer2name, peer1name)
|
ring1.Transfer(peer2name, peer1name)
|
||||||
@@ -480,7 +498,7 @@ func TestTransfer(t *testing.T) {
|
|||||||
|
|
||||||
// Second test is what happens when a token exists at the end of a range but is transferred
|
// Second test is what happens when a token exists at the end of a range but is transferred
|
||||||
// - does it get resurrected correctly?
|
// - does it get resurrected correctly?
|
||||||
ring1 = New(start, end, peer1name)
|
ring1 = NewRing(start, end, peer1name)
|
||||||
ring1.ClaimItAll()
|
ring1.ClaimItAll()
|
||||||
ring1.GrantRangeToHost(middle, end, peer2name)
|
ring1.GrantRangeToHost(middle, end, peer2name)
|
||||||
ring1.Transfer(peer2name, peer1name)
|
ring1.Transfer(peer2name, peer1name)
|
||||||
@@ -489,11 +507,11 @@ func TestTransfer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestOwner(t *testing.T) {
|
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.True(t, ring1.Contains(start), "start should be in ring")
|
||||||
require.False(t, ring1.Contains(end), "end should not be in ring")
|
require.False(t, ring1.Contains(end), "end should not be in ring")
|
||||||
|
|
||||||
require.Equal(t, router.UnknownPeerName, ring1.Owner(start))
|
require.Equal(t, mesh.UnknownPeerName, ring1.Owner(start))
|
||||||
|
|
||||||
ring1.ClaimItAll()
|
ring1.ClaimItAll()
|
||||||
ring1.GrantRangeToHost(middle, end, peer2name)
|
ring1.GrantRangeToHost(middle, end, peer2name)
|
||||||
@@ -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
|
type addressSlice []address.Address
|
||||||
|
|
||||||
func (s addressSlice) Len() int { return len(s) }
|
func (s addressSlice) Len() int { return len(s) }
|
||||||
@@ -517,10 +565,9 @@ func TestFuzzRing(t *testing.T) {
|
|||||||
iterations = 1000
|
iterations = 1000
|
||||||
)
|
)
|
||||||
|
|
||||||
peers := make([]router.PeerName, numPeers)
|
peers := make([]mesh.PeerName, numPeers)
|
||||||
for i := 0; i < numPeers; i++ {
|
for i := 0; i < numPeers; i++ {
|
||||||
peer, _ := router.PeerNameFromString(fmt.Sprintf("%02d:00:00:00:02:00", i))
|
peers[i] = makePeerName(i)
|
||||||
peers[i] = peer
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make a valid, random ring
|
// Make a valid, random ring
|
||||||
@@ -539,7 +586,7 @@ func TestFuzzRing(t *testing.T) {
|
|||||||
sort.Sort(addressSlice(tokens))
|
sort.Sort(addressSlice(tokens))
|
||||||
|
|
||||||
peer := peers[rand.Intn(len(peers))]
|
peer := peers[rand.Intn(len(peers))]
|
||||||
ring := New(start, end, peer)
|
ring := NewRing(start, end, peer)
|
||||||
for _, token := range tokens {
|
for _, token := range tokens {
|
||||||
peer = peers[rand.Intn(len(peers))]
|
peer = peers[rand.Intn(len(peers))]
|
||||||
ring.Entries = append(ring.Entries, &entry{Token: start + token, Peer: peer})
|
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
|
// Merge them - this might fail, we don't care
|
||||||
// We just want to make sure it doesn't panic
|
// We just want to make sure it doesn't panic
|
||||||
ring1.Merge(*ring2)
|
merge(ring1, ring2)
|
||||||
|
|
||||||
// Check whats left still passes assertions
|
// Check whats left still passes assertions
|
||||||
ring1.assertInvariants()
|
ring1.assertInvariants()
|
||||||
@@ -573,7 +620,7 @@ func TestFuzzRing(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
peer := peers[rand.Intn(len(peers))]
|
peer := peers[rand.Intn(len(peers))]
|
||||||
ring := New(start, end, peer)
|
ring := NewRing(start, end, peer)
|
||||||
for _, token := range tokens {
|
for _, token := range tokens {
|
||||||
peer = peers[rand.Intn(len(peers))]
|
peer = peers[rand.Intn(len(peers))]
|
||||||
ring.Entries = append(ring.Entries, &entry{Token: start + token, Peer: peer})
|
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
|
// Merge them - this might fail, we don't care
|
||||||
// We just want to make sure it doesn't panic
|
// We just want to make sure it doesn't panic
|
||||||
ring1.Merge(*ring2)
|
merge(ring1, ring2)
|
||||||
|
|
||||||
// Check whats left still passes assertions
|
// Check whats left still passes assertions
|
||||||
ring1.assertInvariants()
|
ring1.assertInvariants()
|
||||||
@@ -601,17 +648,17 @@ func TestFuzzRingHard(t *testing.T) {
|
|||||||
var (
|
var (
|
||||||
numPeers = 100
|
numPeers = 100
|
||||||
iterations = 3000
|
iterations = 3000
|
||||||
peers []router.PeerName
|
peers []mesh.PeerName
|
||||||
rings []*Ring
|
rings []*Ring
|
||||||
nextPeerID = 0
|
nextPeerID = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
addPeer := func() {
|
addPeer := func() {
|
||||||
peer, _ := router.PeerNameFromString(fmt.Sprintf("%02d:%02d:00:00:00:00", nextPeerID/10, nextPeerID%10))
|
peer := makePeerName(nextPeerID)
|
||||||
common.Log.Debugf("%s: Adding peer", peer)
|
common.Log.Debugf("%s: Adding peer", peer)
|
||||||
nextPeerID++
|
nextPeerID++
|
||||||
peers = append(peers, peer)
|
peers = append(peers, peer)
|
||||||
rings = append(rings, New(start, end, peer))
|
rings = append(rings, NewRing(start, end, peer))
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < numPeers; i++ {
|
for i := 0; i < numPeers; i++ {
|
||||||
@@ -620,7 +667,7 @@ func TestFuzzRingHard(t *testing.T) {
|
|||||||
|
|
||||||
rings[0].ClaimItAll()
|
rings[0].ClaimItAll()
|
||||||
|
|
||||||
randomPeer := func(exclude int) (int, router.PeerName, *Ring) {
|
randomPeer := func(exclude int) (int, mesh.PeerName, *Ring) {
|
||||||
var peerIndex int
|
var peerIndex int
|
||||||
if exclude >= 0 {
|
if exclude >= 0 {
|
||||||
peerIndex = rand.Intn(len(peers) - 1)
|
peerIndex = rand.Intn(len(peers) - 1)
|
||||||
@@ -650,7 +697,7 @@ func TestFuzzRingHard(t *testing.T) {
|
|||||||
|
|
||||||
// We need to be in a ~converged ring to rmpeer
|
// We need to be in a ~converged ring to rmpeer
|
||||||
for _, ring := range rings {
|
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)
|
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
|
// And now tell everyone about the transfer - rmpeer is
|
||||||
// not partition safe
|
// not partition safe
|
||||||
for i, ring := range rings {
|
for i, ring := range rings {
|
||||||
require.NoError(t, ring.Merge(*otherRing))
|
require.NoError(t, merge(ring, otherRing))
|
||||||
theRanges[i] = ring.OwnedRanges()
|
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)
|
// Now 'gossip' this to a random host (note, note could be same host as above)
|
||||||
otherIndex, _, otherRing := randomPeer(-1)
|
otherIndex, _, otherRing := randomPeer(-1)
|
||||||
common.Log.Debugf("%s: 'Gossiping' to %s", ring.Peer, otherRing.Peer)
|
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[indexWithRanges] = ring.OwnedRanges()
|
||||||
theRanges[otherIndex] = otherRing.OwnedRanges()
|
theRanges[otherIndex] = otherRing.OwnedRanges()
|
||||||
@@ -712,7 +759,7 @@ func TestFuzzRingHard(t *testing.T) {
|
|||||||
ring1 := ringsWithEntries[rand.Intn(len(ringsWithEntries))]
|
ring1 := ringsWithEntries[rand.Intn(len(ringsWithEntries))]
|
||||||
ring2index, _, ring2 := randomPeer(-1)
|
ring2index, _, ring2 := randomPeer(-1)
|
||||||
common.Log.Debugf("%s: 'Gossiping' to %s", ring1.Peer, ring2.Peer)
|
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()
|
theRanges[ring2index] = ring2.OwnedRanges()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -729,7 +776,7 @@ func TestFuzzRingHard(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Ring) ClaimItAll() {
|
func (r *Ring) ClaimItAll() {
|
||||||
r.ClaimForPeers([]router.PeerName{r.Peer})
|
r.ClaimForPeers([]mesh.PeerName{r.Peer})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es entries) String() string {
|
func (es entries) String() string {
|
||||||
|
|||||||
+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.
|
// Clear removes all spaces from this space set. Used during node shutdown.
|
||||||
func (s *Space) Clear() {
|
func (s *Space) Clear() {
|
||||||
s.free = s.free[:0]
|
|
||||||
s.ours = s.ours[:0]
|
s.ours = s.ours[:0]
|
||||||
|
s.free = s.free[:0]
|
||||||
}
|
}
|
||||||
|
|
||||||
// Walk down the free list calling f() on the in-range portions, until
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Space) NumFreeAddressesInRange(r address.Range) address.Offset {
|
func (s *Space) NumOwnedAddresses() address.Count {
|
||||||
res := address.Offset(0)
|
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 {
|
s.walkFree(r, func(chunk address.Range) bool {
|
||||||
res += chunk.Size()
|
res += chunk.Size()
|
||||||
return false
|
return false
|
||||||
@@ -111,11 +127,14 @@ func (s *Space) Free(addr address.Address) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *Space) biggestFreeRange(r address.Range) (biggest address.Range) {
|
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 {
|
s.walkFree(r, func(chunk address.Range) bool {
|
||||||
if size := chunk.Size(); size >= biggestSize {
|
if size := chunk.Size(); size >= biggestSize {
|
||||||
biggest = chunk
|
chunk = chunk.BiggestCIDRRange()
|
||||||
biggestSize = size
|
if size = chunk.Size(); size >= biggestSize {
|
||||||
|
biggest = chunk
|
||||||
|
biggestSize = size
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
@@ -129,9 +148,12 @@ func (s *Space) Donate(r address.Range) (address.Range, bool) {
|
|||||||
return address.Range{}, false
|
return address.Range{}, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// Donate half of that biggest free range. Note size/2 rounds down, so
|
// Donate no more than half what we have available
|
||||||
// the resulting donation size rounds up, and in particular can't be empty.
|
if biggest.Size() > s.NumFreeAddressesInRange(r)/2 {
|
||||||
biggest.Start = address.Add(biggest.Start, biggest.Size()/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.ours = subtract(s.ours, biggest.Start, biggest.End)
|
||||||
s.free = subtract(s.free, 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
|
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) {
|
func TestLowlevel(t *testing.T) {
|
||||||
a := []address.Address{}
|
a := []address.Address{}
|
||||||
a = add(a, 100, 200)
|
a = add(a, 100, 200)
|
||||||
@@ -61,24 +51,24 @@ func TestLowlevel(t *testing.T) {
|
|||||||
require.Equal(t, []address.Address{}, a)
|
require.Equal(t, []address.Address{}, a)
|
||||||
|
|
||||||
s := New()
|
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))
|
ok, got := s.Allocate(address.NewRange(0, 1000))
|
||||||
require.False(t, ok, "allocate in empty space should fail")
|
require.False(t, ok, "allocate in empty space should fail")
|
||||||
|
|
||||||
s.Add(100, 100)
|
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))
|
ok, got = s.Allocate(address.NewRange(0, 1000))
|
||||||
require.True(t, ok && got == 100, "allocate")
|
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.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.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(0), "free not allocated")
|
||||||
wt.AssertErrorInterface(t, (*error)(nil), s.Free(100), "double free")
|
wt.AssertErrorInterface(t, (*error)(nil), s.Free(100), "double free")
|
||||||
|
|
||||||
r, ok := s.Donate(address.NewRange(0, 1000))
|
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
|
// test Donate when addresses are scarce
|
||||||
s = New()
|
s = New()
|
||||||
@@ -88,7 +78,7 @@ func TestLowlevel(t *testing.T) {
|
|||||||
require.NoError(t, s.Claim(0))
|
require.NoError(t, s.Claim(0))
|
||||||
require.NoError(t, s.Claim(2))
|
require.NoError(t, s.Claim(2))
|
||||||
r, ok = s.Donate(address.NewRange(0, 1000))
|
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))
|
r, ok = s.Donate(address.NewRange(0, 1000))
|
||||||
require.True(t, !ok, "donate should fail")
|
require.True(t, !ok, "donate should fail")
|
||||||
}
|
}
|
||||||
@@ -107,19 +97,22 @@ func TestSpaceAllocate(t *testing.T) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
space1 := makeSpace(start, size)
|
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()
|
space1.assertInvariants()
|
||||||
|
|
||||||
_, addr1 := space1.Allocate(address.NewRange(start, size))
|
_, addr1 := space1.Allocate(address.NewRange(start, size))
|
||||||
require.Equal(t, testAddr1, addr1.String(), "address")
|
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()
|
space1.assertInvariants()
|
||||||
|
|
||||||
_, addr2 := space1.Allocate(address.NewRange(start, size))
|
_, addr2 := space1.Allocate(address.NewRange(start, size))
|
||||||
require.False(t, addr2.String() == testAddr1, "address")
|
require.False(t, addr2.String() == testAddr1, "address")
|
||||||
require.Equal(t, address.Offset(18), space1.NumFreeAddresses())
|
require.Equal(t, address.Count(18), space1.NumFreeAddresses())
|
||||||
require.Equal(t, address.Offset(13), space1.NumFreeAddressesInRange(address.Range{Start: ip(testAddr1), End: ip(testAddrx)}))
|
require.Equal(t, address.Count(2), space1.NumOwnedAddresses())
|
||||||
require.Equal(t, address.Offset(18), space1.NumFreeAddressesInRange(address.Range{Start: ip(testAddr1), End: ip(testAddry)}))
|
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.assertInvariants()
|
||||||
|
|
||||||
space1.Free(addr2)
|
space1.Free(addr2)
|
||||||
@@ -134,20 +127,21 @@ func TestSpaceAllocate(t *testing.T) {
|
|||||||
|
|
||||||
func TestSpaceFree(t *testing.T) {
|
func TestSpaceFree(t *testing.T) {
|
||||||
const (
|
const (
|
||||||
testAddr1 = "10.0.3.4"
|
testAddr1 = "10.0.3.16"
|
||||||
testAddrx = "10.0.3.19"
|
testAddrx = "10.0.3.19"
|
||||||
testAddry = "10.0.9.19"
|
testAddry = "10.0.9.19"
|
||||||
containerID = "deadbeef"
|
containerID = "deadbeef"
|
||||||
|
size = 16
|
||||||
)
|
)
|
||||||
|
|
||||||
entireRange := address.NewRange(ip(testAddr1), 20)
|
entireRange := address.NewRange(ip(testAddr1), size)
|
||||||
space := makeSpace(ip(testAddr1), 20)
|
space := makeSpace(ip(testAddr1), size)
|
||||||
|
|
||||||
// Check we are prepared to give up the entire space
|
// Check we are prepared to give up the entire space
|
||||||
r := space.biggestFreeRange(entireRange)
|
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)
|
ok, _ := space.Allocate(entireRange)
|
||||||
require.True(t, ok, "Failed to get address")
|
require.True(t, ok, "Failed to get address")
|
||||||
}
|
}
|
||||||
@@ -155,48 +149,48 @@ func TestSpaceFree(t *testing.T) {
|
|||||||
// Check we are full
|
// Check we are full
|
||||||
ok, _ := space.Allocate(entireRange)
|
ok, _ := space.Allocate(entireRange)
|
||||||
require.True(t, !ok, "Should have failed to get address")
|
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")
|
require.True(t, r.Size() == 0, "Wrong space")
|
||||||
|
|
||||||
// Free in the middle
|
// 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")))
|
require.NoError(t, space.Free(ip("10.0.3.23")))
|
||||||
r = space.biggestFreeRange(entireRange)
|
r = space.biggestFreeRange(entireRange)
|
||||||
require.True(t, r.Start == ip("10.0.3.23") && r.Size() == 1, "Wrong space")
|
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
|
// 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.22")))
|
||||||
require.NoError(t, space.Free(ip("10.0.3.21")))
|
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)
|
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.Equal(t, []address.Range{{Start: ip("10.0.3.16"), End: ip("10.0.3.32")}}, space.OwnedRanges())
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDonateSimple(t *testing.T) {
|
func TestDonateSimple(t *testing.T) {
|
||||||
const (
|
const (
|
||||||
testAddr1 = "10.0.1.0"
|
testAddr1 = "10.0.1.0"
|
||||||
testAddr2 = "10.0.1.32"
|
testAddr2 = "10.0.1.32"
|
||||||
size = 48
|
size = 64
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -209,9 +203,9 @@ func TestDonateSimple(t *testing.T) {
|
|||||||
r, ok := ps1.Donate(address.NewRange(ip(testAddr1), size))
|
r, ok := ps1.Donate(address.NewRange(ip(testAddr1), size))
|
||||||
numGivenUp := r.Size()
|
numGivenUp := r.Size()
|
||||||
require.True(t, ok, "Donate result")
|
require.True(t, ok, "Donate result")
|
||||||
require.Equal(t, "10.0.1.24", r.Start.String(), "Invalid start")
|
require.Equal(t, "10.0.1.32", r.Start.String(), "Invalid start")
|
||||||
require.Equal(t, address.Offset(size/2), numGivenUp)
|
require.Equal(t, address.Count(size/2), numGivenUp)
|
||||||
require.Equal(t, address.Offset(size/2), ps1.NumFreeAddresses())
|
require.Equal(t, address.Count(size/2), ps1.NumFreeAddresses())
|
||||||
|
|
||||||
// Now check we can give the rest up.
|
// Now check we can give the rest up.
|
||||||
count := 0 // count to avoid infinite loop
|
count := 0 // count to avoid infinite loop
|
||||||
@@ -222,8 +216,8 @@ func TestDonateSimple(t *testing.T) {
|
|||||||
}
|
}
|
||||||
numGivenUp += r.Size()
|
numGivenUp += r.Size()
|
||||||
}
|
}
|
||||||
require.Equal(t, address.Offset(0), ps1.NumFreeAddresses())
|
require.Equal(t, address.Count(0), ps1.NumFreeAddresses())
|
||||||
require.Equal(t, address.Offset(size), numGivenUp)
|
require.Equal(t, address.Count(size), numGivenUp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDonateHard(t *testing.T) {
|
func TestDonateHard(t *testing.T) {
|
||||||
@@ -240,7 +234,7 @@ func TestDonateHard(t *testing.T) {
|
|||||||
require.True(t, ok, "Failed to get IP!")
|
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
|
// Now free all but the last address
|
||||||
// this will force us to split the free list
|
// this will force us to split the free list
|
||||||
@@ -251,13 +245,13 @@ func TestDonateHard(t *testing.T) {
|
|||||||
// Now split
|
// Now split
|
||||||
newRange, ok := spaceset.Donate(address.NewRange(start, size))
|
newRange, ok := spaceset.Donate(address.NewRange(start, size))
|
||||||
require.True(t, ok, "GiveUpSpace result")
|
require.True(t, ok, "GiveUpSpace result")
|
||||||
require.Equal(t, ip("10.0.1.23"), newRange.Start)
|
require.Equal(t, address.NewRange(ip("10.0.1.16"), 16), newRange, "Wrong space")
|
||||||
require.Equal(t, address.Offset(24), newRange.Size())
|
require.Equal(t, address.Count(31), spaceset.NumFreeAddresses())
|
||||||
require.Equal(t, address.Offset(23), spaceset.NumFreeAddresses())
|
|
||||||
|
|
||||||
//Space set should now have 2 spaces
|
//Space set should now have 3 spaces
|
||||||
expected := New()
|
expected := &Space{
|
||||||
expected.Add(start, 23)
|
ours: add(nil, ip("10.0.1.47"), ip("10.0.1.48")),
|
||||||
expected.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)
|
require.Equal(t, expected, spaceset)
|
||||||
}
|
}
|
||||||
+32
-11
@@ -1,6 +1,7 @@
|
|||||||
package ipam
|
package ipam
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"github.com/weaveworks/weave/ipam/paxos"
|
"github.com/weaveworks/weave/ipam/paxos"
|
||||||
"github.com/weaveworks/weave/net/address"
|
"github.com/weaveworks/weave/net/address"
|
||||||
)
|
)
|
||||||
@@ -8,6 +9,8 @@ import (
|
|||||||
type Status struct {
|
type Status struct {
|
||||||
Paxos *paxos.Status
|
Paxos *paxos.Status
|
||||||
Range string
|
Range string
|
||||||
|
RangeNumIPs int
|
||||||
|
ActiveIPs int
|
||||||
DefaultSubnet string
|
DefaultSubnet string
|
||||||
Entries []EntryStatus
|
Entries []EntryStatus
|
||||||
PendingClaims []ClaimStatus
|
PendingClaims []ClaimStatus
|
||||||
@@ -15,14 +18,17 @@ type Status struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type EntryStatus struct {
|
type EntryStatus struct {
|
||||||
Token string
|
Token string
|
||||||
Peer string
|
Size uint32
|
||||||
Version uint32
|
Peer string
|
||||||
|
Nickname string
|
||||||
|
IsKnownPeer bool
|
||||||
|
Version uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
type ClaimStatus struct {
|
type ClaimStatus struct {
|
||||||
Ident string
|
Ident string
|
||||||
Address address.Address
|
CIDR address.CIDR
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewStatus(allocator *Allocator, defaultSubnet address.CIDR) *Status {
|
func NewStatus(allocator *Allocator, defaultSubnet address.CIDR) *Status {
|
||||||
@@ -31,8 +37,14 @@ func NewStatus(allocator *Allocator, defaultSubnet address.CIDR) *Status {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var paxosStatus *paxos.Status
|
var paxosStatus *paxos.Status
|
||||||
if allocator.paxosTicker != nil {
|
if allocator.awaitingConsensus && allocator.paxos != nil {
|
||||||
paxosStatus = paxos.NewStatus(allocator.paxos)
|
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)
|
resultChan := make(chan *Status)
|
||||||
@@ -40,6 +52,8 @@ func NewStatus(allocator *Allocator, defaultSubnet address.CIDR) *Status {
|
|||||||
resultChan <- &Status{
|
resultChan <- &Status{
|
||||||
paxosStatus,
|
paxosStatus,
|
||||||
allocator.universe.String(),
|
allocator.universe.String(),
|
||||||
|
int(allocator.universe.Size()),
|
||||||
|
int(allocator.space.NumOwnedAddresses()),
|
||||||
defaultSubnet.String(),
|
defaultSubnet.String(),
|
||||||
newEntryStatusSlice(allocator),
|
newEntryStatusSlice(allocator),
|
||||||
newClaimStatusSlice(allocator),
|
newClaimStatusSlice(allocator),
|
||||||
@@ -56,8 +70,15 @@ func newEntryStatusSlice(allocator *Allocator) []EntryStatus {
|
|||||||
return slice
|
return slice
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, entry := range allocator.ring.Entries {
|
for _, r := range allocator.ring.AllRangeInfo() {
|
||||||
slice = append(slice, EntryStatus{entry.Token.String(), entry.Peer.String(), entry.Version})
|
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
|
return slice
|
||||||
@@ -67,7 +88,7 @@ func newClaimStatusSlice(allocator *Allocator) []ClaimStatus {
|
|||||||
var slice []ClaimStatus
|
var slice []ClaimStatus
|
||||||
for _, op := range allocator.pendingClaims {
|
for _, op := range allocator.pendingClaims {
|
||||||
claim := op.(*claim)
|
claim := op.(*claim)
|
||||||
slice = append(slice, ClaimStatus{claim.ident, claim.addr})
|
slice = append(slice, ClaimStatus{claim.ident, claim.cidr})
|
||||||
}
|
}
|
||||||
return slice
|
return slice
|
||||||
}
|
}
|
||||||
@@ -76,7 +97,7 @@ func newAllocateIdentSlice(allocator *Allocator) []string {
|
|||||||
var slice []string
|
var slice []string
|
||||||
for _, op := range allocator.pendingAllocates {
|
for _, op := range allocator.pendingAllocates {
|
||||||
allocate := op.(*allocate)
|
allocate := op.(*allocate)
|
||||||
slice = append(slice, allocate.ident)
|
slice = append(slice, fmt.Sprintf("%s %s", allocate.ident, allocate.r.String()))
|
||||||
}
|
}
|
||||||
return slice
|
return slice
|
||||||
}
|
}
|
||||||
+45
-23
@@ -7,14 +7,15 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/weaveworks/mesh"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
"github.com/weaveworks/weave/net/address"
|
"github.com/weaveworks/weave/net/address"
|
||||||
"github.com/weaveworks/weave/router"
|
|
||||||
"github.com/weaveworks/weave/testing/gossip"
|
"github.com/weaveworks/weave/testing/gossip"
|
||||||
)
|
)
|
||||||
|
|
||||||
type mockMessage struct {
|
type mockMessage struct {
|
||||||
dst router.PeerName
|
dst mesh.PeerName
|
||||||
msgType byte
|
msgType byte
|
||||||
buf []byte
|
buf []byte
|
||||||
}
|
}
|
||||||
@@ -48,13 +49,13 @@ func (m *mockGossipComms) String() string {
|
|||||||
// that the contents of messages are never re-ordered. Which, for instance,
|
// that the contents of messages are never re-ordered. Which, for instance,
|
||||||
// requires they are not based off iterating through a map.
|
// requires they are not based off iterating through a map.
|
||||||
|
|
||||||
func (m *mockGossipComms) GossipBroadcast(update router.GossipData) error {
|
func (m *mockGossipComms) GossipBroadcast(update mesh.GossipData) {
|
||||||
m.Lock()
|
m.Lock()
|
||||||
defer m.Unlock()
|
defer m.Unlock()
|
||||||
buf := []byte{}
|
buf := []byte{}
|
||||||
if len(m.messages) == 0 {
|
if len(m.messages) == 0 {
|
||||||
require.FailNow(m, fmt.Sprintf("%s: Gossip broadcast message unexpected: \n%x", m.name, buf))
|
require.FailNow(m, fmt.Sprintf("%s: Gossip broadcast message unexpected: \n%x", m.name, buf))
|
||||||
} else if msg := m.messages[0]; msg.dst != router.UnknownPeerName {
|
} else if msg := m.messages[0]; msg.dst != mesh.UnknownPeerName {
|
||||||
require.FailNow(m, fmt.Sprintf("%s: Expected Gossip message to %s but got broadcast", m.name, msg.dst))
|
require.FailNow(m, fmt.Sprintf("%s: Expected Gossip message to %s but got broadcast", m.name, msg.dst))
|
||||||
} else if msg.buf != nil && !equalByteBuffer(msg.buf, buf) {
|
} else if msg.buf != nil && !equalByteBuffer(msg.buf, buf) {
|
||||||
require.FailNow(m, fmt.Sprintf("%s: Gossip message not sent as expected: \nwant: %x\ngot : %x", m.name, msg.buf, buf))
|
require.FailNow(m, fmt.Sprintf("%s: Gossip message not sent as expected: \nwant: %x\ngot : %x", m.name, msg.buf, buf))
|
||||||
@@ -62,7 +63,6 @@ func (m *mockGossipComms) GossipBroadcast(update router.GossipData) error {
|
|||||||
// Swallow this message
|
// Swallow this message
|
||||||
m.messages = m.messages[1:]
|
m.messages = m.messages[1:]
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func equalByteBuffer(a, b []byte) bool {
|
func equalByteBuffer(a, b []byte) bool {
|
||||||
@@ -77,12 +77,12 @@ func equalByteBuffer(a, b []byte) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockGossipComms) GossipUnicast(dstPeerName router.PeerName, buf []byte) error {
|
func (m *mockGossipComms) GossipUnicast(dstPeerName mesh.PeerName, buf []byte) error {
|
||||||
m.Lock()
|
m.Lock()
|
||||||
defer m.Unlock()
|
defer m.Unlock()
|
||||||
if len(m.messages) == 0 {
|
if len(m.messages) == 0 {
|
||||||
require.FailNow(m, fmt.Sprintf("%s: Gossip message to %s unexpected: \n%s", m.name, dstPeerName, buf))
|
require.FailNow(m, fmt.Sprintf("%s: Gossip message to %s unexpected: \n%s", m.name, dstPeerName, buf))
|
||||||
} else if msg := m.messages[0]; msg.dst == router.UnknownPeerName {
|
} else if msg := m.messages[0]; msg.dst == mesh.UnknownPeerName {
|
||||||
require.FailNow(m, fmt.Sprintf("%s: Expected Gossip broadcast message but got dest %s", m.name, dstPeerName))
|
require.FailNow(m, fmt.Sprintf("%s: Expected Gossip broadcast message but got dest %s", m.name, dstPeerName))
|
||||||
} else if msg.dst != dstPeerName {
|
} else if msg.dst != dstPeerName {
|
||||||
require.FailNow(m, fmt.Sprintf("%s: Expected Gossip message to %s but got dest %s", m.name, msg.dst, dstPeerName))
|
require.FailNow(m, fmt.Sprintf("%s: Expected Gossip message to %s but got dest %s", m.name, msg.dst, dstPeerName))
|
||||||
@@ -99,7 +99,7 @@ func (m *mockGossipComms) GossipUnicast(dstPeerName router.PeerName, buf []byte)
|
|||||||
|
|
||||||
func ExpectMessage(alloc *Allocator, dst string, msgType byte, buf []byte) {
|
func ExpectMessage(alloc *Allocator, dst string, msgType byte, buf []byte) {
|
||||||
m := alloc.gossip.(*mockGossipComms)
|
m := alloc.gossip.(*mockGossipComms)
|
||||||
dstPeerName, _ := router.PeerNameFromString(dst)
|
dstPeerName, _ := mesh.PeerNameFromString(dst)
|
||||||
m.Lock()
|
m.Lock()
|
||||||
m.messages = append(m.messages, mockMessage{dstPeerName, msgType, buf})
|
m.messages = append(m.messages, mockMessage{dstPeerName, msgType, buf})
|
||||||
m.Unlock()
|
m.Unlock()
|
||||||
@@ -108,7 +108,7 @@ func ExpectMessage(alloc *Allocator, dst string, msgType byte, buf []byte) {
|
|||||||
func ExpectBroadcastMessage(alloc *Allocator, buf []byte) {
|
func ExpectBroadcastMessage(alloc *Allocator, buf []byte) {
|
||||||
m := alloc.gossip.(*mockGossipComms)
|
m := alloc.gossip.(*mockGossipComms)
|
||||||
m.Lock()
|
m.Lock()
|
||||||
m.messages = append(m.messages, mockMessage{router.UnknownPeerName, 0, buf})
|
m.messages = append(m.messages, mockMessage{mesh.UnknownPeerName, 0, buf})
|
||||||
m.Unlock()
|
m.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,24 +123,35 @@ func CheckAllExpectedMessagesSent(allocs ...*Allocator) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeAllocator(name string, cidrStr string, quorum uint) (*Allocator, address.Range) {
|
type mockDB struct{}
|
||||||
peername, err := router.PeerNameFromString(name)
|
|
||||||
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, cidr, err := address.ParseCIDR(cidrStr)
|
cidr, err := address.ParseCIDR(cidrStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
alloc := NewAllocator(peername, router.PeerUID(rand.Int63()),
|
return NewAllocator(Config{
|
||||||
"nick-"+name, cidr.Range(), quorum)
|
OurName: peername,
|
||||||
|
OurUID: mesh.PeerUID(rand.Int63()),
|
||||||
return alloc, cidr.HostRange()
|
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)
|
alloc, subnet := makeAllocator(name, universeCIDR, quorum)
|
||||||
gossip := &mockGossipComms{T: t, name: name}
|
gossip := &mockGossipComms{T: t, name: name}
|
||||||
alloc.SetInterfaces(gossip)
|
alloc.SetInterfaces(gossip)
|
||||||
@@ -149,7 +160,7 @@ func makeAllocatorWithMockGossip(t *testing.T, name string, universeCIDR string,
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (alloc *Allocator) claimRingForTesting(allocs ...*Allocator) {
|
func (alloc *Allocator) claimRingForTesting(allocs ...*Allocator) {
|
||||||
peers := []router.PeerName{alloc.ourName}
|
peers := []mesh.PeerName{alloc.ourName}
|
||||||
for _, alloc2 := range allocs {
|
for _, alloc2 := range allocs {
|
||||||
peers = append(peers, alloc2.ourName)
|
peers = append(peers, alloc2.ourName)
|
||||||
}
|
}
|
||||||
@@ -157,14 +168,22 @@ func (alloc *Allocator) claimRingForTesting(allocs ...*Allocator) {
|
|||||||
alloc.space.AddRanges(alloc.ring.OwnedRanges())
|
alloc.space.AddRanges(alloc.ring.OwnedRanges())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (alloc *Allocator) NumFreeAddresses(r address.Range) address.Offset {
|
func (alloc *Allocator) NumFreeAddresses(r address.Range) address.Count {
|
||||||
resultChan := make(chan address.Offset)
|
resultChan := make(chan address.Count)
|
||||||
alloc.actionChan <- func() {
|
alloc.actionChan <- func() {
|
||||||
resultChan <- alloc.space.NumFreeAddressesInRange(r)
|
resultChan <- alloc.space.NumFreeAddressesInRange(r)
|
||||||
}
|
}
|
||||||
return <-resultChan
|
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
|
// Check whether or not something was sent on a channel
|
||||||
func AssertSent(t *testing.T, ch <-chan bool) {
|
func AssertSent(t *testing.T, ch <-chan bool) {
|
||||||
timeout := time.After(10 * time.Second)
|
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)
|
gossipRouter := gossip.NewTestRouter(0.0)
|
||||||
allocs := make([]*Allocator, size)
|
allocs := make([]*Allocator, size)
|
||||||
var subnet address.Range
|
var subnet address.CIDR
|
||||||
|
|
||||||
for i := 0; i < size; i++ {
|
for i := 0; i < size; i++ {
|
||||||
var alloc *Allocator
|
var alloc *Allocator
|
||||||
@@ -213,7 +232,10 @@ func makeNetworkOfAllocators(size int, cidr string) ([]*Allocator, *gossip.TestR
|
|||||||
return allocs, gossipRouter, subnet
|
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 {
|
for _, alloc := range allocs {
|
||||||
alloc.Stop()
|
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
|
||||||
|
}
|
||||||
+77
-24
@@ -5,8 +5,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
@@ -17,7 +19,6 @@ import (
|
|||||||
const (
|
const (
|
||||||
topDomain = "."
|
topDomain = "."
|
||||||
reverseDNSdomain = "in-addr.arpa."
|
reverseDNSdomain = "in-addr.arpa."
|
||||||
etcResolvConf = "/etc/resolv.conf"
|
|
||||||
udpBuffSize = uint16(4096)
|
udpBuffSize = uint16(4096)
|
||||||
minUDPSize = 512
|
minUDPSize = 512
|
||||||
|
|
||||||
@@ -26,16 +27,52 @@ const (
|
|||||||
DefaultClientTimeout = 5 * time.Second
|
DefaultClientTimeout = 5 * time.Second
|
||||||
)
|
)
|
||||||
|
|
||||||
type DNSServer struct {
|
type Upstream interface {
|
||||||
ns *Nameserver
|
Config() (*dns.ClientConfig, error)
|
||||||
domain string
|
}
|
||||||
ttl uint32
|
|
||||||
address string
|
|
||||||
|
|
||||||
servers []*dns.Server
|
func NewUpstream(resolvConf, filterAddress string) Upstream {
|
||||||
upstream *dns.ClientConfig
|
return &upstream{
|
||||||
tcpClient *dns.Client
|
resolvConf: resolvConf,
|
||||||
udpClient *dns.Client
|
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 {
|
func filter(ss []string, s string) []string {
|
||||||
@@ -49,24 +86,30 @@ func filter(ss []string, s string) []string {
|
|||||||
return ss
|
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{
|
s := &DNSServer{
|
||||||
ns: ns,
|
ns: ns,
|
||||||
domain: dns.Fqdn(domain),
|
domain: dns.Fqdn(domain),
|
||||||
ttl: ttl,
|
ttl: ttl,
|
||||||
address: address,
|
address: address,
|
||||||
|
upstream: upstream,
|
||||||
tcpClient: &dns.Client{Net: "tcp", ReadTimeout: clientTimeout},
|
tcpClient: &dns.Client{Net: "tcp", ReadTimeout: clientTimeout},
|
||||||
udpClient: &dns.Client{Net: "udp", ReadTimeout: clientTimeout, UDPSize: udpBuffSize},
|
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
|
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) {
|
func (h *handler) handleLocal(w dns.ResponseWriter, req *dns.Msg) {
|
||||||
h.ns.debugf("local request: %+v", *req)
|
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)
|
h.nameError(w, req)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -149,6 +192,12 @@ func (h *handler) handleLocal(w dns.ResponseWriter, req *dns.Msg) {
|
|||||||
h.nameError(w, req)
|
h.nameError(w, req)
|
||||||
return
|
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{
|
header := dns.RR_Header{
|
||||||
Name: req.Question[0].Name,
|
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)
|
h.ns.debugf("recursive request: %+v", *req)
|
||||||
|
|
||||||
// Resolve unqualified names locally
|
// 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)
|
hostname := dns.Fqdn(req.Question[0].Name)
|
||||||
if strings.Count(hostname, ".") == 1 {
|
if strings.Count(hostname, ".") == 1 {
|
||||||
h.handleLocal(w, req)
|
h.handleLocal(w, req)
|
||||||
@@ -212,11 +261,15 @@ 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 := req.Copy()
|
||||||
reqCopy.Id = dns.Id()
|
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 || response == nil {
|
if (err != nil && err != dns.ErrTruncated) || response == nil {
|
||||||
h.ns.debugf("error trying %s: %v", server, err)
|
h.ns.debugf("error trying %s: %v", server, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|||||||
+18
-13
@@ -11,21 +11,26 @@ import (
|
|||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/weaveworks/mesh"
|
||||||
"github.com/weaveworks/weave/net/address"
|
"github.com/weaveworks/weave/net/address"
|
||||||
"github.com/weaveworks/weave/router"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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) {
|
func startServer(t *testing.T, upstream *dns.ClientConfig) (*DNSServer, *Nameserver, int, int) {
|
||||||
peername, err := router.PeerNameFromString("00:00:00:02:00:00")
|
peername, err := mesh.PeerNameFromString("00:00:00:02:00:00")
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
nameserver := New(peername, nil, nil, "")
|
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)
|
require.Nil(t, err)
|
||||||
udpPort := dnsserver.servers[0].PacketConn.LocalAddr().(*net.UDPAddr).Port
|
udpPort := dnsserver.servers[0].PacketConn.LocalAddr().(*net.UDPAddr).Port
|
||||||
tcpPort := dnsserver.servers[1].Listener.Addr().(*net.TCPAddr).Port
|
tcpPort := dnsserver.servers[1].Listener.Addr().(*net.TCPAddr).Port
|
||||||
if upstream != nil {
|
|
||||||
dnsserver.upstream = upstream
|
|
||||||
}
|
|
||||||
go dnsserver.ActivateAndServe()
|
go dnsserver.ActivateAndServe()
|
||||||
return dnsserver, nameserver, udpPort, tcpPort
|
return dnsserver, nameserver, udpPort, tcpPort
|
||||||
}
|
}
|
||||||
@@ -39,20 +44,20 @@ func TestTruncation(t *testing.T) {
|
|||||||
addrs := []address.Address{}
|
addrs := []address.Address{}
|
||||||
for i := address.Address(0); i < 100; i++ {
|
for i := address.Address(0); i < 100; i++ {
|
||||||
addrs = append(addrs, i)
|
addrs = append(addrs, i)
|
||||||
nameserver.AddEntry("foo.weave.local.", "", router.UnknownPeerName, i)
|
nameserver.AddEntry("foo.weave.local.", "", mesh.UnknownPeerName, i)
|
||||||
}
|
}
|
||||||
|
|
||||||
doRequest := func(client *dns.Client, request *dns.Msg, port int) *dns.Msg {
|
doRequest := func(client *dns.Client, request *dns.Msg, port int, expectedErr error) *dns.Msg {
|
||||||
request.SetQuestion("foo.weave.local.", dns.TypeA)
|
request.SetQuestion("foo.weave.local.", dns.TypeA)
|
||||||
response, _, err := client.Exchange(request, fmt.Sprintf("127.0.0.1:%d", port))
|
response, _, err := client.Exchange(request, fmt.Sprintf("127.0.0.1:%d", port))
|
||||||
require.Nil(t, err)
|
require.Equal(t, expectedErr, err)
|
||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
// do a udp query, ensure we get a truncated response
|
// do a udp query, ensure we get a truncated response
|
||||||
{
|
{
|
||||||
udpClient := dns.Client{Net: "udp", UDPSize: minUDPSize}
|
udpClient := dns.Client{Net: "udp", UDPSize: minUDPSize}
|
||||||
response := doRequest(&udpClient, &dns.Msg{}, udpPort)
|
response := doRequest(&udpClient, &dns.Msg{}, udpPort, dns.ErrTruncated)
|
||||||
require.True(t, response.MsgHdr.Truncated)
|
require.True(t, response.MsgHdr.Truncated)
|
||||||
require.True(t, len(response.Answer) < 100)
|
require.True(t, len(response.Answer) < 100)
|
||||||
}
|
}
|
||||||
@@ -62,7 +67,7 @@ func TestTruncation(t *testing.T) {
|
|||||||
udpClient := dns.Client{Net: "udp", UDPSize: 65535}
|
udpClient := dns.Client{Net: "udp", UDPSize: 65535}
|
||||||
request := &dns.Msg{}
|
request := &dns.Msg{}
|
||||||
request.SetEdns0(65535, false)
|
request.SetEdns0(65535, false)
|
||||||
response := doRequest(&udpClient, request, udpPort)
|
response := doRequest(&udpClient, request, udpPort, nil)
|
||||||
require.False(t, response.MsgHdr.Truncated)
|
require.False(t, response.MsgHdr.Truncated)
|
||||||
require.Equal(t, len(response.Answer), 100)
|
require.Equal(t, len(response.Answer), 100)
|
||||||
}
|
}
|
||||||
@@ -70,7 +75,7 @@ func TestTruncation(t *testing.T) {
|
|||||||
// do a tcp query, ensure we don't get a truncated response
|
// do a tcp query, ensure we don't get a truncated response
|
||||||
{
|
{
|
||||||
tcpClient := dns.Client{Net: "tcp"}
|
tcpClient := dns.Client{Net: "tcp"}
|
||||||
response := doRequest(&tcpClient, &dns.Msg{}, tcpPort)
|
response := doRequest(&tcpClient, &dns.Msg{}, tcpPort, nil)
|
||||||
require.False(t, response.MsgHdr.Truncated)
|
require.False(t, response.MsgHdr.Truncated)
|
||||||
require.Equal(t, len(response.Answer), 100)
|
require.Equal(t, len(response.Answer), 100)
|
||||||
}
|
}
|
||||||
|
|||||||
+136
-41
@@ -5,26 +5,47 @@ import (
|
|||||||
"encoding/gob"
|
"encoding/gob"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/weaveworks/mesh"
|
||||||
|
|
||||||
"github.com/weaveworks/weave/net/address"
|
"github.com/weaveworks/weave/net/address"
|
||||||
"github.com/weaveworks/weave/router"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var now = func() int64 { return time.Now().Unix() }
|
var now = func() int64 { return time.Now().Unix() }
|
||||||
|
|
||||||
type Entry struct {
|
type Entry struct {
|
||||||
ContainerID string
|
ContainerID string
|
||||||
Origin router.PeerName
|
Origin mesh.PeerName
|
||||||
Addr address.Address
|
Addr address.Address
|
||||||
Hostname string
|
Hostname string // as supplied
|
||||||
|
lHostname string // lowercased (not exported, so not encoded by gob)
|
||||||
Version int
|
Version int
|
||||||
Tombstone int64 // timestamp of when it was deleted
|
Tombstone int64 // timestamp of when it was deleted
|
||||||
}
|
}
|
||||||
|
|
||||||
type Entries []Entry
|
type Entries []Entry
|
||||||
|
type CaseSensitive Entries
|
||||||
|
type CaseInsensitive Entries
|
||||||
|
type SortableEntries interface {
|
||||||
|
sort.Interface
|
||||||
|
Get(i int) Entry
|
||||||
|
}
|
||||||
|
|
||||||
func (e1 *Entry) equal(e2 *Entry) bool {
|
// Gossip messages are sorted in a case sensitive order...
|
||||||
|
func (es CaseSensitive) Len() int { return len(es) }
|
||||||
|
func (es CaseSensitive) Swap(i, j int) { es[i], es[j] = es[j], es[i] }
|
||||||
|
func (es CaseSensitive) Get(i int) Entry { return es[i] }
|
||||||
|
func (es CaseSensitive) Less(i, j int) bool { return es[i].less(&es[j]) }
|
||||||
|
|
||||||
|
// ... but we store entries in a case insensitive order.
|
||||||
|
func (es CaseInsensitive) Len() int { return len(es) }
|
||||||
|
func (es CaseInsensitive) Swap(i, j int) { es[i], es[j] = es[j], es[i] }
|
||||||
|
func (es CaseInsensitive) Get(i int) Entry { return es[i] }
|
||||||
|
func (es CaseInsensitive) Less(i, j int) bool { return es[i].insensitiveLess(&es[j]) }
|
||||||
|
|
||||||
|
func (e1 Entry) equal(e2 Entry) bool {
|
||||||
return e1.ContainerID == e2.ContainerID &&
|
return e1.ContainerID == e2.ContainerID &&
|
||||||
e1.Origin == e2.Origin &&
|
e1.Origin == e2.Origin &&
|
||||||
e1.Addr == e2.Addr &&
|
e1.Addr == e2.Addr &&
|
||||||
@@ -48,6 +69,24 @@ func (e1 *Entry) less(e2 *Entry) bool {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e1 *Entry) insensitiveLess(e2 *Entry) bool {
|
||||||
|
// Entries are kept sorted by Hostname, Origin, ContainerID then address
|
||||||
|
e1Hostname, e2Hostname := e1.lHostname, e2.lHostname
|
||||||
|
switch {
|
||||||
|
case e1Hostname != e2Hostname:
|
||||||
|
return e1Hostname < e2Hostname
|
||||||
|
|
||||||
|
case e1.Origin != e2.Origin:
|
||||||
|
return e1.Origin < e2.Origin
|
||||||
|
|
||||||
|
case e1.ContainerID != e2.ContainerID:
|
||||||
|
return e1.ContainerID < e2.ContainerID
|
||||||
|
|
||||||
|
default:
|
||||||
|
return e1.Addr < e2.Addr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// returns true to indicate a change
|
// returns true to indicate a change
|
||||||
func (e1 *Entry) merge(e2 *Entry) bool {
|
func (e1 *Entry) merge(e2 *Entry) bool {
|
||||||
// we know container id, origin, add and hostname are equal
|
// we know container id, origin, add and hostname are equal
|
||||||
@@ -55,6 +94,9 @@ func (e1 *Entry) merge(e2 *Entry) bool {
|
|||||||
e1.Version = e2.Version
|
e1.Version = e2.Version
|
||||||
e1.Tombstone = e2.Tombstone
|
e1.Tombstone = e2.Tombstone
|
||||||
return true
|
return true
|
||||||
|
} else if e2.Version == e1.Version && e2.Tombstone > e1.Tombstone {
|
||||||
|
e1.Tombstone = e2.Tombstone
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@@ -63,37 +105,51 @@ func (e1 *Entry) String() string {
|
|||||||
return fmt.Sprintf("%s -> %s", e1.Hostname, e1.Addr.String())
|
return fmt.Sprintf("%s -> %s", e1.Hostname, e1.Addr.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es Entries) Len() int { return len(es) }
|
func (e1 *Entry) addLowercase() {
|
||||||
func (es Entries) Swap(i, j int) { panic("Swap") }
|
e1.lHostname = strings.ToLower(e1.Hostname)
|
||||||
func (es Entries) Less(i, j int) bool { return es[i].less(&es[j]) }
|
}
|
||||||
|
|
||||||
func (es Entries) check() error {
|
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) {
|
if !sort.IsSorted(es) {
|
||||||
return fmt.Errorf("Not sorted!")
|
return fmt.Errorf("Not sorted!")
|
||||||
}
|
}
|
||||||
for i := 1; i < len(es); i++ {
|
for i := 1; i < es.Len(); i++ {
|
||||||
if es[i].equal(&es[i-1]) {
|
if es.Get(i).equal(es.Get(i - 1)) {
|
||||||
return fmt.Errorf("Duplicate entry: %d:%v and %d:%v", i-1, es[i-1], i, es[i])
|
return fmt.Errorf("Duplicate entry: %d:%v and %d:%v", i-1, es.Get(i-1), i, es.Get(i))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *Entries) checkAndPanic() {
|
func checkAndPanic(es SortableEntries) {
|
||||||
if err := es.check(); err != nil {
|
if err := check(es); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *Entries) add(hostname, containerid string, origin router.PeerName, addr address.Address) Entry {
|
func (es *Entries) checkAndPanic() *Entries {
|
||||||
es.checkAndPanic()
|
checkAndPanic(CaseInsensitive(*es))
|
||||||
defer es.checkAndPanic()
|
return es
|
||||||
|
}
|
||||||
|
|
||||||
entry := Entry{Hostname: hostname, Origin: origin, ContainerID: containerid, Addr: addr}
|
func (es *Entries) add(hostname, containerid string, origin mesh.PeerName, addr address.Address) Entry {
|
||||||
|
defer es.checkAndPanic().checkAndPanic()
|
||||||
|
|
||||||
|
entry := Entry{Hostname: hostname, lHostname: strings.ToLower(hostname),
|
||||||
|
Origin: origin, ContainerID: containerid, Addr: addr}
|
||||||
i := sort.Search(len(*es), func(i int) bool {
|
i := sort.Search(len(*es), func(i int) bool {
|
||||||
return !(*es)[i].less(&entry)
|
return !(*es)[i].insensitiveLess(&entry)
|
||||||
})
|
})
|
||||||
if i < len(*es) && (*es)[i].equal(&entry) {
|
if i < len(*es) && (*es)[i].equal(entry) {
|
||||||
if (*es)[i].Tombstone > 0 {
|
if (*es)[i].Tombstone > 0 {
|
||||||
(*es)[i].Tombstone = 0
|
(*es)[i].Tombstone = 0
|
||||||
(*es)[i].Version++
|
(*es)[i].Version++
|
||||||
@@ -107,16 +163,17 @@ func (es *Entries) add(hostname, containerid string, origin router.PeerName, add
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (es *Entries) merge(incoming Entries) Entries {
|
func (es *Entries) merge(incoming Entries) Entries {
|
||||||
es.checkAndPanic()
|
defer es.checkAndPanic().checkAndPanic()
|
||||||
defer es.checkAndPanic()
|
incoming.checkAndPanic()
|
||||||
|
|
||||||
newEntries := Entries{}
|
newEntries := Entries{}
|
||||||
i := 0
|
i := 0
|
||||||
|
|
||||||
for _, entry := range incoming {
|
for _, entry := range incoming {
|
||||||
for i < len(*es) && (*es)[i].less(&entry) {
|
for i < len(*es) && (*es)[i].insensitiveLess(&entry) {
|
||||||
i++
|
i++
|
||||||
}
|
}
|
||||||
if i < len(*es) && (*es)[i].equal(&entry) {
|
if i < len(*es) && (*es)[i].equal(entry) {
|
||||||
if (*es)[i].merge(&entry) {
|
if (*es)[i].merge(&entry) {
|
||||||
newEntries = append(newEntries, entry)
|
newEntries = append(newEntries, entry)
|
||||||
}
|
}
|
||||||
@@ -132,14 +189,12 @@ func (es *Entries) merge(incoming Entries) Entries {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// f returning true means keep the entry.
|
// f returning true means keep the entry.
|
||||||
func (es *Entries) tombstone(ourname router.PeerName, f func(*Entry) bool) Entries {
|
func (es *Entries) tombstone(ourname mesh.PeerName, f func(*Entry) bool) Entries {
|
||||||
es.checkAndPanic()
|
defer es.checkAndPanic().checkAndPanic()
|
||||||
defer es.checkAndPanic()
|
|
||||||
tombstoned := Entries{}
|
tombstoned := Entries{}
|
||||||
for i, e := range *es {
|
for i, e := range *es {
|
||||||
if f(&e) && e.Origin == ourname {
|
if f(&e) && e.Origin == ourname && e.tombstone() {
|
||||||
e.Version++
|
|
||||||
e.Tombstone = now()
|
|
||||||
(*es)[i] = e
|
(*es)[i] = e
|
||||||
tombstoned = append(tombstoned, e)
|
tombstoned = append(tombstoned, e)
|
||||||
}
|
}
|
||||||
@@ -147,9 +202,10 @@ func (es *Entries) tombstone(ourname router.PeerName, f func(*Entry) bool) Entri
|
|||||||
return tombstoned
|
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) {
|
func (es *Entries) filter(f func(*Entry) bool) {
|
||||||
es.checkAndPanic()
|
defer es.checkAndPanic().checkAndPanic()
|
||||||
defer es.checkAndPanic()
|
|
||||||
i := 0
|
i := 0
|
||||||
for _, e := range *es {
|
for _, e := range *es {
|
||||||
if !f(&e) {
|
if !f(&e) {
|
||||||
@@ -161,25 +217,38 @@ func (es *Entries) filter(f func(*Entry) bool) {
|
|||||||
*es = (*es)[:i]
|
*es = (*es)[:i]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (es Entries) findEqual(e *Entry) (*Entry, bool) {
|
||||||
|
i := sort.Search(len(es), func(i int) bool {
|
||||||
|
return !es[i].insensitiveLess(e)
|
||||||
|
})
|
||||||
|
if i < len(es) && es[i].equal(*e) {
|
||||||
|
return &es[i], true
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
func (es Entries) lookup(hostname string) Entries {
|
func (es Entries) lookup(hostname string) Entries {
|
||||||
es.checkAndPanic()
|
es.checkAndPanic()
|
||||||
|
|
||||||
|
lowerHostname := strings.ToLower(hostname)
|
||||||
i := sort.Search(len(es), func(i int) bool {
|
i := sort.Search(len(es), func(i int) bool {
|
||||||
return es[i].Hostname >= hostname
|
return es[i].lHostname >= lowerHostname
|
||||||
})
|
})
|
||||||
if i >= len(es) || es[i].Hostname != hostname {
|
if i >= len(es) || es[i].lHostname != lowerHostname {
|
||||||
return Entries{}
|
return Entries{}
|
||||||
}
|
}
|
||||||
|
|
||||||
j := sort.Search(len(es)-i, func(j int) bool {
|
j := sort.Search(len(es)-i, func(j int) bool {
|
||||||
return es[i+j].Hostname > hostname
|
return es[i+j].lHostname > lowerHostname
|
||||||
})
|
})
|
||||||
|
|
||||||
return es[i : i+j]
|
return es[i : i+j]
|
||||||
}
|
}
|
||||||
|
|
||||||
func (es *Entries) first(f func(*Entry) bool) (*Entry, error) {
|
func (es Entries) first(f func(*Entry) bool) (*Entry, error) {
|
||||||
es.checkAndPanic()
|
es.checkAndPanic()
|
||||||
for _, e := range *es {
|
|
||||||
|
for _, e := range es {
|
||||||
if f(&e) {
|
if f(&e) {
|
||||||
return &e, nil
|
return &e, nil
|
||||||
}
|
}
|
||||||
@@ -187,23 +256,49 @@ func (es *Entries) first(f func(*Entry) bool) (*Entry, error) {
|
|||||||
return nil, fmt.Errorf("Not found")
|
return nil, fmt.Errorf("Not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (es Entries) addLowercase() {
|
||||||
|
for i := range es {
|
||||||
|
es[i].addLowercase()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type GossipData struct {
|
type GossipData struct {
|
||||||
Timestamp int64
|
Timestamp int64
|
||||||
Entries
|
Entries
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GossipData) Merge(o router.GossipData) {
|
func (g *GossipData) Merge(o mesh.GossipData) mesh.GossipData {
|
||||||
other := o.(*GossipData)
|
other := o.(*GossipData)
|
||||||
g.Entries.merge(other.Entries)
|
gossip := g.copy()
|
||||||
if g.Timestamp < other.Timestamp {
|
gossip.Entries.merge(other.Entries)
|
||||||
g.Timestamp = other.Timestamp
|
if gossip.Timestamp < other.Timestamp {
|
||||||
|
gossip.Timestamp = other.Timestamp
|
||||||
}
|
}
|
||||||
|
return gossip
|
||||||
|
}
|
||||||
|
|
||||||
|
func (g *GossipData) Decode(msg []byte) error {
|
||||||
|
if err := gob.NewDecoder(bytes.NewReader(msg)).Decode(g); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
g.Entries.addLowercase() // lowercase strings not sent on the wire
|
||||||
|
sort.Sort(CaseInsensitive(g.Entries))
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GossipData) Encode() [][]byte {
|
func (g *GossipData) Encode() [][]byte {
|
||||||
|
g2 := g.copy()
|
||||||
|
sort.Sort(CaseSensitive(g2.Entries))
|
||||||
buf := &bytes.Buffer{}
|
buf := &bytes.Buffer{}
|
||||||
if err := gob.NewEncoder(buf).Encode(g); err != nil {
|
if err := gob.NewEncoder(buf).Encode(g2); err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
return [][]byte{buf.Bytes()}
|
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
|
||||||
|
}
|
||||||
+70
-64
@@ -4,80 +4,71 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/weaveworks/mesh"
|
||||||
|
|
||||||
"github.com/weaveworks/weave/net/address"
|
"github.com/weaveworks/weave/net/address"
|
||||||
"github.com/weaveworks/weave/router"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func l(es Entries) Entries {
|
||||||
|
es.addLowercase()
|
||||||
|
return es
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeEntries(values string) Entries {
|
||||||
|
entries := make(Entries, len(values))
|
||||||
|
for i, c := range values {
|
||||||
|
entries[i] = Entry{Hostname: string(c)}
|
||||||
|
}
|
||||||
|
return l(entries)
|
||||||
|
}
|
||||||
|
|
||||||
func TestAdd(t *testing.T) {
|
func TestAdd(t *testing.T) {
|
||||||
oldNow := now
|
oldNow := now
|
||||||
defer func() { now = oldNow }()
|
defer func() { now = oldNow }()
|
||||||
now = func() int64 { return 1234 }
|
now = func() int64 { return 1234 }
|
||||||
|
|
||||||
entries := Entries{}
|
entries := Entries{}
|
||||||
entries.add("A", "", router.UnknownPeerName, address.Address(0))
|
entries.add("A", "", mesh.UnknownPeerName, address.Address(0))
|
||||||
expected := Entries{
|
expected := l(Entries{
|
||||||
Entry{Hostname: "A", Origin: router.UnknownPeerName, Addr: address.Address(0)},
|
Entry{Hostname: "A", Origin: mesh.UnknownPeerName, Addr: address.Address(0)},
|
||||||
}
|
})
|
||||||
require.Equal(t, entries, expected)
|
require.Equal(t, entries, expected)
|
||||||
|
|
||||||
entries.tombstone(router.UnknownPeerName, func(e *Entry) bool { return e.Hostname == "A" })
|
entries.tombstone(mesh.UnknownPeerName, func(e *Entry) bool { return e.Hostname == "A" })
|
||||||
expected = Entries{
|
expected = l(Entries{
|
||||||
Entry{Hostname: "A", Origin: router.UnknownPeerName, Addr: address.Address(0), Version: 1, Tombstone: 1234},
|
Entry{Hostname: "A", Origin: mesh.UnknownPeerName, Addr: address.Address(0), Version: 1, Tombstone: 1234},
|
||||||
}
|
})
|
||||||
require.Equal(t, entries, expected)
|
require.Equal(t, entries, expected)
|
||||||
|
|
||||||
entries.add("A", "", router.UnknownPeerName, address.Address(0))
|
entries.add("A", "", mesh.UnknownPeerName, address.Address(0))
|
||||||
expected = Entries{
|
expected = l(Entries{
|
||||||
Entry{Hostname: "A", Origin: router.UnknownPeerName, Addr: address.Address(0), Version: 2},
|
Entry{Hostname: "A", Origin: mesh.UnknownPeerName, Addr: address.Address(0), Version: 2},
|
||||||
}
|
})
|
||||||
require.Equal(t, entries, expected)
|
require.Equal(t, entries, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMerge(t *testing.T) {
|
func TestMerge(t *testing.T) {
|
||||||
e1 := Entries{
|
e1 := makeEntries("ACDF")
|
||||||
Entry{Hostname: "A"},
|
e2 := makeEntries("BEF")
|
||||||
Entry{Hostname: "C"},
|
|
||||||
Entry{Hostname: "D"},
|
|
||||||
Entry{Hostname: "F"},
|
|
||||||
}
|
|
||||||
|
|
||||||
e2 := Entries{
|
|
||||||
Entry{Hostname: "B"},
|
|
||||||
Entry{Hostname: "E"},
|
|
||||||
Entry{Hostname: "F"},
|
|
||||||
}
|
|
||||||
|
|
||||||
diff := e1.merge(e2)
|
diff := e1.merge(e2)
|
||||||
expectedDiff := Entries{
|
|
||||||
Entry{Hostname: "B"},
|
|
||||||
Entry{Hostname: "E"},
|
|
||||||
}
|
|
||||||
require.Equal(t, expectedDiff, diff)
|
|
||||||
|
|
||||||
expected := Entries{
|
require.Equal(t, makeEntries("BE"), diff)
|
||||||
Entry{Hostname: "A"},
|
require.Equal(t, makeEntries("ABCDEF"), e1)
|
||||||
Entry{Hostname: "B"},
|
|
||||||
Entry{Hostname: "C"},
|
|
||||||
Entry{Hostname: "D"},
|
|
||||||
Entry{Hostname: "E"},
|
|
||||||
Entry{Hostname: "F"},
|
|
||||||
}
|
|
||||||
require.Equal(t, expected, e1)
|
|
||||||
|
|
||||||
diff = e1.merge(e1)
|
diff = e1.merge(e1)
|
||||||
require.Equal(t, Entries{}, diff)
|
require.Equal(t, Entries{}, diff)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestOldMerge(t *testing.T) {
|
func TestOldMerge(t *testing.T) {
|
||||||
e1 := Entries{Entry{Hostname: "A", Version: 0}}
|
e1 := l(Entries{Entry{Hostname: "A", Version: 0}})
|
||||||
diff := e1.merge(Entries{Entry{Hostname: "A", Version: 1}})
|
diff := e1.merge(l(Entries{Entry{Hostname: "A", Version: 1}}))
|
||||||
require.Equal(t, Entries{Entry{Hostname: "A", Version: 1}}, diff)
|
require.Equal(t, l(Entries{Entry{Hostname: "A", Version: 1}}), diff)
|
||||||
require.Equal(t, Entries{Entry{Hostname: "A", Version: 1}}, e1)
|
require.Equal(t, l(Entries{Entry{Hostname: "A", Version: 1}}), e1)
|
||||||
|
|
||||||
diff = e1.merge(Entries{Entry{Hostname: "A", Version: 0}})
|
diff = e1.merge(l(Entries{Entry{Hostname: "A", Version: 0}}))
|
||||||
require.Equal(t, Entries{}, diff)
|
require.Equal(t, Entries{}, diff)
|
||||||
require.Equal(t, Entries{Entry{Hostname: "A", Version: 1}}, e1)
|
require.Equal(t, l(Entries{Entry{Hostname: "A", Version: 1}}), e1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTombstone(t *testing.T) {
|
func TestTombstone(t *testing.T) {
|
||||||
@@ -85,48 +76,63 @@ func TestTombstone(t *testing.T) {
|
|||||||
defer func() { now = oldNow }()
|
defer func() { now = oldNow }()
|
||||||
now = func() int64 { return 1234 }
|
now = func() int64 { return 1234 }
|
||||||
|
|
||||||
es := Entries{
|
es := makeEntries("AB")
|
||||||
Entry{Hostname: "A"},
|
|
||||||
Entry{Hostname: "B"},
|
|
||||||
}
|
|
||||||
|
|
||||||
es.tombstone(router.UnknownPeerName, func(e *Entry) bool {
|
es.tombstone(mesh.UnknownPeerName, func(e *Entry) bool {
|
||||||
return e.Hostname == "B"
|
return e.Hostname == "B"
|
||||||
})
|
})
|
||||||
expected := Entries{
|
expected := l(Entries{
|
||||||
Entry{Hostname: "A"},
|
Entry{Hostname: "A"},
|
||||||
Entry{Hostname: "B", Version: 1, Tombstone: 1234},
|
Entry{Hostname: "B", Version: 1, Tombstone: 1234},
|
||||||
}
|
})
|
||||||
require.Equal(t, expected, es)
|
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) {
|
func TestDelete(t *testing.T) {
|
||||||
es := Entries{
|
es := makeEntries("AB")
|
||||||
Entry{Hostname: "A"},
|
|
||||||
Entry{Hostname: "B"},
|
|
||||||
}
|
|
||||||
|
|
||||||
es.filter(func(e *Entry) bool {
|
es.filter(func(e *Entry) bool {
|
||||||
return e.Hostname != "A"
|
return e.Hostname != "A"
|
||||||
})
|
})
|
||||||
expected := Entries{
|
require.Equal(t, makeEntries("B"), es)
|
||||||
Entry{Hostname: "B"},
|
|
||||||
}
|
|
||||||
require.Equal(t, expected, es)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestLookup(t *testing.T) {
|
func TestLookup(t *testing.T) {
|
||||||
es := Entries{
|
es := l(Entries{
|
||||||
Entry{Hostname: "A"},
|
Entry{Hostname: "A"},
|
||||||
Entry{Hostname: "B", ContainerID: "bar"},
|
Entry{Hostname: "B", ContainerID: "bar"},
|
||||||
Entry{Hostname: "B", ContainerID: "foo"},
|
Entry{Hostname: "B", ContainerID: "foo"},
|
||||||
Entry{Hostname: "C"},
|
Entry{Hostname: "C"},
|
||||||
}
|
})
|
||||||
|
|
||||||
have := es.lookup("B")
|
have := es.lookup("B")
|
||||||
want := Entries{
|
want := l(Entries{
|
||||||
Entry{Hostname: "B", ContainerID: "bar"},
|
Entry{Hostname: "B", ContainerID: "bar"},
|
||||||
Entry{Hostname: "B", ContainerID: "foo"},
|
Entry{Hostname: "B", ContainerID: "foo"},
|
||||||
}
|
})
|
||||||
require.Equal(t, have, want)
|
require.Equal(t, have, want)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGossipDataMerge(t *testing.T) {
|
||||||
|
g1 := GossipData{Entries: makeEntries("AcDf")}
|
||||||
|
g2 := GossipData{Entries: makeEntries("BEf")}
|
||||||
|
|
||||||
|
g3 := g1.Merge(&g2).(*GossipData)
|
||||||
|
|
||||||
|
require.Equal(t, GossipData{Entries: makeEntries("ABcDEf")}, *g3)
|
||||||
|
}
|
||||||
+9
-11
@@ -8,6 +8,7 @@ import (
|
|||||||
"github.com/gorilla/mux"
|
"github.com/gorilla/mux"
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
|
||||||
|
"github.com/weaveworks/weave/common/docker"
|
||||||
"github.com/weaveworks/weave/net/address"
|
"github.com/weaveworks/weave/net/address"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ func (n *Nameserver) badRequest(w http.ResponseWriter, err error) {
|
|||||||
n.infof("%v", err)
|
n.infof("%v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Nameserver) HandleHTTP(router *mux.Router) {
|
func (n *Nameserver) HandleHTTP(router *mux.Router, dockerCli *docker.Client) {
|
||||||
router.Methods("GET").Path("/domain").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
router.Methods("GET").Path("/domain").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
fmt.Fprint(w, n.domain)
|
fmt.Fprint(w, n.domain)
|
||||||
})
|
})
|
||||||
@@ -34,16 +35,16 @@ func (n *Nameserver) HandleHTTP(router *mux.Router) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := n.AddEntry(hostname, container, n.ourName, ip); err != nil {
|
if !dns.IsSubDomain(n.domain, hostname) {
|
||||||
n.badRequest(w, fmt.Errorf("Unable to add entry: %v", err))
|
n.infof("Ignoring registration %s %s %s (not a subdomain of %s)", hostname, ipStr, container, n.domain)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.FormValue("check-alive") == "true" && n.docker.IsContainerNotRunning(container) {
|
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)
|
n.infof("container '%s' is not running: removing", container)
|
||||||
if err := n.Delete(hostname, container, ipStr, ip); err != nil {
|
n.Delete(hostname, container, ipStr, ip)
|
||||||
n.infof("failed to remove: %v", err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
w.WriteHeader(204)
|
w.WriteHeader(204)
|
||||||
@@ -73,10 +74,7 @@ func (n *Nameserver) HandleHTTP(router *mux.Router) {
|
|||||||
ipStr = "*"
|
ipStr = "*"
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := n.Delete(hostname, container, ipStr, ip); err != nil {
|
n.Delete(hostname, container, ipStr, ip)
|
||||||
n.badRequest(w, fmt.Errorf("Unable to delete entries: %v", err))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
w.WriteHeader(204)
|
w.WriteHeader(204)
|
||||||
}
|
}
|
||||||
router.Methods("DELETE").Path("/name/{container}/{ip}").HandlerFunc(deleteHandler)
|
router.Methods("DELETE").Path("/name/{container}/{ip}").HandlerFunc(deleteHandler)
|
||||||
|
|||||||
+80
-63
@@ -1,18 +1,15 @@
|
|||||||
package nameserver
|
package nameserver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/gob"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/miekg/dns"
|
"github.com/miekg/dns"
|
||||||
|
"github.com/weaveworks/mesh"
|
||||||
|
|
||||||
. "github.com/weaveworks/weave/common"
|
"github.com/weaveworks/weave/common"
|
||||||
"github.com/weaveworks/weave/common/docker"
|
|
||||||
"github.com/weaveworks/weave/net/address"
|
"github.com/weaveworks/weave/net/address"
|
||||||
"github.com/weaveworks/weave/router"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -37,30 +34,24 @@ const (
|
|||||||
// - Update is O(n) for now
|
// - Update is O(n) for now
|
||||||
type Nameserver struct {
|
type Nameserver struct {
|
||||||
sync.RWMutex
|
sync.RWMutex
|
||||||
ourName router.PeerName
|
ourName mesh.PeerName
|
||||||
domain string
|
domain string
|
||||||
gossip router.Gossip
|
gossip mesh.Gossip
|
||||||
docker *docker.Client
|
entries Entries
|
||||||
entries Entries
|
isKnownPeer func(mesh.PeerName) bool
|
||||||
peers *router.Peers
|
quit chan struct{}
|
||||||
quit chan struct{}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func New(ourName router.PeerName, peers *router.Peers, docker *docker.Client, domain string) *Nameserver {
|
func New(ourName mesh.PeerName, domain string, isKnownPeer func(mesh.PeerName) bool) *Nameserver {
|
||||||
ns := &Nameserver{
|
return &Nameserver{
|
||||||
ourName: ourName,
|
ourName: ourName,
|
||||||
domain: dns.Fqdn(domain),
|
domain: dns.Fqdn(domain),
|
||||||
peers: peers,
|
isKnownPeer: isKnownPeer,
|
||||||
docker: docker,
|
quit: make(chan struct{}),
|
||||||
quit: make(chan struct{}),
|
|
||||||
}
|
}
|
||||||
if peers != nil {
|
|
||||||
peers.OnGC(ns.PeerGone)
|
|
||||||
}
|
|
||||||
return ns
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Nameserver) SetGossip(gossip router.Gossip) {
|
func (n *Nameserver) SetGossip(gossip mesh.Gossip) {
|
||||||
n.gossip = gossip
|
n.gossip = gossip
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -82,22 +73,22 @@ func (n *Nameserver) Stop() {
|
|||||||
n.quit <- struct{}{}
|
n.quit <- struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Nameserver) broadcastEntries(es ...Entry) error {
|
func (n *Nameserver) broadcastEntries(es ...Entry) {
|
||||||
if n.gossip != nil {
|
if n.gossip == nil || len(es) == 0 {
|
||||||
return n.gossip.GossipBroadcast(&GossipData{
|
return
|
||||||
Entries: Entries(es),
|
|
||||||
Timestamp: now(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
return nil
|
n.gossip.GossipBroadcast(&GossipData{
|
||||||
|
Entries: Entries(es),
|
||||||
|
Timestamp: now(),
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Nameserver) AddEntry(hostname, containerid string, origin router.PeerName, addr address.Address) error {
|
func (n *Nameserver) AddEntry(hostname, containerid string, origin mesh.PeerName, addr address.Address) {
|
||||||
n.infof("adding entry %s -> %s", hostname, addr.String())
|
|
||||||
n.Lock()
|
n.Lock()
|
||||||
|
n.infof("adding entry for %s: %s -> %s", containerid, hostname, addr.String())
|
||||||
entry := n.entries.add(hostname, containerid, origin, addr)
|
entry := n.entries.add(hostname, containerid, origin, addr)
|
||||||
n.Unlock()
|
n.Unlock()
|
||||||
return n.broadcastEntries(entry)
|
n.broadcastEntries(entry)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Nameserver) Lookup(hostname string) []address.Address {
|
func (n *Nameserver) Lookup(hostname string) []address.Address {
|
||||||
@@ -130,6 +121,9 @@ func (n *Nameserver) ReverseLookup(ip address.Address) (string, error) {
|
|||||||
return match.Hostname, nil
|
return match.Hostname, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *Nameserver) ContainerStarted(ident string) {}
|
||||||
|
func (n *Nameserver) ContainerDestroyed(ident string) {}
|
||||||
|
|
||||||
func (n *Nameserver) ContainerDied(ident string) {
|
func (n *Nameserver) ContainerDied(ident string) {
|
||||||
n.Lock()
|
n.Lock()
|
||||||
entries := n.entries.tombstone(n.ourName, func(e *Entry) bool {
|
entries := n.entries.tombstone(n.ourName, func(e *Entry) bool {
|
||||||
@@ -140,23 +134,19 @@ func (n *Nameserver) ContainerDied(ident string) {
|
|||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
n.Unlock()
|
n.Unlock()
|
||||||
if len(entries) > 0 {
|
n.broadcastEntries(entries...)
|
||||||
if err := n.broadcastEntries(entries...); err != nil {
|
|
||||||
n.errorf("failed to broadcast container %s death: %v", ident, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Nameserver) PeerGone(peer *router.Peer) {
|
func (n *Nameserver) PeerGone(peer mesh.PeerName) {
|
||||||
n.infof("peer %s gone", peer.String())
|
n.infof("peer %s gone", peer.String())
|
||||||
n.Lock()
|
n.Lock()
|
||||||
defer n.Unlock()
|
defer n.Unlock()
|
||||||
n.entries.filter(func(e *Entry) bool {
|
n.entries.filter(func(e *Entry) bool {
|
||||||
return e.Origin != peer.Name
|
return e.Origin != peer
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
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.Lock()
|
||||||
n.infof("tombstoning hostname=%s, container=%s, ip=%s", hostname, containerid, ipStr)
|
n.infof("tombstoning hostname=%s, container=%s, ip=%s", hostname, containerid, ipStr)
|
||||||
entries := n.entries.tombstone(n.ourName, func(e *Entry) bool {
|
entries := n.entries.tombstone(n.ourName, func(e *Entry) bool {
|
||||||
@@ -176,7 +166,7 @@ func (n *Nameserver) Delete(hostname, containerid, ipStr string, ip address.Addr
|
|||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
n.Unlock()
|
n.Unlock()
|
||||||
return n.broadcastEntries(entries...)
|
n.broadcastEntries(entries...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Nameserver) deleteTombstones() {
|
func (n *Nameserver) deleteTombstones() {
|
||||||
@@ -188,7 +178,7 @@ func (n *Nameserver) deleteTombstones() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Nameserver) Gossip() router.GossipData {
|
func (n *Nameserver) Gossip() mesh.GossipData {
|
||||||
n.RLock()
|
n.RLock()
|
||||||
defer n.RUnlock()
|
defer n.RUnlock()
|
||||||
gossip := &GossipData{
|
gossip := &GossipData{
|
||||||
@@ -199,34 +189,56 @@ func (n *Nameserver) Gossip() router.GossipData {
|
|||||||
return gossip
|
return gossip
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Nameserver) OnGossipUnicast(sender router.PeerName, msg []byte) error {
|
func (n *Nameserver) OnGossipUnicast(sender mesh.PeerName, msg []byte) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Nameserver) receiveGossip(msg []byte) (router.GossipData, router.GossipData, error) {
|
func (n *Nameserver) receiveGossip(msg []byte) (mesh.GossipData, mesh.GossipData, error) {
|
||||||
var gossip GossipData
|
var gossip GossipData
|
||||||
if err := gob.NewDecoder(bytes.NewReader(msg)).Decode(&gossip); err != nil {
|
if err := gossip.Decode(msg); err != nil {
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if delta := gossip.Timestamp - now(); delta > gossipWindow || delta < -gossipWindow {
|
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gossip.Entries.check(); err != nil {
|
// Filter to remove entries from unknown peers, done before we take
|
||||||
return nil, nil, err
|
// 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()
|
n.Lock()
|
||||||
defer n.Unlock()
|
|
||||||
|
|
||||||
if n.peers != nil {
|
// Check entries claiming to originate from us against our current data
|
||||||
gossip.Entries.filter(func(e *Entry) bool {
|
gossip.Entries.filter(func(e *Entry) bool {
|
||||||
return n.peers.Fetch(e.Origin) != nil
|
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)
|
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 {
|
if len(newEntries) > 0 {
|
||||||
return &GossipData{Entries: newEntries, Timestamp: now()}, &gossip, nil
|
return &GossipData{Entries: newEntries, Timestamp: now()}, &gossip, nil
|
||||||
}
|
}
|
||||||
@@ -235,24 +247,29 @@ func (n *Nameserver) receiveGossip(msg []byte) (router.GossipData, router.Gossip
|
|||||||
|
|
||||||
// merge received data into state and return "everything new I've
|
// merge received data into state and return "everything new I've
|
||||||
// just learnt", or nil if nothing in the received data was new
|
// just learnt", or nil if nothing in the received data was new
|
||||||
func (n *Nameserver) OnGossip(msg []byte) (router.GossipData, error) {
|
func (n *Nameserver) OnGossip(msg []byte) (mesh.GossipData, error) {
|
||||||
newEntries, _, err := n.receiveGossip(msg)
|
newEntries, _, err := n.receiveGossip(msg)
|
||||||
return newEntries, err
|
return newEntries, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// merge received data into state and return a representation of
|
// merge received data into state and return a representation of
|
||||||
// the received data, for further propagation
|
// the received data, for further propagation
|
||||||
func (n *Nameserver) OnGossipBroadcast(_ router.PeerName, msg []byte) (router.GossipData, error) {
|
func (n *Nameserver) OnGossipBroadcast(_ mesh.PeerName, msg []byte) (mesh.GossipData, error) {
|
||||||
_, entries, err := n.receiveGossip(msg)
|
_, entries, err := n.receiveGossip(msg)
|
||||||
return entries, err
|
return entries, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Logging
|
||||||
|
|
||||||
func (n *Nameserver) infof(fmt string, args ...interface{}) {
|
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{}) {
|
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{}) {
|
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,20 +10,23 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/weaveworks/mesh"
|
||||||
|
|
||||||
"github.com/weaveworks/weave/net/address"
|
"github.com/weaveworks/weave/net/address"
|
||||||
"github.com/weaveworks/weave/router"
|
|
||||||
wt "github.com/weaveworks/weave/testing"
|
|
||||||
"github.com/weaveworks/weave/testing/gossip"
|
"github.com/weaveworks/weave/testing/gossip"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func makeNameserver(name mesh.PeerName) *Nameserver {
|
||||||
|
return New(name, "", func(mesh.PeerName) bool { return true })
|
||||||
|
}
|
||||||
|
|
||||||
func makeNetwork(size int) ([]*Nameserver, *gossip.TestRouter) {
|
func makeNetwork(size int) ([]*Nameserver, *gossip.TestRouter) {
|
||||||
gossipRouter := gossip.NewTestRouter(0.0)
|
gossipRouter := gossip.NewTestRouter(0.0)
|
||||||
nameservers := make([]*Nameserver, size)
|
nameservers := make([]*Nameserver, size)
|
||||||
|
|
||||||
for i := 0; i < size; i++ {
|
for i := 0; i < size; i++ {
|
||||||
name, _ := router.PeerNameFromString(fmt.Sprintf("%02d:00:00:02:00:00", i))
|
name, _ := mesh.PeerNameFromString(fmt.Sprintf("%02d:00:00:02:00:00", i))
|
||||||
nameserver := New(name, nil, nil, "")
|
nameserver := makeNameserver(name)
|
||||||
nameserver.SetGossip(gossipRouter.Connect(nameserver.ourName, nameserver))
|
nameserver.SetGossip(gossipRouter.Connect(nameserver.ourName, nameserver))
|
||||||
nameserver.Start()
|
nameserver.Start()
|
||||||
nameservers[i] = nameserver
|
nameservers[i] = nameserver
|
||||||
@@ -40,7 +43,7 @@ func stopNetwork(nameservers []*Nameserver, grouter *gossip.TestRouter) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type pair struct {
|
type pair struct {
|
||||||
origin router.PeerName
|
origin mesh.PeerName
|
||||||
addr address.Address
|
addr address.Address
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,18 +75,16 @@ func (m mapping) Addrs() []address.Address {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestNameservers(t *testing.T) {
|
func TestNameservers(t *testing.T) {
|
||||||
wt.RunWithTimeout(t, time.Minute, func() {
|
|
||||||
testNameservers(t)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func testNameservers(t *testing.T) {
|
|
||||||
//common.SetLogLevel("debug")
|
//common.SetLogLevel("debug")
|
||||||
|
|
||||||
lookupTimeout := 10 // ms
|
lookupTimeout := 10 // ms
|
||||||
nameservers, grouter := makeNetwork(50)
|
nameservers, grouter := makeNetwork(30)
|
||||||
defer stopNetwork(nameservers, grouter)
|
defer stopNetwork(nameservers, grouter)
|
||||||
nameserversByName := map[router.PeerName]*Nameserver{}
|
// This subset will sometimes lose touch with some of the others
|
||||||
|
badNameservers := nameservers[25:]
|
||||||
|
// This subset will remain well-connected, and we will deal mainly with them
|
||||||
|
nameservers = nameservers[:25]
|
||||||
|
nameserversByName := map[mesh.PeerName]*Nameserver{}
|
||||||
for _, n := range nameservers {
|
for _, n := range nameservers {
|
||||||
nameserversByName[n.ourName] = n
|
nameserversByName[n.ourName] = n
|
||||||
}
|
}
|
||||||
@@ -106,11 +107,17 @@ func testNameservers(t *testing.T) {
|
|||||||
addMapping := func() {
|
addMapping := func() {
|
||||||
nameserver := nameservers[rand.Intn(len(nameservers))]
|
nameserver := nameservers[rand.Intn(len(nameservers))]
|
||||||
addr := address.Address(rand.Int31())
|
addr := address.Address(rand.Int31())
|
||||||
hostname := fmt.Sprintf("hostname%d", rand.Int63())
|
// Create a hostname which has some upper and lowercase letters,
|
||||||
|
// and a unique number so we don't have to check if we allocated it already
|
||||||
|
randomBits := rand.Int63()
|
||||||
|
firstLetter := 'H' + (randomBits&1)*32
|
||||||
|
secondLetter := 'O' + (randomBits&2)*16
|
||||||
|
randomBits = randomBits >> 2
|
||||||
|
hostname := fmt.Sprintf("%c%cstname%d", firstLetter, secondLetter, randomBits)
|
||||||
mapping := mapping{hostname, []pair{{nameserver.ourName, addr}}}
|
mapping := mapping{hostname, []pair{{nameserver.ourName, addr}}}
|
||||||
mappings = append(mappings, mapping)
|
mappings = append(mappings, mapping)
|
||||||
|
|
||||||
require.Nil(t, nameserver.AddEntry(hostname, "", nameserver.ourName, addr))
|
nameserver.AddEntry(hostname, "", nameserver.ourName, addr)
|
||||||
check(nameserver, mapping)
|
check(nameserver, mapping)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,10 +132,16 @@ func testNameservers(t *testing.T) {
|
|||||||
mapping.addrs = append(mapping.addrs, pair{nameserver.ourName, addr})
|
mapping.addrs = append(mapping.addrs, pair{nameserver.ourName, addr})
|
||||||
mappings[i] = mapping
|
mappings[i] = mapping
|
||||||
|
|
||||||
require.Nil(t, nameserver.AddEntry(mapping.hostname, "", nameserver.ourName, addr))
|
nameserver.AddEntry(mapping.hostname, "", nameserver.ourName, addr)
|
||||||
check(nameserver, mapping)
|
check(nameserver, mapping)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
loseConnection := func() {
|
||||||
|
nameserver1 := badNameservers[rand.Intn(len(badNameservers))]
|
||||||
|
nameserver2 := nameservers[rand.Intn(len(nameservers))]
|
||||||
|
nameserver1.PeerGone(nameserver2.ourName)
|
||||||
|
}
|
||||||
|
|
||||||
deleteMapping := func() {
|
deleteMapping := func() {
|
||||||
if len(mappings) <= 0 {
|
if len(mappings) <= 0 {
|
||||||
return
|
return
|
||||||
@@ -144,7 +157,7 @@ func testNameservers(t *testing.T) {
|
|||||||
mappings[i] = mapping
|
mappings[i] = mapping
|
||||||
nameserver := nameserversByName[pair.origin]
|
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)
|
check(nameserver, mapping)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +192,7 @@ func testNameservers(t *testing.T) {
|
|||||||
require.Equal(t, mapping.hostname, hostname)
|
require.Equal(t, mapping.hostname, hostname)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < 1000; i++ {
|
for i := 0; i < 800; i++ {
|
||||||
r := rand.Float32()
|
r := rand.Float32()
|
||||||
switch {
|
switch {
|
||||||
case r < 0.1:
|
case r < 0.1:
|
||||||
@@ -191,7 +204,10 @@ func testNameservers(t *testing.T) {
|
|||||||
case 0.2 <= r && r < 0.3:
|
case 0.2 <= r && r < 0.3:
|
||||||
deleteMapping()
|
deleteMapping()
|
||||||
|
|
||||||
case 0.3 <= r && r < 0.9:
|
case 0.3 <= r && r < 0.35:
|
||||||
|
loseConnection()
|
||||||
|
|
||||||
|
case 0.35 <= r && r < 0.9:
|
||||||
doLookup()
|
doLookup()
|
||||||
|
|
||||||
case 0.9 <= r:
|
case 0.9 <= r:
|
||||||
@@ -203,22 +219,20 @@ func testNameservers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestContainerAndPeerDeath(t *testing.T) {
|
func TestContainerAndPeerDeath(t *testing.T) {
|
||||||
peername, err := router.PeerNameFromString("00:00:00:02:00:00")
|
peername, err := mesh.PeerNameFromString("00:00:00:02:00:00")
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
nameserver := New(peername, nil, nil, "")
|
nameserver := makeNameserver(peername)
|
||||||
|
|
||||||
err = nameserver.AddEntry("hostname", "containerid", peername, address.Address(0))
|
nameserver.AddEntry("hostname", "containerid", peername, address.Address(0))
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, []address.Address{0}, nameserver.Lookup("hostname"))
|
require.Equal(t, []address.Address{0}, nameserver.Lookup("hostname"))
|
||||||
|
|
||||||
nameserver.ContainerDied("containerid")
|
nameserver.ContainerDied("containerid")
|
||||||
require.Equal(t, []address.Address{}, nameserver.Lookup("hostname"))
|
require.Equal(t, []address.Address{}, nameserver.Lookup("hostname"))
|
||||||
|
|
||||||
err = nameserver.AddEntry("hostname", "containerid", peername, address.Address(0))
|
nameserver.AddEntry("hostname", "containerid", peername, address.Address(0))
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, []address.Address{0}, nameserver.Lookup("hostname"))
|
require.Equal(t, []address.Address{0}, nameserver.Lookup("hostname"))
|
||||||
|
|
||||||
nameserver.PeerGone(&router.Peer{Name: peername})
|
nameserver.PeerGone(peername)
|
||||||
require.Equal(t, []address.Address{}, nameserver.Lookup("hostname"))
|
require.Equal(t, []address.Address{}, nameserver.Lookup("hostname"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -227,28 +241,26 @@ func TestTombstoneDeletion(t *testing.T) {
|
|||||||
defer func() { now = oldNow }()
|
defer func() { now = oldNow }()
|
||||||
now = func() int64 { return 1234 }
|
now = func() int64 { return 1234 }
|
||||||
|
|
||||||
peername, err := router.PeerNameFromString("00:00:00:02:00:00")
|
peername, err := mesh.PeerNameFromString("00:00:00:02:00:00")
|
||||||
require.Nil(t, err)
|
require.Nil(t, err)
|
||||||
nameserver := New(peername, nil, nil, "")
|
nameserver := makeNameserver(peername)
|
||||||
|
|
||||||
err = nameserver.AddEntry("hostname", "containerid", peername, address.Address(0))
|
nameserver.AddEntry("hostname", "containerid", peername, address.Address(0))
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, []address.Address{0}, nameserver.Lookup("hostname"))
|
require.Equal(t, []address.Address{0}, nameserver.Lookup("hostname"))
|
||||||
|
|
||||||
nameserver.deleteTombstones()
|
nameserver.deleteTombstones()
|
||||||
require.Equal(t, []address.Address{0}, nameserver.Lookup("hostname"))
|
require.Equal(t, []address.Address{0}, nameserver.Lookup("hostname"))
|
||||||
|
|
||||||
err = nameserver.Delete("hostname", "containerid", "", address.Address(0))
|
nameserver.Delete("hostname", "containerid", "", address.Address(0))
|
||||||
require.Nil(t, err)
|
|
||||||
require.Equal(t, []address.Address{}, nameserver.Lookup("hostname"))
|
require.Equal(t, []address.Address{}, nameserver.Lookup("hostname"))
|
||||||
require.Equal(t, Entries{{
|
require.Equal(t, l(Entries{Entry{
|
||||||
ContainerID: "containerid",
|
ContainerID: "containerid",
|
||||||
Origin: peername,
|
Origin: peername,
|
||||||
Addr: address.Address(0),
|
Addr: address.Address(0),
|
||||||
Hostname: "hostname",
|
Hostname: "hostname",
|
||||||
Version: 1,
|
Version: 1,
|
||||||
Tombstone: 1234,
|
Tombstone: 1234,
|
||||||
}}, nameserver.entries)
|
}}), nameserver.entries)
|
||||||
|
|
||||||
now = func() int64 { return 1234 + int64(tombstoneTimeout/time.Second) + 1 }
|
now = func() int64 { return 1234 + int64(tombstoneTimeout/time.Second) + 1 }
|
||||||
nameserver.deleteTombstones()
|
nameserver.deleteTombstones()
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
package nameserver
|
package nameserver
|
||||||
|
|
||||||
type Status struct {
|
type Status struct {
|
||||||
Domain string
|
Domain string
|
||||||
Address string
|
Upstream []string
|
||||||
TTL uint32
|
Address string
|
||||||
Entries []EntryStatus
|
TTL uint32
|
||||||
|
Entries []EntryStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
type EntryStatus struct {
|
type EntryStatus struct {
|
||||||
@@ -35,8 +36,10 @@ func NewStatus(ns *Nameserver, dnsServer *DNSServer) *Status {
|
|||||||
entry.Tombstone})
|
entry.Tombstone})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
upstreamConfig, _ := dnsServer.upstream.Config()
|
||||||
return &Status{
|
return &Status{
|
||||||
dnsServer.domain,
|
dnsServer.domain,
|
||||||
|
upstreamConfig.Servers,
|
||||||
dnsServer.address,
|
dnsServer.address,
|
||||||
dnsServer.ttl,
|
dnsServer.ttl,
|
||||||
entryStatusSlice}
|
entryStatusSlice}
|
||||||
|
|||||||
+106
-12
@@ -10,6 +10,7 @@ import (
|
|||||||
// Using 32-bit integer to represent IPv4 address
|
// Using 32-bit integer to represent IPv4 address
|
||||||
type Address uint32
|
type Address uint32
|
||||||
type Offset uint32
|
type Offset uint32
|
||||||
|
type Count uint32
|
||||||
|
|
||||||
type Range struct {
|
type Range struct {
|
||||||
Start, End Address // [Start, End); Start <= End
|
Start, End Address // [Start, End); Start <= End
|
||||||
@@ -18,8 +19,8 @@ type Range struct {
|
|||||||
func NewRange(start Address, size Offset) Range {
|
func NewRange(start Address, size Offset) Range {
|
||||||
return Range{Start: start, End: Add(start, size)}
|
return Range{Start: start, End: Add(start, size)}
|
||||||
}
|
}
|
||||||
func (r Range) Size() Offset { return Subtract(r.End, r.Start) }
|
func (r Range) Size() Count { return Length(r.End, r.Start) }
|
||||||
func (r Range) String() string { return fmt.Sprintf("[%s-%s)", r.Start, r.End) }
|
func (r Range) String() string { return fmt.Sprintf("%s-%s", r.Start, r.End-1) }
|
||||||
func (r Range) Overlaps(or Range) bool { return !(r.Start >= or.End || r.End <= or.Start) }
|
func (r Range) Overlaps(or Range) bool { return !(r.Start >= or.End || r.End <= or.Start) }
|
||||||
func (r Range) Contains(addr Address) bool { return addr >= r.Start && addr < r.End }
|
func (r Range) Contains(addr Address) bool { return addr >= r.Start && addr < r.End }
|
||||||
|
|
||||||
@@ -31,14 +32,81 @@ func (r Range) AsCIDRString() string {
|
|||||||
}
|
}
|
||||||
prefixLen--
|
prefixLen--
|
||||||
}
|
}
|
||||||
return CIDR{Start: r.Start, PrefixLen: prefixLen}.String()
|
return CIDR{Addr: r.Start, PrefixLen: prefixLen}.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// return the highest bit set in v
|
||||||
|
// algorithm from http://graphics.stanford.edu/~seander/bithacks.html#RoundUpPowerOf2
|
||||||
|
func msb(v Count) Count {
|
||||||
|
v |= v >> 1
|
||||||
|
v |= v >> 2
|
||||||
|
v |= v >> 4
|
||||||
|
v |= v >> 8
|
||||||
|
v |= v >> 16
|
||||||
|
// Cast to 64-bit to avoid overflow when original v >= 0x80000000
|
||||||
|
return Count((uint64(v) + 1) / 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r Range) BiggestCIDRRange() Range {
|
||||||
|
sizeMsb := Offset(msb(r.Size()))
|
||||||
|
maskedSize := Offset(r.Size()) & (sizeMsb - 1)
|
||||||
|
maskedStart := Offset(r.Start) & (sizeMsb - 1)
|
||||||
|
if maskedStart == 0 {
|
||||||
|
return NewRange(r.Start, sizeMsb)
|
||||||
|
}
|
||||||
|
if sizeMsb-maskedStart <= maskedSize {
|
||||||
|
return NewRange(Add(r.Start, sizeMsb-maskedStart), sizeMsb)
|
||||||
|
}
|
||||||
|
size := sizeMsb / 2
|
||||||
|
if size >= maskedStart {
|
||||||
|
return NewRange(Add(r.Start, size-maskedStart), size)
|
||||||
|
}
|
||||||
|
return NewRange(Add(r.Start, sizeMsb-maskedStart), size)
|
||||||
|
}
|
||||||
|
|
||||||
|
func MakeCIDR(subnet CIDR, addr Address) CIDR {
|
||||||
|
return CIDR{Addr: addr, PrefixLen: subnet.PrefixLen}
|
||||||
}
|
}
|
||||||
|
|
||||||
type CIDR struct {
|
type CIDR struct {
|
||||||
Start Address
|
Addr Address
|
||||||
PrefixLen int
|
PrefixLen int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CIDRs returns a list of CIDR-aligned ranges which cover this range.
|
||||||
|
func (r Range) CIDRs() []CIDR {
|
||||||
|
const (
|
||||||
|
fullMask = ^Address(0)
|
||||||
|
cidrMaxPrefixLen = 32
|
||||||
|
)
|
||||||
|
var cidrs []CIDR
|
||||||
|
|
||||||
|
for start, end := r.Start, r.End-1; end >= start; {
|
||||||
|
mask, prefixLen := fullMask, cidrMaxPrefixLen
|
||||||
|
// Find the smallest mask which would cover some part of [start;end].
|
||||||
|
// Once we found such, apply it by OR'ing
|
||||||
|
for mask > 0 {
|
||||||
|
tmpMask := mask << 1
|
||||||
|
// Check whether mask neither too short nor too long
|
||||||
|
if (start&tmpMask) != start || (start|^tmpMask) > end {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
mask = tmpMask
|
||||||
|
prefixLen--
|
||||||
|
}
|
||||||
|
cidrs = append(cidrs, CIDR{start, prefixLen})
|
||||||
|
// Apply mask
|
||||||
|
start |= ^mask
|
||||||
|
// Check for overflow
|
||||||
|
if start+1 < start {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
start++
|
||||||
|
}
|
||||||
|
|
||||||
|
return cidrs
|
||||||
|
}
|
||||||
|
|
||||||
func ParseIP(s string) (Address, error) {
|
func ParseIP(s string) (Address, error) {
|
||||||
if ip := net.ParseIP(s); ip != nil {
|
if ip := net.ParseIP(s); ip != nil {
|
||||||
return FromIP4(ip), nil
|
return FromIP4(ip), nil
|
||||||
@@ -46,29 +114,50 @@ func ParseIP(s string) (Address, error) {
|
|||||||
return 0, &net.ParseError{Type: "IP Address", Text: s}
|
return 0, &net.ParseError{Type: "IP Address", Text: s}
|
||||||
}
|
}
|
||||||
|
|
||||||
func ParseCIDR(s string) (Address, CIDR, error) {
|
func ParseCIDR(s string) (CIDR, error) {
|
||||||
if ip, ipnet, err := net.ParseCIDR(s); err != nil {
|
if ip, ipnet, err := net.ParseCIDR(s); err != nil {
|
||||||
return 0, CIDR{}, err
|
return CIDR{}, err
|
||||||
} else if ipnet.IP.To4() == nil {
|
} else if ipnet.IP.To4() == nil {
|
||||||
return 0, CIDR{}, &net.ParseError{Type: "Non-IPv4 address not supported", Text: s}
|
return CIDR{}, &net.ParseError{Type: "Non-IPv4 address not supported", Text: s}
|
||||||
} else {
|
} else {
|
||||||
prefixLen, _ := ipnet.Mask.Size()
|
prefixLen, _ := ipnet.Mask.Size()
|
||||||
return FromIP4(ip), CIDR{Start: FromIP4(ipnet.IP), PrefixLen: prefixLen}, nil
|
return CIDR{Addr: FromIP4(ip), PrefixLen: prefixLen}, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewCIDRs(ranges []Range) (cidrs []CIDR) {
|
||||||
|
for _, r := range ranges {
|
||||||
|
cidrs = append(cidrs, r.CIDRs()...)
|
||||||
|
}
|
||||||
|
return cidrs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cidr CIDR) Start() Address {
|
||||||
|
return cidr.Addr
|
||||||
|
}
|
||||||
|
|
||||||
|
// cidr = [Start; End)
|
||||||
|
func (cidr CIDR) End() Address {
|
||||||
|
return cidr.Range().End
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cidr CIDR) IsSubnet() bool {
|
||||||
|
mask := cidr.Size() - 1
|
||||||
|
return Offset(cidr.Addr)&mask == 0
|
||||||
|
}
|
||||||
|
|
||||||
func (cidr CIDR) Size() Offset { return 1 << uint(32-cidr.PrefixLen) }
|
func (cidr CIDR) Size() Offset { return 1 << uint(32-cidr.PrefixLen) }
|
||||||
|
|
||||||
func (cidr CIDR) Range() Range {
|
func (cidr CIDR) Range() Range {
|
||||||
return NewRange(cidr.Start, cidr.Size())
|
return NewRange(cidr.Addr, cidr.Size())
|
||||||
}
|
}
|
||||||
func (cidr CIDR) HostRange() Range {
|
func (cidr CIDR) HostRange() Range {
|
||||||
// Respect RFC1122 exclusions of first and last addresses
|
// Respect RFC1122 exclusions of first and last addresses
|
||||||
return NewRange(cidr.Start+1, cidr.Size()-2)
|
return NewRange(cidr.Addr+1, cidr.Size()-2)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cidr CIDR) String() string {
|
func (cidr CIDR) String() string {
|
||||||
return fmt.Sprintf("%s/%d", cidr.Start.String(), cidr.PrefixLen)
|
return fmt.Sprintf("%s/%d", cidr.Addr.String(), cidr.PrefixLen)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FromIP4 converts an ipv4 address to our integer address type
|
// FromIP4 converts an ipv4 address to our integer address type
|
||||||
@@ -107,7 +196,12 @@ func Subtract(a, b Address) Offset {
|
|||||||
return Offset(a - b)
|
return Offset(a - b)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Min(a, b Offset) Offset {
|
func Length(a, b Address) Count {
|
||||||
|
common.Assert(a >= b)
|
||||||
|
return Count(a - b)
|
||||||
|
}
|
||||||
|
|
||||||
|
func Min(a, b Count) Count {
|
||||||
if a > b {
|
if a > b {
|
||||||
return b
|
return b
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package address
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
"testing/quick"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func isPower2(x Count) bool {
|
||||||
|
if x == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for ; x > 1; x /= 2 {
|
||||||
|
if x&1 != 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestBiggestPow2AlignedRange(t *testing.T) {
|
||||||
|
require.Equal(t, NewRange(0, 1), NewRange(0, 1).BiggestCIDRRange())
|
||||||
|
require.Equal(t, NewRange(1, 1), NewRange(1, 2).BiggestCIDRRange())
|
||||||
|
require.Equal(t, NewRange(2, 2), NewRange(1, 3).BiggestCIDRRange())
|
||||||
|
require.Equal(t, NewRange(0, 0x40000000), NewRange(0, 0x7fffffff).BiggestCIDRRange())
|
||||||
|
require.Equal(t, NewRange(0xfffffffe, 1), NewRange(0xfffffffe, 1).BiggestCIDRRange())
|
||||||
|
prop := func(start Address, size Offset) bool {
|
||||||
|
if size > Offset(0xffffffff)-Offset(start) { // out of range
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
r := NewRange(start, size)
|
||||||
|
result := r.BiggestCIDRRange()
|
||||||
|
return r.Contains(result.Start) &&
|
||||||
|
r.Contains(result.End-1) &&
|
||||||
|
isPower2(result.Size()) &&
|
||||||
|
result.Size() > r.Size()/4 &&
|
||||||
|
Count(result.Start)%result.Size() == 0
|
||||||
|
}
|
||||||
|
require.NoError(t, quick.Check(prop, &quick.Config{MaxCount: 1000000}))
|
||||||
|
}
|
||||||
|
|
||||||
|
func ip(s string) Address {
|
||||||
|
addr, _ := ParseIP(s)
|
||||||
|
return addr
|
||||||
|
}
|
||||||
|
|
||||||
|
func cidr(s string) CIDR {
|
||||||
|
c, err := ParseCIDR(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCIDRs(t *testing.T) {
|
||||||
|
start := ip("10.0.0.1")
|
||||||
|
end := ip("10.0.0.9")
|
||||||
|
r := NewRange(start, Subtract(end, start))
|
||||||
|
require.Equal(t,
|
||||||
|
[]CIDR{cidr("10.0.0.1/32"), cidr("10.0.0.2/31"), cidr("10.0.0.4/30"), cidr("10.0.0.8/32")},
|
||||||
|
r.CIDRs())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestCIDRStartAndEnd(t *testing.T) {
|
||||||
|
cidr, _ := ParseCIDR("10.0.0.0/24")
|
||||||
|
require.Equal(t, ip("10.0.0.0"), cidr.Start(), "")
|
||||||
|
require.Equal(t, ip("10.0.1.0"), cidr.End(), "")
|
||||||
|
}
|
||||||
+43
@@ -0,0 +1,43 @@
|
|||||||
|
package net
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
import "io"
|
||||||
|
import "os"
|
||||||
|
|
||||||
|
// Configure the ARP cache parameters for the given interface. This
|
||||||
|
// makes containers react more quickly to a change in the MAC address
|
||||||
|
// associated with an IP address.
|
||||||
|
func ConfigureARPCache(name string) error {
|
||||||
|
if err := sysctl(fmt.Sprintf("net/ipv4/neigh/%s/base_reachable_time", name), "5"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := sysctl(fmt.Sprintf("net/ipv4/neigh/%s/delay_first_probe_time", name), "2"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := sysctl(fmt.Sprintf("net/ipv4/neigh/%s/ucast_solicit", name), "1"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func sysctl(variable, value string) error {
|
||||||
|
f, err := os.OpenFile(fmt.Sprintf("/proc/sys/%s", variable), os.O_WRONLY, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
n, err := io.WriteString(f, value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if n < len(value) {
|
||||||
|
return io.ErrShortWrite
|
||||||
|
}
|
||||||
|
|
||||||
|
err = f.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BridgeType int
|
||||||
|
|
||||||
|
const (
|
||||||
|
WeaveBridgeName = "weave"
|
||||||
|
DatapathName = "datapath"
|
||||||
|
|
||||||
|
None BridgeType = iota
|
||||||
|
Bridge
|
||||||
|
Fastdp
|
||||||
|
BridgedFastdp
|
||||||
|
Inconsistent
|
||||||
|
)
|
||||||
|
|
||||||
|
func DetectBridgeType(weaveBridgeName, datapathName string) BridgeType {
|
||||||
|
bridge, _ := netlink.LinkByName(weaveBridgeName)
|
||||||
|
datapath, _ := netlink.LinkByName(datapathName)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case bridge == nil && datapath == nil:
|
||||||
|
return None
|
||||||
|
case isBridge(bridge) && datapath == nil:
|
||||||
|
return Bridge
|
||||||
|
case isDatapath(bridge) && datapath == nil:
|
||||||
|
return Fastdp
|
||||||
|
case isDatapath(datapath) && isBridge(bridge):
|
||||||
|
return BridgedFastdp
|
||||||
|
default:
|
||||||
|
return Inconsistent
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isBridge(link netlink.Link) bool {
|
||||||
|
_, isBridge := link.(*netlink.Bridge)
|
||||||
|
return isBridge
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDatapath(link netlink.Link) bool {
|
||||||
|
switch link.(type) {
|
||||||
|
case *netlink.GenericLink:
|
||||||
|
return link.Type() == "openvswitch"
|
||||||
|
case *netlink.Device:
|
||||||
|
// Assume it's our openvswitch device, and the kernel has not been updated to report the kind.
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
package net
|
||||||
|
|
||||||
|
import "fmt"
|
||||||
|
import "syscall"
|
||||||
|
import "unsafe"
|
||||||
|
|
||||||
|
const (
|
||||||
|
SIOCETHTOOL = 0x8946 // linux/sockios.h
|
||||||
|
ETHTOOL_GTXCSUM = 0x00000016 // linux/ethtool.h
|
||||||
|
ETHTOOL_STXCSUM = 0x00000017 // linux/ethtool.h
|
||||||
|
IFNAMSIZ = 16 // linux/if.h
|
||||||
|
)
|
||||||
|
|
||||||
|
// linux/if.h 'struct ifreq'
|
||||||
|
type IFReqData struct {
|
||||||
|
Name [IFNAMSIZ]byte
|
||||||
|
Data uintptr
|
||||||
|
}
|
||||||
|
|
||||||
|
// linux/ethtool.h 'struct ethtool_value'
|
||||||
|
type EthtoolValue struct {
|
||||||
|
Cmd uint32
|
||||||
|
Data uint32
|
||||||
|
}
|
||||||
|
|
||||||
|
func ioctlEthtool(fd int, argp uintptr) error {
|
||||||
|
_, _, errno := syscall.RawSyscall(syscall.SYS_IOCTL, uintptr(fd), uintptr(SIOCETHTOOL), argp)
|
||||||
|
if errno != 0 {
|
||||||
|
return errno
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disable TX checksum offload on specified interface
|
||||||
|
func EthtoolTXOff(name string) error {
|
||||||
|
if len(name)+1 > IFNAMSIZ {
|
||||||
|
return fmt.Errorf("name too long")
|
||||||
|
}
|
||||||
|
|
||||||
|
socket, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, 0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer syscall.Close(socket)
|
||||||
|
|
||||||
|
// Request current value
|
||||||
|
value := EthtoolValue{Cmd: ETHTOOL_GTXCSUM}
|
||||||
|
request := IFReqData{Data: uintptr(unsafe.Pointer(&value))}
|
||||||
|
copy(request.Name[:], name)
|
||||||
|
|
||||||
|
if err := ioctlEthtool(socket, uintptr(unsafe.Pointer(&request))); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if value.Data == 0 { // if already off, don't try to change
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
value = EthtoolValue{ETHTOOL_STXCSUM, 0}
|
||||||
|
return ioctlEthtool(socket, uintptr(unsafe.Pointer(&request)))
|
||||||
|
}
|
||||||
@@ -5,28 +5,35 @@ import (
|
|||||||
"net"
|
"net"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"github.com/vishvananda/netlink/nl"
|
"github.com/vishvananda/netlink"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Wait for an interface to come up.
|
// Wait for an interface to come up.
|
||||||
func EnsureInterface(ifaceName string) (*net.Interface, error) {
|
func EnsureInterface(ifaceName string) (*net.Interface, error) {
|
||||||
s, err := nl.Subscribe(syscall.NETLINK_ROUTE, syscall.RTNLGRP_LINK)
|
iface, err := ensureInterface(ifaceName)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer s.Close()
|
|
||||||
iface, err := ensureInterface(s, ifaceName)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return iface, err
|
return iface, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func ensureInterface(s *nl.NetlinkSocket, ifaceName string) (*net.Interface, error) {
|
func ensureInterface(ifaceName string) (*net.Interface, error) {
|
||||||
|
ch := make(chan netlink.LinkUpdate)
|
||||||
|
// NB: We do not supply (and eventually close) a 'done' channel
|
||||||
|
// here since that can cause incorrect file descriptor
|
||||||
|
// re-usage. See https://github.com/weaveworks/weave/issues/2120
|
||||||
|
if err := netlink.LinkSubscribe(ch, nil); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
// check for currently-existing interface after subscribing, to avoid race
|
||||||
if iface, err := findInterface(ifaceName); err == nil {
|
if iface, err := findInterface(ifaceName); err == nil {
|
||||||
return iface, nil
|
return iface, nil
|
||||||
}
|
}
|
||||||
netlinkReceiveUntil(s, matchIfName(ifaceName))
|
for update := range ch {
|
||||||
|
if ifaceName == update.Link.Attrs().Name && update.IfInfomsg.Flags&syscall.IFF_UP != 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
iface, err := findInterface(ifaceName)
|
iface, err := findInterface(ifaceName)
|
||||||
return iface, err
|
return iface, err
|
||||||
}
|
}
|
||||||
@@ -35,21 +42,35 @@ func ensureInterface(s *nl.NetlinkSocket, ifaceName string) (*net.Interface, err
|
|||||||
// This matches the behaviour in 'weave attach', which is the only context in which
|
// This matches the behaviour in 'weave attach', which is the only context in which
|
||||||
// we expect this to be called. If you change one, change the other to match.
|
// we expect this to be called. If you change one, change the other to match.
|
||||||
func EnsureInterfaceAndMcastRoute(ifaceName string) (*net.Interface, error) {
|
func EnsureInterfaceAndMcastRoute(ifaceName string) (*net.Interface, error) {
|
||||||
s, err := nl.Subscribe(syscall.NETLINK_ROUTE, syscall.RTNLGRP_LINK, syscall.RTNLGRP_IPV4_ROUTE)
|
iface, err := ensureInterface(ifaceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
defer s.Close()
|
ch := make(chan netlink.RouteUpdate)
|
||||||
iface, err := ensureInterface(s, ifaceName)
|
if err := netlink.RouteSubscribe(ch, nil); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
dest := net.IPv4(224, 0, 0, 0)
|
dest := net.IPv4(224, 0, 0, 0)
|
||||||
if CheckRouteExists(ifaceName, dest) {
|
check := func(route netlink.Route) bool {
|
||||||
return iface, err
|
return route.Dst != nil && route.Dst.IP.Equal(dest)
|
||||||
}
|
}
|
||||||
netlinkReceiveUntil(s, matchRoute(ifaceName, dest))
|
// check for currently-existing route after subscribing, to avoid race
|
||||||
return iface, err
|
routes, err := netlink.RouteList(nil, netlink.FAMILY_V4)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
for _, route := range routes {
|
||||||
|
if check(route) {
|
||||||
|
return iface, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for update := range ch {
|
||||||
|
if check(update.Route) {
|
||||||
|
return iface, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// should never get here
|
||||||
|
return iface, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func findInterface(ifaceName string) (iface *net.Interface, err error) {
|
func findInterface(ifaceName string) (iface *net.Interface, err error) {
|
||||||
@@ -61,40 +82,3 @@ func findInterface(ifaceName string) (iface *net.Interface, err error) {
|
|||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func netlinkReceiveUntil(s *nl.NetlinkSocket, f func(syscall.NetlinkMessage) (bool, error)) error {
|
|
||||||
for {
|
|
||||||
msgs, err := s.Receive()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
for _, m := range msgs {
|
|
||||||
if done, err := f(m); done {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func matchIfName(ifaceName string) func(m syscall.NetlinkMessage) (bool, error) {
|
|
||||||
return func(m syscall.NetlinkMessage) (bool, error) {
|
|
||||||
switch m.Header.Type {
|
|
||||||
case syscall.RTM_NEWLINK: // receive this type for link 'up'
|
|
||||||
ifmsg := nl.DeserializeIfInfomsg(m.Data)
|
|
||||||
attrs, err := syscall.ParseNetlinkRouteAttr(&m)
|
|
||||||
if err != nil {
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
name := ""
|
|
||||||
for _, attr := range attrs {
|
|
||||||
if attr.Attr.Type == syscall.IFA_LABEL {
|
|
||||||
name = string(attr.Value[:len(attr.Value)-1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ifaceName == name && ifmsg.Flags&syscall.IFF_UP != 0 {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
package net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ListenUnixSocket(pathname string) (net.Listener, error) {
|
||||||
|
if err := os.Remove(pathname); err != nil && !os.IsNotExist(err) {
|
||||||
|
return nil, fmt.Errorf("Error removing existing unix socket: %s", err)
|
||||||
|
}
|
||||||
|
listener, err := net.Listen("unix", pathname)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("ListenUnixSocket failed: %s", err)
|
||||||
|
}
|
||||||
|
return listener, nil
|
||||||
|
}
|
||||||
+116
@@ -0,0 +1,116 @@
|
|||||||
|
package net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
"github.com/vishvananda/netns"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Dev struct {
|
||||||
|
Name string `json:"Name,omitempty"`
|
||||||
|
MAC net.HardwareAddr `json:"MAC,omitempty"`
|
||||||
|
CIDRs []*net.IPNet `json:"CIDRs,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func LinkToNetDev(link netlink.Link) (Dev, error) {
|
||||||
|
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
||||||
|
if err != nil {
|
||||||
|
return Dev{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
netDev := Dev{Name: link.Attrs().Name, MAC: link.Attrs().HardwareAddr}
|
||||||
|
for _, addr := range addrs {
|
||||||
|
netDev.CIDRs = append(netDev.CIDRs, addr.IPNet)
|
||||||
|
}
|
||||||
|
return netDev, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectedToBridgeVethPeerIds returns peer indexes of veth links connected to
|
||||||
|
// the given bridge. The peer index is used to query from a container netns
|
||||||
|
// whether the container is connected to the bridge.
|
||||||
|
func ConnectedToBridgeVethPeerIds(bridgeName string) ([]int, error) {
|
||||||
|
var ids []int
|
||||||
|
|
||||||
|
br, err := netlink.LinkByName(bridgeName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
links, err := netlink.LinkList()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, link := range links {
|
||||||
|
if _, isveth := link.(*netlink.Veth); isveth && link.Attrs().MasterIndex == br.Attrs().Index {
|
||||||
|
peerID := link.Attrs().ParentIndex
|
||||||
|
if peerID == 0 {
|
||||||
|
// perhaps running on an older kernel where ParentIndex doesn't work.
|
||||||
|
// as fall-back, assume the peers are consecutive
|
||||||
|
peerID = link.Attrs().Index - 1
|
||||||
|
}
|
||||||
|
ids = append(ids, peerID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ids, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lookup the weave interface of a container
|
||||||
|
func GetWeaveNetDevs(processID int) ([]Dev, error) {
|
||||||
|
peerIDs, err := ConnectedToBridgeVethPeerIds("weave")
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return GetNetDevsByVethPeerIds(processID, peerIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetNetDevsByVethPeerIds(processID int, peerIDs []int) ([]Dev, error) {
|
||||||
|
// Bail out if this container is running in the root namespace
|
||||||
|
netnsRoot, err := netns.GetFromPid(1)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to open root namespace: %s", err)
|
||||||
|
}
|
||||||
|
defer netnsRoot.Close()
|
||||||
|
netnsContainer, err := netns.GetFromPid(processID)
|
||||||
|
if err != nil {
|
||||||
|
// Unable to find a namespace for this process - just return nothing
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("unable to open process %d namespace: %s", processID, err)
|
||||||
|
}
|
||||||
|
defer netnsContainer.Close()
|
||||||
|
if netnsRoot.Equal(netnsContainer) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var netdevs []Dev
|
||||||
|
peersStr := make([]string, len(peerIDs))
|
||||||
|
for i, id := range peerIDs {
|
||||||
|
peersStr[i] = strconv.Itoa(id)
|
||||||
|
}
|
||||||
|
nds, err := WithNetNSByPid(processID, "list-netdevs", peersStr...)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("list-netdevs failed: %s", err)
|
||||||
|
}
|
||||||
|
err = json.Unmarshal(nds, &netdevs)
|
||||||
|
|
||||||
|
return netdevs, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the weave bridge interface.
|
||||||
|
// NB: Should be called from the root network namespace.
|
||||||
|
func GetBridgeNetDev(bridgeName string) (Dev, error) {
|
||||||
|
link, err := netlink.LinkByName(bridgeName)
|
||||||
|
if err != nil {
|
||||||
|
return Dev{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return LinkToNetDev(link)
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os/exec"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
"github.com/vishvananda/netns"
|
||||||
|
)
|
||||||
|
|
||||||
|
var ErrLinkNotFound = errors.New("Link not found")
|
||||||
|
|
||||||
|
// NB: The following function is unsafe, because:
|
||||||
|
// - It changes a network namespace (netns) of an OS thread which runs
|
||||||
|
// the function. During execution, the Go runtime might clone a new OS thread
|
||||||
|
// for scheduling other go-routines, thus they might end up running in
|
||||||
|
// a "wrong" netns.
|
||||||
|
// - runtime.LockOSThread does not guarantee that a spawned go-routine on
|
||||||
|
// the locked thread will be run by it. Thus, the work function is
|
||||||
|
// not allowed to spawn any go-routine which is dependent on the given netns.
|
||||||
|
|
||||||
|
// Please see https://github.com/weaveworks/weave/issues/2388#issuecomment-228365069
|
||||||
|
// for more details and make sure that you understand the implications before
|
||||||
|
// using the function!
|
||||||
|
func WithNetNSUnsafe(ns netns.NsHandle, work func() error) error {
|
||||||
|
runtime.LockOSThread()
|
||||||
|
defer runtime.UnlockOSThread()
|
||||||
|
|
||||||
|
oldNs, err := netns.Get()
|
||||||
|
if err == nil {
|
||||||
|
defer oldNs.Close()
|
||||||
|
|
||||||
|
err = netns.Set(ns)
|
||||||
|
if err == nil {
|
||||||
|
defer netns.Set(oldNs)
|
||||||
|
|
||||||
|
err = work()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithNetNSLinkUnsafe(ns netns.NsHandle, ifName string, work func(link netlink.Link) error) error {
|
||||||
|
return WithNetNSUnsafe(ns, func() error {
|
||||||
|
link, err := netlink.LinkByName(ifName)
|
||||||
|
if err != nil {
|
||||||
|
if err.Error() == errors.New("Link not found").Error() {
|
||||||
|
return ErrLinkNotFound
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return work(link)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
var WeaveUtilCmd = "weaveutil"
|
||||||
|
|
||||||
|
// A safe version of WithNetNS* which creates a process executing
|
||||||
|
// "nsenter --net=<ns-path> weaveutil <cmd> [args]".
|
||||||
|
func WithNetNS(nsPath string, cmd string, args ...string) ([]byte, error) {
|
||||||
|
var stdout, stderr bytes.Buffer
|
||||||
|
|
||||||
|
args = append([]string{"--net=" + nsPath, WeaveUtilCmd, cmd}, args...)
|
||||||
|
c := exec.Command("nsenter", args...)
|
||||||
|
c.Stdout = &stdout
|
||||||
|
c.Stderr = &stderr
|
||||||
|
if err := c.Run(); err != nil {
|
||||||
|
return nil, fmt.Errorf("%s: %s", string(stderr.Bytes()), err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return stdout.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithNetNSByPid(pid int, cmd string, args ...string) ([]byte, error) {
|
||||||
|
return WithNetNS(NSPathByPid(pid), cmd, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func NSPathByPid(pid int) string {
|
||||||
|
return fmt.Sprintf("/proc/%d/ns/net", pid)
|
||||||
|
}
|
||||||
+22
-66
@@ -3,16 +3,15 @@ package net
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"syscall"
|
"os"
|
||||||
|
|
||||||
"github.com/vishvananda/netlink"
|
"github.com/vishvananda/netlink"
|
||||||
"github.com/vishvananda/netlink/nl"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// A network is considered free if it does not overlap any existing
|
// A network is considered free if it does not overlap any existing
|
||||||
// routes on this host. This is the same approach taken by Docker.
|
// routes on this host. This is the same approach taken by Docker.
|
||||||
func CheckNetworkFree(subnet *net.IPNet, ignoreIfaceNames map[string]struct{}) error {
|
func CheckNetworkFree(subnet *net.IPNet, ignoreIfaceNames map[string]struct{}) error {
|
||||||
return forEachRoute(ignoreIfaceNames, func(name string, route netlink.Route) error {
|
return forEachRoute(ignoreIfaceNames, func(route netlink.Route) error {
|
||||||
if route.Dst != nil && overlaps(route.Dst, subnet) {
|
if route.Dst != nil && overlaps(route.Dst, subnet) {
|
||||||
return fmt.Errorf("Network %s overlaps with existing route %s on host.", subnet, route.Dst)
|
return fmt.Errorf("Network %s overlaps with existing route %s on host.", subnet, route.Dst)
|
||||||
}
|
}
|
||||||
@@ -28,7 +27,7 @@ func overlaps(n1, n2 *net.IPNet) bool {
|
|||||||
// For a specific address, we only care if it is actually *inside* an
|
// For a specific address, we only care if it is actually *inside* an
|
||||||
// existing route, because weave-local traffic never hits IP routing.
|
// existing route, because weave-local traffic never hits IP routing.
|
||||||
func CheckAddressOverlap(addr net.IP, ignoreIfaceNames map[string]struct{}) error {
|
func CheckAddressOverlap(addr net.IP, ignoreIfaceNames map[string]struct{}) error {
|
||||||
return forEachRoute(ignoreIfaceNames, func(name string, route netlink.Route) error {
|
return forEachRoute(ignoreIfaceNames, func(route netlink.Route) error {
|
||||||
if route.Dst != nil && route.Dst.Contains(addr) {
|
if route.Dst != nil && route.Dst.Contains(addr) {
|
||||||
return fmt.Errorf("Address %s overlaps with existing route %s on host.", addr, route.Dst)
|
return fmt.Errorf("Address %s overlaps with existing route %s on host.", addr, route.Dst)
|
||||||
}
|
}
|
||||||
@@ -36,80 +35,37 @@ func CheckAddressOverlap(addr net.IP, ignoreIfaceNames map[string]struct{}) erro
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func forEachRoute(ignoreIfaceNames map[string]struct{}, check func(name string, r netlink.Route) error) error {
|
func forEachRoute(ignoreIfaceNames map[string]struct{}, check func(r netlink.Route) error) error {
|
||||||
|
ignoreIfaceIndices := make(map[int]struct{})
|
||||||
|
for ifaceName := range ignoreIfaceNames {
|
||||||
|
if iface, err := net.InterfaceByName(ifaceName); err == nil {
|
||||||
|
ignoreIfaceIndices[iface.Index] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
routes, err := netlink.RouteList(nil, netlink.FAMILY_V4)
|
routes, err := netlink.RouteList(nil, netlink.FAMILY_V4)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, route := range routes {
|
for _, route := range routes {
|
||||||
link, err := netlink.LinkByIndex(route.LinkIndex)
|
if _, found := ignoreIfaceIndices[route.LinkIndex]; found {
|
||||||
if err == nil {
|
continue
|
||||||
if _, found := ignoreIfaceNames[link.Attrs().Name]; found {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if err := check(link.Attrs().Name, route); err != nil {
|
if err := check(route); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func CheckRouteExists(ifaceName string, dest net.IP) bool {
|
func AddRoute(link netlink.Link, scope netlink.Scope, dst *net.IPNet, gw net.IP) error {
|
||||||
found := false
|
err := netlink.RouteAdd(&netlink.Route{
|
||||||
forEachRoute(map[string]struct{}{}, func(name string, route netlink.Route) error {
|
LinkIndex: link.Attrs().Index,
|
||||||
if name == ifaceName && route.Dst.IP.Equal(dest) {
|
Scope: scope,
|
||||||
found = true
|
Dst: dst,
|
||||||
}
|
Gw: gw,
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
return found
|
if os.IsExist(err) { // squash duplicate route errors
|
||||||
}
|
err = nil
|
||||||
|
|
||||||
func matchRoute(ifaceName string, dest net.IP) func(m syscall.NetlinkMessage) (bool, error) {
|
|
||||||
return func(m syscall.NetlinkMessage) (bool, error) {
|
|
||||||
switch m.Header.Type {
|
|
||||||
case syscall.RTM_NEWROUTE:
|
|
||||||
route, err := deserializeRoute(m.Data)
|
|
||||||
if err != nil {
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
link, _ := netlink.LinkByIndex(route.LinkIndex)
|
|
||||||
if link.Attrs().Name == ifaceName && route.Dst.IP.Equal(dest) {
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
}
|
return err
|
||||||
|
|
||||||
// This code proposed for addition to the vishvananda/netlink library
|
|
||||||
// deserializeRoute decodes a binary netlink message into a Route struct
|
|
||||||
func deserializeRoute(m []byte) (netlink.Route, error) {
|
|
||||||
route := netlink.Route{}
|
|
||||||
msg := nl.DeserializeRtMsg(m)
|
|
||||||
attrs, err := nl.ParseRouteAttr(m[msg.Len():])
|
|
||||||
if err != nil {
|
|
||||||
return route, err
|
|
||||||
}
|
|
||||||
route.Scope = netlink.Scope(msg.Scope)
|
|
||||||
|
|
||||||
native := nl.NativeEndian()
|
|
||||||
for _, attr := range attrs {
|
|
||||||
switch attr.Attr.Type {
|
|
||||||
case syscall.RTA_GATEWAY:
|
|
||||||
route.Gw = net.IP(attr.Value)
|
|
||||||
case syscall.RTA_PREFSRC:
|
|
||||||
route.Src = net.IP(attr.Value)
|
|
||||||
case syscall.RTA_DST:
|
|
||||||
route.Dst = &net.IPNet{
|
|
||||||
IP: attr.Value,
|
|
||||||
Mask: net.CIDRMask(int(msg.Dst_len), 8*len(attr.Value)),
|
|
||||||
}
|
|
||||||
case syscall.RTA_OIF:
|
|
||||||
routeIndex := int(native.Uint32(attr.Value[0:4]))
|
|
||||||
route.LinkIndex = routeIndex
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return route, nil
|
|
||||||
}
|
}
|
||||||
+273
@@ -0,0 +1,273 @@
|
|||||||
|
package net
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/coreos/go-iptables/iptables"
|
||||||
|
"github.com/j-keck/arping"
|
||||||
|
"github.com/vishvananda/netlink"
|
||||||
|
"github.com/vishvananda/netns"
|
||||||
|
|
||||||
|
"github.com/weaveworks/weave/common/odp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// create and attach a veth to the Weave bridge
|
||||||
|
func CreateAndAttachVeth(name, peerName, bridgeName string, mtu int, keepTXOn bool, init func(peer netlink.Link) error) (*netlink.Veth, error) {
|
||||||
|
bridge, err := netlink.LinkByName(bridgeName)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf(`bridge "%s" not present; did you launch weave?`, bridgeName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if mtu == 0 {
|
||||||
|
mtu = bridge.Attrs().MTU
|
||||||
|
}
|
||||||
|
veth := &netlink.Veth{
|
||||||
|
LinkAttrs: netlink.LinkAttrs{
|
||||||
|
Name: name,
|
||||||
|
MTU: mtu},
|
||||||
|
PeerName: peerName,
|
||||||
|
}
|
||||||
|
if err := netlink.LinkAdd(veth); err != nil {
|
||||||
|
return nil, fmt.Errorf(`could not create veth pair %s-%s: %s`, name, peerName, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup := func(format string, a ...interface{}) (*netlink.Veth, error) {
|
||||||
|
netlink.LinkDel(veth)
|
||||||
|
return nil, fmt.Errorf(format, a...)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch bridgeType := DetectBridgeType(bridgeName, DatapathName); bridgeType {
|
||||||
|
case Bridge, BridgedFastdp:
|
||||||
|
if err := netlink.LinkSetMasterByIndex(veth, bridge.Attrs().Index); err != nil {
|
||||||
|
return cleanup(`unable to set master of %s: %s`, name, err)
|
||||||
|
}
|
||||||
|
if bridgeType == Bridge && !keepTXOn {
|
||||||
|
if err := EthtoolTXOff(peerName); err != nil {
|
||||||
|
return cleanup(`unable to set tx off on %q: %s`, peerName, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case Fastdp:
|
||||||
|
if err := odp.AddDatapathInterface(bridgeName, name); err != nil {
|
||||||
|
return cleanup(`failed to attach %s to device "%s": %s`, name, bridgeName, err)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return cleanup(`invalid bridge configuration`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if init != nil {
|
||||||
|
peer, err := netlink.LinkByName(peerName)
|
||||||
|
if err != nil {
|
||||||
|
return cleanup("unable to find peer veth %s: %s", peerName, err)
|
||||||
|
}
|
||||||
|
if err := init(peer); err != nil {
|
||||||
|
return cleanup("initializing veth: %s", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.LinkSetUp(veth); err != nil {
|
||||||
|
return cleanup("unable to bring veth up: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return veth, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddAddresses(link netlink.Link, cidrs []*net.IPNet) (newAddrs []*net.IPNet, err error) {
|
||||||
|
existingAddrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to get IP address for %q: %v", link.Attrs().Name, err)
|
||||||
|
}
|
||||||
|
for _, ipnet := range cidrs {
|
||||||
|
if contains(existingAddrs, ipnet) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := netlink.AddrAdd(link, &netlink.Addr{IPNet: ipnet}); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to add IP address to %q: %v", link.Attrs().Name, err)
|
||||||
|
}
|
||||||
|
newAddrs = append(newAddrs, ipnet)
|
||||||
|
}
|
||||||
|
return newAddrs, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func contains(addrs []netlink.Addr, addr *net.IPNet) bool {
|
||||||
|
for _, x := range addrs {
|
||||||
|
if addr.IP.Equal(x.IPNet.IP) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
VethName = "ethwe" // name inside container namespace
|
||||||
|
vethPrefix = "v" + VethName // starts with "veth" to suppress UI notifications
|
||||||
|
)
|
||||||
|
|
||||||
|
func interfaceExistsInNamespace(netNSPath string, ifName string) bool {
|
||||||
|
_, err := WithNetNS(netNSPath, "check-iface", ifName)
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NB: This function can be used only by a process that terminates immediately
|
||||||
|
// after calling the function as it changes netns via WithNetNSLinkUnsafe.
|
||||||
|
func AttachContainer(netNSPath, id, ifName, bridgeName string, mtu int, withMulticastRoute bool, cidrs []*net.IPNet, keepTXOn bool) error {
|
||||||
|
ns, err := netns.GetFromPath(netNSPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer ns.Close()
|
||||||
|
|
||||||
|
ipt, err := iptables.New()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !interfaceExistsInNamespace(netNSPath, ifName) {
|
||||||
|
maxIDLen := IFNAMSIZ - 1 - len(vethPrefix+"pl")
|
||||||
|
if len(id) > maxIDLen {
|
||||||
|
id = id[:maxIDLen] // trim passed ID if too long
|
||||||
|
}
|
||||||
|
name, peerName := vethPrefix+"pl"+id, vethPrefix+"pg"+id
|
||||||
|
_, err := CreateAndAttachVeth(name, peerName, bridgeName, mtu, keepTXOn, func(veth netlink.Link) error {
|
||||||
|
if err := netlink.LinkSetNsFd(veth, int(ns)); err != nil {
|
||||||
|
return fmt.Errorf("failed to move veth to container netns: %s", err)
|
||||||
|
}
|
||||||
|
if _, err := WithNetNS(netNSPath, "setup-iface", peerName, ifName); err != nil {
|
||||||
|
return fmt.Errorf("error setting up interface: %s", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := WithNetNSLinkUnsafe(ns, ifName, func(veth netlink.Link) error {
|
||||||
|
newAddresses, err := AddAddresses(veth, cidrs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add multicast ACCEPT rules for new subnets
|
||||||
|
for _, ipnet := range newAddresses {
|
||||||
|
acceptRule := []string{"-i", ifName, "-s", subnet(ipnet), "-d", "224.0.0.0/4", "-j", "ACCEPT"}
|
||||||
|
exists, err := ipt.Exists("filter", "INPUT", acceptRule...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
if err := ipt.Insert("filter", "INPUT", 1, acceptRule...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := netlink.LinkSetUp(veth); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, ipnet := range newAddresses {
|
||||||
|
// If we don't wait for a bit here, we see the arp fail to reach the bridge.
|
||||||
|
time.Sleep(1 * time.Millisecond)
|
||||||
|
arping.GratuitousArpOverIfaceByName(ipnet.IP, ifName)
|
||||||
|
}
|
||||||
|
if withMulticastRoute {
|
||||||
|
/* Route multicast packets across the weave network.
|
||||||
|
This must come last in 'attach'. If you change this, change weavewait to match.
|
||||||
|
|
||||||
|
TODO: Add the MTU lock to prevent PMTU discovery for multicast
|
||||||
|
destinations. Without that, the kernel sets the DF flag on
|
||||||
|
multicast packets. Since RFC1122 prohibits sending of ICMP
|
||||||
|
errors for packets with multicast destinations, that causes
|
||||||
|
packets larger than the PMTU to be dropped silently. */
|
||||||
|
|
||||||
|
_, multicast, _ := net.ParseCIDR("224.0.0.0/4")
|
||||||
|
if err := AddRoute(veth, netlink.SCOPE_LINK, multicast, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NB: This function can be used only by a process that terminates immediately
|
||||||
|
// after calling the function as it changes netns via WithNetNSLinkUnsafe.
|
||||||
|
func DetachContainer(netNSPath, id, ifName string, cidrs []*net.IPNet) error {
|
||||||
|
ns, err := netns.GetFromPath(netNSPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer ns.Close()
|
||||||
|
|
||||||
|
ipt, err := iptables.New()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return WithNetNSLinkUnsafe(ns, ifName, func(veth netlink.Link) error {
|
||||||
|
existingAddrs, err := netlink.AddrList(veth, netlink.FAMILY_V4)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get IP address for %q: %v", veth.Attrs().Name, err)
|
||||||
|
}
|
||||||
|
for _, ipnet := range cidrs {
|
||||||
|
if !contains(existingAddrs, ipnet) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := netlink.AddrDel(veth, &netlink.Addr{IPNet: ipnet}); err != nil {
|
||||||
|
return fmt.Errorf("failed to remove IP address from %q: %v", veth.Attrs().Name, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addrs, err := netlink.AddrList(veth, netlink.FAMILY_V4)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to get IP address for %q: %v", veth.Attrs().Name, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove multicast ACCEPT rules for subnets we no longer have addresses in
|
||||||
|
subnets := subnets(addrs)
|
||||||
|
rules, err := ipt.List("filter", "INPUT")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, rule := range rules {
|
||||||
|
ps := strings.Split(rule, " ")
|
||||||
|
if len(ps) == 10 &&
|
||||||
|
ps[0] == "-A" && ps[2] == "-s" && ps[4] == "-d" && ps[5] == "224.0.0.0/4" &&
|
||||||
|
ps[6] == "-i" && ps[7] == ifName && ps[8] == "-j" && ps[9] == "ACCEPT" {
|
||||||
|
|
||||||
|
if _, found := subnets[ps[3]]; !found {
|
||||||
|
if err := ipt.Delete("filter", "INPUT", ps[2:]...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(addrs) == 0 { // all addresses gone: remove the interface
|
||||||
|
if err := ipt.Delete("filter", "INPUT", "-i", ifName, "-d", "224.0.0.0/4", "-j", "DROP"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := netlink.LinkDel(veth); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func subnet(ipn *net.IPNet) string {
|
||||||
|
ones, _ := ipn.Mask.Size()
|
||||||
|
return fmt.Sprintf("%s/%d", ipn.IP.Mask(ipn.Mask).String(), ones)
|
||||||
|
}
|
||||||
|
|
||||||
|
func subnets(addrs []netlink.Addr) map[string]struct{} {
|
||||||
|
subnets := make(map[string]struct{})
|
||||||
|
for _, addr := range addrs {
|
||||||
|
subnets[subnet(addr.IPNet)] = struct{}{}
|
||||||
|
}
|
||||||
|
return subnets
|
||||||
|
}
|
||||||
+114
@@ -0,0 +1,114 @@
|
|||||||
|
package npc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"k8s.io/client-go/pkg/api"
|
||||||
|
extnapi "k8s.io/client-go/pkg/apis/extensions/v1beta1"
|
||||||
|
"k8s.io/client-go/pkg/util/intstr"
|
||||||
|
|
||||||
|
"github.com/weaveworks/weave/npc/ipset"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (ns *ns) analysePolicy(policy *extnapi.NetworkPolicy) (
|
||||||
|
rules map[string]*ruleSpec,
|
||||||
|
nsSelectors, podSelectors map[string]*selectorSpec,
|
||||||
|
err error) {
|
||||||
|
|
||||||
|
nsSelectors = make(map[string]*selectorSpec)
|
||||||
|
podSelectors = make(map[string]*selectorSpec)
|
||||||
|
rules = make(map[string]*ruleSpec)
|
||||||
|
|
||||||
|
dstSelector, err := newSelectorSpec(&policy.Spec.PodSelector, ns.name, ipset.HashIP)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
podSelectors[dstSelector.key] = dstSelector
|
||||||
|
|
||||||
|
for _, ingressRule := range policy.Spec.Ingress {
|
||||||
|
if ingressRule.Ports != nil && len(ingressRule.Ports) == 0 {
|
||||||
|
// Ports is empty, this rule matches no ports (no traffic matches).
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ingressRule.From != nil && len(ingressRule.From) == 0 {
|
||||||
|
// From is empty, this rule matches no sources (no traffic matches).
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if ingressRule.From == nil {
|
||||||
|
// From is not provided, this rule matches all sources (traffic not restricted by source).
|
||||||
|
if ingressRule.Ports == nil {
|
||||||
|
// Ports is not provided, this rule matches all ports (traffic not restricted by port).
|
||||||
|
rule := newRuleSpec(nil, nil, dstSelector, nil)
|
||||||
|
rules[rule.key] = rule
|
||||||
|
} else {
|
||||||
|
// Ports is present and contains at least one item, then this rule allows traffic
|
||||||
|
// only if the traffic matches at least one port in the ports list.
|
||||||
|
withNormalisedProtoAndPort(ingressRule.Ports, func(proto, port string) {
|
||||||
|
rule := newRuleSpec(&proto, nil, dstSelector, &port)
|
||||||
|
rules[rule.key] = rule
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// From is present and contains at least on item, this rule allows traffic only if the
|
||||||
|
// traffic matches at least one item in the from list.
|
||||||
|
for _, peer := range ingressRule.From {
|
||||||
|
var srcSelector *selectorSpec
|
||||||
|
if peer.PodSelector != nil {
|
||||||
|
srcSelector, err = newSelectorSpec(peer.PodSelector, ns.name, ipset.HashIP)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
podSelectors[srcSelector.key] = srcSelector
|
||||||
|
}
|
||||||
|
if peer.NamespaceSelector != nil {
|
||||||
|
srcSelector, err = newSelectorSpec(peer.NamespaceSelector, "", ipset.ListSet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
nsSelectors[srcSelector.key] = srcSelector
|
||||||
|
}
|
||||||
|
|
||||||
|
if ingressRule.Ports == nil {
|
||||||
|
// Ports is not provided, this rule matches all ports (traffic not restricted by port).
|
||||||
|
rule := newRuleSpec(nil, srcSelector, dstSelector, nil)
|
||||||
|
rules[rule.key] = rule
|
||||||
|
} else {
|
||||||
|
// Ports is present and contains at least one item, then this rule allows traffic
|
||||||
|
// only if the traffic matches at least one port in the ports list.
|
||||||
|
withNormalisedProtoAndPort(ingressRule.Ports, func(proto, port string) {
|
||||||
|
rule := newRuleSpec(&proto, srcSelector, dstSelector, &port)
|
||||||
|
rules[rule.key] = rule
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rules, nsSelectors, podSelectors, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func withNormalisedProtoAndPort(npps []extnapi.NetworkPolicyPort, f func(proto, port string)) {
|
||||||
|
for _, npp := range npps {
|
||||||
|
// If no proto is specified, default to TCP
|
||||||
|
proto := string(api.ProtocolTCP)
|
||||||
|
if npp.Protocol != nil {
|
||||||
|
proto = string(*npp.Protocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no port is specified, match any port. Let iptables executable handle
|
||||||
|
// service name resolution
|
||||||
|
port := "0:65535"
|
||||||
|
if npp.Port != nil {
|
||||||
|
switch npp.Port.Type {
|
||||||
|
case intstr.Int:
|
||||||
|
port = fmt.Sprintf("%d", npp.Port.IntVal)
|
||||||
|
case intstr.String:
|
||||||
|
port = npp.Port.StrVal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
f(proto, port)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
package npc
|
||||||
|
|
||||||
|
// These types are defined in https://github.com/kubernetes/kubernetes/blob/master/docs/proposals/network-policy.md
|
||||||
|
// but not in the k8s API yet. Copies are included here for decoding the `net.beta.kubernetes.io/network-policy`
|
||||||
|
// annotation specified in the above document.
|
||||||
|
|
||||||
|
type IngressIsolationPolicy string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Deny all ingress traffic to pods in this namespace. Ingress means
|
||||||
|
// any incoming traffic to pods, whether that be from other pods within this namespace
|
||||||
|
// or any source outside of this namespace.
|
||||||
|
DefaultDeny IngressIsolationPolicy = "DefaultDeny"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Standard NamespaceSpec object, modified to include a new
|
||||||
|
// NamespaceNetworkPolicy field.
|
||||||
|
type NamespaceSpec struct {
|
||||||
|
// This is a pointer so that it can be left undefined.
|
||||||
|
NetworkPolicy *NamespaceNetworkPolicy `json:"networkPolicy,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type NamespaceNetworkPolicy struct {
|
||||||
|
// Ingress configuration for this namespace. This config is
|
||||||
|
// applied to all pods within this namespace. For now, only
|
||||||
|
// ingress is supported. This field is optional - if not
|
||||||
|
// defined, then the cluster default for ingress is applied.
|
||||||
|
Ingress *NamespaceIngressPolicy `json:"ingress,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Configuration for ingress to pods within this namespace.
|
||||||
|
// For now, this only supports specifying an isolation policy.
|
||||||
|
type NamespaceIngressPolicy struct {
|
||||||
|
// The isolation policy to apply to pods in this namespace.
|
||||||
|
// Currently this field only supports "DefaultDeny", but could
|
||||||
|
// be extended to support other policies in the future. When set to DefaultDeny,
|
||||||
|
// pods in this namespace are denied ingress traffic by default. When not defined,
|
||||||
|
// the cluster default ingress isolation policy is applied (currently allow all).
|
||||||
|
Isolation *IngressIsolationPolicy `json:"isolation,omitempty"`
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package npc
|
||||||
|
|
||||||
|
const (
|
||||||
|
TableFilter = "filter"
|
||||||
|
|
||||||
|
MainChain = "WEAVE-NPC"
|
||||||
|
DefaultChain = "WEAVE-NPC-DEFAULT"
|
||||||
|
IngressChain = "WEAVE-NPC-INGRESS"
|
||||||
|
)
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
package npc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sync"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/coreos/go-iptables/iptables"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
coreapi "k8s.io/client-go/pkg/api/v1"
|
||||||
|
extnapi "k8s.io/client-go/pkg/apis/extensions/v1beta1"
|
||||||
|
|
||||||
|
"github.com/weaveworks/weave/npc/ipset"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NetworkPolicyController interface {
|
||||||
|
AddNamespace(ns *coreapi.Namespace) error
|
||||||
|
UpdateNamespace(oldObj, newObj *coreapi.Namespace) error
|
||||||
|
DeleteNamespace(ns *coreapi.Namespace) error
|
||||||
|
|
||||||
|
AddPod(obj *coreapi.Pod) error
|
||||||
|
UpdatePod(oldObj, newObj *coreapi.Pod) error
|
||||||
|
DeletePod(obj *coreapi.Pod) error
|
||||||
|
|
||||||
|
AddNetworkPolicy(obj *extnapi.NetworkPolicy) error
|
||||||
|
UpdateNetworkPolicy(oldObj, newObj *extnapi.NetworkPolicy) error
|
||||||
|
DeleteNetworkPolicy(obj *extnapi.NetworkPolicy) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type controller struct {
|
||||||
|
sync.Mutex
|
||||||
|
|
||||||
|
ipt *iptables.IPTables
|
||||||
|
ips ipset.Interface
|
||||||
|
|
||||||
|
nss map[string]*ns // ns name -> ns struct
|
||||||
|
nsSelectors *selectorSet // selector string -> nsSelector
|
||||||
|
}
|
||||||
|
|
||||||
|
func New(ipt *iptables.IPTables, ips ipset.Interface) NetworkPolicyController {
|
||||||
|
c := &controller{
|
||||||
|
ipt: ipt,
|
||||||
|
ips: ips,
|
||||||
|
nss: make(map[string]*ns)}
|
||||||
|
|
||||||
|
c.nsSelectors = newSelectorSet(ips, c.onNewNsSelector)
|
||||||
|
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
|
||||||
|
func (npc *controller) onNewNsSelector(selector *selector) error {
|
||||||
|
for _, ns := range npc.nss {
|
||||||
|
if ns.namespace != nil {
|
||||||
|
if selector.matches(ns.namespace.ObjectMeta.Labels) {
|
||||||
|
if err := ns.ips.AddEntry(selector.spec.ipsetName, string(ns.allPods.ipsetName)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (npc *controller) withNS(name string, f func(ns *ns) error) error {
|
||||||
|
ns, found := npc.nss[name]
|
||||||
|
if !found {
|
||||||
|
newNs, err := newNS(name, npc.ipt, npc.ips, npc.nsSelectors)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
npc.nss[name] = newNs
|
||||||
|
ns = newNs
|
||||||
|
}
|
||||||
|
if err := f(ns); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if ns.empty() {
|
||||||
|
if err := ns.destroy(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
delete(npc.nss, name)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (npc *controller) AddPod(obj *coreapi.Pod) error {
|
||||||
|
npc.Lock()
|
||||||
|
defer npc.Unlock()
|
||||||
|
|
||||||
|
log.Infof("EVENT AddPod %#v", obj)
|
||||||
|
return npc.withNS(obj.ObjectMeta.Namespace, func(ns *ns) error {
|
||||||
|
return errors.Wrap(ns.addPod(obj), "add pod")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (npc *controller) UpdatePod(oldObj, newObj *coreapi.Pod) error {
|
||||||
|
npc.Lock()
|
||||||
|
defer npc.Unlock()
|
||||||
|
|
||||||
|
log.Infof("EVENT UpdatePod %#v %#v", oldObj, newObj)
|
||||||
|
return npc.withNS(oldObj.ObjectMeta.Namespace, func(ns *ns) error {
|
||||||
|
return errors.Wrap(ns.updatePod(oldObj, newObj), "update pod")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (npc *controller) DeletePod(obj *coreapi.Pod) error {
|
||||||
|
npc.Lock()
|
||||||
|
defer npc.Unlock()
|
||||||
|
|
||||||
|
log.Infof("EVENT DeletePod %#v", obj)
|
||||||
|
return npc.withNS(obj.ObjectMeta.Namespace, func(ns *ns) error {
|
||||||
|
return errors.Wrap(ns.deletePod(obj), "delete pod")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (npc *controller) AddNetworkPolicy(obj *extnapi.NetworkPolicy) error {
|
||||||
|
npc.Lock()
|
||||||
|
defer npc.Unlock()
|
||||||
|
|
||||||
|
log.Infof("EVENT AddNetworkPolicy %#v", obj)
|
||||||
|
return npc.withNS(obj.ObjectMeta.Namespace, func(ns *ns) error {
|
||||||
|
return errors.Wrap(ns.addNetworkPolicy(obj), "add network policy")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (npc *controller) UpdateNetworkPolicy(oldObj, newObj *extnapi.NetworkPolicy) error {
|
||||||
|
npc.Lock()
|
||||||
|
defer npc.Unlock()
|
||||||
|
|
||||||
|
log.Infof("EVENT UpdateNetworkPolicy %#v %#v", oldObj, newObj)
|
||||||
|
return npc.withNS(oldObj.ObjectMeta.Namespace, func(ns *ns) error {
|
||||||
|
return errors.Wrap(ns.updateNetworkPolicy(oldObj, newObj), "update network policy")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (npc *controller) DeleteNetworkPolicy(obj *extnapi.NetworkPolicy) error {
|
||||||
|
npc.Lock()
|
||||||
|
defer npc.Unlock()
|
||||||
|
|
||||||
|
log.Infof("EVENT DeleteNetworkPolicy %#v", obj)
|
||||||
|
return npc.withNS(obj.ObjectMeta.Namespace, func(ns *ns) error {
|
||||||
|
return errors.Wrap(ns.deleteNetworkPolicy(obj), "delete network policy")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (npc *controller) AddNamespace(obj *coreapi.Namespace) error {
|
||||||
|
npc.Lock()
|
||||||
|
defer npc.Unlock()
|
||||||
|
|
||||||
|
log.Infof("EVENT AddNamespace %#v", obj)
|
||||||
|
return npc.withNS(obj.ObjectMeta.Name, func(ns *ns) error {
|
||||||
|
return errors.Wrap(ns.addNamespace(obj), "add namespace")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (npc *controller) UpdateNamespace(oldObj, newObj *coreapi.Namespace) error {
|
||||||
|
npc.Lock()
|
||||||
|
defer npc.Unlock()
|
||||||
|
|
||||||
|
log.Infof("EVENT UpdateNamespace%#v %#v", oldObj, newObj)
|
||||||
|
return npc.withNS(oldObj.ObjectMeta.Name, func(ns *ns) error {
|
||||||
|
return errors.Wrap(ns.updateNamespace(oldObj, newObj), "update namespace")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (npc *controller) DeleteNamespace(obj *coreapi.Namespace) error {
|
||||||
|
npc.Lock()
|
||||||
|
defer npc.Unlock()
|
||||||
|
|
||||||
|
log.Infof("EVENT DeleteNamespace %#v", obj)
|
||||||
|
return npc.withNS(obj.ObjectMeta.Name, func(ns *ns) error {
|
||||||
|
return errors.Wrap(ns.deleteNamespace(obj), "delete namespace")
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package ipset
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Name string
|
||||||
|
|
||||||
|
type Type string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ListSet = Type("list:set")
|
||||||
|
HashIP = Type("hash:ip")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Interface interface {
|
||||||
|
Create(ipsetName Name, ipsetType Type) error
|
||||||
|
AddEntry(ipsetName Name, entry string) error
|
||||||
|
DelEntry(ipsetName Name, entry string) error
|
||||||
|
Flush(ipsetName Name) error
|
||||||
|
Destroy(ipsetName Name) error
|
||||||
|
|
||||||
|
FlushAll() error
|
||||||
|
DestroyAll() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type ipset struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func New() Interface {
|
||||||
|
return &ipset{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ipset) Create(ipsetName Name, ipsetType Type) error {
|
||||||
|
return doExec("create", string(ipsetName), string(ipsetType))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ipset) AddEntry(ipsetName Name, entry string) error {
|
||||||
|
return doExec("add", string(ipsetName), entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ipset) DelEntry(ipsetName Name, entry string) error {
|
||||||
|
return doExec("del", string(ipsetName), entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ipset) Flush(ipsetName Name) error {
|
||||||
|
return doExec("flush", string(ipsetName))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ipset) FlushAll() error {
|
||||||
|
return doExec("flush")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ipset) Destroy(ipsetName Name) error {
|
||||||
|
return doExec("destroy", string(ipsetName))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *ipset) DestroyAll() error {
|
||||||
|
return doExec("destroy")
|
||||||
|
}
|
||||||
|
|
||||||
|
func doExec(args ...string) error {
|
||||||
|
if output, err := exec.Command("ipset", args...).CombinedOutput(); err != nil {
|
||||||
|
return errors.Wrapf(err, "ipset %v failed: %s", args, output)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
package metrics
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/google/gopacket"
|
||||||
|
"github.com/google/gopacket/layers"
|
||||||
|
"github.com/google/gopacket/pcapgo"
|
||||||
|
"github.com/prometheus/client_golang/prometheus"
|
||||||
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
blockedConnections = prometheus.NewCounterVec(
|
||||||
|
prometheus.CounterOpts{
|
||||||
|
Name: "weavenpc_blocked_connections_total",
|
||||||
|
Help: "Connection attempts blocked by policy controller.",
|
||||||
|
},
|
||||||
|
[]string{"protocol", "dport"},
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
func gatherMetrics() {
|
||||||
|
pipe, err := os.Open("/var/log/ulogd.pcap")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to open pcap: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
reader, err := pcapgo.NewReader(pipe)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to read pcap header: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for {
|
||||||
|
data, _, err := reader.ReadPacketData()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to read pcap packet: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
packet := gopacket.NewPacket(data, layers.LayerTypeIPv4, gopacket.Default)
|
||||||
|
|
||||||
|
if tcpLayer := packet.Layer(layers.LayerTypeTCP); tcpLayer != nil {
|
||||||
|
tcp, _ := tcpLayer.(*layers.TCP)
|
||||||
|
if tcp.SYN && !tcp.ACK { // Only plain SYN constitutes a NEW TCP connection
|
||||||
|
blockedConnections.With(prometheus.Labels{"protocol": "tcp", "dport": strconv.Itoa(int(tcp.DstPort))}).Inc()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if udpLayer := packet.Layer(layers.LayerTypeUDP); udpLayer != nil {
|
||||||
|
udp, _ := udpLayer.(*layers.UDP)
|
||||||
|
blockedConnections.With(prometheus.Labels{"protocol": "udp", "dport": strconv.Itoa(int(udp.DstPort))}).Inc()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Start(addr string) error {
|
||||||
|
if err := prometheus.Register(blockedConnections); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
http.Handle("/metrics", promhttp.Handler())
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
log.Infof("Serving /metrics on %s", addr)
|
||||||
|
if err := http.ListenAndServe(addr, nil); err != nil {
|
||||||
|
log.Fatalf("Failed to bind metrics server: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go gatherMetrics()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,337 @@
|
|||||||
|
package npc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/coreos/go-iptables/iptables"
|
||||||
|
"k8s.io/client-go/pkg/api/unversioned"
|
||||||
|
coreapi "k8s.io/client-go/pkg/api/v1"
|
||||||
|
extnapi "k8s.io/client-go/pkg/apis/extensions/v1beta1"
|
||||||
|
"k8s.io/client-go/pkg/types"
|
||||||
|
"k8s.io/client-go/pkg/util/uuid"
|
||||||
|
|
||||||
|
"github.com/weaveworks/weave/npc/ipset"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ns struct {
|
||||||
|
ipt *iptables.IPTables // interface to iptables
|
||||||
|
ips ipset.Interface // interface to ipset
|
||||||
|
|
||||||
|
name string // k8s Namespace name
|
||||||
|
namespace *coreapi.Namespace // k8s Namespace object
|
||||||
|
pods map[types.UID]*coreapi.Pod // k8s Pod objects by UID
|
||||||
|
policies map[types.UID]*extnapi.NetworkPolicy // k8s NetworkPolicy objects by UID
|
||||||
|
|
||||||
|
uid types.UID // surrogate UID to own allPods selector
|
||||||
|
allPods *selectorSpec // hash:ip ipset of all pod IPs in this namespace
|
||||||
|
|
||||||
|
nsSelectors *selectorSet
|
||||||
|
podSelectors *selectorSet
|
||||||
|
rules *ruleSet
|
||||||
|
}
|
||||||
|
|
||||||
|
func newNS(name string, ipt *iptables.IPTables, ips ipset.Interface, nsSelectors *selectorSet) (*ns, error) {
|
||||||
|
allPods, err := newSelectorSpec(&unversioned.LabelSelector{}, name, ipset.HashIP)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ns := &ns{
|
||||||
|
ipt: ipt,
|
||||||
|
ips: ips,
|
||||||
|
name: name,
|
||||||
|
pods: make(map[types.UID]*coreapi.Pod),
|
||||||
|
policies: make(map[types.UID]*extnapi.NetworkPolicy),
|
||||||
|
uid: uuid.NewUUID(),
|
||||||
|
allPods: allPods,
|
||||||
|
nsSelectors: nsSelectors,
|
||||||
|
rules: newRuleSet(ipt)}
|
||||||
|
|
||||||
|
ns.podSelectors = newSelectorSet(ips, ns.onNewPodSelector)
|
||||||
|
|
||||||
|
if err := ns.podSelectors.provision(ns.uid, nil, map[string]*selectorSpec{ns.allPods.key: ns.allPods}); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return ns, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *ns) empty() bool {
|
||||||
|
return len(ns.pods) == 0 && len(ns.policies) == 0 && ns.namespace == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *ns) destroy() error {
|
||||||
|
if err := ns.podSelectors.deprovision(ns.uid, map[string]*selectorSpec{ns.allPods.key: ns.allPods}, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *ns) onNewPodSelector(selector *selector) error {
|
||||||
|
for _, pod := range ns.pods {
|
||||||
|
if hasIP(pod) {
|
||||||
|
if selector.matches(pod.ObjectMeta.Labels) {
|
||||||
|
if err := selector.addEntry(pod.Status.PodIP); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *ns) addPod(obj *coreapi.Pod) error {
|
||||||
|
ns.pods[obj.ObjectMeta.UID] = obj
|
||||||
|
|
||||||
|
if !hasIP(obj) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ns.podSelectors.addToMatching(obj.ObjectMeta.Labels, obj.Status.PodIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *ns) updatePod(oldObj, newObj *coreapi.Pod) error {
|
||||||
|
delete(ns.pods, oldObj.ObjectMeta.UID)
|
||||||
|
ns.pods[newObj.ObjectMeta.UID] = newObj
|
||||||
|
|
||||||
|
if !hasIP(oldObj) && !hasIP(newObj) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if hasIP(oldObj) && !hasIP(newObj) {
|
||||||
|
return ns.podSelectors.delFromMatching(oldObj.ObjectMeta.Labels, oldObj.Status.PodIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !hasIP(oldObj) && hasIP(newObj) {
|
||||||
|
return ns.podSelectors.addToMatching(newObj.ObjectMeta.Labels, newObj.Status.PodIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !equals(oldObj.ObjectMeta.Labels, newObj.ObjectMeta.Labels) ||
|
||||||
|
oldObj.Status.PodIP != newObj.Status.PodIP {
|
||||||
|
|
||||||
|
for _, ps := range ns.podSelectors.entries {
|
||||||
|
oldMatch := ps.matches(oldObj.ObjectMeta.Labels)
|
||||||
|
newMatch := ps.matches(newObj.ObjectMeta.Labels)
|
||||||
|
if oldMatch == newMatch && oldObj.Status.PodIP == newObj.Status.PodIP {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if oldMatch {
|
||||||
|
if err := ps.delEntry(oldObj.Status.PodIP); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if newMatch {
|
||||||
|
if err := ps.addEntry(newObj.Status.PodIP); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *ns) deletePod(obj *coreapi.Pod) error {
|
||||||
|
delete(ns.pods, obj.ObjectMeta.UID)
|
||||||
|
|
||||||
|
if !hasIP(obj) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ns.podSelectors.delFromMatching(obj.ObjectMeta.Labels, obj.Status.PodIP)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *ns) addNetworkPolicy(obj *extnapi.NetworkPolicy) error {
|
||||||
|
ns.policies[obj.ObjectMeta.UID] = obj
|
||||||
|
|
||||||
|
// Analyse policy, determine which rules and ipsets are required
|
||||||
|
rules, nsSelectors, podSelectors, err := ns.analysePolicy(obj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Provision required resources in dependency order
|
||||||
|
if err := ns.nsSelectors.provision(obj.ObjectMeta.UID, nil, nsSelectors); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ns.podSelectors.provision(obj.ObjectMeta.UID, nil, podSelectors); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ns.rules.provision(obj.ObjectMeta.UID, nil, rules); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *ns) updateNetworkPolicy(oldObj, newObj *extnapi.NetworkPolicy) error {
|
||||||
|
delete(ns.policies, oldObj.ObjectMeta.UID)
|
||||||
|
ns.policies[newObj.ObjectMeta.UID] = newObj
|
||||||
|
|
||||||
|
// Analyse the old and the new policy so we can determine differences
|
||||||
|
oldRules, oldNsSelectors, oldPodSelectors, err := ns.analysePolicy(oldObj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
newRules, newNsSelectors, newPodSelectors, err := ns.analysePolicy(newObj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprovision unused and provision newly required resources in dependency order
|
||||||
|
if err := ns.rules.deprovision(oldObj.ObjectMeta.UID, oldRules, newRules); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ns.nsSelectors.deprovision(oldObj.ObjectMeta.UID, oldNsSelectors, newNsSelectors); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ns.podSelectors.deprovision(oldObj.ObjectMeta.UID, oldPodSelectors, newPodSelectors); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ns.nsSelectors.provision(oldObj.ObjectMeta.UID, oldNsSelectors, newNsSelectors); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ns.podSelectors.provision(oldObj.ObjectMeta.UID, oldPodSelectors, newNsSelectors); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ns.rules.provision(oldObj.ObjectMeta.UID, oldRules, newRules); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *ns) deleteNetworkPolicy(obj *extnapi.NetworkPolicy) error {
|
||||||
|
delete(ns.policies, obj.ObjectMeta.UID)
|
||||||
|
|
||||||
|
// Analyse network policy to free resources
|
||||||
|
rules, nsSelectors, podSelectors, err := ns.analysePolicy(obj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deprovision unused resources in dependency order
|
||||||
|
if err := ns.rules.deprovision(obj.ObjectMeta.UID, rules, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ns.nsSelectors.deprovision(obj.ObjectMeta.UID, nsSelectors, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ns.podSelectors.deprovision(obj.ObjectMeta.UID, podSelectors, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func bypassRule(nsIpsetName ipset.Name) []string {
|
||||||
|
return []string{"-m", "set", "--match-set", string(nsIpsetName), "dst", "-j", "ACCEPT"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *ns) ensureBypassRule(nsIpsetName ipset.Name) error {
|
||||||
|
return ns.ipt.Append(TableFilter, DefaultChain, bypassRule(ns.allPods.ipsetName)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *ns) deleteBypassRule(nsIpsetName ipset.Name) error {
|
||||||
|
return ns.ipt.Delete(TableFilter, DefaultChain, bypassRule(ns.allPods.ipsetName)...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *ns) addNamespace(obj *coreapi.Namespace) error {
|
||||||
|
ns.namespace = obj
|
||||||
|
|
||||||
|
// Insert a rule to bypass policies if namespace is DefaultAllow
|
||||||
|
if !isDefaultDeny(obj) {
|
||||||
|
return ns.ensureBypassRule(ns.allPods.ipsetName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add namespace ipset to matching namespace selectors
|
||||||
|
return ns.nsSelectors.addToMatching(obj.ObjectMeta.Labels, string(ns.allPods.ipsetName))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *ns) updateNamespace(oldObj, newObj *coreapi.Namespace) error {
|
||||||
|
ns.namespace = newObj
|
||||||
|
|
||||||
|
// Update bypass rule if ingress default has changed
|
||||||
|
oldDefaultDeny := isDefaultDeny(oldObj)
|
||||||
|
newDefaultDeny := isDefaultDeny(newObj)
|
||||||
|
|
||||||
|
if oldDefaultDeny != newDefaultDeny {
|
||||||
|
if oldDefaultDeny {
|
||||||
|
return ns.ensureBypassRule(ns.allPods.ipsetName)
|
||||||
|
}
|
||||||
|
if newDefaultDeny {
|
||||||
|
return ns.deleteBypassRule(ns.allPods.ipsetName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Re-evaluate namespace selector membership if labels have changed
|
||||||
|
if !equals(oldObj.ObjectMeta.Labels, newObj.ObjectMeta.Labels) {
|
||||||
|
for _, selector := range ns.nsSelectors.entries {
|
||||||
|
oldMatch := selector.matches(oldObj.ObjectMeta.Labels)
|
||||||
|
newMatch := selector.matches(newObj.ObjectMeta.Labels)
|
||||||
|
if oldMatch == newMatch {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if oldMatch {
|
||||||
|
if err := selector.delEntry(string(ns.allPods.ipsetName)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if newMatch {
|
||||||
|
if err := selector.addEntry(string(ns.allPods.ipsetName)); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ns *ns) deleteNamespace(obj *coreapi.Namespace) error {
|
||||||
|
ns.namespace = nil
|
||||||
|
|
||||||
|
// Remove bypass rule
|
||||||
|
if !isDefaultDeny(obj) {
|
||||||
|
return ns.deleteBypassRule(ns.allPods.ipsetName)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove namespace ipset from any matching namespace selectors
|
||||||
|
return ns.nsSelectors.delFromMatching(obj.ObjectMeta.Labels, string(ns.allPods.ipsetName))
|
||||||
|
}
|
||||||
|
|
||||||
|
func hasIP(pod *coreapi.Pod) bool {
|
||||||
|
// Ensure pod has an IP address and isn't sharing the host network namespace
|
||||||
|
return len(pod.Status.PodIP) > 0 && !pod.Spec.HostNetwork
|
||||||
|
}
|
||||||
|
|
||||||
|
func equals(a, b map[string]string) bool {
|
||||||
|
if len(a) != len(b) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for ak, av := range a {
|
||||||
|
if b[ak] != av {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func isDefaultDeny(namespace *coreapi.Namespace) bool {
|
||||||
|
nnpJson, found := namespace.ObjectMeta.Annotations["net.beta.kubernetes.io/network-policy"]
|
||||||
|
if !found {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var nnp NamespaceNetworkPolicy
|
||||||
|
if err := json.Unmarshal([]byte(nnpJson), &nnp); err != nil {
|
||||||
|
log.Warn("Ignoring network policy annotation: unmarshal failed:", err)
|
||||||
|
// If we can't understand the annotation, behave as if it isn't present
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return nnp.Ingress != nil &&
|
||||||
|
nnp.Ingress.Isolation != nil &&
|
||||||
|
*(nnp.Ingress.Isolation) == DefaultDeny
|
||||||
|
}
|
||||||
+77
@@ -0,0 +1,77 @@
|
|||||||
|
package npc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"github.com/coreos/go-iptables/iptables"
|
||||||
|
"k8s.io/client-go/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ruleSpec struct {
|
||||||
|
key string
|
||||||
|
args []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRuleSpec(proto *string, srcHost *selectorSpec, dstHost *selectorSpec, dstPort *string) *ruleSpec {
|
||||||
|
args := []string{}
|
||||||
|
if proto != nil {
|
||||||
|
args = append(args, "-p", *proto)
|
||||||
|
}
|
||||||
|
if srcHost != nil {
|
||||||
|
args = append(args, "-m", "set", "--match-set", string(srcHost.ipsetName), "src")
|
||||||
|
}
|
||||||
|
if dstHost != nil {
|
||||||
|
args = append(args, "-m", "set", "--match-set", string(dstHost.ipsetName), "dst")
|
||||||
|
}
|
||||||
|
if dstPort != nil {
|
||||||
|
args = append(args, "--dport", *dstPort)
|
||||||
|
}
|
||||||
|
args = append(args, "-j", "ACCEPT")
|
||||||
|
key := strings.Join(args, " ")
|
||||||
|
|
||||||
|
return &ruleSpec{key, args}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ruleSet struct {
|
||||||
|
ipt *iptables.IPTables
|
||||||
|
users map[string]map[types.UID]struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newRuleSet(ipt *iptables.IPTables) *ruleSet {
|
||||||
|
return &ruleSet{ipt, make(map[string]map[types.UID]struct{})}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *ruleSet) deprovision(user types.UID, current, desired map[string]*ruleSpec) error {
|
||||||
|
for key, spec := range current {
|
||||||
|
if _, found := desired[key]; !found {
|
||||||
|
delete(rs.users[key], user)
|
||||||
|
if len(rs.users[key]) == 0 {
|
||||||
|
log.Infof("deleting rule: %v", spec.args)
|
||||||
|
if err := rs.ipt.Delete(TableFilter, IngressChain, spec.args...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
delete(rs.users, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *ruleSet) provision(user types.UID, current, desired map[string]*ruleSpec) error {
|
||||||
|
for key, spec := range desired {
|
||||||
|
if _, found := current[key]; !found {
|
||||||
|
if _, found := rs.users[key]; !found {
|
||||||
|
log.Infof("adding rule: %v", spec.args)
|
||||||
|
if err := rs.ipt.Append(TableFilter, IngressChain, spec.args...); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
rs.users[key] = make(map[types.UID]struct{})
|
||||||
|
}
|
||||||
|
rs.users[key][user] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
+130
@@ -0,0 +1,130 @@
|
|||||||
|
package npc
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"k8s.io/client-go/pkg/api/unversioned"
|
||||||
|
"k8s.io/client-go/pkg/labels"
|
||||||
|
"k8s.io/client-go/pkg/types"
|
||||||
|
|
||||||
|
"github.com/weaveworks/weave/npc/ipset"
|
||||||
|
)
|
||||||
|
|
||||||
|
type selectorSpec struct {
|
||||||
|
key string // string representation (for hash keying/equality comparison)
|
||||||
|
selector labels.Selector // k8s Selector object (for matching)
|
||||||
|
|
||||||
|
ipsetType ipset.Type // type of ipset to provision
|
||||||
|
ipsetName ipset.Name // generated ipset name
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSelectorSpec(json *unversioned.LabelSelector, nsName string, ipsetType ipset.Type) (*selectorSpec, error) {
|
||||||
|
selector, err := unversioned.LabelSelectorAsSelector(json)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
key := selector.String()
|
||||||
|
return &selectorSpec{
|
||||||
|
key: key,
|
||||||
|
selector: selector,
|
||||||
|
// We prefix the selector string with the namespace name when generating
|
||||||
|
// the shortname because you can specify the same selector in multiple
|
||||||
|
// namespaces - we need those to map to distinct ipsets
|
||||||
|
ipsetName: ipset.Name("weave-" + shortName(nsName+":"+key)),
|
||||||
|
ipsetType: ipsetType}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type selector struct {
|
||||||
|
ips ipset.Interface
|
||||||
|
spec *selectorSpec
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *selector) matches(labelMap map[string]string) bool {
|
||||||
|
return s.spec.selector.Matches(labels.Set(labelMap))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *selector) addEntry(entry string) error {
|
||||||
|
log.Infof("adding entry %s to %s", entry, s.spec.ipsetName)
|
||||||
|
return s.ips.AddEntry(s.spec.ipsetName, entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *selector) delEntry(entry string) error {
|
||||||
|
log.Infof("deleting entry %s from %s", entry, s.spec.ipsetName)
|
||||||
|
return s.ips.DelEntry(s.spec.ipsetName, entry)
|
||||||
|
}
|
||||||
|
|
||||||
|
type selectorFn func(selector *selector) error
|
||||||
|
|
||||||
|
type selectorSet struct {
|
||||||
|
ips ipset.Interface
|
||||||
|
onNewSelector selectorFn
|
||||||
|
users map[string]map[types.UID]struct{} // list of users per selector
|
||||||
|
entries map[string]*selector
|
||||||
|
}
|
||||||
|
|
||||||
|
func newSelectorSet(ips ipset.Interface, onNewSelector selectorFn) *selectorSet {
|
||||||
|
return &selectorSet{
|
||||||
|
ips: ips,
|
||||||
|
onNewSelector: onNewSelector,
|
||||||
|
users: make(map[string]map[types.UID]struct{}),
|
||||||
|
entries: make(map[string]*selector)}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *selectorSet) addToMatching(labelMap map[string]string, entry string) error {
|
||||||
|
for _, s := range ss.entries {
|
||||||
|
if s.matches(labelMap) {
|
||||||
|
if err := s.addEntry(entry); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *selectorSet) delFromMatching(labelMap map[string]string, entry string) error {
|
||||||
|
for _, s := range ss.entries {
|
||||||
|
if s.matches(labelMap) {
|
||||||
|
if err := s.delEntry(entry); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *selectorSet) deprovision(user types.UID, current, desired map[string]*selectorSpec) error {
|
||||||
|
for key, spec := range current {
|
||||||
|
if _, found := desired[key]; !found {
|
||||||
|
delete(ss.users[key], user)
|
||||||
|
if len(ss.users[key]) == 0 {
|
||||||
|
log.Infof("destroying ipset: %#v", spec)
|
||||||
|
if err := ss.ips.Destroy(spec.ipsetName); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
delete(ss.entries, key)
|
||||||
|
delete(ss.users, key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ss *selectorSet) provision(user types.UID, current, desired map[string]*selectorSpec) error {
|
||||||
|
for key, spec := range desired {
|
||||||
|
if _, found := current[key]; !found {
|
||||||
|
if _, found := ss.users[key]; !found {
|
||||||
|
log.Infof("creating ipset: %#v", spec)
|
||||||
|
if err := ss.ips.Create(spec.ipsetName, spec.ipsetType); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
selector := &selector{ss.ips, spec}
|
||||||
|
if err := ss.onNewSelector(selector); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ss.users[key] = make(map[types.UID]struct{})
|
||||||
|
ss.entries[key] = selector
|
||||||
|
}
|
||||||
|
ss.users[key][user] = struct{}{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
package npc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/sha1"
|
||||||
|
"math/big"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// This array:
|
||||||
|
// * Must include only printable UTF8 characters that are represented with a single byte
|
||||||
|
// * Must be at least of length 85 (`len("weave-") + l(2^160)/l(85)` equals 31, the maximum ipset name length)
|
||||||
|
// * Must not include commas as those are treated specially by `ipset add` when adding a named set to a list:set
|
||||||
|
// * Should not include space for readability
|
||||||
|
// * Should not include single quote or backslash to be nice to shell users
|
||||||
|
ShortNameSymbols = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789(){}[]<>_$%^&*|/?.;:@#~"
|
||||||
|
)
|
||||||
|
|
||||||
|
// sha1 hash an arbitrary string and represent it using the full range of
|
||||||
|
// printable ascii characters
|
||||||
|
func shortName(arbitrary string) string {
|
||||||
|
symbols := []byte(ShortNameSymbols)
|
||||||
|
sum := sha1.Sum([]byte(arbitrary))
|
||||||
|
i := big.NewInt(0).SetBytes(sum[:])
|
||||||
|
base := big.NewInt(int64(len(symbols)))
|
||||||
|
zero := big.NewInt(0)
|
||||||
|
result := make([]byte, 0)
|
||||||
|
|
||||||
|
for i.Cmp(zero) > 0 {
|
||||||
|
remainder := new(big.Int).Mod(i, base)
|
||||||
|
i.Sub(i, remainder)
|
||||||
|
i.Div(i, base)
|
||||||
|
result = append(result, symbols[remainder.Int64()])
|
||||||
|
}
|
||||||
|
|
||||||
|
return string(result)
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
package ulogd
|
||||||
|
|
||||||
|
import (
|
||||||
|
log "github.com/Sirupsen/logrus"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func waitForExit(cmd *exec.Cmd) {
|
||||||
|
if err := cmd.Wait(); err != nil {
|
||||||
|
log.Fatalf("ulogd terminated: %v", err)
|
||||||
|
}
|
||||||
|
log.Fatal("ulogd terminated normally")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Start() error {
|
||||||
|
cmd := exec.Command("/usr/sbin/ulogd", "-v")
|
||||||
|
stdout, err := cmd.StderrPipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
go io.Copy(os.Stdout, stdout)
|
||||||
|
go waitForExit(cmd)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
# Weave network driver extension for Docker and Kubernetes
|
||||||
|
|
||||||
|
This directory implements both a
|
||||||
|
[Docker plugin](http://docs.docker.com/engine/extend/plugin_api/) to
|
||||||
|
integrate [Weave Net](http://weave.works/net/) with Docker, and a
|
||||||
|
[Container Network Interface (CNI) plugin](https://github.com/appc/cni#cni---the-container-network-interface),
|
||||||
|
to integrate Weave Net with [Kubernetes](http://kubernetes.io/).
|
||||||
|
|
||||||
|
The Docker plugin runs automatically when you `weave launch`, provided your
|
||||||
|
Docker daemon is version 1.9 or newer.
|
||||||
|
|
||||||
|
More detail on the Docker plugin [here](https://www.weave.works/docs/net/latest/plugin/).
|
||||||
|
|
||||||
|
More detail on the CNI plugin [here](https://www.weave.works/docs/net/latest/cni-plugin/).
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
package ipamplugin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
|
||||||
|
"github.com/appc/cni/pkg/skel"
|
||||||
|
"github.com/appc/cni/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (i *Ipam) CmdAdd(args *skel.CmdArgs) error {
|
||||||
|
result, err := i.Allocate(args)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return result.Print()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Ipam) Allocate(args *skel.CmdArgs) (*types.Result, error) {
|
||||||
|
// extract the things we care about
|
||||||
|
conf, err := loadIPAMConf(args.StdinData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if conf == nil {
|
||||||
|
conf = &ipamConf{}
|
||||||
|
}
|
||||||
|
containerID := args.ContainerID
|
||||||
|
if containerID == "" {
|
||||||
|
return nil, fmt.Errorf("Weave CNI Allocate: blank container name")
|
||||||
|
}
|
||||||
|
var ipnet *net.IPNet
|
||||||
|
|
||||||
|
if conf.Subnet == "" {
|
||||||
|
ipnet, err = i.weave.AllocateIP(containerID)
|
||||||
|
} else {
|
||||||
|
var subnet *net.IPNet
|
||||||
|
subnet, err = types.ParseCIDR(conf.Subnet)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("subnet given in config, but not parseable: %s", err)
|
||||||
|
}
|
||||||
|
ipnet, err = i.weave.AllocateIPInSubnet(containerID, subnet)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
result := &types.Result{
|
||||||
|
IP4: &types.IPConfig{
|
||||||
|
IP: *ipnet,
|
||||||
|
Gateway: conf.Gateway,
|
||||||
|
Routes: conf.Routes,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Ipam) CmdDel(args *skel.CmdArgs) error {
|
||||||
|
return i.Release(args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Ipam) Release(args *skel.CmdArgs) error {
|
||||||
|
return i.weave.ReleaseIPsFor(args.ContainerID)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ipamConf struct {
|
||||||
|
Subnet string `json:"subnet,omitempty"`
|
||||||
|
Gateway net.IP `json:"gateway,omitempty"`
|
||||||
|
Routes []types.Route `json:"routes"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type netConf struct {
|
||||||
|
IPAM *ipamConf `json:"ipam"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadIPAMConf(stdinData []byte) (*ipamConf, error) {
|
||||||
|
var conf netConf
|
||||||
|
return conf.IPAM, json.Unmarshal(stdinData, &conf)
|
||||||
|
}
|
||||||
Loaded 100 of 380 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user