diff --git a/libcni/api_test.go b/libcni/api_test.go index 9aeb52a4..c71d1971 100644 --- a/libcni/api_test.go +++ b/libcni/api_test.go @@ -52,7 +52,7 @@ var _ = Describe("Invoking the plugin", func() { } Expect(debug.WriteDebug(debugFilePath)).To(Succeed()) - cniBinPath = filepath.Dir(pathToPlugin) + cniBinPath = filepath.Dir(pluginPaths["noop"]) pluginConfig = `{ "type": "noop", "some-key": "some-value", "cniVersion": "0.2.0" }` cniConfig = libcni.CNIConfig{Path: []string{cniBinPath}} netConfig = &libcni.NetworkConfig{ diff --git a/libcni/backwards_compatibility_test.go b/libcni/backwards_compatibility_test.go index c13b23a1..ceb9f358 100644 --- a/libcni/backwards_compatibility_test.go +++ b/libcni/backwards_compatibility_test.go @@ -17,12 +17,15 @@ package libcni_test import ( "fmt" "os" + "os/exec" "path/filepath" + "strings" "github.com/containernetworking/cni/libcni" "github.com/containernetworking/cni/pkg/version/legacy_examples" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/onsi/gomega/gexec" ) var _ = Describe("Backwards compatibility", func() { @@ -50,4 +53,31 @@ var _ = Describe("Backwards compatibility", func() { Expect(os.RemoveAll(pluginPath)).To(Succeed()) }) + + It("correctly handles the request from a runtime with an older libcni", func() { + // We need to be root (or have CAP_SYS_ADMIN...) + if os.Geteuid() != 0 { + Fail("must be run as root") + } + + example := legacy_examples.V010_Runtime + + binPath, err := example.Build() + Expect(err).NotTo(HaveOccurred()) + + for _, configName := range example.NetConfs { + configStr, ok := legacy_examples.NetConfs[configName] + if !ok { + Fail("Invalid config name " + configName) + } + + cmd := exec.Command(binPath, pluginDirs...) + cmd.Stdin = strings.NewReader(configStr) + + session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter) + Expect(err).NotTo(HaveOccurred()) + + Eventually(session).Should(gexec.Exit(0)) + } + }) }) diff --git a/libcni/libcni_suite_test.go b/libcni/libcni_suite_test.go index f78977bf..bc4ecc5e 100644 --- a/libcni/libcni_suite_test.go +++ b/libcni/libcni_suite_test.go @@ -15,6 +15,10 @@ package libcni_test import ( + "fmt" + "path/filepath" + "strings" + . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" @@ -27,17 +31,35 @@ func TestLibcni(t *testing.T) { RunSpecs(t, "Libcni Suite") } -const packagePath = "github.com/containernetworking/cni/plugins/test/noop" +var plugins = map[string]string{ + "noop": "github.com/containernetworking/cni/plugins/test/noop", + "ptp": "github.com/containernetworking/cni/plugins/main/ptp", + "host-local": "github.com/containernetworking/cni/plugins/ipam/host-local", +} -var pathToPlugin string +var pluginPaths map[string]string +var pluginDirs []string // array of plugin dirs var _ = SynchronizedBeforeSuite(func() []byte { - var err error - pathToPlugin, err = gexec.Build(packagePath) - Expect(err).NotTo(HaveOccurred()) - return []byte(pathToPlugin) + dirs := make([]string, 0, len(plugins)) + + for name, packagePath := range plugins { + execPath, err := gexec.Build(packagePath) + Expect(err).NotTo(HaveOccurred()) + dirs = append(dirs, fmt.Sprintf("%s=%s", name, execPath)) + } + + return []byte(strings.Join(dirs, ":")) }, func(crossNodeData []byte) { - pathToPlugin = string(crossNodeData) + pluginPaths = make(map[string]string) + for _, str := range strings.Split(string(crossNodeData), ":") { + kvs := strings.SplitN(str, "=", 2) + if len(kvs) != 2 { + Fail("Invalid inter-node data...") + } + pluginPaths[kvs[0]] = kvs[1] + pluginDirs = append(pluginDirs, filepath.Dir(kvs[1])) + } }) var _ = SynchronizedAfterSuite(func() {}, func() { diff --git a/pkg/version/legacy_examples/example_runtime.go b/pkg/version/legacy_examples/example_runtime.go new file mode 100644 index 00000000..a461981f --- /dev/null +++ b/pkg/version/legacy_examples/example_runtime.go @@ -0,0 +1,167 @@ +// Copyright 2016 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 legacy_examples + +// An ExampleRuntime is a small program that uses libcni to invoke a network plugin. +// It should call ADD and DELETE, verifying all intermediate steps +// and data structures. +type ExampleRuntime struct { + Example + NetConfs []string // The network configuration names to pass +} + +// NetConfs are various versioned network configuration files. Examples should +// specify which version they expect +var NetConfs = map[string]string{ + "unversioned": `{ + "name": "default", + "type": "ptp", + "ipam": { + "type": "host-local", + "subnet": "10.1.2.0/24" + } +}`, + "0.1.0": `{ + "cniVersion": "0.1.0", + "name": "default", + "type": "ptp", + "ipam": { + "type": "host-local", + "subnet": "10.1.2.0/24" + } +}`, +} + +// V010_Runtime creates a simple ptp network configuration, then +// executes libcni against the currently-built plugins. +var V010_Runtime = ExampleRuntime{ + NetConfs: []string{"unversioned", "0.1.0"}, + Example: Example{ + Name: "example_invoker_v010", + CNIRepoGitRef: "c0d34c69", //version with ns.Do + PluginSource: `package main + +import ( + "fmt" + "io/ioutil" + "net" + "os" + + "github.com/containernetworking/cni/pkg/ns" + "github.com/containernetworking/cni/libcni" +) + +func main(){ + code := exec() + os.Exit(code) +} + +func exec() int { + confBytes, err := ioutil.ReadAll(os.Stdin) + if err != nil { + fmt.Printf("could not read netconfig from stdin: %+v", err) + return 1 + } + + netConf, err := libcni.ConfFromBytes(confBytes) + if err != nil { + fmt.Printf("could not parse netconfig: %+v", err) + return 1 + } + fmt.Printf("Parsed network configuration: %+v\n", netConf.Network) + + if len(os.Args) == 1 { + fmt.Printf("Expect CNI plugin paths in argv") + return 1 + } + + targetNs, err := ns.NewNS() + if err != nil { + fmt.Printf("Could not create ns: %+v", err) + return 1 + } + defer targetNs.Close() + + ifName := "eth0" + + runtimeConf := &libcni.RuntimeConf{ + ContainerID: "some-container-id", + NetNS: targetNs.Path(), + IfName: ifName, + } + + cniConfig := &libcni.CNIConfig{Path: os.Args[1:]} + + result, err := cniConfig.AddNetwork(netConf, runtimeConf) + if err != nil { + fmt.Printf("AddNetwork failed: %+v", err) + return 2 + } + fmt.Printf("AddNetwork result: %+v", result) + + expectedIP := result.IP4.IP + + err = targetNs.Do(func(ns.NetNS) error { + netif, err := net.InterfaceByName(ifName) + if err != nil { + return fmt.Errorf("could not retrieve interface: %v", err) + } + + addrs, err := netif.Addrs() + if err != nil { + return fmt.Errorf("could not retrieve addresses, %+v", err) + } + + found := false + for _, addr := range addrs { + if addr.String() == expectedIP.String() { + found = true + break + } + } + + if !found { + return fmt.Errorf("Far-side link did not have expected address %s", expectedIP) + } + return nil + }) + if err != nil { + fmt.Println(err) + return 4 + } + + err = cniConfig.DelNetwork(netConf, runtimeConf) + if err != nil { + fmt.Printf("DelNetwork failed: %v", err) + return 5 + } + + err = targetNs.Do(func(ns.NetNS) error { + _, err := net.InterfaceByName(ifName) + if err == nil { + return fmt.Errorf("interface was not deleted") + } + return nil + }) + if err != nil { + fmt.Println(err) + return 6 + } + + return 0 +} +`, + }, +}