Gabe Rosenhouse c17e700759 pkg/skel: missing env var log lines appear in stderr
Previously, the log lines appeared in stdout before the JSON encoding of
the error message.  That would break JSON parsing of stdout.  Instead, we use
stderr for these unstructured logs, consistent with the CNI spec.
2016-07-14 00:24:27 -04:00

253 lines
6.6 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/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
stderr *bytes.Buffer
cmdAdd, cmdDel *fakeCmd
dispatch *dispatcher
expectedCmdArgs *CmdArgs
)
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" }`)
stderr = &bytes.Buffer{}
dispatch = &dispatcher{
Getenv: func(key string) string { return environment[key] },
Stdin: stdin,
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)
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)
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)
Expect(err).NotTo(HaveOccurred())
Expect(cmdDel.CallCount).To(Equal(0))
})
DescribeTable("required / optional env vars", envVarChecker,
// TODO: 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)).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)
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)
Expect(err).NotTo(HaveOccurred())
Expect(cmdAdd.CallCount).To(Equal(0))
})
DescribeTable("required / optional env vars", envVarChecker,
// TODO: 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 unrecognized", func() {
BeforeEach(func() {
environment["CNI_COMMAND"] = "NOPE"
})
It("does not call any cmd callback", func() {
dispatch.pluginMain(cmdAdd.Func, cmdDel.Func)
Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0))
})
It("returns an error", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func)
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)
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)
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)
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)
Expect(err).To(Equal(&types.Error{
Code: 100,
Msg: "potato",
}))
})
})
})
})