github.com/mccv1r0/cni@v0.7.0-alpha1/libcni/conf_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 "io/ioutil" 19 "os" 20 "path/filepath" 21 22 "github.com/containernetworking/cni/libcni" 23 "github.com/containernetworking/cni/pkg/types" 24 . "github.com/onsi/ginkgo" 25 . "github.com/onsi/gomega" 26 ) 27 28 var _ = Describe("Loading configuration from disk", func() { 29 Describe("LoadConf", func() { 30 var ( 31 configDir string 32 pluginConfig []byte 33 ) 34 35 BeforeEach(func() { 36 var err error 37 configDir, err = ioutil.TempDir("", "plugin-conf") 38 Expect(err).NotTo(HaveOccurred()) 39 40 pluginConfig = []byte(`{ "name": "some-plugin", "type": "foobar", "some-key": "some-value" }`) 41 Expect(ioutil.WriteFile(filepath.Join(configDir, "50-whatever.conf"), pluginConfig, 0600)).To(Succeed()) 42 }) 43 44 AfterEach(func() { 45 Expect(os.RemoveAll(configDir)).To(Succeed()) 46 }) 47 48 It("finds the network config file for the plugin of the given type", func() { 49 netConfig, err := libcni.LoadConf(configDir, "some-plugin") 50 Expect(err).NotTo(HaveOccurred()) 51 Expect(netConfig).To(Equal(&libcni.NetworkConfig{ 52 Network: &types.NetConf{ 53 Name: "some-plugin", 54 Type: "foobar", 55 }, 56 Bytes: pluginConfig, 57 })) 58 }) 59 60 Context("when the config directory does not exist", func() { 61 BeforeEach(func() { 62 Expect(os.RemoveAll(configDir)).To(Succeed()) 63 }) 64 65 It("returns a useful error", func() { 66 _, err := libcni.LoadConf(configDir, "some-plugin") 67 Expect(err).To(MatchError(libcni.NoConfigsFoundError{Dir: configDir})) 68 }) 69 }) 70 71 Context("when the config file is .json extension instead of .conf", func() { 72 BeforeEach(func() { 73 Expect(os.Remove(configDir + "/50-whatever.conf")).To(Succeed()) 74 pluginConfig = []byte(`{ "name": "some-plugin", "some-key": "some-value", "type": "foobar" }`) 75 Expect(ioutil.WriteFile(filepath.Join(configDir, "50-whatever.json"), pluginConfig, 0600)).To(Succeed()) 76 }) 77 It("finds the network config file for the plugin of the given type", func() { 78 netConfig, err := libcni.LoadConf(configDir, "some-plugin") 79 Expect(err).NotTo(HaveOccurred()) 80 Expect(netConfig).To(Equal(&libcni.NetworkConfig{ 81 Network: &types.NetConf{ 82 Name: "some-plugin", 83 Type: "foobar", 84 }, 85 Bytes: pluginConfig, 86 })) 87 }) 88 }) 89 90 Context("when there is no config for the desired plugin", func() { 91 It("returns a useful error", func() { 92 _, err := libcni.LoadConf(configDir, "some-other-plugin") 93 Expect(err).To(MatchError(ContainSubstring(`no net configuration with name "some-other-plugin" in`))) 94 }) 95 }) 96 97 Context("when a config file is malformed", func() { 98 BeforeEach(func() { 99 Expect(ioutil.WriteFile(filepath.Join(configDir, "00-bad.conf"), []byte(`{`), 0600)).To(Succeed()) 100 }) 101 102 It("returns a useful error", func() { 103 _, err := libcni.LoadConf(configDir, "some-plugin") 104 Expect(err).To(MatchError(`error parsing configuration: unexpected end of JSON input`)) 105 }) 106 }) 107 108 Context("when the config is in a nested subdir", func() { 109 BeforeEach(func() { 110 subdir := filepath.Join(configDir, "subdir1", "subdir2") 111 Expect(os.MkdirAll(subdir, 0700)).To(Succeed()) 112 113 pluginConfig = []byte(`{ "name": "deep", "some-key": "some-value" }`) 114 Expect(ioutil.WriteFile(filepath.Join(subdir, "90-deep.conf"), pluginConfig, 0600)).To(Succeed()) 115 }) 116 117 It("will not find the config", func() { 118 _, err := libcni.LoadConf(configDir, "deep") 119 Expect(err).To(MatchError(HavePrefix("no net configuration with name"))) 120 }) 121 }) 122 }) 123 124 Describe("Capabilities", func() { 125 var configDir string 126 127 BeforeEach(func() { 128 var err error 129 configDir, err = ioutil.TempDir("", "plugin-conf") 130 Expect(err).NotTo(HaveOccurred()) 131 132 pluginConfig := []byte(`{ "name": "some-plugin", "type": "noop", "cniVersion": "0.3.1", "capabilities": { "portMappings": true, "somethingElse": true, "noCapability": false } }`) 133 Expect(ioutil.WriteFile(filepath.Join(configDir, "50-whatever.conf"), pluginConfig, 0600)).To(Succeed()) 134 }) 135 136 AfterEach(func() { 137 Expect(os.RemoveAll(configDir)).To(Succeed()) 138 }) 139 140 It("reads plugin capabilities from network config", func() { 141 netConfig, err := libcni.LoadConf(configDir, "some-plugin") 142 Expect(err).NotTo(HaveOccurred()) 143 Expect(netConfig.Network.Capabilities).To(Equal(map[string]bool{ 144 "portMappings": true, 145 "somethingElse": true, 146 "noCapability": false, 147 })) 148 }) 149 }) 150 151 Describe("ConfFromFile", func() { 152 Context("when the file cannot be opened", func() { 153 It("returns a useful error", func() { 154 _, err := libcni.ConfFromFile("/tmp/nope/not-here") 155 Expect(err).To(MatchError(HavePrefix(`error reading /tmp/nope/not-here: open /tmp/nope/not-here`))) 156 }) 157 }) 158 159 Context("when the file is missing 'type'", func() { 160 var fileName, configDir string 161 BeforeEach(func() { 162 var err error 163 configDir, err = ioutil.TempDir("", "plugin-conf") 164 Expect(err).NotTo(HaveOccurred()) 165 166 fileName = filepath.Join(configDir, "50-whatever.conf") 167 pluginConfig := []byte(`{ "name": "some-plugin", "some-key": "some-value" }`) 168 Expect(ioutil.WriteFile(fileName, pluginConfig, 0600)).To(Succeed()) 169 }) 170 171 AfterEach(func() { 172 Expect(os.RemoveAll(configDir)).To(Succeed()) 173 }) 174 175 It("returns a useful error", func() { 176 _, err := libcni.ConfFromFile(fileName) 177 Expect(err).To(MatchError(`error parsing configuration: missing 'type'`)) 178 }) 179 }) 180 }) 181 182 Describe("ConfFromBytes", func() { 183 Context("when the config is missing 'type'", func() { 184 It("returns a useful error", func() { 185 _, err := libcni.ConfFromBytes([]byte(`{ "name": "some-plugin", "some-key": "some-value" }`)) 186 Expect(err).To(MatchError(`error parsing configuration: missing 'type'`)) 187 }) 188 }) 189 }) 190 191 Describe("LoadConfList", func() { 192 var ( 193 configDir string 194 configList []byte 195 ) 196 197 BeforeEach(func() { 198 var err error 199 configDir, err = ioutil.TempDir("", "plugin-conf") 200 Expect(err).NotTo(HaveOccurred()) 201 202 configList = []byte(`{ 203 "name": "some-list", 204 "cniVersion": "0.2.0", 205 "plugins": [ 206 { 207 "type": "host-local", 208 "subnet": "10.0.0.1/24" 209 }, 210 { 211 "type": "bridge", 212 "mtu": 1400 213 }, 214 { 215 "type": "port-forwarding", 216 "ports": {"20.0.0.1:8080": "80"} 217 } 218 ] 219 }`) 220 Expect(ioutil.WriteFile(filepath.Join(configDir, "50-whatever.conflist"), configList, 0600)).To(Succeed()) 221 }) 222 223 AfterEach(func() { 224 Expect(os.RemoveAll(configDir)).To(Succeed()) 225 }) 226 227 It("finds the network config file for the plugin of the given type", func() { 228 netConfigList, err := libcni.LoadConfList(configDir, "some-list") 229 Expect(err).NotTo(HaveOccurred()) 230 Expect(netConfigList).To(Equal(&libcni.NetworkConfigList{ 231 Name: "some-list", 232 CNIVersion: "0.2.0", 233 Plugins: []*libcni.NetworkConfig{ 234 { 235 Network: &types.NetConf{Type: "host-local"}, 236 Bytes: []byte(`{"subnet":"10.0.0.1/24","type":"host-local"}`), 237 }, 238 { 239 Network: &types.NetConf{Type: "bridge"}, 240 Bytes: []byte(`{"mtu":1400,"type":"bridge"}`), 241 }, 242 { 243 Network: &types.NetConf{Type: "port-forwarding"}, 244 Bytes: []byte(`{"ports":{"20.0.0.1:8080":"80"},"type":"port-forwarding"}`), 245 }, 246 }, 247 Bytes: configList, 248 })) 249 }) 250 251 Context("when there is a config file with the same name as the list", func() { 252 BeforeEach(func() { 253 configFile := []byte(`{ 254 "name": "some-list", 255 "cniVersion": "0.2.0", 256 "type": "bridge" 257 }`) 258 Expect(ioutil.WriteFile(filepath.Join(configDir, "49-whatever.conf"), configFile, 0600)).To(Succeed()) 259 }) 260 261 It("Loads the config list first", func() { 262 netConfigList, err := libcni.LoadConfList(configDir, "some-list") 263 Expect(err).NotTo(HaveOccurred()) 264 Expect(len(netConfigList.Plugins)).To(Equal(3)) 265 }) 266 267 It("falls back to the config file", func() { 268 Expect(os.Remove(filepath.Join(configDir, "50-whatever.conflist"))).To(Succeed()) 269 270 netConfigList, err := libcni.LoadConfList(configDir, "some-list") 271 Expect(err).NotTo(HaveOccurred()) 272 Expect(len(netConfigList.Plugins)).To(Equal(1)) 273 Expect(netConfigList.Plugins[0].Network.Type).To(Equal("bridge")) 274 }) 275 }) 276 277 Context("when the config directory does not exist", func() { 278 BeforeEach(func() { 279 Expect(os.RemoveAll(configDir)).To(Succeed()) 280 }) 281 282 It("returns a useful error", func() { 283 _, err := libcni.LoadConfList(configDir, "some-plugin") 284 Expect(err).To(MatchError(libcni.NoConfigsFoundError{Dir: configDir})) 285 }) 286 }) 287 288 Context("when there is no config for the desired plugin list", func() { 289 It("returns a useful error", func() { 290 _, err := libcni.LoadConfList(configDir, "some-other-plugin") 291 Expect(err).To(MatchError(libcni.NotFoundError{Dir: configDir, Name: "some-other-plugin"})) 292 }) 293 }) 294 295 Context("when a config file is malformed", func() { 296 BeforeEach(func() { 297 Expect(ioutil.WriteFile(filepath.Join(configDir, "00-bad.conflist"), []byte(`{`), 0600)).To(Succeed()) 298 }) 299 300 It("returns a useful error", func() { 301 _, err := libcni.LoadConfList(configDir, "some-plugin") 302 Expect(err).To(MatchError(`error parsing configuration list: unexpected end of JSON input`)) 303 }) 304 }) 305 306 Context("when the config is in a nested subdir", func() { 307 BeforeEach(func() { 308 subdir := filepath.Join(configDir, "subdir1", "subdir2") 309 Expect(os.MkdirAll(subdir, 0700)).To(Succeed()) 310 311 configList = []byte(`{ 312 "name": "deep", 313 "cniVersion": "0.2.0", 314 "plugins": [ 315 { 316 "type": "host-local", 317 "subnet": "10.0.0.1/24" 318 }, 319 ] 320 }`) 321 Expect(ioutil.WriteFile(filepath.Join(subdir, "90-deep.conflist"), configList, 0600)).To(Succeed()) 322 }) 323 324 It("will not find the config", func() { 325 _, err := libcni.LoadConfList(configDir, "deep") 326 Expect(err).To(MatchError(HavePrefix("no net configuration with name"))) 327 }) 328 }) 329 }) 330 331 Describe("ConfListFromFile", func() { 332 Context("when the file cannot be opened", func() { 333 It("returns a useful error", func() { 334 _, err := libcni.ConfListFromFile("/tmp/nope/not-here") 335 Expect(err).To(MatchError(HavePrefix(`error reading /tmp/nope/not-here: open /tmp/nope/not-here`))) 336 }) 337 }) 338 }) 339 340 Describe("InjectConf", func() { 341 var testNetConfig *libcni.NetworkConfig 342 343 BeforeEach(func() { 344 testNetConfig = &libcni.NetworkConfig{Network: &types.NetConf{Name: "some-plugin", Type: "foobar"}, 345 Bytes: []byte(`{ "name": "some-plugin", "type": "foobar" }`)} 346 }) 347 348 Context("when function parameters are incorrect", func() { 349 It("returns unmarshal error", func() { 350 conf := &libcni.NetworkConfig{Network: &types.NetConf{Name: "some-plugin"}, 351 Bytes: []byte(`{ cc cc cc}`)} 352 353 _, err := libcni.InjectConf(conf, map[string]interface{}{"": nil}) 354 Expect(err).To(MatchError(HavePrefix(`unmarshal existing network bytes`))) 355 }) 356 357 It("returns key error", func() { 358 _, err := libcni.InjectConf(testNetConfig, map[string]interface{}{"": nil}) 359 Expect(err).To(MatchError(HavePrefix(`keys cannot be empty`))) 360 }) 361 362 It("returns newValue error", func() { 363 _, err := libcni.InjectConf(testNetConfig, map[string]interface{}{"test": nil}) 364 Expect(err).To(MatchError(HavePrefix(`key 'test' value must not be nil`))) 365 }) 366 }) 367 368 Context("when new string value added", func() { 369 It("adds the new key & value to the config", func() { 370 newPluginConfig := []byte(`{"name":"some-plugin","test":"test","type":"foobar"}`) 371 372 resultConfig, err := libcni.InjectConf(testNetConfig, map[string]interface{}{"test": "test"}) 373 Expect(err).NotTo(HaveOccurred()) 374 Expect(resultConfig).To(Equal(&libcni.NetworkConfig{ 375 Network: &types.NetConf{ 376 Name: "some-plugin", 377 Type: "foobar", 378 }, 379 Bytes: newPluginConfig, 380 })) 381 }) 382 383 It("adds the new value for exiting key", func() { 384 newPluginConfig := []byte(`{"name":"some-plugin","test":"changedValue","type":"foobar"}`) 385 386 resultConfig, err := libcni.InjectConf(testNetConfig, map[string]interface{}{"test": "test"}) 387 Expect(err).NotTo(HaveOccurred()) 388 389 resultConfig, err = libcni.InjectConf(resultConfig, map[string]interface{}{"test": "changedValue"}) 390 Expect(err).NotTo(HaveOccurred()) 391 392 Expect(resultConfig).To(Equal(&libcni.NetworkConfig{ 393 Network: &types.NetConf{ 394 Name: "some-plugin", 395 Type: "foobar", 396 }, 397 Bytes: newPluginConfig, 398 })) 399 }) 400 401 It("adds existing key & value", func() { 402 newPluginConfig := []byte(`{"name":"some-plugin","test":"test","type":"foobar"}`) 403 404 resultConfig, err := libcni.InjectConf(testNetConfig, map[string]interface{}{"test": "test"}) 405 Expect(err).NotTo(HaveOccurred()) 406 407 resultConfig, err = libcni.InjectConf(resultConfig, map[string]interface{}{"test": "test"}) 408 Expect(err).NotTo(HaveOccurred()) 409 410 Expect(resultConfig).To(Equal(&libcni.NetworkConfig{ 411 Network: &types.NetConf{ 412 Name: "some-plugin", 413 Type: "foobar", 414 }, 415 Bytes: newPluginConfig, 416 })) 417 }) 418 419 It("adds sub-fields of NetworkConfig.Network to the config", func() { 420 421 expectedPluginConfig := []byte(`{"dns":{"domain":"local","nameservers":["server1","server2"]},"name":"some-plugin","type":"bridge"}`) 422 servers := []string{"server1", "server2"} 423 newDNS := &types.DNS{Nameservers: servers, Domain: "local"} 424 425 // inject DNS 426 resultConfig, err := libcni.InjectConf(testNetConfig, map[string]interface{}{"dns": newDNS}) 427 Expect(err).NotTo(HaveOccurred()) 428 429 // inject type 430 resultConfig, err = libcni.InjectConf(resultConfig, map[string]interface{}{"type": "bridge"}) 431 Expect(err).NotTo(HaveOccurred()) 432 433 Expect(resultConfig).To(Equal(&libcni.NetworkConfig{ 434 Network: &types.NetConf{Name: "some-plugin", Type: "bridge", DNS: types.DNS{Nameservers: servers, Domain: "local"}}, 435 Bytes: expectedPluginConfig, 436 })) 437 }) 438 }) 439 }) 440 }) 441 442 var _ = Describe("ConfListFromConf", func() { 443 var testNetConfig *libcni.NetworkConfig 444 445 BeforeEach(func() { 446 pb := []byte(`{"name":"some-plugin","cniVersion":"0.3.1", "type":"foobar"}`) 447 tc, err := libcni.ConfFromBytes(pb) 448 Expect(err).NotTo(HaveOccurred()) 449 testNetConfig = tc 450 }) 451 452 It("correctly upconverts a NetworkConfig to a NetworkConfigList", func() { 453 ncl, err := libcni.ConfListFromConf(testNetConfig) 454 Expect(err).NotTo(HaveOccurred()) 455 bytes := ncl.Bytes 456 457 // null out the json - we don't care about the exact marshalling 458 ncl.Bytes = nil 459 ncl.Plugins[0].Bytes = nil 460 testNetConfig.Bytes = nil 461 462 Expect(ncl).To(Equal(&libcni.NetworkConfigList{ 463 Name: "some-plugin", 464 CNIVersion: "0.3.1", 465 Plugins: []*libcni.NetworkConfig{testNetConfig}, 466 })) 467 468 //Test that the json unmarshals to the same data 469 ncl2, err := libcni.ConfListFromBytes(bytes) 470 Expect(err).NotTo(HaveOccurred()) 471 ncl2.Bytes = nil 472 ncl2.Plugins[0].Bytes = nil 473 474 Expect(ncl2).To(Equal(ncl)) 475 }) 476 477 })