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