Compare commits
180 Commits
Author | SHA1 | Date | |
---|---|---|---|
9b8de6a613 | |||
dacb671328 | |||
799d3cbf4c | |||
bd589992fb | |||
839d918e35 | |||
c50490eb76 | |||
01a8de9997 | |||
8a88f90f94 | |||
2b6cd5467f | |||
3aadb402e4 | |||
021462563b | |||
d713ec692c | |||
813f541d30 | |||
6eb8e31d21 | |||
051452cdcf | |||
877602d627 | |||
c90b165c6e | |||
28773dc925 | |||
2bd04cb92f | |||
d4775ecff5 | |||
e1f8f9bee5 | |||
68a80bcf9b | |||
1fb9793607 | |||
5cb3a5e897 | |||
b76fdd7c03 | |||
67175607ad | |||
b9560fd5c1 | |||
79192cb1f1 | |||
ad10b6fa91 | |||
219eb9e046 | |||
f7a2fc97e4 | |||
6957f6ca4e | |||
02bfece2e9 | |||
5af9ff493e | |||
44d92c19de | |||
5e0fbd8374 | |||
a78853f29f | |||
2d2583ee33 | |||
f4332fec59 | |||
ed16760739 | |||
30776ff858 | |||
2a48d68937 | |||
117e30ff21 | |||
486ef96e6f | |||
8a0e3fe10e | |||
ca419073e4 | |||
47a9fd80c8 | |||
112288ecb2 | |||
32fc3ee9d3 | |||
c7e2cf7602 | |||
53854dd948 | |||
5c512194eb | |||
a9b4e04bc4 | |||
f5c3d1b1ba | |||
8bf6a7b362 | |||
66e0aaf9c1 | |||
45fd949465 | |||
2ff84a481e | |||
37207f05b4 | |||
832f2699c2 | |||
d1360b82ab | |||
1f33fb729a | |||
754e153b03 | |||
0edf8a4208 | |||
db7e6cfabf | |||
963aaf86e6 | |||
cd9d6b28da | |||
0452c1dd10 | |||
d671d29ad5 | |||
cc6154603e | |||
62b36d2fbc | |||
e5fdd449dd | |||
8db5e4d41b | |||
ec8f6c99d0 | |||
7dea2a4c1b | |||
5a02c5bc61 | |||
bf8f171041 | |||
3603738c6a | |||
d8b1289098 | |||
6551165853 | |||
10a01b09ae | |||
497560f35f | |||
58dd90b996 | |||
d5efdfe1f6 | |||
05f121a406 | |||
825fbd8a95 | |||
1a30688da0 | |||
bee8d6cf30 | |||
a16232968d | |||
1880421389 | |||
a2ed3d9a69 | |||
7bcaae263f | |||
e1f955d9bf | |||
2583a0b4ad | |||
85083ea434 | |||
2290fc8d8a | |||
411d060b81 | |||
5915b49b38 | |||
c25c62742b | |||
b7ffa24326 | |||
15894b36a0 | |||
77b51f0bc9 | |||
bd63528b0b | |||
cf187287af | |||
0dff883769 | |||
d0eeb27494 | |||
e70558cbe1 | |||
0a1421a08c | |||
0f19aa2f8d | |||
e91889678b | |||
8ec6bd6a42 | |||
fc7059c1ae | |||
a96c469e62 | |||
291ab6cc84 | |||
90125f40ba | |||
23d5525ec3 | |||
fd42109a06 | |||
4bb288193c | |||
e8365e126d | |||
7e68430081 | |||
f81a529ebd | |||
630a4d8db6 | |||
3d56f7504d | |||
659a09f34e | |||
b76ace9c64 | |||
0d0dcfc02f | |||
485be65581 | |||
ca82120019 | |||
c9e1c0c1ed | |||
2d6d4b260a | |||
ad7c1d189b | |||
a069a5f1a3 | |||
ccd683e1a3 | |||
a11cb626b0 | |||
f36dbc2031 | |||
e9d511c5bc | |||
91a68d56f9 | |||
8902d2614a | |||
df9af9ab41 | |||
5e2e365291 | |||
4b68f56820 | |||
ded2f17577 | |||
57650a1e5b | |||
7ba2bcfeab | |||
3fb8dcfd4c | |||
966bbcb8a5 | |||
7d76537d4a | |||
f3b1ffc960 | |||
ce9560712e | |||
e2984e7840 | |||
eb1ff18c4c | |||
e8771b36a2 | |||
7f8ea631e5 | |||
0eddc554c0 | |||
e8a25e33cd | |||
96bd10f679 | |||
303299b7b2 | |||
d42007865a | |||
ce60e8eb3d | |||
addbcd34b4 | |||
e8c953999e | |||
13fbc4afdf | |||
545a77f4bb | |||
c204dbd47c | |||
660685a8af | |||
2b6808807f | |||
869d5ec873 | |||
93919752fb | |||
fe60fcddb8 | |||
e308211d34 | |||
29a431f1fc | |||
7d75ab66a4 | |||
d5c8b4b206 | |||
5a4085f1fa | |||
37d2ee1d5d | |||
7f9b1844b8 | |||
2753b9af8f | |||
d35c96dda6 | |||
344d343431 | |||
79b1c402c4 |
13
.travis.yml
13
.travis.yml
@ -1,31 +1,33 @@
|
||||
language: go
|
||||
sudo: required
|
||||
dist: trusty
|
||||
dist: xenial
|
||||
|
||||
go:
|
||||
- 1.10.x
|
||||
- 1.11.x
|
||||
- 1.13.x
|
||||
- 1.14.x
|
||||
|
||||
env:
|
||||
global:
|
||||
- PATH=$GOROOT/bin:$GOPATH/bin:$PATH
|
||||
- CGO_ENABLED=0
|
||||
matrix:
|
||||
- TARGET=386
|
||||
- TARGET=amd64
|
||||
- TARGET=arm
|
||||
- TARGET=arm64
|
||||
- TARGET=ppc64le
|
||||
- TARGET=s390x
|
||||
- TARGET=mips64le
|
||||
|
||||
matrix:
|
||||
fast_finish: true
|
||||
include:
|
||||
- os: windows
|
||||
env: TARGET=amd64
|
||||
go: 1.10.x
|
||||
go: 1.13.x
|
||||
- os: windows
|
||||
env: TARGET=amd64
|
||||
go: 1.11.x
|
||||
go: 1.14.x
|
||||
|
||||
install:
|
||||
- go get github.com/onsi/ginkgo/ginkgo
|
||||
@ -33,6 +35,7 @@ install:
|
||||
- go get golang.org/x/tools/cmd/cover
|
||||
- go get github.com/modocache/gover
|
||||
- go get github.com/mattn/goveralls
|
||||
- go mod vendor
|
||||
|
||||
script:
|
||||
- |
|
||||
|
@ -72,10 +72,11 @@ vagrant ssh
|
||||
# you're now in a shell in a virtual machine
|
||||
sudo su
|
||||
go get github.com/onsi/ginkgo/ginkgo
|
||||
go install github.com/containernetworking/cni/cnitool
|
||||
cd /go/src/github.com/containernetworking/plugins
|
||||
|
||||
# to run the full test suite
|
||||
./test.sh
|
||||
./test_linux.sh
|
||||
|
||||
# to focus on a particular test suite
|
||||
cd plugins/main/loopback
|
||||
|
367
Godeps/Godeps.json
generated
367
Godeps/Godeps.json
generated
@ -1,367 +0,0 @@
|
||||
{
|
||||
"ImportPath": "github.com/containernetworking/plugins",
|
||||
"GoVersion": "go1.7",
|
||||
"GodepVersion": "v80",
|
||||
"Packages": [
|
||||
"./..."
|
||||
],
|
||||
"Deps": [
|
||||
{
|
||||
"ImportPath": "github.com/Microsoft/go-winio",
|
||||
"Comment": "v0.4.11",
|
||||
"Rev": "97e4973ce50b2ff5f09635a57e2b88a037aae829"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Microsoft/hcsshim",
|
||||
"Comment": "v0.7.6",
|
||||
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Microsoft/hcsshim/internal/guid",
|
||||
"Comment": "v0.7.4",
|
||||
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Microsoft/hcsshim/internal/hcs",
|
||||
"Comment": "v0.7.4",
|
||||
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Microsoft/hcsshim/internal/hcserror",
|
||||
"Comment": "v0.7.4",
|
||||
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Microsoft/hcsshim/internal/hns",
|
||||
"Comment": "v0.7.4",
|
||||
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Microsoft/hcsshim/internal/interop",
|
||||
"Comment": "v0.7.4",
|
||||
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Microsoft/hcsshim/internal/longpath",
|
||||
"Comment": "v0.7.4",
|
||||
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Microsoft/hcsshim/internal/mergemaps",
|
||||
"Comment": "v0.7.4",
|
||||
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Microsoft/hcsshim/internal/safefile",
|
||||
"Comment": "v0.7.4",
|
||||
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Microsoft/hcsshim/internal/schema1",
|
||||
"Comment": "v0.7.4",
|
||||
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Microsoft/hcsshim/internal/timeout",
|
||||
"Comment": "v0.7.4",
|
||||
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/Microsoft/hcsshim/internal/wclayer",
|
||||
"Comment": "v0.7.4",
|
||||
"Rev": "e44e499d29527b244d6858772f1b9090eeaddc4e"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/alexflint/go-filemutex",
|
||||
"Rev": "72bdc8eae2aef913234599b837f5dda445ca9bd9"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/buger/jsonparser",
|
||||
"Rev": "f4dd9f5a6b441265aefc1d44872a6f8c10f42b37"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/containernetworking/cni/libcni",
|
||||
"Comment": "v0.7.0",
|
||||
"Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/containernetworking/cni/pkg/invoke",
|
||||
"Comment": "v0.7.0",
|
||||
"Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/containernetworking/cni/pkg/skel",
|
||||
"Comment": "v0.7.0",
|
||||
"Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/containernetworking/cni/pkg/types",
|
||||
"Comment": "v0.7.0",
|
||||
"Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/containernetworking/cni/pkg/types/020",
|
||||
"Comment": "v0.7.0",
|
||||
"Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/containernetworking/cni/pkg/types/current",
|
||||
"Comment": "v0.7.0",
|
||||
"Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/containernetworking/cni/pkg/version",
|
||||
"Comment": "v0.7.0",
|
||||
"Rev": "7d76556571b6cf1ab90d7026a73092ac8d5e0c23"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/go-iptables/iptables",
|
||||
"Comment": "v0.4.1",
|
||||
"Rev": "78b5fff24e6df8886ef8eca9411f683a884349a5"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/go-systemd/activation",
|
||||
"Comment": "v17",
|
||||
"Rev": "39ca1b05acc7ad1220e09f133283b8859a8b71ab"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/d2g/dhcp4",
|
||||
"Rev": "f0e4d29ff0231dce36e250b2ed9ff08412584bca"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/d2g/dhcp4client",
|
||||
"Rev": "bed07e1bc5b85f69c6f0fd73393aa35ec68ed892"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/d2g/dhcp4server",
|
||||
"Rev": "477b11cea4dcc56af002849238d4f9c1e093c744"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/d2g/dhcp4server/leasepool",
|
||||
"Rev": "477b11cea4dcc56af002849238d4f9c1e093c744"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/d2g/dhcp4server/leasepool/memorypool",
|
||||
"Rev": "477b11cea4dcc56af002849238d4f9c1e093c744"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/godbus/dbus",
|
||||
"Comment": "v4.1.0-6-g885f9cc",
|
||||
"Rev": "885f9cc04c9c1a6a61a2008e211d36c5737be3f5"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/j-keck/arping",
|
||||
"Rev": "2cf9dc699c5640a7e2c81403a44127bf28033600"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/juju/errors",
|
||||
"Rev": "22422dad46e14561a0854ad42497a75af9b61909"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/mattn/go-shellwords",
|
||||
"Comment": "v1.0.3",
|
||||
"Rev": "02e3cf038dcea8290e44424da473dd12be796a8a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/config",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/extensions/table",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/codelocation",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/containernode",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/failer",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/leafnodes",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/remote",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/spec",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/specrunner",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/suite",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/testingtproxy",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/internal/writer",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/reporters",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/reporters/stenographer",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/ginkgo/types",
|
||||
"Comment": "v1.2.0-29-g7f8ab55",
|
||||
"Rev": "7f8ab55aaf3b86885aa55b762e803744d1674700"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/format",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/gbytes",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/gexec",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/internal/assertion",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/internal/asyncassertion",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/internal/oraclematcher",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/internal/testingtsupport",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/matchers",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/bipartitegraph",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/edge",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/node",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/matchers/support/goraph/util",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/onsi/gomega/types",
|
||||
"Comment": "v1.0-71-g2152b45",
|
||||
"Rev": "2152b45fa28a361beba9aab0885972323a444e28"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/safchain/ethtool",
|
||||
"Rev": "7ff1ba29eca231991280817541cb3903f6be15d1"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/sirupsen/logrus",
|
||||
"Comment": "v1.0.6",
|
||||
"Rev": "3e01752db0189b9157070a0e1668a620f9a85da2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/vishvananda/netlink",
|
||||
"Comment": "v1.0.0-40-g023a6da",
|
||||
"Rev": "023a6dafdcdfa7068ac83b260ab7f03cd4131aca"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/vishvananda/netlink/nl",
|
||||
"Comment": "v1.0.0-40-g023a6da",
|
||||
"Rev": "023a6dafdcdfa7068ac83b260ab7f03cd4131aca"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/vishvananda/netns",
|
||||
"Rev": "13995c7128ccc8e51e9a6bd2b551020a27180abd"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/crypto/ssh/terminal",
|
||||
"Rev": "7c1a557ab941a71c619514f229f0b27ccb0c27cf"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/bpf",
|
||||
"Rev": "49bb7cea24b1df9410e1712aa6433dae904ff66a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/internal/iana",
|
||||
"Rev": "49bb7cea24b1df9410e1712aa6433dae904ff66a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/ipv4",
|
||||
"Rev": "49bb7cea24b1df9410e1712aa6433dae904ff66a"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/sys/unix",
|
||||
"Rev": "66b7b1311ac80bbafcd2daeef9a5e6e2cd1e2399"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/sys/windows",
|
||||
"Rev": "66b7b1311ac80bbafcd2daeef9a5e6e2cd1e2399"
|
||||
},
|
||||
{
|
||||
"ImportPath": "golang.org/x/net/internal/socket",
|
||||
"Rev": "49bb7cea24b1df9410e1712aa6433dae904ff66a"
|
||||
}
|
||||
]
|
||||
}
|
5
Godeps/Readme
generated
5
Godeps/Readme
generated
@ -1,5 +0,0 @@
|
||||
This directory tree is generated automatically by godep.
|
||||
|
||||
Please do not edit.
|
||||
|
||||
See https://github.com/tools/godep for more information.
|
@ -1,8 +1,10 @@
|
||||
# Owners
|
||||
This is the official list of the CNI network plugins owners:
|
||||
- Bruce Ma <brucema19901024@gmail.com> (@mars1024)
|
||||
- Bryan Boreham <bryan@weave.works> (@bboreham)
|
||||
- Casey Callendrello <casey.callendrello@coreos.com> (@squeed)
|
||||
- Casey Callendrello <cdc@redhat.com> (@squeed)
|
||||
- Dan Williams <dcbw@redhat.com> (@dcbw)
|
||||
- Gabe Rosenhouse <grosenhouse@pivotal.io> (@rosenhouse)
|
||||
- Matt Dupre <matt@tigera.io> (@matthewdupre)
|
||||
- Stefan Junker <stefan.junker@coreos.com> (@steveeJ)
|
||||
- Michael Cambria <mcambria@redhat.com> (@mccv1r0)
|
||||
- Piotr Skarmuk <piotr.skarmuk@gmail.com> (@jellonek)
|
||||
|
@ -32,3 +32,11 @@ Read [CONTRIBUTING](CONTRIBUTING.md) for build and test instructions.
|
||||
|
||||
### Sample
|
||||
The sample plugin provides an example for building your own plugin.
|
||||
|
||||
## Contact
|
||||
|
||||
For any questions about CNI, please reach out via:
|
||||
- Email: [cni-dev](https://groups.google.com/forum/#!forum/cni-dev)
|
||||
- Slack: #cni on the [CNCF slack](https://slack.cncf.io/).
|
||||
|
||||
If you have a _security_ issue to report, please do so privately to the email addresses listed in the [OWNERS](OWNERS.md) file.
|
||||
|
18
Vagrantfile
vendored
18
Vagrantfile
vendored
@ -1,18 +0,0 @@
|
||||
# -*- mode: ruby -*-
|
||||
# vi: set ft=ruby :
|
||||
|
||||
Vagrant.configure(2) do |config|
|
||||
config.vm.box = "bento/ubuntu-16.04"
|
||||
|
||||
config.vm.synced_folder "..", "/go/src/github.com/containernetworking"
|
||||
|
||||
config.vm.provision "shell", inline: <<-SHELL
|
||||
set -e -x -u
|
||||
apt-get update -y || (sleep 40 && apt-get update -y)
|
||||
apt-get install -y git gcc-multilib gcc-mingw-w64
|
||||
wget -qO- https://storage.googleapis.com/golang/go1.11.1.linux-amd64.tar.gz | tar -C /usr/local -xz
|
||||
echo 'export GOPATH=/go' >> /root/.bashrc
|
||||
echo 'export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin' >> /root/.bashrc
|
||||
cd /go/src/github.com/containernetworking/plugins
|
||||
SHELL
|
||||
end
|
@ -16,6 +16,7 @@ fi
|
||||
|
||||
export GOPATH=${PWD}/gopath
|
||||
export GO="${GO:-go}"
|
||||
export GOFLAGS="${GOFLAGS} -mod=vendor"
|
||||
|
||||
mkdir -p "${PWD}/bin"
|
||||
|
||||
|
@ -12,6 +12,8 @@ ln -s ${PWD} ${GOPATH}/src/${REPO_PATH} || exit 255
|
||||
|
||||
export GO="${GO:-go}"
|
||||
export GOOS=windows
|
||||
export GOFLAGS="${GOFLAGS} -mod=vendor"
|
||||
echo $GOFLAGS
|
||||
|
||||
PLUGINS=$(cat plugins/windows_only.txt)
|
||||
for d in $PLUGINS; do
|
||||
|
33
go.mod
Normal file
33
go.mod
Normal file
@ -0,0 +1,33 @@
|
||||
module github.com/containernetworking/plugins
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/Microsoft/go-winio v0.4.11 // indirect
|
||||
github.com/Microsoft/hcsshim v0.8.6
|
||||
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae
|
||||
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44
|
||||
github.com/containernetworking/cni v0.8.0
|
||||
github.com/coreos/go-iptables v0.4.5
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7
|
||||
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c
|
||||
github.com/d2g/dhcp4client v1.0.0
|
||||
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5
|
||||
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4 // indirect
|
||||
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c
|
||||
github.com/golang/protobuf v1.3.1 // indirect
|
||||
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56
|
||||
github.com/mattn/go-shellwords v1.0.3
|
||||
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b
|
||||
github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a
|
||||
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8
|
||||
github.com/sirupsen/logrus v1.0.6 // indirect
|
||||
github.com/stretchr/testify v1.3.0 // indirect
|
||||
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf
|
||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc // indirect
|
||||
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941 // indirect
|
||||
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 // indirect
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9 // indirect
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 // indirect
|
||||
)
|
59
go.sum
Normal file
59
go.sum
Normal file
@ -0,0 +1,59 @@
|
||||
github.com/Microsoft/go-winio v0.4.11 h1:zoIOcVf0xPN1tnMVbTtEdI+P8OofVk3NObnwOQ6nK2Q=
|
||||
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||
github.com/Microsoft/hcsshim v0.8.6 h1:ZfF0+zZeYdzMIVMZHKtDKJvLHj76XCuVae/jNkjj0IA=
|
||||
github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg=
|
||||
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae h1:AMzIhMUqU3jMrZiTuW0zkYeKlKDAFD+DG20IoO421/Y=
|
||||
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0=
|
||||
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44 h1:y853v6rXx+zefEcjET3JuKAqvhj+FKflQijjeaSv2iA=
|
||||
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||
github.com/containernetworking/cni v0.8.0 h1:BT9lpgGoH4jw3lFC7Odz2prU5ruiYKcgAjMCbgybcKI=
|
||||
github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY=
|
||||
github.com/coreos/go-iptables v0.4.5 h1:DpHb9vJrZQEFMcVLFKAAGMUVX0XoRC0ptCthinRYm38=
|
||||
github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7 h1:u9SHYsPQNyt5tgDm3YN7+9dYrpK96E5wFilTFWIDZOM=
|
||||
github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
|
||||
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c h1:Xo2rK1pzOm0jO6abTPIQwbAmqBIOj132otexc1mmzFc=
|
||||
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
|
||||
github.com/d2g/dhcp4client v1.0.0 h1:suYBsYZIkSlUMEz4TAYCczKf62IA2UWC+O8+KtdOhCo=
|
||||
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
|
||||
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5 h1:+CpLbZIeUn94m02LdEKPcgErLJ347NUwxPKs5u8ieiY=
|
||||
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
|
||||
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4 h1:itqmmf1PFpC4n5JW+j4BU7X4MTfVurhYRTjODoPb2Y8=
|
||||
github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c h1:RBUpb2b14UnmRHNd2uHz20ZHLDK+SW5Us/vWF5IHRaY=
|
||||
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
||||
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56 h1:742eGXur0715JMq73aD95/FU0XpVKXqNuTnEfXsLOYQ=
|
||||
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
|
||||
github.com/mattn/go-shellwords v1.0.3 h1:K/VxK7SZ+cvuPgFSLKi5QPI9Vr/ipOf4C1gN+ntueUk=
|
||||
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b h1:Ey6yH0acn50T/v6CB75bGP4EMJqnv9WvnjN7oZaj+xE=
|
||||
github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
|
||||
github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a h1:KfNOeFvoAssuZLT7IntKZElKwi/5LRuxY71k+t6rfaM=
|
||||
github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8 h1:2c1EFnZHIPCW8qKWgHMH/fX2PkSabFc5mrVzfUNdg5U=
|
||||
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
|
||||
github.com/sirupsen/logrus v1.0.6 h1:hcP1GmhGigz/O7h1WVUM5KklBp1JoNS9FggWKdj/j3s=
|
||||
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
|
||||
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
|
||||
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf h1:3J37+NPjNyGW/dbfXtj3yWuF9OEepIdGOXRaJGbORV8=
|
||||
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
|
||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc h1:R83G5ikgLMxrBvLh22JhdfI8K6YXEPHx5P03Uu3DRs4=
|
||||
github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
|
||||
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941 h1:qBTHLajHecfu+xzRI9PqVDcqx7SdHj9d4B+EzSn3tAc=
|
||||
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 h1:Y/KGZSOdz/2r0WJ9Mkmz6NJBusp0kiNx1Cn82lzJQ6w=
|
||||
golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f h1:25KHgbfyiSm6vwQLbM3zZIe1v9p/3ea4Rz+nnM5K/i4=
|
||||
golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo=
|
||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2 h1:OAj3g0cR6Dx/R07QgQe8wkA9RNjB2u4i700xBkIT4e0=
|
||||
gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo=
|
@ -149,12 +149,12 @@ var _ = Describe("Basic PTP using cnitool", func() {
|
||||
})
|
||||
|
||||
Measure("limits traffic only on the restricted bandwith veth device", func(b Benchmarker) {
|
||||
ipRegexp := regexp.MustCompile("10\\.11\\.2\\.\\d{1,3}")
|
||||
ipRegexp := regexp.MustCompile("10\\.1[12]\\.2\\.\\d{1,3}")
|
||||
|
||||
By(fmt.Sprintf("adding %s to %s\n\n", "chained-bridge-bandwidth", contNS1.ShortName()))
|
||||
chainedBridgeBandwidthEnv.runInNS(hostNS, cnitoolBin, "add", "network-chain-test", contNS1.LongName())
|
||||
chainedBridgeIP := ipRegexp.FindString(chainedBridgeBandwidthEnv.runInNS(contNS1, "ip", "addr"))
|
||||
Expect(chainedBridgeIP).To(ContainSubstring("10.11.2."))
|
||||
Expect(chainedBridgeIP).To(ContainSubstring("10.12.2."))
|
||||
|
||||
By(fmt.Sprintf("adding %s to %s\n\n", "basic-bridge", contNS2.ShortName()))
|
||||
basicBridgeEnv.runInNS(hostNS, cnitoolBin, "add", "network-chain-test", contNS2.LongName())
|
||||
|
@ -4,12 +4,12 @@
|
||||
"plugins": [
|
||||
{
|
||||
"type": "bridge",
|
||||
"bridge": "test-bridge-0",
|
||||
"bridge": "test-bridge-1",
|
||||
"isDefaultGateway": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.11.2.0/24",
|
||||
"dataDir": "/tmp/foo"
|
||||
"subnet": "10.12.2.0/24",
|
||||
"dataDir": "/tmp/bar"
|
||||
}
|
||||
},
|
||||
{
|
||||
|
37
pkg/errors/errors.go
Normal file
37
pkg/errors/errors.go
Normal file
@ -0,0 +1,37 @@
|
||||
// Copyright 2020 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package errors
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Annotate is used to add extra context to an existing error. The return will be
|
||||
// a new error which carries error message from both context message and existing error.
|
||||
func Annotate(err error, message string) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("%s: %v", message, err)
|
||||
}
|
||||
|
||||
// Annotatef is used to add extra context with args to an existing error. The return will be
|
||||
// a new error which carries error message from both context message and existing error.
|
||||
func Annotatef(err error, message string, args ...interface{}) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("%s: %v", fmt.Sprintf(message, args...), err)
|
||||
}
|
96
pkg/errors/errors_test.go
Normal file
96
pkg/errors/errors_test.go
Normal file
@ -0,0 +1,96 @@
|
||||
// Copyright 2020 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package errors
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAnnotate(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
existingErr error
|
||||
contextMessage string
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
"nil error",
|
||||
nil,
|
||||
"context",
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"normal case",
|
||||
errors.New("existing error"),
|
||||
"context",
|
||||
errors.New("context: existing error"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if !reflect.DeepEqual(Annotatef(test.existingErr, test.contextMessage), test.expectedErr) {
|
||||
t.Errorf("test case %s fails", test.name)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAnnotatef(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
existingErr error
|
||||
contextMessage string
|
||||
contextArgs []interface{}
|
||||
expectedErr error
|
||||
}{
|
||||
{
|
||||
"nil error",
|
||||
nil,
|
||||
"context",
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
"normal case",
|
||||
errors.New("existing error"),
|
||||
"context",
|
||||
nil,
|
||||
errors.New("context: existing error"),
|
||||
},
|
||||
{
|
||||
"normal case with args",
|
||||
errors.New("existing error"),
|
||||
"context %s %d",
|
||||
[]interface{}{
|
||||
"arg",
|
||||
100,
|
||||
},
|
||||
errors.New("context arg 100: existing error"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
if !reflect.DeepEqual(Annotatef(test.existingErr, test.contextMessage, test.contextArgs...), test.expectedErr) {
|
||||
t.Errorf("test case %s fails", test.name)
|
||||
return
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -21,9 +21,10 @@ import (
|
||||
|
||||
"github.com/Microsoft/hcsshim"
|
||||
"github.com/Microsoft/hcsshim/hcn"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/juju/errors"
|
||||
"github.com/containernetworking/plugins/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -64,19 +65,22 @@ func GenerateHnsEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcsshim.HNSEndpoint
|
||||
// run the IPAM plugin and get back the config to apply
|
||||
hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epInfo.EndpointName)
|
||||
if err != nil && !hcsshim.IsNotExist(err) {
|
||||
return nil, errors.Annotatef(err, "Attempt to get endpoint \"%v\" failed", epInfo.EndpointName)
|
||||
return nil, errors.Annotatef(err, "failed to get endpoint %q", epInfo.EndpointName)
|
||||
}
|
||||
|
||||
if hnsEndpoint != nil {
|
||||
if hnsEndpoint.VirtualNetwork != epInfo.NetworkId {
|
||||
_, err = hnsEndpoint.Delete()
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "Failed to delete endpoint %v", epInfo.EndpointName)
|
||||
return nil, errors.Annotatef(err, "failed to delete endpoint %s", epInfo.EndpointName)
|
||||
}
|
||||
hnsEndpoint = nil
|
||||
}
|
||||
}
|
||||
|
||||
if n.LoopbackDSR {
|
||||
n.ApplyLoopbackDSR(&epInfo.IpAddress)
|
||||
}
|
||||
if hnsEndpoint == nil {
|
||||
hnsEndpoint = &hcsshim.HNSEndpoint{
|
||||
Name: epInfo.EndpointName,
|
||||
@ -95,7 +99,7 @@ func GenerateHcnEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcn.HostComputeEndp
|
||||
// run the IPAM plugin and get back the config to apply
|
||||
hcnEndpoint, err := hcn.GetEndpointByName(epInfo.EndpointName)
|
||||
if err != nil && !hcn.IsNotFoundError(err) {
|
||||
return nil, errors.Annotatef(err, "Attempt to get endpoint \"%v\" failed", epInfo.EndpointName)
|
||||
return nil, errors.Annotatef(err, "failed to get endpoint %q", epInfo.EndpointName)
|
||||
}
|
||||
|
||||
if hcnEndpoint != nil {
|
||||
@ -105,26 +109,18 @@ func GenerateHcnEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcn.HostComputeEndp
|
||||
if !strings.EqualFold(hcnEndpoint.HostComputeNetwork, epInfo.NetworkId) {
|
||||
err = hcnEndpoint.Delete()
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "Failed to delete endpoint %v", epInfo.EndpointName)
|
||||
hcnEndpoint = nil
|
||||
|
||||
return nil, errors.Annotatef(err, "failed to delete endpoint %s", epInfo.EndpointName)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("Endpoint \"%v\" already exits", epInfo.EndpointName)
|
||||
return nil, fmt.Errorf("endpoint %q already exits", epInfo.EndpointName)
|
||||
}
|
||||
}
|
||||
|
||||
if hcnEndpoint == nil {
|
||||
routes := []hcn.Route{
|
||||
{
|
||||
NextHop: GetIpString(&epInfo.Gateway),
|
||||
DestinationPrefix: func() string {
|
||||
destinationPrefix := "0.0.0.0/0"
|
||||
if ipv6 := epInfo.Gateway.To4(); ipv6 == nil {
|
||||
destinationPrefix = "::/0"
|
||||
}
|
||||
return destinationPrefix
|
||||
}(),
|
||||
NextHop: GetIpString(&epInfo.Gateway),
|
||||
DestinationPrefix: GetDefaultDestinationPrefix(&epInfo.Gateway),
|
||||
},
|
||||
}
|
||||
|
||||
@ -138,6 +134,9 @@ func GenerateHcnEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcn.HostComputeEndp
|
||||
}
|
||||
ipConfigs := []hcn.IpConfig{hcnIpConfig}
|
||||
|
||||
if n.LoopbackDSR {
|
||||
n.ApplyLoopbackDSR(&epInfo.IpAddress)
|
||||
}
|
||||
hcnEndpoint = &hcn.HostComputeEndpoint{
|
||||
SchemaVersion: hcn.Version{Major: 2},
|
||||
Name: epInfo.EndpointName,
|
||||
@ -270,7 +269,7 @@ func AddHcnEndpoint(epName string, expectedNetworkId string, namespace string,
|
||||
if err != nil {
|
||||
return nil, errors.Annotatef(err, "failed to Remove Endpoint after AddNamespaceEndpoint failure")
|
||||
}
|
||||
return nil, errors.Annotatef(err, "Failed to Add endpoint to namespace")
|
||||
return nil, errors.Annotate(err, "failed to Add endpoint to namespace")
|
||||
}
|
||||
return hcnEndpoint, nil
|
||||
|
||||
|
@ -17,18 +17,29 @@ package hns
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"strings"
|
||||
|
||||
"github.com/Microsoft/hcsshim/hcn"
|
||||
"github.com/buger/jsonparser"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// NetConf is the CNI spec
|
||||
type NetConf struct {
|
||||
types.NetConf
|
||||
// ApiVersion is either 1 or 2, which specifies which hns APIs to call
|
||||
ApiVersion int `json:"ApiVersion"`
|
||||
// V2 Api Policies
|
||||
HcnPolicyArgs []hcn.EndpointPolicy `json:"HcnPolicyArgs,omitempty"`
|
||||
Policies []policy `json:"policies,omitempty"`
|
||||
RuntimeConfig RuntimeConfig `json:"runtimeConfig"`
|
||||
// V1 Api Policies
|
||||
Policies []policy `json:"policies,omitempty"`
|
||||
// Options to be passed in by the runtime
|
||||
RuntimeConfig RuntimeConfig `json:"runtimeConfig"`
|
||||
// If true, adds a policy to endpoints to support loopback direct server return
|
||||
LoopbackDSR bool `json:"loopbackDSR"`
|
||||
}
|
||||
|
||||
type RuntimeDNS struct {
|
||||
@ -36,8 +47,16 @@ type RuntimeDNS struct {
|
||||
Search []string `json:"searches,omitempty"`
|
||||
}
|
||||
|
||||
type PortMapEntry struct {
|
||||
HostPort int `json:"hostPort"`
|
||||
ContainerPort int `json:"containerPort"`
|
||||
Protocol string `json:"protocol"`
|
||||
HostIP string `json:"hostIP,omitempty"`
|
||||
}
|
||||
|
||||
type RuntimeConfig struct {
|
||||
DNS RuntimeDNS `json:"dns"`
|
||||
DNS RuntimeDNS `json:"dns"`
|
||||
PortMaps []PortMapEntry `json:"portMappings,omitempty"`
|
||||
}
|
||||
|
||||
type policy struct {
|
||||
@ -45,6 +64,31 @@ type policy struct {
|
||||
Value json.RawMessage `json:"value"`
|
||||
}
|
||||
|
||||
func GetDefaultDestinationPrefix(ip *net.IP) string {
|
||||
destinationPrefix := "0.0.0.0/0"
|
||||
if ipv6 := ip.To4(); ipv6 == nil {
|
||||
destinationPrefix = "::/0"
|
||||
}
|
||||
return destinationPrefix
|
||||
}
|
||||
|
||||
func (n *NetConf) ApplyLoopbackDSR(ip *net.IP) {
|
||||
value := fmt.Sprintf(`"Destinations" : ["%s"]`, ip.String())
|
||||
if n.ApiVersion == 2 {
|
||||
hcnLoopbackRoute := hcn.EndpointPolicy{
|
||||
Type: "OutBoundNAT",
|
||||
Settings: []byte(fmt.Sprintf("{%s}", value)),
|
||||
}
|
||||
n.HcnPolicyArgs = append(n.HcnPolicyArgs, hcnLoopbackRoute)
|
||||
} else {
|
||||
hnsLoopbackRoute := policy{
|
||||
Name: "EndpointPolicy",
|
||||
Value: []byte(fmt.Sprintf(`{"Type": "OutBoundNAT", %s}`, value)),
|
||||
}
|
||||
n.Policies = append(n.Policies, hnsLoopbackRoute)
|
||||
}
|
||||
}
|
||||
|
||||
// If runtime dns values are there use that else use cni conf supplied dns
|
||||
func (n *NetConf) GetDNS() types.DNS {
|
||||
dnsResult := n.DNS
|
||||
@ -172,3 +216,21 @@ func (n *NetConf) ApplyDefaultPAPolicy(paAddress string) {
|
||||
Value: []byte(`{"Type": "PA", "PA": "` + paAddress + `"}`),
|
||||
})
|
||||
}
|
||||
|
||||
// ApplyPortMappingPolicy is used to configure HostPort<>ContainerPort mapping in HNS
|
||||
func (n *NetConf) ApplyPortMappingPolicy(portMappings []PortMapEntry) {
|
||||
if portMappings == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if n.Policies == nil {
|
||||
n.Policies = make([]policy, 0)
|
||||
}
|
||||
|
||||
for _, portMapping := range portMappings {
|
||||
n.Policies = append(n.Policies, policy{
|
||||
Name: "EndpointPolicy",
|
||||
Value: []byte(fmt.Sprintf(`{"Type": "NAT", "InternalPort": %d, "ExternalPort": %d, "Protocol": "%s"}`, portMapping.ContainerPort, portMapping.HostPort, portMapping.Protocol)),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -128,6 +128,53 @@ var _ = Describe("HNS NetConf", func() {
|
||||
})
|
||||
})
|
||||
|
||||
Describe("ApplyPortMappingPolicy", func() {
|
||||
Context("when portMappings not activated", func() {
|
||||
It("does nothing", func() {
|
||||
n := NetConf{}
|
||||
n.ApplyPortMappingPolicy(nil)
|
||||
Expect(n.Policies).Should(BeNil())
|
||||
|
||||
n.ApplyPortMappingPolicy([]PortMapEntry{})
|
||||
Expect(n.Policies).Should(HaveLen(0))
|
||||
})
|
||||
})
|
||||
|
||||
Context("when portMappings is activated", func() {
|
||||
It("creates NAT policies", func() {
|
||||
n := NetConf{}
|
||||
n.ApplyPortMappingPolicy([]PortMapEntry{
|
||||
{
|
||||
ContainerPort: 80,
|
||||
HostPort: 8080,
|
||||
Protocol: "TCP",
|
||||
HostIP: "ignored",
|
||||
},
|
||||
})
|
||||
|
||||
Expect(n.Policies).Should(HaveLen(1))
|
||||
|
||||
policy := n.Policies[0]
|
||||
Expect(policy.Name).Should(Equal("EndpointPolicy"))
|
||||
|
||||
value := make(map[string]interface{})
|
||||
json.Unmarshal(policy.Value, &value)
|
||||
|
||||
Expect(value).Should(HaveKey("Type"))
|
||||
Expect(value["Type"]).Should(Equal("NAT"))
|
||||
|
||||
Expect(value).Should(HaveKey("InternalPort"))
|
||||
Expect(value["InternalPort"]).Should(Equal(float64(80)))
|
||||
|
||||
Expect(value).Should(HaveKey("ExternalPort"))
|
||||
Expect(value["ExternalPort"]).Should(Equal(float64(8080)))
|
||||
|
||||
Expect(value).Should(HaveKey("Protocol"))
|
||||
Expect(value["Protocol"]).Should(Equal("TCP"))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Describe("MarshalPolicies", func() {
|
||||
Context("when not set by user", func() {
|
||||
It("sets it by adding a policy", func() {
|
||||
|
@ -21,10 +21,12 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/utils/hwaddr"
|
||||
"github.com/safchain/ethtool"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/utils/hwaddr"
|
||||
"github.com/containernetworking/plugins/pkg/utils/sysctl"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -60,11 +62,15 @@ func peerExists(name string) bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func makeVeth(name string, mtu int) (peerName string, veth netlink.Link, err error) {
|
||||
func makeVeth(name, vethPeerName string, mtu int) (peerName string, veth netlink.Link, err error) {
|
||||
for i := 0; i < 10; i++ {
|
||||
peerName, err = RandomVethName()
|
||||
if err != nil {
|
||||
return
|
||||
if vethPeerName != "" {
|
||||
peerName = vethPeerName
|
||||
} else {
|
||||
peerName, err = RandomVethName()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
veth, err = makeVethPair(name, peerName, mtu)
|
||||
@ -73,7 +79,7 @@ func makeVeth(name string, mtu int) (peerName string, veth netlink.Link, err err
|
||||
return
|
||||
|
||||
case os.IsExist(err):
|
||||
if peerExists(peerName) {
|
||||
if peerExists(peerName) && vethPeerName == "" {
|
||||
continue
|
||||
}
|
||||
err = fmt.Errorf("container veth name provided (%v) already exists", name)
|
||||
@ -121,12 +127,13 @@ func ifaceFromNetlinkLink(l netlink.Link) net.Interface {
|
||||
}
|
||||
}
|
||||
|
||||
// SetupVeth sets up a pair of virtual ethernet devices.
|
||||
// Call SetupVeth from inside the container netns. It will create both veth
|
||||
// SetupVethWithName sets up a pair of virtual ethernet devices.
|
||||
// Call SetupVethWithName from inside the container netns. It will create both veth
|
||||
// devices and move the host-side veth into the provided hostNS namespace.
|
||||
// On success, SetupVeth returns (hostVeth, containerVeth, nil)
|
||||
func SetupVeth(contVethName string, mtu int, hostNS ns.NetNS) (net.Interface, net.Interface, error) {
|
||||
hostVethName, contVeth, err := makeVeth(contVethName, mtu)
|
||||
// hostVethName: If hostVethName is not specified, the host-side veth name will use a random string.
|
||||
// On success, SetupVethWithName returns (hostVeth, containerVeth, nil)
|
||||
func SetupVethWithName(contVethName, hostVethName string, mtu int, hostNS ns.NetNS) (net.Interface, net.Interface, error) {
|
||||
hostVethName, contVeth, err := makeVeth(contVethName, hostVethName, mtu)
|
||||
if err != nil {
|
||||
return net.Interface{}, net.Interface{}, err
|
||||
}
|
||||
@ -153,6 +160,9 @@ func SetupVeth(contVethName string, mtu int, hostNS ns.NetNS) (net.Interface, ne
|
||||
if err = netlink.LinkSetUp(hostVeth); err != nil {
|
||||
return fmt.Errorf("failed to set %q up: %v", hostVethName, err)
|
||||
}
|
||||
|
||||
// we want to own the routes for this interface
|
||||
_, _ = sysctl.Sysctl(fmt.Sprintf("net/ipv6/conf/%s/accept_ra", hostVethName), "0")
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
@ -161,11 +171,19 @@ func SetupVeth(contVethName string, mtu int, hostNS ns.NetNS) (net.Interface, ne
|
||||
return ifaceFromNetlinkLink(hostVeth), ifaceFromNetlinkLink(contVeth), nil
|
||||
}
|
||||
|
||||
// SetupVeth sets up a pair of virtual ethernet devices.
|
||||
// Call SetupVeth from inside the container netns. It will create both veth
|
||||
// devices and move the host-side veth into the provided hostNS namespace.
|
||||
// On success, SetupVeth returns (hostVeth, containerVeth, nil)
|
||||
func SetupVeth(contVethName string, mtu int, hostNS ns.NetNS) (net.Interface, net.Interface, error) {
|
||||
return SetupVethWithName(contVethName, "", mtu, hostNS)
|
||||
}
|
||||
|
||||
// DelLinkByName removes an interface link.
|
||||
func DelLinkByName(ifName string) error {
|
||||
iface, err := netlink.LinkByName(ifName)
|
||||
if err != nil {
|
||||
if err.Error() == "Link not found" {
|
||||
if _, ok := err.(netlink.LinkNotFoundError); ok {
|
||||
return ErrLinkNotFound
|
||||
}
|
||||
return fmt.Errorf("failed to lookup %q: %v", ifName, err)
|
||||
@ -182,7 +200,7 @@ func DelLinkByName(ifName string) error {
|
||||
func DelLinkByNameAddr(ifName string) ([]*net.IPNet, error) {
|
||||
iface, err := netlink.LinkByName(ifName)
|
||||
if err != nil {
|
||||
if err != nil && err.Error() == "Link not found" {
|
||||
if _, ok := err.(netlink.LinkNotFoundError); ok {
|
||||
return nil, ErrLinkNotFound
|
||||
}
|
||||
return nil, fmt.Errorf("failed to lookup %q: %v", ifName, err)
|
||||
|
@ -189,9 +189,8 @@ var _ = Describe("Link", func() {
|
||||
It("returns useful error", func() {
|
||||
_ = containerNetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := ip.SetupVeth(containerVethName, mtu, hostNetNS)
|
||||
Expect(err.Error()).To(Equal("failed to move veth to host netns: file exists"))
|
||||
Expect(err.Error()).To(HavePrefix("failed to move veth to host netns: "))
|
||||
|
||||
return nil
|
||||
})
|
||||
|
@ -16,10 +16,8 @@ package ipam
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"github.com/containernetworking/cni/pkg/invoke"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"os"
|
||||
)
|
||||
|
||||
func ExecAdd(plugin string, netconf []byte) (types.Result, error) {
|
||||
@ -31,13 +29,5 @@ func ExecCheck(plugin string, netconf []byte) error {
|
||||
}
|
||||
|
||||
func ExecDel(plugin string, netconf []byte) error {
|
||||
cmd := os.Getenv("CNI_COMMAND")
|
||||
if cmd == "" {
|
||||
return fmt.Errorf("environment variable CNI_COMMAND must be specified.")
|
||||
}
|
||||
// Set CNI_COMMAND to DEL explicity. We might be deleting due to an ADD gone wrong.
|
||||
// restore CNI_COMMAND to original value upon return.
|
||||
os.Setenv("CNI_COMMAND", "DEL")
|
||||
defer os.Setenv("CNI_COMMAND", cmd)
|
||||
return invoke.DelegateDel(context.TODO(), plugin, netconf, nil)
|
||||
}
|
||||
|
@ -26,6 +26,11 @@ import (
|
||||
|
||||
// Returns an object representing the current OS thread's network namespace
|
||||
func GetCurrentNS() (NetNS, error) {
|
||||
// Lock the thread in case other goroutine executes in it and changes its
|
||||
// network namespace after getCurrentThreadNetNSPath(), otherwise it might
|
||||
// return an unexpected network namespace.
|
||||
runtime.LockOSThread()
|
||||
defer runtime.UnlockOSThread()
|
||||
return GetNS(getCurrentThreadNetNSPath())
|
||||
}
|
||||
|
||||
@ -178,7 +183,16 @@ func (ns *netNS) Do(toRun func(NetNS) error) error {
|
||||
if err = ns.Set(); err != nil {
|
||||
return fmt.Errorf("error switching to ns %v: %v", ns.file.Name(), err)
|
||||
}
|
||||
defer threadNS.Set() // switch back
|
||||
defer func() {
|
||||
err := threadNS.Set() // switch back
|
||||
if err == nil {
|
||||
// Unlock the current thread only when we successfully switched back
|
||||
// to the original namespace; otherwise leave the thread locked which
|
||||
// will force the runtime to scrap the current thread, that is maybe
|
||||
// not as optimal but at least always safe to do.
|
||||
runtime.UnlockOSThread()
|
||||
}
|
||||
}()
|
||||
|
||||
return toRun(hostNS)
|
||||
}
|
||||
@ -193,6 +207,10 @@ func (ns *netNS) Do(toRun func(NetNS) error) error {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(1)
|
||||
|
||||
// Start the callback in a new green thread so that if we later fail
|
||||
// to switch the namespace back to the original one, we can safely
|
||||
// leave the thread locked to die without a risk of the current thread
|
||||
// left lingering with incorrect namespace.
|
||||
var innerError error
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
@ -118,6 +119,33 @@ var _ = Describe("Linux namespace operations", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
Context("when called concurrently", func() {
|
||||
It("provides the original namespace as the argument to the callback", func() {
|
||||
concurrency := 200
|
||||
origNS, err := ns.GetCurrentNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
origNSInode, err := getInodeNS(origNS)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(concurrency)
|
||||
for i := 0; i < concurrency; i++ {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
targetNetNS.Do(func(hostNS ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
hostNSInode, err := getInodeNS(hostNS)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(hostNSInode).To(Equal(origNSInode))
|
||||
return nil
|
||||
})
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
})
|
||||
})
|
||||
|
||||
Context("when the callback returns an error", func() {
|
||||
It("restores the calling thread to the original namespace before returning", func() {
|
||||
err := originalNetNS.Do(func(ns.NetNS) error {
|
||||
|
60
pkg/testutils/dns.go
Normal file
60
pkg/testutils/dns.go
Normal file
@ -0,0 +1,60 @@
|
||||
// Copyright 2019 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package testutils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
)
|
||||
|
||||
// TmpResolvConf will create a temporary file and write the provided DNS settings to
|
||||
// it in the resolv.conf format. It returns the path of the created temporary file or
|
||||
// an error if any occurs while creating/writing the file. It is the caller's
|
||||
// responsibility to remove the file.
|
||||
func TmpResolvConf(dnsConf types.DNS) (string, error) {
|
||||
f, err := ioutil.TempFile("", "cni_test_resolv.conf")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get temp file for CNI test resolv.conf: %v", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
path := f.Name()
|
||||
defer func() {
|
||||
if err != nil {
|
||||
os.RemoveAll(path)
|
||||
}
|
||||
}()
|
||||
|
||||
// see "man 5 resolv.conf" for the format of resolv.conf
|
||||
var resolvConfLines []string
|
||||
for _, nameserver := range dnsConf.Nameservers {
|
||||
resolvConfLines = append(resolvConfLines, fmt.Sprintf("nameserver %s", nameserver))
|
||||
}
|
||||
resolvConfLines = append(resolvConfLines, fmt.Sprintf("domain %s", dnsConf.Domain))
|
||||
resolvConfLines = append(resolvConfLines, fmt.Sprintf("search %s", strings.Join(dnsConf.Search, " ")))
|
||||
resolvConfLines = append(resolvConfLines, fmt.Sprintf("options %s", strings.Join(dnsConf.Options, " ")))
|
||||
|
||||
resolvConf := strings.Join(resolvConfLines, "\n")
|
||||
_, err = f.Write([]byte(resolvConf))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to write temp resolv.conf for CNI test: %v", err)
|
||||
}
|
||||
|
||||
return path, err
|
||||
}
|
@ -22,17 +22,36 @@ import (
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"golang.org/x/sys/unix"
|
||||
)
|
||||
|
||||
const nsRunDir = "/var/run/netns"
|
||||
func getNsRunDir() string {
|
||||
xdgRuntimeDir := os.Getenv("XDG_RUNTIME_DIR")
|
||||
|
||||
/// If XDG_RUNTIME_DIR is set, check if the current user owns /var/run. If
|
||||
// the owner is different, we are most likely running in a user namespace.
|
||||
// In that case use $XDG_RUNTIME_DIR/netns as runtime dir.
|
||||
if xdgRuntimeDir != "" {
|
||||
if s, err := os.Stat("/var/run"); err == nil {
|
||||
st, ok := s.Sys().(*syscall.Stat_t)
|
||||
if ok && int(st.Uid) != os.Geteuid() {
|
||||
return path.Join(xdgRuntimeDir, "netns")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "/var/run/netns"
|
||||
}
|
||||
|
||||
// Creates a new persistent (bind-mounted) network namespace and returns an object
|
||||
// representing that namespace, without switching to it.
|
||||
func NewNS() (ns.NetNS, error) {
|
||||
|
||||
nsRunDir := getNsRunDir()
|
||||
|
||||
b := make([]byte, 16)
|
||||
_, err := rand.Reader.Read(b)
|
||||
if err != nil {
|
||||
@ -135,7 +154,7 @@ func NewNS() (ns.NetNS, error) {
|
||||
func UnmountNS(ns ns.NetNS) error {
|
||||
nsPath := ns.Path()
|
||||
// Only unmount if it's been bind-mounted (don't touch namespaces in /proc...)
|
||||
if strings.HasPrefix(nsPath, nsRunDir) {
|
||||
if strings.HasPrefix(nsPath, getNsRunDir()) {
|
||||
if err := unix.Unmount(nsPath, 0); err != nil {
|
||||
return fmt.Errorf("failed to unmount NS: at %s: %v", nsPath, err)
|
||||
}
|
||||
|
121
pkg/utils/iptables.go
Normal file
121
pkg/utils/iptables.go
Normal file
@ -0,0 +1,121 @@
|
||||
// Copyright 2017 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
)
|
||||
|
||||
const statusChainExists = 1
|
||||
|
||||
// EnsureChain idempotently creates the iptables chain. It does not
|
||||
// return an error if the chain already exists.
|
||||
func EnsureChain(ipt *iptables.IPTables, table, chain string) error {
|
||||
if ipt == nil {
|
||||
return errors.New("failed to ensure iptable chain: IPTables was nil")
|
||||
}
|
||||
exists, err := ChainExists(ipt, table, chain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list iptables chains: %v", err)
|
||||
}
|
||||
if !exists {
|
||||
err = ipt.NewChain(table, chain)
|
||||
if err != nil {
|
||||
eerr, eok := err.(*iptables.Error)
|
||||
if eok && eerr.ExitStatus() != statusChainExists {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ChainExists checks whether an iptables chain exists.
|
||||
func ChainExists(ipt *iptables.IPTables, table, chain string) (bool, error) {
|
||||
if ipt == nil {
|
||||
return false, errors.New("failed to check iptable chain: IPTables was nil")
|
||||
}
|
||||
chains, err := ipt.ListChains(table)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, ch := range chains {
|
||||
if ch == chain {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// DeleteRule idempotently delete the iptables rule in the specified table/chain.
|
||||
// It does not return an error if the referring chain doesn't exist
|
||||
func DeleteRule(ipt *iptables.IPTables, table, chain string, rulespec ...string) error {
|
||||
if ipt == nil {
|
||||
return errors.New("failed to ensure iptable chain: IPTables was nil")
|
||||
}
|
||||
if err := ipt.Delete(table, chain, rulespec...); err != nil {
|
||||
eerr, eok := err.(*iptables.Error)
|
||||
switch {
|
||||
case eok && eerr.IsNotExist():
|
||||
// swallow here, the chain was already deleted
|
||||
return nil
|
||||
case eok && eerr.ExitStatus() == 2:
|
||||
// swallow here, invalid command line parameter because the referring rule is missing
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("Failed to delete referring rule %s %s: %v", table, chain, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeleteChain idempotently deletes the specified table/chain.
|
||||
// It does not return an errors if the chain does not exist
|
||||
func DeleteChain(ipt *iptables.IPTables, table, chain string) error {
|
||||
if ipt == nil {
|
||||
return errors.New("failed to ensure iptable chain: IPTables was nil")
|
||||
}
|
||||
|
||||
err := ipt.DeleteChain(table, chain)
|
||||
eerr, eok := err.(*iptables.Error)
|
||||
switch {
|
||||
case eok && eerr.IsNotExist():
|
||||
// swallow here, the chain was already deleted
|
||||
return nil
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// ClearChain idempotently clear the iptables rules in the specified table/chain.
|
||||
// If the chain does not exist, a new one will be created
|
||||
func ClearChain(ipt *iptables.IPTables, table, chain string) error {
|
||||
if ipt == nil {
|
||||
return errors.New("failed to ensure iptable chain: IPTables was nil")
|
||||
}
|
||||
err := ipt.ClearChain(table, chain)
|
||||
eerr, eok := err.(*iptables.Error)
|
||||
switch {
|
||||
case eok && eerr.IsNotExist():
|
||||
// swallow here, the chain was already deleted
|
||||
return EnsureChain(ipt, table, chain)
|
||||
default:
|
||||
return err
|
||||
}
|
||||
}
|
97
pkg/utils/iptables_test.go
Normal file
97
pkg/utils/iptables_test.go
Normal file
@ -0,0 +1,97 @@
|
||||
// Copyright 2017-2018 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
const TABLE = "filter" // We'll monkey around here
|
||||
|
||||
var _ = Describe("chain tests", func() {
|
||||
var testChain string
|
||||
var ipt *iptables.IPTables
|
||||
var cleanup func()
|
||||
|
||||
BeforeEach(func() {
|
||||
|
||||
// Save a reference to the original namespace,
|
||||
// Add a new NS
|
||||
currNs, err := ns.GetCurrentNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
testNs, err := testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
testChain = fmt.Sprintf("cni-test-%d", rand.Intn(10000000))
|
||||
|
||||
ipt, err = iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
runtime.LockOSThread()
|
||||
err = testNs.Set()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
cleanup = func() {
|
||||
if ipt == nil {
|
||||
return
|
||||
}
|
||||
ipt.ClearChain(TABLE, testChain)
|
||||
ipt.DeleteChain(TABLE, testChain)
|
||||
currNs.Set()
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
cleanup()
|
||||
})
|
||||
|
||||
Describe("EnsureChain", func() {
|
||||
It("creates chains idempotently", func() {
|
||||
err := EnsureChain(ipt, TABLE, testChain)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Create it again!
|
||||
err = EnsureChain(ipt, TABLE, testChain)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("DeleteChain", func() {
|
||||
It("delete chains idempotently", func() {
|
||||
// Create chain
|
||||
err := EnsureChain(ipt, TABLE, testChain)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Delete chain
|
||||
err = DeleteChain(ipt, TABLE, testChain)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Delete it again!
|
||||
err = DeleteChain(ipt, TABLE, testChain)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
})
|
@ -35,7 +35,7 @@ func Sysctl(name string, params ...string) (string, error) {
|
||||
}
|
||||
|
||||
func getSysctl(name string) (string, error) {
|
||||
fullName := filepath.Join("/proc/sys", strings.Replace(name, ".", "/", -1))
|
||||
fullName := filepath.Join("/proc/sys", toNormalName(name))
|
||||
fullName = filepath.Clean(fullName)
|
||||
data, err := ioutil.ReadFile(fullName)
|
||||
if err != nil {
|
||||
@ -46,7 +46,7 @@ func getSysctl(name string) (string, error) {
|
||||
}
|
||||
|
||||
func setSysctl(name, value string) (string, error) {
|
||||
fullName := filepath.Join("/proc/sys", strings.Replace(name, ".", "/", -1))
|
||||
fullName := filepath.Join("/proc/sys", toNormalName(name))
|
||||
fullName = filepath.Clean(fullName)
|
||||
if err := ioutil.WriteFile(fullName, []byte(value), 0644); err != nil {
|
||||
return "", err
|
||||
@ -54,3 +54,27 @@ func setSysctl(name, value string) (string, error) {
|
||||
|
||||
return getSysctl(name)
|
||||
}
|
||||
|
||||
// Normalize names by using slash as separator
|
||||
// Sysctl names can use dots or slashes as separator:
|
||||
// - if dots are used, dots and slashes are interchanged.
|
||||
// - if slashes are used, slashes and dots are left intact.
|
||||
// Separator in use is determined by first occurrence.
|
||||
func toNormalName(name string) string {
|
||||
interchange := false
|
||||
for _, c := range name {
|
||||
if c == '.' {
|
||||
interchange = true
|
||||
break
|
||||
}
|
||||
if c == '/' {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if interchange {
|
||||
r := strings.NewReplacer(".", "/", "/", ".")
|
||||
return r.Replace(name)
|
||||
}
|
||||
return name
|
||||
}
|
||||
|
114
pkg/utils/sysctl/sysctl_linux_test.go
Normal file
114
pkg/utils/sysctl/sysctl_linux_test.go
Normal file
@ -0,0 +1,114 @@
|
||||
// Copyright 2017-2020 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sysctl_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
"github.com/containernetworking/plugins/pkg/utils/sysctl"
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
const (
|
||||
sysctlDotKeyTemplate = "net.ipv4.conf.%s.proxy_arp"
|
||||
sysctlSlashKeyTemplate = "net/ipv4/conf/%s/proxy_arp"
|
||||
)
|
||||
|
||||
var _ = Describe("Sysctl tests", func() {
|
||||
var testIfaceName string
|
||||
var cleanup func()
|
||||
|
||||
BeforeEach(func() {
|
||||
|
||||
// Save a reference to the original namespace,
|
||||
// Add a new NS
|
||||
currNs, err := ns.GetCurrentNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
testNs, err := testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
testIfaceName = fmt.Sprintf("cnitest.%d", rand.Intn(100000))
|
||||
testIface := &netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: testIfaceName,
|
||||
Namespace: netlink.NsFd(int(testNs.Fd())),
|
||||
},
|
||||
}
|
||||
|
||||
err = netlink.LinkAdd(testIface)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
runtime.LockOSThread()
|
||||
err = testNs.Set()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
cleanup = func() {
|
||||
netlink.LinkDel(testIface)
|
||||
currNs.Set()
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
cleanup()
|
||||
})
|
||||
|
||||
Describe("Sysctl", func() {
|
||||
It("reads keys with dot separators", func() {
|
||||
sysctlIfaceName := strings.Replace(testIfaceName, ".", "/", -1)
|
||||
sysctlKey := fmt.Sprintf(sysctlDotKeyTemplate, sysctlIfaceName)
|
||||
|
||||
_, err := sysctl.Sysctl(sysctlKey)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Sysctl", func() {
|
||||
It("reads keys with slash separators", func() {
|
||||
sysctlKey := fmt.Sprintf(sysctlSlashKeyTemplate, testIfaceName)
|
||||
|
||||
_, err := sysctl.Sysctl(sysctlKey)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Sysctl", func() {
|
||||
It("writes keys with dot separators", func() {
|
||||
sysctlIfaceName := strings.Replace(testIfaceName, ".", "/", -1)
|
||||
sysctlKey := fmt.Sprintf(sysctlDotKeyTemplate, sysctlIfaceName)
|
||||
|
||||
_, err := sysctl.Sysctl(sysctlKey, "1")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Sysctl", func() {
|
||||
It("writes keys with slash separators", func() {
|
||||
sysctlKey := fmt.Sprintf(sysctlSlashKeyTemplate, testIfaceName)
|
||||
|
||||
_, err := sysctl.Sysctl(sysctlKey, "1")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
})
|
27
pkg/utils/sysctl/sysctl_suite_test.go
Normal file
27
pkg/utils/sysctl/sysctl_suite_test.go
Normal file
@ -0,0 +1,27 @@
|
||||
// Copyright 2017-2020 CNI authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package sysctl_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
func TestSysctl(t *testing.T) {
|
||||
RegisterFailHandler(Fail)
|
||||
RunSpecs(t, "Sysctl Suite")
|
||||
}
|
@ -22,16 +22,22 @@ import (
|
||||
const (
|
||||
maxChainLength = 28
|
||||
chainPrefix = "CNI-"
|
||||
prefixLength = len(chainPrefix)
|
||||
)
|
||||
|
||||
// Generates a chain name to be used with iptables.
|
||||
// Ensures that the generated chain name is exactly
|
||||
// maxChainLength chars in length
|
||||
// FormatChainName generates a chain name to be used
|
||||
// with iptables. Ensures that the generated chain
|
||||
// name is exactly maxChainLength chars in length.
|
||||
func FormatChainName(name string, id string) string {
|
||||
chainBytes := sha512.Sum512([]byte(name + id))
|
||||
chain := fmt.Sprintf("%s%x", chainPrefix, chainBytes)
|
||||
return chain[:maxChainLength]
|
||||
return MustFormatChainNameWithPrefix(name, id, "")
|
||||
}
|
||||
|
||||
// MustFormatChainNameWithPrefix generates a chain name similar
|
||||
// to FormatChainName, but adds a custom prefix between
|
||||
// chainPrefix and unique identifier. Ensures that the
|
||||
// generated chain name is exactly maxChainLength chars in length.
|
||||
// Panics if the given prefix is too long.
|
||||
func MustFormatChainNameWithPrefix(name string, id string, prefix string) string {
|
||||
return MustFormatHashWithPrefix(maxChainLength, chainPrefix+prefix, name+id)
|
||||
}
|
||||
|
||||
// FormatComment returns a comment used for easier
|
||||
@ -39,3 +45,16 @@ func FormatChainName(name string, id string) string {
|
||||
func FormatComment(name string, id string) string {
|
||||
return fmt.Sprintf("name: %q id: %q", name, id)
|
||||
}
|
||||
|
||||
const MaxHashLen = sha512.Size * 2
|
||||
|
||||
// MustFormatHashWithPrefix returns a string of given length that begins with the
|
||||
// given prefix. It is filled with entropy based on the given string toHash.
|
||||
func MustFormatHashWithPrefix(length int, prefix string, toHash string) string {
|
||||
if len(prefix) >= length || length > MaxHashLen {
|
||||
panic("invalid length")
|
||||
}
|
||||
|
||||
output := sha512.Sum512([]byte(toHash))
|
||||
return fmt.Sprintf("%s%x", prefix, output)[:length]
|
||||
}
|
||||
|
@ -15,37 +15,151 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
)
|
||||
|
||||
var _ = Describe("Utils", func() {
|
||||
It("must format a short name", func() {
|
||||
chain := FormatChainName("test", "1234")
|
||||
Expect(len(chain)).To(Equal(maxChainLength))
|
||||
Expect(chain).To(Equal("CNI-2bbe0c48b91a7d1b8a6753a8"))
|
||||
Describe("FormatChainName", func() {
|
||||
It("must format a short name", func() {
|
||||
chain := FormatChainName("test", "1234")
|
||||
Expect(len(chain)).To(Equal(maxChainLength))
|
||||
Expect(chain).To(Equal("CNI-2bbe0c48b91a7d1b8a6753a8"))
|
||||
})
|
||||
|
||||
It("must truncate a long name", func() {
|
||||
chain := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
|
||||
Expect(len(chain)).To(Equal(maxChainLength))
|
||||
Expect(chain).To(Equal("CNI-374f33fe84ab0ed84dcdebe3"))
|
||||
})
|
||||
|
||||
It("must be predictable", func() {
|
||||
chain1 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
|
||||
chain2 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
|
||||
Expect(len(chain1)).To(Equal(maxChainLength))
|
||||
Expect(len(chain2)).To(Equal(maxChainLength))
|
||||
Expect(chain1).To(Equal(chain2))
|
||||
})
|
||||
|
||||
It("must change when a character changes", func() {
|
||||
chain1 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
|
||||
chain2 := FormatChainName("testalongnamethatdoesnotmakesense", "1235")
|
||||
Expect(len(chain1)).To(Equal(maxChainLength))
|
||||
Expect(len(chain2)).To(Equal(maxChainLength))
|
||||
Expect(chain1).To(Equal("CNI-374f33fe84ab0ed84dcdebe3"))
|
||||
Expect(chain1).NotTo(Equal(chain2))
|
||||
})
|
||||
})
|
||||
|
||||
It("must truncate a long name", func() {
|
||||
chain := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
|
||||
Expect(len(chain)).To(Equal(maxChainLength))
|
||||
Expect(chain).To(Equal("CNI-374f33fe84ab0ed84dcdebe3"))
|
||||
Describe("MustFormatChainNameWithPrefix", func() {
|
||||
It("generates a chain name with a prefix", func() {
|
||||
chain := MustFormatChainNameWithPrefix("test", "1234", "PREFIX-")
|
||||
Expect(len(chain)).To(Equal(maxChainLength))
|
||||
Expect(chain).To(Equal("CNI-PREFIX-2bbe0c48b91a7d1b8"))
|
||||
})
|
||||
|
||||
It("must format a short name", func() {
|
||||
chain := MustFormatChainNameWithPrefix("test", "1234", "PREFIX-")
|
||||
Expect(len(chain)).To(Equal(maxChainLength))
|
||||
Expect(chain).To(Equal("CNI-PREFIX-2bbe0c48b91a7d1b8"))
|
||||
})
|
||||
|
||||
It("must truncate a long name", func() {
|
||||
chain := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1234", "PREFIX-")
|
||||
Expect(len(chain)).To(Equal(maxChainLength))
|
||||
Expect(chain).To(Equal("CNI-PREFIX-374f33fe84ab0ed84"))
|
||||
})
|
||||
|
||||
It("must be predictable", func() {
|
||||
chain1 := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1234", "PREFIX-")
|
||||
chain2 := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1234", "PREFIX-")
|
||||
Expect(len(chain1)).To(Equal(maxChainLength))
|
||||
Expect(len(chain2)).To(Equal(maxChainLength))
|
||||
Expect(chain1).To(Equal(chain2))
|
||||
})
|
||||
|
||||
It("must change when a character changes", func() {
|
||||
chain1 := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1234", "PREFIX-")
|
||||
chain2 := MustFormatChainNameWithPrefix("testalongnamethatdoesnotmakesense", "1235", "PREFIX-")
|
||||
Expect(len(chain1)).To(Equal(maxChainLength))
|
||||
Expect(len(chain2)).To(Equal(maxChainLength))
|
||||
Expect(chain1).To(Equal("CNI-PREFIX-374f33fe84ab0ed84"))
|
||||
Expect(chain1).NotTo(Equal(chain2))
|
||||
})
|
||||
|
||||
It("panics when prefix is too large", func() {
|
||||
longPrefix := strings.Repeat("PREFIX-", 4)
|
||||
Expect(func() {
|
||||
MustFormatChainNameWithPrefix("test", "1234", longPrefix)
|
||||
}).To(Panic())
|
||||
})
|
||||
})
|
||||
|
||||
It("must be predictable", func() {
|
||||
chain1 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
|
||||
chain2 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
|
||||
Expect(len(chain1)).To(Equal(maxChainLength))
|
||||
Expect(len(chain2)).To(Equal(maxChainLength))
|
||||
Expect(chain1).To(Equal(chain2))
|
||||
Describe("MustFormatHashWithPrefix", func() {
|
||||
It("always returns a string with the given prefix", func() {
|
||||
Expect(MustFormatHashWithPrefix(10, "AAA", "some string")).To(HavePrefix("AAA"))
|
||||
Expect(MustFormatHashWithPrefix(10, "foo", "some string")).To(HavePrefix("foo"))
|
||||
Expect(MustFormatHashWithPrefix(10, "bar", "some string")).To(HavePrefix("bar"))
|
||||
})
|
||||
|
||||
It("always returns a string of the given length", func() {
|
||||
Expect(MustFormatHashWithPrefix(10, "AAA", "some string")).To(HaveLen(10))
|
||||
Expect(MustFormatHashWithPrefix(15, "AAA", "some string")).To(HaveLen(15))
|
||||
Expect(MustFormatHashWithPrefix(5, "AAA", "some string")).To(HaveLen(5))
|
||||
})
|
||||
|
||||
It("is deterministic", func() {
|
||||
val1 := MustFormatHashWithPrefix(10, "AAA", "some string")
|
||||
val2 := MustFormatHashWithPrefix(10, "AAA", "some string")
|
||||
val3 := MustFormatHashWithPrefix(10, "AAA", "some string")
|
||||
Expect(val1).To(Equal(val2))
|
||||
Expect(val1).To(Equal(val3))
|
||||
})
|
||||
|
||||
It("is (nearly) perfect (injective function)", func() {
|
||||
hashes := map[string]int{}
|
||||
|
||||
for i := 0; i < 1000; i++ {
|
||||
name := fmt.Sprintf("string %d", i)
|
||||
hashes[MustFormatHashWithPrefix(8, "", name)]++
|
||||
}
|
||||
|
||||
for key, count := range hashes {
|
||||
Expect(count).To(Equal(1), "for key "+key+" got non-unique correspondence")
|
||||
}
|
||||
})
|
||||
|
||||
assertPanicWith := func(f func(), expectedErrorMessage string) {
|
||||
defer func() {
|
||||
Expect(recover()).To(Equal(expectedErrorMessage))
|
||||
}()
|
||||
f()
|
||||
Fail("function should have panicked but did not")
|
||||
}
|
||||
|
||||
It("panics when prefix is longer than the length", func() {
|
||||
assertPanicWith(
|
||||
func() { MustFormatHashWithPrefix(3, "AAA", "some string") },
|
||||
"invalid length",
|
||||
)
|
||||
})
|
||||
|
||||
It("panics when length is not positive", func() {
|
||||
assertPanicWith(
|
||||
func() { MustFormatHashWithPrefix(0, "", "some string") },
|
||||
"invalid length",
|
||||
)
|
||||
})
|
||||
|
||||
It("panics when length is larger than MaxLen", func() {
|
||||
assertPanicWith(
|
||||
func() { MustFormatHashWithPrefix(MaxHashLen+1, "", "some string") },
|
||||
"invalid length",
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
It("must change when a character changes", func() {
|
||||
chain1 := FormatChainName("testalongnamethatdoesnotmakesense", "1234")
|
||||
chain2 := FormatChainName("testalongnamethatdoesnotmakesense", "1235")
|
||||
Expect(len(chain1)).To(Equal(maxChainLength))
|
||||
Expect(len(chain2)).To(Equal(maxChainLength))
|
||||
Expect(chain1).To(Equal("CNI-374f33fe84ab0ed84dcdebe3"))
|
||||
Expect(chain1).NotTo(Equal(chain2))
|
||||
})
|
||||
})
|
||||
|
@ -34,7 +34,6 @@ import (
|
||||
)
|
||||
|
||||
const listenFdsStart = 3
|
||||
const resendCount = 3
|
||||
|
||||
var errNoMoreTries = errors.New("no more tries")
|
||||
|
||||
|
@ -34,7 +34,7 @@ import (
|
||||
// RFC 2131 suggests using exponential backoff, starting with 4sec
|
||||
// and randomized to +/- 1sec
|
||||
const resendDelay0 = 4 * time.Second
|
||||
const resendDelayMax = 32 * time.Second
|
||||
const resendDelayMax = 62 * time.Second
|
||||
|
||||
const (
|
||||
leaseStateBound = iota
|
||||
@ -335,8 +335,9 @@ func jitter(span time.Duration) time.Duration {
|
||||
|
||||
func backoffRetry(f func() (*dhcp4.Packet, error)) (*dhcp4.Packet, error) {
|
||||
var baseDelay time.Duration = resendDelay0
|
||||
var sleepTime time.Duration
|
||||
|
||||
for i := 0; i < resendCount; i++ {
|
||||
for {
|
||||
pkt, err := f()
|
||||
if err == nil {
|
||||
return pkt, nil
|
||||
@ -344,10 +345,16 @@ func backoffRetry(f func() (*dhcp4.Packet, error)) (*dhcp4.Packet, error) {
|
||||
|
||||
log.Print(err)
|
||||
|
||||
time.Sleep(baseDelay + jitter(time.Second))
|
||||
sleepTime = baseDelay + jitter(time.Second)
|
||||
|
||||
log.Printf("retrying in %f seconds", sleepTime.Seconds())
|
||||
|
||||
time.Sleep(sleepTime)
|
||||
|
||||
if baseDelay < resendDelayMax {
|
||||
baseDelay *= 2
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,7 +40,7 @@ func NewIPAllocator(s *RangeSet, store backend.Store, id int) *IPAllocator {
|
||||
}
|
||||
}
|
||||
|
||||
// Get alocates an IP
|
||||
// Get allocates an IP
|
||||
func (a *IPAllocator) Get(id string, ifname string, requestedIP net.IP) (*current.IPConfig, error) {
|
||||
a.store.Lock()
|
||||
defer a.store.Unlock()
|
||||
@ -73,6 +73,17 @@ func (a *IPAllocator) Get(id string, ifname string, requestedIP net.IP) (*curren
|
||||
gw = r.Gateway
|
||||
|
||||
} else {
|
||||
// try to get allocated IPs for this given id, if exists, just return error
|
||||
// because duplicate allocation is not allowed in SPEC
|
||||
// https://github.com/containernetworking/cni/blob/master/SPEC.md
|
||||
allocatedIPs := a.store.GetByID(id, ifname)
|
||||
for _, allocatedIP := range allocatedIPs {
|
||||
// check whether the existing IP belong to this range set
|
||||
if _, err := a.rangeset.RangeFor(allocatedIP); err == nil {
|
||||
return nil, fmt.Errorf("%s has been allocated to %s, duplicate allocation is not allowed", allocatedIP.String(), id)
|
||||
}
|
||||
}
|
||||
|
||||
iter, err := a.GetIter()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -221,14 +221,14 @@ var _ = Describe("host-local ip allocator", func() {
|
||||
It("should not allocate the broadcast address", func() {
|
||||
alloc := mkalloc()
|
||||
for i := 2; i < 7; i++ {
|
||||
res, err := alloc.Get("ID", "eth0", nil)
|
||||
res, err := alloc.Get(fmt.Sprintf("ID%d", i), "eth0", nil)
|
||||
Expect(err).ToNot(HaveOccurred())
|
||||
s := fmt.Sprintf("192.168.1.%d/29", i)
|
||||
Expect(s).To(Equal(res.Address.String()))
|
||||
fmt.Fprintln(GinkgoWriter, "got ip", res.Address.String())
|
||||
}
|
||||
|
||||
x, err := alloc.Get("ID", "eth0", nil)
|
||||
x, err := alloc.Get("ID8", "eth0", nil)
|
||||
fmt.Fprintln(GinkgoWriter, "got ip", x)
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
@ -19,10 +19,10 @@ import (
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/plugins/plugins/ipam/host-local/backend"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
const lastIPFilePrefix = "last_reserved_ip."
|
||||
@ -172,6 +172,35 @@ func (s *Store) ReleaseByID(id string, ifname string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// GetByID returns the IPs which have been allocated to the specific ID
|
||||
func (s *Store) GetByID(id string, ifname string) []net.IP {
|
||||
var ips []net.IP
|
||||
|
||||
match := strings.TrimSpace(id) + LineBreak + ifname
|
||||
// matchOld for backwards compatibility
|
||||
matchOld := strings.TrimSpace(id)
|
||||
|
||||
// walk through all ips in this network to get the ones which belong to a specific ID
|
||||
_ = filepath.Walk(s.dataDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil || info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
data, err := ioutil.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
if strings.TrimSpace(string(data)) == match || strings.TrimSpace(string(data)) == matchOld {
|
||||
_, ipString := filepath.Split(path)
|
||||
if ip := net.ParseIP(ipString); ip != nil {
|
||||
ips = append(ips, ip)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
return ips
|
||||
}
|
||||
|
||||
func GetEscapedPath(dataDir string, fname string) string {
|
||||
if runtime.GOOS == "windows" {
|
||||
fname = strings.Replace(fname, ":", "_", -1)
|
||||
|
@ -24,4 +24,5 @@ type Store interface {
|
||||
LastReservedIP(rangeID string) (net.IP, error)
|
||||
Release(ip net.IP) error
|
||||
ReleaseByID(id string, ifname string) error
|
||||
GetByID(id string, ifname string) []net.IP
|
||||
}
|
||||
|
@ -81,6 +81,16 @@ func (s *FakeStore) ReleaseByID(id string, ifname string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *FakeStore) GetByID(id string, ifname string) []net.IP {
|
||||
var ips []net.IP
|
||||
for k, v := range s.ipMap {
|
||||
if v == id {
|
||||
ips = append(ips, net.ParseIP(k))
|
||||
}
|
||||
}
|
||||
return ips
|
||||
}
|
||||
|
||||
func (s *FakeStore) SetIPMap(m map[string]string) {
|
||||
s.ipMap = m
|
||||
}
|
||||
|
@ -65,12 +65,11 @@ options four
|
||||
|
||||
func parse(contents string) (*types.DNS, error) {
|
||||
f, err := ioutil.TempFile("", "host_local_resolv")
|
||||
defer f.Close()
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
defer os.Remove(f.Name())
|
||||
|
||||
if _, err := f.WriteString(contents); err != nil {
|
||||
return nil, err
|
||||
|
@ -250,6 +250,94 @@ var _ = Describe("host-local Operations", func() {
|
||||
Expect(err).To(HaveOccurred())
|
||||
})
|
||||
|
||||
It("repeat allocating addresses on specific interface for same container ID with ADD", func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
|
||||
tmpDir, err := getTmpDir()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer os.RemoveAll(tmpDir)
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet0",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"dataDir": "%s",
|
||||
"ranges": [
|
||||
[{ "subnet": "10.1.2.0/24" }]
|
||||
]
|
||||
}
|
||||
}`, tmpDir)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: nspath,
|
||||
IfName: ifname,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
args1 := &skel.CmdArgs{
|
||||
ContainerID: "dummy1",
|
||||
Netns: nspath,
|
||||
IfName: ifname,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
// Allocate the IP
|
||||
r0, raw, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
|
||||
|
||||
result0, err := current.GetResult(r0)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(result0.IPs)).Should(Equal(1))
|
||||
Expect(result0.IPs[0].Address.String()).Should(Equal("10.1.2.2/24"))
|
||||
|
||||
// Allocate the IP with the same container ID
|
||||
_, _, err = testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
// Allocate the IP with the another container ID
|
||||
r1, raw, err := testutils.CmdAddWithArgs(args1, func() error {
|
||||
return cmdAdd(args1)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
|
||||
|
||||
result1, err := current.GetResult(r1)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(result1.IPs)).Should(Equal(1))
|
||||
Expect(result1.IPs[0].Address.String()).Should(Equal("10.1.2.3/24"))
|
||||
|
||||
// Allocate the IP with the same container ID again
|
||||
_, _, err = testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
ipFilePath := filepath.Join(tmpDir, "mynet0", "10.1.2.2")
|
||||
|
||||
// Release the IPs
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
_, err = os.Stat(ipFilePath)
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
err = testutils.CmdDelWithArgs(args1, func() error {
|
||||
return cmdDel(args1)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("Verify DEL works on backwards compatible allocate", func() {
|
||||
const nspath string = "/some/where"
|
||||
const ifname string = "eth0"
|
||||
|
@ -52,3 +52,17 @@ The following [CNI_ARGS](https://github.com/containernetworking/cni/blob/master/
|
||||
* `GATEWAY`: request a specific gateway address
|
||||
|
||||
(example: CNI_ARGS="IP=10.10.0.1/24;GATEWAY=10.10.0.254")
|
||||
|
||||
The plugin also support following [capability argument](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md).
|
||||
|
||||
* `ips`: Pass IP addresses for CNI interface
|
||||
|
||||
The following [args conventions](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md#args-in-network-config) are supported:
|
||||
|
||||
* `ips` (array of strings): A list of custom IPs to attempt to allocate, with prefix (e.g. '10.10.0.1/24')
|
||||
|
||||
Notice: If some of above are used at same time, only one will work according to the priorities below
|
||||
|
||||
1. [capability argument](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md)
|
||||
1. [args conventions](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md#args-in-network-config)
|
||||
1. [CNI_ARGS](https://github.com/containernetworking/cni/blob/master/SPEC.md#parameters)
|
||||
|
@ -34,6 +34,13 @@ type Net struct {
|
||||
Name string `json:"name"`
|
||||
CNIVersion string `json:"cniVersion"`
|
||||
IPAM *IPAMConfig `json:"ipam"`
|
||||
|
||||
RuntimeConfig struct {
|
||||
IPs []string `json:"ips,omitempty"`
|
||||
} `json:"runtimeConfig,omitempty"`
|
||||
Args *struct {
|
||||
A *IPAMArgs `json:"cni"`
|
||||
} `json:"args"`
|
||||
}
|
||||
|
||||
type IPAMConfig struct {
|
||||
@ -50,6 +57,10 @@ type IPAMEnvArgs struct {
|
||||
GATEWAY types.UnmarshallableString `json:"gateway,omitempty"`
|
||||
}
|
||||
|
||||
type IPAMArgs struct {
|
||||
IPs []string `json:"ips"`
|
||||
}
|
||||
|
||||
type Address struct {
|
||||
AddressStr string `json:"address"`
|
||||
Gateway net.IP `json:"gateway,omitempty"`
|
||||
@ -134,6 +145,65 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
// load IP from CNI_ARGS
|
||||
if envArgs != "" {
|
||||
e := IPAMEnvArgs{}
|
||||
err := types.LoadArgs(envArgs, &e)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if e.IP != "" {
|
||||
for _, item := range strings.Split(string(e.IP), ",") {
|
||||
ipstr := strings.TrimSpace(item)
|
||||
|
||||
ip, subnet, err := net.ParseCIDR(ipstr)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("invalid CIDR %s: %s", ipstr, err)
|
||||
}
|
||||
|
||||
addr := Address{
|
||||
Address: net.IPNet{IP: ip, Mask: subnet.Mask},
|
||||
AddressStr: ipstr,
|
||||
}
|
||||
n.IPAM.Addresses = append(n.IPAM.Addresses, addr)
|
||||
}
|
||||
}
|
||||
|
||||
if e.GATEWAY != "" {
|
||||
for _, item := range strings.Split(string(e.GATEWAY), ",") {
|
||||
gwip := net.ParseIP(strings.TrimSpace(item))
|
||||
if gwip == nil {
|
||||
return nil, "", fmt.Errorf("invalid gateway address: %s", item)
|
||||
}
|
||||
|
||||
for i := range n.IPAM.Addresses {
|
||||
if n.IPAM.Addresses[i].Address.Contains(gwip) {
|
||||
n.IPAM.Addresses[i].Gateway = gwip
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// import address from args
|
||||
if n.Args != nil && n.Args.A != nil && len(n.Args.A.IPs) != 0 {
|
||||
// args IP overwrites IP, so clear IPAM Config
|
||||
n.IPAM.Addresses = make([]Address, 0, len(n.Args.A.IPs))
|
||||
for _, addr := range n.Args.A.IPs {
|
||||
n.IPAM.Addresses = append(n.IPAM.Addresses, Address{AddressStr: addr})
|
||||
}
|
||||
}
|
||||
|
||||
// import address from runtimeConfig
|
||||
if len(n.RuntimeConfig.IPs) != 0 {
|
||||
// runtimeConfig IP overwrites IP, so clear IPAM Config
|
||||
n.IPAM.Addresses = make([]Address, 0, len(n.RuntimeConfig.IPs))
|
||||
for _, addr := range n.RuntimeConfig.IPs {
|
||||
n.IPAM.Addresses = append(n.IPAM.Addresses, Address{AddressStr: addr})
|
||||
}
|
||||
}
|
||||
|
||||
if n.IPAM == nil {
|
||||
return nil, "", fmt.Errorf("IPAM config missing 'ipam' key")
|
||||
}
|
||||
@ -163,50 +233,6 @@ func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) {
|
||||
}
|
||||
}
|
||||
|
||||
if envArgs != "" {
|
||||
e := IPAMEnvArgs{}
|
||||
err := types.LoadArgs(envArgs, &e)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if e.IP != "" {
|
||||
for _, item := range strings.Split(string(e.IP), ",") {
|
||||
ipstr := strings.TrimSpace(item)
|
||||
|
||||
ip, subnet, err := net.ParseCIDR(ipstr)
|
||||
if err != nil {
|
||||
return nil, "", fmt.Errorf("invalid CIDR %s: %s", ipstr, err)
|
||||
}
|
||||
|
||||
addr := Address{Address: net.IPNet{IP: ip, Mask: subnet.Mask}}
|
||||
if addr.Address.IP.To4() != nil {
|
||||
addr.Version = "4"
|
||||
numV4++
|
||||
} else {
|
||||
addr.Version = "6"
|
||||
numV6++
|
||||
}
|
||||
n.IPAM.Addresses = append(n.IPAM.Addresses, addr)
|
||||
}
|
||||
}
|
||||
|
||||
if e.GATEWAY != "" {
|
||||
for _, item := range strings.Split(string(e.GATEWAY), ",") {
|
||||
gwip := net.ParseIP(strings.TrimSpace(item))
|
||||
if gwip == nil {
|
||||
return nil, "", fmt.Errorf("invalid gateway address: %s", item)
|
||||
}
|
||||
|
||||
for i := range n.IPAM.Addresses {
|
||||
if n.IPAM.Addresses[i].Address.Contains(gwip) {
|
||||
n.IPAM.Addresses[i].Gateway = gwip
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CNI spec 0.2.0 and below supported only one v4 and v6 address
|
||||
if numV4 > 1 || numV6 > 1 {
|
||||
for _, v := range types020.SupportedVersions {
|
||||
|
@ -265,6 +265,221 @@ var _ = Describe("static Operations", func() {
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("allocates and releases multiple addresses with ADD/DEL, from RuntimeConfig", func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
|
||||
conf := `{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
"capabilities": {"ips": true},
|
||||
"ipam": {
|
||||
"type": "static",
|
||||
"routes": [
|
||||
{ "dst": "0.0.0.0/0", "gw": "10.10.0.254" },
|
||||
{ "dst": "3ffe:ffff:0:01ff::1/64",
|
||||
"gw": "3ffe:ffff:0::1" } ],
|
||||
"dns": {
|
||||
"nameservers" : ["8.8.8.8"],
|
||||
"domain": "example.com",
|
||||
"search": [ "example.com" ]
|
||||
}
|
||||
},
|
||||
"RuntimeConfig": {
|
||||
"ips" : ["10.10.0.1/24", "3ffe:ffff:0:01ff::1/64"]
|
||||
}
|
||||
}`
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: nspath,
|
||||
IfName: ifname,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
// Allocate the IP
|
||||
r, raw, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Gomega is cranky about slices with different caps
|
||||
Expect(*result.IPs[0]).To(Equal(
|
||||
current.IPConfig{
|
||||
Version: "4",
|
||||
Address: mustCIDR("10.10.0.1/24"),
|
||||
}))
|
||||
Expect(*result.IPs[1]).To(Equal(
|
||||
current.IPConfig{
|
||||
Version: "6",
|
||||
Address: mustCIDR("3ffe:ffff:0:01ff::1/64"),
|
||||
},
|
||||
))
|
||||
Expect(len(result.IPs)).To(Equal(2))
|
||||
Expect(result.Routes).To(Equal([]*types.Route{
|
||||
{Dst: mustCIDR("0.0.0.0/0"), GW: net.ParseIP("10.10.0.254")},
|
||||
{Dst: mustCIDR("3ffe:ffff:0:01ff::1/64"), GW: net.ParseIP("3ffe:ffff:0::1")},
|
||||
}))
|
||||
|
||||
// Release the IP
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("allocates and releases multiple addresses with ADD/DEL, from args", func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
|
||||
conf := `{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
"ipam": {
|
||||
"type": "static",
|
||||
"routes": [
|
||||
{ "dst": "0.0.0.0/0", "gw": "10.10.0.254" },
|
||||
{ "dst": "3ffe:ffff:0:01ff::1/64",
|
||||
"gw": "3ffe:ffff:0::1" } ],
|
||||
"dns": {
|
||||
"nameservers" : ["8.8.8.8"],
|
||||
"domain": "example.com",
|
||||
"search": [ "example.com" ]
|
||||
}
|
||||
},
|
||||
"args": {
|
||||
"cni": {
|
||||
"ips" : ["10.10.0.1/24", "3ffe:ffff:0:01ff::1/64"]
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: nspath,
|
||||
IfName: ifname,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
// Allocate the IP
|
||||
r, raw, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Gomega is cranky about slices with different caps
|
||||
Expect(*result.IPs[0]).To(Equal(
|
||||
current.IPConfig{
|
||||
Version: "4",
|
||||
Address: mustCIDR("10.10.0.1/24"),
|
||||
}))
|
||||
Expect(*result.IPs[1]).To(Equal(
|
||||
current.IPConfig{
|
||||
Version: "6",
|
||||
Address: mustCIDR("3ffe:ffff:0:01ff::1/64"),
|
||||
},
|
||||
))
|
||||
Expect(len(result.IPs)).To(Equal(2))
|
||||
Expect(result.Routes).To(Equal([]*types.Route{
|
||||
{Dst: mustCIDR("0.0.0.0/0"), GW: net.ParseIP("10.10.0.254")},
|
||||
{Dst: mustCIDR("3ffe:ffff:0:01ff::1/64"), GW: net.ParseIP("3ffe:ffff:0::1")},
|
||||
}))
|
||||
|
||||
// Release the IP
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("allocates and releases multiple addresses with ADD/DEL, from RuntimeConfig/ARGS/CNI_ARGS", func() {
|
||||
const ifname string = "eth0"
|
||||
const nspath string = "/some/where"
|
||||
|
||||
conf := `{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "ipvlan",
|
||||
"master": "foo0",
|
||||
"capabilities": {"ips": true},
|
||||
"ipam": {
|
||||
"type": "static",
|
||||
"routes": [
|
||||
{ "dst": "0.0.0.0/0", "gw": "10.10.0.254" },
|
||||
{ "dst": "3ffe:ffff:0:01ff::1/64",
|
||||
"gw": "3ffe:ffff:0::1" } ],
|
||||
"dns": {
|
||||
"nameservers" : ["8.8.8.8"],
|
||||
"domain": "example.com",
|
||||
"search": [ "example.com" ]
|
||||
}
|
||||
},
|
||||
"RuntimeConfig": {
|
||||
"ips" : ["10.10.0.1/24", "3ffe:ffff:0:01ff::1/64"]
|
||||
},
|
||||
"args": {
|
||||
"cni": {
|
||||
"ips" : ["10.10.0.2/24", "3ffe:ffff:0:01ff::2/64"]
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: nspath,
|
||||
IfName: ifname,
|
||||
StdinData: []byte(conf),
|
||||
Args: "IP=10.10.0.3/24,11.11.0.3/24;GATEWAY=10.10.0.254",
|
||||
}
|
||||
|
||||
// Allocate the IP
|
||||
r, raw, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(strings.Index(string(raw), "\"version\":")).Should(BeNumerically(">", 0))
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// only addresses in runtimeConfig configured because of its priorities
|
||||
Expect(*result.IPs[0]).To(Equal(
|
||||
current.IPConfig{
|
||||
Version: "4",
|
||||
Address: mustCIDR("10.10.0.1/24"),
|
||||
}))
|
||||
Expect(*result.IPs[1]).To(Equal(
|
||||
current.IPConfig{
|
||||
Version: "6",
|
||||
Address: mustCIDR("3ffe:ffff:0:01ff::1/64"),
|
||||
},
|
||||
))
|
||||
Expect(len(result.IPs)).To(Equal(2))
|
||||
Expect(result.Routes).To(Equal([]*types.Route{
|
||||
{Dst: mustCIDR("0.0.0.0/0"), GW: net.ParseIP("10.10.0.254")},
|
||||
{Dst: mustCIDR("3ffe:ffff:0:01ff::1/64"), GW: net.ParseIP("3ffe:ffff:0::1")},
|
||||
}))
|
||||
|
||||
// Release the IP
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
func mustCIDR(s string) net.IPNet {
|
||||
|
@ -14,17 +14,18 @@ If the bridge is missing, the plugin will create one on first use and, if gatewa
|
||||
## Example configuration
|
||||
```
|
||||
{
|
||||
"name": "mynet",
|
||||
"type": "bridge",
|
||||
"bridge": "mynet0",
|
||||
"isDefaultGateway": true,
|
||||
"forceAddress": false,
|
||||
"ipMasq": true,
|
||||
"hairpinMode": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.10.0.0/16"
|
||||
}
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "bridge",
|
||||
"bridge": "mynet0",
|
||||
"isDefaultGateway": true,
|
||||
"forceAddress": false,
|
||||
"ipMasq": true,
|
||||
"hairpinMode": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.10.0.0/16"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@ -32,10 +33,10 @@ If the bridge is missing, the plugin will create one on first use and, if gatewa
|
||||
```
|
||||
{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "bridge",
|
||||
"bridge": "mynet0",
|
||||
"ipam": {}
|
||||
"name": "mynet",
|
||||
"type": "bridge",
|
||||
"bridge": "mynet0",
|
||||
"ipam": {}
|
||||
}
|
||||
```
|
||||
|
||||
@ -56,4 +57,4 @@ If the bridge is missing, the plugin will create one on first use and, if gatewa
|
||||
|
||||
*Note:* The VLAN parameter configures the VLAN tag on the host end of the veth and also enables the vlan_filtering feature on the bridge interface.
|
||||
|
||||
*Note:* To configure uplink for L2 network you need to allow the vlan on the uplink interface by using the following command ``` bridge vlan add vid VLAN_ID dev DEV```.
|
||||
*Note:* To configure uplink for L2 network you need to allow the vlan on the uplink interface by using the following command ``` bridge vlan add vid VLAN_ID dev DEV```.
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"net"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/j-keck/arping"
|
||||
"github.com/vishvananda/netlink"
|
||||
@ -35,6 +36,7 @@ import (
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/utils"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
"github.com/containernetworking/plugins/pkg/utils/sysctl"
|
||||
)
|
||||
|
||||
// For testcases to force an error after IPAM has been performed
|
||||
@ -75,6 +77,9 @@ func loadNetConf(bytes []byte) (*NetConf, string, error) {
|
||||
if err := json.Unmarshal(bytes, n); err != nil {
|
||||
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
|
||||
}
|
||||
if n.Vlan < 0 || n.Vlan > 4094 {
|
||||
return nil, "", fmt.Errorf("invalid VLAN ID %d (must be between 0 and 4094)", n.Vlan)
|
||||
}
|
||||
return n, n.CNIVersion, nil
|
||||
}
|
||||
|
||||
@ -175,7 +180,7 @@ func ensureAddr(br netlink.Link, family int, ipn *net.IPNet, forceAddress bool)
|
||||
}
|
||||
|
||||
addr := &netlink.Addr{IPNet: ipn, Label: ""}
|
||||
if err := netlink.AddrAdd(br, addr); err != nil {
|
||||
if err := netlink.AddrAdd(br, addr); err != nil && err != syscall.EEXIST {
|
||||
return fmt.Errorf("could not add IP address to %q: %v", br.Attrs().Name, err)
|
||||
}
|
||||
|
||||
@ -221,7 +226,9 @@ func ensureBridge(brName string, mtu int, promiscMode, vlanFiltering bool) (*net
|
||||
// default packet limit
|
||||
TxQLen: -1,
|
||||
},
|
||||
VlanFiltering: &vlanFiltering,
|
||||
}
|
||||
if vlanFiltering {
|
||||
br.VlanFiltering = &vlanFiltering
|
||||
}
|
||||
|
||||
err := netlink.LinkAdd(br)
|
||||
@ -242,6 +249,9 @@ func ensureBridge(brName string, mtu int, promiscMode, vlanFiltering bool) (*net
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// we want to own the routes for this interface
|
||||
_, _ = sysctl.Sysctl(fmt.Sprintf("net/ipv6/conf/%s/accept_ra", brName), "0")
|
||||
|
||||
if err := netlink.LinkSetUp(br); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -439,11 +449,6 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
|
||||
// Configure the container hardware address and IP address(es)
|
||||
if err := netns.Do(func(_ ns.NetNS) error {
|
||||
contVeth, err := net.InterfaceByName(args.IfName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Disable IPv6 DAD just in case hairpin mode is enabled on the
|
||||
// bridge. Hairpin mode causes echos of neighbor solicitation
|
||||
// packets, which causes DAD failures.
|
||||
@ -460,8 +465,36 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
if err := ipam.ConfigureIface(args.IfName, result); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// check bridge port state
|
||||
retries := []int{0, 50, 500, 1000, 1000}
|
||||
for idx, sleep := range retries {
|
||||
time.Sleep(time.Duration(sleep) * time.Millisecond)
|
||||
|
||||
hostVeth, err := netlink.LinkByName(hostInterface.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if hostVeth.Attrs().OperState == netlink.OperUp {
|
||||
break
|
||||
}
|
||||
|
||||
if idx == len(retries)-1 {
|
||||
return fmt.Errorf("bridge port in error state: %s", hostVeth.Attrs().OperState)
|
||||
}
|
||||
}
|
||||
|
||||
// Send a gratuitous arp
|
||||
if err := netns.Do(func(_ ns.NetNS) error {
|
||||
contVeth, err := net.InterfaceByName(args.IfName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Send a gratuitous arp
|
||||
for _, ipc := range result.IPs {
|
||||
if ipc.Version == "4" {
|
||||
_ = arping.GratuitousArpOverIface(ipc.Address.IP, *contVeth)
|
||||
@ -517,7 +550,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
chain := utils.FormatChainName(n.Name, args.ContainerID)
|
||||
comment := utils.FormatComment(n.Name, args.ContainerID)
|
||||
for _, ipc := range result.IPs {
|
||||
if err = ip.SetupIPMasq(ip.Network(&ipc.Address), chain, comment); err != nil {
|
||||
if err = ip.SetupIPMasq(&ipc.Address, chain, comment); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -17,15 +17,17 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/vishvananda/netlink/nl"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"github.com/vishvananda/netlink/nl"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/020"
|
||||
types020 "github.com/containernetworking/cni/pkg/types/020"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
@ -70,6 +72,7 @@ type testCase struct {
|
||||
isLayer2 bool
|
||||
expGWCIDRs []string // Expected gateway addresses in CIDR form
|
||||
vlan int
|
||||
ipMasq bool
|
||||
}
|
||||
|
||||
// Range definition for each entry in the ranges list
|
||||
@ -105,8 +108,7 @@ const (
|
||||
"vlan": %d`
|
||||
|
||||
netDefault = `,
|
||||
"isDefaultGateway": true,
|
||||
"ipMasq": false`
|
||||
"isDefaultGateway": true`
|
||||
|
||||
ipamStartStr = `,
|
||||
"ipam": {
|
||||
@ -115,6 +117,9 @@ const (
|
||||
ipamDataDirStr = `,
|
||||
"dataDir": "%s"`
|
||||
|
||||
ipMasqConfStr = `,
|
||||
"ipMasq": %t`
|
||||
|
||||
// Single subnet configuration (legacy)
|
||||
subnetConfStr = `,
|
||||
"subnet": "%s"`
|
||||
@ -147,6 +152,9 @@ func (tc testCase) netConfJSON(dataDir string) string {
|
||||
if tc.vlan != 0 {
|
||||
conf += fmt.Sprintf(vlan, tc.vlan)
|
||||
}
|
||||
if tc.ipMasq {
|
||||
conf += tc.ipMasqConfig()
|
||||
}
|
||||
|
||||
if !tc.isLayer2 {
|
||||
conf += netDefault
|
||||
@ -178,6 +186,11 @@ func (tc testCase) subnetConfig() string {
|
||||
return conf
|
||||
}
|
||||
|
||||
func (tc testCase) ipMasqConfig() string {
|
||||
conf := fmt.Sprintf(ipMasqConfStr, tc.ipMasq)
|
||||
return conf
|
||||
}
|
||||
|
||||
func (tc testCase) rangesConfig() string {
|
||||
conf := rangesStartStr
|
||||
for i, tcRange := range tc.ranges {
|
||||
@ -1112,6 +1125,7 @@ var _ = Describe("bridge Operations", func() {
|
||||
AfterEach(func() {
|
||||
Expect(os.RemoveAll(dataDir)).To(Succeed())
|
||||
Expect(originalNS.Close()).To(Succeed())
|
||||
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
|
||||
})
|
||||
|
||||
It("creates a bridge", func() {
|
||||
@ -1595,4 +1609,84 @@ var _ = Describe("bridge Operations", func() {
|
||||
cmdAddDelCheckTest(originalNS, tc, dataDir)
|
||||
}
|
||||
})
|
||||
|
||||
It("configures a bridge and ipMasq rules for 0.4.0 config", func() {
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
tc := testCase{
|
||||
ranges: []rangeInfo{{
|
||||
subnet: "10.1.2.0/24",
|
||||
}},
|
||||
ipMasq: true,
|
||||
cniVersion: "0.4.0",
|
||||
}
|
||||
|
||||
args := tc.createCmdArgs(originalNS, dataDir)
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
result, err := current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(result.IPs).Should(HaveLen(1))
|
||||
|
||||
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
rules, err := ipt.List("nat", "POSTROUTING")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(rules).Should(ContainElement(ContainSubstring(result.IPs[0].Address.IP.String())))
|
||||
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("check vlan id when loading net conf", func() {
|
||||
tests := []struct {
|
||||
tc testCase
|
||||
err error
|
||||
}{
|
||||
{
|
||||
tc: testCase{
|
||||
cniVersion: "0.4.0",
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
tc: testCase{
|
||||
cniVersion: "0.4.0",
|
||||
vlan: 0,
|
||||
},
|
||||
err: nil,
|
||||
},
|
||||
{
|
||||
tc: testCase{
|
||||
cniVersion: "0.4.0",
|
||||
vlan: -100,
|
||||
},
|
||||
err: fmt.Errorf("invalid VLAN ID -100 (must be between 0 and 4094)"),
|
||||
},
|
||||
{
|
||||
tc: testCase{
|
||||
cniVersion: "0.4.0",
|
||||
vlan: 5000,
|
||||
},
|
||||
err: fmt.Errorf("invalid VLAN ID 5000 (must be between 0 and 4094)"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
_, _, err := loadNetConf([]byte(test.tc.netConfJSON("")))
|
||||
if test.err == nil {
|
||||
Expect(err).To(BeNil())
|
||||
} else {
|
||||
Expect(err).To(Equal(test.err))
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -1,17 +1,28 @@
|
||||
# host-device
|
||||
|
||||
Move an already-existing device into a container.
|
||||
|
||||
This simple plugin will move the requested device from the host's network namespace
|
||||
to the container's. Nothing else will be done - no IPAM, no addresses.
|
||||
## Overview
|
||||
|
||||
The device can be specified with any one of three properties:
|
||||
This simple plugin will move the requested device from the host's network namespace
|
||||
to the container's. IPAM configuration can be used for this plugin.
|
||||
|
||||
## Network configuration reference
|
||||
|
||||
The device can be specified with any one of four properties:
|
||||
* `device`: The device name, e.g. `eth0`, `can0`
|
||||
* `hwaddr`: A MAC address
|
||||
* `kernelpath`: The kernel device kobj, e.g. `/sys/devices/pci0000:00/0000:00:1f.6`
|
||||
* `pciBusID`: A PCI address of network device, e.g `0000:00:1f.6`
|
||||
|
||||
For this plugin, `CNI_IFNAME` will be ignored. Upon DEL, the device will be moved back.
|
||||
|
||||
A sample configuration might look like:
|
||||
The plugin also supports the following [capability argument](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md):
|
||||
* `deviceID`: A PCI address of the network device, e.g `0000:00:1f.6`
|
||||
|
||||
## Example configuration
|
||||
|
||||
A sample configuration with `device` property looks like:
|
||||
|
||||
```json
|
||||
{
|
||||
@ -20,3 +31,36 @@ A sample configuration might look like:
|
||||
"device": "enp0s1"
|
||||
}
|
||||
```
|
||||
|
||||
A sample configuration with `pciBusID` property looks like:
|
||||
|
||||
```json
|
||||
{
|
||||
"cniVersion": "0.3.1",
|
||||
"type": "host-device",
|
||||
"pciBusID": "0000:3d:00.1"
|
||||
}
|
||||
```
|
||||
|
||||
A sample configuration utilizing `deviceID` runtime configuration looks like:
|
||||
|
||||
1. From operator perspective:
|
||||
```json
|
||||
{
|
||||
"cniVersion": "0.3.1",
|
||||
"type": "host-device",
|
||||
"capabilities": {
|
||||
"deviceID": true
|
||||
}
|
||||
}
|
||||
```
|
||||
2. From plugin perspective:
|
||||
```json
|
||||
{
|
||||
"cniVersion": "0.3.1",
|
||||
"type": "host-device",
|
||||
"runtimeConfig": {
|
||||
"deviceID": "0000:3d:00.1"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
@ -38,12 +39,20 @@ import (
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
const (
|
||||
sysBusPCI = "/sys/bus/pci/devices"
|
||||
)
|
||||
|
||||
//NetConf for host-device config, look the README to learn how to use those parameters
|
||||
type NetConf struct {
|
||||
types.NetConf
|
||||
Device string `json:"device"` // Device-Name, something like eth0 or can0 etc.
|
||||
HWAddr string `json:"hwaddr"` // MAC Address of target network interface
|
||||
KernelPath string `json:"kernelpath"` // Kernelpath of the device
|
||||
Device string `json:"device"` // Device-Name, something like eth0 or can0 etc.
|
||||
HWAddr string `json:"hwaddr"` // MAC Address of target network interface
|
||||
KernelPath string `json:"kernelpath"` // Kernelpath of the device
|
||||
PCIAddr string `json:"pciBusID"` // PCI Address of target network device
|
||||
RuntimeConfig struct {
|
||||
DeviceID string `json:"deviceID,omitempty"`
|
||||
} `json:"runtimeConfig,omitempty"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -58,9 +67,16 @@ func loadConf(bytes []byte) (*NetConf, error) {
|
||||
if err := json.Unmarshal(bytes, n); err != nil {
|
||||
return nil, fmt.Errorf("failed to load netconf: %v", err)
|
||||
}
|
||||
if n.Device == "" && n.HWAddr == "" && n.KernelPath == "" {
|
||||
return nil, fmt.Errorf(`specify either "device", "hwaddr" or "kernelpath"`)
|
||||
|
||||
if n.RuntimeConfig.DeviceID != "" {
|
||||
// Override PCI device with the standardized DeviceID provided in Runtime Config.
|
||||
n.PCIAddr = n.RuntimeConfig.DeviceID
|
||||
}
|
||||
|
||||
if n.Device == "" && n.HWAddr == "" && n.KernelPath == "" && n.PCIAddr == "" {
|
||||
return nil, fmt.Errorf(`specify either "device", "hwaddr", "kernelpath" or "pciBusID"`)
|
||||
}
|
||||
|
||||
return n, nil
|
||||
}
|
||||
|
||||
@ -75,7 +91,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
defer containerNs.Close()
|
||||
|
||||
hostDev, err := getLink(cfg.Device, cfg.HWAddr, cfg.KernelPath)
|
||||
hostDev, err := getLink(cfg.Device, cfg.HWAddr, cfg.KernelPath, cfg.PCIAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find host device: %v", err)
|
||||
}
|
||||
@ -177,6 +193,10 @@ func moveLinkIn(hostDev netlink.Link, containerNs ns.NetNS, ifName string) (netl
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find %q: %v", hostDev.Attrs().Name, err)
|
||||
}
|
||||
// Devices can be renamed only when down
|
||||
if err = netlink.LinkSetDown(contDev); err != nil {
|
||||
return fmt.Errorf("failed to set %q down: %v", hostDev.Attrs().Name, err)
|
||||
}
|
||||
// Save host device name into the container device's alias property
|
||||
if err := netlink.LinkSetAlias(contDev, hostDev.Attrs().Name); err != nil {
|
||||
return fmt.Errorf("failed to set alias to %q: %v", hostDev.Attrs().Name, err)
|
||||
@ -212,14 +232,23 @@ func moveLinkOut(containerNs ns.NetNS, ifName string) error {
|
||||
}
|
||||
|
||||
// Devices can be renamed only when down
|
||||
if err := netlink.LinkSetDown(dev); err != nil {
|
||||
if err = netlink.LinkSetDown(dev); err != nil {
|
||||
return fmt.Errorf("failed to set %q down: %v", ifName, err)
|
||||
}
|
||||
|
||||
// Rename device to it's original name
|
||||
if err := netlink.LinkSetName(dev, dev.Attrs().Alias); err != nil {
|
||||
if err = netlink.LinkSetName(dev, dev.Attrs().Alias); err != nil {
|
||||
return fmt.Errorf("failed to restore %q to original name %q: %v", ifName, dev.Attrs().Alias, err)
|
||||
}
|
||||
if err := netlink.LinkSetNsFd(dev, int(defaultNs.Fd())); err != nil {
|
||||
defer func() {
|
||||
if err != nil {
|
||||
// if moving device to host namespace fails, we should revert device name
|
||||
// to ifName to make sure that device can be found in retries
|
||||
_ = netlink.LinkSetName(dev, ifName)
|
||||
}
|
||||
}()
|
||||
|
||||
if err = netlink.LinkSetNsFd(dev, int(defaultNs.Fd())); err != nil {
|
||||
return fmt.Errorf("failed to move %q to host netns: %v", dev.Attrs().Alias, err)
|
||||
}
|
||||
return nil
|
||||
@ -240,7 +269,7 @@ func printLink(dev netlink.Link, cniVersion string, containerNs ns.NetNS) error
|
||||
return types.PrintResult(&result, cniVersion)
|
||||
}
|
||||
|
||||
func getLink(devname, hwaddr, kernelpath string) (netlink.Link, error) {
|
||||
func getLink(devname, hwaddr, kernelpath, pciaddr string) (netlink.Link, error) {
|
||||
links, err := netlink.LinkList()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to list node links: %v", err)
|
||||
@ -278,6 +307,24 @@ func getLink(devname, hwaddr, kernelpath string) (netlink.Link, error) {
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if len(pciaddr) > 0 {
|
||||
netDir := filepath.Join(sysBusPCI, pciaddr, "net")
|
||||
if _, err := os.Lstat(netDir); err != nil {
|
||||
virtioNetDir := filepath.Join(sysBusPCI, pciaddr, "virtio*", "net")
|
||||
matches, err := filepath.Glob(virtioNetDir)
|
||||
if matches == nil || err != nil {
|
||||
return nil, fmt.Errorf("no net directory under pci device %s", pciaddr)
|
||||
}
|
||||
netDir = matches[0]
|
||||
}
|
||||
fInfo, err := ioutil.ReadDir(netDir)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to read net directory %s: %q", netDir, err)
|
||||
}
|
||||
if len(fInfo) > 0 {
|
||||
return netlink.LinkByName(fInfo[0].Name())
|
||||
}
|
||||
return nil, fmt.Errorf("failed to find device name for pci address %s", pciaddr)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to find physical interface")
|
||||
|
@ -40,6 +40,7 @@ type Net struct {
|
||||
Device string `json:"device"` // Device-Name, something like eth0 or can0 etc.
|
||||
HWAddr string `json:"hwaddr"` // MAC Address of target network interface
|
||||
KernelPath string `json:"kernelpath"` // Kernelpath of the device
|
||||
PCIAddr string `json:"pciBusID"` // PCI Address of target network device
|
||||
IPAM *IPAMConfig `json:"ipam,omitempty"`
|
||||
DNS types.DNS `json:"dns"`
|
||||
RawPrevResult map[string]interface{} `json:"prevResult,omitempty"`
|
||||
@ -232,14 +233,15 @@ var _ = Describe("base functionality", func() {
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
originalNS.Close()
|
||||
Expect(originalNS.Close()).To(Succeed())
|
||||
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
|
||||
})
|
||||
|
||||
It("Works with a valid config without IPAM", func() {
|
||||
var origLink netlink.Link
|
||||
|
||||
// prepare ifname in original namespace
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
@ -253,7 +255,6 @@ var _ = Describe("base functionality", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// call CmdAdd
|
||||
targetNS, err := testutils.NewNS()
|
||||
@ -293,26 +294,24 @@ var _ = Describe("base functionality", func() {
|
||||
}))
|
||||
|
||||
// assert that dummy0 is now in the target namespace
|
||||
err = targetNS.Do(func(ns.NetNS) error {
|
||||
_ = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
link, err := netlink.LinkByName(cniName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// assert that dummy0 is now NOT in the original namespace anymore
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
_, err := netlink.LinkByName(ifname)
|
||||
Expect(err).To(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Check that deleting the device moves it back and restores the name
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
@ -323,15 +322,16 @@ var _ = Describe("base functionality", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
})
|
||||
|
||||
It("Works with a valid config with IPAM", func() {
|
||||
var origLink netlink.Link
|
||||
It("Test idempotence of CmdDel", func() {
|
||||
var (
|
||||
origLink netlink.Link
|
||||
conflictLink netlink.Link
|
||||
)
|
||||
|
||||
// prepare ifname in original namespace
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
// prepare host device in original namespace
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
@ -345,8 +345,142 @@ var _ = Describe("base functionality", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// call CmdAdd
|
||||
targetNS, err := testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
cniName := "eth0"
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.0",
|
||||
"name": "cni-plugin-host-device-test",
|
||||
"type": "host-device",
|
||||
"device": %q
|
||||
}`, ifname)
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: cniName,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
var resI types.Result
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
var err error
|
||||
resI, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
|
||||
return err
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// check that the result is sane
|
||||
res, err := current.NewResultFromResult(resI)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res.Interfaces).To(Equal([]*current.Interface{
|
||||
{
|
||||
Name: cniName,
|
||||
Mac: origLink.Attrs().HardwareAddr.String(),
|
||||
Sandbox: targetNS.Path(),
|
||||
},
|
||||
}))
|
||||
|
||||
// assert that dummy0 is now in the target namespace
|
||||
_ = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
link, err := netlink.LinkByName(cniName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
|
||||
return nil
|
||||
})
|
||||
|
||||
// assert that dummy0 is now NOT in the original namespace anymore
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
_, err := netlink.LinkByName(ifname)
|
||||
Expect(err).To(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// create another conflict host device with same name "dummy0"
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifname,
|
||||
},
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
conflictLink, err = netlink.LinkByName(ifname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = netlink.LinkSetUp(conflictLink)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// try to call CmdDel and fails
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).To(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// assert container interface "eth0" still exists in target namespace
|
||||
_ = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
link, err := netlink.LinkByName(cniName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
|
||||
return nil
|
||||
})
|
||||
|
||||
// remove the conflict host device
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err = netlink.LinkDel(conflictLink)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// try to call CmdDel and succeed
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// assert that dummy0 is now back in the original namespace
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
_, err := netlink.LinkByName(ifname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
It("Works with a valid config with IPAM", func() {
|
||||
var origLink netlink.Link
|
||||
|
||||
// prepare ifname in original namespace
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifname,
|
||||
},
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
origLink, err = netlink.LinkByName(ifname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = netlink.LinkSetUp(origLink)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// call CmdAdd
|
||||
targetNS, err := testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -393,7 +527,7 @@ var _ = Describe("base functionality", func() {
|
||||
}))
|
||||
|
||||
// assert that dummy0 is now in the target namespace
|
||||
err = targetNS.Do(func(ns.NetNS) error {
|
||||
_ = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
link, err := netlink.LinkByName(cniName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -408,19 +542,17 @@ var _ = Describe("base functionality", func() {
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// assert that dummy0 is now NOT in the original namespace anymore
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
_, err := netlink.LinkByName(ifname)
|
||||
Expect(err).To(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Check that deleting the device moves it back and restores the name
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
@ -431,8 +563,6 @@ var _ = Describe("base functionality", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
})
|
||||
|
||||
It("fails an invalid config", func() {
|
||||
@ -449,7 +579,7 @@ var _ = Describe("base functionality", func() {
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
_, _, err := testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
|
||||
Expect(err).To(MatchError(`specify either "device", "hwaddr" or "kernelpath"`))
|
||||
Expect(err).To(MatchError(`specify either "device", "hwaddr", "kernelpath" or "pciBusID"`))
|
||||
|
||||
})
|
||||
|
||||
@ -457,7 +587,7 @@ var _ = Describe("base functionality", func() {
|
||||
var origLink netlink.Link
|
||||
|
||||
// prepare ifname in original namespace
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
@ -471,7 +601,6 @@ var _ = Describe("base functionality", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// call CmdAdd
|
||||
targetNS, err := testutils.NewNS()
|
||||
@ -511,23 +640,21 @@ var _ = Describe("base functionality", func() {
|
||||
}))
|
||||
|
||||
// assert that dummy0 is now in the target namespace
|
||||
err = targetNS.Do(func(ns.NetNS) error {
|
||||
_ = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
link, err := netlink.LinkByName(cniName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// assert that dummy0 is now NOT in the original namespace anymore
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
_, err := netlink.LinkByName(ifname)
|
||||
Expect(err).To(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// call CmdCheck
|
||||
n := &Net{}
|
||||
@ -554,7 +681,7 @@ var _ = Describe("base functionality", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Check that deleting the device moves it back and restores the name
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
@ -565,15 +692,13 @@ var _ = Describe("base functionality", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
})
|
||||
|
||||
It("Works with a valid 0.4.0 config with IPAM", func() {
|
||||
var origLink netlink.Link
|
||||
|
||||
// prepare ifname in original namespace
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
@ -587,7 +712,6 @@ var _ = Describe("base functionality", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// call CmdAdd
|
||||
targetNS, err := testutils.NewNS()
|
||||
@ -635,7 +759,7 @@ var _ = Describe("base functionality", func() {
|
||||
}))
|
||||
|
||||
// assert that dummy0 is now in the target namespace
|
||||
err = targetNS.Do(func(ns.NetNS) error {
|
||||
_ = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
link, err := netlink.LinkByName(cniName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
@ -650,16 +774,14 @@ var _ = Describe("base functionality", func() {
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// assert that dummy0 is now NOT in the original namespace anymore
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
_, err := netlink.LinkByName(ifname)
|
||||
Expect(err).To(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// call CmdCheck
|
||||
n := &Net{}
|
||||
@ -689,7 +811,7 @@ var _ = Describe("base functionality", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Check that deleting the device moves it back and restores the name
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
@ -700,8 +822,144 @@ var _ = Describe("base functionality", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
})
|
||||
|
||||
It("Test idempotence of CmdDel with 0.4.0 config", func() {
|
||||
var (
|
||||
origLink netlink.Link
|
||||
conflictLink netlink.Link
|
||||
)
|
||||
|
||||
// prepare host device in original namespace
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifname,
|
||||
},
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
origLink, err = netlink.LinkByName(ifname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = netlink.LinkSetUp(origLink)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// call CmdAdd
|
||||
targetNS, err := testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
cniName := "eth0"
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "cni-plugin-host-device-test",
|
||||
"type": "host-device",
|
||||
"device": %q
|
||||
}`, ifname)
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNS.Path(),
|
||||
IfName: cniName,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
var resI types.Result
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
var err error
|
||||
resI, _, err = testutils.CmdAddWithArgs(args, func() error { return cmdAdd(args) })
|
||||
return err
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// check that the result is sane
|
||||
res, err := current.NewResultFromResult(resI)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(res.Interfaces).To(Equal([]*current.Interface{
|
||||
{
|
||||
Name: cniName,
|
||||
Mac: origLink.Attrs().HardwareAddr.String(),
|
||||
Sandbox: targetNS.Path(),
|
||||
},
|
||||
}))
|
||||
|
||||
// assert that dummy0 is now in the target namespace
|
||||
_ = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
link, err := netlink.LinkByName(cniName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
|
||||
return nil
|
||||
})
|
||||
|
||||
// assert that dummy0 is now NOT in the original namespace anymore
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
_, err := netlink.LinkByName(ifname)
|
||||
Expect(err).To(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// create another conflict host device with same name "dummy0"
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err := netlink.LinkAdd(&netlink.Dummy{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: ifname,
|
||||
},
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
conflictLink, err = netlink.LinkByName(ifname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = netlink.LinkSetUp(conflictLink)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// try to call CmdDel and fails
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).To(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// assert container interface "eth0" still exists in target namespace
|
||||
err = targetNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
link, err := netlink.LinkByName(cniName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(origLink.Attrs().HardwareAddr))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// remove the conflict host device
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err = netlink.LinkDel(conflictLink)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// try to call CmdDel and succeed
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
|
||||
// assert that dummy0 is now back in the original namespace
|
||||
_ = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
_, err := netlink.LinkByName(ifname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -297,6 +297,7 @@ var _ = Describe("ipvlan Operations", func() {
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(originalNS.Close()).To(Succeed())
|
||||
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
|
||||
})
|
||||
|
||||
It("creates an ipvlan link in a non-default namespace", func() {
|
||||
|
@ -15,9 +15,15 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
@ -25,9 +31,34 @@ import (
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
func parseNetConf(bytes []byte) (*types.NetConf, error) {
|
||||
conf := &types.NetConf{}
|
||||
if err := json.Unmarshal(bytes, conf); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse network config: %v", err)
|
||||
}
|
||||
|
||||
if conf.RawPrevResult != nil {
|
||||
if err := version.ParsePrevResult(conf); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse prevResult: %v", err)
|
||||
}
|
||||
if _, err := current.NewResultFromResult(conf.PrevResult); err != nil {
|
||||
return nil, fmt.Errorf("failed to convert result to current version: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
return conf, nil
|
||||
}
|
||||
|
||||
func cmdAdd(args *skel.CmdArgs) error {
|
||||
conf, err := parseNetConf(args.StdinData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var v4Addr, v6Addr *net.IPNet
|
||||
|
||||
args.IfName = "lo" // ignore config, this only works for loopback
|
||||
err := ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||
link, err := netlink.LinkByName(args.IfName)
|
||||
if err != nil {
|
||||
return err // not tested
|
||||
@ -38,14 +69,69 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
return err // not tested
|
||||
}
|
||||
|
||||
v4Addrs, err := netlink.AddrList(link, netlink.FAMILY_V4)
|
||||
if err != nil {
|
||||
return err // not tested
|
||||
}
|
||||
if len(v4Addrs) != 0 {
|
||||
v4Addr = v4Addrs[0].IPNet
|
||||
// sanity check that this is a loopback address
|
||||
for _, addr := range v4Addrs {
|
||||
if !addr.IP.IsLoopback() {
|
||||
return fmt.Errorf("loopback interface found with non-loopback address %q", addr.IP)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
v6Addrs, err := netlink.AddrList(link, netlink.FAMILY_V6)
|
||||
if err != nil {
|
||||
return err // not tested
|
||||
}
|
||||
if len(v6Addrs) != 0 {
|
||||
v6Addr = v6Addrs[0].IPNet
|
||||
// sanity check that this is a loopback address
|
||||
for _, addr := range v6Addrs {
|
||||
if !addr.IP.IsLoopback() {
|
||||
return fmt.Errorf("loopback interface found with non-loopback address %q", addr.IP)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err // not tested
|
||||
}
|
||||
|
||||
result := current.Result{}
|
||||
return result.Print()
|
||||
var result types.Result
|
||||
if conf.PrevResult != nil {
|
||||
// If loopback has previous result which passes from previous CNI plugin,
|
||||
// loopback should pass it transparently
|
||||
result = conf.PrevResult
|
||||
} else {
|
||||
loopbackInterface := ¤t.Interface{Name: args.IfName, Mac: "00:00:00:00:00:00", Sandbox: args.Netns}
|
||||
r := ¤t.Result{CNIVersion: conf.CNIVersion, Interfaces: []*current.Interface{loopbackInterface}}
|
||||
|
||||
if v4Addr != nil {
|
||||
r.IPs = append(r.IPs, ¤t.IPConfig{
|
||||
Version: "4",
|
||||
Interface: current.Int(0),
|
||||
Address: *v4Addr,
|
||||
})
|
||||
}
|
||||
|
||||
if v6Addr != nil {
|
||||
r.IPs = append(r.IPs, ¤t.IPConfig{
|
||||
Version: "6",
|
||||
Interface: current.Int(0),
|
||||
Address: *v6Addr,
|
||||
})
|
||||
}
|
||||
|
||||
result = r
|
||||
}
|
||||
|
||||
return types.PrintResult(result, conf.CNIVersion)
|
||||
}
|
||||
|
||||
func cmdDel(args *skel.CmdArgs) error {
|
||||
@ -78,6 +164,18 @@ func main() {
|
||||
}
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
// TODO: implement
|
||||
return nil
|
||||
args.IfName = "lo" // ignore config, this only works for loopback
|
||||
|
||||
return ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||
link, err := netlink.LinkByName(args.IfName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if link.Attrs().Flags&net.FlagUp != net.FlagUp {
|
||||
return errors.New("loopback interface is down")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
@ -45,21 +45,20 @@ var _ = Describe("Loopback", func() {
|
||||
environ = []string{
|
||||
fmt.Sprintf("CNI_CONTAINERID=%s", "dummy"),
|
||||
fmt.Sprintf("CNI_NETNS=%s", networkNS.Path()),
|
||||
fmt.Sprintf("CNI_IFNAME=%s", "this is ignored"),
|
||||
fmt.Sprintf("CNI_IFNAME=%s", "lo"),
|
||||
fmt.Sprintf("CNI_ARGS=%s", "none"),
|
||||
fmt.Sprintf("CNI_PATH=%s", "/some/test/path"),
|
||||
}
|
||||
command.Stdin = strings.NewReader(`{ "cniVersion": "0.1.0" }`)
|
||||
command.Stdin = strings.NewReader(`{ "name": "loopback-test", "cniVersion": "0.1.0" }`)
|
||||
})
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(networkNS.Close()).To(Succeed())
|
||||
Expect(testutils.UnmountNS(networkNS)).To(Succeed())
|
||||
})
|
||||
|
||||
Context("when given a network namespace", func() {
|
||||
It("sets the lo device to UP", func() {
|
||||
|
||||
Skip("TODO: add network name")
|
||||
command.Env = append(environ, fmt.Sprintf("CNI_COMMAND=%s", "ADD"))
|
||||
|
||||
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
|
||||
@ -80,8 +79,6 @@ var _ = Describe("Loopback", func() {
|
||||
})
|
||||
|
||||
It("sets the lo device to DOWN", func() {
|
||||
|
||||
Skip("TODO: add network name")
|
||||
command.Env = append(environ, fmt.Sprintf("CNI_COMMAND=%s", "DEL"))
|
||||
|
||||
session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter)
|
||||
|
@ -23,12 +23,12 @@ Since each macvlan interface has its own MAC address, it makes it easy to use wi
|
||||
|
||||
* `name` (string, required): the name of the network
|
||||
* `type` (string, required): "macvlan"
|
||||
* `master` (string, optional): name of the host interface to enslave. Defaults to default route interace.
|
||||
* `master` (string, optional): name of the host interface to enslave. Defaults to default route interface.
|
||||
* `mode` (string, optional): one of "bridge", "private", "vepa", "passthru". Defaults to "bridge".
|
||||
* `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to the value chosen by the kernel.
|
||||
* `mtu` (integer, optional): explicitly set MTU to the specified value. Defaults to the value chosen by the kernel. The value must be \[0, master's MTU\].
|
||||
* `ipam` (dictionary, required): IPAM configuration to be used for this network. For interface only without ip address, create empty dictionary.
|
||||
|
||||
## Notes
|
||||
|
||||
* If are testing on a laptop, please remember that most wireless cards do not support being enslaved by macvlan.
|
||||
* If you are testing on a laptop, please remember that most wireless cards do not support being enslaved by macvlan.
|
||||
* A single master interface can not be enslaved by both `macvlan` and `ipvlan`.
|
||||
|
@ -45,6 +45,17 @@ type NetConf struct {
|
||||
Master string `json:"master"`
|
||||
Mode string `json:"mode"`
|
||||
MTU int `json:"mtu"`
|
||||
Mac string `json:"mac,omitempty"`
|
||||
|
||||
RuntimeConfig struct {
|
||||
Mac string `json:"mac,omitempty"`
|
||||
} `json:"runtimeConfig,omitempty"`
|
||||
}
|
||||
|
||||
// MacEnvArgs represents CNI_ARG
|
||||
type MacEnvArgs struct {
|
||||
types.CommonArgs
|
||||
MAC types.UnmarshallableString `json:"mac,omitempty"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -73,7 +84,7 @@ func getDefaultRouteInterfaceName() (string, error) {
|
||||
return "", fmt.Errorf("no default route interface found")
|
||||
}
|
||||
|
||||
func loadConf(bytes []byte) (*NetConf, string, error) {
|
||||
func loadConf(bytes []byte, envArgs string) (*NetConf, string, error) {
|
||||
n := &NetConf{}
|
||||
if err := json.Unmarshal(bytes, n); err != nil {
|
||||
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
|
||||
@ -85,9 +96,43 @@ func loadConf(bytes []byte) (*NetConf, string, error) {
|
||||
}
|
||||
n.Master = defaultRouteInterface
|
||||
}
|
||||
|
||||
// check existing and MTU of master interface
|
||||
masterMTU, err := getMTUByName(n.Master)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if n.MTU < 0 || n.MTU > masterMTU {
|
||||
return nil, "", fmt.Errorf("invalid MTU %d, must be [0, master MTU(%d)]", n.MTU, masterMTU)
|
||||
}
|
||||
|
||||
if envArgs != "" {
|
||||
e := MacEnvArgs{}
|
||||
err := types.LoadArgs(envArgs, &e)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if e.MAC != "" {
|
||||
n.Mac = string(e.MAC)
|
||||
}
|
||||
}
|
||||
|
||||
if n.RuntimeConfig.Mac != "" {
|
||||
n.Mac = n.RuntimeConfig.Mac
|
||||
}
|
||||
|
||||
return n, n.CNIVersion, nil
|
||||
}
|
||||
|
||||
func getMTUByName(ifName string) (int, error) {
|
||||
link, err := netlink.LinkByName(ifName)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return link.Attrs().MTU, nil
|
||||
}
|
||||
|
||||
func modeFromString(s string) (netlink.MacvlanMode, error) {
|
||||
switch s {
|
||||
case "", "bridge":
|
||||
@ -138,14 +183,24 @@ func createMacvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Inter
|
||||
return nil, err
|
||||
}
|
||||
|
||||
linkAttrs := netlink.LinkAttrs{
|
||||
MTU: conf.MTU,
|
||||
Name: tmpName,
|
||||
ParentIndex: m.Attrs().Index,
|
||||
Namespace: netlink.NsFd(int(netns.Fd())),
|
||||
}
|
||||
|
||||
if conf.Mac != "" {
|
||||
addr, err := net.ParseMAC(conf.Mac)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid args %v for MAC addr: %v", conf.Mac, err)
|
||||
}
|
||||
linkAttrs.HardwareAddr = addr
|
||||
}
|
||||
|
||||
mv := &netlink.Macvlan{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
MTU: conf.MTU,
|
||||
Name: tmpName,
|
||||
ParentIndex: m.Attrs().Index,
|
||||
Namespace: netlink.NsFd(int(netns.Fd())),
|
||||
},
|
||||
Mode: mode,
|
||||
LinkAttrs: linkAttrs,
|
||||
Mode: mode,
|
||||
}
|
||||
|
||||
if err := netlink.LinkAdd(mv); err != nil {
|
||||
@ -186,7 +241,7 @@ func createMacvlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Inter
|
||||
}
|
||||
|
||||
func cmdAdd(args *skel.CmdArgs) error {
|
||||
n, cniVersion, err := loadConf(args.StdinData)
|
||||
n, cniVersion, err := loadConf(args.StdinData, args.Args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -293,7 +348,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
func cmdDel(args *skel.CmdArgs) error {
|
||||
n, _, err := loadConf(args.StdinData)
|
||||
n, _, err := loadConf(args.StdinData, args.Args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -331,7 +386,7 @@ func main() {
|
||||
|
||||
func cmdCheck(args *skel.CmdArgs) error {
|
||||
|
||||
n, _, err := loadConf(args.StdinData)
|
||||
n, _, err := loadConf(args.StdinData, args.Args)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -123,6 +123,7 @@ var _ = Describe("macvlan Operations", func() {
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(originalNS.Close()).To(Succeed())
|
||||
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
|
||||
})
|
||||
|
||||
It("creates an macvlan link in a non-default namespace", func() {
|
||||
@ -593,4 +594,176 @@ var _ = Describe("macvlan Operations", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("configures and deconfigures l2 macvlan link with mac address (from CNI_ARGS) with CNI v4 ADD/DEL", func() {
|
||||
const IFNAME = "macvl0"
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "macvlan",
|
||||
"master": "%s",
|
||||
"ipam": {}
|
||||
}`, MASTER_NAME)
|
||||
|
||||
targetNs, err := testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer targetNs.Close()
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNs.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: []byte(conf),
|
||||
Args: "IgnoreUnknown=true;MAC=c2:11:22:33:44:55",
|
||||
}
|
||||
|
||||
var result *current.Result
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err = current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(len(result.Interfaces)).To(Equal(1))
|
||||
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
|
||||
Expect(len(result.IPs)).To(Equal(0))
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Make sure macvlan link exists in the target namespace
|
||||
err = targetNs.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().Name).To(Equal(IFNAME))
|
||||
|
||||
hwaddr, err := net.ParseMAC("c2:11:22:33:44:55")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr))
|
||||
|
||||
addrs, err := netlink.AddrList(link, syscall.AF_INET)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(addrs)).To(Equal(0))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
err := testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Make sure macvlan link has been deleted
|
||||
err = targetNs.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(link).To(BeNil())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("configures and deconfigures l2 macvlan link with mac address (from RuntimeConfig) with ADD/DEL", func() {
|
||||
const IFNAME = "macvl0"
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"capabilities": {"mac": true},
|
||||
"RuntimeConfig": {
|
||||
"mac": "c2:11:22:33:44:55"
|
||||
},
|
||||
"name": "mynet",
|
||||
"type": "macvlan",
|
||||
"master": "%s",
|
||||
"ipam": {}
|
||||
}`, MASTER_NAME)
|
||||
|
||||
targetNs, err := testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer targetNs.Close()
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: targetNs.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
var result *current.Result
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err = current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(len(result.Interfaces)).To(Equal(1))
|
||||
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
|
||||
Expect(len(result.IPs)).To(Equal(0))
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Make sure macvlan link exists in the target namespace
|
||||
err = targetNs.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().Name).To(Equal(IFNAME))
|
||||
|
||||
hwaddr, err := net.ParseMAC("c2:11:22:33:44:55")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(hwaddr))
|
||||
|
||||
addrs, err := netlink.AddrList(link, syscall.AF_INET)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(addrs)).To(Equal(0))
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
err := testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Make sure macvlan link has been deleted
|
||||
err = targetNs.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(link).To(BeNil())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
@ -228,7 +228,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
defer netns.Close()
|
||||
|
||||
hostInterface, containerInterface, err := setupContainerVeth(netns, args.IfName, conf.MTU, result)
|
||||
hostInterface, _, err := setupContainerVeth(netns, args.IfName, conf.MTU, result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -247,12 +247,23 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
}
|
||||
|
||||
result.DNS = conf.DNS
|
||||
result.Interfaces = []*current.Interface{hostInterface, containerInterface}
|
||||
// Only override the DNS settings in the previous result if any DNS fields
|
||||
// were provided to the ptp plugin. This allows, for example, IPAM plugins
|
||||
// to specify the DNS settings instead of the ptp plugin.
|
||||
if dnsConfSet(conf.DNS) {
|
||||
result.DNS = conf.DNS
|
||||
}
|
||||
|
||||
return types.PrintResult(result, conf.CNIVersion)
|
||||
}
|
||||
|
||||
func dnsConfSet(dnsConf types.DNS) bool {
|
||||
return dnsConf.Nameservers != nil ||
|
||||
dnsConf.Search != nil ||
|
||||
dnsConf.Options != nil ||
|
||||
dnsConf.Domain != ""
|
||||
}
|
||||
|
||||
func cmdDel(args *skel.CmdArgs) error {
|
||||
conf := NetConf{}
|
||||
if err := json.Unmarshal(args.StdinData, &conf); err != nil {
|
||||
|
@ -17,6 +17,7 @@ package main
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
@ -98,9 +99,10 @@ var _ = Describe("ptp Operations", func() {
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(originalNS.Close()).To(Succeed())
|
||||
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
|
||||
})
|
||||
|
||||
doTest := func(conf string, numIPs int) {
|
||||
doTest := func(conf string, numIPs int, expectedDNSConf types.DNS) {
|
||||
const IFNAME = "ptp0"
|
||||
|
||||
targetNs, err := testutils.NewNS()
|
||||
@ -175,6 +177,9 @@ var _ = Describe("ptp Operations", func() {
|
||||
Expect(res.Interfaces[1].Mac).To(Equal(wantMac))
|
||||
Expect(res.Interfaces[1].Sandbox).To(Equal(targetNs.Path()))
|
||||
|
||||
// make sure DNS is correct
|
||||
Expect(res.DNS).To(Equal(expectedDNSConf))
|
||||
|
||||
// Call the plugins with the DEL command, deleting the veth endpoints
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
@ -327,7 +332,16 @@ var _ = Describe("ptp Operations", func() {
|
||||
}
|
||||
|
||||
It("configures and deconfigures a ptp link with ADD/DEL", func() {
|
||||
conf := `{
|
||||
dnsConf := types.DNS{
|
||||
Nameservers: []string{"10.1.2.123"},
|
||||
Domain: "some.domain.test",
|
||||
Search: []string{"search.test"},
|
||||
Options: []string{"option1:foo"},
|
||||
}
|
||||
dnsConfBytes, err := json.Marshal(dnsConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "ptp",
|
||||
@ -336,10 +350,11 @@ var _ = Describe("ptp Operations", func() {
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24"
|
||||
}
|
||||
}`
|
||||
},
|
||||
"dns": %s
|
||||
}`, string(dnsConfBytes))
|
||||
|
||||
doTest(conf, 1)
|
||||
doTest(conf, 1, dnsConf)
|
||||
})
|
||||
|
||||
It("configures and deconfigures a dual-stack ptp link with ADD/DEL", func() {
|
||||
@ -358,7 +373,112 @@ var _ = Describe("ptp Operations", func() {
|
||||
}
|
||||
}`
|
||||
|
||||
doTest(conf, 2)
|
||||
doTest(conf, 2, types.DNS{})
|
||||
})
|
||||
|
||||
It("does not override IPAM DNS settings if no DNS settings provided", func() {
|
||||
ipamDNSConf := types.DNS{
|
||||
Nameservers: []string{"10.1.2.123"},
|
||||
Domain: "some.domain.test",
|
||||
Search: []string{"search.test"},
|
||||
Options: []string{"option1:foo"},
|
||||
}
|
||||
resolvConfPath, err := testutils.TmpResolvConf(ipamDNSConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer os.RemoveAll(resolvConfPath)
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "ptp",
|
||||
"ipMasq": true,
|
||||
"mtu": 5000,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24",
|
||||
"resolvConf": "%s"
|
||||
}
|
||||
}`, resolvConfPath)
|
||||
|
||||
doTest(conf, 1, ipamDNSConf)
|
||||
})
|
||||
|
||||
It("overrides IPAM DNS settings if any DNS settings provided", func() {
|
||||
ipamDNSConf := types.DNS{
|
||||
Nameservers: []string{"10.1.2.123"},
|
||||
Domain: "some.domain.test",
|
||||
Search: []string{"search.test"},
|
||||
Options: []string{"option1:foo"},
|
||||
}
|
||||
resolvConfPath, err := testutils.TmpResolvConf(ipamDNSConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer os.RemoveAll(resolvConfPath)
|
||||
|
||||
for _, ptpDNSConf := range []types.DNS{
|
||||
{
|
||||
Nameservers: []string{"10.1.2.234"},
|
||||
},
|
||||
{
|
||||
Domain: "someother.domain.test",
|
||||
},
|
||||
{
|
||||
Search: []string{"search.elsewhere.test"},
|
||||
},
|
||||
{
|
||||
Options: []string{"option2:bar"},
|
||||
},
|
||||
} {
|
||||
dnsConfBytes, err := json.Marshal(ptpDNSConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "ptp",
|
||||
"ipMasq": true,
|
||||
"mtu": 5000,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24",
|
||||
"resolvConf": "%s"
|
||||
},
|
||||
"dns": %s
|
||||
}`, resolvConfPath, string(dnsConfBytes))
|
||||
|
||||
doTest(conf, 1, ptpDNSConf)
|
||||
}
|
||||
})
|
||||
|
||||
It("overrides IPAM DNS settings if any empty list DNS settings provided", func() {
|
||||
ipamDNSConf := types.DNS{
|
||||
Nameservers: []string{"10.1.2.123"},
|
||||
Domain: "some.domain.test",
|
||||
Search: []string{"search.test"},
|
||||
Options: []string{"option1:foo"},
|
||||
}
|
||||
resolvConfPath, err := testutils.TmpResolvConf(ipamDNSConf)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer os.RemoveAll(resolvConfPath)
|
||||
|
||||
conf := fmt.Sprintf(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "ptp",
|
||||
"ipMasq": true,
|
||||
"mtu": 5000,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24",
|
||||
"resolvConf": "%s"
|
||||
},
|
||||
"dns": {
|
||||
"nameservers": [],
|
||||
"search": [],
|
||||
"options": []
|
||||
}
|
||||
}`, resolvConfPath)
|
||||
|
||||
doTest(conf, 1, types.DNS{})
|
||||
})
|
||||
|
||||
It("deconfigures an unconfigured ptp link with DEL", func() {
|
||||
|
@ -53,14 +53,32 @@ func loadConf(bytes []byte) (*NetConf, string, error) {
|
||||
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
|
||||
}
|
||||
if n.Master == "" {
|
||||
return nil, "", fmt.Errorf(`"master" field is required. It specifies the host interface name to create the VLAN for.`)
|
||||
return nil, "", fmt.Errorf("\"master\" field is required. It specifies the host interface name to create the VLAN for.")
|
||||
}
|
||||
if n.VlanId < 0 || n.VlanId > 4094 {
|
||||
return nil, "", fmt.Errorf(`invalid VLAN ID %d (must be between 0 and 4095 inclusive)`, n.VlanId)
|
||||
return nil, "", fmt.Errorf("invalid VLAN ID %d (must be between 0 and 4095 inclusive)", n.VlanId)
|
||||
}
|
||||
|
||||
// check existing and MTU of master interface
|
||||
masterMTU, err := getMTUByName(n.Master)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
if n.MTU < 0 || n.MTU > masterMTU {
|
||||
return nil, "", fmt.Errorf("invalid MTU %d, must be [0, master MTU(%d)]", n.MTU, masterMTU)
|
||||
}
|
||||
|
||||
return n, n.CNIVersion, nil
|
||||
}
|
||||
|
||||
func getMTUByName(ifName string) (int, error) {
|
||||
link, err := netlink.LinkByName(ifName)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return link.Attrs().MTU, nil
|
||||
}
|
||||
|
||||
func createVlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interface, error) {
|
||||
vlan := ¤t.Interface{}
|
||||
|
||||
@ -76,10 +94,6 @@ func createVlan(conf *NetConf, ifName string, netns ns.NetNS) (*current.Interfac
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if conf.MTU <= 0 {
|
||||
conf.MTU = m.Attrs().MTU
|
||||
}
|
||||
|
||||
v := &netlink.Vlan{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
MTU: conf.MTU,
|
||||
@ -193,7 +207,7 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
|
||||
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||
err = ip.DelLinkByName(args.IfName)
|
||||
if err != nil && err != ip.ErrLinkNotFound {
|
||||
if err != nil && err == ip.ErrLinkNotFound {
|
||||
return nil
|
||||
}
|
||||
return err
|
||||
|
@ -121,6 +121,7 @@ var _ = Describe("vlan Operations", func() {
|
||||
|
||||
AfterEach(func() {
|
||||
Expect(originalNS.Close()).To(Succeed())
|
||||
Expect(testutils.UnmountNS(originalNS)).To(Succeed())
|
||||
})
|
||||
|
||||
It("creates an vlan link in a non-default namespace with given MTU", func() {
|
||||
@ -291,6 +292,19 @@ var _ = Describe("vlan Operations", func() {
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// DEL can be called multiple times, make sure no error is returned
|
||||
// if the device is already removed.
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
err = testutils.CmdDelWithArgs(args, func() error {
|
||||
return cmdDel(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("configures and deconfigures an CNI V4 vlan link with ADD/CHECK/DEL", func() {
|
||||
@ -407,4 +421,75 @@ var _ = Describe("vlan Operations", func() {
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
Describe("fails to create vlan link with invalid MTU", func() {
|
||||
conf := `{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "mynet",
|
||||
"type": "vlan",
|
||||
"master": "%s",
|
||||
"mtu": %d,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.1.2.0/24"
|
||||
}
|
||||
}`
|
||||
BeforeEach(func() {
|
||||
var err error
|
||||
err = originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
// set master link's MTU to 1500
|
||||
link, err := netlink.LinkByName(MASTER_NAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = netlink.LinkSetMTU(link, 1500)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("fails to create vlan link with greater MTU than master interface", func() {
|
||||
var err error
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: "/var/run/netns/test",
|
||||
IfName: "eth0",
|
||||
StdinData: []byte(fmt.Sprintf(conf, MASTER_NAME, 1600)),
|
||||
}
|
||||
|
||||
_ = originalNS.Do(func(netNS ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err = testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).To(Equal(fmt.Errorf("invalid MTU 1600, must be [0, master MTU(1500)]")))
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
It("fails to create vlan link with negative MTU", func() {
|
||||
var err error
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: "/var/run/netns/test",
|
||||
IfName: "eth0",
|
||||
StdinData: []byte(fmt.Sprintf(conf, MASTER_NAME, -100)),
|
||||
}
|
||||
|
||||
_ = originalNS.Do(func(netNS ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err = testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).To(Equal(fmt.Errorf("invalid MTU -100, must be [0, master MTU(1500)]")))
|
||||
return nil
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -32,9 +32,11 @@ With win-bridge plugin, all containers (on the same host) are plugged into an L2
|
||||
"NeedEncap": true
|
||||
}
|
||||
}
|
||||
].
|
||||
],
|
||||
"loopbackDSR": true,
|
||||
"capabilities": {
|
||||
"dns": true
|
||||
"dns": true,
|
||||
"portMappings": true
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -51,5 +53,7 @@ With win-bridge plugin, all containers (on the same host) are plugged into an L2
|
||||
* `ipam` (dictionary, optional): IPAM configuration to be used for this network.
|
||||
* `Policies` (list, optional): List of hns policies to be used (only used when ApiVersion is < 2).
|
||||
* `HcnPolicyArgs` (list, optional): List of hcn policies to be used (only used when ApiVersion is 2).
|
||||
* `capabilities` (dictionary, optional): runtime capabilities to enable.
|
||||
* `dns` (boolean, optional): if true will take the dns config supplied by the runtime and override other settings.
|
||||
* `loopbackDSR` (bool, optional): If true, will add a policy to allow the interface to support loopback direct server return.
|
||||
* `capabilities` (dictionary, optional): Runtime capabilities to enable.
|
||||
* `dns` (boolean, optional): If true, will take the dns config supplied by the runtime and override other settings.
|
||||
* `portMappings` (boolean, optional): If true, will handle HostPort<>ContainerPort mapping using NAT HNS Policies
|
@ -39,6 +39,7 @@
|
||||
"NeedEncap":true
|
||||
}
|
||||
}
|
||||
]
|
||||
],
|
||||
"loopbackDSR": true
|
||||
}
|
||||
}
|
@ -19,17 +19,16 @@ import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"os"
|
||||
|
||||
"github.com/Microsoft/hcsshim"
|
||||
"github.com/Microsoft/hcsshim/hcn"
|
||||
"github.com/juju/errors"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/errors"
|
||||
"github.com/containernetworking/plugins/pkg/hns"
|
||||
"github.com/containernetworking/plugins/pkg/ipam"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
@ -39,7 +38,6 @@ type NetConf struct {
|
||||
hns.NetConf
|
||||
|
||||
IPMasqNetwork string `json:"ipMasqNetwork,omitempty"`
|
||||
ApiVersion int `json:"ApiVersion"`
|
||||
}
|
||||
|
||||
func init() {
|
||||
@ -74,7 +72,7 @@ func ProcessEndpointArgs(args *skel.CmdArgs, n *NetConf) (*hns.EndpointInfo, err
|
||||
return nil, errors.Annotatef(err, "error while NewResultFromResult")
|
||||
} else {
|
||||
if len(result.IPs) == 0 {
|
||||
return nil, errors.New("IPAM plugin return is missing IP config")
|
||||
return nil, fmt.Errorf("IPAM plugin return is missing IP config")
|
||||
}
|
||||
epInfo.IpAddress = result.IPs[0].Address.IP
|
||||
epInfo.Gateway = result.IPs[0].Address.IP.Mask(result.IPs[0].Address.Mask)
|
||||
@ -88,6 +86,9 @@ func ProcessEndpointArgs(args *skel.CmdArgs, n *NetConf) (*hns.EndpointInfo, err
|
||||
n.ApplyOutboundNatPolicy(n.IPMasqNetwork)
|
||||
}
|
||||
|
||||
// Add HostPort mapping if any present
|
||||
n.ApplyPortMappingPolicy(n.RuntimeConfig.PortMaps)
|
||||
|
||||
epInfo.DNS = n.GetDNS()
|
||||
|
||||
return epInfo, nil
|
||||
@ -104,12 +105,11 @@ func cmdHnsAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
|
||||
return nil, fmt.Errorf("network %v not found", networkName)
|
||||
}
|
||||
|
||||
if !strings.EqualFold(hnsNetwork.Type, "L2Bridge") {
|
||||
if !strings.EqualFold(hnsNetwork.Type, "L2Bridge") && !strings.EqualFold(hnsNetwork.Type, "L2Tunnel") {
|
||||
return nil, fmt.Errorf("network %v is of an unexpected type: %v", networkName, hnsNetwork.Type)
|
||||
}
|
||||
|
||||
epName := hns.ConstructEndpointName(args.ContainerID, args.Netns, n.Name)
|
||||
|
||||
hnsEndpoint, err := hns.ProvisionEndpoint(epName, hnsNetwork.Id, args.ContainerID, args.Netns, func() (*hcsshim.HNSEndpoint, error) {
|
||||
epInfo, err := ProcessEndpointArgs(args, n)
|
||||
epInfo.NetworkId = hnsNetwork.Id
|
||||
@ -132,7 +132,6 @@ func cmdHnsAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
|
||||
}
|
||||
|
||||
return result, nil
|
||||
|
||||
}
|
||||
|
||||
func cmdHcnAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
|
||||
@ -146,7 +145,7 @@ func cmdHcnAdd(args *skel.CmdArgs, n *NetConf) (*current.Result, error) {
|
||||
return nil, fmt.Errorf("network %v not found", networkName)
|
||||
}
|
||||
|
||||
if hcnNetwork.Type != hcn.L2Bridge {
|
||||
if hcnNetwork.Type != hcn.L2Bridge && hcnNetwork.Type != hcn.L2Tunnel {
|
||||
return nil, fmt.Errorf("network %v is of unexpected type: %v", networkName, hcnNetwork.Type)
|
||||
}
|
||||
|
||||
@ -191,14 +190,12 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
os.Setenv("CNI_COMMAND", "DEL")
|
||||
ipam.ExecDel(n.IPAM.Type, args.StdinData)
|
||||
os.Setenv("CNI_COMMAND", "ADD")
|
||||
return errors.Annotate(err, "error while executing ADD command")
|
||||
}
|
||||
|
||||
if (result == nil) {
|
||||
return errors.New("result for ADD not populated correctly")
|
||||
if result == nil {
|
||||
return fmt.Errorf("result for ADD not populated correctly")
|
||||
}
|
||||
return types.PrintResult(result, cniVersion)
|
||||
}
|
||||
|
@ -14,11 +14,11 @@ With win-overlay plugin, all containers (on the same host) are plugged into an O
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"subnet": "10.10.0.0/16"
|
||||
}
|
||||
},
|
||||
"loopbackDSR": true,
|
||||
"capabilites": {
|
||||
"dns": true
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
@ -33,5 +33,6 @@ With win-overlay plugin, all containers (on the same host) are plugged into an O
|
||||
* `endpointMacPrefix` (string, optional): set to the MAC prefix configured for Flannel.
|
||||
* `Policies` (list, optional): List of hns policies to be used.
|
||||
* `ipam` (dictionary, required): IPAM configuration to be used for this network.
|
||||
* `loopbackDSR` (bool, optional): If true, will add a policy to allow the interface to support loopback direct server return.
|
||||
* `capabilities` (dictionary, optional): runtime capabilities to be parsed and injected by runtime.
|
||||
* `dns` (boolean, optional): if true will take the dns config supplied by the runtime and override other settings.
|
||||
* `dns` (boolean, optional): If true, will take the dns config supplied by the runtime and override other settings.
|
@ -19,16 +19,15 @@ import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"strings"
|
||||
"os"
|
||||
|
||||
"github.com/Microsoft/hcsshim"
|
||||
"github.com/juju/errors"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/errors"
|
||||
"github.com/containernetworking/plugins/pkg/hns"
|
||||
"github.com/containernetworking/plugins/pkg/ipam"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
@ -101,12 +100,12 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
if len(result.IPs) == 0 {
|
||||
return nil, errors.New("IPAM plugin return is missing IP config")
|
||||
return nil, fmt.Errorf("IPAM plugin return is missing IP config")
|
||||
}
|
||||
|
||||
ipAddr := result.IPs[0].Address.IP.To4()
|
||||
if ipAddr == nil {
|
||||
return nil, errors.New("win-overlay doesn't support IPv6 now")
|
||||
return nil, fmt.Errorf("win-overlay doesn't support IPv6 now")
|
||||
}
|
||||
|
||||
// conjure a MAC based on the IP for Overlay
|
||||
@ -119,7 +118,9 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
result.DNS = n.GetDNS()
|
||||
|
||||
if n.LoopbackDSR {
|
||||
n.ApplyLoopbackDSR(&ipAddr)
|
||||
}
|
||||
hnsEndpoint := &hcsshim.HNSEndpoint{
|
||||
Name: epName,
|
||||
VirtualNetwork: hnsNetwork.Id,
|
||||
@ -135,9 +136,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
})
|
||||
defer func() {
|
||||
if !success {
|
||||
os.Setenv("CNI_COMMAND", "DEL")
|
||||
ipam.ExecDel(n.IPAM.Type, args.StdinData)
|
||||
os.Setenv("CNI_COMMAND", "ADD")
|
||||
}
|
||||
}()
|
||||
if err != nil {
|
||||
|
@ -105,7 +105,7 @@ var _ = Describe("bandwidth test", func() {
|
||||
hostIP = net.IP{169, 254, 0, 1}
|
||||
containerIP = net.IP{10, 254, 0, 1}
|
||||
hostIfaceMTU = 1024
|
||||
ifbDeviceName = "5b6c"
|
||||
ifbDeviceName = "bwpa8eda89404b7"
|
||||
|
||||
createVeth(hostNs.Path(), hostIfname, containerNs.Path(), containerIfname, hostIP, containerIP, hostIfaceMTU)
|
||||
})
|
||||
@ -621,10 +621,276 @@ var _ = Describe("bandwidth test", func() {
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Validating input", func() {
|
||||
It("Should allow only 4GB burst rate", func() {
|
||||
err := validateRateAndBurst(5000, 4*1024*1024*1024*8-16) // 2 bytes less than the max should pass
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
err = validateRateAndBurst(5000, 4*1024*1024*1024*8) // we're 1 bit above MaxUint32
|
||||
Expect(err).To(HaveOccurred())
|
||||
err = validateRateAndBurst(0, 1)
|
||||
Expect(err).To(HaveOccurred())
|
||||
err = validateRateAndBurst(1, 0)
|
||||
Expect(err).To(HaveOccurred())
|
||||
err = validateRateAndBurst(0, 0)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
Describe("Getting the host interface which plugin should work on from veth peer of container interface", func() {
|
||||
It("Should work with multiple host veth interfaces", func() {
|
||||
conf := `{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "cni-plugin-bandwidth-test",
|
||||
"type": "bandwidth",
|
||||
"ingressRate": 8,
|
||||
"ingressBurst": 8,
|
||||
"egressRate": 16,
|
||||
"egressBurst": 8,
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": ""
|
||||
},
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": ""
|
||||
},
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": ""
|
||||
},
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": "%s"
|
||||
}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "%s/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 1
|
||||
}
|
||||
],
|
||||
"routes": []
|
||||
}
|
||||
}`
|
||||
|
||||
// create veth peer in host ns
|
||||
vethName, peerName := "host-veth-peer1", "host-veth-peer2"
|
||||
createVethInOneNs(hostNs.Path(), vethName, peerName)
|
||||
|
||||
conf = fmt.Sprintf(conf, vethName, peerName, hostIfname, containerIfname, containerNs.Path(), containerIP.String())
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: containerNs.Path(),
|
||||
IfName: containerIfname,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
r, out, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
||||
Expect(err).NotTo(HaveOccurred(), string(out))
|
||||
result, err := current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(result.Interfaces).To(HaveLen(5))
|
||||
Expect(result.Interfaces[4].Name).To(Equal(ifbDeviceName))
|
||||
Expect(result.Interfaces[4].Sandbox).To(Equal(""))
|
||||
|
||||
ifbLink, err := netlink.LinkByName(ifbDeviceName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(ifbLink.Attrs().MTU).To(Equal(hostIfaceMTU))
|
||||
|
||||
qdiscs, err := netlink.QdiscList(ifbLink)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(qdiscs).To(HaveLen(1))
|
||||
Expect(qdiscs[0].Attrs().LinkIndex).To(Equal(ifbLink.Attrs().Index))
|
||||
|
||||
Expect(qdiscs[0]).To(BeAssignableToTypeOf(&netlink.Tbf{}))
|
||||
Expect(qdiscs[0].(*netlink.Tbf).Rate).To(Equal(uint64(2)))
|
||||
Expect(qdiscs[0].(*netlink.Tbf).Limit).To(Equal(uint32(1)))
|
||||
|
||||
hostVethLink, err := netlink.LinkByName(hostIfname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
qdiscFilters, err := netlink.FilterList(hostVethLink, netlink.MakeHandle(0xffff, 0))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(qdiscFilters).To(HaveLen(1))
|
||||
Expect(qdiscFilters[0].(*netlink.U32).Actions[0].(*netlink.MirredAction).Ifindex).To(Equal(ifbLink.Attrs().Index))
|
||||
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
|
||||
Expect(hostNs.Do(func(n ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
ifbLink, err := netlink.LinkByName(hostIfname)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
qdiscs, err := netlink.QdiscList(ifbLink)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(qdiscs).To(HaveLen(2))
|
||||
Expect(qdiscs[0].Attrs().LinkIndex).To(Equal(ifbLink.Attrs().Index))
|
||||
|
||||
Expect(qdiscs[0]).To(BeAssignableToTypeOf(&netlink.Tbf{}))
|
||||
Expect(qdiscs[0].(*netlink.Tbf).Rate).To(Equal(uint64(1)))
|
||||
Expect(qdiscs[0].(*netlink.Tbf).Limit).To(Equal(uint32(1)))
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
|
||||
})
|
||||
|
||||
It("Should fail when container interface has no veth peer", func() {
|
||||
conf := `{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "cni-plugin-bandwidth-test",
|
||||
"type": "bandwidth",
|
||||
"ingressRate": 8,
|
||||
"ingressBurst": 8,
|
||||
"egressRate": 16,
|
||||
"egressBurst": 8,
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": ""
|
||||
},
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": "%s"
|
||||
}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "%s/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 1
|
||||
}
|
||||
],
|
||||
"routes": []
|
||||
}
|
||||
}`
|
||||
|
||||
// create a macvlan device to be container interface
|
||||
macvlanContainerIfname := "container-macv"
|
||||
createMacvlan(containerNs.Path(), containerIfname, macvlanContainerIfname)
|
||||
|
||||
conf = fmt.Sprintf(conf, hostIfname, macvlanContainerIfname, containerNs.Path(), containerIP.String())
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: containerNs.Path(),
|
||||
IfName: macvlanContainerIfname,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
})
|
||||
|
||||
It("Should fail when preResult has no interfaces", func() {
|
||||
conf := `{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "cni-plugin-bandwidth-test",
|
||||
"type": "bandwidth",
|
||||
"ingressRate": 8,
|
||||
"ingressBurst": 8,
|
||||
"egressRate": 16,
|
||||
"egressBurst": 8,
|
||||
"prevResult": {
|
||||
"interfaces": [],
|
||||
"ips": [],
|
||||
"routes": []
|
||||
}
|
||||
}`
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: containerNs.Path(),
|
||||
IfName: "eth0",
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
})
|
||||
|
||||
It("Should fail when veth peer of container interface does not match any of host interfaces in preResult", func() {
|
||||
conf := `{
|
||||
"cniVersion": "0.4.0",
|
||||
"name": "cni-plugin-bandwidth-test",
|
||||
"type": "bandwidth",
|
||||
"ingressRate": 8,
|
||||
"ingressBurst": 8,
|
||||
"egressRate": 16,
|
||||
"egressBurst": 8,
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": ""
|
||||
},
|
||||
{
|
||||
"name": "%s",
|
||||
"sandbox": "%s"
|
||||
}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "%s/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 1
|
||||
}
|
||||
],
|
||||
"routes": []
|
||||
}
|
||||
}`
|
||||
|
||||
// fake a non-exist host interface name
|
||||
fakeHostIfname := fmt.Sprintf("%s-fake", hostIfname)
|
||||
|
||||
conf = fmt.Sprintf(conf, fakeHostIfname, containerIfname, containerNs.Path(), containerIP.String())
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: containerNs.Path(),
|
||||
IfName: containerIfname,
|
||||
StdinData: []byte(conf),
|
||||
}
|
||||
|
||||
Expect(hostNs.Do(func(netNS ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
_, _, err := testutils.CmdAdd(containerNs.Path(), args.ContainerID, "", []byte(conf), func() error { return cmdAdd(args) })
|
||||
Expect(err).To(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})).To(Succeed())
|
||||
})
|
||||
})
|
||||
|
||||
Context("when chaining bandwidth plugin with PTP using 0.3.0 config", func() {
|
||||
var ptpConf string
|
||||
var rateInBits int
|
||||
var burstInBits int
|
||||
var rateInBits uint64
|
||||
var burstInBits uint64
|
||||
var packetInBytes int
|
||||
var containerWithoutTbfNS ns.NetNS
|
||||
var containerWithTbfNS ns.NetNS
|
||||
@ -638,7 +904,7 @@ var _ = Describe("bandwidth test", func() {
|
||||
|
||||
BeforeEach(func() {
|
||||
rateInBytes := 1000
|
||||
rateInBits = rateInBytes * 8
|
||||
rateInBits = uint64(rateInBytes * 8)
|
||||
burstInBits = rateInBits * 2
|
||||
packetInBytes = rateInBytes * 25
|
||||
|
||||
@ -768,8 +1034,8 @@ var _ = Describe("bandwidth test", func() {
|
||||
|
||||
Context("when chaining bandwidth plugin with PTP using 0.4.0 config", func() {
|
||||
var ptpConf string
|
||||
var rateInBits int
|
||||
var burstInBits int
|
||||
var rateInBits uint64
|
||||
var burstInBits uint64
|
||||
var packetInBytes int
|
||||
var containerWithoutTbfNS ns.NetNS
|
||||
var containerWithTbfNS ns.NetNS
|
||||
@ -783,7 +1049,7 @@ var _ = Describe("bandwidth test", func() {
|
||||
|
||||
BeforeEach(func() {
|
||||
rateInBytes := 1000
|
||||
rateInBits = rateInBytes * 8
|
||||
rateInBits = uint64(rateInBytes * 8)
|
||||
burstInBits = rateInBits * 2
|
||||
packetInBytes = rateInBytes * 25
|
||||
|
||||
|
@ -199,3 +199,61 @@ func createVeth(hostNamespace string, hostVethIfName string, containerNamespace
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
func createVethInOneNs(namespace, vethName, peerName string) {
|
||||
vethDeviceRequest := &netlink.Veth{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
Name: vethName,
|
||||
Flags: net.FlagUp,
|
||||
},
|
||||
PeerName: peerName,
|
||||
}
|
||||
|
||||
netNS, err := ns.GetNS(namespace)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = netNS.Do(func(_ ns.NetNS) error {
|
||||
if err := netlink.LinkAdd(vethDeviceRequest); err != nil {
|
||||
return fmt.Errorf("failed to create veth pair: %v", err)
|
||||
}
|
||||
|
||||
_, err := netlink.LinkByName(peerName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find newly-created veth device %q: %v", peerName, err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
func createMacvlan(namespace, master, macvlanName string) {
|
||||
netNS, err := ns.GetNS(namespace)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = netNS.Do(func(_ ns.NetNS) error {
|
||||
m, err := netlink.LinkByName(master)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to lookup master %q: %v", master, err)
|
||||
}
|
||||
|
||||
macvlanDeviceRequest := &netlink.Macvlan{
|
||||
LinkAttrs: netlink.LinkAttrs{
|
||||
MTU: m.Attrs().MTU,
|
||||
Name: macvlanName,
|
||||
ParentIndex: m.Attrs().Index,
|
||||
},
|
||||
Mode: netlink.MACVLAN_MODE_BRIDGE,
|
||||
}
|
||||
|
||||
if err = netlink.LinkAdd(macvlanDeviceRequest); err != nil {
|
||||
return fmt.Errorf("failed to create macvlan device: %s", err)
|
||||
}
|
||||
|
||||
_, err = netlink.LinkByName(macvlanName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find newly-created macvlan device %q: %v", macvlanName, err)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ func TeardownIfb(deviceName string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func CreateIngressQdisc(rateInBits, burstInBits int, hostDeviceName string) error {
|
||||
func CreateIngressQdisc(rateInBits, burstInBits uint64, hostDeviceName string) error {
|
||||
hostDevice, err := netlink.LinkByName(hostDeviceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get host device: %s", err)
|
||||
@ -58,7 +58,7 @@ func CreateIngressQdisc(rateInBits, burstInBits int, hostDeviceName string) erro
|
||||
return createTBF(rateInBits, burstInBits, hostDevice.Attrs().Index)
|
||||
}
|
||||
|
||||
func CreateEgressQdisc(rateInBits, burstInBits int, hostDeviceName string, ifbDeviceName string) error {
|
||||
func CreateEgressQdisc(rateInBits, burstInBits uint64, hostDeviceName string, ifbDeviceName string) error {
|
||||
ifbDevice, err := netlink.LinkByName(ifbDeviceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("get ifb device: %s", err)
|
||||
@ -113,7 +113,7 @@ func CreateEgressQdisc(rateInBits, burstInBits int, hostDeviceName string, ifbDe
|
||||
return nil
|
||||
}
|
||||
|
||||
func createTBF(rateInBits, burstInBits, linkIndex int) error {
|
||||
func createTBF(rateInBits, burstInBits uint64, linkIndex int) error {
|
||||
// Equivalent to
|
||||
// tc qdisc add dev link root tbf
|
||||
// rate netConf.BandwidthLimits.Rate
|
||||
|
@ -15,10 +15,9 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
@ -28,17 +27,22 @@ import (
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ip"
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/utils"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
const maxIfbDeviceLength = 15
|
||||
const ifbDevicePrefix = "bwp"
|
||||
|
||||
// BandwidthEntry corresponds to a single entry in the bandwidth argument,
|
||||
// see CONVENTIONS.md
|
||||
type BandwidthEntry struct {
|
||||
IngressRate int `json:"ingressRate"` //Bandwidth rate in bps for traffic through container. 0 for no limit. If ingressRate is set, ingressBurst must also be set
|
||||
IngressBurst int `json:"ingressBurst"` //Bandwidth burst in bits for traffic through container. 0 for no limit. If ingressBurst is set, ingressRate must also be set
|
||||
IngressRate uint64 `json:"ingressRate"` //Bandwidth rate in bps for traffic through container. 0 for no limit. If ingressRate is set, ingressBurst must also be set
|
||||
IngressBurst uint64 `json:"ingressBurst"` //Bandwidth burst in bits for traffic through container. 0 for no limit. If ingressBurst is set, ingressRate must also be set
|
||||
|
||||
EgressRate int `json:"egressRate"` //Bandwidth rate in bps for traffic through container. 0 for no limit. If egressRate is set, egressBurst must also be set
|
||||
EgressBurst int `json:"egressBurst"` //Bandwidth burst in bits for traffic through container. 0 for no limit. If egressBurst is set, egressRate must also be set
|
||||
EgressRate uint64 `json:"egressRate"` //Bandwidth rate in bps for traffic through container. 0 for no limit. If egressRate is set, egressBurst must also be set
|
||||
EgressBurst uint64 `json:"egressBurst"` //Bandwidth burst in bits for traffic through container. 0 for no limit. If egressBurst is set, egressRate must also be set
|
||||
}
|
||||
|
||||
func (bw *BandwidthEntry) isZero() bool {
|
||||
@ -98,7 +102,7 @@ func getBandwidth(conf *PluginConf) *BandwidthEntry {
|
||||
return conf.BandwidthEntry
|
||||
}
|
||||
|
||||
func validateRateAndBurst(rate int, burst int) error {
|
||||
func validateRateAndBurst(rate, burst uint64) error {
|
||||
switch {
|
||||
case burst < 0 || rate < 0:
|
||||
return fmt.Errorf("rate and burst must be a positive integer")
|
||||
@ -106,19 +110,15 @@ func validateRateAndBurst(rate int, burst int) error {
|
||||
return fmt.Errorf("if rate is set, burst must also be set")
|
||||
case rate == 0 && burst != 0:
|
||||
return fmt.Errorf("if burst is set, rate must also be set")
|
||||
case burst/8 >= math.MaxUint32:
|
||||
return fmt.Errorf("burst cannot be more than 4GB")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getIfbDeviceName(networkName string, containerId string) (string, error) {
|
||||
hash := sha1.New()
|
||||
_, err := hash.Write([]byte(networkName + containerId))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%x", hash.Sum(nil))[:4], nil
|
||||
func getIfbDeviceName(networkName string, containerId string) string {
|
||||
return utils.MustFormatHashWithPrefix(maxIfbDeviceLength, ifbDevicePrefix, networkName+containerId)
|
||||
}
|
||||
|
||||
func getMTU(deviceName string) (int, error) {
|
||||
@ -130,21 +130,35 @@ func getMTU(deviceName string) (int, error) {
|
||||
return link.Attrs().MTU, nil
|
||||
}
|
||||
|
||||
func getHostInterface(interfaces []*current.Interface) (*current.Interface, error) {
|
||||
// get the veth peer of container interface in host namespace
|
||||
func getHostInterface(interfaces []*current.Interface, containerIfName string, netns ns.NetNS) (*current.Interface, error) {
|
||||
if len(interfaces) == 0 {
|
||||
return nil, errors.New("no interfaces provided")
|
||||
return nil, fmt.Errorf("no interfaces provided")
|
||||
}
|
||||
|
||||
// get veth peer index of container interface
|
||||
var peerIndex int
|
||||
var err error
|
||||
_ = netns.Do(func(_ ns.NetNS) error {
|
||||
_, peerIndex, err = ip.GetVethPeerIfindex(containerIfName)
|
||||
return nil
|
||||
})
|
||||
if peerIndex <= 0 {
|
||||
return nil, fmt.Errorf("container interface %s has no veth peer: %v", containerIfName, err)
|
||||
}
|
||||
|
||||
// find host interface by index
|
||||
link, err := netlink.LinkByIndex(peerIndex)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("veth peer with index %d is not in host ns", peerIndex)
|
||||
}
|
||||
for _, iface := range interfaces {
|
||||
if iface.Sandbox == "" { // host interface
|
||||
_, _, err = ip.GetVethPeerIfindex(iface.Name)
|
||||
if err == nil {
|
||||
return iface, err
|
||||
}
|
||||
if iface.Sandbox == "" && iface.Name == link.Attrs().Name {
|
||||
return iface, nil
|
||||
}
|
||||
}
|
||||
return nil, errors.New(fmt.Sprintf("no host interface found. last error: %s", err))
|
||||
|
||||
return nil, fmt.Errorf("no veth peer of container interface found in host ns")
|
||||
}
|
||||
|
||||
func cmdAdd(args *skel.CmdArgs) error {
|
||||
@ -166,7 +180,14 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not convert result to current version: %v", err)
|
||||
}
|
||||
hostInterface, err := getHostInterface(result.Interfaces)
|
||||
|
||||
netns, err := ns.GetNS(args.Netns)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open netns %q: %v", netns, err)
|
||||
}
|
||||
defer netns.Close()
|
||||
|
||||
hostInterface, err := getHostInterface(result.Interfaces, args.IfName, netns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -184,10 +205,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
return err
|
||||
}
|
||||
|
||||
ifbDeviceName, err := getIfbDeviceName(conf.Name, args.ContainerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ifbDeviceName := getIfbDeviceName(conf.Name, args.ContainerID)
|
||||
|
||||
err = CreateIfb(ifbDeviceName, mtu)
|
||||
if err != nil {
|
||||
@ -218,10 +236,7 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
return err
|
||||
}
|
||||
|
||||
ifbDeviceName, err := getIfbDeviceName(conf.Name, args.ContainerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ifbDeviceName := getIfbDeviceName(conf.Name, args.ContainerID)
|
||||
|
||||
if err := TeardownIfb(ifbDeviceName); err != nil {
|
||||
return err
|
||||
@ -266,7 +281,13 @@ func cmdCheck(args *skel.CmdArgs) error {
|
||||
return fmt.Errorf("could not convert result to current version: %v", err)
|
||||
}
|
||||
|
||||
hostInterface, err := getHostInterface(result.Interfaces)
|
||||
netns, err := ns.GetNS(args.Netns)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to open netns %q: %v", netns, err)
|
||||
}
|
||||
defer netns.Close()
|
||||
|
||||
hostInterface, err := getHostInterface(result.Interfaces, args.IfName, netns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -316,10 +337,7 @@ func cmdCheck(args *skel.CmdArgs) error {
|
||||
latency := latencyInUsec(latencyInMillis)
|
||||
limitInBytes := limit(uint64(rateInBytes), latency, uint32(burstInBytes))
|
||||
|
||||
ifbDeviceName, err := getIfbDeviceName(bwConf.Name, args.ContainerID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
ifbDeviceName := getIfbDeviceName(bwConf.Name, args.ContainerID)
|
||||
|
||||
ifbDevice, err := netlink.LinkByName(ifbDeviceName)
|
||||
if err != nil {
|
||||
|
@ -28,7 +28,7 @@ The following network configuration file
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "firewall",
|
||||
"type": "firewall"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -72,7 +72,7 @@ look like:
|
||||
},
|
||||
{
|
||||
"type": "firewall",
|
||||
"backend": "firewalld"
|
||||
"backend": "firewalld"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -111,7 +111,7 @@ look like:
|
||||
},
|
||||
{
|
||||
"type": "firewall",
|
||||
"backend": "iptables"
|
||||
"backend": "iptables"
|
||||
}
|
||||
]
|
||||
}
|
||||
@ -129,7 +129,21 @@ when containers are created and from where rules will be removed when containers
|
||||
CNI-FORWARD will have a pair of rules added, one for each direction, using the IPAM assigned IP address
|
||||
of the container as shown:
|
||||
|
||||
`CNI_FORWARD` chain:
|
||||
- `-s 10.88.0.2 -m conntrack --ctstate RELATED,ESTABLISHED -j CNI-FORWARD`
|
||||
- `-d 10.88.0.2 -j CNI-FORWARD`
|
||||
`CNI-FORWARD` chain:
|
||||
- `-s 10.88.0.2 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT`
|
||||
- `-d 10.88.0.2 -j ACCEPT`
|
||||
|
||||
The `CNI-FORWARD` chain first sends all traffic to `CNI-ADMIN` chain, which is intended as an user-controlled chain for custom rules that run prior to rules managed by the `firewall` plugin. The `firewall` plugin does not add, delete or modify rules in the `CNI-ADMIN` chain.
|
||||
|
||||
`CNI-FORWARD` chain:
|
||||
- `-j CNI-ADMIN`
|
||||
|
||||
The chain name `CNI-ADMIN` can be overridden by specifying `iptablesAdminChainName` in the plugin configuration
|
||||
|
||||
```
|
||||
{
|
||||
"type": "firewall",
|
||||
"backend": "iptables",
|
||||
"iptablesAdminChainName": "SOME-OTHER-CHAIN-NAME",
|
||||
}
|
||||
```
|
||||
|
@ -27,7 +27,6 @@ import (
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/cni/pkg/version"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
bv "github.com/containernetworking/plugins/pkg/utils/buildversion"
|
||||
)
|
||||
|
||||
@ -68,9 +67,15 @@ func parseConf(data []byte) (*FirewallNetConf, *current.Result, error) {
|
||||
return nil, nil, fmt.Errorf("failed to load netconf: %v", err)
|
||||
}
|
||||
|
||||
// Default the firewalld zone to trusted
|
||||
if conf.FirewalldZone == "" {
|
||||
conf.FirewalldZone = "trusted"
|
||||
}
|
||||
|
||||
// Parse previous result.
|
||||
if conf.RawPrevResult == nil {
|
||||
return nil, nil, fmt.Errorf("missing prevResult from earlier plugin")
|
||||
// return early if there was no previous result, which is allowed for DEL calls
|
||||
return &conf, ¤t.Result{}, nil
|
||||
}
|
||||
|
||||
// Parse previous result.
|
||||
@ -85,11 +90,6 @@ func parseConf(data []byte) (*FirewallNetConf, *current.Result, error) {
|
||||
return nil, nil, fmt.Errorf("could not convert result to current version: %v", err)
|
||||
}
|
||||
|
||||
// Default the firewalld zone to trusted
|
||||
if conf.FirewalldZone == "" {
|
||||
conf.FirewalldZone = "trusted"
|
||||
}
|
||||
|
||||
return &conf, result, nil
|
||||
}
|
||||
|
||||
@ -116,6 +116,10 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
return err
|
||||
}
|
||||
|
||||
if conf.PrevResult == nil {
|
||||
return fmt.Errorf("missing prevResult from earlier plugin")
|
||||
}
|
||||
|
||||
backend, err := getBackend(conf)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -142,12 +146,6 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Tolerate errors if the container namespace has been torn down already
|
||||
containerNS, err := ns.GetNS(args.Netns)
|
||||
if err == nil {
|
||||
defer containerNS.Close()
|
||||
}
|
||||
|
||||
// Runtime errors are ignored
|
||||
if err := backend.Del(conf, result); err != nil {
|
||||
return err
|
||||
@ -167,8 +165,8 @@ func cmdCheck(args *skel.CmdArgs) error {
|
||||
}
|
||||
|
||||
// Ensure we have previous result.
|
||||
if result == nil {
|
||||
return fmt.Errorf("Required prevResult missing")
|
||||
if conf.PrevResult == nil {
|
||||
return fmt.Errorf("missing prevResult from earlier plugin")
|
||||
}
|
||||
|
||||
backend, err := getBackend(conf)
|
||||
|
@ -270,6 +270,13 @@ var _ = Describe("firewall plugin iptables backend", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
validateFullRuleset(fullConf)
|
||||
|
||||
// ensure creation is idempotent
|
||||
_, _, err = testutils.CmdAdd(targetNS.Path(), args.ContainerID, IFNAME, fullConf, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"net"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/containernetworking/plugins/pkg/utils"
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
)
|
||||
|
||||
@ -32,20 +33,6 @@ func getPrivChainRules(ip string) [][]string {
|
||||
return rules
|
||||
}
|
||||
|
||||
func ensureChain(ipt *iptables.IPTables, table, chain string) error {
|
||||
chains, err := ipt.ListChains(table)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to list iptables chains: %v", err)
|
||||
}
|
||||
for _, ch := range chains {
|
||||
if ch == chain {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return ipt.NewChain(table, chain)
|
||||
}
|
||||
|
||||
func generateFilterRule(privChainName string) []string {
|
||||
return []string{"-m", "comment", "--comment", "CNI firewall plugin rules", "-j", privChainName}
|
||||
}
|
||||
@ -70,13 +57,13 @@ func ensureFirstChainRule(ipt *iptables.IPTables, chain string, rule []string) e
|
||||
|
||||
func (ib *iptablesBackend) setupChains(ipt *iptables.IPTables) error {
|
||||
privRule := generateFilterRule(ib.privChainName)
|
||||
adminRule := generateFilterRule(ib.adminChainName)
|
||||
adminRule := generateAdminRule(ib.adminChainName)
|
||||
|
||||
// Ensure our private chains exist
|
||||
if err := ensureChain(ipt, "filter", ib.privChainName); err != nil {
|
||||
if err := utils.EnsureChain(ipt, "filter", ib.privChainName); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ensureChain(ipt, "filter", ib.adminChainName); err != nil {
|
||||
if err := utils.EnsureChain(ipt, "filter", ib.adminChainName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -160,10 +147,10 @@ func (ib *iptablesBackend) checkRules(conf *FirewallNetConf, result *current.Res
|
||||
}
|
||||
|
||||
// Ensure our private chains exist
|
||||
if err := ensureChain(ipt, "filter", ib.privChainName); err != nil {
|
||||
if err := utils.EnsureChain(ipt, "filter", ib.privChainName); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := ensureChain(ipt, "filter", ib.adminChainName); err != nil {
|
||||
if err := utils.EnsureChain(ipt, "filter", ib.adminChainName); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -178,7 +165,7 @@ func (ib *iptablesBackend) checkRules(conf *FirewallNetConf, result *current.Res
|
||||
}
|
||||
|
||||
// Ensure our admin override chain rule exists in our private chain
|
||||
adminRule := generateFilterRule(ib.adminChainName)
|
||||
adminRule := generateAdminRule(ib.adminChainName)
|
||||
adminExists, err := ipt.Exists("filter", ib.privChainName, adminRule...)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -89,7 +89,7 @@ Additionally, for the bridge plugin, `isGateway` will be set to `true`, if not p
|
||||
|
||||
## Windows Support (Experimental)
|
||||
This plugin supports delegating to the windows CNI plugins (overlay.exe, l2bridge.exe) to work in conjunction with [Flannel on Windows](https://github.com/coreos/flannel/issues/833).
|
||||
Flannel sets up an [HNS Network](https://docs.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/container-networking) in L2Bridge mode for host-gw and in Overlay mode for vxlan.
|
||||
Flannel sets up an [HNS Network](https://docs.microsoft.com/en-us/virtualization/windowscontainers/container-networking/architecture) in L2Bridge mode for host-gw and in Overlay mode for vxlan.
|
||||
|
||||
The following fields must be set in the delegated plugin configuration:
|
||||
* `name` (string, required): the name of the network (must match the name in Flannel config / name of the HNS network)
|
||||
@ -132,4 +132,4 @@ For this example, Flannel CNI would generate the following config to delegate to
|
||||
"type": "host-local"
|
||||
}
|
||||
}
|
||||
```
|
||||
```
|
||||
|
@ -146,12 +146,19 @@ func saveScratchNetConf(containerID, dataDir string, netconf []byte) error {
|
||||
return ioutil.WriteFile(path, netconf, 0600)
|
||||
}
|
||||
|
||||
func consumeScratchNetConf(containerID, dataDir string) ([]byte, error) {
|
||||
func consumeScratchNetConf(containerID, dataDir string) (func(error), []byte, error) {
|
||||
path := filepath.Join(dataDir, containerID)
|
||||
// Ignore errors when removing - Per spec safe to continue during DEL
|
||||
defer os.Remove(path)
|
||||
|
||||
return ioutil.ReadFile(path)
|
||||
// cleanup will do clean job when no error happens in consuming/using process
|
||||
cleanup := func(err error) {
|
||||
if err == nil {
|
||||
// Ignore errors when removing - Per spec safe to continue during DEL
|
||||
_ = os.Remove(path)
|
||||
}
|
||||
}
|
||||
netConfBytes, err := ioutil.ReadFile(path)
|
||||
|
||||
return cleanup, netConfBytes, err
|
||||
}
|
||||
|
||||
func delegateAdd(cid, dataDir string, netconf map[string]interface{}) error {
|
||||
|
@ -68,8 +68,8 @@ func doCmdAdd(args *skel.CmdArgs, n *NetConf, fenv *subnetEnv) error {
|
||||
return delegateAdd(args.ContainerID, n.DataDir, n.Delegate)
|
||||
}
|
||||
|
||||
func doCmdDel(args *skel.CmdArgs, n *NetConf) error {
|
||||
netconfBytes, err := consumeScratchNetConf(args.ContainerID, n.DataDir)
|
||||
func doCmdDel(args *skel.CmdArgs, n *NetConf) (err error) {
|
||||
cleanup, netConfBytes, err := consumeScratchNetConf(args.ContainerID, n.DataDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// Per spec should ignore error if resources are missing / already removed
|
||||
@ -78,10 +78,15 @@ func doCmdDel(args *skel.CmdArgs, n *NetConf) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// cleanup will work when no error happens
|
||||
defer func() {
|
||||
cleanup(err)
|
||||
}()
|
||||
|
||||
nc := &types.NetConf{}
|
||||
if err = json.Unmarshal(netconfBytes, nc); err != nil {
|
||||
if err = json.Unmarshal(netConfBytes, nc); err != nil {
|
||||
return fmt.Errorf("failed to parse netconf: %v", err)
|
||||
}
|
||||
|
||||
return invoke.DelegateDel(context.TODO(), nc.Type, netconfBytes, nil)
|
||||
return invoke.DelegateDel(context.TODO(), nc.Type, netConfBytes, nil)
|
||||
}
|
||||
|
@ -55,8 +55,8 @@ func doCmdAdd(args *skel.CmdArgs, n *NetConf, fenv *subnetEnv) error {
|
||||
return delegateAdd(hns.GetSandboxContainerID(args.ContainerID, args.Netns), n.DataDir, n.Delegate)
|
||||
}
|
||||
|
||||
func doCmdDel(args *skel.CmdArgs, n *NetConf) error {
|
||||
netconfBytes, err := consumeScratchNetConf(hns.GetSandboxContainerID(args.ContainerID, args.Netns), n.DataDir)
|
||||
func doCmdDel(args *skel.CmdArgs, n *NetConf) (err error) {
|
||||
cleanup, netConfBytes, err := consumeScratchNetConf(hns.GetSandboxContainerID(args.ContainerID, args.Netns), n.DataDir)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
// Per spec should ignore error if resources are missing / already removed
|
||||
@ -65,10 +65,15 @@ func doCmdDel(args *skel.CmdArgs, n *NetConf) error {
|
||||
return err
|
||||
}
|
||||
|
||||
// cleanup will work when no error happens
|
||||
defer func() {
|
||||
cleanup(err)
|
||||
}()
|
||||
|
||||
nc := &types.NetConf{}
|
||||
if err = json.Unmarshal(netconfBytes, nc); err != nil {
|
||||
if err = json.Unmarshal(netConfBytes, nc); err != nil {
|
||||
return fmt.Errorf("failed to parse netconf: %v", err)
|
||||
}
|
||||
|
||||
return invoke.DelegateDel(context.TODO(), nc.Type, netconfBytes, nil)
|
||||
return invoke.DelegateDel(context.TODO(), nc.Type, netConfBytes, nil)
|
||||
}
|
||||
|
@ -72,7 +72,7 @@ will masquerade traffic as needed.
|
||||
The DNAT rule rewrites the destination port and address of new connections.
|
||||
There is a top-level chain, `CNI-HOSTPORT-DNAT` which is always created and
|
||||
never deleted. Each plugin execution creates an additional chain for ease
|
||||
of cleanup. So, if a single container exists on IP 172.16.30.2 with ports
|
||||
of cleanup. So, if a single container exists on IP 172.16.30.2/24 with ports
|
||||
8080 and 8043 on the host forwarded to ports 80 and 443 in the container, the
|
||||
rules look like this:
|
||||
|
||||
@ -86,10 +86,10 @@ rules look like this:
|
||||
- `-j MARK --set-xmark 0x2000/0x2000`
|
||||
|
||||
`CNI-DN-xxxxxx` chain:
|
||||
- `-p tcp -s 172.16.30.2 --dport 8080 -j CNI-HOSTPORT-SETMARK` (masquerade hairpin traffic)
|
||||
- `-p tcp -s 172.16.30.0/24 --dport 8080 -j CNI-HOSTPORT-SETMARK` (masquerade hairpin traffic)
|
||||
- `-p tcp -s 127.0.0.1 --dport 8080 -j CNI-HOSTPORT-SETMARK` (masquerade localhost traffic)
|
||||
- `-p tcp --dport 8080 -j DNAT --to-destination 172.16.30.2:80` (rewrite destination)
|
||||
- `-p tcp -s 172.16.30.2 --dport 8043 -j CNI-HOSTPORT-SETMARK`
|
||||
- `-p tcp -s 172.16.30.0/24 --dport 8043 -j CNI-HOSTPORT-SETMARK`
|
||||
- `-p tcp -s 127.0.0.1 --dport 8043 -j CNI-HOSTPORT-SETMARK`
|
||||
- `-p tcp --dport 8043 -j DNAT --to-destination 172.16.30.2:443`
|
||||
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/utils"
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
"github.com/mattn/go-shellwords"
|
||||
)
|
||||
@ -35,16 +36,11 @@ type chain struct {
|
||||
|
||||
// setup idempotently creates the chain. It will not error if the chain exists.
|
||||
func (c *chain) setup(ipt *iptables.IPTables) error {
|
||||
// create the chain
|
||||
exists, err := chainExists(ipt, c.table, c.name)
|
||||
|
||||
err := utils.EnsureChain(ipt, c.table, c.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
if err := ipt.NewChain(c.table, c.name); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Add the rules to the chain
|
||||
for _, rule := range c.rules {
|
||||
@ -74,7 +70,7 @@ func (c *chain) teardown(ipt *iptables.IPTables) error {
|
||||
// flush the chain
|
||||
// This will succeed *and create the chain* if it does not exist.
|
||||
// If the chain doesn't exist, the next checks will fail.
|
||||
if err := ipt.ClearChain(c.table, c.name); err != nil {
|
||||
if err := utils.ClearChain(ipt, c.table, c.name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@ -94,17 +90,15 @@ func (c *chain) teardown(ipt *iptables.IPTables) error {
|
||||
}
|
||||
chainParts = chainParts[2:] // List results always include an -A CHAINNAME
|
||||
|
||||
if err := ipt.Delete(c.table, entryChain, chainParts...); err != nil {
|
||||
return fmt.Errorf("Failed to delete referring rule %s %s: %v", c.table, entryChainRule, err)
|
||||
if err := utils.DeleteRule(ipt, c.table, entryChain, chainParts...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := ipt.DeleteChain(c.table, c.name); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
return utils.DeleteChain(ipt, c.table, c.name)
|
||||
}
|
||||
|
||||
// insertUnique will add a rule to a chain if it does not already exist.
|
||||
@ -125,24 +119,10 @@ func insertUnique(ipt *iptables.IPTables, table, chain string, prepend bool, rul
|
||||
}
|
||||
}
|
||||
|
||||
func chainExists(ipt *iptables.IPTables, tableName, chainName string) (bool, error) {
|
||||
chains, err := ipt.ListChains(tableName)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, ch := range chains {
|
||||
if ch == chainName {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// check the chain.
|
||||
func (c *chain) check(ipt *iptables.IPTables) error {
|
||||
|
||||
exists, err := chainExists(ipt, c.table, c.name)
|
||||
exists, err := utils.ChainExists(ipt, c.table, c.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"sync"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/ns"
|
||||
"github.com/containernetworking/plugins/pkg/testutils"
|
||||
@ -32,6 +33,7 @@ const TABLE = "filter" // We'll monkey around here
|
||||
var _ = Describe("chain tests", func() {
|
||||
var testChain chain
|
||||
var ipt *iptables.IPTables
|
||||
var testNs ns.NetNS
|
||||
var cleanup func()
|
||||
|
||||
BeforeEach(func() {
|
||||
@ -41,7 +43,7 @@ var _ = Describe("chain tests", func() {
|
||||
currNs, err := ns.GetCurrentNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
testNs, err := testutils.NewNS()
|
||||
testNs, err = testutils.NewNS()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
tlChainName := fmt.Sprintf("cni-test-%d", rand.Intn(10000000))
|
||||
@ -195,4 +197,38 @@ var _ = Describe("chain tests", func() {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
It("deletes chains idempotently in parallel", func() {
|
||||
defer cleanup()
|
||||
// number of parallel executions
|
||||
N := 10
|
||||
var wg sync.WaitGroup
|
||||
err := testChain.setup(ipt)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
errCh := make(chan error, N)
|
||||
for i := 0; i < N; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
// teardown chain
|
||||
errCh <- testNs.Do(func(ns.NetNS) error {
|
||||
return testChain.teardown(ipt)
|
||||
})
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
close(errCh)
|
||||
for err := range errCh {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
chains, err := ipt.ListChains(TABLE)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
for _, chain := range chains {
|
||||
if chain == testChain.name {
|
||||
Fail("Chain was not deleted")
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
})
|
||||
|
@ -60,9 +60,9 @@ type PortMapConf struct {
|
||||
|
||||
// These are fields parsed out of the config or the environment;
|
||||
// included here for convenience
|
||||
ContainerID string `json:"-"`
|
||||
ContIPv4 net.IP `json:"-"`
|
||||
ContIPv6 net.IP `json:"-"`
|
||||
ContainerID string `json:"-"`
|
||||
ContIPv4 net.IPNet `json:"-"`
|
||||
ContIPv6 net.IPNet `json:"-"`
|
||||
}
|
||||
|
||||
// The default mark bit to signal that masquerading is required
|
||||
@ -85,13 +85,13 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
|
||||
netConf.ContainerID = args.ContainerID
|
||||
|
||||
if netConf.ContIPv4 != nil {
|
||||
if netConf.ContIPv4.IP != nil {
|
||||
if err := forwardPorts(netConf, netConf.ContIPv4); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if netConf.ContIPv6 != nil {
|
||||
if netConf.ContIPv6.IP != nil {
|
||||
if err := forwardPorts(netConf, netConf.ContIPv6); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -107,6 +107,10 @@ func cmdDel(args *skel.CmdArgs) error {
|
||||
return fmt.Errorf("failed to parse config: %v", err)
|
||||
}
|
||||
|
||||
if len(netConf.RuntimeConfig.PortMaps) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
netConf.ContainerID = args.ContainerID
|
||||
|
||||
// We don't need to parse out whether or not we're using v6 or snat,
|
||||
@ -138,13 +142,13 @@ func cmdCheck(args *skel.CmdArgs) error {
|
||||
|
||||
conf.ContainerID = args.ContainerID
|
||||
|
||||
if conf.ContIPv4 != nil {
|
||||
if conf.ContIPv4.IP != nil {
|
||||
if err := checkPorts(conf, conf.ContIPv4); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if conf.ContIPv6 != nil {
|
||||
if conf.ContIPv6.IP != nil {
|
||||
if err := checkPorts(conf, conf.ContIPv6); err != nil {
|
||||
return err
|
||||
}
|
||||
@ -205,9 +209,9 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, *current.Result, er
|
||||
|
||||
if conf.PrevResult != nil {
|
||||
for _, ip := range result.IPs {
|
||||
if ip.Version == "6" && conf.ContIPv6 != nil {
|
||||
if ip.Version == "6" && conf.ContIPv6.IP != nil {
|
||||
continue
|
||||
} else if ip.Version == "4" && conf.ContIPv4 != nil {
|
||||
} else if ip.Version == "4" && conf.ContIPv4.IP != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
@ -223,9 +227,9 @@ func parseConfig(stdin []byte, ifName string) (*PortMapConf, *current.Result, er
|
||||
}
|
||||
switch ip.Version {
|
||||
case "6":
|
||||
conf.ContIPv6 = ip.Address.IP
|
||||
conf.ContIPv6 = ip.Address
|
||||
case "4":
|
||||
conf.ContIPv4 = ip.Address.IP
|
||||
conf.ContIPv4 = ip.Address
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/containernetworking/plugins/pkg/utils"
|
||||
"github.com/containernetworking/plugins/pkg/utils/sysctl"
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
)
|
||||
@ -47,9 +48,9 @@ const MarkMasqChainName = "CNI-HOSTPORT-MASQ"
|
||||
const OldTopLevelSNATChainName = "CNI-HOSTPORT-SNAT"
|
||||
|
||||
// forwardPorts establishes port forwarding to a given container IP.
|
||||
// containerIP can be either v4 or v6.
|
||||
func forwardPorts(config *PortMapConf, containerIP net.IP) error {
|
||||
isV6 := (containerIP.To4() == nil)
|
||||
// containerNet.IP can be either v4 or v6.
|
||||
func forwardPorts(config *PortMapConf, containerNet net.IPNet) error {
|
||||
isV6 := (containerNet.IP.To4() == nil)
|
||||
|
||||
var ipt *iptables.IPTables
|
||||
var err error
|
||||
@ -85,7 +86,7 @@ func forwardPorts(config *PortMapConf, containerIP net.IP) error {
|
||||
if !isV6 {
|
||||
// Set the route_localnet bit on the host interface, so that
|
||||
// 127/8 can cross a routing boundary.
|
||||
hostIfName := getRoutableHostIF(containerIP)
|
||||
hostIfName := getRoutableHostIF(containerNet.IP)
|
||||
if hostIfName != "" {
|
||||
if err := enableLocalnetRouting(hostIfName); err != nil {
|
||||
return fmt.Errorf("unable to enable route_localnet: %v", err)
|
||||
@ -103,7 +104,7 @@ func forwardPorts(config *PortMapConf, containerIP net.IP) error {
|
||||
dnatChain := genDnatChain(config.Name, config.ContainerID)
|
||||
// First, idempotently tear down this chain in case there was some
|
||||
// sort of collision or bad state.
|
||||
fillDnatRules(&dnatChain, config, containerIP)
|
||||
fillDnatRules(&dnatChain, config, containerNet)
|
||||
if err := dnatChain.setup(ipt); err != nil {
|
||||
return fmt.Errorf("unable to setup DNAT: %v", err)
|
||||
}
|
||||
@ -111,10 +112,10 @@ func forwardPorts(config *PortMapConf, containerIP net.IP) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkPorts(config *PortMapConf, containerIP net.IP) error {
|
||||
func checkPorts(config *PortMapConf, containerNet net.IPNet) error {
|
||||
|
||||
dnatChain := genDnatChain(config.Name, config.ContainerID)
|
||||
fillDnatRules(&dnatChain, config, containerIP)
|
||||
fillDnatRules(&dnatChain, config, containerNet)
|
||||
|
||||
ip4t := maybeGetIptables(false)
|
||||
ip6t := maybeGetIptables(true)
|
||||
@ -123,7 +124,7 @@ func checkPorts(config *PortMapConf, containerIP net.IP) error {
|
||||
}
|
||||
|
||||
if ip4t != nil {
|
||||
exists, err := chainExists(ip4t, dnatChain.table, dnatChain.name)
|
||||
exists, err := utils.ChainExists(ip4t, dnatChain.table, dnatChain.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -136,7 +137,7 @@ func checkPorts(config *PortMapConf, containerIP net.IP) error {
|
||||
}
|
||||
|
||||
if ip6t != nil {
|
||||
exists, err := chainExists(ip6t, dnatChain.table, dnatChain.name)
|
||||
exists, err := utils.ChainExists(ip6t, dnatChain.table, dnatChain.name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -172,15 +173,15 @@ func genToplevelDnatChain() chain {
|
||||
func genDnatChain(netName, containerID string) chain {
|
||||
return chain{
|
||||
table: "nat",
|
||||
name: formatChainName("DN-", netName, containerID),
|
||||
name: utils.MustFormatChainNameWithPrefix(netName, containerID, "DN-"),
|
||||
entryChains: []string{TopLevelDNATChainName},
|
||||
}
|
||||
}
|
||||
|
||||
// dnatRules generates the destination NAT rules, one per port, to direct
|
||||
// traffic from hostip:hostport to podip:podport
|
||||
func fillDnatRules(c *chain, config *PortMapConf, containerIP net.IP) {
|
||||
isV6 := (containerIP.To4() == nil)
|
||||
func fillDnatRules(c *chain, config *PortMapConf, containerNet net.IPNet) {
|
||||
isV6 := (containerNet.IP.To4() == nil)
|
||||
comment := trimComment(fmt.Sprintf(`dnat name: "%s" id: "%s"`, config.Name, config.ContainerID))
|
||||
entries := config.RuntimeConfig.PortMaps
|
||||
setMarkChainName := SetMarkChainName
|
||||
@ -223,10 +224,27 @@ func fillDnatRules(c *chain, config *PortMapConf, containerIP net.IP) {
|
||||
// the ordering is important here; the mark rules must be first.
|
||||
c.rules = make([][]string, 0, 3*len(entries))
|
||||
for _, entry := range entries {
|
||||
// If a HostIP is given, only process the entry if host and container address families match
|
||||
// and append it to the iptables rules
|
||||
addRuleBaseDst := false
|
||||
if entry.HostIP != "" {
|
||||
hostIP := net.ParseIP(entry.HostIP)
|
||||
isHostV6 := (hostIP.To4() == nil)
|
||||
|
||||
if isV6 != isHostV6 {
|
||||
continue
|
||||
}
|
||||
|
||||
// Unspecified addresses can not be used as destination
|
||||
if !hostIP.IsUnspecified() {
|
||||
addRuleBaseDst = true
|
||||
}
|
||||
}
|
||||
|
||||
ruleBase := []string{
|
||||
"-p", entry.Protocol,
|
||||
"--dport", strconv.Itoa(entry.HostPort)}
|
||||
if entry.HostIP != "" {
|
||||
if addRuleBaseDst {
|
||||
ruleBase = append(ruleBase,
|
||||
"-d", entry.HostIP)
|
||||
}
|
||||
@ -238,7 +256,7 @@ func fillDnatRules(c *chain, config *PortMapConf, containerIP net.IP) {
|
||||
copy(hpRule, ruleBase)
|
||||
|
||||
hpRule = append(hpRule,
|
||||
"-s", containerIP.String(),
|
||||
"-s", containerNet.String(),
|
||||
"-j", setMarkChainName,
|
||||
)
|
||||
c.rules = append(c.rules, hpRule)
|
||||
@ -261,7 +279,7 @@ func fillDnatRules(c *chain, config *PortMapConf, containerIP net.IP) {
|
||||
copy(dnatRule, ruleBase)
|
||||
dnatRule = append(dnatRule,
|
||||
"-j", "DNAT",
|
||||
"--to-destination", fmtIpPort(containerIP, entry.ContainerPort),
|
||||
"--to-destination", fmtIpPort(containerNet.IP, entry.ContainerPort),
|
||||
)
|
||||
c.rules = append(c.rules, dnatRule)
|
||||
}
|
||||
@ -323,11 +341,9 @@ func enableLocalnetRouting(ifName string) error {
|
||||
// genOldSnatChain is no longer used, but used to be created. We'll try and
|
||||
// tear it down in case the plugin version changed between ADD and DEL
|
||||
func genOldSnatChain(netName, containerID string) chain {
|
||||
name := formatChainName("SN-", netName, containerID)
|
||||
|
||||
return chain{
|
||||
table: "nat",
|
||||
name: name,
|
||||
name: utils.MustFormatChainNameWithPrefix(netName, containerID, "SN-"),
|
||||
entryChains: []string{OldTopLevelSNATChainName},
|
||||
}
|
||||
}
|
||||
|
@ -96,119 +96,133 @@ var _ = Describe("portmap integration tests", func() {
|
||||
}
|
||||
})
|
||||
|
||||
// This needs to be done using Ginkgo's asynchronous testing mode.
|
||||
It("forwards a TCP port on ipv4", func(done Done) {
|
||||
var err error
|
||||
hostPort := rand.Intn(10000) + 1025
|
||||
runtimeConfig := libcni.RuntimeConf{
|
||||
ContainerID: fmt.Sprintf("unit-test-%d", hostPort),
|
||||
NetNS: targetNS.Path(),
|
||||
IfName: "eth0",
|
||||
CapabilityArgs: map[string]interface{}{
|
||||
"portMappings": []map[string]interface{}{
|
||||
{
|
||||
"hostPort": hostPort,
|
||||
"containerPort": containerPort,
|
||||
"protocol": "tcp",
|
||||
Describe("Creating an interface in a namespace with the ptp plugin", func() {
|
||||
// This needs to be done using Ginkgo's asynchronous testing mode.
|
||||
It("forwards a TCP port on ipv4", func(done Done) {
|
||||
var err error
|
||||
hostPort := rand.Intn(10000) + 1025
|
||||
runtimeConfig := libcni.RuntimeConf{
|
||||
ContainerID: fmt.Sprintf("unit-test-%d", hostPort),
|
||||
NetNS: targetNS.Path(),
|
||||
IfName: "eth0",
|
||||
CapabilityArgs: map[string]interface{}{
|
||||
"portMappings": []map[string]interface{}{
|
||||
{
|
||||
"hostPort": hostPort,
|
||||
"containerPort": containerPort,
|
||||
"protocol": "tcp",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// Make delete idempotent, so we can clean up on failure
|
||||
netDeleted := false
|
||||
deleteNetwork := func() error {
|
||||
if netDeleted {
|
||||
return nil
|
||||
}
|
||||
netDeleted = true
|
||||
return cniConf.DelNetworkList(context.TODO(), configList, &runtimeConfig)
|
||||
}
|
||||
|
||||
// we'll also manually check the iptables chains
|
||||
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
dnatChainName := genDnatChain("cni-portmap-unit-test", runtimeConfig.ContainerID).name
|
||||
|
||||
// Create the network
|
||||
resI, err := cniConf.AddNetworkList(context.TODO(), configList, &runtimeConfig)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer deleteNetwork()
|
||||
|
||||
// Undo Docker's forwarding policy
|
||||
cmd := exec.Command("iptables", "-t", "filter",
|
||||
"-P", "FORWARD", "ACCEPT")
|
||||
cmd.Stderr = GinkgoWriter
|
||||
err = cmd.Run()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Check the chain exists
|
||||
_, err = ipt.List("nat", dnatChainName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err := current.GetResult(resI)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
var contIP net.IP
|
||||
|
||||
for _, ip := range result.IPs {
|
||||
intfIndex := *ip.Interface
|
||||
if result.Interfaces[intfIndex].Sandbox == "" {
|
||||
continue
|
||||
// Make delete idempotent, so we can clean up on failure
|
||||
netDeleted := false
|
||||
deleteNetwork := func() error {
|
||||
if netDeleted {
|
||||
return nil
|
||||
}
|
||||
netDeleted = true
|
||||
return cniConf.DelNetworkList(context.TODO(), configList, &runtimeConfig)
|
||||
}
|
||||
contIP = ip.Address.IP
|
||||
}
|
||||
if contIP == nil {
|
||||
Fail("could not determine container IP")
|
||||
}
|
||||
|
||||
hostIP := getLocalIP()
|
||||
fmt.Fprintf(GinkgoWriter, "hostIP: %s:%d, contIP: %s:%d\n",
|
||||
hostIP, hostPort, contIP, containerPort)
|
||||
// we'll also manually check the iptables chains
|
||||
ipt, err := iptables.NewWithProtocol(iptables.ProtocolIPv4)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
dnatChainName := genDnatChain("cni-portmap-unit-test", runtimeConfig.ContainerID).name
|
||||
|
||||
// dump iptables-save output for debugging
|
||||
cmd = exec.Command("iptables-save")
|
||||
cmd.Stderr = GinkgoWriter
|
||||
cmd.Stdout = GinkgoWriter
|
||||
Expect(cmd.Run()).To(Succeed())
|
||||
// Create the network
|
||||
resI, err := cniConf.AddNetworkList(context.TODO(), configList, &runtimeConfig)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
defer deleteNetwork()
|
||||
|
||||
// Sanity check: verify that the container is reachable directly
|
||||
contOK := testEchoServer(contIP.String(), containerPort, "")
|
||||
// Undo Docker's forwarding policy
|
||||
cmd := exec.Command("iptables", "-t", "filter",
|
||||
"-P", "FORWARD", "ACCEPT")
|
||||
cmd.Stderr = GinkgoWriter
|
||||
err = cmd.Run()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Verify that a connection to the forwarded port works
|
||||
dnatOK := testEchoServer(hostIP, hostPort, "")
|
||||
// Check the chain exists
|
||||
_, err = ipt.List("nat", dnatChainName)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Verify that a connection to localhost works
|
||||
snatOK := testEchoServer("127.0.0.1", hostPort, "")
|
||||
result, err := current.GetResult(resI)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
var contIP net.IP
|
||||
|
||||
// verify that hairpin works
|
||||
hairpinOK := testEchoServer(hostIP, hostPort, targetNS.Path())
|
||||
for _, ip := range result.IPs {
|
||||
intfIndex := *ip.Interface
|
||||
if result.Interfaces[intfIndex].Sandbox == "" {
|
||||
continue
|
||||
}
|
||||
contIP = ip.Address.IP
|
||||
}
|
||||
if contIP == nil {
|
||||
Fail("could not determine container IP")
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
session.Terminate()
|
||||
err = deleteNetwork()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
hostIP := getLocalIP()
|
||||
fmt.Fprintf(GinkgoWriter, "hostIP: %s:%d, contIP: %s:%d\n",
|
||||
hostIP, hostPort, contIP, containerPort)
|
||||
|
||||
// Verify iptables rules are gone
|
||||
_, err = ipt.List("nat", dnatChainName)
|
||||
Expect(err).To(MatchError(ContainSubstring("iptables: No chain/target/match by that name.")))
|
||||
// dump iptables-save output for debugging
|
||||
cmd = exec.Command("iptables-save")
|
||||
cmd.Stderr = GinkgoWriter
|
||||
cmd.Stdout = GinkgoWriter
|
||||
Expect(cmd.Run()).To(Succeed())
|
||||
|
||||
// Check that everything succeeded *after* we clean up the network
|
||||
if !contOK {
|
||||
Fail("connection direct to " + contIP.String() + " failed")
|
||||
}
|
||||
if !dnatOK {
|
||||
Fail("Connection to " + hostIP + " was not forwarded")
|
||||
}
|
||||
if !snatOK {
|
||||
Fail("connection to 127.0.0.1 was not forwarded")
|
||||
}
|
||||
if !hairpinOK {
|
||||
Fail("Hairpin connection failed")
|
||||
}
|
||||
// dump ip routes output for debugging
|
||||
cmd = exec.Command("ip", "route")
|
||||
cmd.Stderr = GinkgoWriter
|
||||
cmd.Stdout = GinkgoWriter
|
||||
Expect(cmd.Run()).To(Succeed())
|
||||
|
||||
close(done)
|
||||
// dump ip addresses output for debugging
|
||||
cmd = exec.Command("ip", "addr")
|
||||
cmd.Stderr = GinkgoWriter
|
||||
cmd.Stdout = GinkgoWriter
|
||||
Expect(cmd.Run()).To(Succeed())
|
||||
|
||||
}, TIMEOUT*9)
|
||||
// Sanity check: verify that the container is reachable directly
|
||||
contOK := testEchoServer(contIP.String(), containerPort, "")
|
||||
|
||||
// Verify that a connection to the forwarded port works
|
||||
dnatOK := testEchoServer(hostIP, hostPort, "")
|
||||
|
||||
// Verify that a connection to localhost works
|
||||
snatOK := testEchoServer("127.0.0.1", hostPort, "")
|
||||
|
||||
// verify that hairpin works
|
||||
hairpinOK := testEchoServer(hostIP, hostPort, targetNS.Path())
|
||||
|
||||
// Cleanup
|
||||
session.Terminate()
|
||||
err = deleteNetwork()
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
// Verify iptables rules are gone
|
||||
_, err = ipt.List("nat", dnatChainName)
|
||||
Expect(err).To(MatchError(ContainSubstring("iptables: No chain/target/match by that name.")))
|
||||
|
||||
// Check that everything succeeded *after* we clean up the network
|
||||
if !contOK {
|
||||
Fail("connection direct to " + contIP.String() + " failed")
|
||||
}
|
||||
if !dnatOK {
|
||||
Fail("Connection to " + hostIP + " was not forwarded")
|
||||
}
|
||||
if !snatOK {
|
||||
Fail("connection to 127.0.0.1 was not forwarded")
|
||||
}
|
||||
if !hairpinOK {
|
||||
Fail("Hairpin connection failed")
|
||||
}
|
||||
|
||||
close(done)
|
||||
|
||||
}, TIMEOUT*9)
|
||||
})
|
||||
})
|
||||
|
||||
// testEchoServer returns true if we found an echo server on the port
|
||||
|
@ -16,7 +16,8 @@ package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
@ -77,8 +78,10 @@ var _ = Describe("portmapping configuration", func() {
|
||||
Expect(c.SNAT).To(Equal(&fvar))
|
||||
Expect(c.Name).To(Equal("test"))
|
||||
|
||||
Expect(c.ContIPv4).To(Equal(net.ParseIP("10.0.0.2")))
|
||||
Expect(c.ContIPv6).To(Equal(net.ParseIP("2001:db8:1::2")))
|
||||
n, err := types.ParseCIDR("10.0.0.2/24")
|
||||
Expect(c.ContIPv4).To(Equal(*n))
|
||||
n, err = types.ParseCIDR("2001:db8:1::2/64")
|
||||
Expect(c.ContIPv6).To(Equal(*n))
|
||||
})
|
||||
|
||||
It("Correctly parses a DEL config", func() {
|
||||
@ -167,7 +170,11 @@ var _ = Describe("portmapping configuration", func() {
|
||||
{ "hostPort": 8080, "containerPort": 80, "protocol": "tcp"},
|
||||
{ "hostPort": 8081, "containerPort": 80, "protocol": "tcp"},
|
||||
{ "hostPort": 8080, "containerPort": 81, "protocol": "udp"},
|
||||
{ "hostPort": 8082, "containerPort": 82, "protocol": "udp"}
|
||||
{ "hostPort": 8082, "containerPort": 82, "protocol": "udp"},
|
||||
{ "hostPort": 8083, "containerPort": 83, "protocol": "tcp", "hostIP": "192.168.0.2"},
|
||||
{ "hostPort": 8084, "containerPort": 84, "protocol": "tcp", "hostIP": "0.0.0.0"},
|
||||
{ "hostPort": 8085, "containerPort": 85, "protocol": "tcp", "hostIP": "2001:db8:a::1"},
|
||||
{ "hostPort": 8086, "containerPort": 86, "protocol": "tcp", "hostIP": "::"}
|
||||
]
|
||||
},
|
||||
"snat": true,
|
||||
@ -186,14 +193,15 @@ var _ = Describe("portmapping configuration", func() {
|
||||
entryChains: []string{"CNI-HOSTPORT-DNAT"},
|
||||
}))
|
||||
|
||||
fillDnatRules(&ch, conf, net.ParseIP("10.0.0.2"))
|
||||
n, err := types.ParseCIDR("10.0.0.2/24")
|
||||
fillDnatRules(&ch, conf, *n)
|
||||
|
||||
Expect(ch.entryRules).To(Equal([][]string{
|
||||
{"-m", "comment", "--comment",
|
||||
fmt.Sprintf("dnat name: \"test\" id: \"%s\"", containerID),
|
||||
"-m", "multiport",
|
||||
"-p", "tcp",
|
||||
"--destination-ports", "8080,8081",
|
||||
"--destination-ports", "8080,8081,8083,8084,8085,8086",
|
||||
"a", "b"},
|
||||
{"-m", "comment", "--comment",
|
||||
fmt.Sprintf("dnat name: \"test\" id: \"%s\"", containerID),
|
||||
@ -204,34 +212,53 @@ var _ = Describe("portmapping configuration", func() {
|
||||
}))
|
||||
|
||||
Expect(ch.rules).To(Equal([][]string{
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "10.0.0.2", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
// tcp rules and not hostIP
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||
{"-p", "tcp", "--dport", "8081", "-s", "10.0.0.2", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8081", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8081", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||
{"-p", "udp", "--dport", "8080", "-s", "10.0.0.2", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
// udp rules and not hostIP
|
||||
{"-p", "udp", "--dport", "8080", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8080", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:81"},
|
||||
{"-p", "udp", "--dport", "8082", "-s", "10.0.0.2", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8082", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8082", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "10.0.0.2:82"},
|
||||
// tcp rules and hostIP
|
||||
{"-p", "tcp", "--dport", "8083", "-d", "192.168.0.2", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8083", "-d", "192.168.0.2", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8083", "-d", "192.168.0.2", "-j", "DNAT", "--to-destination", "10.0.0.2:83"},
|
||||
// tcp rules and hostIP = "0.0.0.0"
|
||||
{"-p", "tcp", "--dport", "8084", "-s", "10.0.0.2/24", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8084", "-s", "127.0.0.1", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8084", "-j", "DNAT", "--to-destination", "10.0.0.2:84"},
|
||||
}))
|
||||
|
||||
ch.rules = nil
|
||||
ch.entryRules = nil
|
||||
|
||||
fillDnatRules(&ch, conf, net.ParseIP("2001:db8::2"))
|
||||
n, err = types.ParseCIDR("2001:db8::2/64")
|
||||
fillDnatRules(&ch, conf, *n)
|
||||
|
||||
Expect(ch.rules).To(Equal([][]string{
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "2001:db8::2", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
// tcp rules and not hostIP
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "[2001:db8::2]:80"},
|
||||
{"-p", "tcp", "--dport", "8081", "-s", "2001:db8::2", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8081", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "[2001:db8::2]:80"},
|
||||
{"-p", "udp", "--dport", "8080", "-s", "2001:db8::2", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
// udp rules and not hostIP
|
||||
{"-p", "udp", "--dport", "8080", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "[2001:db8::2]:81"},
|
||||
{"-p", "udp", "--dport", "8082", "-s", "2001:db8::2", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8082", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "[2001:db8::2]:82"},
|
||||
// tcp rules and hostIP
|
||||
{"-p", "tcp", "--dport", "8085", "-d", "2001:db8:a::1", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8085", "-d", "2001:db8:a::1", "-j", "DNAT", "--to-destination", "[2001:db8::2]:85"},
|
||||
// tcp rules and hostIP = "::"
|
||||
{"-p", "tcp", "--dport", "8086", "-s", "2001:db8::2/64", "-j", "CNI-HOSTPORT-SETMARK"},
|
||||
{"-p", "tcp", "--dport", "8086", "-j", "DNAT", "--to-destination", "[2001:db8::2]:86"},
|
||||
}))
|
||||
|
||||
// Disable snat, generate rules
|
||||
@ -240,12 +267,15 @@ var _ = Describe("portmapping configuration", func() {
|
||||
fvar := false
|
||||
conf.SNAT = &fvar
|
||||
|
||||
fillDnatRules(&ch, conf, net.ParseIP("10.0.0.2"))
|
||||
n, err = types.ParseCIDR("10.0.0.2/24")
|
||||
fillDnatRules(&ch, conf, *n)
|
||||
Expect(ch.rules).To(Equal([][]string{
|
||||
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||
{"-p", "tcp", "--dport", "8081", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||
{"-p", "udp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:81"},
|
||||
{"-p", "udp", "--dport", "8082", "-j", "DNAT", "--to-destination", "10.0.0.2:82"},
|
||||
{"-p", "tcp", "--dport", "8083", "-d", "192.168.0.2", "-j", "DNAT", "--to-destination", "10.0.0.2:83"},
|
||||
{"-p", "tcp", "--dport", "8084", "-j", "DNAT", "--to-destination", "10.0.0.2:84"},
|
||||
}))
|
||||
})
|
||||
|
||||
@ -276,9 +306,10 @@ var _ = Describe("portmapping configuration", func() {
|
||||
conf.ContainerID = containerID
|
||||
|
||||
ch = genDnatChain(conf.Name, containerID)
|
||||
fillDnatRules(&ch, conf, net.ParseIP("10.0.0.2"))
|
||||
n, err := types.ParseCIDR("10.0.0.2/24")
|
||||
fillDnatRules(&ch, conf, *n)
|
||||
Expect(ch.rules).To(Equal([][]string{
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "10.0.0.2", "-j", "PLZ-SET-MARK"},
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "10.0.0.2/24", "-j", "PLZ-SET-MARK"},
|
||||
{"-p", "tcp", "--dport", "8080", "-s", "127.0.0.1", "-j", "PLZ-SET-MARK"},
|
||||
{"-p", "tcp", "--dport", "8080", "-j", "DNAT", "--to-destination", "10.0.0.2:80"},
|
||||
}))
|
||||
|
@ -15,7 +15,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"crypto/sha512"
|
||||
"fmt"
|
||||
"net"
|
||||
"strconv"
|
||||
@ -24,8 +23,6 @@ import (
|
||||
"github.com/vishvananda/netlink"
|
||||
)
|
||||
|
||||
const maxChainNameLength = 28
|
||||
|
||||
// fmtIpPort correctly formats ip:port literals for iptables and ip6tables -
|
||||
// need to wrap v6 literals in a []
|
||||
func fmtIpPort(ip net.IP, port int) string {
|
||||
@ -62,12 +59,6 @@ func getRoutableHostIF(containerIP net.IP) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func formatChainName(prefix, name, id string) string {
|
||||
chainBytes := sha512.Sum512([]byte(name + id))
|
||||
chain := fmt.Sprintf("CNI-%s%x", prefix, chainBytes)
|
||||
return chain[:maxChainNameLength]
|
||||
}
|
||||
|
||||
// groupByProto groups port numbers by protocol
|
||||
func groupByProto(entries []PortMapEntry) map[string][]int {
|
||||
if len(entries) == 0 {
|
||||
|
@ -237,7 +237,7 @@ func doRoutes(ipCfgs []*current.IPConfig, origRoutes []*types.Route, iface strin
|
||||
if ipCfg.Version == "4" {
|
||||
src.Mask = net.CIDRMask(32, 32)
|
||||
} else {
|
||||
src.Mask = net.CIDRMask(64, 64)
|
||||
src.Mask = net.CIDRMask(128, 128)
|
||||
}
|
||||
|
||||
log.Printf("Source to use %s", src.String())
|
||||
@ -258,7 +258,7 @@ func doRoutes(ipCfgs []*current.IPConfig, origRoutes []*types.Route, iface strin
|
||||
dest.Mask = net.CIDRMask(0, 32)
|
||||
} else {
|
||||
dest.IP = net.IPv6zero
|
||||
dest.Mask = net.CIDRMask(0, 64)
|
||||
dest.Mask = net.CIDRMask(0, 128)
|
||||
}
|
||||
|
||||
route := netlink.Route{
|
||||
@ -296,6 +296,10 @@ func doRoutes(ipCfgs []*current.IPConfig, origRoutes []*types.Route, iface strin
|
||||
|
||||
route.Table = table
|
||||
|
||||
// Reset the route flags since if it is dynamically created,
|
||||
// adding it to the new table will fail with "invalid argument"
|
||||
route.Flags = 0
|
||||
|
||||
// We use route replace in case the route already exists, which
|
||||
// is possible for the default gateway we added above.
|
||||
err = netlink.RouteReplace(&route)
|
||||
|
@ -61,3 +61,13 @@ The following [CNI_ARGS](https://github.com/containernetworking/cni/blob/master/
|
||||
|
||||
Note: You may add `IgnoreUnknown=true` to allow loose CNI argument verification (see CNI's issue[#560](https://github.com/containernetworking/cni/issues/560)).
|
||||
|
||||
The plugin also support following [capability argument](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md).
|
||||
|
||||
* `mac`: Pass MAC addresses for CNI interface
|
||||
|
||||
The following [args conventions](https://github.com/containernetworking/cni/blob/master/CONVENTIONS.md#args-in-network-config) are supported:
|
||||
|
||||
* `mac` (string, optional): MAC address (i.e. hardware address) of interface
|
||||
* `mtu` (integer, optional): MTU of interface
|
||||
* `promisc` (bool, optional): Change the promiscuous mode of interface
|
||||
* `sysctl` (object, optional): Change system controls
|
||||
|
@ -25,6 +25,7 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/j-keck/arping"
|
||||
"github.com/vishvananda/netlink"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/skel"
|
||||
@ -43,9 +44,24 @@ type TuningConf struct {
|
||||
Mac string `json:"mac,omitempty"`
|
||||
Promisc bool `json:"promisc,omitempty"`
|
||||
Mtu int `json:"mtu,omitempty"`
|
||||
|
||||
RuntimeConfig struct {
|
||||
Mac string `json:"mac,omitempty"`
|
||||
} `json:"runtimeConfig,omitempty"`
|
||||
Args *struct {
|
||||
A *IPAMArgs `json:"cni"`
|
||||
} `json:"args"`
|
||||
}
|
||||
|
||||
type MACEnvArgs struct {
|
||||
type IPAMArgs struct {
|
||||
SysCtl *map[string]string `json:"sysctl"`
|
||||
Mac *string `json:"mac,omitempty"`
|
||||
Promisc *bool `json:"promisc,omitempty"`
|
||||
Mtu *int `json:"mtu,omitempty"`
|
||||
}
|
||||
|
||||
// MacEnvArgs represents CNI_ARG
|
||||
type MacEnvArgs struct {
|
||||
types.CommonArgs
|
||||
MAC types.UnmarshallableString `json:"mac,omitempty"`
|
||||
}
|
||||
@ -56,9 +72,9 @@ func parseConf(data []byte, envArgs string) (*TuningConf, error) {
|
||||
return nil, fmt.Errorf("failed to load netconf: %v", err)
|
||||
}
|
||||
|
||||
// Parse custom MAC from both env args
|
||||
// Parse custom Mac from both env args
|
||||
if envArgs != "" {
|
||||
e := MACEnvArgs{}
|
||||
e := MacEnvArgs{}
|
||||
err := types.LoadArgs(envArgs, &e)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -69,6 +85,33 @@ func parseConf(data []byte, envArgs string) (*TuningConf, error) {
|
||||
}
|
||||
}
|
||||
|
||||
// Parse custom Mac from RuntimeConfig
|
||||
if conf.RuntimeConfig.Mac != "" {
|
||||
conf.Mac = conf.RuntimeConfig.Mac
|
||||
}
|
||||
|
||||
// Get args
|
||||
if conf.Args != nil && conf.Args.A != nil {
|
||||
if conf.Args.A.SysCtl != nil {
|
||||
for k, v := range *conf.Args.A.SysCtl {
|
||||
conf.SysCtl[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
if conf.Args.A.Mac != nil {
|
||||
conf.Mac = *conf.Args.A.Mac
|
||||
}
|
||||
|
||||
if conf.Args.A.Promisc != nil {
|
||||
conf.Promisc = *conf.Args.A.Promisc
|
||||
}
|
||||
|
||||
if conf.Args.A.Mtu != nil {
|
||||
conf.Mtu = *conf.Args.A.Mtu
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return &conf, nil
|
||||
}
|
||||
|
||||
@ -83,15 +126,7 @@ func changeMacAddr(ifName string, newMacAddr string) error {
|
||||
return fmt.Errorf("failed to get %q: %v", ifName, err)
|
||||
}
|
||||
|
||||
err = netlink.LinkSetDown(link)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set %q down: %v", ifName, err)
|
||||
}
|
||||
err = netlink.LinkSetHardwareAddr(link, addr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to set %q address to %q: %v", ifName, newMacAddr, err)
|
||||
}
|
||||
return netlink.LinkSetUp(link)
|
||||
return netlink.LinkSetHardwareAddr(link, addr)
|
||||
}
|
||||
|
||||
func updateResultsMacAddr(config TuningConf, ifName string, newMacAddr string) {
|
||||
@ -145,7 +180,7 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = current.NewResultFromResult(tuningConf.PrevResult)
|
||||
result, err := current.NewResultFromResult(tuningConf.PrevResult)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -174,6 +209,13 @@ func cmdAdd(args *skel.CmdArgs) error {
|
||||
if err = changeMacAddr(args.IfName, tuningConf.Mac); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, ipc := range result.IPs {
|
||||
if ipc.Version == "4" {
|
||||
_ = arping.GratuitousArpOverIfaceByName(ipc.Address.IP, args.IfName)
|
||||
}
|
||||
}
|
||||
|
||||
updateResultsMacAddr(*tuningConf, args.IfName, tuningConf.Mac)
|
||||
}
|
||||
|
||||
@ -230,7 +272,7 @@ func cmdCheck(args *skel.CmdArgs) error {
|
||||
|
||||
err = ns.WithNetNSPath(args.Netns, func(_ ns.NetNS) error {
|
||||
// Check each configured value vs what's currently in the container
|
||||
for key, conf_value := range tuningConf.SysCtl {
|
||||
for key, confValue := range tuningConf.SysCtl {
|
||||
fileName := filepath.Join("/proc/sys", strings.Replace(key, ".", "/", -1))
|
||||
fileName = filepath.Clean(fileName)
|
||||
|
||||
@ -238,9 +280,9 @@ func cmdCheck(args *skel.CmdArgs) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
cur_value := strings.TrimSuffix(string(contents), "\n")
|
||||
if conf_value != cur_value {
|
||||
return fmt.Errorf("Error: Tuning configured value of %s is %s, current value is %s", fileName, conf_value, cur_value)
|
||||
curValue := strings.TrimSuffix(string(contents), "\n")
|
||||
if confValue != curValue {
|
||||
return fmt.Errorf("Error: Tuning configured value of %s is %s, current value is %s", fileName, confValue, curValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -215,6 +215,67 @@ var _ = Describe("tuning plugin", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("configures and deconfigures promiscuous mode from args with ADD/DEL", func() {
|
||||
conf := []byte(`{
|
||||
"name": "test",
|
||||
"type": "iplink",
|
||||
"cniVersion": "0.3.1",
|
||||
"args": {
|
||||
"cni": {
|
||||
"promisc": true
|
||||
}
|
||||
},
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{"name": "dummy0", "sandbox":"netns"}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "10.0.0.2/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: originalNS.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: conf,
|
||||
}
|
||||
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(len(result.Interfaces)).To(Equal(1))
|
||||
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
|
||||
Expect(len(result.IPs)).To(Equal(1))
|
||||
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
|
||||
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().Promisc).To(Equal(1))
|
||||
|
||||
err = testutils.CmdDel(originalNS.Path(),
|
||||
args.ContainerID, "", func() error { return cmdDel(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("configures and deconfigures mtu with ADD/DEL", func() {
|
||||
conf := []byte(`{
|
||||
"name": "test",
|
||||
@ -272,6 +333,67 @@ var _ = Describe("tuning plugin", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("configures and deconfigures mtu from args with ADD/DEL", func() {
|
||||
conf := []byte(`{
|
||||
"name": "test",
|
||||
"type": "iplink",
|
||||
"cniVersion": "0.3.1",
|
||||
"args": {
|
||||
"cni": {
|
||||
"mtu": 1454
|
||||
}
|
||||
},
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{"name": "dummy0", "sandbox":"netns"}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "10.0.0.2/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: originalNS.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: conf,
|
||||
}
|
||||
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(len(result.Interfaces)).To(Equal(1))
|
||||
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
|
||||
Expect(len(result.IPs)).To(Equal(1))
|
||||
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
|
||||
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().MTU).To(Equal(1454))
|
||||
|
||||
err = testutils.CmdDel(originalNS.Path(),
|
||||
args.ContainerID, "", func() error { return cmdDel(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("configures and deconfigures mac address (from conf file) with ADD/DEL", func() {
|
||||
conf := []byte(`{
|
||||
"name": "test",
|
||||
@ -331,6 +453,69 @@ var _ = Describe("tuning plugin", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("configures and deconfigures mac address (from args) with ADD/DEL", func() {
|
||||
conf := []byte(`{
|
||||
"name": "test",
|
||||
"type": "iplink",
|
||||
"cniVersion": "0.3.1",
|
||||
"args": {
|
||||
"cni": {
|
||||
"mac": "c2:11:22:33:44:55"
|
||||
}
|
||||
},
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{"name": "dummy0", "sandbox":"netns"}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "10.0.0.2/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: originalNS.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: conf,
|
||||
}
|
||||
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(len(result.Interfaces)).To(Equal(1))
|
||||
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
|
||||
Expect(len(result.IPs)).To(Equal(1))
|
||||
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
|
||||
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
hw, err := net.ParseMAC("c2:11:22:33:44:55")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(hw))
|
||||
|
||||
err = testutils.CmdDel(originalNS.Path(),
|
||||
args.ContainerID, "", func() error { return cmdDel(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("configures and deconfigures mac address (from CNI_ARGS) with ADD/DEL", func() {
|
||||
conf := []byte(`{
|
||||
"name": "test",
|
||||
@ -379,6 +564,7 @@ var _ = Describe("tuning plugin", func() {
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
hw, err := net.ParseMAC("c2:11:22:33:44:66")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
fmt.Printf("%v, %v\n", link.Attrs().HardwareAddr, hw)
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(hw))
|
||||
|
||||
err = testutils.CmdDel(originalNS.Path(),
|
||||
@ -681,4 +867,67 @@ var _ = Describe("tuning plugin", func() {
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("configures and deconfigures mac address (from RuntimeConfig) with ADD/DEL", func() {
|
||||
conf := []byte(`{
|
||||
"name": "test",
|
||||
"type": "iplink",
|
||||
"cniVersion": "0.3.1",
|
||||
"capabilities": {"mac": true},
|
||||
"RuntimeConfig": {
|
||||
"mac": "c2:11:22:33:44:55"
|
||||
},
|
||||
"prevResult": {
|
||||
"interfaces": [
|
||||
{"name": "dummy0", "sandbox":"netns"}
|
||||
],
|
||||
"ips": [
|
||||
{
|
||||
"version": "4",
|
||||
"address": "10.0.0.2/24",
|
||||
"gateway": "10.0.0.1",
|
||||
"interface": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
}`)
|
||||
|
||||
args := &skel.CmdArgs{
|
||||
ContainerID: "dummy",
|
||||
Netns: originalNS.Path(),
|
||||
IfName: IFNAME,
|
||||
StdinData: conf,
|
||||
}
|
||||
|
||||
err := originalNS.Do(func(ns.NetNS) error {
|
||||
defer GinkgoRecover()
|
||||
|
||||
r, _, err := testutils.CmdAddWithArgs(args, func() error {
|
||||
return cmdAdd(args)
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
result, err := current.GetResult(r)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
Expect(len(result.Interfaces)).To(Equal(1))
|
||||
Expect(result.Interfaces[0].Name).To(Equal(IFNAME))
|
||||
Expect(len(result.IPs)).To(Equal(1))
|
||||
Expect(result.IPs[0].Address.String()).To(Equal("10.0.0.2/24"))
|
||||
|
||||
link, err := netlink.LinkByName(IFNAME)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
hw, err := net.ParseMAC("c2:11:22:33:44:55")
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(link.Attrs().HardwareAddr).To(Equal(hw))
|
||||
|
||||
err = testutils.CmdDel(originalNS.Path(),
|
||||
args.ContainerID, "", func() error { return cmdDel(args) })
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
return nil
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
})
|
||||
|
@ -16,12 +16,12 @@ rm -Rf ${SRC_DIR}/${RELEASE_DIR}
|
||||
mkdir -p ${SRC_DIR}/${RELEASE_DIR}
|
||||
mkdir -p ${OUTPUT_DIR}
|
||||
|
||||
$DOCKER run -v ${SRC_DIR}:/go/src/github.com/containernetworking/plugins --rm golang:1.10-alpine \
|
||||
$DOCKER run -ti -v ${SRC_DIR}:/go/src/github.com/containernetworking/plugins --rm golang:1.14-alpine \
|
||||
/bin/sh -xe -c "\
|
||||
apk --no-cache add bash tar;
|
||||
cd /go/src/github.com/containernetworking/plugins; umask 0022;
|
||||
|
||||
for arch in amd64 arm arm64 ppc64le s390x; do \
|
||||
for arch in amd64 arm arm64 ppc64le s390x mips64le; do \
|
||||
rm -f ${OUTPUT_DIR}/*; \
|
||||
CGO_ENABLED=0 GOARCH=\$arch ./build_linux.sh ${BUILDFLAGS}; \
|
||||
for format in tgz; do \
|
||||
|
@ -57,3 +57,7 @@ if [ -n "${vetRes}" ]; then
|
||||
echo -e "govet checking failed:\n${vetRes}"
|
||||
exit 255
|
||||
fi
|
||||
|
||||
# Run the pkg/ns tests as non root user
|
||||
mkdir /tmp/cni-rootless
|
||||
(export XDG_RUNTIME_DIR=/tmp/cni-rootless; cd pkg/ns/; unshare -rmn go test)
|
||||
|
191
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/LICENSE
generated
vendored
191
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/LICENSE
generated
vendored
@ -1,191 +0,0 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
Copyright 2014 Docker, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
22
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/NOTICE
generated
vendored
22
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/NOTICE
generated
vendored
@ -1,22 +0,0 @@
|
||||
runhcs is a fork of runc.
|
||||
|
||||
The following is runc's legal notice.
|
||||
|
||||
---
|
||||
|
||||
runc
|
||||
|
||||
Copyright 2012-2015 Docker, Inc.
|
||||
|
||||
This product includes software developed at Docker, Inc. (http://www.docker.com).
|
||||
|
||||
The following is courtesy of our legal counsel:
|
||||
|
||||
Use and transfer of Docker may be subject to certain restrictions by the
|
||||
United States and other governments.
|
||||
It is your responsibility to ensure that your use and/or transfer does not
|
||||
violate applicable laws.
|
||||
|
||||
For more information, please see http://www.bis.doc.gov
|
||||
|
||||
See also http://www.apache.org/dev/crypto.html and/or seek legal counsel.
|
848
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/container.go
generated
vendored
848
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/container.go
generated
vendored
@ -1,848 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
winio "github.com/Microsoft/go-winio"
|
||||
"github.com/Microsoft/hcsshim/internal/cni"
|
||||
"github.com/Microsoft/hcsshim/internal/guid"
|
||||
"github.com/Microsoft/hcsshim/internal/hcs"
|
||||
"github.com/Microsoft/hcsshim/internal/hcsoci"
|
||||
"github.com/Microsoft/hcsshim/internal/logfields"
|
||||
"github.com/Microsoft/hcsshim/internal/regstate"
|
||||
"github.com/Microsoft/hcsshim/internal/runhcs"
|
||||
"github.com/Microsoft/hcsshim/internal/uvm"
|
||||
"github.com/Microsoft/hcsshim/osversion"
|
||||
specs "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
var errContainerStopped = errors.New("container is stopped")
|
||||
|
||||
type persistedState struct {
|
||||
// ID is the id of this container/UVM.
|
||||
ID string `json:",omitempty"`
|
||||
// Owner is the owner value passed into the runhcs command and may be `""`.
|
||||
Owner string `json:",omitempty"`
|
||||
// SandboxID is the sandbox identifer passed in via OCI specifications. This
|
||||
// can either be the sandbox itself or the sandbox this container should run
|
||||
// in. See `parseSandboxAnnotations`.
|
||||
SandboxID string `json:",omitempty"`
|
||||
// HostID will be VM ID hosting this container. If a sandbox is used it will
|
||||
// match the `SandboxID`.
|
||||
HostID string `json:",omitempty"`
|
||||
// Bundle is the folder path on disk where the container state and spec files
|
||||
// reside.
|
||||
Bundle string `json:",omitempty"`
|
||||
Created time.Time `json:",omitempty"`
|
||||
Rootfs string `json:",omitempty"`
|
||||
// Spec is the in memory deserialized values found on `Bundle\config.json`.
|
||||
Spec *specs.Spec `json:",omitempty"`
|
||||
RequestedNetNS string `json:",omitempty"`
|
||||
// IsHost is `true` when this is a VM isolated config.
|
||||
IsHost bool `json:",omitempty"`
|
||||
// UniqueID is a unique ID generated per container config.
|
||||
UniqueID guid.GUID `json:",omitempty"`
|
||||
// HostUniqueID is the unique ID of the hosting VM if this container is
|
||||
// hosted.
|
||||
HostUniqueID guid.GUID `json:",omitempty"`
|
||||
}
|
||||
|
||||
type containerStatus string
|
||||
|
||||
const (
|
||||
containerRunning containerStatus = "running"
|
||||
containerStopped containerStatus = "stopped"
|
||||
containerCreated containerStatus = "created"
|
||||
containerPaused containerStatus = "paused"
|
||||
containerUnknown containerStatus = "unknown"
|
||||
|
||||
keyState = "state"
|
||||
keyResources = "resources"
|
||||
keyShimPid = "shim"
|
||||
keyInitPid = "pid"
|
||||
keyNetNS = "netns"
|
||||
// keyPidMapFmt is the format to use when mapping a host OS pid to a guest
|
||||
// pid.
|
||||
keyPidMapFmt = "pid-%d"
|
||||
)
|
||||
|
||||
type container struct {
|
||||
persistedState
|
||||
ShimPid int
|
||||
hc *hcs.System
|
||||
resources *hcsoci.Resources
|
||||
}
|
||||
|
||||
func startProcessShim(id, pidFile, logFile string, spec *specs.Process) (_ *os.Process, err error) {
|
||||
// Ensure the stdio handles inherit to the child process. This isn't undone
|
||||
// after the StartProcess call because the caller never launches another
|
||||
// process before exiting.
|
||||
for _, f := range []*os.File{os.Stdin, os.Stdout, os.Stderr} {
|
||||
err = windows.SetHandleInformation(windows.Handle(f.Fd()), windows.HANDLE_FLAG_INHERIT, windows.HANDLE_FLAG_INHERIT)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
args := []string{
|
||||
"--stdin", strconv.Itoa(int(os.Stdin.Fd())),
|
||||
"--stdout", strconv.Itoa(int(os.Stdout.Fd())),
|
||||
"--stderr", strconv.Itoa(int(os.Stderr.Fd())),
|
||||
}
|
||||
if spec != nil {
|
||||
args = append(args, "--exec")
|
||||
}
|
||||
if strings.HasPrefix(logFile, runhcs.SafePipePrefix) {
|
||||
args = append(args, "--log-pipe", logFile)
|
||||
}
|
||||
args = append(args, id)
|
||||
return launchShim("shim", pidFile, logFile, args, spec)
|
||||
}
|
||||
|
||||
func launchShim(cmd, pidFile, logFile string, args []string, data interface{}) (_ *os.Process, err error) {
|
||||
executable, err := os.Executable()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Create a pipe to use as stderr for the shim process. This is used to
|
||||
// retrieve early error information, up to the point that the shim is ready
|
||||
// to launch a process in the container.
|
||||
rp, wp, err := os.Pipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rp.Close()
|
||||
defer wp.Close()
|
||||
|
||||
// Create a pipe to send the data, if one is provided.
|
||||
var rdatap, wdatap *os.File
|
||||
if data != nil {
|
||||
rdatap, wdatap, err = os.Pipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rdatap.Close()
|
||||
defer wdatap.Close()
|
||||
}
|
||||
|
||||
var log *os.File
|
||||
fullargs := []string{os.Args[0]}
|
||||
if logFile != "" {
|
||||
if !strings.HasPrefix(logFile, runhcs.SafePipePrefix) {
|
||||
log, err = os.OpenFile(logFile, os.O_CREATE|os.O_WRONLY|os.O_APPEND|os.O_SYNC, 0666)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer log.Close()
|
||||
}
|
||||
|
||||
fullargs = append(fullargs, "--log-format", logFormat)
|
||||
if logrus.GetLevel() == logrus.DebugLevel {
|
||||
fullargs = append(fullargs, "--debug")
|
||||
}
|
||||
}
|
||||
fullargs = append(fullargs, cmd)
|
||||
fullargs = append(fullargs, args...)
|
||||
attr := &os.ProcAttr{
|
||||
Files: []*os.File{rdatap, wp, log},
|
||||
}
|
||||
p, err := os.StartProcess(executable, fullargs, attr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
p.Kill()
|
||||
}
|
||||
}()
|
||||
|
||||
wp.Close()
|
||||
|
||||
// Write the data if provided.
|
||||
if data != nil {
|
||||
rdatap.Close()
|
||||
dataj, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, err = wdatap.Write(dataj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
wdatap.Close()
|
||||
}
|
||||
|
||||
err = runhcs.GetErrorFromPipe(rp, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if pidFile != "" {
|
||||
if err = createPidFile(pidFile, p.Pid); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
// parseSandboxAnnotations searches `a` for various annotations used by
|
||||
// different runtimes to represent a sandbox ID, and sandbox type.
|
||||
//
|
||||
// If found returns the tuple `(sandboxID, isSandbox)` where `isSandbox == true`
|
||||
// indicates the identifer is the sandbox itself; `isSandbox == false` indicates
|
||||
// the identifer is the sandbox in which to place this container. Otherwise
|
||||
// returns `("", false)`.
|
||||
func parseSandboxAnnotations(a map[string]string) (string, bool) {
|
||||
var t, id string
|
||||
if t = a["io.kubernetes.cri.container-type"]; t != "" {
|
||||
id = a["io.kubernetes.cri.sandbox-id"]
|
||||
} else if t = a["io.kubernetes.cri-o.ContainerType"]; t != "" {
|
||||
id = a["io.kubernetes.cri-o.SandboxID"]
|
||||
} else if t = a["io.kubernetes.docker.type"]; t != "" {
|
||||
id = a["io.kubernetes.sandbox.id"]
|
||||
if t == "podsandbox" {
|
||||
t = "sandbox"
|
||||
}
|
||||
}
|
||||
if t == "container" {
|
||||
return id, false
|
||||
}
|
||||
if t == "sandbox" {
|
||||
return id, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// parseAnnotationsBool searches `a` for `key` and if found verifies that the
|
||||
// value is `true` or `false` in any case. If `key` is not found returns `def`.
|
||||
func parseAnnotationsBool(a map[string]string, key string, def bool) bool {
|
||||
if v, ok := a[key]; ok {
|
||||
switch strings.ToLower(v) {
|
||||
case "true":
|
||||
return true
|
||||
case "false":
|
||||
return false
|
||||
default:
|
||||
logrus.WithFields(logrus.Fields{
|
||||
logfields.OCIAnnotation: key,
|
||||
logfields.Value: v,
|
||||
logfields.ExpectedType: logfields.Bool,
|
||||
}).Warning("annotation could not be parsed")
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// parseAnnotationsCPU searches `s.Annotations` for the CPU annotation. If
|
||||
// not found searches `s` for the Windows CPU section. If neither are found
|
||||
// returns `def`.
|
||||
func parseAnnotationsCPU(s *specs.Spec, annotation string, def int32) int32 {
|
||||
if m := parseAnnotationsUint64(s.Annotations, annotation, 0); m != 0 {
|
||||
return int32(m)
|
||||
}
|
||||
if s.Windows != nil &&
|
||||
s.Windows.Resources != nil &&
|
||||
s.Windows.Resources.CPU != nil &&
|
||||
s.Windows.Resources.CPU.Count != nil &&
|
||||
*s.Windows.Resources.CPU.Count > 0 {
|
||||
return int32(*s.Windows.Resources.CPU.Count)
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// parseAnnotationsMemory searches `s.Annotations` for the memory annotation. If
|
||||
// not found searches `s` for the Windows memory section. If neither are found
|
||||
// returns `def`.
|
||||
func parseAnnotationsMemory(s *specs.Spec, annotation string, def int32) int32 {
|
||||
if m := parseAnnotationsUint64(s.Annotations, annotation, 0); m != 0 {
|
||||
return int32(m)
|
||||
}
|
||||
if s.Windows != nil &&
|
||||
s.Windows.Resources != nil &&
|
||||
s.Windows.Resources.Memory != nil &&
|
||||
s.Windows.Resources.Memory.Limit != nil &&
|
||||
*s.Windows.Resources.Memory.Limit > 0 {
|
||||
return int32(*s.Windows.Resources.Memory.Limit)
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// parseAnnotationsPreferredRootFSType searches `a` for `key` and verifies that the
|
||||
// value is in the set of allowed values. If `key` is not found returns `def`.
|
||||
func parseAnnotationsPreferredRootFSType(a map[string]string, key string, def uvm.PreferredRootFSType) uvm.PreferredRootFSType {
|
||||
if v, ok := a[key]; ok {
|
||||
switch v {
|
||||
case "initrd":
|
||||
return uvm.PreferredRootFSTypeInitRd
|
||||
case "vhd":
|
||||
return uvm.PreferredRootFSTypeVHD
|
||||
default:
|
||||
logrus.Warningf("annotation: '%s', with value: '%s' must be 'initrd' or 'vhd'", key, v)
|
||||
}
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// parseAnnotationsUint32 searches `a` for `key` and if found verifies that the
|
||||
// value is a 32 bit unsigned integer. If `key` is not found returns `def`.
|
||||
func parseAnnotationsUint32(a map[string]string, key string, def uint32) uint32 {
|
||||
if v, ok := a[key]; ok {
|
||||
countu, err := strconv.ParseUint(v, 10, 32)
|
||||
if err == nil {
|
||||
v := uint32(countu)
|
||||
return v
|
||||
}
|
||||
logrus.WithFields(logrus.Fields{
|
||||
logfields.OCIAnnotation: key,
|
||||
logfields.Value: v,
|
||||
logfields.ExpectedType: logfields.Uint32,
|
||||
logrus.ErrorKey: err,
|
||||
}).Warning("annotation could not be parsed")
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// parseAnnotationsUint64 searches `a` for `key` and if found verifies that the
|
||||
// value is a 64 bit unsigned integer. If `key` is not found returns `def`.
|
||||
func parseAnnotationsUint64(a map[string]string, key string, def uint64) uint64 {
|
||||
if v, ok := a[key]; ok {
|
||||
countu, err := strconv.ParseUint(v, 10, 64)
|
||||
if err == nil {
|
||||
return countu
|
||||
}
|
||||
logrus.WithFields(logrus.Fields{
|
||||
logfields.OCIAnnotation: key,
|
||||
logfields.Value: v,
|
||||
logfields.ExpectedType: logfields.Uint64,
|
||||
logrus.ErrorKey: err,
|
||||
}).Warning("annotation could not be parsed")
|
||||
}
|
||||
return def
|
||||
}
|
||||
|
||||
// startVMShim starts a vm-shim command with the specified `opts`. `opts` can be `uvm.OptionsWCOW` or `uvm.OptionsLCOW`
|
||||
func (c *container) startVMShim(logFile string, opts interface{}) (*os.Process, error) {
|
||||
var os string
|
||||
if _, ok := opts.(*uvm.OptionsLCOW); ok {
|
||||
os = "linux"
|
||||
} else {
|
||||
os = "windows"
|
||||
}
|
||||
args := []string{"--os", os}
|
||||
if strings.HasPrefix(logFile, runhcs.SafePipePrefix) {
|
||||
args = append(args, "--log-pipe", logFile)
|
||||
}
|
||||
args = append(args, c.VMPipePath())
|
||||
return launchShim("vmshim", "", logFile, args, opts)
|
||||
}
|
||||
|
||||
type containerConfig struct {
|
||||
ID string
|
||||
Owner string
|
||||
HostID string
|
||||
PidFile string
|
||||
ShimLogFile, VMLogFile string
|
||||
Spec *specs.Spec
|
||||
VMConsolePipe string
|
||||
}
|
||||
|
||||
func createContainer(cfg *containerConfig) (_ *container, err error) {
|
||||
// Store the container information in a volatile registry key.
|
||||
cwd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
vmisolated := cfg.Spec.Linux != nil || (cfg.Spec.Windows != nil && cfg.Spec.Windows.HyperV != nil)
|
||||
|
||||
sandboxID, isSandbox := parseSandboxAnnotations(cfg.Spec.Annotations)
|
||||
hostID := cfg.HostID
|
||||
if isSandbox {
|
||||
if sandboxID != cfg.ID {
|
||||
return nil, errors.New("sandbox ID must match ID")
|
||||
}
|
||||
} else if sandboxID != "" {
|
||||
// Validate that the sandbox container exists.
|
||||
sandbox, err := getContainer(sandboxID, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer sandbox.Close()
|
||||
if sandbox.SandboxID != sandboxID {
|
||||
return nil, fmt.Errorf("container %s is not a sandbox", sandboxID)
|
||||
}
|
||||
if hostID == "" {
|
||||
// Use the sandbox's host.
|
||||
hostID = sandbox.HostID
|
||||
} else if sandbox.HostID == "" {
|
||||
return nil, fmt.Errorf("sandbox container %s is not running in a VM host, but host %s was specified", sandboxID, hostID)
|
||||
} else if hostID != sandbox.HostID {
|
||||
return nil, fmt.Errorf("sandbox container %s has a different host %s from the requested host %s", sandboxID, sandbox.HostID, hostID)
|
||||
}
|
||||
if vmisolated && hostID == "" {
|
||||
return nil, fmt.Errorf("container %s is not a VM isolated sandbox", sandboxID)
|
||||
}
|
||||
}
|
||||
|
||||
uniqueID := guid.New()
|
||||
|
||||
newvm := false
|
||||
var hostUniqueID guid.GUID
|
||||
if hostID != "" {
|
||||
host, err := getContainer(hostID, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer host.Close()
|
||||
if !host.IsHost {
|
||||
return nil, fmt.Errorf("host container %s is not a VM host", hostID)
|
||||
}
|
||||
hostUniqueID = host.UniqueID
|
||||
} else if vmisolated && (isSandbox || cfg.Spec.Linux != nil || osversion.Get().Build >= osversion.RS5) {
|
||||
// This handles all LCOW, Pod Sandbox, and (Windows Xenon V2 for RS5+)
|
||||
hostID = cfg.ID
|
||||
newvm = true
|
||||
hostUniqueID = uniqueID
|
||||
}
|
||||
|
||||
// Make absolute the paths in Root.Path and Windows.LayerFolders.
|
||||
rootfs := ""
|
||||
if cfg.Spec.Root != nil {
|
||||
rootfs = cfg.Spec.Root.Path
|
||||
if rootfs != "" && !filepath.IsAbs(rootfs) && !strings.HasPrefix(rootfs, `\\?\`) {
|
||||
rootfs = filepath.Join(cwd, rootfs)
|
||||
cfg.Spec.Root.Path = rootfs
|
||||
}
|
||||
}
|
||||
|
||||
netNS := ""
|
||||
if cfg.Spec.Windows != nil {
|
||||
for i, f := range cfg.Spec.Windows.LayerFolders {
|
||||
if !filepath.IsAbs(f) && !strings.HasPrefix(rootfs, `\\?\`) {
|
||||
cfg.Spec.Windows.LayerFolders[i] = filepath.Join(cwd, f)
|
||||
}
|
||||
}
|
||||
|
||||
// Determine the network namespace to use.
|
||||
if cfg.Spec.Windows.Network != nil {
|
||||
if cfg.Spec.Windows.Network.NetworkSharedContainerName != "" {
|
||||
// RS4 case
|
||||
err = stateKey.Get(cfg.Spec.Windows.Network.NetworkSharedContainerName, keyNetNS, &netNS)
|
||||
if err != nil {
|
||||
if _, ok := err.(*regstate.NoStateError); !ok {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
} else if cfg.Spec.Windows.Network.NetworkNamespace != "" {
|
||||
// RS5 case
|
||||
netNS = cfg.Spec.Windows.Network.NetworkNamespace
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Store the initial container state in the registry so that the delete
|
||||
// command can clean everything up if something goes wrong.
|
||||
c := &container{
|
||||
persistedState: persistedState{
|
||||
ID: cfg.ID,
|
||||
Owner: cfg.Owner,
|
||||
Bundle: cwd,
|
||||
Rootfs: rootfs,
|
||||
Created: time.Now(),
|
||||
Spec: cfg.Spec,
|
||||
SandboxID: sandboxID,
|
||||
HostID: hostID,
|
||||
IsHost: newvm,
|
||||
RequestedNetNS: netNS,
|
||||
UniqueID: uniqueID,
|
||||
HostUniqueID: hostUniqueID,
|
||||
},
|
||||
}
|
||||
err = stateKey.Create(cfg.ID, keyState, &c.persistedState)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
c.Remove()
|
||||
}
|
||||
}()
|
||||
if isSandbox && vmisolated {
|
||||
cnicfg := cni.NewPersistedNamespaceConfig(netNS, cfg.ID, hostUniqueID)
|
||||
err = cnicfg.Store()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
cnicfg.Remove()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Start a VM if necessary.
|
||||
if newvm {
|
||||
var opts interface{}
|
||||
|
||||
const (
|
||||
annotationAllowOvercommit = "io.microsoft.virtualmachine.computetopology.memory.allowovercommit"
|
||||
annotationEnableDeferredCommit = "io.microsoft.virtualmachine.computetopology.memory.enabledeferredcommit"
|
||||
annotationMemorySizeInMB = "io.microsoft.virtualmachine.computetopology.memory.sizeinmb"
|
||||
annotationProcessorCount = "io.microsoft.virtualmachine.computetopology.processor.count"
|
||||
annotationVPMemCount = "io.microsoft.virtualmachine.devices.virtualpmem.maximumcount"
|
||||
annotationVPMemSize = "io.microsoft.virtualmachine.devices.virtualpmem.maximumsizebytes"
|
||||
annotationPreferredRootFSType = "io.microsoft.virtualmachine.lcow.preferredrootfstype"
|
||||
)
|
||||
|
||||
if cfg.Spec.Linux != nil {
|
||||
lopts := uvm.NewDefaultOptionsLCOW(vmID(c.ID), cfg.Owner)
|
||||
lopts.MemorySizeInMB = parseAnnotationsMemory(cfg.Spec, annotationMemorySizeInMB, lopts.MemorySizeInMB)
|
||||
lopts.AllowOvercommit = parseAnnotationsBool(cfg.Spec.Annotations, annotationAllowOvercommit, lopts.AllowOvercommit)
|
||||
lopts.EnableDeferredCommit = parseAnnotationsBool(cfg.Spec.Annotations, annotationEnableDeferredCommit, lopts.EnableDeferredCommit)
|
||||
lopts.ProcessorCount = parseAnnotationsCPU(cfg.Spec, annotationProcessorCount, lopts.ProcessorCount)
|
||||
lopts.ConsolePipe = cfg.VMConsolePipe
|
||||
lopts.VPMemDeviceCount = parseAnnotationsUint32(cfg.Spec.Annotations, annotationVPMemCount, lopts.VPMemDeviceCount)
|
||||
lopts.VPMemSizeBytes = parseAnnotationsUint64(cfg.Spec.Annotations, annotationVPMemSize, lopts.VPMemSizeBytes)
|
||||
lopts.PreferredRootFSType = parseAnnotationsPreferredRootFSType(cfg.Spec.Annotations, annotationPreferredRootFSType, lopts.PreferredRootFSType)
|
||||
switch lopts.PreferredRootFSType {
|
||||
case uvm.PreferredRootFSTypeInitRd:
|
||||
lopts.RootFSFile = uvm.InitrdFile
|
||||
case uvm.PreferredRootFSTypeVHD:
|
||||
lopts.RootFSFile = uvm.VhdFile
|
||||
}
|
||||
opts = lopts
|
||||
} else {
|
||||
wopts := uvm.NewDefaultOptionsWCOW(vmID(c.ID), cfg.Owner)
|
||||
wopts.MemorySizeInMB = parseAnnotationsMemory(cfg.Spec, annotationMemorySizeInMB, wopts.MemorySizeInMB)
|
||||
wopts.AllowOvercommit = parseAnnotationsBool(cfg.Spec.Annotations, annotationAllowOvercommit, wopts.AllowOvercommit)
|
||||
wopts.EnableDeferredCommit = parseAnnotationsBool(cfg.Spec.Annotations, annotationEnableDeferredCommit, wopts.EnableDeferredCommit)
|
||||
wopts.ProcessorCount = parseAnnotationsCPU(cfg.Spec, annotationProcessorCount, wopts.ProcessorCount)
|
||||
|
||||
// In order for the UVM sandbox.vhdx not to collide with the actual
|
||||
// nested Argon sandbox.vhdx we append the \vm folder to the last entry
|
||||
// in the list.
|
||||
layersLen := len(cfg.Spec.Windows.LayerFolders)
|
||||
layers := make([]string, layersLen)
|
||||
copy(layers, cfg.Spec.Windows.LayerFolders)
|
||||
|
||||
vmPath := filepath.Join(layers[layersLen-1], "vm")
|
||||
err := os.MkdirAll(vmPath, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
layers[layersLen-1] = vmPath
|
||||
|
||||
wopts.LayerFolders = layers
|
||||
opts = wopts
|
||||
}
|
||||
|
||||
shim, err := c.startVMShim(cfg.VMLogFile, opts)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
shim.Release()
|
||||
}
|
||||
|
||||
if c.HostID != "" {
|
||||
// Call to the VM shim process to create the container. This is done so
|
||||
// that the VM process can keep track of the VM's virtual hardware
|
||||
// resource use.
|
||||
err = c.issueVMRequest(runhcs.OpCreateContainer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
c.hc, err = hcs.OpenComputeSystem(cfg.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
// Create the container directly from this process.
|
||||
err = createContainerInHost(c, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
// Create the shim process for the container.
|
||||
err = startContainerShim(c, cfg.PidFile, cfg.ShimLogFile)
|
||||
if err != nil {
|
||||
if e := c.Kill(); e == nil {
|
||||
c.Remove()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func (c *container) ShimPipePath() string {
|
||||
return runhcs.SafePipePath("runhcs-shim-" + c.UniqueID.String())
|
||||
}
|
||||
|
||||
func (c *container) VMPipePath() string {
|
||||
return runhcs.VMPipePath(c.HostUniqueID)
|
||||
}
|
||||
|
||||
func (c *container) VMIsolated() bool {
|
||||
return c.HostID != ""
|
||||
}
|
||||
|
||||
func (c *container) unmountInHost(vm *uvm.UtilityVM, all bool) error {
|
||||
resources := &hcsoci.Resources{}
|
||||
err := stateKey.Get(c.ID, keyResources, resources)
|
||||
if _, ok := err.(*regstate.NoStateError); ok {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = hcsoci.ReleaseResources(resources, vm, all)
|
||||
if err != nil {
|
||||
stateKey.Set(c.ID, keyResources, resources)
|
||||
return err
|
||||
}
|
||||
|
||||
err = stateKey.Clear(c.ID, keyResources)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *container) Unmount(all bool) error {
|
||||
if c.VMIsolated() {
|
||||
op := runhcs.OpUnmountContainerDiskOnly
|
||||
if all {
|
||||
op = runhcs.OpUnmountContainer
|
||||
}
|
||||
err := c.issueVMRequest(op)
|
||||
if err != nil {
|
||||
if _, ok := err.(*noVMError); ok {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
logfields.ContainerID: c.ID,
|
||||
logfields.UVMID: c.HostID,
|
||||
logrus.ErrorKey: errors.New("failed to unmount container resources"),
|
||||
}).Warning("VM shim could not be contacted")
|
||||
} else {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else {
|
||||
c.unmountInHost(nil, false)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func createContainerInHost(c *container, vm *uvm.UtilityVM) (err error) {
|
||||
if c.hc != nil {
|
||||
return errors.New("container already created")
|
||||
}
|
||||
|
||||
// Create the container without starting it.
|
||||
opts := &hcsoci.CreateOptions{
|
||||
ID: c.ID,
|
||||
Owner: c.Owner,
|
||||
Spec: c.Spec,
|
||||
HostingSystem: vm,
|
||||
NetworkNamespace: c.RequestedNetNS,
|
||||
}
|
||||
vmid := ""
|
||||
if vm != nil {
|
||||
vmid = vm.ID()
|
||||
}
|
||||
logrus.WithFields(logrus.Fields{
|
||||
logfields.ContainerID: c.ID,
|
||||
logfields.UVMID: vmid,
|
||||
}).Info("creating container in UVM")
|
||||
hc, resources, err := hcsoci.CreateContainer(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err != nil {
|
||||
hc.Terminate()
|
||||
hc.Wait()
|
||||
hcsoci.ReleaseResources(resources, vm, true)
|
||||
}
|
||||
}()
|
||||
|
||||
// Record the network namespace to support namespace sharing by container ID.
|
||||
if resources.NetNS() != "" {
|
||||
err = stateKey.Set(c.ID, keyNetNS, resources.NetNS())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = stateKey.Set(c.ID, keyResources, resources)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.hc = hc
|
||||
return nil
|
||||
}
|
||||
|
||||
func startContainerShim(c *container, pidFile, logFile string) error {
|
||||
// Launch a shim process to later execute a process in the container.
|
||||
shim, err := startProcessShim(c.ID, pidFile, logFile, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer shim.Release()
|
||||
defer func() {
|
||||
if err != nil {
|
||||
shim.Kill()
|
||||
}
|
||||
}()
|
||||
|
||||
c.ShimPid = shim.Pid
|
||||
err = stateKey.Set(c.ID, keyShimPid, shim.Pid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if pidFile != "" {
|
||||
if err = createPidFile(pidFile, shim.Pid); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *container) Close() error {
|
||||
if c.hc == nil {
|
||||
return nil
|
||||
}
|
||||
return c.hc.Close()
|
||||
}
|
||||
|
||||
func (c *container) Exec() error {
|
||||
err := c.hc.Start()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if c.Spec.Process == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Alert the shim that the container is ready.
|
||||
pipe, err := winio.DialPipe(c.ShimPipePath(), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer pipe.Close()
|
||||
|
||||
shim, err := os.FindProcess(c.ShimPid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer shim.Release()
|
||||
|
||||
err = runhcs.GetErrorFromPipe(pipe, shim)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func getContainer(id string, notStopped bool) (*container, error) {
|
||||
var c container
|
||||
err := stateKey.Get(id, keyState, &c.persistedState)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = stateKey.Get(id, keyShimPid, &c.ShimPid)
|
||||
if err != nil {
|
||||
if _, ok := err.(*regstate.NoStateError); !ok {
|
||||
return nil, err
|
||||
}
|
||||
c.ShimPid = -1
|
||||
}
|
||||
if notStopped && c.ShimPid == 0 {
|
||||
return nil, errContainerStopped
|
||||
}
|
||||
|
||||
hc, err := hcs.OpenComputeSystem(c.ID)
|
||||
if err == nil {
|
||||
c.hc = hc
|
||||
} else if !hcs.IsNotExist(err) {
|
||||
return nil, err
|
||||
} else if notStopped {
|
||||
return nil, errContainerStopped
|
||||
}
|
||||
|
||||
return &c, nil
|
||||
}
|
||||
|
||||
func (c *container) Remove() error {
|
||||
// Unmount any layers or mapped volumes.
|
||||
err := c.Unmount(!c.IsHost)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Follow kata's example and delay tearing down the VM until the owning
|
||||
// container is removed.
|
||||
if c.IsHost {
|
||||
vm, err := hcs.OpenComputeSystem(vmID(c.ID))
|
||||
if err == nil {
|
||||
if err := vm.Terminate(); hcs.IsPending(err) {
|
||||
vm.Wait()
|
||||
}
|
||||
}
|
||||
}
|
||||
return stateKey.Remove(c.ID)
|
||||
}
|
||||
|
||||
func (c *container) Kill() error {
|
||||
if c.hc == nil {
|
||||
return nil
|
||||
}
|
||||
err := c.hc.Terminate()
|
||||
if hcs.IsPending(err) {
|
||||
err = c.hc.Wait()
|
||||
}
|
||||
if hcs.IsAlreadyStopped(err) {
|
||||
err = nil
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *container) Status() (containerStatus, error) {
|
||||
if c.hc == nil || c.ShimPid == 0 {
|
||||
return containerStopped, nil
|
||||
}
|
||||
props, err := c.hc.Properties()
|
||||
if err != nil {
|
||||
if !strings.Contains(err.Error(), "operation is not valid in the current state") {
|
||||
return "", err
|
||||
}
|
||||
return containerUnknown, nil
|
||||
}
|
||||
state := containerUnknown
|
||||
switch props.State {
|
||||
case "", "Created":
|
||||
state = containerCreated
|
||||
case "Running":
|
||||
state = containerRunning
|
||||
case "Paused":
|
||||
state = containerPaused
|
||||
case "Stopped":
|
||||
state = containerStopped
|
||||
}
|
||||
return state, nil
|
||||
}
|
71
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/create-scratch.go
generated
vendored
71
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/create-scratch.go
generated
vendored
@ -1,71 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/appargs"
|
||||
"github.com/Microsoft/hcsshim/internal/lcow"
|
||||
"github.com/Microsoft/hcsshim/internal/uvm"
|
||||
"github.com/Microsoft/hcsshim/osversion"
|
||||
gcsclient "github.com/Microsoft/opengcs/client"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var createScratchCommand = cli.Command{
|
||||
Name: "create-scratch",
|
||||
Usage: "creates a scratch vhdx at 'destpath' that is ext4 formatted",
|
||||
Description: "Creates a scratch vhdx at 'destpath' that is ext4 formatted",
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "destpath",
|
||||
Usage: "Required: describes the destination vhd path",
|
||||
},
|
||||
},
|
||||
Before: appargs.Validate(),
|
||||
Action: func(context *cli.Context) error {
|
||||
dest := context.String("destpath")
|
||||
if dest == "" {
|
||||
return errors.New("'destpath' is required")
|
||||
}
|
||||
|
||||
// If we only have v1 lcow support do it the old way.
|
||||
if osversion.Get().Build < osversion.RS5 {
|
||||
cfg := gcsclient.Config{
|
||||
Options: gcsclient.Options{
|
||||
KirdPath: filepath.Join(os.Getenv("ProgramFiles"), "Linux Containers"),
|
||||
KernelFile: "kernel",
|
||||
InitrdFile: uvm.InitrdFile,
|
||||
},
|
||||
Name: "createscratch-uvm",
|
||||
UvmTimeoutSeconds: 5 * 60, // 5 Min
|
||||
}
|
||||
|
||||
if err := cfg.StartUtilityVM(); err != nil {
|
||||
return errors.Wrapf(err, "failed to start '%s'", cfg.Name)
|
||||
}
|
||||
defer cfg.Uvm.Terminate()
|
||||
|
||||
if err := cfg.CreateExt4Vhdx(dest, lcow.DefaultScratchSizeGB, ""); err != nil {
|
||||
return errors.Wrapf(err, "failed to create ext4vhdx for '%s'", cfg.Name)
|
||||
}
|
||||
} else {
|
||||
opts := uvm.NewDefaultOptionsLCOW("createscratch-uvm", context.GlobalString("owner"))
|
||||
convertUVM, err := uvm.CreateLCOW(opts)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to create '%s'", opts.ID)
|
||||
}
|
||||
defer convertUVM.Close()
|
||||
if err := convertUVM.Start(); err != nil {
|
||||
return errors.Wrapf(err, "failed to start '%s'", opts.ID)
|
||||
}
|
||||
|
||||
if err := lcow.CreateScratch(convertUVM, dest, lcow.DefaultScratchSizeGB, "", ""); err != nil {
|
||||
return errors.Wrapf(err, "failed to create ext4vhdx for '%s'", opts.ID)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
},
|
||||
}
|
100
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/create.go
generated
vendored
100
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/create.go
generated
vendored
@ -1,100 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/Microsoft/hcsshim/internal/appargs"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var createRunFlags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "bundle, b",
|
||||
Value: "",
|
||||
Usage: `path to the root of the bundle directory, defaults to the current directory`,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "pid-file",
|
||||
Value: "",
|
||||
Usage: "specify the file to write the process id to",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "shim-log",
|
||||
Value: "",
|
||||
Usage: `path to the log file or named pipe (e.g. \\.\pipe\ProtectedPrefix\Administrators\runhcs-<container-id>-shim-log) for the launched shim process`,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "vm-log",
|
||||
Value: "",
|
||||
Usage: `path to the log file or named pipe (e.g. \\.\pipe\ProtectedPrefix\Administrators\runhcs-<container-id>-vm-log) for the launched VM shim process`,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "vm-console",
|
||||
Value: "",
|
||||
Usage: `path to the pipe for the VM's console (e.g. \\.\pipe\debugpipe)`,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "host",
|
||||
Value: "",
|
||||
Usage: "host container whose VM this container should run in",
|
||||
},
|
||||
}
|
||||
|
||||
var createCommand = cli.Command{
|
||||
Name: "create",
|
||||
Usage: "create a container",
|
||||
ArgsUsage: `<container-id>
|
||||
|
||||
Where "<container-id>" is your name for the instance of the container that you
|
||||
are starting. The name you provide for the container instance must be unique on
|
||||
your host.`,
|
||||
Description: `The create command creates an instance of a container for a bundle. The bundle
|
||||
is a directory with a specification file named "` + specConfig + `" and a root
|
||||
filesystem.
|
||||
|
||||
The specification file includes an args parameter. The args parameter is used
|
||||
to specify command(s) that get run when the container is started. To change the
|
||||
command(s) that get executed on start, edit the args parameter of the spec. See
|
||||
"runc spec --help" for more explanation.`,
|
||||
Flags: append(createRunFlags),
|
||||
Before: appargs.Validate(argID),
|
||||
Action: func(context *cli.Context) error {
|
||||
cfg, err := containerConfigFromContext(context)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = createContainer(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}
|
||||
|
||||
func containerConfigFromContext(context *cli.Context) (*containerConfig, error) {
|
||||
id := context.Args().First()
|
||||
pidFile, err := absPathOrEmpty(context.String("pid-file"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
shimLog, err := absPathOrEmpty(context.String("shim-log"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vmLog, err := absPathOrEmpty(context.String("vm-log"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spec, err := setupSpec(context)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &containerConfig{
|
||||
ID: id,
|
||||
Owner: context.GlobalString("owner"),
|
||||
PidFile: pidFile,
|
||||
ShimLogFile: shimLog,
|
||||
VMLogFile: vmLog,
|
||||
VMConsolePipe: context.String("vm-console"),
|
||||
Spec: spec,
|
||||
HostID: context.String("host"),
|
||||
}, nil
|
||||
}
|
73
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/delete.go
generated
vendored
73
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/delete.go
generated
vendored
@ -1,73 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/appargs"
|
||||
"github.com/Microsoft/hcsshim/internal/regstate"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var deleteCommand = cli.Command{
|
||||
Name: "delete",
|
||||
Usage: "delete any resources held by the container often used with detached container",
|
||||
ArgsUsage: `<container-id>
|
||||
|
||||
Where "<container-id>" is the name for the instance of the container.
|
||||
|
||||
EXAMPLE:
|
||||
For example, if the container id is "ubuntu01" and runhcs list currently shows the
|
||||
status of "ubuntu01" as "stopped" the following will delete resources held for
|
||||
"ubuntu01" removing "ubuntu01" from the runhcs list of containers:
|
||||
|
||||
# runhcs delete ubuntu01`,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "force, f",
|
||||
Usage: "Forcibly deletes the container if it is still running (uses SIGKILL)",
|
||||
},
|
||||
},
|
||||
Before: appargs.Validate(argID),
|
||||
Action: func(context *cli.Context) error {
|
||||
id := context.Args().First()
|
||||
force := context.Bool("force")
|
||||
container, err := getContainer(id, false)
|
||||
if err != nil {
|
||||
if _, ok := err.(*regstate.NoStateError); ok {
|
||||
if e := stateKey.Remove(id); e != nil {
|
||||
fmt.Fprintf(os.Stderr, "remove %s: %v\n", id, e)
|
||||
}
|
||||
if force {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
defer container.Close()
|
||||
s, err := container.Status()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kill := false
|
||||
switch s {
|
||||
case containerStopped:
|
||||
case containerCreated:
|
||||
kill = true
|
||||
default:
|
||||
if !force {
|
||||
return fmt.Errorf("cannot delete container %s that is not stopped: %s\n", id, s)
|
||||
}
|
||||
kill = true
|
||||
}
|
||||
|
||||
if kill {
|
||||
err = container.Kill()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return container.Remove()
|
||||
},
|
||||
}
|
160
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/exec.go
generated
vendored
160
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/exec.go
generated
vendored
@ -1,160 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/appargs"
|
||||
"github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var execCommand = cli.Command{
|
||||
Name: "exec",
|
||||
Usage: "execute new process inside the container",
|
||||
ArgsUsage: `<container-id> <command> [command options] || -p process.json <container-id>
|
||||
|
||||
Where "<container-id>" is the name for the instance of the container and
|
||||
"<command>" is the command to be executed in the container.
|
||||
"<command>" can't be empty unless a "-p" flag provided.
|
||||
|
||||
EXAMPLE:
|
||||
For example, if the container is configured to run the linux ps command the
|
||||
following will output a list of processes running in the container:
|
||||
|
||||
# runhcs exec <container-id> ps`,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "cwd",
|
||||
Usage: "current working directory in the container",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "env, e",
|
||||
Usage: "set environment variables",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "tty, t",
|
||||
Usage: "allocate a pseudo-TTY",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "user, u",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "process, p",
|
||||
Usage: "path to the process.json",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "detach,d",
|
||||
Usage: "detach from the container's process",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "pid-file",
|
||||
Value: "",
|
||||
Usage: "specify the file to write the process id to",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "shim-log",
|
||||
Value: "",
|
||||
Usage: `path to the log file or named pipe (e.g. \\.\pipe\ProtectedPrefix\Administrators\runhcs-<container-id>-<exec-id>-log) for the launched shim process`,
|
||||
},
|
||||
},
|
||||
Before: appargs.Validate(argID, appargs.Rest(appargs.String)),
|
||||
Action: func(context *cli.Context) error {
|
||||
id := context.Args().First()
|
||||
pidFile, err := absPathOrEmpty(context.String("pid-file"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
shimLog, err := absPathOrEmpty(context.String("shim-log"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c, err := getContainer(id, false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.Close()
|
||||
status, err := c.Status()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if status != containerRunning {
|
||||
return errContainerStopped
|
||||
}
|
||||
spec, err := getProcessSpec(context, c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p, err := startProcessShim(id, pidFile, shimLog, spec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !context.Bool("detach") {
|
||||
state, err := p.Wait()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
os.Exit(int(state.Sys().(syscall.WaitStatus).ExitCode))
|
||||
}
|
||||
return nil
|
||||
},
|
||||
SkipArgReorder: true,
|
||||
}
|
||||
|
||||
func getProcessSpec(context *cli.Context, c *container) (*specs.Process, error) {
|
||||
if path := context.String("process"); path != "" {
|
||||
f, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
var p specs.Process
|
||||
if err := json.NewDecoder(f).Decode(&p); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &p, validateProcessSpec(&p)
|
||||
}
|
||||
|
||||
// process via cli flags
|
||||
p := c.Spec.Process
|
||||
|
||||
if len(context.Args()) == 1 {
|
||||
return nil, fmt.Errorf("process args cannot be empty")
|
||||
}
|
||||
p.Args = context.Args()[1:]
|
||||
// override the cwd, if passed
|
||||
if context.String("cwd") != "" {
|
||||
p.Cwd = context.String("cwd")
|
||||
}
|
||||
// append the passed env variables
|
||||
p.Env = append(p.Env, context.StringSlice("env")...)
|
||||
|
||||
// set the tty
|
||||
if context.IsSet("tty") {
|
||||
p.Terminal = context.Bool("tty")
|
||||
}
|
||||
// override the user, if passed
|
||||
if context.String("user") != "" {
|
||||
p.User.Username = context.String("user")
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func validateProcessSpec(spec *specs.Process) error {
|
||||
if spec.Cwd == "" {
|
||||
return fmt.Errorf("Cwd property must not be empty")
|
||||
}
|
||||
// IsAbs doesnt recognize Unix paths on Windows builds so handle that case
|
||||
// here.
|
||||
if !filepath.IsAbs(spec.Cwd) && !strings.HasPrefix(spec.Cwd, "/") {
|
||||
return fmt.Errorf("Cwd must be an absolute path")
|
||||
}
|
||||
if len(spec.Args) == 0 {
|
||||
return fmt.Errorf("args must not be empty")
|
||||
}
|
||||
return nil
|
||||
}
|
178
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/kill.go
generated
vendored
178
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/kill.go
generated
vendored
@ -1,178 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/Microsoft/hcsshim/internal/appargs"
|
||||
"github.com/Microsoft/hcsshim/internal/guestrequest"
|
||||
"github.com/Microsoft/hcsshim/internal/hcs"
|
||||
"github.com/Microsoft/hcsshim/internal/schema1"
|
||||
"github.com/Microsoft/hcsshim/osversion"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
var killCommand = cli.Command{
|
||||
Name: "kill",
|
||||
Usage: "kill sends the specified signal (default: SIGTERM) to the container's init process",
|
||||
ArgsUsage: `<container-id> [signal]
|
||||
|
||||
Where "<container-id>" is the name for the instance of the container and
|
||||
"[signal]" is the signal to be sent to the init process.
|
||||
|
||||
EXAMPLE:
|
||||
For example, if the container id is "ubuntu01" the following will send a "KILL"
|
||||
signal to the init process of the "ubuntu01" container:
|
||||
|
||||
# runhcs kill ubuntu01 KILL`,
|
||||
Flags: []cli.Flag{},
|
||||
Before: appargs.Validate(argID, appargs.Optional(appargs.String)),
|
||||
Action: func(context *cli.Context) error {
|
||||
id := context.Args().First()
|
||||
c, err := getContainer(id, true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer c.Close()
|
||||
status, err := c.Status()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if status != containerRunning {
|
||||
return errContainerStopped
|
||||
}
|
||||
|
||||
signalsSupported := false
|
||||
|
||||
// The Signal feature was added in RS5
|
||||
if osversion.Get().Build >= osversion.RS5 {
|
||||
if c.IsHost || c.HostID != "" {
|
||||
var hostID string
|
||||
if c.IsHost {
|
||||
// This is the LCOW, Pod Sandbox, or Windows Xenon V2 for RS5+
|
||||
hostID = vmID(c.ID)
|
||||
} else {
|
||||
// This is the Nth container in a Pod
|
||||
hostID = c.HostID
|
||||
}
|
||||
uvm, err := hcs.OpenComputeSystem(hostID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer uvm.Close()
|
||||
if props, err := uvm.Properties(schema1.PropertyTypeGuestConnection); err == nil &&
|
||||
props.GuestConnectionInfo.GuestDefinedCapabilities.SignalProcessSupported {
|
||||
signalsSupported = true
|
||||
}
|
||||
} else if c.Spec.Linux == nil && c.Spec.Windows.HyperV == nil {
|
||||
// RS5+ Windows Argon
|
||||
signalsSupported = true
|
||||
}
|
||||
}
|
||||
|
||||
signal := 0
|
||||
if signalsSupported {
|
||||
signal, err = validateSigstr(context.Args().Get(1), signalsSupported, c.Spec.Linux != nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var pid int
|
||||
if err := stateKey.Get(id, keyInitPid, &pid); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
p, err := c.hc.OpenProcess(pid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer p.Close()
|
||||
|
||||
if signalsSupported && (c.Spec.Linux != nil || !c.Spec.Process.Terminal) {
|
||||
opts := guestrequest.SignalProcessOptions{
|
||||
Signal: signal,
|
||||
}
|
||||
return p.Signal(opts)
|
||||
}
|
||||
|
||||
// Legacy signal issue a kill
|
||||
return p.Kill()
|
||||
},
|
||||
}
|
||||
|
||||
func validateSigstr(sigstr string, signalsSupported bool, isLcow bool) (int, error) {
|
||||
errInvalidSignal := errors.Errorf("invalid signal '%s'", sigstr)
|
||||
|
||||
// All flavors including legacy default to SIGTERM on LCOW CtrlC on Windows
|
||||
if sigstr == "" {
|
||||
if isLcow {
|
||||
return 0xf, nil
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
sigstr = strings.ToUpper(sigstr)
|
||||
|
||||
if !signalsSupported {
|
||||
// If signals arent supported we just validate that its a known signal.
|
||||
// We already return 0 since we only supported a platform Kill() at that
|
||||
// time.
|
||||
if isLcow {
|
||||
switch sigstr {
|
||||
case "15":
|
||||
fallthrough
|
||||
case "TERM":
|
||||
fallthrough
|
||||
case "SIGTERM":
|
||||
return 0, nil
|
||||
default:
|
||||
return 0, errInvalidSignal
|
||||
}
|
||||
}
|
||||
switch sigstr {
|
||||
// Docker sends a UNIX term in the supported Windows Signal map.
|
||||
case "15":
|
||||
fallthrough
|
||||
case "TERM":
|
||||
fallthrough
|
||||
case "0":
|
||||
fallthrough
|
||||
case "CTRLC":
|
||||
return 0, nil
|
||||
case "9":
|
||||
fallthrough
|
||||
case "KILL":
|
||||
return 0, nil
|
||||
default:
|
||||
return 0, errInvalidSignal
|
||||
}
|
||||
}
|
||||
|
||||
var sigmap map[string]int
|
||||
if isLcow {
|
||||
sigmap = signalMapLcow
|
||||
} else {
|
||||
sigmap = signalMapWindows
|
||||
}
|
||||
|
||||
signal, err := strconv.Atoi(sigstr)
|
||||
if err != nil {
|
||||
// Signal might still match the string value
|
||||
for k, v := range sigmap {
|
||||
if k == sigstr {
|
||||
return v, nil
|
||||
}
|
||||
}
|
||||
return 0, errInvalidSignal
|
||||
}
|
||||
|
||||
// Match signal by value
|
||||
for _, v := range sigmap {
|
||||
if signal == v {
|
||||
return signal, nil
|
||||
}
|
||||
}
|
||||
return 0, errInvalidSignal
|
||||
}
|
95
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/kill_test.go
generated
vendored
95
vendor/github.com/Microsoft/hcsshim/cmd/runhcs/kill_test.go
generated
vendored
@ -1,95 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func runValidateSigstrTest(sigstr string, signalsSupported, isLcow bool,
|
||||
expectedSignal int, expectedError bool, t *testing.T) {
|
||||
signal, err := validateSigstr(sigstr, signalsSupported, isLcow)
|
||||
if expectedError {
|
||||
if err == nil {
|
||||
t.Fatalf("Expected err: %v, got: nil", expectedError)
|
||||
} else if err.Error() != fmt.Sprintf("invalid signal '%s'", sigstr) {
|
||||
t.Fatalf("Expected err: %v, got: %v", expectedError, err)
|
||||
}
|
||||
}
|
||||
if signal != expectedSignal {
|
||||
t.Fatalf("Test - Signal: %s, Support: %v, LCOW: %v\nExpected signal: %v, got: %v",
|
||||
sigstr, signalsSupported, isLcow,
|
||||
expectedSignal, signal)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateSigstrEmpty(t *testing.T) {
|
||||
runValidateSigstrTest("", false, false, 0, false, t)
|
||||
runValidateSigstrTest("", false, true, 0xf, false, t)
|
||||
runValidateSigstrTest("", true, false, 0, false, t)
|
||||
runValidateSigstrTest("", true, true, 0xf, false, t)
|
||||
}
|
||||
|
||||
func TestValidateSigstrDefaultLCOW(t *testing.T) {
|
||||
runValidateSigstrTest("15", false, true, 0, false, t)
|
||||
runValidateSigstrTest("TERM", false, true, 0, false, t)
|
||||
runValidateSigstrTest("SIGTERM", false, true, 0, false, t)
|
||||
}
|
||||
|
||||
func TestValidateSigstrDefaultLCOWInvalid(t *testing.T) {
|
||||
runValidateSigstrTest("2", false, true, 0, true, t)
|
||||
runValidateSigstrTest("test", false, true, 0, true, t)
|
||||
}
|
||||
|
||||
func TestValidateSigstrDefaultWCOW(t *testing.T) {
|
||||
runValidateSigstrTest("15", false, false, 0, false, t)
|
||||
runValidateSigstrTest("TERM", false, false, 0, false, t)
|
||||
runValidateSigstrTest("0", false, false, 0, false, t)
|
||||
runValidateSigstrTest("CTRLC", false, false, 0, false, t)
|
||||
runValidateSigstrTest("9", false, false, 0, false, t)
|
||||
runValidateSigstrTest("KILL", false, false, 0, false, t)
|
||||
}
|
||||
|
||||
func TestValidateSigstrDefaultWCOWInvalid(t *testing.T) {
|
||||
runValidateSigstrTest("2", false, false, 0, true, t)
|
||||
runValidateSigstrTest("test", false, false, 0, true, t)
|
||||
}
|
||||
|
||||
func TestValidateSignalStringLCOW(t *testing.T) {
|
||||
for k, v := range signalMapLcow {
|
||||
runValidateSigstrTest(k, true, true, v, false, t)
|
||||
// run it again with a case not in the map
|
||||
lc := strings.ToLower(k)
|
||||
if k == lc {
|
||||
t.Fatalf("Expected lower casing - map: %v, got: %v", k, lc)
|
||||
}
|
||||
runValidateSigstrTest(lc, true, true, v, false, t)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateSignalStringWCOW(t *testing.T) {
|
||||
for k, v := range signalMapWindows {
|
||||
runValidateSigstrTest(k, true, false, v, false, t)
|
||||
// run it again with a case not in the map
|
||||
lc := strings.ToLower(k)
|
||||
if k == lc {
|
||||
t.Fatalf("Expected lower casing - map: %v, got: %v", k, lc)
|
||||
}
|
||||
runValidateSigstrTest(lc, true, false, v, false, t)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateSignalValueLCOW(t *testing.T) {
|
||||
for _, v := range signalMapLcow {
|
||||
str := strconv.Itoa(v)
|
||||
runValidateSigstrTest(str, true, true, v, false, t)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateSignalValueWCOW(t *testing.T) {
|
||||
for _, v := range signalMapWindows {
|
||||
str := strconv.Itoa(v)
|
||||
runValidateSigstrTest(str, true, false, v, false, t)
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user