347 lines
9.7 KiB
Go
347 lines
9.7 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"
|
|
"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
|
|
stdinData string
|
|
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",
|
|
}
|
|
|
|
stdinData = `{ "some": "config", "cniVersion": "9.8.7" }`
|
|
stdout = &bytes.Buffer{}
|
|
stderr = &bytes.Buffer{}
|
|
versionInfo = version.PluginSupports("9.8.7")
|
|
dispatch = &dispatcher{
|
|
Getenv: func(key string) string { return environment[key] },
|
|
Stdin: strings.NewReader(stdinData),
|
|
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(stdinData),
|
|
}
|
|
})
|
|
|
|
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_CONTAINERID", 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 stdin data is missing the required cniVersion config", func() {
|
|
BeforeEach(func() {
|
|
dispatch.Stdin = strings.NewReader(`{ "some": "config" }`)
|
|
})
|
|
|
|
Context("when the plugin supports version 0.1.0", func() {
|
|
BeforeEach(func() {
|
|
versionInfo = version.PluginSupports("0.1.0")
|
|
expectedCmdArgs.StdinData = []byte(`{ "some": "config" }`)
|
|
})
|
|
|
|
It("infers the config is 0.1.0 and calls the cmdAdd callback", func() {
|
|
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
|
Expect(err).NotTo(HaveOccurred())
|
|
|
|
Expect(cmdAdd.CallCount).To(Equal(1))
|
|
Expect(cmdAdd.Received.CmdArgs).To(Equal(expectedCmdArgs))
|
|
})
|
|
})
|
|
|
|
Context("when the plugin does not support 0.1.0", func() {
|
|
BeforeEach(func() {
|
|
versionInfo = version.PluginSupports("4.3.2")
|
|
})
|
|
|
|
It("immediately returns a useful error", func() {
|
|
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
|
Expect(err.Code).To(Equal(types.ErrIncompatibleCNIVersion)) // see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes
|
|
Expect(err.Msg).To(Equal("incompatible CNI versions"))
|
|
Expect(err.Details).To(Equal(`config is "0.1.0", plugin supports ["4.3.2"]`))
|
|
})
|
|
|
|
It("does not call either callback", func() {
|
|
dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
|
Expect(cmdAdd.CallCount).To(Equal(0))
|
|
Expect(cmdDel.CallCount).To(Equal(0))
|
|
})
|
|
})
|
|
})
|
|
})
|
|
|
|
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_CONTAINERID", 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.3.1",
|
|
"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_CONTAINERID", 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 stdin is empty", func() {
|
|
BeforeEach(func() {
|
|
dispatch.Stdin = strings.NewReader("")
|
|
})
|
|
|
|
It("succeeds without error", func() {
|
|
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
|
|
|
|
Expect(err).NotTo(HaveOccurred())
|
|
Expect(stdout).To(MatchJSON(`{
|
|
"cniVersion": "0.3.1",
|
|
"supportedVersions": ["9.8.7"]
|
|
}`))
|
|
})
|
|
})
|
|
})
|
|
|
|
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",
|
|
}))
|
|
})
|
|
})
|
|
})
|
|
})
|