github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/provider/azure/environ_test.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package azure_test 5 6 import ( 7 "encoding/json" 8 "fmt" 9 "io/ioutil" 10 "net/http" 11 "path" 12 "reflect" 13 "time" 14 15 autorestazure "github.com/Azure/azure-sdk-for-go/Godeps/_workspace/src/github.com/Azure/go-autorest/autorest/azure" 16 "github.com/Azure/azure-sdk-for-go/Godeps/_workspace/src/github.com/Azure/go-autorest/autorest/mocks" 17 "github.com/Azure/azure-sdk-for-go/Godeps/_workspace/src/github.com/Azure/go-autorest/autorest/to" 18 "github.com/Azure/azure-sdk-for-go/arm/compute" 19 "github.com/Azure/azure-sdk-for-go/arm/network" 20 "github.com/Azure/azure-sdk-for-go/arm/resources" 21 "github.com/Azure/azure-sdk-for-go/arm/storage" 22 "github.com/juju/names" 23 jc "github.com/juju/testing/checkers" 24 "github.com/juju/utils/arch" 25 gc "gopkg.in/check.v1" 26 27 "github.com/juju/juju/api" 28 "github.com/juju/juju/cloudconfig/instancecfg" 29 "github.com/juju/juju/constraints" 30 "github.com/juju/juju/environs" 31 "github.com/juju/juju/environs/imagemetadata" 32 "github.com/juju/juju/environs/simplestreams" 33 "github.com/juju/juju/environs/tags" 34 envtesting "github.com/juju/juju/environs/testing" 35 envtools "github.com/juju/juju/environs/tools" 36 "github.com/juju/juju/instance" 37 "github.com/juju/juju/mongo" 38 "github.com/juju/juju/provider/azure" 39 "github.com/juju/juju/provider/azure/internal/azuretesting" 40 "github.com/juju/juju/testing" 41 "github.com/juju/juju/tools" 42 "github.com/juju/version" 43 ) 44 45 type environSuite struct { 46 testing.BaseSuite 47 48 provider environs.EnvironProvider 49 requests []*http.Request 50 storageClient azuretesting.MockStorageClient 51 sender azuretesting.Senders 52 53 tags map[string]*string 54 vmSizes *compute.VirtualMachineSizeListResult 55 storageNameAvailabilityResult *storage.CheckNameAvailabilityResult 56 storageAccount *storage.Account 57 storageAccountKeys *storage.AccountKeys 58 vnet *network.VirtualNetwork 59 subnet *network.Subnet 60 ubuntuServerSKUs []compute.VirtualMachineImageResource 61 publicIPAddress *network.PublicIPAddress 62 oldNetworkInterfaces *network.InterfaceListResult 63 newNetworkInterface *network.Interface 64 jujuAvailabilitySet *compute.AvailabilitySet 65 virtualMachine *compute.VirtualMachine 66 } 67 68 var _ = gc.Suite(&environSuite{}) 69 70 func (s *environSuite) SetUpTest(c *gc.C) { 71 s.BaseSuite.SetUpTest(c) 72 s.storageClient = azuretesting.MockStorageClient{} 73 s.sender = nil 74 s.provider, _ = newProviders(c, azure.ProviderConfig{ 75 Sender: &s.sender, 76 RequestInspector: requestRecorder(&s.requests), 77 NewStorageClient: s.storageClient.NewClient, 78 }) 79 80 emptyTags := make(map[string]*string) 81 s.tags = map[string]*string{ 82 "juju-machine-name": to.StringPtr("machine-0"), 83 } 84 85 vmSizes := []compute.VirtualMachineSize{{ 86 Name: to.StringPtr("Standard_D1"), 87 NumberOfCores: to.IntPtr(1), 88 OsDiskSizeInMB: to.IntPtr(1047552), 89 ResourceDiskSizeInMB: to.IntPtr(51200), 90 MemoryInMB: to.IntPtr(3584), 91 MaxDataDiskCount: to.IntPtr(2), 92 }} 93 s.vmSizes = &compute.VirtualMachineSizeListResult{Value: &vmSizes} 94 95 s.storageNameAvailabilityResult = &storage.CheckNameAvailabilityResult{ 96 NameAvailable: to.BoolPtr(true), 97 } 98 99 s.storageAccount = &storage.Account{ 100 Name: to.StringPtr("my-storage-account"), 101 Type: to.StringPtr("Standard_LRS"), 102 Properties: &storage.AccountProperties{ 103 PrimaryEndpoints: &storage.Endpoints{ 104 Blob: to.StringPtr(fmt.Sprintf("https://%s.blob.storage.azurestack.local/", fakeStorageAccount)), 105 }, 106 }, 107 } 108 109 s.storageAccountKeys = &storage.AccountKeys{ 110 Key1: to.StringPtr("key-1"), 111 } 112 113 addressPrefixes := make([]string, 256) 114 for i := range addressPrefixes { 115 addressPrefixes[i] = fmt.Sprintf("10.%d.0.0/16", i) 116 } 117 s.vnet = &network.VirtualNetwork{ 118 ID: to.StringPtr("juju-internal"), 119 Name: to.StringPtr("juju-internal"), 120 Location: to.StringPtr("westus"), 121 Tags: &emptyTags, 122 Properties: &network.VirtualNetworkPropertiesFormat{ 123 AddressSpace: &network.AddressSpace{&addressPrefixes}, 124 }, 125 } 126 127 s.subnet = &network.Subnet{ 128 ID: to.StringPtr("subnet-id"), 129 Name: to.StringPtr("juju-testenv-model-deadbeef-0bad-400d-8000-4b1d0d06f00d"), 130 Properties: &network.SubnetPropertiesFormat{ 131 AddressPrefix: to.StringPtr("10.0.0.0/16"), 132 }, 133 } 134 135 s.ubuntuServerSKUs = []compute.VirtualMachineImageResource{ 136 {Name: to.StringPtr("12.04-LTS")}, 137 {Name: to.StringPtr("12.10")}, 138 {Name: to.StringPtr("14.04-LTS")}, 139 {Name: to.StringPtr("15.04")}, 140 {Name: to.StringPtr("15.10")}, 141 } 142 143 s.publicIPAddress = &network.PublicIPAddress{ 144 ID: to.StringPtr("public-ip-id"), 145 Name: to.StringPtr("machine-0-public-ip"), 146 Location: to.StringPtr("westus"), 147 Tags: &s.tags, 148 Properties: &network.PublicIPAddressPropertiesFormat{ 149 PublicIPAllocationMethod: network.Dynamic, 150 IPAddress: to.StringPtr("1.2.3.4"), 151 }, 152 } 153 154 // Existing IPs/NICs. These are the results of querying NICs so we 155 // can tell which IP to allocate. 156 oldIPConfigurations := []network.InterfaceIPConfiguration{{ 157 ID: to.StringPtr("ip-configuration-0-id"), 158 Name: to.StringPtr("ip-configuration-0"), 159 Properties: &network.InterfaceIPConfigurationPropertiesFormat{ 160 PrivateIPAddress: to.StringPtr("10.0.0.4"), 161 PrivateIPAllocationMethod: network.Static, 162 Subnet: &network.SubResource{ID: s.subnet.ID}, 163 }, 164 }} 165 oldNetworkInterfaces := []network.Interface{{ 166 ID: to.StringPtr("network-interface-0-id"), 167 Name: to.StringPtr("network-interface-0"), 168 Properties: &network.InterfacePropertiesFormat{ 169 IPConfigurations: &oldIPConfigurations, 170 Primary: to.BoolPtr(true), 171 }, 172 }} 173 s.oldNetworkInterfaces = &network.InterfaceListResult{ 174 Value: &oldNetworkInterfaces, 175 } 176 177 // nsgID is the name of the internal network security group. This NSG 178 // is created when the environment is created. 179 nsgID := path.Join( 180 "/subscriptions", fakeSubscriptionId, 181 "resourceGroups", "juju-testenv-model-"+testing.ModelTag.Id(), 182 "providers/Microsoft.Network/networkSecurityGroups/juju-internal", 183 ) 184 185 // The newly created IP/NIC. 186 newIPConfigurations := []network.InterfaceIPConfiguration{{ 187 ID: to.StringPtr("ip-configuration-1-id"), 188 Name: to.StringPtr("primary"), 189 Properties: &network.InterfaceIPConfigurationPropertiesFormat{ 190 PrivateIPAddress: to.StringPtr("10.0.0.5"), 191 PrivateIPAllocationMethod: network.Static, 192 Subnet: &network.SubResource{ID: s.subnet.ID}, 193 PublicIPAddress: &network.SubResource{ID: s.publicIPAddress.ID}, 194 }, 195 }} 196 s.newNetworkInterface = &network.Interface{ 197 ID: to.StringPtr("network-interface-1-id"), 198 Name: to.StringPtr("network-interface-1"), 199 Location: to.StringPtr("westus"), 200 Tags: &s.tags, 201 Properties: &network.InterfacePropertiesFormat{ 202 IPConfigurations: &newIPConfigurations, 203 NetworkSecurityGroup: &network.SubResource{to.StringPtr(nsgID)}, 204 }, 205 } 206 207 s.jujuAvailabilitySet = &compute.AvailabilitySet{ 208 ID: to.StringPtr("juju-availability-set-id"), 209 Name: to.StringPtr("juju"), 210 Location: to.StringPtr("westus"), 211 Tags: &emptyTags, 212 } 213 214 sshPublicKeys := []compute.SSHPublicKey{{ 215 Path: to.StringPtr("/home/ubuntu/.ssh/authorized_keys"), 216 KeyData: to.StringPtr(testing.FakeAuthKeys), 217 }} 218 networkInterfaceReferences := []compute.NetworkInterfaceReference{{ 219 ID: s.newNetworkInterface.ID, 220 Properties: &compute.NetworkInterfaceReferenceProperties{ 221 Primary: to.BoolPtr(true), 222 }, 223 }} 224 s.virtualMachine = &compute.VirtualMachine{ 225 ID: to.StringPtr("machine-0-id"), 226 Name: to.StringPtr("machine-0"), 227 Location: to.StringPtr("westus"), 228 Tags: &s.tags, 229 Properties: &compute.VirtualMachineProperties{ 230 HardwareProfile: &compute.HardwareProfile{ 231 VMSize: "Standard_D1", 232 }, 233 StorageProfile: &compute.StorageProfile{ 234 ImageReference: &compute.ImageReference{ 235 Publisher: to.StringPtr("Canonical"), 236 Offer: to.StringPtr("UbuntuServer"), 237 Sku: to.StringPtr("12.10"), 238 Version: to.StringPtr("latest"), 239 }, 240 OsDisk: &compute.OSDisk{ 241 Name: to.StringPtr("machine-0"), 242 CreateOption: compute.FromImage, 243 Caching: compute.ReadWrite, 244 Vhd: &compute.VirtualHardDisk{ 245 URI: to.StringPtr(fmt.Sprintf( 246 "https://%s.blob.storage.azurestack.local/osvhds/machine-0.vhd", 247 fakeStorageAccount, 248 )), 249 }, 250 }, 251 }, 252 OsProfile: &compute.OSProfile{ 253 ComputerName: to.StringPtr("machine-0"), 254 CustomData: to.StringPtr("<juju-goes-here>"), 255 AdminUsername: to.StringPtr("ubuntu"), 256 LinuxConfiguration: &compute.LinuxConfiguration{ 257 DisablePasswordAuthentication: to.BoolPtr(true), 258 SSH: &compute.SSHConfiguration{ 259 PublicKeys: &sshPublicKeys, 260 }, 261 }, 262 }, 263 NetworkProfile: &compute.NetworkProfile{ 264 NetworkInterfaces: &networkInterfaceReferences, 265 }, 266 AvailabilitySet: &compute.SubResource{ID: s.jujuAvailabilitySet.ID}, 267 ProvisioningState: to.StringPtr("Successful"), 268 }, 269 } 270 } 271 272 func (s *environSuite) openEnviron(c *gc.C, attrs ...testing.Attrs) environs.Environ { 273 attrs = append([]testing.Attrs{{"storage-account": fakeStorageAccount}}, attrs...) 274 return openEnviron(c, s.provider, &s.sender, attrs...) 275 } 276 277 func openEnviron( 278 c *gc.C, 279 provider environs.EnvironProvider, 280 sender *azuretesting.Senders, 281 attrs ...testing.Attrs, 282 ) environs.Environ { 283 // Opening the environment should not incur network communication, 284 // so we don't set s.sender until after opening. 285 cfg := makeTestModelConfig(c, attrs...) 286 env, err := provider.Open(cfg) 287 c.Assert(err, jc.ErrorIsNil) 288 289 // Force an explicit refresh of the access token, so it isn't done 290 // implicitly during the tests. 291 *sender = azuretesting.Senders{tokenRefreshSender()} 292 err = azure.ForceTokenRefresh(env) 293 c.Assert(err, jc.ErrorIsNil) 294 return env 295 } 296 297 func prepareForBootstrap( 298 c *gc.C, 299 ctx environs.BootstrapContext, 300 provider environs.EnvironProvider, 301 sender *azuretesting.Senders, 302 attrs ...testing.Attrs, 303 ) environs.Environ { 304 // Opening the environment should not incur network communication, 305 // so we don't set s.sender until after opening. 306 cfg := makeTestModelConfig(c, attrs...) 307 cfg, err := cfg.Remove([]string{"controller-resource-group"}) 308 c.Assert(err, jc.ErrorIsNil) 309 *sender = azuretesting.Senders{tokenRefreshSender()} 310 cfg, err = provider.BootstrapConfig(environs.BootstrapConfigParams{ 311 Config: cfg, 312 CloudRegion: "westus", 313 CloudEndpoint: "https://management.azure.com", 314 CloudStorageEndpoint: "https://core.windows.net", 315 Credentials: fakeUserPassCredential(), 316 }) 317 c.Assert(err, jc.ErrorIsNil) 318 env, err := provider.PrepareForBootstrap(ctx, cfg) 319 c.Assert(err, jc.ErrorIsNil) 320 return env 321 } 322 323 func tokenRefreshSender() *azuretesting.MockSender { 324 tokenRefreshSender := azuretesting.NewSenderWithValue(&autorestazure.Token{ 325 AccessToken: "access-token", 326 ExpiresOn: fmt.Sprint(time.Now().Add(time.Hour).Unix()), 327 Type: "Bearer", 328 }) 329 tokenRefreshSender.PathPattern = ".*/oauth2/token" 330 return tokenRefreshSender 331 } 332 333 func (s *environSuite) initResourceGroupSenders() azuretesting.Senders { 334 resourceGroupName := "juju-testenv-model-deadbeef-0bad-400d-8000-4b1d0d06f00d" 335 return azuretesting.Senders{ 336 s.makeSender(".*/resourcegroups/"+resourceGroupName, &resources.Group{}), 337 s.makeSender(".*/virtualnetworks/juju-internal", s.vnet), 338 s.makeSender(".*/networkSecurityGroups/juju-internal", &network.SecurityGroup{}), 339 s.makeSender(".*/virtualnetworks/juju-internal/subnets/"+resourceGroupName, &s.subnet), 340 s.makeSender(".*/checkNameAvailability", s.storageNameAvailabilityResult), 341 s.makeSender(".*/storageAccounts/.*", s.storageAccount), 342 s.makeSender(".*/storageAccounts/.*/listKeys", s.storageAccountKeys), 343 } 344 } 345 346 func (s *environSuite) startInstanceSenders(controller bool) azuretesting.Senders { 347 senders := azuretesting.Senders{ 348 s.vmSizesSender(), 349 s.makeSender(".*/subnets/juju-testenv-model-deadbeef-0bad-400d-8000-4b1d0d06f00d", s.subnet), 350 s.makeSender(".*/Canonical/.*/UbuntuServer/skus", s.ubuntuServerSKUs), 351 s.makeSender(".*/publicIPAddresses/machine-0-public-ip", s.publicIPAddress), 352 s.makeSender(".*/networkInterfaces", s.oldNetworkInterfaces), 353 s.makeSender(".*/networkInterfaces/machine-0-primary", s.newNetworkInterface), 354 } 355 if controller { 356 senders = append(senders, 357 s.makeSender(".*/networkSecurityGroups/juju-internal", &network.SecurityGroup{ 358 Properties: &network.SecurityGroupPropertiesFormat{}, 359 }), 360 s.makeSender(".*/networkSecurityGroups/juju-internal", &network.SecurityGroup{}), 361 ) 362 } 363 senders = append(senders, 364 s.makeSender(".*/availabilitySets/.*", s.jujuAvailabilitySet), 365 s.makeSender(".*/virtualMachines/machine-0", s.virtualMachine), 366 ) 367 return senders 368 } 369 370 func (s *environSuite) networkInterfacesSender(nics ...network.Interface) *azuretesting.MockSender { 371 return s.makeSender(".*/networkInterfaces", network.InterfaceListResult{Value: &nics}) 372 } 373 374 func (s *environSuite) publicIPAddressesSender(pips ...network.PublicIPAddress) *azuretesting.MockSender { 375 return s.makeSender(".*/publicIPAddresses", network.PublicIPAddressListResult{Value: &pips}) 376 } 377 378 func (s *environSuite) virtualMachinesSender(vms ...compute.VirtualMachine) *azuretesting.MockSender { 379 return s.makeSender(".*/virtualMachines", compute.VirtualMachineListResult{Value: &vms}) 380 } 381 382 func (s *environSuite) vmSizesSender() *azuretesting.MockSender { 383 return s.makeSender(".*/vmSizes", s.vmSizes) 384 } 385 386 func (s *environSuite) makeSender(pattern string, v interface{}) *azuretesting.MockSender { 387 sender := azuretesting.NewSenderWithValue(v) 388 sender.PathPattern = pattern 389 return sender 390 } 391 392 func makeStartInstanceParams(c *gc.C, series string) environs.StartInstanceParams { 393 machineTag := names.NewMachineTag("0") 394 stateInfo := &mongo.MongoInfo{ 395 Info: mongo.Info{ 396 CACert: testing.CACert, 397 Addrs: []string{"localhost:123"}, 398 }, 399 Password: "password", 400 Tag: machineTag, 401 } 402 apiInfo := &api.Info{ 403 Addrs: []string{"localhost:246"}, 404 CACert: testing.CACert, 405 Password: "admin", 406 Tag: machineTag, 407 ModelTag: testing.ModelTag, 408 } 409 410 const secureServerConnections = true 411 icfg, err := instancecfg.NewInstanceConfig( 412 machineTag.Id(), "yanonce", imagemetadata.ReleasedStream, 413 series, "", secureServerConnections, stateInfo, apiInfo, 414 ) 415 c.Assert(err, jc.ErrorIsNil) 416 417 return environs.StartInstanceParams{ 418 Tools: makeToolsList(series), 419 InstanceConfig: icfg, 420 } 421 } 422 423 func makeToolsList(series string) tools.List { 424 var toolsVersion version.Binary 425 toolsVersion.Number = version.MustParse("1.26.0") 426 toolsVersion.Arch = arch.AMD64 427 toolsVersion.Series = series 428 return tools.List{{ 429 Version: toolsVersion, 430 URL: fmt.Sprintf("http://example.com/tools/juju-%s.tgz", toolsVersion), 431 SHA256: "1234567890abcdef", 432 Size: 1024, 433 }} 434 } 435 436 func unmarshalRequestBody(c *gc.C, req *http.Request, out interface{}) { 437 bytes, err := ioutil.ReadAll(req.Body) 438 c.Assert(err, jc.ErrorIsNil) 439 err = json.Unmarshal(bytes, out) 440 c.Assert(err, jc.ErrorIsNil) 441 } 442 443 func assertRequestBody(c *gc.C, req *http.Request, expect interface{}) { 444 unmarshalled := reflect.New(reflect.TypeOf(expect).Elem()).Interface() 445 unmarshalRequestBody(c, req, unmarshalled) 446 c.Assert(unmarshalled, jc.DeepEquals, expect) 447 } 448 449 func (s *environSuite) TestOpen(c *gc.C) { 450 cfg := makeTestModelConfig(c) 451 env, err := s.provider.Open(cfg) 452 c.Assert(err, jc.ErrorIsNil) 453 c.Assert(env, gc.NotNil) 454 } 455 456 func (s *environSuite) TestCloudEndpointManagementURI(c *gc.C) { 457 env := s.openEnviron(c) 458 459 sender := mocks.NewSender() 460 sender.EmitContent("{}") 461 s.sender = azuretesting.Senders{sender} 462 s.requests = nil 463 env.AllInstances() // trigger a query 464 465 c.Assert(s.requests, gc.HasLen, 1) 466 c.Assert(s.requests[0].URL.Host, gc.Equals, "api.azurestack.local") 467 } 468 469 func (s *environSuite) TestStartInstance(c *gc.C) { 470 env := s.openEnviron(c) 471 s.sender = s.startInstanceSenders(false) 472 s.requests = nil 473 result, err := env.StartInstance(makeStartInstanceParams(c, "quantal")) 474 c.Assert(err, jc.ErrorIsNil) 475 c.Assert(result, gc.NotNil) 476 c.Assert(result.Instance, gc.NotNil) 477 c.Assert(result.NetworkInfo, gc.HasLen, 0) 478 c.Assert(result.Volumes, gc.HasLen, 0) 479 c.Assert(result.VolumeAttachments, gc.HasLen, 0) 480 481 arch := "amd64" 482 mem := uint64(3584) 483 rootDisk := uint64(29495) // ~30 GB 484 cpuCores := uint64(1) 485 c.Assert(result.Hardware, jc.DeepEquals, &instance.HardwareCharacteristics{ 486 Arch: &arch, 487 Mem: &mem, 488 RootDisk: &rootDisk, 489 CpuCores: &cpuCores, 490 }) 491 requests := s.assertStartInstanceRequests(c) 492 availabilitySetName := path.Base(requests.availabilitySet.URL.Path) 493 c.Assert(availabilitySetName, gc.Equals, "juju") 494 } 495 496 func (s *environSuite) TestStartInstanceDistributionGroup(c *gc.C) { 497 c.Skip("TODO: test StartInstance's DistributionGroup behaviour") 498 } 499 500 func (s *environSuite) TestStartInstanceServiceAvailabilitySet(c *gc.C) { 501 env := s.openEnviron(c) 502 s.sender = s.startInstanceSenders(false) 503 s.requests = nil 504 unitsDeployed := "mysql/0 wordpress/0" 505 params := makeStartInstanceParams(c, "quantal") 506 params.InstanceConfig.Tags[tags.JujuUnitsDeployed] = unitsDeployed 507 _, err := env.StartInstance(params) 508 c.Assert(err, jc.ErrorIsNil) 509 s.tags[tags.JujuUnitsDeployed] = &unitsDeployed 510 requests := s.assertStartInstanceRequests(c) 511 availabilitySetName := path.Base(requests.availabilitySet.URL.Path) 512 c.Assert(availabilitySetName, gc.Equals, "mysql") 513 } 514 515 func (s *environSuite) assertStartInstanceRequests(c *gc.C) startInstanceRequests { 516 // Clear the fields that don't get sent in the request. 517 s.publicIPAddress.ID = nil 518 s.publicIPAddress.Name = nil 519 s.publicIPAddress.Properties.IPAddress = nil 520 s.newNetworkInterface.ID = nil 521 s.newNetworkInterface.Name = nil 522 (*s.newNetworkInterface.Properties.IPConfigurations)[0].ID = nil 523 s.jujuAvailabilitySet.ID = nil 524 s.jujuAvailabilitySet.Name = nil 525 s.virtualMachine.ID = nil 526 s.virtualMachine.Name = nil 527 s.virtualMachine.Properties.ProvisioningState = nil 528 529 // Validate HTTP request bodies. 530 c.Assert(s.requests, gc.HasLen, 8) 531 c.Assert(s.requests[0].Method, gc.Equals, "GET") // vmSizes 532 c.Assert(s.requests[1].Method, gc.Equals, "GET") // juju-testenv-model-deadbeef-0bad-400d-8000-4b1d0d06f00d 533 c.Assert(s.requests[2].Method, gc.Equals, "GET") // skus 534 c.Assert(s.requests[3].Method, gc.Equals, "PUT") 535 assertRequestBody(c, s.requests[3], s.publicIPAddress) 536 c.Assert(s.requests[4].Method, gc.Equals, "GET") // NICs 537 c.Assert(s.requests[5].Method, gc.Equals, "PUT") 538 assertRequestBody(c, s.requests[5], s.newNetworkInterface) 539 c.Assert(s.requests[6].Method, gc.Equals, "PUT") 540 assertRequestBody(c, s.requests[6], s.jujuAvailabilitySet) 541 c.Assert(s.requests[7].Method, gc.Equals, "PUT") 542 543 // CustomData is non-deterministic, so don't compare it. 544 // TODO(axw) shouldn't CustomData be deterministic? Look into this. 545 var virtualMachine compute.VirtualMachine 546 unmarshalRequestBody(c, s.requests[7], &virtualMachine) 547 c.Assert(to.String(virtualMachine.Properties.OsProfile.CustomData), gc.Not(gc.HasLen), 0) 548 virtualMachine.Properties.OsProfile.CustomData = to.StringPtr("<juju-goes-here>") 549 c.Assert(&virtualMachine, jc.DeepEquals, s.virtualMachine) 550 551 return startInstanceRequests{ 552 vmSizes: s.requests[0], 553 subnet: s.requests[1], 554 skus: s.requests[2], 555 publicIPAddress: s.requests[3], 556 nics: s.requests[4], 557 networkInterface: s.requests[5], 558 availabilitySet: s.requests[6], 559 virtualMachine: s.requests[7], 560 } 561 } 562 563 type startInstanceRequests struct { 564 vmSizes *http.Request 565 subnet *http.Request 566 skus *http.Request 567 publicIPAddress *http.Request 568 nics *http.Request 569 networkInterface *http.Request 570 availabilitySet *http.Request 571 virtualMachine *http.Request 572 } 573 574 func (s *environSuite) TestBootstrap(c *gc.C) { 575 defer envtesting.DisableFinishBootstrap()() 576 577 ctx := envtesting.BootstrapContext(c) 578 env := prepareForBootstrap(c, ctx, s.provider, &s.sender) 579 580 s.sender = s.initResourceGroupSenders() 581 s.sender = append(s.sender, s.startInstanceSenders(true)...) 582 s.requests = nil 583 result, err := env.Bootstrap( 584 ctx, environs.BootstrapParams{ 585 AvailableTools: makeToolsList("trusty"), 586 }, 587 ) 588 c.Assert(err, jc.ErrorIsNil) 589 c.Assert(result.Arch, gc.Equals, "amd64") 590 c.Assert(result.Series, gc.Equals, "trusty") 591 592 c.Assert(len(s.requests), gc.Equals, 17) 593 594 c.Assert(s.requests[0].Method, gc.Equals, "PUT") // resource group 595 c.Assert(s.requests[1].Method, gc.Equals, "PUT") // vnet 596 c.Assert(s.requests[2].Method, gc.Equals, "PUT") // network security group 597 c.Assert(s.requests[3].Method, gc.Equals, "PUT") // subnet 598 c.Assert(s.requests[4].Method, gc.Equals, "POST") // check storage account name 599 c.Assert(s.requests[5].Method, gc.Equals, "PUT") // create storage account 600 c.Assert(s.requests[6].Method, gc.Equals, "POST") // get storage account keys 601 602 emptyTags := map[string]*string{} 603 assertRequestBody(c, s.requests[0], &resources.Group{ 604 Location: to.StringPtr("westus"), 605 Tags: &emptyTags, 606 }) 607 608 s.vnet.ID = nil 609 s.vnet.Name = nil 610 assertRequestBody(c, s.requests[1], s.vnet) 611 612 securityRules := []network.SecurityRule{{ 613 Name: to.StringPtr("SSHInbound"), 614 Properties: &network.SecurityRulePropertiesFormat{ 615 Description: to.StringPtr("Allow SSH access to all machines"), 616 Protocol: network.SecurityRuleProtocolTCP, 617 SourceAddressPrefix: to.StringPtr("*"), 618 SourcePortRange: to.StringPtr("*"), 619 DestinationAddressPrefix: to.StringPtr("*"), 620 DestinationPortRange: to.StringPtr("22"), 621 Access: network.Allow, 622 Priority: to.IntPtr(100), 623 Direction: network.Inbound, 624 }, 625 }} 626 assertRequestBody(c, s.requests[2], &network.SecurityGroup{ 627 Location: to.StringPtr("westus"), 628 Tags: &emptyTags, 629 Properties: &network.SecurityGroupPropertiesFormat{ 630 SecurityRules: &securityRules, 631 }, 632 }) 633 634 s.subnet.ID = nil 635 s.subnet.Name = nil 636 assertRequestBody(c, s.requests[3], s.subnet) 637 638 assertRequestBody(c, s.requests[4], &storage.AccountCheckNameAvailabilityParameters{ 639 Name: to.StringPtr(fakeStorageAccount), 640 Type: to.StringPtr("Microsoft.Storage/storageAccounts"), 641 }) 642 643 assertRequestBody(c, s.requests[5], &storage.AccountCreateParameters{ 644 Location: to.StringPtr("westus"), 645 Tags: &emptyTags, 646 Properties: &storage.AccountPropertiesCreateParameters{ 647 AccountType: "Standard_LRS", 648 }, 649 }) 650 } 651 652 func (s *environSuite) TestAllInstancesResourceGroupNotFound(c *gc.C) { 653 env := s.openEnviron(c) 654 sender := mocks.NewSender() 655 sender.EmitStatus("resource group not found", http.StatusNotFound) 656 s.sender = azuretesting.Senders{sender} 657 _, err := env.AllInstances() 658 c.Assert(err, jc.ErrorIsNil) 659 } 660 661 func (s *environSuite) TestStopInstancesNotFound(c *gc.C) { 662 env := s.openEnviron(c) 663 sender := mocks.NewSender() 664 sender.EmitStatus("vm not found", http.StatusNotFound) 665 s.sender = azuretesting.Senders{sender, sender, sender} 666 err := env.StopInstances("a", "b") 667 c.Assert(err, jc.ErrorIsNil) 668 } 669 670 func (s *environSuite) TestStopInstances(c *gc.C) { 671 env := s.openEnviron(c) 672 673 // Security group has rules for machine-0 but not machine-1, and 674 // has a rule that doesn't match either. 675 nsg := makeSecurityGroup( 676 makeSecurityRule("machine-0-80", "10.0.0.4", "80"), 677 makeSecurityRule("machine-0-1000-2000", "10.0.0.4", "1000-2000"), 678 makeSecurityRule("machine-42", "10.0.0.5", "*"), 679 ) 680 681 // Create an IP configuration with a public IP reference. This will 682 // cause an update to the NIC to detach public IPs. 683 nic0IPConfiguration := makeIPConfiguration("10.0.0.4") 684 nic0IPConfiguration.Properties.PublicIPAddress = &network.SubResource{} 685 nic0 := makeNetworkInterface("nic-0", "machine-0", nic0IPConfiguration) 686 687 s.sender = azuretesting.Senders{ 688 s.networkInterfacesSender( 689 nic0, 690 makeNetworkInterface("nic-1", "machine-1"), 691 makeNetworkInterface("nic-2", "machine-1"), 692 ), 693 s.virtualMachinesSender(makeVirtualMachine("machine-0")), 694 s.publicIPAddressesSender( 695 makePublicIPAddress("pip-0", "machine-0", "1.2.3.4"), 696 ), 697 s.makeSender(".*/virtualMachines/machine-0", nil), // DELETE 698 s.makeSender(".*/networkSecurityGroups/juju-internal", nsg), // GET 699 s.makeSender(".*/networkSecurityGroups/juju-internal/securityRules/machine-0-80", nil), // DELETE 700 s.makeSender(".*/networkSecurityGroups/juju-internal/securityRules/machine-0-1000-2000", nil), // DELETE 701 s.makeSender(".*/networkInterfaces/nic-0", nic0), // PUT 702 s.makeSender(".*/publicIPAddresses/pip-0", nil), // DELETE 703 s.makeSender(".*/networkInterfaces/nic-0", nil), // DELETE 704 s.makeSender(".*/virtualMachines/machine-1", nil), // DELETE 705 s.makeSender(".*/networkSecurityGroups/juju-internal", nsg), // GET 706 s.makeSender(".*/networkInterfaces/nic-1", nil), // DELETE 707 s.makeSender(".*/networkInterfaces/nic-2", nil), // DELETE 708 } 709 err := env.StopInstances("machine-0", "machine-1", "machine-2") 710 c.Assert(err, jc.ErrorIsNil) 711 712 s.storageClient.CheckCallNames(c, 713 "NewClient", "DeleteBlobIfExists", "DeleteBlobIfExists", 714 ) 715 s.storageClient.CheckCall(c, 1, "DeleteBlobIfExists", "osvhds", "machine-0") 716 s.storageClient.CheckCall(c, 2, "DeleteBlobIfExists", "osvhds", "machine-1") 717 } 718 719 func (s *environSuite) TestConstraintsValidatorUnsupported(c *gc.C) { 720 validator := s.constraintsValidator(c) 721 unsupported, err := validator.Validate(constraints.MustParse( 722 "arch=amd64 tags=foo cpu-power=100 virt-type=kvm", 723 )) 724 c.Assert(err, jc.ErrorIsNil) 725 c.Assert(unsupported, jc.SameContents, []string{"tags", "cpu-power", "virt-type"}) 726 } 727 728 func (s *environSuite) TestConstraintsValidatorVocabulary(c *gc.C) { 729 validator := s.constraintsValidator(c) 730 _, err := validator.Validate(constraints.MustParse("arch=armhf")) 731 c.Assert(err, gc.ErrorMatches, 732 "invalid constraint value: arch=armhf\nvalid values are: \\[amd64\\]", 733 ) 734 _, err = validator.Validate(constraints.MustParse("instance-type=t1.micro")) 735 c.Assert(err, gc.ErrorMatches, 736 "invalid constraint value: instance-type=t1.micro\nvalid values are: \\[D1 Standard_D1\\]", 737 ) 738 } 739 740 func (s *environSuite) TestConstraintsValidatorMerge(c *gc.C) { 741 validator := s.constraintsValidator(c) 742 cons, err := validator.Merge( 743 constraints.MustParse("mem=3G arch=amd64"), 744 constraints.MustParse("instance-type=D1"), 745 ) 746 c.Assert(err, jc.ErrorIsNil) 747 c.Assert(cons.String(), gc.Equals, "instance-type=D1") 748 } 749 750 func (s *environSuite) constraintsValidator(c *gc.C) constraints.Validator { 751 env := s.openEnviron(c) 752 s.sender = azuretesting.Senders{s.vmSizesSender()} 753 validator, err := env.ConstraintsValidator() 754 c.Assert(err, jc.ErrorIsNil) 755 return validator 756 } 757 758 func (s *environSuite) TestAgentMirror(c *gc.C) { 759 env := s.openEnviron(c) 760 c.Assert(env, gc.Implements, new(envtools.HasAgentMirror)) 761 cloudSpec, err := env.(envtools.HasAgentMirror).AgentMirror() 762 c.Assert(err, jc.ErrorIsNil) 763 c.Assert(cloudSpec, gc.Equals, simplestreams.CloudSpec{ 764 Region: "westus", 765 Endpoint: "https://storage.azurestack.local/", 766 }) 767 }