github.com/baraj55/containernetworking-cni@v0.7.2-0.20200219164625-56ace59a9e7f/libcni/api_test.go (about)

     1  // Copyright 2016 CNI authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package libcni_test
    16  
    17  import (
    18  	"bytes"
    19  	"context"
    20  	"encoding/json"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"net"
    24  	"os"
    25  	"path/filepath"
    26  	"reflect"
    27  	"strings"
    28  	"time"
    29  
    30  	"github.com/containernetworking/cni/libcni"
    31  	"github.com/containernetworking/cni/pkg/skel"
    32  	"github.com/containernetworking/cni/pkg/types"
    33  	"github.com/containernetworking/cni/pkg/types/current"
    34  	noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
    35  
    36  	. "github.com/onsi/ginkgo"
    37  	. "github.com/onsi/gomega"
    38  )
    39  
    40  type pluginInfo struct {
    41  	debugFilePath string
    42  	debug         *noop_debug.Debug
    43  	config        string
    44  	stdinData     []byte
    45  }
    46  
    47  type portMapping struct {
    48  	HostPort      int    `json:"hostPort"`
    49  	ContainerPort int    `json:"containerPort"`
    50  	Protocol      string `json:"protocol"`
    51  }
    52  
    53  func stringInList(s string, list []string) bool {
    54  	for _, item := range list {
    55  		if s == item {
    56  			return true
    57  		}
    58  	}
    59  	return false
    60  }
    61  
    62  func newPluginInfo(configValue, prevResult string, injectDebugFilePath bool, result string, runtimeConfig map[string]interface{}, capabilities []string) pluginInfo {
    63  	debugFile, err := ioutil.TempFile("", "cni_debug")
    64  	Expect(err).NotTo(HaveOccurred())
    65  	Expect(debugFile.Close()).To(Succeed())
    66  	debugFilePath := debugFile.Name()
    67  
    68  	debug := &noop_debug.Debug{
    69  		ReportResult: result,
    70  	}
    71  	Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
    72  
    73  	// config is what would be in the plugin's on-disk configuration
    74  	// without runtime injected keys
    75  	config := fmt.Sprintf(`{"type": "noop", "some-key": "%s"`, configValue)
    76  	if prevResult != "" {
    77  		config += fmt.Sprintf(`, "prevResult": %s`, prevResult)
    78  	}
    79  	if injectDebugFilePath {
    80  		config += fmt.Sprintf(`, "debugFile": %q`, debugFilePath)
    81  	}
    82  	if len(capabilities) > 0 {
    83  		config += `, "capabilities": {`
    84  		for i, c := range capabilities {
    85  			if i > 0 {
    86  				config += ", "
    87  			}
    88  			config += fmt.Sprintf(`"%s": true`, c)
    89  		}
    90  		config += "}"
    91  	}
    92  	config += "}"
    93  
    94  	// stdinData is what the runtime should pass to the plugin's stdin,
    95  	// including injected keys like 'name', 'cniVersion', and 'runtimeConfig'
    96  	newConfig := make(map[string]interface{})
    97  	err = json.Unmarshal([]byte(config), &newConfig)
    98  	Expect(err).NotTo(HaveOccurred())
    99  	newConfig["name"] = "some-list"
   100  	newConfig["cniVersion"] = current.ImplementedSpecVersion
   101  
   102  	// Only include standard runtime config and capability args that this plugin advertises
   103  	newRuntimeConfig := make(map[string]interface{})
   104  	for key, value := range runtimeConfig {
   105  		if stringInList(key, capabilities) {
   106  			newRuntimeConfig[key] = value
   107  		}
   108  	}
   109  	if len(newRuntimeConfig) > 0 {
   110  		newConfig["runtimeConfig"] = newRuntimeConfig
   111  	}
   112  
   113  	stdinData, err := json.Marshal(newConfig)
   114  	Expect(err).NotTo(HaveOccurred())
   115  
   116  	return pluginInfo{
   117  		debugFilePath: debugFilePath,
   118  		debug:         debug,
   119  		config:        config,
   120  		stdinData:     stdinData,
   121  	}
   122  }
   123  
   124  func resultCacheFilePath(cacheDirPath, netName string, rt *libcni.RuntimeConf) string {
   125  	fName := fmt.Sprintf("%s-%s-%s", netName, rt.ContainerID, rt.IfName)
   126  	return filepath.Join(cacheDirPath, "results", fName)
   127  }
   128  
   129  var _ = Describe("Invoking plugins", func() {
   130  	var cacheDirPath string
   131  
   132  	BeforeEach(func() {
   133  		var err error
   134  		cacheDirPath, err = ioutil.TempDir("", "cni_cachedir")
   135  		Expect(err).NotTo(HaveOccurred())
   136  	})
   137  
   138  	AfterEach(func() {
   139  		Expect(os.RemoveAll(cacheDirPath)).To(Succeed())
   140  	})
   141  
   142  	Describe("Capabilities", func() {
   143  		var (
   144  			debugFilePath string
   145  			debug         *noop_debug.Debug
   146  			pluginConfig  []byte
   147  			cniConfig     *libcni.CNIConfig
   148  			runtimeConfig *libcni.RuntimeConf
   149  			netConfig     *libcni.NetworkConfig
   150  			ctx           context.Context
   151  		)
   152  
   153  		BeforeEach(func() {
   154  			debugFile, err := ioutil.TempFile("", "cni_debug")
   155  			Expect(err).NotTo(HaveOccurred())
   156  			Expect(debugFile.Close()).To(Succeed())
   157  			debugFilePath = debugFile.Name()
   158  
   159  			debug = &noop_debug.Debug{}
   160  			Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
   161  
   162  			pluginConfig = []byte(fmt.Sprintf(`{
   163  				"type": "noop",
   164  				"name": "apitest",
   165  				"cniVersion": "%s",
   166  				"capabilities": {
   167  					"portMappings": true,
   168  					"somethingElse": true,
   169  					"noCapability": false
   170  				}
   171  			}`, current.ImplementedSpecVersion))
   172  			netConfig, err = libcni.ConfFromBytes(pluginConfig)
   173  			Expect(err).NotTo(HaveOccurred())
   174  
   175  			cniConfig = libcni.NewCNIConfigWithCacheDir([]string{filepath.Dir(pluginPaths["noop"])}, cacheDirPath, nil)
   176  
   177  			runtimeConfig = &libcni.RuntimeConf{
   178  				ContainerID: "some-container-id",
   179  				NetNS:       "/some/netns/path",
   180  				IfName:      "some-eth0",
   181  				Args:        [][2]string{{"DEBUG", debugFilePath}},
   182  				CapabilityArgs: map[string]interface{}{
   183  					"portMappings": []portMapping{
   184  						{HostPort: 8080, ContainerPort: 80, Protocol: "tcp"},
   185  					},
   186  					"somethingElse": []string{"foobar", "baz"},
   187  					"noCapability":  true,
   188  					"notAdded":      []bool{true, false},
   189  				},
   190  			}
   191  			ctx = context.TODO()
   192  		})
   193  
   194  		AfterEach(func() {
   195  			Expect(os.RemoveAll(debugFilePath)).To(Succeed())
   196  		})
   197  
   198  		It("adds correct runtime config for capabilities to stdin", func() {
   199  			_, err := cniConfig.AddNetwork(ctx, netConfig, runtimeConfig)
   200  			Expect(err).NotTo(HaveOccurred())
   201  
   202  			debug, err = noop_debug.ReadDebug(debugFilePath)
   203  			Expect(err).NotTo(HaveOccurred())
   204  			Expect(debug.Command).To(Equal("ADD"))
   205  
   206  			conf := make(map[string]interface{})
   207  			err = json.Unmarshal(debug.CmdArgs.StdinData, &conf)
   208  			Expect(err).NotTo(HaveOccurred())
   209  
   210  			// We expect runtimeConfig keys only for portMappings and somethingElse
   211  			rawRc := conf["runtimeConfig"]
   212  			rc, ok := rawRc.(map[string]interface{})
   213  			Expect(ok).To(Equal(true))
   214  			expectedKeys := []string{"portMappings", "somethingElse"}
   215  			Expect(len(rc)).To(Equal(len(expectedKeys)))
   216  			for _, key := range expectedKeys {
   217  				_, ok := rc[key]
   218  				Expect(ok).To(Equal(true))
   219  			}
   220  		})
   221  
   222  		It("adds no runtimeConfig when the plugin advertises no used capabilities", func() {
   223  			// Replace CapabilityArgs with ones we know the plugin
   224  			// doesn't support
   225  			runtimeConfig.CapabilityArgs = map[string]interface{}{
   226  				"portMappings22": []portMapping{
   227  					{HostPort: 8080, ContainerPort: 80, Protocol: "tcp"},
   228  				},
   229  				"somethingElse22": []string{"foobar", "baz"},
   230  			}
   231  
   232  			_, err := cniConfig.AddNetwork(ctx, netConfig, runtimeConfig)
   233  			Expect(err).NotTo(HaveOccurred())
   234  
   235  			debug, err = noop_debug.ReadDebug(debugFilePath)
   236  			Expect(err).NotTo(HaveOccurred())
   237  			Expect(debug.Command).To(Equal("ADD"))
   238  
   239  			conf := make(map[string]interface{})
   240  			err = json.Unmarshal(debug.CmdArgs.StdinData, &conf)
   241  			Expect(err).NotTo(HaveOccurred())
   242  
   243  			// No intersection of plugin capabilities and CapabilityArgs,
   244  			// so plugin should not receive a "runtimeConfig" key
   245  			_, ok := conf["runtimeConfig"]
   246  			Expect(ok).Should(BeFalse())
   247  		})
   248  
   249  		It("outputs correct capabilities for validate", func() {
   250  			caps, err := cniConfig.ValidateNetwork(ctx, netConfig)
   251  			Expect(err).NotTo(HaveOccurred())
   252  			Expect(caps).To(ConsistOf("portMappings", "somethingElse"))
   253  		})
   254  
   255  	})
   256  
   257  	Describe("Invoking a single plugin", func() {
   258  		var (
   259  			debugFilePath string
   260  			debug         *noop_debug.Debug
   261  			cniBinPath    string
   262  			pluginConfig  string
   263  			cniConfig     *libcni.CNIConfig
   264  			netConfig     *libcni.NetworkConfig
   265  			runtimeConfig *libcni.RuntimeConf
   266  			ctx           context.Context
   267  
   268  			expectedCmdArgs skel.CmdArgs
   269  		)
   270  
   271  		BeforeEach(func() {
   272  			debugFile, err := ioutil.TempFile("", "cni_debug")
   273  			Expect(err).NotTo(HaveOccurred())
   274  			Expect(debugFile.Close()).To(Succeed())
   275  			debugFilePath = debugFile.Name()
   276  
   277  			debug = &noop_debug.Debug{
   278  				ReportResult: `{
   279  					"cniVersion": "0.4.0",
   280  					"ips": [{"version": "4", "address": "10.1.2.3/24"}],
   281  					"dns": {}
   282  				}`,
   283  			}
   284  			Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
   285  
   286  			portMappings := []portMapping{
   287  				{HostPort: 8080, ContainerPort: 80, Protocol: "tcp"},
   288  			}
   289  
   290  			cniBinPath = filepath.Dir(pluginPaths["noop"])
   291  			pluginConfig = fmt.Sprintf(`{
   292  				"type": "noop",
   293  				"name": "apitest",
   294  				"some-key": "some-value",
   295  				"cniVersion": "%s",
   296  				"capabilities": { "portMappings": true }
   297  			}`, current.ImplementedSpecVersion)
   298  			cniConfig = libcni.NewCNIConfigWithCacheDir([]string{cniBinPath}, cacheDirPath, nil)
   299  			netConfig, err = libcni.ConfFromBytes([]byte(pluginConfig))
   300  			Expect(err).NotTo(HaveOccurred())
   301  			runtimeConfig = &libcni.RuntimeConf{
   302  				ContainerID: "some-container-id",
   303  				NetNS:       "/some/netns/path",
   304  				IfName:      "some-eth0",
   305  				Args:        [][2]string{{"DEBUG", debugFilePath}},
   306  				CapabilityArgs: map[string]interface{}{
   307  					"portMappings": portMappings,
   308  				},
   309  			}
   310  
   311  			// inject runtime args into the expected plugin config
   312  			conf := make(map[string]interface{})
   313  			err = json.Unmarshal([]byte(pluginConfig), &conf)
   314  			Expect(err).NotTo(HaveOccurred())
   315  			conf["runtimeConfig"] = map[string]interface{}{
   316  				"portMappings": portMappings,
   317  			}
   318  			newBytes, err := json.Marshal(conf)
   319  			Expect(err).NotTo(HaveOccurred())
   320  
   321  			expectedCmdArgs = skel.CmdArgs{
   322  				ContainerID: "some-container-id",
   323  				Netns:       "/some/netns/path",
   324  				IfName:      "some-eth0",
   325  				Args:        "DEBUG=" + debugFilePath,
   326  				Path:        cniBinPath,
   327  				StdinData:   newBytes,
   328  			}
   329  			ctx = context.TODO()
   330  		})
   331  
   332  		AfterEach(func() {
   333  			Expect(os.RemoveAll(debugFilePath)).To(Succeed())
   334  		})
   335  
   336  		Describe("AddNetwork", func() {
   337  			It("executes the plugin with command ADD", func() {
   338  				r, err := cniConfig.AddNetwork(ctx, netConfig, runtimeConfig)
   339  				Expect(err).NotTo(HaveOccurred())
   340  
   341  				result, err := current.GetResult(r)
   342  				Expect(err).NotTo(HaveOccurred())
   343  
   344  				Expect(result).To(Equal(&current.Result{
   345  					CNIVersion: current.ImplementedSpecVersion,
   346  					IPs: []*current.IPConfig{
   347  						{
   348  							Version: "4",
   349  							Address: net.IPNet{
   350  								IP:   net.ParseIP("10.1.2.3"),
   351  								Mask: net.IPv4Mask(255, 255, 255, 0),
   352  							},
   353  						},
   354  					},
   355  				}))
   356  
   357  				debug, err := noop_debug.ReadDebug(debugFilePath)
   358  				Expect(err).NotTo(HaveOccurred())
   359  				Expect(debug.Command).To(Equal("ADD"))
   360  				Expect(debug.CmdArgs).To(Equal(expectedCmdArgs))
   361  				Expect(string(debug.CmdArgs.StdinData)).To(ContainSubstring("\"portMappings\":"))
   362  
   363  				// Ensure the cached config matches the sent one
   364  				cachedConfig, newRt, err := cniConfig.GetNetworkCachedConfig(netConfig, runtimeConfig)
   365  				Expect(err).NotTo(HaveOccurred())
   366  				Expect(reflect.DeepEqual(cachedConfig, []byte(pluginConfig))).To(BeTrue())
   367  				Expect(reflect.DeepEqual(newRt.Args, runtimeConfig.Args)).To(BeTrue())
   368  				// CapabilityArgs are freeform, so we have to match their JSON not
   369  				// the Go structs (which could be unmarshalled differently than the
   370  				// struct that was marshalled).
   371  				expectedCABytes, err := json.Marshal(runtimeConfig.CapabilityArgs)
   372  				Expect(err).NotTo(HaveOccurred())
   373  				foundCABytes, err := json.Marshal(newRt.CapabilityArgs)
   374  				Expect(err).NotTo(HaveOccurred())
   375  				Expect(foundCABytes).To(MatchJSON(expectedCABytes))
   376  
   377  				// Ensure the cached result matches the returned one
   378  				cachedResult, err := cniConfig.GetNetworkCachedResult(netConfig, runtimeConfig)
   379  				Expect(err).NotTo(HaveOccurred())
   380  				result2, err := current.GetResult(cachedResult)
   381  				Expect(err).NotTo(HaveOccurred())
   382  				cachedJson, err := json.Marshal(result2)
   383  				Expect(err).NotTo(HaveOccurred())
   384  
   385  				returnedJson, err := json.Marshal(result)
   386  				Expect(err).NotTo(HaveOccurred())
   387  				Expect(cachedJson).To(MatchJSON(returnedJson))
   388  			})
   389  
   390  			Context("when finding the plugin fails", func() {
   391  				BeforeEach(func() {
   392  					netConfig.Network.Type = "does-not-exist"
   393  				})
   394  
   395  				It("returns the error", func() {
   396  					_, err := cniConfig.AddNetwork(ctx, netConfig, runtimeConfig)
   397  					Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`)))
   398  				})
   399  			})
   400  
   401  			Context("when the plugin errors", func() {
   402  				BeforeEach(func() {
   403  					debug.ReportError = "plugin error: banana"
   404  					Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
   405  				})
   406  				It("unmarshals and returns the error", func() {
   407  					result, err := cniConfig.AddNetwork(ctx, netConfig, runtimeConfig)
   408  					Expect(result).To(BeNil())
   409  					Expect(err).To(MatchError("plugin error: banana"))
   410  				})
   411  			})
   412  
   413  			Context("when the cache directory cannot be accessed", func() {
   414  				It("returns an error", func() {
   415  					// Make the results directory inaccessible by making it a
   416  					// file instead of a directory
   417  					tmpPath := filepath.Join(cacheDirPath, "results")
   418  					err := ioutil.WriteFile(tmpPath, []byte("afdsasdfasdf"), 0600)
   419  					Expect(err).NotTo(HaveOccurred())
   420  
   421  					result, err := cniConfig.AddNetwork(ctx, netConfig, runtimeConfig)
   422  					Expect(result).To(BeNil())
   423  					Expect(err).To(HaveOccurred())
   424  				})
   425  			})
   426  		})
   427  
   428  		Describe("CheckNetwork", func() {
   429  			It("executes the plugin with command CHECK", func() {
   430  				cacheFile := resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig)
   431  				err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
   432  				Expect(err).NotTo(HaveOccurred())
   433  				cachedJson := `{
   434  					"cniVersion": "0.4.0",
   435  					"ips": [{"version": "4", "address": "10.1.2.3/24"}],
   436  					"dns": {}
   437  				}`
   438  				err = ioutil.WriteFile(cacheFile, []byte(cachedJson), 0600)
   439  				Expect(err).NotTo(HaveOccurred())
   440  
   441  				err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig)
   442  				Expect(err).NotTo(HaveOccurred())
   443  
   444  				debug, err := noop_debug.ReadDebug(debugFilePath)
   445  				Expect(err).NotTo(HaveOccurred())
   446  				Expect(debug.Command).To(Equal("CHECK"))
   447  				Expect(string(debug.CmdArgs.StdinData)).To(ContainSubstring("\"portMappings\":"))
   448  
   449  				// Explicitly match stdin data as json after
   450  				// inserting the expected prevResult
   451  				var data, data2 map[string]interface{}
   452  				err = json.Unmarshal(expectedCmdArgs.StdinData, &data)
   453  				Expect(err).NotTo(HaveOccurred())
   454  				err = json.Unmarshal([]byte(cachedJson), &data2)
   455  				Expect(err).NotTo(HaveOccurred())
   456  				data["prevResult"] = data2
   457  				expectedStdinJson, err := json.Marshal(data)
   458  				Expect(err).NotTo(HaveOccurred())
   459  				Expect(debug.CmdArgs.StdinData).To(MatchJSON(expectedStdinJson))
   460  
   461  				debug.CmdArgs.StdinData = nil
   462  				expectedCmdArgs.StdinData = nil
   463  				Expect(debug.CmdArgs).To(Equal(expectedCmdArgs))
   464  			})
   465  
   466  			Context("when finding the plugin fails", func() {
   467  				BeforeEach(func() {
   468  					netConfig.Network.Type = "does-not-exist"
   469  				})
   470  
   471  				It("returns the error", func() {
   472  					err := cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig)
   473  					Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`)))
   474  				})
   475  			})
   476  
   477  			Context("when the plugin errors", func() {
   478  				BeforeEach(func() {
   479  					debug.ReportError = "plugin error: banana"
   480  					Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
   481  				})
   482  				It("unmarshals and returns the error", func() {
   483  					err := cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig)
   484  					Expect(err).To(MatchError("plugin error: banana"))
   485  				})
   486  			})
   487  
   488  			Context("when CHECK is called with a configuration version", func() {
   489  				var cacheFile string
   490  
   491  				BeforeEach(func() {
   492  					cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig)
   493  					err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
   494  					Expect(err).NotTo(HaveOccurred())
   495  				})
   496  
   497  				Context("less than 0.4.0", func() {
   498  					It("fails as CHECK is not supported before 0.4.0", func() {
   499  						// Generate plugin config with older version
   500  						pluginConfig = `{
   501  							"type": "noop",
   502  							"name": "apitest",
   503  							"cniVersion": "0.3.1"
   504  						}`
   505  						var err error
   506  						netConfig, err = libcni.ConfFromBytes([]byte(pluginConfig))
   507  						Expect(err).NotTo(HaveOccurred())
   508  						err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig)
   509  						Expect(err).To(MatchError("configuration version \"0.3.1\" does not support the CHECK command"))
   510  
   511  						debug, err := noop_debug.ReadDebug(debugFilePath)
   512  						Expect(err).NotTo(HaveOccurred())
   513  						Expect(string(debug.CmdArgs.StdinData)).NotTo(ContainSubstring("\"prevResult\":"))
   514  					})
   515  				})
   516  
   517  				Context("containing only a cached result", func() {
   518  					It("only passes a prevResult to the plugin", func() {
   519  						err := ioutil.WriteFile(cacheFile, []byte(`{
   520  							"cniVersion": "0.4.0",
   521  							"ips": [{"version": "4", "address": "10.1.2.3/24"}],
   522  							"dns": {}
   523  						}`), 0600)
   524  						Expect(err).NotTo(HaveOccurred())
   525  
   526  						err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig)
   527  						Expect(err).NotTo(HaveOccurred())
   528  						debug, err := noop_debug.ReadDebug(debugFilePath)
   529  						Expect(err).NotTo(HaveOccurred())
   530  						Expect(string(debug.CmdArgs.StdinData)).To(ContainSubstring("\"prevResult\":"))
   531  						Expect(string(debug.CmdArgs.StdinData)).NotTo(ContainSubstring("\"config\":"))
   532  						Expect(string(debug.CmdArgs.StdinData)).NotTo(ContainSubstring("\"kind\":"))
   533  					})
   534  				})
   535  
   536  				Context("equal to 0.4.0", func() {
   537  					It("passes a prevResult to the plugin", func() {
   538  						err := ioutil.WriteFile(cacheFile, []byte(`{
   539  							"cniVersion": "0.4.0",
   540  							"ips": [{"version": "4", "address": "10.1.2.3/24"}],
   541  							"dns": {}
   542  						}`), 0600)
   543  						Expect(err).NotTo(HaveOccurred())
   544  
   545  						err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig)
   546  						Expect(err).NotTo(HaveOccurred())
   547  						debug, err := noop_debug.ReadDebug(debugFilePath)
   548  						Expect(err).NotTo(HaveOccurred())
   549  						Expect(string(debug.CmdArgs.StdinData)).To(ContainSubstring("\"prevResult\":"))
   550  					})
   551  				})
   552  			})
   553  
   554  			Context("when the cached result", func() {
   555  				var cacheFile string
   556  
   557  				BeforeEach(func() {
   558  					cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig)
   559  					err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
   560  					Expect(err).NotTo(HaveOccurred())
   561  				})
   562  
   563  				Context("is invalid JSON", func() {
   564  					It("returns an error", func() {
   565  						err := ioutil.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0600)
   566  						Expect(err).NotTo(HaveOccurred())
   567  
   568  						err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig)
   569  						Expect(err).To(MatchError("failed to get network \"apitest\" cached result: decoding version from network config: invalid character 'a' looking for beginning of value"))
   570  					})
   571  				})
   572  
   573  				Context("version doesn't match the config version", func() {
   574  					It("succeeds when the cached result can be converted", func() {
   575  						err := ioutil.WriteFile(cacheFile, []byte(`{
   576  							"cniVersion": "0.3.1",
   577  							"ips": [{"version": "4", "address": "10.1.2.3/24"}],
   578  							"dns": {}
   579  						}`), 0600)
   580  						Expect(err).NotTo(HaveOccurred())
   581  
   582  						err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig)
   583  						Expect(err).NotTo(HaveOccurred())
   584  					})
   585  
   586  					It("returns an error when the cached result cannot be converted", func() {
   587  						err := ioutil.WriteFile(cacheFile, []byte(`{
   588  							"cniVersion": "0.4567.0",
   589  							"ips": [{"version": "4", "address": "10.1.2.3/24"}],
   590  							"dns": {}
   591  						}`), 0600)
   592  						Expect(err).NotTo(HaveOccurred())
   593  
   594  						err = cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig)
   595  						Expect(err).To(MatchError("failed to get network \"apitest\" cached result: unsupported CNI result version \"0.4567.0\""))
   596  					})
   597  				})
   598  			})
   599  		})
   600  
   601  		Describe("DelNetwork", func() {
   602  			It("executes the plugin with command DEL", func() {
   603  				cacheFile := resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig)
   604  				err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
   605  				Expect(err).NotTo(HaveOccurred())
   606  				cachedJson := `{
   607  					"cniVersion": "0.4.0",
   608  					"ips": [{"version": "4", "address": "10.1.2.3/24"}],
   609  					"dns": {}
   610  				}`
   611  				err = ioutil.WriteFile(cacheFile, []byte(cachedJson), 0600)
   612  				Expect(err).NotTo(HaveOccurred())
   613  
   614  				err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
   615  				Expect(err).NotTo(HaveOccurred())
   616  
   617  				debug, err := noop_debug.ReadDebug(debugFilePath)
   618  				Expect(err).NotTo(HaveOccurred())
   619  				Expect(debug.Command).To(Equal("DEL"))
   620  				Expect(string(debug.CmdArgs.StdinData)).To(ContainSubstring("\"portMappings\":"))
   621  
   622  				// Explicitly match stdin data as json after
   623  				// inserting the expected prevResult
   624  				var data, data2 map[string]interface{}
   625  				err = json.Unmarshal(expectedCmdArgs.StdinData, &data)
   626  				Expect(err).NotTo(HaveOccurred())
   627  				err = json.Unmarshal([]byte(cachedJson), &data2)
   628  				Expect(err).NotTo(HaveOccurred())
   629  				data["prevResult"] = data2
   630  				expectedStdinJson, err := json.Marshal(data)
   631  				Expect(err).NotTo(HaveOccurred())
   632  				Expect(debug.CmdArgs.StdinData).To(MatchJSON(expectedStdinJson))
   633  
   634  				debug.CmdArgs.StdinData = nil
   635  				expectedCmdArgs.StdinData = nil
   636  				Expect(debug.CmdArgs).To(Equal(expectedCmdArgs))
   637  
   638  				// Ensure the cached result no longer exists
   639  				cachedResult, err := cniConfig.GetNetworkCachedResult(netConfig, runtimeConfig)
   640  				Expect(err).NotTo(HaveOccurred())
   641  				Expect(cachedResult).To(BeNil())
   642  
   643  				// Ensure the cached config no longer exists
   644  				cachedConfig, newRt, err := cniConfig.GetNetworkCachedConfig(netConfig, runtimeConfig)
   645  				Expect(err).NotTo(HaveOccurred())
   646  				Expect(cachedConfig).To(BeNil())
   647  				Expect(newRt).To(BeNil())
   648  			})
   649  
   650  			Context("when finding the plugin fails", func() {
   651  				BeforeEach(func() {
   652  					netConfig.Network.Type = "does-not-exist"
   653  				})
   654  
   655  				It("returns the error", func() {
   656  					err := cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
   657  					Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`)))
   658  				})
   659  			})
   660  
   661  			Context("when the plugin errors", func() {
   662  				BeforeEach(func() {
   663  					debug.ReportError = "plugin error: banana"
   664  					Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
   665  				})
   666  				It("unmarshals and returns the error", func() {
   667  					err := cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
   668  					Expect(err).To(MatchError("plugin error: banana"))
   669  				})
   670  			})
   671  
   672  			Context("when DEL is called twice", func() {
   673  				var resultCacheFile string
   674  
   675  				BeforeEach(func() {
   676  					resultCacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig)
   677  					err := os.MkdirAll(filepath.Dir(resultCacheFile), 0700)
   678  					Expect(err).NotTo(HaveOccurred())
   679  				})
   680  
   681  				It("deletes the cached result and config after the first DEL", func() {
   682  					err := ioutil.WriteFile(resultCacheFile, []byte(`{
   683  						"cniVersion": "0.4.0",
   684  						"ips": [{"version": "4", "address": "10.1.2.3/24"}],
   685  						"dns": {}
   686  					}`), 0600)
   687  					Expect(err).NotTo(HaveOccurred())
   688  
   689  					err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
   690  					Expect(err).NotTo(HaveOccurred())
   691  					_, err = ioutil.ReadFile(resultCacheFile)
   692  					Expect(err).To(HaveOccurred())
   693  
   694  					err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
   695  					Expect(err).NotTo(HaveOccurred())
   696  				})
   697  			})
   698  
   699  			Context("when DEL is called with a configuration version", func() {
   700  				var cacheFile string
   701  
   702  				BeforeEach(func() {
   703  					cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig)
   704  					err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
   705  					Expect(err).NotTo(HaveOccurred())
   706  				})
   707  
   708  				Context("less than 0.4.0", func() {
   709  					It("does not pass a prevResult to the plugin", func() {
   710  						err := ioutil.WriteFile(cacheFile, []byte(`{
   711  							"cniVersion": "0.3.1",
   712  							"ips": [{"version": "4", "address": "10.1.2.3/24"}],
   713  							"dns": {}
   714  						}`), 0600)
   715  						Expect(err).NotTo(HaveOccurred())
   716  
   717  						// Generate plugin config with older version
   718  						pluginConfig = `{
   719  							"type": "noop",
   720  							"name": "apitest",
   721  							"cniVersion": "0.3.1"
   722  						}`
   723  						netConfig, err = libcni.ConfFromBytes([]byte(pluginConfig))
   724  						Expect(err).NotTo(HaveOccurred())
   725  						err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
   726  						Expect(err).NotTo(HaveOccurred())
   727  
   728  						debug, err := noop_debug.ReadDebug(debugFilePath)
   729  						Expect(err).NotTo(HaveOccurred())
   730  						Expect(debug.Command).To(Equal("DEL"))
   731  						Expect(string(debug.CmdArgs.StdinData)).NotTo(ContainSubstring("\"prevResult\":"))
   732  					})
   733  				})
   734  
   735  				Context("equal to 0.4.0", func() {
   736  					It("passes a prevResult to the plugin", func() {
   737  						err := ioutil.WriteFile(cacheFile, []byte(`{
   738  							"cniVersion": "0.4.0",
   739  							"ips": [{"version": "4", "address": "10.1.2.3/24"}],
   740  							"dns": {}
   741  						}`), 0600)
   742  						Expect(err).NotTo(HaveOccurred())
   743  
   744  						err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
   745  						Expect(err).NotTo(HaveOccurred())
   746  
   747  						debug, err := noop_debug.ReadDebug(debugFilePath)
   748  						Expect(err).NotTo(HaveOccurred())
   749  						Expect(debug.Command).To(Equal("DEL"))
   750  						Expect(string(debug.CmdArgs.StdinData)).To(ContainSubstring("\"prevResult\":"))
   751  					})
   752  				})
   753  			})
   754  
   755  			Context("when the cached", func() {
   756  				var cacheFile string
   757  
   758  				BeforeEach(func() {
   759  					cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig)
   760  					err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
   761  					Expect(err).NotTo(HaveOccurred())
   762  				})
   763  
   764  				Context("result is invalid JSON", func() {
   765  					It("returns an error", func() {
   766  						err := ioutil.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0600)
   767  						Expect(err).NotTo(HaveOccurred())
   768  
   769  						err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
   770  						Expect(err).To(MatchError("failed to get network \"apitest\" cached result: decoding version from network config: invalid character 'a' looking for beginning of value"))
   771  					})
   772  				})
   773  
   774  				Context("result version doesn't match the config version", func() {
   775  					It("succeeds when the cached result can be converted", func() {
   776  						err := ioutil.WriteFile(cacheFile, []byte(`{
   777  							"cniVersion": "0.3.1",
   778  							"ips": [{"version": "4", "address": "10.1.2.3/24"}],
   779  							"dns": {}
   780  						}`), 0600)
   781  						Expect(err).NotTo(HaveOccurred())
   782  
   783  						err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
   784  						Expect(err).NotTo(HaveOccurred())
   785  					})
   786  
   787  					It("returns an error when the cached result cannot be converted", func() {
   788  						err := ioutil.WriteFile(cacheFile, []byte(`{
   789  							"cniVersion": "0.4567.0",
   790  							"ips": [{"version": "4", "address": "10.1.2.3/24"}],
   791  							"dns": {}
   792  						}`), 0600)
   793  						Expect(err).NotTo(HaveOccurred())
   794  
   795  						err = cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
   796  						Expect(err).To(MatchError("failed to get network \"apitest\" cached result: unsupported CNI result version \"0.4567.0\""))
   797  					})
   798  				})
   799  			})
   800  		})
   801  
   802  		Describe("GetVersionInfo", func() {
   803  			It("executes the plugin with the command VERSION", func() {
   804  				versionInfo, err := cniConfig.GetVersionInfo(ctx, "noop")
   805  				Expect(err).NotTo(HaveOccurred())
   806  
   807  				Expect(versionInfo).NotTo(BeNil())
   808  				Expect(versionInfo.SupportedVersions()).To(Equal([]string{
   809  					"0.-42.0", "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0",
   810  				}))
   811  			})
   812  
   813  			Context("when finding the plugin fails", func() {
   814  				It("returns the error", func() {
   815  					_, err := cniConfig.GetVersionInfo(ctx, "does-not-exist")
   816  					Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`)))
   817  				})
   818  			})
   819  		})
   820  
   821  		Describe("ValidateNetwork", func() {
   822  			It("validates a good configuration", func() {
   823  				_, err := cniConfig.ValidateNetwork(ctx, netConfig)
   824  				Expect(err).NotTo(HaveOccurred())
   825  			})
   826  
   827  			It("catches non-existent plugins", func() {
   828  				netConfig.Network.Type = "nope"
   829  				_, err := cniConfig.ValidateNetwork(ctx, netConfig)
   830  				Expect(err).To(MatchError("failed to find plugin \"nope\" in path [" + cniConfig.Path[0] + "]"))
   831  			})
   832  
   833  			It("catches unsupported versions", func() {
   834  				netConfig.Network.CNIVersion = "broken"
   835  				_, err := cniConfig.ValidateNetwork(ctx, netConfig)
   836  				Expect(err).To(MatchError("plugin noop does not support config version \"broken\""))
   837  			})
   838  			It("allows version to be omitted", func() {
   839  				netConfig.Network.CNIVersion = ""
   840  				_, err := cniConfig.ValidateNetwork(ctx, netConfig)
   841  				Expect(err).NotTo(HaveOccurred())
   842  			})
   843  		})
   844  	})
   845  
   846  	Describe("Invoking a plugin list", func() {
   847  		var (
   848  			plugins       []pluginInfo
   849  			cniBinPath    string
   850  			cniConfig     *libcni.CNIConfig
   851  			netConfigList *libcni.NetworkConfigList
   852  			runtimeConfig *libcni.RuntimeConf
   853  			ctx           context.Context
   854  			ipResult      string
   855  
   856  			expectedCmdArgs skel.CmdArgs
   857  		)
   858  
   859  		BeforeEach(func() {
   860  			var err error
   861  
   862  			capabilityArgs := map[string]interface{}{
   863  				"portMappings": []portMapping{
   864  					{HostPort: 8080, ContainerPort: 80, Protocol: "tcp"},
   865  				},
   866  				"otherCapability": 33,
   867  			}
   868  
   869  			cniBinPath = filepath.Dir(pluginPaths["noop"])
   870  			cniConfig = libcni.NewCNIConfigWithCacheDir([]string{cniBinPath}, cacheDirPath, nil)
   871  			runtimeConfig = &libcni.RuntimeConf{
   872  				ContainerID:    "some-container-id",
   873  				NetNS:          "/some/netns/path",
   874  				IfName:         "some-eth0",
   875  				Args:           [][2]string{{"FOO", "BAR"}},
   876  				CapabilityArgs: capabilityArgs,
   877  			}
   878  
   879  			expectedCmdArgs = skel.CmdArgs{
   880  				ContainerID: runtimeConfig.ContainerID,
   881  				Netns:       runtimeConfig.NetNS,
   882  				IfName:      runtimeConfig.IfName,
   883  				Args:        "FOO=BAR",
   884  				Path:        cniBinPath,
   885  			}
   886  
   887  			rc := map[string]interface{}{
   888  				"containerId": runtimeConfig.ContainerID,
   889  				"netNs":       runtimeConfig.NetNS,
   890  				"ifName":      runtimeConfig.IfName,
   891  				"args": map[string]string{
   892  					"FOO": "BAR",
   893  				},
   894  				"portMappings":    capabilityArgs["portMappings"],
   895  				"otherCapability": capabilityArgs["otherCapability"],
   896  			}
   897  
   898  			ipResult = fmt.Sprintf(`{"cniVersion": "%s", "dns":{},"ips":[{"version": "4", "address": "10.1.2.3/24"}]}`, current.ImplementedSpecVersion)
   899  			plugins = make([]pluginInfo, 3)
   900  			plugins[0] = newPluginInfo("some-value", "", true, ipResult, rc, []string{"portMappings", "otherCapability"})
   901  			plugins[1] = newPluginInfo("some-other-value", ipResult, true, "PASSTHROUGH", rc, []string{"otherCapability"})
   902  			plugins[2] = newPluginInfo("yet-another-value", ipResult, true, "INJECT-DNS", rc, []string{})
   903  
   904  			configList := []byte(fmt.Sprintf(`{
   905    "name": "some-list",
   906    "cniVersion": "%s",
   907    "plugins": [
   908      %s,
   909      %s,
   910      %s
   911    ]
   912  }`, current.ImplementedSpecVersion, plugins[0].config, plugins[1].config, plugins[2].config))
   913  
   914  			netConfigList, err = libcni.ConfListFromBytes(configList)
   915  			Expect(err).NotTo(HaveOccurred())
   916  			ctx = context.TODO()
   917  		})
   918  
   919  		AfterEach(func() {
   920  			for _, p := range plugins {
   921  				Expect(os.RemoveAll(p.debugFilePath)).To(Succeed())
   922  			}
   923  		})
   924  
   925  		Describe("AddNetworkList", func() {
   926  			It("executes all plugins with command ADD and returns an intermediate result", func() {
   927  				r, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig)
   928  				Expect(err).NotTo(HaveOccurred())
   929  
   930  				result, err := current.GetResult(r)
   931  				Expect(err).NotTo(HaveOccurred())
   932  
   933  				Expect(result).To(Equal(&current.Result{
   934  					CNIVersion: current.ImplementedSpecVersion,
   935  					// IP4 added by first plugin
   936  					IPs: []*current.IPConfig{
   937  						{
   938  							Version: "4",
   939  							Address: net.IPNet{
   940  								IP:   net.ParseIP("10.1.2.3"),
   941  								Mask: net.IPv4Mask(255, 255, 255, 0),
   942  							},
   943  						},
   944  					},
   945  					// DNS injected by last plugin
   946  					DNS: types.DNS{
   947  						Nameservers: []string{"1.2.3.4"},
   948  					},
   949  				}))
   950  
   951  				for i := 0; i < len(plugins); i++ {
   952  					debug, err := noop_debug.ReadDebug(plugins[i].debugFilePath)
   953  					Expect(err).NotTo(HaveOccurred())
   954  					Expect(debug.Command).To(Equal("ADD"))
   955  
   956  					// Must explicitly match JSON due to dict element ordering
   957  					Expect(debug.CmdArgs.StdinData).To(MatchJSON(plugins[i].stdinData))
   958  					debug.CmdArgs.StdinData = nil
   959  					Expect(debug.CmdArgs).To(Equal(expectedCmdArgs))
   960  				}
   961  			})
   962  
   963  			It("writes the correct cached result", func() {
   964  				r, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig)
   965  				Expect(err).NotTo(HaveOccurred())
   966  
   967  				result, err := current.GetResult(r)
   968  				Expect(err).NotTo(HaveOccurred())
   969  
   970  				// Ensure the cached result matches the returned one
   971  				cachedResult, err := cniConfig.GetNetworkListCachedResult(netConfigList, runtimeConfig)
   972  				Expect(err).NotTo(HaveOccurred())
   973  				result2, err := current.GetResult(cachedResult)
   974  				Expect(err).NotTo(HaveOccurred())
   975  				cachedJson, err := json.Marshal(result2)
   976  				Expect(err).NotTo(HaveOccurred())
   977  				returnedJson, err := json.Marshal(result)
   978  				Expect(err).NotTo(HaveOccurred())
   979  				Expect(cachedJson).To(MatchJSON(returnedJson))
   980  			})
   981  
   982  			It("writes the correct cached config", func() {
   983  				_, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig)
   984  				Expect(err).NotTo(HaveOccurred())
   985  
   986  				// Ensure the cached config matches the returned one
   987  				cachedConfig, newRt, err := cniConfig.GetNetworkListCachedConfig(netConfigList, runtimeConfig)
   988  				Expect(err).NotTo(HaveOccurred())
   989  				Expect(bytes.Equal(cachedConfig, netConfigList.Bytes)).To(BeTrue())
   990  				Expect(reflect.DeepEqual(newRt.Args, runtimeConfig.Args)).To(BeTrue())
   991  				// CapabilityArgs are freeform, so we have to match their JSON not
   992  				// the Go structs (which could be unmarshalled differently than the
   993  				// struct that was marshalled).
   994  				expectedCABytes, err := json.Marshal(runtimeConfig.CapabilityArgs)
   995  				Expect(err).NotTo(HaveOccurred())
   996  				foundCABytes, err := json.Marshal(newRt.CapabilityArgs)
   997  				Expect(err).NotTo(HaveOccurred())
   998  				Expect(foundCABytes).To(MatchJSON(expectedCABytes))
   999  			})
  1000  
  1001  			Context("when finding the plugin fails", func() {
  1002  				BeforeEach(func() {
  1003  					netConfigList.Plugins[1].Network.Type = "does-not-exist"
  1004  				})
  1005  
  1006  				It("returns the error", func() {
  1007  					_, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig)
  1008  					Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`)))
  1009  				})
  1010  			})
  1011  
  1012  			Context("when there is an invalid containerID", func() {
  1013  				BeforeEach(func() {
  1014  					runtimeConfig.ContainerID = "some-%%container-id"
  1015  				})
  1016  
  1017  				It("returns the error", func() {
  1018  					_, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig)
  1019  					Expect(err).To(Equal(&types.Error{
  1020  						Code:    types.ErrInvalidEnvironmentVariables,
  1021  						Msg:     "invalid characters in containerID",
  1022  						Details: "some-%%container-id",
  1023  					}))
  1024  				})
  1025  			})
  1026  
  1027  			Context("when there is an invalid networkName", func() {
  1028  				BeforeEach(func() {
  1029  					netConfigList.Name = "invalid-%%-name"
  1030  				})
  1031  
  1032  				It("returns the error", func() {
  1033  					_, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig)
  1034  					Expect(err).To(Equal(&types.Error{
  1035  						Code:    types.ErrInvalidNetworkConfig,
  1036  						Msg:     "invalid characters found in network name",
  1037  						Details: "invalid-%%-name",
  1038  					}))
  1039  				})
  1040  			})
  1041  
  1042  			Context("return errors when interface name is invalid", func() {
  1043  				It("interface name is empty", func() {
  1044  					runtimeConfig.IfName = ""
  1045  
  1046  					_, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig)
  1047  					Expect(err).To(Equal(&types.Error{
  1048  						Code:    types.ErrInvalidEnvironmentVariables,
  1049  						Msg:     "interface name is empty",
  1050  						Details: "",
  1051  					}))
  1052  				})
  1053  
  1054  				It("interface name is too long", func() {
  1055  					runtimeConfig.IfName = "1234567890123456"
  1056  
  1057  					_, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig)
  1058  					Expect(err).To(Equal(&types.Error{
  1059  						Code:    types.ErrInvalidEnvironmentVariables,
  1060  						Msg:     "interface name is too long",
  1061  						Details: "interface name should be less than 16 characters",
  1062  					}))
  1063  				})
  1064  
  1065  				It("interface name is .", func() {
  1066  					runtimeConfig.IfName = "."
  1067  
  1068  					_, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig)
  1069  					Expect(err).To(Equal(&types.Error{
  1070  						Code:    types.ErrInvalidEnvironmentVariables,
  1071  						Msg:     "interface name is . or ..",
  1072  						Details: "",
  1073  					}))
  1074  				})
  1075  
  1076  				It("interface name is ..", func() {
  1077  					runtimeConfig.IfName = ".."
  1078  
  1079  					_, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig)
  1080  					Expect(err).To(Equal(&types.Error{
  1081  						Code:    types.ErrInvalidEnvironmentVariables,
  1082  						Msg:     "interface name is . or ..",
  1083  						Details: "",
  1084  					}))
  1085  				})
  1086  
  1087  				It("interface name contains invalid characters /", func() {
  1088  					runtimeConfig.IfName = "test/test"
  1089  
  1090  					_, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig)
  1091  					Expect(err).To(Equal(&types.Error{
  1092  						Code:    types.ErrInvalidEnvironmentVariables,
  1093  						Msg:     "interface name contains / or : or whitespace characters",
  1094  						Details: "",
  1095  					}))
  1096  				})
  1097  
  1098  				It("interface name contains invalid characters :", func() {
  1099  					runtimeConfig.IfName = "test:test"
  1100  
  1101  					_, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig)
  1102  					Expect(err).To(Equal(&types.Error{
  1103  						Code:    types.ErrInvalidEnvironmentVariables,
  1104  						Msg:     "interface name contains / or : or whitespace characters",
  1105  						Details: "",
  1106  					}))
  1107  				})
  1108  
  1109  				It("interface name contains invalid characters whitespace", func() {
  1110  					runtimeConfig.IfName = "test test"
  1111  
  1112  					_, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig)
  1113  					Expect(err).To(Equal(&types.Error{
  1114  						Code:    types.ErrInvalidEnvironmentVariables,
  1115  						Msg:     "interface name contains / or : or whitespace characters",
  1116  						Details: "",
  1117  					}))
  1118  				})
  1119  			})
  1120  
  1121  			Context("when the second plugin errors", func() {
  1122  				BeforeEach(func() {
  1123  					plugins[1].debug.ReportError = "plugin error: banana"
  1124  					Expect(plugins[1].debug.WriteDebug(plugins[1].debugFilePath)).To(Succeed())
  1125  				})
  1126  				It("unmarshals and returns the error", func() {
  1127  					result, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig)
  1128  					Expect(result).To(BeNil())
  1129  					Expect(err).To(MatchError("plugin error: banana"))
  1130  				})
  1131  				It("should not have written cache files", func() {
  1132  					resultCacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig)
  1133  					_, err := ioutil.ReadFile(resultCacheFile)
  1134  					Expect(err).To(HaveOccurred())
  1135  				})
  1136  			})
  1137  
  1138  			Context("when the cache directory cannot be accessed", func() {
  1139  				It("returns an error when the results cache file cannot be written", func() {
  1140  					// Make the results directory inaccessible by making it a
  1141  					// file instead of a directory
  1142  					tmpPath := filepath.Join(cacheDirPath, "results")
  1143  					err := ioutil.WriteFile(tmpPath, []byte("afdsasdfasdf"), 0600)
  1144  					Expect(err).NotTo(HaveOccurred())
  1145  
  1146  					result, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig)
  1147  					Expect(result).To(BeNil())
  1148  					Expect(err).To(HaveOccurred())
  1149  				})
  1150  			})
  1151  		})
  1152  
  1153  		Describe("CheckNetworkList", func() {
  1154  			It("executes all plugins with command CHECK", func() {
  1155  				cacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig)
  1156  				err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
  1157  				Expect(err).NotTo(HaveOccurred())
  1158  				err = ioutil.WriteFile(cacheFile, []byte(ipResult), 0600)
  1159  				Expect(err).NotTo(HaveOccurred())
  1160  
  1161  				err = cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig)
  1162  				Expect(err).NotTo(HaveOccurred())
  1163  
  1164  				for i := 0; i < len(plugins); i++ {
  1165  					debug, err := noop_debug.ReadDebug(plugins[i].debugFilePath)
  1166  					Expect(err).NotTo(HaveOccurred())
  1167  					Expect(debug.Command).To(Equal("CHECK"))
  1168  
  1169  					// Ensure each plugin gets the prevResult from the cache
  1170  					asMap := make(map[string]interface{})
  1171  					err = json.Unmarshal(debug.CmdArgs.StdinData, &asMap)
  1172  					Expect(err).NotTo(HaveOccurred())
  1173  					Expect(asMap["prevResult"]).NotTo(BeNil())
  1174  					foo, err := json.Marshal(asMap["prevResult"])
  1175  					Expect(err).NotTo(HaveOccurred())
  1176  					Expect(foo).To(MatchJSON(ipResult))
  1177  
  1178  					debug.CmdArgs.StdinData = nil
  1179  					Expect(debug.CmdArgs).To(Equal(expectedCmdArgs))
  1180  				}
  1181  			})
  1182  
  1183  			It("does not executes plugins with command CHECK when disableCheck is true", func() {
  1184  				netConfigList.DisableCheck = true
  1185  				err := cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig)
  1186  				Expect(err).NotTo(HaveOccurred())
  1187  
  1188  				for i := 0; i < len(plugins); i++ {
  1189  					debug, err := noop_debug.ReadDebug(plugins[i].debugFilePath)
  1190  					Expect(err).NotTo(HaveOccurred())
  1191  					Expect(debug.Command).To(Equal(""))
  1192  				}
  1193  			})
  1194  
  1195  			Context("when the configuration version", func() {
  1196  				var cacheFile string
  1197  
  1198  				BeforeEach(func() {
  1199  					cacheFile = resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig)
  1200  					err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
  1201  					Expect(err).NotTo(HaveOccurred())
  1202  				})
  1203  
  1204  				Context("is 0.4.0", func() {
  1205  					It("passes a cached result to the first plugin", func() {
  1206  						cachedJson := `{
  1207  							"cniVersion": "0.4.0",
  1208  							"ips": [{"version": "4", "address": "10.1.2.3/24"}],
  1209  							"dns": {}
  1210  						}`
  1211  						err := ioutil.WriteFile(cacheFile, []byte(cachedJson), 0600)
  1212  						Expect(err).NotTo(HaveOccurred())
  1213  
  1214  						err = cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig)
  1215  						Expect(err).NotTo(HaveOccurred())
  1216  
  1217  						// Match the first plugin's stdin config to the cached result JSON
  1218  						debug, err := noop_debug.ReadDebug(plugins[0].debugFilePath)
  1219  						Expect(err).NotTo(HaveOccurred())
  1220  
  1221  						var data map[string]interface{}
  1222  						err = json.Unmarshal(debug.CmdArgs.StdinData, &data)
  1223  						Expect(err).NotTo(HaveOccurred())
  1224  						stdinPrevResult, err := json.Marshal(data["prevResult"])
  1225  						Expect(err).NotTo(HaveOccurred())
  1226  						Expect(stdinPrevResult).To(MatchJSON(cachedJson))
  1227  					})
  1228  				})
  1229  
  1230  				Context("is less than 0.4.0", func() {
  1231  					It("fails as CHECK is not supported before 0.4.0", func() {
  1232  						// Set an older CNI version
  1233  						confList := make(map[string]interface{})
  1234  						err := json.Unmarshal(netConfigList.Bytes, &confList)
  1235  						Expect(err).NotTo(HaveOccurred())
  1236  						confList["cniVersion"] = "0.3.1"
  1237  						newBytes, err := json.Marshal(confList)
  1238  						Expect(err).NotTo(HaveOccurred())
  1239  
  1240  						netConfigList, err = libcni.ConfListFromBytes(newBytes)
  1241  						Expect(err).NotTo(HaveOccurred())
  1242  						err = cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig)
  1243  						Expect(err).To(MatchError("configuration version \"0.3.1\" does not support the CHECK command"))
  1244  					})
  1245  				})
  1246  			})
  1247  
  1248  			Context("when finding the plugin fails", func() {
  1249  				BeforeEach(func() {
  1250  					netConfigList.Plugins[1].Network.Type = "does-not-exist"
  1251  				})
  1252  
  1253  				It("returns the error", func() {
  1254  					err := cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig)
  1255  					Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`)))
  1256  				})
  1257  			})
  1258  
  1259  			Context("when the second plugin errors", func() {
  1260  				BeforeEach(func() {
  1261  					plugins[1].debug.ReportError = "plugin error: banana"
  1262  					Expect(plugins[1].debug.WriteDebug(plugins[1].debugFilePath)).To(Succeed())
  1263  				})
  1264  				It("unmarshals and returns the error", func() {
  1265  					err := cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig)
  1266  					Expect(err).To(MatchError("plugin error: banana"))
  1267  				})
  1268  			})
  1269  
  1270  			Context("when the cached result is invalid", func() {
  1271  				It("returns an error", func() {
  1272  					cacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig)
  1273  					err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
  1274  					Expect(err).NotTo(HaveOccurred())
  1275  					err = ioutil.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0600)
  1276  					Expect(err).NotTo(HaveOccurred())
  1277  
  1278  					err = cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig)
  1279  					Expect(err).To(MatchError("failed to get network \"some-list\" cached result: decoding version from network config: invalid character 'a' looking for beginning of value"))
  1280  				})
  1281  			})
  1282  		})
  1283  
  1284  		Describe("DelNetworkList", func() {
  1285  			It("executes all the plugins in reverse order with command DEL", func() {
  1286  				err := cniConfig.DelNetworkList(ctx, netConfigList, runtimeConfig)
  1287  				Expect(err).NotTo(HaveOccurred())
  1288  
  1289  				for i := 0; i < len(plugins); i++ {
  1290  					debug, err := noop_debug.ReadDebug(plugins[i].debugFilePath)
  1291  					Expect(err).NotTo(HaveOccurred())
  1292  					Expect(debug.Command).To(Equal("DEL"))
  1293  
  1294  					// Must explicitly match JSON due to dict element ordering
  1295  					Expect(debug.CmdArgs.StdinData).To(MatchJSON(plugins[i].stdinData))
  1296  					debug.CmdArgs.StdinData = nil
  1297  					Expect(debug.CmdArgs).To(Equal(expectedCmdArgs))
  1298  				}
  1299  
  1300  				// Ensure the cached result no longer exists
  1301  				cachedResult, err := cniConfig.GetNetworkListCachedResult(netConfigList, runtimeConfig)
  1302  				Expect(err).NotTo(HaveOccurred())
  1303  				Expect(cachedResult).To(BeNil())
  1304  
  1305  				// Ensure the cached config no longer exists
  1306  				cachedConfig, newRt, err := cniConfig.GetNetworkListCachedConfig(netConfigList, runtimeConfig)
  1307  				Expect(err).NotTo(HaveOccurred())
  1308  				Expect(cachedConfig).To(BeNil())
  1309  				Expect(newRt).To(BeNil())
  1310  			})
  1311  
  1312  			Context("when the configuration version", func() {
  1313  				var cacheFile string
  1314  
  1315  				BeforeEach(func() {
  1316  					cacheFile = resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig)
  1317  					err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
  1318  					Expect(err).NotTo(HaveOccurred())
  1319  				})
  1320  
  1321  				Context("is 0.4.0", func() {
  1322  					It("passes a cached result to the first plugin", func() {
  1323  						cachedJson := `{
  1324  							"cniVersion": "0.4.0",
  1325  							"ips": [{"version": "4", "address": "10.1.2.3/24"}],
  1326  							"dns": {}
  1327  						}`
  1328  						err := ioutil.WriteFile(cacheFile, []byte(cachedJson), 0600)
  1329  						Expect(err).NotTo(HaveOccurred())
  1330  
  1331  						err = cniConfig.DelNetworkList(ctx, netConfigList, runtimeConfig)
  1332  						Expect(err).NotTo(HaveOccurred())
  1333  
  1334  						// Match the first plugin's stdin config to the cached result JSON
  1335  						debug, err := noop_debug.ReadDebug(plugins[0].debugFilePath)
  1336  						Expect(err).NotTo(HaveOccurred())
  1337  
  1338  						var data map[string]interface{}
  1339  						err = json.Unmarshal(debug.CmdArgs.StdinData, &data)
  1340  						Expect(err).NotTo(HaveOccurred())
  1341  						stdinPrevResult, err := json.Marshal(data["prevResult"])
  1342  						Expect(err).NotTo(HaveOccurred())
  1343  						Expect(stdinPrevResult).To(MatchJSON(cachedJson))
  1344  					})
  1345  				})
  1346  
  1347  				Context("is less than 0.4.0", func() {
  1348  					It("does not pass a cached result to the first plugin", func() {
  1349  						err := ioutil.WriteFile(cacheFile, []byte(`{
  1350  							"cniVersion": "0.3.1",
  1351  							"ips": [{"version": "4", "address": "10.1.2.3/24"}],
  1352  							"dns": {}
  1353  						}`), 0600)
  1354  						Expect(err).NotTo(HaveOccurred())
  1355  
  1356  						// Set an older CNI version
  1357  						confList := make(map[string]interface{})
  1358  						err = json.Unmarshal(netConfigList.Bytes, &confList)
  1359  						Expect(err).NotTo(HaveOccurred())
  1360  						confList["cniVersion"] = "0.3.1"
  1361  						newBytes, err := json.Marshal(confList)
  1362  						Expect(err).NotTo(HaveOccurred())
  1363  
  1364  						netConfigList, err = libcni.ConfListFromBytes(newBytes)
  1365  						Expect(err).NotTo(HaveOccurred())
  1366  						err = cniConfig.DelNetworkList(ctx, netConfigList, runtimeConfig)
  1367  						Expect(err).NotTo(HaveOccurred())
  1368  
  1369  						// Make sure first plugin does not receive a prevResult
  1370  						debug, err := noop_debug.ReadDebug(plugins[0].debugFilePath)
  1371  						Expect(err).NotTo(HaveOccurred())
  1372  						Expect(string(debug.CmdArgs.StdinData)).NotTo(ContainSubstring("\"prevResult\":"))
  1373  					})
  1374  				})
  1375  			})
  1376  
  1377  			Context("when finding the plugin fails", func() {
  1378  				BeforeEach(func() {
  1379  					netConfigList.Plugins[1].Network.Type = "does-not-exist"
  1380  				})
  1381  
  1382  				It("returns the error", func() {
  1383  					err := cniConfig.DelNetworkList(ctx, netConfigList, runtimeConfig)
  1384  					Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`)))
  1385  				})
  1386  			})
  1387  
  1388  			Context("when the plugin errors", func() {
  1389  				BeforeEach(func() {
  1390  					plugins[1].debug.ReportError = "plugin error: banana"
  1391  					Expect(plugins[1].debug.WriteDebug(plugins[1].debugFilePath)).To(Succeed())
  1392  				})
  1393  				It("unmarshals and returns the error", func() {
  1394  					err := cniConfig.DelNetworkList(ctx, netConfigList, runtimeConfig)
  1395  					Expect(err).To(MatchError("plugin error: banana"))
  1396  				})
  1397  			})
  1398  
  1399  			Context("when the cached result is invalid", func() {
  1400  				It("returns an error", func() {
  1401  					cacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig)
  1402  					err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
  1403  					Expect(err).NotTo(HaveOccurred())
  1404  					err = ioutil.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0600)
  1405  					Expect(err).NotTo(HaveOccurred())
  1406  
  1407  					err = cniConfig.DelNetworkList(ctx, netConfigList, runtimeConfig)
  1408  					Expect(err).To(MatchError("failed to get network \"some-list\" cached result: decoding version from network config: invalid character 'a' looking for beginning of value"))
  1409  				})
  1410  			})
  1411  		})
  1412  		Describe("ValidateNetworkList", func() {
  1413  			It("Checks that all plugins exist", func() {
  1414  				caps, err := cniConfig.ValidateNetworkList(ctx, netConfigList)
  1415  				Expect(err).NotTo(HaveOccurred())
  1416  				Expect(caps).To(ConsistOf("portMappings", "otherCapability"))
  1417  
  1418  				netConfigList.Plugins[1].Network.Type = "nope"
  1419  				_, err = cniConfig.ValidateNetworkList(ctx, netConfigList)
  1420  				Expect(err).To(MatchError("[failed to find plugin \"nope\" in path [" + cniConfig.Path[0] + "]]"))
  1421  			})
  1422  
  1423  			It("Checks that the plugins support the needed version", func() {
  1424  				netConfigList.CNIVersion = "broken"
  1425  				_, err := cniConfig.ValidateNetworkList(ctx, netConfigList)
  1426  
  1427  				// The config list is just noop 3 times, so we get 3 errors
  1428  				Expect(err).To(MatchError("[plugin noop does not support config version \"broken\" plugin noop does not support config version \"broken\" plugin noop does not support config version \"broken\"]"))
  1429  			})
  1430  		})
  1431  	})
  1432  
  1433  	Describe("Invoking a sleep plugin", func() {
  1434  		var (
  1435  			debugFilePath string
  1436  			debug         *noop_debug.Debug
  1437  			cniBinPath    string
  1438  			pluginConfig  string
  1439  			cniConfig     *libcni.CNIConfig
  1440  			netConfig     *libcni.NetworkConfig
  1441  			runtimeConfig *libcni.RuntimeConf
  1442  			netConfigList *libcni.NetworkConfigList
  1443  		)
  1444  
  1445  		BeforeEach(func() {
  1446  			debugFile, err := ioutil.TempFile("", "cni_debug")
  1447  			Expect(err).NotTo(HaveOccurred())
  1448  			Expect(debugFile.Close()).To(Succeed())
  1449  			debugFilePath = debugFile.Name()
  1450  
  1451  			debug = &noop_debug.Debug{
  1452  				ReportResult: `{ "ips": [{ "version": "4", "address": "10.1.2.3/24" }], "dns": {} }`,
  1453  			}
  1454  			Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
  1455  
  1456  			portMappings := []portMapping{
  1457  				{HostPort: 8080, ContainerPort: 80, Protocol: "tcp"},
  1458  			}
  1459  
  1460  			pluginConfig = fmt.Sprintf(`{
  1461  				"type": "sleep",
  1462  				"name": "apitest",
  1463  				"some-key": "some-value",
  1464  				"cniVersion": "%s",
  1465  				"capabilities": { "portMappings": true }
  1466  			}`, current.ImplementedSpecVersion)
  1467  
  1468  			cniBinPath = filepath.Dir(pluginPaths["sleep"])
  1469  			cniConfig = libcni.NewCNIConfig([]string{cniBinPath}, nil)
  1470  			netConfig, err = libcni.ConfFromBytes([]byte(pluginConfig))
  1471  			Expect(err).NotTo(HaveOccurred())
  1472  			runtimeConfig = &libcni.RuntimeConf{
  1473  				ContainerID: "some-container-id",
  1474  				NetNS:       "/some/netns/path",
  1475  				IfName:      "some-eth0",
  1476  				Args:        [][2]string{{"DEBUG", debugFilePath}},
  1477  			}
  1478  
  1479  			// inject runtime args into the expected plugin config
  1480  			conf := make(map[string]interface{})
  1481  			err = json.Unmarshal([]byte(pluginConfig), &conf)
  1482  			Expect(err).NotTo(HaveOccurred())
  1483  			conf["runtimeConfig"] = map[string]interface{}{
  1484  				"portMappings": portMappings,
  1485  			}
  1486  
  1487  			configList := []byte(fmt.Sprintf(`{
  1488    "name": "some-list",
  1489    "cniVersion": "%s",
  1490    "plugins": [
  1491      %s
  1492    ]
  1493  }`, current.ImplementedSpecVersion, pluginConfig))
  1494  
  1495  			netConfigList, err = libcni.ConfListFromBytes(configList)
  1496  			Expect(err).NotTo(HaveOccurred())
  1497  
  1498  		})
  1499  
  1500  		AfterEach(func() {
  1501  			Expect(os.RemoveAll(debugFilePath)).To(Succeed())
  1502  		})
  1503  
  1504  		Describe("AddNetwork", func() {
  1505  			Context("when the plugin timeout", func() {
  1506  				It("returns the timeout error", func() {
  1507  					ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
  1508  					result, err := cniConfig.AddNetwork(ctx, netConfig, runtimeConfig)
  1509  					cancel()
  1510  					Expect(result).To(BeNil())
  1511  					Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
  1512  				})
  1513  
  1514  			})
  1515  		})
  1516  
  1517  		Describe("DelNetwork", func() {
  1518  			Context("when the plugin timeout", func() {
  1519  				It("returns the timeout error", func() {
  1520  					ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
  1521  					err := cniConfig.DelNetwork(ctx, netConfig, runtimeConfig)
  1522  					cancel()
  1523  					Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
  1524  				})
  1525  
  1526  			})
  1527  		})
  1528  
  1529  		Describe("CheckNetwork", func() {
  1530  			Context("when the plugin timeout", func() {
  1531  				It("returns the timeout error", func() {
  1532  					ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
  1533  					err := cniConfig.CheckNetwork(ctx, netConfig, runtimeConfig)
  1534  					cancel()
  1535  					Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
  1536  				})
  1537  
  1538  			})
  1539  		})
  1540  
  1541  		Describe("GetVersionInfo", func() {
  1542  			Context("when the plugin timeout", func() {
  1543  				It("returns the timeout error", func() {
  1544  					ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
  1545  					result, err := cniConfig.GetVersionInfo(ctx, "sleep")
  1546  					cancel()
  1547  					Expect(result).To(BeNil())
  1548  					Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
  1549  				})
  1550  
  1551  			})
  1552  		})
  1553  
  1554  		Describe("ValidateNetwork", func() {
  1555  			Context("when the plugin timeout", func() {
  1556  				It("returns the timeout error", func() {
  1557  					ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
  1558  					_, err := cniConfig.ValidateNetwork(ctx, netConfig)
  1559  					cancel()
  1560  					Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
  1561  				})
  1562  
  1563  			})
  1564  		})
  1565  
  1566  		Describe("AddNetworkList", func() {
  1567  			Context("when the plugin timeout", func() {
  1568  				It("returns the timeout error", func() {
  1569  					ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
  1570  					result, err := cniConfig.AddNetworkList(ctx, netConfigList, runtimeConfig)
  1571  					cancel()
  1572  					Expect(result).To(BeNil())
  1573  					Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
  1574  				})
  1575  
  1576  			})
  1577  		})
  1578  
  1579  		Describe("DelNetworkList", func() {
  1580  			Context("when the plugin timeout", func() {
  1581  				It("returns the timeout error", func() {
  1582  					ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
  1583  					err := cniConfig.DelNetworkList(ctx, netConfigList, runtimeConfig)
  1584  					cancel()
  1585  					Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
  1586  				})
  1587  
  1588  			})
  1589  		})
  1590  
  1591  		Describe("CheckNetworkList", func() {
  1592  			Context("when the plugin timeout", func() {
  1593  				It("returns the timeout error", func() {
  1594  					ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
  1595  					err := cniConfig.CheckNetworkList(ctx, netConfigList, runtimeConfig)
  1596  					cancel()
  1597  					Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
  1598  				})
  1599  
  1600  			})
  1601  		})
  1602  
  1603  		Describe("ValidateNetworkList", func() {
  1604  			Context("when the plugin timeout", func() {
  1605  				It("returns the timeout error", func() {
  1606  					ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
  1607  					_, err := cniConfig.ValidateNetworkList(ctx, netConfigList)
  1608  					cancel()
  1609  					Expect(err).To(MatchError(ContainSubstring("netplugin failed with no error message")))
  1610  				})
  1611  
  1612  			})
  1613  		})
  1614  
  1615  	})
  1616  
  1617  	Describe("Cache operations", func() {
  1618  		var (
  1619  			debugFilePath string
  1620  			debug         *noop_debug.Debug
  1621  			cniBinPath    string
  1622  			pluginConfig  string
  1623  			cniConfig     *libcni.CNIConfig
  1624  			netConfig     *libcni.NetworkConfig
  1625  			runtimeConfig *libcni.RuntimeConf
  1626  
  1627  			ctx context.Context
  1628  		)
  1629  		firstIP := "10.1.2.3/24"
  1630  		firstIfname := "eth0"
  1631  		secondIP := "10.1.2.5/24"
  1632  		secondIfname := "eth1"
  1633  		containerID := "some-container-id"
  1634  		netName := "cachetest"
  1635  		netNS := "/some/netns/path"
  1636  
  1637  		BeforeEach(func() {
  1638  			debugFile, err := ioutil.TempFile("", "cni_debug")
  1639  			Expect(err).NotTo(HaveOccurred())
  1640  			Expect(debugFile.Close()).To(Succeed())
  1641  			debugFilePath = debugFile.Name()
  1642  
  1643  			debug = &noop_debug.Debug{
  1644  				ReportResult: fmt.Sprintf(`{
  1645  					"cniVersion": "%s",
  1646  					"ips": [{"version": "4", "address": "%s"}]
  1647  				}`, current.ImplementedSpecVersion, firstIP),
  1648  			}
  1649  			Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
  1650  
  1651  			cniBinPath = filepath.Dir(pluginPaths["noop"])
  1652  			pluginConfig = fmt.Sprintf(`{
  1653  				"type": "noop",
  1654  				"name": "%s",
  1655  				"cniVersion": "%s"
  1656  			}`, netName, current.ImplementedSpecVersion)
  1657  			cniConfig = libcni.NewCNIConfigWithCacheDir([]string{cniBinPath}, cacheDirPath, nil)
  1658  			netConfig, err = libcni.ConfFromBytes([]byte(pluginConfig))
  1659  			Expect(err).NotTo(HaveOccurred())
  1660  			runtimeConfig = &libcni.RuntimeConf{
  1661  				ContainerID: containerID,
  1662  				NetNS:       netNS,
  1663  				IfName:      firstIfname,
  1664  				Args:        [][2]string{{"DEBUG", debugFilePath}},
  1665  			}
  1666  			ctx = context.TODO()
  1667  		})
  1668  
  1669  		AfterEach(func() {
  1670  			Expect(os.RemoveAll(debugFilePath)).To(Succeed())
  1671  		})
  1672  
  1673  		It("creates separate result cache files for multiple attachments to the same network", func() {
  1674  			_, err := cniConfig.AddNetwork(ctx, netConfig, runtimeConfig)
  1675  			Expect(err).NotTo(HaveOccurred())
  1676  
  1677  			debug.ReportResult = fmt.Sprintf(`{
  1678  				"cniVersion": "%s",
  1679  				"ips": [{"version": "4", "address": "%s"}]
  1680  			}`, current.ImplementedSpecVersion, secondIP)
  1681  			Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
  1682  			runtimeConfig.IfName = secondIfname
  1683  			_, err = cniConfig.AddNetwork(ctx, netConfig, runtimeConfig)
  1684  			Expect(err).NotTo(HaveOccurred())
  1685  
  1686  			resultsDir := filepath.Join(cacheDirPath, "results")
  1687  			files, err := ioutil.ReadDir(resultsDir)
  1688  			Expect(err).NotTo(HaveOccurred())
  1689  			Expect(len(files)).To(Equal(2))
  1690  			var foundFirst, foundSecond bool
  1691  			for _, f := range files {
  1692  				type cachedConfig struct {
  1693  					Kind        string `json:"kind"`
  1694  					IfName      string `json:"ifName"`
  1695  					ContainerID string `json:"containerId"`
  1696  					NetworkName string `json:"networkName"`
  1697  				}
  1698  
  1699  				data, err := ioutil.ReadFile(filepath.Join(resultsDir, f.Name()))
  1700  				Expect(err).NotTo(HaveOccurred())
  1701  				cc := &cachedConfig{}
  1702  				err = json.Unmarshal(data, cc)
  1703  				Expect(err).NotTo(HaveOccurred())
  1704  				Expect(cc.Kind).To(Equal("cniCacheV1"))
  1705  				Expect(cc.ContainerID).To(Equal(containerID))
  1706  				Expect(cc.NetworkName).To(Equal(netName))
  1707  				if strings.HasSuffix(f.Name(), firstIfname) {
  1708  					foundFirst = true
  1709  					Expect(strings.Contains(string(data), firstIP)).To(BeTrue())
  1710  					Expect(cc.IfName).To(Equal(firstIfname))
  1711  				} else if strings.HasSuffix(f.Name(), secondIfname) {
  1712  					foundSecond = true
  1713  					Expect(strings.Contains(string(data), secondIP)).To(BeTrue())
  1714  					Expect(cc.IfName).To(Equal(secondIfname))
  1715  				}
  1716  			}
  1717  			Expect(foundFirst).To(BeTrue())
  1718  			Expect(foundSecond).To(BeTrue())
  1719  		})
  1720  
  1721  		It("returns an updated copy of RuntimeConf filled with cached info", func() {
  1722  			_, err := cniConfig.AddNetwork(ctx, netConfig, runtimeConfig)
  1723  			Expect(err).NotTo(HaveOccurred())
  1724  
  1725  			// Ensure the cached config matches the sent one
  1726  			rt := &libcni.RuntimeConf{
  1727  				ContainerID: containerID,
  1728  				NetNS:       netNS,
  1729  				IfName:      firstIfname,
  1730  			}
  1731  			_, newRt, err := cniConfig.GetNetworkListCachedConfig(&libcni.NetworkConfigList{Name: netName}, rt)
  1732  			Expect(err).NotTo(HaveOccurred())
  1733  			Expect(newRt.IfName).To(Equal(runtimeConfig.IfName))
  1734  			Expect(newRt.ContainerID).To(Equal(runtimeConfig.ContainerID))
  1735  			Expect(reflect.DeepEqual(newRt.Args, runtimeConfig.Args)).To(BeTrue())
  1736  			expectedCABytes, err := json.Marshal(runtimeConfig.CapabilityArgs)
  1737  			Expect(err).NotTo(HaveOccurred())
  1738  			foundCABytes, err := json.Marshal(newRt.CapabilityArgs)
  1739  			Expect(err).NotTo(HaveOccurred())
  1740  			Expect(foundCABytes).To(MatchJSON(expectedCABytes))
  1741  		})
  1742  
  1743  		Context("when the RuntimeConf is incomplete", func() {
  1744  			var (
  1745  				testRt          *libcni.RuntimeConf
  1746  				testNetConf     *libcni.NetworkConfig
  1747  				testNetConfList *libcni.NetworkConfigList
  1748  			)
  1749  
  1750  			BeforeEach(func() {
  1751  				testRt = &libcni.RuntimeConf{}
  1752  				testNetConf = &libcni.NetworkConfig{
  1753  					Network: &types.NetConf{},
  1754  				}
  1755  				testNetConfList = &libcni.NetworkConfigList{}
  1756  			})
  1757  
  1758  			It("returns an error on missing network name", func() {
  1759  				testRt.ContainerID = containerID
  1760  				testRt.IfName = firstIfname
  1761  				cachedConfig, newRt, err := cniConfig.GetNetworkCachedConfig(testNetConf, testRt)
  1762  				Expect(err).To(MatchError("cache file path requires network name (\"\"), container ID (\"some-container-id\"), and interface name (\"eth0\")"))
  1763  				Expect(cachedConfig).To(BeNil())
  1764  				Expect(newRt).To(BeNil())
  1765  
  1766  				cachedConfig, newRt, err = cniConfig.GetNetworkListCachedConfig(testNetConfList, testRt)
  1767  				Expect(err).To(MatchError("cache file path requires network name (\"\"), container ID (\"some-container-id\"), and interface name (\"eth0\")"))
  1768  				Expect(cachedConfig).To(BeNil())
  1769  				Expect(newRt).To(BeNil())
  1770  			})
  1771  
  1772  			It("returns an error on missing container ID", func() {
  1773  				testNetConf.Network.Name = "foobar"
  1774  				testNetConfList.Name = "foobar"
  1775  				testRt.IfName = firstIfname
  1776  
  1777  				cachedConfig, newRt, err := cniConfig.GetNetworkCachedConfig(testNetConf, testRt)
  1778  				Expect(err).To(MatchError("cache file path requires network name (\"foobar\"), container ID (\"\"), and interface name (\"eth0\")"))
  1779  				Expect(cachedConfig).To(BeNil())
  1780  				Expect(newRt).To(BeNil())
  1781  
  1782  				cachedConfig, newRt, err = cniConfig.GetNetworkListCachedConfig(testNetConfList, testRt)
  1783  				Expect(err).To(MatchError("cache file path requires network name (\"foobar\"), container ID (\"\"), and interface name (\"eth0\")"))
  1784  				Expect(cachedConfig).To(BeNil())
  1785  				Expect(newRt).To(BeNil())
  1786  			})
  1787  
  1788  			It("returns an error on missing interface name", func() {
  1789  				testNetConf.Network.Name = "foobar"
  1790  				testNetConfList.Name = "foobar"
  1791  				testRt.ContainerID = containerID
  1792  
  1793  				cachedConfig, newRt, err := cniConfig.GetNetworkCachedConfig(testNetConf, testRt)
  1794  				Expect(err).To(MatchError("cache file path requires network name (\"foobar\"), container ID (\"some-container-id\"), and interface name (\"\")"))
  1795  				Expect(cachedConfig).To(BeNil())
  1796  				Expect(newRt).To(BeNil())
  1797  
  1798  				cachedConfig, newRt, err = cniConfig.GetNetworkListCachedConfig(testNetConfList, testRt)
  1799  				Expect(err).To(MatchError("cache file path requires network name (\"foobar\"), container ID (\"some-container-id\"), and interface name (\"\")"))
  1800  				Expect(cachedConfig).To(BeNil())
  1801  				Expect(newRt).To(BeNil())
  1802  			})
  1803  		})
  1804  
  1805  		Context("when the cached config", func() {
  1806  			Context("is invalid JSON", func() {
  1807  				It("returns an error", func() {
  1808  					resultCacheFile := resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig)
  1809  					err := os.MkdirAll(filepath.Dir(resultCacheFile), 0700)
  1810  					Expect(err).NotTo(HaveOccurred())
  1811  
  1812  					err = ioutil.WriteFile(resultCacheFile, []byte("adfadsfasdfasfdsafaf"), 0600)
  1813  					Expect(err).NotTo(HaveOccurred())
  1814  
  1815  					cachedConfig, newRt, err := cniConfig.GetNetworkCachedConfig(netConfig, runtimeConfig)
  1816  					Expect(err).To(MatchError("failed to unmarshal cached network \"cachetest\" config: invalid character 'a' looking for beginning of value"))
  1817  					Expect(cachedConfig).To(BeNil())
  1818  					Expect(newRt).To(BeNil())
  1819  				})
  1820  			})
  1821  			Context("is missing", func() {
  1822  				It("returns no error", func() {
  1823  					resultCacheFile := resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig)
  1824  					err := os.MkdirAll(filepath.Dir(resultCacheFile), 0700)
  1825  					Expect(err).NotTo(HaveOccurred())
  1826  
  1827  					cachedConfig, newRt, err := cniConfig.GetNetworkCachedConfig(netConfig, runtimeConfig)
  1828  					Expect(err).NotTo(HaveOccurred())
  1829  					Expect(cachedConfig).To(BeNil())
  1830  					Expect(newRt).To(BeNil())
  1831  				})
  1832  			})
  1833  		})
  1834  
  1835  	})
  1836  })