testing: test invocation of newer plugins with an older libcni
authorCasey Callendrello <c1@caseyc.net>
Fri, 14 Oct 2016 14:38:32 +0000 (16:38 +0200)
committerCasey Callendrello <c1@caseyc.net>
Tue, 25 Oct 2016 14:33:35 +0000 (16:33 +0200)
libcni/api_test.go
libcni/backwards_compatibility_test.go
libcni/libcni_suite_test.go
pkg/version/legacy_examples/example_runtime.go [new file with mode: 0644]

index 9aeb52a..c71d197 100644 (file)
@@ -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{
index c13b23a..ceb9f35 100644 (file)
@@ -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))
+               }
+       })
 })
index f78977b..bc4ecc5 100644 (file)
 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 (file)
index 0000000..a461981
--- /dev/null
@@ -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
+}
+`,
+       },
+}