pkg/invoke: refactor plugin exec and backfill unit tests
This commit is contained in:
parent
383c84031e
commit
bd3ade0c54
@ -15,35 +15,41 @@
|
|||||||
package invoke
|
package invoke
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
|
|
||||||
"github.com/containernetworking/cni/pkg/types"
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
"github.com/containernetworking/cni/pkg/version"
|
"github.com/containernetworking/cni/pkg/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
func pluginErr(err error, output []byte) error {
|
func ExecPluginWithResult(pluginPath string, netconf []byte, args CNIArgs) (*types.Result, error) {
|
||||||
if _, ok := err.(*exec.ExitError); ok {
|
return defaultPluginExec.WithResult(pluginPath, netconf, args)
|
||||||
emsg := types.Error{}
|
|
||||||
if perr := json.Unmarshal(output, &emsg); perr != nil {
|
|
||||||
return fmt.Errorf("netplugin failed but error parsing its diagnostic message %q: %v", string(output), perr)
|
|
||||||
}
|
|
||||||
details := ""
|
|
||||||
if emsg.Details != "" {
|
|
||||||
details = fmt.Sprintf("; %v", emsg.Details)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%v%v", emsg.Msg, details)
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExecPluginWithResult(pluginPath string, netconf []byte, args CNIArgs) (*types.Result, error) {
|
func ExecPluginWithoutResult(pluginPath string, netconf []byte, args CNIArgs) error {
|
||||||
stdoutBytes, err := execPlugin(pluginPath, netconf, args)
|
return defaultPluginExec.WithoutResult(pluginPath, netconf, args)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExecPluginForVersion(pluginPath string) (version.PluginInfo, error) {
|
||||||
|
return defaultPluginExec.GetVersion(pluginPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
var defaultPluginExec = &PluginExec{
|
||||||
|
RawExec: &RawExec{Stderr: os.Stderr},
|
||||||
|
VersionDecoder: &version.Decoder{},
|
||||||
|
}
|
||||||
|
|
||||||
|
type PluginExec struct {
|
||||||
|
RawExec interface {
|
||||||
|
ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error)
|
||||||
|
}
|
||||||
|
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())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -53,44 +59,17 @@ func ExecPluginWithResult(pluginPath string, netconf []byte, args CNIArgs) (*typ
|
|||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExecPluginWithoutResult(pluginPath string, netconf []byte, args CNIArgs) error {
|
func (e *PluginExec) WithoutResult(pluginPath string, netconf []byte, args CNIArgs) error {
|
||||||
_, err := execPlugin(pluginPath, netconf, args)
|
_, err := e.RawExec.ExecPlugin(pluginPath, netconf, args.AsEnv())
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
func ExecPluginForVersion(pluginPath string) (version.PluginInfo, error) {
|
func (e *PluginExec) GetVersion(pluginPath string) (version.PluginInfo, error) {
|
||||||
stdoutBytes, err := execPlugin(pluginPath, nil, &Args{Command: "VERSION"})
|
args := &Args{Command: "VERSION"}
|
||||||
|
stdoutBytes, err := e.RawExec.ExecPlugin(pluginPath, nil, args.AsEnv())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return version.Decode(stdoutBytes)
|
return e.VersionDecoder.Decode(stdoutBytes)
|
||||||
}
|
|
||||||
|
|
||||||
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: environ,
|
|
||||||
Path: pluginPath,
|
|
||||||
Args: []string{pluginPath},
|
|
||||||
Stdin: bytes.NewBuffer(stdinData),
|
|
||||||
Stdout: stdout,
|
|
||||||
Stderr: e.Stderr,
|
|
||||||
}
|
|
||||||
if err := c.Run(); err != nil {
|
|
||||||
return nil, pluginErr(err, stdout.Bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
return stdout.Bytes(), nil
|
|
||||||
}
|
}
|
||||||
|
@ -15,109 +15,115 @@
|
|||||||
package invoke_test
|
package invoke_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"errors"
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/containernetworking/cni/pkg/invoke"
|
"github.com/containernetworking/cni/pkg/invoke"
|
||||||
|
"github.com/containernetworking/cni/pkg/invoke/fakes"
|
||||||
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
|
"github.com/containernetworking/cni/pkg/version"
|
||||||
|
|
||||||
. "github.com/onsi/ginkgo"
|
. "github.com/onsi/ginkgo"
|
||||||
. "github.com/onsi/gomega"
|
. "github.com/onsi/gomega"
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("RawExec", func() {
|
var _ = Describe("Executing a plugin", func() {
|
||||||
var (
|
var (
|
||||||
debugFileName string
|
pluginExec *invoke.PluginExec
|
||||||
debug *noop_debug.Debug
|
rawExec *fakes.RawExec
|
||||||
environ []string
|
versionDecoder *fakes.VersionDecoder
|
||||||
stdin []byte
|
|
||||||
execer *invoke.RawExec
|
pluginPath string
|
||||||
|
netconf []byte
|
||||||
|
cniargs *fakes.CNIArgs
|
||||||
)
|
)
|
||||||
|
|
||||||
const reportResult = `{ "some": "result" }`
|
|
||||||
|
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
debugFile, err := ioutil.TempFile("", "cni_debug")
|
rawExec = &fakes.RawExec{}
|
||||||
Expect(err).NotTo(HaveOccurred())
|
rawExec.ExecPluginCall.Returns.ResultBytes = []byte(`{ "ip4": { "ip": "1.2.3.4/24" } }`)
|
||||||
Expect(debugFile.Close()).To(Succeed())
|
|
||||||
debugFileName = debugFile.Name()
|
|
||||||
|
|
||||||
debug = &noop_debug.Debug{
|
versionDecoder = &fakes.VersionDecoder{}
|
||||||
ReportResult: reportResult,
|
versionDecoder.DecodeCall.Returns.PluginInfo = version.PluginSupports("0.42.0")
|
||||||
ReportStderr: "some stderr message",
|
|
||||||
|
pluginExec = &invoke.PluginExec{
|
||||||
|
RawExec: rawExec,
|
||||||
|
VersionDecoder: versionDecoder,
|
||||||
}
|
}
|
||||||
Expect(debug.WriteDebug(debugFileName)).To(Succeed())
|
pluginPath = "/some/plugin/path"
|
||||||
|
netconf = []byte(`{ "some": "stdin" }`)
|
||||||
environ = []string{
|
cniargs = &fakes.CNIArgs{}
|
||||||
"CNI_COMMAND=ADD",
|
cniargs.AsEnvCall.Returns.Env = []string{"SOME=ENV"}
|
||||||
"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() {
|
Describe("returning a result", func() {
|
||||||
Expect(os.Remove(debugFileName)).To(Succeed())
|
It("unmarshals the result bytes into the Result type", func() {
|
||||||
})
|
result, err := pluginExec.WithResult(pluginPath, netconf, cniargs)
|
||||||
|
|
||||||
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(err).NotTo(HaveOccurred())
|
||||||
|
Expect(result.IP4.IP.IP.String()).To(Equal("1.2.3.4"))
|
||||||
|
})
|
||||||
|
|
||||||
Expect(stderrBuffer.String()).To(Equal("some stderr message"))
|
It("passes its arguments through to the rawExec", func() {
|
||||||
|
pluginExec.WithResult(pluginPath, netconf, cniargs)
|
||||||
|
Expect(rawExec.ExecPluginCall.Received.PluginPath).To(Equal(pluginPath))
|
||||||
|
Expect(rawExec.ExecPluginCall.Received.StdinData).To(Equal(netconf))
|
||||||
|
Expect(rawExec.ExecPluginCall.Received.Environ).To(Equal([]string{"SOME=ENV"}))
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when the rawExec fails", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
rawExec.ExecPluginCall.Returns.Error = errors.New("banana")
|
||||||
|
})
|
||||||
|
It("returns the error", func() {
|
||||||
|
_, err := pluginExec.WithResult(pluginPath, netconf, cniargs)
|
||||||
|
Expect(err).To(MatchError("banana"))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Context("when the plugin errors", func() {
|
Describe("without returning a result", func() {
|
||||||
|
It("passes its arguments through to the rawExec", func() {
|
||||||
|
pluginExec.WithoutResult(pluginPath, netconf, cniargs)
|
||||||
|
Expect(rawExec.ExecPluginCall.Received.PluginPath).To(Equal(pluginPath))
|
||||||
|
Expect(rawExec.ExecPluginCall.Received.StdinData).To(Equal(netconf))
|
||||||
|
Expect(rawExec.ExecPluginCall.Received.Environ).To(Equal([]string{"SOME=ENV"}))
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when the rawExec fails", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
rawExec.ExecPluginCall.Returns.Error = errors.New("banana")
|
||||||
|
})
|
||||||
|
It("returns the error", func() {
|
||||||
|
err := pluginExec.WithoutResult(pluginPath, netconf, cniargs)
|
||||||
|
Expect(err).To(MatchError("banana"))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
Describe("discovering the plugin version", func() {
|
||||||
BeforeEach(func() {
|
BeforeEach(func() {
|
||||||
debug.ReportError = "banana"
|
rawExec.ExecPluginCall.Returns.ResultBytes = []byte(`{ "some": "version-info" }`)
|
||||||
Expect(debug.WriteDebug(debugFileName)).To(Succeed())
|
|
||||||
})
|
})
|
||||||
|
|
||||||
It("wraps and returns the error", func() {
|
It("execs the plugin with the command VERSION", func() {
|
||||||
_, err := execer.ExecPlugin(pathToPlugin, stdin, environ)
|
pluginExec.GetVersion(pluginPath)
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(rawExec.ExecPluginCall.Received.PluginPath).To(Equal(pluginPath))
|
||||||
Expect(err).To(MatchError("banana"))
|
Expect(rawExec.ExecPluginCall.Received.StdinData).To(BeNil())
|
||||||
|
Expect(rawExec.ExecPluginCall.Received.Environ).To(ContainElement("CNI_COMMAND=VERSION"))
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
Context("when the system is unable to execute the plugin", func() {
|
It("decodes and returns the version info", func() {
|
||||||
It("returns the error", func() {
|
versionInfo, err := pluginExec.GetVersion(pluginPath)
|
||||||
_, err := execer.ExecPlugin("/tmp/some/invalid/plugin/path", stdin, environ)
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(err).To(HaveOccurred())
|
Expect(versionInfo.SupportedVersions()).To(Equal([]string{"0.42.0"}))
|
||||||
Expect(err).To(MatchError(ContainSubstring("/tmp/some/invalid/plugin/path")))
|
Expect(versionDecoder.DecodeCall.Received.JSONBytes).To(MatchJSON(`{ "some": "version-info" }`))
|
||||||
|
})
|
||||||
|
|
||||||
|
Context("when the rawExec fails", func() {
|
||||||
|
BeforeEach(func() {
|
||||||
|
rawExec.ExecPluginCall.Returns.Error = errors.New("banana")
|
||||||
|
})
|
||||||
|
It("returns the error", func() {
|
||||||
|
_, err := pluginExec.GetVersion(pluginPath)
|
||||||
|
Expect(err).To(MatchError("banana"))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
27
invoke/fakes/cni_args.go
Normal file
27
invoke/fakes/cni_args.go
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
// 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 fakes
|
||||||
|
|
||||||
|
type CNIArgs struct {
|
||||||
|
AsEnvCall struct {
|
||||||
|
Returns struct {
|
||||||
|
Env []string
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *CNIArgs) AsEnv() []string {
|
||||||
|
return a.AsEnvCall.Returns.Env
|
||||||
|
}
|
36
invoke/fakes/raw_exec.go
Normal file
36
invoke/fakes/raw_exec.go
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
// 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 fakes
|
||||||
|
|
||||||
|
type RawExec struct {
|
||||||
|
ExecPluginCall struct {
|
||||||
|
Received struct {
|
||||||
|
PluginPath string
|
||||||
|
StdinData []byte
|
||||||
|
Environ []string
|
||||||
|
}
|
||||||
|
Returns struct {
|
||||||
|
ResultBytes []byte
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *RawExec) ExecPlugin(pluginPath string, stdinData []byte, environ []string) ([]byte, error) {
|
||||||
|
e.ExecPluginCall.Received.PluginPath = pluginPath
|
||||||
|
e.ExecPluginCall.Received.StdinData = stdinData
|
||||||
|
e.ExecPluginCall.Received.Environ = environ
|
||||||
|
return e.ExecPluginCall.Returns.ResultBytes, e.ExecPluginCall.Returns.Error
|
||||||
|
}
|
34
invoke/fakes/version_decoder.go
Normal file
34
invoke/fakes/version_decoder.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// 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 fakes
|
||||||
|
|
||||||
|
import "github.com/containernetworking/cni/pkg/version"
|
||||||
|
|
||||||
|
type VersionDecoder struct {
|
||||||
|
DecodeCall struct {
|
||||||
|
Received struct {
|
||||||
|
JSONBytes []byte
|
||||||
|
}
|
||||||
|
Returns struct {
|
||||||
|
PluginInfo version.PluginInfo
|
||||||
|
Error error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *VersionDecoder) Decode(jsonData []byte) (version.PluginInfo, error) {
|
||||||
|
e.DecodeCall.Received.JSONBytes = jsonData
|
||||||
|
return e.DecodeCall.Returns.PluginInfo, e.DecodeCall.Returns.Error
|
||||||
|
}
|
63
invoke/raw_exec.go
Normal file
63
invoke/raw_exec.go
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os/exec"
|
||||||
|
|
||||||
|
"github.com/containernetworking/cni/pkg/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
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: environ,
|
||||||
|
Path: pluginPath,
|
||||||
|
Args: []string{pluginPath},
|
||||||
|
Stdin: bytes.NewBuffer(stdinData),
|
||||||
|
Stdout: stdout,
|
||||||
|
Stderr: e.Stderr,
|
||||||
|
}
|
||||||
|
if err := c.Run(); err != nil {
|
||||||
|
return nil, pluginErr(err, stdout.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
return stdout.Bytes(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func pluginErr(err error, output []byte) error {
|
||||||
|
if _, ok := err.(*exec.ExitError); ok {
|
||||||
|
emsg := types.Error{}
|
||||||
|
if perr := json.Unmarshal(output, &emsg); perr != nil {
|
||||||
|
return fmt.Errorf("netplugin failed but error parsing its diagnostic message %q: %v", string(output), perr)
|
||||||
|
}
|
||||||
|
details := ""
|
||||||
|
if emsg.Details != "" {
|
||||||
|
details = fmt.Sprintf("; %v", emsg.Details)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("%v%v", emsg.Msg, details)
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
123
invoke/raw_exec_test.go
Normal file
123
invoke/raw_exec_test.go
Normal file
@ -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")))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
@ -61,7 +61,9 @@ func PluginSupports(supportedVersions ...string) PluginInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Decode(jsonBytes []byte) (PluginInfo, error) {
|
type Decoder struct{}
|
||||||
|
|
||||||
|
func (_ *Decoder) Decode(jsonBytes []byte) (PluginInfo, error) {
|
||||||
var info simple
|
var info simple
|
||||||
err := json.Unmarshal(jsonBytes, &info)
|
err := json.Unmarshal(jsonBytes, &info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -21,8 +21,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var _ = Describe("Decode", func() {
|
var _ = Describe("Decode", func() {
|
||||||
|
var decoder *version.Decoder
|
||||||
|
|
||||||
|
BeforeEach(func() {
|
||||||
|
decoder = &version.Decoder{}
|
||||||
|
})
|
||||||
|
|
||||||
It("returns a PluginInfo that represents the given json bytes", func() {
|
It("returns a PluginInfo that represents the given json bytes", func() {
|
||||||
pluginInfo, err := version.Decode([]byte(`{
|
pluginInfo, err := decoder.Decode([]byte(`{
|
||||||
"cniVersion": "some-library-version",
|
"cniVersion": "some-library-version",
|
||||||
"supportedVersions": [ "some-version", "some-other-version" ]
|
"supportedVersions": [ "some-version", "some-other-version" ]
|
||||||
}`))
|
}`))
|
||||||
@ -36,14 +42,14 @@ var _ = Describe("Decode", func() {
|
|||||||
|
|
||||||
Context("when the bytes cannot be decoded as json", func() {
|
Context("when the bytes cannot be decoded as json", func() {
|
||||||
It("returns a meaningful error", func() {
|
It("returns a meaningful error", func() {
|
||||||
_, err := version.Decode([]byte(`{{{`))
|
_, err := decoder.Decode([]byte(`{{{`))
|
||||||
Expect(err).To(MatchError("decoding version info: invalid character '{' looking for beginning of object key string"))
|
Expect(err).To(MatchError("decoding version info: invalid character '{' looking for beginning of object key string"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
Context("when the json bytes are missing the required CNIVersion field", func() {
|
Context("when the json bytes are missing the required CNIVersion field", func() {
|
||||||
It("returns a meaningful error", func() {
|
It("returns a meaningful error", func() {
|
||||||
_, err := version.Decode([]byte(`{ "supportedVersions": [ "foo" ] }`))
|
_, err := decoder.Decode([]byte(`{ "supportedVersions": [ "foo" ] }`))
|
||||||
Expect(err).To(MatchError("decoding version info: missing field cniVersion"))
|
Expect(err).To(MatchError("decoding version info: missing field cniVersion"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -51,7 +57,7 @@ var _ = Describe("Decode", func() {
|
|||||||
Context("when there are no supported versions", func() {
|
Context("when there are no supported versions", func() {
|
||||||
Context("when the cniVersion is 0.2.0", func() {
|
Context("when the cniVersion is 0.2.0", func() {
|
||||||
It("infers the supported versions are 0.1.0 and 0.2.0", func() {
|
It("infers the supported versions are 0.1.0 and 0.2.0", func() {
|
||||||
pluginInfo, err := version.Decode([]byte(`{ "cniVersion": "0.2.0" }`))
|
pluginInfo, err := decoder.Decode([]byte(`{ "cniVersion": "0.2.0" }`))
|
||||||
Expect(err).NotTo(HaveOccurred())
|
Expect(err).NotTo(HaveOccurred())
|
||||||
Expect(pluginInfo).NotTo(BeNil())
|
Expect(pluginInfo).NotTo(BeNil())
|
||||||
Expect(pluginInfo.SupportedVersions()).To(Equal([]string{
|
Expect(pluginInfo.SupportedVersions()).To(Equal([]string{
|
||||||
@ -63,7 +69,7 @@ var _ = Describe("Decode", func() {
|
|||||||
|
|
||||||
Context("when the cniVersion is >= 0.3.0", func() {
|
Context("when the cniVersion is >= 0.3.0", func() {
|
||||||
It("returns a meaningful error", func() {
|
It("returns a meaningful error", func() {
|
||||||
_, err := version.Decode([]byte(`{ "cniVersion": "0.3.0" }`))
|
_, err := decoder.Decode([]byte(`{ "cniVersion": "0.3.0" }`))
|
||||||
Expect(err).To(MatchError("decoding version info: missing field supportedVersions"))
|
Expect(err).To(MatchError("decoding version info: missing field supportedVersions"))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user