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(¤t.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(¤t.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 })