Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

skel: clean up errors in skel and add some well-known error codes #686

Merged
merged 5 commits into from
Aug 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions SPEC.md
Original file line number Diff line number Diff line change
Expand Up @@ -758,4 +758,8 @@ Error codes 1-99 must not be used other than as specified here.
- `1` - Incompatible CNI version
- `2` - Unsupported field in network configuration. The error message must contain the key and value of the unsupported field.
- `3` - Container unknown or does not exist. This error implies the runtime does not need to perform any container network cleanup (for example, calling the `DEL` action on the container).
- `4` - Invalid necessary environment variables, like CNI_COMMAND, CNI_CONTAINERID, etc. The error message must contain the names of invalid variables.
- `5` - I/O failure. For example, failed to read network config bytes from stdin.
- `6` - Failed to decode content. For example, failed to unmarshal network config from bytes or failed to decode version info from string.
- `7` - Invalid network config. If some validations on network configs do not pass, this error will be raised.
- `11` - Try again later. If the plugin detects some transient condition that should clear up, it can use this code to notify the runtime it should re-try the operation later.
68 changes: 32 additions & 36 deletions pkg/skel/skel.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func (e missingEnvError) Error() string {
return e.msg
}

func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, error) {
func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, *types.Error) {
var cmd, contID, netns, ifName, args, path string

vars := []struct {
Expand Down Expand Up @@ -138,7 +138,7 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, error) {

if len(argsMissing) > 0 {
joined := strings.Join(argsMissing, ",")
return "", nil, missingEnvError{fmt.Sprintf("required env variables [%s] missing", joined)}
return "", nil, types.NewError(types.ErrInvalidEnvironmentVariables, fmt.Sprintf("required env variables [%s] missing", joined), "")
}

if cmd == "VERSION" {
Expand All @@ -147,7 +147,7 @@ func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, error) {

stdinData, err := ioutil.ReadAll(t.Stdin)
if err != nil {
return "", nil, fmt.Errorf("error reading from stdin: %v", err)
return "", nil, types.NewError(types.ErrIOFailure, fmt.Sprintf("error reading from stdin: %v", err), "")
}

cmdArgs := &CmdArgs{
Expand All @@ -168,32 +168,36 @@ func createTypedError(f string, args ...interface{}) *types.Error {
}
}

func (t *dispatcher) checkVersionAndCall(cmdArgs *CmdArgs, pluginVersionInfo version.PluginInfo, toCall func(*CmdArgs) error) error {
func (t *dispatcher) checkVersionAndCall(cmdArgs *CmdArgs, pluginVersionInfo version.PluginInfo, toCall func(*CmdArgs) error) *types.Error {
configVersion, err := t.ConfVersionDecoder.Decode(cmdArgs.StdinData)
if err != nil {
return err
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
}
verErr := t.VersionReconciler.Check(configVersion, pluginVersionInfo)
if verErr != nil {
return &types.Error{
Code: types.ErrIncompatibleCNIVersion,
Msg: "incompatible CNI versions",
Details: verErr.Details(),
return types.NewError(types.ErrIncompatibleCNIVersion, "incompatible CNI versions", verErr.Details())
}

if err = toCall(cmdArgs); err != nil {
if e, ok := err.(*types.Error); ok {
// don't wrap Error in Error
return e
}
return types.NewError(types.ErrInternal, err.Error(), "")
}

return toCall(cmdArgs)
return nil
}

func validateConfig(jsonBytes []byte) error {
func validateConfig(jsonBytes []byte) *types.Error {
var conf struct {
Name string `json:"name"`
}
if err := json.Unmarshal(jsonBytes, &conf); err != nil {
return fmt.Errorf("error reading network config: %s", err)
return types.NewError(types.ErrDecodingFailure, fmt.Sprintf("error unmarshall network config: %v", err), "")
}
if conf.Name == "" {
return fmt.Errorf("missing network name")
return types.NewError(types.ErrInvalidNetworkConfig, "missing network name", "")
}
return nil
}
Expand All @@ -202,17 +206,17 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error,
cmd, cmdArgs, err := t.getCmdArgsFromEnv()
if err != nil {
// Print the about string to stderr when no command is set
if _, ok := err.(missingEnvError); ok && t.Getenv("CNI_COMMAND") == "" && about != "" {
fmt.Fprintln(t.Stderr, about)
if err.Code == types.ErrInvalidEnvironmentVariables && t.Getenv("CNI_COMMAND") == "" && about != "" {
_, _ = fmt.Fprintln(t.Stderr, about)
return nil
}
return createTypedError(err.Error())
return err
}

if cmd != "VERSION" {
err = validateConfig(cmdArgs.StdinData)
if err != nil {
return createTypedError(err.Error())
return err
}
}

Expand All @@ -222,45 +226,37 @@ func (t *dispatcher) pluginMain(cmdAdd, cmdCheck, cmdDel func(_ *CmdArgs) error,
case "CHECK":
configVersion, err := t.ConfVersionDecoder.Decode(cmdArgs.StdinData)
if err != nil {
return createTypedError(err.Error())
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
}
if gtet, err := version.GreaterThanOrEqualTo(configVersion, "0.4.0"); err != nil {
return createTypedError(err.Error())
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
} else if !gtet {
return &types.Error{
Code: types.ErrIncompatibleCNIVersion,
Msg: "config version does not allow CHECK",
}
return types.NewError(types.ErrIncompatibleCNIVersion, "config version does not allow CHECK", "")
}
for _, pluginVersion := range versionInfo.SupportedVersions() {
gtet, err := version.GreaterThanOrEqualTo(pluginVersion, configVersion)
if err != nil {
return createTypedError(err.Error())
return types.NewError(types.ErrDecodingFailure, err.Error(), "")
} else if gtet {
if err := t.checkVersionAndCall(cmdArgs, versionInfo, cmdCheck); err != nil {
return createTypedError(err.Error())
return err
}
return nil
}
}
return &types.Error{
Code: types.ErrIncompatibleCNIVersion,
Msg: "plugin version does not allow CHECK",
}
return types.NewError(types.ErrIncompatibleCNIVersion, "plugin version does not allow CHECK", "")
case "DEL":
err = t.checkVersionAndCall(cmdArgs, versionInfo, cmdDel)
case "VERSION":
err = versionInfo.Encode(t.Stdout)
if err := versionInfo.Encode(t.Stdout); err != nil {
return types.NewError(types.ErrIOFailure, err.Error(), "")
}
default:
return createTypedError("unknown CNI_COMMAND: %v", cmd)
return types.NewError(types.ErrInvalidEnvironmentVariables, fmt.Sprintf("unknown CNI_COMMAND: %v", cmd), "")
}

if err != nil {
if e, ok := err.(*types.Error); ok {
// don't wrap Error in Error
return e
}
return createTypedError(err.Error())
return err
}
return nil
}
Expand Down
22 changes: 11 additions & 11 deletions pkg/skel/skel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ var _ = Describe("dispatching to the correct callback", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
if isRequired {
Expect(err).To(Equal(&types.Error{
Code: 100,
Code: types.ErrInvalidEnvironmentVariables,
Msg: "required env variables [" + envVar + "] missing",
}))
} else {
Expand Down Expand Up @@ -143,7 +143,7 @@ var _ = Describe("dispatching to the correct callback", func() {
Expect(err).To(HaveOccurred())

Expect(err).To(Equal(&types.Error{
Code: 100,
Code: types.ErrInvalidEnvironmentVariables,
Msg: "required env variables [CNI_NETNS,CNI_IFNAME,CNI_PATH] missing",
}))
})
Expand Down Expand Up @@ -236,7 +236,7 @@ var _ = Describe("dispatching to the correct callback", func() {
Expect(err).To(HaveOccurred())

Expect(err).To(Equal(&types.Error{
Code: 100,
Code: types.ErrInvalidEnvironmentVariables,
Msg: "required env variables [CNI_NETNS,CNI_IFNAME,CNI_PATH] missing",
}))
})
Expand Down Expand Up @@ -269,10 +269,10 @@ var _ = Describe("dispatching to the correct callback", func() {

Context("when the config has a bad version", func() {
It("immediately returns a useful error", func() {
dispatch.Stdin = strings.NewReader(`{ "cniVersion": "adsfsadf", "some": "config" }`)
dispatch.Stdin = strings.NewReader(`{ "cniVersion": "adsfsadf", "some": "config", "name": "test" }`)
versionInfo = version.PluginSupports("0.1.0", "0.2.0", "0.3.0")
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
Expect(err.Code).To(Equal(uint(100)))
Expect(err.Code).To(Equal(uint(types.ErrDecodingFailure)))
Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdCheck.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0))
Expand All @@ -281,10 +281,10 @@ var _ = Describe("dispatching to the correct callback", func() {

Context("when the plugin has a bad version", func() {
It("immediately returns a useful error", func() {
dispatch.Stdin = strings.NewReader(`{ "cniVersion": "0.4.0", "some": "config" }`)
dispatch.Stdin = strings.NewReader(`{ "cniVersion": "0.4.0", "some": "config", "name": "test" }`)
versionInfo = version.PluginSupports("0.1.0", "0.2.0", "adsfasdf")
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")
Expect(err.Code).To(Equal(uint(100)))
Expect(err.Code).To(Equal(uint(types.ErrDecodingFailure)))
Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdCheck.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0))
Expand Down Expand Up @@ -385,7 +385,7 @@ var _ = Describe("dispatching to the correct callback", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")

Expect(err).To(Equal(&types.Error{
Code: 100,
Code: types.ErrInvalidEnvironmentVariables,
Msg: "unknown CNI_COMMAND: NOPE",
}))
})
Expand Down Expand Up @@ -417,7 +417,7 @@ var _ = Describe("dispatching to the correct callback", func() {
Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0))
Expect(err).To(Equal(&types.Error{
Code: 100,
Code: types.ErrInvalidEnvironmentVariables,
Msg: "required env variables [CNI_COMMAND] missing",
}))
})
Expand All @@ -439,7 +439,7 @@ var _ = Describe("dispatching to the correct callback", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")

Expect(err).To(Equal(&types.Error{
Code: 100,
Code: types.ErrIOFailure,
Msg: "error reading from stdin: banana",
}))
})
Expand Down Expand Up @@ -473,7 +473,7 @@ var _ = Describe("dispatching to the correct callback", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdCheck.Func, cmdDel.Func, versionInfo, "")

Expect(err).To(Equal(&types.Error{
Code: 100,
Code: types.ErrInternal,
Msg: "potato",
}))
})
Expand Down
21 changes: 18 additions & 3 deletions pkg/types/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,16 @@ func (r *Route) String() string {
// Well known error codes
// see /~https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes
const (
ErrUnknown uint = iota // 0
ErrIncompatibleCNIVersion // 1
ErrUnsupportedField // 2
ErrUnknown uint = iota // 0
ErrIncompatibleCNIVersion // 1
ErrUnsupportedField // 2
ErrUnknownContainer // 3
ErrInvalidEnvironmentVariables // 4
ErrIOFailure // 5
ErrDecodingFailure // 6
ErrInvalidNetworkConfig // 7
ErrTryAgainLater uint = 11
ErrInternal uint = 999
)

type Error struct {
Expand All @@ -144,6 +151,14 @@ type Error struct {
Details string `json:"details,omitempty"`
}

func NewError(code uint, msg, details string) *Error {
return &Error{
Code: code,
Msg: msg,
Details: details,
}
}

func (e *Error) Error() string {
details := ""
if e.Details != "" {
Expand Down
5 changes: 5 additions & 0 deletions pkg/types/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,5 +137,10 @@ var _ = Describe("Types", func() {
})
})
})

It("NewError method", func() {
err := types.NewError(1234, "some message", "some details")
Expect(err).To(Equal(example))
})
})
})
3 changes: 2 additions & 1 deletion plugins/test/noop/noop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"strings"

"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/version"
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
. "github.com/onsi/ginkgo"
Expand Down Expand Up @@ -219,7 +220,7 @@ var _ = Describe("No-op plugin", func() {
session, err := gexec.Start(cmd, GinkgoWriter, GinkgoWriter)
Expect(err).NotTo(HaveOccurred())
Eventually(session).Should(gexec.Exit(1))
Expect(session.Out.Contents()).To(MatchJSON(`{ "code": 100, "msg": "banana" }`))
Expect(session.Out.Contents()).To(MatchJSON(fmt.Sprintf(`{ "code": %d, "msg": "banana" }`, types.ErrInternal)))
})
})

Expand Down