2016-09-06 15:37:42 -04:00

291 lines
7.8 KiB
Go

// 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 skel
import (
"bytes"
"errors"
"io"
"strings"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/version"
"github.com/containernetworking/cni/pkg/testutils"
. "github.com/onsi/ginkgo"
. "github.com/onsi/ginkgo/extensions/table"
. "github.com/onsi/gomega"
)
type fakeCmd struct {
CallCount int
Returns struct {
Error error
}
Received struct {
CmdArgs *CmdArgs
}
}
func (c *fakeCmd) Func(args *CmdArgs) error {
c.CallCount++
c.Received.CmdArgs = args
return c.Returns.Error
}
var _ = Describe("dispatching to the correct callback", func() {
var (
environment map[string]string
stdin io.Reader
stdout, stderr *bytes.Buffer
cmdAdd, cmdDel *fakeCmd
dispatch *dispatcher
expectedCmdArgs *CmdArgs
versionInfo version.PluginInfo
)
BeforeEach(func() {
environment = map[string]string{
"CNI_COMMAND": "ADD",
"CNI_CONTAINERID": "some-container-id",
"CNI_NETNS": "/some/netns/path",
"CNI_IFNAME": "eth0",
"CNI_ARGS": "some;extra;args",
"CNI_PATH": "/some/cni/path",
}
stdin = strings.NewReader(`{ "some": "config" }`)
stdout = &bytes.Buffer{}
stderr = &bytes.Buffer{}
versionInfo = version.PluginSupports("9.8.7")
dispatch = &dispatcher{
Getenv: func(key string) string { return environment[key] },
Stdin: stdin,
Stdout: stdout,
Stderr: stderr,
}
cmdAdd = &fakeCmd{}
cmdDel = &fakeCmd{}
expectedCmdArgs = &CmdArgs{
ContainerID: "some-container-id",
Netns: "/some/netns/path",
IfName: "eth0",
Args: "some;extra;args",
Path: "/some/cni/path",
StdinData: []byte(`{ "some": "config" }`),
}
})
var envVarChecker = func(envVar string, isRequired bool) {
delete(environment, envVar)
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
if isRequired {
Expect(err).To(Equal(&types.Error{
Code: 100,
Msg: "required env variables missing",
}))
Expect(stderr.String()).To(ContainSubstring(envVar + " env variable missing\n"))
} else {
Expect(err).NotTo(HaveOccurred())
}
}
Context("when the CNI_COMMAND is ADD", func() {
It("extracts env vars and stdin data and calls cmdAdd", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
Expect(err).NotTo(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(1))
Expect(cmdDel.CallCount).To(Equal(0))
Expect(cmdAdd.Received.CmdArgs).To(Equal(expectedCmdArgs))
})
It("does not call cmdDel", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
Expect(err).NotTo(HaveOccurred())
Expect(cmdDel.CallCount).To(Equal(0))
})
DescribeTable("required / optional env vars", envVarChecker,
Entry("command", "CNI_COMMAND", true),
Entry("container id", "CNI_CONTAINER_ID", false),
Entry("net ns", "CNI_NETNS", true),
Entry("if name", "CNI_IFNAME", true),
Entry("args", "CNI_ARGS", false),
Entry("path", "CNI_PATH", true),
)
Context("when multiple required env vars are missing", func() {
BeforeEach(func() {
delete(environment, "CNI_NETNS")
delete(environment, "CNI_IFNAME")
delete(environment, "CNI_PATH")
})
It("reports that all of them are missing, not just the first", func() {
Expect(dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)).NotTo(Succeed())
log := stderr.String()
Expect(log).To(ContainSubstring("CNI_NETNS env variable missing\n"))
Expect(log).To(ContainSubstring("CNI_IFNAME env variable missing\n"))
Expect(log).To(ContainSubstring("CNI_PATH env variable missing\n"))
})
})
})
Context("when the CNI_COMMAND is DEL", func() {
BeforeEach(func() {
environment["CNI_COMMAND"] = "DEL"
})
It("calls cmdDel with the env vars and stdin data", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
Expect(err).NotTo(HaveOccurred())
Expect(cmdDel.CallCount).To(Equal(1))
Expect(cmdDel.Received.CmdArgs).To(Equal(expectedCmdArgs))
})
It("does not call cmdAdd", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
Expect(err).NotTo(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0))
})
DescribeTable("required / optional env vars", envVarChecker,
Entry("command", "CNI_COMMAND", true),
Entry("container id", "CNI_CONTAINER_ID", false),
Entry("net ns", "CNI_NETNS", false),
Entry("if name", "CNI_IFNAME", true),
Entry("args", "CNI_ARGS", false),
Entry("path", "CNI_PATH", true),
)
})
Context("when the CNI_COMMAND is VERSION", func() {
BeforeEach(func() {
environment["CNI_COMMAND"] = "VERSION"
})
It("prints the version to stdout", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
Expect(err).NotTo(HaveOccurred())
Expect(stdout).To(MatchJSON(`{
"cniVersion": "0.2.0",
"supportedVersions": ["9.8.7"]
}`))
})
It("does not call cmdAdd or cmdDel", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
Expect(err).NotTo(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0))
})
DescribeTable("VERSION does not need the usual env vars", envVarChecker,
Entry("command", "CNI_COMMAND", true),
Entry("container id", "CNI_CONTAINER_ID", false),
Entry("net ns", "CNI_NETNS", false),
Entry("if name", "CNI_IFNAME", false),
Entry("args", "CNI_ARGS", false),
Entry("path", "CNI_PATH", false),
)
})
Context("when the CNI_COMMAND is unrecognized", func() {
BeforeEach(func() {
environment["CNI_COMMAND"] = "NOPE"
})
It("does not call any cmd callback", func() {
dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0))
})
It("returns an error", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
Expect(err).To(Equal(&types.Error{
Code: 100,
Msg: "unknown CNI_COMMAND: NOPE",
}))
})
})
Context("when stdin cannot be read", func() {
BeforeEach(func() {
dispatch.Stdin = &testutils.BadReader{}
})
It("does not call any cmd callback", func() {
dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0))
})
It("wraps and returns the error", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
Expect(err).To(Equal(&types.Error{
Code: 100,
Msg: "error reading from stdin: banana",
}))
})
})
Context("when the callback returns an error", func() {
Context("when it is a typed Error", func() {
BeforeEach(func() {
cmdAdd.Returns.Error = &types.Error{
Code: 1234,
Msg: "insufficient something",
}
})
It("returns the error as-is", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
Expect(err).To(Equal(&types.Error{
Code: 1234,
Msg: "insufficient something",
}))
})
})
Context("when it is an unknown error", func() {
BeforeEach(func() {
cmdAdd.Returns.Error = errors.New("potato")
})
It("wraps and returns the error", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
Expect(err).To(Equal(&types.Error{
Code: 100,
Msg: "potato",
}))
})
})
})
})