-
-
Save rogpeppe/2317d3d453443260003e732672d57be4 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 libcni_test | |
import ( | |
"bytes" | |
"context" | |
"encoding/json" | |
"fmt" | |
"io/ioutil" | |
"net" | |
"os" | |
"path/filepath" | |
"reflect" | |
"strings" | |
"time" | |
"github.com/containernetworking/cni/libcni" | |
"github.com/containernetworking/cni/pkg/skel" | |
"github.com/containernetworking/cni/pkg/types" | |
"github.com/containernetworking/cni/pkg/types/current" | |
noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug" | |
gk "github.com/onsi/ginkgo" | |
gm "github.com/onsi/gomega" | |
) | |
type pluginInfo struct { | |
debugFilePath string | |
debug *noop_debug.Debug | |
config string | |
stdinData []byte | |
} | |
type portMapping struct { | |
HostPort int `json:"hostPort"` | |
ContainerPort int `json:"containerPort"` | |
Protocol string `json:"protocol"` | |
} | |
func stringInList(s string, list []string) bool { | |
for _, item := range list { | |
if s == item { | |
return true | |
} | |
} | |
return false | |
} | |
func newPluginInfo(configValue, prevResult string, injectDebugFilePath bool, result string, runtimeConfig map[string]interface{}, capabilities []string) pluginInfo { | |
debugFile, err := ioutil.TempFile("", "cni_debug") | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
gm.Expect(debugFile.Close()).To(gm.Succeed()) | |
debugFilePath := debugFile.Name() | |
debug := &noop_debug.Debug{ | |
ReportResult: result, | |
} | |
gm.Expect(debug.WriteDebug(debugFilePath)).To(gm.Succeed()) | |
// config is what would be in the plugin's on-disk configuration | |
// without runtime injected keys | |
config := fmt.Sprintf(`{"type": "noop", "some-key": "%s"`, configValue) | |
if prevResult != "" { | |
config += fmt.Sprintf(`, "prevResult": %s`, prevResult) | |
} | |
if injectDebugFilePath { | |
config += fmt.Sprintf(`, "debugFile": %q`, debugFilePath) | |
} | |
if len(capabilities) > 0 { | |
config += `, "capabilities": {` | |
for i, c := range capabilities { | |
if i > 0 { | |
config += ", " | |
} | |
config += fmt.Sprintf(`"%s": true`, c) | |
} | |
config += "}" | |
} | |
config += "}" | |
// stdinData is what the runtime should pass to the plugin's stdin, | |
// including injected keys like 'name', 'cniVersion', and 'runtimeConfig' | |
newConfig := make(map[string]interface{}) | |
err = json.Unmarshal([]byte(config), &newConfig) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
newConfig["name"] = "some-list" | |
newConfig["cniVersion"] = current.ImplementedSpecVersion | |
// Only include standard runtime config and capability args that this plugin advertises | |
newRuntimeConfig := make(map[string]interface{}) | |
for key, value := range runtimeConfig { | |
if stringInList(key, capabilities) { | |
newRuntimeConfig[key] = value | |
} | |
} | |
if len(newRuntimeConfig) > 0 { | |
newConfig["runtimeConfig"] = newRuntimeConfig | |
} | |
stdinData, err := json.Marshal(newConfig) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
return pluginInfo{ | |
debugFilePath: debugFilePath, | |
debug: debug, | |
config: config, | |
stdinData: stdinData, | |
} | |
} | |
func resultCacheFilePath(cacheDirPath, netName string, rt *libcni.RuntimeConf) string { | |
fName := fmt.Sprintf("%s-%s-%s", netName, rt.ContainerID, rt.IfName) | |
return filepath.Join(cacheDirPath, "results", fName) | |
} | |
var _ = gk.Describe("Invoking plugins", func() { | |
var cacheDirPath string | |
gk.BeforeEach(func() { | |
var err error | |
cacheDirPath, err = ioutil.TempDir("", "cni_cachedir") | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
}) | |
checko(func() { | |
gm.Expect(os.RemoveAll(cacheDirPath)).To(gm.Succeed()) | |
}) | |
gk.Describe("Capabilities", func() { | |
var ( | |
debugFilePath string | |
debug *noop_debug.Debug | |
pluginConfig []byte | |
cniConfig *libcni.CNIConfig | |
runtimeConfig *libcni.RuntimeConf | |
netConfig *libcni.NetworkConfig | |
ctx context.Context | |
) | |
gk.BeforeEach(func() { | |
debugFile, err := ioutil.TempFile("", "cni_debug") | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
gm.Expect(debugFile.Close()).To(gm.Succeed()) | |
debugFilePath = debugFile.Name() | |
debug = &noop_debug.Debug{} | |
gm.Expect(debug.WriteDebug(debugFilePath)).To(gm.Succeed()) | |
pluginConfig = []byte(fmt.Sprintf(`{ | |
"type": "noop", | |
"name": "apitest", | |
"cniVersion": "%s", | |
"capabilities": { | |
"portMappings": true, | |
"somethingElse": true, | |
"noCapability": false | |
} | |
}`, current.ImplementedSpecVersion)) | |
netConfig, err = libcni.ConfFromgk.Bytes(pluginConfig) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
cniConfig = libcni.NewCNIConfigWithCacheDir([]string{filepath.Dir(pluginPaths["noop"])}, cacheDirPath, nil) | |
runtimeConfig = &libcni.RuntimeConf{ | |
ContainerID: "some-container-id", | |
NetNS: "/some/netns/path", | |
IfName: "some-eth0", | |
Args: [][2]string{{"DEBUG", debugFilePath}}, | |
CapabilityArgs: map[string]interface{}{ | |
"portMappings": []portMapping{ | |
{HostPort: 8080, ContainerPort: 80, Protocol: "tcp"}, | |
}, | |
"somethingElse": []string{"foobar", "baz"}, | |
"noCapability": true, | |
"notAdded": []bool{true, false}, | |
}, | |
} | |
ctx = context.TODO() | |
}) | |
gk.AfterEach(func() { | |
gm.Expect(os.RemoveAll(debugFilePath)).To(gm.Succeed()) | |
}) | |
gk.It("adds correct runtime config for capabilities to stdin", func() { | |
_, err := cniConfig.AddNetwork(ctx, netConfig, runtimeConfig) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
debug, err = noop_debug.ReadDebug(debugFilePath) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
gm.Expect(debug.Command).To(gm.Equal("ADD")) | |
conf := make(map[string]interface{}) | |
err = json.Unmarshal(debug.CmdArgs.StdinData, &conf) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
// We expect runtimeConfig keys only for portMappings and somethingElse | |
rawRc := conf["runtimeConfig"] | |
rc, ok := rawRc.(map[string]interface{}) | |
gm.Expect(ok).To(gm.Equal(true)) | |
expectedKeys := []string{"portMappings", "somethingElse"} | |
gm.Expect(len(rc)).To(gm.Equal(len(expectedKeys))) | |
for _, key := range expectedKeys { | |
_, ok := rc[key] | |
gm.Expect(ok).To(gm.Equal(true)) | |
} | |
}) | |
gk.It("adds no runtimeConfig when the plugin advertises no used capabilities", func() { | |
// Replace CapabilityArgs with ones we know the plugin | |
// doesn't support | |
runtimeConfig.CapabilityArgs = map[string]interface{}{ | |
"portMappings22": []portMapping{ | |
{HostPort: 8080, ContainerPort: 80, Protocol: "tcp"}, | |
}, | |
"somethingElse22": []string{"foobar", "baz"}, | |
} | |
_, err := cniConfig.AddNetwork(ctx, netConfig, runtimeConfig) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
debug, err = noop_debug.ReadDebug(debugFilePath) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
gm.Expect(debug.Command).To(gm.Equal("ADD")) | |
conf := make(map[string]interface{}) | |
err = json.Unmarshal(debug.CmdArgs.StdinData, &conf) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
// No intersection of plugin capabilities and CapabilityArgs, | |
// so plugin should not receive a "runtimeConfig" key | |
_, ok := conf["runtimeConfig"] | |
gm.Expect(ok).Should(gm.BeFalse()) | |
}) | |
gk.It("outputs correct capabilities for validate", func() { | |
caps, err := cniConfig.ValidateNetwork(ctx, netConfig) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
gm.Expect(caps).To(gm.ConsistOf("portMappings", "somethingElse")) | |
}) | |
}) | |
gk.Describe("Invoking a single plugin", func() { | |
var ( | |
debugFilePath string | |
debug *noop_debug.Debug | |
cniBinPath string | |
pluginConfig string | |
cniConfig *libcni.CNIConfig | |
netConfig *libcni.NetworkConfig | |
runtimeConfig *libcni.RuntimeConf | |
ctx context.Context | |
expectedCmdArgs skel.CmdArgs | |
) | |
gk.BeforeEach(func() { | |
debugFile, err := ioutil.TempFile("", "cni_debug") | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
gm.Expect(debugFile.Close()).To(gm.Succeed()) | |
debugFilePath = debugFile.Name() | |
debug = &noop_debug.Debug{ | |
ReportResult: `{ | |
"cniVersion": "0.4.0", | |
"ips": [{"version": "4", "address": "10.1.2.3/24"}], | |
"dns": {} | |
}`, | |
} | |
gm.Expect(debug.WriteDebug(debugFilePath)).To(gm.Succeed()) | |
portMappings := []portMapping{ | |
{HostPort: 8080, ContainerPort: 80, Protocol: "tcp"}, | |
} | |
cniBinPath = filepath.Dir(pluginPaths["noop"]) | |
pluginConfig = fmt.Sprintf(`{ | |
"type": "noop", | |
"name": "apitest", | |
"some-key": "some-value", | |
"cniVersion": "%s", | |
"capabilities": { "portMappings": true } | |
}`, current.ImplementedSpecVersion) | |
cniConfig = libcni.NewCNIConfigWithCacheDir([]string{cniBinPath}, cacheDirPath, nil) | |
netConfig, err = libcni.ConfFromgk.Bytes([]byte(pluginConfig)) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
runtimeConfig = &libcni.RuntimeConf{ | |
ContainerID: "some-container-id", | |
NetNS: "/some/netns/path", | |
IfName: "some-eth0", | |
Args: [][2]string{{"DEBUG", debugFilePath}}, | |
CapabilityArgs: map[string]interface{}{ | |
"portMappings": portMappings, | |
}, | |
} | |
// inject runtime args into the expected plugin config | |
conf := make(map[string]interface{}) | |
err = json.Unmarshal([]byte(pluginConfig), &conf) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
conf["runtimeConfig"] = map[string]interface{}{ | |
"portMappings": portMappings, | |
} | |
newgk.Bytes, err := json.Marshal(conf) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
expectedCmdArgs = skel.CmdArgs{ | |
ContainerID: "some-container-id", | |
Netns: "/some/netns/path", | |
IfName: "some-eth0", | |
Args: "DEBUG=" + debugFilePath, | |
Path: cniBinPath, | |
StdinData: newgk.Bytes, | |
} | |
ctx = context.TODO() | |
}) | |
gk.AfterEach(func() { | |
gm.Expect(os.RemoveAll(debugFilePath)).To(gm.Succeed()) | |
}) | |
gk.Describe("AddNetwork", func() { | |
gk.It("executes the plugin with command ADD", func() { | |
r, err := cniConfig.AddNetwork(ctx, netConfig, runtimeConfig) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
result, err := current.GetResult(r) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
gm.Expect(result).To(gm.Equal(¤t.Result{ | |
CNIVersion: current.ImplementedSpecVersion, | |
IPs: []*current.IPConfig{ | |
{ | |
Version: "4", | |
Address: net.IPNet{ | |
IP: net.ParseIP("10.1.2.3"), | |
Mask: net.IPv4Mask(255, 255, 255, 0), | |
}, | |
}, | |
}, | |
})) | |
debug, err := noop_debug.ReadDebug(debugFilePath) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
gm.Expect(debug.Command).To(gm.Equal("ADD")) | |
gm.Expect(debug.CmdArgs).To(gm.Equal(expectedCmdArgs)) | |
gm.Expect(string(debug.CmdArgs.StdinData)).To(gm.ContainSubstring("\"portMappings\":")) | |
// Ensure the cached config matches the sent one | |
cachedConfig, newRt, err := cniConfig.GetNetworkCachedConfig(netConfig, runtimeConfig) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
gm.Expect(reflect.Deepgm.Equal(cachedConfig, []byte(pluginConfig))).To(gm.BeTrue()) | |
gm.Expect(reflect.Deepgm.Equal(newRt.Args, runtimeConfig.Args)).To(gm.BeTrue()) | |
// CapabilityArgs are freeform, so we have to match their JSON not | |
// the Go structs (which could be unmarshalled differently than the | |
// struct that was marshalled). | |
expectedCAgk.Bytes, err := json.Marshal(runtimeConfig.CapabilityArgs) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
foundCAgk.Bytes, err := json.Marshal(newRt.CapabilityArgs) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
gm.Expect(foundCAgk.Bytes).To(gm.MatchJSON(expectedCAgk.Bytes)) | |
// Ensure the cached result matches the returned one | |
cachedResult, err := cniConfig.GetNetworkCachedResult(netConfig, runtimeConfig) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
result2, err := current.GetResult(cachedResult) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
cachedJson, err := json.Marshal(result2) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
returnedJson, err := json.Marshal(result) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
gm.Expect(cachedJson).To(gm.MatchJSON(returnedJson)) | |
}) | |
gk.Context("when finding the plugin fails", func() { | |
gk.BeforeEach(func() { | |
netConfig.Network.Type = "does-not-exist" | |
}) | |
gk.It("returns the error", func() { | |
_, err := cniConfig.AddNetwork(ctx, netConfig, runtimeConfig) | |
gm.Expect(err).To(gm.MatchError(gm.ContainSubstring(`failed to find plugin "does-not-exist"`))) | |
}) | |
}) | |
gk.Context("when the plugin errors", func() { | |
gk.BeforeEach(func() { | |
debug.ReportError = "plugin error: banana" | |
gm.Expect(debug.WriteDebug(debugFilePath)).To(gm.Succeed()) | |
}) | |
gk.It("unmarshals and returns the error", func() { | |
result, err := cniConfig.AddNetwork(ctx, netConfig, runtimeConfig) | |
gm.Expect(result).To(gm.BeNil()) | |
gm.Expect(err).To(gm.MatchError("plugin error: banana")) | |
}) | |
}) | |
gk.Context("when the cache directory cannot be accessed", func() { | |
gk.It("returns an error", func() { | |
// Make the results directory inaccessible by making it a | |
// file instead of a directory | |
tmpPath := filepath.Join(cacheDirPath, "results") | |
err := ioutil.WriteFile(tmpPath, []byte("afdsasdfasdf"), 0600) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
result, err := cniConfig.AddNetwork(ctx, netConfig, runtimeConfig) | |
gm.Expect(result).To(gm.BeNil()) | |
gm.Expect(err).To(gm.HaveOccurred()) | |
}) | |
}) | |
}) | |
gk.Describe("CheckNetwork", func() { | |
gk.It("executes the plugin with command CHECK", func() { | |
cacheFile := resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig) | |
err := os.MkdirAll(filepath.Dir(cacheFile), 0700) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
cachedJson := `{ | |
"cniVersion": "0.4.0", | |
"ips": [{"version": "4", "address": "10.1.2.3/24"}], | |
"dns": {} | |
}` | |
err = ioutil.WriteFile(cacheFile, []byte(cachedJson), 0600) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
debug, err := noop_debug.ReadDebug(debugFilePath) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
gm.Expect(debug.Command).To(gm.Equal("CHECK")) | |
gm.Expect(string(debug.CmdArgs.StdinData)).To(gm.ContainSubstring("\"portMappings\":")) | |
// Explicitly match stdin data as json after | |
// inserting the expected prevResult | |
var data, data2 map[string]interface{} | |
err = json.Unmarshal(expectedCmdArgs.StdinData, &data) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
err = json.Unmarshal([]byte(cachedJson), &data2) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
data["prevResult"] = data2 | |
expectedStdinJson, err := json.Marshal(data) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
gm.Expect(debug.CmdArgs.StdinData).To(gm.MatchJSON(expectedStdinJson)) | |
debug.CmdArgs.StdinData = nil | |
expectedCmdArgs.StdinData = nil | |
gm.Expect(debug.CmdArgs).To(gm.Equal(expectedCmdArgs)) | |
}) | |
gk.Context("when finding the plugin fails", func() { | |
gk.BeforeEach(func() { | |
netConfig.Network.Type = "does-not-exist" | |
}) | |
gk.It("returns the error", func() { | |
err := cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig) | |
gm.Expect(err).To(gm.MatchError(gm.ContainSubstring(`failed to find plugin "does-not-exist"`))) | |
}) | |
}) | |
gk.Context("when the plugin errors", func() { | |
gk.BeforeEach(func() { | |
debug.ReportError = "plugin error: banana" | |
gm.Expect(debug.WriteDebug(debugFilePath)).To(gm.Succeed()) | |
}) | |
gk.It("unmarshals and returns the error", func() { | |
err := cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig) | |
gm.Expect(err).To(gm.MatchError("plugin error: banana")) | |
}) | |
}) | |
gk.Context("when CHECK is called with a configuration version", func() { | |
var cacheFile string | |
gk.BeforeEach(func() { | |
cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig) | |
err := os.MkdirAll(filepath.Dir(cacheFile), 0700) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
}) | |
gk.Context("less than 0.4.0", func() { | |
gk.It("fails as CHECK is not supported before 0.4.0", func() { | |
// Generate plugin config with older version | |
pluginConfig = `{ | |
"type": "noop", | |
"name": "apitest", | |
"cniVersion": "0.3.1" | |
}` | |
var err error | |
netConfig, err = libcni.ConfFromgk.Bytes([]byte(pluginConfig)) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig) | |
gm.Expect(err).To(gm.MatchError("configuration version \"0.3.1\" does not support the CHECK command")) | |
debug, err := noop_debug.ReadDebug(debugFilePath) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
gm.Expect(string(debug.CmdArgs.StdinData)).NotTo(gm.ContainSubstring("\"prevResult\":")) | |
}) | |
}) | |
gk.Context("containing only a cached result", func() { | |
gk.It("only passes a prevResult to the plugin", func() { | |
err := ioutil.WriteFile(cacheFile, []byte(`{ | |
"cniVersion": "0.4.0", | |
"ips": [{"version": "4", "address": "10.1.2.3/24"}], | |
"dns": {} | |
}`), 0600) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
debug, err := noop_debug.ReadDebug(debugFilePath) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
gm.Expect(string(debug.CmdArgs.StdinData)).To(gm.ContainSubstring("\"prevResult\":")) | |
gm.Expect(string(debug.CmdArgs.StdinData)).NotTo(gm.ContainSubstring("\"config\":")) | |
gm.Expect(string(debug.CmdArgs.StdinData)).NotTo(gm.ContainSubstring("\"kind\":")) | |
}) | |
}) | |
gk.Context("equal to 0.4.0", func() { | |
gk.It("passes a prevResult to the plugin", func() { | |
err := ioutil.WriteFile(cacheFile, []byte(`{ | |
"cniVersion": "0.4.0", | |
"ips": [{"version": "4", "address": "10.1.2.3/24"}], | |
"dns": {} | |
}`), 0600) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
debug, err := noop_debug.ReadDebug(debugFilePath) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
gm.Expect(string(debug.CmdArgs.StdinData)).To(gm.ContainSubstring("\"prevResult\":")) | |
}) | |
}) | |
}) | |
gk.Context("when the cached result", func() { | |
var cacheFile string | |
gk.BeforeEach(func() { | |
cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig) | |
err := os.MkdirAll(filepath.Dir(cacheFile), 0700) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
}) | |
gk.Context("is invalid JSON", func() { | |
gk.It("returns an error", func() { | |
err := ioutil.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0600) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig) | |
gm.Expect(err).To(gm.MatchError("failed to get network \"apitest\" cached result: decoding version from network config: invalid character 'a' looking for beginning of value")) | |
}) | |
}) | |
gk.Context("version doesn't match the config version", func() { | |
gk.It("succeeds when the cached result can be converted", func() { | |
err := ioutil.WriteFile(cacheFile, []byte(`{ | |
"cniVersion": "0.3.1", | |
"ips": [{"version": "4", "address": "10.1.2.3/24"}], | |
"dns": {} | |
}`), 0600) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
}) | |
gk.It("returns an error when the cached result cannot be converted", func() { | |
err := ioutil.WriteFile(cacheFile, []byte(`{ | |
"cniVersion": "0.4567.0", | |
"ips": [{"version": "4", "address": "10.1.2.3/24"}], | |
"dns": {} | |
}`), 0600) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig) | |
gm.Expect(err).To(gm.MatchError("failed to get network \"apitest\" cached result: unsupported CNI result version \"0.4567.0\"")) | |
}) | |
}) | |
}) | |
}) | |
gk.Describe("DelNetwork", func() { | |
gk.It("executes the plugin with command DEL", func() { | |
cacheFile := resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig) | |
err := os.MkdirAll(filepath.Dir(cacheFile), 0700) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
cachedJson := `{ | |
"cniVersion": "0.4.0", | |
"ips": [{"version": "4", "address": "10.1.2.3/24"}], | |
"dns": {} | |
}` | |
err = ioutil.WriteFile(cacheFile, []byte(cachedJson), 0600) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
debug, err := noop_debug.ReadDebug(debugFilePath) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
gm.Expect(debug.Command).To(gm.Equal("DEL")) | |
gm.Expect(string(debug.CmdArgs.StdinData)).To(gm.ContainSubstring("\"portMappings\":")) | |
// Explicitly match stdin data as json after | |
// inserting the expected prevResult | |
var data, data2 map[string]interface{} | |
err = json.Unmarshal(expectedCmdArgs.StdinData, &data) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
err = json.Unmarshal([]byte(cachedJson), &data2) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
data["prevResult"] = data2 | |
expectedStdinJson, err := json.Marshal(data) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
gm.Expect(debug.CmdArgs.StdinData).To(gm.MatchJSON(expectedStdinJson)) | |
debug.CmdArgs.StdinData = nil | |
expectedCmdArgs.StdinData = nil | |
gm.Expect(debug.CmdArgs).To(gm.Equal(expectedCmdArgs)) | |
// Ensure the cached result no longer exists | |
cachedResult, err := cniConfig.GetNetworkCachedResult(netConfig, runtimeConfig) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
gm.Expect(cachedResult).To(gm.BeNil()) | |
// Ensure the cached config no longer exists | |
cachedConfig, newRt, err := cniConfig.GetNetworkCachedConfig(netConfig, runtimeConfig) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
gm.Expect(cachedConfig).To(gm.BeNil()) | |
gm.Expect(newRt).To(gm.BeNil()) | |
}) | |
gk.Context("when finding the plugin fails", func() { | |
gk.BeforeEach(func() { | |
netConfig.Network.Type = "does-not-exist" | |
}) | |
gk.It("returns the error", func() { | |
err := cniConfig.DelNetwork(ctx, netConfig, runtimeConfig) | |
gm.Expect(err).To(gm.MatchError(gm.ContainSubstring(`failed to find plugin "does-not-exist"`))) | |
}) | |
}) | |
gk.Context("when the plugin errors", func() { | |
gk.BeforeEach(func() { | |
debug.ReportError = "plugin error: banana" | |
gm.Expect(debug.WriteDebug(debugFilePath)).To(gm.Succeed()) | |
}) | |
gk.It("unmarshals and returns the error", func() { | |
err := cniConfig.DelNetwork(ctx, netConfig, runtimeConfig) | |
gm.Expect(err).To(gm.MatchError("plugin error: banana")) | |
}) | |
}) | |
gk.Context("when DEL is called twice", func() { | |
var resultCacheFile string | |
gk.BeforeEach(func() { | |
resultCacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig) | |
err := os.MkdirAll(filepath.Dir(resultCacheFile), 0700) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
}) | |
gk.It("deletes the cached result and config after the first DEL", func() { | |
err := ioutil.WriteFile(resultCacheFile, []byte(`{ | |
"cniVersion": "0.4.0", | |
"ips": [{"version": "4", "address": "10.1.2.3/24"}], | |
"dns": {} | |
}`), 0600) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
_, err = ioutil.ReadFile(resultCacheFile) | |
gm.Expect(err).To(gm.HaveOccurred()) | |
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
}) | |
}) | |
gk.Context("when DEL is called with a configuration version", func() { | |
var cacheFile string | |
gk.BeforeEach(func() { | |
cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig) | |
err := os.MkdirAll(filepath.Dir(cacheFile), 0700) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
}) | |
gk.Context("less than 0.4.0", func() { | |
gk.It("does not pass a prevResult to the plugin", func() { | |
err := ioutil.WriteFile(cacheFile, []byte(`{ | |
"cniVersion": "0.3.1", | |
"ips": [{"version": "4", "address": "10.1.2.3/24"}], | |
"dns": {} | |
}`), 0600) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
// Generate plugin config with older version | |
pluginConfig = `{ | |
"type": "noop", | |
"name": "apitest", | |
"cniVersion": "0.3.1" | |
}` | |
netConfig, err = libcni.ConfFromgk.Bytes([]byte(pluginConfig)) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
debug, err := noop_debug.ReadDebug(debugFilePath) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
gm.Expect(debug.Command).To(gm.Equal("DEL")) | |
gm.Expect(string(debug.CmdArgs.StdinData)).NotTo(gm.ContainSubstring("\"prevResult\":")) | |
}) | |
}) | |
gk.Context("equal to 0.4.0", func() { | |
gk.It("passes a prevResult to the plugin", func() { | |
err := ioutil.WriteFile(cacheFile, []byte(`{ | |
"cniVersion": "0.4.0", | |
"ips": [{"version": "4", "address": "10.1.2.3/24"}], | |
"dns": {} | |
}`), 0600) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
debug, err := noop_debug.ReadDebug(debugFilePath) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
gm.Expect(debug.Command).To(gm.Equal("DEL")) | |
gm.Expect(string(debug.CmdArgs.StdinData)).To(gm.ContainSubstring("\"prevResult\":")) | |
}) | |
}) | |
}) | |
gk.Context("when the cached", func() { | |
var cacheFile string | |
gk.BeforeEach(func() { | |
cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig) | |
err := os.MkdirAll(filepath.Dir(cacheFile), 0700) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
}) | |
gk.Context("result is invalid JSON", func() { | |
gk.It("returns an error", func() { | |
err := ioutil.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0600) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig) | |
gm.Expect(err).To(gm.MatchError("failed to get network \"apitest\" cached result: decoding version from network config: invalid character 'a' looking for beginning of value")) | |
}) | |
}) | |
gk.Context("result version doesn't match the config version", func() { | |
gk.It("succeeds when the cached result can be converted", func() { | |
err := ioutil.WriteFile(cacheFile, []byte(`{ | |
"cniVersion": "0.3.1", | |
"ips": [{"version": "4", "address": "10.1.2.3/24"}], | |
"dns": {} | |
}`), 0600) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
}) | |
gk.It("returns an error when the cached result cannot be converted", func() { | |
err := ioutil.WriteFile(cacheFile, []byte(`{ | |
"cniVersion": "0.4567.0", | |
"ips": [{"version": "4", "address": "10.1.2.3/24"}], | |
"dns": {} | |
}`), 0600) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig) | |
gm.Expect(err).To(gm.MatchError("failed to get network \"apitest\" cached result: unsupported CNI result version \"0.4567.0\"")) | |
}) | |
}) | |
}) | |
}) | |
gk.Describe("GetVersionInfo", func() { | |
gk.It("executes the plugin with the command VERSION", func() { | |
versionInfo, err := cniConfig.GetVersionInfo(ctx, "noop") | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
gm.Expect(versionInfo).NotTo(gm.BeNil()) | |
gm.Expect(versionInfo.SupportedVersions()).To(gm.Equal([]string{ | |
"0.-42.0", "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0", | |
})) | |
}) | |
gk.Context("when finding the plugin fails", func() { | |
gk.It("returns the error", func() { | |
_, err := cniConfig.GetVersionInfo(ctx, "does-not-exist") | |
gm.Expect(err).To(gm.MatchError(gm.ContainSubstring(`failed to find plugin "does-not-exist"`))) | |
}) | |
}) | |
}) | |
gk.Describe("ValidateNetwork", func() { | |
gk.It("validates a good configuration", func() { | |
_, err := cniConfig.ValidateNetwork(ctx, netConfig) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
}) | |
gk.It("catches non-existent plugins", func() { | |
netConfig.Network.Type = "nope" | |
_, err := cniConfig.ValidateNetwork(ctx, netConfig) | |
gm.Expect(err).To(gm.MatchError("failed to find plugin \"nope\" in path [" + cniConfig.Path[0] + "]")) | |
}) | |
gk.It("catches unsupported versions", func() { | |
netConfig.Network.CNIVersion = "broken" | |
_, err := cniConfig.ValidateNetwork(ctx, netConfig) | |
gm.Expect(err).To(gm.MatchError("plugin noop does not support config version \"broken\"")) | |
}) | |
}) | |
}) | |
gk.Describe("Invoking a plugin list", func() { | |
var ( | |
plugins []pluginInfo | |
cniBinPath string | |
cniConfig *libcni.CNIConfig | |
netConfigList *libcni.NetworkConfigList | |
runtimeConfig *libcni.RuntimeConf | |
ctx context.Context | |
ipResult string | |
expectedCmdArgs skel.CmdArgs | |
) | |
gk.BeforeEach(func() { | |
var err error | |
capabilityArgs := map[string]interface{}{ | |
"portMappings": []portMapping{ | |
{HostPort: 8080, ContainerPort: 80, Protocol: "tcp"}, | |
}, | |
"otherCapability": 33, | |
} | |
cniBinPath = filepath.Dir(pluginPaths["noop"]) | |
cniConfig = libcni.NewCNIConfigWithCacheDir([]string{cniBinPath}, cacheDirPath, nil) | |
runtimeConfig = &libcni.RuntimeConf{ | |
ContainerID: "some-container-id", | |
NetNS: "/some/netns/path", | |
IfName: "some-eth0", | |
Args: [][2]string{{"FOO", "BAR"}}, | |
CapabilityArgs: capabilityArgs, | |
} | |
expectedCmdArgs = skel.CmdArgs{ | |
ContainerID: runtimeConfig.ContainerID, | |
Netns: runtimeConfig.NetNS, | |
IfName: runtimeConfig.IfName, | |
Args: "FOO=BAR", | |
Path: cniBinPath, | |
} | |
rc := map[string]interface{}{ | |
"containerId": runtimeConfig.ContainerID, | |
"netNs": runtimeConfig.NetNS, | |
"ifName": runtimeConfig.IfName, | |
"args": map[string]string{ | |
"FOO": "BAR", | |
}, | |
"portMappings": capabilityArgs["portMappings"], | |
"otherCapability": capabilityArgs["otherCapability"], | |
} | |
ipResult = fmt.Sprintf(`{"cniVersion": "%s", "dns":{},"ips":[{"version": "4", "address": "10.1.2.3/24"}]}`, current.ImplementedSpecVersion) | |
plugins = make([]pluginInfo, 3) | |
plugins[0] = newPluginInfo("some-value", "", true, ipResult, rc, []string{"portMappings", "otherCapability"}) | |
plugins[1] = newPluginInfo("some-other-value", ipResult, true, "PASSTHROUGH", rc, []string{"otherCapability"}) | |
plugins[2] = newPluginInfo("yet-another-value", ipResult, true, "INJECT-DNS", rc, []string{}) | |
configList := []byte(fmt.Sprintf(`{ | |
"name": "some-list", | |
"cniVersion": "%s", | |
"plugins": [ | |
%s, | |
%s, | |
%s | |
] | |
}`, current.ImplementedSpecVersion, plugins[0].config, plugins[1].config, plugins[2].config)) | |
netConfigList, err = libcni.ConfListFromgk.Bytes(configList) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
ctx = context.TODO() | |
}) | |
gk.AfterEach(func() { | |
for _, p := range plugins { | |
gm.Expect(os.RemoveAll(p.debugFilePath)).To(gm.Succeed()) | |
} | |
}) | |
gk.Describe("AddNetworkList", func() { | |
gk.It("executes all plugins with command ADD and returns an intermediate result", func() { | |
r, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
result, err := current.GetResult(r) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
gm.Expect(result).To(gm.Equal(¤t.Result{ | |
CNIVersion: current.ImplementedSpecVersion, | |
// IP4 added by first plugin | |
IPs: []*current.IPConfig{ | |
{ | |
Version: "4", | |
Address: net.IPNet{ | |
IP: net.ParseIP("10.1.2.3"), | |
Mask: net.IPv4Mask(255, 255, 255, 0), | |
}, | |
}, | |
}, | |
// DNS injected by last plugin | |
DNS: types.DNS{ | |
Nameservers: []string{"1.2.3.4"}, | |
}, | |
})) | |
for i := 0; i < len(plugins); i++ { | |
debug, err := noop_debug.ReadDebug(plugins[i].debugFilePath) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
gm.Expect(debug.Command).To(gm.Equal("ADD")) | |
// Must explicitly match JSON due to dict element ordering | |
gm.Expect(debug.CmdArgs.StdinData).To(gm.MatchJSON(plugins[i].stdinData)) | |
debug.CmdArgs.StdinData = nil | |
gm.Expect(debug.CmdArgs).To(gm.Equal(expectedCmdArgs)) | |
} | |
}) | |
gk.It("writes the correct cached result", func() { | |
r, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
result, err := current.GetResult(r) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
// Ensure the cached result matches the returned one | |
cachedResult, err := cniConfig.GetNetworkListCachedResult(netConfigList, runtimeConfig) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
result2, err := current.GetResult(cachedResult) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
cachedJson, err := json.Marshal(result2) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
returnedJson, err := json.Marshal(result) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
gm.Expect(cachedJson).To(gm.MatchJSON(returnedJson)) | |
}) | |
gk.It("writes the correct cached config", func() { | |
_, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig) | |
gm.Expect(err).NotTo(gm.HaveOccurred()) | |
// Ensure the cached config matches the returned one | |
cached |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment