diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 0108d70e..6254ad02 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -26,9 +26,14 @@ are very busy and read the mailing lists. ## Getting Started - Fork the repository on GitHub -- Read the [README](README.md) for build and test instructions - Play with the project, submit bugs, submit pull requests! + +## Building + +Each plugin is compiled simply with `go build`. A script, `build.sh`, +is supplied which will build all the plugins in the repo. + ## Contribution workflow This is a rough outline of how to prepare a contribution: diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index bac5e108..29b99d79 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -12,38 +12,38 @@ }, { "ImportPath": "github.com/containernetworking/cni/libcni", - "Comment": "v0.6.0-rc1", - "Rev": "a2da8f8d7fd8e6dc25f336408a8ac86f050fbd88" + "Comment": "v0.7.0-alpha1", + "Rev": "07c1a6da47b7fbf8b357f4949ecce2113e598491" }, { "ImportPath": "github.com/containernetworking/cni/pkg/invoke", - "Comment": "v0.6.0-rc1", - "Rev": "a2da8f8d7fd8e6dc25f336408a8ac86f050fbd88" + "Comment": "v0.7.0-alpha1", + "Rev": "07c1a6da47b7fbf8b357f4949ecce2113e598491" }, { "ImportPath": "github.com/containernetworking/cni/pkg/skel", - "Comment": "v0.6.0-rc1", - "Rev": "a2da8f8d7fd8e6dc25f336408a8ac86f050fbd88" + "Comment": "v0.7.0-alpha1", + "Rev": "07c1a6da47b7fbf8b357f4949ecce2113e598491" }, { "ImportPath": "github.com/containernetworking/cni/pkg/types", - "Comment": "v0.6.0-rc1", - "Rev": "a2da8f8d7fd8e6dc25f336408a8ac86f050fbd88" + "Comment": "v0.7.0-alpha1", + "Rev": "07c1a6da47b7fbf8b357f4949ecce2113e598491" }, { "ImportPath": "github.com/containernetworking/cni/pkg/types/020", - "Comment": "v0.6.0-rc1", - "Rev": "a2da8f8d7fd8e6dc25f336408a8ac86f050fbd88" + "Comment": "v0.7.0-alpha1", + "Rev": "07c1a6da47b7fbf8b357f4949ecce2113e598491" }, { "ImportPath": "github.com/containernetworking/cni/pkg/types/current", - "Comment": "v0.6.0-rc1", - "Rev": "a2da8f8d7fd8e6dc25f336408a8ac86f050fbd88" + "Comment": "v0.7.0-alpha1", + "Rev": "07c1a6da47b7fbf8b357f4949ecce2113e598491" }, { "ImportPath": "github.com/containernetworking/cni/pkg/version", - "Comment": "v0.6.0-rc1", - "Rev": "a2da8f8d7fd8e6dc25f336408a8ac86f050fbd88" + "Comment": "v0.7.0-alpha1", + "Rev": "07c1a6da47b7fbf8b357f4949ecce2113e598491" }, { "ImportPath": "github.com/coreos/go-iptables/iptables", @@ -52,8 +52,8 @@ }, { "ImportPath": "github.com/coreos/go-systemd/activation", - "Comment": "v2-53-g2688e91", - "Rev": "2688e91251d9d8e404e86dd8f096e23b2f086958" + "Comment": "v17", + "Rev": "39ca1b05acc7ad1220e09f133283b8859a8b71ab" }, { "ImportPath": "github.com/d2g/dhcp4", diff --git a/README.md b/README.md index f0e44435..86460176 100644 --- a/README.md +++ b/README.md @@ -4,12 +4,14 @@ # plugins Some CNI network plugins, maintained by the containernetworking team. For more information, see the individual READMEs. +Read [CONTRIBUTING](CONTRIBUTING.md) for build and test instructions. + ## Plugins supplied: ### Main: interface-creating * `bridge`: Creates a bridge, adds the host and the container to it. -* `ipvlan`: Adds an [ipvlan](https://www.kernel.org/doc/Documentation/networking/ipvlan.txt) interface in the container -* `loopback`: Creates a loopback interface -* `macvlan`: Creates a new MAC address, forwards all traffic to that to the container +* `ipvlan`: Adds an [ipvlan](https://www.kernel.org/doc/Documentation/networking/ipvlan.txt) interface in the container. +* `loopback`: Set the state of loopback interface to up. +* `macvlan`: Creates a new MAC address, forwards all traffic to that to the container. * `ptp`: Creates a veth pair. * `vlan`: Allocates a vlan device. diff --git a/pkg/ipam/ipam.go b/pkg/ipam/ipam.go index 904b2557..5ee713fd 100644 --- a/pkg/ipam/ipam.go +++ b/pkg/ipam/ipam.go @@ -20,9 +20,9 @@ import ( ) func ExecAdd(plugin string, netconf []byte) (types.Result, error) { - return invoke.DelegateAdd(plugin, netconf) + return invoke.DelegateAdd(plugin, netconf, nil) } func ExecDel(plugin string, netconf []byte) error { - return invoke.DelegateDel(plugin, netconf) + return invoke.DelegateDel(plugin, netconf, nil) } diff --git a/plugins/ipam/dhcp/daemon.go b/plugins/ipam/dhcp/daemon.go index 8ab977a3..2404db8f 100644 --- a/plugins/ipam/dhcp/daemon.go +++ b/plugins/ipam/dhcp/daemon.go @@ -128,7 +128,7 @@ func (d *DHCP) clearLease(contID, netName string) { } func getListener() (net.Listener, error) { - l, err := activation.Listeners(true) + l, err := activation.Listeners() if err != nil { return nil, err } diff --git a/plugins/ipam/dhcp/main.go b/plugins/ipam/dhcp/main.go index a0400a6d..f393dd67 100644 --- a/plugins/ipam/dhcp/main.go +++ b/plugins/ipam/dhcp/main.go @@ -44,7 +44,8 @@ func main() { os.Exit(1) } } else { - skel.PluginMain(cmdAdd, cmdDel, version.All) + // TODO: implement plugin version + skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO") } } @@ -72,6 +73,11 @@ func cmdDel(args *skel.CmdArgs) error { return nil } +func cmdGet(args *skel.CmdArgs) error { + // TODO: implement + return fmt.Errorf("not implemented") +} + func rpcCall(method string, args *skel.CmdArgs, result interface{}) error { client, err := rpc.DialHTTP("unix", socketPath) if err != nil { diff --git a/plugins/ipam/host-local/backend/allocator/config_test.go b/plugins/ipam/host-local/backend/allocator/config_test.go index cbae3d15..fc3793f7 100644 --- a/plugins/ipam/host-local/backend/allocator/config_test.go +++ b/plugins/ipam/host-local/backend/allocator/config_test.go @@ -372,7 +372,7 @@ var _ = Describe("IPAM config", func() { "type": "host-local", "ranges": [ [{"subnet": "10.1.2.0/24"}], - [{"subnet": "2001:db8:1::/24"}] + [{"subnet": "2001:db8:1::/48"}] ] } }` diff --git a/plugins/ipam/host-local/backend/allocator/range.go b/plugins/ipam/host-local/backend/allocator/range.go index e696b024..515afd0d 100644 --- a/plugins/ipam/host-local/backend/allocator/range.go +++ b/plugins/ipam/host-local/backend/allocator/range.go @@ -40,6 +40,12 @@ func (r *Range) Canonicalize() error { return fmt.Errorf("IPNet IP and Mask version mismatch") } + // Ensure Subnet IP is the network address, not some other address + networkIP := r.Subnet.IP.Mask(r.Subnet.Mask) + if !r.Subnet.IP.Equal(networkIP) { + return fmt.Errorf("Network has host bits set. For a subnet mask of length %d the network address is %s", ones, networkIP.String()) + } + // If the gateway is nil, claim .1 if r.Gateway == nil { r.Gateway = ip.NextIP(r.Subnet.IP) diff --git a/plugins/ipam/host-local/backend/allocator/range_test.go b/plugins/ipam/host-local/backend/allocator/range_test.go index cb8ca01b..9cdb5383 100644 --- a/plugins/ipam/host-local/backend/allocator/range_test.go +++ b/plugins/ipam/host-local/backend/allocator/range_test.go @@ -25,7 +25,7 @@ import ( ) var _ = Describe("IP ranges", func() { - It("should generate sane defaults for ipv4", func() { + It("should generate sane defaults for ipv4 with a clean prefix", func() { snstr := "192.0.2.0/24" r := Range{Subnet: mustSubnet(snstr)} @@ -33,7 +33,7 @@ var _ = Describe("IP ranges", func() { Expect(err).NotTo(HaveOccurred()) Expect(r).To(Equal(Range{ - Subnet: mustSubnet(snstr), + Subnet: networkSubnet(snstr), RangeStart: net.IP{192, 0, 2, 1}, RangeEnd: net.IP{192, 0, 2, 254}, Gateway: net.IP{192, 0, 2, 1}, @@ -47,13 +47,41 @@ var _ = Describe("IP ranges", func() { Expect(err).NotTo(HaveOccurred()) Expect(r).To(Equal(Range{ - Subnet: mustSubnet(snstr), + Subnet: networkSubnet(snstr), RangeStart: net.IP{192, 0, 2, 1}, RangeEnd: net.IP{192, 0, 2, 126}, Gateway: net.IP{192, 0, 2, 1}, })) }) - It("should generate sane defaults for ipv6", func() { + It("should reject ipv4 subnet using a masked address", func() { + snstr := "192.0.2.12/24" + r := Range{Subnet: mustSubnet(snstr)} + + err := r.Canonicalize() + Expect(err).Should(MatchError("Network has host bits set. For a subnet mask of length 24 the network address is 192.0.2.0")) + }) + It("should reject ipv6 subnet using a masked address", func() { + snstr := "2001:DB8:1::24:19ff:fee1:c44a/64" + r := Range{Subnet: mustSubnet(snstr)} + + err := r.Canonicalize() + Expect(err).Should(MatchError("Network has host bits set. For a subnet mask of length 64 the network address is 2001:db8:1::")) + }) + It("should reject ipv6 prefix with host bit set", func() { + snstr := "2001:DB8:24:19ff::/63" + r := Range{Subnet: mustSubnet(snstr)} + + err := r.Canonicalize() + Expect(err).Should(MatchError("Network has host bits set. For a subnet mask of length 63 the network address is 2001:db8:24:19fe::")) + }) + It("should reject ipv4 network with host bit set", func() { + snstr := "192.168.127.0/23" + r := Range{Subnet: mustSubnet(snstr)} + + err := r.Canonicalize() + Expect(err).Should(MatchError("Network has host bits set. For a subnet mask of length 23 the network address is 192.168.126.0")) + }) + It("should generate sane defaults for ipv6 with a clean prefix", func() { snstr := "2001:DB8:1::/64" r := Range{Subnet: mustSubnet(snstr)} @@ -61,7 +89,7 @@ var _ = Describe("IP ranges", func() { Expect(err).NotTo(HaveOccurred()) Expect(r).To(Equal(Range{ - Subnet: mustSubnet(snstr), + Subnet: networkSubnet(snstr), RangeStart: net.ParseIP("2001:DB8:1::1"), RangeEnd: net.ParseIP("2001:DB8:1::ffff:ffff:ffff:ffff"), Gateway: net.ParseIP("2001:DB8:1::1"), @@ -75,16 +103,17 @@ var _ = Describe("IP ranges", func() { }) It("should reject invalid RangeStart and RangeEnd specifications", func() { - r := Range{Subnet: mustSubnet("192.0.2.0/24"), RangeStart: net.ParseIP("192.0.3.0")} + snstr := "192.0.2.0/24" + r := Range{Subnet: mustSubnet(snstr), RangeStart: net.ParseIP("192.0.3.0")} err := r.Canonicalize() Expect(err).Should(MatchError("RangeStart 192.0.3.0 not in network 192.0.2.0/24")) - r = Range{Subnet: mustSubnet("192.0.2.0/24"), RangeEnd: net.ParseIP("192.0.4.0")} + r = Range{Subnet: mustSubnet(snstr), RangeEnd: net.ParseIP("192.0.4.0")} err = r.Canonicalize() Expect(err).Should(MatchError("RangeEnd 192.0.4.0 not in network 192.0.2.0/24")) r = Range{ - Subnet: mustSubnet("192.0.2.0/24"), + Subnet: networkSubnet(snstr), RangeStart: net.ParseIP("192.0.2.50"), RangeEnd: net.ParseIP("192.0.2.40"), } @@ -99,8 +128,9 @@ var _ = Describe("IP ranges", func() { }) It("should parse all fields correctly", func() { + snstr := "192.0.2.0/24" r := Range{ - Subnet: mustSubnet("192.0.2.0/24"), + Subnet: mustSubnet(snstr), RangeStart: net.ParseIP("192.0.2.40"), RangeEnd: net.ParseIP("192.0.2.50"), Gateway: net.ParseIP("192.0.2.254"), @@ -109,7 +139,7 @@ var _ = Describe("IP ranges", func() { Expect(err).NotTo(HaveOccurred()) Expect(r).To(Equal(Range{ - Subnet: mustSubnet("192.0.2.0/24"), + Subnet: networkSubnet(snstr), RangeStart: net.IP{192, 0, 2, 40}, RangeEnd: net.IP{192, 0, 2, 50}, Gateway: net.IP{192, 0, 2, 254}, @@ -207,3 +237,9 @@ func mustSubnet(s string) types.IPNet { canonicalizeIP(&n.IP) return types.IPNet(*n) } + +func networkSubnet(s string) types.IPNet { + net := mustSubnet(s) + net.IP = net.IP.Mask(net.Mask) + return net +} diff --git a/plugins/ipam/host-local/host_local_test.go b/plugins/ipam/host-local/host_local_test.go index cf5a39b2..653d6175 100644 --- a/plugins/ipam/host-local/host_local_test.go +++ b/plugins/ipam/host-local/host_local_test.go @@ -444,7 +444,7 @@ var _ = Describe("host-local Operations", func() { "dataDir": "%s", "ranges": [ [{"subnet":"172.16.1.0/24"}, { "subnet": "10.1.2.0/24" }], - [{ "subnet": "2001:db8:1::/24" }] + [{ "subnet": "2001:db8:1::/48" }] ] }, "args": { diff --git a/plugins/ipam/host-local/main.go b/plugins/ipam/host-local/main.go index 9e2bacc2..132391d0 100644 --- a/plugins/ipam/host-local/main.go +++ b/plugins/ipam/host-local/main.go @@ -29,7 +29,13 @@ import ( ) func main() { - skel.PluginMain(cmdAdd, cmdDel, version.All) + // TODO: implement plugin version + skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO") +} + +func cmdGet(args *skel.CmdArgs) error { + // TODO: implement + return fmt.Errorf("not implemented") } func cmdAdd(args *skel.CmdArgs) error { diff --git a/plugins/ipam/static/main.go b/plugins/ipam/static/main.go index e079c8dc..3a196d6d 100644 --- a/plugins/ipam/static/main.go +++ b/plugins/ipam/static/main.go @@ -57,7 +57,13 @@ type Address struct { } func main() { - skel.PluginMain(cmdAdd, cmdDel, version.All) + // TODO: implement plugin version + skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO") +} + +func cmdGet(args *skel.CmdArgs) error { + // TODO: implement + return fmt.Errorf("not implemented") } // canonicalizeIP makes sure a provided ip is in standard form @@ -72,7 +78,9 @@ func canonicalizeIP(ip *net.IP) error { return fmt.Errorf("IP %s not v4 nor v6", *ip) } -// NewIPAMConfig creates a NetworkConfig from the given network name. +// LoadIPAMConfig creates IPAMConfig using json encoded configuration provided +// as `bytes`. At the moment values provided in envArgs are ignored so there +// is no possibility to overload the json configuration using envArgs func LoadIPAMConfig(bytes []byte, envArgs string) (*IPAMConfig, string, error) { n := Net{} if err := json.Unmarshal(bytes, &n); err != nil { @@ -167,7 +175,6 @@ func cmdAdd(args *skel.CmdArgs) error { Gateway: v.Gateway}) } - result.Routes = ipamConf.Routes return types.PrintResult(result, confVersion) } diff --git a/plugins/main/bridge/bridge.go b/plugins/main/bridge/bridge.go index 63e0d89a..2f14e603 100644 --- a/plugins/main/bridge/bridge.go +++ b/plugins/main/bridge/bridge.go @@ -19,6 +19,7 @@ import ( "errors" "fmt" "net" + "os" "runtime" "syscall" @@ -36,6 +37,9 @@ import ( "github.com/vishvananda/netlink" ) +// For testcases to force an error after IPAM has been performed +var debugPostIPAMError error + const defaultBrName = "cni0" type NetConf struct { @@ -323,6 +327,8 @@ func enableIPForward(family int) error { } func cmdAdd(args *skel.CmdArgs) error { + var success bool = false + n, cniVersion, err := loadNetConf(args.StdinData) if err != nil { return err @@ -358,6 +364,15 @@ func cmdAdd(args *skel.CmdArgs) error { return err } + // release IP in case of failure + defer func() { + if !success { + os.Setenv("CNI_COMMAND", "DEL") + ipam.ExecDel(n.IPAM.Type, args.StdinData) + os.Setenv("CNI_COMMAND", "ADD") + } + }() + // Convert whatever the IPAM result was into the current Result type result, err := current.NewResultFromResult(r) if err != nil { @@ -454,6 +469,13 @@ func cmdAdd(args *skel.CmdArgs) error { result.DNS = n.DNS + // Return an error requested by testcases, if any + if debugPostIPAMError != nil { + return debugPostIPAMError + } + + success = true + return types.PrintResult(result, cniVersion) } @@ -502,5 +524,11 @@ func cmdDel(args *skel.CmdArgs) error { } func main() { - skel.PluginMain(cmdAdd, cmdDel, version.All) + // TODO: implement plugin version + skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO") +} + +func cmdGet(args *skel.CmdArgs) error { + // TODO: implement + return fmt.Errorf("not implemented") } diff --git a/plugins/main/bridge/bridge_test.go b/plugins/main/bridge/bridge_test.go index bbfa3a68..6ed07a5e 100644 --- a/plugins/main/bridge/bridge_test.go +++ b/plugins/main/bridge/bridge_test.go @@ -16,7 +16,9 @@ package main import ( "fmt" + "io/ioutil" "net" + "os" "strings" "github.com/containernetworking/cni/pkg/skel" @@ -83,6 +85,9 @@ const ( "ipam": { "type": "host-local"` + ipamDataDirStr = `, + "dataDir": "%s"` + // Single subnet configuration (legacy) subnetConfStr = `, "subnet": "%s"` @@ -110,10 +115,13 @@ const ( // netConfJSON() generates a JSON network configuration string // for a test case. -func (tc testCase) netConfJSON() string { +func (tc testCase) netConfJSON(dataDir string) string { conf := fmt.Sprintf(netConfStr, tc.cniVersion, BRNAME) if tc.subnet != "" || tc.ranges != nil { conf += ipamStartStr + if dataDir != "" { + conf += fmt.Sprintf(ipamDataDirStr, dataDir) + } if tc.subnet != "" { conf += tc.subnetConfig() } @@ -152,8 +160,8 @@ var counter uint // createCmdArgs generates network configuration and creates command // arguments for a test case. -func (tc testCase) createCmdArgs(targetNS ns.NetNS) *skel.CmdArgs { - conf := tc.netConfJSON() +func (tc testCase) createCmdArgs(targetNS ns.NetNS, dataDir string) *skel.CmdArgs { + conf := tc.netConfJSON(dataDir) defer func() { counter += 1 }() return &skel.CmdArgs{ ContainerID: fmt.Sprintf("dummy-%d", counter), @@ -213,9 +221,27 @@ func ipVersion(ip net.IP) string { return "6" } +func countIPAMIPs(path string) (int, error) { + count := 0 + files, err := ioutil.ReadDir(path) + if err != nil { + return -1, err + } + for _, file := range files { + if file.IsDir() { + continue + } + + if net.ParseIP(file.Name()) != nil { + count++ + } + } + return count, nil +} + type cmdAddDelTester interface { setNS(testNS ns.NetNS, targetNS ns.NetNS) - cmdAddTest(tc testCase) + cmdAddTest(tc testCase, dataDir string) cmdDelTest(tc testCase) } @@ -240,9 +266,9 @@ func (tester *testerV03x) setNS(testNS ns.NetNS, targetNS ns.NetNS) { tester.targetNS = targetNS } -func (tester *testerV03x) cmdAddTest(tc testCase) { +func (tester *testerV03x) cmdAddTest(tc testCase, dataDir string) { // Generate network config and command arguments - tester.args = tc.createCmdArgs(tester.targetNS) + tester.args = tc.createCmdArgs(tester.targetNS, dataDir) // Execute cmdADD on the plugin var result *current.Result @@ -419,9 +445,9 @@ func (tester *testerV01xOr02x) setNS(testNS ns.NetNS, targetNS ns.NetNS) { tester.targetNS = targetNS } -func (tester *testerV01xOr02x) cmdAddTest(tc testCase) { +func (tester *testerV01xOr02x) cmdAddTest(tc testCase, dataDir string) { // Generate network config and calculate gateway addresses - tester.args = tc.createCmdArgs(tester.targetNS) + tester.args = tc.createCmdArgs(tester.targetNS, dataDir) // Execute cmdADD on the plugin err := tester.testNS.Do(func(ns.NetNS) error { @@ -537,7 +563,7 @@ func (tester *testerV01xOr02x) cmdDelTest(tc testCase) { Expect(err).NotTo(HaveOccurred()) } -func cmdAddDelTest(testNS ns.NetNS, tc testCase) { +func cmdAddDelTest(testNS ns.NetNS, tc testCase, dataDir string) { // Get a Add/Del tester based on test case version tester := testerByVersion(tc.cniVersion) @@ -547,7 +573,7 @@ func cmdAddDelTest(testNS ns.NetNS, tc testCase) { tester.setNS(testNS, targetNS) // Test IP allocation - tester.cmdAddTest(tc) + tester.cmdAddTest(tc, dataDir) // Test IP Release tester.cmdDelTest(tc) @@ -558,15 +584,23 @@ func cmdAddDelTest(testNS ns.NetNS, tc testCase) { var _ = Describe("bridge Operations", func() { var originalNS ns.NetNS + var dataDir string BeforeEach(func() { // Create a new NetNS so we don't modify the host var err error originalNS, err = testutils.NewNS() Expect(err).NotTo(HaveOccurred()) + + dataDir, err = ioutil.TempDir("", "bridge_test") + Expect(err).NotTo(HaveOccurred()) + + // Do not emulate an error, each test will set this if needed + debugPostIPAMError = nil }) AfterEach(func() { + Expect(os.RemoveAll(dataDir)).To(Succeed()) Expect(originalNS.Close()).To(Succeed()) }) @@ -661,7 +695,7 @@ var _ = Describe("bridge Operations", func() { } for _, tc := range testCases { tc.cniVersion = "0.3.0" - cmdAddDelTest(originalNS, tc) + cmdAddDelTest(originalNS, tc, dataDir) } }) @@ -691,7 +725,7 @@ var _ = Describe("bridge Operations", func() { } for _, tc := range testCases { tc.cniVersion = "0.3.1" - cmdAddDelTest(originalNS, tc) + cmdAddDelTest(originalNS, tc, dataDir) } }) @@ -707,7 +741,7 @@ var _ = Describe("bridge Operations", func() { Expect(err).NotTo(HaveOccurred()) defer targetNS.Close() tester.setNS(originalNS, targetNS) - tester.args = tc.createCmdArgs(targetNS) + tester.args = tc.createCmdArgs(targetNS, dataDir) // Execute cmdDEL on the plugin, expect no errors tester.cmdDelTest(tc) @@ -739,7 +773,7 @@ var _ = Describe("bridge Operations", func() { } for _, tc := range testCases { tc.cniVersion = "0.1.0" - cmdAddDelTest(originalNS, tc) + cmdAddDelTest(originalNS, tc, dataDir) } }) @@ -909,11 +943,44 @@ var _ = Describe("bridge Operations", func() { Expect(err).NotTo(HaveOccurred()) origMac := link.Attrs().HardwareAddr - cmdAddDelTest(originalNS, tc) + cmdAddDelTest(originalNS, tc, dataDir) link, err = netlink.LinkByName(BRNAME) Expect(err).NotTo(HaveOccurred()) Expect(link.Attrs().HardwareAddr).To(Equal(origMac)) } }) + + It("checks ip release in case of error", func() { + err := originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + tc := testCase{ + cniVersion: "0.3.1", + subnet: "10.1.2.0/24", + } + + _, _, err := setupBridge(tc.netConf()) + Expect(err).NotTo(HaveOccurred()) + + args := tc.createCmdArgs(originalNS, dataDir) + + // get number of allocated IPs before asking for a new one + before, err := countIPAMIPs(dataDir) + Expect(err).NotTo(HaveOccurred()) + + debugPostIPAMError = fmt.Errorf("debugPostIPAMError") + _, _, err = testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).To(MatchError("debugPostIPAMError")) + + // get number of allocated IPs after failure + after, err := countIPAMIPs(dataDir) + Expect(err).NotTo(HaveOccurred()) + + Expect(before).To(Equal(after)) + return nil + }) + Expect(err).NotTo(HaveOccurred()) + }) }) diff --git a/plugins/main/host-device/host-device.go b/plugins/main/host-device/host-device.go index fea5b980..7884b0ed 100644 --- a/plugins/main/host-device/host-device.go +++ b/plugins/main/host-device/host-device.go @@ -217,5 +217,11 @@ func getLink(devname, hwaddr, kernelpath string) (netlink.Link, error) { } func main() { - skel.PluginMain(cmdAdd, cmdDel, version.All) + // TODO: implement plugin version + skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO") +} + +func cmdGet(args *skel.CmdArgs) error { + // TODO: implement + return fmt.Errorf("not implemented") } diff --git a/plugins/main/ipvlan/ipvlan.go b/plugins/main/ipvlan/ipvlan.go index 269fa8e8..fa83e1bc 100644 --- a/plugins/main/ipvlan/ipvlan.go +++ b/plugins/main/ipvlan/ipvlan.go @@ -245,5 +245,11 @@ func cmdDel(args *skel.CmdArgs) error { } func main() { - skel.PluginMain(cmdAdd, cmdDel, version.All) + // TODO: implement plugin version + skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO") +} + +func cmdGet(args *skel.CmdArgs) error { + // TODO: implement + return fmt.Errorf("not implemented") } diff --git a/plugins/main/loopback/loopback.go b/plugins/main/loopback/loopback.go index 08c84a5d..7fb88149 100644 --- a/plugins/main/loopback/loopback.go +++ b/plugins/main/loopback/loopback.go @@ -15,6 +15,8 @@ package main import ( + "fmt" + "github.com/containernetworking/cni/pkg/skel" "github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/cni/pkg/version" @@ -68,5 +70,11 @@ func cmdDel(args *skel.CmdArgs) error { } func main() { - skel.PluginMain(cmdAdd, cmdDel, version.All) + // TODO: implement plugin version + skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO") +} + +func cmdGet(args *skel.CmdArgs) error { + // TODO: implement + return fmt.Errorf("not implemented") } diff --git a/plugins/main/loopback/loopback_test.go b/plugins/main/loopback/loopback_test.go index dd5d5c02..13693833 100644 --- a/plugins/main/loopback/loopback_test.go +++ b/plugins/main/loopback/loopback_test.go @@ -58,6 +58,8 @@ var _ = Describe("Loopback", func() { 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) @@ -78,6 +80,8 @@ 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) diff --git a/plugins/main/macvlan/macvlan.go b/plugins/main/macvlan/macvlan.go index 618fe96d..dba60978 100644 --- a/plugins/main/macvlan/macvlan.go +++ b/plugins/main/macvlan/macvlan.go @@ -255,5 +255,11 @@ func cmdDel(args *skel.CmdArgs) error { } func main() { - skel.PluginMain(cmdAdd, cmdDel, version.All) + // TODO: implement plugin version + skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO") +} + +func cmdGet(args *skel.CmdArgs) error { + // TODO: implement + return fmt.Errorf("not implemented") } diff --git a/plugins/main/ptp/ptp.go b/plugins/main/ptp/ptp.go index da07b1b5..a1b6183d 100644 --- a/plugins/main/ptp/ptp.go +++ b/plugins/main/ptp/ptp.go @@ -285,5 +285,11 @@ func cmdDel(args *skel.CmdArgs) error { } func main() { - skel.PluginMain(cmdAdd, cmdDel, version.All) + // TODO: implement plugin version + skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO") +} + +func cmdGet(args *skel.CmdArgs) error { + // TODO: implement + return fmt.Errorf("not implemented") } diff --git a/plugins/main/vlan/vlan.go b/plugins/main/vlan/vlan.go index 694d85ae..c34a752e 100644 --- a/plugins/main/vlan/vlan.go +++ b/plugins/main/vlan/vlan.go @@ -192,5 +192,11 @@ func cmdDel(args *skel.CmdArgs) error { } func main() { - skel.PluginMain(cmdAdd, cmdDel, version.All) + // TODO: implement plugin version + skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO") +} + +func cmdGet(args *skel.CmdArgs) error { + // TODO: implement + return fmt.Errorf("not implemented") } diff --git a/plugins/meta/bandwidth/README.md b/plugins/meta/bandwidth/README.md index 7129055e..3fa49acb 100644 --- a/plugins/meta/bandwidth/README.md +++ b/plugins/meta/bandwidth/README.md @@ -48,10 +48,10 @@ The following is an example [json configuration list](https://github.com/contain The result is an `ifb` device in the host namespace redirecting to the `host-interface`, with `tc tbf` applied on the `ifb` device and the `container-interface` ## Network configuration reference -* ingressRate: is the rate in Kbps at which traffic can enter an interface. (See http://man7.org/linux/man-pages/man8/tbf.8.html) -* ingressBurst: is the maximum amount in Kb that tokens can be made available for instantaneously. (See http://man7.org/linux/man-pages/man8/tbf.8.html) -* egressRate: is the rate in Kbps at which traffic can leave an interface. (See http://man7.org/linux/man-pages/man8/tbf.8.html) -* egressBurst: is the maximum amount in Kb that tokens can be made available for instantaneously. (See http://man7.org/linux/man-pages/man8/tbf.8.html) +* ingressRate: is the rate in bps at which traffic can enter an interface. (See http://man7.org/linux/man-pages/man8/tbf.8.html) +* ingressBurst: is the maximum amount in bits that tokens can be made available for instantaneously. (See http://man7.org/linux/man-pages/man8/tbf.8.html) +* egressRate: is the rate in bps at which traffic can leave an interface. (See http://man7.org/linux/man-pages/man8/tbf.8.html) +* egressBurst: is the maximum amount in bits that tokens can be made available for instantaneously. (See http://man7.org/linux/man-pages/man8/tbf.8.html) Both ingressRate and ingressBurst must be set in order to limit ingress bandwidth. If neither one is set, then ingress bandwidth is not limited. Both egressRate and egressBurst must be set in order to limit egress bandwidth. If neither one is set, then egress bandwidth is not limited. diff --git a/plugins/meta/bandwidth/bandwidth_linux_test.go b/plugins/meta/bandwidth/bandwidth_linux_test.go index bd0418c7..d877b740 100644 --- a/plugins/meta/bandwidth/bandwidth_linux_test.go +++ b/plugins/meta/bandwidth/bandwidth_linux_test.go @@ -80,7 +80,7 @@ var _ = Describe("bandwidth test", func() { "ingressRate": 8, "ingressBurst": 8, "egressRate": 16, - "egressBurst": 9, + "egressBurst": 8, "prevResult": { "interfaces": [ { @@ -135,7 +135,7 @@ var _ = Describe("bandwidth test", func() { 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(9))) + Expect(qdiscs[0].(*netlink.Tbf).Limit).To(Equal(uint32(1))) hostVethLink, err := netlink.LinkByName(hostIfname) Expect(err).NotTo(HaveOccurred()) @@ -163,7 +163,7 @@ var _ = Describe("bandwidth test", func() { 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(8))) + Expect(qdiscs[0].(*netlink.Tbf).Limit).To(Equal(uint32(1))) return nil })).To(Succeed()) @@ -176,8 +176,8 @@ var _ = Describe("bandwidth test", func() { "type": "bandwidth", "ingressRate": 0, "ingressBurst": 0, - "egressRate": 8, - "egressBurst": 1, + "egressRate": 8000, + "egressBurst": 80, "prevResult": { "interfaces": [ { @@ -245,8 +245,8 @@ var _ = Describe("bandwidth test", func() { "type": "bandwidth", "egressRate": 0, "egressBurst": 0, - "ingressRate": 8, - "ingressBurst": 1, + "ingressRate": 8000, + "ingressBurst": 80, "prevResult": { "interfaces": [ { @@ -302,8 +302,8 @@ var _ = Describe("bandwidth test", func() { Expect(qdiscs[0].Attrs().LinkIndex).To(Equal(containerIfLink.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))) + Expect(qdiscs[0].(*netlink.Tbf).Rate).To(Equal(uint64(1000))) + Expect(qdiscs[0].(*netlink.Tbf).Limit).To(Equal(uint32(35))) return nil })).To(Succeed()) @@ -426,7 +426,7 @@ var _ = Describe("bandwidth test", func() { 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(9))) + Expect(qdiscs[0].(*netlink.Tbf).Limit).To(Equal(uint32(1))) hostVethLink, err := netlink.LinkByName(hostIfname) Expect(err).NotTo(HaveOccurred()) @@ -454,7 +454,7 @@ var _ = Describe("bandwidth test", func() { 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(8))) + Expect(qdiscs[0].(*netlink.Tbf).Limit).To(Equal(uint32(1))) return nil })).To(Succeed()) @@ -625,7 +625,7 @@ var _ = Describe("bandwidth test", func() { defer GinkgoRecover() containerWithTbfRes, _, err = testutils.CmdAdd(containerWithTbfNS.Path(), "dummy", containerWithTbfIFName, []byte(ptpConf), func() error { - r, err := invoke.DelegateAdd("ptp", []byte(ptpConf)) + r, err := invoke.DelegateAdd("ptp", []byte(ptpConf), nil) Expect(r.Print()).To(Succeed()) return err @@ -633,7 +633,7 @@ var _ = Describe("bandwidth test", func() { Expect(err).NotTo(HaveOccurred()) containerWithoutTbfRes, _, err = testutils.CmdAdd(containerWithoutTbfNS.Path(), "dummy2", containerWithoutTbfIFName, []byte(ptpConf), func() error { - r, err := invoke.DelegateAdd("ptp", []byte(ptpConf)) + r, err := invoke.DelegateAdd("ptp", []byte(ptpConf), nil) Expect(r.Print()).To(Succeed()) return err diff --git a/plugins/meta/bandwidth/ifb_creator.go b/plugins/meta/bandwidth/ifb_creator.go index a7032c0e..c7f90cd0 100644 --- a/plugins/meta/bandwidth/ifb_creator.go +++ b/plugins/meta/bandwidth/ifb_creator.go @@ -125,9 +125,10 @@ func createTBF(rateInBits, burstInBits, linkIndex int) error { return fmt.Errorf("invalid burst: %d", burstInBits) } rateInBytes := rateInBits / 8 - bufferInBytes := buffer(uint64(rateInBytes), uint32(burstInBits)) + burstInBytes := burstInBits / 8 + bufferInBytes := buffer(uint64(rateInBytes), uint32(burstInBytes)) latency := latencyInUsec(latencyInMillis) - limitInBytes := limit(uint64(rateInBytes), latency, uint32(bufferInBytes)) + limitInBytes := limit(uint64(rateInBytes), latency, uint32(burstInBytes)) qdisc := &netlink.Tbf{ QdiscAttrs: netlink.QdiscAttrs{ @@ -159,7 +160,7 @@ func buffer(rate uint64, burst uint32) uint32 { } func limit(rate uint64, latency float64, buffer uint32) uint32 { - return uint32(float64(rate) / float64(netlink.TIME_UNITS_PER_SEC) * (latency + float64(tick2Time(buffer)))) + return uint32(float64(rate)*latency/float64(netlink.TIME_UNITS_PER_SEC)) + buffer } func latencyInUsec(latencyInMillis float64) float64 { diff --git a/plugins/meta/bandwidth/main.go b/plugins/meta/bandwidth/main.go index da580ae0..5a2e5630 100644 --- a/plugins/meta/bandwidth/main.go +++ b/plugins/meta/bandwidth/main.go @@ -32,11 +32,11 @@ import ( // BandwidthEntry corresponds to a single entry in the bandwidth argument, // see CONVENTIONS.md type BandwidthEntry struct { - IngressRate int `json:"ingressRate"` //Bandwidth rate in Kbps for traffic through container. 0 for no limit. If ingressRate is set, ingressBurst must also be set - IngressBurst int `json:"ingressBurst"` //Bandwidth burst in Kb for traffic through container. 0 for no limit. If ingressBurst is set, ingressRate must also be set + 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 - EgressRate int `json:"egressRate"` //Bandwidth rate in Kbps for traffic through container. 0 for no limit. If egressRate is set, egressBurst must also be set - EgressBurst int `json:"egressBurst"` //Bandwidth burst in Kb for traffic through container. 0 for no limit. If egressBurst is set, egressRate 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 } func (bw *BandwidthEntry) isZero() bool { @@ -232,5 +232,11 @@ func cmdDel(args *skel.CmdArgs) error { } func main() { - skel.PluginMain(cmdAdd, cmdDel, version.PluginSupports("0.3.0", "0.3.1", version.Current())) + // TODO: implement plugin version + skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.PluginSupports("0.3.0", "0.3.1", version.Current()), "TODO") +} + +func cmdGet(args *skel.CmdArgs) error { + // TODO: implement + return fmt.Errorf("not implemented") } diff --git a/plugins/meta/flannel/flannel.go b/plugins/meta/flannel/flannel.go index 6ec0e8fc..21190281 100644 --- a/plugins/meta/flannel/flannel.go +++ b/plugins/meta/flannel/flannel.go @@ -159,7 +159,7 @@ func delegateAdd(cid, dataDir string, netconf map[string]interface{}) error { return err } - result, err := invoke.DelegateAdd(netconf["type"].(string), netconfBytes) + result, err := invoke.DelegateAdd(netconf["type"].(string), netconfBytes, nil) if err != nil { return err } @@ -261,9 +261,15 @@ func cmdDel(args *skel.CmdArgs) error { return fmt.Errorf("failed to parse netconf: %v", err) } - return invoke.DelegateDel(n.Type, netconfBytes) + return invoke.DelegateDel(n.Type, netconfBytes, nil) } func main() { - skel.PluginMain(cmdAdd, cmdDel, version.All) + // TODO: implement plugin version + skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO") +} + +func cmdGet(args *skel.CmdArgs) error { + // TODO: implement + return fmt.Errorf("not implemented") } diff --git a/plugins/meta/portmap/main.go b/plugins/meta/portmap/main.go index dfc52994..2f44bc95 100644 --- a/plugins/meta/portmap/main.go +++ b/plugins/meta/portmap/main.go @@ -118,7 +118,13 @@ func cmdDel(args *skel.CmdArgs) error { } func main() { - skel.PluginMain(cmdAdd, cmdDel, version.PluginSupports("", "0.1.0", "0.2.0", "0.3.0", version.Current())) + // TODO: implement plugin version + skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO") +} + +func cmdGet(args *skel.CmdArgs) error { + // TODO: implement + return fmt.Errorf("not implemented") } // parseConfig parses the supplied configuration (and prevResult) from stdin. diff --git a/plugins/meta/tuning/tuning.go b/plugins/meta/tuning/tuning.go index ec9fe2e4..156a4b73 100644 --- a/plugins/meta/tuning/tuning.go +++ b/plugins/meta/tuning/tuning.go @@ -21,6 +21,7 @@ import ( "encoding/json" "fmt" "io/ioutil" + "net" "path/filepath" "strings" @@ -29,6 +30,7 @@ import ( "github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/cni/pkg/version" "github.com/containernetworking/plugins/pkg/ns" + "github.com/vishvananda/netlink" ) // TuningConf represents the network tuning configuration. @@ -37,14 +39,35 @@ type TuningConf struct { SysCtl map[string]string `json:"sysctl"` RawPrevResult map[string]interface{} `json:"prevResult,omitempty"` PrevResult *current.Result `json:"-"` + Mac string `json:"mac,omitempty"` + Promisc bool `json:"promisc,omitempty"` + Mtu int `json:"mtu,omitempty"` } -func parseConf(data []byte) (*TuningConf, error) { - conf := TuningConf{} +type MACEnvArgs struct { + types.CommonArgs + MAC types.UnmarshallableString `json:"mac,omitempty"` +} + +func parseConf(data []byte, envArgs string) (*TuningConf, error) { + conf := TuningConf{Promisc: false} if err := json.Unmarshal(data, &conf); err != nil { return nil, fmt.Errorf("failed to load netconf: %v", err) } + // Parse custom MAC from both env args + if envArgs != "" { + e := MACEnvArgs{} + err := types.LoadArgs(envArgs, &e) + if err != nil { + return nil, err + } + + if e.MAC != "" { + conf.Mac = string(e.MAC) + } + } + // Parse previous result. if conf.RawPrevResult != nil { resultBytes, err := json.Marshal(conf.RawPrevResult) @@ -65,8 +88,58 @@ func parseConf(data []byte) (*TuningConf, error) { return &conf, nil } +func changeMacAddr(ifName string, newMacAddr string) error { + addr, err := net.ParseMAC(newMacAddr) + if err != nil { + return fmt.Errorf("invalid args %v for MAC addr: %v", newMacAddr, err) + } + + link, err := netlink.LinkByName(ifName) + if err != nil { + 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) +} + +func updateResultsMacAddr(config TuningConf, ifName string, newMacAddr string) { + for _, i := range config.PrevResult.Interfaces { + if i.Name == ifName { + i.Mac = newMacAddr + } + } +} + +func changePromisc(ifName string, val bool) error { + link, err := netlink.LinkByName(ifName) + if err != nil { + return fmt.Errorf("failed to get %q: %v", ifName, err) + } + + if val { + return netlink.SetPromiscOn(link) + } + return netlink.SetPromiscOff(link) +} + +func changeMtu(ifName string, mtu int) error { + link, err := netlink.LinkByName(ifName) + if err != nil { + return fmt.Errorf("failed to get %q: %v", ifName, err) + } + return netlink.LinkSetMTU(link, mtu) +} + func cmdAdd(args *skel.CmdArgs) error { - tuningConf, err := parseConf(args.StdinData) + tuningConf, err := parseConf(args.StdinData, args.Args) if err != nil { return err } @@ -90,6 +163,25 @@ func cmdAdd(args *skel.CmdArgs) error { return err } } + + if tuningConf.Mac != "" { + if err = changeMacAddr(args.IfName, tuningConf.Mac); err != nil { + return err + } + updateResultsMacAddr(*tuningConf, args.IfName, tuningConf.Mac) + } + + if tuningConf.Promisc != false { + if err = changePromisc(args.IfName, true); err != nil { + return err + } + } + + if tuningConf.Mtu != 0 { + if err = changeMtu(args.IfName, tuningConf.Mtu); err != nil { + return err + } + } return nil }) if err != nil { @@ -107,5 +199,11 @@ func cmdDel(args *skel.CmdArgs) error { } func main() { - skel.PluginMain(cmdAdd, cmdDel, version.All) + // TODO: implement plugin version + skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO") +} + +func cmdGet(args *skel.CmdArgs) error { + // TODO: implement + return fmt.Errorf("not implemented") } diff --git a/plugins/meta/tuning/tuning_test.go b/plugins/meta/tuning/tuning_test.go index ed74123d..6bba6a3a 100644 --- a/plugins/meta/tuning/tuning_test.go +++ b/plugins/meta/tuning/tuning_test.go @@ -19,6 +19,7 @@ import ( "github.com/containernetworking/cni/pkg/types/current" "github.com/containernetworking/plugins/pkg/ns" "github.com/containernetworking/plugins/pkg/testutils" + "net" "github.com/vishvananda/netlink" @@ -109,4 +110,236 @@ var _ = Describe("tuning plugin", func() { }) Expect(err).NotTo(HaveOccurred()) }) + + It("configures and deconfigures promiscas mode with ADD/DEL", func() { + conf := []byte(`{ + "name": "test", + "type": "iplink", + "cniVersion": "0.3.1", + "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", + "type": "iplink", + "cniVersion": "0.3.1", + "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", + "type": "iplink", + "cniVersion": "0.3.1", + "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", + "type": "iplink", + "cniVersion": "0.3.1", + "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, + Args: "IgnoreUnknown=true;MAC=c2:11:22:33:44:66", + } + + 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:66") + 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()) + }) }) diff --git a/plugins/sample/main.go b/plugins/sample/main.go index 1abdc165..65676270 100644 --- a/plugins/sample/main.go +++ b/plugins/sample/main.go @@ -141,5 +141,11 @@ func cmdDel(args *skel.CmdArgs) error { } func main() { - skel.PluginMain(cmdAdd, cmdDel, version.PluginSupports("", "0.1.0", "0.2.0", version.Current())) + // TODO: implement plugin version + skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO") +} + +func cmdGet(args *skel.CmdArgs) error { + // TODO: implement + return fmt.Errorf("not implemented") } diff --git a/scripts/release.sh b/scripts/release.sh index b01b27f8..0956e9b8 100755 --- a/scripts/release.sh +++ b/scripts/release.sh @@ -15,7 +15,7 @@ rm -Rf ${SRC_DIR}/${RELEASE_DIR} mkdir -p ${SRC_DIR}/${RELEASE_DIR} mkdir -p ${OUTPUT_DIR} -docker run -ti -v ${SRC_DIR}:/go/src/github.com/containernetworking/plugins --rm golang:1.10-alpine \ +docker run -v ${SRC_DIR}:/go/src/github.com/containernetworking/plugins --rm golang:1.10-alpine \ /bin/sh -xe -c "\ apk --no-cache add bash tar; cd /go/src/github.com/containernetworking/plugins; umask 0022; diff --git a/vendor/github.com/containernetworking/cni/libcni/api.go b/vendor/github.com/containernetworking/cni/libcni/api.go index a23cbb2c..d494e43d 100644 --- a/vendor/github.com/containernetworking/cni/libcni/api.go +++ b/vendor/github.com/containernetworking/cni/libcni/api.go @@ -15,7 +15,11 @@ package libcni import ( + "encoding/json" + "fmt" + "io/ioutil" "os" + "path/filepath" "strings" "github.com/containernetworking/cni/pkg/invoke" @@ -23,6 +27,14 @@ import ( "github.com/containernetworking/cni/pkg/version" ) +var ( + CacheDir = "/var/lib/cni" +) + +// A RuntimeConf holds the arguments to one invocation of a CNI plugin +// excepting the network configuration, with the nested exception that +// the `runtimeConfig` from the network configuration is included +// here. type RuntimeConf struct { ContainerID string NetNS string @@ -34,6 +46,9 @@ type RuntimeConf struct { // in this map which match the capabilities of the plugin are passed // to the plugin CapabilityArgs map[string]interface{} + + // A cache directory in which to library data. Defaults to CacheDir + CacheDir string } type NetworkConfig struct { @@ -50,25 +65,38 @@ type NetworkConfigList struct { type CNI interface { AddNetworkList(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error) + GetNetworkList(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error) DelNetworkList(net *NetworkConfigList, rt *RuntimeConf) error AddNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) + GetNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) DelNetwork(net *NetworkConfig, rt *RuntimeConf) error } type CNIConfig struct { Path []string + exec invoke.Exec } // CNIConfig implements the CNI interface var _ CNI = &CNIConfig{} -func buildOneConfig(list *NetworkConfigList, orig *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (*NetworkConfig, error) { +// NewCNIConfig returns a new CNIConfig object that will search for plugins +// in the given paths and use the given exec interface to run those plugins, +// or if the exec interface is not given, will use a default exec handler. +func NewCNIConfig(path []string, exec invoke.Exec) *CNIConfig { + return &CNIConfig{ + Path: path, + exec: exec, + } +} + +func buildOneConfig(name, cniVersion string, orig *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (*NetworkConfig, error) { var err error inject := map[string]interface{}{ - "name": list.Name, - "cniVersion": list.CNIVersion, + "name": name, + "cniVersion": cniVersion, } // Add previous plugin result if prevResult != nil { @@ -119,21 +147,37 @@ func injectRuntimeConfig(orig *NetworkConfig, rt *RuntimeConf) (*NetworkConfig, return orig, nil } -// AddNetworkList executes a sequence of plugins with the ADD command -func (c *CNIConfig) AddNetworkList(list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) { - var prevResult types.Result +// ensure we have a usable exec if the CNIConfig was not given one +func (c *CNIConfig) ensureExec() invoke.Exec { + if c.exec == nil { + c.exec = &invoke.DefaultExec{ + RawExec: &invoke.RawExec{Stderr: os.Stderr}, + PluginDecoder: version.PluginDecoder{}, + } + } + return c.exec +} + +func (c *CNIConfig) addOrGetNetwork(command, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) { + c.ensureExec() + pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path) + if err != nil { + return nil, err + } + + newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt) + if err != nil { + return nil, err + } + + return invoke.ExecPluginWithResult(pluginPath, newConf.Bytes, c.args(command, rt), c.exec) +} + +// Note that only GET requests should pass an initial prevResult +func (c *CNIConfig) addOrGetNetworkList(command string, prevResult types.Result, list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) { + var err error for _, net := range list.Plugins { - pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path) - if err != nil { - return nil, err - } - - newConf, err := buildOneConfig(list, net, prevResult, rt) - if err != nil { - return nil, err - } - - prevResult, err = invoke.ExecPluginWithResult(pluginPath, newConf.Bytes, c.args("ADD", rt)) + prevResult, err = c.addOrGetNetwork(command, list.Name, list.CNIVersion, net, prevResult, rt) if err != nil { return nil, err } @@ -142,68 +186,194 @@ func (c *CNIConfig) AddNetworkList(list *NetworkConfigList, rt *RuntimeConf) (ty return prevResult, nil } +func getResultCacheFilePath(netName string, rt *RuntimeConf) string { + cacheDir := rt.CacheDir + if cacheDir == "" { + cacheDir = CacheDir + } + return filepath.Join(cacheDir, "results", fmt.Sprintf("%s-%s", netName, rt.ContainerID)) +} + +func setCachedResult(result types.Result, netName string, rt *RuntimeConf) error { + data, err := json.Marshal(result) + if err != nil { + return err + } + fname := getResultCacheFilePath(netName, rt) + if err := os.MkdirAll(filepath.Dir(fname), 0700); err != nil { + return err + } + return ioutil.WriteFile(fname, data, 0600) +} + +func delCachedResult(netName string, rt *RuntimeConf) error { + fname := getResultCacheFilePath(netName, rt) + return os.Remove(fname) +} + +func getCachedResult(netName, cniVersion string, rt *RuntimeConf) (types.Result, error) { + fname := getResultCacheFilePath(netName, rt) + data, err := ioutil.ReadFile(fname) + if err != nil { + // Ignore read errors; the cached result may not exist on-disk + return nil, nil + } + + // Read the version of the cached result + decoder := version.ConfigDecoder{} + resultCniVersion, err := decoder.Decode(data) + if err != nil { + return nil, err + } + + // Ensure we can understand the result + result, err := version.NewResult(resultCniVersion, data) + if err != nil { + return nil, err + } + + // Convert to the config version to ensure plugins get prevResult + // in the same version as the config. The cached result version + // should match the config version unless the config was changed + // while the container was running. + result, err = result.GetAsVersion(cniVersion) + if err != nil && resultCniVersion != cniVersion { + return nil, fmt.Errorf("failed to convert cached result version %q to config version %q: %v", resultCniVersion, cniVersion, err) + } + return result, err +} + +// AddNetworkList executes a sequence of plugins with the ADD command +func (c *CNIConfig) AddNetworkList(list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) { + result, err := c.addOrGetNetworkList("ADD", nil, list, rt) + if err != nil { + return nil, err + } + + if err = setCachedResult(result, list.Name, rt); err != nil { + return nil, fmt.Errorf("failed to set network '%s' cached result: %v", list.Name, err) + } + + return result, nil +} + +// GetNetworkList executes a sequence of plugins with the GET command +func (c *CNIConfig) GetNetworkList(list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) { + // GET was added in CNI spec version 0.4.0 and higher + if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil { + return nil, err + } else if !gtet { + return nil, fmt.Errorf("configuration version %q does not support the GET command", list.CNIVersion) + } + + cachedResult, err := getCachedResult(list.Name, list.CNIVersion, rt) + if err != nil { + return nil, fmt.Errorf("failed to get network '%s' cached result: %v", list.Name, err) + } + return c.addOrGetNetworkList("GET", cachedResult, list, rt) +} + +func (c *CNIConfig) delNetwork(name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) error { + c.ensureExec() + pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path) + if err != nil { + return err + } + + newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt) + if err != nil { + return err + } + + return invoke.ExecPluginWithoutResult(pluginPath, newConf.Bytes, c.args("DEL", rt), c.exec) +} + // DelNetworkList executes a sequence of plugins with the DEL command func (c *CNIConfig) DelNetworkList(list *NetworkConfigList, rt *RuntimeConf) error { + var cachedResult types.Result + + // Cached result on DEL was added in CNI spec version 0.4.0 and higher + if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil { + return err + } else if gtet { + cachedResult, err = getCachedResult(list.Name, list.CNIVersion, rt) + if err != nil { + return fmt.Errorf("failed to get network '%s' cached result: %v", list.Name, err) + } + } + for i := len(list.Plugins) - 1; i >= 0; i-- { net := list.Plugins[i] - - pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path) - if err != nil { - return err - } - - newConf, err := buildOneConfig(list, net, nil, rt) - if err != nil { - return err - } - - if err := invoke.ExecPluginWithoutResult(pluginPath, newConf.Bytes, c.args("DEL", rt)); err != nil { + if err := c.delNetwork(list.Name, list.CNIVersion, net, cachedResult, rt); err != nil { return err } } + _ = delCachedResult(list.Name, rt) return nil } // AddNetwork executes the plugin with the ADD command func (c *CNIConfig) AddNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) { - pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path) + result, err := c.addOrGetNetwork("ADD", net.Network.Name, net.Network.CNIVersion, net, nil, rt) if err != nil { return nil, err } - net, err = injectRuntimeConfig(net, rt) - if err != nil { - return nil, err + if err = setCachedResult(result, net.Network.Name, rt); err != nil { + return nil, fmt.Errorf("failed to set network '%s' cached result: %v", net.Network.Name, err) } - return invoke.ExecPluginWithResult(pluginPath, net.Bytes, c.args("ADD", rt)) + return result, nil +} + +// GetNetwork executes the plugin with the GET command +func (c *CNIConfig) GetNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) { + // GET was added in CNI spec version 0.4.0 and higher + if gtet, err := version.GreaterThanOrEqualTo(net.Network.CNIVersion, "0.4.0"); err != nil { + return nil, err + } else if !gtet { + return nil, fmt.Errorf("configuration version %q does not support the GET command", net.Network.CNIVersion) + } + + cachedResult, err := getCachedResult(net.Network.Name, net.Network.CNIVersion, rt) + if err != nil { + return nil, fmt.Errorf("failed to get network '%s' cached result: %v", net.Network.Name, err) + } + return c.addOrGetNetwork("GET", net.Network.Name, net.Network.CNIVersion, net, cachedResult, rt) } // DelNetwork executes the plugin with the DEL command func (c *CNIConfig) DelNetwork(net *NetworkConfig, rt *RuntimeConf) error { - pluginPath, err := invoke.FindInPath(net.Network.Type, c.Path) - if err != nil { + var cachedResult types.Result + + // Cached result on DEL was added in CNI spec version 0.4.0 and higher + if gtet, err := version.GreaterThanOrEqualTo(net.Network.CNIVersion, "0.4.0"); err != nil { return err + } else if gtet { + cachedResult, err = getCachedResult(net.Network.Name, net.Network.CNIVersion, rt) + if err != nil { + return fmt.Errorf("failed to get network '%s' cached result: %v", net.Network.Name, err) + } } - net, err = injectRuntimeConfig(net, rt) - if err != nil { + if err := c.delNetwork(net.Network.Name, net.Network.CNIVersion, net, cachedResult, rt); err != nil { return err } - - return invoke.ExecPluginWithoutResult(pluginPath, net.Bytes, c.args("DEL", rt)) + _ = delCachedResult(net.Network.Name, rt) + return nil } // GetVersionInfo reports which versions of the CNI spec are supported by // the given plugin. func (c *CNIConfig) GetVersionInfo(pluginType string) (version.PluginInfo, error) { - pluginPath, err := invoke.FindInPath(pluginType, c.Path) + c.ensureExec() + pluginPath, err := c.exec.FindInPath(pluginType, c.Path) if err != nil { return nil, err } - return invoke.GetVersionInfo(pluginPath) + return invoke.GetVersionInfo(pluginPath, c.exec) } // ===== diff --git a/vendor/github.com/containernetworking/cni/libcni/conf.go b/vendor/github.com/containernetworking/cni/libcni/conf.go index c7738c66..9834d715 100644 --- a/vendor/github.com/containernetworking/cni/libcni/conf.go +++ b/vendor/github.com/containernetworking/cni/libcni/conf.go @@ -45,6 +45,9 @@ func ConfFromBytes(bytes []byte) (*NetworkConfig, error) { if err := json.Unmarshal(bytes, &conf.Network); err != nil { return nil, fmt.Errorf("error parsing configuration: %s", err) } + if conf.Network.Type == "" { + return nil, fmt.Errorf("error parsing configuration: missing 'type'") + } return conf, nil } diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go b/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go index c78a69ee..21efdf80 100644 --- a/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/delegate.go @@ -22,32 +22,54 @@ import ( "github.com/containernetworking/cni/pkg/types" ) -func DelegateAdd(delegatePlugin string, netconf []byte) (types.Result, error) { - if os.Getenv("CNI_COMMAND") != "ADD" { - return nil, fmt.Errorf("CNI_COMMAND is not ADD") +func delegateAddOrGet(command, delegatePlugin string, netconf []byte, exec Exec) (types.Result, error) { + if exec == nil { + exec = defaultExec } paths := filepath.SplitList(os.Getenv("CNI_PATH")) - - pluginPath, err := FindInPath(delegatePlugin, paths) + pluginPath, err := exec.FindInPath(delegatePlugin, paths) if err != nil { return nil, err } - return ExecPluginWithResult(pluginPath, netconf, ArgsFromEnv()) + return ExecPluginWithResult(pluginPath, netconf, ArgsFromEnv(), exec) } -func DelegateDel(delegatePlugin string, netconf []byte) error { +// DelegateAdd calls the given delegate plugin with the CNI ADD action and +// JSON configuration +func DelegateAdd(delegatePlugin string, netconf []byte, exec Exec) (types.Result, error) { + if os.Getenv("CNI_COMMAND") != "ADD" { + return nil, fmt.Errorf("CNI_COMMAND is not ADD") + } + return delegateAddOrGet("ADD", delegatePlugin, netconf, exec) +} + +// DelegateGet calls the given delegate plugin with the CNI GET action and +// JSON configuration +func DelegateGet(delegatePlugin string, netconf []byte, exec Exec) (types.Result, error) { + if os.Getenv("CNI_COMMAND") != "GET" { + return nil, fmt.Errorf("CNI_COMMAND is not GET") + } + return delegateAddOrGet("GET", delegatePlugin, netconf, exec) +} + +// DelegateDel calls the given delegate plugin with the CNI DEL action and +// JSON configuration +func DelegateDel(delegatePlugin string, netconf []byte, exec Exec) error { + if exec == nil { + exec = defaultExec + } + if os.Getenv("CNI_COMMAND") != "DEL" { return fmt.Errorf("CNI_COMMAND is not DEL") } paths := filepath.SplitList(os.Getenv("CNI_PATH")) - - pluginPath, err := FindInPath(delegatePlugin, paths) + pluginPath, err := exec.FindInPath(delegatePlugin, paths) if err != nil { return err } - return ExecPluginWithoutResult(pluginPath, netconf, ArgsFromEnv()) + return ExecPluginWithoutResult(pluginPath, netconf, ArgsFromEnv(), exec) } diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go b/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go index fc47e7c8..cf019d3a 100644 --- a/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/exec.go @@ -22,34 +22,62 @@ import ( "github.com/containernetworking/cni/pkg/version" ) -func ExecPluginWithResult(pluginPath string, netconf []byte, args CNIArgs) (types.Result, error) { - return defaultPluginExec.WithResult(pluginPath, netconf, args) +// Exec is an interface encapsulates all operations that deal with finding +// and executing a CNI plugin. Tests may provide a fake implementation +// to avoid writing fake plugins to temporary directories during the test. +type Exec interface { + ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) + FindInPath(plugin string, paths []string) (string, error) + Decode(jsonBytes []byte) (version.PluginInfo, error) } -func ExecPluginWithoutResult(pluginPath string, netconf []byte, args CNIArgs) error { - return defaultPluginExec.WithoutResult(pluginPath, netconf, args) -} +// For example, a testcase could pass an instance of the following fakeExec +// object to ExecPluginWithResult() to verify the incoming stdin and environment +// and provide a tailored response: +// +//import ( +// "encoding/json" +// "path" +// "strings" +//) +// +//type fakeExec struct { +// version.PluginDecoder +//} +// +//func (f *fakeExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) { +// net := &types.NetConf{} +// err := json.Unmarshal(stdinData, net) +// if err != nil { +// return nil, fmt.Errorf("failed to unmarshal configuration: %v", err) +// } +// pluginName := path.Base(pluginPath) +// if pluginName != net.Type { +// return nil, fmt.Errorf("plugin name %q did not match config type %q", pluginName, net.Type) +// } +// for _, e := range environ { +// // Check environment for forced failure request +// parts := strings.Split(e, "=") +// if len(parts) > 0 && parts[0] == "FAIL" { +// return nil, fmt.Errorf("failed to execute plugin %s", pluginName) +// } +// } +// return []byte("{\"CNIVersion\":\"0.4.0\"}"), nil +//} +// +//func (f *fakeExec) FindInPath(plugin string, paths []string) (string, error) { +// if len(paths) > 0 { +// return path.Join(paths[0], plugin), nil +// } +// return "", fmt.Errorf("failed to find plugin %s in paths %v", plugin, paths) +//} -func GetVersionInfo(pluginPath string) (version.PluginInfo, error) { - return defaultPluginExec.GetVersionInfo(pluginPath) -} - -var defaultPluginExec = &PluginExec{ - RawExec: &RawExec{Stderr: os.Stderr}, - VersionDecoder: &version.PluginDecoder{}, -} - -type PluginExec struct { - RawExec interface { - ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) +func ExecPluginWithResult(pluginPath string, netconf []byte, args CNIArgs, exec Exec) (types.Result, error) { + if exec == nil { + exec = defaultExec } - VersionDecoder interface { - Decode(jsonBytes []byte) (version.PluginInfo, error) - } -} -func (e *PluginExec) WithResult(pluginPath string, netconf []byte, args CNIArgs) (types.Result, error) { - stdoutBytes, err := e.RawExec.ExecPlugin(pluginPath, netconf, args.AsEnv()) + stdoutBytes, err := exec.ExecPlugin(pluginPath, netconf, args.AsEnv()) if err != nil { return nil, err } @@ -64,8 +92,11 @@ func (e *PluginExec) WithResult(pluginPath string, netconf []byte, args CNIArgs) return version.NewResult(confVersion, stdoutBytes) } -func (e *PluginExec) WithoutResult(pluginPath string, netconf []byte, args CNIArgs) error { - _, err := e.RawExec.ExecPlugin(pluginPath, netconf, args.AsEnv()) +func ExecPluginWithoutResult(pluginPath string, netconf []byte, args CNIArgs, exec Exec) error { + if exec == nil { + exec = defaultExec + } + _, err := exec.ExecPlugin(pluginPath, netconf, args.AsEnv()) return err } @@ -73,7 +104,10 @@ func (e *PluginExec) WithoutResult(pluginPath string, netconf []byte, args CNIAr // For recent-enough plugins, it uses the information returned by the VERSION // command. For older plugins which do not recognize that command, it reports // version 0.1.0 -func (e *PluginExec) GetVersionInfo(pluginPath string) (version.PluginInfo, error) { +func GetVersionInfo(pluginPath string, exec Exec) (version.PluginInfo, error) { + if exec == nil { + exec = defaultExec + } args := &Args{ Command: "VERSION", @@ -83,7 +117,7 @@ func (e *PluginExec) GetVersionInfo(pluginPath string) (version.PluginInfo, erro Path: "dummy", } stdin := []byte(fmt.Sprintf(`{"cniVersion":%q}`, version.Current())) - stdoutBytes, err := e.RawExec.ExecPlugin(pluginPath, stdin, args.AsEnv()) + stdoutBytes, err := exec.ExecPlugin(pluginPath, stdin, args.AsEnv()) if err != nil { if err.Error() == "unknown CNI_COMMAND: VERSION" { return version.PluginSupports("0.1.0"), nil @@ -91,5 +125,19 @@ func (e *PluginExec) GetVersionInfo(pluginPath string) (version.PluginInfo, erro return nil, err } - return e.VersionDecoder.Decode(stdoutBytes) + return exec.Decode(stdoutBytes) +} + +// DefaultExec is an object that implements the Exec interface which looks +// for and executes plugins from disk. +type DefaultExec struct { + *RawExec + version.PluginDecoder +} + +// DefaultExec implements the Exec interface +var _ Exec = &DefaultExec{} + +var defaultExec = &DefaultExec{ + RawExec: &RawExec{Stderr: os.Stderr}, } diff --git a/vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec.go b/vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec.go index 93f1e75d..a598f09c 100644 --- a/vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec.go +++ b/vendor/github.com/containernetworking/cni/pkg/invoke/raw_exec.go @@ -57,3 +57,7 @@ func pluginErr(err error, output []byte) error { return err } + +func (e *RawExec) FindInPath(plugin string, paths []string) (string, error) { + return FindInPath(plugin, paths) +} diff --git a/vendor/github.com/containernetworking/cni/pkg/skel/skel.go b/vendor/github.com/containernetworking/cni/pkg/skel/skel.go index 8644c25e..e565c85d 100644 --- a/vendor/github.com/containernetworking/cni/pkg/skel/skel.go +++ b/vendor/github.com/containernetworking/cni/pkg/skel/skel.go @@ -17,6 +17,8 @@ package skel import ( + "bytes" + "encoding/json" "fmt" "io" "io/ioutil" @@ -63,6 +65,7 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, error) { &cmd, reqForCmdEntry{ "ADD": true, + "GET": true, "DEL": true, }, }, @@ -70,8 +73,9 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, error) { "CNI_CONTAINERID", &contID, reqForCmdEntry{ - "ADD": false, - "DEL": false, + "ADD": true, + "GET": true, + "DEL": true, }, }, { @@ -79,6 +83,7 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, error) { &netns, reqForCmdEntry{ "ADD": true, + "GET": true, "DEL": false, }, }, @@ -87,6 +92,7 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, error) { &ifName, reqForCmdEntry{ "ADD": true, + "GET": true, "DEL": true, }, }, @@ -95,6 +101,7 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, error) { &args, reqForCmdEntry{ "ADD": false, + "GET": false, "DEL": false, }, }, @@ -103,6 +110,7 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, error) { &path, reqForCmdEntry{ "ADD": true, + "GET": true, "DEL": true, }, }, @@ -123,6 +131,10 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, error) { return "", nil, fmt.Errorf("required env variables missing") } + if cmd == "VERSION" { + t.Stdin = bytes.NewReader(nil) + } + stdinData, err := ioutil.ReadAll(t.Stdin) if err != nil { return "", nil, fmt.Errorf("error reading from stdin: %v", err) @@ -159,18 +171,71 @@ func (t *dispatcher) checkVersionAndCall(cmdArgs *CmdArgs, pluginVersionInfo ver Details: verErr.Details(), } } + return toCall(cmdArgs) } -func (t *dispatcher) pluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo) *types.Error { +func validateConfig(jsonBytes []byte) error { + var conf struct { + Name string `json:"name"` + } + if err := json.Unmarshal(jsonBytes, &conf); err != nil { + return fmt.Errorf("error reading network config: %s", err) + } + if conf.Name == "" { + return fmt.Errorf("missing network name") + } + return nil +} + +func (t *dispatcher) pluginMain(cmdAdd, cmdGet, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo, about string) *types.Error { cmd, cmdArgs, err := t.getCmdArgsFromEnv() if err != nil { + // Print the about string to stderr when no command is set + if t.Getenv("CNI_COMMAND") == "" && about != "" { + fmt.Fprintln(t.Stderr, about) + } return createTypedError(err.Error()) } + if cmd != "VERSION" { + err = validateConfig(cmdArgs.StdinData) + if err != nil { + return createTypedError(err.Error()) + } + } + switch cmd { case "ADD": err = t.checkVersionAndCall(cmdArgs, versionInfo, cmdAdd) + case "GET": + configVersion, err := t.ConfVersionDecoder.Decode(cmdArgs.StdinData) + if err != nil { + return createTypedError(err.Error()) + } + if gtet, err := version.GreaterThanOrEqualTo(configVersion, "0.4.0"); err != nil { + return createTypedError(err.Error()) + } else if !gtet { + return &types.Error{ + Code: types.ErrIncompatibleCNIVersion, + Msg: "config version does not allow GET", + } + } + for _, pluginVersion := range versionInfo.SupportedVersions() { + gtet, err := version.GreaterThanOrEqualTo(pluginVersion, configVersion) + if err != nil { + return createTypedError(err.Error()) + } else if gtet { + if err := t.checkVersionAndCall(cmdArgs, versionInfo, cmdGet); err != nil { + return createTypedError(err.Error()) + } + return nil + } + } + return &types.Error{ + Code: types.ErrIncompatibleCNIVersion, + Msg: "plugin version does not allow GET", + } case "DEL": err = t.checkVersionAndCall(cmdArgs, versionInfo, cmdDel) case "VERSION": @@ -190,7 +255,7 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error, versionIn } // PluginMainWithError is the core "main" for a plugin. It accepts -// callback functions for add and del CNI commands and returns an error. +// callback functions for add, get, and del CNI commands and returns an error. // // The caller must also specify what CNI spec versions the plugin supports. // @@ -201,25 +266,28 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error, versionIn // // To let this package automatically handle errors and call os.Exit(1) for you, // use PluginMain() instead. -func PluginMainWithError(cmdAdd, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo) *types.Error { +func PluginMainWithError(cmdAdd, cmdGet, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo, about string) *types.Error { return (&dispatcher{ Getenv: os.Getenv, Stdin: os.Stdin, Stdout: os.Stdout, Stderr: os.Stderr, - }).pluginMain(cmdAdd, cmdDel, versionInfo) + }).pluginMain(cmdAdd, cmdGet, cmdDel, versionInfo, about) } // PluginMain is the core "main" for a plugin which includes automatic error handling. // // The caller must also specify what CNI spec versions the plugin supports. // -// When an error occurs in either cmdAdd or cmdDel, PluginMain will print the error +// The caller can specify an "about" string, which is printed on stderr +// when no CNI_COMMAND is specified. The reccomended output is "CNI plugin v" +// +// When an error occurs in either cmdAdd, cmdGet, or cmdDel, PluginMain will print the error // as JSON to stdout and call os.Exit(1). // // To have more control over error handling, use PluginMainWithError() instead. -func PluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo) { - if e := PluginMainWithError(cmdAdd, cmdDel, versionInfo); e != nil { +func PluginMain(cmdAdd, cmdGet, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo, about string) { + if e := PluginMainWithError(cmdAdd, cmdGet, cmdDel, versionInfo, about); e != nil { if err := e.Print(); err != nil { log.Print("Error writing error JSON to stdout: ", err) } diff --git a/vendor/github.com/containernetworking/cni/pkg/types/current/types.go b/vendor/github.com/containernetworking/cni/pkg/types/current/types.go index caac92ba..92980c1a 100644 --- a/vendor/github.com/containernetworking/cni/pkg/types/current/types.go +++ b/vendor/github.com/containernetworking/cni/pkg/types/current/types.go @@ -24,9 +24,9 @@ import ( "github.com/containernetworking/cni/pkg/types/020" ) -const ImplementedSpecVersion string = "0.3.1" +const ImplementedSpecVersion string = "0.4.0" -var SupportedVersions = []string{"0.3.0", ImplementedSpecVersion} +var SupportedVersions = []string{"0.3.0", "0.3.1", ImplementedSpecVersion} func NewResult(data []byte) (types.Result, error) { result := &Result{} @@ -196,7 +196,7 @@ func (r *Result) Version() string { func (r *Result) GetAsVersion(version string) (types.Result, error) { switch version { - case "0.3.0", ImplementedSpecVersion: + case "0.3.0", "0.3.1", ImplementedSpecVersion: r.CNIVersion = version return r, nil case types020.SupportedVersions[0], types020.SupportedVersions[1], types020.SupportedVersions[2]: diff --git a/vendor/github.com/containernetworking/cni/pkg/types/types.go b/vendor/github.com/containernetworking/cni/pkg/types/types.go index 64127560..4684a320 100644 --- a/vendor/github.com/containernetworking/cni/pkg/types/types.go +++ b/vendor/github.com/containernetworking/cni/pkg/types/types.go @@ -63,10 +63,12 @@ type NetConf struct { Name string `json:"name,omitempty"` Type string `json:"type,omitempty"` Capabilities map[string]bool `json:"capabilities,omitempty"` - IPAM struct { - Type string `json:"type,omitempty"` - } `json:"ipam,omitempty"` - DNS DNS `json:"dns"` + IPAM IPAM `json:"ipam,omitempty"` + DNS DNS `json:"dns"` +} + +type IPAM struct { + Type string `json:"type,omitempty"` } // NetConfList describes an ordered list of networks. @@ -167,7 +169,7 @@ func (r *Route) UnmarshalJSON(data []byte) error { return nil } -func (r *Route) MarshalJSON() ([]byte, error) { +func (r Route) MarshalJSON() ([]byte, error) { rt := route{ Dst: IPNet(r.Dst), GW: r.GW, diff --git a/vendor/github.com/containernetworking/cni/pkg/version/plugin.go b/vendor/github.com/containernetworking/cni/pkg/version/plugin.go index 8a467281..612335a8 100644 --- a/vendor/github.com/containernetworking/cni/pkg/version/plugin.go +++ b/vendor/github.com/containernetworking/cni/pkg/version/plugin.go @@ -18,6 +18,8 @@ import ( "encoding/json" "fmt" "io" + "strconv" + "strings" ) // PluginInfo reports information about CNI versioning @@ -79,3 +81,60 @@ func (*PluginDecoder) Decode(jsonBytes []byte) (PluginInfo, error) { } return &info, nil } + +// ParseVersion parses a version string like "3.0.1" or "0.4.5" into major, +// minor, and micro numbers or returns an error +func ParseVersion(version string) (int, int, int, error) { + var major, minor, micro int + parts := strings.Split(version, ".") + if len(parts) == 0 || len(parts) >= 4 { + return -1, -1, -1, fmt.Errorf("invalid version %q: too many or too few parts", version) + } + + major, err := strconv.Atoi(parts[0]) + if err != nil { + return -1, -1, -1, fmt.Errorf("failed to convert major version part %q: %v", parts[0], err) + } + + if len(parts) >= 2 { + minor, err = strconv.Atoi(parts[1]) + if err != nil { + return -1, -1, -1, fmt.Errorf("failed to convert minor version part %q: %v", parts[1], err) + } + } + + if len(parts) >= 3 { + micro, err = strconv.Atoi(parts[2]) + if err != nil { + return -1, -1, -1, fmt.Errorf("failed to convert micro version part %q: %v", parts[2], err) + } + } + + return major, minor, micro, nil +} + +// GreaterThanOrEqualTo takes two string versions, parses them into major/minor/micro +// nubmers, and compares them to determine whether the first version is greater +// than or equal to the second +func GreaterThanOrEqualTo(version, otherVersion string) (bool, error) { + firstMajor, firstMinor, firstMicro, err := ParseVersion(version) + if err != nil { + return false, err + } + + secondMajor, secondMinor, secondMicro, err := ParseVersion(otherVersion) + if err != nil { + return false, err + } + + if firstMajor > secondMajor { + return true, nil + } else if firstMajor == secondMajor { + if firstMinor > secondMinor { + return true, nil + } else if firstMinor == secondMinor && firstMicro >= secondMicro { + return true, nil + } + } + return false, nil +} diff --git a/vendor/github.com/containernetworking/cni/pkg/version/version.go b/vendor/github.com/containernetworking/cni/pkg/version/version.go index efe8ea87..c8e46d55 100644 --- a/vendor/github.com/containernetworking/cni/pkg/version/version.go +++ b/vendor/github.com/containernetworking/cni/pkg/version/version.go @@ -24,7 +24,7 @@ import ( // Current reports the version of the CNI spec implemented by this library func Current() string { - return "0.3.1" + return "0.4.0" } // Legacy PluginInfo describes a plugin that is backwards compatible with the @@ -35,7 +35,7 @@ func Current() string { // Any future CNI spec versions which meet this definition should be added to // this list. var Legacy = PluginSupports("0.1.0", "0.2.0") -var All = PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.3.1") +var All = PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0") var resultFactories = []struct { supportedVersions []string diff --git a/vendor/github.com/coreos/go-systemd/NOTICE b/vendor/github.com/coreos/go-systemd/NOTICE new file mode 100644 index 00000000..23a0ada2 --- /dev/null +++ b/vendor/github.com/coreos/go-systemd/NOTICE @@ -0,0 +1,5 @@ +CoreOS Project +Copyright 2018 CoreOS, Inc + +This product includes software developed at CoreOS, Inc. +(http://www.coreos.com/). diff --git a/vendor/github.com/coreos/go-systemd/activation/files.go b/vendor/github.com/coreos/go-systemd/activation/files.go index c8e85fcd..29dd18de 100644 --- a/vendor/github.com/coreos/go-systemd/activation/files.go +++ b/vendor/github.com/coreos/go-systemd/activation/files.go @@ -18,18 +18,26 @@ package activation import ( "os" "strconv" + "strings" "syscall" ) -// based on: https://gist.github.com/alberts/4640792 const ( + // listenFdsStart corresponds to `SD_LISTEN_FDS_START`. listenFdsStart = 3 ) +// Files returns a slice containing a `os.File` object for each +// file descriptor passed to this process via systemd fd-passing protocol. +// +// The order of the file descriptors is preserved in the returned slice. +// `unsetEnv` is typically set to `true` in order to avoid clashes in +// fd usage and to avoid leaking environment flags to child processes. func Files(unsetEnv bool) []*os.File { if unsetEnv { defer os.Unsetenv("LISTEN_PID") defer os.Unsetenv("LISTEN_FDS") + defer os.Unsetenv("LISTEN_FDNAMES") } pid, err := strconv.Atoi(os.Getenv("LISTEN_PID")) @@ -42,10 +50,17 @@ func Files(unsetEnv bool) []*os.File { return nil } + names := strings.Split(os.Getenv("LISTEN_FDNAMES"), ":") + files := make([]*os.File, 0, nfds) for fd := listenFdsStart; fd < listenFdsStart+nfds; fd++ { syscall.CloseOnExec(fd) - files = append(files, os.NewFile(uintptr(fd), "LISTEN_FD_"+strconv.Itoa(fd))) + name := "LISTEN_FD_" + strconv.Itoa(fd) + offset := fd - listenFdsStart + if offset < len(names) && len(names[offset]) > 0 { + name = names[offset] + } + files = append(files, os.NewFile(uintptr(fd), name)) } return files diff --git a/vendor/github.com/coreos/go-systemd/activation/listeners.go b/vendor/github.com/coreos/go-systemd/activation/listeners.go index a30cb893..bb5cc231 100644 --- a/vendor/github.com/coreos/go-systemd/activation/listeners.go +++ b/vendor/github.com/coreos/go-systemd/activation/listeners.go @@ -15,6 +15,7 @@ package activation import ( + "crypto/tls" "net" ) @@ -24,14 +25,79 @@ import ( // The order of the file descriptors is preserved in the returned slice. // Nil values are used to fill any gaps. For example if systemd were to return file descriptors // corresponding with "udp, tcp, tcp", then the slice would contain {nil, net.Listener, net.Listener} -func Listeners(unsetEnv bool) ([]net.Listener, error) { - files := Files(unsetEnv) +func Listeners() ([]net.Listener, error) { + files := Files(true) listeners := make([]net.Listener, len(files)) for i, f := range files { if pc, err := net.FileListener(f); err == nil { listeners[i] = pc + f.Close() } } return listeners, nil } + +// ListenersWithNames maps a listener name to a set of net.Listener instances. +func ListenersWithNames() (map[string][]net.Listener, error) { + files := Files(true) + listeners := map[string][]net.Listener{} + + for _, f := range files { + if pc, err := net.FileListener(f); err == nil { + current, ok := listeners[f.Name()] + if !ok { + listeners[f.Name()] = []net.Listener{pc} + } else { + listeners[f.Name()] = append(current, pc) + } + f.Close() + } + } + return listeners, nil +} + +// TLSListeners returns a slice containing a net.listener for each matching TCP socket type +// passed to this process. +// It uses default Listeners func and forces TCP sockets handlers to use TLS based on tlsConfig. +func TLSListeners(tlsConfig *tls.Config) ([]net.Listener, error) { + listeners, err := Listeners() + + if listeners == nil || err != nil { + return nil, err + } + + if tlsConfig != nil && err == nil { + for i, l := range listeners { + // Activate TLS only for TCP sockets + if l.Addr().Network() == "tcp" { + listeners[i] = tls.NewListener(l, tlsConfig) + } + } + } + + return listeners, err +} + +// TLSListenersWithNames maps a listener name to a net.Listener with +// the associated TLS configuration. +func TLSListenersWithNames(tlsConfig *tls.Config) (map[string][]net.Listener, error) { + listeners, err := ListenersWithNames() + + if listeners == nil || err != nil { + return nil, err + } + + if tlsConfig != nil && err == nil { + for _, ll := range listeners { + // Activate TLS only for TCP sockets + for i, l := range ll { + if l.Addr().Network() == "tcp" { + ll[i] = tls.NewListener(l, tlsConfig) + } + } + } + } + + return listeners, err +} diff --git a/vendor/github.com/coreos/go-systemd/activation/packetconns.go b/vendor/github.com/coreos/go-systemd/activation/packetconns.go index 48b2ca02..a9720678 100644 --- a/vendor/github.com/coreos/go-systemd/activation/packetconns.go +++ b/vendor/github.com/coreos/go-systemd/activation/packetconns.go @@ -24,13 +24,14 @@ import ( // The order of the file descriptors is preserved in the returned slice. // Nil values are used to fill any gaps. For example if systemd were to return file descriptors // corresponding with "udp, tcp, udp", then the slice would contain {net.PacketConn, nil, net.PacketConn} -func PacketConns(unsetEnv bool) ([]net.PacketConn, error) { - files := Files(unsetEnv) +func PacketConns() ([]net.PacketConn, error) { + files := Files(true) conns := make([]net.PacketConn, len(files)) for i, f := range files { if pc, err := net.FilePacketConn(f); err == nil { conns[i] = pc + f.Close() } } return conns, nil