github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/provider/azure/environ.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package azure 5 6 import ( 7 "fmt" 8 "net/http" 9 "net/url" 10 "sort" 11 "strings" 12 "sync" 13 14 "github.com/Azure/azure-sdk-for-go/arm/compute" 15 "github.com/Azure/azure-sdk-for-go/arm/network" 16 "github.com/Azure/azure-sdk-for-go/arm/resources/resources" 17 "github.com/Azure/azure-sdk-for-go/arm/storage" 18 azurestorage "github.com/Azure/azure-sdk-for-go/storage" 19 "github.com/Azure/go-autorest/autorest" 20 "github.com/Azure/go-autorest/autorest/to" 21 "github.com/juju/errors" 22 "github.com/juju/loggo" 23 "github.com/juju/utils/arch" 24 "github.com/juju/utils/os" 25 jujuseries "github.com/juju/utils/series" 26 "gopkg.in/juju/names.v2" 27 28 "github.com/juju/juju/cloudconfig/instancecfg" 29 "github.com/juju/juju/cloudconfig/providerinit" 30 "github.com/juju/juju/constraints" 31 "github.com/juju/juju/environs" 32 "github.com/juju/juju/environs/config" 33 "github.com/juju/juju/environs/instances" 34 "github.com/juju/juju/environs/simplestreams" 35 "github.com/juju/juju/environs/tags" 36 "github.com/juju/juju/instance" 37 jujunetwork "github.com/juju/juju/network" 38 "github.com/juju/juju/provider/azure/internal/armtemplates" 39 internalazurestorage "github.com/juju/juju/provider/azure/internal/azurestorage" 40 "github.com/juju/juju/provider/azure/internal/errorutils" 41 "github.com/juju/juju/provider/azure/internal/tracing" 42 "github.com/juju/juju/provider/common" 43 "github.com/juju/juju/state" 44 "github.com/juju/juju/tools" 45 ) 46 47 const ( 48 jujuMachineNameTag = tags.JujuTagPrefix + "machine-name" 49 50 // defaultRootDiskSize is the default root disk size to give 51 // to a VM, if none is specified. 52 defaultRootDiskSize = 30 * 1024 // 30 GiB 53 54 // serviceErrorCodeDeploymentCannotBeCancelled is the error code for 55 // service errors in response to an attempt to cancel a deployment 56 // that cannot be cancelled. 57 serviceErrorCodeDeploymentCannotBeCancelled = "DeploymentCannotBeCancelled" 58 59 // controllerAvailabilitySet is the name of the availability set 60 // used for controller machines. 61 controllerAvailabilitySet = "juju-controller" 62 ) 63 64 type azureEnviron struct { 65 // provider is the azureEnvironProvider used to open this environment. 66 provider *azureEnvironProvider 67 68 // cloud defines the cloud configuration for this environment. 69 cloud environs.CloudSpec 70 71 // location is the canonicalized location name. Use this instead 72 // of cloud.Region in API calls. 73 location string 74 75 // subscriptionId is the Azure account subscription ID. 76 subscriptionId string 77 78 // storageEndpoint is the Azure storage endpoint. This is the host 79 // portion of the storage endpoint URL only; use this instead of 80 // cloud.StorageEndpoint in API calls. 81 storageEndpoint string 82 83 // resourceGroup is the name of the Resource Group in the Azure 84 // subscription that corresponds to the environment. 85 resourceGroup string 86 87 // envName is the name of the environment. 88 envName string 89 90 // authorizer is the authorizer we use for Azure. 91 authorizer *cloudSpecAuth 92 93 compute compute.ManagementClient 94 resources resources.ManagementClient 95 storage storage.ManagementClient 96 network network.ManagementClient 97 storageClient azurestorage.Client 98 storageAccountName string 99 100 mu sync.Mutex 101 config *azureModelConfig 102 instanceTypes map[string]instances.InstanceType 103 storageAccount *storage.Account 104 storageAccountKey *storage.AccountKey 105 } 106 107 var _ environs.Environ = (*azureEnviron)(nil) 108 var _ state.Prechecker = (*azureEnviron)(nil) 109 110 // newEnviron creates a new azureEnviron. 111 func newEnviron( 112 provider *azureEnvironProvider, 113 cloud environs.CloudSpec, 114 cfg *config.Config, 115 ) (*azureEnviron, error) { 116 117 // The Azure storage code wants the endpoint host only, not the URL. 118 storageEndpointURL, err := url.Parse(cloud.StorageEndpoint) 119 if err != nil { 120 return nil, errors.Annotate(err, "parsing storage endpoint URL") 121 } 122 123 env := azureEnviron{ 124 provider: provider, 125 cloud: cloud, 126 location: canonicalLocation(cloud.Region), 127 storageEndpoint: storageEndpointURL.Host, 128 } 129 if err := env.initEnviron(); err != nil { 130 return nil, errors.Trace(err) 131 } 132 133 if err := env.SetConfig(cfg); err != nil { 134 return nil, errors.Trace(err) 135 } 136 137 modelTag := names.NewModelTag(cfg.UUID()) 138 env.resourceGroup = resourceGroupName(modelTag, cfg.Name()) 139 env.envName = cfg.Name() 140 141 // We need a deterministic storage account name, so that we can 142 // defer creation of the storage account to the VM deployment, 143 // and retain the ability to create multiple deployments in 144 // parallel. 145 // 146 // We use the last 20 non-hyphen hex characters of the model's 147 // UUID as the storage account name, prefixed with "juju". The 148 // probability of clashing with another storage account should 149 // be negligible. 150 uuidAlphaNumeric := strings.Replace(env.config.Config.UUID(), "-", "", -1) 151 env.storageAccountName = "juju" + uuidAlphaNumeric[len(uuidAlphaNumeric)-20:] 152 153 return &env, nil 154 } 155 156 func (env *azureEnviron) initEnviron() error { 157 credAttrs := env.cloud.Credential.Attributes() 158 env.subscriptionId = credAttrs[credAttrSubscriptionId] 159 env.authorizer = &cloudSpecAuth{ 160 cloud: env.cloud, 161 sender: env.provider.config.Sender, 162 } 163 164 env.compute = compute.NewWithBaseURI(env.cloud.Endpoint, env.subscriptionId) 165 env.resources = resources.NewWithBaseURI(env.cloud.Endpoint, env.subscriptionId) 166 env.storage = storage.NewWithBaseURI(env.cloud.Endpoint, env.subscriptionId) 167 env.network = network.NewWithBaseURI(env.cloud.Endpoint, env.subscriptionId) 168 clients := map[string]*autorest.Client{ 169 "azure.compute": &env.compute.Client, 170 "azure.resources": &env.resources.Client, 171 "azure.storage": &env.storage.Client, 172 "azure.network": &env.network.Client, 173 } 174 for id, client := range clients { 175 client.Authorizer = env.authorizer 176 logger := loggo.GetLogger(id) 177 if env.provider.config.Sender != nil { 178 client.Sender = env.provider.config.Sender 179 } 180 client.ResponseInspector = tracing.RespondDecorator(logger) 181 client.RequestInspector = tracing.PrepareDecorator(logger) 182 if env.provider.config.RequestInspector != nil { 183 tracer := client.RequestInspector 184 inspector := env.provider.config.RequestInspector 185 client.RequestInspector = func(p autorest.Preparer) autorest.Preparer { 186 p = tracer(p) 187 p = inspector(p) 188 return p 189 } 190 } 191 } 192 return nil 193 } 194 195 // PrepareForBootstrap is part of the Environ interface. 196 func (env *azureEnviron) PrepareForBootstrap(ctx environs.BootstrapContext) error { 197 if ctx.ShouldVerifyCredentials() { 198 if err := verifyCredentials(env); err != nil { 199 return errors.Trace(err) 200 } 201 } 202 return nil 203 } 204 205 // Create is part of the Environ interface. 206 func (env *azureEnviron) Create(args environs.CreateParams) error { 207 if err := verifyCredentials(env); err != nil { 208 return errors.Trace(err) 209 } 210 return errors.Trace(env.initResourceGroup(args.ControllerUUID)) 211 } 212 213 // Bootstrap is part of the Environ interface. 214 func (env *azureEnviron) Bootstrap( 215 ctx environs.BootstrapContext, 216 args environs.BootstrapParams, 217 ) (*environs.BootstrapResult, error) { 218 if err := env.initResourceGroup(args.ControllerConfig.ControllerUUID()); err != nil { 219 return nil, errors.Annotate(err, "creating controller resource group") 220 } 221 result, err := common.Bootstrap(ctx, env, args) 222 if err != nil { 223 logger.Errorf("bootstrap failed, destroying model: %v", err) 224 if err := env.Destroy(); err != nil { 225 logger.Errorf("failed to destroy model: %v", err) 226 } 227 return nil, errors.Trace(err) 228 } 229 return result, nil 230 } 231 232 // initResourceGroup creates a resource group for this environment. 233 func (env *azureEnviron) initResourceGroup(controllerUUID string) error { 234 location := env.location 235 resourceGroupsClient := resources.GroupsClient{env.resources} 236 237 env.mu.Lock() 238 tags := tags.ResourceTags( 239 names.NewModelTag(env.config.Config.UUID()), 240 names.NewControllerTag(controllerUUID), 241 env.config, 242 ) 243 env.mu.Unlock() 244 245 logger.Debugf("creating resource group %q", env.resourceGroup) 246 err := env.callAPI(func() (autorest.Response, error) { 247 group, err := resourceGroupsClient.CreateOrUpdate(env.resourceGroup, resources.ResourceGroup{ 248 Location: to.StringPtr(location), 249 Tags: to.StringMapPtr(tags), 250 }) 251 return group.Response, err 252 }) 253 return errors.Annotate(err, "creating resource group") 254 } 255 256 // ControllerInstances is specified in the Environ interface. 257 func (env *azureEnviron) ControllerInstances(controllerUUID string) ([]instance.Id, error) { 258 instances, err := env.allInstances(env.resourceGroup, false, true) 259 if err != nil { 260 return nil, err 261 } 262 if len(instances) == 0 { 263 return nil, environs.ErrNoInstances 264 } 265 ids := make([]instance.Id, len(instances)) 266 for i, inst := range instances { 267 ids[i] = inst.Id() 268 } 269 return ids, nil 270 } 271 272 // Config is specified in the Environ interface. 273 func (env *azureEnviron) Config() *config.Config { 274 env.mu.Lock() 275 defer env.mu.Unlock() 276 return env.config.Config 277 } 278 279 // SetConfig is specified in the Environ interface. 280 func (env *azureEnviron) SetConfig(cfg *config.Config) error { 281 env.mu.Lock() 282 defer env.mu.Unlock() 283 284 var old *config.Config 285 if env.config != nil { 286 old = env.config.Config 287 } 288 ecfg, err := validateConfig(cfg, old) 289 if err != nil { 290 return err 291 } 292 env.config = ecfg 293 294 return nil 295 } 296 297 // ConstraintsValidator is defined on the Environs interface. 298 func (env *azureEnviron) ConstraintsValidator() (constraints.Validator, error) { 299 instanceTypes, err := env.getInstanceTypes() 300 if err != nil { 301 return nil, err 302 } 303 instTypeNames := make([]string, 0, len(instanceTypes)) 304 for instTypeName := range instanceTypes { 305 instTypeNames = append(instTypeNames, instTypeName) 306 } 307 sort.Strings(instTypeNames) 308 309 validator := constraints.NewValidator() 310 validator.RegisterUnsupported([]string{ 311 constraints.CpuPower, 312 constraints.Tags, 313 constraints.VirtType, 314 }) 315 validator.RegisterVocabulary( 316 constraints.Arch, 317 []string{arch.AMD64}, 318 ) 319 validator.RegisterVocabulary( 320 constraints.InstanceType, 321 instTypeNames, 322 ) 323 validator.RegisterConflicts( 324 []string{constraints.InstanceType}, 325 []string{ 326 constraints.Mem, 327 constraints.Cores, 328 constraints.Arch, 329 }, 330 ) 331 return validator, nil 332 } 333 334 // PrecheckInstance is defined on the state.Prechecker interface. 335 func (env *azureEnviron) PrecheckInstance(series string, cons constraints.Value, placement string) error { 336 if placement != "" { 337 return fmt.Errorf("unknown placement directive: %s", placement) 338 } 339 if !cons.HasInstanceType() { 340 return nil 341 } 342 // Constraint has an instance-type constraint so let's see if it is valid. 343 instanceTypes, err := env.getInstanceTypes() 344 if err != nil { 345 return err 346 } 347 for _, instanceType := range instanceTypes { 348 if instanceType.Name == *cons.InstanceType { 349 return nil 350 } 351 } 352 return fmt.Errorf("invalid instance type %q", *cons.InstanceType) 353 } 354 355 // MaintainInstance is specified in the InstanceBroker interface. 356 func (*azureEnviron) MaintainInstance(args environs.StartInstanceParams) error { 357 return nil 358 } 359 360 // StartInstance is specified in the InstanceBroker interface. 361 func (env *azureEnviron) StartInstance(args environs.StartInstanceParams) (*environs.StartInstanceResult, error) { 362 if args.ControllerUUID == "" { 363 return nil, errors.New("missing controller UUID") 364 } 365 366 // Get the required configuration and config-dependent information 367 // required to create the instance. We take the lock just once, to 368 // ensure we obtain all information based on the same configuration. 369 env.mu.Lock() 370 envTags := tags.ResourceTags( 371 names.NewModelTag(env.config.Config.UUID()), 372 names.NewControllerTag(args.ControllerUUID), 373 env.config, 374 ) 375 storageAccountType := env.config.storageAccountType 376 imageStream := env.config.ImageStream() 377 instanceTypes, err := env.getInstanceTypesLocked() 378 if err != nil { 379 env.mu.Unlock() 380 return nil, errors.Trace(err) 381 } 382 env.mu.Unlock() 383 384 // If the user has not specified a root-disk size, then 385 // set a sensible default. 386 var rootDisk uint64 387 if args.Constraints.RootDisk != nil { 388 rootDisk = *args.Constraints.RootDisk 389 } else { 390 rootDisk = defaultRootDiskSize 391 args.Constraints.RootDisk = &rootDisk 392 } 393 394 // Identify the instance type and image to provision. 395 series := args.Tools.OneSeries() 396 instanceSpec, err := findInstanceSpec( 397 compute.VirtualMachineImagesClient{env.compute}, 398 instanceTypes, 399 &instances.InstanceConstraint{ 400 Region: env.location, 401 Series: series, 402 Arches: args.Tools.Arches(), 403 Constraints: args.Constraints, 404 }, 405 imageStream, 406 ) 407 if err != nil { 408 return nil, err 409 } 410 if rootDisk < uint64(instanceSpec.InstanceType.RootDisk) { 411 // The InstanceType's RootDisk is set to the maximum 412 // OS disk size; override it with the user-specified 413 // or default root disk size. 414 instanceSpec.InstanceType.RootDisk = rootDisk 415 } 416 417 // Windows images are 127GiB, and cannot be made smaller. 418 const windowsMinRootDiskMB = 127 * 1024 419 seriesOS, err := jujuseries.GetOSFromSeries(series) 420 if err != nil { 421 return nil, errors.Trace(err) 422 } 423 if seriesOS == os.Windows { 424 if instanceSpec.InstanceType.RootDisk < windowsMinRootDiskMB { 425 instanceSpec.InstanceType.RootDisk = windowsMinRootDiskMB 426 } 427 } 428 429 // Pick tools by filtering the available tools down to the architecture of 430 // the image that will be provisioned. 431 selectedTools, err := args.Tools.Match(tools.Filter{ 432 Arch: instanceSpec.Image.Arch, 433 }) 434 if err != nil { 435 return nil, errors.Trace(err) 436 } 437 logger.Infof("picked tools %q", selectedTools[0].Version) 438 439 // Finalize the instance config, which we'll render to CustomData below. 440 if err := args.InstanceConfig.SetTools(selectedTools); err != nil { 441 return nil, errors.Trace(err) 442 } 443 if err := instancecfg.FinishInstanceConfig( 444 args.InstanceConfig, env.Config(), 445 ); err != nil { 446 return nil, err 447 } 448 449 machineTag := names.NewMachineTag(args.InstanceConfig.MachineId) 450 vmName := resourceName(machineTag) 451 vmTags := make(map[string]string) 452 for k, v := range args.InstanceConfig.Tags { 453 vmTags[k] = v 454 } 455 // jujuMachineNameTag identifies the VM name, in which is encoded 456 // the Juju machine name. We tag all resources related to the 457 // machine with this. 458 vmTags[jujuMachineNameTag] = vmName 459 460 if err := env.createVirtualMachine( 461 vmName, vmTags, envTags, 462 instanceSpec, args.InstanceConfig, 463 storageAccountType, 464 ); err != nil { 465 logger.Errorf("creating instance failed, destroying: %v", err) 466 if err := env.StopInstances(instance.Id(vmName)); err != nil { 467 logger.Errorf("could not destroy failed virtual machine: %v", err) 468 } 469 return nil, errors.Annotatef(err, "creating virtual machine %q", vmName) 470 } 471 472 // Note: the instance is initialised without addresses to keep the 473 // API chatter down. We will refresh the instance if we need to know 474 // the addresses. 475 inst := &azureInstance{vmName, "Creating", env, nil, nil} 476 amd64 := arch.AMD64 477 hc := &instance.HardwareCharacteristics{ 478 Arch: &amd64, 479 Mem: &instanceSpec.InstanceType.Mem, 480 RootDisk: &instanceSpec.InstanceType.RootDisk, 481 CpuCores: &instanceSpec.InstanceType.CpuCores, 482 } 483 return &environs.StartInstanceResult{ 484 Instance: inst, 485 Hardware: hc, 486 }, nil 487 } 488 489 // createVirtualMachine creates a virtual machine and related resources. 490 // 491 // All resources created are tagged with the specified "vmTags", so if 492 // this function fails then all resources can be deleted by tag. 493 func (env *azureEnviron) createVirtualMachine( 494 vmName string, 495 vmTags, envTags map[string]string, 496 instanceSpec *instances.InstanceSpec, 497 instanceConfig *instancecfg.InstanceConfig, 498 storageAccountType string, 499 ) error { 500 501 deploymentsClient := resources.DeploymentsClient{env.resources} 502 503 var apiPort int 504 if instanceConfig.Controller != nil { 505 apiPortValue := instanceConfig.Controller.Config.APIPort() 506 apiPort = apiPortValue 507 } else { 508 apiPorts := instanceConfig.APIInfo.Ports() 509 if len(apiPorts) != 1 { 510 return errors.Errorf("expected one API port, found %v", apiPorts) 511 } 512 apiPort = apiPorts[0] 513 } 514 resources := networkTemplateResources(env.location, envTags, apiPort) 515 resources = append(resources, storageAccountTemplateResource( 516 env.location, envTags, 517 env.storageAccountName, storageAccountType, 518 )) 519 520 osProfile, seriesOS, err := newOSProfile( 521 vmName, instanceConfig, 522 env.provider.config.RandomWindowsAdminPassword, 523 ) 524 if err != nil { 525 return errors.Annotate(err, "creating OS profile") 526 } 527 storageProfile, err := newStorageProfile(vmName, env.storageAccountName, instanceSpec) 528 if err != nil { 529 return errors.Annotate(err, "creating storage profile") 530 } 531 532 var vmDependsOn []string 533 var availabilitySetSubResource *compute.SubResource 534 availabilitySetName, err := availabilitySetName( 535 vmName, vmTags, instanceConfig.Controller != nil, 536 ) 537 if err != nil { 538 return errors.Annotate(err, "getting availability set name") 539 } 540 if availabilitySetName != "" { 541 availabilitySetId := fmt.Sprintf( 542 `[resourceId('Microsoft.Compute/availabilitySets','%s')]`, 543 availabilitySetName, 544 ) 545 resources = append(resources, armtemplates.Resource{ 546 APIVersion: compute.APIVersion, 547 Type: "Microsoft.Compute/availabilitySets", 548 Name: availabilitySetName, 549 Location: env.location, 550 Tags: envTags, 551 }) 552 availabilitySetSubResource = &compute.SubResource{ 553 ID: to.StringPtr(availabilitySetId), 554 } 555 vmDependsOn = append(vmDependsOn, availabilitySetId) 556 } 557 558 publicIPAddressName := vmName + "-public-ip" 559 publicIPAddressId := fmt.Sprintf(`[resourceId('Microsoft.Network/publicIPAddresses', '%s')]`, publicIPAddressName) 560 resources = append(resources, armtemplates.Resource{ 561 APIVersion: network.APIVersion, 562 Type: "Microsoft.Network/publicIPAddresses", 563 Name: publicIPAddressName, 564 Location: env.location, 565 Tags: vmTags, 566 Properties: &network.PublicIPAddressPropertiesFormat{ 567 PublicIPAllocationMethod: network.Dynamic, 568 }, 569 }) 570 571 // Controller and non-controller machines are assigned to separate 572 // subnets. This enables us to create controller-specific NSG rules 573 // just by targeting the controller subnet. 574 subnetName := internalSubnetName 575 subnetPrefix := internalSubnetPrefix 576 if instanceConfig.Controller != nil { 577 subnetName = controllerSubnetName 578 subnetPrefix = controllerSubnetPrefix 579 } 580 subnetId := fmt.Sprintf( 581 `[concat(resourceId('Microsoft.Network/virtualNetworks', '%s'), '/subnets/%s')]`, 582 internalNetworkName, subnetName, 583 ) 584 585 privateIP, err := machineSubnetIP(subnetPrefix, instanceConfig.MachineId) 586 if err != nil { 587 return errors.Annotatef(err, "computing private IP address") 588 } 589 nicName := vmName + "-primary" 590 nicId := fmt.Sprintf(`[resourceId('Microsoft.Network/networkInterfaces', '%s')]`, nicName) 591 ipConfigurations := []network.InterfaceIPConfiguration{{ 592 Name: to.StringPtr("primary"), 593 Properties: &network.InterfaceIPConfigurationPropertiesFormat{ 594 Primary: to.BoolPtr(true), 595 PrivateIPAddress: to.StringPtr(privateIP.String()), 596 PrivateIPAllocationMethod: network.Static, 597 Subnet: &network.Subnet{ID: to.StringPtr(subnetId)}, 598 PublicIPAddress: &network.PublicIPAddress{ 599 ID: to.StringPtr(publicIPAddressId), 600 }, 601 }, 602 }} 603 resources = append(resources, armtemplates.Resource{ 604 APIVersion: network.APIVersion, 605 Type: "Microsoft.Network/networkInterfaces", 606 Name: nicName, 607 Location: env.location, 608 Tags: vmTags, 609 Properties: &network.InterfacePropertiesFormat{ 610 IPConfigurations: &ipConfigurations, 611 }, 612 DependsOn: []string{ 613 publicIPAddressId, 614 fmt.Sprintf( 615 `[resourceId('Microsoft.Network/virtualNetworks', '%s')]`, 616 internalNetworkName, 617 ), 618 }, 619 }) 620 621 nics := []compute.NetworkInterfaceReference{{ 622 ID: to.StringPtr(nicId), 623 Properties: &compute.NetworkInterfaceReferenceProperties{ 624 Primary: to.BoolPtr(true), 625 }, 626 }} 627 vmDependsOn = append(vmDependsOn, nicId) 628 vmDependsOn = append(vmDependsOn, fmt.Sprintf( 629 `[resourceId('Microsoft.Storage/storageAccounts', '%s')]`, 630 env.storageAccountName, 631 )) 632 resources = append(resources, armtemplates.Resource{ 633 APIVersion: compute.APIVersion, 634 Type: "Microsoft.Compute/virtualMachines", 635 Name: vmName, 636 Location: env.location, 637 Tags: vmTags, 638 Properties: &compute.VirtualMachineProperties{ 639 HardwareProfile: &compute.HardwareProfile{ 640 VMSize: compute.VirtualMachineSizeTypes( 641 instanceSpec.InstanceType.Name, 642 ), 643 }, 644 StorageProfile: storageProfile, 645 OsProfile: osProfile, 646 NetworkProfile: &compute.NetworkProfile{ 647 &nics, 648 }, 649 AvailabilitySet: availabilitySetSubResource, 650 }, 651 DependsOn: vmDependsOn, 652 }) 653 654 // On Windows and CentOS, we must add the CustomScript VM 655 // extension to run the CustomData script. 656 switch seriesOS { 657 case os.Windows, os.CentOS: 658 properties, err := vmExtensionProperties(seriesOS) 659 if err != nil { 660 return errors.Annotate( 661 err, "creating virtual machine extension", 662 ) 663 } 664 resources = append(resources, armtemplates.Resource{ 665 APIVersion: compute.APIVersion, 666 Type: "Microsoft.Compute/virtualMachines/extensions", 667 Name: vmName + "/" + extensionName, 668 Location: env.location, 669 Tags: vmTags, 670 Properties: properties, 671 DependsOn: []string{"Microsoft.Compute/virtualMachines/" + vmName}, 672 }) 673 } 674 675 logger.Debugf("- creating virtual machine deployment") 676 template := armtemplates.Template{Resources: resources} 677 // NOTE(axw) VMs take a long time to go to "Succeeded", so we do not 678 // block waiting for them to be fully provisioned. This means we won't 679 // return an error from StartInstance if the VM fails provisioning; 680 // we will instead report the error via the instance's status. 681 deploymentsClient.ResponseInspector = asyncCreationRespondDecorator( 682 deploymentsClient.ResponseInspector, 683 ) 684 if err := createDeployment( 685 env.callAPI, 686 deploymentsClient, 687 env.resourceGroup, 688 vmName, // deployment name 689 template, 690 ); err != nil { 691 return errors.Trace(err) 692 } 693 return nil 694 } 695 696 // createAvailabilitySet creates the availability set for a machine to use 697 // if it doesn't already exist, and returns the availability set's ID. The 698 // algorithm used for choosing the availability set is: 699 // - if the machine is a controller, use the availability set name 700 // "juju-controller"; 701 // - if the machine has units assigned, create an availability 702 // name with a name based on the value of the tags.JujuUnitsDeployed tag 703 // in vmTags, if it exists; 704 // - otherwise, do not assign the machine to an availability set 705 func availabilitySetName( 706 vmName string, 707 vmTags map[string]string, 708 controller bool, 709 ) (string, error) { 710 logger.Debugf("selecting availability set for %q", vmName) 711 if controller { 712 return controllerAvailabilitySet, nil 713 } 714 715 // We'll have to create an availability set. Use the name of one of the 716 // services assigned to the machine. 717 var availabilitySetName string 718 if unitNames, ok := vmTags[tags.JujuUnitsDeployed]; ok { 719 for _, unitName := range strings.Fields(unitNames) { 720 if !names.IsValidUnit(unitName) { 721 continue 722 } 723 serviceName, err := names.UnitApplication(unitName) 724 if err != nil { 725 return "", errors.Annotate(err, "getting service name") 726 } 727 availabilitySetName = serviceName 728 break 729 } 730 } 731 return availabilitySetName, nil 732 } 733 734 // newStorageProfile creates the storage profile for a virtual machine, 735 // based on the series and chosen instance spec. 736 func newStorageProfile( 737 vmName string, 738 storageAccountName string, 739 instanceSpec *instances.InstanceSpec, 740 ) (*compute.StorageProfile, error) { 741 logger.Debugf("creating storage profile for %q", vmName) 742 743 urnParts := strings.SplitN(instanceSpec.Image.Id, ":", 4) 744 if len(urnParts) != 4 { 745 return nil, errors.Errorf("invalid image ID %q", instanceSpec.Image.Id) 746 } 747 publisher := urnParts[0] 748 offer := urnParts[1] 749 sku := urnParts[2] 750 version := urnParts[3] 751 752 osDisksRoot := fmt.Sprintf( 753 `reference(resourceId('Microsoft.Storage/storageAccounts', '%s'), '%s').primaryEndpoints.blob`, 754 storageAccountName, storage.APIVersion, 755 ) 756 osDiskName := vmName 757 osDiskURI := fmt.Sprintf( 758 `[concat(%s, '%s/%s%s')]`, 759 osDisksRoot, osDiskVHDContainer, osDiskName, vhdExtension, 760 ) 761 osDiskSizeGB := mibToGB(instanceSpec.InstanceType.RootDisk) 762 osDisk := &compute.OSDisk{ 763 Name: to.StringPtr(osDiskName), 764 CreateOption: compute.FromImage, 765 Caching: compute.ReadWrite, 766 Vhd: &compute.VirtualHardDisk{URI: to.StringPtr(osDiskURI)}, 767 DiskSizeGB: to.Int32Ptr(int32(osDiskSizeGB)), 768 } 769 return &compute.StorageProfile{ 770 ImageReference: &compute.ImageReference{ 771 Publisher: to.StringPtr(publisher), 772 Offer: to.StringPtr(offer), 773 Sku: to.StringPtr(sku), 774 Version: to.StringPtr(version), 775 }, 776 OsDisk: osDisk, 777 }, nil 778 } 779 780 func mibToGB(mib uint64) uint64 { 781 b := float64(mib * 1024 * 1024) 782 return uint64(b / (1000 * 1000 * 1000)) 783 } 784 785 func newOSProfile( 786 vmName string, 787 instanceConfig *instancecfg.InstanceConfig, 788 randomAdminPassword func() string, 789 ) (*compute.OSProfile, os.OSType, error) { 790 logger.Debugf("creating OS profile for %q", vmName) 791 792 customData, err := providerinit.ComposeUserData(instanceConfig, nil, AzureRenderer{}) 793 if err != nil { 794 return nil, os.Unknown, errors.Annotate(err, "composing user data") 795 } 796 797 osProfile := &compute.OSProfile{ 798 ComputerName: to.StringPtr(vmName), 799 CustomData: to.StringPtr(string(customData)), 800 } 801 802 seriesOS, err := jujuseries.GetOSFromSeries(instanceConfig.Series) 803 if err != nil { 804 return nil, os.Unknown, errors.Trace(err) 805 } 806 switch seriesOS { 807 case os.Ubuntu, os.CentOS: 808 // SSH keys are handled by custom data, but must also be 809 // specified in order to forego providing a password, and 810 // disable password authentication. 811 publicKeys := []compute.SSHPublicKey{{ 812 Path: to.StringPtr("/home/ubuntu/.ssh/authorized_keys"), 813 KeyData: to.StringPtr(instanceConfig.AuthorizedKeys), 814 }} 815 osProfile.AdminUsername = to.StringPtr("ubuntu") 816 osProfile.LinuxConfiguration = &compute.LinuxConfiguration{ 817 DisablePasswordAuthentication: to.BoolPtr(true), 818 SSH: &compute.SSHConfiguration{PublicKeys: &publicKeys}, 819 } 820 case os.Windows: 821 osProfile.AdminUsername = to.StringPtr("JujuAdministrator") 822 // A password is required by Azure, but we will never use it. 823 // We generate something sufficiently long and random that it 824 // should be infeasible to guess. 825 osProfile.AdminPassword = to.StringPtr(randomAdminPassword()) 826 osProfile.WindowsConfiguration = &compute.WindowsConfiguration{ 827 ProvisionVMAgent: to.BoolPtr(true), 828 EnableAutomaticUpdates: to.BoolPtr(true), 829 // TODO(?) add WinRM configuration here. 830 } 831 default: 832 return nil, os.Unknown, errors.NotSupportedf("%s", seriesOS) 833 } 834 return osProfile, seriesOS, nil 835 } 836 837 // StopInstances is specified in the InstanceBroker interface. 838 func (env *azureEnviron) StopInstances(ids ...instance.Id) error { 839 if len(ids) == 0 { 840 return nil 841 } 842 843 // First up, cancel the deployments. Then we can identify the resources 844 // that need to be deleted without racing with their creation. 845 var wg sync.WaitGroup 846 var existing int 847 cancelResults := make([]error, len(ids)) 848 for i, id := range ids { 849 logger.Debugf("canceling deployment for instance %q", id) 850 wg.Add(1) 851 go func(i int, id instance.Id) { 852 defer wg.Done() 853 cancelResults[i] = errors.Annotatef( 854 env.cancelDeployment(string(id)), 855 "canceling deployment %q", id, 856 ) 857 }(i, id) 858 } 859 wg.Wait() 860 for _, err := range cancelResults { 861 if err == nil { 862 existing++ 863 } else if !errors.IsNotFound(err) { 864 return err 865 } 866 } 867 if existing == 0 { 868 // None of the instances exist, so we can stop now. 869 return nil 870 } 871 872 maybeStorageClient, err := env.getStorageClient() 873 if errors.IsNotFound(err) { 874 // It is possible, if unlikely, that the first deployment for a 875 // hosted model will fail or be canceled before the model's 876 // storage account is created. We must therefore cater for the 877 // account being missing or incomplete here. 878 maybeStorageClient = nil 879 } else if err != nil { 880 return errors.Trace(err) 881 } 882 883 // List network interfaces and public IP addresses. 884 instanceNics, err := instanceNetworkInterfaces( 885 env.callAPI, env.resourceGroup, 886 network.InterfacesClient{env.network}, 887 ) 888 if err != nil { 889 return errors.Trace(err) 890 } 891 instancePips, err := instancePublicIPAddresses( 892 env.callAPI, env.resourceGroup, 893 network.PublicIPAddressesClient{env.network}, 894 ) 895 if err != nil { 896 return errors.Trace(err) 897 } 898 899 // Delete the deployments, virtual machines, and related resources. 900 deleteResults := make([]error, len(ids)) 901 for i, id := range ids { 902 if errors.IsNotFound(cancelResults[i]) { 903 continue 904 } 905 // The deployment does not exist, so there's nothing more to do. 906 logger.Debugf("deleting instance %q", id) 907 wg.Add(1) 908 go func(i int, id instance.Id) { 909 defer wg.Done() 910 err := env.deleteVirtualMachine( 911 id, 912 maybeStorageClient, 913 instanceNics[id], 914 instancePips[id], 915 ) 916 deleteResults[i] = errors.Annotatef( 917 err, "deleting instance %q", id, 918 ) 919 }(i, id) 920 } 921 wg.Wait() 922 for _, err := range deleteResults { 923 if err != nil && !errors.IsNotFound(err) { 924 return errors.Trace(err) 925 } 926 } 927 928 return nil 929 } 930 931 // cancelDeployment cancels a template deployment. 932 func (env *azureEnviron) cancelDeployment(name string) error { 933 deploymentsClient := resources.DeploymentsClient{env.resources} 934 logger.Debugf("- canceling deployment %q", name) 935 var cancelResult autorest.Response 936 if err := env.callAPI(func() (autorest.Response, error) { 937 var err error 938 cancelResult, err = deploymentsClient.Cancel(env.resourceGroup, name) 939 return cancelResult, err 940 }); err != nil { 941 if cancelResult.Response != nil { 942 switch cancelResult.StatusCode { 943 case http.StatusNotFound: 944 return errors.NewNotFound(err, fmt.Sprintf("deployment %q not found", name)) 945 case http.StatusConflict: 946 if err, ok := errorutils.ServiceError(err); ok { 947 if err.Code == serviceErrorCodeDeploymentCannotBeCancelled { 948 // Deployments can only canceled while they're running. 949 return nil 950 } 951 } 952 } 953 } 954 return errors.Annotatef(err, "canceling deployment %q", name) 955 } 956 return nil 957 } 958 959 // deleteVirtualMachine deletes a virtual machine and all of the resources that 960 // it owns, and any corresponding network security rules. 961 func (env *azureEnviron) deleteVirtualMachine( 962 instId instance.Id, 963 maybeStorageClient internalazurestorage.Client, 964 networkInterfaces []network.Interface, 965 publicIPAddresses []network.PublicIPAddress, 966 ) error { 967 vmClient := compute.VirtualMachinesClient{env.compute} 968 nicClient := network.InterfacesClient{env.network} 969 nsgClient := network.SecurityGroupsClient{env.network} 970 securityRuleClient := network.SecurityRulesClient{env.network} 971 pipClient := network.PublicIPAddressesClient{env.network} 972 deploymentsClient := resources.DeploymentsClient{env.resources} 973 vmName := string(instId) 974 975 logger.Debugf("- deleting virtual machine (%s)", vmName) 976 if err := deleteResource(env.callAPI, vmClient, env.resourceGroup, vmName); err != nil { 977 if !errors.IsNotFound(err) { 978 return errors.Annotate(err, "deleting virtual machine") 979 } 980 } 981 982 if maybeStorageClient != nil { 983 logger.Debugf("- deleting OS VHD (%s)", vmName) 984 blobClient := maybeStorageClient.GetBlobService() 985 if _, err := blobClient.DeleteBlobIfExists(osDiskVHDContainer, vmName, nil); err != nil { 986 return errors.Annotate(err, "deleting OS VHD") 987 } 988 } 989 990 logger.Debugf("- deleting security rules (%s)", vmName) 991 if err := deleteInstanceNetworkSecurityRules( 992 env.resourceGroup, instId, nsgClient, 993 securityRuleClient, env.callAPI, 994 ); err != nil { 995 return errors.Annotate(err, "deleting network security rules") 996 } 997 998 logger.Debugf("- deleting network interfaces (%s)", vmName) 999 for _, nic := range networkInterfaces { 1000 nicName := to.String(nic.Name) 1001 logger.Tracef("deleting NIC %q", nicName) 1002 if err := deleteResource(env.callAPI, nicClient, env.resourceGroup, nicName); err != nil { 1003 if !errors.IsNotFound(err) { 1004 return errors.Annotate(err, "deleting NIC") 1005 } 1006 } 1007 } 1008 1009 logger.Debugf("- deleting public IPs (%s)", vmName) 1010 for _, pip := range publicIPAddresses { 1011 pipName := to.String(pip.Name) 1012 logger.Tracef("deleting public IP %q", pipName) 1013 if err := deleteResource(env.callAPI, pipClient, env.resourceGroup, pipName); err != nil { 1014 if !errors.IsNotFound(err) { 1015 return errors.Annotate(err, "deleting public IP") 1016 } 1017 } 1018 } 1019 1020 // The deployment must be deleted last, or we risk leaking resources. 1021 logger.Debugf("- deleting deployment (%s)", vmName) 1022 if err := deleteResource(env.callAPI, deploymentsClient, env.resourceGroup, vmName); err != nil { 1023 if !errors.IsNotFound(err) { 1024 return errors.Annotate(err, "deleting deployment") 1025 } 1026 } 1027 return nil 1028 } 1029 1030 // Instances is specified in the Environ interface. 1031 func (env *azureEnviron) Instances(ids []instance.Id) ([]instance.Instance, error) { 1032 return env.instances(env.resourceGroup, ids, true /* refresh addresses */) 1033 } 1034 1035 func (env *azureEnviron) instances( 1036 resourceGroup string, 1037 ids []instance.Id, 1038 refreshAddresses bool, 1039 ) ([]instance.Instance, error) { 1040 if len(ids) == 0 { 1041 return nil, nil 1042 } 1043 all, err := env.allInstances(resourceGroup, refreshAddresses, false) 1044 if err != nil { 1045 return nil, errors.Trace(err) 1046 } 1047 byId := make(map[instance.Id]instance.Instance) 1048 for _, inst := range all { 1049 byId[inst.Id()] = inst 1050 } 1051 var found int 1052 matching := make([]instance.Instance, len(ids)) 1053 for i, id := range ids { 1054 inst, ok := byId[id] 1055 if !ok { 1056 continue 1057 } 1058 matching[i] = inst 1059 found++ 1060 } 1061 if found == 0 { 1062 return nil, environs.ErrNoInstances 1063 } else if found < len(ids) { 1064 return matching, environs.ErrPartialInstances 1065 } 1066 return matching, nil 1067 } 1068 1069 // AllInstances is specified in the InstanceBroker interface. 1070 func (env *azureEnviron) AllInstances() ([]instance.Instance, error) { 1071 return env.allInstances(env.resourceGroup, true /* refresh addresses */, false /* all instances */) 1072 } 1073 1074 // allInstances returns all of the instances in the given resource group, 1075 // and optionally ensures that each instance's addresses are up-to-date. 1076 func (env *azureEnviron) allInstances( 1077 resourceGroup string, 1078 refreshAddresses bool, 1079 controllerOnly bool, 1080 ) ([]instance.Instance, error) { 1081 deploymentsClient := resources.DeploymentsClient{env.resources} 1082 var deploymentsResult resources.DeploymentListResult 1083 if err := env.callAPI(func() (autorest.Response, error) { 1084 var err error 1085 deploymentsResult, err = deploymentsClient.List(resourceGroup, "", nil) 1086 return deploymentsResult.Response, err 1087 }); err != nil { 1088 if deploymentsResult.Response.Response != nil && deploymentsResult.StatusCode == http.StatusNotFound { 1089 // This will occur if the resource group does not 1090 // exist, e.g. in a fresh hosted environment. 1091 return nil, nil 1092 } 1093 return nil, errors.Trace(err) 1094 } 1095 if deploymentsResult.Value == nil || len(*deploymentsResult.Value) == 0 { 1096 return nil, nil 1097 } 1098 1099 azureInstances := make([]*azureInstance, 0, len(*deploymentsResult.Value)) 1100 for _, deployment := range *deploymentsResult.Value { 1101 name := to.String(deployment.Name) 1102 if deployment.Properties == nil || deployment.Properties.Dependencies == nil { 1103 continue 1104 } 1105 if controllerOnly && !isControllerDeployment(deployment) { 1106 continue 1107 } 1108 provisioningState := to.String(deployment.Properties.ProvisioningState) 1109 inst := &azureInstance{name, provisioningState, env, nil, nil} 1110 azureInstances = append(azureInstances, inst) 1111 } 1112 1113 if len(azureInstances) > 0 && refreshAddresses { 1114 if err := setInstanceAddresses( 1115 env.callAPI, 1116 resourceGroup, 1117 network.InterfacesClient{env.network}, 1118 network.PublicIPAddressesClient{env.network}, 1119 azureInstances, 1120 ); err != nil { 1121 return nil, errors.Trace(err) 1122 } 1123 } 1124 1125 instances := make([]instance.Instance, len(azureInstances)) 1126 for i, inst := range azureInstances { 1127 instances[i] = inst 1128 } 1129 return instances, nil 1130 } 1131 1132 func isControllerDeployment(deployment resources.DeploymentExtended) bool { 1133 for _, d := range *deployment.Properties.Dependencies { 1134 if d.DependsOn == nil { 1135 continue 1136 } 1137 if to.String(d.ResourceType) != "Microsoft.Compute/virtualMachines" { 1138 continue 1139 } 1140 for _, on := range *d.DependsOn { 1141 if to.String(on.ResourceType) != "Microsoft.Compute/availabilitySets" { 1142 continue 1143 } 1144 if to.String(on.ResourceName) == controllerAvailabilitySet { 1145 return true 1146 } 1147 } 1148 } 1149 return false 1150 } 1151 1152 // Destroy is specified in the Environ interface. 1153 func (env *azureEnviron) Destroy() error { 1154 logger.Debugf("destroying model %q", env.envName) 1155 logger.Debugf("- deleting resource group %q", env.resourceGroup) 1156 if err := env.deleteResourceGroup(env.resourceGroup); err != nil { 1157 return errors.Trace(err) 1158 } 1159 // Resource groups are self-contained and fully encompass 1160 // all environ resources. Once you delete the group, there 1161 // is nothing else to do. 1162 return nil 1163 } 1164 1165 // DestroyController is specified in the Environ interface. 1166 func (env *azureEnviron) DestroyController(controllerUUID string) error { 1167 logger.Debugf("destroying model %q", env.envName) 1168 logger.Debugf("- deleting resource groups") 1169 if err := env.deleteControllerManagedResourceGroups(controllerUUID); err != nil { 1170 return errors.Trace(err) 1171 } 1172 // Resource groups are self-contained and fully encompass 1173 // all environ resources. Once you delete the group, there 1174 // is nothing else to do. 1175 return nil 1176 } 1177 1178 func (env *azureEnviron) deleteControllerManagedResourceGroups(controllerUUID string) error { 1179 filter := fmt.Sprintf( 1180 "tagname eq '%s' and tagvalue eq '%s'", 1181 tags.JujuController, controllerUUID, 1182 ) 1183 client := resources.GroupsClient{env.resources} 1184 var result resources.ResourceGroupListResult 1185 if err := env.callAPI(func() (autorest.Response, error) { 1186 var err error 1187 result, err = client.List(filter, nil) 1188 return result.Response, err 1189 }); err != nil { 1190 return errors.Annotate(err, "listing resource groups") 1191 } 1192 if result.Value == nil { 1193 return nil 1194 } 1195 1196 // Deleting groups can take a long time, so make sure they are 1197 // deleted in parallel. 1198 var wg sync.WaitGroup 1199 errs := make([]error, len(*result.Value)) 1200 for i, group := range *result.Value { 1201 groupName := to.String(group.Name) 1202 logger.Debugf(" - deleting resource group %q", groupName) 1203 wg.Add(1) 1204 go func(i int) { 1205 defer wg.Done() 1206 if err := env.deleteResourceGroup(groupName); err != nil { 1207 errs[i] = errors.Annotatef( 1208 err, "deleting resource group %q", groupName, 1209 ) 1210 } 1211 }(i) 1212 } 1213 wg.Wait() 1214 1215 // If there is just one error, return it. If there are multiple, 1216 // then combine their messages. 1217 var nonNilErrs []error 1218 for _, err := range errs { 1219 if err != nil { 1220 nonNilErrs = append(nonNilErrs, err) 1221 } 1222 } 1223 switch len(nonNilErrs) { 1224 case 0: 1225 return nil 1226 case 1: 1227 return nonNilErrs[0] 1228 } 1229 combined := make([]string, len(nonNilErrs)) 1230 for i, err := range nonNilErrs { 1231 combined[i] = err.Error() 1232 } 1233 return errors.New(strings.Join(combined, "; ")) 1234 } 1235 1236 func (env *azureEnviron) deleteResourceGroup(resourceGroup string) error { 1237 client := resources.GroupsClient{env.resources} 1238 var result autorest.Response 1239 if err := env.callAPI(func() (autorest.Response, error) { 1240 var err error 1241 result, err = client.Delete(resourceGroup, nil) 1242 return result, err 1243 }); err != nil { 1244 if result.Response == nil || result.StatusCode != http.StatusNotFound { 1245 return errors.Annotatef(err, "deleting resource group %q", resourceGroup) 1246 } 1247 } 1248 return nil 1249 } 1250 1251 var errNoFwGlobal = errors.New("global firewall mode is not supported") 1252 1253 // OpenPorts is specified in the Environ interface. However, Azure does not 1254 // support the global firewall mode. 1255 func (env *azureEnviron) OpenPorts(ports []jujunetwork.PortRange) error { 1256 return errNoFwGlobal 1257 } 1258 1259 // ClosePorts is specified in the Environ interface. However, Azure does not 1260 // support the global firewall mode. 1261 func (env *azureEnviron) ClosePorts(ports []jujunetwork.PortRange) error { 1262 return errNoFwGlobal 1263 } 1264 1265 // Ports is specified in the Environ interface. 1266 func (env *azureEnviron) Ports() ([]jujunetwork.PortRange, error) { 1267 return nil, errNoFwGlobal 1268 } 1269 1270 // Provider is specified in the Environ interface. 1271 func (env *azureEnviron) Provider() environs.EnvironProvider { 1272 return env.provider 1273 } 1274 1275 // resourceGroupName returns the name of the environment's resource group. 1276 func resourceGroupName(modelTag names.ModelTag, modelName string) string { 1277 return fmt.Sprintf("juju-%s-%s", modelName, resourceName(modelTag)) 1278 } 1279 1280 // resourceName returns the string to use for a resource's Name tag, 1281 // to help users identify Juju-managed resources in the Azure portal. 1282 // 1283 // Since resources are grouped under resource groups, we just use the 1284 // tag. 1285 func resourceName(tag names.Tag) string { 1286 return tag.String() 1287 } 1288 1289 // getInstanceTypes gets the instance types available for the configured 1290 // location, keyed by name. 1291 func (env *azureEnviron) getInstanceTypes() (map[string]instances.InstanceType, error) { 1292 env.mu.Lock() 1293 defer env.mu.Unlock() 1294 instanceTypes, err := env.getInstanceTypesLocked() 1295 if err != nil { 1296 return nil, errors.Annotate(err, "getting instance types") 1297 } 1298 return instanceTypes, nil 1299 } 1300 1301 // getInstanceTypesLocked returns the instance types for Azure, by listing the 1302 // role sizes available to the subscription. 1303 func (env *azureEnviron) getInstanceTypesLocked() (map[string]instances.InstanceType, error) { 1304 if env.instanceTypes != nil { 1305 return env.instanceTypes, nil 1306 } 1307 1308 location := env.location 1309 client := compute.VirtualMachineSizesClient{env.compute} 1310 1311 var result compute.VirtualMachineSizeListResult 1312 if err := env.callAPI(func() (autorest.Response, error) { 1313 var err error 1314 result, err = client.List(location) 1315 return result.Response, err 1316 }); err != nil { 1317 return nil, errors.Annotate(err, "listing VM sizes") 1318 } 1319 instanceTypes := make(map[string]instances.InstanceType) 1320 if result.Value != nil { 1321 for _, size := range *result.Value { 1322 instanceType := newInstanceType(size) 1323 instanceTypes[instanceType.Name] = instanceType 1324 // Create aliases for standard role sizes. 1325 if strings.HasPrefix(instanceType.Name, "Standard_") { 1326 instanceTypes[instanceType.Name[len("Standard_"):]] = instanceType 1327 } 1328 } 1329 } 1330 env.instanceTypes = instanceTypes 1331 return instanceTypes, nil 1332 } 1333 1334 // getStorageClient queries the storage account key, and uses it to construct 1335 // a new storage client. 1336 func (env *azureEnviron) getStorageClient() (internalazurestorage.Client, error) { 1337 env.mu.Lock() 1338 defer env.mu.Unlock() 1339 storageAccount, err := env.getStorageAccountLocked(false) 1340 if err != nil { 1341 return nil, errors.Annotate(err, "getting storage account") 1342 } 1343 storageAccountKey, err := env.getStorageAccountKeyLocked( 1344 to.String(storageAccount.Name), false, 1345 ) 1346 if err != nil { 1347 return nil, errors.Annotate(err, "getting storage account key") 1348 } 1349 client, err := getStorageClient( 1350 env.provider.config.NewStorageClient, 1351 env.storageEndpoint, 1352 storageAccount, 1353 storageAccountKey, 1354 ) 1355 if err != nil { 1356 return nil, errors.Annotate(err, "getting storage client") 1357 } 1358 return client, nil 1359 } 1360 1361 // getStorageAccount returns the storage account for this environment's 1362 // resource group. If refresh is true, cached details will be refreshed. 1363 func (env *azureEnviron) getStorageAccount(refresh bool) (*storage.Account, error) { 1364 env.mu.Lock() 1365 defer env.mu.Unlock() 1366 return env.getStorageAccountLocked(refresh) 1367 } 1368 1369 func (env *azureEnviron) getStorageAccountLocked(refresh bool) (*storage.Account, error) { 1370 if !refresh && env.storageAccount != nil { 1371 return env.storageAccount, nil 1372 } 1373 client := storage.AccountsClient{env.storage} 1374 var account storage.Account 1375 if err := env.callAPI(func() (autorest.Response, error) { 1376 var err error 1377 account, err = client.GetProperties(env.resourceGroup, env.storageAccountName) 1378 return account.Response, err 1379 }); err != nil { 1380 if account.Response.Response != nil && account.Response.StatusCode == http.StatusNotFound { 1381 return nil, errors.NewNotFound(err, fmt.Sprintf("storage account not found")) 1382 } 1383 return nil, errors.Annotate(err, "getting storage account") 1384 } 1385 env.storageAccount = &account 1386 return env.storageAccount, nil 1387 } 1388 1389 // getStorageAccountKeysLocked returns a storage account key for this 1390 // environment's storage account. If refresh is true, any cached key 1391 // will be refreshed. This method assumes that env.mu is held. 1392 func (env *azureEnviron) getStorageAccountKeyLocked(accountName string, refresh bool) (*storage.AccountKey, error) { 1393 if !refresh && env.storageAccountKey != nil { 1394 return env.storageAccountKey, nil 1395 } 1396 client := storage.AccountsClient{env.storage} 1397 key, err := getStorageAccountKey( 1398 env.callAPI, 1399 client, 1400 env.resourceGroup, 1401 accountName, 1402 ) 1403 if err != nil { 1404 return nil, errors.Trace(err) 1405 } 1406 env.storageAccountKey = key 1407 return key, nil 1408 } 1409 1410 // AgentMirror is specified in the tools.HasAgentMirror interface. 1411 // 1412 // TODO(axw) 2016-04-11 #1568715 1413 // When we have image simplestreams, we should rename this to "Region", 1414 // to implement simplestreams.HasRegion. 1415 func (env *azureEnviron) AgentMirror() (simplestreams.CloudSpec, error) { 1416 return simplestreams.CloudSpec{ 1417 Region: env.location, 1418 // The endpoints published in simplestreams 1419 // data are the storage endpoints. 1420 Endpoint: fmt.Sprintf("https://%s/", env.storageEndpoint), 1421 }, nil 1422 } 1423 1424 func (env *azureEnviron) callAPI(f func() (autorest.Response, error)) error { 1425 return backoffAPIRequestCaller{env.provider.config.RetryClock}.call(f) 1426 }