github.com/john-lin/cni@v0.6.0-rc1.0.20170712150331-b69e640cc0e2/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": "%s"`, 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"] = "0.3.1" 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 var _ = Describe("Invoking plugins", func() { 120 Describe("Capabilities", func() { 121 var ( 122 debugFilePath string 123 debug *noop_debug.Debug 124 pluginConfig []byte 125 cniConfig libcni.CNIConfig 126 runtimeConfig *libcni.RuntimeConf 127 netConfig *libcni.NetworkConfig 128 ) 129 130 BeforeEach(func() { 131 debugFile, err := ioutil.TempFile("", "cni_debug") 132 Expect(err).NotTo(HaveOccurred()) 133 Expect(debugFile.Close()).To(Succeed()) 134 debugFilePath = debugFile.Name() 135 136 debug = &noop_debug.Debug{} 137 Expect(debug.WriteDebug(debugFilePath)).To(Succeed()) 138 139 pluginConfig = []byte(`{ "type": "noop", "cniVersion": "0.3.1", "capabilities": { "portMappings": true, "somethingElse": true, "noCapability": false } }`) 140 netConfig, err = libcni.ConfFromBytes(pluginConfig) 141 Expect(err).NotTo(HaveOccurred()) 142 143 cniConfig = libcni.CNIConfig{Path: []string{filepath.Dir(pluginPaths["noop"])}} 144 145 runtimeConfig = &libcni.RuntimeConf{ 146 ContainerID: "some-container-id", 147 NetNS: "/some/netns/path", 148 IfName: "some-eth0", 149 Args: [][2]string{{"DEBUG", debugFilePath}}, 150 CapabilityArgs: map[string]interface{}{ 151 "portMappings": []portMapping{ 152 {HostPort: 8080, ContainerPort: 80, Protocol: "tcp"}, 153 }, 154 "somethingElse": []string{"foobar", "baz"}, 155 "noCapability": true, 156 "notAdded": []bool{true, false}, 157 }, 158 } 159 }) 160 161 AfterEach(func() { 162 Expect(os.RemoveAll(debugFilePath)).To(Succeed()) 163 }) 164 165 It("adds correct runtime config for capabilities to stdin", func() { 166 _, err := cniConfig.AddNetwork(netConfig, runtimeConfig) 167 Expect(err).NotTo(HaveOccurred()) 168 169 debug, err = noop_debug.ReadDebug(debugFilePath) 170 Expect(err).NotTo(HaveOccurred()) 171 Expect(debug.Command).To(Equal("ADD")) 172 173 conf := make(map[string]interface{}) 174 err = json.Unmarshal(debug.CmdArgs.StdinData, &conf) 175 Expect(err).NotTo(HaveOccurred()) 176 177 // We expect runtimeConfig keys only for portMappings and somethingElse 178 rawRc := conf["runtimeConfig"] 179 rc, ok := rawRc.(map[string]interface{}) 180 Expect(ok).To(Equal(true)) 181 expectedKeys := []string{"portMappings", "somethingElse"} 182 Expect(len(rc)).To(Equal(len(expectedKeys))) 183 for _, key := range expectedKeys { 184 _, ok := rc[key] 185 Expect(ok).To(Equal(true)) 186 } 187 }) 188 189 It("adds no runtimeConfig when the plugin advertises no used capabilities", func() { 190 // Replace CapabilityArgs with ones we know the plugin 191 // doesn't support 192 runtimeConfig.CapabilityArgs = map[string]interface{}{ 193 "portMappings22": []portMapping{ 194 {HostPort: 8080, ContainerPort: 80, Protocol: "tcp"}, 195 }, 196 "somethingElse22": []string{"foobar", "baz"}, 197 } 198 199 _, err := cniConfig.AddNetwork(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 // No intersection of plugin capabilities and CapabilityArgs, 211 // so plugin should not receive a "runtimeConfig" key 212 _, ok := conf["runtimeConfig"] 213 Expect(ok).Should(BeFalse()) 214 }) 215 }) 216 217 Describe("Invoking a single plugin", func() { 218 var ( 219 debugFilePath string 220 debug *noop_debug.Debug 221 cniBinPath string 222 pluginConfig string 223 cniConfig libcni.CNIConfig 224 netConfig *libcni.NetworkConfig 225 runtimeConfig *libcni.RuntimeConf 226 227 expectedCmdArgs skel.CmdArgs 228 ) 229 230 BeforeEach(func() { 231 debugFile, err := ioutil.TempFile("", "cni_debug") 232 Expect(err).NotTo(HaveOccurred()) 233 Expect(debugFile.Close()).To(Succeed()) 234 debugFilePath = debugFile.Name() 235 236 debug = &noop_debug.Debug{ 237 ReportResult: `{ "ips": [{ "version": "4", "address": "10.1.2.3/24" }], "dns": {} }`, 238 } 239 Expect(debug.WriteDebug(debugFilePath)).To(Succeed()) 240 241 portMappings := []portMapping{ 242 {HostPort: 8080, ContainerPort: 80, Protocol: "tcp"}, 243 } 244 245 cniBinPath = filepath.Dir(pluginPaths["noop"]) 246 pluginConfig = `{ "type": "noop", "some-key": "some-value", "cniVersion": "0.3.1", "capabilities": { "portMappings": true } }` 247 cniConfig = libcni.CNIConfig{Path: []string{cniBinPath}} 248 netConfig = &libcni.NetworkConfig{ 249 Network: &types.NetConf{ 250 Type: "noop", 251 Capabilities: map[string]bool{ 252 "portMappings": true, 253 }, 254 }, 255 Bytes: []byte(pluginConfig), 256 } 257 runtimeConfig = &libcni.RuntimeConf{ 258 ContainerID: "some-container-id", 259 NetNS: "/some/netns/path", 260 IfName: "some-eth0", 261 Args: [][2]string{{"DEBUG", debugFilePath}}, 262 CapabilityArgs: map[string]interface{}{ 263 "portMappings": portMappings, 264 }, 265 } 266 267 // inject runtime args into the expected plugin config 268 conf := make(map[string]interface{}) 269 err = json.Unmarshal([]byte(pluginConfig), &conf) 270 Expect(err).NotTo(HaveOccurred()) 271 conf["runtimeConfig"] = map[string]interface{}{ 272 "portMappings": portMappings, 273 } 274 newBytes, err := json.Marshal(conf) 275 Expect(err).NotTo(HaveOccurred()) 276 277 expectedCmdArgs = skel.CmdArgs{ 278 ContainerID: "some-container-id", 279 Netns: "/some/netns/path", 280 IfName: "some-eth0", 281 Args: "DEBUG=" + debugFilePath, 282 Path: cniBinPath, 283 StdinData: newBytes, 284 } 285 }) 286 287 AfterEach(func() { 288 Expect(os.RemoveAll(debugFilePath)).To(Succeed()) 289 }) 290 291 Describe("AddNetwork", func() { 292 It("executes the plugin with command ADD", func() { 293 r, err := cniConfig.AddNetwork(netConfig, runtimeConfig) 294 Expect(err).NotTo(HaveOccurred()) 295 296 result, err := current.GetResult(r) 297 Expect(err).NotTo(HaveOccurred()) 298 299 Expect(result).To(Equal(¤t.Result{ 300 CNIVersion: current.ImplementedSpecVersion, 301 IPs: []*current.IPConfig{ 302 { 303 Version: "4", 304 Address: net.IPNet{ 305 IP: net.ParseIP("10.1.2.3"), 306 Mask: net.IPv4Mask(255, 255, 255, 0), 307 }, 308 }, 309 }, 310 })) 311 312 debug, err := noop_debug.ReadDebug(debugFilePath) 313 Expect(err).NotTo(HaveOccurred()) 314 Expect(debug.Command).To(Equal("ADD")) 315 Expect(debug.CmdArgs).To(Equal(expectedCmdArgs)) 316 Expect(string(debug.CmdArgs.StdinData)).To(ContainSubstring("\"portMappings\":")) 317 }) 318 319 Context("when finding the plugin fails", func() { 320 BeforeEach(func() { 321 netConfig.Network.Type = "does-not-exist" 322 }) 323 324 It("returns the error", func() { 325 _, err := cniConfig.AddNetwork(netConfig, runtimeConfig) 326 Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`))) 327 }) 328 }) 329 330 Context("when the plugin errors", func() { 331 BeforeEach(func() { 332 debug.ReportError = "plugin error: banana" 333 Expect(debug.WriteDebug(debugFilePath)).To(Succeed()) 334 }) 335 It("unmarshals and returns the error", func() { 336 result, err := cniConfig.AddNetwork(netConfig, runtimeConfig) 337 Expect(result).To(BeNil()) 338 Expect(err).To(MatchError("plugin error: banana")) 339 }) 340 }) 341 }) 342 343 Describe("DelNetwork", func() { 344 It("executes the plugin with command DEL", func() { 345 err := cniConfig.DelNetwork(netConfig, runtimeConfig) 346 Expect(err).NotTo(HaveOccurred()) 347 348 debug, err := noop_debug.ReadDebug(debugFilePath) 349 Expect(err).NotTo(HaveOccurred()) 350 Expect(debug.Command).To(Equal("DEL")) 351 Expect(debug.CmdArgs).To(Equal(expectedCmdArgs)) 352 Expect(string(debug.CmdArgs.StdinData)).To(ContainSubstring("\"portMappings\":")) 353 }) 354 355 Context("when finding the plugin fails", func() { 356 BeforeEach(func() { 357 netConfig.Network.Type = "does-not-exist" 358 }) 359 360 It("returns the error", func() { 361 err := cniConfig.DelNetwork(netConfig, runtimeConfig) 362 Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`))) 363 }) 364 }) 365 366 Context("when the plugin errors", func() { 367 BeforeEach(func() { 368 debug.ReportError = "plugin error: banana" 369 Expect(debug.WriteDebug(debugFilePath)).To(Succeed()) 370 }) 371 It("unmarshals and returns the error", func() { 372 err := cniConfig.DelNetwork(netConfig, runtimeConfig) 373 Expect(err).To(MatchError("plugin error: banana")) 374 }) 375 }) 376 }) 377 378 Describe("GetVersionInfo", func() { 379 It("executes the plugin with the command VERSION", func() { 380 versionInfo, err := cniConfig.GetVersionInfo("noop") 381 Expect(err).NotTo(HaveOccurred()) 382 383 Expect(versionInfo).NotTo(BeNil()) 384 Expect(versionInfo.SupportedVersions()).To(Equal([]string{ 385 "0.-42.0", "0.1.0", "0.2.0", "0.3.0", "0.3.1", 386 })) 387 }) 388 389 Context("when finding the plugin fails", func() { 390 It("returns the error", func() { 391 _, err := cniConfig.GetVersionInfo("does-not-exist") 392 Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`))) 393 }) 394 }) 395 }) 396 }) 397 398 Describe("Invoking a plugin list", func() { 399 var ( 400 plugins []pluginInfo 401 cniBinPath string 402 cniConfig libcni.CNIConfig 403 netConfigList *libcni.NetworkConfigList 404 runtimeConfig *libcni.RuntimeConf 405 406 expectedCmdArgs skel.CmdArgs 407 ) 408 409 BeforeEach(func() { 410 var err error 411 412 capabilityArgs := map[string]interface{}{ 413 "portMappings": []portMapping{ 414 {HostPort: 8080, ContainerPort: 80, Protocol: "tcp"}, 415 }, 416 "otherCapability": 33, 417 } 418 419 cniBinPath = filepath.Dir(pluginPaths["noop"]) 420 cniConfig = libcni.CNIConfig{Path: []string{cniBinPath}} 421 runtimeConfig = &libcni.RuntimeConf{ 422 ContainerID: "some-container-id", 423 NetNS: "/some/netns/path", 424 IfName: "some-eth0", 425 Args: [][2]string{{"FOO", "BAR"}}, 426 CapabilityArgs: capabilityArgs, 427 } 428 429 expectedCmdArgs = skel.CmdArgs{ 430 ContainerID: runtimeConfig.ContainerID, 431 Netns: runtimeConfig.NetNS, 432 IfName: runtimeConfig.IfName, 433 Args: "FOO=BAR", 434 Path: cniBinPath, 435 } 436 437 rc := map[string]interface{}{ 438 "containerId": runtimeConfig.ContainerID, 439 "netNs": runtimeConfig.NetNS, 440 "ifName": runtimeConfig.IfName, 441 "args": map[string]string{ 442 "FOO": "BAR", 443 }, 444 "portMappings": capabilityArgs["portMappings"], 445 "otherCapability": capabilityArgs["otherCapability"], 446 } 447 448 ipResult := `{"dns":{},"ips":[{"version": "4", "address": "10.1.2.3/24"}]}` 449 plugins = make([]pluginInfo, 3, 3) 450 plugins[0] = newPluginInfo("some-value", "", true, ipResult, rc, []string{"portMappings", "otherCapability"}) 451 plugins[1] = newPluginInfo("some-other-value", ipResult, true, "PASSTHROUGH", rc, []string{"otherCapability"}) 452 plugins[2] = newPluginInfo("yet-another-value", ipResult, true, "INJECT-DNS", rc, []string{}) 453 454 configList := []byte(fmt.Sprintf(`{ 455 "name": "some-list", 456 "cniVersion": "0.3.1", 457 "plugins": [ 458 %s, 459 %s, 460 %s 461 ] 462 }`, plugins[0].config, plugins[1].config, plugins[2].config)) 463 464 netConfigList, err = libcni.ConfListFromBytes(configList) 465 Expect(err).NotTo(HaveOccurred()) 466 }) 467 468 AfterEach(func() { 469 for _, p := range plugins { 470 Expect(os.RemoveAll(p.debugFilePath)).To(Succeed()) 471 } 472 }) 473 474 Describe("AddNetworkList", func() { 475 It("executes all plugins with command ADD and returns an intermediate result", func() { 476 r, err := cniConfig.AddNetworkList(netConfigList, runtimeConfig) 477 Expect(err).NotTo(HaveOccurred()) 478 479 result, err := current.GetResult(r) 480 Expect(err).NotTo(HaveOccurred()) 481 482 Expect(result).To(Equal(¤t.Result{ 483 CNIVersion: current.ImplementedSpecVersion, 484 // IP4 added by first plugin 485 IPs: []*current.IPConfig{ 486 { 487 Version: "4", 488 Address: net.IPNet{ 489 IP: net.ParseIP("10.1.2.3"), 490 Mask: net.IPv4Mask(255, 255, 255, 0), 491 }, 492 }, 493 }, 494 // DNS injected by last plugin 495 DNS: types.DNS{ 496 Nameservers: []string{"1.2.3.4"}, 497 }, 498 })) 499 500 for i := 0; i < len(plugins); i++ { 501 debug, err := noop_debug.ReadDebug(plugins[i].debugFilePath) 502 Expect(err).NotTo(HaveOccurred()) 503 Expect(debug.Command).To(Equal("ADD")) 504 505 // Must explicitly match JSON due to dict element ordering 506 Expect(debug.CmdArgs.StdinData).To(MatchJSON(plugins[i].stdinData)) 507 debug.CmdArgs.StdinData = nil 508 Expect(debug.CmdArgs).To(Equal(expectedCmdArgs)) 509 } 510 }) 511 512 Context("when finding the plugin fails", func() { 513 BeforeEach(func() { 514 netConfigList.Plugins[1].Network.Type = "does-not-exist" 515 }) 516 517 It("returns the error", func() { 518 _, err := cniConfig.AddNetworkList(netConfigList, runtimeConfig) 519 Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`))) 520 }) 521 }) 522 523 Context("when the second plugin errors", func() { 524 BeforeEach(func() { 525 plugins[1].debug.ReportError = "plugin error: banana" 526 Expect(plugins[1].debug.WriteDebug(plugins[1].debugFilePath)).To(Succeed()) 527 }) 528 It("unmarshals and returns the error", func() { 529 result, err := cniConfig.AddNetworkList(netConfigList, runtimeConfig) 530 Expect(result).To(BeNil()) 531 Expect(err).To(MatchError("plugin error: banana")) 532 }) 533 }) 534 }) 535 536 Describe("DelNetworkList", func() { 537 It("executes all the plugins in reverse order with command DEL", func() { 538 err := cniConfig.DelNetworkList(netConfigList, runtimeConfig) 539 Expect(err).NotTo(HaveOccurred()) 540 541 for i := 0; i < len(plugins); i++ { 542 debug, err := noop_debug.ReadDebug(plugins[i].debugFilePath) 543 Expect(err).NotTo(HaveOccurred()) 544 Expect(debug.Command).To(Equal("DEL")) 545 546 // Must explicitly match JSON due to dict element ordering 547 Expect(debug.CmdArgs.StdinData).To(MatchJSON(plugins[i].stdinData)) 548 debug.CmdArgs.StdinData = nil 549 Expect(debug.CmdArgs).To(Equal(expectedCmdArgs)) 550 } 551 }) 552 553 Context("when finding the plugin fails", func() { 554 BeforeEach(func() { 555 netConfigList.Plugins[1].Network.Type = "does-not-exist" 556 }) 557 558 It("returns the error", func() { 559 err := cniConfig.DelNetworkList(netConfigList, runtimeConfig) 560 Expect(err).To(MatchError(ContainSubstring(`failed to find plugin "does-not-exist"`))) 561 }) 562 }) 563 564 Context("when the plugin errors", func() { 565 BeforeEach(func() { 566 plugins[1].debug.ReportError = "plugin error: banana" 567 Expect(plugins[1].debug.WriteDebug(plugins[1].debugFilePath)).To(Succeed()) 568 }) 569 It("unmarshals and returns the error", func() { 570 err := cniConfig.DelNetworkList(netConfigList, runtimeConfig) 571 Expect(err).To(MatchError("plugin error: banana")) 572 }) 573 }) 574 }) 575 576 }) 577 })