plugins: adds new no-op plugin that may be used as a test-double
authorGabe Rosenhouse <rosenhouse@gmail.com>
Thu, 14 Jul 2016 20:59:10 +0000 (13:59 -0700)
committerGabe Rosenhouse <rosenhouse@gmail.com>
Fri, 15 Jul 2016 19:48:02 +0000 (12:48 -0700)
Plugin can be configured to record all inputs and to respond with
arbitrary stdout or error message.  Will support upcoming integration
testing.

build
pkg/testutils/bad_reader.go
plugins/test/noop/debug/debug.go [new file with mode: 0644]
plugins/test/noop/main.go [new file with mode: 0644]
plugins/test/noop/noop_suite_test.go [new file with mode: 0644]
plugins/test/noop/noop_test.go [new file with mode: 0644]
test

diff --git a/build b/build
index 4f5cfc7..f1faf26 100755 (executable)
--- a/build
+++ b/build
@@ -20,7 +20,7 @@ echo "Building reference CLI"
 go install "$@" ${REPO_PATH}/cnitool
 
 echo "Building plugins"
-PLUGINS="plugins/meta/* plugins/main/* plugins/ipam/*"
+PLUGINS="plugins/meta/* plugins/main/* plugins/ipam/* plugins/test/*"
 for d in $PLUGINS; do
        if [ -d $d ]; then
                plugin=$(basename $d)
index ca06c5e..f9d0ade 100644 (file)
@@ -16,6 +16,7 @@ package testutils
 
 import "errors"
 
+// BadReader is an io.Reader which always errors
 type BadReader struct {
        Error error
 }
diff --git a/plugins/test/noop/debug/debug.go b/plugins/test/noop/debug/debug.go
new file mode 100644 (file)
index 0000000..d9fb84c
--- /dev/null
@@ -0,0 +1,62 @@
+// 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.
+
+// debug supports tests that use the noop plugin
+package debug
+
+import (
+       "encoding/json"
+       "io/ioutil"
+
+       "github.com/containernetworking/cni/pkg/skel"
+)
+
+// Debug is used to control and record the behavior of the noop plugin
+type Debug struct {
+       ReportResult string
+       ReportError  string
+       Command      string
+       CmdArgs      skel.CmdArgs
+}
+
+// ReadDebug will return a debug file recorded by the noop plugin
+func ReadDebug(debugFilePath string) (*Debug, error) {
+       debugBytes, err := ioutil.ReadFile(debugFilePath)
+       if err != nil {
+               return nil, err
+       }
+
+       var debug Debug
+       err = json.Unmarshal(debugBytes, &debug)
+       if err != nil {
+               return nil, err
+       }
+
+       return &debug, nil
+}
+
+// WriteDebug will create a debug file to control the noop plugin
+func (debug *Debug) WriteDebug(debugFilePath string) error {
+       debugBytes, err := json.Marshal(debug)
+       if err != nil {
+               return err
+       }
+
+       err = ioutil.WriteFile(debugFilePath, debugBytes, 0600)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
diff --git a/plugins/test/noop/main.go b/plugins/test/noop/main.go
new file mode 100644 (file)
index 0000000..d0db902
--- /dev/null
@@ -0,0 +1,73 @@
+// 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.
+
+/*
+Noop plugin is a CNI plugin designed for use as a test-double.
+
+When calling, set the CNI_ARGS env var equal to the path of a file containing
+the JSON encoding of a Debug.
+*/
+
+package main
+
+import (
+       "errors"
+       "fmt"
+       "os"
+       "strings"
+
+       "github.com/containernetworking/cni/pkg/skel"
+       "github.com/containernetworking/cni/plugins/test/noop/debug"
+)
+
+func debugBehavior(args *skel.CmdArgs, command string) error {
+       if !strings.HasPrefix(args.Args, "DEBUG=") {
+               fmt.Printf(`{}`)
+               os.Stderr.WriteString("CNI_ARGS empty, no debug behavior\n")
+               return nil
+       }
+       debugFilePath := strings.TrimPrefix(args.Args, "DEBUG=")
+       debug, err := debug.ReadDebug(debugFilePath)
+       if err != nil {
+               return err
+       }
+
+       debug.CmdArgs = *args
+       debug.Command = command
+
+       err = debug.WriteDebug(debugFilePath)
+       if err != nil {
+               return err
+       }
+
+       if debug.ReportError != "" {
+               return errors.New(debug.ReportError)
+       } else {
+               os.Stdout.WriteString(debug.ReportResult)
+       }
+
+       return nil
+}
+
+func cmdAdd(args *skel.CmdArgs) error {
+       return debugBehavior(args, "ADD")
+}
+
+func cmdDel(args *skel.CmdArgs) error {
+       return debugBehavior(args, "DEL")
+}
+
+func main() {
+       skel.PluginMain(cmdAdd, cmdDel)
+}
diff --git a/plugins/test/noop/noop_suite_test.go b/plugins/test/noop/noop_suite_test.go
new file mode 100644 (file)
index 0000000..7d2cb32
--- /dev/null
@@ -0,0 +1,45 @@
+// 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 main_test
+
+import (
+       . "github.com/onsi/ginkgo"
+       . "github.com/onsi/gomega"
+       "github.com/onsi/gomega/gexec"
+
+       "testing"
+)
+
+func TestNoop(t *testing.T) {
+       RegisterFailHandler(Fail)
+       RunSpecs(t, "No-op plugin Suite")
+}
+
+const packagePath = "github.com/containernetworking/cni/plugins/test/noop"
+
+var pathToPlugin string
+
+var _ = SynchronizedBeforeSuite(func() []byte {
+       var err error
+       pathToPlugin, err = gexec.Build(packagePath)
+       Expect(err).NotTo(HaveOccurred())
+       return []byte(pathToPlugin)
+}, func(crossNodeData []byte) {
+       pathToPlugin = string(crossNodeData)
+})
+
+var _ = SynchronizedAfterSuite(func() {}, func() {
+       gexec.CleanupBuildArtifacts()
+})
diff --git a/plugins/test/noop/noop_test.go b/plugins/test/noop/noop_test.go
new file mode 100644 (file)
index 0000000..c4c35b4
--- /dev/null
@@ -0,0 +1,135 @@
+// 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 main_test
+
+import (
+       "io/ioutil"
+       "os"
+       "os/exec"
+       "strings"
+
+       "github.com/containernetworking/cni/pkg/skel"
+       noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
+       . "github.com/onsi/ginkgo"
+       . "github.com/onsi/gomega"
+       "github.com/onsi/gomega/gexec"
+)
+
+var _ = Describe("No-op plugin", func() {
+       var (
+               cmd           *exec.Cmd
+               debugFileName string
+               debug         *noop_debug.Debug
+       )
+
+       BeforeEach(func() {
+               debug = &noop_debug.Debug{
+                       ReportResult: `{ "ip4": { "ip": "10.1.2.3/24" }, "dns": {} }`,
+               }
+
+               debugFile, err := ioutil.TempFile("", "cni_debug")
+               Expect(err).NotTo(HaveOccurred())
+               Expect(debugFile.Close()).To(Succeed())
+               debugFileName = debugFile.Name()
+
+               Expect(debug.WriteDebug(debugFileName)).To(Succeed())
+
+               cmd = exec.Command(pathToPlugin)
+               cmd.Env = []string{
+                       "CNI_COMMAND=ADD",
+                       "CNI_CONTAINERID=some-container-id",
+                       "CNI_ARGS=DEBUG=" + debugFile.Name(),
+                       "CNI_NETNS=/some/netns/path",
+                       "CNI_IFNAME=some-eth0",
+                       "CNI_PATH=/some/bin/path",
+               }
+               cmd.Stdin = strings.NewReader(`{"some":"stdin-json"}`)
+       })
+
+       AfterEach(func() {
+               os.Remove(debugFileName)
+       })
+
+       It("responds to ADD using the ReportResult debug field", func() {
+               session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
+               Expect(err).NotTo(HaveOccurred())
+               Eventually(session).Should(gexec.Exit(0))
+               Expect(session.Out.Contents()).To(MatchJSON(`{
+                                "ip4": { "ip": "10.1.2.3/24" },
+                                "dns": {}
+      }`))
+       })
+
+       It("records all the args provided by skel.PluginMain", func() {
+               session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
+               Expect(err).NotTo(HaveOccurred())
+               Eventually(session).Should(gexec.Exit(0))
+
+               debug, err := noop_debug.ReadDebug(debugFileName)
+               Expect(err).NotTo(HaveOccurred())
+               Expect(debug.Command).To(Equal("ADD"))
+               Expect(debug.CmdArgs).To(Equal(skel.CmdArgs{
+                       ContainerID: "some-container-id",
+                       Netns:       "/some/netns/path",
+                       IfName:      "some-eth0",
+                       Args:        "DEBUG=" + debugFileName,
+                       Path:        "/some/bin/path",
+                       StdinData:   []byte(`{"some":"stdin-json"}`),
+               }))
+       })
+
+       Context("when the ReportError debug field is set", func() {
+               BeforeEach(func() {
+                       debug.ReportError = "banana"
+                       Expect(debug.WriteDebug(debugFileName)).To(Succeed())
+               })
+
+               It("returns an error to skel.PluginMain, causing the process to exit code 1", func() {
+                       session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
+                       Expect(err).NotTo(HaveOccurred())
+                       Eventually(session).Should(gexec.Exit(1))
+                       Expect(session.Out.Contents()).To(MatchJSON(`{ "code": 100, "msg": "banana" }`))
+               })
+       })
+
+       Context("when the CNI_COMMAND is DEL", func() {
+               BeforeEach(func() {
+                       cmd.Env[0] = "CNI_COMMAND=DEL"
+                       debug.ReportResult = `{ "some": "delete-data" }`
+                       Expect(debug.WriteDebug(debugFileName)).To(Succeed())
+               })
+
+               It("still does all the debug behavior", func() {
+                       session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
+                       Expect(err).NotTo(HaveOccurred())
+                       Eventually(session).Should(gexec.Exit(0))
+                       Expect(session.Out.Contents()).To(MatchJSON(`{
+                               "some": "delete-data"
+      }`))
+                       debug, err := noop_debug.ReadDebug(debugFileName)
+                       Expect(err).NotTo(HaveOccurred())
+                       Expect(debug.Command).To(Equal("DEL"))
+                       Expect(debug.CmdArgs).To(Equal(skel.CmdArgs{
+                               ContainerID: "some-container-id",
+                               Netns:       "/some/netns/path",
+                               IfName:      "some-eth0",
+                               Args:        "DEBUG=" + debugFileName,
+                               Path:        "/some/bin/path",
+                               StdinData:   []byte(`{"some":"stdin-json"}`),
+                       }))
+               })
+
+       })
+})
diff --git a/test b/test
index 82077f5..b7755a2 100755 (executable)
--- a/test
+++ b/test
@@ -11,8 +11,8 @@ set -e
 
 source ./build
 
-TESTABLE="plugins/ipam/dhcp plugins/ipam/host-local plugins/main/loopback pkg/invoke pkg/ns pkg/skel pkg/types pkg/utils plugins/main/ipvlan plugins/main/macvlan plugins/main/bridge plugins/main/ptp"
-FORMATTABLE="$TESTABLE libcni pkg/ip pkg/ipam pkg/testutils plugins/ipam/host-local plugins/main/bridge plugins/meta/flannel plugins/meta/tuning"
+TESTABLE="plugins/ipam/dhcp plugins/ipam/host-local plugins/main/loopback pkg/invoke pkg/ns pkg/skel pkg/types pkg/utils plugins/main/ipvlan plugins/main/macvlan plugins/main/bridge plugins/main/ptp plugins/test/noop"
+FORMATTABLE="$TESTABLE libcni pkg/ip pkg/ipam pkg/testutils plugins/meta/flannel plugins/meta/tuning"
 
 # user has not provided PKG override
 if [ -z "$PKG" ]; then