github.com/mccv1r0/cni@v0.7.0-alpha1/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  	"encoding/json"
    19  	"fmt"
    20  	"io/ioutil"
    21  	"net"
    22  	"os"
    23  	"path/filepath"
    24  
    25  	"github.com/containernetworking/cni/libcni"
    26  	"github.com/containernetworking/cni/pkg/skel"
    27  	"github.com/containernetworking/cni/pkg/types"
    28  	"github.com/containernetworking/cni/pkg/types/current"
    29  	noop_debug "github.com/containernetworking/cni/plugins/test/noop/debug"
    30  
    31  	. "github.com/onsi/ginkgo"
    32  	. "github.com/onsi/gomega"
    33  )
    34  
    35  type pluginInfo struct {
    36  	debugFilePath string
    37  	debug         *noop_debug.Debug
    38  	config        string
    39  	stdinData     []byte
    40  }
    41  
    42  type portMapping struct {
    43  	HostPort      int    `json:"hostPort"`
    44  	ContainerPort int    `json:"containerPort"`
    45  	Protocol      string `json:"protocol"`
    46  }
    47  
    48  func stringInList(s string, list []string) bool {
    49  	for _, item := range list {
    50  		if s == item {
    51  			return true
    52  		}
    53  	}
    54  	return false
    55  }
    56  
    57  func newPluginInfo(configValue, prevResult string, injectDebugFilePath bool, result string, runtimeConfig map[string]interface{}, capabilities []string) pluginInfo {
    58  	debugFile, err := ioutil.TempFile("", "cni_debug")
    59  	Expect(err).NotTo(HaveOccurred())
    60  	Expect(debugFile.Close()).To(Succeed())
    61  	debugFilePath := debugFile.Name()
    62  
    63  	debug := &noop_debug.Debug{
    64  		ReportResult: result,
    65  	}
    66  	Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
    67  
    68  	// config is what would be in the plugin's on-disk configuration
    69  	// without runtime injected keys
    70  	config := fmt.Sprintf(`{"type": "noop", "some-key": "%s"`, configValue)
    71  	if prevResult != "" {
    72  		config += fmt.Sprintf(`, "prevResult": %s`, prevResult)
    73  	}
    74  	if injectDebugFilePath {
    75  		config += fmt.Sprintf(`, "debugFile": %q`, debugFilePath)
    76  	}
    77  	if len(capabilities) > 0 {
    78  		config += `, "capabilities": {`
    79  		for i, c := range capabilities {
    80  			if i > 0 {
    81  				config += ", "
    82  			}
    83  			config += fmt.Sprintf(`"%s": true`, c)
    84  		}
    85  		config += "}"
    86  	}
    87  	config += "}"
    88  
    89  	// stdinData is what the runtime should pass to the plugin's stdin,
    90  	// including injected keys like 'name', 'cniVersion', and 'runtimeConfig'
    91  	newConfig := make(map[string]interface{})
    92  	err = json.Unmarshal([]byte(config), &newConfig)
    93  	Expect(err).NotTo(HaveOccurred())
    94  	newConfig["name"] = "some-list"
    95  	newConfig["cniVersion"] = current.ImplementedSpecVersion
    96  
    97  	// Only include standard runtime config and capability args that this plugin advertises
    98  	newRuntimeConfig := make(map[string]interface{})
    99  	for key, value := range runtimeConfig {
   100  		if stringInList(key, capabilities) {
   101  			newRuntimeConfig[key] = value
   102  		}
   103  	}
   104  	if len(newRuntimeConfig) > 0 {
   105  		newConfig["runtimeConfig"] = newRuntimeConfig
   106  	}
   107  
   108  	stdinData, err := json.Marshal(newConfig)
   109  	Expect(err).NotTo(HaveOccurred())
   110  
   111  	return pluginInfo{
   112  		debugFilePath: debugFilePath,
   113  		debug:         debug,
   114  		config:        config,
   115  		stdinData:     stdinData,
   116  	}
   117  }
   118  
   119  func resultCacheFilePath(cacheDirPath, netName, containerID string) string {
   120  	return filepath.Join(cacheDirPath, "results", netName+"-"+containerID)
   121  }
   122  
   123  var _ = Describe("Invoking plugins", func() {
   124  	var cacheDirPath string
   125  
   126  	BeforeEach(func() {
   127  		var err error
   128  		cacheDirPath, err = ioutil.TempDir("", "cni_cachedir")
   129  		Expect(err).NotTo(HaveOccurred())
   130  	})
   131  
   132  	AfterEach(func() {
   133  		Expect(os.RemoveAll(cacheDirPath)).To(Succeed())
   134  	})
   135  
   136  	Describe("Capabilities", func() {
   137  		var (
   138  			debugFilePath string
   139  			debug         *noop_debug.Debug
   140  			pluginConfig  []byte
   141  			cniConfig     *libcni.CNIConfig
   142  			runtimeConfig *libcni.RuntimeConf
   143  			netConfig     *libcni.NetworkConfig
   144  		)
   145  
   146  		BeforeEach(func() {
   147  			debugFile, err := ioutil.TempFile("", "cni_debug")
   148  			Expect(err).NotTo(HaveOccurred())
   149  			Expect(debugFile.Close()).To(Succeed())
   150  			debugFilePath = debugFile.Name()
   151  
   152  			debug = &noop_debug.Debug{}
   153  			Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
   154  
   155  			pluginConfig = []byte(fmt.Sprintf(`{
   156  				"type": "noop",
   157  				"name": "apitest",
   158  				"cniVersion": "%s",
   159  				"capabilities": {
   160  					"portMappings": true,
   161  					"somethingElse": true,
   162  					"noCapability": false
   163  				}
   164  			}`, current.ImplementedSpecVersion))
   165  			netConfig, err = libcni.ConfFromBytes(pluginConfig)
   166  			Expect(err).NotTo(HaveOccurred())
   167  
   168  			cniConfig = libcni.NewCNIConfig([]string{filepath.Dir(pluginPaths["noop"])}, nil)
   169  
   170  			runtimeConfig = &libcni.RuntimeConf{
   171  				ContainerID: "some-container-id",
   172  				NetNS:       "/some/netns/path",
   173  				IfName:      "some-eth0",
   174  				Args:        [][2]string{{"DEBUG", debugFilePath}},
   175  				CapabilityArgs: map[string]interface{}{
   176  					"portMappings": []portMapping{
   177  						{HostPort: 8080, ContainerPort: 80, Protocol: "tcp"},
   178  					},
   179  					"somethingElse": []string{"foobar", "baz"},
   180  					"noCapability":  true,
   181  					"notAdded":      []bool{true, false},
   182  				},
   183  				CacheDir: cacheDirPath,
   184  			}
   185  		})
   186  
   187  		AfterEach(func() {
   188  			Expect(os.RemoveAll(debugFilePath)).To(Succeed())
   189  		})
   190  
   191  		It("adds correct runtime config for capabilities to stdin", func() {
   192  			_, err := cniConfig.AddNetwork(netConfig, runtimeConfig)
   193  			Expect(err).NotTo(HaveOccurred())
   194  
   195  			debug, err = noop_debug.ReadDebug(debugFilePath)
   196  			Expect(err).NotTo(HaveOccurred())
   197  			Expect(debug.Command).To(Equal("ADD"))
   198  
   199  			conf := make(map[string]interface{})
   200  			err = json.Unmarshal(debug.CmdArgs.StdinData, &conf)
   201  			Expect(err).NotTo(HaveOccurred())
   202  
   203  			// We expect runtimeConfig keys only for portMappings and somethingElse
   204  			rawRc := conf["runtimeConfig"]
   205  			rc, ok := rawRc.(map[string]interface{})
   206  			Expect(ok).To(Equal(true))
   207  			expectedKeys := []string{"portMappings", "somethingElse"}
   208  			Expect(len(rc)).To(Equal(len(expectedKeys)))
   209  			for _, key := range expectedKeys {
   210  				_, ok := rc[key]
   211  				Expect(ok).To(Equal(true))
   212  			}
   213  		})
   214  
   215  		It("adds no runtimeConfig when the plugin advertises no used capabilities", func() {
   216  			// Replace CapabilityArgs with ones we know the plugin
   217  			// doesn't support
   218  			runtimeConfig.CapabilityArgs = map[string]interface{}{
   219  				"portMappings22": []portMapping{
   220  					{HostPort: 8080, ContainerPort: 80, Protocol: "tcp"},
   221  				},
   222  				"somethingElse22": []string{"foobar", "baz"},
   223  			}
   224  
   225  			_, err := cniConfig.AddNetwork(netConfig, runtimeConfig)
   226  			Expect(err).NotTo(HaveOccurred())
   227  
   228  			debug, err = noop_debug.ReadDebug(debugFilePath)
   229  			Expect(err).NotTo(HaveOccurred())
   230  			Expect(debug.Command).To(Equal("ADD"))
   231  
   232  			conf := make(map[string]interface{})
   233  			err = json.Unmarshal(debug.CmdArgs.StdinData, &conf)
   234  			Expect(err).NotTo(HaveOccurred())
   235  
   236  			// No intersection of plugin capabilities and CapabilityArgs,
   237  			// so plugin should not receive a "runtimeConfig" key
   238  			_, ok := conf["runtimeConfig"]
   239  			Expect(ok).Should(BeFalse())
   240  		})
   241  	})
   242  
   243  	Describe("Invoking a single plugin", func() {
   244  		var (
   245  			debugFilePath string
   246  			debug         *noop_debug.Debug
   247  			cniBinPath    string
   248  			pluginConfig  string
   249  			cniConfig     *libcni.CNIConfig
   250  			netConfig     *libcni.NetworkConfig
   251  			runtimeConfig *libcni.RuntimeConf
   252  
   253  			expectedCmdArgs skel.CmdArgs
   254  		)
   255  
   256  		BeforeEach(func() {
   257  			debugFile, err := ioutil.TempFile("", "cni_debug")
   258  			Expect(err).NotTo(HaveOccurred())
   259  			Expect(debugFile.Close()).To(Succeed())
   260  			debugFilePath = debugFile.Name()
   261  
   262  			debug = &noop_debug.Debug{
   263  				ReportResult: `{
   264  					"cniVersion": "0.4.0",
   265  					"ips": [{"version": "4", "address": "10.1.2.3/24"}],
   266  					"dns": {}
   267  				}`,
   268  			}
   269  			Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
   270  
   271  			portMappings := []portMapping{
   272  				{HostPort: 8080, ContainerPort: 80, Protocol: "tcp"},
   273  			}
   274  
   275  			cniBinPath = filepath.Dir(pluginPaths["noop"])
   276  			pluginConfig = fmt.Sprintf(`{
   277  				"type": "noop",
   278  				"name": "apitest",
   279  				"some-key": "some-value",
   280  				"cniVersion": "%s",
   281  				"capabilities": { "portMappings": true }
   282  			}`, current.ImplementedSpecVersion)
   283  			cniConfig = libcni.NewCNIConfig([]string{cniBinPath}, nil)
   284  			netConfig, err = libcni.ConfFromBytes([]byte(pluginConfig))
   285  			Expect(err).NotTo(HaveOccurred())
   286  			runtimeConfig = &libcni.RuntimeConf{
   287  				ContainerID: "some-container-id",
   288  				NetNS:       "/some/netns/path",
   289  				IfName:      "some-eth0",
   290  				CacheDir:    cacheDirPath,
   291  				Args:        [][2]string{{"DEBUG", debugFilePath}},
   292  				CapabilityArgs: map[string]interface{}{
   293  					"portMappings": portMappings,
   294  				},
   295  			}
   296  
   297  			// inject runtime args into the expected plugin config
   298  			conf := make(map[string]interface{})
   299  			err = json.Unmarshal([]byte(pluginConfig), &conf)
   300  			Expect(err).NotTo(HaveOccurred())
   301  			conf["runtimeConfig"] = map[string]interface{}{
   302  				"portMappings": portMappings,
   303  			}
   304  			newBytes, err := json.Marshal(conf)
   305  			Expect(err).NotTo(HaveOccurred())
   306  
   307  			expectedCmdArgs = skel.CmdArgs{
   308  				ContainerID: "some-container-id",
   309  				Netns:       "/some/netns/path",
   310  				IfName:      "some-eth0",
   311  				Args:        "DEBUG=" + debugFilePath,
   312  				Path:        cniBinPath,
   313  				StdinData:   newBytes,
   314  			}
   315  		})
   316  
   317  		AfterEach(func() {
   318  			Expect(os.RemoveAll(debugFilePath)).To(Succeed())
   319  		})
   320  
   321  		Describe("AddNetwork", func() {
   322  			It("executes the plugin with command ADD", func() {
   323  				r, err := cniConfig.AddNetwork(netConfig, runtimeConfig)
   324  				Expect(err).NotTo(HaveOccurred())
   325  
   326  				result, err := current.GetResult(r)
   327  				Expect(err).NotTo(HaveOccurred())
   328  
   329  				Expect(result).To(Equal(&current.Result{
   330  					CNIVersion: current.ImplementedSpecVersion,
   331  					IPs: []*current.IPConfig{
   332  						{
   333  							Version: "4",
   334  							Address: net.IPNet{
   335  								IP:   net.ParseIP("10.1.2.3"),
   336  								Mask: net.IPv4Mask(255, 255, 255, 0),
   337  							},
   338  						},
   339  					},
   340  				}))
   341  
   342  				debug, err := noop_debug.ReadDebug(debugFilePath)
   343  				Expect(err).NotTo(HaveOccurred())
   344  				Expect(debug.Command).To(Equal("ADD"))
   345  				Expect(debug.CmdArgs).To(Equal(expectedCmdArgs))
   346  				Expect(string(debug.CmdArgs.StdinData)).To(ContainSubstring("\"portMappings\":"))
   347  
   348  				// Ensure the cached result matches the returned one
   349  				cacheFile := resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig.ContainerID)
   350  				_, err = os.Stat(cacheFile)
   351  				Expect(err).NotTo(HaveOccurred())
   352  				cachedData, err := ioutil.ReadFile(cacheFile)
   353  				Expect(err).NotTo(HaveOccurred())
   354  				returnedData, err := json.Marshal(result)
   355  				Expect(err).NotTo(HaveOccurred())
   356  				Expect(cachedData).To(MatchJSON(returnedData))
   357  			})
   358  
   359  			Context("when finding the plugin fails", func() {
   360  				BeforeEach(func() {
   361  					netConfig.Network.Type = "does-not-exist"
   362  				})
   363  
   364  				It("returns the error", func() {
   365  					_, err := cniConfig.AddNetwork(netConfig, runtimeConfig)
   366  					Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`)))
   367  				})
   368  			})
   369  
   370  			Context("when the plugin errors", func() {
   371  				BeforeEach(func() {
   372  					debug.ReportError = "plugin error: banana"
   373  					Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
   374  				})
   375  				It("unmarshals and returns the error", func() {
   376  					result, err := cniConfig.AddNetwork(netConfig, runtimeConfig)
   377  					Expect(result).To(BeNil())
   378  					Expect(err).To(MatchError("plugin error: banana"))
   379  				})
   380  			})
   381  
   382  			Context("when the result cache directory cannot be accessed", func() {
   383  				It("returns an error", func() {
   384  					// Make the results directory inaccessble by making it a
   385  					// file instead of a directory
   386  					tmpPath := filepath.Join(cacheDirPath, "results")
   387  					err := ioutil.WriteFile(tmpPath, []byte("afdsasdfasdf"), 0600)
   388  					Expect(err).NotTo(HaveOccurred())
   389  
   390  					result, err := cniConfig.AddNetwork(netConfig, runtimeConfig)
   391  					Expect(result).To(BeNil())
   392  					Expect(err).To(HaveOccurred())
   393  				})
   394  			})
   395  		})
   396  
   397  		Describe("GetNetwork", func() {
   398  			It("executes the plugin with command GET", func() {
   399  				cacheFile := resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig.ContainerID)
   400  				err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
   401  				Expect(err).NotTo(HaveOccurred())
   402  				cachedJson := `{
   403  					"cniVersion": "0.4.0",
   404  					"ips": [{"version": "4", "address": "10.1.2.3/24"}],
   405  					"dns": {}
   406  				}`
   407  				err = ioutil.WriteFile(cacheFile, []byte(cachedJson), 0600)
   408  				Expect(err).NotTo(HaveOccurred())
   409  
   410  				r, err := cniConfig.GetNetwork(netConfig, runtimeConfig)
   411  				Expect(err).NotTo(HaveOccurred())
   412  
   413  				result, err := current.GetResult(r)
   414  				Expect(err).NotTo(HaveOccurred())
   415  
   416  				Expect(result).To(Equal(&current.Result{
   417  					CNIVersion: current.ImplementedSpecVersion,
   418  					IPs: []*current.IPConfig{
   419  						{
   420  							Version: "4",
   421  							Address: net.IPNet{
   422  								IP:   net.ParseIP("10.1.2.3"),
   423  								Mask: net.IPv4Mask(255, 255, 255, 0),
   424  							},
   425  						},
   426  					},
   427  				}))
   428  
   429  				debug, err := noop_debug.ReadDebug(debugFilePath)
   430  				Expect(err).NotTo(HaveOccurred())
   431  				Expect(debug.Command).To(Equal("GET"))
   432  				Expect(string(debug.CmdArgs.StdinData)).To(ContainSubstring("\"portMappings\":"))
   433  
   434  				// Explicitly match stdin data as json after
   435  				// inserting the expected prevResult
   436  				var data, data2 map[string]interface{}
   437  				err = json.Unmarshal(expectedCmdArgs.StdinData, &data)
   438  				Expect(err).NotTo(HaveOccurred())
   439  				err = json.Unmarshal([]byte(cachedJson), &data2)
   440  				Expect(err).NotTo(HaveOccurred())
   441  				data["prevResult"] = data2
   442  				expectedStdinJson, err := json.Marshal(data)
   443  				Expect(err).NotTo(HaveOccurred())
   444  				Expect(debug.CmdArgs.StdinData).To(MatchJSON(expectedStdinJson))
   445  
   446  				debug.CmdArgs.StdinData = nil
   447  				expectedCmdArgs.StdinData = nil
   448  				Expect(debug.CmdArgs).To(Equal(expectedCmdArgs))
   449  			})
   450  
   451  			Context("when finding the plugin fails", func() {
   452  				BeforeEach(func() {
   453  					netConfig.Network.Type = "does-not-exist"
   454  				})
   455  
   456  				It("returns the error", func() {
   457  					_, err := cniConfig.GetNetwork(netConfig, runtimeConfig)
   458  					Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`)))
   459  				})
   460  			})
   461  
   462  			Context("when the plugin errors", func() {
   463  				BeforeEach(func() {
   464  					debug.ReportError = "plugin error: banana"
   465  					Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
   466  				})
   467  				It("unmarshals and returns the error", func() {
   468  					result, err := cniConfig.GetNetwork(netConfig, runtimeConfig)
   469  					Expect(result).To(BeNil())
   470  					Expect(err).To(MatchError("plugin error: banana"))
   471  				})
   472  			})
   473  
   474  			Context("when GET is called with a configuration version", func() {
   475  				var cacheFile string
   476  
   477  				BeforeEach(func() {
   478  					cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig.ContainerID)
   479  					err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
   480  					Expect(err).NotTo(HaveOccurred())
   481  				})
   482  
   483  				Context("less than 0.4.0", func() {
   484  					It("fails as GET is not supported before 0.4.0", func() {
   485  						// Generate plugin config with older version
   486  						pluginConfig = `{
   487  							"type": "noop",
   488  							"name": "apitest",
   489  							"cniVersion": "0.3.1"
   490  						}`
   491  						var err error
   492  						netConfig, err = libcni.ConfFromBytes([]byte(pluginConfig))
   493  						Expect(err).NotTo(HaveOccurred())
   494  						_, err = cniConfig.GetNetwork(netConfig, runtimeConfig)
   495  						Expect(err).To(MatchError("configuration version \"0.3.1\" does not support the GET command"))
   496  
   497  						debug, err := noop_debug.ReadDebug(debugFilePath)
   498  						Expect(err).NotTo(HaveOccurred())
   499  						Expect(string(debug.CmdArgs.StdinData)).NotTo(ContainSubstring("\"prevResult\":"))
   500  					})
   501  				})
   502  
   503  				Context("equal to 0.4.0", func() {
   504  					It("passes a prevResult to the plugin", func() {
   505  						err := ioutil.WriteFile(cacheFile, []byte(`{
   506  							"cniVersion": "0.4.0",
   507  							"ips": [{"version": "4", "address": "10.1.2.3/24"}],
   508  							"dns": {}
   509  						}`), 0600)
   510  						Expect(err).NotTo(HaveOccurred())
   511  
   512  						_, err = cniConfig.GetNetwork(netConfig, runtimeConfig)
   513  						Expect(err).NotTo(HaveOccurred())
   514  						debug, err := noop_debug.ReadDebug(debugFilePath)
   515  						Expect(err).NotTo(HaveOccurred())
   516  						Expect(string(debug.CmdArgs.StdinData)).To(ContainSubstring("\"prevResult\":"))
   517  					})
   518  				})
   519  			})
   520  
   521  			Context("when the cached result", func() {
   522  				var cacheFile string
   523  
   524  				BeforeEach(func() {
   525  					cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig.ContainerID)
   526  					err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
   527  					Expect(err).NotTo(HaveOccurred())
   528  				})
   529  
   530  				Context("is invalid JSON", func() {
   531  					It("returns an error", func() {
   532  						err := ioutil.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0600)
   533  						Expect(err).NotTo(HaveOccurred())
   534  
   535  						result, err := cniConfig.GetNetwork(netConfig, runtimeConfig)
   536  						Expect(result).To(BeNil())
   537  						Expect(err).To(MatchError("failed to get network 'apitest' cached result: decoding version from network config: invalid character 'a' looking for beginning of value"))
   538  					})
   539  				})
   540  
   541  				Context("version doesn't match the config version", func() {
   542  					It("succeeds when the cached result can be converted", func() {
   543  						err := ioutil.WriteFile(cacheFile, []byte(`{
   544  							"cniVersion": "0.3.1",
   545  							"ips": [{"version": "4", "address": "10.1.2.3/24"}],
   546  							"dns": {}
   547  						}`), 0600)
   548  						Expect(err).NotTo(HaveOccurred())
   549  
   550  						_, err = cniConfig.GetNetwork(netConfig, runtimeConfig)
   551  						Expect(err).NotTo(HaveOccurred())
   552  					})
   553  
   554  					It("returns an error when the cached result cannot be converted", func() {
   555  						err := ioutil.WriteFile(cacheFile, []byte(`{
   556  							"cniVersion": "0.4567.0",
   557  							"ips": [{"version": "4", "address": "10.1.2.3/24"}],
   558  							"dns": {}
   559  						}`), 0600)
   560  						Expect(err).NotTo(HaveOccurred())
   561  
   562  						result, err := cniConfig.GetNetwork(netConfig, runtimeConfig)
   563  						Expect(result).To(BeNil())
   564  						Expect(err).To(MatchError("failed to get network 'apitest' cached result: unsupported CNI result version \"0.4567.0\""))
   565  					})
   566  				})
   567  			})
   568  		})
   569  
   570  		Describe("DelNetwork", func() {
   571  			It("executes the plugin with command DEL", func() {
   572  				cacheFile := resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig.ContainerID)
   573  				err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
   574  				Expect(err).NotTo(HaveOccurred())
   575  				cachedJson := `{
   576  					"cniVersion": "0.4.0",
   577  					"ips": [{"version": "4", "address": "10.1.2.3/24"}],
   578  					"dns": {}
   579  				}`
   580  				err = ioutil.WriteFile(cacheFile, []byte(cachedJson), 0600)
   581  				Expect(err).NotTo(HaveOccurred())
   582  
   583  				err = cniConfig.DelNetwork(netConfig, runtimeConfig)
   584  				Expect(err).NotTo(HaveOccurred())
   585  
   586  				debug, err := noop_debug.ReadDebug(debugFilePath)
   587  				Expect(err).NotTo(HaveOccurred())
   588  				Expect(debug.Command).To(Equal("DEL"))
   589  				Expect(string(debug.CmdArgs.StdinData)).To(ContainSubstring("\"portMappings\":"))
   590  
   591  				// Explicitly match stdin data as json after
   592  				// inserting the expected prevResult
   593  				var data, data2 map[string]interface{}
   594  				err = json.Unmarshal(expectedCmdArgs.StdinData, &data)
   595  				Expect(err).NotTo(HaveOccurred())
   596  				err = json.Unmarshal([]byte(cachedJson), &data2)
   597  				Expect(err).NotTo(HaveOccurred())
   598  				data["prevResult"] = data2
   599  				expectedStdinJson, err := json.Marshal(data)
   600  				Expect(err).NotTo(HaveOccurred())
   601  				Expect(debug.CmdArgs.StdinData).To(MatchJSON(expectedStdinJson))
   602  
   603  				debug.CmdArgs.StdinData = nil
   604  				expectedCmdArgs.StdinData = nil
   605  				Expect(debug.CmdArgs).To(Equal(expectedCmdArgs))
   606  			})
   607  
   608  			Context("when finding the plugin fails", func() {
   609  				BeforeEach(func() {
   610  					netConfig.Network.Type = "does-not-exist"
   611  				})
   612  
   613  				It("returns the error", func() {
   614  					err := cniConfig.DelNetwork(netConfig, runtimeConfig)
   615  					Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`)))
   616  				})
   617  			})
   618  
   619  			Context("when the plugin errors", func() {
   620  				BeforeEach(func() {
   621  					debug.ReportError = "plugin error: banana"
   622  					Expect(debug.WriteDebug(debugFilePath)).To(Succeed())
   623  				})
   624  				It("unmarshals and returns the error", func() {
   625  					err := cniConfig.DelNetwork(netConfig, runtimeConfig)
   626  					Expect(err).To(MatchError("plugin error: banana"))
   627  				})
   628  			})
   629  
   630  			Context("when DEL is called twice", func() {
   631  				var cacheFile string
   632  
   633  				BeforeEach(func() {
   634  					cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig.ContainerID)
   635  					err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
   636  					Expect(err).NotTo(HaveOccurred())
   637  				})
   638  
   639  				It("deletes the cached result after the first DEL", func() {
   640  					err := ioutil.WriteFile(cacheFile, []byte(`{
   641  						"cniVersion": "0.4.0",
   642  						"ips": [{"version": "4", "address": "10.1.2.3/24"}],
   643  						"dns": {}
   644  					}`), 0600)
   645  					Expect(err).NotTo(HaveOccurred())
   646  
   647  					err = cniConfig.DelNetwork(netConfig, runtimeConfig)
   648  					Expect(err).NotTo(HaveOccurred())
   649  					_, err = ioutil.ReadFile(cacheFile)
   650  					Expect(err).To(HaveOccurred())
   651  
   652  					err = cniConfig.DelNetwork(netConfig, runtimeConfig)
   653  					Expect(err).NotTo(HaveOccurred())
   654  				})
   655  			})
   656  
   657  			Context("when DEL is called with a configuration version", func() {
   658  				var cacheFile string
   659  
   660  				BeforeEach(func() {
   661  					cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig.ContainerID)
   662  					err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
   663  					Expect(err).NotTo(HaveOccurred())
   664  				})
   665  
   666  				Context("less than 0.4.0", func() {
   667  					It("does not pass a prevResult to the plugin", func() {
   668  						err := ioutil.WriteFile(cacheFile, []byte(`{
   669  							"cniVersion": "0.3.1",
   670  							"ips": [{"version": "4", "address": "10.1.2.3/24"}],
   671  							"dns": {}
   672  						}`), 0600)
   673  						Expect(err).NotTo(HaveOccurred())
   674  
   675  						// Generate plugin config with older version
   676  						pluginConfig = `{
   677  							"type": "noop",
   678  							"name": "apitest",
   679  							"cniVersion": "0.3.1"
   680  						}`
   681  						netConfig, err = libcni.ConfFromBytes([]byte(pluginConfig))
   682  						Expect(err).NotTo(HaveOccurred())
   683  						err = cniConfig.DelNetwork(netConfig, runtimeConfig)
   684  						Expect(err).NotTo(HaveOccurred())
   685  
   686  						debug, err := noop_debug.ReadDebug(debugFilePath)
   687  						Expect(err).NotTo(HaveOccurred())
   688  						Expect(debug.Command).To(Equal("DEL"))
   689  						Expect(string(debug.CmdArgs.StdinData)).NotTo(ContainSubstring("\"prevResult\":"))
   690  					})
   691  				})
   692  
   693  				Context("equal to 0.4.0", func() {
   694  					It("passes a prevResult to the plugin", func() {
   695  						err := ioutil.WriteFile(cacheFile, []byte(`{
   696  							"cniVersion": "0.4.0",
   697  							"ips": [{"version": "4", "address": "10.1.2.3/24"}],
   698  							"dns": {}
   699  						}`), 0600)
   700  						Expect(err).NotTo(HaveOccurred())
   701  
   702  						err = cniConfig.DelNetwork(netConfig, runtimeConfig)
   703  						Expect(err).NotTo(HaveOccurred())
   704  
   705  						debug, err := noop_debug.ReadDebug(debugFilePath)
   706  						Expect(err).NotTo(HaveOccurred())
   707  						Expect(debug.Command).To(Equal("DEL"))
   708  						Expect(string(debug.CmdArgs.StdinData)).To(ContainSubstring("\"prevResult\":"))
   709  					})
   710  				})
   711  			})
   712  
   713  			Context("when the cached result", func() {
   714  				var cacheFile string
   715  
   716  				BeforeEach(func() {
   717  					cacheFile = resultCacheFilePath(cacheDirPath, netConfig.Network.Name, runtimeConfig.ContainerID)
   718  					err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
   719  					Expect(err).NotTo(HaveOccurred())
   720  				})
   721  
   722  				Context("is invalid JSON", func() {
   723  					It("returns an error", func() {
   724  						err := ioutil.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0600)
   725  						Expect(err).NotTo(HaveOccurred())
   726  
   727  						err = cniConfig.DelNetwork(netConfig, runtimeConfig)
   728  						Expect(err).To(MatchError("failed to get network 'apitest' cached result: decoding version from network config: invalid character 'a' looking for beginning of value"))
   729  					})
   730  				})
   731  
   732  				Context("version doesn't match the config version", func() {
   733  					It("succeeds when the cached result can be converted", func() {
   734  						err := ioutil.WriteFile(cacheFile, []byte(`{
   735  							"cniVersion": "0.3.1",
   736  							"ips": [{"version": "4", "address": "10.1.2.3/24"}],
   737  							"dns": {}
   738  						}`), 0600)
   739  						Expect(err).NotTo(HaveOccurred())
   740  
   741  						err = cniConfig.DelNetwork(netConfig, runtimeConfig)
   742  						Expect(err).NotTo(HaveOccurred())
   743  					})
   744  
   745  					It("returns an error when the cached result cannot be converted", func() {
   746  						err := ioutil.WriteFile(cacheFile, []byte(`{
   747  							"cniVersion": "0.4567.0",
   748  							"ips": [{"version": "4", "address": "10.1.2.3/24"}],
   749  							"dns": {}
   750  						}`), 0600)
   751  						Expect(err).NotTo(HaveOccurred())
   752  
   753  						err = cniConfig.DelNetwork(netConfig, runtimeConfig)
   754  						Expect(err).To(MatchError("failed to get network 'apitest' cached result: unsupported CNI result version \"0.4567.0\""))
   755  					})
   756  				})
   757  			})
   758  		})
   759  
   760  		Describe("GetVersionInfo", func() {
   761  			It("executes the plugin with the command VERSION", func() {
   762  				versionInfo, err := cniConfig.GetVersionInfo("noop")
   763  				Expect(err).NotTo(HaveOccurred())
   764  
   765  				Expect(versionInfo).NotTo(BeNil())
   766  				Expect(versionInfo.SupportedVersions()).To(Equal([]string{
   767  					"0.-42.0", "0.1.0", "0.2.0", "0.3.0", "0.3.1", "0.4.0",
   768  				}))
   769  			})
   770  
   771  			Context("when finding the plugin fails", func() {
   772  				It("returns the error", func() {
   773  					_, err := cniConfig.GetVersionInfo("does-not-exist")
   774  					Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`)))
   775  				})
   776  			})
   777  		})
   778  	})
   779  
   780  	Describe("Invoking a plugin list", func() {
   781  		var (
   782  			plugins       []pluginInfo
   783  			cniBinPath    string
   784  			cniConfig     *libcni.CNIConfig
   785  			netConfigList *libcni.NetworkConfigList
   786  			runtimeConfig *libcni.RuntimeConf
   787  
   788  			expectedCmdArgs skel.CmdArgs
   789  		)
   790  
   791  		BeforeEach(func() {
   792  			var err error
   793  
   794  			capabilityArgs := map[string]interface{}{
   795  				"portMappings": []portMapping{
   796  					{HostPort: 8080, ContainerPort: 80, Protocol: "tcp"},
   797  				},
   798  				"otherCapability": 33,
   799  			}
   800  
   801  			cniBinPath = filepath.Dir(pluginPaths["noop"])
   802  			cniConfig = libcni.NewCNIConfig([]string{cniBinPath}, nil)
   803  			runtimeConfig = &libcni.RuntimeConf{
   804  				ContainerID:    "some-container-id",
   805  				NetNS:          "/some/netns/path",
   806  				IfName:         "some-eth0",
   807  				Args:           [][2]string{{"FOO", "BAR"}},
   808  				CapabilityArgs: capabilityArgs,
   809  				CacheDir:       cacheDirPath,
   810  			}
   811  
   812  			expectedCmdArgs = skel.CmdArgs{
   813  				ContainerID: runtimeConfig.ContainerID,
   814  				Netns:       runtimeConfig.NetNS,
   815  				IfName:      runtimeConfig.IfName,
   816  				Args:        "FOO=BAR",
   817  				Path:        cniBinPath,
   818  			}
   819  
   820  			rc := map[string]interface{}{
   821  				"containerId": runtimeConfig.ContainerID,
   822  				"netNs":       runtimeConfig.NetNS,
   823  				"ifName":      runtimeConfig.IfName,
   824  				"args": map[string]string{
   825  					"FOO": "BAR",
   826  				},
   827  				"portMappings":    capabilityArgs["portMappings"],
   828  				"otherCapability": capabilityArgs["otherCapability"],
   829  			}
   830  
   831  			ipResult := fmt.Sprintf(`{"cniVersion": "%s", "dns":{},"ips":[{"version": "4", "address": "10.1.2.3/24"}]}`, current.ImplementedSpecVersion)
   832  			plugins = make([]pluginInfo, 3, 3)
   833  			plugins[0] = newPluginInfo("some-value", "", true, ipResult, rc, []string{"portMappings", "otherCapability"})
   834  			plugins[1] = newPluginInfo("some-other-value", ipResult, true, "PASSTHROUGH", rc, []string{"otherCapability"})
   835  			plugins[2] = newPluginInfo("yet-another-value", ipResult, true, "INJECT-DNS", rc, []string{})
   836  
   837  			configList := []byte(fmt.Sprintf(`{
   838    "name": "some-list",
   839    "cniVersion": "%s",
   840    "plugins": [
   841      %s,
   842      %s,
   843      %s
   844    ]
   845  }`, current.ImplementedSpecVersion, plugins[0].config, plugins[1].config, plugins[2].config))
   846  
   847  			netConfigList, err = libcni.ConfListFromBytes(configList)
   848  			Expect(err).NotTo(HaveOccurred())
   849  		})
   850  
   851  		AfterEach(func() {
   852  			for _, p := range plugins {
   853  				Expect(os.RemoveAll(p.debugFilePath)).To(Succeed())
   854  			}
   855  		})
   856  
   857  		Describe("AddNetworkList", func() {
   858  			It("executes all plugins with command ADD and returns an intermediate result", func() {
   859  				r, err := cniConfig.AddNetworkList(netConfigList, runtimeConfig)
   860  				Expect(err).NotTo(HaveOccurred())
   861  
   862  				result, err := current.GetResult(r)
   863  				Expect(err).NotTo(HaveOccurred())
   864  
   865  				Expect(result).To(Equal(&current.Result{
   866  					CNIVersion: current.ImplementedSpecVersion,
   867  					// IP4 added by first plugin
   868  					IPs: []*current.IPConfig{
   869  						{
   870  							Version: "4",
   871  							Address: net.IPNet{
   872  								IP:   net.ParseIP("10.1.2.3"),
   873  								Mask: net.IPv4Mask(255, 255, 255, 0),
   874  							},
   875  						},
   876  					},
   877  					// DNS injected by last plugin
   878  					DNS: types.DNS{
   879  						Nameservers: []string{"1.2.3.4"},
   880  					},
   881  				}))
   882  
   883  				for i := 0; i < len(plugins); i++ {
   884  					debug, err := noop_debug.ReadDebug(plugins[i].debugFilePath)
   885  					Expect(err).NotTo(HaveOccurred())
   886  					Expect(debug.Command).To(Equal("ADD"))
   887  
   888  					// Must explicitly match JSON due to dict element ordering
   889  					Expect(debug.CmdArgs.StdinData).To(MatchJSON(plugins[i].stdinData))
   890  					debug.CmdArgs.StdinData = nil
   891  					Expect(debug.CmdArgs).To(Equal(expectedCmdArgs))
   892  				}
   893  			})
   894  
   895  			It("writes the correct cached result", func() {
   896  				r, err := cniConfig.AddNetworkList(netConfigList, runtimeConfig)
   897  				Expect(err).NotTo(HaveOccurred())
   898  
   899  				result, err := current.GetResult(r)
   900  				Expect(err).NotTo(HaveOccurred())
   901  
   902  				// Ensure the cached result matches the returned one
   903  				cacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig.ContainerID)
   904  				_, err = os.Stat(cacheFile)
   905  				Expect(err).NotTo(HaveOccurred())
   906  				cachedData, err := ioutil.ReadFile(cacheFile)
   907  				Expect(err).NotTo(HaveOccurred())
   908  				returnedData, err := json.Marshal(result)
   909  				Expect(err).NotTo(HaveOccurred())
   910  				Expect(cachedData).To(MatchJSON(returnedData))
   911  			})
   912  
   913  			Context("when finding the plugin fails", func() {
   914  				BeforeEach(func() {
   915  					netConfigList.Plugins[1].Network.Type = "does-not-exist"
   916  				})
   917  
   918  				It("returns the error", func() {
   919  					_, err := cniConfig.AddNetworkList(netConfigList, runtimeConfig)
   920  					Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`)))
   921  				})
   922  			})
   923  
   924  			Context("when the second plugin errors", func() {
   925  				BeforeEach(func() {
   926  					plugins[1].debug.ReportError = "plugin error: banana"
   927  					Expect(plugins[1].debug.WriteDebug(plugins[1].debugFilePath)).To(Succeed())
   928  				})
   929  				It("unmarshals and returns the error", func() {
   930  					result, err := cniConfig.AddNetworkList(netConfigList, runtimeConfig)
   931  					Expect(result).To(BeNil())
   932  					Expect(err).To(MatchError("plugin error: banana"))
   933  				})
   934  			})
   935  
   936  			Context("when the result cache directory cannot be accessed", func() {
   937  				It("returns an error", func() {
   938  					// Make the results directory inaccessble by making it a
   939  					// file instead of a directory
   940  					tmpPath := filepath.Join(cacheDirPath, "results")
   941  					err := ioutil.WriteFile(tmpPath, []byte("afdsasdfasdf"), 0600)
   942  					Expect(err).NotTo(HaveOccurred())
   943  
   944  					result, err := cniConfig.AddNetworkList(netConfigList, runtimeConfig)
   945  					Expect(result).To(BeNil())
   946  					Expect(err).To(HaveOccurred())
   947  				})
   948  			})
   949  		})
   950  
   951  		Describe("GetNetworkList", func() {
   952  			It("executes all plugins with command GET and returns an intermediate result", func() {
   953  				r, err := cniConfig.GetNetworkList(netConfigList, runtimeConfig)
   954  				Expect(err).NotTo(HaveOccurred())
   955  
   956  				result, err := current.GetResult(r)
   957  				Expect(err).NotTo(HaveOccurred())
   958  
   959  				Expect(result).To(Equal(&current.Result{
   960  					CNIVersion: current.ImplementedSpecVersion,
   961  					// IP4 added by first plugin
   962  					IPs: []*current.IPConfig{
   963  						{
   964  							Version: "4",
   965  							Address: net.IPNet{
   966  								IP:   net.ParseIP("10.1.2.3"),
   967  								Mask: net.IPv4Mask(255, 255, 255, 0),
   968  							},
   969  						},
   970  					},
   971  					// DNS injected by last plugin
   972  					DNS: types.DNS{
   973  						Nameservers: []string{"1.2.3.4"},
   974  					},
   975  				}))
   976  
   977  				for i := 0; i < len(plugins); i++ {
   978  					debug, err := noop_debug.ReadDebug(plugins[i].debugFilePath)
   979  					Expect(err).NotTo(HaveOccurred())
   980  					Expect(debug.Command).To(Equal("GET"))
   981  
   982  					// Must explicitly match JSON due to dict element ordering
   983  					Expect(debug.CmdArgs.StdinData).To(MatchJSON(plugins[i].stdinData))
   984  					debug.CmdArgs.StdinData = nil
   985  					Expect(debug.CmdArgs).To(Equal(expectedCmdArgs))
   986  				}
   987  			})
   988  
   989  			Context("when the configuration version", func() {
   990  				var cacheFile string
   991  
   992  				BeforeEach(func() {
   993  					cacheFile = resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig.ContainerID)
   994  					err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
   995  					Expect(err).NotTo(HaveOccurred())
   996  				})
   997  
   998  				Context("is 0.4.0", func() {
   999  					It("passes a cached result to the first plugin", func() {
  1000  						cachedJson := `{
  1001  							"cniVersion": "0.4.0",
  1002  							"ips": [{"version": "4", "address": "10.1.2.3/24"}],
  1003  							"dns": {}
  1004  						}`
  1005  						err := ioutil.WriteFile(cacheFile, []byte(cachedJson), 0600)
  1006  						Expect(err).NotTo(HaveOccurred())
  1007  
  1008  						_, err = cniConfig.GetNetworkList(netConfigList, runtimeConfig)
  1009  						Expect(err).NotTo(HaveOccurred())
  1010  
  1011  						// Match the first plugin's stdin config to the cached result JSON
  1012  						debug, err := noop_debug.ReadDebug(plugins[0].debugFilePath)
  1013  						Expect(err).NotTo(HaveOccurred())
  1014  
  1015  						var data map[string]interface{}
  1016  						err = json.Unmarshal(debug.CmdArgs.StdinData, &data)
  1017  						Expect(err).NotTo(HaveOccurred())
  1018  						stdinPrevResult, err := json.Marshal(data["prevResult"])
  1019  						Expect(err).NotTo(HaveOccurred())
  1020  						Expect(stdinPrevResult).To(MatchJSON(cachedJson))
  1021  					})
  1022  				})
  1023  
  1024  				Context("is less than 0.4.0", func() {
  1025  					It("fails as GET is not supported before 0.4.0", func() {
  1026  						// Set an older CNI version
  1027  						confList := make(map[string]interface{})
  1028  						err := json.Unmarshal(netConfigList.Bytes, &confList)
  1029  						Expect(err).NotTo(HaveOccurred())
  1030  						confList["cniVersion"] = "0.3.1"
  1031  						newBytes, err := json.Marshal(confList)
  1032  						Expect(err).NotTo(HaveOccurred())
  1033  
  1034  						netConfigList, err = libcni.ConfListFromBytes(newBytes)
  1035  						Expect(err).NotTo(HaveOccurred())
  1036  						_, err = cniConfig.GetNetworkList(netConfigList, runtimeConfig)
  1037  						Expect(err).To(MatchError("configuration version \"0.3.1\" does not support the GET command"))
  1038  					})
  1039  				})
  1040  			})
  1041  
  1042  			Context("when finding the plugin fails", func() {
  1043  				BeforeEach(func() {
  1044  					netConfigList.Plugins[1].Network.Type = "does-not-exist"
  1045  				})
  1046  
  1047  				It("returns the error", func() {
  1048  					_, err := cniConfig.GetNetworkList(netConfigList, runtimeConfig)
  1049  					Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`)))
  1050  				})
  1051  			})
  1052  
  1053  			Context("when the second plugin errors", func() {
  1054  				BeforeEach(func() {
  1055  					plugins[1].debug.ReportError = "plugin error: banana"
  1056  					Expect(plugins[1].debug.WriteDebug(plugins[1].debugFilePath)).To(Succeed())
  1057  				})
  1058  				It("unmarshals and returns the error", func() {
  1059  					result, err := cniConfig.GetNetworkList(netConfigList, runtimeConfig)
  1060  					Expect(result).To(BeNil())
  1061  					Expect(err).To(MatchError("plugin error: banana"))
  1062  				})
  1063  			})
  1064  
  1065  			Context("when the cached result is invalid", func() {
  1066  				It("returns an error", func() {
  1067  					cacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig.ContainerID)
  1068  					err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
  1069  					Expect(err).NotTo(HaveOccurred())
  1070  					err = ioutil.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0600)
  1071  					Expect(err).NotTo(HaveOccurred())
  1072  
  1073  					result, err := cniConfig.GetNetworkList(netConfigList, runtimeConfig)
  1074  					Expect(result).To(BeNil())
  1075  					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"))
  1076  				})
  1077  			})
  1078  		})
  1079  
  1080  		Describe("DelNetworkList", func() {
  1081  			It("executes all the plugins in reverse order with command DEL", func() {
  1082  				err := cniConfig.DelNetworkList(netConfigList, runtimeConfig)
  1083  				Expect(err).NotTo(HaveOccurred())
  1084  
  1085  				for i := 0; i < len(plugins); i++ {
  1086  					debug, err := noop_debug.ReadDebug(plugins[i].debugFilePath)
  1087  					Expect(err).NotTo(HaveOccurred())
  1088  					Expect(debug.Command).To(Equal("DEL"))
  1089  
  1090  					// Must explicitly match JSON due to dict element ordering
  1091  					Expect(debug.CmdArgs.StdinData).To(MatchJSON(plugins[i].stdinData))
  1092  					debug.CmdArgs.StdinData = nil
  1093  					Expect(debug.CmdArgs).To(Equal(expectedCmdArgs))
  1094  				}
  1095  			})
  1096  
  1097  			Context("when the configuration version", func() {
  1098  				var cacheFile string
  1099  
  1100  				BeforeEach(func() {
  1101  					cacheFile = resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig.ContainerID)
  1102  					err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
  1103  					Expect(err).NotTo(HaveOccurred())
  1104  				})
  1105  
  1106  				Context("is 0.4.0", func() {
  1107  					It("passes a cached result to the first plugin", func() {
  1108  						cachedJson := `{
  1109  							"cniVersion": "0.4.0",
  1110  							"ips": [{"version": "4", "address": "10.1.2.3/24"}],
  1111  							"dns": {}
  1112  						}`
  1113  						err := ioutil.WriteFile(cacheFile, []byte(cachedJson), 0600)
  1114  						Expect(err).NotTo(HaveOccurred())
  1115  
  1116  						err = cniConfig.DelNetworkList(netConfigList, runtimeConfig)
  1117  						Expect(err).NotTo(HaveOccurred())
  1118  
  1119  						// Match the first plugin's stdin config to the cached result JSON
  1120  						debug, err := noop_debug.ReadDebug(plugins[0].debugFilePath)
  1121  						Expect(err).NotTo(HaveOccurred())
  1122  
  1123  						var data map[string]interface{}
  1124  						err = json.Unmarshal(debug.CmdArgs.StdinData, &data)
  1125  						Expect(err).NotTo(HaveOccurred())
  1126  						stdinPrevResult, err := json.Marshal(data["prevResult"])
  1127  						Expect(err).NotTo(HaveOccurred())
  1128  						Expect(stdinPrevResult).To(MatchJSON(cachedJson))
  1129  					})
  1130  				})
  1131  
  1132  				Context("is less than 0.4.0", func() {
  1133  					It("does not pass a cached result to the first plugin", func() {
  1134  						err := ioutil.WriteFile(cacheFile, []byte(`{
  1135  							"cniVersion": "0.3.1",
  1136  							"ips": [{"version": "4", "address": "10.1.2.3/24"}],
  1137  							"dns": {}
  1138  						}`), 0600)
  1139  						Expect(err).NotTo(HaveOccurred())
  1140  
  1141  						// Set an older CNI version
  1142  						confList := make(map[string]interface{})
  1143  						err = json.Unmarshal(netConfigList.Bytes, &confList)
  1144  						Expect(err).NotTo(HaveOccurred())
  1145  						confList["cniVersion"] = "0.3.1"
  1146  						newBytes, err := json.Marshal(confList)
  1147  						Expect(err).NotTo(HaveOccurred())
  1148  
  1149  						netConfigList, err = libcni.ConfListFromBytes(newBytes)
  1150  						Expect(err).NotTo(HaveOccurred())
  1151  						err = cniConfig.DelNetworkList(netConfigList, runtimeConfig)
  1152  						Expect(err).NotTo(HaveOccurred())
  1153  
  1154  						// Make sure first plugin does not receive a prevResult
  1155  						debug, err := noop_debug.ReadDebug(plugins[0].debugFilePath)
  1156  						Expect(err).NotTo(HaveOccurred())
  1157  						Expect(string(debug.CmdArgs.StdinData)).NotTo(ContainSubstring("\"prevResult\":"))
  1158  					})
  1159  				})
  1160  			})
  1161  
  1162  			Context("when finding the plugin fails", func() {
  1163  				BeforeEach(func() {
  1164  					netConfigList.Plugins[1].Network.Type = "does-not-exist"
  1165  				})
  1166  
  1167  				It("returns the error", func() {
  1168  					err := cniConfig.DelNetworkList(netConfigList, runtimeConfig)
  1169  					Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`)))
  1170  				})
  1171  			})
  1172  
  1173  			Context("when the plugin errors", func() {
  1174  				BeforeEach(func() {
  1175  					plugins[1].debug.ReportError = "plugin error: banana"
  1176  					Expect(plugins[1].debug.WriteDebug(plugins[1].debugFilePath)).To(Succeed())
  1177  				})
  1178  				It("unmarshals and returns the error", func() {
  1179  					err := cniConfig.DelNetworkList(netConfigList, runtimeConfig)
  1180  					Expect(err).To(MatchError("plugin error: banana"))
  1181  				})
  1182  			})
  1183  
  1184  			Context("when the cached result is invalid", func() {
  1185  				It("returns an error", func() {
  1186  					cacheFile := resultCacheFilePath(cacheDirPath, netConfigList.Name, runtimeConfig.ContainerID)
  1187  					err := os.MkdirAll(filepath.Dir(cacheFile), 0700)
  1188  					Expect(err).NotTo(HaveOccurred())
  1189  					err = ioutil.WriteFile(cacheFile, []byte("adfadsfasdfasfdsafaf"), 0600)
  1190  					Expect(err).NotTo(HaveOccurred())
  1191  
  1192  					err = cniConfig.DelNetworkList(netConfigList, runtimeConfig)
  1193  					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"))
  1194  				})
  1195  			})
  1196  		})
  1197  
  1198  	})
  1199  })