github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/apiserver/provisioner/container_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package provisioner_test 5 6 import ( 7 "encoding/hex" 8 "fmt" 9 "strings" 10 11 "github.com/juju/errors" 12 "github.com/juju/loggo" 13 jc "github.com/juju/testing/checkers" 14 "github.com/juju/utils" 15 gc "gopkg.in/check.v1" 16 17 "github.com/juju/juju/apiserver/params" 18 "github.com/juju/juju/apiserver/provisioner" 19 apiservertesting "github.com/juju/juju/apiserver/testing" 20 "github.com/juju/juju/instance" 21 "github.com/juju/juju/network" 22 "github.com/juju/juju/state" 23 ) 24 25 // containerSuite has methods useful to tests working with containers. Notably 26 // around testing PrepareContainerInterfaceInfo and ReleaseContainerAddresses. 27 type containerSuite struct { 28 provisionerSuite 29 30 provAPI *provisioner.ProvisionerAPI 31 } 32 33 func (s *containerSuite) SetUpTest(c *gc.C) { 34 s.setUpTest(c, false) 35 // Reset any "broken" dummy provider methods. 36 s.breakEnvironMethods(c) 37 } 38 39 func (s *containerSuite) newCustomAPI(c *gc.C, hostInstId instance.Id, addContainer, provisionContainer bool) *state.Machine { 40 anAuthorizer := s.authorizer 41 anAuthorizer.EnvironManager = false 42 anAuthorizer.Tag = s.machines[0].Tag() 43 aProvisioner, err := provisioner.NewProvisionerAPI(s.State, s.resources, anAuthorizer) 44 c.Assert(err, jc.ErrorIsNil) 45 c.Assert(aProvisioner, gc.NotNil) 46 s.provAPI = aProvisioner 47 48 if hostInstId != "" { 49 err = s.machines[0].SetProvisioned(hostInstId, "fake_nonce", nil) 50 c.Assert(err, jc.ErrorIsNil) 51 } 52 53 if !addContainer { 54 return nil 55 } 56 container, err := s.State.AddMachineInsideMachine( 57 state.MachineTemplate{ 58 Series: "quantal", 59 Jobs: []state.MachineJob{state.JobHostUnits}, 60 }, 61 s.machines[0].Id(), 62 instance.LXC, 63 ) 64 c.Assert(err, jc.ErrorIsNil) 65 if provisionContainer { 66 password, err := utils.RandomPassword() 67 c.Assert(err, jc.ErrorIsNil) 68 err = container.SetPassword(password) 69 c.Assert(err, jc.ErrorIsNil) 70 err = container.SetProvisioned("foo", "fake_nonce", nil) 71 c.Assert(err, jc.ErrorIsNil) 72 } 73 return container 74 } 75 76 func (s *containerSuite) makeArgs(machines ...*state.Machine) params.Entities { 77 args := params.Entities{Entities: make([]params.Entity, len(machines))} 78 for i, m := range machines { 79 args.Entities[i].Tag = m.Tag().String() 80 } 81 return args 82 } 83 84 func (s *containerSuite) breakEnvironMethods(c *gc.C, methods ...string) { 85 s.AssertConfigParameterUpdated(c, "broken", strings.Join(methods, " ")) 86 } 87 88 // prepareSuite contains only tests around 89 // PrepareContainerInterfaceInfo method. 90 type prepareSuite struct { 91 containerSuite 92 } 93 94 var _ = gc.Suite(&prepareSuite{}) 95 96 func (s *prepareSuite) newAPI(c *gc.C, provisionHost, addContainer bool) *state.Machine { 97 var hostInstId instance.Id 98 if provisionHost { 99 hostInstId = "i-host" 100 } 101 return s.newCustomAPI(c, hostInstId, addContainer, false) 102 } 103 104 func (s *prepareSuite) makeErrors(errors ...*params.Error) *params.MachineNetworkConfigResults { 105 results := ¶ms.MachineNetworkConfigResults{ 106 Results: make([]params.MachineNetworkConfigResult, len(errors)), 107 } 108 for i, err := range errors { 109 results.Results[i].Error = err 110 } 111 return results 112 } 113 114 func (s *prepareSuite) makeResults(cfgs ...[]params.NetworkConfig) *params.MachineNetworkConfigResults { 115 results := ¶ms.MachineNetworkConfigResults{ 116 Results: make([]params.MachineNetworkConfigResult, len(cfgs)), 117 } 118 for i, cfg := range cfgs { 119 results.Results[i].Config = cfg 120 } 121 return results 122 } 123 124 func (s *prepareSuite) assertCall(c *gc.C, args params.Entities, expectResults *params.MachineNetworkConfigResults, expectErr string) (error, []loggo.TestLogValues) { 125 126 // Capture the logs for later inspection. 127 logger := loggo.GetLogger("juju.apiserver.provisioner") 128 defer logger.SetLogLevel(logger.LogLevel()) 129 logger.SetLogLevel(loggo.TRACE) 130 var tw loggo.TestWriter 131 c.Assert(loggo.RegisterWriter("test", &tw, loggo.TRACE), gc.IsNil) 132 defer loggo.RemoveWriter("test") 133 134 results, err := s.provAPI.PrepareContainerInterfaceInfo(args) 135 c.Logf("PrepareContainerInterfaceInfo returned: err=%v, results=%v", err, results) 136 c.Assert(results.Results, gc.HasLen, len(args.Entities)) 137 if expectErr == "" { 138 c.Assert(err, jc.ErrorIsNil) 139 c.Assert(expectResults, gc.NotNil) 140 c.Assert(results.Results, gc.HasLen, len(expectResults.Results)) 141 // Check for any "regex:" prefixes first. Then replace 142 // addresses in expected with the actual ones, so we can use 143 // jc.DeepEquals on the whole result below. 144 // Also check MAC addresses are valid, but as they're randomly 145 // generated we can't test specific values. 146 for i, expect := range expectResults.Results { 147 cfg := results.Results[i].Config 148 c.Assert(cfg, gc.HasLen, len(expect.Config)) 149 for j, expCfg := range expect.Config { 150 if strings.HasPrefix(expCfg.Address, "regex:") { 151 rex := strings.TrimPrefix(expCfg.Address, "regex:") 152 c.Assert(cfg[j].Address, gc.Matches, rex) 153 expectResults.Results[i].Config[j].Address = cfg[j].Address 154 } 155 macAddress := cfg[j].MACAddress 156 c.Assert(macAddress[:8], gc.Equals, provisioner.MACAddressTemplate[:8]) 157 remainder := strings.Replace(macAddress[8:], ":", "", 3) 158 c.Assert(remainder, gc.HasLen, 6) 159 _, err = hex.DecodeString(remainder) 160 c.Assert(err, jc.ErrorIsNil) 161 expectResults.Results[i].Config[j].MACAddress = macAddress 162 } 163 } 164 165 c.Assert(results, jc.DeepEquals, *expectResults) 166 } else { 167 c.Assert(err, gc.ErrorMatches, expectErr) 168 if len(args.Entities) > 0 { 169 result := results.Results[0] 170 // Not using jc.ErrorIsNil below because 171 // (*params.Error)(nil) does not satisfy the error 172 // interface. 173 c.Assert(result.Error, gc.IsNil) 174 c.Assert(result.Config, gc.IsNil) 175 } 176 } 177 return err, tw.Log() 178 } 179 180 func (s *prepareSuite) TestErrorWitnNoFeatureFlag(c *gc.C) { 181 s.SetFeatureFlags() // clear the flags. 182 container := s.newAPI(c, true, true) 183 args := s.makeArgs(container) 184 s.assertCall(c, args, ¶ms.MachineNetworkConfigResults{}, 185 `address allocation not supported`, 186 ) 187 } 188 189 func (s *prepareSuite) TestErrorWithNonProvisionedHost(c *gc.C) { 190 container := s.newAPI(c, false, true) 191 args := s.makeArgs(container) 192 s.assertCall(c, args, nil, 193 `cannot allocate addresses: host machine "0" not provisioned`, 194 ) 195 } 196 197 func (s *prepareSuite) TestErrorWithProvisionedContainer(c *gc.C) { 198 container := s.newAPI(c, true, true) 199 err := container.SetProvisioned("i-foo", "fake_nonce", nil) 200 c.Assert(err, jc.ErrorIsNil) 201 args := s.makeArgs(container) 202 s.assertCall(c, args, s.makeErrors( 203 apiservertesting.ServerError( 204 `container "0/lxc/0" already provisioned as "i-foo"`, 205 ), 206 ), "") 207 } 208 209 func (s *prepareSuite) TestErrorWithHostInsteadOfContainer(c *gc.C) { 210 s.newAPI(c, true, false) 211 args := s.makeArgs(s.machines[0]) 212 s.assertCall(c, args, s.makeErrors( 213 apiservertesting.ServerError( 214 `cannot allocate address for "machine-0": not a container`, 215 ), 216 ), "") 217 } 218 219 func (s *prepareSuite) TestErrorsWithDifferentHosts(c *gc.C) { 220 s.newAPI(c, true, false) 221 args := s.makeArgs(s.machines[1], s.machines[2]) 222 s.assertCall(c, args, s.makeErrors( 223 apiservertesting.ErrUnauthorized, 224 apiservertesting.ErrUnauthorized, 225 ), "") 226 } 227 228 func (s *prepareSuite) TestErrorsWithContainersOnDifferentHost(c *gc.C) { 229 s.newAPI(c, true, false) 230 var containers []*state.Machine 231 for i := 0; i < 2; i++ { 232 container, err := s.State.AddMachineInsideMachine( 233 state.MachineTemplate{ 234 Series: "quantal", 235 Jobs: []state.MachineJob{state.JobHostUnits}, 236 }, 237 s.machines[1].Id(), 238 instance.LXC, 239 ) 240 c.Assert(err, jc.ErrorIsNil) 241 containers = append(containers, container) 242 } 243 args := s.makeArgs(containers...) 244 s.assertCall(c, args, s.makeErrors( 245 apiservertesting.ErrUnauthorized, 246 apiservertesting.ErrUnauthorized, 247 ), "") 248 } 249 250 func (s *prepareSuite) TestErrorsWithNonMachineOrInvalidTags(c *gc.C) { 251 s.newAPI(c, true, false) 252 args := params.Entities{Entities: []params.Entity{ 253 {Tag: "unit-wordpress-0"}, 254 {Tag: "service-wordpress"}, 255 {Tag: "network-foo"}, 256 {Tag: "anything-invalid"}, 257 {Tag: "42"}, 258 {Tag: "machine-42"}, 259 {Tag: ""}, 260 }} 261 262 s.assertCall(c, args, s.makeErrors( 263 apiservertesting.ServerError( 264 `"unit-wordpress-0" is not a valid machine tag`), 265 apiservertesting.ServerError( 266 `"service-wordpress" is not a valid machine tag`), 267 apiservertesting.ServerError( 268 `"network-foo" is not a valid machine tag`), 269 apiservertesting.ServerError( 270 `"anything-invalid" is not a valid tag`), 271 apiservertesting.ServerError( 272 `"42" is not a valid tag`), 273 apiservertesting.ErrUnauthorized, 274 apiservertesting.ServerError( 275 `"" is not a valid tag`), 276 ), "") 277 } 278 279 func (s *prepareSuite) fillSubnet(c *gc.C, numAllocated int) { 280 // Create the 0.10.0.0/24 subnet in state and pre-allocate up to 281 // numAllocated of the range. This ensures the tests will run 282 // quickly, rather than retrying potentiallu until the full /24 283 // range is exhausted. 284 subInfo := state.SubnetInfo{ 285 ProviderId: "dummy-private", 286 CIDR: "0.10.0.0/24", 287 VLANTag: 0, 288 AllocatableIPLow: "0.10.0.0", 289 AllocatableIPHigh: "0.10.0.10", // Intentionally use shorter range. 290 } 291 sub, err := s.BackingState.AddSubnet(subInfo) 292 c.Assert(err, jc.ErrorIsNil) 293 for i := 0; i <= numAllocated; i++ { 294 addr := network.NewAddress(fmt.Sprintf("0.10.0.%d", i)) 295 ipaddr, err := s.BackingState.AddIPAddress(addr, sub.ID()) 296 c.Check(err, jc.ErrorIsNil) 297 err = ipaddr.SetState(state.AddressStateAllocated) 298 c.Check(err, jc.ErrorIsNil) 299 } 300 } 301 302 func (s *prepareSuite) TestErrorWithEnvironMethodsFailing(c *gc.C) { 303 container := s.newAPI(c, true, true) 304 args := s.makeArgs(container) 305 306 s.fillSubnet(c, 10) 307 308 // NOTE: We're testing AllocateAddress and ReleaseAddress separately. 309 for i, test := range []struct { 310 method string 311 err string 312 errCheck func(error) bool 313 }{{ 314 method: "NetworkInterfaces", 315 err: "cannot allocate addresses: dummy.NetworkInterfaces is broken", 316 }, { 317 method: "Subnets", 318 err: "cannot allocate addresses: dummy.Subnets is broken", 319 }, { 320 method: "SupportsAddressAllocation", 321 err: "cannot allocate addresses: address allocation on any available subnets is not supported", 322 errCheck: errors.IsNotSupported, 323 }} { 324 c.Logf("test %d: broken %q", i, test.method) 325 s.breakEnvironMethods(c, test.method) 326 var err error 327 if test.err != "" { 328 err, _ = s.assertCall(c, args, nil, test.err) 329 } 330 if test.errCheck != nil { 331 c.Check(err, jc.Satisfies, test.errCheck) 332 } 333 } 334 } 335 336 func (s *prepareSuite) TestRetryingOnAllocateAddressFailure(c *gc.C) { 337 // This test verifies the retrying logic when AllocateAddress 338 // and/or setAddrState return errors. 339 340 // Pre-allocate the first 5 addresses. 341 s.fillSubnet(c, 5) 342 343 // Now break AllocateAddress so it returns an error to verify the 344 // retry logic kicks in. Because it will always fail, the end 345 // result will always be an address exhaustion error. 346 s.breakEnvironMethods(c, "AllocateAddress") 347 348 container := s.newAPI(c, true, true) 349 args := s.makeArgs(container) 350 351 // Record each time setAddrState is called along with the address 352 // to verify the logs later. 353 var addresses []string 354 origSetAddrState := *provisioner.SetAddrState 355 s.PatchValue(provisioner.SetAddrState, func(ip *state.IPAddress, st state.AddressState) error { 356 c.Logf("setAddrState called for address %q, state %q", ip.String(), st) 357 c.Assert(st, gc.Equals, state.AddressStateUnavailable) 358 addresses = append(addresses, ip.Value()) 359 360 // Return an error every other call to test it's handled ok. 361 if len(addresses)%2 == 0 { 362 return errors.New("pow!") 363 } 364 return origSetAddrState(ip, st) 365 }) 366 367 _, testLog := s.assertCall(c, args, s.makeErrors(apiservertesting.ServerError( 368 `failed to allocate an address for "0/lxc/0": `+ 369 `allocatable IP addresses exhausted for subnet "0.10.0.0/24"`, 370 )), "") 371 372 // Verify the expected addresses, ignoring the order as the 373 // addresses are picked at random. 374 c.Assert(addresses, jc.SameContents, []string{ 375 "0.10.0.6", 376 "0.10.0.7", 377 "0.10.0.8", 378 "0.10.0.9", 379 "0.10.0.10", 380 }) 381 382 // Now verify the logs. 383 c.Assert(testLog, jc.LogMatches, jc.SimpleMessages{{ 384 loggo.WARNING, 385 `allocating address ".+" on instance ".+" and subnet ".+" failed: ` + 386 `dummy.AllocateAddress is broken \(retrying\)`, 387 }, { 388 loggo.TRACE, 389 `setting address ".+" to "unavailable" and retrying`, 390 }, { 391 loggo.TRACE, 392 `picked new address ".+" on subnet ".+"`, 393 }, { 394 loggo.WARNING, 395 `allocating address ".+" on instance ".+" and subnet ".+" failed: ` + 396 `dummy.AllocateAddress is broken \(retrying\)`, 397 }, { 398 loggo.WARNING, 399 `cannot set address ".+" to "unavailable": pow! \(ignoring and retrying\)`, 400 }}) 401 } 402 403 func (s *prepareSuite) TestReleaseAndCleanupWhenAllocateAndOrSetFail(c *gc.C) { 404 // This test verifies the retrying, releasing, and cleanup logic 405 // when AllocateAddress succeeds, but both ReleaseAddress and 406 // setAddrsTo fail, and allocateAddrTo either succeeds or fails. 407 408 // Pre-allocate the first 5 addresses. 409 s.fillSubnet(c, 5) 410 411 // Now break ReleaseAddress to test the how it's handled during 412 // the release/cleanup loop. 413 s.breakEnvironMethods(c, "ReleaseAddress") 414 415 container := s.newAPI(c, true, true) 416 args := s.makeArgs(container) 417 418 // Record each time allocateAddrTo, setAddrsTo, and setAddrState 419 // are called along with the addresses to verify the logs later. 420 var allocAttemptedAddrs, allocAddrsOK, setAddrs, releasedAddrs []string 421 s.PatchValue(provisioner.AllocateAddrTo, func(ip *state.IPAddress, m *state.Machine, mac string) error { 422 c.Logf("allocateAddrTo called for address %q, machine %q", ip.String(), m) 423 c.Assert(m.Id(), gc.Equals, container.Id()) 424 allocAttemptedAddrs = append(allocAttemptedAddrs, ip.Value()) 425 426 // Succeed on every other call to give a chance to call 427 // setAddrsTo as well. 428 if len(allocAttemptedAddrs)%2 == 0 { 429 allocAddrsOK = append(allocAddrsOK, ip.Value()) 430 return nil 431 } 432 return errors.New("crash!") 433 }) 434 s.PatchValue(provisioner.SetAddrsTo, func(ip *state.IPAddress, m *state.Machine) error { 435 c.Logf("setAddrsTo called for address %q, machine %q", ip.String(), m) 436 c.Assert(m.Id(), gc.Equals, container.Id()) 437 setAddrs = append(setAddrs, ip.Value()) 438 return errors.New("boom!") 439 }) 440 s.PatchValue(provisioner.SetAddrState, func(ip *state.IPAddress, st state.AddressState) error { 441 c.Logf("setAddrState called for address %q, state %q", ip.String(), st) 442 c.Assert(st, gc.Equals, state.AddressStateUnavailable) 443 releasedAddrs = append(releasedAddrs, ip.Value()) 444 return nil 445 }) 446 447 _, testLog := s.assertCall(c, args, s.makeErrors(apiservertesting.ServerError( 448 `failed to allocate an address for "0/lxc/0": `+ 449 `allocatable IP addresses exhausted for subnet "0.10.0.0/24"`, 450 )), "") 451 452 // Verify the expected addresses, ignoring the order as the 453 // addresses are picked at random. 454 expectAddrs := []string{ 455 "0.10.0.6", 456 "0.10.0.7", 457 "0.10.0.8", 458 "0.10.0.9", 459 "0.10.0.10", 460 } 461 // Verify that for each allocated address an attempt is made to 462 // assign it to the container by calling allocateAddrTo 463 // (successful or not doesn't matter). 464 c.Check(allocAttemptedAddrs, jc.SameContents, expectAddrs) 465 466 // Verify that for each allocated address an attempt is made to do 467 // release/cleanup by calling setAddrState(unavailable), after 468 // either allocateAddrTo or setAddrsTo fails. 469 c.Check(releasedAddrs, jc.SameContents, expectAddrs) 470 471 // Verify that for every allocateAddrTo call that passed, the 472 // corresponding setAddrsTo was also called. 473 c.Check(allocAddrsOK, jc.SameContents, setAddrs) 474 475 // Now verify the logs. 476 c.Assert(testLog, jc.LogMatches, jc.SimpleMessages{{ 477 loggo.INFO, 478 `allocated address "public:.+" on instance "i-host" and subnet "dummy-private"`, 479 }, { 480 loggo.WARNING, 481 `failed to mark address ".+" as "allocated" to container ".*": crash! \(releasing and retrying\)`, 482 }, { 483 loggo.WARNING, 484 `failed to release address ".+" on instance "i-host" and subnet ".+": ` + 485 `dummy.ReleaseAddress is broken \(ignoring and retrying\)`, 486 }, { 487 loggo.INFO, 488 `allocated address "public:.+" on instance "i-host" and subnet "dummy-private"`, 489 }}) 490 } 491 492 func (s *prepareSuite) TestReleaseAndRetryWhenSetOnlyFails(c *gc.C) { 493 // This test verifies the releasing, and cleanup, as well as 494 // retrying logic when AllocateAddress and allocateAddrTo succeed, 495 // but then both setAddrsTo and setAddrState fail. 496 497 // Pre-allocate the first 9 addresses, so the only address left 498 // will be 0.10.0.10. 499 s.fillSubnet(c, 9) 500 501 container := s.newAPI(c, true, true) 502 args := s.makeArgs(container) 503 504 s.PatchValue(provisioner.SetAddrsTo, func(ip *state.IPAddress, m *state.Machine) error { 505 c.Logf("setAddrsTo called for address %q, machine %q", ip.String(), m) 506 c.Assert(m.Id(), gc.Equals, container.Id()) 507 c.Assert(ip.Value(), gc.Equals, "0.10.0.10") 508 return errors.New("boom!") 509 }) 510 s.PatchValue(provisioner.SetAddrState, func(ip *state.IPAddress, st state.AddressState) error { 511 c.Logf("setAddrState called for address %q, state %q", ip.String(), st) 512 c.Assert(st, gc.Equals, state.AddressStateUnavailable) 513 c.Assert(ip.Value(), gc.Equals, "0.10.0.10") 514 return errors.New("pow!") 515 }) 516 517 // After failing twice, we'll successfully release the address and retry to succeed. 518 _, testLog := s.assertCall(c, args, s.makeResults([]params.NetworkConfig{{ 519 ProviderId: "dummy-eth0", 520 ProviderSubnetId: "dummy-private", 521 NetworkName: "juju-private", 522 CIDR: "0.10.0.0/24", 523 DeviceIndex: 0, 524 InterfaceName: "eth0", 525 VLANTag: 0, 526 Disabled: false, 527 NoAutoStart: false, 528 ConfigType: "static", 529 Address: "0.10.0.10", 530 DNSServers: []string{"ns1.dummy", "ns2.dummy"}, 531 GatewayAddress: "0.10.0.2", 532 ExtraConfig: nil, 533 }}), "") 534 535 c.Assert(testLog, jc.LogMatches, jc.SimpleMessages{{ 536 loggo.INFO, 537 `allocated address "public:0.10.0.10" on instance "i-host" and subnet "dummy-private"`, 538 }, { 539 loggo.WARNING, 540 `failed to mark address ".+" as "allocated" to container ".*": boom! \(releasing and retrying\)`, 541 }, { 542 loggo.WARNING, 543 `cannot set address "public:0.10.0.10" to "unavailable": pow! \(ignoring and releasing\)`, 544 }, { 545 loggo.INFO, 546 `address "public:0.10.0.10" released; trying to allocate new`, 547 }}) 548 } 549 550 func (s *prepareSuite) TestErrorWhenNoSubnetsAvailable(c *gc.C) { 551 // The magic "i-no-subnets-" instance id prefix for the host 552 // causes the dummy provider to return no results and no errors 553 // from Subnets(). 554 container := s.newCustomAPI(c, "i-no-subnets-here", true, false) 555 args := s.makeArgs(container) 556 s.assertCall(c, args, nil, "cannot allocate addresses: no subnets available") 557 } 558 559 func (s *prepareSuite) TestErrorWithDisabledNIC(c *gc.C) { 560 // The magic "i-disabled-nic-" instance id prefix for the host 561 // causes the dummy provider to return a disabled NIC from 562 // NetworkInterfaces(), which should not be used for the container. 563 container := s.newCustomAPI(c, "i-no-subnets-here", true, false) 564 args := s.makeArgs(container) 565 s.assertCall(c, args, nil, "cannot allocate addresses: no subnets available") 566 } 567 568 func (s *prepareSuite) TestErrorWhenNoAllocatableSubnetsAvailable(c *gc.C) { 569 // The magic "i-no-alloc-all" instance id for the host causes the 570 // dummy provider's Subnets() method to return all subnets without 571 // an allocatable range 572 container := s.newCustomAPI(c, "i-no-alloc-all", true, false) 573 args := s.makeArgs(container) 574 err, _ := s.assertCall(c, args, nil, "cannot allocate addresses: address allocation on any available subnets is not supported") 575 c.Assert(err, jc.Satisfies, errors.IsNotSupported) 576 } 577 578 func (s *prepareSuite) TestErrorWhenNoNICSAvailable(c *gc.C) { 579 // The magic "i-no-nics-" instance id prefix for the host 580 // causes the dummy provider to return no results and no errors 581 // from NetworkInterfaces(). 582 container := s.newCustomAPI(c, "i-no-nics-here", true, false) 583 args := s.makeArgs(container) 584 s.assertCall(c, args, nil, "cannot allocate addresses: no interfaces available") 585 } 586 587 func (s *prepareSuite) TestErrorWithNICNoSubnetAvailable(c *gc.C) { 588 // The magic "i-nic-no-subnet-" instance id prefix for the host 589 // causes the dummy provider to return a nic that has no associated 590 // subnet from NetworkInterfaces(). 591 container := s.newCustomAPI(c, "i-nic-no-subnet-here", true, false) 592 args := s.makeArgs(container) 593 s.assertCall(c, args, nil, "cannot allocate addresses: no subnets available") 594 } 595 596 func (s *prepareSuite) TestSuccessWithSingleContainer(c *gc.C) { 597 container := s.newAPI(c, true, true) 598 args := s.makeArgs(container) 599 _, testLog := s.assertCall(c, args, s.makeResults([]params.NetworkConfig{{ 600 ProviderId: "dummy-eth0", 601 ProviderSubnetId: "dummy-private", 602 NetworkName: "juju-private", 603 CIDR: "0.10.0.0/24", 604 DeviceIndex: 0, 605 InterfaceName: "eth0", 606 VLANTag: 0, 607 Disabled: false, 608 NoAutoStart: false, 609 ConfigType: "static", 610 Address: "regex:0.10.0.[0-9]{1,3}", // we don't care about the actual value. 611 DNSServers: []string{"ns1.dummy", "ns2.dummy"}, 612 GatewayAddress: "0.10.0.2", 613 ExtraConfig: nil, 614 }}), "") 615 616 c.Assert(testLog, jc.LogMatches, jc.SimpleMessages{{ 617 loggo.INFO, 618 `allocated address ".+" on instance "i-host" and subnet "dummy-private"`, 619 }, { 620 loggo.INFO, 621 `assigned address ".+" to container "0/lxc/0"`, 622 }}) 623 } 624 625 func (s *prepareSuite) TestSuccessWhenFirstSubnetNotAllocatable(c *gc.C) { 626 // Using "i-no-alloc-0" for the host instance id will cause the 627 // dummy provider to change the Subnets() results to return no 628 // allocatable range for the first subnet (dummy-private), and 629 // also change its ProviderId to "noalloc-private", which in turn 630 // will cause SupportsAddressAllocation() to return false for it. 631 // We test here that we keep looking for other allocatable 632 // subnets. 633 container := s.newCustomAPI(c, "i-no-alloc-0", true, false) 634 args := s.makeArgs(container) 635 _, testLog := s.assertCall(c, args, s.makeResults([]params.NetworkConfig{{ 636 ProviderId: "dummy-eth1", 637 ProviderSubnetId: "dummy-public", 638 NetworkName: "juju-public", 639 CIDR: "0.20.0.0/24", 640 DeviceIndex: 1, 641 InterfaceName: "eth1", 642 VLANTag: 1, 643 Disabled: false, 644 NoAutoStart: true, 645 ConfigType: "static", 646 Address: "regex:0.20.0.[0-9]{1,3}", // we don't care about the actual value. 647 DNSServers: []string{"ns1.dummy", "ns2.dummy"}, 648 GatewayAddress: "0.20.0.2", 649 ExtraConfig: nil, 650 }}), "") 651 652 c.Assert(testLog, jc.LogMatches, jc.SimpleMessages{{ 653 loggo.TRACE, 654 `ignoring subnet "noalloc-private" - no allocatable range set`, 655 }, { 656 loggo.INFO, 657 `allocated address ".+" on instance "i-no-alloc-0" and subnet "dummy-public"`, 658 }, { 659 loggo.INFO, 660 `assigned address ".+" to container "0/lxc/0"`, 661 }}) 662 } 663 664 // releaseSuite contains only tests around 665 // ReleaseContainerAddresses method. 666 type releaseSuite struct { 667 containerSuite 668 } 669 670 var _ = gc.Suite(&releaseSuite{}) 671 672 func (s *releaseSuite) newAPI(c *gc.C, provisionHost, addContainer bool) *state.Machine { 673 var hostInstId instance.Id 674 if provisionHost { 675 hostInstId = "i-host" 676 } 677 return s.newCustomAPI(c, hostInstId, addContainer, true) 678 } 679 680 func (s *releaseSuite) makeErrors(errors ...*params.Error) *params.ErrorResults { 681 results := ¶ms.ErrorResults{ 682 Results: make([]params.ErrorResult, len(errors)), 683 } 684 for i, err := range errors { 685 results.Results[i].Error = err 686 } 687 return results 688 } 689 690 func (s *releaseSuite) assertCall(c *gc.C, args params.Entities, expectResults *params.ErrorResults, expectErr string) error { 691 results, err := s.provAPI.ReleaseContainerAddresses(args) 692 c.Logf("ReleaseContainerAddresses returned: err=%v, results=%v", err, results) 693 c.Assert(results.Results, gc.HasLen, len(args.Entities)) 694 if expectErr == "" { 695 c.Assert(err, jc.ErrorIsNil) 696 c.Assert(expectResults, gc.NotNil) 697 c.Assert(results.Results, gc.HasLen, len(expectResults.Results)) 698 c.Assert(results, jc.DeepEquals, *expectResults) 699 } else { 700 c.Assert(err, gc.ErrorMatches, expectErr) 701 if len(args.Entities) > 0 { 702 result := results.Results[0] 703 // Not using jc.ErrorIsNil below because 704 // (*params.Error)(nil) does not satisfy the error 705 // interface. 706 c.Assert(result.Error, gc.IsNil) 707 } 708 } 709 return err 710 } 711 712 func (s *releaseSuite) TestErrorWithNoFeatureFlag(c *gc.C) { 713 s.SetFeatureFlags() // clear the flags. 714 s.newAPI(c, true, false) 715 args := s.makeArgs(s.machines[0]) 716 s.assertCall(c, args, ¶ms.ErrorResults{}, 717 "address allocation not supported", 718 ) 719 } 720 721 func (s *releaseSuite) TestErrorWithHostInsteadOfContainer(c *gc.C) { 722 s.newAPI(c, true, false) 723 args := s.makeArgs(s.machines[0]) 724 err := s.assertCall(c, args, s.makeErrors( 725 apiservertesting.ServerError( 726 `cannot mark addresses for removal for "machine-0": not a container`, 727 ), 728 ), "") 729 c.Assert(err, jc.ErrorIsNil) 730 } 731 732 func (s *releaseSuite) TestErrorsWithDifferentHosts(c *gc.C) { 733 s.newAPI(c, true, false) 734 args := s.makeArgs(s.machines[1], s.machines[2]) 735 err := s.assertCall(c, args, s.makeErrors( 736 apiservertesting.ErrUnauthorized, 737 apiservertesting.ErrUnauthorized, 738 ), "") 739 c.Assert(err, jc.ErrorIsNil) 740 } 741 742 func (s *releaseSuite) TestErrorsWithContainersOnDifferentHost(c *gc.C) { 743 s.newAPI(c, true, false) 744 var containers []*state.Machine 745 for i := 0; i < 2; i++ { 746 container, err := s.State.AddMachineInsideMachine( 747 state.MachineTemplate{ 748 Series: "quantal", 749 Jobs: []state.MachineJob{state.JobHostUnits}, 750 }, 751 s.machines[1].Id(), 752 instance.LXC, 753 ) 754 c.Assert(err, jc.ErrorIsNil) 755 containers = append(containers, container) 756 } 757 args := s.makeArgs(containers...) 758 err := s.assertCall(c, args, s.makeErrors( 759 apiservertesting.ErrUnauthorized, 760 apiservertesting.ErrUnauthorized, 761 ), "") 762 c.Assert(err, jc.ErrorIsNil) 763 } 764 765 func (s *releaseSuite) TestErrorsWithNonMachineOrInvalidTags(c *gc.C) { 766 s.newAPI(c, true, false) 767 args := params.Entities{Entities: []params.Entity{ 768 {Tag: "unit-wordpress-0"}, 769 {Tag: "service-wordpress"}, 770 {Tag: "network-foo"}, 771 {Tag: "anything-invalid"}, 772 {Tag: "42"}, 773 {Tag: "machine-42"}, 774 {Tag: ""}, 775 }} 776 777 err := s.assertCall(c, args, s.makeErrors( 778 apiservertesting.ErrUnauthorized, 779 apiservertesting.ErrUnauthorized, 780 apiservertesting.ErrUnauthorized, 781 apiservertesting.ErrUnauthorized, 782 apiservertesting.ErrUnauthorized, 783 apiservertesting.ErrUnauthorized, 784 apiservertesting.ErrUnauthorized, 785 ), "") 786 c.Assert(err, jc.ErrorIsNil) 787 } 788 789 func (s *releaseSuite) allocateAddresses(c *gc.C, containerId string, numAllocated int) { 790 // Create the 0.10.0.0/24 subnet in state and pre-allocate up to 791 // numAllocated of the range. It also allocates them to the specified 792 // container. 793 subInfo := state.SubnetInfo{ 794 ProviderId: "dummy-private", 795 CIDR: "0.10.0.0/24", 796 VLANTag: 0, 797 AllocatableIPLow: "0.10.0.0", 798 AllocatableIPHigh: "0.10.0.10", 799 } 800 sub, err := s.BackingState.AddSubnet(subInfo) 801 c.Assert(err, jc.ErrorIsNil) 802 for i := 0; i < numAllocated; i++ { 803 addr := network.NewAddress(fmt.Sprintf("0.10.0.%d", i)) 804 ipaddr, err := s.BackingState.AddIPAddress(addr, sub.ID()) 805 c.Check(err, jc.ErrorIsNil) 806 err = ipaddr.AllocateTo(containerId, "", "") 807 c.Check(err, jc.ErrorIsNil) 808 } 809 } 810 811 func (s *releaseSuite) TestSuccess(c *gc.C) { 812 container := s.newAPI(c, true, true) 813 args := s.makeArgs(container) 814 815 s.allocateAddresses(c, container.Id(), 2) 816 err := s.assertCall(c, args, s.makeErrors(nil), "") 817 c.Assert(err, jc.ErrorIsNil) 818 addresses, err := s.BackingState.AllocatedIPAddresses(container.Id()) 819 c.Assert(err, jc.ErrorIsNil) 820 c.Assert(addresses, gc.HasLen, 2) 821 for _, addr := range addresses { 822 c.Assert(addr.Life(), gc.Equals, state.Dead) 823 } 824 }