Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c7a235a54a | ||
|
|
bef50d3730 | ||
|
|
a46d18da0c | ||
|
|
95c020fbe0 | ||
|
|
7c2084b9af | ||
|
|
f78c4dacc5 | ||
|
|
9245b73913 | ||
|
|
6ee961b068 | ||
|
|
1f920513de | ||
|
|
15f21f1899 | ||
|
|
09a204b978 | ||
|
|
77fbb2c12b | ||
|
|
6ad3e69faf | ||
|
|
e2088ef401 | ||
|
|
0f3864968e | ||
|
|
2f235daa1c | ||
|
|
438a5f5fd1 | ||
|
|
b051ef0878 | ||
|
|
48015aa7be | ||
|
|
14730f8572 | ||
|
|
e7a24eaf0a | ||
|
|
15ad210bec | ||
|
|
6bc0e5e71c | ||
|
|
0ca831f50b | ||
|
|
57bca8e955 | ||
|
|
709f820ab6 | ||
|
|
599b99a6c4 | ||
|
|
4e3acfdc02 | ||
|
|
11915948a2 | ||
|
|
bb51a4f36d | ||
|
|
5d0e44bb6f | ||
|
|
6e7a469a62 | ||
|
|
ca0498b860 | ||
|
|
bbfa682083 | ||
|
|
f763c40c10 | ||
|
|
67037b43b0 | ||
|
|
861395011c | ||
|
|
8bdfcd3042 | ||
|
|
83024b946f | ||
|
|
4f48670345 | ||
|
|
4b30d97d4e | ||
|
|
c9e813cfcb | ||
|
|
c97c50b76e | ||
|
|
f919d06e2c | ||
|
|
526538d9d1 | ||
|
|
f43f3fc2e8 | ||
|
|
8b220f6383 | ||
|
|
d44e34584a | ||
|
|
6d2366ca4c | ||
|
|
7c1b615c98 | ||
|
|
390a4099d0 | ||
|
|
01237446eb | ||
|
|
18deb42df1 | ||
|
|
5c508f837e | ||
|
|
e000472af7 | ||
|
|
6d70284c21 | ||
|
|
4529d1587c | ||
|
|
b0d05f5e37 | ||
|
|
4a5d203e58 | ||
|
|
640c5c346c | ||
|
|
3780ce4dfb | ||
|
|
b871c1eecb | ||
|
|
02ecea4576 | ||
|
|
6876507641 | ||
|
|
d2c78d3196 | ||
|
|
5b8365687e | ||
|
|
a43bd88ddd | ||
|
|
e8fcd21baa | ||
|
|
6a48da60ec | ||
|
|
886dea9b10 | ||
|
|
80e9dee9b2 | ||
|
|
5110a604f8 | ||
|
|
404f1bbef3 | ||
|
|
56491b35f1 | ||
|
|
dd70668b89 | ||
|
|
e5441b44e9 | ||
|
|
4da2501d7a | ||
|
|
04680fc52c | ||
|
|
cb4a8769a8 | ||
|
|
62c1d28524 | ||
|
|
92b21ccd44 | ||
|
|
88cd86a5f2 | ||
|
|
b18c88524d | ||
|
|
90e0c43bf2 | ||
|
|
4215618177 | ||
|
|
cb5798f94d | ||
|
|
79e032c984 | ||
|
|
821b787774 | ||
|
|
3ed7894e39 | ||
|
|
30ed34bd2c | ||
|
|
f5c2f51964 | ||
|
|
4d7f0f5014 | ||
|
|
d1ed6c5747 | ||
|
|
44f91950cb | ||
|
|
d2f0287fa3 | ||
|
|
9c09e36135 | ||
|
|
cb8e06c994 | ||
|
|
753c7cce1e | ||
|
|
30b2bada28 | ||
|
|
eeec3833a3 | ||
|
|
ee47f0c3ee | ||
|
|
36a2146ed8 | ||
|
|
6afedb881c | ||
|
|
05ab1139db | ||
|
|
e90cbf290b | ||
|
|
1e0431f6b2 | ||
|
|
9971fc3dac | ||
|
|
7aa7e3c00d | ||
|
|
a4dd7fa198 | ||
|
|
e2157a5d04 | ||
|
|
fc199d0df5 | ||
|
|
cc6d10c9b1 | ||
|
|
ace2c12608 | ||
|
|
57c464d60e | ||
|
|
65b3894a8e | ||
|
|
77bfa6a7e4 | ||
|
|
9d485d0f21 | ||
|
|
991945081a | ||
|
|
123762ec8d | ||
|
|
b07db6479a | ||
|
|
d3f62905b7 | ||
|
|
22b3393cfd | ||
|
|
c8e7dbd600 | ||
|
|
ef26b306e4 | ||
|
|
1588a7259d | ||
|
|
3a92050ebb | ||
|
|
04c63879b8 | ||
|
|
96291d1de4 | ||
|
|
9090fddd9f | ||
|
|
5bcd33c930 | ||
|
|
959d0bf2e2 | ||
|
|
a547d4571c | ||
|
|
b05c5f63c8 | ||
|
|
309a85b29c | ||
|
|
90ccc4a014 | ||
|
|
a6f2ee5ccc | ||
|
|
dcadc0f8cc | ||
|
|
332808c524 | ||
|
|
db006060e6 | ||
|
|
7a6ba40982 | ||
|
|
75e106b667 | ||
|
|
b28e2ee384 | ||
|
|
785b6b4584 | ||
|
|
391613d015 | ||
|
|
7334869134 | ||
|
|
bb4f51499d | ||
|
|
cc88aa2145 | ||
|
|
4b5d827d94 | ||
|
|
802ede1cd3 | ||
|
|
5e69e8a7b4 | ||
|
|
2d912263b1 | ||
|
|
6e78a34a00 | ||
|
|
ac9d72875d | ||
|
|
51d7f455c9 | ||
|
|
89db74338e | ||
|
|
4c6879679d | ||
|
|
d35f2236e1 | ||
|
|
27848992b1 | ||
|
|
690b6ff2cd | ||
|
|
ea2084d871 | ||
|
|
8a4fc9c02a | ||
|
|
760bc10074 | ||
|
|
20d55fe47a | ||
|
|
4a1fbc7c38 | ||
|
|
11c55a3a07 | ||
|
|
a400d43df7 | ||
|
|
189cfa995c | ||
|
|
47d8f24e56 | ||
|
|
4f8fe2c13f | ||
|
|
873781ab58 | ||
|
|
06e0e6ace0 | ||
|
|
eb7ba691ee | ||
|
|
15b9b7416a | ||
|
|
5024270232 | ||
|
|
bd24dd7dae | ||
|
|
2c2a074a83 | ||
|
|
698e1b6d87 | ||
|
|
b248e021ec | ||
|
|
0d481ef0e9 | ||
|
|
0c2e046c86 | ||
|
|
21bc724632 | ||
|
|
16051a89b6 | ||
|
|
6130699ae5 | ||
|
|
e23e364476 | ||
|
|
8a5aa1a6ae | ||
|
|
9654df4d50 | ||
|
|
6f39a15b53 | ||
|
|
e8f35a848e | ||
|
|
43d3339b39 | ||
|
|
13644ee1b0 | ||
|
|
54d24e65ef | ||
|
|
b8bce1c28f | ||
|
|
e1e4a7887a | ||
|
|
9f0ac3dbe7 | ||
|
|
17b45d9efa | ||
|
|
27178b1c79 | ||
|
|
1bf07d86aa | ||
|
|
bba7b44248 | ||
|
|
d4d9ba1e35 | ||
|
|
f6c22ef9b5 | ||
|
|
040cb19fc5 | ||
|
|
928d08fb19 | ||
|
|
f82440f792 | ||
|
|
ad2dc562af | ||
|
|
ab1953e8ca | ||
|
|
a332af3146 | ||
|
|
5e271363cf | ||
|
|
d2a9a51296 | ||
|
|
99e360ce3b | ||
|
|
1c855f9bc1 | ||
|
|
6b6580413d | ||
|
|
583eea1ec5 | ||
|
|
81b17469f4 | ||
|
|
06669325a7 | ||
|
|
1c81fcb693 | ||
|
|
4a22dda13f | ||
|
|
6e3f8a1c7a | ||
|
|
eb77970534 | ||
|
|
f53dfdea61 | ||
|
|
6c58bd7815 | ||
|
|
83d9292a5b | ||
|
|
37bc04f851 | ||
|
|
a5161b28f5 | ||
|
|
c6cd5f53db | ||
|
|
29bbd321b4 | ||
|
|
ba469bd641 | ||
|
|
bc9939fcdc | ||
|
|
c48c746791 | ||
|
|
d3c5241f12 | ||
|
|
4d4584db2b | ||
|
|
427d694e1b | ||
|
|
f16d4561e0 | ||
|
|
f334575e69 | ||
|
|
ea423755cc | ||
|
|
db697fdaa5 | ||
|
|
c3aa03229c | ||
|
|
637bdf66b1 | ||
|
|
4a40380d0f | ||
|
|
df467be26e | ||
|
|
edba711283 | ||
|
|
1b01da45f5 | ||
|
|
bf9985b0c8 | ||
|
|
408e5f7bdd | ||
|
|
46177657b2 | ||
|
|
d351b9f020 | ||
|
|
727fe46b2a | ||
|
|
b10af5a6cb | ||
|
|
965b5c627e | ||
|
|
d00e7335bb | ||
|
|
e3eb16a1f9 | ||
|
|
078b76f3f1 | ||
|
|
6b2631e378 | ||
|
|
59c1abe9b9 | ||
|
|
4f9f0dfc3e | ||
|
|
8d44f49edd | ||
|
|
7a959df648 | ||
|
|
53fd33044a | ||
|
|
1806ca5665 | ||
|
|
7141ccc271 | ||
|
|
a73330f752 | ||
|
|
427e7a6868 | ||
|
|
b3cb38eda4 | ||
|
|
62758ca337 | ||
|
|
3c67c3c5f1 | ||
|
|
79f77f94f4 | ||
|
|
fc759e2df9 | ||
|
|
1fcd521f67 | ||
|
|
66bd443183 | ||
|
|
fa74e9f290 | ||
|
|
008eeb03ad | ||
|
|
41c48dc678 | ||
|
|
09be35dee0 | ||
|
|
c3ad641d94 | ||
|
|
bacb3952a3 | ||
|
|
70257a6c19 | ||
|
|
bfe9f00e55 | ||
|
|
591be2d08f | ||
|
|
c3ce052cc5 | ||
|
|
7dcd4bdf47 | ||
|
|
2d8373249a | ||
|
|
4979ce1f5f | ||
|
|
79d812a9c2 | ||
|
|
78e00f9a3a | ||
|
|
2ff0bbe150 | ||
|
|
a182bd4514 | ||
|
|
231d0c633e | ||
|
|
a6e1165e71 | ||
|
|
0e26b4621d | ||
|
|
fc211b15e9 | ||
|
|
acc24f1237 | ||
|
|
612e265123 | ||
|
|
9cf50588b5 | ||
|
|
6c2de466c0 | ||
|
|
6f26c732f1 | ||
|
|
ddfec13f17 | ||
|
|
b8b47787be | ||
|
|
4d1f968eb6 | ||
|
|
16a8988e41 | ||
|
|
c9f1d30f49 | ||
|
|
4b9c533b94 | ||
|
|
90907a7647 | ||
|
|
ad4e4238a5 | ||
|
|
40980aeb90 | ||
|
|
f68579654b | ||
|
|
9899780f35 | ||
|
|
dd5994c7cf | ||
|
|
e6ba59f029 | ||
|
|
e0359a32f2 | ||
|
|
a19d025677 | ||
|
|
528baedd99 | ||
|
|
e1579ecc1e | ||
|
|
c72e8bbd36 | ||
|
|
6d921802e2 | ||
|
|
7c78cdd45d | ||
|
|
6625e29071 | ||
|
|
d1d618ccb3 | ||
|
|
80e2ff0353 | ||
|
|
d5e864736b | ||
|
|
5b4040704b | ||
|
|
c85bc059eb | ||
|
|
8fb3da6fc8 | ||
|
|
55354900da | ||
|
|
c654640bde | ||
|
|
1814bbd2f2 | ||
|
|
374fed4699 | ||
|
|
cd48ec5959 | ||
|
|
aec8756151 | ||
|
|
c742ec4993 | ||
|
|
b8bb7d7696 | ||
|
|
8288da2240 | ||
|
|
dcaf77a900 | ||
|
|
e64656d16f | ||
|
|
e3231bb373 | ||
|
|
44e0c6add7 | ||
|
|
1a8794e90f | ||
|
|
e3576e13a7 | ||
|
|
b3e77e81ea | ||
|
|
b13eb5ada5 | ||
|
|
6337972c8a | ||
|
|
8620d0ae4f | ||
|
|
fc77e33d1d | ||
|
|
0164d24583 | ||
|
|
76ee9874d6 | ||
|
|
3699f89b98 | ||
|
|
911751d0c1 | ||
|
|
e669ac6ef8 | ||
|
|
4263ac1ac5 | ||
|
|
8176048268 | ||
|
|
e2d6ee4223 | ||
|
|
90a81ce736 | ||
|
|
9cf0dd1b87 | ||
|
|
e65cf32372 | ||
|
|
0ad2001ab4 | ||
|
|
70ff510e49 | ||
|
|
6377aeabdd | ||
|
|
1649080a35 | ||
|
|
c1ee358f66 | ||
|
|
2c708b57a1 | ||
|
|
c5772c814c | ||
|
|
10a5b6fb51 | ||
|
|
07d31a8558 | ||
|
|
cf3a3be8f2 | ||
|
|
49224ec9f7 | ||
|
|
dee24aec65 | ||
|
|
9a63ade1b5 | ||
|
|
18a6237ec7 | ||
|
|
be8298926a | ||
|
|
86958be6cf | ||
|
|
a2d07c85da | ||
|
|
295ddc5248 | ||
|
|
a85a19e40a | ||
|
|
cec3c8a846 | ||
|
|
7da7ce85df | ||
|
|
6055431b6f | ||
|
|
c0e2deb5a0 | ||
|
|
1f86ed55ba | ||
|
|
35f1ba17e1 | ||
|
|
b84fbf86ec | ||
|
|
a5e0b3452d | ||
|
|
0a1ca30aae | ||
|
|
2e85018c26 | ||
|
|
affdc6f5af | ||
|
|
9198bb5a68 | ||
|
|
9769c8d5b9 | ||
|
|
688328dc57 | ||
|
|
8904a4b144 | ||
|
|
8d1ee752d4 | ||
|
|
f02a11b27b | ||
|
|
5e675f26f7 | ||
|
|
497a9c7fd0 | ||
|
|
5afcd0595d | ||
|
|
f27d4d2117 | ||
|
|
79727a86fa | ||
|
|
d474c1c26d | ||
|
|
29ec43bdc8 | ||
|
|
cabc05b06c | ||
|
|
89e820d62e | ||
|
|
88616007b0 | ||
|
|
b262a1eedc | ||
|
|
973e1075b9 | ||
|
|
b8ce722a2c | ||
|
|
b8fa4af06a | ||
|
|
7f1945f06e | ||
|
|
ea8f041a20 | ||
|
|
52e8cdb011 | ||
|
|
ac9f47783c | ||
|
|
27f9aa16d6 | ||
|
|
08dc649b9a | ||
|
|
bb1a373272 | ||
|
|
e192f68891 | ||
|
|
73654791c1 | ||
|
|
8908458942 | ||
|
|
26792283ab | ||
|
|
32a92a4695 | ||
|
|
3bea2ae419 | ||
|
|
58eaf19026 | ||
|
|
d513e68fab | ||
|
|
ca8bbb27a0 | ||
|
|
287dab27ae | ||
|
|
25c26519ed | ||
|
|
cf66c8d507 | ||
|
|
318bb767a5 | ||
|
|
ad6148f0f6 | ||
|
|
8042d3f22a | ||
|
|
c7259fc424 | ||
|
|
62c949df6e | ||
|
|
8c7dc342e0 | ||
|
|
d15439aeac | ||
|
|
c19c64a990 | ||
|
|
8e374d9416 | ||
|
|
49216ce365 | ||
|
|
812c58297f | ||
|
|
2a04df1049 | ||
|
|
2eda624653 | ||
|
|
2f8aa7a351 | ||
|
|
b6115f0147 | ||
|
|
5ff22a1a77 | ||
|
|
7fdf8fee13 | ||
|
|
45a9ea666e | ||
|
|
efd5d0e410 | ||
|
|
6dbe95a0b5 | ||
|
|
a0ec06a487 | ||
|
|
ebf4b4b48a | ||
|
|
4f99efaaf4 | ||
|
|
62c30d9130 | ||
|
|
21fb953d0f | ||
|
|
27c98070e8 | ||
|
|
039750a7fa | ||
|
|
9b0d1bd054 | ||
|
|
4c1c2a5661 | ||
|
|
791d9aecfc | ||
|
|
48aaac90fb | ||
|
|
bfaa3a3a92 | ||
|
|
6dce685105 | ||
|
|
8c4e33faef | ||
|
|
81582b04f9 | ||
|
|
dbcc72a6f6 | ||
|
|
9f207f161a | ||
|
|
1ee4039681 | ||
|
|
3203814940 | ||
|
|
acc8c33260 | ||
|
|
22ecf505e3 | ||
|
|
c2c14410d2 | ||
|
|
ba578c91a7 | ||
|
|
7b46391e30 | ||
|
|
ae551cafea | ||
|
|
672c770dbe | ||
|
|
847046a225 | ||
|
|
4da998ab45 | ||
|
|
b534ea60f5 | ||
|
|
549ccff5eb | ||
|
|
9cf6749b2e | ||
|
|
d2999cf1d7 | ||
|
|
391b55ceaf | ||
|
|
e05345d3bb | ||
|
|
67bd54e55d | ||
|
|
20919fd6db | ||
|
|
ada2af04ff | ||
|
|
fcb6f762a9 | ||
|
|
5645f31934 | ||
|
|
a7295e2d03 | ||
|
|
c3e6ac2cac | ||
|
|
f1f3c8c8fe | ||
|
|
81b2f2e15a | ||
|
|
f6fdaf6492 | ||
|
|
519c5d686a | ||
|
|
f7e62ccbcc | ||
|
|
19161c0c92 | ||
|
|
c528303841 | ||
|
|
a439288765 | ||
|
|
416410912d | ||
|
|
b3f26f00e2 | ||
|
|
90e6ecdd11 | ||
|
|
842354bc5a | ||
|
|
4d3875cec2 | ||
|
|
74dfe49046 | ||
|
|
d0f23ed9e0 | ||
|
|
8a0992315e | ||
|
|
18b5cb10dc | ||
|
|
8afc6f208d | ||
|
|
d35d931179 | ||
|
|
093174e757 | ||
|
|
ac78762910 | ||
|
|
fff656f7e4 | ||
|
|
83b36e4c76 | ||
|
|
b42895b5c0 | ||
|
|
42e4b9addc | ||
|
|
91ab0e4a76 | ||
|
|
51feec3785 | ||
|
|
20eee3818e | ||
|
|
94cd6226ea | ||
|
|
5534ecf1b1 | ||
|
|
d498df3933 | ||
|
|
75bf62ca80 | ||
|
|
ad42357dd6 | ||
|
|
2de899342a | ||
|
|
8df48dc69d | ||
|
|
0f2809d2e9 | ||
|
|
aa44153af7 | ||
|
|
ce9c5162bd | ||
|
|
5c0da56ad5 | ||
|
|
08858c2a10 | ||
|
|
0268aeb25f | ||
|
|
ea3aba8971 | ||
|
|
265bfcf9d7 | ||
|
|
7a9fae5892 | ||
|
|
f66b0cc0aa | ||
|
|
e7f30106c0 | ||
|
|
86d2800b59 | ||
|
|
e65006042d | ||
|
|
65efc89b78 | ||
|
|
ae38834507 | ||
|
|
4261777613 | ||
|
|
7bc089f5c5 | ||
|
|
c46e3e8cf7 | ||
|
|
180d554205 | ||
|
|
a450d2e741 | ||
|
|
d528196033 | ||
|
|
0da2cbf84e | ||
|
|
20f981bc42 | ||
|
|
9a5514ffe0 | ||
|
|
439b4ad190 | ||
|
|
9a9c06966b | ||
|
|
b3f9baac8f | ||
|
|
bb961489b0 | ||
|
|
d6ae1f8375 | ||
|
|
5a1361167e | ||
|
|
4d5172f67e | ||
|
|
6f01c31458 | ||
|
|
6cfb208f3f | ||
|
|
3074892f1b | ||
|
|
b05d977cf9 | ||
|
|
36d183d988 | ||
|
|
b154fd56fb | ||
|
|
3419f69246 | ||
|
|
8e23301028 | ||
|
|
72724ee389 | ||
|
|
ae59a053b1 | ||
|
|
e71919226b | ||
|
|
6c5dd75216 | ||
|
|
23095728f5 | ||
|
|
498dacb4d2 | ||
|
|
b373a069e9 | ||
|
|
1d67e2f955 | ||
|
|
9a6935bf40 | ||
|
|
921ffefd90 | ||
|
|
a4fccd43eb | ||
|
|
48e1a8295b | ||
|
|
890c8c1905 | ||
|
|
38d50ce6fb | ||
|
|
14768d6947 | ||
|
|
b96399b563 | ||
|
|
d8c008dc7d | ||
|
|
b2ae5f4f4b | ||
|
|
ba871d7855 | ||
|
|
a0c8fc2ddb | ||
|
|
2a12053122 | ||
|
|
1860f21efb | ||
|
|
541adf6bb1 | ||
|
|
a71e90ddb8 | ||
|
|
60f4b3cdb8 | ||
|
|
0c249fa126 | ||
|
|
db1b57900b | ||
|
|
12b4898b3d | ||
|
|
f5f9bab77c | ||
|
|
ba7aa674d1 | ||
|
|
f670be4315 | ||
|
|
a658861fde | ||
|
|
192e5a1f79 | ||
|
|
475b15e372 | ||
|
|
d2ed91ccc8 | ||
|
|
333f4a1d3c | ||
|
|
832fe2f0b0 | ||
|
|
ea76185873 | ||
|
|
51c3ea184a | ||
|
|
692bccc4fc | ||
|
|
59fe76f29b | ||
|
|
51c2a0d732 | ||
|
|
2ab01b55c9 | ||
|
|
461f9e736b | ||
|
|
8e16b03ae8 | ||
|
|
6469809b9a | ||
|
|
0205adcedb | ||
|
|
0a0dbe115a | ||
|
|
ebbf3d4244 | ||
|
|
ceb36d1891 | ||
|
|
4caa850853 | ||
|
|
96cecf1796 | ||
|
|
51c7af8fb4 | ||
|
|
347466aba2 | ||
|
|
bee1c44859 | ||
|
|
a3fd36e2bb | ||
|
|
f07238c8bb | ||
|
|
3b499ce27c | ||
|
|
4dda216398 | ||
|
|
f0e511510a | ||
|
|
570ab0b10b | ||
|
|
214cf6cb2e | ||
|
|
07459f0cec | ||
|
|
90f4f7b62c | ||
|
|
987ac98fbf | ||
|
|
49ed40a301 | ||
|
|
c67bc704e1 | ||
|
|
3501ad45e6 | ||
|
|
70a13c9c12 | ||
|
|
3afb8d3ddf | ||
|
|
fd2f91e2ee | ||
|
|
cb0c9e5fba | ||
|
|
6a0e39e5ee | ||
|
|
a552f194b4 | ||
|
|
267bd8b51d | ||
|
|
497dddabc1 | ||
|
|
71b39489da | ||
|
|
ed8d92cfc6 | ||
|
|
f431ee289b | ||
|
|
7ea82204e0 | ||
|
|
44692b997c | ||
|
|
21086a0b2a | ||
|
|
d7119169df | ||
|
|
e1eb0a8617 | ||
|
|
cacc190764 | ||
|
|
89e5d935a6 | ||
|
|
884e1d7a6b | ||
|
|
5b1ca02ecc | ||
|
|
f51347feac | ||
|
|
b605b832ed | ||
|
|
e148b72284 | ||
|
|
16db921349 | ||
|
|
4c235b3648 | ||
|
|
1a8d479227 | ||
|
|
7aad364116 | ||
|
|
4958d9e423 | ||
|
|
474f1f3673 | ||
|
|
9aa7044f1e | ||
|
|
7c39f96d08 | ||
|
|
3c3e0b0352 | ||
|
|
8467742d6d | ||
|
|
5cfc3f8c47 | ||
|
|
62dbe03193 | ||
|
|
8755da4b5a | ||
|
|
25cff16bd1 | ||
|
|
ade6738789 | ||
|
|
ea0e3a14e0 | ||
|
|
4a1a1e83c8 | ||
|
|
ee48700b40 | ||
|
|
0791ce8ab0 | ||
|
|
1ac543aba7 | ||
|
|
3d5f80630d | ||
|
|
be2da07735 | ||
|
|
276d0b621b | ||
|
|
6733dac9fc | ||
|
|
80b286ebf7 | ||
|
|
2c143612e2 | ||
|
|
be412812f9 | ||
|
|
bfed7c24f8 | ||
|
|
fe859952e0 | ||
|
|
86361c5b60 | ||
|
|
35c017a787 | ||
|
|
79d65bc98b | ||
|
|
6bc69199d3 | ||
|
|
292cbc484a | ||
|
|
1a98cbfff9 | ||
|
|
071f077cc5 | ||
|
|
c16653a396 | ||
|
|
b157544394 | ||
|
|
18f65a1914 | ||
|
|
ba16135b3a | ||
|
|
df688cfb53 | ||
|
|
88679d66fc | ||
|
|
ab1cbcbbf8 | ||
|
|
a02fb753df | ||
|
|
98b002f7c0 | ||
|
|
e251bfe807 | ||
|
|
eb88893312 | ||
|
|
a362bf68bb | ||
|
|
3c53aa66b7 | ||
|
|
c97d4731dc | ||
|
|
a69546c9ee | ||
|
|
b0f78e97f4 | ||
|
|
49f7e38133 | ||
|
|
2e7f44feee | ||
|
|
48ceb49832 | ||
|
|
dd772f04fe | ||
|
|
47597c941d | ||
|
|
c0e8af56e2 | ||
|
|
72c0172c48 | ||
|
|
507f5345c5 | ||
|
|
79d4aec1ec | ||
|
|
89c7e6d337 | ||
|
|
b8d7af3822 | ||
|
|
3c9c968174 | ||
|
|
38ca0f90c7 | ||
|
|
8bf08c3cf8 | ||
|
|
14c79d2184 | ||
|
|
b0519af573 | ||
|
|
dc7acd6c70 | ||
|
|
d63a21363e | ||
|
|
a330dd4970 | ||
|
|
c0388c0136 | ||
|
|
aeca987a4c | ||
|
|
3aff5f2467 | ||
|
|
0c63a8d0e5 | ||
|
|
65b45e8dd1 | ||
|
|
03885d4b40 | ||
|
|
40e78fea99 | ||
|
|
ecba88e445 | ||
|
|
29c9b2c064 | ||
|
|
ff1cd7c008 | ||
|
|
6a871d13a6 | ||
|
|
c2a224b436 | ||
|
|
dd8d4b1d9b | ||
|
|
527bcffe3b | ||
|
|
de1d409c6a | ||
|
|
2555e491e1 | ||
|
|
4b5299d124 | ||
|
|
294c16c186 | ||
|
|
4b720bffdf | ||
|
|
7df458fcdd | ||
|
|
608b8f0a06 | ||
|
|
089a993299 | ||
|
|
bc4c31e48b | ||
|
|
5c210c90b5 | ||
|
|
a6cd456f8b | ||
|
|
2ede1066e9 | ||
|
|
462033dc99 | ||
|
|
0521bdb3b9 | ||
|
|
1f4165c132 | ||
|
|
a1fb08216e | ||
|
|
381996b225 | ||
|
|
08bca70192 | ||
|
|
10d25056a0 | ||
|
|
f889dda9f3 | ||
|
|
b0cd84c940 | ||
|
|
411fd2d831 | ||
|
|
d18782afd7 | ||
|
|
43dfbdc8e3 | ||
|
|
ea0f156b6f | ||
|
|
98def2f5fc | ||
|
|
c089cb508c | ||
|
|
1797cee84f | ||
|
|
45c97c82e1 | ||
|
|
bccde69a33 | ||
|
|
d9aa1a5abd | ||
|
|
46e1153f92 | ||
|
|
3dc642c007 | ||
|
|
bd9bc9093e | ||
|
|
6f0680cc7b | ||
|
|
911bf289bd | ||
|
|
393b98a463 | ||
|
|
2126d2314e | ||
|
|
d4d9a8fa9a | ||
|
|
6a21e3a2ca | ||
|
|
7119348b31 | ||
|
|
bf291b1863 | ||
|
|
e6a1acddcb | ||
|
|
8a8cd70879 | ||
|
|
b744e41e9e | ||
|
|
c101957fb9 | ||
|
|
a8dc7986af | ||
|
|
d0c9763d0d | ||
|
|
5988fede53 | ||
|
|
25c8b46c70 | ||
|
|
60c3d97c7a | ||
|
|
ee0bd2d747 | ||
|
|
c0e94cabb6 | ||
|
|
867e3231d2 | ||
|
|
00bfa3ae03 | ||
|
|
7ae83d6a5b | ||
|
|
5d6f6456d3 | ||
|
|
69b6077221 | ||
|
|
dcf82f8db7 | ||
|
|
69f83b352a | ||
|
|
2899789147 | ||
|
|
d33f06634f | ||
|
|
1d3bdd6a34 | ||
|
|
5a38e04789 | ||
|
|
1f1b22cee7 | ||
|
|
16ce5eacbb | ||
|
|
69e311d021 | ||
|
|
ea48915007 | ||
|
|
165c8c1d0f | ||
|
|
b237eaf6bf | ||
|
|
4de70e31c6 | ||
|
|
dd29302d57 | ||
|
|
4f3cf40e30 | ||
|
|
9fdb40c9b8 | ||
|
|
3af2a070e3 | ||
|
|
6e5f0a8eea | ||
|
|
d0320dd0c6 | ||
|
|
5f25bba012 | ||
|
|
42e650bba8 | ||
|
|
c6f29d685c | ||
|
|
a45c5e1170 | ||
|
|
683b4c7c18 | ||
|
|
1b5d6033b0 | ||
|
|
c209761689 | ||
|
|
59112209d0 | ||
|
|
28ea18d3a6 | ||
|
|
fac669156e | ||
|
|
102f4ad4a4 | ||
|
|
e10ce2345b | ||
|
|
d06eaf14af | ||
|
|
21623118d3 | ||
|
|
4dfcaa470a | ||
|
|
9cc98b0ee8 | ||
|
|
acf2b4dc7d | ||
|
|
029c6f9caf | ||
|
|
34767f4986 | ||
|
|
fe852d8519 | ||
|
|
0eded20bdf | ||
|
|
9ec9e72ade | ||
|
|
c9559666ef | ||
|
|
81c0d44c09 | ||
|
|
2651fe2ed4 | ||
|
|
e1e40fc68c | ||
|
|
dff0f08cb5 | ||
|
|
5483e81d75 | ||
|
|
905d923880 | ||
|
|
c3d96ca448 | ||
|
|
7ea8761685 | ||
|
|
0e6e5e3a4d | ||
|
|
afbc7aaa14 | ||
|
|
8cc3179a36 | ||
|
|
324d013f88 | ||
|
|
70924f6f3a | ||
|
|
a2602bb712 | ||
|
|
5fc94ca01d | ||
|
|
ddf2c99240 | ||
|
|
f3e83dcdb9 | ||
|
|
773aeda980 | ||
|
|
bea58ef6dc | ||
|
|
35b4fca59f | ||
|
|
afc2f13841 | ||
|
|
4e71e4db86 | ||
|
|
dd1fc02f9f | ||
|
|
760a76cbd0 | ||
|
|
a81720d64c | ||
|
|
d0ad4ad022 | ||
|
|
2f9f500ecb | ||
|
|
1d09a35bcd | ||
|
|
e8310ab1b7 | ||
|
|
3b029896c9 | ||
|
|
61f83cd463 | ||
|
|
0c36b87bcc | ||
|
|
14e97185b3 | ||
|
|
fa512ee22a | ||
|
|
58dc68d112 | ||
|
|
7e265100c3 | ||
|
|
9d0a0253c9 | ||
|
|
e41b5d9a1c | ||
|
|
7af82945d9 | ||
|
|
6e90c56d03 | ||
|
|
887d72e45f | ||
|
|
3086a6821b | ||
|
|
599d5e4863 | ||
|
|
d59aab3f71 | ||
|
|
d356afd6cf | ||
|
|
474ff5815b | ||
|
|
5379826662 | ||
|
|
b551c15f5c | ||
|
|
8554f0f896 | ||
|
|
b8a5d49100 | ||
|
|
05ed63aaf4 | ||
|
|
f37f8308c8 | ||
|
|
136c4521d4 | ||
|
|
7a34d997aa | ||
|
|
6ca9b2c006 | ||
|
|
fdc01a41de | ||
|
|
fb138b54fa | ||
|
|
efde637e1c | ||
|
|
448518d500 | ||
|
|
b562cc9b98 | ||
|
|
7b6a21f612 | ||
|
|
cf1a1116f4 | ||
|
|
cbc3d5b04c | ||
|
|
219b03cb6b | ||
|
|
c33cd2657c | ||
|
|
e26b0663e3 | ||
|
|
7f93c403e9 | ||
|
|
4a1c2c5d09 | ||
|
|
8e445f349b | ||
|
|
09984001fc | ||
|
|
f598338a5f | ||
|
|
650adb47fd | ||
|
|
ad8a943eb2 | ||
|
|
1b7efab40a | ||
|
|
43c608e25c | ||
|
|
6aba9411a6 | ||
|
|
1e70992835 | ||
|
|
38a84e95ad | ||
|
|
5492d046d1 | ||
|
|
16dbd5eea0 | ||
|
|
098a3c59c7 | ||
|
|
16272f0d75 | ||
|
|
4e41e422c5 | ||
|
|
227d45a40c | ||
|
|
6bd529ef66 | ||
|
|
61e798d400 | ||
|
|
d84154326f | ||
|
|
cba793524e | ||
|
|
9ec8cb9bf4 | ||
|
|
be52e08f2d | ||
|
|
4edff2f17f | ||
|
|
ed70e6e344 | ||
|
|
920bba2ffd | ||
|
|
2113498348 | ||
|
|
8e7e1a3b26 | ||
|
|
f638fac152 | ||
|
|
d2ebaf3ab7 | ||
|
|
90575278b0 | ||
|
|
fdbffce93a | ||
|
|
afac7c4d09 | ||
|
|
3fdca717c1 | ||
|
|
3959149077 | ||
|
|
f17053fcee | ||
|
|
fcbbf89083 | ||
|
|
3138602661 | ||
|
|
562f49b3ad | ||
|
|
02328ae43f | ||
|
|
e132f6c385 | ||
|
|
2b8fb49be2 | ||
|
|
51601a3258 | ||
|
|
31267f36ed | ||
|
|
540d1cbf06 | ||
|
|
050f671c65 | ||
|
|
35e2179271 | ||
|
|
52d06513bb | ||
|
|
124ef94500 | ||
|
|
166d5bfc5b | ||
|
|
efd290a3be | ||
|
|
742ef03495 | ||
|
|
92a6694ac8 | ||
|
|
1354ad1884 | ||
|
|
15a4b189f4 | ||
|
|
53cf6c2f8c | ||
|
|
64518c3bd4 | ||
|
|
20711cb8bc | ||
|
|
2914b9d220 | ||
|
|
677c872d5a | ||
|
|
015809c83a | ||
|
|
092ec8d83d | ||
|
|
9d2237cfd4 | ||
|
|
8b615e58e8 | ||
|
|
e351d0c3c0 | ||
|
|
a943f66afd | ||
|
|
3df1a0943c | ||
|
|
b5759c5b6d | ||
|
|
6b4c6c84b4 | ||
|
|
d8222d76d9 | ||
|
|
88a3b41135 | ||
|
|
fb7aa9298d | ||
|
|
b79cc948b3 | ||
|
|
e7092612a9 | ||
|
|
15c512388d | ||
|
|
f37377264b | ||
|
|
bd44b20b4a | ||
|
|
e25da3c619 | ||
|
|
3499dbfe09 | ||
|
|
81c4a0ffce | ||
|
|
a0064f7535 | ||
|
|
79c9eff2b9 | ||
|
|
b8bcf0c87d | ||
|
|
179a560e1d | ||
|
|
2245bd25e9 | ||
|
|
c256daceb0 | ||
|
|
73b29050b9 | ||
|
|
162da63522 | ||
|
|
01194ee267 | ||
|
|
d958cff58e | ||
|
|
d0662b519f | ||
|
|
d75a96e9ba | ||
|
|
bd7ed1b41a | ||
|
|
b5e6d235ba | ||
|
|
3cc537b978 | ||
|
|
ac0c0db942 | ||
|
|
0affbad976 | ||
|
|
ced84955d3 | ||
|
|
b86fc67adf | ||
|
|
59de4c3435 | ||
|
|
8e418b8743 | ||
|
|
f2b003379d | ||
|
|
0d083077ab | ||
|
|
db1a94a02c | ||
|
|
fdbc7dfe04 | ||
|
|
70f004cc62 | ||
|
|
c02cb45c6c | ||
|
|
8dccc88613 | ||
|
|
bb5914dbce | ||
|
|
4698ec1a37 | ||
|
|
e29c98faad | ||
|
|
444d6be6c9 | ||
|
|
28aa181480 | ||
|
|
97e0fcb765 | ||
|
|
052fdb30c0 | ||
|
|
2e2b667864 | ||
|
|
666631134c | ||
|
|
de4cfe2b8b | ||
|
|
a7ebe53cfa | ||
|
|
66a569fc40 | ||
|
|
3a646ffdba | ||
|
|
7cfd71c0bd | ||
|
|
3265d60d52 | ||
|
|
640b417b39 | ||
|
|
b655a3aede | ||
|
|
819b395ae4 | ||
|
|
2584335b11 | ||
|
|
4e5a8f348a | ||
|
|
f91a9c2562 | ||
|
|
ea098ac53e | ||
|
|
a0d0b00938 | ||
|
|
31a4657467 | ||
|
|
2ddb8fcec3 | ||
|
|
21d4fff450 | ||
|
|
00036ec277 | ||
|
|
a5d7fb66f0 | ||
|
|
b01a204c1f | ||
|
|
e9310eba98 | ||
|
|
69285270e5 | ||
|
|
6f96d6d63e | ||
|
|
91f476d2c6 | ||
|
|
7871a0b7a4 | ||
|
|
6bd11a426e | ||
|
|
1abdc3afcf | ||
|
|
b8f98a5387 | ||
|
|
cb3b858df6 | ||
|
|
d7025e7dfa | ||
|
|
b2b9c47a50 | ||
|
|
211003f542 | ||
|
|
afb0d5bd3d | ||
|
|
f8227c3f47 | ||
|
|
258d764af8 | ||
|
|
2d3e070288 | ||
|
|
138141ac9b | ||
|
|
fa56ad8bc3 | ||
|
|
9d29da8d27 | ||
|
|
37526728e0 | ||
|
|
68f2cb9caa | ||
|
|
d13c7ea7ee | ||
|
|
4c7c702b6d | ||
|
|
c05955d598 | ||
|
|
ca64ab54e7 | ||
|
|
7f4ad03a5c | ||
|
|
d60ad1f40a | ||
|
|
5106317e5d | ||
|
|
a5e4a19f06 | ||
|
|
067ce6de9f | ||
|
|
9facab53b7 | ||
|
|
e64442f6de | ||
|
|
4f20f545dc | ||
|
|
4bbf84f551 | ||
|
|
987a1618e9 | ||
|
|
0ab95cdd27 | ||
|
|
c617612794 | ||
|
|
0c89bbb8df | ||
|
|
a7206bee95 | ||
|
|
0e3a749f69 | ||
|
|
b0d0c3d731 | ||
|
|
534dd8e16a | ||
|
|
535bf9b066 | ||
|
|
79e703c8ac | ||
|
|
d4a98a58f4 | ||
|
|
ceb7206117 | ||
|
|
1273157ace | ||
|
|
5a92200dc6 | ||
|
|
d021e61dfa | ||
|
|
3ae505d6c6 | ||
|
|
a1ddb336c1 | ||
|
|
646ac3c74d | ||
|
|
d383a29e00 | ||
|
|
b44e295562 | ||
|
|
8a1f8d549e | ||
|
|
a2b674e094 | ||
|
|
c4796dcdf5 | ||
|
|
992b4a65df | ||
|
|
6928929bc9 | ||
|
|
508b424234 | ||
|
|
7f68a96f75 | ||
|
|
535e22e5f8 | ||
|
|
348a926af4 | ||
|
|
aa84c4788a | ||
|
|
66293e8caa | ||
|
|
1e60cff2ef | ||
|
|
701519a15c | ||
|
|
72532324ed | ||
|
|
3cb6569d55 | ||
|
|
bebaa48520 | ||
|
|
af246225a9 | ||
|
|
582a792c71 | ||
|
|
98c4bd9ee5 | ||
|
|
933b395de4 | ||
|
|
4f485f856b | ||
|
|
280651da9c | ||
|
|
77201e949d | ||
|
|
292a50a891 | ||
|
|
a36fc867d1 | ||
|
|
def9f2d407 | ||
|
|
b0da4f6b91 | ||
|
|
cbf1230f6d | ||
|
|
71d6e881f2 | ||
|
|
da40771739 | ||
|
|
694314cc00 | ||
|
|
3025738377 | ||
|
|
cbe0f4e6e5 | ||
|
|
68106d804e | ||
|
|
e0088e0252 | ||
|
|
efff6d6e63 | ||
|
|
6bbd8d7a4c | ||
|
|
e0bb49aa23 | ||
|
|
81d79a64e6 | ||
|
|
9cbf0289d8 | ||
|
|
4f08879321 | ||
|
|
6d6394c408 | ||
|
|
bf3ecaf48a | ||
|
|
6ba2bf80b8 | ||
|
|
9f4030cdb6 | ||
|
|
cce818995a | ||
|
|
1e2dc43df6 | ||
|
|
06caf03d70 | ||
|
|
7226b26e8e | ||
|
|
7c72d69e2f | ||
|
|
bc1d6c47ba | ||
|
|
8ec0e025d3 | ||
|
|
9f40d913c9 | ||
|
|
3595c71d8b | ||
|
|
9740478151 | ||
|
|
255d087416 | ||
|
|
442a55d539 | ||
|
|
01fce0c6b1 | ||
|
|
0e3ac00ff5 | ||
|
|
2285942384 | ||
|
|
8668f6bb1e | ||
|
|
95ee99d652 | ||
|
|
0273491e6e | ||
|
|
b1917d909a | ||
|
|
edbf85655a | ||
|
|
be917eb86b | ||
|
|
0a2521e7a4 | ||
|
|
1b8d115b63 | ||
|
|
70929cec57 | ||
|
|
30583ec8f2 | ||
|
|
e370ef7007 | ||
|
|
42953faf70 | ||
|
|
7611cd963c | ||
|
|
3e0eb4ac1e | ||
|
|
f8d501ae50 | ||
|
|
cb614f01c3 | ||
|
|
9be11d9cf0 | ||
|
|
8ca83de332 | ||
|
|
89d3fedcd4 | ||
|
|
71ca91db09 | ||
|
|
5cf973f03a | ||
|
|
2c7693b2c5 | ||
|
|
43249a8d39 | ||
|
|
b654ac178c | ||
|
|
ccac18b21c | ||
|
|
8a5fef98ab | ||
|
|
c85fff8f23 | ||
|
|
fc57e72853 | ||
|
|
ce94e69fd5 | ||
|
|
6c29a1f648 | ||
|
|
b46ccc0937 | ||
|
|
fa29b18be7 | ||
|
|
e89fc8f66a | ||
|
|
ad4fe86285 | ||
|
|
40db885b3a | ||
|
|
ae0c16f375 | ||
|
|
34ec7bd1c9 | ||
|
|
909b751d6d | ||
|
|
9419ad16bf | ||
|
|
9a90898c7d | ||
|
|
8e80cd1441 | ||
|
|
2c12c4f67e | ||
|
|
d5d45de5a4 | ||
|
|
2ebd358aeb | ||
|
|
be11db45d8 | ||
|
|
30213bd71e | ||
|
|
bb66b6161a | ||
|
|
b14501f358 | ||
|
|
e93a145bab | ||
|
|
f7130b8c21 | ||
|
|
a497e1ab8a | ||
|
|
8b245a442c | ||
|
|
e1ff2197c0 | ||
|
|
f684ea563d | ||
|
|
041b6fe251 | ||
|
|
88cb871654 | ||
|
|
507e734d1a | ||
|
|
d128488339 | ||
|
|
c8c61adca4 | ||
|
|
7c8f33f525 | ||
|
|
cb8599ebd5 | ||
|
|
7714952574 | ||
|
|
15bf6eab1e | ||
|
|
10333b0f3e | ||
|
|
aaa974353a | ||
|
|
52a5ca8f88 | ||
|
|
1cca9d6d79 | ||
|
|
b19f2df6f8 | ||
|
|
b3ac56e3d2 | ||
|
|
d28b1dcd9f | ||
|
|
e4cab53a11 | ||
|
|
c342bc4188 | ||
|
|
73a0df56b2 | ||
|
|
e0303a0dda | ||
|
|
cc77ad2c65 | ||
|
|
280a0fed69 | ||
|
|
24c2743a58 | ||
|
|
264a2cd2ad | ||
|
|
907ba8f53b | ||
|
|
d2cbb2749e | ||
|
|
eab5ec206a | ||
|
|
b49f5bdb99 | ||
|
|
28a1578d20 | ||
|
|
e78be1fc35 | ||
|
|
fa8753cab3 | ||
|
|
9459d4b739 | ||
|
|
f517a09f0f | ||
|
|
34642a04a4 | ||
|
|
4bb4d943ba | ||
|
|
db855715ab | ||
|
|
c27e4d020d | ||
|
|
109e249aa6 | ||
|
|
a16ad4d6e6 | ||
|
|
33ce8c5060 | ||
|
|
f2568627af | ||
|
|
334974472d | ||
|
|
8227a3f67c | ||
|
|
4f7d476907 | ||
|
|
a731d47d97 | ||
|
|
44beed4027 | ||
|
|
9fd1a43769 | ||
|
|
1c6abbbe41 | ||
|
|
c4a715747a | ||
|
|
3bdea732fc | ||
|
|
bfe8e79653 | ||
|
|
7d812e3087 | ||
|
|
05d9cf8235 | ||
|
|
108f4784a6 | ||
|
|
a7feb41e62 | ||
|
|
fddb3ee06e | ||
|
|
c59c86c660 | ||
|
|
8f02c81f16 | ||
|
|
b3fac8ed6a | ||
|
|
3574da267b | ||
|
|
c929b2d5f9 | ||
|
|
f479e57870 | ||
|
|
ae625a18c8 | ||
|
|
41a261bdcd | ||
|
|
4ad1c22ef6 | ||
|
|
ef9f95f653 | ||
|
|
3b54372eab | ||
|
|
f05bb6c4e0 | ||
|
|
c8e0c12d34 | ||
|
|
ab380ed2c1 | ||
|
|
9494f0655d | ||
|
|
c3a41286fd | ||
|
|
8297652204 | ||
|
|
a26fc67ded | ||
|
|
abadbf883a | ||
|
|
833eb6166f | ||
|
|
e7e3608642 | ||
|
|
dd232775e6 | ||
|
|
a8eb7966b6 | ||
|
|
8051f51d46 | ||
|
|
6fd9f471ff | ||
|
|
2d037d874c | ||
|
|
8e5384ee01 | ||
|
|
7dd37d2a13 | ||
|
|
36521b04a2 | ||
|
|
f5d0a277fb | ||
|
|
986a3f8079 | ||
|
|
de4ec23809 | ||
|
|
1cc0794e75 | ||
|
|
3cca0887c7 | ||
|
|
0e1d39120e | ||
|
|
a072cf2e0a | ||
|
|
0f0f3649a4 | ||
|
|
54c95c04af | ||
|
|
7c5dda353d | ||
|
|
c42400de4f | ||
|
|
9b5cef9167 | ||
|
|
6081c400fb | ||
|
|
cb339523de | ||
|
|
538902b207 | ||
|
|
8961041469 | ||
|
|
cfe26ae346 | ||
|
|
bdfa06dbe9 | ||
|
|
1a3537a8f4 | ||
|
|
09606db9c3 | ||
|
|
4a708c2a82 | ||
|
|
735a7f9154 | ||
|
|
7478e78ab7 | ||
|
|
01bfa602ad | ||
|
|
332762201a | ||
|
|
568290afb7 | ||
|
|
3c11c8aa38 | ||
|
|
2ba360f33a | ||
|
|
7fc56e54b3 | ||
|
|
16062ea62d | ||
|
|
e852ae99ca | ||
|
|
0ff37d3492 | ||
|
|
b3866ebd94 | ||
|
|
3031372442 | ||
|
|
d3d2de5f1f | ||
|
|
d7306c66f7 | ||
|
|
1f42a9d2f9 | ||
|
|
8c917eeb17 | ||
|
|
a81a873703 | ||
|
|
da04d97fb5 | ||
|
|
4f42029228 | ||
|
|
4916badcc4 | ||
|
|
82b56848a9 | ||
|
|
889dc856af | ||
|
|
2ba8764615 | ||
|
|
eb5435d275 | ||
|
|
b048b6deb0 | ||
|
|
0c23e47124 | ||
|
|
0af2111535 | ||
|
|
5bbd886e6f | ||
|
|
402fe094f4 | ||
|
|
b753bcd982 | ||
|
|
d6a5eaec3f | ||
|
|
771f57029e | ||
|
|
c223ff1c7d | ||
|
|
481214776b | ||
|
|
98e6571059 | ||
|
|
19a31a5b82 | ||
|
|
090878c157 | ||
|
|
53cc270f47 | ||
|
|
ceb0186f46 | ||
|
|
46762a40b0 | ||
|
|
c291324d14 | ||
|
|
b307b9872a | ||
|
|
5e290aa11f | ||
|
|
5f6c5f6692 | ||
|
|
1c1b3248c0 | ||
|
|
bc47866880 | ||
|
|
60c5e11871 | ||
|
|
e6a692424b | ||
|
|
f293063538 | ||
|
|
6321512ddb | ||
|
|
0ca125ff2c | ||
|
|
57efe0145b | ||
|
|
bd53462ab2 | ||
|
|
c44d98b798 | ||
|
|
c8adcb2e79 | ||
|
|
768f89c3de | ||
|
|
e2d644b86b | ||
|
|
09977a2e04 | ||
|
|
63a0b964ab | ||
|
|
5cae747b36 | ||
|
|
1b7c386351 | ||
|
|
ef0dae4ed3 | ||
|
|
293f1990bc | ||
|
|
0bb62ae75a | ||
|
|
2686be1b93 | ||
|
|
77b193791a | ||
|
|
74a7bef472 | ||
|
|
fc78bf4d11 | ||
|
|
84cdf54079 | ||
|
|
fa08bb9df6 | ||
|
|
b845360091 | ||
|
|
953d451105 | ||
|
|
75d80c4828 | ||
|
|
3e030f7971 | ||
|
|
877658d22e | ||
|
|
1c797ca901 | ||
|
|
520a72c8e6 | ||
|
|
98fec7e5aa | ||
|
|
d0e0df9bd4 | ||
|
|
f5bdf916f5 | ||
|
|
038aac41fd | ||
|
|
ea36d232ac | ||
|
|
c54fe016b1 | ||
|
|
9c9b46479d | ||
|
|
916ff7aa39 | ||
|
|
28c290074b | ||
|
|
b687484dda | ||
|
|
e74783b987 | ||
|
|
0b4c19a369 | ||
|
|
def9d8d004 | ||
|
|
fd731beb87 | ||
|
|
35cc653737 | ||
|
|
1254d680ed | ||
|
|
23c0b6a04b | ||
|
|
ede1ec970c | ||
|
|
2301e8dbf7 | ||
|
|
fbab5a7280 | ||
|
|
ac430e4c42 | ||
|
|
a92cfc1ffe | ||
|
|
178cf85183 | ||
|
|
272161b00b | ||
|
|
c6457981ab | ||
|
|
56155d0819 | ||
|
|
c711826996 | ||
|
|
eef6965590 | ||
|
|
4926cb6f9a | ||
|
|
d9ca54e777 | ||
|
|
1f6a6349a9 | ||
|
|
8f27f8f442 | ||
|
|
681109ba71 | ||
|
|
99c691745a | ||
|
|
1af5699948 | ||
|
|
ef7571f1ef | ||
|
|
8862d4f76e | ||
|
|
d313ec935c | ||
|
|
40e759733d | ||
|
|
dee57aca37 | ||
|
|
7ac2301247 | ||
|
|
c0726923f5 | ||
|
|
4d9eadde5b | ||
|
|
1d55ee19e4 | ||
|
|
9da5cee8d9 | ||
|
|
ca22ecdf09 | ||
|
|
2e58e92d48 | ||
|
|
fd0c53b424 | ||
|
|
7b1e8ac1cf | ||
|
|
9810a84475 | ||
|
|
31b7e063e7 | ||
|
|
b5172f46c1 | ||
|
|
5a6cb8e708 | ||
|
|
c3c28ea27e | ||
|
|
b128562b41 | ||
|
|
8f44dc0986 | ||
|
|
623abeef4c | ||
|
|
5f30846092 | ||
|
|
6ddbc5cb57 | ||
|
|
f9f3b18ce4 | ||
|
|
dc2925263c | ||
|
|
56fd45f7ea | ||
|
|
21a0fc30e8 | ||
|
|
673d0af97d | ||
|
|
ca523fa335 | ||
|
|
5860332e49 | ||
|
|
c06d4d1b00 | ||
|
|
bec2470ddf | ||
|
|
402400d4cb | ||
|
|
b1a66012bd | ||
|
|
9516b1a91b | ||
|
|
8468183738 | ||
|
|
39563fc142 | ||
|
|
104d2240ec | ||
|
|
9bec84da5b | ||
|
|
85fa976a25 | ||
|
|
e3a37c8d71 | ||
|
|
fd28740108 | ||
|
|
fa74d1b9bb | ||
|
|
11d120bb5b | ||
|
|
e2c96cfa85 | ||
|
|
eee3e8d331 | ||
|
|
0c68ad4acb | ||
|
|
640b487914 | ||
|
|
7c84e24f48 | ||
|
|
26b566b541 | ||
|
|
16bbee2831 | ||
|
|
96f7c6ebab | ||
|
|
5b06063442 |
No files matched your search
@@ -0,0 +1,123 @@
|
||||
version: 2
|
||||
defaults: &defaults
|
||||
docker:
|
||||
- image: weaveworks/weavebuild:go-1-13-1-5110a604
|
||||
working_directory: /go/src/github.com/weaveworks/weave
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- checkout
|
||||
- run: git submodule update --init
|
||||
- run: make DEBUG=1 BUILD_IN_CONTAINER=false lint
|
||||
|
||||
unit-test:
|
||||
<<: *defaults
|
||||
parallelism: 2
|
||||
steps:
|
||||
- checkout
|
||||
- run: COVERDIR=test/coverage make BUILD_IN_CONTAINER=false tests
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- test/coverage
|
||||
|
||||
build:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- checkout
|
||||
- setup_remote_docker
|
||||
- run: git submodule update --init
|
||||
- run: make COVERAGE=true BUILD_IN_CONTAINER=false SUDO= exes all
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- weave.tar.gz
|
||||
- tools/runner/runner
|
||||
- test/tls/tls
|
||||
|
||||
smoke-tests:
|
||||
machine:
|
||||
image: circleci/classic:201709-01
|
||||
working_directory: /home/circleci/src/github.com/weaveworks/weave
|
||||
environment:
|
||||
SRCDIR: /home/circleci/src/github.com/weaveworks/weave
|
||||
CIRCLE_ARTIFACTS: /tmp/artifacts
|
||||
parallelism: 2
|
||||
steps:
|
||||
- checkout
|
||||
- run: mkdir $CIRCLE_ARTIFACTS
|
||||
- run: git submodule update --init
|
||||
- attach_workspace:
|
||||
at: .
|
||||
# kick off creation of test VMs
|
||||
- run: bin/provision_test_vms.sh
|
||||
# when VMs are ready, copy built software to them
|
||||
- run: bin/circle-test-pre
|
||||
- run:
|
||||
command: bin/circle-test-smoke
|
||||
no_output_timeout: 360s
|
||||
# Destroy testing VMs:
|
||||
- run:
|
||||
command: bin/circle-destroy-vms
|
||||
background: true
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- test/coverage
|
||||
- store_artifacts:
|
||||
path: /tmp/artifacts
|
||||
|
||||
gen-coverage:
|
||||
<<: *defaults
|
||||
environment:
|
||||
CIRCLE_ARTIFACTS: /tmp/artifacts
|
||||
steps:
|
||||
- checkout
|
||||
- run: mkdir $CIRCLE_ARTIFACTS
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- run: cd test; ./gen_coverage_reports.sh
|
||||
- run: goveralls -repotoken $COVERALLS_REPO_TOKEN -coverprofile=test/profile.cov -service=circleci
|
||||
- run: cp test/coverage.* $CIRCLE_ARTIFACTS
|
||||
- store_artifacts:
|
||||
path: /tmp/artifacts
|
||||
|
||||
deploy:
|
||||
<<: *defaults
|
||||
steps:
|
||||
- checkout
|
||||
- setup_remote_docker
|
||||
# Register qemu hooks, rebuild without coverage and push to dockerhub
|
||||
- deploy:
|
||||
command: |
|
||||
docker login -u $DOCKER_USER -p $DOCKER_PASS
|
||||
docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
||||
wget https://github.com/estesp/manifest-tool/releases/download/v0.8.0/manifest-tool-linux-amd64
|
||||
chmod +x manifest-tool-linux-amd64
|
||||
make clean-bin
|
||||
make publish MANIFEST_TOOL_CMD=./manifest-tool-linux-amd64 UPDATE_LATEST=latest-only SUDO= BUILD_IN_CONTAINER=false
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
build_test:
|
||||
jobs:
|
||||
- lint
|
||||
- unit-test
|
||||
- build
|
||||
- smoke-tests:
|
||||
requires:
|
||||
- lint
|
||||
- unit-test
|
||||
- build
|
||||
- gen-coverage:
|
||||
requires:
|
||||
- unit-test
|
||||
- smoke-tests
|
||||
- deploy:
|
||||
requires:
|
||||
- smoke-tests
|
||||
filters:
|
||||
branches:
|
||||
only: master
|
||||
@@ -0,0 +1,65 @@
|
||||
<!--
|
||||
Hi, thank you for opening an issue!
|
||||
Before hitting the button...
|
||||
|
||||
** Is this a REQUEST FOR HELP? **
|
||||
If so, please have a look at:
|
||||
- our FAQ: https://www.weave.works/docs/net/latest/faq/
|
||||
- our troubleshooting page: https://www.weave.works/docs/net/latest/troubleshooting/
|
||||
- our help page, to choose the best channel (Slack, etc.) to reach out: https://www.weave.works/help/
|
||||
|
||||
** Is this a FEATURE REQUEST? **
|
||||
If so, please search existing feature requests, and if you find a similar one, up-vote it and/or add your comments to it instead.
|
||||
If you did not find a similar one, please describe in details:
|
||||
- why: your use-case, specific constraints you may have, etc.
|
||||
- what: the feature/behaviour/change you would like to see in Weave Net
|
||||
Do not hesitate, when appropriate, to share the exact commands or API you would like, and/or to share a diagram (e.g.: asciiflow.com): "a picture is worth a thousand words".
|
||||
|
||||
** Is this a BUG REPORT? **
|
||||
Please fill in as much of the template below as you can.
|
||||
|
||||
Thank you!
|
||||
-->
|
||||
|
||||
## What you expected to happen?
|
||||
|
||||
## What happened?
|
||||
<!-- Error message, actual behaviour, etc. -->
|
||||
|
||||
## How to reproduce it?
|
||||
<!-- Specific steps, as minimally and precisely as possible. -->
|
||||
|
||||
## Anything else we need to know?
|
||||
<!-- Cloud provider? Hardware? How did you configure your cluster? Kubernetes YAML, KOPS, etc. -->
|
||||
|
||||
## Versions:
|
||||
<!-- Please paste in the output of these commands; 'kubectl' only if using Kubernetes -->
|
||||
```
|
||||
$ weave version
|
||||
$ docker version
|
||||
$ uname -a
|
||||
$ kubectl version
|
||||
```
|
||||
|
||||
## Logs:
|
||||
```
|
||||
$ docker logs weave
|
||||
```
|
||||
or, if using Kubernetes:
|
||||
```
|
||||
$ kubectl logs -n kube-system <weave-net-pod> weave
|
||||
```
|
||||
<!-- (If output is long, please consider a Gist.) -->
|
||||
<!-- Anything interesting or unusual output by the below, potentially relevant, commands?
|
||||
$ journalctl -u docker.service --no-pager
|
||||
$ journalctl -u kubelet --no-pager
|
||||
$ kubectl get events
|
||||
-->
|
||||
|
||||
## Network:
|
||||
<!-- If your problem has anything to do with one network endpoint not being able to contact another, please run the following commands -->
|
||||
```
|
||||
$ ip route
|
||||
$ ip -4 -o addr
|
||||
$ sudo iptables-save
|
||||
```
|
||||
+12
-6
@@ -33,19 +33,21 @@ coverage.html
|
||||
# Vim backup files
|
||||
*.swp
|
||||
|
||||
# Sed backup files
|
||||
*.bak
|
||||
|
||||
# Project specific
|
||||
prog/weaver/weaver
|
||||
prog/weavedns/weavedns
|
||||
prog/weavehosts/weavehosts
|
||||
prog/weaveproxy/weaveproxy
|
||||
prog/sigproxy/sigproxy
|
||||
prog/weavewait/weavewait
|
||||
prog/weavewait/weavewait_noop
|
||||
prog/weavewait/weavewait_nomcast
|
||||
prog/weaveutil/weaveutil
|
||||
prog/*/Dockerfile.weaveworks
|
||||
prog/kube-peers/kube-peers
|
||||
prog/weave-kube/kube-peers
|
||||
prog/kube-utils/kube-utils
|
||||
prog/weave-kube/kube-utils
|
||||
prog/weave-npc/weave-npc
|
||||
prog/plugin/plugin
|
||||
testing/cover/cover
|
||||
@@ -53,20 +55,24 @@ testing/runner/runner
|
||||
releases
|
||||
.*.uptodate
|
||||
weave*.tar.gz
|
||||
prog/weaveexec/weave
|
||||
prog/weaver/weave
|
||||
prog/weaveexec/weavehosts
|
||||
prog/weaveexec/weaveproxy
|
||||
prog/weaveexec/weavewait
|
||||
prog/weaveexec/weavewait_noop
|
||||
prog/weaveexec/weavewait_nomcast
|
||||
prog/weaveexec/sigproxy
|
||||
prog/weaveexec/weaveutil
|
||||
prog/weaver/weaveutil
|
||||
prog/weaveexec/docker*
|
||||
Vagrantfile.local
|
||||
test/tls/tls
|
||||
test/tls/*.pem
|
||||
test/coverage
|
||||
test/coverage.*
|
||||
test/images/network-tester/webserver
|
||||
*qemu-*-static
|
||||
terraform.tfstate
|
||||
terraform.tfstate.backup
|
||||
prog/net-plugin/rootfs/
|
||||
|
||||
# Website
|
||||
site-build
|
||||
-123
@@ -1,126 +1,3 @@
|
||||
[submodule "tools"]
|
||||
path = tools
|
||||
url = https://github.com/weaveworks/build-tools
|
||||
[submodule "vendor/github.com/vishvananda/netlink"]
|
||||
path = vendor/github.com/vishvananda/netlink
|
||||
url = https://github.com/vishvananda/netlink.git
|
||||
[submodule "vendor/github.com/gorilla/mux"]
|
||||
path = vendor/github.com/gorilla/mux
|
||||
url = https://github.com/gorilla/mux.git
|
||||
[submodule "vendor/github.com/gorilla/context"]
|
||||
path = vendor/github.com/gorilla/context
|
||||
url = https://github.com/gorilla/context.git
|
||||
[submodule "vendor/github.com/google/gopacket"]
|
||||
path = vendor/github.com/google/gopacket
|
||||
url = https://github.com/google/gopacket.git
|
||||
[submodule "vendor/github.com/weaveworks/go-odp"]
|
||||
path = vendor/github.com/weaveworks/go-odp
|
||||
url = https://github.com/weaveworks/go-odp.git
|
||||
[submodule "vendor/github.com/opencontainers/runc"]
|
||||
path = vendor/github.com/opencontainers/runc
|
||||
url = https://github.com/opencontainers/runc.git
|
||||
[submodule "vendor/github.com/fsouza/go-dockerclient"]
|
||||
path = vendor/github.com/fsouza/go-dockerclient
|
||||
url = https://github.com/fsouza/go-dockerclient.git
|
||||
[submodule "vendor/github.com/docker/libnetwork"]
|
||||
path = vendor/github.com/docker/libnetwork
|
||||
url = https://github.com/docker/libnetwork.git
|
||||
[submodule "vendor/github.com/docker/docker"]
|
||||
path = vendor/github.com/docker/docker
|
||||
url = https://github.com/docker/docker.git
|
||||
[submodule "vendor/github.com/Sirupsen/logrus"]
|
||||
path = vendor/github.com/Sirupsen/logrus
|
||||
url = https://github.com/Sirupsen/logrus.git
|
||||
[submodule "vendor/github.com/andybalholm/go-bit"]
|
||||
path = vendor/github.com/andybalholm/go-bit
|
||||
url = https://github.com/andybalholm/go-bit.git
|
||||
[submodule "vendor/github.com/miekg/dns"]
|
||||
path = vendor/github.com/miekg/dns
|
||||
url = https://github.com/miekg/dns.git
|
||||
[submodule "vendor/github.com/stretchr/testify"]
|
||||
path = vendor/github.com/stretchr/testify
|
||||
url = https://github.com/stretchr/testify.git
|
||||
[submodule "vendor/golang.org/x/crypto"]
|
||||
path = vendor/golang.org/x/crypto
|
||||
url = https://github.com/golang/crypto.git
|
||||
[submodule "vendor/github.com/docker/machine"]
|
||||
path = vendor/github.com/docker/machine
|
||||
url = https://github.com/docker/machine.git
|
||||
[submodule "vendor/github.com/weaveworks/mesh"]
|
||||
path = vendor/github.com/weaveworks/mesh
|
||||
url = https://github.com/weaveworks/mesh
|
||||
[submodule "vendor/github.com/vishvananda/netns"]
|
||||
path = vendor/github.com/vishvananda/netns
|
||||
url = https://github.com/vishvananda/netns.git
|
||||
[submodule "vendor/github.com/boltdb/bolt"]
|
||||
path = vendor/github.com/boltdb/bolt
|
||||
url = https://github.com/boltdb/bolt
|
||||
[submodule "vendor/github.com/weaveworks/go-checkpoint"]
|
||||
path = vendor/github.com/weaveworks/go-checkpoint
|
||||
url = https://github.com/weaveworks/go-checkpoint
|
||||
[submodule "vendor/github.com/hashicorp/go-cleanhttp"]
|
||||
path = vendor/github.com/hashicorp/go-cleanhttp
|
||||
url = http://github.com/hashicorp/go-cleanhttp
|
||||
[submodule "vendor/github.com/appc/cni"]
|
||||
path = vendor/github.com/appc/cni
|
||||
url = https://github.com/appc/cni
|
||||
[submodule "vendor/github.com/coreos/go-iptables"]
|
||||
path = vendor/github.com/coreos/go-iptables
|
||||
url = https://github.com/coreos/go-iptables
|
||||
[submodule "vendor/github.com/pkg/profile"]
|
||||
path = vendor/github.com/pkg/profile
|
||||
url = https://github.com/pkg/profile
|
||||
[submodule "vendor/github.com/j-keck/arping"]
|
||||
path = vendor/github.com/j-keck/arping
|
||||
url = https://github.com/j-keck/arping
|
||||
[submodule "vendor/github.com/aws/aws-sdk-go"]
|
||||
path = vendor/github.com/aws/aws-sdk-go
|
||||
url = https://github.com/aws/aws-sdk-go
|
||||
[submodule "vendor/golang.org/x/sys"]
|
||||
path = vendor/golang.org/x/sys
|
||||
url = https://go.googlesource.com/sys
|
||||
[submodule "vendor/github.com/beorn7/perks"]
|
||||
path = vendor/github.com/beorn7/perks
|
||||
url = https://github.com/beorn7/perks
|
||||
[submodule "vendor/github.com/prometheus/procfs"]
|
||||
path = vendor/github.com/prometheus/procfs
|
||||
url = https://github.com/prometheus/procfs
|
||||
[submodule "vendor/github.com/prometheus/client_golang"]
|
||||
path = vendor/github.com/prometheus/client_golang
|
||||
url = https://github.com/prometheus/client_golang
|
||||
[submodule "vendor/github.com/prometheus/client_model"]
|
||||
path = vendor/github.com/prometheus/client_model
|
||||
url = https://github.com/prometheus/client_model
|
||||
[submodule "vendor/github.com/prometheus/common"]
|
||||
path = vendor/github.com/prometheus/common
|
||||
url = https://github.com/prometheus/common
|
||||
[submodule "vendor/github.com/golang/protobuf"]
|
||||
path = vendor/github.com/golang/protobuf
|
||||
url = https://github.com/golang/protobuf
|
||||
[submodule "vendor/github.com/matttproud/golang_protobuf_extensions"]
|
||||
path = vendor/github.com/matttproud/golang_protobuf_extensions
|
||||
url = https://github.com/matttproud/golang_protobuf_extensions
|
||||
[submodule "vendor/k8s.io/client-go"]
|
||||
path = vendor/k8s.io/client-go
|
||||
url = https://github.com/kubernetes/client-go
|
||||
[submodule "vendor/github.com/pkg/errors"]
|
||||
path = vendor/github.com/pkg/errors
|
||||
url = https://github.com/pkg/errors
|
||||
[submodule "vendor/github.com/spf13/cobra"]
|
||||
path = vendor/github.com/spf13/cobra
|
||||
url = https://github.com/spf13/cobra
|
||||
[submodule "vendor/github.com/spf13/plag"]
|
||||
path = vendor/github.com/spf13/plag
|
||||
url = https://github.com/spf13/pflag
|
||||
[submodule "vendor/github.com/spf13/pflag"]
|
||||
path = vendor/github.com/spf13/pflag
|
||||
url = https://github.com/spf13/pflag
|
||||
[submodule "vendor/github.com/mgutz/ansi"]
|
||||
path = vendor/github.com/mgutz/ansi
|
||||
url = https://github.com/mgutz/ansi
|
||||
[submodule "vendor/github.com/estesp/manifest-tool"]
|
||||
path = vendor/github.com/estesp/manifest-tool
|
||||
url = https://github.com/estesp/manifest-tool
|
||||
[submodule "vendor/github.com/weaveworks/common"]
|
||||
path = vendor/github.com/weaveworks/common
|
||||
url = https://github.com/weaveworks/common
|
||||
@@ -4,7 +4,6 @@
|
||||
# avoid breaking the build on make lint:
|
||||
bin/circle-deploy-issues
|
||||
bin/circle-deploy-master
|
||||
bin/circle-teardown-pre
|
||||
bin/circle-test-teardown
|
||||
bin/circle-test-unit
|
||||
bin/install-wordepress
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
2.6.2
|
||||
+695
@@ -1,3 +1,698 @@
|
||||
## Release 2.6.4
|
||||
|
||||
Improves the iptables rule added in 2.6.3 to block just the Weave Net
|
||||
control port and avoid blocking other uses of 127.0.0.1. #3811
|
||||
|
||||
|
||||
## Release 2.6.3
|
||||
|
||||
This release has a couple of security improvements, and some other fixes.
|
||||
|
||||
* Block non-local traffic to the Weave control port #3805
|
||||
* Tell Linux not to accept router advisory messages #3801
|
||||
* Network Policy Controller: add a metric to show errors while operating #3804
|
||||
* Network Policy Controller: don't treat named port as a fatal error #3790
|
||||
|
||||
## Release 2.6.2
|
||||
|
||||
This release fixes a regression found in 2.6.1 release and removes a possible case
|
||||
of CPU spinning when pcap is used in sleeve mode
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Weave Net can not be used in fastdp mode and always falls back to sleeve mode #3781, #3783
|
||||
* Restrict timeout value passed to pcap library to a value less than 2^31 microseconds to
|
||||
prevent CPU spinning #3782
|
||||
|
||||
## Release 2.6.1
|
||||
|
||||
This release fixes a bug discovered since the release of Weave Net 2.6.0 and
|
||||
an improvement to support iptables 1.8
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Weave Net on node restart unable to form connections to peers and connections get stuck in
|
||||
pending state and stops responding to API endpoint #3762 #3763
|
||||
|
||||
### Other improvements
|
||||
|
||||
* Weave Net Kubernetes images by default uses iptables 1.8 with legacy (netfilter) backend with
|
||||
option to choose netfilter as backend #3465, #3747
|
||||
|
||||
|
||||
## Release 2.6.0
|
||||
|
||||
This release reduces CPU and memory usage in larger clusters, by
|
||||
sending notifications to a smaller set of peers and coalescing updates
|
||||
to reduce topology recalculation. #3715, #3732
|
||||
|
||||
The default soft limit on connections has been raised from 100 to 200.
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fix a race condition in Kubernetes addon when reclaiming IP
|
||||
addresses after node deletion #3724, #3716
|
||||
* Buffer events so Docker won't drop them, and Weave Net can clean up
|
||||
after dead containers #3432, #3705
|
||||
* Weave reconnect occasionally fails after network interface
|
||||
disconnect #3666, #3669, #3676
|
||||
* Ingress NetworkPolicy would accepts all traffic when specifying both
|
||||
IPBlock and port #3653, #3654
|
||||
|
||||
### Kubernetes improvements
|
||||
|
||||
* Support both podSelector and namespaceSelector in NetworkPolicy #3312, #3647
|
||||
* Only add default-drop egress rule if network policies are in use #3639
|
||||
* Manifests use 'apps/v1' rather than deprecated 'apps/v1beta1' #3660
|
||||
* Avoid Weave Net pods being evicted by setting priorityClassName: system-node-critical #3697
|
||||
* Manifests use recommended DNS policy ClusterFirstWithHostNet #3692
|
||||
* Weave Net now tolerates 'NoExecute' taint #3655
|
||||
* Allow extra arguments to NetworkPolicy controller to be set in an environment variable #3683
|
||||
* Stop reporting a failure to connect to self #3454, #3585
|
||||
* Minor reduction in log noise when reclaiming IPs #3710
|
||||
|
||||
### Other improvements
|
||||
|
||||
* Avoid isolating nodes which have restarted by automatically
|
||||
repairing inconsistencies in IP allocation data #1962, #3637, #3708
|
||||
* Build Weave Net for the s390x architecture #3685
|
||||
* When a IP address is requested that may be in use, make several
|
||||
attempts to claim it before returning an error #3725
|
||||
* Improve logging for IP allocation updates #3627, #3630
|
||||
* Improve 'expecting PMTU update' log message on initial connection #3603
|
||||
|
||||
### Build and test
|
||||
|
||||
* Shut down Kubernetes on node when testing node deletion #3716
|
||||
* Update the base 'Alpine' container image to version 3.8 #3701
|
||||
* Update Go to version 1.13.3 #3712
|
||||
* Update gopacket library #3590
|
||||
* Pin busybox version to 1.28 to avoid CI failure in Python test #3689
|
||||
* Remove obsolete weave-daemonset.yaml file #3674
|
||||
|
||||
Thanks to contributors @christian-2, @hpdvanwyk, @guirish, @kitt1987,
|
||||
@mmerrill3, @Pensu, @scritchley, @sidharthsurana, @tanishq-dubey
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/78?closed=1)
|
||||
|
||||
|
||||
## Release 2.5.2
|
||||
|
||||
This release fixes several bugs causing inconsistencies in IPAM and fixes a
|
||||
panic in daemon that reclaims and forgets deleted nodes in Kubernetes clusters.
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* leaking IP addresses in IPAM when weave-net hangs or restarting #3587, #3638
|
||||
* Panic in kube-utils after config map update error resulting in reclaim daemon
|
||||
not performing weave forget on deleted node #3613, #3623
|
||||
* preventing inconsistent IPAM entries and resolving the conflict in case of
|
||||
conflict during IPAM ring merge #3629, #3635, #3632, #3444
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/79?closed=1)
|
||||
|
||||
|
||||
## Release 2.5.1
|
||||
|
||||
This release fixes bugs reported for 2.5 release and small improvements.
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* prevent warnings in kernal logs due to use of physdev model for
|
||||
non-bridged traffic #3449, #3453
|
||||
* Check and report the error occurred while getting the list of Kubernetes
|
||||
peers #3581, #3582
|
||||
|
||||
### Other improvements
|
||||
|
||||
* Update Ruby version to match Weaveworks website #3458
|
||||
* Fix the link to "Troubleshooting the Kubernetes Addon" section in the docs #3461
|
||||
* Document concept of connection limit in Weave #3395, #3413
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/77?closed=1)
|
||||
|
||||
|
||||
## Release 2.5.0
|
||||
|
||||
This release adds support for Kubernetes `hostPort` mapping (#3016,#3356)
|
||||
and the `ipBlock` NetworkPolicy feature (#3168,#3367)
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fix a crash at start-up on Docker for Mac #3405, #3408
|
||||
* Network policy: block ingress traffic when no namespaceSelector or
|
||||
podSelector is specified #3347
|
||||
* Reclaim IP addresses which are locked by a non-existent peer #3386, #3416
|
||||
* Fix a crash when blank IP data was loaded #3067, #3415
|
||||
|
||||
### Other improvements
|
||||
|
||||
* If a connection is downgraded to the slower "sleeve" mode, Weave Net
|
||||
will now periodically try to upgrade it to "fast datapath" again. #1737, #3385
|
||||
* Reclaim removed Kubernetes nodes' IP space when they are deleted,
|
||||
rather than on next restart #3372, #3399
|
||||
* Replace Kubernetes livenessProbe with readinessProbe, so the pod is
|
||||
not killed if it runs slowly #3471, #3421
|
||||
* In Kubernetes NetworkPolicy controller, remove the need to maintain
|
||||
a set of local pod IP addresses #3344, #3423
|
||||
* Don't crash on Kubernetes named port in NetworkPolicy, just report as unsupported #3375
|
||||
* Ensure the `weave` network bridge is accessible on Linux kernels
|
||||
older than 3.14 #3442, #3297, #3239
|
||||
* Better reporting in the logs if the `weave` network device is in the Down state #3133, #3381
|
||||
* Change log-level to debug of calls through the Docker proxy, to reduce noise #3439
|
||||
* Add `--without-masquerade` option to `weave expose`, so external
|
||||
services can see the original container IP address #3388
|
||||
* Include Kubernetes cluster information in checkpoint call #3324,#3431
|
||||
* Bump go-odp dependency, so that `fastdp` works on the 4.19 kernel #3430
|
||||
|
||||
### Build and Testing
|
||||
|
||||
* CI builds on master branch now publish images for all platforms
|
||||
* Fix golint path and use https for download of libpcap #3435
|
||||
* Update Kubernetes client-go to v8.0.0, removing code licenced under LGPL3 #3358,#3366
|
||||
* Migrate CircleCI to V2, which is much faster #3255,#3270
|
||||
|
||||
### External Contributors
|
||||
|
||||
Thanks to the following contributors:
|
||||
|
||||
* @Ashiroq
|
||||
* @leprechau
|
||||
* @lkpdn
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/74?closed=1)
|
||||
|
||||
|
||||
## Release 2.4.1
|
||||
|
||||
This release fixes several bugs causing inconsistencies in IPAM for
|
||||
Kubernetes users whose clusters scale up and down over time.
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Nodes unable to connect after Kubernetes addon erroneously reclaimed
|
||||
node without any IP addresses #3392, #3393
|
||||
* Kubernetes addon could have run out of free IP addresses after nodes
|
||||
are deleted #3384, #3400
|
||||
* Kubernetes addon had reduced free IP addresses due to not reclaiming
|
||||
IP addresses when node name is re-used #3397
|
||||
|
||||
### Other improvements
|
||||
|
||||
* Support `--label` in `WEAVE_DOCKER_ARGS` when starting Weave #3370,#3371
|
||||
* Add missing `--token` argument in help for `weave launch` #3226, #3379
|
||||
* Print defunct processes after smoke-tests #3362
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/75?closed=1)
|
||||
|
||||
|
||||
## Release 2.4.0
|
||||
|
||||
This release introduces a support for Kubernetes Egress Network Policy (#2624, #3313)
|
||||
and adds a mechanism for preserving the client source IP address to enable
|
||||
`externalTrafficPolicy: Local` on Kubernetes (#2924, #3298).
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Increase the ipset list size which prevents weave-npc from crashing on older
|
||||
kernels when more than eight Kubernetes Namespaces are used (#3289, #3305).
|
||||
* Avoid a possible livelock when reclaiming IP address space in weave-kube (#3317).
|
||||
* Ensure `xtables.lock` is mounted as a file so that kube-proxy can take the lock
|
||||
if it has started after Weave Net (#3351, #3353).
|
||||
* Upgrade the CNI plugin symlinks only if the plugin has changed (#3337, #3345).
|
||||
|
||||
### Other improvements
|
||||
|
||||
* Manipulate the Kubernetes node status `NetworkUnavailable` so that Pods can be
|
||||
scheduled on nodes when the GCE cloud provider is in use (#3249, #3307, #3332, #3334).
|
||||
* Refrain from creating a subprocess for configuring a network interface in
|
||||
a container network namespace (#3291).
|
||||
* Protect against handling the CNI plugin request with the host namespace which
|
||||
prevents Weave Net from misconfiguring the host network (#3206, #3346).
|
||||
* Weave Net can be run on minikube VM (#3124).
|
||||
* Add `org.opencontainers.image.*` labels to Dockerfiles to improve association
|
||||
of the container images with git revisions (#3299).
|
||||
* Improve the error message when running `weave reset` on Kubernetes (#3319).
|
||||
|
||||
### Build and Testing
|
||||
|
||||
* Use `dep` instead of `git submodules` for managing external packages (#3268).
|
||||
* Fix usage of `manifest-tool` in Makefile (#3320).
|
||||
* Update Kubernetes to 1.11 for the integration tests (#3340).
|
||||
|
||||
### External Contributors
|
||||
|
||||
Thanks to the following contributors:
|
||||
|
||||
* @kitt1987
|
||||
* @louismunro
|
||||
* @Nodraak
|
||||
* @stevenjohnstone
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/73?closed=1)
|
||||
|
||||
## Release 2.3.0
|
||||
|
||||
### Security fixes
|
||||
|
||||
* By default, do not expose Weave "/status" and "/report" to all (0.0.0.0) when
|
||||
running on Kubernetes #3271
|
||||
|
||||
### Other improvements
|
||||
|
||||
* Increase the default connection limit for Weave peers (from 30 to 100) when
|
||||
running on Kubernetes, so that more peers could directly connect #3265
|
||||
|
||||
## Build and test
|
||||
|
||||
* Build Weave Net with Go 1.10.1 #3273
|
||||
* Run integration tests against Kubernetes 1.10.0 #3266
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/70?closed=1)
|
||||
|
||||
## Release 2.2.1
|
||||
|
||||
### Bug fixes
|
||||
|
||||
* Fix a bug in weave-npc which would allow ingress traffic to Kubernetes Pods selected
|
||||
by a NetworkPolicy in which source and destination selectors were the same #3222,#3237
|
||||
* Fix a bug in weave-npc which would crash if a previously deleted Kubernetes Namespace
|
||||
has been created again #3247,#3250
|
||||
|
||||
### Other improvements
|
||||
|
||||
* Increase the default connection limit for Weave peers (from 30 to 100), so that
|
||||
more peers could directly connect #3234
|
||||
* When doing a rolling update of Weave Net on Kubernetes, allow each node five seconds
|
||||
to initialize before rolling next Weave Net Pod, so that issues at startup will halt
|
||||
the rollout and not spread across the whole cluster #3235
|
||||
* Install common CA certificates from Alpine Linux package instead of copying
|
||||
them manually #3236
|
||||
|
||||
### External contributors
|
||||
|
||||
Thanks to the following contributors:
|
||||
|
||||
* @alok87
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/71?closed=1)
|
||||
|
||||
## Release 2.2.0
|
||||
|
||||
This release improves the way Weave Net configures Linux network
|
||||
devices and network filter rules, so that it is more robust in the face
|
||||
of unexpected changes in its environment. #3204,#3224
|
||||
|
||||
As a consequence of these changes, the `weave attach` command will now
|
||||
fail unless the Weave Net daemon is up and running - previously it was
|
||||
possible to run independently as long as you managed all IP addresses
|
||||
yourself.
|
||||
|
||||
## Other improvements
|
||||
|
||||
* Update library miekg/dns for CVE-2017-15133 (details under embargo) #3223,#3227
|
||||
* Reduce the volume of logging from weave-npc #3183
|
||||
* Add ability to set log level for Docker "v2" plugin, and change
|
||||
default log level from DEBUG to INFO #3197
|
||||
* Downgrade log messages about Discovery and Expiration to DEBUG level #3202,#3203
|
||||
* Use command-line parameter for WeaveDNS address in Docker proxy #3196
|
||||
|
||||
## Bug fixes
|
||||
|
||||
* Ensure that rules to block traffic for NetworkPolicy are placed
|
||||
ahead of rules that Kubernetes has added to allow other traffic #3209,#3210
|
||||
|
||||
## Build and test
|
||||
|
||||
* Update CI tests to use Kubernetes 1.9.2 #3229
|
||||
* Remove "daily update" from test VMs that only run for a few minutes #3224
|
||||
|
||||
## External Contributors
|
||||
|
||||
Thanks to the following contributors:
|
||||
@vetal4444
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/67?closed=1)
|
||||
|
||||
|
||||
## Release 2.1.3
|
||||
|
||||
This release fixes a race-condition in the IP reclaim code for weave-kube
|
||||
where two nodes could end up fighting over the same space and break
|
||||
connectivity #3190, #3192
|
||||
|
||||
## Release 2.1.2
|
||||
|
||||
This release fixes a couple of bugs discovered since the release of Weave Net 2.1.0
|
||||
|
||||
##Bug fixes
|
||||
|
||||
* Fix crash seen when starting 10-15 nodes simultaneously #3184,#3186
|
||||
* Fix NetworkPolicy blocking traffic if updates come out of order from Kubernetes #3177,#3181
|
||||
|
||||
Thanks to the following contributors:
|
||||
@zignig
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/66?closed=1)
|
||||
|
||||
|
||||
## Release 2.1.1
|
||||
|
||||
As 2.1.0, but fixing a couple of installation glitches. #3175,#3176
|
||||
|
||||
## Release 2.1.0
|
||||
|
||||
##New Features
|
||||
|
||||
Improved Kubernetes Network Policy - Weave Net now supports the
|
||||
'v1' policies introduced in Kubernetes 1.7 as well as the 'beta'
|
||||
policies supported previously. See [Kubernetes 1.7 changelog](https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG-1.7.md#network)
|
||||
for differences. To use old policies, `--use-legacy-netpol` argument
|
||||
should be passed to `weave-npc`. #3105,#3141,#3151,#3169
|
||||
|
||||
Weave Net now reclaims IP addresses owned by Kubernetes nodes which
|
||||
have been deleted from the cluster - this avoids running out of IP
|
||||
addresses when many nodes are added and deleted over a long period.
|
||||
#2797,#3149,#3170,#3172
|
||||
|
||||
##Other improvements
|
||||
|
||||
* Export a Prometheus-style metric giving count of unreachable peers #3119
|
||||
* Update 'gopacket' library to reduce memory use by approx 15MB #3160
|
||||
* Replace bundling the 'docker' binary with our own code to avoid
|
||||
security vulnerability alerts and save space #2957,#3110
|
||||
|
||||
##Bug fixes
|
||||
|
||||
* When `weave expose` is used, allow traffic into the Weave network -
|
||||
up till version 1.12 Docker would do this for us, but in 1.13 they
|
||||
stopped so now we do it. This change makes `weave expose` require
|
||||
Weave Net to be running. #2758,#3122
|
||||
* Arm64 build now works on non-kubernetes installs #2832,#3110
|
||||
* TX offload was being disabled in 'awsvpc' mode, which slows down packet sending #3089
|
||||
* Removed spurious 'nil' in logs from CNI DEL operation #3143
|
||||
|
||||
##Build and test
|
||||
|
||||
* Images are now also built for the ppc64le platform #3129
|
||||
* Tweak build scripts to run on OSX as well as Linux #3135
|
||||
|
||||
##External Contributors
|
||||
|
||||
Thanks to the following contributors:
|
||||
@caarlos0
|
||||
@dtshepherd
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/59?closed=1)
|
||||
|
||||
|
||||
## Release 2.0.5
|
||||
|
||||
Bug fixes
|
||||
|
||||
* Fix /etc/hosts inside containers so the container's name resolves to
|
||||
its Weave Net address #3136,#3138
|
||||
* New weave-kube config for Kubernetes 1.7 and 1.8 which resolves an
|
||||
intermittent conflict with kube-proxy that could break Weave Net
|
||||
until reboot #2998,#3134
|
||||
* Remove persistence file in `weave reset` when using Docker plugin V2 #3103,#3114
|
||||
|
||||
Build and test
|
||||
|
||||
* Some trivial changes to placate go-lint #3137
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/63?closed=1).
|
||||
|
||||
|
||||
## Release 2.0.4
|
||||
|
||||
Bug fixes
|
||||
|
||||
* weave-npc failed on Centos 7, due to older 'ipset' version in kernel #3099,#3100
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/62?closed=1).
|
||||
|
||||
|
||||
## Release 2.0.3
|
||||
|
||||
Bug fixes
|
||||
|
||||
* Weave-npc would crash on a policy with no 'from' part - regression introduced in 2.0.2 #3095,#3096,#3097
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/61?closed=1).
|
||||
|
||||
|
||||
## Release 2.0.2
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
* Fix race condition in weave-npc which would intermittently block all traffic for a namespace #3057,#3059
|
||||
* Ensure Fast Datapath works on machines that need to mount the kernel module dynamically #3080
|
||||
* Regression: weave-npc would block everything if `kubelet --hostname-override` was used #3049,#3051
|
||||
* Fix netfilter rules to block containers from accessing the Weave Net control endpoint #3093
|
||||
* If DNS server is off then disable proxy DNS registration, to avoid spurious errors #3054,#3088
|
||||
* Add comments to each iptables rule and ipset, to help when troubleshooting #3064
|
||||
* Remove code that checked for an outdated fallback address for Kubernetes api-server #3071
|
||||
* Add a label to the weavedb image so it can be filtered out by tools #3066
|
||||
* Fix various build and continuous-integration failures #3061
|
||||
* Print 'help' text faster in the weave script #3056
|
||||
* Add an option to create continuous integration hosts in different ways #3060
|
||||
* Remove remnants of the pre-2.0 proxy and plugin from build and test #3035,#3036
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/60?closed=1).
|
||||
|
||||
|
||||
## Release 2.0.1
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
* Fall back to slower data path (`sleeve`), rather than crashing, when the machine lacks VXLAN support (required for “fast data path”, `fastdp`) #3043
|
||||
* Fix bug in processing of arguments when Docker has TLS enabled, rather than crashing with invalid peers list, e.g. `lookup --tlsverify: no such host` #3039
|
||||
* Add `kube-system` namespace back to `weave-kube`'s YAMLs, preventing omissions leading to errors like `error contacting APIServer: the server does not allow access to the requested resource` #3033,#3042
|
||||
* Fix release script to prevent ARM64 binaries to end up in AMD64 `net-plugin`, leading to `Error response from daemon: dial unix /run/docker/plugins/<id>/weave.sock: connect: no such file or directory` when installing `net-plugin` #3045
|
||||
* `weave reset` and `weave rmpeer` now only contact Weave Cloud when Weave Net is configured with a Weave Cloud token, preventing unnecessary requests and potentially confusing `401 Unauthorized` errors in Weave Net’s logs #3044
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/58?closed=1).
|
||||
|
||||
|
||||
## Release 2.0.0
|
||||
|
||||
New Features
|
||||
|
||||
Peer Discovery via Weave Cloud
|
||||
|
||||
You can now get all your Weave Net peers to find each other via the
|
||||
Weave Cloud service, instead of maintaining a list of peers at
|
||||
startup. #2799,#2827
|
||||
|
||||
See the [docs
|
||||
page](https://github.com/weaveworks/weave/blob/master/site/using-weave/weave-cloud.md)
|
||||
for more details
|
||||
|
||||
New Docker Plugin
|
||||
|
||||
Docker has a [new plugin
|
||||
system](https://docs.docker.com/engine/extend/) which improves the
|
||||
installation UX and solves some issues around startup. This means
|
||||
Weave Net 2.0 can now run with Docker in "swarm mode" and supports the
|
||||
`docker service` command. #2396,#2397,#2651,#2727,#2805,#2816,#2905,
|
||||
#2906,#2929,#2932,#2945,#2950,#2956,#2963,#2964,#2966,#3019
|
||||
|
||||
The previous Docker Plugin is still available and can be installed as before.
|
||||
|
||||
All of Weave Net now runs in one container
|
||||
|
||||
Previously we had three separate containers for routing, Docker API
|
||||
proxy and Docker plugin. Running everything in one simplifies start-up
|
||||
and removes the need to detect various error
|
||||
conditions. #1642,#2897,#2936,#2945,#2946,#2951,#2960
|
||||
|
||||
The individual commands ‘weave launch-router’, ‘weave launch-plugin’,
|
||||
etc., have been removed. You can turn off the plugin and proxy with
|
||||
new command-line options. In keeping with Semantic Versioning, we have
|
||||
changed the major version number for this release.
|
||||
|
||||
Other new features
|
||||
|
||||
* Kubernetes configuration now comes from our “Launch Generator” that
|
||||
allows different options to be selected via
|
||||
URL. #2754,#2903,#3000,#3001
|
||||
* `weave-kube` now stores data about IP allocation in `/var/lib/weave`
|
||||
on the host instead of in a Kubernetes volume. This means that the
|
||||
data will persist across pod deletion and re-creation, e.g. during
|
||||
an upgrade of Weave Net, which makes restarts more
|
||||
reliable. #2610,#2967
|
||||
* `weave-kube` turned on rolling updates, so careful manual handling
|
||||
of updates is no longer required. #3024
|
||||
|
||||
Bug fixes
|
||||
|
||||
* Kubernetes Network Policies which allowed a specific set of pods to
|
||||
connect would block all pods on other hosts. Revert the change in
|
||||
v1.9.6 which ignored pods on other hosts #3025,#3028
|
||||
|
||||
Features removed
|
||||
|
||||
* `weave run` has been removed. This was the original method provided
|
||||
to start containers with Weave Net, but it always required care over
|
||||
timing of start-up, and we now provide three alternative, better,
|
||||
ways. You can replicate the effect by calling `docker run` then
|
||||
`weave attach`. Similarly `weave start` and `weave restart` were
|
||||
removed. #2353,#2885
|
||||
* Everything deprecated more than one release ago has been removed, so
|
||||
if you use it now you get an error rather than a warning. This
|
||||
includes the ‘create-bridge’ command and older command-line
|
||||
arguments, e.g. `--iprange` was replaced by `--ipalloc-range`
|
||||
#2901,#2909,#2913,#2942,#2989,#2991
|
||||
|
||||
Functions moved from shell-script to Go code.
|
||||
|
||||
This enables more precise error-checking and runs a bit faster. It has
|
||||
also enabled us to shrink the size of images downloaded: `weave-kube`
|
||||
is 101MB compared to 163MB previously #2953,#2954,#2974
|
||||
|
||||
Specific items that moved from shell-script to Go:
|
||||
* Setting up the `weave` bridge #1958,#2975,#2977,#2978
|
||||
* Container attachment #2947
|
||||
* Creation of the ’weave’ default plugin network #2920
|
||||
|
||||
Minor improvements
|
||||
|
||||
* You can now restart the Weave Net router without requiring the proxy to be enabled #2112
|
||||
* Plugin (legacy version) now respects `--ipalloc-default-subnet` option #2919
|
||||
* The `weave` script now detects and issues an error message if
|
||||
`weave-kube` is running and you attempt to launch again from the script. #2709/#2966
|
||||
* It is now possible to choose the the MAC address of the `weave`
|
||||
bridge using `--name`, in case your hosts have identical unique
|
||||
IDs. #2900
|
||||
* Relaxed Kubernetes tolerations for Weave Net's daemonset in order to
|
||||
match any node (previously, only taints directed at master). #3018
|
||||
* Kubernetes' `seLinuxOptions` configuration is now empty by default,
|
||||
to reduce spurious failures on hosts not using seLinux. #3001
|
||||
* Improved reliability of namespace changes via `nsenter`. #2992
|
||||
* `weave ps` now fetches the list container IDs internally, rather
|
||||
than calling out to `docker ps` #2814,#2898
|
||||
* at startup, actively remove dead containers’ Weave Net IP addresses from IPAM #3013
|
||||
* at startup, only check live containers to see if they have an
|
||||
existing Weave Net IP address #2815,#2829
|
||||
* Weave Net CNI plugin now logs but does not raise an error if
|
||||
anything goes wrong during network interface delete, to be more
|
||||
compatible with Kubernetes 1.6. #2928
|
||||
* Stop running a shell in “privileged” mode when it’s only writing a file #2838
|
||||
* New internal REST endpoint to return all IP address mappings. #1350
|
||||
* Changed the wording where we do not log the password #2833
|
||||
* Fixed typo in plugin error messages #2894
|
||||
|
||||
Build and test
|
||||
|
||||
* Weave Net is now built with Go version 1.8, which has better code
|
||||
generation and garbage collection #2914
|
||||
* During smoke-tests, use a webserver instead of just `ping` so we get
|
||||
a more realistic test that the Weave network is working #2918
|
||||
* When installing dependencies for the build container, use a
|
||||
keyserver port that's better for firewalls #2812
|
||||
* Kubernetes test script now scales up to more hosts, and works with
|
||||
Kubernetes 1.6 #2837,#2853,#2923
|
||||
* Other minor build improvements and refactoring #2760,#2910
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/49?closed=1).
|
||||
|
||||
|
||||
## Release 1.9.8
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
* Fix weave-npc blocking NodePort and any other non-local access #3011,#3014
|
||||
* Fix bug where IPAM would duplicate a fixed IP address assigned via Docker plugin #3003,#3010
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/56?closed=1).
|
||||
|
||||
|
||||
## Release 1.9.7
|
||||
|
||||
This is identical to 1.9.6 with one additional bug-fix:
|
||||
|
||||
* weave-npc would block everything if `kubelet --hostname-override` was used #2995,#2996
|
||||
|
||||
## Release 1.9.6
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
* Ensure that Kubernetes pods can contact a service implemented within
|
||||
the same pod, by turning on "hairpin mode". This is required because
|
||||
of a quiet change between Kubernetes 1.5 and 1.6. #2993
|
||||
* Network Policy Controller (`weave-npc`) now checks local addresses
|
||||
only, so it doesn't interfere with cross-cluster traffic. It should
|
||||
be more efficient too #2622,#2973,#2979
|
||||
* Stop reporting back to Kubernetes any issues encountered when
|
||||
deleting a pod's network interface. This is required because
|
||||
of a quiet change between Kubernetes 1.5 and 1.6. #2921,#2928
|
||||
* Fixed an issue whereby `weave-npc` couldn't start because one
|
||||
`ipset` was referring to another one and could not be destroyed #2915,#2949
|
||||
* Improved the code which checks whether the kernel supports `ipset` #2934,#2935
|
||||
* `weave-npc` now creates ipsets with only valid xml characters in the
|
||||
name #2958,#2959
|
||||
|
||||
Build and Testing
|
||||
|
||||
* In build container use cross-compilers from debian package
|
||||
repository, so they match other components #2940
|
||||
* Pin the version of the linting tool `shfmt` so the set of things it
|
||||
checks is stable #2987
|
||||
* Fix lint error in script that runs smoke-tests #2962
|
||||
* Moved website publishing from Wordpress to Netlify #2986
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/55?closed=1).
|
||||
|
||||
## Release 1.9.5
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
* Improve log messages generated if "hairpin" conditions are detected,
|
||||
to make clear which kind is likely to cause problems #2808/#2926
|
||||
* Filter out IPv6 peer addresses from Kubernetes; Weave Net currently
|
||||
only supports IPv4 #2904/#2912
|
||||
* Fix rare crash during initialization of weave-kube #2893/#2892
|
||||
* Include overlay and encryption modes in checkpoint reports, in case
|
||||
this is relevant to a version upgrade #2771/#2907
|
||||
|
||||
Build and Testing
|
||||
|
||||
* Ensure CI build can run gcloud tools #2887
|
||||
* Prevent kubeadm from upgrading Kubernetes if we are trying to test an older version #2886
|
||||
* Upgrade build scripts to support Kubernetes 1.6 #2880
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/54?closed=1).
|
||||
|
||||
## Release 1.9.4
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
* Support Kubernetes 1.6 by creating a new DaemonSet #2777,#2801
|
||||
* Support Kubernetes 1.6 by allowing CNI callers to send a
|
||||
network-delete request for a container that is not running or has
|
||||
never been attached to the network #2850
|
||||
* Leave non-weave ipsets alone in Network Policy Controller (e.g. when
|
||||
running Weave Net alonside keepalived-vip) #2751,#2846
|
||||
* Fix various small issues revealed by 'staticcheck' tool #2843,#2857
|
||||
* Avoid leaving 'defunct' processes when weave-kube container restarts #2836,#2845
|
||||
* When using the CNI plugin with a non-standard network configuration
|
||||
file, the weave bridge could get the same IP as a container, if
|
||||
'weave expose' hadn't run at that point #2839,#2856
|
||||
|
||||
Build and Testing
|
||||
|
||||
* Check that no defunct processes remain after each test #2852
|
||||
* Update build and test scripts to work with Kubernetes 1.6 beta #2851
|
||||
|
||||
[Full list of changes](https://github.com/weaveworks/weave/milestone/53?closed=1).
|
||||
|
||||
## Release 1.9.3
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
* Fixed a race condition in Fast Datapath encrypted connections which could lead Weave Net to crash: #2824, #2825.
|
||||
|
||||
## Release 1.9.2
|
||||
|
||||
Bug fixes and minor improvements
|
||||
|
||||
@@ -24,6 +24,25 @@ taking the time to read this guide.
|
||||
* Keep pull requests small so core developers can review them quickly.
|
||||
* Keep each pull request focused on a specific topic. If you have two things to change, create two pull requests. This helps reviewers to understand the meat of your contribution.
|
||||
|
||||
## Managing Vendored Dependencies
|
||||
|
||||
We copy all Go dependencies into the `vendor` subdirectory and use
|
||||
[`dep`](https://github.com/golang/dep) to manage them.
|
||||
|
||||
If you add a dependency in the code, run `dep ensure` to include it in
|
||||
the config and vendor dir. Same if you remove a dependency from the code.
|
||||
|
||||
To update a single dependency run `dep ensure -update github.com/foo/bar`
|
||||
|
||||
To override `dep`'s choice of library version, you can add
|
||||
[rules](https://golang.github.io/dep/docs/daily-dep.html#rule-changes-in-gopkgtoml).
|
||||
Please add a comment saying why the override is necessary and when it can be
|
||||
removed.
|
||||
|
||||
These operations result in staged but uncommitted changes to your
|
||||
branch; you will need to commit them as normal. Execute them in the
|
||||
root of your checkout.
|
||||
|
||||
## Workflow
|
||||
See [WorkingOnWeave](https://github.com/weaveworks/weave/wiki/WorkingOnWeave)
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
This file lists all individuals having contributed content to the
|
||||
repository.
|
||||
|
||||
Matthew Sackman <matthew@wellquite.org>
|
||||
Matthias Radestock <matthias.radestock@gmail.com>
|
||||
+175
@@ -0,0 +1,175 @@
|
||||
./test/assert.sh is a copy of
|
||||
|
||||
https://github.com/lehmannro/assert.sh/blob/master/assert.sh
|
||||
|
||||
Since it was imported from its original source, it has only received
|
||||
cosmetic modifications. As it is licensed under the LGPL-3, here's the
|
||||
license text in its entirety:
|
||||
|
||||
|
||||
|
||||
GNU LESSER GENERAL PUBLIC LICENSE
|
||||
Version 3, 29 June 2007
|
||||
|
||||
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
|
||||
This version of the GNU Lesser General Public License incorporates
|
||||
the terms and conditions of version 3 of the GNU General Public
|
||||
License, supplemented by the additional permissions listed below.
|
||||
|
||||
0. Additional Definitions.
|
||||
|
||||
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||
General Public License.
|
||||
|
||||
"The Library" refers to a covered work governed by this License,
|
||||
other than an Application or a Combined Work as defined below.
|
||||
|
||||
An "Application" is any work that makes use of an interface provided
|
||||
by the Library, but which is not otherwise based on the Library.
|
||||
Defining a subclass of a class defined by the Library is deemed a mode
|
||||
of using an interface provided by the Library.
|
||||
|
||||
A "Combined Work" is a work produced by combining or linking an
|
||||
Application with the Library. The particular version of the Library
|
||||
with which the Combined Work was made is also called the "Linked
|
||||
Version".
|
||||
|
||||
The "Minimal Corresponding Source" for a Combined Work means the
|
||||
Corresponding Source for the Combined Work, excluding any source code
|
||||
for portions of the Combined Work that, considered in isolation, are
|
||||
based on the Application, and not on the Linked Version.
|
||||
|
||||
The "Corresponding Application Code" for a Combined Work means the
|
||||
object code and/or source code for the Application, including any data
|
||||
and utility programs needed for reproducing the Combined Work from the
|
||||
Application, but excluding the System Libraries of the Combined Work.
|
||||
|
||||
1. Exception to Section 3 of the GNU GPL.
|
||||
|
||||
You may convey a covered work under sections 3 and 4 of this License
|
||||
without being bound by section 3 of the GNU GPL.
|
||||
|
||||
2. Conveying Modified Versions.
|
||||
|
||||
If you modify a copy of the Library, and, in your modifications, a
|
||||
facility refers to a function or data to be supplied by an Application
|
||||
that uses the facility (other than as an argument passed when the
|
||||
facility is invoked), then you may convey a copy of the modified
|
||||
version:
|
||||
|
||||
a) under this License, provided that you make a good faith effort to
|
||||
ensure that, in the event an Application does not supply the
|
||||
function or data, the facility still operates, and performs
|
||||
whatever part of its purpose remains meaningful, or
|
||||
|
||||
b) under the GNU GPL, with none of the additional permissions of
|
||||
this License applicable to that copy.
|
||||
|
||||
3. Object Code Incorporating Material from Library Header Files.
|
||||
|
||||
The object code form of an Application may incorporate material from
|
||||
a header file that is part of the Library. You may convey such object
|
||||
code under terms of your choice, provided that, if the incorporated
|
||||
material is not limited to numerical parameters, data structure
|
||||
layouts and accessors, or small macros, inline functions and templates
|
||||
(ten or fewer lines in length), you do both of the following:
|
||||
|
||||
a) Give prominent notice with each copy of the object code that the
|
||||
Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
4. Combined Works.
|
||||
|
||||
You may convey a Combined Work under terms of your choice that,
|
||||
taken together, effectively do not restrict modification of the
|
||||
portions of the Library contained in the Combined Work and reverse
|
||||
engineering for debugging such modifications, if you also do each of
|
||||
the following:
|
||||
|
||||
a) Give prominent notice with each copy of the Combined Work that
|
||||
the Library is used in it and that the Library and its use are
|
||||
covered by this License.
|
||||
|
||||
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||
document.
|
||||
|
||||
c) For a Combined Work that displays copyright notices during
|
||||
execution, include the copyright notice for the Library among
|
||||
these notices, as well as a reference directing the user to the
|
||||
copies of the GNU GPL and this license document.
|
||||
|
||||
d) Do one of the following:
|
||||
|
||||
0) Convey the Minimal Corresponding Source under the terms of this
|
||||
License, and the Corresponding Application Code in a form
|
||||
suitable for, and under terms that permit, the user to
|
||||
recombine or relink the Application with a modified version of
|
||||
the Linked Version to produce a modified Combined Work, in the
|
||||
manner specified by section 6 of the GNU GPL for conveying
|
||||
Corresponding Source.
|
||||
|
||||
1) Use a suitable shared library mechanism for linking with the
|
||||
Library. A suitable mechanism is one that (a) uses at run time
|
||||
a copy of the Library already present on the user's computer
|
||||
system, and (b) will operate properly with a modified version
|
||||
of the Library that is interface-compatible with the Linked
|
||||
Version.
|
||||
|
||||
e) Provide Installation Information, but only if you would otherwise
|
||||
be required to provide such information under section 6 of the
|
||||
GNU GPL, and only to the extent that such information is
|
||||
necessary to install and execute a modified version of the
|
||||
Combined Work produced by recombining or relinking the
|
||||
Application with a modified version of the Linked Version. (If
|
||||
you use option 4d0, the Installation Information must accompany
|
||||
the Minimal Corresponding Source and Corresponding Application
|
||||
Code. If you use option 4d1, you must provide the Installation
|
||||
Information in the manner specified by section 6 of the GNU GPL
|
||||
for conveying Corresponding Source.)
|
||||
|
||||
5. Combined Libraries.
|
||||
|
||||
You may place library facilities that are a work based on the
|
||||
Library side by side in a single library together with other library
|
||||
facilities that are not Applications and are not covered by this
|
||||
License, and convey such a combined library under terms of your
|
||||
choice, if you do both of the following:
|
||||
|
||||
a) Accompany the combined library with a copy of the same work based
|
||||
on the Library, uncombined with any other library facilities,
|
||||
conveyed under the terms of this License.
|
||||
|
||||
b) Give prominent notice with the combined library that part of it
|
||||
is a work based on the Library, and explaining where to find the
|
||||
accompanying uncombined form of the same work.
|
||||
|
||||
6. Revised Versions of the GNU Lesser General Public License.
|
||||
|
||||
The Free Software Foundation may publish revised and/or new versions
|
||||
of the GNU Lesser General Public License from time to time. Such new
|
||||
versions will be similar in spirit to the present version, but may
|
||||
differ in detail to address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the
|
||||
Library as you received it specifies that a certain numbered version
|
||||
of the GNU Lesser General Public License "or any later version"
|
||||
applies to it, you have the option of following the terms and
|
||||
conditions either of that published version or of any later version
|
||||
published by the Free Software Foundation. If the Library as you
|
||||
received it does not specify a version number of the GNU Lesser
|
||||
General Public License, you may choose any version of the GNU Lesser
|
||||
General Public License ever published by the Free Software Foundation.
|
||||
|
||||
If the Library as you received it specifies that a proxy can decide
|
||||
whether future versions of the GNU Lesser General Public License shall
|
||||
apply, that proxy's public statement of acceptance of any version is
|
||||
permanent authorization for you to choose that version for the
|
||||
Library.
|
||||
+3
-3
@@ -1,3 +1,3 @@
|
||||
DOCKER_VERSION=1.11.2
|
||||
KUBERNETES_VERSION=1.5.2
|
||||
KUBERNETES_CNI_VERSION=0.3.0.1
|
||||
DOCKER_VERSION=17.06
|
||||
KUBERNETES_VERSION=1.11.0
|
||||
KUBERNETES_CNI_VERSION=0.6.0
|
||||
Generated
+1138
File diff suppressed because it is too large.
Load diff
+34
@@ -0,0 +1,34 @@
|
||||
# Pin to version before sctp was added, because latest libnetwork is
|
||||
# incompatible with latest sctp
|
||||
[[constraint]]
|
||||
name = "github.com/docker/libnetwork"
|
||||
revision = "119badbd"
|
||||
|
||||
# Encourage dep not to use the docker-1.9 version
|
||||
[[constraint]]
|
||||
name = "github.com/fsouza/go-dockerclient"
|
||||
version = "1"
|
||||
|
||||
# Use recent version: last tagged version is years old
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/prometheus/client_golang"
|
||||
|
||||
# For compatibility with fsouza/go-dockerclient
|
||||
[[constraint]]
|
||||
branch = "master"
|
||||
name = "github.com/docker/docker"
|
||||
|
||||
# Most recent release tag is incompatible with docker/docker; see https://github.com/docker/distribution/issues/2503
|
||||
[[override]]
|
||||
branch = "master"
|
||||
name = "github.com/docker/distribution"
|
||||
|
||||
# v8.0.0 removes juju/ratelimit licensed under LGPL3
|
||||
[[constraint]]
|
||||
name = "k8s.io/client-go"
|
||||
revision = "v8.0.0"
|
||||
|
||||
[prune]
|
||||
go-tests = true
|
||||
unused-packages = true
|
||||
@@ -176,7 +176,7 @@
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2014-2016 Weaveworks Ltd.
|
||||
Copyright 2014-2017 Weaveworks Ltd.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
.DEFAULT: all
|
||||
.PHONY: all exes testrunner update tests lint publish-one-arch $(PUBLISH) clean clean-bin prerequisites build run-smoketests
|
||||
.PHONY: all exes testrunner update tests lint publish-one-arch $(PUBLISH) clean clean-bin clean-work-dir prerequisites build run-smoketests
|
||||
|
||||
# If you can use docker without being root, you can do "make SUDO="
|
||||
SUDO=$(shell docker info >/dev/null 2>&1 || echo "sudo -E")
|
||||
@@ -17,15 +17,15 @@ QEMU_VERSION=v2.7.0
|
||||
|
||||
# A list of all supported architectures here. Should be named as Go is naming platforms
|
||||
# All supported architectures must have an "ifeq" block below that customizes the parameters
|
||||
ALL_ARCHITECTURES=amd64 arm arm64
|
||||
ML_PLATFORMS=linux/amd64,linux/arm,linux/arm64
|
||||
ALL_ARCHITECTURES=amd64 arm arm64 ppc64le s390x
|
||||
ML_PLATFORMS=linux/amd64,linux/arm,linux/arm64,linux/ppc64le,linux/s390x
|
||||
|
||||
ifeq ($(ARCH),amd64)
|
||||
# The architecture to use when downloading the docker binary
|
||||
WEAVEEXEC_DOCKER_ARCH?=x86_64
|
||||
|
||||
# The name of the alpine baseimage to use as the base for weave images
|
||||
ALPINE_BASEIMAGE?=alpine:3.4
|
||||
ALPINE_BASEIMAGE?=alpine:3.10
|
||||
|
||||
# The extension for the made images
|
||||
# Specifying none means for example weaveworks/weave:latest
|
||||
@@ -42,7 +42,7 @@ ifeq ($(ARCH),arm)
|
||||
WEAVEEXEC_DOCKER_ARCH?=armel
|
||||
|
||||
# Using the (semi-)official alpine image
|
||||
ALPINE_BASEIMAGE?=armhf/alpine:3.4
|
||||
ALPINE_BASEIMAGE?=arm32v6/alpine:3.8
|
||||
|
||||
# arm images have the -arm suffix, for instance weaveworks/weave-arm:latest
|
||||
ARCH_EXT?=-arm
|
||||
@@ -62,7 +62,7 @@ ifeq ($(ARCH),arm64)
|
||||
WEAVEEXEC_DOCKER_ARCH?=armel
|
||||
|
||||
# Using the (semi-)official alpine image
|
||||
ALPINE_BASEIMAGE?=aarch64/alpine:3.5
|
||||
ALPINE_BASEIMAGE?=arm64v8/alpine:3.8
|
||||
|
||||
# arm64 images have the -arm64 suffix, for instance weaveworks/weave-arm64:latest
|
||||
ARCH_EXT?=-arm64
|
||||
@@ -77,93 +77,130 @@ ifeq ($(ARCH),arm64)
|
||||
# Tell the gcc linker to search for libpcap here
|
||||
CGO_LDFLAGS="-L/usr/local/lib/$(CC)"
|
||||
endif
|
||||
ifeq ($(ARCH),ppc64le)
|
||||
# The architecture to use when downloading the docker binary
|
||||
WEAVEEXEC_DOCKER_ARCH?=ppc64le
|
||||
|
||||
# Using the (semi-)official alpine image
|
||||
ALPINE_BASEIMAGE?=ppc64le/alpine:3.8
|
||||
|
||||
# ppc64le images have the -ppc64le suffix, for instance weaveworks/weave-ppc64le:latest
|
||||
ARCH_EXT?=-ppc64le
|
||||
|
||||
# The name of the gcc binary
|
||||
CC=powerpc64le-linux-gnu-gcc
|
||||
|
||||
# The architecture name to use when downloading a prebuilt QEMU binary
|
||||
QEMUARCH=ppc64le
|
||||
|
||||
# In the weaveworks/build image; libpcap libraries for ppc64le are placed here
|
||||
# Tell the gcc linker to search for libpcap here
|
||||
CGO_LDFLAGS="-L/usr/local/lib/$(CC)"
|
||||
endif
|
||||
ifeq ($(ARCH),s390x)
|
||||
# The architecture to use when downloading the docker binary
|
||||
WEAVEEXEC_DOCKER_ARCH?=s390x
|
||||
|
||||
# Using the (semi-)official alpine image
|
||||
ALPINE_BASEIMAGE?=s390x/alpine:3.6
|
||||
|
||||
# s390x images have the -s390x suffix, for instance weaveworks/weave-s390x:latest
|
||||
ARCH_EXT?=-s390x
|
||||
|
||||
# The name of the gcc binary
|
||||
CC=s390x-linux-gnu-gcc
|
||||
|
||||
# The architecture name to use when downloading a prebuilt QEMU binary
|
||||
QEMUARCH=s390x
|
||||
|
||||
# In the weaveworks/build image; libpcap libraries for s390x are placed here
|
||||
# Tell the gcc linker to search for libpcap here
|
||||
CGO_LDFLAGS="-L/usr/local/lib/$(CC)"
|
||||
endif
|
||||
|
||||
# The name of the user that this Makefile should produce image artifacts for. Can/should be overridden
|
||||
DOCKERHUB_USER?=weaveworks
|
||||
# The default version that's chosen when pushing the images. Can/should be overridden
|
||||
GIT_REVISION=$(shell git rev-parse HEAD)
|
||||
WEAVE_VERSION?=git-$(shell git rev-parse --short=12 HEAD)
|
||||
# Docker Store does not allow the "latest" tag.
|
||||
NET_PLUGIN_LATEST=latest_release
|
||||
|
||||
# Paths to all relevant binaries that should be compiled
|
||||
WEAVER_EXE=prog/weaver/weaver
|
||||
WEAVEPROXY_EXE=prog/weaveproxy/weaveproxy
|
||||
SIGPROXY_EXE=prog/sigproxy/sigproxy
|
||||
KUBEPEERS_EXE=prog/kube-peers/kube-peers
|
||||
KUBEUTILS_EXE=prog/kube-utils/kube-utils
|
||||
WEAVENPC_EXE=prog/weave-npc/weave-npc
|
||||
WEAVEWAIT_EXE=prog/weavewait/weavewait
|
||||
WEAVEWAIT_NOOP_EXE=prog/weavewait/weavewait_noop
|
||||
WEAVEWAIT_NOMCAST_EXE=prog/weavewait/weavewait_nomcast
|
||||
WEAVEUTIL_EXE=prog/weaveutil/weaveutil
|
||||
PLUGIN_EXE=prog/plugin/plugin
|
||||
RUNNER_EXE=tools/runner/runner
|
||||
MANIFEST_TOOL_DIR=vendor/github.com/estesp/manifest-tool
|
||||
MANIFEST_TOOL_EXE=$(MANIFEST_TOOL_DIR)/manifest-tool
|
||||
# Get this from https://github.com/estesp/manifest-tool/releases
|
||||
MANIFEST_TOOL_CMD=manifest-tool
|
||||
TEST_TLS_EXE=test/tls/tls
|
||||
NETWORKTESTER_EXE=test/images/network-tester/webserver
|
||||
|
||||
# All binaries together in a list
|
||||
EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(KUBEPEERS_EXE) $(WEAVENPC_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(WEAVEUTIL_EXE) $(PLUGIN_EXE) $(RUNNER_EXE) $(TEST_TLS_EXE) $(MANIFEST_TOOL_EXE)
|
||||
EXES=$(WEAVER_EXE) $(SIGPROXY_EXE) $(KUBEUTILS_EXE) $(WEAVENPC_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(WEAVEUTIL_EXE) $(RUNNER_EXE) $(TEST_TLS_EXE) $(NETWORKTESTER_EXE)
|
||||
|
||||
# These stamp files are used to mark the current state of the build; whether an image has been built or not
|
||||
BUILD_UPTODATE=.build.uptodate
|
||||
WEAVER_UPTODATE=.weaver$(ARCH_EXT).uptodate
|
||||
WEAVEEXEC_UPTODATE=.weaveexec$(ARCH_EXT).uptodate
|
||||
PLUGIN_UPTODATE=.plugin$(ARCH_EXT).uptodate
|
||||
PLUGIN_UPTODATE=.net-plugin$(ARCH_EXT).uptodate
|
||||
WEAVEKUBE_UPTODATE=.weavekube$(ARCH_EXT).uptodate
|
||||
WEAVENPC_UPTODATE=.weavenpc$(ARCH_EXT).uptodate
|
||||
WEAVEDB_UPTODATE=.weavedb.uptodate
|
||||
NETWORKTESTER_UPTODATE=.networktester$(ARCH_EXT).uptodate
|
||||
|
||||
IMAGES_UPTODATE=$(WEAVER_UPTODATE) $(WEAVEEXEC_UPTODATE) $(PLUGIN_UPTODATE) $(WEAVEKUBE_UPTODATE) $(WEAVENPC_UPTODATE)
|
||||
IMAGES_UPTODATE=$(WEAVER_UPTODATE) $(WEAVEEXEC_UPTODATE) $(WEAVEKUBE_UPTODATE) $(WEAVENPC_UPTODATE) $(NETWORKTESTER_UPTODATE)
|
||||
|
||||
# The names of the images. Note that the images for other architectures than amd64 have a suffix in the image name.
|
||||
WEAVER_IMAGE=$(DOCKERHUB_USER)/weave$(ARCH_EXT)
|
||||
WEAVEEXEC_IMAGE=$(DOCKERHUB_USER)/weaveexec$(ARCH_EXT)
|
||||
PLUGIN_IMAGE=$(DOCKERHUB_USER)/plugin$(ARCH_EXT)
|
||||
WEAVEKUBE_IMAGE=$(DOCKERHUB_USER)/weave-kube$(ARCH_EXT)
|
||||
WEAVENPC_IMAGE=$(DOCKERHUB_USER)/weave-npc$(ARCH_EXT)
|
||||
BUILD_IMAGE=weaveworks/weavebuild
|
||||
WEAVEDB_IMAGE=$(DOCKERHUB_USER)/weavedb
|
||||
PLUGIN_IMAGE=$(DOCKERHUB_USER)/net-plugin
|
||||
NETWORKTESTER_IMAGE=$(DOCKERHUB_USER)/network-tester
|
||||
|
||||
IMAGES=$(WEAVER_IMAGE) $(WEAVEEXEC_IMAGE) $(PLUGIN_IMAGE) $(WEAVEKUBE_IMAGE) $(WEAVENPC_IMAGE) $(WEAVEDB_IMAGE)
|
||||
IMAGES=$(WEAVER_IMAGE) $(WEAVEEXEC_IMAGE) $(WEAVEKUBE_IMAGE) $(WEAVENPC_IMAGE) $(WEAVEDB_IMAGE) $(NETWORKTESTER_IMAGE)
|
||||
|
||||
PUBLISH=publish_weave publish_weaveexec publish_plugin publish_weave-kube publish_weave-npc
|
||||
PUSH_ML=push_ml_weave push_ml_weaveexec push_ml_plugin push_ml_weave-kube push_ml_weave-npc
|
||||
PLUGIN_WORK_DIR="prog/net-plugin/rootfs"
|
||||
PLUGIN_BUILD_IMG="plugin-builder"
|
||||
|
||||
PUBLISH=publish_weave publish_weaveexec publish_weave-kube publish_weave-npc
|
||||
PUSH_ML=push_ml_weave push_ml_weaveexec push_ml_weave-kube push_ml_weave-npc
|
||||
|
||||
WEAVE_EXPORT=weave$(ARCH_EXT).tar.gz
|
||||
|
||||
DOCKER_VERSION=1.10.3
|
||||
DOCKER_DISTRIB=prog/weaveexec/docker-$(DOCKER_VERSION).tgz
|
||||
DOCKER_DISTRIB_URL=https://get.docker.com/builds/Linux/$(WEAVEEXEC_DOCKER_ARCH)/docker-$(DOCKER_VERSION).tgz
|
||||
NETGO_CHECK=@strings $@ | grep cgo_stub\\\.go >/dev/null || { \
|
||||
rm $@; \
|
||||
echo "\nYour go standard library was built without the 'netgo' build tag."; \
|
||||
echo "To fix that, run"; \
|
||||
echo " sudo go clean -i net"; \
|
||||
echo " sudo go install -tags netgo std"; \
|
||||
false; \
|
||||
}
|
||||
# The flags we are passing to go build. -extldflags -static for making a static binary,
|
||||
# -linkmode external for linking external C libraries into the binary, -X main.version for telling the
|
||||
# Go binary which version it is, -tags netgo for enforcing the native Go DNS resolver
|
||||
BUILD_FLAGS=-i -ldflags "-linkmode external -extldflags -static -X main.version=$(WEAVE_VERSION)" -tags netgo
|
||||
# -X main.version for telling the Go binary which version it is,
|
||||
# -s -w drop symbol tables used by debuggers (not Go's internal symbol info)
|
||||
# -tags "osusergo netgo" to use native Go UID and DNS implementations
|
||||
BUILD_FLAGS=-ldflags "-extldflags -static -X main.version=$(WEAVE_VERSION) -s -w" -tags "osusergo netgo"
|
||||
|
||||
PACKAGE_BASE=$(shell go list -e ./)
|
||||
|
||||
all: $(WEAVE_EXPORT)
|
||||
testrunner: $(RUNNER_EXE) $(TEST_TLS_EXE)
|
||||
|
||||
$(WEAVER_EXE) $(WEAVEPROXY_EXE) $(WEAVEUTIL_EXE): common/*.go common/*/*.go net/*.go net/*/*.go
|
||||
$(WEAVER_EXE) $(WEAVEUTIL_EXE): common/*.go common/*/*.go net/*.go net/*/*.go
|
||||
$(WEAVER_EXE): router/*.go ipam/*.go ipam/*/*.go db/*.go nameserver/*.go prog/weaver/*.go
|
||||
$(WEAVEPROXY_EXE): proxy/*.go prog/weaveproxy/*.go
|
||||
$(WEAVER_EXE): api/*.go plugin/*.go plugin/*/*
|
||||
$(WEAVER_EXE): proxy/*.go
|
||||
$(WEAVEUTIL_EXE): prog/weaveutil/*.go net/*.go plugin/net/*.go plugin/ipam/*.go db/*.go
|
||||
$(SIGPROXY_EXE): prog/sigproxy/*.go
|
||||
$(KUBEPEERS_EXE): prog/kube-peers/*.go
|
||||
$(KUBEUTILS_EXE): prog/kube-utils/*.go
|
||||
$(WEAVENPC_EXE): prog/weave-npc/*.go npc/*.go npc/*/*.go
|
||||
$(PLUGIN_EXE): prog/plugin/*.go plugin/*/*.go api/*.go common/*.go common/docker/*.go net/*.go
|
||||
$(TEST_TLS_EXE): test/tls/*.go
|
||||
$(RUNNER_EXE): tools/runner/*.go
|
||||
$(MANIFEST_TOOL_EXE): $(MANIFEST_TOOL_DIR)/*.go
|
||||
$(WEAVEWAIT_NOOP_EXE): prog/weavewait/*.go
|
||||
$(WEAVEWAIT_EXE): prog/weavewait/*.go net/*.go
|
||||
$(WEAVEWAIT_NOMCAST_EXE): prog/weavewait/*.go net/*.go
|
||||
$(NETWORKTESTER_EXE): test/images/network-tester/*.go
|
||||
tests: tools/.git
|
||||
lint: tools/.git
|
||||
|
||||
@@ -172,42 +209,36 @@ ifeq ($(BUILD_IN_CONTAINER),true)
|
||||
# This make target compiles all binaries inside of the weaveworks/build container
|
||||
# It bind-mounts the source into the container and passes all important variables
|
||||
exes $(EXES) tests lint: $(BUILD_UPTODATE)
|
||||
git submodule update --init
|
||||
@mkdir -p $(shell pwd)/.pkg
|
||||
$(SUDO) docker run $(RM) $(RUN_FLAGS) \
|
||||
-v $(shell pwd):/go/src/github.com/weaveworks/weave \
|
||||
-v $(shell pwd)/.pkg:/go/pkg \
|
||||
-e GOARCH=$(ARCH) -e CGO_ENABLED=1 -e GOOS=linux -e CIRCLECI -e CIRCLE_BUILD_NUM -e CIRCLE_NODE_TOTAL -e CIRCLE_NODE_INDEX -e COVERDIR -e SLOW -e DEBUG \
|
||||
$(BUILD_IMAGE) COVERAGE=$(COVERAGE) WEAVE_VERSION=$(WEAVE_VERSION) CC=$(CC) QEMUARCH=$(QEMUARCH) CGO_LDFLAGS=$(CGO_LDFLAGS) $@
|
||||
touch $@
|
||||
|
||||
else
|
||||
|
||||
exes: $(EXES)
|
||||
|
||||
$(WEAVER_EXE) $(WEAVEPROXY_EXE) $(PLUGIN_EXE):
|
||||
$(WEAVER_EXE):
|
||||
ifeq ($(COVERAGE),true)
|
||||
$(eval COVERAGE_MODULES := $(shell (go list ./$(@D); go list -f '{{join .Deps "\n"}}' ./$(@D) | grep "^$(PACKAGE_BASE)/") | grep -v "^$(PACKAGE_BASE)/vendor/" | paste -s -d,))
|
||||
go test -c -o ./$@ $(BUILD_FLAGS) -v -covermode=atomic -coverpkg $(COVERAGE_MODULES) ./$(@D)/
|
||||
else
|
||||
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
||||
endif
|
||||
$(NETGO_CHECK)
|
||||
|
||||
$(WEAVEUTIL_EXE) $(KUBEPEERS_EXE) $(WEAVENPC_EXE):
|
||||
$(WEAVEUTIL_EXE) $(KUBEUTILS_EXE) $(WEAVENPC_EXE) $(NETWORKTESTER_EXE):
|
||||
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
||||
$(NETGO_CHECK)
|
||||
|
||||
$(WEAVEWAIT_EXE):
|
||||
go build $(BUILD_FLAGS) -tags "netgo iface mcast" -o $@ ./$(@D)
|
||||
$(NETGO_CHECK)
|
||||
go build $(BUILD_FLAGS) -tags "osusergo netgo iface mcast" -o $@ ./$(@D)
|
||||
|
||||
$(WEAVEWAIT_NOMCAST_EXE):
|
||||
go build $(BUILD_FLAGS) -tags "netgo iface" -o $@ ./$(@D)
|
||||
$(NETGO_CHECK)
|
||||
go build $(BUILD_FLAGS) -tags "osusergo netgo iface" -o $@ ./$(@D)
|
||||
|
||||
# These programs need a separate rule as they fail the netgo check in
|
||||
# the main build stanza due to not importing net package
|
||||
$(SIGPROXY_EXE) $(TEST_TLS_EXE) $(WEAVEWAIT_NOOP_EXE) $(RUNNER_EXE) $(MANIFEST_TOOL_EXE):
|
||||
$(SIGPROXY_EXE) $(TEST_TLS_EXE) $(WEAVEWAIT_NOOP_EXE) $(RUNNER_EXE):
|
||||
go build $(BUILD_FLAGS) -o $@ ./$(@D)
|
||||
|
||||
tests:
|
||||
@@ -220,8 +251,9 @@ endif
|
||||
|
||||
# This rule makes sure the build image is up-to-date.
|
||||
# It also makes sure the multiarch hooks are reqistered in the kernel so the QEMU emulation works
|
||||
$(BUILD_UPTODATE): build/*
|
||||
$(BUILD_UPTODATE): build/* tools/.git
|
||||
$(SUDO) docker build -t $(BUILD_IMAGE) build/
|
||||
$(SUDO) docker tag $(BUILD_IMAGE) $(BUILD_IMAGE):$(shell tools/image-tag)
|
||||
$(SUDO) docker run --rm --privileged multiarch/qemu-user-static:register --reset
|
||||
touch $@
|
||||
|
||||
@@ -234,62 +266,80 @@ $(BUILD_UPTODATE): build/*
|
||||
sed -e "s|DOCKERHUB_USER|$(DOCKERHUB_USER)|g;s|ARCH_EXT|$(ARCH_EXT)|g;s|ALPINE_BASEIMAGE|$(ALPINE_BASEIMAGE)|g;s|QEMUARCH|$(QEMUARCH)|g" $^ > $@
|
||||
ifeq ($(ARCH),amd64)
|
||||
# When building "normally" for amd64, remove the whole line, it has no part in the amd64 image
|
||||
sed -i "/CROSS_BUILD_/d" $@
|
||||
sed -i.bak "/CROSS_BUILD_/d" $@
|
||||
else
|
||||
# When cross-building, only the placeholder "CROSS_BUILD_" should be removed
|
||||
# Register /usr/bin/qemu-ARCH-static as the handler for ARM binaries in the kernel
|
||||
# Register /usr/bin/qemu-ARCH-static as the handler for ARM and ppc64le binaries in the kernel
|
||||
curl -sSL https://github.com/multiarch/qemu-user-static/releases/download/$(QEMU_VERSION)/x86_64_qemu-$(QEMUARCH)-static.tar.gz | tar -xz -C $(shell dirname $@)
|
||||
cd $(shell dirname $@) && sha256sum -c $(shell pwd)/build/shasums/qemu-$(QEMUARCH)-static.sha256sum
|
||||
sed -i "s/CROSS_BUILD_//g" $@
|
||||
sed -i.bak "s/CROSS_BUILD_//g" $@
|
||||
endif
|
||||
|
||||
# The targets below builds the weave images
|
||||
$(WEAVER_UPTODATE): prog/weaver/Dockerfile.$(DOCKERHUB_USER) $(WEAVER_EXE) $(WEAVEEXEC_UPTODATE)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build -f prog/weaver/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVER_IMAGE) prog/weaver
|
||||
$(WEAVER_UPTODATE): prog/weaver/Dockerfile.$(DOCKERHUB_USER) $(WEAVER_EXE) weave $(WEAVEUTIL_EXE)
|
||||
cp $(WEAVEUTIL_EXE) prog/weaver/weaveutil
|
||||
cp weave prog/weaver/weave
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build --build-arg=revision=$(GIT_REVISION) -f prog/weaver/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVER_IMAGE) prog/weaver
|
||||
touch $@
|
||||
|
||||
$(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile.$(DOCKERHUB_USER) prog/weaveexec/symlink $(DOCKER_DISTRIB) weave $(SIGPROXY_EXE) $(WEAVEPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(WEAVEUTIL_EXE)
|
||||
cp weave prog/weaveexec/weave
|
||||
$(WEAVEEXEC_UPTODATE): prog/weaveexec/Dockerfile.$(DOCKERHUB_USER) prog/weaveexec/symlink $(SIGPROXY_EXE) $(WEAVEWAIT_EXE) $(WEAVEWAIT_NOOP_EXE) $(WEAVEWAIT_NOMCAST_EXE) $(WEAVER_UPTODATE)
|
||||
cp $(SIGPROXY_EXE) prog/weaveexec/sigproxy
|
||||
cp $(WEAVEPROXY_EXE) prog/weaveexec/weaveproxy
|
||||
cp $(WEAVEWAIT_EXE) prog/weaveexec/weavewait
|
||||
cp $(WEAVEWAIT_NOOP_EXE) prog/weaveexec/weavewait_noop
|
||||
cp $(WEAVEWAIT_NOMCAST_EXE) prog/weaveexec/weavewait_nomcast
|
||||
cp $(WEAVEUTIL_EXE) prog/weaveexec/weaveutil
|
||||
tar -xf $(DOCKER_DISTRIB) usr/local/bin/docker -O > prog/weaveexec/docker
|
||||
chmod +x prog/weaveexec/docker
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build -f prog/weaveexec/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVEEXEC_IMAGE) prog/weaveexec
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker build --build-arg=revision=$(GIT_REVISION) -f prog/weaveexec/Dockerfile.$(DOCKERHUB_USER) -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
|
||||
# Builds Docker plugin.
|
||||
$(PLUGIN_UPTODATE): prog/net-plugin/launch.sh prog/net-plugin/config.json $(WEAVER_UPTODATE)
|
||||
-$(SUDO) docker rm -f $(PLUGIN_BUILD_IMG) 2>/dev/null
|
||||
$(SUDO) docker create --name=$(PLUGIN_BUILD_IMG) $(WEAVER_IMAGE) true
|
||||
rm -rf $(PLUGIN_WORK_DIR)
|
||||
mkdir $(PLUGIN_WORK_DIR)
|
||||
$(SUDO) docker export $(PLUGIN_BUILD_IMG) | tar -x -C $(PLUGIN_WORK_DIR)
|
||||
$(SUDO) docker rm -f $(PLUGIN_BUILD_IMG)
|
||||
cp prog/net-plugin/launch.sh $(PLUGIN_WORK_DIR)/home/weave/launch.sh
|
||||
-$(SUDO) docker plugin disable $(PLUGIN_IMAGE):$(WEAVE_VERSION) 2>/dev/null
|
||||
-$(SUDO) docker plugin rm $(PLUGIN_IMAGE):$(WEAVE_VERSION) 2>/dev/null
|
||||
$(SUDO) docker plugin create $(PLUGIN_IMAGE):$(WEAVE_VERSION) prog/net-plugin
|
||||
-$(SUDO) docker plugin disable $(PLUGIN_IMAGE):$(NET_PLUGIN_LATEST) 2>/dev/null
|
||||
-$(SUDO) docker plugin rm $(PLUGIN_IMAGE):$(NET_PLUGIN_LATEST) 2>/dev/null
|
||||
$(SUDO) docker plugin create $(PLUGIN_IMAGE):$(NET_PLUGIN_LATEST) prog/net-plugin
|
||||
touch $@
|
||||
|
||||
$(WEAVEKUBE_UPTODATE): prog/weave-kube/Dockerfile.$(DOCKERHUB_USER) prog/weave-kube/launch.sh $(KUBEPEERS_EXE) $(WEAVER_UPTODATE)
|
||||
cp $(KUBEPEERS_EXE) prog/weave-kube/
|
||||
$(SUDO) docker build -f prog/weave-kube/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVEKUBE_IMAGE) prog/weave-kube
|
||||
$(WEAVEKUBE_UPTODATE): prog/weave-kube/Dockerfile.$(DOCKERHUB_USER) prog/weave-kube/launch.sh $(KUBEUTILS_EXE) $(WEAVER_UPTODATE)
|
||||
cp $(KUBEUTILS_EXE) prog/weave-kube/
|
||||
$(SUDO) docker build --build-arg=revision=$(GIT_REVISION) -f prog/weave-kube/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVEKUBE_IMAGE) prog/weave-kube
|
||||
touch $@
|
||||
|
||||
$(WEAVENPC_UPTODATE): prog/weave-npc/Dockerfile.$(DOCKERHUB_USER) $(WEAVENPC_EXE) prog/weave-npc/ulogd.conf
|
||||
$(SUDO) docker build -f prog/weave-npc/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVENPC_IMAGE) prog/weave-npc
|
||||
$(SUDO) docker build --build-arg=revision=$(GIT_REVISION) -f prog/weave-npc/Dockerfile.$(DOCKERHUB_USER) -t $(WEAVENPC_IMAGE) prog/weave-npc
|
||||
touch $@
|
||||
|
||||
$(WEAVEDB_UPTODATE): prog/weavedb/Dockerfile
|
||||
$(SUDO) docker build -t $(WEAVEDB_IMAGE) prog/weavedb
|
||||
$(SUDO) docker build --build-arg=revision=$(GIT_REVISION) -t $(WEAVEDB_IMAGE) prog/weavedb
|
||||
touch $@
|
||||
|
||||
$(NETWORKTESTER_UPTODATE): test/images/network-tester/Dockerfile $(NETWORKTESTER_EXE)
|
||||
$(SUDO) docker build -t $(NETWORKTESTER_IMAGE) test/images/network-tester
|
||||
touch $@
|
||||
|
||||
$(WEAVE_EXPORT): $(IMAGES_UPTODATE) $(WEAVEDB_UPTODATE)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker save $(addsuffix :latest,$(IMAGES)) | gzip > $@
|
||||
|
||||
$(DOCKER_DISTRIB):
|
||||
curl -o $(DOCKER_DISTRIB) $(DOCKER_DISTRIB_URL)
|
||||
cd $(shell dirname $@) && sha256sum -c $(shell pwd)/build/shasums/docker-tgz-$(WEAVEEXEC_DOCKER_ARCH).sha256sum
|
||||
|
||||
tools/.git $(MANIFEST_TOOL_DIR)/.git:
|
||||
tools/.git:
|
||||
git submodule update --init
|
||||
|
||||
# CODE FOR PUBLISHING THE IMAGES
|
||||
|
||||
# Push plugin
|
||||
plugin_publish: $(PLUGIN_UPTODATE)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker plugin push $(PLUGIN_IMAGE):$(WEAVE_VERSION)
|
||||
# "$(NET_PLUGIN_LATEST)" means "stable release" here, so only push that if explicitly told to
|
||||
ifeq ($(UPDATE_LATEST),true)
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker plugin push $(PLUGIN_IMAGE):$(NET_PLUGIN_LATEST)
|
||||
endif
|
||||
|
||||
# This target first runs "make publish" for each architecture
|
||||
# Then it pushes the manifest lists
|
||||
publish: $(addprefix sub-publish-,$(ALL_ARCHITECTURES)) $(WEAVEDB_UPTODATE)
|
||||
@@ -309,7 +359,7 @@ publish-one-arch: $(PUBLISH)
|
||||
$(PUBLISH): publish_%: $(IMAGES_UPTODATE)
|
||||
# Tag :latest with the real version
|
||||
$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker tag $(DOCKERHUB_USER)/$*$(ARCH_EXT) $(DOCKERHUB_USER)/$*$(ARCH_EXT):$(WEAVE_VERSION)
|
||||
# Push the image with the arch suffix for arm and arm64, and without suffix for amd64
|
||||
# Push the image with the arch suffix for arm, arm64 and ppc64le, and without suffix for amd64
|
||||
$(MAKE) DOCKER_HOST=$(DOCKER_HOST) DOCKERHUB_USER=$(DOCKERHUB_USER) WEAVE_VERSION=$(WEAVE_VERSION) UPDATE_LATEST=$(UPDATE_LATEST) push_$*$(ARCH_EXT)
|
||||
|
||||
ifeq ($(ARCH),amd64)
|
||||
@@ -333,28 +383,30 @@ endif
|
||||
|
||||
# This target pushes a manifest list; it takes one parameter, the image name.
|
||||
# The variable UPDATE_LATEST controls whether it updates the versioned tag and/or the latest tag
|
||||
$(PUSH_ML): push_ml_%: $(MANIFEST_TOOL_EXE)
|
||||
$(PUSH_ML): push_ml_%:
|
||||
ifneq ($(UPDATE_LATEST),latest-only)
|
||||
$(MANIFEST_TOOL_EXE) push from-args --platforms $(ML_PLATFORMS) --template $(DOCKERHUB_USER)/$*-ARCH:$(WEAVE_VERSION) --target $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
|
||||
$(MANIFEST_TOOL_CMD) push from-args --platforms $(ML_PLATFORMS) --template $(DOCKERHUB_USER)/$*-ARCH:$(WEAVE_VERSION) --target $(DOCKERHUB_USER)/$*:$(WEAVE_VERSION)
|
||||
endif
|
||||
ifneq ($(UPDATE_LATEST),false)
|
||||
# Push the manifest list to :latest as well
|
||||
$(MANIFEST_TOOL_EXE) push from-args --platforms $(ML_PLATFORMS) --template $(DOCKERHUB_USER)/$*-ARCH:latest --target $(DOCKERHUB_USER)/$*:latest
|
||||
$(MANIFEST_TOOL_CMD) push from-args --platforms $(ML_PLATFORMS) --template $(DOCKERHUB_USER)/$*-ARCH:latest --target $(DOCKERHUB_USER)/$*:latest
|
||||
endif
|
||||
|
||||
clean-work-dir:
|
||||
rm -rf $(PLUGIN_WORK_DIR)
|
||||
|
||||
clean-bin:
|
||||
-$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker rmi $(IMAGES)
|
||||
find prog -type f -name "Dockerfile.*" -not -name "Dockerfile.template" -print | xargs rm -f
|
||||
find prog -type f -name "*qemu-*" -print | xargs rm -f
|
||||
rm -rf $(EXES) $(IMAGES_UPTODATE) $(WEAVEDB_UPTODATE) weave*.tar.gz $(DOCKER_DISTRIB) prog/weaveexec/docker .pkg
|
||||
go clean ./...
|
||||
rm -rf $(EXES) $(IMAGES_UPTODATE) $(WEAVEDB_UPTODATE) weave*.tar.gz .cache
|
||||
|
||||
clean: clean-bin
|
||||
clean: clean-bin clean-work-dir
|
||||
-$(SUDO) DOCKER_HOST=$(DOCKER_HOST) docker rmi $(BUILD_IMAGE)
|
||||
rm -rf test/tls/*.pem test/coverage.* test/coverage $(BUILD_UPTODATE) $(MANIFEST_TOOL_EXE)
|
||||
rm -rf test/tls/*.pem test/coverage.* test/coverage $(BUILD_UPTODATE)
|
||||
|
||||
build:
|
||||
$(SUDO) go clean -i net
|
||||
$(SUDO) go install -tags netgo std
|
||||
$(MAKE)
|
||||
|
||||
run-smoketests: all testrunner
|
||||
@@ -376,3 +428,14 @@ integration-tests: all testrunner
|
||||
# integration-tests
|
||||
#
|
||||
RUNNER_ARGS="-parallel" ./test/run-integration-tests.sh
|
||||
|
||||
# This target is only intended for use in Netlify CI environment for generating preview pages on feature branches and pull requests.
|
||||
# We need to obtain website repository (checked out under `site-build`) and place `site` directory into the context (`site-build/_weave_net_docs`).
|
||||
# We then run make in `site-build` and Netlify will publish the output (`site-build/_site`).
|
||||
netlify-site-preview:
|
||||
@mkdir -p site-build
|
||||
@curl --user $(WEBSITE_GITHUB_USER) --silent 'https://codeload.github.com/weaveworks/website-next/tar.gz/$(WEBSITE_BRANCH)' \
|
||||
| tar --extract --gunzip --directory site-build --strip 1
|
||||
@cp -r site site-build/_weave_net_docs
|
||||
@$(MAKE) -C site-build netlify_ensure_install
|
||||
@$(MAKE) -C site-build BUILD_ENV=netlify
|
||||
@@ -3,19 +3,19 @@
|
||||
[](https://circleci.com/gh/weaveworks/weave)
|
||||
[](https://coveralls.io/r/weaveworks/weave)
|
||||
[](https://goreportcard.com/report/github.com/weaveworks/weave)
|
||||
[](https://slack.weave.works)
|
||||
[](https://hub.docker.com/r/weaveworks/weave/)
|
||||
|
||||
# About Weaveworks
|
||||
|
||||
[Weaveworks](https://www.weave.works) is the company that delivers the most productive way for developers to connect, observe and control
|
||||
Docker containers.
|
||||
[Weaveworks](https://www.weave.works) is the company that delivers the most productive way for developers to connect, observe and control Docker containers.
|
||||
|
||||
This repository contains [Weave Net](https://www.weave.works/products/weave-net/), the first product developed by Weaveworks, and with over 8 million downloads to date, it enables you to get started with Docker clusters and portable apps in a fraction of the time compared with other solutions.
|
||||
This repository contains [Weave Net](https://www.weave.works/products/weave-net/), the first product developed by Weaveworks, with over 8 million downloads to date. Weave Net enables you to get started with Docker clusters and portable apps in a fraction of the time required by other solutions.
|
||||
|
||||
Weaveworks' product [Weave Cloud](https://www.weave.works/solution/cloud/) takes the heavy lifting out of building, integrating and operating open source components, so you don’t have to manage databases, storage and availability.
|
||||
## Weave Cloud:
|
||||
|
||||
Weave Cloud is built using these Open Source projects: [Weave Scope](https://www.weave.works/products/weave-scope/), a powerful container monitoring tool that automatically maps Docker containers and their interactions, [Weave Cortex](https://github.com/weaveworks/cortex), a horizontally-scalable version of Prometheus, and [Weave Flux](https://www.weave.works/products/weave-flux/), a continuous deployment tool that works with Kubernetes.
|
||||
[Weave Cloud](https://www.weave.works/product/cloud/) is software-as-a-service that simplifies deployment, monitoring and management for containers and microservices. It extends and complements popular orchestrators, and enables developers and DevOps to realize faster deployments, insightful monitoring, visualization and networking.
|
||||
|
||||
Weave Cloud is built using these Open Source projects: [Weave Scope](https://www.weave.works/products/weave-scope/), a powerful container visualization tool that automatically maps Docker containers and their interactions, [Weave Cortex](https://github.com/weaveworks/cortex), a horizontally-scalable version of Prometheus, and [Weave Flux](https://www.weave.works/products/weave-flux/), a continuous deployment tool that works with Kubernetes.
|
||||
|
||||
# Weave Net
|
||||
|
||||
@@ -27,16 +27,15 @@ Services provided by application containers on the Weave network can be exposed
|
||||
|
||||
If you have any questions about, feedback for or a problem with Weave Net:
|
||||
|
||||
- Read [the Weave Net docs](https://www.weave.works/docs/net/latest/introducing-weave/).
|
||||
- Invite yourself to the <a href="https://weaveworks.github.io/community-slack/" target="_blank"> #weave-community </a> slack channel.
|
||||
- Ask a question on the <a href="https://weave-community.slack.com/messages/general/"> #weave-community</a> slack channel.
|
||||
- Read [the Weave Net docs](https://www.weave.works/docs/net/latest/overview).
|
||||
- Invite yourself to the <a href="https://slack.weave.works/" target="_blank">Weave Users Slack</a>.
|
||||
- Ask a question on the [#net-dev](https://weave-community.slack.com/messages/net-dev/) slack channel.
|
||||
- Join the <a href="https://www.meetup.com/pro/Weave/"> Weave User Group </a> and get invited to online talks, hands-on training and meetups in your area.
|
||||
- Send an email to <a href="mailto:weave-users@weave.works">weave-users@weave.works</a>
|
||||
- <a href="https://github.com/weaveworks/weave/issues/new">File an issue.</a>
|
||||
- [File an issue](https://github.com/weaveworks/weave/issues/new).
|
||||
|
||||
Your feedback is always welcome!
|
||||
|
||||
##Further Information
|
||||
## Further Information
|
||||
|
||||
* [Installing Weave Net](https://www.weave.works/docs/net/latest/installing-weave/)
|
||||
* [Weave Net Product Overview](https://www.weave.works/products/weave-net/)
|
||||
* [Installing Weave Net](https://www.weave.works/docs/net/latest/install/installing-weave)
|
||||
* [Weave Net Product Overview](https://www.weave.works/products/weave-net)
|
||||
@@ -0,0 +1,17 @@
|
||||
# Use of vendored code in Weave Net
|
||||
|
||||
Weave Net is licensed under the [Apache 2.0 license](LICENSE).
|
||||
|
||||
Some vendored code is under different licenses though, all of them ship the
|
||||
entire license text they are under.
|
||||
|
||||
- https://github.com/weaveworks/go-checkpoint
|
||||
can be found in the ./vendor/ directory, is under MPL-2.0.
|
||||
|
||||
- Pulled in by dependencies are
|
||||
https://github.com/hashicorp/golang-lru (MPL-2.0)
|
||||
https://github.com/hashicorp/go-cleanhttp (MPL-2.0)
|
||||
https://github.com/opencontainers/go-digest (docs are under CC by-sa 4.0)
|
||||
|
||||
[One file used in tests](COPYING.LGPL-3) is under LGPL-3, that's why we ship
|
||||
the license text in this repository.
|
||||
+25
-10
@@ -3,6 +3,7 @@ package api
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// Special token used in place of a container identifier when:
|
||||
@@ -14,21 +15,29 @@ import (
|
||||
// which should be kept in mind if the entry needs to be removed at any point.
|
||||
const NoContainerID string = "_"
|
||||
|
||||
func (client *Client) ipamOp(ID string, op string) (*net.IPNet, error) {
|
||||
ip, err := client.httpVerb(op, fmt.Sprintf("/ip/%s", ID), nil)
|
||||
func (client *Client) ipamOp(ID string, op string, values url.Values) (*net.IPNet, error) {
|
||||
ip, err := client.httpVerb(op, fmt.Sprintf("/ip/%s", ID), values)
|
||||
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 ipamValues(checkAlive bool) url.Values {
|
||||
values := make(url.Values)
|
||||
if checkAlive {
|
||||
values.Set("check-alive", "true")
|
||||
}
|
||||
return values
|
||||
}
|
||||
|
||||
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)
|
||||
// returns an IP for the ID given, allocating a fresh one if necessary
|
||||
func (client *Client) AllocateIP(ID string, checkAlive bool) (*net.IPNet, error) {
|
||||
return client.ipamOp(ID, "POST", ipamValues(checkAlive))
|
||||
}
|
||||
|
||||
func (client *Client) AllocateIPInSubnet(ID string, subnet *net.IPNet, checkAlive bool) (*net.IPNet, error) {
|
||||
ip, err := client.httpVerb("POST", fmt.Sprintf("/ip/%s/%s", ID, subnet), ipamValues(checkAlive))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -38,12 +47,12 @@ func (client *Client) AllocateIPInSubnet(ID string, subnet *net.IPNet) (*net.IPN
|
||||
// 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")
|
||||
return client.ipamOp(ID, "GET", nil)
|
||||
}
|
||||
|
||||
// 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)
|
||||
func (client *Client) ClaimIP(ID string, cidr *net.IPNet, checkAlive bool) error {
|
||||
_, err := client.httpVerb("PUT", fmt.Sprintf("/ip/%s/%s", ID, cidr), ipamValues(checkAlive))
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -53,6 +62,12 @@ func (client *Client) ReleaseIPsFor(ID string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// release all IP space owned by a peer
|
||||
func (client *Client) RmPeer(peerName string) (string, error) {
|
||||
result, err := client.httpVerb("DELETE", fmt.Sprintf("/peer/%s", peerName), nil)
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (client *Client) DefaultSubnet() (*net.IPNet, error) {
|
||||
cidr, err := client.httpVerb("GET", fmt.Sprintf("/ipinfo/defaultsubnet"), nil)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
// Expose calls the router to assign the given IP addr to the weave bridge.
|
||||
func (client *Client) Expose(ipAddr *net.IPNet) error {
|
||||
_, err := client.httpVerb("POST", fmt.Sprintf("/expose/%s", ipAddr), nil)
|
||||
return err
|
||||
}
|
||||
|
||||
// ReplacePeers replace the current set of peers
|
||||
func (client *Client) ReplacePeers(peers []string) error {
|
||||
_, err := client.httpVerb("POST", "/connect", url.Values{"replace": {"true"}, "peer": peers})
|
||||
return err
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -76,6 +77,22 @@ func (client *Client) Connect(remote string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// IsReady returns true if the API server is up and running
|
||||
// (note it returns StatusServiceUnavailable until all parts are ready,
|
||||
// but callers of this function are expected to be ok with a partial service)
|
||||
func (client *Client) IsReady() bool {
|
||||
resp, err := http.Get(client.baseURL + "/status")
|
||||
return err == nil &&
|
||||
(resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusServiceUnavailable)
|
||||
}
|
||||
|
||||
// WaitAPIServer waits until the API server is ready to serve.
|
||||
func (client *Client) WaitAPIServer(n int) {
|
||||
for i := 0; i < n && !client.IsReady(); i++ {
|
||||
time.Sleep(2 * time.Second)
|
||||
}
|
||||
}
|
||||
|
||||
type Logger interface {
|
||||
Infof(string, ...interface{})
|
||||
Debugf(string, ...interface{})
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
source "$STATE"
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ]; then
|
||||
cd "$SRCDIR"
|
||||
git submodule update --init
|
||||
cd "$SRCDIR/build"
|
||||
../tools/rebuild-image weaveworks/weavebuild . Dockerfile build.sh
|
||||
touch "$SRCDIR/.build.uptodate"
|
||||
sudo chown -R ubuntu /usr/local/go
|
||||
cd "$SRCDIR"
|
||||
make testrunner
|
||||
make COVERAGE=true RM= exes all
|
||||
fi
|
||||
@@ -1,36 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
echo "TEST_AND_PUBLISH=1" >"$STATE"
|
||||
|
||||
# Avoid doing a full build if we can. Note if the PR doesn't exist
|
||||
# when the build is kicked off, we can't run this check so we do a
|
||||
# full build. Subsequent pushes to the same branch will have the desired behaviour
|
||||
if [ -n "$CI_PULL_REQUEST" -a "$CI_PULL_REQUEST" = "$CI_PULL_REQUESTS" ]; then
|
||||
if PR=$(echo "$CI_PULL_REQUEST" | grep -oP '(?<=^https://github.com/weaveworks/weave/pull/)[0-9]+$'); then
|
||||
echo "GH_PR=$PR" >>"$STATE"
|
||||
if FILES=$(curl -s "https://api.github.com/repos/weaveworks/weave/pulls/$PR/files" | jq -r '.[]|.filename'); then
|
||||
echo "PR touches [$FILES]"
|
||||
if ! echo "$FILES" | grep -v ^site/; then
|
||||
echo "PR contains site changes only - skipping tests"
|
||||
echo "TEST_AND_PUBLISH=" >>"$STATE"
|
||||
else
|
||||
echo "PR contains non-site changes - running tests"
|
||||
fi
|
||||
else
|
||||
echo "Unable to get files for PR $PR" >&2
|
||||
fi
|
||||
else
|
||||
echo "Unable to extract PR number from $CI_PULL_REQUEST" >&2
|
||||
fi
|
||||
fi
|
||||
|
||||
source "$STATE"
|
||||
|
||||
mkdir -p "$(dirname "$SRCDIR")" && cp -r "$(pwd)/" "$SRCDIR"
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ]; then
|
||||
cd "$SRCDIR"
|
||||
git submodule update --init
|
||||
fi
|
||||
@@ -1,43 +0,0 @@
|
||||
#!/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
|
||||
@@ -2,16 +2,7 @@
|
||||
|
||||
set -e
|
||||
|
||||
source "$STATE"
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ] ; then
|
||||
docker login -e $DOCKER_EMAIL -u $DOCKER_USER -p $DOCKER_PASS
|
||||
cd $SRCDIR
|
||||
make clean-bin
|
||||
# CircleCI doesn't let us install the qemu module to do ARM docker builds
|
||||
make publish ALL_ARCHITECTURES=amd64 UPDATE_LATEST=latest-only
|
||||
fi
|
||||
|
||||
cd $SRCDIR
|
||||
bin/install-wordepress v1.0.0
|
||||
bin/publish-site "$WP_DEV_URL" "$WP_DEV_USER" "$WP_DEV_PASSWORD"
|
||||
docker login -u $DOCKER_USER -p $DOCKER_PASS
|
||||
make clean-bin
|
||||
# CircleCI doesn't let us install the qemu module to do ARM docker builds
|
||||
make publish ALL_ARCHITECTURES=amd64 UPDATE_LATEST=latest-only SUDO= BUILD_IN_CONTAINER=false
|
||||
@@ -1,7 +0,0 @@
|
||||
#!/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
+7
@@ -0,0 +1,7 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
source "$SRCDIR/bin/circle-env"
|
||||
cd "$SRCDIR/test"
|
||||
./run-integration-tests.sh destroy
|
||||
@@ -0,0 +1,11 @@
|
||||
# Set environment variables here because CircleCI can't expand $FOO in 'env' section
|
||||
|
||||
export PATH="$PATH:$HOME/.local/bin:/opt/google-cloud-sdk/bin"
|
||||
export CLOUDSDK_CORE_DISABLE_PROMPTS=1
|
||||
# Base name of VMs for integration tests:
|
||||
export NAME=test-$CIRCLE_BUILD_NUM-$CIRCLE_NODE_INDEX
|
||||
export TEST_VMS_PROV_AND_CONF_LOCK_FILE=$CIRCLE_ARTIFACTS/test_vms_provisioned_and_configured.txt
|
||||
export TEST_VMS_READY_LOCK_FILE=$CIRCLE_ARTIFACTS/test_vms_ready.txt
|
||||
# Google Cloud Platorm images for integration tests:
|
||||
export USE_IMAGE=1
|
||||
export DISK_NAME_PREFIX=test-$CIRCLE_BUILD_NUM-0
|
||||
@@ -1,13 +0,0 @@
|
||||
#!/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
|
||||
+14
-15
@@ -9,22 +9,21 @@ function signal_failure() {
|
||||
}
|
||||
trap signal_failure ERR
|
||||
|
||||
source "$STATE"
|
||||
source "$(dirname "$0")/wait_for.sh"
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ]; then
|
||||
[ -n "$SECRET_KEY" ] || {
|
||||
echo "Cannot run smoke tests: no secret key"
|
||||
exit 1
|
||||
}
|
||||
[ -n "$SECRET_KEY" ] || {
|
||||
echo "Cannot run smoke tests: no secret key"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Provisioning from image typically take 90 seconds,
|
||||
# but if we are creating a GCP image, it can take more than 10 minutes:
|
||||
wait_for 900 "$TEST_VMS_PROV_AND_CONF_LOCK_FILE"
|
||||
source "$SRCDIR/bin/circle-env"
|
||||
|
||||
# Upload built binaries to testing VMs:
|
||||
cd "$SRCDIR/test" # Ensures we generate Terraform state files in the right folder, for later use by integration tests.
|
||||
./run-integration-tests.sh setup >>"$TEST_VMS_SETUP_OUTPUT_FILE" 2>&1
|
||||
echo "OK" >"$TEST_VMS_READY_LOCK_FILE"
|
||||
echo "Test VMs now ready. $(date)." >>"$TEST_VMS_SETUP_OUTPUT_FILE"
|
||||
fi
|
||||
# Provisioning from image typically take 90 seconds,
|
||||
# but if we are creating a GCP image, it can take more than 10 minutes:
|
||||
wait_for 900 "$TEST_VMS_PROV_AND_CONF_LOCK_FILE"
|
||||
|
||||
# Upload built binaries to testing VMs:
|
||||
cd "$SRCDIR/test" # Ensures we generate Terraform state files in the right folder, for later use by integration tests.
|
||||
./run-integration-tests.sh setup
|
||||
echo "OK" >"$TEST_VMS_READY_LOCK_FILE"
|
||||
echo "Test VMs now ready. $(date)."
|
||||
+14
-15
@@ -2,22 +2,21 @@
|
||||
|
||||
set -e
|
||||
|
||||
source "$STATE"
|
||||
source "$(dirname "$0")/wait_for.sh"
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ]; then
|
||||
[ -n "$SECRET_KEY" ] || {
|
||||
echo "Cannot run smoke tests: no secret key"
|
||||
exit 1
|
||||
}
|
||||
[ -n "$SECRET_KEY" ] || {
|
||||
echo "Cannot run smoke tests: no secret key"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Provisioning from image typically take 90 seconds,
|
||||
# but if we are creating a GCP image, it can take more than 10 minutes:
|
||||
wait_for 900 "$TEST_VMS_READY_LOCK_FILE"
|
||||
source "$SRCDIR/bin/circle-env"
|
||||
|
||||
# Run integration tests:
|
||||
export COVERAGE=true
|
||||
export WEAVE_NET_SANITY_CHECKS_FILES="$CIRCLE_ARTIFACTS/weave_net_sanity_check_*.log"
|
||||
cd "$SRCDIR/test" # Ensures we generate code coverage files in the right folder, and use the Terraform state of previously provisionned VMs.
|
||||
./run-integration-tests.sh test
|
||||
fi
|
||||
# Provisioning from image typically take 90 seconds,
|
||||
# but if we are creating a GCP image, it can take more than 10 minutes:
|
||||
wait_for 900 "$TEST_VMS_READY_LOCK_FILE"
|
||||
|
||||
# Run integration tests:
|
||||
export COVERAGE=true
|
||||
export WEAVE_NET_SANITY_CHECKS_FILES="$CIRCLE_ARTIFACTS/weave_net_sanity_check_*.log"
|
||||
cd "$SRCDIR/test" # Ensures we generate code coverage files in the right folder, and use the Terraform state of previously provisionned VMs.
|
||||
./run-integration-tests.sh test
|
||||
@@ -1,10 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
source "$STATE"
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ] ; then
|
||||
cd $SRCDIR/test
|
||||
test "$CIRCLE_NODE_INDEX" != "0" || (cd $SRCDIR/test; ./gen_coverage_reports.sh)
|
||||
fi
|
||||
@@ -1,11 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -e
|
||||
|
||||
source "$STATE"
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ] ; then
|
||||
cd $SRCDIR
|
||||
make DEBUG=1 lint
|
||||
COVERDIR=test/coverage make RM= tests
|
||||
fi
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/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
|
||||
+2
-2
@@ -41,7 +41,7 @@ case "$1" in
|
||||
for i in $(seq $START $FINISH); do
|
||||
IP=$SUBNET.$i
|
||||
sudo ip addr add $IP/24 dev multiweave
|
||||
weave $i launch-router --datapath '' --iface '' --no-dns --host $IP \
|
||||
weave $i launch --datapath '' --iface '' --no-dns --host $IP \
|
||||
--name $(random_mac) --nickname weave$i "$@"
|
||||
done
|
||||
echo $(date -R) connecting
|
||||
@@ -57,7 +57,7 @@ case "$1" in
|
||||
for i in $(seq $FINISH -1 $START); do
|
||||
IP=$SUBNET.$i
|
||||
echo Stopping weave$i
|
||||
weave $i stop-router
|
||||
weave $i stop
|
||||
sudo ip addr del $IP/24 dev multiweave || true
|
||||
done
|
||||
[ -z "$(ip -4 -o addr show multiweave)" ] && sudo ip link del multiweave
|
||||
|
||||
+13
-22
@@ -9,33 +9,24 @@ function signal_failure() {
|
||||
}
|
||||
trap signal_failure ERR
|
||||
|
||||
source "$STATE"
|
||||
|
||||
function install_terraform() {
|
||||
curl -fsS https://releases.hashicorp.com/terraform/0.8.5/terraform_0.8.5_linux_amd64.zip | gunzip >terraform && chmod +x terraform && sudo mv terraform /usr/bin
|
||||
curl -fsS https://releases.hashicorp.com/terraform/0.11.14/terraform_0.11.14_linux_amd64.zip | gunzip >terraform && chmod +x terraform && sudo mv terraform /usr/bin
|
||||
}
|
||||
|
||||
function install_ansible() {
|
||||
sudo apt-get update || true
|
||||
sudo apt-get install -qq -y python-pip python-dev libffi-dev libssl-dev && pip install --user -U cffi && pip install --user ansible
|
||||
export PATH="$PATH:$HOME/.local/bin"
|
||||
[ -n "$SECRET_KEY" ] || {
|
||||
echo "Cannot run smoke tests: no secret key"
|
||||
exit 1
|
||||
}
|
||||
|
||||
if [ -n "$TEST_AND_PUBLISH" ]; then
|
||||
[ -n "$SECRET_KEY" ] || {
|
||||
echo "Cannot run smoke tests: no secret key"
|
||||
exit 1
|
||||
}
|
||||
source "$SRCDIR/bin/circle-env"
|
||||
|
||||
install_terraform >>"$TEST_VMS_SETUP_OUTPUT_FILE" 2>&1
|
||||
install_ansible >>"$TEST_VMS_SETUP_OUTPUT_FILE" 2>&1
|
||||
install_terraform
|
||||
|
||||
# Only attempt to create GCP image in first container, wait for it to be created otherwise:
|
||||
[ "$CIRCLE_NODE_INDEX" != "0" ] && export CREATE_IMAGE=0
|
||||
# Only attempt to create GCP image in first container, wait for it to be created otherwise:
|
||||
[ "$CIRCLE_NODE_INDEX" != "0" ] && export CREATE_IMAGE=0
|
||||
|
||||
# Provision and configure testing VMs:
|
||||
cd "$SRCDIR/test" # Ensures we generate Terraform state files in the right folder, for later use by integration tests.
|
||||
./run-integration-tests.sh configure >>"$TEST_VMS_SETUP_OUTPUT_FILE" 2>&1
|
||||
echo "OK" >"$TEST_VMS_PROV_AND_CONF_LOCK_FILE"
|
||||
echo "Test VMs now provisioned and configured. $(date)." >>"$TEST_VMS_SETUP_OUTPUT_FILE"
|
||||
fi
|
||||
# Provision and configure testing VMs:
|
||||
cd "$SRCDIR/test" # Ensures we generate Terraform state files in the right folder, for later use by integration tests.
|
||||
./run-integration-tests.sh configure
|
||||
echo "OK" >"$TEST_VMS_PROV_AND_CONF_LOCK_FILE"
|
||||
echo "Test VMs now provisioned and configured. $(date)."
|
||||
@@ -1,46 +0,0 @@
|
||||
#!/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
|
||||
+74
-6
@@ -88,8 +88,12 @@ build() {
|
||||
|
||||
## Inject the version numbers and build the distributables
|
||||
## (library versions?)
|
||||
sed -i "/SCRIPT_VERSION=/ c\SCRIPT_VERSION=\"$VERSION\"" ./weave
|
||||
sed -i -e "s/:latest/:$VERSION/" -e "/imagePullPolicy: Always/d" ./prog/weave-kube/weave-daemonset.yaml
|
||||
sed -i.bak "/SCRIPT_VERSION=/ c\SCRIPT_VERSION=\"$VERSION\"" ./weave
|
||||
sed -i.bak -e "s/:latest/:$VERSION/" -e "/imagePullPolicy: Always/d" ./prog/weave-kube/weave-daemonset-k8s-1.6.yaml
|
||||
sed -i.bak -e "s/:latest/:$VERSION/" -e "/imagePullPolicy: Always/d" ./prog/weave-kube/weave-daemonset-k8s-1.7.yaml
|
||||
sed -i.bak -e "s/:latest/:$VERSION/" -e "/imagePullPolicy: Always/d" ./prog/weave-kube/weave-daemonset-k8s-1.8.yaml
|
||||
sed -i.bak -e "s/:latest/:$VERSION/" -e "/imagePullPolicy: Always/d" ./prog/weave-kube/weave-daemonset-k8s-1.9.yaml
|
||||
sed -i.bak -e "s/:latest/:$VERSION/" -e "/imagePullPolicy: Always/d" ./prog/weave-kube/weave-daemonset-k8s-1.11.yaml
|
||||
make SUDO=$SUDO WEAVE_VERSION=$VERSION DOCKERHUB_USER=$DOCKERHUB_USER
|
||||
|
||||
if make tests; then
|
||||
@@ -162,8 +166,36 @@ draft() {
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag $LATEST_TAG \
|
||||
--name "weave-daemonset.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset.yaml"
|
||||
--name "weave-daemonset-k8s-1.6.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset-k8s-1.6.yaml"
|
||||
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag $LATEST_TAG \
|
||||
--name "weave-daemonset-k8s-1.7.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset-k8s-1.7.yaml"
|
||||
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag $LATEST_TAG \
|
||||
--name "weave-daemonset-k8s-1.8.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset-k8s-1.8.yaml"
|
||||
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag $LATEST_TAG \
|
||||
--name "weave-daemonset-k8s-1.9.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset-k8s-1.9.yaml"
|
||||
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag $LATEST_TAG \
|
||||
--name "weave-daemonset-k8s-1.11.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset-k8s-1.11.yaml"
|
||||
|
||||
echo "** Draft $TYPE $RELEASE_NAME $VERSION created at"
|
||||
echo -e "\thttps://github.com/$GITHUB_USER/weave/releases/$LATEST_TAG"
|
||||
@@ -179,6 +211,10 @@ publish() {
|
||||
fi
|
||||
|
||||
if [ "$RELEASE_TYPE" = 'PRERELEASE' ] ; then
|
||||
echo "== Building and pushing plugin"
|
||||
make UPDATE_LATEST=false SUDO=$SUDO WEAVE_VERSION=$VERSION DOCKERHUB_USER=$DOCKERHUB_USER plugin_publish
|
||||
echo "** Plugin built and pushed"
|
||||
|
||||
echo "== Tagging and pushing images on docker hub as user $DOCKERHUB_USER"
|
||||
make UPDATE_LATEST=$IS_MAINLINE SUDO=$SUDO WEAVE_VERSION=$VERSION DOCKERHUB_USER=$DOCKERHUB_USER publish
|
||||
echo "** Docker images tagged and pushed"
|
||||
@@ -211,6 +247,10 @@ publish() {
|
||||
fi
|
||||
echo '** Sanity checks OK for publishing tag' $LATEST_TAG as $DOCKERHUB_USER/weave:$VERSION
|
||||
|
||||
echo "== Building and pushing plugin"
|
||||
make UPDATE_LATEST=true SUDO=$SUDO WEAVE_VERSION=$VERSION DOCKERHUB_USER=$DOCKERHUB_USER plugin_publish
|
||||
echo "** Plugin built and pushed"
|
||||
|
||||
echo "== Tagging and pushing images on docker hub as user $DOCKERHUB_USER"
|
||||
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"
|
||||
@@ -248,8 +288,36 @@ publish() {
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag latest_release \
|
||||
--name "weave-daemonset.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset.yaml"
|
||||
--name "weave-daemonset-k8s-1.6.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset-k8s-1.6.yaml"
|
||||
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag latest_release \
|
||||
--name "weave-daemonset-k8s-1.7.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset-k8s-1.7.yaml"
|
||||
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag latest_release \
|
||||
--name "weave-daemonset-k8s-1.8.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset-k8s-1.8.yaml"
|
||||
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag latest_release \
|
||||
--name "weave-daemonset-k8s-1.9.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset-k8s-1.9.yaml"
|
||||
|
||||
github-release upload \
|
||||
--user $GITHUB_USER \
|
||||
--repo weave \
|
||||
--tag latest_release \
|
||||
--name "weave-daemonset-k8s-1.11.yaml" \
|
||||
--file "./prog/weave-kube/weave-daemonset-k8s-1.11.yaml"
|
||||
|
||||
echo "** Release $RELEASE_NAME $VERSION published at"
|
||||
echo -e "\thttps://github.com/$GITHUB_USER/weave/releases/$LATEST_TAG"
|
||||
|
||||
+1
-1
@@ -16,6 +16,6 @@ wait_for() {
|
||||
if ! ((i % 10)); then echo "[$i seconds]: Waiting for $lock_file to be created..."; fi
|
||||
sleep 1
|
||||
done
|
||||
echo "Timed out waiting for test VMs to be ready. See details in: $TEST_VMS_SETUP_OUTPUT_FILE" >&2
|
||||
echo "Timed out waiting for test VMs to be ready." >&2
|
||||
return 1
|
||||
}
|
||||
+24
-26
@@ -1,32 +1,34 @@
|
||||
FROM golang:1.7.4
|
||||
FROM golang:1.13.3-buster
|
||||
|
||||
# Support Raspberry Pi 2 and newer
|
||||
ENV GOARM 7
|
||||
|
||||
# The names of the architectures that we're fetching cross-compilers for from the Ubuntu packages
|
||||
# The names of the architectures that we're fetching cross-compilers for from the Debian packages
|
||||
ENV DEB_CROSSPLATFORMS \
|
||||
armhf \
|
||||
arm64
|
||||
arm64 \
|
||||
ppc64el \
|
||||
s390x
|
||||
|
||||
# The go platforms we are supporting to cross-compile
|
||||
ENV GO_CROSSPLATFORMS \
|
||||
linux/arm \
|
||||
linux/arm64
|
||||
linux/arm64 \
|
||||
linux/ppc64le \
|
||||
linux/s390x
|
||||
|
||||
# The names of the gcc cross-compilers we have installed
|
||||
ENV GCC_CROSSCOMPILERS \
|
||||
arm-linux-gnueabihf \
|
||||
aarch64-linux-gnu
|
||||
aarch64-linux-gnu \
|
||||
powerpc64le-linux-gnu \
|
||||
s390x-linux-gnu
|
||||
|
||||
# Temporarily add the Ubuntu repositories, because we're going to install gcc cross-compilers from there
|
||||
# Install the build-essential and crossbuild-essential-ARCH packages
|
||||
RUN echo "deb http://archive.ubuntu.com/ubuntu xenial main universe" > /etc/apt/sources.list.d/cgocrosscompiling.list \
|
||||
&& apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 40976EAF437D05B5 3B4FE6ACC0B21F32 \
|
||||
RUN for platform in ${DEB_CROSSPLATFORMS}; do dpkg --add-architecture $platform; done \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y build-essential \
|
||||
&& for platform in ${DEB_CROSSPLATFORMS}; do apt-get install -y crossbuild-essential-${platform}; done \
|
||||
&& rm /etc/apt/sources.list.d/cgocrosscompiling.list \
|
||||
&& apt-get update \
|
||||
&& apt-get clean && rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install some required packages
|
||||
@@ -41,34 +43,27 @@ RUN apt-get update \
|
||||
flex \
|
||||
bison
|
||||
|
||||
RUN curl -fsSLo shfmt https://github.com/mvdan/sh/releases/download/v1.3.0/shfmt_v1.3.0_linux_amd64 && \
|
||||
echo "b1925c2c405458811f0c227266402cf1868b4de529f114722c2e3a5af4ac7bb2 shfmt" | sha256sum -c && \
|
||||
chmod +x shfmt && \
|
||||
mv shfmt /usr/bin
|
||||
|
||||
# Install common Go tools
|
||||
RUN go get \
|
||||
github.com/golang/lint/golint \
|
||||
github.com/weaveworks/build-tools/cover \
|
||||
github.com/mattn/goveralls \
|
||||
golang.org/x/lint/golint \
|
||||
github.com/fzipp/gocyclo \
|
||||
gopkg.in/mvdan/sh.v1/cmd/shfmt \
|
||||
github.com/fatih/hclfmt \
|
||||
github.com/client9/misspell/cmd/misspell
|
||||
|
||||
# First clean, because we're gonna rebuild the std both with -race enabled and disabled
|
||||
# Running go install std twice is intentional and required, as content is written in two different directories
|
||||
# (/usr/local/go/pkg/linux_amd64 vs /usr/local/go/pkg/linux_amd64_race)
|
||||
# The other architectures do not have support for -race
|
||||
RUN go clean -i net \
|
||||
&& go install -tags netgo std \
|
||||
&& go install -race -tags netgo std
|
||||
|
||||
# Prebuild std for the other architectures
|
||||
RUN for platform in ${GO_CROSSPLATFORMS}; do \
|
||||
GOOS=${platform%/*} GOARCH=${platform##*/} \
|
||||
go install -tags netgo std; done
|
||||
|
||||
# Allow full write access to the Go folders for anyone
|
||||
RUN chmod -R a+w /usr/local/go
|
||||
|
||||
# The libpcap version from Debian packages is 1.6.2, matching that version here although newer versions of libpcap have been released
|
||||
# We have to cross-compile the libpcap library for the new architectures, that's what we're doing here
|
||||
ENV LIBPCAP_CROSS_VERSION=1.6.2
|
||||
RUN curl -sSL http://www.tcpdump.org/release/libpcap-${LIBPCAP_CROSS_VERSION}.tar.gz | tar -xz \
|
||||
RUN curl -sSL https://www.tcpdump.org/release/libpcap-${LIBPCAP_CROSS_VERSION}.tar.gz | tar -xz \
|
||||
&& cd libpcap-${LIBPCAP_CROSS_VERSION} \
|
||||
&& for crosscompiler in ${GCC_CROSSCOMPILERS}; do \
|
||||
CC=${crosscompiler}-gcc ac_cv_linux_vers=2 ./configure --host=${crosscompiler} --with-pcap=linux \
|
||||
@@ -80,5 +75,8 @@ RUN curl -sSL http://www.tcpdump.org/release/libpcap-${LIBPCAP_CROSS_VERSION}.ta
|
||||
&& ln -sf libpcap.so.${MAJOR_VER} /usr/local/lib/libpcap.so \
|
||||
&& make clean; done
|
||||
|
||||
# Install Docker
|
||||
RUN curl -fsSL https://get.docker.com | VERSION=18.06.0-ce /bin/sh
|
||||
|
||||
COPY build.sh /
|
||||
ENTRYPOINT ["sh", "/build.sh"]
|
||||
@@ -1 +0,0 @@
|
||||
8f313a9df867c207ae9ceac6e0935d1d2a0360aafce780dd5d1e0687ee98e0c3 docker-1.10.3.tgz
|
||||
@@ -1 +0,0 @@
|
||||
7541b5490cee1183d911f909c0e91d3d9d57b2b775e40f14a1a7a6de216ca4a8 docker-1.10.3.tgz
|
||||
@@ -0,0 +1 @@
|
||||
926f7f265332076ea4abf6e9586288376dbed2322c155b8d0cafbee614aaa7f2 qemu-ppc64le-static
|
||||
@@ -0,0 +1 @@
|
||||
bab3b4d04263479b3d9f85cf59bd0cad1f2b434f29856b2204f8288f2f3598e4 qemu-s390x-static
|
||||
-76
@@ -1,76 +0,0 @@
|
||||
general:
|
||||
branches:
|
||||
ignore:
|
||||
- gh\-pages
|
||||
|
||||
machine:
|
||||
pre:
|
||||
- curl -sSL https://s3.amazonaws.com/circle-downloads/install-circleci-docker.sh | bash -s -- 1.10.0
|
||||
services:
|
||||
- docker
|
||||
environment:
|
||||
GOPATH: /home/ubuntu:$GOPATH
|
||||
SRCDIR: /home/ubuntu/src/github.com/weaveworks/weave
|
||||
PATH: $PATH:$HOME/.local/bin
|
||||
CLOUDSDK_CORE_DISABLE_PROMPTS: 1
|
||||
STATE: /home/ubuntu/state.env
|
||||
# Base name of VMs for integration tests:
|
||||
NAME: test-$CIRCLE_BUILD_NUM-$CIRCLE_NODE_INDEX
|
||||
TEST_VMS_SETUP_OUTPUT_FILE: $CIRCLE_ARTIFACTS/test_vms_setup_output.txt
|
||||
TEST_VMS_PROV_AND_CONF_LOCK_FILE: $CIRCLE_ARTIFACTS/test_vms_provisioned_and_configured.txt
|
||||
TEST_VMS_READY_LOCK_FILE: $CIRCLE_ARTIFACTS/test_vms_ready.txt
|
||||
# Google Cloud Platorm images for integration tests:
|
||||
USE_IMAGE: 1
|
||||
DISK_NAME_PREFIX: test-$CIRCLE_BUILD_NUM-0
|
||||
|
||||
dependencies:
|
||||
cache_directories:
|
||||
- "~/docker"
|
||||
post:
|
||||
- bin/circle-dependencies-post-sources
|
||||
# Provision & configure testing VMs:
|
||||
- "while [ ! -d $SRCDIR ]; do sleep 1; done; $SRCDIR/bin/provision_test_vms.sh":
|
||||
parallel: true
|
||||
background: true
|
||||
- bin/circle-dependencies-post
|
||||
|
||||
test:
|
||||
pre:
|
||||
- "$SRCDIR/bin/circle-test-pre":
|
||||
parallel: true
|
||||
background: true
|
||||
override:
|
||||
- "$SRCDIR/bin/circle-test-unit":
|
||||
parallel: true
|
||||
- "$SRCDIR/bin/circle-test-smoke":
|
||||
parallel: true
|
||||
timeout: 300
|
||||
post:
|
||||
- "$SRCDIR/bin/circle-test-teardown":
|
||||
parallel: true
|
||||
|
||||
teardown:
|
||||
pre:
|
||||
# Destroy testing VMs:
|
||||
- "cd $SRCDIR/test && ./run-integration-tests.sh destroy":
|
||||
parallel: true
|
||||
background: true
|
||||
- "$SRCDIR/bin/circle-teardown-pre":
|
||||
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"
|
||||
@@ -0,0 +1,7 @@
|
||||
## Community Code of Conduct
|
||||
|
||||
Weaveworks follows the [CNCF Code of Conduct](https://github.com/cncf/foundation/blob/master/code-of-conduct.md).
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior
|
||||
may be reported by contacting a Weaveworks project maintainer, or
|
||||
Alexis Richardson alexis@weave.works.
|
||||
+41
-3
@@ -99,7 +99,9 @@ func (c *Client) AddObserver(ob ContainerObserver) error {
|
||||
pending := make(pendingStarts)
|
||||
retryInterval := InitialInterval
|
||||
for {
|
||||
events := make(chan *docker.APIEvents)
|
||||
// Use a buffered chan so the client library can run ahead of the listener
|
||||
// - Docker will drop an event if it is not collected quickly enough.
|
||||
events := make(chan *docker.APIEvents, 1024)
|
||||
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 {
|
||||
@@ -165,13 +167,25 @@ func (pending pendingStarts) finish(id string) {
|
||||
}
|
||||
}
|
||||
|
||||
// AllContainerIDs returns all the IDs of Docker containers,
|
||||
// whether they are running or not.
|
||||
func (c *Client) AllContainerIDs() ([]string, error) {
|
||||
all, err := c.ListContainers(docker.ListContainersOptions{All: true})
|
||||
return c.containerIDs(true)
|
||||
}
|
||||
|
||||
// RunningContainerIDs returns all the IDs of the running
|
||||
// Docker containers.
|
||||
func (c *Client) RunningContainerIDs() ([]string, error) {
|
||||
return c.containerIDs(false)
|
||||
}
|
||||
|
||||
func (c *Client) containerIDs(all bool) ([]string, error) {
|
||||
containers, err := c.ListContainers(docker.ListContainersOptions{All: all})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var ids []string
|
||||
for _, c := range all {
|
||||
for _, c := range containers {
|
||||
ids = append(ids, c.ID)
|
||||
}
|
||||
return ids, nil
|
||||
@@ -213,6 +227,30 @@ func (c *Client) GetContainerIP(nameOrID string) (string, error) {
|
||||
return info.NetworkSettings.IPAddress, nil
|
||||
}
|
||||
|
||||
func (c *Client) EnsureNetwork(networkName, driver, subnet string, options map[string]interface{}) error {
|
||||
_, err := c.CreateNetwork(
|
||||
docker.CreateNetworkOptions{
|
||||
Name: networkName,
|
||||
CheckDuplicate: true,
|
||||
Driver: driver,
|
||||
IPAM: &docker.IPAMOptions{
|
||||
Driver: driver,
|
||||
Config: []docker.IPAMConfig{{Subnet: subnet}},
|
||||
},
|
||||
Options: options,
|
||||
})
|
||||
if err != docker.ErrNetworkAlreadyExists && err != nil {
|
||||
// Despite appearances to the contrary, CreateNetwork does
|
||||
// sometimes(always?) *not* return ErrNetworkAlreadyExists
|
||||
// when the network already exists. Hence we need to check for
|
||||
// this explicitly.
|
||||
if _, err2 := c.NetworkInfo(networkName); err2 != nil {
|
||||
return fmt.Errorf("unable to create network: %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// logging
|
||||
|
||||
func (c *Client) errorf(fmt string, args ...interface{}) {
|
||||
|
||||
+7
-1
@@ -6,7 +6,7 @@ import (
|
||||
"log"
|
||||
"strings"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type textFormatter struct {
|
||||
@@ -54,6 +54,12 @@ func SetLogLevel(levelname string) {
|
||||
Log.Level = level
|
||||
}
|
||||
|
||||
func CheckError(e error) {
|
||||
if e != nil {
|
||||
Log.Error(e)
|
||||
}
|
||||
}
|
||||
|
||||
func CheckFatal(e error) {
|
||||
if e != nil {
|
||||
Log.Fatal(e)
|
||||
|
||||
@@ -75,6 +75,14 @@ func DeleteDatapath(dpname string) error {
|
||||
}
|
||||
|
||||
func AddDatapathInterface(dpname string, ifname string) error {
|
||||
return addDatapathInterface(dpname, ifname, false)
|
||||
}
|
||||
|
||||
func AddDatapathInterfaceIfNotExist(dpname string, ifname string) error {
|
||||
return addDatapathInterface(dpname, ifname, true)
|
||||
}
|
||||
|
||||
func addDatapathInterface(dpname, ifname string, ignoreIfExist bool) error {
|
||||
dpif, err := odp.NewDpif()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -87,5 +95,9 @@ func AddDatapathInterface(dpname string, ifname string) error {
|
||||
}
|
||||
|
||||
_, err = dp.CreateVport(odp.NewNetdevVportSpec(ifname))
|
||||
if ignoreIfExist && err == odp.NetlinkError(syscall.EEXIST) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
package common
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
type WaitGroup struct {
|
||||
wg sync.WaitGroup
|
||||
count int32
|
||||
}
|
||||
|
||||
// Increase the count of things waiting and return a closure which calls Done
|
||||
func (w *WaitGroup) Add() func() {
|
||||
atomic.AddInt32(&w.count, 1)
|
||||
w.wg.Add(1)
|
||||
return func() {
|
||||
w.wg.Done()
|
||||
atomic.AddInt32(&w.count, -1)
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WaitGroup) IsDone() bool {
|
||||
return atomic.LoadInt32(&w.count) == 0
|
||||
}
|
||||
+9
-3
@@ -26,10 +26,15 @@ var (
|
||||
|
||||
const (
|
||||
NameIdent = "peername"
|
||||
FileName = "data.db"
|
||||
fileName = "data.db"
|
||||
)
|
||||
|
||||
func NewBoltDB(dbPathname string) (*BoltDB, error) {
|
||||
func Pathname(dbPrefix string) string {
|
||||
return dbPrefix + fileName
|
||||
}
|
||||
|
||||
func NewBoltDB(dbPrefix string) (*BoltDB, error) {
|
||||
dbPathname := Pathname(dbPrefix)
|
||||
db, err := bolt.Open(dbPathname, 0660, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[boltDB] Unable to open %s: %s", dbPathname, err)
|
||||
@@ -38,8 +43,9 @@ func NewBoltDB(dbPathname string) (*BoltDB, error) {
|
||||
return &BoltDB{db: db}, err
|
||||
}
|
||||
|
||||
func NewBoltDBReadOnly(dbPathname string) (*BoltDB, error) {
|
||||
func NewBoltDBReadOnly(dbPrefix string) (*BoltDB, error) {
|
||||
options := bolt.Options{Timeout: time.Millisecond * 50, ReadOnly: true}
|
||||
dbPathname := Pathname(dbPrefix)
|
||||
db, err := bolt.Open(dbPathname, 0660, &options)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("[boltDB] Unable to open %s: %s", dbPathname, err)
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
# Weave Net Benchmarks
|
||||
|
||||
## 2017-02-16: Fast Datapath Encryption
|
||||
|
||||
Results: https://www.weave.works/weave-net-performance-fast-datapath/
|
||||
|
||||
* Two `c3.8xlarge` instances running Ubuntu 16.04 LTS (`ami-d8f4deab`) in
|
||||
`eu-west-1` region of AWS.
|
||||
* 10 Gigabit [Enhanced Networking](http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/enhanced-networking.html).
|
||||
* `iperf 3.0.11`.
|
||||
|
||||
### fastdp
|
||||
|
||||
* Encrypted:
|
||||
|
||||
```
|
||||
host1$ WEAVE_MTU=8916 weave launch --password=foobar
|
||||
host1$ docker $(weave config) run --rm -ti --name server networkstatic/iperf3 -s
|
||||
|
||||
host2$ WEAVE_MTU=8916 weave launch --password=foobar host1
|
||||
host2$ docker $(weave config) run --rm -ti networkstatic/iperf3 -c server
|
||||
```
|
||||
|
||||
* Non-encrypted:
|
||||
|
||||
```
|
||||
host1$ WEAVE_MTU=8950 weave launch
|
||||
host1$ docker $(weave config) run --rm -ti --name server networkstatic/iperf3 -s
|
||||
|
||||
host2$ WEAVE_MTU=8950 weave launch --password=foobar host1
|
||||
host2$ docker $(weave config) run --rm -ti networkstatic/iperf3 -c server
|
||||
```
|
||||
|
||||
### sleeve
|
||||
|
||||
* Encrypted:
|
||||
|
||||
```
|
||||
host1$ WEAVE_NO_FASTDP=1 weave launch --password=foobar
|
||||
host1$ docker $(weave config) run --rm -ti --name server networkstatic/iperf3 -s
|
||||
|
||||
host2$ WEAVE_NO_FASTDP=1 weave launch --password=foobar host1
|
||||
host2$ docker $(weave config) run --rm -ti networkstatic/iperf3 -c server
|
||||
```
|
||||
|
||||
* Non-encrypted:
|
||||
|
||||
```
|
||||
host1$ WEAVE_NO_FASTDP=1 weave launch
|
||||
host1$ docker $(weave config) run --rm -ti --name server networkstatic/iperf3 -s
|
||||
|
||||
host2$ WEAVE_NO_FASTDP=1 weave launch --password=foobar host1
|
||||
host2$ docker $(weave config) run --rm -ti networkstatic/iperf3 -c server
|
||||
```
|
||||
|
||||
### host
|
||||
|
||||
* Non-encrypted:
|
||||
|
||||
```
|
||||
host1$ iperf -s
|
||||
host2$ iperf -c host1
|
||||
```
|
||||
|
||||
* Encrypted:
|
||||
|
||||
```
|
||||
host1$ export KEY1="0x466454f2b1a770f8f872f9afbc35ebeac57e00fc11ac86ed1f82716f010b20f0cf532274"
|
||||
host1$ export KEY2="0x3531151241a770f8f872f9afbc35ebeac57e00fc11ac86ed1f82716f010b20f0cf532274"
|
||||
host1# ip xfrm state add src ${IP1} dst ${IP2} proto esp spi 0x4c856ffc replay-window 256 flag esn reqid 0 mode transport aead 'rfc4106(gcm(aes))' ${KEY1} 128
|
||||
host1# ip xfrm state add src ${IP2} dst ${IP1} proto esp spi 0x9b0830bc replay-window 256 flag esn reqid 0 mode transport aead 'rfc4106(gcm(aes))' ${KEY2} 128
|
||||
host1# ip xfrm policy add src ${IP1}/32 dst ${IP2}/32 dir out tmpl src ${IP1} dst ${IP2} proto esp spi 0x4c856ffc reqid 0 mode transport
|
||||
host1$ iperf -s
|
||||
|
||||
host2$ export KEY1="0x466454f2b1a770f8f872f9afbc35ebeac57e00fc11ac86ed1f82716f010b20f0cf532274"
|
||||
host2$ export KEY2="0x3531151241a770f8f872f9afbc35ebeac57e00fc11ac86ed1f82716f010b20f0cf532274"
|
||||
host2# ip xfrm state add src ${IP2} dst ${IP1} proto esp spi 0x9b0830bc reqid 0 replay-window 256 flag esn mode transport aead 'rfc4106(gcm(aes))' ${KEY2} 128
|
||||
host2# ip xfrm state add src ${IP1} dst ${IP2} proto esp spi 0x4c856ffc reqid 0 replay-window 256 flag esn mode transport aead 'rfc4106(gcm(aes))' ${KEY1} 128
|
||||
host2# ip xfrm policy add src ${IP2}/32 dst ${IP1}/32 dir out tmpl src ${IP1} dst ${IP2} proto esp spi 0x9b0830bc reqid 0 mode transport
|
||||
host2$ iperf -c host1
|
||||
```
|
||||
@@ -0,0 +1,70 @@
|
||||
# Entrypoints
|
||||
|
||||
This document describes the various ways that weave as a binary gets launched, and where and how it processes options.
|
||||
|
||||
The goal is to enable maintainers to modify functionality without having to reverse engineer the code to find all of the places you can start a weave binary and what it depends upon.
|
||||
|
||||
## Components
|
||||
### weaver
|
||||
|
||||
`weaver` is the primary binary: it sets up the weave bridge to which containers are attached, manages IP address allocation, serves DNS requests and does many more things.
|
||||
|
||||
Since version 2.0, `weaver` also bundles the Docker Plugin (legacy version) and Docker API Proxy which used to be shipped as separate containers.
|
||||
|
||||
### weaveutil
|
||||
weaveutil is a binary which provides a number of functions for managing an existing weave network. It gets information about the network, attaches and detaches containers, etc.
|
||||
|
||||
For the majority of interactions with an existing weave network, you will launch `weaveutil` in some manner or another.
|
||||
|
||||
Almost all options can be passed as `--option` to `weaveutil`. These options are created in `prog/weaveutil/main.go` with a table mapping each command to a dedicated golang function.
|
||||
|
||||
In addition to operating in normal mode, `weaveutil` has several additional operating modes. Before processing any commands, `weaveutil` checks the filename with which it was called.
|
||||
|
||||
* If it was `weave-ipam` it delegates responsibility to the `cniIPAM()` function in `prog/weaveutil/cni.go`, which, in turn, calls the standard CNI plugin function `cni.PluginMain()`, passing it weave's IPAM implementation from `plugin/ipam`.
|
||||
* If it was `weave-net` it delegates responsibility to the `cniNet()` function in `prog/weaveutil/cni.go`, which, in turn, calls the standard CNI plugin function `cni.PluginMain()`, passing it weave's net plugin implementation from `plugin/net`.
|
||||
|
||||
Finally, `weaveutil` is called by `weaver` when it needs to operate in a different network namespace.
|
||||
Go programs cannot safely switch namespaces; see this [issue](http://github.com/vishvananda/netns/issues/17).
|
||||
The call chain is `weaver`->`nsenter`->`weaveutil`.
|
||||
|
||||
### weave
|
||||
Wrapping `weaver` and `weaveutil` is `weave`, a `sh` script that provides help information and calls `weaveutil` as relevant.
|
||||
|
||||
### weave-kube
|
||||
weave-kube is an image with the weave binaries and a wrapper script installed. It is responsible for:
|
||||
|
||||
1. Setting up the weave network
|
||||
2. Connecting to peers
|
||||
3. Copy the weave CNI-compatible binary plugin `weaveutil` to `/opt/cni/bin/weave-net` and weave config in `/etc/cni/net.d/10-weave.conflist`
|
||||
4. Running the weave network policy controller (weave-npc) to implement kubernetes' `NetworkPolicy`
|
||||
|
||||
Once installation is complete, each network-relevant change in a container leads `kubelet` to:
|
||||
|
||||
1. Set certain environment variables to configure the CNI plugin, mostly related to the container ID
|
||||
2. Launch `/opt/cni/bin/weave-net`
|
||||
3. Pass the CNI config - the contents of `/etc/cni/net.d/10-weave.conflist` to `weave-net`
|
||||
|
||||
Thus, when each container is changed, and `kubelet` calls weave as a CNI plugin, it really just is launching `weaveutil` as `weave-net`.
|
||||
|
||||
The installation and setup of all of the above - and therefore the entrypoint to the weave-kube image - is the script `prog/weave-kube/launch.sh`. `launch.sh` does the following:
|
||||
|
||||
1. Read configuration variables from the environment. When the documentation for `weave-kube` describes configuring the weave network by changing the environment variables in the daemonset in the `.yml` file, `launch.sh` reads these environment variables.
|
||||
2. Set up the config file at `/etc/cni/net.d/10-weave.conflist`
|
||||
3. Run the `weave` initialization script
|
||||
4. Run `weaver` with the correct configuration passed as command-line options
|
||||
|
||||
|
||||
## Adding Options
|
||||
To add new options to how weave should run with each invocation, you would do the following:
|
||||
|
||||
1. determine to which `weave` command you want to add the option(s). `weave` normally is launched as `weave <command> <options> ...`.
|
||||
2. Add the option to `weave` script help for each `weave` command you wish to make it available. As of this writing, all of the commands and their options are listed in the function `usage_no_exist()`.
|
||||
3. Add the option to `weave` script option processing for the `weave` command, under the `case $COMMAND in` in the main `weave` script.
|
||||
4. In the function for the `weave` command, determine if the command should be passed on to `weaveutil` or `weaver`. Pass the option on to `weaveutil` or `weaver`, as appropriate, in the format `--option`, in the function for the `weave` command.
|
||||
5. Add a command-line option `--option` to `weaveutil` or `weaver` as appropriate.
|
||||
6. If the option can be configured for CNI:
|
||||
* have `prog/weave-kube/launch.sh` read it as an environment variable and set inside
|
||||
* set a default for the environment variable in `prog/weave-kube/weave-daemonset-k8s-N.N.yaml`
|
||||
* if it should be set via `weaver` globally on its one-time initialization invocation, pass it on in `launch.sh`
|
||||
* if it needs to be set via `weaveutil` on each invocation of `weave-net`, have `launch.sh` save it as an option in the CNI config file `/etc/cni/net.d/10-weave.conflist` and then have the CNI code in `plugin/net/cni.go` in `weaveutil` read it and use where appropriate
|
||||
7. Document it!
|
||||
@@ -0,0 +1,33 @@
|
||||
# Source IP addr of packet sent to k8s Service
|
||||
|
||||
## Setup
|
||||
|
||||
* Weave Net: v2.2.1.
|
||||
* Kubernetes: v1.10.0.
|
||||
* kube-proxy: iptables/ipvs mode with `--masquerade-all=false` and `--cluster-cidr` unspecified(default kubeadm options).
|
||||
* Hosts: `h1` and `h2`.
|
||||
|
||||
## Source IP
|
||||
|
||||
\# | Src | Target | Dst | Src IP | `-j WEAVE-NPC`
|
||||
---|-------------|-------------------|--------|------------------|--------------
|
||||
1 | Pod_h1 | ip(Pod_h2) | Pod_h2 | ip(Pod_h1) | OK
|
||||
2 | h1 | ClusterIP | Pod_h1 | ip(weave_h1) | **NOK**
|
||||
3 | h1 | ClusterIP | Pod_h2 | ip(weave_h1) | OK
|
||||
4 | Pod_h1 | ClusterIP | Pod_h1 | ip(Pod_h1) | OK
|
||||
5 | Pod_h1 | ClusterIP | Pod_h2 | ip(weave_h1) | OK
|
||||
6 | h1 | ip(h1):NodePort | Pod_h1 | ip(weave_h1) | **NOK**
|
||||
7 | h1 | ip(h1):NodePort | Pod_h2 | ip(weave_h1) | OK
|
||||
8 | h1 | ip(h2):NodePort | Pod_h1 | ip(weave_h2) | OK
|
||||
9 | h1 | ip(h2):NodePort | Pod_h2 | ip(weave_h2) | ??? Can't reproduce
|
||||
10 | Pod_h1 | ip(h1):NodePort | Pod_h1 | ip(weave_h1) | **NOK**
|
||||
11 | Pod_h1 | ip(h1):NodePort | Pod_h2 | ip(weave_h1) | OK
|
||||
12 | Pod_h1 | ip(h2):NodePort | Pod_h1 | ip(weave_h2) | OK
|
||||
13 | Pod_h1 | ip(h2):NodePort | Pod_h2 | ip(weave_h2) | OK
|
||||
|
||||
Remarks:
|
||||
|
||||
* **Src IP** is of a packet which is captured on the weave bridge.
|
||||
* **-j WEAVE-NPC** - whether a packet enters the `filter/WEAVE-NPC` iptables chain (OK = NetworkPolicy is enforced as required).
|
||||
* **Pod_h1** - a Pod running on the `h1` host.
|
||||
* **ip(weave_h1)** - IP addr of the weave bridge on the `h1` host.
|
||||
+45
-4
@@ -3,9 +3,9 @@
|
||||
|
||||
* Up-to-date versions of `git`, `go` etc
|
||||
* Install GitHub release tool `go get github.com/weaveworks/github-release`
|
||||
* Install manifest tool `go get github.com/estesp/manifest-tool`
|
||||
* Create a [github token for
|
||||
github-release](https://help.github.com/articles/creating-an-access-token-for-command-line-use/);
|
||||
set and export `$GITHUB_TOKEN` with this value
|
||||
github-release](https://help.github.com/articles/creating-an-access-token-for-command-line-use/); select the `repo` OAuth scope; set and export `$GITHUB_TOKEN` with this value
|
||||
|
||||
## Release Types
|
||||
|
||||
@@ -128,18 +128,59 @@ Finally, for **Mainline** releases only:
|
||||
|
||||
* Images tagged `latest` are updated on DockerHub
|
||||
|
||||
Now, you can validate whether images were published for all platforms:
|
||||
|
||||
# Required platforms for each image:
|
||||
grep '^ML_PLATFORMS=' Makefile
|
||||
|
||||
# Published platforms per image:
|
||||
for img in $(grep '^PUBLISH=' Makefile); do
|
||||
img="weaveworks/$(echo $img | cut -d_ -f2):${TAG#v}"
|
||||
platforms=$(manifest-tool \
|
||||
inspect --raw "$img" | \
|
||||
jq '.[].Platform | .os + "/" +.architecture' | \
|
||||
tr '\n' ' ')
|
||||
echo "$img: $platforms"
|
||||
done
|
||||
|
||||
|
||||
### Docker Store
|
||||
|
||||
Weave Net is [available in Docker Store](https://store.docker.com/plugins/weave-net-plugin) and should also be released there.
|
||||
|
||||
* Go to https://store.docker.com/ and log in.
|
||||
* Go to "[_Publish_](https://store.docker.com/publisher/center)".
|
||||
* Under "_My Products_", select "_owners (weaveworks)_". Weave Net should be listed among our products.
|
||||
* Click "_Actions_" > "_Edit Product_".
|
||||
* Go to "_Plans & Pricing_" > "_Free Tier_".
|
||||
* Under "_Source Repositories & Tags_", click on "_Add Source_", select "_net-plugin_" under "_Repository_" and the version to release under "_Tag_".
|
||||
* Under "_Resources_" > "_Installation Instructions_", update the lines containing `export NET_VERSION=<version>`.
|
||||
* Click "_Save_"
|
||||
* Click "_Submit For Review_". You should see a message like: "_Your product has been submitted for approval. [...] We'll be in touch with next steps soon!_".
|
||||
* You should receive an email saying:
|
||||
|
||||
> This email confirms that we received your submission on \<date\> of Weave Net to the Docker Store.
|
||||
>
|
||||
> We're reviewing your submission to ensure that it meets our [security guidelines](https://success.docker.com/Store#Security_and_Audit_Policies) and complies with our [best practices](https://success.docker.com/Store#Create_Great_Content). Don't worry! We'll let you know if there's anything you need to change before we publish your submission. You should hear back from us within the next 14 days.
|
||||
>
|
||||
> Thanks for submitting your content to the Docker Store!
|
||||
|
||||
* Hope Docker eventually performs the release or contacts us. If nothing happens within 14 days, contact their support team: publisher-support@docker.com.
|
||||
|
||||
### Finish up
|
||||
|
||||
* If not on master, merge branch into master and push to GitHub.
|
||||
* 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
|
||||
tag, push to GitHub and set [WEAVE_NET_REV](https://github.com/weaveworks/website-next/blob/master/Makefile)
|
||||
to X.Y via a PR - 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
|
||||
* File a PR to update the version of the daemonset at
|
||||
https://github.com/kubernetes/kops/tree/master/upup/models/cloudup/resources/addons/networking.weave
|
||||
and kops/upup/pkg/fi/cloudup/bootstrapchannelbuilder.go
|
||||
and kops/upup/pkg/fi/cloudup/tests/bootstrapchannelbuilder/weave/manifest.yaml
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
|
||||
+64
-12
@@ -9,7 +9,9 @@ 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
|
||||
pods in that namespace for which default ingress is allowed
|
||||
* A `hash:ip` set per namespace, containing the IP addresses of all
|
||||
pods in that namespace for which default egress is allowed
|
||||
* 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
|
||||
@@ -18,6 +20,9 @@ NetworkPolicy object updates from the k8s API server:
|
||||
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
|
||||
* A `hash:net` set for each distinct (within the scope of the
|
||||
containing network policy's namespace) `except` list of CIDR's mentioned in
|
||||
the network policies
|
||||
|
||||
IPsets are implemented by the kernel module `xt_set`, without which
|
||||
weave-npc will not work.
|
||||
@@ -38,11 +43,21 @@ 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`.
|
||||
The policy controller maintains several iptables chains in response to
|
||||
changes to pods, namespaces and network policies.
|
||||
|
||||
## Static `WEAVE-NPC` chain
|
||||
|
||||
`WEAVE-NPC` chain contains static rules to ACCEPT traffic that is `RELATED,ESTABLISHED`
|
||||
and run `NEW` traffic through `WEAVE-NPC-DEFAULT` followed by `WEAVE-NPC-INGRESS` chains
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
## Dynamically maintained `WEAVE-NPC-DEFAULT` chain
|
||||
|
||||
@@ -63,22 +78,59 @@ 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 `WEAVE-NPC-EGRESS` chain
|
||||
|
||||
`WEAVE-NPC-EGRESS` chain contains static rules to ACCEPT traffic that is `RELATED,ESTABLISHED`
|
||||
and run `NEW` traffic through `WEAVE-NPC-EGRESS-DEFAULT` followed by `WEAVE-NPC-EGRESS-CUSTOM` chains
|
||||
|
||||
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
|
||||
iptables -A WEAVE-NPC-EGRESS -m state --state RELATED,ESTABLISHED -j ACCEPT
|
||||
iptables -A WEAVE-NPC-EGRESS -m state --state NEW -j WEAVE-NPC-EGRESS-DEFAULT
|
||||
iptables -A WEAVE-NPC-EGRESS -m state --state NEW -m mark ! --mark 0x40000/0x40000 -j WEAVE-NPC-EGRESS-CUSTOM
|
||||
iptables -A WEAVE-NPC-EGRESS -m mark ! --mark 0x40000/0x40000 -j DROP
|
||||
```
|
||||
|
||||
## Dynamically maintained `WEAVE-NPC-EGRESS-DEFAULT` chain
|
||||
|
||||
The policy controller maintains a rule in this chain for every
|
||||
namespace whose egress isolation policy is `DefaultAllow`. The
|
||||
purpose of this rule is simply to ACCEPT any traffic originating from such namespace before it reaches the egress chain.
|
||||
|
||||
```
|
||||
iptables -A WEAVE-NPC-EGRESS-DEFAULT -m set --match-set $NSIPSET src -j WEAVE-NPC-EGRESS-ACCEPT
|
||||
iptables -A WEAVE-NPC-EGRESS-DEFAULT -m set --match-set $NSIPSET src -j RETURN
|
||||
```
|
||||
|
||||
## Static `WEAVE-NPC-EGRESS-ACCEPT` chain
|
||||
|
||||
`WEAVE-NPC-EGRESS-ACCEPTS` chain contains static rules to mark traffic
|
||||
|
||||
Static configuration:
|
||||
|
||||
```
|
||||
iptables -A WEAVE-NPC-EGRESS-ACCEPT -j MARK --set-xmark 0x40000/0x40000
|
||||
```
|
||||
|
||||
## Dynamically maintained `WEAVE-NPC-EGRESS-CUSTOM` chain
|
||||
|
||||
For each namespace network policy egress rule peer/port combination:
|
||||
|
||||
```
|
||||
iptables -A WEAVE-NPC-EGRESS-CUSTOM -p $PROTO [-m set --match-set $SRCSET] -m set --match-set $DSTSET --dport $DPORT -j ACCEPT
|
||||
```
|
||||
|
||||
|
||||
# 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
|
||||
```
|
||||
iptables -A INPUT -i weave -j WEAVE-NPC-EGRESS
|
||||
iptables -A FORWARD -i weave -j WEAVE-NPC-EGRESS
|
||||
iptables -A FORWARD -o weave -j WEAVE-NPC
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
+28
-7
@@ -74,6 +74,7 @@ type Allocator struct {
|
||||
isKnownPeer func(mesh.PeerName) bool
|
||||
quorum func() uint
|
||||
now func() time.Time
|
||||
tracker tracker.LocalRangeTracker
|
||||
}
|
||||
|
||||
// PreClaims are IP addresses discovered before we could initialize IPAM
|
||||
@@ -130,6 +131,7 @@ func NewAllocator(config Config) *Allocator {
|
||||
quorum: config.Quorum,
|
||||
dead: make(map[string]time.Time),
|
||||
now: time.Now,
|
||||
tracker: config.Tracker,
|
||||
}
|
||||
|
||||
alloc.pendingClaims = make([]operation, len(config.PreClaims))
|
||||
@@ -210,9 +212,9 @@ func (alloc *Allocator) doOperation(op operation, ops *[]operation) {
|
||||
// already succeeded. If it is on the queue, we call
|
||||
// cancel on it, allowing callers waiting for the resultChans
|
||||
// to unblock.
|
||||
func (alloc *Allocator) cancelOp(op operation, ops *[]operation) {
|
||||
func (alloc *Allocator) cancelOp(opToCancel operation, ops *[]operation) {
|
||||
for i, op := range *ops {
|
||||
if op == op {
|
||||
if op == opToCancel {
|
||||
*ops = append((*ops)[:i], (*ops)[i+1:]...)
|
||||
op.Cancel()
|
||||
break
|
||||
@@ -265,11 +267,15 @@ func (alloc *Allocator) tryPendingOps() {
|
||||
alloc.tryOps(&alloc.pendingAllocates)
|
||||
}
|
||||
|
||||
func (alloc *Allocator) havePendingOps() bool {
|
||||
return len(alloc.pendingPrimes)+len(alloc.pendingClaims)+len(alloc.pendingAllocates) > 0
|
||||
}
|
||||
|
||||
func (alloc *Allocator) spaceRequestDenied(sender mesh.PeerName, r address.Range) {
|
||||
for i := 0; i < len(alloc.pendingClaims); {
|
||||
claim := alloc.pendingClaims[i].(*claim)
|
||||
if r.Contains(claim.cidr.Addr) {
|
||||
claim.deniedBy(alloc, sender)
|
||||
claim.deniedBy(alloc, sender, nil)
|
||||
alloc.pendingClaims = append(alloc.pendingClaims[:i], alloc.pendingClaims[i+1:]...)
|
||||
continue
|
||||
}
|
||||
@@ -696,8 +702,12 @@ func (alloc *Allocator) actorLoop(actionChan <-chan func(), stopChan <-chan stru
|
||||
// Retry things in case messages got lost between here and recipients
|
||||
if alloc.awaitingConsensus {
|
||||
alloc.propose()
|
||||
} else {
|
||||
alloc.tryPendingOps()
|
||||
} else if alloc.havePendingOps() {
|
||||
if alloc.ring.Empty() {
|
||||
alloc.establishRing()
|
||||
} else {
|
||||
alloc.tryPendingOps()
|
||||
}
|
||||
}
|
||||
alloc.removeDeadContainers()
|
||||
}
|
||||
@@ -804,6 +814,16 @@ func (alloc *Allocator) sendRingUpdate(dest mesh.PeerName) {
|
||||
alloc.gossip.GossipUnicast(dest, msg)
|
||||
}
|
||||
|
||||
func (alloc *Allocator) hasAllocations(rs []address.Range) bool {
|
||||
for _, r := range rs {
|
||||
alloc.debugf("hasAllocations: %v %v vs %v", r, alloc.space.NumFreeAddressesInRange(r), r.Size())
|
||||
if alloc.space.NumFreeAddressesInRange(r) != r.Size() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (alloc *Allocator) update(sender mesh.PeerName, msg []byte) error {
|
||||
reader := bytes.NewReader(msg)
|
||||
decoder := gob.NewDecoder(reader)
|
||||
@@ -822,7 +842,7 @@ func (alloc *Allocator) update(sender mesh.PeerName, msg []byte) error {
|
||||
// If someone sent us a ring, merge it into ours. Note this will move us
|
||||
// out of the awaiting-consensus state if we didn't have a ring already.
|
||||
case data.Ring != nil:
|
||||
updated, err := alloc.ring.Merge(*data.Ring)
|
||||
updated, err := alloc.ring.Merge(*data.Ring, alloc.hasAllocations)
|
||||
switch err {
|
||||
case nil:
|
||||
if updated {
|
||||
@@ -900,6 +920,7 @@ func (alloc *Allocator) donateSpace(r address.Range, to mesh.PeerName) {
|
||||
alloc.debugln("Giving range", chunk, "to", to)
|
||||
alloc.ring.GrantRangeToHost(chunk.Start, chunk.End, to)
|
||||
alloc.persistRing()
|
||||
alloc.gossip.GossipBroadcast(alloc.Gossip())
|
||||
}
|
||||
|
||||
func (alloc *Allocator) assertInvariants() {
|
||||
@@ -975,7 +996,7 @@ func (alloc *Allocator) loadPersistedData() bool {
|
||||
alloc.persistOwned()
|
||||
}
|
||||
|
||||
if !nameFound || !ringFound {
|
||||
if !nameFound || !ringFound || persistedRing == nil || persistedRing.Empty() {
|
||||
overwritePersisted("No valid persisted data")
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/weaveworks/weave/api"
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/testing/gossip"
|
||||
@@ -30,6 +31,10 @@ func (alloc *Allocator) SimplyClaim(ident string, cidr address.CIDR) error {
|
||||
return alloc.Claim(ident, cidr, true, true, returnFalse)
|
||||
}
|
||||
|
||||
func (alloc *Allocator) AnonymousClaim(cidr address.CIDR) error {
|
||||
return alloc.Claim(api.NoContainerID, cidr, false, true, returnFalse)
|
||||
}
|
||||
|
||||
func TestAllocFree(t *testing.T) {
|
||||
const (
|
||||
container1 = "abcdef"
|
||||
@@ -170,6 +175,8 @@ func TestAllocatorClaim(t *testing.T) {
|
||||
universe = "10.0.3.0/24"
|
||||
testAddr1 = "10.0.3.5/24"
|
||||
testAddr2 = "10.0.4.2/24"
|
||||
testAddr4 = "10.0.2.3/23"
|
||||
subnet4 = "10.0.2.0/23"
|
||||
testPre = "10.0.3.1/24"
|
||||
)
|
||||
|
||||
@@ -186,6 +193,7 @@ func TestAllocatorClaim(t *testing.T) {
|
||||
alloc.Prime()
|
||||
// Do an allocate on the other peer, which we will try to claim later
|
||||
addrx, err := allocs[0].Allocate(container1, subnet, true, returnFalse)
|
||||
require.NoError(t, err)
|
||||
// Should not get the address we pre-claimed
|
||||
require.NotEqual(t, addrx, preAddr)
|
||||
router.Flush()
|
||||
@@ -202,6 +210,9 @@ func TestAllocatorClaim(t *testing.T) {
|
||||
// claim for a different container should fail
|
||||
err = alloc.SimplyClaim(container1, addr1)
|
||||
require.Error(t, err)
|
||||
// claim for an anonymous container, will assume it's the same one
|
||||
err = alloc.AnonymousClaim(addr1)
|
||||
require.NoError(t, err)
|
||||
// claiming the address allocated on the other peer should fail
|
||||
err = alloc.SimplyClaim(container1, address.MakeCIDR(subnet, addrx))
|
||||
require.Error(t, err, "claiming address allocated on other peer should fail")
|
||||
@@ -214,6 +225,15 @@ func TestAllocatorClaim(t *testing.T) {
|
||||
addr2, _ := address.ParseCIDR(testAddr2)
|
||||
err = alloc.SimplyClaim(container1, addr2)
|
||||
require.NoError(t, err)
|
||||
err = alloc.SimplyClaim(container3, addr2)
|
||||
require.Error(t, err)
|
||||
// Anonymous claim outside of our universe
|
||||
addr4, _ := address.ParseCIDR(testAddr4)
|
||||
subnet4cidr, _ := address.ParseCIDR(subnet4)
|
||||
require.NoError(t, alloc.AnonymousClaim(addr4))
|
||||
addr5, err := alloc.Allocate(api.NoContainerID, subnet4cidr, false, returnFalse)
|
||||
require.NoError(t, err)
|
||||
require.NotEqual(t, addr4.Start(), addr5)
|
||||
}
|
||||
|
||||
func (alloc *Allocator) pause() func() {
|
||||
|
||||
+37
-11
@@ -12,6 +12,7 @@ import (
|
||||
|
||||
type claim struct {
|
||||
resultChan chan<- error
|
||||
tryCount int
|
||||
ident string // a container ID, something like "weave:expose", or api.NoContainerID
|
||||
cidr address.CIDR // single address being claimed
|
||||
isContainer bool // true if ident is a container ID
|
||||
@@ -19,6 +20,8 @@ type claim struct {
|
||||
hasBeenCancelled func() bool
|
||||
}
|
||||
|
||||
const maxTryCount = 5
|
||||
|
||||
// Send an error (or nil for success) back to caller listening on resultChan
|
||||
func (c *claim) sendResult(result error) {
|
||||
// Make sure we only send a result once, since listener stops listening after that
|
||||
@@ -40,10 +43,31 @@ func (c *claim) Try(alloc *Allocator) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
c.tryCount++
|
||||
|
||||
addOwned := func() {
|
||||
if c.ident == api.NoContainerID {
|
||||
alloc.addOwned(c.cidr.Addr.String(), c.cidr, c.isContainer)
|
||||
} else {
|
||||
alloc.addOwned(c.ident, c.cidr, c.isContainer)
|
||||
}
|
||||
}
|
||||
|
||||
if !alloc.ring.Contains(c.cidr.Addr) {
|
||||
// Address not within our universe; assume user knows what they are doing
|
||||
alloc.infof("Address %s claimed by %s - not in our range", c.cidr, c.ident)
|
||||
alloc.addOwned(c.ident, c.cidr, c.isContainer)
|
||||
previousOwner := alloc.findOwner(c.cidr.Addr)
|
||||
switch {
|
||||
case previousOwner == "":
|
||||
addOwned()
|
||||
case previousOwner == c.ident: // already owned by this ID
|
||||
case c.ident == api.NoContainerID: // already owned by anonymous container
|
||||
// do nothing (no automatic fall-through in Go)
|
||||
case !alloc.dead[previousOwner].IsZero(): // already owned by dead container
|
||||
alloc.removeOwned(previousOwner, c.cidr.Addr)
|
||||
addOwned()
|
||||
default:
|
||||
c.sendResult(fmt.Errorf("address %s already in use by %s", c.cidr, previousOwner))
|
||||
}
|
||||
c.sendResult(nil)
|
||||
return true
|
||||
}
|
||||
@@ -71,8 +95,10 @@ func (c *claim) Try(alloc *Allocator) bool {
|
||||
if c.noErrorOnUnknown {
|
||||
alloc.infof("Claim %s for %s: %s; will try later.", c.cidr, c.ident, err)
|
||||
c.sendResult(nil)
|
||||
} else { // just tell the user they can't do this.
|
||||
c.deniedBy(alloc, owner)
|
||||
} else if c.tryCount > maxTryCount {
|
||||
// give up, tell the user they can't do this
|
||||
c.deniedBy(alloc, owner, err)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
@@ -85,11 +111,7 @@ func (c *claim) Try(alloc *Allocator) bool {
|
||||
// Unused address, we try to claim it:
|
||||
if err := alloc.space.Claim(c.cidr.Addr); err == nil {
|
||||
alloc.debugln("Claimed", c.cidr, "for", c.ident)
|
||||
if c.ident == api.NoContainerID {
|
||||
alloc.addOwned(c.cidr.Addr.String(), c.cidr, c.isContainer)
|
||||
} else {
|
||||
alloc.addOwned(c.ident, c.cidr, c.isContainer)
|
||||
}
|
||||
addOwned()
|
||||
c.sendResult(nil)
|
||||
} else {
|
||||
c.sendResult(err)
|
||||
@@ -113,12 +135,16 @@ func (c *claim) Try(alloc *Allocator) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *claim) deniedBy(alloc *Allocator, owner mesh.PeerName) {
|
||||
func (c *claim) deniedBy(alloc *Allocator, owner mesh.PeerName, err error) {
|
||||
name, found := alloc.nicknames[owner]
|
||||
if found {
|
||||
name = " (" + name + ")"
|
||||
}
|
||||
c.sendResult(fmt.Errorf("address %s is owned by other peer %s%s", c.cidr.String(), owner, name))
|
||||
reason := ""
|
||||
if err != nil {
|
||||
reason = fmt.Sprintf(" - %s", err)
|
||||
}
|
||||
c.sendResult(fmt.Errorf("address %s is owned by other peer %s%s%s", c.cidr.String(), owner, name, reason))
|
||||
}
|
||||
|
||||
func (c *claim) Cancel() {
|
||||
|
||||
+39
-12
@@ -1,6 +1,7 @@
|
||||
package ipam
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
@@ -87,7 +88,7 @@ func (alloc *Allocator) handleHTTPClaim(dockerCli *docker.Client, w http.Respons
|
||||
}
|
||||
|
||||
// HandleHTTP wires up ipams HTTP endpoints to the provided mux.
|
||||
func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CIDR, tracker string, dockerCli *docker.Client) {
|
||||
func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CIDR, dockerCli *docker.Client) {
|
||||
router.Methods("GET").Path("/ipinfo/defaultsubnet").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintf(w, "%s", defaultSubnet)
|
||||
})
|
||||
@@ -127,6 +128,39 @@ func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CID
|
||||
writeAddresses(w, addrs)
|
||||
})
|
||||
|
||||
router.Methods("GET").Path("/ip").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
type mapping struct {
|
||||
ContainerID string `json:"containerid"`
|
||||
Addrs []string `json:"addrs"`
|
||||
}
|
||||
|
||||
type mappings struct {
|
||||
Owned []mapping `json:"owned"`
|
||||
}
|
||||
|
||||
resultChan := make(chan mappings)
|
||||
alloc.actionChan <- func() {
|
||||
ms := mappings{}
|
||||
for containerid, d := range alloc.owned {
|
||||
m := mapping{
|
||||
ContainerID: containerid,
|
||||
Addrs: []string{},
|
||||
}
|
||||
for _, addr := range d.Cidrs {
|
||||
m.Addrs = append(m.Addrs, addr.String())
|
||||
}
|
||||
ms.Owned = append(ms.Owned, m)
|
||||
}
|
||||
resultChan <- ms
|
||||
}
|
||||
ms := <-resultChan
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(ms); err != nil {
|
||||
common.Log.Warningln("[allocator]:", err.Error())
|
||||
}
|
||||
})
|
||||
|
||||
router.Methods("POST").Path("/ip/{id}/{ip}/{prefixlen}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
if subnet, ok := parseCIDR(w, vars["ip"]+"/"+vars["prefixlen"], true); ok {
|
||||
@@ -164,18 +198,11 @@ func (alloc *Allocator) HandleHTTP(router *mux.Router, defaultSubnet address.CID
|
||||
w.WriteHeader(204)
|
||||
})
|
||||
|
||||
router.Methods("DELETE").Path("/peer").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
alloc.Shutdown()
|
||||
w.WriteHeader(204)
|
||||
})
|
||||
|
||||
router.Methods("DELETE").Path("/peer/{id}").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ident := mux.Vars(r)["id"]
|
||||
transferred := alloc.AdminTakeoverRanges(ident)
|
||||
fmt.Fprintf(w, "%d IPs taken over from %s\n", transferred, ident)
|
||||
})
|
||||
|
||||
router.Methods("GET").Path("/ipinfo/tracker").HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tracker := ""
|
||||
if alloc.tracker != nil {
|
||||
tracker = alloc.tracker.String()
|
||||
}
|
||||
fmt.Fprintf(w, tracker)
|
||||
})
|
||||
}
|
||||
+2
-2
@@ -43,7 +43,7 @@ func listenHTTP(alloc *Allocator, subnet address.CIDR) int {
|
||||
router.HandleFunc("/status", func(w http.ResponseWriter, r *http.Request) {
|
||||
io.WriteString(w, fmt.Sprintln(alloc))
|
||||
})
|
||||
alloc.HandleHTTP(router, subnet, "", nil)
|
||||
alloc.HandleHTTP(router, subnet, nil)
|
||||
|
||||
httpListener, err := net.Listen("tcp", ":0")
|
||||
if err != nil {
|
||||
@@ -132,7 +132,7 @@ func TestBadHttp(t *testing.T) {
|
||||
// Verb that's not handled
|
||||
resp, err := doHTTP("HEAD", fmt.Sprintf("http://localhost:%d/ip/%s/%s", port, containerID, testAddr1))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, http.StatusNotFound, resp.StatusCode, "http response")
|
||||
require.Equal(t, http.StatusMethodNotAllowed, resp.StatusCode, "http response")
|
||||
// Mis-spelled URL
|
||||
resp, err = doHTTP("POST", fmt.Sprintf("http://localhost:%d/xip/%s/", port, containerID))
|
||||
require.NoError(t, err)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package ring
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
"github.com/weaveworks/mesh"
|
||||
@@ -25,6 +26,7 @@ func (e *entry) Equal(e2 *entry) bool {
|
||||
func (e *entry) update(peername mesh.PeerName, free address.Count) {
|
||||
e.Peer = peername
|
||||
e.Version++
|
||||
common.Log.Debugln(fmt.Sprintf("Updating ring token=%s current peer=%s updated peer=%s new version=%s", e.Token, e.Peer, peername, fmt.Sprint(e.Version)))
|
||||
e.Free = free
|
||||
}
|
||||
|
||||
|
||||
+65
-18
@@ -213,7 +213,7 @@ func (r *Ring) GrantRangeToHost(start, end address.Address, peer mesh.PeerName)
|
||||
|
||||
// Merge the given ring into this ring and indicate whether this ring
|
||||
// got updated as a result.
|
||||
func (r *Ring) Merge(gossip Ring) (bool, error) {
|
||||
func (r *Ring) Merge(gossip Ring, hasAllocations func(r []address.Range) bool) (bool, error) {
|
||||
r.assertInvariants()
|
||||
defer r.trackUpdates()()
|
||||
|
||||
@@ -238,12 +238,22 @@ func (r *Ring) Merge(gossip Ring) (bool, error) {
|
||||
return false, ErrDifferentRange
|
||||
}
|
||||
|
||||
result, updated, err := r.Entries.merge(gossip.Entries, r.Peer)
|
||||
result, updated, err := r.Entries.merge(gossip.Entries, r.Peer, r, hasAllocations)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
// reset the free space for entries if there is invalid free space
|
||||
// due to accepting an unexpected update from the peers
|
||||
for i := 0; i < len(result); i++ {
|
||||
distance := r.distance(result.entry(i).Token, result.entry(i+1).Token)
|
||||
if result.entry(i).Peer == r.Peer && result.entry(i).Free > distance {
|
||||
// case that can arise when a range that we own that got split and had no allocations
|
||||
result.entry(i).Free = distance
|
||||
}
|
||||
}
|
||||
|
||||
if err := r.checkEntries(result); err != nil {
|
||||
return false, fmt.Errorf("Merge of incoming data causes: %s", err)
|
||||
}
|
||||
@@ -260,7 +270,7 @@ func (r *Ring) Merge(gossip Ring) (bool, error) {
|
||||
// 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) {
|
||||
func (es entries) merge(other entries, ourPeer mesh.PeerName, r *Ring, hasAllocations func(r []address.Range) bool) (result entries, updated bool, err error) {
|
||||
var mine, theirs *entry
|
||||
var previousOwner *mesh.PeerName
|
||||
addToResult := func(e entry) { result = append(result, &e) }
|
||||
@@ -274,6 +284,7 @@ func (es entries) merge(other entries, ourPeer mesh.PeerName) (result entries, u
|
||||
var i, j int
|
||||
for i < len(es) && j < len(other) {
|
||||
mine, theirs = es[i], other[j]
|
||||
common.Log.Debugf("[ring %s]: Merge mine.Token=%s theirs.Token=%s mine.Peer=%s theirs.Peer=%s mine.Version=%v theirs.Version=%v", ourPeer, mine.Token, theirs.Token, mine.Peer, theirs.Peer, mine.Version, theirs.Version)
|
||||
switch {
|
||||
case mine.Token < theirs.Token:
|
||||
addToResult(*mine)
|
||||
@@ -282,8 +293,11 @@ func (es entries) merge(other entries, ourPeer mesh.PeerName) (result entries, u
|
||||
case mine.Token > theirs.Token:
|
||||
// insert, checking that a range owned by us hasn't been split
|
||||
if previousOwner != nil && *previousOwner == ourPeer && theirs.Peer != ourPeer {
|
||||
err = errEntryInMyRange(theirs)
|
||||
return
|
||||
// check we have no allocations in the range that got split
|
||||
if hasAllocations(r.makeRanges(theirs.Token, mine.Token)) {
|
||||
err = errEntryInMyRange(theirs)
|
||||
return
|
||||
}
|
||||
}
|
||||
addTheirs(*theirs)
|
||||
j++
|
||||
@@ -292,17 +306,44 @@ func (es entries) merge(other entries, ourPeer mesh.PeerName) (result entries, u
|
||||
switch {
|
||||
case mine.Version >= theirs.Version:
|
||||
if mine.Version == theirs.Version && !mine.Equal(theirs) {
|
||||
err = errInconsistentEntry(mine, theirs)
|
||||
return
|
||||
if mine.Peer == ourPeer {
|
||||
// if we own the entry and has allocations
|
||||
if hasAllocations(r.makeRanges(theirs.Token, es.entry(i+1).Token)) {
|
||||
err = errInconsistentEntry(mine, theirs)
|
||||
return
|
||||
}
|
||||
}
|
||||
// tie-break here, pick the entry with the highest free count
|
||||
if mine.Free >= theirs.Free {
|
||||
addToResult(*mine)
|
||||
previousOwner = &mine.Peer
|
||||
} else {
|
||||
addTheirs(*theirs)
|
||||
}
|
||||
} else {
|
||||
addToResult(*mine)
|
||||
previousOwner = &mine.Peer
|
||||
}
|
||||
addToResult(*mine)
|
||||
previousOwner = &mine.Peer
|
||||
case mine.Version < theirs.Version:
|
||||
if mine.Peer == ourPeer { // We shouldn't receive updates to our own tokens
|
||||
err = errNewerVersion(mine, theirs)
|
||||
return
|
||||
if mine.Peer == ourPeer {
|
||||
// We received update to our own tokens accept the received entry
|
||||
// if either it belongs to a different peer and we do not have allocations
|
||||
// in the range effectively given away, or it belongs to our own peer, in
|
||||
// which case we should set our version to the one received plus one,
|
||||
// effectively imposing our existing entry.
|
||||
if theirs.Peer != ourPeer && !hasAllocations(r.makeRanges(mine.Token, es.entry(i+1).Token)) {
|
||||
common.Log.Infof("ring: repair inconsistent data by accepting token %v from %v - no allocations up to %v", theirs.Token, theirs.Peer, es.entry(i+1).Token)
|
||||
addTheirs(*theirs)
|
||||
} else if theirs.Peer == ourPeer {
|
||||
mine.Version = theirs.Version + 1
|
||||
addToResult(*mine)
|
||||
} else {
|
||||
err = errNewerVersion(mine, theirs)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
addTheirs(*theirs)
|
||||
}
|
||||
addTheirs(*theirs)
|
||||
}
|
||||
i++
|
||||
j++
|
||||
@@ -334,6 +375,11 @@ func (r *Ring) Empty() bool {
|
||||
return len(r.Entries) == 0
|
||||
}
|
||||
|
||||
// convenience function to normalise a single range that may span zero
|
||||
func (r *Ring) makeRanges(start, end address.Address) []address.Range {
|
||||
return r.splitRangesOverZero([]address.Range{{Start: start, End: end}})
|
||||
}
|
||||
|
||||
// Given a slice of ranges which are all in the right order except
|
||||
// possibly the last one spans zero, fix that up and return the slice
|
||||
func (r *Ring) splitRangesOverZero(ranges []address.Range) []address.Range {
|
||||
@@ -384,10 +430,7 @@ type RangeInfo struct {
|
||||
|
||||
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 {
|
||||
for _, r := range r.makeRanges(entry.Token, r.Entries.entry(i+1).Token) {
|
||||
result = append(result, RangeInfo{entry.Peer, r, entry.Version})
|
||||
}
|
||||
}
|
||||
@@ -485,6 +528,7 @@ func (r *Ring) ReportFree(freespace map[address.Address]address.Count) (updated
|
||||
|
||||
entries[i].Free = free
|
||||
entries[i].Version++
|
||||
common.Log.Debugf("[ring %s]: ReportFree token=%s peer=%s version=%v", r.Peer, entries[i].Token, entries[i].Peer, entries[i].Version)
|
||||
updated = true
|
||||
}
|
||||
return
|
||||
@@ -565,7 +609,10 @@ func (r *Ring) Transfer(from, to mesh.PeerName) []address.Range {
|
||||
for i, entry := range r.Entries {
|
||||
if entry.Peer == from {
|
||||
entry.Peer = to
|
||||
entry.Version++
|
||||
// bump version by large value so that transfer of range takes precedence (if there is any conflict in merging entry)
|
||||
// over other opertaions on the entry that increments the version
|
||||
entry.Version = entry.Version + 100
|
||||
common.Log.Debugf("[ring %s]: Transfer token=%s from=%s to=%s version=%v", r.Peer, entry.Token, from, to, entry.Version)
|
||||
newRanges = append(newRanges, address.Range{Start: entry.Token, End: r.Entries.entry(i + 1).Token})
|
||||
}
|
||||
}
|
||||
|
||||
+76
-8
@@ -32,7 +32,27 @@ func ParseIP(s string) address.Address {
|
||||
}
|
||||
|
||||
func merge(r1, r2 *Ring) error {
|
||||
_, err := r1.Merge(*r2)
|
||||
distance := func(start, end address.Address) address.Count {
|
||||
if end > start {
|
||||
return address.Count(end - start)
|
||||
}
|
||||
return address.Count((r1.End - start) + (end - r1.Start))
|
||||
}
|
||||
check := func(rs []address.Range) bool {
|
||||
for _, r := range rs {
|
||||
size := distance(r.Start, r.End)
|
||||
entry, found := r1.Entries.get(r.Start)
|
||||
if !found {
|
||||
continue
|
||||
}
|
||||
if entry.Free < size {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
_, err := r1.Merge(*r2, check)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -219,6 +239,61 @@ func TestMergeSimple(t *testing.T) {
|
||||
require.Equal(t, ring2.Entries, ring1.Entries)
|
||||
}
|
||||
|
||||
func TestMergeWithConflicts(t *testing.T) {
|
||||
|
||||
// received update to entry with a token and version identical to an entry we have
|
||||
// but but holding different content, if there are no allocations, resolve the
|
||||
// conflict by picking entry one with high free count
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
ring2 := NewRing(start, end, peer2name)
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 256, Version: 1}}
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer2name, Free: 256, Version: 1}}
|
||||
require.NoError(t, merge(ring1, ring2))
|
||||
|
||||
// received update to entry with a token and version identical to an entry we have
|
||||
// but but holding different content, if there are allocations, then reject the received
|
||||
// entry
|
||||
ring1 = NewRing(start, end, peer1name)
|
||||
ring2 = NewRing(start, end, peer2name)
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 128, Version: 1}}
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer2name, Free: 256, Version: 1}}
|
||||
require.Error(t, merge(ring1, ring2), "Expected error")
|
||||
|
||||
// received an entry with update to one of our own tokens and with new version,
|
||||
// accept the received entry if its still going to belong to us
|
||||
ring1 = NewRing(start, end, peer1name)
|
||||
ring2 = NewRing(start, end, peer2name)
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name}}
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer1name, Version: 1}}
|
||||
require.NoError(t, merge(ring1, ring2))
|
||||
|
||||
// received an entry with update to one of our own tokens and with new version,
|
||||
// but belongs to a different peer, accept the received entry if we do not
|
||||
// have allocations in the range
|
||||
ring1 = NewRing(start, end, peer1name)
|
||||
ring2 = NewRing(start, end, peer2name)
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 256}}
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer2name, Free: 256, Version: 1}}
|
||||
require.NoError(t, merge(ring1, ring2))
|
||||
|
||||
// received an entry with update to one of our own tokens and with new version,
|
||||
// reject received entry if we have allocations in the range
|
||||
ring1 = NewRing(start, end, peer1name)
|
||||
ring2 = NewRing(start, end, peer2name)
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 128}}
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer2name, Version: 1}}
|
||||
require.Error(t, merge(ring1, ring2), "Expected error")
|
||||
|
||||
// we receive an entry that splits one of our ranges, giving some of it
|
||||
// away to another peer. accept the entry provided that we have no allocations
|
||||
// in the range that got given away.
|
||||
ring1 = NewRing(start, end, peer1name)
|
||||
ring2 = NewRing(start, end, peer2name)
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name, Free: 256, Version: 1}}
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer1name, Free: 128, Version: 2}, {Token: middle, Peer: peer2name, Free: 128}}
|
||||
require.NoError(t, merge(ring1, ring2))
|
||||
}
|
||||
|
||||
func TestMergeErrors(t *testing.T) {
|
||||
// Cannot Merge in an invalid ring
|
||||
ring1 := NewRing(start, end, peer1name)
|
||||
@@ -231,13 +306,6 @@ func TestMergeErrors(t *testing.T) {
|
||||
ring2.Entries = []*entry{}
|
||||
require.True(t, merge(ring1, ring2) == ErrDifferentRange, "Expected ErrDifferentRange")
|
||||
|
||||
// Cannot Merge newer version of entry I own
|
||||
ring2 = NewRing(start, end, peer2name)
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name}}
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer1name, Version: 1}}
|
||||
fmt.Println(merge(ring1, ring2))
|
||||
require.Error(t, merge(ring1, ring2), "Expected error")
|
||||
|
||||
// Cannot Merge two entries with same version but different hosts
|
||||
ring1.Entries = []*entry{{Token: start, Peer: peer1name}}
|
||||
ring2.Entries = []*entry{{Token: start, Peer: peer2name}}
|
||||
|
||||
@@ -52,12 +52,12 @@ func TestLowlevel(t *testing.T) {
|
||||
|
||||
s := New()
|
||||
require.Equal(t, address.Count(0), s.NumFreeAddresses())
|
||||
ok, got := s.Allocate(address.NewRange(0, 1000))
|
||||
ok, _ := s.Allocate(address.NewRange(0, 1000))
|
||||
require.False(t, ok, "allocate in empty space should fail")
|
||||
|
||||
s.Add(100, 100)
|
||||
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.Equal(t, address.Count(99), s.NumFreeAddresses())
|
||||
require.NoError(t, s.Claim(150))
|
||||
@@ -68,6 +68,7 @@ func TestLowlevel(t *testing.T) {
|
||||
wt.AssertErrorInterface(t, (*error)(nil), s.Free(100), "double free")
|
||||
|
||||
r, ok := s.Donate(address.NewRange(0, 1000))
|
||||
require.True(t, ok, "donate")
|
||||
require.Equal(t, address.NewRange(0xa0, 0x20), r, "donate")
|
||||
|
||||
// test Donate when addresses are scarce
|
||||
@@ -78,6 +79,7 @@ func TestLowlevel(t *testing.T) {
|
||||
require.NoError(t, s.Claim(0))
|
||||
require.NoError(t, s.Claim(2))
|
||||
r, ok = s.Donate(address.NewRange(0, 1000))
|
||||
require.True(t, ok, "donate")
|
||||
require.Equal(t, address.NewRange(1, 1), r, "donate")
|
||||
r, ok = s.Donate(address.NewRange(0, 1000))
|
||||
require.True(t, !ok, "donate should fail")
|
||||
|
||||
@@ -65,6 +65,9 @@ func (m *mockGossipComms) GossipBroadcast(update mesh.GossipData) {
|
||||
}
|
||||
}
|
||||
|
||||
func (m *mockGossipComms) GossipNeighbourSubset(update mesh.GossipData) {
|
||||
m.GossipBroadcast(update)
|
||||
}
|
||||
func equalByteBuffer(a, b []byte) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
@@ -117,7 +120,7 @@ func CheckAllExpectedMessagesSent(allocs ...*Allocator) {
|
||||
m := alloc.gossip.(*mockGossipComms)
|
||||
m.RLock()
|
||||
if len(m.messages) > 0 {
|
||||
require.FailNow(m, fmt.Sprintf("%s: Gossip message(s) not sent as expected: \n%x", m.name, m.messages))
|
||||
require.FailNow(m, fmt.Sprintf("%s: Gossip message(s) not sent as expected: \n%v", m.name, m.messages))
|
||||
}
|
||||
m.RUnlock()
|
||||
}
|
||||
|
||||
+8
-46
@@ -30,7 +30,6 @@ import (
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
wnet "github.com/weaveworks/weave/net"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
@@ -42,7 +41,7 @@ type AWSVPCTracker struct {
|
||||
}
|
||||
|
||||
// NewAWSVPCTracker creates and initialises AWS VPC based tracker.
|
||||
func NewAWSVPCTracker() (*AWSVPCTracker, error) {
|
||||
func NewAWSVPCTracker(bridgeName string) (*AWSVPCTracker, error) {
|
||||
var (
|
||||
err error
|
||||
session = session.New()
|
||||
@@ -69,9 +68,9 @@ func NewAWSVPCTracker() (*AWSVPCTracker, error) {
|
||||
t.routeTableID = *routeTableID
|
||||
|
||||
// Detect Weave bridge link index
|
||||
link, err := netlink.LinkByName(wnet.WeaveBridgeName)
|
||||
link, err := netlink.LinkByName(bridgeName)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot find \"%s\" interface: %s", wnet.WeaveBridgeName, err)
|
||||
return nil, fmt.Errorf("cannot find \"%s\" interface: %s", bridgeName, err)
|
||||
}
|
||||
t.linkIndex = link.Attrs().Index
|
||||
|
||||
@@ -85,7 +84,7 @@ func NewAWSVPCTracker() (*AWSVPCTracker, error) {
|
||||
func (t *AWSVPCTracker) HandleUpdate(prevRanges, currRanges []address.Range, local bool) error {
|
||||
t.debugf("replacing %q by %q; local(%t)", prevRanges, currRanges, local)
|
||||
|
||||
prev, curr := removeCommon(address.NewCIDRs(merge(prevRanges)), address.NewCIDRs(merge(currRanges)))
|
||||
prev, curr := RemoveCommon(address.NewCIDRs(Merge(prevRanges)), address.NewCIDRs(Merge(currRanges)))
|
||||
|
||||
// It might make sense to do the removal first and then add entries
|
||||
// because of the 50 routes limit. However, in such case a container might
|
||||
@@ -124,6 +123,10 @@ func (t *AWSVPCTracker) HandleUpdate(prevRanges, currRanges []address.Range, loc
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *AWSVPCTracker) String() string {
|
||||
return "awsvpc"
|
||||
}
|
||||
|
||||
func (t *AWSVPCTracker) createVPCRoute(cidr string) (*ec2.CreateRouteOutput, error) {
|
||||
route := &ec2.CreateRouteInput{
|
||||
RouteTableId: &t.routeTableID,
|
||||
@@ -233,47 +236,6 @@ func (t *AWSVPCTracker) infof(fmt string, args ...interface{}) {
|
||||
|
||||
// Helpers
|
||||
|
||||
// merge merges adjacent range entries.
|
||||
// The given slice has to be sorted in increasing order.
|
||||
func merge(r []address.Range) []address.Range {
|
||||
var merged []address.Range
|
||||
|
||||
for i := range r {
|
||||
if prev := len(merged) - 1; prev >= 0 && merged[prev].End == r[i].Start {
|
||||
merged[prev].End = r[i].End
|
||||
} else {
|
||||
merged = append(merged, r[i])
|
||||
}
|
||||
}
|
||||
|
||||
return merged
|
||||
}
|
||||
|
||||
// removeCommon filters out CIDR ranges which are contained in both a and b slices.
|
||||
// Both slices have to be sorted in increasing order.
|
||||
func removeCommon(a, b []address.CIDR) (newA, newB []address.CIDR) {
|
||||
i, j := 0, 0
|
||||
|
||||
for i < len(a) && j < len(b) {
|
||||
switch {
|
||||
case a[i].Start() < b[j].Start() || a[i].End() < b[j].End():
|
||||
newA = append(newA, a[i])
|
||||
i++
|
||||
case a[i].Start() > b[j].Start() || a[i].End() > b[j].End():
|
||||
newB = append(newB, b[j])
|
||||
j++
|
||||
default:
|
||||
i++
|
||||
j++
|
||||
}
|
||||
|
||||
}
|
||||
newA = append(newA, a[i:]...)
|
||||
newB = append(newB, b[j:]...)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func parseCIDR(cidr string) (*net.IPNet, error) {
|
||||
ip, ipnet, err := net.ParseCIDR(cidr)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package tracker
|
||||
|
||||
import (
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
)
|
||||
|
||||
// Merge merges adjacent range entries.
|
||||
// The given slice has to be sorted in increasing order.
|
||||
func Merge(r []address.Range) []address.Range {
|
||||
var merged []address.Range
|
||||
|
||||
for i := range r {
|
||||
if prev := len(merged) - 1; prev >= 0 && merged[prev].End == r[i].Start {
|
||||
merged[prev].End = r[i].End
|
||||
} else {
|
||||
merged = append(merged, r[i])
|
||||
}
|
||||
}
|
||||
|
||||
return merged
|
||||
}
|
||||
|
||||
// RemoveCommon filters out CIDR ranges which are contained in both a and b slices.
|
||||
// Both slices have to be sorted in increasing order.
|
||||
func RemoveCommon(a, b []address.CIDR) (newA, newB []address.CIDR) {
|
||||
i, j := 0, 0
|
||||
|
||||
for i < len(a) && j < len(b) {
|
||||
switch {
|
||||
case a[i].Start() < b[j].Start() || a[i].End() < b[j].End():
|
||||
newA = append(newA, a[i])
|
||||
i++
|
||||
case a[i].Start() > b[j].Start() || a[i].End() > b[j].End():
|
||||
newB = append(newB, b[j])
|
||||
j++
|
||||
default:
|
||||
i++
|
||||
j++
|
||||
}
|
||||
|
||||
}
|
||||
newA = append(newA, a[i:]...)
|
||||
newB = append(newB, b[j:]...)
|
||||
|
||||
return
|
||||
}
|
||||
@@ -19,7 +19,7 @@ var (
|
||||
func TestRemoveCommon(t *testing.T) {
|
||||
a := []address.CIDR{r0to127, r1dot0to255}
|
||||
b := []address.CIDR{r1dot0to255, r2dot0to255}
|
||||
newA, newB := removeCommon(a, b)
|
||||
newA, newB := RemoveCommon(a, b)
|
||||
require.Equal(t, []address.CIDR{r0to127}, newA)
|
||||
require.Equal(t, []address.CIDR{r2dot0to255}, newB)
|
||||
}
|
||||
@@ -30,7 +30,7 @@ func TestMerge(t *testing.T) {
|
||||
r128to255.Range(),
|
||||
r2dot0to255.Range(),
|
||||
}
|
||||
require.Equal(t, []address.Range{r0to255.Range(), r2dot0to255.Range()}, merge(ranges))
|
||||
require.Equal(t, []address.Range{r0to255.Range(), r2dot0to255.Range()}, Merge(ranges))
|
||||
}
|
||||
|
||||
// Helper
|
||||
@@ -17,4 +17,7 @@ type LocalRangeTracker interface {
|
||||
// The local parameter indicates whether the ranges belong to the peer
|
||||
// by which the method is called.
|
||||
HandleUpdate(prevRanges, currRanges []address.Range, local bool) error
|
||||
|
||||
// String returns the tracker name
|
||||
String() string
|
||||
}
|
||||
+3
-8
@@ -27,7 +27,7 @@ func (n *Nameserver) HandleHTTP(router *mux.Router, dockerCli *docker.Client) {
|
||||
vars = mux.Vars(r)
|
||||
container = vars["container"]
|
||||
ipStr = vars["ip"]
|
||||
hostname = dns.Fqdn(r.FormValue("fqdn"))
|
||||
fqdn = r.FormValue("fqdn")
|
||||
ip, err = address.ParseIP(ipStr)
|
||||
)
|
||||
if err != nil {
|
||||
@@ -35,16 +35,11 @@ func (n *Nameserver) HandleHTTP(router *mux.Router, dockerCli *docker.Client) {
|
||||
return
|
||||
}
|
||||
|
||||
if !dns.IsSubDomain(n.domain, hostname) {
|
||||
n.infof("Ignoring registration %s %s %s (not a subdomain of %s)", hostname, ipStr, container, n.domain)
|
||||
return
|
||||
}
|
||||
|
||||
n.AddEntry(hostname, container, n.ourName, ip)
|
||||
n.AddEntryFQDN(fqdn, 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.Delete(hostname, container, ipStr, ip)
|
||||
n.Delete(dns.Fqdn(fqdn), container, ipStr, ip)
|
||||
}
|
||||
|
||||
w.WriteHeader(204)
|
||||
|
||||
@@ -91,6 +91,15 @@ func (n *Nameserver) AddEntry(hostname, containerid string, origin mesh.PeerName
|
||||
n.broadcastEntries(entry)
|
||||
}
|
||||
|
||||
func (n *Nameserver) AddEntryFQDN(fqdn, containerid string, origin mesh.PeerName, addr address.Address) {
|
||||
hostname := dns.Fqdn(fqdn)
|
||||
if !dns.IsSubDomain(n.domain, hostname) {
|
||||
n.infof("Ignoring registration %s %s %s (not a subdomain of %s)", hostname, addr.String(), containerid, n.domain)
|
||||
return
|
||||
}
|
||||
n.AddEntry(hostname, containerid, origin, addr)
|
||||
}
|
||||
|
||||
func (n *Nameserver) Lookup(hostname string) []address.Address {
|
||||
n.RLock()
|
||||
defer n.RUnlock()
|
||||
|
||||
@@ -160,6 +160,11 @@ func (cidr CIDR) String() string {
|
||||
return fmt.Sprintf("%s/%d", cidr.Addr.String(), cidr.PrefixLen)
|
||||
}
|
||||
|
||||
func (cidr CIDR) IPNet() (r *net.IPNet) {
|
||||
mask := net.CIDRMask(cidr.PrefixLen, 8*net.IPv4len)
|
||||
return &net.IPNet{IP: cidr.Addr.IP4(), Mask: mask}
|
||||
}
|
||||
|
||||
// FromIP4 converts an ipv4 address to our integer address type
|
||||
func FromIP4(ip4 net.IP) (r Address) {
|
||||
for _, b := range ip4.To4() {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package address
|
||||
|
||||
import (
|
||||
"net"
|
||||
"testing"
|
||||
"testing/quick"
|
||||
|
||||
@@ -67,3 +68,15 @@ func TestCIDRStartAndEnd(t *testing.T) {
|
||||
require.Equal(t, ip("10.0.0.0"), cidr.Start(), "")
|
||||
require.Equal(t, ip("10.0.1.0"), cidr.End(), "")
|
||||
}
|
||||
|
||||
func TestConversions(t *testing.T) {
|
||||
addrStr := "10.2.3.0"
|
||||
cidrStr := addrStr + "/24"
|
||||
addr := ip(addrStr)
|
||||
require.Equal(t, addr.String(), addrStr)
|
||||
require.Equal(t, addr.IP4(), net.ParseIP(addrStr).To4())
|
||||
cidr, _ := ParseCIDR(cidrStr)
|
||||
require.Equal(t, cidr.String(), cidrStr)
|
||||
_, ipnet, _ := net.ParseCIDR(cidrStr)
|
||||
require.Equal(t, cidr.IPNet(), ipnet)
|
||||
}
|
||||
+12
-12
@@ -1,27 +1,27 @@
|
||||
package net
|
||||
|
||||
import "fmt"
|
||||
import "io"
|
||||
import "os"
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
// 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 {
|
||||
func configureARPCache(procPath, name string) error {
|
||||
if err := sysctl(procPath, fmt.Sprintf("net/ipv4/neigh/%s/base_reachable_time", name), "5"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := sysctl(fmt.Sprintf("net/ipv4/neigh/%s/delay_first_probe_time", name), "2"); err != nil {
|
||||
if err := sysctl(procPath, 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
|
||||
return sysctl(procPath, fmt.Sprintf("net/ipv4/neigh/%s/ucast_solicit", name), "1")
|
||||
}
|
||||
|
||||
func sysctl(variable, value string) error {
|
||||
f, err := os.OpenFile(fmt.Sprintf("/proc/sys/%s", variable), os.O_WRONLY, 0)
|
||||
func sysctl(procPath, variable, value string) error {
|
||||
f, err := os.OpenFile(filepath.Join(procPath, "/sys/", variable), os.O_WRONLY, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
+563
-16
@@ -2,43 +2,144 @@ package net
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"path/filepath"
|
||||
"syscall"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/vishvananda/netlink"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/common/odp"
|
||||
"github.com/weaveworks/weave/ipam/tracker"
|
||||
"github.com/weaveworks/weave/net/address"
|
||||
"github.com/weaveworks/weave/net/ipset"
|
||||
"github.com/weaveworks/weave/npc"
|
||||
)
|
||||
|
||||
type BridgeType int
|
||||
/* This code implements three possible configurations to connect
|
||||
containers to the Weave Net overlay:
|
||||
|
||||
1. Bridge
|
||||
+-------+
|
||||
(container-veth)-+ weave +-(vethwe-bridge)--(vethwe-pcap)
|
||||
+-------+
|
||||
|
||||
"weave" is a Linux bridge. "vethwe-pcap" (end of veth pair) is used
|
||||
to capture and inject packets, by router/pcap.go.
|
||||
|
||||
2. BridgedFastdp
|
||||
|
||||
+-------+ /----------\
|
||||
(container-veth)-+ weave +-(vethwe-bridge)--(vethwe-datapath)-+ datapath +
|
||||
+-------+ \----------/
|
||||
|
||||
"weave" is a Linux bridge and "datapath" is an Open vSwitch datapath;
|
||||
they are connected via a veth pair. Packet capture and injection use
|
||||
the "datapath" device, via "router/fastdp.go:fastDatapathBridge"
|
||||
|
||||
3. Fastdp
|
||||
|
||||
/-------\
|
||||
(container-veth)-+ weave +
|
||||
\-------/
|
||||
|
||||
"weave" is an Open vSwitch datapath, and capture/injection are as in
|
||||
BridgedFastdp. Not used by default due to missing conntrack support in
|
||||
datapath of old kernel versions (https://github.com/weaveworks/weave/issues/1577).
|
||||
*/
|
||||
|
||||
const (
|
||||
WeaveBridgeName = "weave"
|
||||
DatapathName = "datapath"
|
||||
|
||||
None BridgeType = iota
|
||||
Bridge
|
||||
Fastdp
|
||||
BridgedFastdp
|
||||
Inconsistent
|
||||
WeaveBridgeName = "weave"
|
||||
DatapathName = "datapath"
|
||||
DatapathIfName = "vethwe-datapath"
|
||||
BridgeIfName = "vethwe-bridge"
|
||||
PcapIfName = "vethwe-pcap"
|
||||
NoMasqLocalIpset = ipset.Name("weaver-no-masq-local")
|
||||
)
|
||||
|
||||
func DetectBridgeType(weaveBridgeName, datapathName string) BridgeType {
|
||||
type Bridge interface {
|
||||
init(procPath string, config *BridgeConfig) error // create and initialise bridge device(s)
|
||||
attach(veth *netlink.Veth) error // attach veth to bridge
|
||||
IsFastdp() bool // does this bridge use fastdp?
|
||||
String() string // human-readable type string
|
||||
}
|
||||
|
||||
// Used to indicate a fallback to the Bridge type
|
||||
var errBridgeNotSupported = errors.New("bridge not supported")
|
||||
|
||||
type bridgeImpl struct{ bridge netlink.Link }
|
||||
type fastdpImpl struct{ datapathName string }
|
||||
type bridgedFastdpImpl struct {
|
||||
bridgeImpl
|
||||
fastdpImpl
|
||||
}
|
||||
|
||||
// Returns a string that is consistent with the weave script
|
||||
func (bridgeImpl) String() string { return "bridge" }
|
||||
func (fastdpImpl) String() string { return "fastdp" }
|
||||
func (bridgedFastdpImpl) String() string { return "bridged_fastdp" }
|
||||
|
||||
// Used to decide whether to manage ODP tunnels
|
||||
func (bridgeImpl) IsFastdp() bool { return false }
|
||||
func (fastdpImpl) IsFastdp() bool { return true }
|
||||
func (bridgedFastdpImpl) IsFastdp() bool { return true }
|
||||
|
||||
func ExistingBridgeType(weaveBridgeName, datapathName string) (Bridge, error) {
|
||||
bridge, _ := netlink.LinkByName(weaveBridgeName)
|
||||
datapath, _ := netlink.LinkByName(datapathName)
|
||||
|
||||
switch {
|
||||
case bridge == nil && datapath == nil:
|
||||
return None
|
||||
return nil, nil
|
||||
case isBridge(bridge) && datapath == nil:
|
||||
return Bridge
|
||||
return bridgeImpl{bridge: bridge}, nil
|
||||
case isDatapath(bridge) && datapath == nil:
|
||||
return Fastdp
|
||||
return fastdpImpl{datapathName: datapathName}, nil
|
||||
case isDatapath(datapath) && isBridge(bridge):
|
||||
return BridgedFastdp
|
||||
return bridgedFastdpImpl{bridgeImpl{bridge: bridge}, fastdpImpl{datapathName: datapathName}}, nil
|
||||
default:
|
||||
return Inconsistent
|
||||
return nil, errors.New("Inconsistent bridge state detected. Please do 'weave reset' and try again")
|
||||
}
|
||||
}
|
||||
|
||||
func EnforceAddrAssignType(bridgeName string) (setAddr bool, err error) {
|
||||
sysctlFilename := filepath.Join("/sys/class/net/", bridgeName, "/addr_assign_type")
|
||||
addrAssignType, err := ioutil.ReadFile(sysctlFilename)
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "reading %q", sysctlFilename)
|
||||
}
|
||||
|
||||
// From include/uapi/linux/netdevice.h
|
||||
// #define NET_ADDR_PERM 0 /* address is permanent (default) */
|
||||
// #define NET_ADDR_RANDOM 1 /* address is generated randomly */
|
||||
// #define NET_ADDR_STOLEN 2 /* address is stolen from other device */
|
||||
// #define NET_ADDR_SET 3 /* address is set using dev_set_mac_address() */
|
||||
// Note the file typically has a newline at the end, so we just look at the first char
|
||||
if addrAssignType[0] != '3' {
|
||||
link, err := netlink.LinkByName(bridgeName)
|
||||
if err != nil {
|
||||
return false, errors.Wrapf(err, "EnforceAddrAssignType finding bridge %s", bridgeName)
|
||||
}
|
||||
|
||||
mac, err := RandomMAC()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "creating random MAC")
|
||||
}
|
||||
|
||||
if err := netlink.LinkSetHardwareAddr(link, mac); err != nil {
|
||||
return false, errors.Wrapf(err, "setting bridge %s address to %v", bridgeName, mac)
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func isBridge(link netlink.Link) bool {
|
||||
_, isBridge := link.(*netlink.Bridge)
|
||||
return isBridge
|
||||
@@ -108,3 +209,449 @@ func FindBridgeIP(bridgeName string, subnet *net.IPNet) (net.IP, error) {
|
||||
// No subnet, or none in the required subnet; just return the first one
|
||||
return netdev.CIDRs[0].IP, nil
|
||||
}
|
||||
|
||||
type BridgeConfig struct {
|
||||
DockerBridgeName string
|
||||
WeaveBridgeName string
|
||||
DatapathName string
|
||||
NoFastdp bool
|
||||
NoBridgedFastdp bool
|
||||
AWSVPC bool
|
||||
NPC bool
|
||||
MTU int
|
||||
Mac string
|
||||
Port int
|
||||
ControlPort string
|
||||
NoMasqLocal bool
|
||||
}
|
||||
|
||||
func (config *BridgeConfig) configuredBridgeType() Bridge {
|
||||
switch {
|
||||
case config.NoFastdp:
|
||||
return bridgeImpl{}
|
||||
case config.NoBridgedFastdp:
|
||||
return fastdpImpl{datapathName: config.WeaveBridgeName}
|
||||
default:
|
||||
return bridgedFastdpImpl{fastdpImpl: fastdpImpl{datapathName: config.DatapathName}}
|
||||
}
|
||||
}
|
||||
|
||||
func EnsureBridge(procPath string, config *BridgeConfig, log *logrus.Logger, ips ipset.Interface) (Bridge, error) {
|
||||
existingBridgeType, err := ExistingBridgeType(config.WeaveBridgeName, config.DatapathName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
bridgeType := config.configuredBridgeType()
|
||||
|
||||
if existingBridgeType != nil && bridgeType.String() != existingBridgeType.String() {
|
||||
return nil,
|
||||
fmt.Errorf("Existing bridge type %q is different than requested %q. Please do 'weave reset' and try again",
|
||||
existingBridgeType, bridgeType)
|
||||
}
|
||||
|
||||
for {
|
||||
if err := bridgeType.init(procPath, config); err != nil {
|
||||
if errors.Cause(err) == errBridgeNotSupported {
|
||||
log.Warnf("Skipping bridge creation of %q due to: %s", bridgeType, err)
|
||||
bridgeType = bridgeImpl{}
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if err := configureIPTables(config, ips); err != nil {
|
||||
return bridgeType, errors.Wrap(err, "configuring iptables")
|
||||
}
|
||||
|
||||
if config.AWSVPC {
|
||||
// Set proxy_arp on the bridge, so that it could accept packets destined
|
||||
// to containers within the same subnet but running on remote hosts.
|
||||
// Without it, exact routes on each container are required.
|
||||
if err := sysctl(procPath, "net/ipv4/conf/"+config.WeaveBridgeName+"/proxy_arp", "1"); err != nil {
|
||||
return bridgeType, errors.Wrap(err, "setting proxy_arp")
|
||||
}
|
||||
// Avoid delaying the first ARP request. Also, setting it to 0 avoids
|
||||
// placing the request into a bounded queue as it can be seen:
|
||||
// https://git.kernel.org/cgit/linux/kernel/git/stable/linux-stable.git/tree/net/ipv4/arp.c?id=refs/tags/v4.6.1#n819
|
||||
if err := sysctl(procPath, "net/ipv4/neigh/"+config.WeaveBridgeName+"/proxy_delay", "0"); err != nil {
|
||||
return bridgeType, errors.Wrap(err, "setting proxy_arp")
|
||||
}
|
||||
}
|
||||
// No ipv6 router advertisments please
|
||||
if err := sysctl(procPath, "net/ipv6/conf/"+config.WeaveBridgeName+"/accept_ra", "0"); err != nil {
|
||||
return bridgeType, errors.Wrap(err, "setting accept_ra to 0")
|
||||
}
|
||||
|
||||
if err := linkSetUpByName(config.WeaveBridgeName); err != nil {
|
||||
return bridgeType, err
|
||||
}
|
||||
|
||||
if err := monitorInterface(config.WeaveBridgeName, log); err != nil {
|
||||
return bridgeType, err
|
||||
}
|
||||
|
||||
if err := configureARPCache(procPath, config.WeaveBridgeName); err != nil {
|
||||
return bridgeType, errors.Wrapf(err, "configuring ARP cache on bridge %q", config.WeaveBridgeName)
|
||||
}
|
||||
|
||||
// NB: No concurrent call to Expose is possible, as EnsureBridge is called
|
||||
// before any service has been started.
|
||||
if err := reexpose(config, log); err != nil {
|
||||
return bridgeType, err
|
||||
}
|
||||
|
||||
return bridgeType, nil
|
||||
}
|
||||
|
||||
func (b bridgeImpl) initPrep(config *BridgeConfig) error {
|
||||
mac, err := net.ParseMAC(config.Mac)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "parsing bridge MAC %q", config.Mac)
|
||||
}
|
||||
|
||||
linkAttrs := netlink.NewLinkAttrs()
|
||||
linkAttrs.Name = config.WeaveBridgeName
|
||||
// NB: Do not set MAC addr when creating the bridge, set it manually
|
||||
// afterwards instead. Otherwise, on an older than 3.14 kernel FDB
|
||||
// entry won't be created which results in containers not being able to
|
||||
// reach the bridge w/o promiscuous mode.
|
||||
if config.MTU == 0 {
|
||||
config.MTU = 65535
|
||||
}
|
||||
b.bridge = &netlink.Bridge{LinkAttrs: linkAttrs}
|
||||
if err := LinkAddIfNotExist(b.bridge); err != nil {
|
||||
return errors.Wrapf(err, "creating bridge %q", config.WeaveBridgeName)
|
||||
}
|
||||
if err := netlink.LinkSetHardwareAddr(b.bridge, mac); err != nil {
|
||||
return errors.Wrapf(err, "setting bridge %q mac %v", config.WeaveBridgeName, mac)
|
||||
}
|
||||
// Attempting to set the bridge MTU to a high value directly
|
||||
// fails. Bridges take the lowest MTU of their interfaces. So
|
||||
// instead we create a temporary interface with the desired MTU,
|
||||
// attach that to the bridge, and then remove it again.
|
||||
dummy := &netlink.Dummy{LinkAttrs: netlink.NewLinkAttrs()}
|
||||
dummy.LinkAttrs.Name = "vethwedu"
|
||||
if err = netlink.LinkAdd(dummy); err != nil {
|
||||
return errors.Wrap(err, "creating dummy interface")
|
||||
}
|
||||
if err := netlink.LinkSetMTU(dummy, config.MTU); err != nil {
|
||||
return errors.Wrapf(err, "setting dummy interface mtu to %d", config.MTU)
|
||||
}
|
||||
if err := netlink.LinkSetMasterByIndex(dummy, b.bridge.Attrs().Index); err != nil {
|
||||
return errors.Wrap(err, "setting dummy interface master")
|
||||
}
|
||||
if err := netlink.LinkDel(dummy); err != nil {
|
||||
return errors.Wrap(err, "deleting dummy interface")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b bridgeImpl) init(procPath string, config *BridgeConfig) error {
|
||||
if err := b.initPrep(config); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := CreateAndAttachVeth(procPath, BridgeIfName, PcapIfName, config.WeaveBridgeName, config.MTU, true, false, func(veth netlink.Link) error {
|
||||
return netlink.LinkSetUp(veth)
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "creating pcap veth pair")
|
||||
}
|
||||
if err := EthtoolTXOff(config.WeaveBridgeName); err != nil {
|
||||
return errors.Wrap(err, "setting tx off")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f fastdpImpl) init(procPath string, config *BridgeConfig) error {
|
||||
odpSupported, err := odp.CreateDatapath(f.datapathName)
|
||||
if !odpSupported {
|
||||
msg := ""
|
||||
if err != nil {
|
||||
msg = err.Error()
|
||||
}
|
||||
return errors.Wrap(errBridgeNotSupported, msg)
|
||||
}
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "creating datapath %q", f.datapathName)
|
||||
}
|
||||
datapath, err := netlink.LinkByName(f.datapathName)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "finding datapath %q", f.datapathName)
|
||||
}
|
||||
if config.MTU == 0 {
|
||||
/* GCE has the lowest underlay network MTU we're likely to encounter on
|
||||
a local network, at 1460 bytes. To get the overlay MTU from that we
|
||||
subtract 20 bytes for the outer IPv4 header, 8 bytes for the outer
|
||||
UDP header, 8 bytes for the vxlan header, and 14 bytes for the inner
|
||||
ethernet header. In addition, we subtract 34 bytes for the ESP overhead
|
||||
which is needed for the vxlan encryption. */
|
||||
config.MTU = 1376
|
||||
}
|
||||
if err := netlink.LinkSetMTU(datapath, config.MTU); err != nil {
|
||||
return errors.Wrapf(err, "setting datapath %q mtu %d", f.datapathName, config.MTU)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (bf bridgedFastdpImpl) init(procPath string, config *BridgeConfig) error {
|
||||
if err := bf.fastdpImpl.init(procPath, config); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := bf.bridgeImpl.initPrep(config); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := CreateAndAttachVeth(procPath, BridgeIfName, DatapathIfName, config.WeaveBridgeName, config.MTU, true, false, func(veth netlink.Link) error {
|
||||
if err := netlink.LinkSetUp(veth); err != nil {
|
||||
return errors.Wrapf(err, "setting link up on %q", veth.Attrs().Name)
|
||||
}
|
||||
if err := odp.AddDatapathInterfaceIfNotExist(bf.datapathName, veth.Attrs().Name); err != nil {
|
||||
return errors.Wrapf(err, "adding interface %q to datapath %q", veth.Attrs().Name, bf.datapathName)
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return errors.Wrap(err, "creating bridged fastdp veth pair")
|
||||
}
|
||||
|
||||
return linkSetUpByName(bf.datapathName)
|
||||
}
|
||||
|
||||
func (b bridgeImpl) attach(veth *netlink.Veth) error {
|
||||
return netlink.LinkSetMasterByIndex(veth, b.bridge.Attrs().Index)
|
||||
}
|
||||
|
||||
func (bf bridgedFastdpImpl) attach(veth *netlink.Veth) error {
|
||||
return bf.bridgeImpl.attach(veth)
|
||||
}
|
||||
|
||||
func (f fastdpImpl) attach(veth *netlink.Veth) error {
|
||||
return odp.AddDatapathInterfaceIfNotExist(f.datapathName, veth.Attrs().Name)
|
||||
}
|
||||
|
||||
func configureIPTables(config *BridgeConfig, ips ipset.Interface) error {
|
||||
ipt, err := iptables.New()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "creating iptables object")
|
||||
}
|
||||
|
||||
// The order among weave filter/FORWARD rules is important!
|
||||
fwdRules := make([][]string, 0)
|
||||
|
||||
if config.DockerBridgeName != "" {
|
||||
if config.WeaveBridgeName != config.DockerBridgeName {
|
||||
fwdRules = append(fwdRules, []string{"-i", config.DockerBridgeName, "-o", config.WeaveBridgeName, "-j", "DROP"})
|
||||
}
|
||||
|
||||
dockerBridgeIP, err := FindBridgeIP(config.DockerBridgeName, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// forbid traffic to the Weave port from other containers
|
||||
if err = ipt.AppendUnique("filter", "INPUT", "-i", config.DockerBridgeName, "-p", "tcp", "--dst", dockerBridgeIP.String(), "--dport", fmt.Sprint(config.Port), "-j", "DROP"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = ipt.AppendUnique("filter", "INPUT", "-i", config.DockerBridgeName, "-p", "udp", "--dst", dockerBridgeIP.String(), "--dport", fmt.Sprint(config.Port), "-j", "DROP"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = ipt.AppendUnique("filter", "INPUT", "-i", config.DockerBridgeName, "-p", "udp", "--dst", dockerBridgeIP.String(), "--dport", fmt.Sprint(config.Port+1), "-j", "DROP"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// let DNS traffic to weaveDNS, since otherwise it might get blocked by the likes of UFW
|
||||
if err = ipt.AppendUnique("filter", "INPUT", "-i", config.DockerBridgeName, "-p", "udp", "--dport", "53", "-j", "ACCEPT"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err = ipt.AppendUnique("filter", "INPUT", "-i", config.DockerBridgeName, "-p", "tcp", "--dport", "53", "-j", "ACCEPT"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if config.ControlPort != "" {
|
||||
if err = ipt.AppendUnique("filter", "INPUT", "-p", "tcp", "--dst", "127.0.0.1", "--dport", config.ControlPort,
|
||||
"-m", "addrtype", "!", "--src-type", "LOCAL",
|
||||
"-m", "conntrack", "!", "--ctstate", "RELATED,ESTABLISHED",
|
||||
"-m", "comment", "--comment", "Block non-local access to Weave Net control port",
|
||||
"-j", "DROP"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Remove the rule from Weave Net 2.6.3 which dropped too much.
|
||||
_ = ipt.Delete("filter", "INPUT", "-p", "tcp", "--dst", "127.0.0.1", "-m", "addrtype", "!", "--src-type", "LOCAL", "-m", "conntrack", "!", "--ctstate", "RELATED,ESTABLISHED", "-j", "DROP")
|
||||
|
||||
if config.NPC {
|
||||
// Steer traffic via the NPC.
|
||||
|
||||
if err = ensureChains(ipt, "filter", npc.MainChain, npc.EgressChain); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Steer egress traffic destined to local node.
|
||||
if err = ipt.AppendUnique("filter", "INPUT", "-i", config.WeaveBridgeName, "-j", npc.EgressChain); err != nil {
|
||||
return err
|
||||
}
|
||||
fwdRules = append(fwdRules,
|
||||
[][]string{
|
||||
// Might include ingress traffic which is fine as long as we do not
|
||||
// ACCEPT in WEAVE-NPC-EGRESS chain
|
||||
{"-i", config.WeaveBridgeName,
|
||||
"-m", "comment", "--comment", "NOTE: this must go before '-j KUBE-FORWARD'",
|
||||
"-j", npc.EgressChain},
|
||||
// The following rules are for ingress NPC processing
|
||||
{"-o", config.WeaveBridgeName,
|
||||
"-m", "comment", "--comment", "NOTE: this must go before '-j KUBE-FORWARD'",
|
||||
"-j", npc.MainChain},
|
||||
{"-o", config.WeaveBridgeName, "-m", "state", "--state", "NEW", "-j", "NFLOG", "--nflog-group", "86"},
|
||||
{"-o", config.WeaveBridgeName, "-j", "DROP"},
|
||||
}...)
|
||||
} else {
|
||||
// Work around the situation where there are no rules allowing traffic
|
||||
// across our bridge. E.g. ufw
|
||||
fwdRules = append(fwdRules, []string{"-i", config.WeaveBridgeName, "-o", config.WeaveBridgeName, "-j", "ACCEPT"})
|
||||
}
|
||||
|
||||
if !config.NPC {
|
||||
// Create/Flush a chain for allowing ingress traffic when the bridge is exposed
|
||||
if err := ipt.ClearChain("filter", "WEAVE-EXPOSE"); err != nil {
|
||||
return errors.Wrap(err, "failed to clear/create filter/WEAVE-EXPOSE chain")
|
||||
}
|
||||
|
||||
fwdRules = append(fwdRules, []string{"-o", config.WeaveBridgeName, "-j", "WEAVE-EXPOSE"})
|
||||
}
|
||||
|
||||
// Forward from weave to the rest of the world
|
||||
fwdRules = append(fwdRules, []string{"-i", config.WeaveBridgeName, "!", "-o", config.WeaveBridgeName, "-j", "ACCEPT"})
|
||||
// and allow replies back
|
||||
fwdRules = append(fwdRules, []string{"-o", config.WeaveBridgeName, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"})
|
||||
|
||||
if err := ensureRulesAtTop("filter", "FORWARD", fwdRules, ipt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create a chain for masquerading
|
||||
if err := ipt.ClearChain("nat", "WEAVE"); err != nil {
|
||||
return errors.Wrap(err, "failed to clear/create nat/WEAVE chain")
|
||||
}
|
||||
if err := ipt.AppendUnique("nat", "POSTROUTING", "-j", "WEAVE"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// For the cases where the weave bridge is the default gateway for
|
||||
// containers (e.g. Kubernetes): create the ipset to store CIDRs allocated
|
||||
// by IPAM for local containers. In the case of Kubernetes, external traffic
|
||||
// sent to these CIDRs avoids SNAT'ing so that NodePort with
|
||||
// `"externalTrafficPolicy":"Local"` would receive packets with correct
|
||||
// src IP addr.
|
||||
if config.NoMasqLocal {
|
||||
ips := ipset.New(common.LogLogger(), 0)
|
||||
_ = ips.Destroy(NoMasqLocalIpset)
|
||||
if err := ips.Create(NoMasqLocalIpset, ipset.HashNet); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ipt.Insert("nat", "WEAVE", 1,
|
||||
"-m", "set", "--match-set", string(NoMasqLocalIpset), "dst",
|
||||
"-m", "comment", "--comment", "Prevent SNAT to locally running containers",
|
||||
"-j", "RETURN"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type NoMasqLocalTracker struct {
|
||||
ips ipset.Interface
|
||||
owner types.UID
|
||||
}
|
||||
|
||||
func NewNoMasqLocalTracker(ips ipset.Interface) *NoMasqLocalTracker {
|
||||
return &NoMasqLocalTracker{
|
||||
ips: ips,
|
||||
owner: types.UID(0), // dummy ipset owner
|
||||
}
|
||||
}
|
||||
|
||||
func (t *NoMasqLocalTracker) String() string {
|
||||
return "no-masq-local"
|
||||
}
|
||||
|
||||
func (t *NoMasqLocalTracker) HandleUpdate(prevRanges, currRanges []address.Range, local bool) error {
|
||||
if !local {
|
||||
return nil
|
||||
}
|
||||
|
||||
prev, curr := tracker.RemoveCommon(
|
||||
address.NewCIDRs(tracker.Merge(prevRanges)),
|
||||
address.NewCIDRs(tracker.Merge(currRanges)))
|
||||
|
||||
for _, cidr := range curr {
|
||||
if err := t.ips.AddEntry(t.owner, NoMasqLocalIpset, cidr.String(), ""); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, cidr := range prev {
|
||||
if err := t.ips.DelEntry(t.owner, NoMasqLocalIpset, cidr.String()); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func linkSetUpByName(linkName string) error {
|
||||
link, err := netlink.LinkByName(linkName)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "setting link up on %q", linkName)
|
||||
}
|
||||
return netlink.LinkSetUp(link)
|
||||
}
|
||||
|
||||
func reexpose(config *BridgeConfig, log *logrus.Logger) error {
|
||||
// Get existing IP addrs of the weave bridge.
|
||||
// If the bridge hasn't been exposed, then this functions does nothing.
|
||||
//
|
||||
// Ideally, we should consult IPAM for IP addrs allocated to "weave:expose",
|
||||
// but we don't want to introduce dependency on IPAM, as weave should be able
|
||||
// to run w/o IPAM.
|
||||
link, err := netlink.LinkByName(config.WeaveBridgeName)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "cannot find bridge %q", config.WeaveBridgeName)
|
||||
}
|
||||
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "cannot list IPv4 addrs of bridge %q", config.WeaveBridgeName)
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
log.Infof("Re-exposing %s on bridge %q", addr.IPNet, config.WeaveBridgeName)
|
||||
if err := Expose(config.WeaveBridgeName, addr.IPNet, config.AWSVPC, config.NPC, false); err != nil {
|
||||
return errors.Wrapf(err, "unable to re-expose %s on bridge: %q", addr.IPNet, config.WeaveBridgeName)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func monitorInterface(ifaceName string, log *logrus.Logger) error {
|
||||
_, err := netlink.LinkByName(ifaceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Unable to find link %q: %s", ifaceName, err)
|
||||
}
|
||||
|
||||
updatesChannel := make(chan netlink.LinkUpdate)
|
||||
if err := netlink.LinkSubscribe(updatesChannel, nil); err != nil {
|
||||
return errors.Wrapf(err, "error monitoring link %q for UP/DOWN notifications", ifaceName)
|
||||
}
|
||||
|
||||
go func() {
|
||||
for update := range updatesChannel {
|
||||
if update.Link.Attrs().Name == ifaceName && update.IfInfomsg.Flags&syscall.IFF_UP == 0 {
|
||||
log.Errorf("Interface %q which needs to be in UP state for Weave functioning is found to be in DOWN state", ifaceName)
|
||||
}
|
||||
}
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"net"
|
||||
"syscall"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"github.com/j-keck/arping"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
// Expose makes the network accessible from a host by assigning a given IP address
|
||||
// to the weave bridge.
|
||||
//
|
||||
// List of params:
|
||||
// * "bridgeName" - a name of the weave bridge.
|
||||
// * "ipAddr" - IP addr to be assigned to the bridge.
|
||||
// * "removeDefaultRoute" - whether to remove a default route installed by the kernel (used only in the AWSVPC mode).
|
||||
// * "npc" - whether is Weave NPC running.
|
||||
// * "skipNAT" - whether to skip adding iptables NAT rules
|
||||
func Expose(bridgeName string, ipAddr *net.IPNet, removeDefaultRoute, npc bool, skipNAT bool) error {
|
||||
ipt, err := iptables.New()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "iptables.New")
|
||||
}
|
||||
cidr := ipAddr.String()
|
||||
|
||||
if err := addBridgeIPAddr(bridgeName, ipAddr, removeDefaultRoute); err != nil {
|
||||
return errors.Wrap(err, "addBridgeIPAddr")
|
||||
}
|
||||
|
||||
if !skipNAT {
|
||||
if err := exposeNAT(ipt, cidr); err != nil {
|
||||
return errors.Wrap(err, "exposeNAT")
|
||||
}
|
||||
}
|
||||
|
||||
if !npc {
|
||||
// Docker 1.13 has changed a default policy of FORWARD chain to DROP
|
||||
// (https://github.com/moby/moby/pull/28257) which makes containers
|
||||
// inaccessible from a remote host when the bridge is exposed.
|
||||
//
|
||||
// The change breaks e.g. the AWSVPC mode. To overcome this we install
|
||||
// an explicit rule for accepting forwarded ingress traffic to an
|
||||
// exposed subnet.
|
||||
if err := ipt.AppendUnique("filter", "WEAVE-EXPOSE", "-d", cidr, "-j", "ACCEPT"); err != nil {
|
||||
return errors.Wrap(err, "ipt.AppendUnique")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func addBridgeIPAddr(bridgeName string, addr *net.IPNet, removeDefaultRoute bool) error {
|
||||
link, err := netlink.LinkByName(bridgeName)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "addBridgeIPAddr finding bridge %q", bridgeName)
|
||||
}
|
||||
err = netlink.AddrAdd(link, &netlink.Addr{IPNet: addr})
|
||||
// The IP addr might have been already set by a concurrent request, just ignore then
|
||||
if err != nil && err != syscall.Errno(syscall.EEXIST) {
|
||||
return errors.Wrapf(err, "adding address %v to %q", addr, bridgeName)
|
||||
}
|
||||
|
||||
// Sending multiple ARP REQUESTs in the case of EEXIST above does not hurt
|
||||
arping.GratuitousArpOverIfaceByName(addr.IP, bridgeName)
|
||||
|
||||
// Remove a default route installed by the kernel. Required by the AWSVPC mode.
|
||||
if removeDefaultRoute {
|
||||
routeFilter := &netlink.Route{
|
||||
LinkIndex: link.Attrs().Index,
|
||||
Dst: &net.IPNet{IP: addr.IP.Mask(addr.Mask), Mask: addr.Mask},
|
||||
Protocol: 2, // RTPROT_KERNEL
|
||||
}
|
||||
filterMask := netlink.RT_FILTER_OIF | netlink.RT_FILTER_DST | netlink.RT_FILTER_PROTOCOL
|
||||
routes, err := netlink.RouteListFiltered(netlink.FAMILY_V4, routeFilter, filterMask)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to get route list for bridge %q", bridgeName)
|
||||
}
|
||||
for _, r := range routes {
|
||||
err = netlink.RouteDel(&r)
|
||||
// Again, there might be a concurrent request for removing routes
|
||||
if err != nil && err != syscall.Errno(syscall.ESRCH) {
|
||||
return errors.Wrapf(err, "failed to delete default route %+v", r)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func exposeNAT(ipt *iptables.IPTables, cidr string) error {
|
||||
if err := addNatRule(ipt, "-s", cidr, "-d", "224.0.0.0/4", "-j", "RETURN"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := addNatRule(ipt, "-d", cidr, "!", "-s", cidr, "-j", "MASQUERADE"); err != nil {
|
||||
return err
|
||||
}
|
||||
return addNatRule(ipt, "-s", cidr, "!", "-d", cidr, "-j", "MASQUERADE")
|
||||
}
|
||||
|
||||
func addNatRule(ipt *iptables.IPTables, rulespec ...string) error {
|
||||
// Loop until we get an exit code other than "temporarily unavailable"
|
||||
for {
|
||||
if err := ipt.AppendUnique("nat", "WEAVE", rulespec...); err != nil {
|
||||
if ierr, ok := err.(*iptables.Error); ok {
|
||||
if status, ok := ierr.ExitError.Sys().(syscall.WaitStatus); ok {
|
||||
// (magic exit code 4 found in iptables source code; undocumented)
|
||||
if status.ExitStatus() == 4 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -82,3 +82,25 @@ func findInterface(ifaceName string) (iface *net.Interface, err error) {
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func LocalAddresses() ([]*net.IPNet, error) {
|
||||
links, err := netlink.LinkList()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
retval := []*net.IPNet{}
|
||||
for _, link := range links {
|
||||
if _, isBridge := link.(*netlink.Bridge); isBridge || (link.Attrs().Flags&net.FlagLoopback) != 0 {
|
||||
continue
|
||||
}
|
||||
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, addr := range addrs {
|
||||
retval = append(retval, addr.IPNet)
|
||||
}
|
||||
}
|
||||
return retval, nil
|
||||
}
|
||||
+24
-21
@@ -12,9 +12,9 @@ import (
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/Sirupsen/logrus"
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/vishvananda/netlink"
|
||||
"golang.org/x/crypto/hkdf"
|
||||
|
||||
@@ -85,7 +85,7 @@ func New(log *logrus.Logger) (*IPSec, error) {
|
||||
|
||||
// InitSALocal initializes inbound ipsec from remotePeer and triggers
|
||||
// the initialization on remotePeer.
|
||||
func (ipsec *IPSec) InitSALocal(localPeer, remotePeer mesh.PeerName, connUID uint64, localIP, remoteIP net.IP, udpPort int, sessionKey *[32]byte, initRemote func([]byte) error) error {
|
||||
func (ipsec *IPSec) InitSALocal(localPeer, remotePeer mesh.PeerName, connUID uint64, localIP, remoteIP net.IP, udpPort int, sessionKey *[32]byte) ([]byte, error) {
|
||||
// ID of inbound SPI
|
||||
spiID := getSPIId(remotePeer, localPeer, connUID)
|
||||
|
||||
@@ -95,17 +95,17 @@ func (ipsec *IPSec) InitSALocal(localPeer, remotePeer mesh.PeerName, connUID uin
|
||||
// Derive SA key
|
||||
nonce, err := genNonce()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "generate nonce")
|
||||
return nil, errors.Wrap(err, "generate nonce")
|
||||
}
|
||||
key, err := deriveKey(sessionKey[:], nonce, localPeer)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "derive key")
|
||||
return nil, errors.Wrap(err, "derive key")
|
||||
}
|
||||
|
||||
// Allocate SA
|
||||
sa, err := netlink.XfrmStateAllocSpi(xfrmAllocSpiState(remoteIP, localIP))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("ip xfrm state allocspi (in, %s, %s)", remoteIP, localIP))
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("ip xfrm state allocspi (in, %s, %s)", remoteIP, localIP))
|
||||
}
|
||||
|
||||
// Use SPI generated by the kernel. The kernel ensures {dstIP, spi} to
|
||||
@@ -117,28 +117,24 @@ func (ipsec *IPSec) InitSALocal(localPeer, remotePeer mesh.PeerName, connUID uin
|
||||
// Create SA
|
||||
if sa, err := xfrmState(remoteIP, localIP, spi, false, key); err == nil {
|
||||
if err := netlink.XfrmStateUpdate(sa); err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("xfrm state update (in, %s, %s, 0x%x)", sa.Src, sa.Dst, sa.Spi))
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("xfrm state update (in, %s, %s, 0x%x)", sa.Src, sa.Dst, sa.Spi))
|
||||
}
|
||||
} else {
|
||||
return errors.Wrap(err, "new xfrm state (in)")
|
||||
return nil, errors.Wrap(err, "new xfrm state (in)")
|
||||
}
|
||||
|
||||
// Install iptables rules
|
||||
if err := ipsec.installDropNonEncrypted(localIP, remoteIP, udpPort, spi); err != nil {
|
||||
return errors.Wrap(err, fmt.Sprintf("install protecting rules (%s, %s, %d, 0x%x)", localIP, remoteIP, udpPort, spi))
|
||||
return nil, errors.Wrap(err, fmt.Sprintf("install protecting rules (%s, %s, %d, 0x%x)", localIP, remoteIP, udpPort, spi))
|
||||
}
|
||||
|
||||
si := spiInfo{spi: spi, isDirOut: false}
|
||||
ipsec.spiInfo[spiID] = si
|
||||
ipsec.spis[spi] = &si
|
||||
|
||||
// Trigger the initialization on the remote peer
|
||||
// Generate the message to trigger initialization on the remote peer
|
||||
msg := &msgInitSARemote{nonce, spi}
|
||||
if err := initRemote(msg.serialize()); err != nil {
|
||||
return errors.Wrap(err, "send InitSARemote")
|
||||
}
|
||||
|
||||
return nil
|
||||
return msg.serialize(), nil
|
||||
}
|
||||
|
||||
// InitSARemote initializes outbound ipsec to remotePeer.
|
||||
@@ -223,9 +219,19 @@ func (ipsec *IPSec) Destroy(localPeer, remotePeer mesh.PeerName, connUID uint64,
|
||||
|
||||
if outSPIInfo, ok := ipsec.spiInfo[outSPIID]; ok {
|
||||
ipsec.log.Infof("ipsec: destroy: out %s -> %s 0x%x", localIP, remoteIP, outSPIInfo.spi)
|
||||
|
||||
if err := netlink.XfrmPolicyDel(xfrmPolicy(localIP, remoteIP, outSPIInfo.spi)); err != nil {
|
||||
ipsec.log.Warnf("ipsec: xfrm policy del (%s, %s, 0x%x) failed: %s", localIP, remoteIP, outSPIInfo.spi, err)
|
||||
policy, err := netlink.XfrmPolicyGet(xfrmPolicy(localIP, remoteIP, outSPIInfo.spi))
|
||||
if err != nil {
|
||||
ipsec.log.Warnf("ipsec: xfrm policy get (%s, %s, 0x%x) failed: %s", localIP, remoteIP, outSPIInfo.spi, err)
|
||||
} else {
|
||||
if len(policy.Tmpls) == 1 {
|
||||
if policy.Tmpls[0].Spi == int(outSPIInfo.spi) {
|
||||
if err := netlink.XfrmPolicyDel(xfrmPolicy(localIP, remoteIP, outSPIInfo.spi)); err != nil {
|
||||
ipsec.log.Warnf("ipsec: xfrm policy del (%s, %s, 0x%x) failed: %s", localIP, remoteIP, outSPIInfo.spi, err)
|
||||
}
|
||||
} else {
|
||||
ipsec.log.Debugf("ipsec: xfrm not my policy (%s, %s, 0x%x) got 0x%x ", localIP, remoteIP, outSPIInfo.spi, policy.Tmpls[0].Spi)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
outSA := &netlink.XfrmState{
|
||||
@@ -421,10 +427,7 @@ func (ipsec *IPSec) installDropNonEncrypted(srcIP, dstIP net.IP, udpPort int, in
|
||||
|
||||
func (ipsec *IPSec) removeDropNonEncrypted(srcIP, dstIP net.IP, udpPort int, inSPI SPI) error {
|
||||
rules := rulesDropNonEncrypted(srcIP, dstIP, udpPort, inSPI)
|
||||
if err := ipsec.resetRules(rules, true); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return ipsec.resetRules(rules, true)
|
||||
}
|
||||
|
||||
func (ipsec *IPSec) removeDropNonEncryptedInbound(srcIP, dstIP net.IP, inSPI SPI) error {
|
||||
|
||||
@@ -0,0 +1,214 @@
|
||||
package ipset
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
)
|
||||
|
||||
type Name string
|
||||
|
||||
type Type string
|
||||
|
||||
const (
|
||||
ListSet = Type("list:set")
|
||||
HashIP = Type("hash:ip")
|
||||
HashNet = Type("hash:net")
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
Create(ipsetName Name, ipsetType Type) error
|
||||
AddEntry(user types.UID, ipsetName Name, entry string, comment string) error
|
||||
DelEntry(user types.UID, ipsetName Name, entry string) error
|
||||
Exist(user types.UID, ipsetName Name, entry string) bool
|
||||
Flush(ipsetName Name) error
|
||||
Destroy(ipsetName Name) error
|
||||
|
||||
List(prefix string) ([]Name, error)
|
||||
|
||||
FlushAll() error
|
||||
DestroyAll() error
|
||||
}
|
||||
|
||||
type entryKey struct {
|
||||
ipsetName Name
|
||||
entry string
|
||||
}
|
||||
|
||||
type ipset struct {
|
||||
*log.Logger
|
||||
enableComments bool
|
||||
maxListSize int
|
||||
// List of users per ipset entry. User is either a namespace or a pod.
|
||||
// There might be multiple users for the same ipset & entry pair because
|
||||
// events from k8s API server might be out of order causing duplicate IPs:
|
||||
// https://github.com/weaveworks/weave/issues/2792.
|
||||
users map[entryKey]map[types.UID]struct{}
|
||||
}
|
||||
|
||||
func New(logger *log.Logger, maxListSize int) Interface {
|
||||
ips := &ipset{
|
||||
Logger: logger,
|
||||
enableComments: true,
|
||||
maxListSize: maxListSize,
|
||||
users: make(map[entryKey]map[types.UID]struct{}),
|
||||
}
|
||||
|
||||
// Check for comment support
|
||||
|
||||
// To prevent from a race when more than one process check for the support
|
||||
// we append a random nonce to the test ipset name. The final name is
|
||||
// shorter than 31 chars (max ipset name).
|
||||
nonce := make([]byte, 4)
|
||||
rand.Read(nonce)
|
||||
testIpsetName := Name("weave-test-comment" + hex.EncodeToString(nonce))
|
||||
|
||||
// Clear it out if it already exists
|
||||
_ = ips.Destroy(testIpsetName)
|
||||
// Test for comment support
|
||||
if err := ips.Create(testIpsetName, HashIP); err != nil {
|
||||
ips.Logger.Printf("failed to create %s; disabling comment support", testIpsetName)
|
||||
ips.enableComments = false
|
||||
}
|
||||
// If it was created, destroy it
|
||||
_ = ips.Destroy(testIpsetName)
|
||||
|
||||
return ips
|
||||
}
|
||||
|
||||
func (i *ipset) Create(ipsetName Name, ipsetType Type) error {
|
||||
args := []string{"create", string(ipsetName), string(ipsetType)}
|
||||
if ipsetType == ListSet && i.maxListSize > 0 {
|
||||
args = append(args, "size", fmt.Sprintf("%d", i.maxListSize))
|
||||
}
|
||||
if i.enableComments {
|
||||
args = append(args, "comment")
|
||||
}
|
||||
return doExec(args...)
|
||||
}
|
||||
|
||||
func (i *ipset) AddEntry(user types.UID, ipsetName Name, entry string, comment string) error {
|
||||
i.Logger.Printf("adding entry %s to %s of %s", entry, ipsetName, user)
|
||||
|
||||
if !i.addUser(user, ipsetName, entry) { // already in the set
|
||||
return nil
|
||||
}
|
||||
|
||||
i.Logger.Printf("added entry %s to %s of %s", entry, ipsetName, user)
|
||||
|
||||
args := []string{"add", string(ipsetName), entry}
|
||||
if i.enableComments {
|
||||
args = append(args, "comment", comment)
|
||||
}
|
||||
return doExec(args...)
|
||||
}
|
||||
|
||||
func (i *ipset) DelEntry(user types.UID, ipsetName Name, entry string) error {
|
||||
i.Logger.Printf("deleting entry %s from %s of %s", entry, ipsetName, user)
|
||||
|
||||
if !i.delUser(user, ipsetName, entry) { // still needed
|
||||
return nil
|
||||
}
|
||||
|
||||
i.Logger.Printf("deleted entry %s from %s of %s", entry, ipsetName, user)
|
||||
|
||||
return doExec("del", string(ipsetName), entry)
|
||||
}
|
||||
|
||||
func (i *ipset) Exist(user types.UID, ipsetName Name, entry string) bool {
|
||||
return i.existUser(user, ipsetName, entry)
|
||||
}
|
||||
|
||||
func (i *ipset) Flush(ipsetName Name) error {
|
||||
i.removeSetFromUsers(ipsetName)
|
||||
return doExec("flush", string(ipsetName))
|
||||
}
|
||||
|
||||
func (i *ipset) FlushAll() error {
|
||||
i.users = make(map[entryKey]map[types.UID]struct{})
|
||||
return doExec("flush")
|
||||
}
|
||||
|
||||
func (i *ipset) Destroy(ipsetName Name) error {
|
||||
i.removeSetFromUsers(ipsetName)
|
||||
return doExec("destroy", string(ipsetName))
|
||||
}
|
||||
|
||||
func (i *ipset) DestroyAll() error {
|
||||
i.users = make(map[entryKey]map[types.UID]struct{})
|
||||
return doExec("destroy")
|
||||
}
|
||||
|
||||
// Fetch a list of all existing sets with a given prefix
|
||||
func (i *ipset) List(prefix string) ([]Name, error) {
|
||||
output, err := exec.Command("ipset", "list", "-name", "-output", "plain").Output()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var selected []Name
|
||||
sets := strings.Split(string(output), "\n")
|
||||
for _, v := range sets {
|
||||
if strings.HasPrefix(v, prefix) {
|
||||
selected = append(selected, Name(v))
|
||||
}
|
||||
}
|
||||
|
||||
return selected, err
|
||||
}
|
||||
|
||||
// Returns true if entry does not exist in ipset (entry has to be inserted into ipset).
|
||||
func (i *ipset) addUser(user types.UID, ipsetName Name, entry string) bool {
|
||||
k := entryKey{ipsetName, entry}
|
||||
add := false
|
||||
|
||||
if i.users[k] == nil {
|
||||
i.users[k] = make(map[types.UID]struct{})
|
||||
}
|
||||
if len(i.users[k]) == 0 {
|
||||
add = true
|
||||
}
|
||||
i.users[k][user] = struct{}{}
|
||||
|
||||
return add
|
||||
}
|
||||
|
||||
// Returns true if user is the last owner of entry (entry has to be removed from ipset).
|
||||
func (i *ipset) delUser(user types.UID, ipsetName Name, entry string) bool {
|
||||
k := entryKey{ipsetName, entry}
|
||||
|
||||
oneLeft := len(i.users[k]) == 1
|
||||
delete(i.users[k], user)
|
||||
|
||||
if len(i.users[k]) == 0 {
|
||||
delete(i.users, k)
|
||||
}
|
||||
|
||||
return oneLeft && (len(i.users[k]) == 0)
|
||||
}
|
||||
|
||||
func (i *ipset) existUser(user types.UID, ipsetName Name, entry string) bool {
|
||||
_, ok := i.users[entryKey{ipsetName, entry}][user]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (i *ipset) removeSetFromUsers(ipsetName Name) {
|
||||
for k := range i.users {
|
||||
if k.ipsetName == ipsetName {
|
||||
delete(i.users, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// AddChainWithRules creates a chain and appends given rules to it.
|
||||
//
|
||||
// If the chain exists, but its rules are not the same as the given ones, the
|
||||
// function will flush the chain and then will append the rules.
|
||||
func AddChainWithRules(ipt *iptables.IPTables, table, chain string, rulespecs [][]string) error {
|
||||
if err := ensureChains(ipt, table, chain); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
currRuleSpecs, err := ipt.List(table, chain)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "iptables -S. table: %q, chain: %q", table, chain)
|
||||
}
|
||||
|
||||
// First returned rule is "-N $(chain)", so ignore it
|
||||
currRules := strings.Join(currRuleSpecs[1:], "\n")
|
||||
rules := make([]string, 0)
|
||||
for _, r := range rulespecs {
|
||||
rules = append(rules, strings.Join(r, " "))
|
||||
}
|
||||
reqRules := strings.Join(rules, "\n")
|
||||
|
||||
if currRules == reqRules {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := ipt.ClearChain(table, chain); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, r := range rulespecs {
|
||||
if err := ipt.Append(table, chain, r...); err != nil {
|
||||
return errors.Wrapf(err, "iptables -A. table: %q, chain: %q, rule: %s", table, chain, r)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensureChains creates given chains if they do not exist.
|
||||
func ensureChains(ipt *iptables.IPTables, table string, chains ...string) error {
|
||||
existingChains, err := ipt.ListChains(table)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "ipt.ListChains(%s)", table)
|
||||
}
|
||||
chainMap := make(map[string]struct{})
|
||||
for _, c := range existingChains {
|
||||
chainMap[c] = struct{}{}
|
||||
}
|
||||
|
||||
for _, c := range chains {
|
||||
if _, found := chainMap[c]; !found {
|
||||
if err := ipt.NewChain(table, c); err != nil {
|
||||
return errors.Wrapf(err, "ipt.NewChain(%s, %s)", table, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ensureRulesAtTop ensures the presence of given iptables rules.
|
||||
//
|
||||
// If any rule from the list is missing, the function deletes all given
|
||||
// rules and re-inserts them at the top of the chain to ensure the order of the rules.
|
||||
func ensureRulesAtTop(table, chain string, rulespecs [][]string, ipt *iptables.IPTables) error {
|
||||
allFound := true
|
||||
|
||||
for _, rs := range rulespecs {
|
||||
found, err := ipt.Exists(table, chain, rs...)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "ipt.Exists(%s, %s, %s)", table, chain, rs)
|
||||
}
|
||||
if !found {
|
||||
allFound = false
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// All rules exist, do nothing.
|
||||
if allFound {
|
||||
return nil
|
||||
}
|
||||
|
||||
for pos, rs := range rulespecs {
|
||||
// If any is missing, then delete all, as we need to preserve the order of
|
||||
// given rules. Ignore errors, as rule might not exist.
|
||||
if !allFound {
|
||||
ipt.Delete(table, chain, rs...)
|
||||
}
|
||||
if err := ipt.Insert(table, chain, pos+1, rs...); err != nil {
|
||||
return errors.Wrapf(err, "ipt.Append(%s, %s, %s)", table, chain, rs)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+25
-13
@@ -1,11 +1,9 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"strconv"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
"github.com/vishvananda/netns"
|
||||
@@ -17,7 +15,7 @@ type Dev struct {
|
||||
CIDRs []*net.IPNet `json:"CIDRs,omitempty"`
|
||||
}
|
||||
|
||||
func LinkToNetDev(link netlink.Link) (Dev, error) {
|
||||
func linkToNetDev(link netlink.Link) (Dev, error) {
|
||||
addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
||||
if err != nil {
|
||||
return Dev{}, err
|
||||
@@ -90,16 +88,30 @@ func GetNetDevsByVethPeerIds(processID int, peerIDs []int) ([]Dev, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// convert list of peerIDs into a map for faster lookup
|
||||
indexes := make(map[int]struct{})
|
||||
for _, id := range peerIDs {
|
||||
indexes[id] = struct{}{}
|
||||
}
|
||||
|
||||
var netdevs []Dev
|
||||
peersStr := make([]string, len(peerIDs))
|
||||
for i, id := range peerIDs {
|
||||
peersStr[i] = strconv.Itoa(id)
|
||||
}
|
||||
nds, err := WithNetNSByPid(processID, "list-netdevs", peersStr...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("list-netdevs failed: %s", err)
|
||||
}
|
||||
err = json.Unmarshal(nds, &netdevs)
|
||||
err = WithNetNS(netnsContainer, func() error {
|
||||
links, err := netlink.LinkList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, link := range links {
|
||||
if _, found := indexes[link.Attrs().Index]; found {
|
||||
netdev, err := linkToNetDev(link)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
netdevs = append(netdevs, netdev)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return netdevs, err
|
||||
}
|
||||
@@ -112,5 +124,5 @@ func GetBridgeNetDev(bridgeName string) (Dev, error) {
|
||||
return Dev{}, err
|
||||
}
|
||||
|
||||
return LinkToNetDev(link)
|
||||
return linkToNetDev(link)
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package net
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
func LinkAddIfNotExist(link netlink.Link) error {
|
||||
err := netlink.LinkAdd(link)
|
||||
if err != nil && err == syscall.EEXIST {
|
||||
return nil
|
||||
}
|
||||
return errors.Wrapf(err, "creating link %q", link)
|
||||
}
|
||||
+16
-37
@@ -1,10 +1,11 @@
|
||||
// +build go1.10
|
||||
|
||||
package net
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
@@ -13,19 +14,7 @@ import (
|
||||
|
||||
var ErrLinkNotFound = errors.New("Link not found")
|
||||
|
||||
// NB: The following function is unsafe, because:
|
||||
// - It changes a network namespace (netns) of an OS thread which runs
|
||||
// the function. During execution, the Go runtime might clone a new OS thread
|
||||
// for scheduling other go-routines, thus they might end up running in
|
||||
// a "wrong" netns.
|
||||
// - runtime.LockOSThread does not guarantee that a spawned go-routine on
|
||||
// the locked thread will be run by it. Thus, the work function is
|
||||
// not allowed to spawn any go-routine which is dependent on the given netns.
|
||||
|
||||
// Please see https://github.com/weaveworks/weave/issues/2388#issuecomment-228365069
|
||||
// for more details and make sure that you understand the implications before
|
||||
// using the function!
|
||||
func WithNetNSUnsafe(ns netns.NsHandle, work func() error) error {
|
||||
func WithNetNS(ns netns.NsHandle, work func() error) error {
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
|
||||
@@ -44,8 +33,8 @@ func WithNetNSUnsafe(ns netns.NsHandle, work func() error) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func WithNetNSLinkUnsafe(ns netns.NsHandle, ifName string, work func(link netlink.Link) error) error {
|
||||
return WithNetNSUnsafe(ns, func() error {
|
||||
func WithNetNSLink(ns netns.NsHandle, ifName string, work func(link netlink.Link) error) error {
|
||||
return WithNetNS(ns, func() error {
|
||||
link, err := netlink.LinkByName(ifName)
|
||||
if err != nil {
|
||||
if err.Error() == errors.New("Link not found").Error() {
|
||||
@@ -57,28 +46,18 @@ func WithNetNSLinkUnsafe(ns netns.NsHandle, ifName string, work func(link netlin
|
||||
})
|
||||
}
|
||||
|
||||
var WeaveUtilCmd = "weaveutil"
|
||||
|
||||
// A safe version of WithNetNS* which creates a process executing
|
||||
// "nsenter --net=<ns-path> weaveutil <cmd> [args]".
|
||||
func WithNetNS(nsPath string, cmd string, args ...string) ([]byte, error) {
|
||||
var stdout, stderr bytes.Buffer
|
||||
|
||||
args = append([]string{"--net=" + nsPath, WeaveUtilCmd, cmd}, args...)
|
||||
c := exec.Command("nsenter", args...)
|
||||
c.Stdout = &stdout
|
||||
c.Stderr = &stderr
|
||||
if err := c.Run(); err != nil {
|
||||
return nil, fmt.Errorf("%s: %s", string(stderr.Bytes()), err)
|
||||
func WithNetNSByPath(path string, work func() error) error {
|
||||
ns, err := netns.GetFromPath(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return stdout.Bytes(), nil
|
||||
}
|
||||
|
||||
func WithNetNSByPid(pid int, cmd string, args ...string) ([]byte, error) {
|
||||
return WithNetNS(NSPathByPid(pid), cmd, args...)
|
||||
return WithNetNS(ns, work)
|
||||
}
|
||||
|
||||
func NSPathByPid(pid int) string {
|
||||
return fmt.Sprintf("/proc/%d/ns/net", pid)
|
||||
return NSPathByPidWithProc("/proc", pid)
|
||||
}
|
||||
|
||||
func NSPathByPidWithProc(procPath string, pid int) string {
|
||||
return filepath.Join(procPath, fmt.Sprint(pid), "/ns/net")
|
||||
}
|
||||
+4
-5
@@ -36,8 +36,8 @@ func getSystemUUID(hostRoot string) ([]byte, error) {
|
||||
return append(machineid, uuid...), nil
|
||||
}
|
||||
|
||||
func getPersistedPeerName(pathName string) (mesh.PeerName, error) {
|
||||
d, err := db.NewBoltDBReadOnly(pathName)
|
||||
func getPersistedPeerName(dbPrefix string) (mesh.PeerName, error) {
|
||||
d, err := db.NewBoltDBReadOnly(dbPrefix)
|
||||
if err != nil {
|
||||
return mesh.UnknownPeerName, err
|
||||
}
|
||||
@@ -56,9 +56,8 @@ func getPersistedPeerName(pathName string) (mesh.PeerName, error) {
|
||||
func GetSystemPeerName(dbPrefix, hostRoot string) (string, error) {
|
||||
// Check if we have a persisted name that matches the old-style ID for this host
|
||||
if oldUUID, err := getOldStyleSystemUUID(); err == nil {
|
||||
pathName := dbPrefix + db.FileName
|
||||
if _, err := os.Stat(pathName); err == nil {
|
||||
persistedPeerName, err := getPersistedPeerName(pathName)
|
||||
if _, err := os.Stat(db.Pathname(dbPrefix)); err == nil {
|
||||
persistedPeerName, err := getPersistedPeerName(dbPrefix)
|
||||
if err != nil && !os.IsNotExist(err) {
|
||||
return "", err
|
||||
}
|
||||
|
||||
+136
-115
@@ -4,19 +4,16 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"github.com/j-keck/arping"
|
||||
"github.com/vishvananda/netlink"
|
||||
"github.com/vishvananda/netns"
|
||||
|
||||
"github.com/weaveworks/weave/common/odp"
|
||||
)
|
||||
|
||||
// create and attach a veth to the Weave bridge
|
||||
func CreateAndAttachVeth(name, peerName, bridgeName string, mtu int, keepTXOn bool, init func(peer netlink.Link) error) (*netlink.Veth, error) {
|
||||
func CreateAndAttachVeth(procPath, name, peerName, bridgeName string, mtu int, keepTXOn bool, errIfLinkExist 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)
|
||||
@@ -31,7 +28,12 @@ func CreateAndAttachVeth(name, peerName, bridgeName string, mtu int, keepTXOn bo
|
||||
MTU: mtu},
|
||||
PeerName: peerName,
|
||||
}
|
||||
if err := netlink.LinkAdd(veth); err != nil {
|
||||
|
||||
linkAdd := LinkAddIfNotExist
|
||||
if errIfLinkExist {
|
||||
linkAdd = netlink.LinkAdd
|
||||
}
|
||||
if err := linkAdd(veth); err != nil {
|
||||
return nil, fmt.Errorf(`could not create veth pair %s-%s: %s`, name, peerName, err)
|
||||
}
|
||||
|
||||
@@ -40,22 +42,24 @@ func CreateAndAttachVeth(name, peerName, bridgeName string, mtu int, keepTXOn bo
|
||||
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)
|
||||
bridgeType, err := ExistingBridgeType(bridgeName, DatapathName)
|
||||
if err != nil {
|
||||
return cleanup("detect bridge type: %s", err)
|
||||
}
|
||||
if err := bridgeType.attach(veth); err != nil {
|
||||
return cleanup("attaching veth %q to %q: %s", name, bridgeName, err)
|
||||
}
|
||||
// No ipv6 router advertisments please
|
||||
if err := sysctl(procPath, "net/ipv6/conf/"+name+"/accept_ra", "0"); err != nil {
|
||||
return cleanup("setting accept_ra to 0: %s", err)
|
||||
}
|
||||
if err := sysctl(procPath, "net/ipv6/conf/"+peerName+"/accept_ra", "0"); err != nil {
|
||||
return cleanup("setting accept_ra to 0: %s", err)
|
||||
}
|
||||
if !bridgeType.IsFastdp() && !keepTXOn {
|
||||
if err := EthtoolTXOff(veth.PeerName); err != nil {
|
||||
return cleanup(`unable to set tx off on %q: %s`, peerName, 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 {
|
||||
@@ -107,35 +111,36 @@ const (
|
||||
)
|
||||
|
||||
func interfaceExistsInNamespace(netNSPath string, ifName string) bool {
|
||||
_, err := WithNetNS(netNSPath, "check-iface", ifName)
|
||||
err := WithNetNSByPath(netNSPath, func() error {
|
||||
_, err := netlink.LinkByName(ifName)
|
||||
return err
|
||||
})
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// 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 {
|
||||
func AttachContainer(netNSPath, id, ifName, bridgeName string, mtu int, withMulticastRoute bool, cidrs []*net.IPNet, keepTXOn bool, hairpinMode bool) error {
|
||||
// AttachContainer expects to be called in host pid namespace
|
||||
const procPath = "/proc"
|
||||
|
||||
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 {
|
||||
veth, err := CreateAndAttachVeth(procPath, name, peerName, bridgeName, mtu, keepTXOn, true, 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 {
|
||||
if err := WithNetNS(ns, func() error {
|
||||
return setupIface(procPath, peerName, ifName)
|
||||
}); err != nil {
|
||||
return fmt.Errorf("error setting up interface: %s", err)
|
||||
}
|
||||
return nil
|
||||
@@ -143,61 +148,113 @@ func AttachContainer(netNSPath, id, ifName, bridgeName string, mtu int, withMult
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = netlink.LinkSetHairpin(veth, hairpinMode); err != nil {
|
||||
return fmt.Errorf("unable to set hairpin mode to %t for bridge side of veth %s: %s", hairpinMode, name, 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
|
||||
if err := WithNetNSLink(ns, ifName, func(veth netlink.Link) error {
|
||||
return setupIfaceAddrs(veth, withMulticastRoute, cidrs)
|
||||
}); err != nil {
|
||||
return fmt.Errorf("error setting up interface addresses: %s", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// setupIfaceAddrs expects to be called in the container's netns
|
||||
func setupIfaceAddrs(veth netlink.Link, withMulticastRoute bool, cidrs []*net.IPNet) error {
|
||||
newAddresses, err := AddAddresses(veth, cidrs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ifName := veth.Attrs().Name
|
||||
ipt, err := iptables.New()
|
||||
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
|
||||
}
|
||||
|
||||
// setupIface expects to be called in the container's netns
|
||||
func setupIface(procPath, ifaceName, newIfName string) error {
|
||||
ipt, err := iptables.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
link, err := netlink.LinkByName(ifaceName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := netlink.LinkSetName(link, newIfName); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := configureARPCache(procPath, newIfName); err != nil {
|
||||
return err
|
||||
}
|
||||
return ipt.Append("filter", "INPUT", "-i", newIfName, "-d", "224.0.0.0/4", "-j", "DROP")
|
||||
}
|
||||
|
||||
// configureARP is a helper for the Docker plugin which doesn't set the addresses itself
|
||||
func ConfigureARP(prefix, procPath string) error {
|
||||
links, err := netlink.LinkList()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, link := range links {
|
||||
ifName := link.Attrs().Name
|
||||
if strings.HasPrefix(ifName, prefix) {
|
||||
configureARPCache(procPath, ifName)
|
||||
if addrs, err := netlink.AddrList(link, netlink.FAMILY_V4); err == nil {
|
||||
for _, addr := range addrs {
|
||||
arping.GratuitousArpOverIfaceByName(addr.IPNet.IP, ifName)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
@@ -210,7 +267,7 @@ func DetachContainer(netNSPath, id, ifName string, cidrs []*net.IPNet) error {
|
||||
return err
|
||||
}
|
||||
|
||||
return WithNetNSLinkUnsafe(ns, ifName, func(veth netlink.Link) error {
|
||||
return WithNetNSLink(ns, ifName, func(veth netlink.Link) error {
|
||||
existingAddrs, err := netlink.AddrList(veth, netlink.FAMILY_V4)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get IP address for %q: %v", veth.Attrs().Name, err)
|
||||
@@ -272,39 +329,3 @@ func subnets(addrs []netlink.Addr) map[string]struct{} {
|
||||
}
|
||||
return subnets
|
||||
}
|
||||
|
||||
func addNatRule(ipt *iptables.IPTables, rulespec ...string) error {
|
||||
// Loop until we get an exit code other than "temporarily unavailable"
|
||||
for {
|
||||
if err := ipt.AppendUnique("nat", "WEAVE", rulespec...); err != nil {
|
||||
if ierr, ok := err.(*iptables.Error); ok {
|
||||
if status, ok := ierr.ExitError.Sys().(syscall.WaitStatus); ok {
|
||||
// (magic exit code 4 found in iptables source code; undocumented)
|
||||
if status.ExitStatus() == 4 {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func ExposeNAT(ipnet net.IPNet) error {
|
||||
ipt, err := iptables.New()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cidr := ipnet.String()
|
||||
if err := addNatRule(ipt, "-s", cidr, "-d", "224.0.0.0/4", "-j", "RETURN"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := addNatRule(ipt, "-d", cidr, "!", "-s", cidr, "-j", "MASQUERADE"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := addNatRule(ipt, "-s", cidr, "!", "-d", cidr, "-j", "MASQUERADE"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
+202
-78
@@ -3,112 +3,236 @@ 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"
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
|
||||
"github.com/weaveworks/weave/npc/ipset"
|
||||
"github.com/weaveworks/weave/net/ipset"
|
||||
)
|
||||
|
||||
func (ns *ns) analysePolicy(policy *extnapi.NetworkPolicy) (
|
||||
func (ns *ns) analysePolicy(policy *networkingv1.NetworkPolicy) (
|
||||
rules map[string]*ruleSpec,
|
||||
nsSelectors, podSelectors map[string]*selectorSpec,
|
||||
nsSelectors, podSelectors, namespacedPodSelectors map[string]*selectorSpec,
|
||||
ipBlocks map[string]*ipBlockSpec,
|
||||
err error) {
|
||||
|
||||
nsSelectors = make(map[string]*selectorSpec)
|
||||
podSelectors = make(map[string]*selectorSpec)
|
||||
namespacedPodSelectors = make(map[string]*selectorSpec)
|
||||
|
||||
ipBlocks = make(map[string]*ipBlockSpec)
|
||||
rules = make(map[string]*ruleSpec)
|
||||
policyTypes := make([]policyType, 0)
|
||||
|
||||
dstSelector, err := newSelectorSpec(&policy.Spec.PodSelector, ns.name, ipset.HashIP)
|
||||
for _, pt := range policy.Spec.PolicyTypes {
|
||||
if pt == networkingv1.PolicyTypeIngress {
|
||||
policyTypes = append(policyTypes, policyTypeIngress)
|
||||
}
|
||||
if pt == networkingv1.PolicyTypeEgress {
|
||||
policyTypes = append(policyTypes, policyTypeEgress)
|
||||
}
|
||||
}
|
||||
// If empty, matches all pods in a namespace
|
||||
targetSelector, err := newSelectorSpec(&policy.Spec.PodSelector, nil, policyTypes, ns.name, ipset.HashIP)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
return nil, nil, 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
|
||||
}
|
||||
// To prevent targetSelector being overwritten by a subsequent selector with
|
||||
// the same key, addIfNotExist MUST be used when adding to podSelectors.
|
||||
// Otherwise, policyTypes of the selector might be lost resulting in
|
||||
// an invalid content in any "default-allow" ipset.
|
||||
addIfNotExist(targetSelector, podSelectors)
|
||||
|
||||
if ingressRule.From != nil && len(ingressRule.From) == 0 {
|
||||
// From is empty, this rule matches no sources (no traffic matches).
|
||||
continue
|
||||
}
|
||||
// If ingress is empty then this NetworkPolicy does not allow any ingress traffic
|
||||
if policy.Spec.Ingress != nil && len(policy.Spec.Ingress) != 0 {
|
||||
for _, ingressRule := range policy.Spec.Ingress {
|
||||
// If Ports is empty or missing, this rule matches all ports
|
||||
allPorts := ingressRule.Ports == nil || len(ingressRule.Ports) == 0
|
||||
// If From is empty or missing, this rule matches all sources
|
||||
allSources := ingressRule.From == nil || len(ingressRule.From) == 0
|
||||
|
||||
if 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
|
||||
})
|
||||
if !allPorts {
|
||||
if err = checkForNamedPorts(ingressRule.Ports); err != nil {
|
||||
return nil, nil, nil, nil, nil, fmt.Errorf("named ports in network policies is not supported yet. "+
|
||||
"Rejecting network policy: %s from further processing. "+err.Error(), policy.Name)
|
||||
}
|
||||
}
|
||||
} 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)
|
||||
if allSources {
|
||||
if allPorts {
|
||||
rule := newRuleSpec(policyTypeIngress, nil, nil, targetSelector, 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)
|
||||
rule := newRuleSpec(policyTypeIngress, &proto, nil, targetSelector, &port)
|
||||
rules[rule.key] = rule
|
||||
})
|
||||
}
|
||||
} else {
|
||||
for _, peer := range ingressRule.From {
|
||||
var srcSelector *selectorSpec
|
||||
var srcRuleHost ruleHost
|
||||
|
||||
// NetworkPolicyPeer describes a peer to allow traffic from.
|
||||
if peer.PodSelector != nil && peer.NamespaceSelector != nil {
|
||||
srcSelector, err = newSelectorSpec(peer.PodSelector, peer.NamespaceSelector, nil, "", ipset.HashIP)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
addIfNotExist(srcSelector, namespacedPodSelectors)
|
||||
srcRuleHost = srcSelector
|
||||
} else if peer.PodSelector != nil {
|
||||
srcSelector, err = newSelectorSpec(peer.PodSelector, nil, nil, ns.name, ipset.HashIP)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
addIfNotExist(srcSelector, podSelectors)
|
||||
srcRuleHost = srcSelector
|
||||
} else if peer.NamespaceSelector != nil {
|
||||
srcSelector, err = newSelectorSpec(nil, peer.NamespaceSelector, nil, "", ipset.ListSet)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
nsSelectors[srcSelector.key] = srcSelector
|
||||
srcRuleHost = srcSelector
|
||||
} else if peer.IPBlock != nil {
|
||||
ipBlock := newIPBlockSpec(peer.IPBlock, ns.name)
|
||||
ipBlocks[ipBlock.key] = ipBlock
|
||||
srcRuleHost = ipBlock
|
||||
}
|
||||
|
||||
if allPorts {
|
||||
rule := newRuleSpec(policyTypeIngress, nil, srcRuleHost, targetSelector, nil)
|
||||
rules[rule.key] = rule
|
||||
} else {
|
||||
withNormalisedProtoAndPort(ingressRule.Ports, func(proto, port string) {
|
||||
rule := newRuleSpec(policyTypeIngress, &proto, srcRuleHost, targetSelector, &port)
|
||||
rules[rule.key] = rule
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rules, nsSelectors, podSelectors, nil
|
||||
// If egress is empty then this NetworkPolicy does not allow any egress traffic
|
||||
if policy.Spec.Egress != nil && len(policy.Spec.Egress) != 0 {
|
||||
for _, egressRule := range policy.Spec.Egress {
|
||||
// If Ports is empty or missing, this rule matches all ports
|
||||
allPorts := egressRule.Ports == nil || len(egressRule.Ports) == 0
|
||||
// If To is empty or missing, this rule matches all destinations
|
||||
allDestinations := egressRule.To == nil || len(egressRule.To) == 0
|
||||
|
||||
if !allPorts {
|
||||
if err = checkForNamedPorts(egressRule.Ports); err != nil {
|
||||
return nil, nil, nil, nil, nil, fmt.Errorf("named ports in network policies is not supported yet. "+
|
||||
"Rejecting network policy: %s from further processing. "+err.Error(), policy.Name)
|
||||
}
|
||||
}
|
||||
if allDestinations {
|
||||
if allPorts {
|
||||
rule := newRuleSpec(policyTypeEgress, nil, targetSelector, nil, nil)
|
||||
rules[rule.key] = rule
|
||||
} else {
|
||||
withNormalisedProtoAndPort(egressRule.Ports, func(proto, port string) {
|
||||
rule := newRuleSpec(policyTypeEgress, &proto, targetSelector, nil, &port)
|
||||
rules[rule.key] = rule
|
||||
})
|
||||
}
|
||||
} else {
|
||||
for _, peer := range egressRule.To {
|
||||
var dstSelector *selectorSpec
|
||||
var dstRuleHost ruleHost
|
||||
|
||||
// NetworkPolicyPeer describes a peer to allow traffic to.
|
||||
if peer.PodSelector != nil && peer.NamespaceSelector != nil {
|
||||
dstSelector, err = newSelectorSpec(peer.PodSelector, peer.NamespaceSelector, nil, "", ipset.HashIP)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
addIfNotExist(dstSelector, namespacedPodSelectors)
|
||||
dstRuleHost = dstSelector
|
||||
} else if peer.PodSelector != nil {
|
||||
dstSelector, err = newSelectorSpec(peer.PodSelector, nil, nil, ns.name, ipset.HashIP)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
addIfNotExist(dstSelector, podSelectors)
|
||||
dstRuleHost = dstSelector
|
||||
|
||||
} else if peer.NamespaceSelector != nil {
|
||||
dstSelector, err = newSelectorSpec(nil, peer.NamespaceSelector, nil, "", ipset.ListSet)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, err
|
||||
}
|
||||
nsSelectors[dstSelector.key] = dstSelector
|
||||
dstRuleHost = dstSelector
|
||||
} else if peer.IPBlock != nil {
|
||||
ipBlock := newIPBlockSpec(peer.IPBlock, ns.name)
|
||||
ipBlocks[ipBlock.key] = ipBlock
|
||||
dstRuleHost = ipBlock
|
||||
}
|
||||
|
||||
if allPorts {
|
||||
rule := newRuleSpec(policyTypeEgress, nil, targetSelector, dstRuleHost, nil)
|
||||
rules[rule.key] = rule
|
||||
} else {
|
||||
withNormalisedProtoAndPort(egressRule.Ports, func(proto, port string) {
|
||||
rule := newRuleSpec(policyTypeEgress, &proto, targetSelector, dstRuleHost, &port)
|
||||
rules[rule.key] = rule
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rules, nsSelectors, podSelectors, namespacedPodSelectors, ipBlocks, nil
|
||||
}
|
||||
|
||||
func withNormalisedProtoAndPort(npps []extnapi.NetworkPolicyPort, f func(proto, port string)) {
|
||||
func addIfNotExist(s *selectorSpec, ss map[string]*selectorSpec) {
|
||||
if _, ok := ss[s.key]; !ok {
|
||||
ss[s.key] = s
|
||||
}
|
||||
}
|
||||
|
||||
func withNormalisedProtoAndPort(npps []networkingv1.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)
|
||||
f(proto(npp.Protocol), port(npp.Port))
|
||||
}
|
||||
}
|
||||
|
||||
func proto(p *apiv1.Protocol) string {
|
||||
// If no proto is specified, default to TCP
|
||||
proto := string(apiv1.ProtocolTCP)
|
||||
if p != nil {
|
||||
proto = string(*p)
|
||||
}
|
||||
|
||||
return proto
|
||||
}
|
||||
|
||||
func port(p *intstr.IntOrString) string {
|
||||
// If no port is specified, match any port. Let iptables executable handle
|
||||
// service name resolution
|
||||
port := "0:65535"
|
||||
if p != nil {
|
||||
switch p.Type {
|
||||
case intstr.Int:
|
||||
port = fmt.Sprintf("%d", p.IntVal)
|
||||
case intstr.String:
|
||||
port = p.StrVal
|
||||
}
|
||||
}
|
||||
|
||||
return port
|
||||
}
|
||||
|
||||
func checkForNamedPorts(ports []networkingv1.NetworkPolicyPort) error {
|
||||
for _, npProtocolPort := range ports {
|
||||
if npProtocolPort.Port != nil && npProtocolPort.Port.Type == intstr.String {
|
||||
return fmt.Errorf("named port %s in network policy", port(npProtocolPort.Port))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -6,4 +6,14 @@ const (
|
||||
MainChain = "WEAVE-NPC"
|
||||
DefaultChain = "WEAVE-NPC-DEFAULT"
|
||||
IngressChain = "WEAVE-NPC-INGRESS"
|
||||
|
||||
EgressChain = "WEAVE-NPC-EGRESS"
|
||||
EgressDefaultChain = "WEAVE-NPC-EGRESS-DEFAULT"
|
||||
EgressCustomChain = "WEAVE-NPC-EGRESS-CUSTOM"
|
||||
EgressMarkChain = "WEAVE-NPC-EGRESS-ACCEPT"
|
||||
EgressMark = "0x40000/0x40000"
|
||||
|
||||
IpsetNamePrefix = "weave-"
|
||||
|
||||
LocalIpset = IpsetNamePrefix + "local-pods"
|
||||
)
|
||||
+117
-26
@@ -1,15 +1,19 @@
|
||||
package npc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"github.com/pkg/errors"
|
||||
coreapi "k8s.io/client-go/pkg/api/v1"
|
||||
extnapi "k8s.io/client-go/pkg/apis/extensions/v1beta1"
|
||||
coreapi "k8s.io/api/core/v1"
|
||||
extnapi "k8s.io/api/extensions/v1beta1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/kubernetes"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/npc/ipset"
|
||||
"github.com/weaveworks/weave/net/ipset"
|
||||
"github.com/weaveworks/weave/npc/iptables"
|
||||
)
|
||||
|
||||
type NetworkPolicyController interface {
|
||||
@@ -21,37 +25,44 @@ type NetworkPolicyController interface {
|
||||
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
|
||||
AddNetworkPolicy(obj interface{}) error
|
||||
UpdateNetworkPolicy(oldObj, newObj interface{}) error
|
||||
DeleteNetworkPolicy(obj interface{}) error
|
||||
}
|
||||
|
||||
type controller struct {
|
||||
sync.Mutex
|
||||
|
||||
ipt *iptables.IPTables
|
||||
ips ipset.Interface
|
||||
nodeName string // my node name
|
||||
|
||||
nss map[string]*ns // ns name -> ns struct
|
||||
nsSelectors *selectorSet // selector string -> nsSelector
|
||||
ipt iptables.Interface
|
||||
ips ipset.Interface
|
||||
clientset kubernetes.Interface
|
||||
nss map[string]*ns // ns name -> ns struct
|
||||
nsSelectors *selectorSet // selector string -> nsSelector
|
||||
namespacedPodSelectors *selectorSet
|
||||
defaultEgressDrop bool // flag to track if base iptable rule to drop egress traffic is added or not
|
||||
}
|
||||
|
||||
func New(ipt *iptables.IPTables, ips ipset.Interface) NetworkPolicyController {
|
||||
func New(nodeName string, ipt iptables.Interface, ips ipset.Interface, clientset kubernetes.Interface) NetworkPolicyController {
|
||||
c := &controller{
|
||||
ipt: ipt,
|
||||
ips: ips,
|
||||
nss: make(map[string]*ns)}
|
||||
|
||||
c.nsSelectors = newSelectorSet(ips, c.onNewNsSelector)
|
||||
nodeName: nodeName,
|
||||
ipt: ipt,
|
||||
ips: ips,
|
||||
clientset: clientset,
|
||||
nss: make(map[string]*ns)}
|
||||
|
||||
doNothing := func(*selector, policyType) error { return nil }
|
||||
c.nsSelectors = newSelectorSet(ips, c.onNewNsSelector, doNothing, doNothing)
|
||||
c.namespacedPodSelectors = newSelectorSet(ips, c.onNewNamespacePodsSelector, doNothing, doNothing)
|
||||
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 {
|
||||
if selector.matchesNamespaceSelector(ns.namespace.ObjectMeta.Labels) {
|
||||
if err := selector.addEntry(ns.namespace.ObjectMeta.UID, string(ns.allPods.ipsetName), namespaceComment(ns)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -60,10 +71,32 @@ func (npc *controller) onNewNsSelector(selector *selector) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (npc *controller) onNewNamespacePodsSelector(selector *selector) error {
|
||||
for _, ns := range npc.nss {
|
||||
if ns.namespace != nil && len(ns.pods) > 0 {
|
||||
for _, pod := range ns.pods {
|
||||
if hasIP(pod) {
|
||||
if selector.matchesNamespacedPodSelector(pod.ObjectMeta.Labels, ns.namespace.ObjectMeta.Labels) {
|
||||
if err := selector.addEntry(pod.ObjectMeta.UID, pod.Status.PodIP, podComment(pod)); 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)
|
||||
namespace, err := npc.clientset.CoreV1().Namespaces().Get(name, metav1.GetOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newNs, err := newNS(name, npc.nodeName, npc.ipt, npc.ips, npc.nsSelectors, npc.namespacedPodSelectors, namespace)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -112,32 +145,62 @@ func (npc *controller) DeletePod(obj *coreapi.Pod) error {
|
||||
})
|
||||
}
|
||||
|
||||
func (npc *controller) AddNetworkPolicy(obj *extnapi.NetworkPolicy) error {
|
||||
func (npc *controller) AddNetworkPolicy(obj interface{}) error {
|
||||
npc.Lock()
|
||||
defer npc.Unlock()
|
||||
|
||||
// lazily add default rule to drop egress traffic only when network policies are applied
|
||||
if !npc.defaultEgressDrop {
|
||||
egressNetworkPolicy, err := isEgressNetworkPolicy(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if egressNetworkPolicy {
|
||||
npc.defaultEgressDrop = true
|
||||
if err := npc.ipt.Append(TableFilter, EgressChain,
|
||||
"-m", "mark", "!", "--mark", EgressMark, "-j", "DROP"); err != nil {
|
||||
npc.defaultEgressDrop = false
|
||||
return fmt.Errorf("Failed to add iptable rule to drop egress traffic from the pods by default due to %s", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
nsName, err := nsName(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
common.Log.Infof("EVENT AddNetworkPolicy %s", js(obj))
|
||||
return npc.withNS(obj.ObjectMeta.Namespace, func(ns *ns) error {
|
||||
return npc.withNS(nsName, func(ns *ns) error {
|
||||
return errors.Wrap(ns.addNetworkPolicy(obj), "add network policy")
|
||||
})
|
||||
}
|
||||
|
||||
func (npc *controller) UpdateNetworkPolicy(oldObj, newObj *extnapi.NetworkPolicy) error {
|
||||
func (npc *controller) UpdateNetworkPolicy(oldObj, newObj interface{}) error {
|
||||
npc.Lock()
|
||||
defer npc.Unlock()
|
||||
|
||||
nsName, err := nsName(oldObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
common.Log.Infof("EVENT UpdateNetworkPolicy %s %s", js(oldObj), js(newObj))
|
||||
return npc.withNS(oldObj.ObjectMeta.Namespace, func(ns *ns) error {
|
||||
return npc.withNS(nsName, func(ns *ns) error {
|
||||
return errors.Wrap(ns.updateNetworkPolicy(oldObj, newObj), "update network policy")
|
||||
})
|
||||
}
|
||||
|
||||
func (npc *controller) DeleteNetworkPolicy(obj *extnapi.NetworkPolicy) error {
|
||||
func (npc *controller) DeleteNetworkPolicy(obj interface{}) error {
|
||||
npc.Lock()
|
||||
defer npc.Unlock()
|
||||
|
||||
nsName, err := nsName(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
common.Log.Infof("EVENT DeleteNetworkPolicy %s", js(obj))
|
||||
return npc.withNS(obj.ObjectMeta.Namespace, func(ns *ns) error {
|
||||
return npc.withNS(nsName, func(ns *ns) error {
|
||||
return errors.Wrap(ns.deleteNetworkPolicy(obj), "delete network policy")
|
||||
})
|
||||
}
|
||||
@@ -171,3 +234,31 @@ func (npc *controller) DeleteNamespace(obj *coreapi.Namespace) error {
|
||||
return errors.Wrap(ns.deleteNamespace(obj), "delete namespace")
|
||||
})
|
||||
}
|
||||
|
||||
func nsName(obj interface{}) (string, error) {
|
||||
switch obj := obj.(type) {
|
||||
case *networkingv1.NetworkPolicy:
|
||||
return obj.ObjectMeta.Namespace, nil
|
||||
case *extnapi.NetworkPolicy:
|
||||
return obj.ObjectMeta.Namespace, nil
|
||||
}
|
||||
|
||||
return "", errInvalidNetworkPolicyObjType
|
||||
}
|
||||
|
||||
func isEgressNetworkPolicy(obj interface{}) (bool, error) {
|
||||
if policy, ok := obj.(*networkingv1.NetworkPolicy); ok {
|
||||
if len(policy.Spec.PolicyTypes) > 0 {
|
||||
for _, policyType := range policy.Spec.PolicyTypes {
|
||||
if policyType == networkingv1.PolicyTypeEgress {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if policy.Spec.Egress != nil {
|
||||
return true, nil
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
return false, errInvalidNetworkPolicyObjType
|
||||
}
|
||||
@@ -0,0 +1,692 @@
|
||||
package npc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/weaveworks/weave/net/ipset"
|
||||
coreapi "k8s.io/api/core/v1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/intstr"
|
||||
"k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
type mockSet struct {
|
||||
name ipset.Name
|
||||
setType ipset.Type
|
||||
subSets map[string]map[types.UID]bool
|
||||
}
|
||||
|
||||
type mockIPSet struct {
|
||||
sets map[string]mockSet
|
||||
}
|
||||
|
||||
func newMockIPSet() mockIPSet {
|
||||
return mockIPSet{sets: make(map[string]mockSet)}
|
||||
}
|
||||
|
||||
func (i *mockIPSet) Create(ipsetName ipset.Name, ipsetType ipset.Type) error {
|
||||
if _, ok := i.sets[string(ipsetName)]; ok {
|
||||
return errors.Errorf("ipset %s already exists", ipsetName)
|
||||
}
|
||||
i.sets[string(ipsetName)] = mockSet{name: ipsetName, setType: ipsetType, subSets: make(map[string]map[types.UID]bool)}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *mockIPSet) AddEntry(user types.UID, ipsetName ipset.Name, entry string, comment string) error {
|
||||
log.Printf("adding entry %s to %s for %s", entry, ipsetName, user)
|
||||
if _, ok := i.sets[string(ipsetName)]; !ok {
|
||||
return errors.Errorf("%s does not exist", entry)
|
||||
}
|
||||
if i.sets[string(ipsetName)].subSets[entry] == nil {
|
||||
i.sets[string(ipsetName)].subSets[entry] = make(map[types.UID]bool)
|
||||
}
|
||||
if _, ok := i.sets[string(ipsetName)].subSets[entry][user]; ok {
|
||||
return errors.Errorf("user %s already owns entry %s", user, entry)
|
||||
}
|
||||
i.sets[string(ipsetName)].subSets[entry][user] = true
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *mockIPSet) DelEntry(user types.UID, ipsetName ipset.Name, entry string) error {
|
||||
log.Printf("deleting entry %s from %s for %s", entry, ipsetName, user)
|
||||
if _, ok := i.sets[string(ipsetName)]; !ok {
|
||||
return errors.Errorf("ipset %s does not exist", ipsetName)
|
||||
}
|
||||
if _, ok := i.sets[string(ipsetName)].subSets[entry][user]; !ok {
|
||||
return errors.Errorf("user %s does not own entry %s", user, entry)
|
||||
}
|
||||
delete(i.sets[string(ipsetName)].subSets[entry], user)
|
||||
|
||||
if len(i.sets[string(ipsetName)].subSets[entry]) == 0 {
|
||||
delete(i.sets[string(ipsetName)].subSets, entry)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *mockIPSet) Exist(user types.UID, ipsetName ipset.Name, entry string) bool {
|
||||
_, ok := i.sets[string(ipsetName)].subSets[entry][user]
|
||||
return ok
|
||||
}
|
||||
|
||||
func (i *mockIPSet) entryExists(ipsetName ipset.Name, entry string) bool {
|
||||
return len(i.sets[string(ipsetName)].subSets[entry]) > 0
|
||||
}
|
||||
|
||||
func (i *mockIPSet) Flush(ipsetName ipset.Name) error {
|
||||
return errors.New("Not Implemented")
|
||||
}
|
||||
|
||||
func (i *mockIPSet) FlushAll() error {
|
||||
return errors.New("Not Implemented")
|
||||
}
|
||||
|
||||
func (i *mockIPSet) Destroy(ipsetName ipset.Name) error {
|
||||
if _, ok := i.sets[string(ipsetName)]; !ok {
|
||||
return errors.Errorf("ipset %s does not exist", ipsetName)
|
||||
}
|
||||
delete(i.sets, string(ipsetName))
|
||||
return nil
|
||||
}
|
||||
|
||||
func (i *mockIPSet) DestroyAll() error {
|
||||
return errors.New("Not Implemented")
|
||||
}
|
||||
|
||||
func (i *mockIPSet) List(prefix string) ([]ipset.Name, error) {
|
||||
return []ipset.Name{}, errors.New("Not Implemented")
|
||||
}
|
||||
|
||||
type mockIPTables struct {
|
||||
rules map[string]map[string]struct{} // chain -> rulespec -> struct{}
|
||||
}
|
||||
|
||||
func newMockIPTables() *mockIPTables {
|
||||
return &mockIPTables{rules: make(map[string]map[string]struct{})}
|
||||
}
|
||||
|
||||
func (ipt *mockIPTables) Append(table, chain string, rulespec ...string) error {
|
||||
if table != TableFilter {
|
||||
return fmt.Errorf("invalid table: %q", table)
|
||||
}
|
||||
|
||||
if _, found := ipt.rules[chain]; !found {
|
||||
ipt.rules[chain] = make(map[string]struct{})
|
||||
}
|
||||
|
||||
rule := strings.Join(rulespec, " ")
|
||||
if _, found := ipt.rules[chain][rule]; found {
|
||||
return fmt.Errorf("rule already exists. chain: %q, rule: %q", chain, rule)
|
||||
}
|
||||
|
||||
ipt.rules[chain][rule] = struct{}{}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ipt *mockIPTables) Delete(table, chain string, rulespec ...string) error {
|
||||
rule := strings.Join(rulespec, " ")
|
||||
|
||||
if _, found := ipt.rules[chain][rule]; !found {
|
||||
return fmt.Errorf("rule does not exist. chain: %q rule: %q", chain, rule)
|
||||
}
|
||||
|
||||
delete(ipt.rules[chain], rule)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ipt *mockIPTables) Insert(table, chain string, pos int, rulespec ...string) error {
|
||||
return errors.New("Not Implemented")
|
||||
}
|
||||
|
||||
func TestRegressionPolicyNamespaceOrdering3059(t *testing.T) {
|
||||
// Test for race condition between namespace and networkpolicy events
|
||||
// https://github.com/weaveworks/weave/issues/3059
|
||||
|
||||
sourceNamespace := &coreapi.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "source",
|
||||
Labels: map[string]string{
|
||||
"app": "source",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
destinationNamespace := &coreapi.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "destination",
|
||||
},
|
||||
}
|
||||
|
||||
port := intstr.FromInt(12345)
|
||||
|
||||
networkPolicy := &networkingv1.NetworkPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "network-policy",
|
||||
Namespace: "destination",
|
||||
},
|
||||
Spec: networkingv1.NetworkPolicySpec{
|
||||
PolicyTypes: []networkingv1.PolicyType{networkingv1.PolicyTypeIngress},
|
||||
Ingress: []networkingv1.NetworkPolicyIngressRule{
|
||||
{
|
||||
From: []networkingv1.NetworkPolicyPeer{
|
||||
{
|
||||
NamespaceSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{
|
||||
"app": "source",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Ports: []networkingv1.NetworkPolicyPort{
|
||||
{
|
||||
Port: &port,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Namespaces first
|
||||
m := newMockIPSet()
|
||||
client := fake.NewSimpleClientset()
|
||||
controller := New("foo", newMockIPTables(), &m, client)
|
||||
client.CoreV1().Namespaces().Create(sourceNamespace)
|
||||
client.CoreV1().Namespaces().Create(destinationNamespace)
|
||||
|
||||
const (
|
||||
selectorIPSetName = "weave-I239Zp%sCvoVt*D6u=A!2]YEk"
|
||||
sourceIPSetName = "weave-HboJG1fGgG]/SR%k9H#hv5e96"
|
||||
)
|
||||
|
||||
controller.AddNamespace(sourceNamespace)
|
||||
controller.AddNamespace(destinationNamespace)
|
||||
|
||||
controller.AddNetworkPolicy(networkPolicy)
|
||||
|
||||
require.Equal(t, true, len(m.sets[selectorIPSetName].subSets[sourceIPSetName]) > 0)
|
||||
|
||||
// NetworkPolicy first
|
||||
m = newMockIPSet()
|
||||
controller = New("foo", newMockIPTables(), &m, &fake.Clientset{})
|
||||
|
||||
controller.AddNetworkPolicy(networkPolicy)
|
||||
|
||||
controller.AddNamespace(sourceNamespace)
|
||||
controller.AddNamespace(destinationNamespace)
|
||||
|
||||
require.Equal(t, true, len(m.sets[selectorIPSetName].subSets[sourceIPSetName]) > 0)
|
||||
}
|
||||
|
||||
// Tests default-allow ipset behavior
|
||||
func TestDefaultAllow(t *testing.T) {
|
||||
const (
|
||||
ingressDefaultAllowIPSetName = "weave-;rGqyMIl1HN^cfDki~Z$3]6!N"
|
||||
egressDefaultAllowIPSetName = "weave-s_+ChJId4Uy_$}G;WdH|~TK)I"
|
||||
fooPodIP = "10.32.0.10"
|
||||
barPodIP = "10.32.0.11"
|
||||
barPodNewIP = "10.32.0.12"
|
||||
)
|
||||
|
||||
m := newMockIPSet()
|
||||
controller := New("bar", newMockIPTables(), &m, &fake.Clientset{})
|
||||
|
||||
defaultNamespace := &coreapi.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "default",
|
||||
},
|
||||
}
|
||||
controller.AddNamespace(defaultNamespace)
|
||||
|
||||
// Should create an ipset for default-allow
|
||||
require.Contains(t, m.sets, ingressDefaultAllowIPSetName)
|
||||
require.Contains(t, m.sets, egressDefaultAllowIPSetName)
|
||||
|
||||
podFoo := &coreapi.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: "foo",
|
||||
Namespace: "default",
|
||||
Name: "foo",
|
||||
Labels: map[string]string{"run": "foo"}},
|
||||
Status: coreapi.PodStatus{PodIP: fooPodIP}}
|
||||
controller.AddPod(podFoo)
|
||||
|
||||
// Should add the foo pod to default-allow
|
||||
require.True(t, m.entryExists(ingressDefaultAllowIPSetName, fooPodIP))
|
||||
require.True(t, m.entryExists(egressDefaultAllowIPSetName, fooPodIP))
|
||||
|
||||
podBar := &coreapi.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: "bar",
|
||||
Namespace: "default",
|
||||
Name: "bar",
|
||||
Labels: map[string]string{"run": "bar"}},
|
||||
Status: coreapi.PodStatus{PodIP: barPodIP}}
|
||||
podBarNoIP := &coreapi.Pod{ObjectMeta: podBar.ObjectMeta}
|
||||
controller.AddPod(podBarNoIP)
|
||||
|
||||
controller.UpdatePod(podBarNoIP, podBar)
|
||||
|
||||
// Should add the bar pod to default-allow
|
||||
require.True(t, m.entryExists(ingressDefaultAllowIPSetName, barPodIP))
|
||||
require.True(t, m.entryExists(egressDefaultAllowIPSetName, barPodIP))
|
||||
|
||||
// Allow access from the bar pod to the foo pod
|
||||
netpol := &networkingv1.NetworkPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "allow-from-bar-to-foo",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: networkingv1.NetworkPolicySpec{
|
||||
PolicyTypes: []networkingv1.PolicyType{
|
||||
networkingv1.PolicyTypeIngress,
|
||||
networkingv1.PolicyTypeEgress,
|
||||
},
|
||||
PodSelector: metav1.LabelSelector{MatchLabels: map[string]string{"run": "foo"}},
|
||||
Ingress: []networkingv1.NetworkPolicyIngressRule{{
|
||||
From: []networkingv1.NetworkPolicyPeer{{
|
||||
PodSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{"run": "bar"},
|
||||
},
|
||||
}},
|
||||
}},
|
||||
Egress: []networkingv1.NetworkPolicyEgressRule{{
|
||||
To: []networkingv1.NetworkPolicyPeer{{
|
||||
IPBlock: &networkingv1.IPBlock{CIDR: "192.168.48.0/24"},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
}
|
||||
controller.AddNetworkPolicy(netpol)
|
||||
|
||||
// Should remove the foo pod from default-allow as the netpol selects it
|
||||
require.False(t, m.entryExists(ingressDefaultAllowIPSetName, fooPodIP))
|
||||
require.False(t, m.entryExists(egressDefaultAllowIPSetName, fooPodIP))
|
||||
require.True(t, m.entryExists(ingressDefaultAllowIPSetName, barPodIP))
|
||||
require.True(t, m.entryExists(egressDefaultAllowIPSetName, barPodIP))
|
||||
|
||||
podBarWithNewIP := *podBar
|
||||
podBarWithNewIP.Status.PodIP = barPodNewIP
|
||||
controller.UpdatePod(podBar, &podBarWithNewIP)
|
||||
|
||||
// Should update IP addr of the bar pod in default-allow
|
||||
require.False(t, m.entryExists(ingressDefaultAllowIPSetName, barPodIP))
|
||||
require.False(t, m.entryExists(egressDefaultAllowIPSetName, barPodIP))
|
||||
require.True(t, m.entryExists(ingressDefaultAllowIPSetName, barPodNewIP))
|
||||
require.True(t, m.entryExists(egressDefaultAllowIPSetName, barPodNewIP))
|
||||
|
||||
controller.UpdatePod(&podBarWithNewIP, podBarNoIP)
|
||||
// Should remove the bar pod from default-allow as it does not have any IP addr
|
||||
require.False(t, m.entryExists(ingressDefaultAllowIPSetName, barPodNewIP))
|
||||
require.False(t, m.entryExists(egressDefaultAllowIPSetName, barPodNewIP))
|
||||
|
||||
podFooWithNewLabel := *podFoo
|
||||
podFooWithNewLabel.ObjectMeta.Labels = map[string]string{"run": "new-foo"}
|
||||
controller.UpdatePod(podFoo, &podFooWithNewLabel)
|
||||
|
||||
// Should bring back the foo pod to default-allow as it does not match dst of any netpol
|
||||
require.True(t, m.entryExists(ingressDefaultAllowIPSetName, fooPodIP))
|
||||
require.True(t, m.entryExists(egressDefaultAllowIPSetName, fooPodIP))
|
||||
|
||||
controller.UpdatePod(&podFooWithNewLabel, podFoo)
|
||||
// Should remove from default-allow as it matches the netpol after the update
|
||||
require.False(t, m.entryExists(ingressDefaultAllowIPSetName, fooPodIP))
|
||||
require.False(t, m.entryExists(egressDefaultAllowIPSetName, fooPodIP))
|
||||
|
||||
controller.DeleteNetworkPolicy(netpol)
|
||||
// Should bring back the foo pod to default-allow as no netpol selects it
|
||||
require.True(t, m.entryExists(ingressDefaultAllowIPSetName, fooPodIP))
|
||||
require.True(t, m.entryExists(egressDefaultAllowIPSetName, fooPodIP))
|
||||
|
||||
controller.DeletePod(podFoo)
|
||||
// Should remove foo pod from default-allow
|
||||
require.False(t, m.entryExists(ingressDefaultAllowIPSetName, fooPodIP))
|
||||
require.False(t, m.entryExists(egressDefaultAllowIPSetName, fooPodIP))
|
||||
|
||||
controller.DeleteNamespace(defaultNamespace)
|
||||
// Should remove default ipset
|
||||
require.NotContains(t, m.sets, ingressDefaultAllowIPSetName)
|
||||
require.NotContains(t, m.sets, egressDefaultAllowIPSetName)
|
||||
}
|
||||
|
||||
func TestOutOfOrderPodEvents(t *testing.T) {
|
||||
const (
|
||||
ingressDefaultAllowIPSetName = "weave-;rGqyMIl1HN^cfDki~Z$3]6!N"
|
||||
runBarIPSetName = "weave-bZ~x=yBgzH)Ht()K*Uv3z{M]Y"
|
||||
podIP = "10.32.0.10"
|
||||
)
|
||||
|
||||
m := newMockIPSet()
|
||||
client := fake.NewSimpleClientset()
|
||||
controller := New("qux", newMockIPTables(), &m, client)
|
||||
|
||||
defaultNamespace := &coreapi.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "default",
|
||||
},
|
||||
}
|
||||
client.CoreV1().Namespaces().Create(defaultNamespace)
|
||||
controller.AddNamespace(defaultNamespace)
|
||||
|
||||
podFoo := &coreapi.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: "foo",
|
||||
Namespace: "default",
|
||||
Name: "foo",
|
||||
Labels: map[string]string{"run": "foo"}},
|
||||
Status: coreapi.PodStatus{PodIP: podIP}}
|
||||
controller.AddPod(podFoo)
|
||||
|
||||
// Should be in default-allow as no netpol selects podFoo
|
||||
require.True(t, m.entryExists(ingressDefaultAllowIPSetName, podIP))
|
||||
|
||||
netpol := &networkingv1.NetworkPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "allow-from-bar-to-foo",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: networkingv1.NetworkPolicySpec{
|
||||
PolicyTypes: []networkingv1.PolicyType{networkingv1.PolicyTypeIngress},
|
||||
PodSelector: metav1.LabelSelector{MatchLabels: map[string]string{"run": "foo"}},
|
||||
Ingress: []networkingv1.NetworkPolicyIngressRule{{
|
||||
From: []networkingv1.NetworkPolicyPeer{{
|
||||
PodSelector: &metav1.LabelSelector{
|
||||
MatchLabels: map[string]string{"run": "bar"},
|
||||
},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
}
|
||||
controller.AddNetworkPolicy(netpol)
|
||||
|
||||
// Shouldn't be in default-allow as netpol above selects podFoo
|
||||
require.False(t, m.entryExists(ingressDefaultAllowIPSetName, podIP))
|
||||
|
||||
podBar := &coreapi.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: "bar",
|
||||
Namespace: "default",
|
||||
Name: "bar",
|
||||
Labels: map[string]string{"run": "bar"}},
|
||||
Status: coreapi.PodStatus{PodIP: podIP}}
|
||||
controller.AddPod(podBar)
|
||||
|
||||
// Should be in default-allow as no netpol selects podBar
|
||||
require.True(t, m.entryExists(ingressDefaultAllowIPSetName, podIP))
|
||||
require.True(t, m.Exist(podBar.ObjectMeta.UID, ingressDefaultAllowIPSetName, podIP))
|
||||
// Should be in run=bar ipset
|
||||
require.True(t, m.entryExists(runBarIPSetName, podIP))
|
||||
|
||||
controller.DeletePod(podFoo)
|
||||
// Multiple duplicate events should not affect npc state
|
||||
controller.DeletePod(podFoo)
|
||||
controller.DeletePod(podFoo)
|
||||
|
||||
// Should be in default-allow as no netpol selects podBar and podFoo removal
|
||||
// should not affect podBar in default-allow
|
||||
require.True(t, m.entryExists(ingressDefaultAllowIPSetName, podIP))
|
||||
|
||||
controller.DeletePod(podBar)
|
||||
|
||||
// Should remove from default-allow and run=bar ipsets
|
||||
require.Equal(t, 0, len(m.sets[ingressDefaultAllowIPSetName].subSets))
|
||||
require.False(t, m.entryExists(runBarIPSetName, podIP))
|
||||
}
|
||||
|
||||
// Test case for https://github.com/weaveworks/weave/issues/3222
|
||||
func TestNewTargetSelector(t *testing.T) {
|
||||
const (
|
||||
ingressDefaultAllowIPSetName = "weave-;rGqyMIl1HN^cfDki~Z$3]6!N"
|
||||
podIP = "10.32.0.10"
|
||||
)
|
||||
|
||||
m := newMockIPSet()
|
||||
client := fake.NewSimpleClientset()
|
||||
controller := New("baz", newMockIPTables(), &m, client)
|
||||
|
||||
defaultNamespace := &coreapi.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "default",
|
||||
},
|
||||
}
|
||||
client.CoreV1().Namespaces().Create(defaultNamespace)
|
||||
controller.AddNamespace(defaultNamespace)
|
||||
|
||||
podFoo := &coreapi.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: "foo",
|
||||
Namespace: "default",
|
||||
Name: "foo",
|
||||
Labels: map[string]string{"run": "foo"}},
|
||||
Status: coreapi.PodStatus{PodIP: podIP}}
|
||||
controller.AddPod(podFoo)
|
||||
|
||||
require.True(t, m.entryExists(ingressDefaultAllowIPSetName, podIP))
|
||||
|
||||
netpolBar := &networkingv1.NetworkPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: "netpol-bar",
|
||||
Name: "allow-from-default-1",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: networkingv1.NetworkPolicySpec{
|
||||
PolicyTypes: []networkingv1.PolicyType{networkingv1.PolicyTypeIngress},
|
||||
PodSelector: metav1.LabelSelector{},
|
||||
Ingress: []networkingv1.NetworkPolicyIngressRule{{
|
||||
From: []networkingv1.NetworkPolicyPeer{{
|
||||
PodSelector: &metav1.LabelSelector{},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
}
|
||||
controller.AddNetworkPolicy(netpolBar)
|
||||
|
||||
// netpolBar target selector selects podFoo
|
||||
require.False(t, m.entryExists(ingressDefaultAllowIPSetName, podIP))
|
||||
|
||||
netpolFoo := &networkingv1.NetworkPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: "netpol-foo",
|
||||
Name: "allow-from-default-2",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: networkingv1.NetworkPolicySpec{
|
||||
PolicyTypes: []networkingv1.PolicyType{networkingv1.PolicyTypeIngress},
|
||||
PodSelector: metav1.LabelSelector{},
|
||||
Ingress: []networkingv1.NetworkPolicyIngressRule{{
|
||||
From: []networkingv1.NetworkPolicyPeer{{
|
||||
PodSelector: &metav1.LabelSelector{},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
}
|
||||
controller.AddNetworkPolicy(netpolFoo)
|
||||
|
||||
controller.DeleteNetworkPolicy(netpolBar)
|
||||
// netpolFoo target-selects podFoo
|
||||
require.False(t, m.entryExists(ingressDefaultAllowIPSetName, podIP))
|
||||
controller.DeleteNetworkPolicy(netpolFoo)
|
||||
// No netpol target-selects podFoo
|
||||
require.True(t, m.entryExists(ingressDefaultAllowIPSetName, podIP))
|
||||
}
|
||||
|
||||
func TestEgressPolicyWithIPBlock(t *testing.T) {
|
||||
const (
|
||||
fooPodIP = "10.32.0.10"
|
||||
exceptIPSetName = "weave-j:W$5om!$8JS})buYAD7q^#sX"
|
||||
exceptIPSetNameInNonDefault = "weave-|*P{aL2C@#N/1IZ!IvQItv_pt"
|
||||
)
|
||||
|
||||
m := newMockIPSet()
|
||||
ipt := newMockIPTables()
|
||||
client := fake.NewSimpleClientset()
|
||||
controller := New("foo", ipt, &m, client)
|
||||
|
||||
defaultNamespace := &coreapi.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "default",
|
||||
},
|
||||
}
|
||||
nonDefaultNamespace := &coreapi.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "non-default",
|
||||
},
|
||||
}
|
||||
client.CoreV1().Namespaces().Create(defaultNamespace)
|
||||
client.CoreV1().Namespaces().Create(nonDefaultNamespace)
|
||||
|
||||
netpolFoo := &networkingv1.NetworkPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: "netpol-foo",
|
||||
Name: "allow-from-bar-to-foo",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: networkingv1.NetworkPolicySpec{
|
||||
PolicyTypes: []networkingv1.PolicyType{
|
||||
networkingv1.PolicyTypeEgress,
|
||||
},
|
||||
PodSelector: metav1.LabelSelector{MatchLabels: map[string]string{"run": "bar"}},
|
||||
Egress: []networkingv1.NetworkPolicyEgressRule{{
|
||||
To: []networkingv1.NetworkPolicyPeer{{
|
||||
IPBlock: &networkingv1.IPBlock{
|
||||
CIDR: "192.168.48.0/24",
|
||||
Except: []string{
|
||||
"192.168.48.1/32",
|
||||
"192.168.48.2/32",
|
||||
},
|
||||
},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
}
|
||||
err := controller.AddNetworkPolicy(netpolFoo)
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, 2, len(m.sets[exceptIPSetName].subSets))
|
||||
require.True(t, m.entryExists(exceptIPSetName, "192.168.48.1/32"))
|
||||
require.True(t, m.entryExists(exceptIPSetName, "192.168.48.2/32"))
|
||||
|
||||
// Each egress rule is represented as two iptables rules (-J MARK and -J RETURN).
|
||||
require.Equal(t, 2, len(ipt.rules[EgressCustomChain]))
|
||||
for rule := range ipt.rules[EgressCustomChain] {
|
||||
require.Contains(t, rule, "-d 192.168.48.0/24 -m set ! --match-set "+exceptIPSetName+" dst")
|
||||
}
|
||||
|
||||
// Check that we create a new ipset for the ipBlock bellow. An ipset with the
|
||||
// same content already exists (created by netpolFoo), but we need to create
|
||||
// a new one, as netpolBar is in a different namespace and weave-npc does
|
||||
// object refcounting only within namespace boundaries.
|
||||
netpolBar := &networkingv1.NetworkPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: "netpol-foo-bar",
|
||||
Name: "allow-from-bar-to-foo-in-non-default",
|
||||
Namespace: "non-default",
|
||||
},
|
||||
Spec: networkingv1.NetworkPolicySpec{
|
||||
PolicyTypes: []networkingv1.PolicyType{
|
||||
networkingv1.PolicyTypeEgress,
|
||||
},
|
||||
PodSelector: metav1.LabelSelector{MatchLabels: map[string]string{"run": "bar"}},
|
||||
Egress: []networkingv1.NetworkPolicyEgressRule{{
|
||||
To: []networkingv1.NetworkPolicyPeer{{
|
||||
IPBlock: &networkingv1.IPBlock{
|
||||
CIDR: "192.168.48.0/24",
|
||||
Except: []string{
|
||||
"192.168.48.1/32",
|
||||
"192.168.48.2/32",
|
||||
},
|
||||
},
|
||||
}},
|
||||
}},
|
||||
},
|
||||
}
|
||||
err = controller.AddNetworkPolicy(netpolBar)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, 2, len(m.sets[exceptIPSetNameInNonDefault].subSets))
|
||||
}
|
||||
|
||||
// Test case for https://github.com/weaveworks/weave/issues/3653
|
||||
func TestIngressPolicyWithIPBlockAndPortSpecified(t *testing.T) {
|
||||
const (
|
||||
barPodIP = "10.32.0.11"
|
||||
runBarIPSetName = "weave-bZ~x=yBgzH)Ht()K*Uv3z{M]Y"
|
||||
)
|
||||
|
||||
m := newMockIPSet()
|
||||
ipt := newMockIPTables()
|
||||
client := fake.NewSimpleClientset()
|
||||
controller := New("any", ipt, &m, client)
|
||||
|
||||
defaultNamespace := &coreapi.Namespace{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "default",
|
||||
},
|
||||
}
|
||||
|
||||
client.CoreV1().Namespaces().Create(defaultNamespace)
|
||||
|
||||
podBar := &coreapi.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: "bar",
|
||||
Namespace: "default",
|
||||
Name: "bar",
|
||||
Labels: map[string]string{"run": "bar"}},
|
||||
Status: coreapi.PodStatus{PodIP: barPodIP}}
|
||||
controller.AddPod(podBar)
|
||||
defer controller.DeletePod(podBar)
|
||||
|
||||
portProtocol := coreapi.ProtocolTCP
|
||||
port := intstr.FromInt(80)
|
||||
netpolicty := &networkingv1.NetworkPolicy{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: "ipblock-bar",
|
||||
Name: "allow-ipblock-to-bar",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: networkingv1.NetworkPolicySpec{
|
||||
PolicyTypes: []networkingv1.PolicyType{
|
||||
networkingv1.PolicyTypeIngress,
|
||||
},
|
||||
PodSelector: metav1.LabelSelector{MatchLabels: podBar.Labels},
|
||||
Ingress: []networkingv1.NetworkPolicyIngressRule{
|
||||
{
|
||||
Ports: []networkingv1.NetworkPolicyPort{
|
||||
{
|
||||
Protocol: &portProtocol,
|
||||
Port: &port,
|
||||
},
|
||||
},
|
||||
From: []networkingv1.NetworkPolicyPeer{
|
||||
{
|
||||
IPBlock: &networkingv1.IPBlock{
|
||||
CIDR: "192.168.48.4/32",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
err := controller.AddNetworkPolicy(netpolicty)
|
||||
require.NoError(t, err)
|
||||
defer controller.DeleteNetworkPolicy(netpolicty)
|
||||
|
||||
require.Equal(t, 1, len(m.sets[runBarIPSetName].subSets))
|
||||
require.True(t, m.entryExists(runBarIPSetName, barPodIP))
|
||||
|
||||
require.Equal(t, 1, len(ipt.rules[IngressChain]))
|
||||
for rule := range ipt.rules[IngressChain] {
|
||||
require.Contains(t, rule, "-s 192.168.48.4/32 -m set --match-set "+runBarIPSetName+" dst --dport 80")
|
||||
}
|
||||
}
|
||||
+116
@@ -0,0 +1,116 @@
|
||||
package npc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/net/ipset"
|
||||
)
|
||||
|
||||
type ipBlockSpec struct {
|
||||
key string
|
||||
ipsetName ipset.Name // ipset for storing excepted CIDRs
|
||||
ipBlock *networkingv1.IPBlock
|
||||
nsName string // Namespace name
|
||||
}
|
||||
|
||||
func newIPBlockSpec(ipb *networkingv1.IPBlock, nsName string) *ipBlockSpec {
|
||||
spec := &ipBlockSpec{ipBlock: ipb, nsName: nsName}
|
||||
|
||||
if len(ipb.Except) > 0 {
|
||||
sort.Strings(ipb.Except)
|
||||
spec.key = strings.Join(ipb.Except, " ")
|
||||
spec.ipsetName = ipset.Name(IpsetNamePrefix + shortName(nsName+":"+"ipblock-except:"+spec.key))
|
||||
}
|
||||
|
||||
return spec
|
||||
}
|
||||
|
||||
func (spec *ipBlockSpec) getRuleSpec(src bool) ([]string, string) {
|
||||
dir, dirOpt := "dst", "-d"
|
||||
if src {
|
||||
dir, dirOpt = "src", "-s"
|
||||
}
|
||||
|
||||
if spec.key == "" {
|
||||
rule := []string{dirOpt, spec.ipBlock.CIDR}
|
||||
comment := fmt.Sprintf("cidr: %s", spec.ipBlock.CIDR)
|
||||
return rule, comment
|
||||
}
|
||||
|
||||
rule := []string{dirOpt, spec.ipBlock.CIDR,
|
||||
"-m", "set", "!", "--match-set", string(spec.ipsetName), dir}
|
||||
comment := fmt.Sprintf("cidr: %s except [%s]", spec.ipBlock.CIDR, spec.key)
|
||||
return rule, comment
|
||||
}
|
||||
|
||||
type ipBlockSet struct {
|
||||
ips ipset.Interface
|
||||
users map[string]map[types.UID]struct{}
|
||||
}
|
||||
|
||||
func newIPBlockSet(ips ipset.Interface) *ipBlockSet {
|
||||
return &ipBlockSet{
|
||||
ips: ips,
|
||||
users: make(map[string]map[types.UID]struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *ipBlockSet) deprovision(user types.UID, current, desired map[string]*ipBlockSpec) error {
|
||||
for key, spec := range current {
|
||||
if key == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, found := desired[key]; !found {
|
||||
delete(s.users[key], user)
|
||||
if len(s.users[key]) == 0 {
|
||||
common.Log.Infof("destroying ipset: %#v", spec)
|
||||
if err := s.ips.Destroy(spec.ipsetName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(s.users, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *ipBlockSet) provision(user types.UID, current, desired map[string]*ipBlockSpec) (err error) {
|
||||
for key, spec := range desired {
|
||||
if key == "" {
|
||||
// No need to provision an ipBlock with empty list of excepted CIDRs
|
||||
// (i.e. no ipset is needed for the related iptables rule).
|
||||
continue
|
||||
}
|
||||
|
||||
if _, found := current[key]; !found {
|
||||
if _, found := s.users[key]; !found {
|
||||
common.Log.Infof("creating ipset: %#v", spec)
|
||||
if err := s.ips.Create(spec.ipsetName, ipset.HashNet); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// TODO(brb) Pass comment to ips.Create instead.
|
||||
comment := "excepted from " + spec.ipBlock.CIDR
|
||||
for _, cidr := range spec.ipBlock.Except {
|
||||
if err = s.ips.AddEntry(user, spec.ipsetName, cidr, comment); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
s.users[key] = make(map[types.UID]struct{})
|
||||
}
|
||||
s.users[key][user] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
package ipset
|
||||
|
||||
import (
|
||||
"os/exec"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Name string
|
||||
|
||||
type Type string
|
||||
|
||||
const (
|
||||
ListSet = Type("list:set")
|
||||
HashIP = Type("hash:ip")
|
||||
)
|
||||
|
||||
type Interface interface {
|
||||
Create(ipsetName Name, ipsetType Type) error
|
||||
AddEntry(ipsetName Name, entry string) error
|
||||
DelEntry(ipsetName Name, entry string) error
|
||||
Flush(ipsetName Name) error
|
||||
Destroy(ipsetName Name) error
|
||||
|
||||
FlushAll() error
|
||||
DestroyAll() error
|
||||
}
|
||||
|
||||
type ipset struct {
|
||||
refCount
|
||||
}
|
||||
|
||||
func New() Interface {
|
||||
return &ipset{refCount: newRefCount()}
|
||||
}
|
||||
|
||||
func (i *ipset) Create(ipsetName Name, ipsetType Type) error {
|
||||
return doExec("create", string(ipsetName), string(ipsetType))
|
||||
}
|
||||
|
||||
func (i *ipset) AddEntry(ipsetName Name, entry string) error {
|
||||
if i.inc(ipsetName, entry) > 1 { // already in the set
|
||||
return nil
|
||||
}
|
||||
return doExec("add", string(ipsetName), entry)
|
||||
}
|
||||
|
||||
func (i *ipset) DelEntry(ipsetName Name, entry string) error {
|
||||
if i.dec(ipsetName, entry) > 0 { // still needed
|
||||
return nil
|
||||
}
|
||||
return doExec("del", string(ipsetName), entry)
|
||||
}
|
||||
|
||||
func (i *ipset) Flush(ipsetName Name) error {
|
||||
i.removeSet(ipsetName)
|
||||
return doExec("flush", string(ipsetName))
|
||||
}
|
||||
|
||||
func (i *ipset) FlushAll() error {
|
||||
i.refCount = newRefCount()
|
||||
return doExec("flush")
|
||||
}
|
||||
|
||||
func (i *ipset) Destroy(ipsetName Name) error {
|
||||
i.removeSet(ipsetName)
|
||||
return doExec("destroy", string(ipsetName))
|
||||
}
|
||||
|
||||
func (i *ipset) DestroyAll() error {
|
||||
i.refCount = newRefCount()
|
||||
return doExec("destroy")
|
||||
}
|
||||
|
||||
func doExec(args ...string) error {
|
||||
if output, err := exec.Command("ipset", args...).CombinedOutput(); err != nil {
|
||||
return errors.Wrapf(err, "ipset %v failed: %s", args, output)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Reference-counting
|
||||
type key struct {
|
||||
ipsetName Name
|
||||
entry string
|
||||
}
|
||||
|
||||
// note no locking is required as all operations are serialised in the controller
|
||||
type refCount struct {
|
||||
ref map[key]int
|
||||
}
|
||||
|
||||
func newRefCount() refCount {
|
||||
return refCount{ref: make(map[key]int)}
|
||||
}
|
||||
|
||||
func (rc *refCount) inc(ipsetName Name, entry string) int {
|
||||
k := key{ipsetName, entry}
|
||||
rc.ref[k]++
|
||||
return rc.ref[k]
|
||||
}
|
||||
|
||||
func (rc *refCount) dec(ipsetName Name, entry string) int {
|
||||
k := key{ipsetName, entry}
|
||||
rc.ref[k]--
|
||||
return rc.ref[k]
|
||||
}
|
||||
|
||||
func (rc *refCount) removeSet(ipsetName Name) {
|
||||
for k := range rc.ref {
|
||||
if k.ipsetName == ipsetName {
|
||||
delete(rc.ref, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
package iptables
|
||||
|
||||
type Interface interface {
|
||||
Append(table, chain string, rulespec ...string) error
|
||||
Delete(table, chain string, rulespec ...string) error
|
||||
Insert(table, chain string, pos int, rulespec ...string) error
|
||||
}
|
||||
+49
@@ -4,7 +4,56 @@ import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// Return JSON suitable for logging an API object.
|
||||
func js(v interface{}) string {
|
||||
// Get the raw JSON
|
||||
a, _ := json.Marshal(v)
|
||||
// Convert this back into a tree of key-value maps
|
||||
var m map[string]interface{}
|
||||
if err := json.Unmarshal(a, &m); err != nil {
|
||||
// If that didn't work, just return the raw version
|
||||
return string(a)
|
||||
}
|
||||
// Trim some bulk, and potentially sensitive areas
|
||||
withMap(m["metadata"], func(status map[string]interface{}) {
|
||||
delete(status, "ownerReferences")
|
||||
})
|
||||
withMap(m["spec"], func(spec map[string]interface{}) {
|
||||
delete(spec, "tolerations")
|
||||
delete(spec, "volumes")
|
||||
rangeSlice(spec["containers"], func(container map[string]interface{}) {
|
||||
delete(container, "args")
|
||||
delete(container, "command")
|
||||
delete(container, "env")
|
||||
delete(container, "livenessProbe")
|
||||
delete(container, "resources")
|
||||
delete(container, "securityContext")
|
||||
delete(container, "volumeMounts")
|
||||
})
|
||||
})
|
||||
withMap(m["status"], func(status map[string]interface{}) {
|
||||
delete(status, "containerStatuses")
|
||||
})
|
||||
// Now marshall what's left to JSON
|
||||
a, _ = json.Marshal(m)
|
||||
return string(a)
|
||||
}
|
||||
|
||||
// Helper function: operate on a map node from a tree of key-value maps
|
||||
func withMap(m interface{}, f func(map[string]interface{})) {
|
||||
if v, ok := m.(map[string]interface{}); ok {
|
||||
f(v)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function: operate on all nodes under i which is a slice in a
|
||||
// tree of key-value maps
|
||||
func rangeSlice(i interface{}, f func(map[string]interface{})) {
|
||||
if s, ok := i.([]interface{}); ok {
|
||||
for _, v := range s {
|
||||
if m, ok := v.(map[string]interface{}); ok {
|
||||
f(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,6 +22,12 @@ var (
|
||||
},
|
||||
[]string{"protocol", "dport"},
|
||||
)
|
||||
PolicyEnforcementErrors = prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Name: "weavenpc_policy_enforcement_errors",
|
||||
Help: "Errors occured in enforcing the network policies.",
|
||||
},
|
||||
)
|
||||
)
|
||||
|
||||
func gatherMetrics() {
|
||||
@@ -95,6 +101,9 @@ func Start(addr string) error {
|
||||
if err := prometheus.Register(blockedConnections); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := prometheus.Register(PolicyEnforcementErrors); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
http.Handle("/metrics", promhttp.Handler())
|
||||
|
||||
|
||||
+396
-114
@@ -1,54 +1,84 @@
|
||||
package npc
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"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"
|
||||
coreapi "k8s.io/api/core/v1"
|
||||
extnapi "k8s.io/api/extensions/v1beta1"
|
||||
networkingv1 "k8s.io/api/networking/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/uuid"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/npc/ipset"
|
||||
"github.com/weaveworks/weave/net/ipset"
|
||||
"github.com/weaveworks/weave/npc/iptables"
|
||||
)
|
||||
|
||||
var errInvalidNetworkPolicyObjType = errors.New("invalid NetworkPolicy object type")
|
||||
|
||||
type ns struct {
|
||||
ipt *iptables.IPTables // interface to iptables
|
||||
ipt iptables.Interface // 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
|
||||
name string // k8s Namespace name
|
||||
nodeName string // my node name
|
||||
namespace *coreapi.Namespace // k8s Namespace object
|
||||
pods map[types.UID]*coreapi.Pod // k8s Pod objects by UID
|
||||
policies map[types.UID]interface{} // 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
|
||||
// stores IP addrs of pods which are not selected by any target podSelector of
|
||||
// any netpol; used as a target in the WEAVE-NPC-{INGRESS,EGRESS}-DEFAULT
|
||||
// iptables chains.
|
||||
ingressDefaultAllowIPSet ipset.Name
|
||||
egressDefaultAllowIPSet ipset.Name
|
||||
|
||||
nsSelectors *selectorSet // reference to global selectorSet that is shared across the `ns`. Used to represent all pods in the matching namespaces
|
||||
podSelectors *selectorSet // used to represent the matching pods in namespace respresented by this `ns`
|
||||
namespacedPodsSelectors *selectorSet // reference to global selectorSet that is shared across the `ns`. Used to represent matching pods in matching namespace
|
||||
ipBlocks *ipBlockSet
|
||||
rules *ruleSet
|
||||
}
|
||||
|
||||
func newNS(name string, ipt *iptables.IPTables, ips ipset.Interface, nsSelectors *selectorSet) (*ns, error) {
|
||||
allPods, err := newSelectorSpec(&unversioned.LabelSelector{}, name, ipset.HashIP)
|
||||
func newNS(name, nodeName string, ipt iptables.Interface, ips ipset.Interface, nsSelectors *selectorSet, namespacedPodsSelectors *selectorSet, namespaceObj *coreapi.Namespace) (*ns, error) {
|
||||
allPods, err := newSelectorSpec(&metav1.LabelSelector{}, nil, nil, 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)}
|
||||
ipt: ipt,
|
||||
ips: ips,
|
||||
name: name,
|
||||
namespace: namespaceObj,
|
||||
nodeName: nodeName,
|
||||
pods: make(map[types.UID]*coreapi.Pod),
|
||||
policies: make(map[types.UID]interface{}),
|
||||
uid: uuid.NewUUID(),
|
||||
allPods: allPods,
|
||||
nsSelectors: nsSelectors,
|
||||
namespacedPodsSelectors: namespacedPodsSelectors,
|
||||
ipBlocks: newIPBlockSet(ips),
|
||||
rules: newRuleSet(ipt),
|
||||
}
|
||||
|
||||
ns.podSelectors = newSelectorSet(ips, ns.onNewPodSelector)
|
||||
ns.podSelectors = newSelectorSet(ips, ns.onNewPodSelector, ns.onNewTargetPodSelector, ns.onDestroyTargetPodSelector)
|
||||
|
||||
ingressDefaultAllowIPSet := ipset.Name(IpsetNamePrefix + shortName("ingress-default-allow:"+name))
|
||||
if err := ips.Create(ingressDefaultAllowIPSet, ipset.HashIP); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ns.ingressDefaultAllowIPSet = ingressDefaultAllowIPSet
|
||||
|
||||
egressDefaultAllowIPSet := ipset.Name(IpsetNamePrefix + shortName("egress-default-allow:"+name))
|
||||
if err := ips.Create(egressDefaultAllowIPSet, ipset.HashIP); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ns.egressDefaultAllowIPSet = egressDefaultAllowIPSet
|
||||
|
||||
if err := ns.podSelectors.provision(ns.uid, nil, map[string]*selectorSpec{ns.allPods.key: ns.allPods}); err != nil {
|
||||
return nil, err
|
||||
@@ -62,25 +92,79 @@ func (ns *ns) empty() bool {
|
||||
}
|
||||
|
||||
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
|
||||
return ns.podSelectors.deprovision(ns.uid, map[string]*selectorSpec{ns.allPods.key: ns.allPods}, 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 {
|
||||
if selector.matchesPodSelector(pod.ObjectMeta.Labels) {
|
||||
if err := selector.addEntry(pod.ObjectMeta.UID, pod.Status.PodIP, podComment(pod)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *ns) onNewTargetPodSelector(selector *selector, policyType policyType) error {
|
||||
for _, pod := range ns.pods {
|
||||
if hasIP(pod) {
|
||||
// Remove the pod from default-allow if dst podselector matches the pod
|
||||
ipset := ns.defaultAllowIPSetName(policyType)
|
||||
if selector.matchesPodSelector(pod.ObjectMeta.Labels) {
|
||||
if err := ns.ips.DelEntry(pod.ObjectMeta.UID, ipset, pod.Status.PodIP); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *ns) onDestroyTargetPodSelector(selector *selector, policyType policyType) error {
|
||||
for _, pod := range ns.pods {
|
||||
if hasIP(pod) {
|
||||
if selector.matchesPodSelector(pod.ObjectMeta.Labels) {
|
||||
if err := ns.addToDefaultAllowIfNoMatching(pod, policyType); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Add pod IP addr to default-allow ipset if there are no matching target selectors
|
||||
func (ns *ns) addToDefaultAllowIfNoMatching(pod *coreapi.Pod, policyType policyType) error {
|
||||
found := false
|
||||
// TODO(mp) optimize (avoid iterating over selectors) by ref counting IP addrs.
|
||||
for _, s := range ns.podSelectors.entries {
|
||||
if ns.podSelectors.targetSelectorExist(s, policyType) && s.matchesPodSelector(pod.ObjectMeta.Labels) {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
ipset := ns.defaultAllowIPSetName(policyType)
|
||||
if err := ns.ips.AddEntry(pod.ObjectMeta.UID, ipset, pod.Status.PodIP, podComment(pod)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *ns) checkLocalPod(obj *coreapi.Pod) bool {
|
||||
if obj.Spec.NodeName != ns.nodeName {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (ns *ns) addPod(obj *coreapi.Pod) error {
|
||||
ns.pods[obj.ObjectMeta.UID] = obj
|
||||
|
||||
@@ -88,7 +172,28 @@ func (ns *ns) addPod(obj *coreapi.Pod) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ns.podSelectors.addToMatching(obj.ObjectMeta.Labels, obj.Status.PodIP)
|
||||
foundIngress, foundEgress, err := ns.podSelectors.addToMatchingPodSelector(obj.ObjectMeta.UID, obj.ObjectMeta.Labels, obj.Status.PodIP, podComment(obj))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// If there are no matching target selectors, add the pod to default-allow
|
||||
if !foundIngress {
|
||||
if err := ns.ips.AddEntry(obj.ObjectMeta.UID, ns.ingressDefaultAllowIPSet, obj.Status.PodIP, podComment(obj)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !foundEgress {
|
||||
if err := ns.ips.AddEntry(obj.ObjectMeta.UID, ns.egressDefaultAllowIPSet, obj.Status.PodIP, podComment(obj)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = ns.namespacedPodsSelectors.addToMatchingNamespacedPodSelector(obj.ObjectMeta.UID, obj.ObjectMeta.Labels, ns.namespace.ObjectMeta.Labels, obj.Status.PodIP, podComment(obj))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *ns) updatePod(oldObj, newObj *coreapi.Pod) error {
|
||||
@@ -100,29 +205,92 @@ func (ns *ns) updatePod(oldObj, newObj *coreapi.Pod) error {
|
||||
}
|
||||
|
||||
if hasIP(oldObj) && !hasIP(newObj) {
|
||||
return ns.podSelectors.delFromMatching(oldObj.ObjectMeta.Labels, oldObj.Status.PodIP)
|
||||
if err := ns.ips.DelEntry(oldObj.ObjectMeta.UID, ns.ingressDefaultAllowIPSet, oldObj.Status.PodIP); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.ips.DelEntry(oldObj.ObjectMeta.UID, ns.egressDefaultAllowIPSet, oldObj.Status.PodIP); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.namespacedPodsSelectors.delFromMatchingNamespacedPodSelector(oldObj.ObjectMeta.UID, oldObj.ObjectMeta.Labels, ns.namespace.ObjectMeta.Labels, oldObj.Status.PodIP); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return ns.podSelectors.delFromMatchingPodSelector(oldObj.ObjectMeta.UID, oldObj.ObjectMeta.Labels, oldObj.Status.PodIP)
|
||||
}
|
||||
|
||||
if !hasIP(oldObj) && hasIP(newObj) {
|
||||
return ns.podSelectors.addToMatching(newObj.ObjectMeta.Labels, newObj.Status.PodIP)
|
||||
foundIngress, foundEgress, err := ns.podSelectors.addToMatchingPodSelector(newObj.ObjectMeta.UID, newObj.ObjectMeta.Labels, newObj.Status.PodIP, podComment(newObj))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !foundIngress {
|
||||
if err := ns.ips.AddEntry(newObj.ObjectMeta.UID, ns.ingressDefaultAllowIPSet, newObj.Status.PodIP, podComment(newObj)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !foundEgress {
|
||||
if err := ns.ips.AddEntry(newObj.ObjectMeta.UID, ns.egressDefaultAllowIPSet, newObj.Status.PodIP, podComment(newObj)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = ns.namespacedPodsSelectors.addToMatchingNamespacedPodSelector(newObj.ObjectMeta.UID, newObj.ObjectMeta.Labels, ns.namespace.ObjectMeta.Labels, newObj.Status.PodIP, podComment(newObj))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if oldObj.Status.PodIP != newObj.Status.PodIP {
|
||||
if err := ns.updateDefaultAllowIPSetEntry(oldObj, newObj, ns.ingressDefaultAllowIPSet); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.updateDefaultAllowIPSetEntry(oldObj, newObj, ns.egressDefaultAllowIPSet); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
oldMatch := ps.matchesPodSelector(oldObj.ObjectMeta.Labels)
|
||||
newMatch := ps.matchesPodSelector(newObj.ObjectMeta.Labels)
|
||||
if oldMatch == newMatch && oldObj.Status.PodIP == newObj.Status.PodIP {
|
||||
continue
|
||||
}
|
||||
if oldMatch {
|
||||
if err := ps.delEntry(oldObj.Status.PodIP); err != nil {
|
||||
if err := ps.delEntry(oldObj.ObjectMeta.UID, oldObj.Status.PodIP); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if newMatch {
|
||||
if err := ps.addEntry(newObj.Status.PodIP); err != nil {
|
||||
if err := ps.addEntry(newObj.ObjectMeta.UID, newObj.Status.PodIP, podComment(newObj)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := ns.addOrRemoveToDefaultAllowIPSet(ps, oldObj, newObj, oldMatch, newMatch, policyTypeIngress); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.addOrRemoveToDefaultAllowIPSet(ps, oldObj, newObj, oldMatch, newMatch, policyTypeEgress); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, ps := range ns.namespacedPodsSelectors.entries {
|
||||
oldMatch := ps.matchesNamespacedPodSelector(oldObj.ObjectMeta.Labels, ns.namespace.ObjectMeta.Labels)
|
||||
newMatch := ps.matchesNamespacedPodSelector(newObj.ObjectMeta.Labels, ns.namespace.ObjectMeta.Labels)
|
||||
if oldMatch == newMatch && oldObj.Status.PodIP == newObj.Status.PodIP {
|
||||
continue
|
||||
}
|
||||
if oldMatch {
|
||||
if err := ps.delEntry(oldObj.ObjectMeta.UID, oldObj.Status.PodIP); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if newMatch {
|
||||
if err := ps.addEntry(newObj.ObjectMeta.UID, newObj.Status.PodIP, podComment(newObj)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -132,6 +300,23 @@ func (ns *ns) updatePod(oldObj, newObj *coreapi.Pod) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *ns) addOrRemoveToDefaultAllowIPSet(ps *selector, oldObj, newObj *coreapi.Pod, oldMatch, newMatch bool, policyType policyType) error {
|
||||
ipset := ns.defaultAllowIPSetName(policyType)
|
||||
if ns.podSelectors.targetSelectorExist(ps, policyType) {
|
||||
switch {
|
||||
case !oldMatch && newMatch:
|
||||
if err := ns.ips.DelEntry(oldObj.ObjectMeta.UID, ipset, oldObj.Status.PodIP); err != nil {
|
||||
return err
|
||||
}
|
||||
case oldMatch && !newMatch:
|
||||
if err := ns.addToDefaultAllowIfNoMatching(newObj, policyType); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *ns) deletePod(obj *coreapi.Pod) error {
|
||||
delete(ns.pods, obj.ObjectMeta.UID)
|
||||
|
||||
@@ -139,147 +324,207 @@ func (ns *ns) deletePod(obj *coreapi.Pod) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
return ns.podSelectors.delFromMatching(obj.ObjectMeta.Labels, obj.Status.PodIP)
|
||||
if err := ns.ips.DelEntry(obj.ObjectMeta.UID, ns.ingressDefaultAllowIPSet, obj.Status.PodIP); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.ips.DelEntry(obj.ObjectMeta.UID, ns.egressDefaultAllowIPSet, obj.Status.PodIP); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.podSelectors.delFromMatchingPodSelector(obj.ObjectMeta.UID, obj.ObjectMeta.Labels, obj.Status.PodIP); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.namespacedPodsSelectors.delFromMatchingNamespacedPodSelector(obj.ObjectMeta.UID, obj.ObjectMeta.Labels, ns.namespace.ObjectMeta.Labels, obj.Status.PodIP); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *ns) addNetworkPolicy(obj *extnapi.NetworkPolicy) error {
|
||||
ns.policies[obj.ObjectMeta.UID] = obj
|
||||
|
||||
func (ns *ns) addNetworkPolicy(obj interface{}) error {
|
||||
// Analyse policy, determine which rules and ipsets are required
|
||||
rules, nsSelectors, podSelectors, err := ns.analysePolicy(obj)
|
||||
|
||||
uid, rules, nsSelectors, podSelectors, namespacedPodsSelectors, ipBlocks, err := ns.analyse(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Provision required resources in dependency order
|
||||
if err := ns.nsSelectors.provision(obj.ObjectMeta.UID, nil, nsSelectors); err != nil {
|
||||
if err := ns.nsSelectors.provision(uid, nil, nsSelectors); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.podSelectors.provision(obj.ObjectMeta.UID, nil, podSelectors); err != nil {
|
||||
if err := ns.podSelectors.provision(uid, nil, podSelectors); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.rules.provision(obj.ObjectMeta.UID, nil, rules); err != nil {
|
||||
if err := ns.namespacedPodsSelectors.provision(uid, nil, namespacedPodsSelectors); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
if err := ns.ipBlocks.provision(uid, nil, ipBlocks); err != nil {
|
||||
return err
|
||||
}
|
||||
return ns.rules.provision(uid, nil, rules)
|
||||
}
|
||||
|
||||
func (ns *ns) updateNetworkPolicy(oldObj, newObj *extnapi.NetworkPolicy) error {
|
||||
delete(ns.policies, oldObj.ObjectMeta.UID)
|
||||
ns.policies[newObj.ObjectMeta.UID] = newObj
|
||||
|
||||
func (ns *ns) updateNetworkPolicy(oldObj, newObj interface{}) error {
|
||||
// Analyse the old and the new policy so we can determine differences
|
||||
oldRules, oldNsSelectors, oldPodSelectors, err := ns.analysePolicy(oldObj)
|
||||
oldUID, oldRules, oldNsSelectors, oldPodSelectors, oldNamespacedPodsSelectors, oldIPBlocks, err := ns.analyse(oldObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
newRules, newNsSelectors, newPodSelectors, err := ns.analysePolicy(newObj)
|
||||
newUID, newRules, newNsSelectors, newPodSelectors, newNamespacedPodsSelectors, newIPBlocks, err := ns.analyse(newObj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(ns.policies, oldUID)
|
||||
ns.policies[newUID] = newObj
|
||||
|
||||
// Deprovision unused and provision newly required resources in dependency order
|
||||
if err := ns.rules.deprovision(oldObj.ObjectMeta.UID, oldRules, newRules); err != nil {
|
||||
if err := ns.rules.deprovision(oldUID, oldRules, newRules); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.nsSelectors.deprovision(oldObj.ObjectMeta.UID, oldNsSelectors, newNsSelectors); err != nil {
|
||||
if err := ns.nsSelectors.deprovision(oldUID, oldNsSelectors, newNsSelectors); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.podSelectors.deprovision(oldObj.ObjectMeta.UID, oldPodSelectors, newPodSelectors); err != nil {
|
||||
if err := ns.podSelectors.deprovision(oldUID, oldPodSelectors, newPodSelectors); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.nsSelectors.provision(oldObj.ObjectMeta.UID, oldNsSelectors, newNsSelectors); err != nil {
|
||||
if err := ns.namespacedPodsSelectors.deprovision(oldUID, oldNamespacedPodsSelectors, newNamespacedPodsSelectors); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.podSelectors.provision(oldObj.ObjectMeta.UID, oldPodSelectors, newPodSelectors); err != nil {
|
||||
if err := ns.ipBlocks.deprovision(oldUID, oldIPBlocks, newIPBlocks); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.rules.provision(oldObj.ObjectMeta.UID, oldRules, newRules); err != nil {
|
||||
if err := ns.nsSelectors.provision(oldUID, oldNsSelectors, newNsSelectors); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
if err := ns.podSelectors.provision(oldUID, oldPodSelectors, newPodSelectors); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.namespacedPodsSelectors.provision(oldUID, oldNamespacedPodsSelectors, newNamespacedPodsSelectors); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.ipBlocks.provision(oldUID, oldIPBlocks, newIPBlocks); err != nil {
|
||||
return err
|
||||
}
|
||||
return ns.rules.provision(oldUID, oldRules, newRules)
|
||||
}
|
||||
|
||||
func (ns *ns) deleteNetworkPolicy(obj *extnapi.NetworkPolicy) error {
|
||||
delete(ns.policies, obj.ObjectMeta.UID)
|
||||
|
||||
func (ns *ns) deleteNetworkPolicy(obj interface{}) error {
|
||||
// Analyse network policy to free resources
|
||||
rules, nsSelectors, podSelectors, err := ns.analysePolicy(obj)
|
||||
uid, rules, nsSelectors, podSelectors, namespacedPodsSelectors, ipBlocks, err := ns.analyse(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(ns.policies, uid)
|
||||
|
||||
// Deprovision unused resources in dependency order
|
||||
if err := ns.rules.deprovision(obj.ObjectMeta.UID, rules, nil); err != nil {
|
||||
if err := ns.rules.deprovision(uid, rules, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.nsSelectors.deprovision(obj.ObjectMeta.UID, nsSelectors, nil); err != nil {
|
||||
if err := ns.nsSelectors.deprovision(uid, nsSelectors, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.podSelectors.deprovision(obj.ObjectMeta.UID, podSelectors, nil); err != nil {
|
||||
if err := ns.podSelectors.deprovision(uid, podSelectors, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.namespacedPodsSelectors.deprovision(uid, namespacedPodsSelectors, nil); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.ipBlocks.deprovision(uid, ipBlocks, 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) updateDefaultAllowIPSetEntry(oldObj, newObj *coreapi.Pod, ipsetName ipset.Name) error {
|
||||
// Instead of iterating over all selectors we check whether old pod IP
|
||||
// has been inserted into default-allow ipset to decide whether the IP
|
||||
// in the ipset has to be updated.
|
||||
if ns.ips.Exist(oldObj.ObjectMeta.UID, ipsetName, oldObj.Status.PodIP) {
|
||||
|
||||
if err := ns.ips.DelEntry(oldObj.ObjectMeta.UID, ipsetName, oldObj.Status.PodIP); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.ips.AddEntry(newObj.ObjectMeta.UID, ipsetName, newObj.Status.PodIP, podComment(newObj)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *ns) ensureBypassRule(nsIpsetName ipset.Name) error {
|
||||
return ns.ipt.Append(TableFilter, DefaultChain, bypassRule(ns.allPods.ipsetName)...)
|
||||
func bypassRules(namespace string, ingress, egress ipset.Name) map[string][][]string {
|
||||
return map[string][][]string{
|
||||
DefaultChain: {
|
||||
{"-m", "set", "--match-set", string(ingress), "dst", "-j", "ACCEPT",
|
||||
"-m", "comment", "--comment", "DefaultAllow ingress isolation for namespace: " + namespace},
|
||||
},
|
||||
EgressDefaultChain: {
|
||||
{"-m", "set", "--match-set", string(egress), "src", "-j", EgressMarkChain,
|
||||
"-m", "comment", "--comment", "DefaultAllow egress isolation for namespace: " + namespace},
|
||||
{"-m", "set", "--match-set", string(egress), "src", "-j", "RETURN",
|
||||
"-m", "comment", "--comment", "DefaultAllow egress isolation for namespace: " + namespace},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (ns *ns) deleteBypassRule(nsIpsetName ipset.Name) error {
|
||||
return ns.ipt.Delete(TableFilter, DefaultChain, bypassRule(ns.allPods.ipsetName)...)
|
||||
func (ns *ns) ensureBypassRules() error {
|
||||
for chain, rules := range bypassRules(ns.name, ns.ingressDefaultAllowIPSet, ns.egressDefaultAllowIPSet) {
|
||||
for _, rule := range rules {
|
||||
common.Log.Debugf("adding rule for DefaultAllow in namespace: %s, chain: %s, %s", ns.name, chain, rule)
|
||||
if err := ns.ipt.Append(TableFilter, chain, rule...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *ns) deleteBypassRules() error {
|
||||
for chain, rules := range bypassRules(ns.name, ns.ingressDefaultAllowIPSet, ns.egressDefaultAllowIPSet) {
|
||||
for _, rule := range rules {
|
||||
common.Log.Debugf("removing rule for DefaultAllow in namespace: %s, chain: %s, %s", ns.name, chain, rule)
|
||||
if err := ns.ipt.Delete(TableFilter, chain, rule...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ns *ns) addNamespace(obj *coreapi.Namespace) error {
|
||||
ns.namespace = obj
|
||||
|
||||
// Insert a rule to bypass policies if namespace is DefaultAllow
|
||||
if !isDefaultDeny(obj) {
|
||||
return ns.ensureBypassRule(ns.allPods.ipsetName)
|
||||
// Insert a rule to bypass policies
|
||||
if err := ns.ensureBypassRules(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Add namespace ipset to matching namespace selectors
|
||||
return ns.nsSelectors.addToMatching(obj.ObjectMeta.Labels, string(ns.allPods.ipsetName))
|
||||
err := ns.nsSelectors.addToMatchingNamespaceSelector(obj.ObjectMeta.UID, obj.ObjectMeta.Labels, string(ns.allPods.ipsetName), namespaceComment(ns))
|
||||
return err
|
||||
}
|
||||
|
||||
func (ns *ns) updateNamespace(oldObj, newObj *coreapi.Namespace) error {
|
||||
ns.namespace = newObj
|
||||
|
||||
// Update bypass rule if ingress default has changed
|
||||
oldDefaultDeny := 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)
|
||||
oldMatch := selector.matchesNamespaceSelector(oldObj.ObjectMeta.Labels)
|
||||
newMatch := selector.matchesNamespaceSelector(newObj.ObjectMeta.Labels)
|
||||
if oldMatch == newMatch {
|
||||
continue
|
||||
}
|
||||
if oldMatch {
|
||||
if err := selector.delEntry(string(ns.allPods.ipsetName)); err != nil {
|
||||
if err := selector.delEntry(ns.namespace.ObjectMeta.UID, string(ns.allPods.ipsetName)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if newMatch {
|
||||
if err := selector.addEntry(string(ns.allPods.ipsetName)); err != nil {
|
||||
if err := selector.addEntry(ns.namespace.ObjectMeta.UID, string(ns.allPods.ipsetName), namespaceComment(ns)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -293,12 +538,24 @@ func (ns *ns) deleteNamespace(obj *coreapi.Namespace) error {
|
||||
ns.namespace = nil
|
||||
|
||||
// Remove bypass rule
|
||||
if !isDefaultDeny(obj) {
|
||||
return ns.deleteBypassRule(ns.allPods.ipsetName)
|
||||
if err := ns.deleteBypassRules(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Remove namespace ipset from any matching namespace selectors
|
||||
return ns.nsSelectors.delFromMatching(obj.ObjectMeta.Labels, string(ns.allPods.ipsetName))
|
||||
err := ns.nsSelectors.delFromMatchingNamespaceSelector(obj.ObjectMeta.UID, obj.ObjectMeta.Labels, string(ns.allPods.ipsetName))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ns.ips.Destroy(ns.ingressDefaultAllowIPSet); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ns.ips.Destroy(ns.egressDefaultAllowIPSet); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func hasIP(pod *coreapi.Pod) bool {
|
||||
@@ -319,20 +576,45 @@ func equals(a, b map[string]string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func isDefaultDeny(namespace *coreapi.Namespace) bool {
|
||||
nnpJSON, found := namespace.ObjectMeta.Annotations["net.beta.kubernetes.io/network-policy"]
|
||||
if !found {
|
||||
return false
|
||||
func namespaceComment(namespace *ns) string {
|
||||
return "namespace: " + namespace.name
|
||||
}
|
||||
|
||||
func podComment(pod *coreapi.Pod) string {
|
||||
return fmt.Sprintf("namespace: %s, pod: %s", pod.ObjectMeta.Namespace, pod.ObjectMeta.Name)
|
||||
}
|
||||
|
||||
func (ns *ns) defaultAllowIPSetName(pt policyType) ipset.Name {
|
||||
ipset := ns.ingressDefaultAllowIPSet
|
||||
if pt == policyTypeEgress {
|
||||
ipset = ns.egressDefaultAllowIPSet
|
||||
}
|
||||
return ipset
|
||||
}
|
||||
|
||||
func (ns *ns) analyse(obj interface{}) (
|
||||
uid types.UID,
|
||||
rules map[string]*ruleSpec,
|
||||
nsSelectors, podSelectors, namespacedPodsSelectors map[string]*selectorSpec,
|
||||
ipBlocks map[string]*ipBlockSpec,
|
||||
err error) {
|
||||
|
||||
switch p := obj.(type) {
|
||||
case *extnapi.NetworkPolicy:
|
||||
uid = p.ObjectMeta.UID
|
||||
case *networkingv1.NetworkPolicy:
|
||||
uid = p.ObjectMeta.UID
|
||||
default:
|
||||
err = errInvalidNetworkPolicyObjType
|
||||
return
|
||||
}
|
||||
ns.policies[uid] = obj
|
||||
|
||||
var nnp NamespaceNetworkPolicy
|
||||
if err := json.Unmarshal([]byte(nnpJSON), &nnp); err != nil {
|
||||
common.Log.Warn("Ignoring network policy annotation: unmarshal failed:", err)
|
||||
// If we can't understand the annotation, behave as if it isn't present
|
||||
return false
|
||||
// Analyse policy, determine which rules and ipsets are required
|
||||
rules, nsSelectors, podSelectors, namespacedPodsSelectors, ipBlocks, err = ns.analysePolicy(obj.(*networkingv1.NetworkPolicy))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
return nnp.Ingress != nil &&
|
||||
nnp.Ingress.Isolation != nil &&
|
||||
*(nnp.Ingress.Isolation) == DefaultDeny
|
||||
return
|
||||
}
|
||||
+71
-20
@@ -1,45 +1,89 @@
|
||||
package npc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"k8s.io/client-go/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/npc/iptables"
|
||||
)
|
||||
|
||||
type ruleSpec struct {
|
||||
key string
|
||||
args []string
|
||||
type ruleHost interface {
|
||||
// getRuleSpec returns source or destination specification and comment which
|
||||
// are used in an iptables rule.
|
||||
//
|
||||
// If src=true then the rulespec is for source, otherwise - for destination.
|
||||
getRuleSpec(src bool) ([]string, string)
|
||||
}
|
||||
|
||||
func newRuleSpec(proto *string, srcHost *selectorSpec, dstHost *selectorSpec, dstPort *string) *ruleSpec {
|
||||
type ruleSpec struct {
|
||||
key string
|
||||
args []string
|
||||
policyType policyType
|
||||
}
|
||||
|
||||
func newRuleSpec(policyType policyType, proto *string, srcHost, dstHost ruleHost, dstPort *string) *ruleSpec {
|
||||
args := []string{}
|
||||
if proto != nil {
|
||||
args = append(args, "-p", *proto)
|
||||
}
|
||||
if srcHost != nil {
|
||||
args = append(args, "-m", "set", "--match-set", string(srcHost.ipsetName), "src")
|
||||
srcComment := "anywhere"
|
||||
if srcHost != nil && !reflect.ValueOf(srcHost).IsNil() {
|
||||
rule, comment := srcHost.getRuleSpec(true)
|
||||
args = append(args, rule...)
|
||||
srcComment = comment
|
||||
}
|
||||
if dstHost != nil {
|
||||
args = append(args, "-m", "set", "--match-set", string(dstHost.ipsetName), "dst")
|
||||
dstComment := "anywhere"
|
||||
if dstHost != nil && !reflect.ValueOf(dstHost).IsNil() {
|
||||
rule, comment := dstHost.getRuleSpec(false)
|
||||
args = append(args, rule...)
|
||||
dstComment = comment
|
||||
}
|
||||
if dstPort != nil {
|
||||
args = append(args, "--dport", *dstPort)
|
||||
}
|
||||
args = append(args, "-j", "ACCEPT")
|
||||
// NOTE: if you remove the comment bellow, then embed `policyType` into `key`.
|
||||
// Otherwise, the rule won't be provisioned if it exists for other policy type.
|
||||
args = append(args, "-m", "comment", "--comment", fmt.Sprintf("%s -> %s (%s)", srcComment, dstComment, policyTypeStr(policyType)))
|
||||
key := strings.Join(args, " ")
|
||||
|
||||
return &ruleSpec{key, args}
|
||||
return &ruleSpec{key, args, policyType}
|
||||
}
|
||||
|
||||
func (spec *ruleSpec) iptChain() string {
|
||||
if spec.policyType == policyTypeEgress {
|
||||
return EgressCustomChain
|
||||
}
|
||||
return IngressChain
|
||||
}
|
||||
|
||||
func (spec *ruleSpec) iptRuleSpecs() [][]string {
|
||||
if spec.policyType == policyTypeIngress {
|
||||
rule := make([]string, len(spec.args))
|
||||
copy(rule, spec.args)
|
||||
rule = append(rule, "-j", "ACCEPT")
|
||||
return [][]string{rule}
|
||||
}
|
||||
|
||||
// policyTypeEgress
|
||||
ruleMark := make([]string, len(spec.args))
|
||||
copy(ruleMark, spec.args)
|
||||
ruleMark = append(ruleMark, "-j", EgressMarkChain)
|
||||
ruleReturn := make([]string, len(spec.args))
|
||||
copy(ruleReturn, spec.args)
|
||||
ruleReturn = append(ruleReturn, "-j", "RETURN")
|
||||
return [][]string{ruleMark, ruleReturn}
|
||||
}
|
||||
|
||||
type ruleSet struct {
|
||||
ipt *iptables.IPTables
|
||||
ipt iptables.Interface
|
||||
users map[string]map[types.UID]struct{}
|
||||
}
|
||||
|
||||
func newRuleSet(ipt *iptables.IPTables) *ruleSet {
|
||||
func newRuleSet(ipt iptables.Interface) *ruleSet {
|
||||
return &ruleSet{ipt, make(map[string]map[types.UID]struct{})}
|
||||
}
|
||||
|
||||
@@ -48,10 +92,14 @@ func (rs *ruleSet) deprovision(user types.UID, current, desired map[string]*rule
|
||||
if _, found := desired[key]; !found {
|
||||
delete(rs.users[key], user)
|
||||
if len(rs.users[key]) == 0 {
|
||||
common.Log.Infof("deleting rule: %v", spec.args)
|
||||
if err := rs.ipt.Delete(TableFilter, IngressChain, spec.args...); err != nil {
|
||||
return err
|
||||
chain := spec.iptChain()
|
||||
for _, rule := range spec.iptRuleSpecs() {
|
||||
common.Log.Infof("deleting rule %v from %q chain", rule, chain)
|
||||
if err := rs.ipt.Delete(TableFilter, chain, rule...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
delete(rs.users, key)
|
||||
}
|
||||
}
|
||||
@@ -64,9 +112,12 @@ func (rs *ruleSet) provision(user types.UID, current, desired map[string]*ruleSp
|
||||
for key, spec := range desired {
|
||||
if _, found := current[key]; !found {
|
||||
if _, found := rs.users[key]; !found {
|
||||
common.Log.Infof("adding rule: %v", spec.args)
|
||||
if err := rs.ipt.Append(TableFilter, IngressChain, spec.args...); err != nil {
|
||||
return err
|
||||
chain := spec.iptChain()
|
||||
for _, rule := range spec.iptRuleSpecs() {
|
||||
common.Log.Infof("adding rule %v to %q chain", rule, chain)
|
||||
if err := rs.ipt.Append(TableFilter, chain, rule...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
rs.users[key] = make(map[types.UID]struct{})
|
||||
}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
package npc
|
||||
|
||||
import (
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRegressionHandleNil3095(t *testing.T) {
|
||||
// Test for handling nil values
|
||||
// https://github.com/weaveworks/weave/issues/3095
|
||||
|
||||
rs := newRuleSpec(policyTypeIngress, nil, nil, nil, nil)
|
||||
|
||||
require.NotEqual(t, rs.key, "")
|
||||
|
||||
}
|
||||
+175
-37
@@ -1,36 +1,73 @@
|
||||
package npc
|
||||
|
||||
import (
|
||||
"k8s.io/client-go/pkg/api/unversioned"
|
||||
"k8s.io/client-go/pkg/labels"
|
||||
"k8s.io/client-go/pkg/types"
|
||||
"fmt"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
"github.com/weaveworks/weave/common"
|
||||
"github.com/weaveworks/weave/npc/ipset"
|
||||
"github.com/weaveworks/weave/net/ipset"
|
||||
)
|
||||
|
||||
type selectorSpec struct {
|
||||
key string // string representation (for hash keying/equality comparison)
|
||||
selector labels.Selector // k8s Selector object (for matching)
|
||||
key string // string representation (for hash keying/equality comparison)
|
||||
podSelector labels.Selector // k8s Selector object (for matching pods)
|
||||
namespaceSelector labels.Selector // k8s Selector object (for matching namespaces)
|
||||
policyTypes []policyType // If non-empty, then selectorSpec is a target selector for given policyTypes.
|
||||
|
||||
ipsetType ipset.Type // type of ipset to provision
|
||||
ipsetName ipset.Name // generated ipset name
|
||||
nsName string // Namespace name
|
||||
}
|
||||
|
||||
func newSelectorSpec(json *unversioned.LabelSelector, nsName string, ipsetType ipset.Type) (*selectorSpec, error) {
|
||||
selector, err := unversioned.LabelSelectorAsSelector(json)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
func newSelectorSpec(podJSON *metav1.LabelSelector, namespaceJSON *metav1.LabelSelector, policyType []policyType, nsName string, ipsetType ipset.Type) (*selectorSpec, error) {
|
||||
var podSelector, namespaceSelector labels.Selector
|
||||
var err error
|
||||
var key string
|
||||
if namespaceJSON != nil {
|
||||
namespaceSelector, err = metav1.LabelSelectorAsSelector(namespaceJSON)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key = namespaceSelector.String()
|
||||
}
|
||||
if podJSON != nil {
|
||||
podSelector, err = metav1.LabelSelectorAsSelector(podJSON)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
key = key + podSelector.String()
|
||||
}
|
||||
key := selector.String()
|
||||
return &selectorSpec{
|
||||
key: key,
|
||||
selector: selector,
|
||||
key: key,
|
||||
podSelector: podSelector,
|
||||
namespaceSelector: namespaceSelector,
|
||||
policyTypes: policyType,
|
||||
// We prefix the selector string with the namespace name when generating
|
||||
// the shortname because you can specify the same selector in multiple
|
||||
// namespaces - we need those to map to distinct ipsets
|
||||
ipsetName: ipset.Name("weave-" + shortName(nsName+":"+key)),
|
||||
ipsetType: ipsetType}, nil
|
||||
ipsetName: ipset.Name(IpsetNamePrefix + shortName(nsName+":"+key)),
|
||||
ipsetType: ipsetType,
|
||||
nsName: nsName}, nil
|
||||
}
|
||||
|
||||
func (spec *selectorSpec) getRuleSpec(src bool) ([]string, string) {
|
||||
dir := "dst"
|
||||
if src {
|
||||
dir = "src"
|
||||
}
|
||||
rule := []string{"-m", "set", "--match-set", string(spec.ipsetName), dir}
|
||||
|
||||
comment := "anywhere"
|
||||
if spec.nsName != "" {
|
||||
comment = fmt.Sprintf("pods: namespace: %s, selector: %s", spec.nsName, spec.key)
|
||||
} else {
|
||||
comment = fmt.Sprintf("namespaces: selector: %s", spec.key)
|
||||
}
|
||||
|
||||
return rule, comment
|
||||
}
|
||||
|
||||
type selector struct {
|
||||
@@ -38,41 +75,81 @@ type selector struct {
|
||||
spec *selectorSpec
|
||||
}
|
||||
|
||||
func (s *selector) matches(labelMap map[string]string) bool {
|
||||
return s.spec.selector.Matches(labels.Set(labelMap))
|
||||
func (s *selector) matchesPodSelector(podsLabelMap map[string]string) bool {
|
||||
return s.spec.podSelector.Matches(labels.Set(podsLabelMap))
|
||||
}
|
||||
|
||||
func (s *selector) addEntry(entry string) error {
|
||||
common.Log.Infof("adding entry %s to %s", entry, s.spec.ipsetName)
|
||||
return s.ips.AddEntry(s.spec.ipsetName, entry)
|
||||
func (s *selector) matchesNamespaceSelector(namespaceLabelMap map[string]string) bool {
|
||||
return s.spec.namespaceSelector.Matches(labels.Set(namespaceLabelMap))
|
||||
}
|
||||
|
||||
func (s *selector) delEntry(entry string) error {
|
||||
common.Log.Infof("deleting entry %s from %s", entry, s.spec.ipsetName)
|
||||
return s.ips.DelEntry(s.spec.ipsetName, entry)
|
||||
func (s *selector) matchesNamespacedPodSelector(podsLabelMap, namespaceLabelMap map[string]string) bool {
|
||||
return s.spec.podSelector.Matches(labels.Set(podsLabelMap)) && s.spec.namespaceSelector.Matches(labels.Set(namespaceLabelMap))
|
||||
}
|
||||
|
||||
func (s *selector) addEntry(user types.UID, entry string, comment string) error {
|
||||
return s.ips.AddEntry(user, s.spec.ipsetName, entry, comment)
|
||||
}
|
||||
|
||||
func (s *selector) delEntry(user types.UID, entry string) error {
|
||||
return s.ips.DelEntry(user, s.spec.ipsetName, entry)
|
||||
}
|
||||
|
||||
type selectorFn func(selector *selector) error
|
||||
type selectorWithPolicyTypeFn func(selector *selector, policyType policyType) 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
|
||||
|
||||
// invoked after target selector has been provisioned for the first time
|
||||
onNewTargetSelector selectorWithPolicyTypeFn
|
||||
// invoked after the last instance of target selector has been deprovisioned
|
||||
onDestroyTargetSelector selectorWithPolicyTypeFn
|
||||
|
||||
users map[string]map[types.UID]struct{} // list of users per selector
|
||||
entries map[string]*selector
|
||||
|
||||
// We need to keep track of target selector instances to be able to invoke
|
||||
// onNewTargetSelector and onDestroyTargetSelector callbacks at the right time;
|
||||
// selectorSpec.Key -> policyType -> count
|
||||
targetSelectorsCount map[string]map[policyType]int
|
||||
}
|
||||
|
||||
func newSelectorSet(ips ipset.Interface, onNewSelector selectorFn) *selectorSet {
|
||||
func newSelectorSet(ips ipset.Interface, onNewSelector selectorFn, onNewTargetSelector, onDestroyTargetSelector selectorWithPolicyTypeFn) *selectorSet {
|
||||
return &selectorSet{
|
||||
ips: ips,
|
||||
onNewSelector: onNewSelector,
|
||||
users: make(map[string]map[types.UID]struct{}),
|
||||
entries: make(map[string]*selector)}
|
||||
ips: ips,
|
||||
onNewSelector: onNewSelector,
|
||||
onNewTargetSelector: onNewTargetSelector,
|
||||
onDestroyTargetSelector: onDestroyTargetSelector,
|
||||
users: make(map[string]map[types.UID]struct{}),
|
||||
entries: make(map[string]*selector),
|
||||
targetSelectorsCount: make(map[string]map[policyType]int)}
|
||||
}
|
||||
|
||||
func (ss *selectorSet) addToMatching(labelMap map[string]string, entry string) error {
|
||||
func (ss *selectorSet) addToMatchingPodSelector(user types.UID, podLabelsMap map[string]string, entry string, comment string) (bool, bool, error) {
|
||||
foundIngress := false
|
||||
foundEgress := false
|
||||
for _, s := range ss.entries {
|
||||
if s.matches(labelMap) {
|
||||
if err := s.addEntry(entry); err != nil {
|
||||
if s.matchesPodSelector(podLabelsMap) {
|
||||
if ss.targetSelectorExist(s, policyTypeIngress) {
|
||||
foundIngress = true
|
||||
}
|
||||
if ss.targetSelectorExist(s, policyTypeEgress) {
|
||||
foundEgress = true
|
||||
}
|
||||
if err := s.addEntry(user, entry, comment); err != nil {
|
||||
return foundIngress, foundEgress, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return foundIngress, foundEgress, nil
|
||||
}
|
||||
|
||||
func (ss *selectorSet) addToMatchingNamespaceSelector(user types.UID, namespaceLabelsMap map[string]string, entry string, comment string) error {
|
||||
for _, s := range ss.entries {
|
||||
if s.matchesNamespaceSelector(namespaceLabelsMap) {
|
||||
if err := s.addEntry(user, entry, comment); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -80,10 +157,10 @@ func (ss *selectorSet) addToMatching(labelMap map[string]string, entry string) e
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ss *selectorSet) delFromMatching(labelMap map[string]string, entry string) error {
|
||||
func (ss *selectorSet) addToMatchingNamespacedPodSelector(user types.UID, podLabelsMap map[string]string, namespaceLabelsMap map[string]string, entry string, comment string) error {
|
||||
for _, s := range ss.entries {
|
||||
if s.matches(labelMap) {
|
||||
if err := s.delEntry(entry); err != nil {
|
||||
if s.matchesNamespacedPodSelector(podLabelsMap, namespaceLabelsMap) {
|
||||
if err := s.addEntry(user, entry, comment); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@@ -91,6 +168,43 @@ func (ss *selectorSet) delFromMatching(labelMap map[string]string, entry string)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ss *selectorSet) delFromMatchingPodSelector(user types.UID, podLabelsMap map[string]string, entry string) error {
|
||||
for _, s := range ss.entries {
|
||||
if s.matchesPodSelector(podLabelsMap) {
|
||||
if err := s.delEntry(user, entry); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ss *selectorSet) delFromMatchingNamespaceSelector(user types.UID, namespaceLabelsMap map[string]string, entry string) error {
|
||||
for _, s := range ss.entries {
|
||||
if s.matchesNamespaceSelector(namespaceLabelsMap) {
|
||||
if err := s.delEntry(user, entry); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ss *selectorSet) delFromMatchingNamespacedPodSelector(user types.UID, podLabelsMap map[string]string, namespaceLabelsMap map[string]string, entry string) error {
|
||||
for _, s := range ss.entries {
|
||||
if s.matchesNamespacedPodSelector(podLabelsMap, namespaceLabelsMap) {
|
||||
if err := s.delEntry(user, entry); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ss *selectorSet) targetSelectorExist(s *selector, policyType policyType) bool {
|
||||
return ss.targetSelectorsCount[s.spec.key][policyType] > 0
|
||||
}
|
||||
|
||||
func (ss *selectorSet) deprovision(user types.UID, current, desired map[string]*selectorSpec) error {
|
||||
for key, spec := range current {
|
||||
if _, found := desired[key]; !found {
|
||||
@@ -100,9 +214,20 @@ func (ss *selectorSet) deprovision(user types.UID, current, desired map[string]*
|
||||
if err := ss.ips.Destroy(spec.ipsetName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
delete(ss.entries, key)
|
||||
delete(ss.users, key)
|
||||
}
|
||||
|
||||
for _, policyType := range spec.policyTypes {
|
||||
ss.targetSelectorsCount[key][policyType]--
|
||||
if ss.targetSelectorsCount[key][policyType] == 0 {
|
||||
if err := ss.onDestroyTargetSelector(&selector{ss.ips, spec}, policyType); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// TODO(brb) delete(...)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@@ -111,12 +236,13 @@ func (ss *selectorSet) deprovision(user types.UID, current, desired map[string]*
|
||||
func (ss *selectorSet) provision(user types.UID, current, desired map[string]*selectorSpec) error {
|
||||
for key, spec := range desired {
|
||||
if _, found := current[key]; !found {
|
||||
selector := &selector{ss.ips, spec}
|
||||
|
||||
if _, found := ss.users[key]; !found {
|
||||
common.Log.Infof("creating ipset: %#v", spec)
|
||||
if err := ss.ips.Create(spec.ipsetName, spec.ipsetType); err != nil {
|
||||
return err
|
||||
}
|
||||
selector := &selector{ss.ips, spec}
|
||||
if err := ss.onNewSelector(selector); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -124,6 +250,18 @@ func (ss *selectorSet) provision(user types.UID, current, desired map[string]*se
|
||||
ss.entries[key] = selector
|
||||
}
|
||||
ss.users[key][user] = struct{}{}
|
||||
|
||||
for _, pt := range spec.policyTypes {
|
||||
if _, found := ss.targetSelectorsCount[key]; !found {
|
||||
ss.targetSelectorsCount[key] = make(map[policyType]int)
|
||||
}
|
||||
ss.targetSelectorsCount[key][pt]++
|
||||
if ss.targetSelectorsCount[key][pt] == 1 {
|
||||
if err := ss.onNewTargetSelector(selector, pt); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
||||
+3
-2
@@ -7,12 +7,13 @@ import (
|
||||
|
||||
const (
|
||||
// This array:
|
||||
// * Must include only printable UTF8 characters that are represented with a single byte
|
||||
// * Must include only printable UTF8 characters that are represented with a single byte (http://www.utf8-chartable.de/)
|
||||
// * 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 invalid chars for XML `<>&"'`
|
||||
// * Should not include single quote or backslash to be nice to shell users
|
||||
ShortNameSymbols = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789(){}[]<>_$%^&*|/?.;:@#~"
|
||||
ShortNameSymbols = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789(){}[]+=_$%^!*|/?.;:@#~"
|
||||
)
|
||||
|
||||
// sha1 hash an arbitrary string and represent it using the full range of
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package npc
|
||||
|
||||
type policyType byte
|
||||
|
||||
const (
|
||||
policyTypeIngress policyType = iota
|
||||
policyTypeEgress
|
||||
)
|
||||
|
||||
func policyTypeStr(policyType policyType) string {
|
||||
if policyType == policyTypeEgress {
|
||||
return "egress"
|
||||
}
|
||||
return "ingress"
|
||||
}
|
||||
Loaded 100 of 3683 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user