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