skel: Plugins require a cniVersion in the NetConf

This commit is contained in:
Gabe Rosenhouse 2016-09-06 20:19:26 -04:00 committed by Gabe Rosenhouse
parent 56032390fe
commit fd150a4c97
14 changed files with 76 additions and 26 deletions

View File

@ -69,6 +69,7 @@ Start out by creating a netconf file to describe a network:
$ mkdir -p /etc/cni/net.d $ mkdir -p /etc/cni/net.d
$ cat >/etc/cni/net.d/10-mynet.conf <<EOF $ cat >/etc/cni/net.d/10-mynet.conf <<EOF
{ {
"cniVersion": "0.2.0",
"name": "mynet", "name": "mynet",
"type": "bridge", "type": "bridge",
"bridge": "cni0", "bridge": "cni0",
@ -85,6 +86,7 @@ $ cat >/etc/cni/net.d/10-mynet.conf <<EOF
EOF EOF
$ cat >/etc/cni/net.d/99-loopback.conf <<EOF $ cat >/etc/cni/net.d/99-loopback.conf <<EOF
{ {
"cniVersion": "0.2.0",
"type": "loopback" "type": "loopback"
} }
EOF EOF

View File

@ -53,7 +53,7 @@ var _ = Describe("Invoking the plugin", func() {
Expect(debug.WriteDebug(debugFilePath)).To(Succeed()) Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
cniBinPath = filepath.Dir(pathToPlugin) cniBinPath = filepath.Dir(pathToPlugin)
pluginConfig = `{ "type": "noop", "some-key": "some-value" }` pluginConfig = `{ "type": "noop", "some-key": "some-value", "cniVersion": "0.2.0" }`
cniConfig = libcni.CNIConfig{Path: []string{cniBinPath}} cniConfig = libcni.CNIConfig{Path: []string{cniBinPath}}
netConfig = &libcni.NetworkConfig{ netConfig = &libcni.NetworkConfig{
Network: &types.NetConf{ Network: &types.NetConf{

View File

@ -16,6 +16,7 @@ package invoke
import ( import (
"encoding/json" "encoding/json"
"fmt"
"os" "os"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
@ -77,7 +78,8 @@ func (e *PluginExec) GetVersionInfo(pluginPath string) (version.PluginInfo, erro
IfName: "dummy", IfName: "dummy",
Path: "dummy", Path: "dummy",
} }
stdoutBytes, err := e.RawExec.ExecPlugin(pluginPath, nil, args.AsEnv()) stdin := []byte(fmt.Sprintf(`{"cniVersion":%q}`, version.Current()))
stdoutBytes, err := e.RawExec.ExecPlugin(pluginPath, stdin, args.AsEnv())
if err != nil { if err != nil {
if err.Error() == "unknown CNI_COMMAND: VERSION" { if err.Error() == "unknown CNI_COMMAND: VERSION" {
return version.PluginSupports("0.1.0"), nil return version.PluginSupports("0.1.0"), nil

View File

@ -15,6 +15,7 @@
package invoke_test package invoke_test
import ( import (
"encoding/json"
"errors" "errors"
"github.com/containernetworking/cni/pkg/invoke" "github.com/containernetworking/cni/pkg/invoke"
@ -48,7 +49,7 @@ var _ = Describe("Executing a plugin, unit tests", func() {
VersionDecoder: versionDecoder, VersionDecoder: versionDecoder,
} }
pluginPath = "/some/plugin/path" pluginPath = "/some/plugin/path"
netconf = []byte(`{ "some": "stdin" }`) netconf = []byte(`{ "some": "stdin", "cniVersion": "0.2.0" }`)
cniargs = &fakes.CNIArgs{} cniargs = &fakes.CNIArgs{}
cniargs.AsEnvCall.Returns.Env = []string{"SOME=ENV"} cniargs.AsEnvCall.Returns.Env = []string{"SOME=ENV"}
}) })
@ -105,8 +106,9 @@ var _ = Describe("Executing a plugin, unit tests", func() {
It("execs the plugin with the command VERSION", func() { It("execs the plugin with the command VERSION", func() {
pluginExec.GetVersionInfo(pluginPath) pluginExec.GetVersionInfo(pluginPath)
Expect(rawExec.ExecPluginCall.Received.PluginPath).To(Equal(pluginPath)) Expect(rawExec.ExecPluginCall.Received.PluginPath).To(Equal(pluginPath))
Expect(rawExec.ExecPluginCall.Received.StdinData).To(BeNil())
Expect(rawExec.ExecPluginCall.Received.Environ).To(ContainElement("CNI_COMMAND=VERSION")) Expect(rawExec.ExecPluginCall.Received.Environ).To(ContainElement("CNI_COMMAND=VERSION"))
expectedStdin, _ := json.Marshal(map[string]string{"cniVersion": version.Current()})
Expect(rawExec.ExecPluginCall.Received.StdinData).To(MatchJSON(expectedStdin))
}) })
It("decodes and returns the version info", func() { It("decodes and returns the version info", func() {
@ -146,6 +148,5 @@ var _ = Describe("Executing a plugin, unit tests", func() {
Expect(env).To(ContainElement("CNI_PATH=dummy")) Expect(env).To(ContainElement("CNI_PATH=dummy"))
}) })
}) })
}) })
}) })

View File

@ -58,7 +58,7 @@ var _ = Describe("RawExec", func() {
"CNI_PATH=/some/bin/path", "CNI_PATH=/some/bin/path",
"CNI_IFNAME=some-eth0", "CNI_IFNAME=some-eth0",
} }
stdin = []byte(`{"some":"stdin-json"}`) stdin = []byte(`{"some":"stdin-json", "cniVersion": "0.2.0"}`)
execer = &invoke.RawExec{} execer = &invoke.RawExec{}
}) })

View File

@ -17,6 +17,7 @@
package skel package skel
import ( import (
"encoding/json"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -143,12 +144,28 @@ func createTypedError(f string, args ...interface{}) *types.Error {
} }
} }
func (t *dispatcher) validateVersion(stdinData []byte) error {
var netconf types.NetConf
if err := json.Unmarshal(stdinData, &netconf); err != nil {
return err
}
if netconf.CNIVersion == "" {
return fmt.Errorf("missing required config cniVersion")
}
return nil
}
func (t *dispatcher) pluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo) *types.Error { func (t *dispatcher) pluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo) *types.Error {
cmd, cmdArgs, err := t.getCmdArgsFromEnv() cmd, cmdArgs, err := t.getCmdArgsFromEnv()
if err != nil { if err != nil {
return createTypedError(err.Error()) return createTypedError(err.Error())
} }
if err = t.validateVersion(cmdArgs.StdinData); err != nil {
return createTypedError(err.Error())
}
switch cmd { switch cmd {
case "ADD": case "ADD":
err = cmdAdd(cmdArgs) err = cmdAdd(cmdArgs)

View File

@ -17,7 +17,6 @@ package skel
import ( import (
"bytes" "bytes"
"errors" "errors"
"io"
"strings" "strings"
"github.com/containernetworking/cni/pkg/types" "github.com/containernetworking/cni/pkg/types"
@ -48,7 +47,7 @@ func (c *fakeCmd) Func(args *CmdArgs) error {
var _ = Describe("dispatching to the correct callback", func() { var _ = Describe("dispatching to the correct callback", func() {
var ( var (
environment map[string]string environment map[string]string
stdin io.Reader stdinData string
stdout, stderr *bytes.Buffer stdout, stderr *bytes.Buffer
cmdAdd, cmdDel *fakeCmd cmdAdd, cmdDel *fakeCmd
dispatch *dispatcher dispatch *dispatcher
@ -65,13 +64,14 @@ var _ = Describe("dispatching to the correct callback", func() {
"CNI_ARGS": "some;extra;args", "CNI_ARGS": "some;extra;args",
"CNI_PATH": "/some/cni/path", "CNI_PATH": "/some/cni/path",
} }
stdin = strings.NewReader(`{ "some": "config" }`)
stdinData = `{ "some": "config", "cniVersion": "9.8.7" }`
stdout = &bytes.Buffer{} stdout = &bytes.Buffer{}
stderr = &bytes.Buffer{} stderr = &bytes.Buffer{}
versionInfo = version.PluginSupports("9.8.7") versionInfo = version.PluginSupports("9.8.7")
dispatch = &dispatcher{ dispatch = &dispatcher{
Getenv: func(key string) string { return environment[key] }, Getenv: func(key string) string { return environment[key] },
Stdin: stdin, Stdin: strings.NewReader(stdinData),
Stdout: stdout, Stdout: stdout,
Stderr: stderr, Stderr: stderr,
} }
@ -83,7 +83,7 @@ var _ = Describe("dispatching to the correct callback", func() {
IfName: "eth0", IfName: "eth0",
Args: "some;extra;args", Args: "some;extra;args",
Path: "/some/cni/path", Path: "/some/cni/path",
StdinData: []byte(`{ "some": "config" }`), StdinData: []byte(stdinData),
} }
}) })
@ -144,6 +144,22 @@ var _ = Describe("dispatching to the correct callback", func() {
}) })
}) })
Context("when the stdin data is missing the required cniVersion config", func() {
BeforeEach(func() {
dispatch.Stdin = strings.NewReader(`{ "some": "config" }`)
})
It("immediately returns a useful error", func() {
err := dispatch.pluginMain(cmdAdd.Func, cmdDel.Func, versionInfo)
Expect(err).To(MatchError("missing required config cniVersion"))
})
It("does not call either callback", func() {
Expect(cmdAdd.CallCount).To(Equal(0))
Expect(cmdDel.CallCount).To(Equal(0))
})
})
}) })
Context("when the CNI_COMMAND is DEL", func() { Context("when the CNI_COMMAND is DEL", func() {

View File

@ -56,6 +56,7 @@ func PluginSupports(supportedVersions ...string) PluginInfo {
} }
} }
// PluginDecoder can decode the response returned by a plugin's VERSION command
type PluginDecoder struct{} type PluginDecoder struct{}
func (_ *PluginDecoder) Decode(jsonBytes []byte) (PluginInfo, error) { func (_ *PluginDecoder) Decode(jsonBytes []byte) (PluginInfo, error) {

View File

@ -51,8 +51,9 @@ var _ = Describe("bridge Operations", func() {
conf := &NetConf{ conf := &NetConf{
NetConf: types.NetConf{ NetConf: types.NetConf{
Name: "testConfig", CNIVersion: "0.2.0",
Type: "bridge", Name: "testConfig",
Type: "bridge",
}, },
BrName: IFNAME, BrName: IFNAME,
IsGW: false, IsGW: false,
@ -95,8 +96,9 @@ var _ = Describe("bridge Operations", func() {
conf := &NetConf{ conf := &NetConf{
NetConf: types.NetConf{ NetConf: types.NetConf{
Name: "testConfig", CNIVersion: "0.2.0",
Type: "bridge", Name: "testConfig",
Type: "bridge",
}, },
BrName: IFNAME, BrName: IFNAME,
IsGW: false, IsGW: false,
@ -126,12 +128,14 @@ var _ = Describe("bridge Operations", func() {
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
conf := fmt.Sprintf(`{ conf := fmt.Sprintf(`{
"cniVersion": "0.2.0",
"name": "mynet", "name": "mynet",
"type": "bridge", "type": "bridge",
"bridge": "%s", "bridge": "%s",
"isDefaultGateway": true, "isDefaultGateway": true,
"ipMasq": false, "ipMasq": false,
"ipam": { "ipam": {
"cniVersion": "0.2.0",
"type": "host-local", "type": "host-local",
"subnet": "%s" "subnet": "%s"
} }
@ -253,15 +257,15 @@ var _ = Describe("bridge Operations", func() {
}) })
It("ensure bridge address", func() { It("ensure bridge address", func() {
const IFNAME = "bridge0" const IFNAME = "bridge0"
const EXPECTED_IP = "10.0.0.0/8" const EXPECTED_IP = "10.0.0.0/8"
const CHANGED_EXPECTED_IP = "10.1.2.3/16" const CHANGED_EXPECTED_IP = "10.1.2.3/16"
conf := &NetConf{ conf := &NetConf{
NetConf: types.NetConf{ NetConf: types.NetConf{
Name: "testConfig", CNIVersion: "0.2.0",
Type: "bridge", Name: "testConfig",
Type: "bridge",
}, },
BrName: IFNAME, BrName: IFNAME,
IsGW: true, IsGW: true,
@ -320,5 +324,4 @@ var _ = Describe("bridge Operations", func() {
}) })
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
}) })
}) })

View File

@ -63,8 +63,9 @@ var _ = Describe("ipvlan Operations", func() {
It("creates an ipvlan link in a non-default namespace", func() { It("creates an ipvlan link in a non-default namespace", func() {
conf := &NetConf{ conf := &NetConf{
NetConf: types.NetConf{ NetConf: types.NetConf{
Name: "testConfig", CNIVersion: "0.2.0",
Type: "ipvlan", Name: "testConfig",
Type: "ipvlan",
}, },
Master: MASTER_NAME, Master: MASTER_NAME,
Mode: "l2", Mode: "l2",
@ -101,10 +102,12 @@ var _ = Describe("ipvlan Operations", func() {
const IFNAME = "ipvl0" const IFNAME = "ipvl0"
conf := fmt.Sprintf(`{ conf := fmt.Sprintf(`{
"cniVersion": "0.2.0",
"name": "mynet", "name": "mynet",
"type": "ipvlan", "type": "ipvlan",
"master": "%s", "master": "%s",
"ipam": { "ipam": {
"cniVersion": "0.2.0",
"type": "host-local", "type": "host-local",
"subnet": "10.1.2.0/24" "subnet": "10.1.2.0/24"
} }

View File

@ -49,7 +49,7 @@ var _ = Describe("Loopback", func() {
fmt.Sprintf("CNI_ARGS=%s", "none"), fmt.Sprintf("CNI_ARGS=%s", "none"),
fmt.Sprintf("CNI_PATH=%s", "/some/test/path"), fmt.Sprintf("CNI_PATH=%s", "/some/test/path"),
} }
command.Stdin = strings.NewReader("this doesn't matter") command.Stdin = strings.NewReader(`{ "cniVersion": "0.1.0" }`)
}) })
AfterEach(func() { AfterEach(func() {

View File

@ -64,8 +64,9 @@ var _ = Describe("macvlan Operations", func() {
It("creates an macvlan link in a non-default namespace", func() { It("creates an macvlan link in a non-default namespace", func() {
conf := &NetConf{ conf := &NetConf{
NetConf: types.NetConf{ NetConf: types.NetConf{
Name: "testConfig", CNIVersion: "0.2.0",
Type: "macvlan", Name: "testConfig",
Type: "macvlan",
}, },
Master: MASTER_NAME, Master: MASTER_NAME,
Mode: "bridge", Mode: "bridge",
@ -101,10 +102,12 @@ var _ = Describe("macvlan Operations", func() {
const IFNAME = "macvl0" const IFNAME = "macvl0"
conf := fmt.Sprintf(`{ conf := fmt.Sprintf(`{
"cniVersion": "0.2.0",
"name": "mynet", "name": "mynet",
"type": "macvlan", "type": "macvlan",
"master": "%s", "master": "%s",
"ipam": { "ipam": {
"cniVersion": "0.2.0",
"type": "host-local", "type": "host-local",
"subnet": "10.1.2.0/24" "subnet": "10.1.2.0/24"
} }

View File

@ -43,11 +43,13 @@ var _ = Describe("ptp Operations", func() {
const IFNAME = "ptp0" const IFNAME = "ptp0"
conf := `{ conf := `{
"cniVersion": "0.2.0",
"name": "mynet", "name": "mynet",
"type": "ptp", "type": "ptp",
"ipMasq": true, "ipMasq": true,
"mtu": 5000, "mtu": 5000,
"ipam": { "ipam": {
"cniVersion": "0.2.0",
"type": "host-local", "type": "host-local",
"subnet": "10.1.2.0/24" "subnet": "10.1.2.0/24"
} }

View File

@ -60,14 +60,14 @@ var _ = Describe("No-op plugin", func() {
"CNI_IFNAME=some-eth0", "CNI_IFNAME=some-eth0",
"CNI_PATH=/some/bin/path", "CNI_PATH=/some/bin/path",
} }
cmd.Stdin = strings.NewReader(`{"some":"stdin-json"}`) cmd.Stdin = strings.NewReader(`{"some":"stdin-json", "cniVersion": "0.2.0"}`)
expectedCmdArgs = skel.CmdArgs{ expectedCmdArgs = skel.CmdArgs{
ContainerID: "some-container-id", ContainerID: "some-container-id",
Netns: "/some/netns/path", Netns: "/some/netns/path",
IfName: "some-eth0", IfName: "some-eth0",
Args: "DEBUG=" + debugFileName, Args: "DEBUG=" + debugFileName,
Path: "/some/bin/path", Path: "/some/bin/path",
StdinData: []byte(`{"some":"stdin-json"}`), StdinData: []byte(`{"some":"stdin-json", "cniVersion": "0.2.0"}`),
} }
}) })