diff --git a/pkg/invoke/exec.go b/pkg/invoke/exec.go index a85eede6..d7e38f21 100644 --- a/pkg/invoke/exec.go +++ b/pkg/invoke/exec.go @@ -18,6 +18,7 @@ import ( "bytes" "encoding/json" "fmt" + "io" "os" "os/exec" @@ -57,15 +58,25 @@ func ExecPluginWithoutResult(pluginPath string, netconf []byte, args CNIArgs) er } func execPlugin(pluginPath string, netconf []byte, args CNIArgs) ([]byte, error) { + return defaultRawExec.ExecPlugin(pluginPath, netconf, args.AsEnv()) +} + +var defaultRawExec = &RawExec{Stderr: os.Stderr} + +type RawExec struct { + Stderr io.Writer +} + +func (e *RawExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) { stdout := &bytes.Buffer{} c := exec.Cmd{ - Env: args.AsEnv(), + Env: environ, Path: pluginPath, Args: []string{pluginPath}, - Stdin: bytes.NewBuffer(netconf), + Stdin: bytes.NewBuffer(stdinData), Stdout: stdout, - Stderr: os.Stderr, + Stderr: e.Stderr, } if err := c.Run(); err != nil { return nil, pluginErr(err, stdout.Bytes()) diff --git a/pkg/invoke/exec_test.go b/pkg/invoke/exec_test.go new file mode 100644 index 00000000..7df60a11 --- /dev/null +++ b/pkg/invoke/exec_test.go @@ -0,0 +1,123 @@ +// 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 invoke_test + +import ( + "bytes" + "io/ioutil" + "os" + + "github.com/containernetworking/cni/pkg/invoke" + + noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("RawExec", func() { + var ( + debugFileName string + debug *noop_debug.Debug + environ []string + stdin []byte + execer *invoke.RawExec + ) + + const reportResult = `{ "some": "result" }` + + BeforeEach(func() { + debugFile, err := ioutil.TempFile("", "cni_debug") + Expect(err).NotTo(HaveOccurred()) + Expect(debugFile.Close()).To(Succeed()) + debugFileName = debugFile.Name() + + debug = &noop_debug.Debug{ + ReportResult: reportResult, + ReportStderr: "some stderr message", + } + Expect(debug.WriteDebug(debugFileName)).To(Succeed()) + + environ = []string{ + "CNI_COMMAND=ADD", + "CNI_CONTAINERID=some-container-id", + "CNI_ARGS=DEBUG=" + debugFileName, + "CNI_NETNS=/some/netns/path", + "CNI_PATH=/some/bin/path", + "CNI_IFNAME=some-eth0", + } + stdin = []byte(`{"some":"stdin-json"}`) + execer = &invoke.RawExec{} + }) + + AfterEach(func() { + Expect(os.Remove(debugFileName)).To(Succeed()) + }) + + It("runs the plugin with the given stdin and environment", func() { + _, err := execer.ExecPlugin(pathToPlugin, stdin, environ) + Expect(err).NotTo(HaveOccurred()) + + debug, err := noop_debug.ReadDebug(debugFileName) + Expect(err).NotTo(HaveOccurred()) + Expect(debug.Command).To(Equal("ADD")) + Expect(debug.CmdArgs.StdinData).To(Equal(stdin)) + Expect(debug.CmdArgs.Netns).To(Equal("/some/netns/path")) + }) + + It("returns the resulting stdout as bytes", func() { + resultBytes, err := execer.ExecPlugin(pathToPlugin, stdin, environ) + Expect(err).NotTo(HaveOccurred()) + + Expect(resultBytes).To(BeEquivalentTo(reportResult)) + }) + + Context("when the Stderr writer is set", func() { + var stderrBuffer *bytes.Buffer + + BeforeEach(func() { + stderrBuffer = &bytes.Buffer{} + execer.Stderr = stderrBuffer + }) + + It("forwards any stderr bytes to the Stderr writer", func() { + _, err := execer.ExecPlugin(pathToPlugin, stdin, environ) + Expect(err).NotTo(HaveOccurred()) + + Expect(stderrBuffer.String()).To(Equal("some stderr message")) + }) + }) + + Context("when the plugin errors", func() { + BeforeEach(func() { + debug.ReportError = "banana" + Expect(debug.WriteDebug(debugFileName)).To(Succeed()) + }) + + It("wraps and returns the error", func() { + _, err := execer.ExecPlugin(pathToPlugin, stdin, environ) + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError("banana")) + }) + }) + + Context("when the system is unable to execute the plugin", func() { + It("returns the error", func() { + _, err := execer.ExecPlugin("/tmp/some/invalid/plugin/path", stdin, environ) + Expect(err).To(HaveOccurred()) + Expect(err).To(MatchError(ContainSubstring("/tmp/some/invalid/plugin/path"))) + }) + }) +}) diff --git a/pkg/invoke/invoke_suite_test.go b/pkg/invoke/invoke_suite_test.go index 72eb837f..7285878a 100644 --- a/pkg/invoke/invoke_suite_test.go +++ b/pkg/invoke/invoke_suite_test.go @@ -17,6 +17,7 @@ package invoke_test import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/onsi/gomega/gexec" "testing" ) @@ -25,3 +26,20 @@ func TestInvoke(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "Invoke 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/debug/debug.go b/plugins/test/noop/debug/debug.go index d9fb84c4..71770bd3 100644 --- a/plugins/test/noop/debug/debug.go +++ b/plugins/test/noop/debug/debug.go @@ -26,6 +26,7 @@ import ( type Debug struct { ReportResult string ReportError string + ReportStderr string Command string CmdArgs skel.CmdArgs } diff --git a/plugins/test/noop/main.go b/plugins/test/noop/main.go index d0db9020..ddb1acae 100644 --- a/plugins/test/noop/main.go +++ b/plugins/test/noop/main.go @@ -51,6 +51,8 @@ func debugBehavior(args *skel.CmdArgs, command string) error { return err } + os.Stderr.WriteString(debug.ReportStderr) + if debug.ReportError != "" { return errors.New(debug.ReportError) } else {