github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/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 "path" 10 "sort" 11 "strings" 12 "sync" 13 14 "github.com/Azure/azure-sdk-for-go/Godeps/_workspace/src/github.com/Azure/go-autorest/autorest" 15 "github.com/Azure/azure-sdk-for-go/Godeps/_workspace/src/github.com/Azure/go-autorest/autorest/to" 16 "github.com/Azure/azure-sdk-for-go/arm/compute" 17 "github.com/Azure/azure-sdk-for-go/arm/network" 18 "github.com/Azure/azure-sdk-for-go/arm/resources" 19 "github.com/Azure/azure-sdk-for-go/arm/storage" 20 azurestorage "github.com/Azure/azure-sdk-for-go/storage" 21 "github.com/juju/errors" 22 "github.com/juju/loggo" 23 "github.com/juju/names" 24 "github.com/juju/utils/arch" 25 "github.com/juju/utils/os" 26 jujuseries "github.com/juju/utils/series" 27 "github.com/juju/utils/set" 28 29 "github.com/juju/juju/cloudconfig/instancecfg" 30 "github.com/juju/juju/cloudconfig/providerinit" 31 "github.com/juju/juju/constraints" 32 "github.com/juju/juju/environs" 33 "github.com/juju/juju/environs/config" 34 "github.com/juju/juju/environs/instances" 35 "github.com/juju/juju/environs/simplestreams" 36 "github.com/juju/juju/environs/tags" 37 "github.com/juju/juju/instance" 38 jujunetwork "github.com/juju/juju/network" 39 internalazurestorage "github.com/juju/juju/provider/azure/internal/azurestorage" 40 "github.com/juju/juju/provider/common" 41 "github.com/juju/juju/state" 42 "github.com/juju/juju/state/multiwatcher" 43 "github.com/juju/juju/tools" 44 ) 45 46 const jujuMachineNameTag = tags.JujuTagPrefix + "machine-name" 47 48 type azureEnviron struct { 49 common.SupportsUnitPlacementPolicy 50 51 // provider is the azureEnvironProvider used to open this environment. 52 provider *azureEnvironProvider 53 54 // resourceGroup is the name of the Resource Group in the Azure 55 // subscription that corresponds to the environment. 56 resourceGroup string 57 58 // controllerResourceGroup is the name of the Resource Group in the 59 // Azure subscription that corresponds to the Juju controller 60 // environment. 61 controllerResourceGroup string 62 63 // envName is the name of the environment. 64 envName string 65 66 mu sync.Mutex 67 config *azureModelConfig 68 instanceTypes map[string]instances.InstanceType 69 // azure management clients 70 compute compute.ManagementClient 71 resources resources.ManagementClient 72 storage storage.ManagementClient 73 network network.ManagementClient 74 storageClient azurestorage.Client 75 } 76 77 var _ environs.Environ = (*azureEnviron)(nil) 78 var _ state.Prechecker = (*azureEnviron)(nil) 79 80 // newEnviron creates a new azureEnviron. 81 func newEnviron(provider *azureEnvironProvider, cfg *config.Config) (*azureEnviron, error) { 82 env := azureEnviron{provider: provider} 83 err := env.SetConfig(cfg) 84 if err != nil { 85 return nil, err 86 } 87 modelTag := names.NewModelTag(cfg.UUID()) 88 env.resourceGroup = resourceGroupName(modelTag, cfg.Name()) 89 env.controllerResourceGroup = env.config.controllerResourceGroup 90 env.envName = cfg.Name() 91 return &env, nil 92 } 93 94 // Bootstrap is specified in the Environ interface. 95 func (env *azureEnviron) Bootstrap( 96 ctx environs.BootstrapContext, 97 args environs.BootstrapParams, 98 ) (*environs.BootstrapResult, error) { 99 100 cfg, err := env.initResourceGroup() 101 if err != nil { 102 return nil, errors.Annotate(err, "creating controller resource group") 103 } 104 if err := env.SetConfig(cfg); err != nil { 105 return nil, errors.Annotate(err, "updating config") 106 } 107 108 result, err := common.Bootstrap(ctx, env, args) 109 if err != nil { 110 logger.Errorf("bootstrap failed, destroying model: %v", err) 111 if err := env.Destroy(); err != nil { 112 logger.Errorf("failed to destroy model: %v", err) 113 } 114 return nil, errors.Trace(err) 115 } 116 return result, nil 117 } 118 119 // initResourceGroup creates and initialises a resource group for this 120 // environment. The resource group will have a storage account and a 121 // subnet associated with it (but not necessarily contained within: 122 // see subnet creation). 123 func (env *azureEnviron) initResourceGroup() (*config.Config, error) { 124 location := env.config.location 125 tags, _ := env.config.ResourceTags() 126 resourceGroupsClient := resources.GroupsClient{env.resources} 127 128 logger.Debugf("creating resource group %q", env.resourceGroup) 129 _, err := resourceGroupsClient.CreateOrUpdate(env.resourceGroup, resources.Group{ 130 Location: to.StringPtr(location), 131 Tags: toTagsPtr(tags), 132 }) 133 if err != nil { 134 return nil, errors.Annotate(err, "creating resource group") 135 } 136 137 var vnetPtr *network.VirtualNetwork 138 if env.resourceGroup == env.controllerResourceGroup { 139 // Create an internal network for all VMs to connect to. 140 vnetPtr, err = createInternalVirtualNetwork( 141 env.network, env.controllerResourceGroup, location, tags, 142 ) 143 if err != nil { 144 return nil, errors.Annotate(err, "creating virtual network") 145 } 146 } else { 147 // We're creating a hosted environment, so we need to fetch 148 // the virtual network to create a subnet below. 149 vnetClient := network.VirtualNetworksClient{env.network} 150 vnet, err := vnetClient.Get(env.controllerResourceGroup, internalNetworkName) 151 if err != nil { 152 return nil, errors.Annotate(err, "getting virtual network") 153 } 154 vnetPtr = &vnet 155 } 156 157 _, err = createInternalSubnet( 158 env.network, env.resourceGroup, env.controllerResourceGroup, 159 vnetPtr, location, tags, 160 ) 161 if err != nil { 162 return nil, errors.Annotate(err, "creating subnet") 163 } 164 165 // Create a storage account for the resource group. 166 storageAccountsClient := storage.AccountsClient{env.storage} 167 storageAccountName, storageAccountKey, err := createStorageAccount( 168 storageAccountsClient, env.config.storageAccountType, 169 env.resourceGroup, location, tags, 170 env.provider.config.StorageAccountNameGenerator, 171 ) 172 if err != nil { 173 return nil, errors.Annotate(err, "creating storage account") 174 } 175 return env.config.Config.Apply(map[string]interface{}{ 176 configAttrStorageAccount: storageAccountName, 177 configAttrStorageAccountKey: storageAccountKey, 178 }) 179 } 180 181 func createStorageAccount( 182 client storage.AccountsClient, 183 accountType storage.AccountType, 184 resourceGroup string, 185 location string, 186 tags map[string]string, 187 accountNameGenerator func() string, 188 ) (string, string, error) { 189 logger.Debugf("creating storage account (finding available name)") 190 const maxAttempts = 10 191 for remaining := maxAttempts; remaining > 0; remaining-- { 192 accountName := accountNameGenerator() 193 logger.Debugf("- checking storage account name %q", accountName) 194 result, err := client.CheckNameAvailability( 195 storage.AccountCheckNameAvailabilityParameters{ 196 Name: to.StringPtr(accountName), 197 // Azure is a little inconsistent with when Type is 198 // required. It's required here. 199 Type: to.StringPtr("Microsoft.Storage/storageAccounts"), 200 }, 201 ) 202 if err != nil { 203 return "", "", errors.Annotate(err, "checking account name availability") 204 } 205 if !to.Bool(result.NameAvailable) { 206 logger.Debugf( 207 "%q is not available (%v): %v", 208 accountName, result.Reason, result.Message, 209 ) 210 continue 211 } 212 createParams := storage.AccountCreateParameters{ 213 Location: to.StringPtr(location), 214 Tags: toTagsPtr(tags), 215 Properties: &storage.AccountPropertiesCreateParameters{ 216 AccountType: accountType, 217 }, 218 } 219 logger.Debugf("- creating %q storage account %q", accountType, accountName) 220 // TODO(axw) account creation can fail if the account name is 221 // available, but contains profanity. We should retry a set 222 // number of times even if creating fails. 223 if _, err := client.Create(resourceGroup, accountName, createParams); err != nil { 224 return "", "", errors.Trace(err) 225 } 226 logger.Debugf("- listing storage account keys") 227 listKeysResult, err := client.ListKeys(resourceGroup, accountName) 228 if err != nil { 229 return "", "", errors.Annotate(err, "listing storage account keys") 230 } 231 return accountName, to.String(listKeysResult.Key1), nil 232 } 233 return "", "", errors.New("could not find available storage account name") 234 } 235 236 // ControllerInstances is specified in the Environ interface. 237 func (env *azureEnviron) ControllerInstances() ([]instance.Id, error) { 238 // controllers are tagged with tags.JujuIsController, so just 239 // list the instances in the controller resource group and pick 240 // those ones out. 241 instances, err := env.allInstances(env.controllerResourceGroup, true) 242 if err != nil { 243 return nil, err 244 } 245 var ids []instance.Id 246 for _, inst := range instances { 247 azureInstance := inst.(*azureInstance) 248 if toTags(azureInstance.Tags)[tags.JujuIsController] == "true" { 249 ids = append(ids, inst.Id()) 250 } 251 } 252 if len(ids) == 0 { 253 return nil, environs.ErrNoInstances 254 } 255 return ids, nil 256 } 257 258 // Config is specified in the Environ interface. 259 func (env *azureEnviron) Config() *config.Config { 260 env.mu.Lock() 261 defer env.mu.Unlock() 262 return env.config.Config 263 } 264 265 // SetConfig is specified in the Environ interface. 266 func (env *azureEnviron) SetConfig(cfg *config.Config) error { 267 env.mu.Lock() 268 defer env.mu.Unlock() 269 270 var old *config.Config 271 if env.config != nil { 272 old = env.config.Config 273 } 274 ecfg, err := validateConfig(cfg, old) 275 if err != nil { 276 return err 277 } 278 env.config = ecfg 279 280 // Initialise clients. 281 env.compute = compute.NewWithBaseURI(ecfg.endpoint, env.config.subscriptionId) 282 env.resources = resources.NewWithBaseURI(ecfg.endpoint, env.config.subscriptionId) 283 env.storage = storage.NewWithBaseURI(ecfg.endpoint, env.config.subscriptionId) 284 env.network = network.NewWithBaseURI(ecfg.endpoint, env.config.subscriptionId) 285 clients := map[string]*autorest.Client{ 286 "azure.compute": &env.compute.Client, 287 "azure.resources": &env.resources.Client, 288 "azure.storage": &env.storage.Client, 289 "azure.network": &env.network.Client, 290 } 291 if env.provider.config.Sender != nil { 292 env.config.token.SetSender(env.provider.config.Sender) 293 } 294 for id, client := range clients { 295 client.Authorizer = env.config.token 296 logger := loggo.GetLogger(id) 297 if env.provider.config.Sender != nil { 298 client.Sender = env.provider.config.Sender 299 } 300 client.ResponseInspector = tracingRespondDecorator(logger) 301 client.RequestInspector = tracingPrepareDecorator(logger) 302 if env.provider.config.RequestInspector != nil { 303 tracer := client.RequestInspector 304 inspector := env.provider.config.RequestInspector 305 client.RequestInspector = func(p autorest.Preparer) autorest.Preparer { 306 p = tracer(p) 307 p = inspector(p) 308 return p 309 } 310 } 311 } 312 313 // Invalidate instance types when the location changes. 314 if old != nil { 315 oldLocation := old.UnknownAttrs()["location"].(string) 316 if env.config.location != oldLocation { 317 env.instanceTypes = nil 318 } 319 } 320 321 return nil 322 } 323 324 // SupportedArchitectures is specified on the EnvironCapability interface. 325 func (env *azureEnviron) SupportedArchitectures() ([]string, error) { 326 return env.supportedArchitectures(), nil 327 } 328 329 func (env *azureEnviron) supportedArchitectures() []string { 330 return []string{arch.AMD64} 331 } 332 333 // ConstraintsValidator is defined on the Environs interface. 334 func (env *azureEnviron) ConstraintsValidator() (constraints.Validator, error) { 335 instanceTypes, err := env.getInstanceTypes() 336 if err != nil { 337 return nil, err 338 } 339 instTypeNames := make([]string, 0, len(instanceTypes)) 340 for instTypeName := range instanceTypes { 341 instTypeNames = append(instTypeNames, instTypeName) 342 } 343 sort.Strings(instTypeNames) 344 345 validator := constraints.NewValidator() 346 validator.RegisterUnsupported([]string{ 347 constraints.CpuPower, 348 constraints.Tags, 349 constraints.VirtType, 350 }) 351 validator.RegisterVocabulary( 352 constraints.Arch, 353 env.supportedArchitectures(), 354 ) 355 validator.RegisterVocabulary( 356 constraints.InstanceType, 357 instTypeNames, 358 ) 359 validator.RegisterConflicts( 360 []string{constraints.InstanceType}, 361 []string{ 362 constraints.Mem, 363 constraints.CpuCores, 364 constraints.Arch, 365 constraints.RootDisk, 366 }, 367 ) 368 return validator, nil 369 } 370 371 // PrecheckInstance is defined on the state.Prechecker interface. 372 func (env *azureEnviron) PrecheckInstance(series string, cons constraints.Value, placement string) error { 373 if placement != "" { 374 return fmt.Errorf("unknown placement directive: %s", placement) 375 } 376 if !cons.HasInstanceType() { 377 return nil 378 } 379 // Constraint has an instance-type constraint so let's see if it is valid. 380 instanceTypes, err := env.getInstanceTypes() 381 if err != nil { 382 return err 383 } 384 for _, instanceType := range instanceTypes { 385 if instanceType.Name == *cons.InstanceType { 386 return nil 387 } 388 } 389 return fmt.Errorf("invalid instance type %q", *cons.InstanceType) 390 } 391 392 // MaintainInstance is specified in the InstanceBroker interface. 393 func (*azureEnviron) MaintainInstance(args environs.StartInstanceParams) error { 394 return nil 395 } 396 397 // StartInstance is specified in the InstanceBroker interface. 398 func (env *azureEnviron) StartInstance(args environs.StartInstanceParams) (*environs.StartInstanceResult, error) { 399 // Get the required configuration and config-dependent information 400 // required to create the instance. We take the lock just once, to 401 // ensure we obtain all information based on the same configuration. 402 env.mu.Lock() 403 location := env.config.location 404 envTags, _ := env.config.ResourceTags() 405 apiPort := env.config.APIPort() 406 vmClient := compute.VirtualMachinesClient{env.compute} 407 availabilitySetClient := compute.AvailabilitySetsClient{env.compute} 408 networkClient := env.network 409 vmImagesClient := compute.VirtualMachineImagesClient{env.compute} 410 vmExtensionClient := compute.VirtualMachineExtensionsClient{env.compute} 411 subscriptionId := env.config.subscriptionId 412 imageStream := env.config.ImageStream() 413 storageEndpoint := env.config.storageEndpoint 414 storageAccountName := env.config.storageAccount 415 instanceTypes, err := env.getInstanceTypesLocked() 416 if err != nil { 417 env.mu.Unlock() 418 return nil, errors.Trace(err) 419 } 420 internalNetworkSubnet, err := env.getInternalSubnetLocked() 421 if err != nil { 422 env.mu.Unlock() 423 return nil, errors.Trace(err) 424 } 425 env.mu.Unlock() 426 427 // Identify the instance type and image to provision. 428 instanceSpec, err := findInstanceSpec( 429 vmImagesClient, 430 instanceTypes, 431 &instances.InstanceConstraint{ 432 Region: location, 433 Series: args.Tools.OneSeries(), 434 Arches: args.Tools.Arches(), 435 Constraints: args.Constraints, 436 }, 437 imageStream, 438 ) 439 if err != nil { 440 return nil, err 441 } 442 443 // Pick tools by filtering the available tools down to the architecture of 444 // the image that will be provisioned. 445 selectedTools, err := args.Tools.Match(tools.Filter{ 446 Arch: instanceSpec.Image.Arch, 447 }) 448 if err != nil { 449 return nil, errors.Trace(err) 450 } 451 logger.Infof("picked tools %q", selectedTools[0].Version) 452 453 // Finalize the instance config, which we'll render to CustomData below. 454 if err := args.InstanceConfig.SetTools(selectedTools); err != nil { 455 return nil, errors.Trace(err) 456 } 457 if err := instancecfg.FinishInstanceConfig( 458 args.InstanceConfig, env.Config(), 459 ); err != nil { 460 return nil, err 461 } 462 463 machineTag := names.NewMachineTag(args.InstanceConfig.MachineId) 464 vmName := resourceName(machineTag) 465 vmTags := make(map[string]string) 466 for k, v := range args.InstanceConfig.Tags { 467 vmTags[k] = v 468 } 469 // jujuMachineNameTag identifies the VM name, in which is encoded 470 // the Juju machine name. We tag all resources related to the 471 // machine with this. 472 vmTags[jujuMachineNameTag] = vmName 473 474 // If the machine will run a controller, then we need to open the 475 // API port for it. 476 var apiPortPtr *int 477 if multiwatcher.AnyJobNeedsState(args.InstanceConfig.Jobs...) { 478 apiPortPtr = &apiPort 479 } 480 481 // Construct the network security group ID for the environment. 482 nsgID := path.Join( 483 "/subscriptions", subscriptionId, "resourceGroups", 484 env.resourceGroup, "providers", "Microsoft.Network", 485 "networkSecurityGroups", internalSecurityGroupName, 486 ) 487 488 vm, err := createVirtualMachine( 489 env.resourceGroup, location, vmName, 490 vmTags, envTags, 491 instanceSpec, args.InstanceConfig, 492 args.DistributionGroup, 493 env.Instances, 494 apiPortPtr, internalNetworkSubnet, nsgID, 495 storageEndpoint, storageAccountName, 496 networkClient, vmClient, 497 availabilitySetClient, vmExtensionClient, 498 ) 499 if err != nil { 500 logger.Errorf("creating instance failed, destroying: %v", err) 501 if err := env.StopInstances(instance.Id(vmName)); err != nil { 502 logger.Errorf("could not destroy failed virtual machine: %v", err) 503 } 504 return nil, errors.Annotatef(err, "creating virtual machine %q", vmName) 505 } 506 507 // Note: the instance is initialised without addresses to keep the 508 // API chatter down. We will refresh the instance if we need to know 509 // the addresses. 510 inst := &azureInstance{vm, env, nil, nil} 511 amd64 := arch.AMD64 512 hc := &instance.HardwareCharacteristics{ 513 Arch: &amd64, 514 Mem: &instanceSpec.InstanceType.Mem, 515 RootDisk: &instanceSpec.InstanceType.RootDisk, 516 CpuCores: &instanceSpec.InstanceType.CpuCores, 517 } 518 return &environs.StartInstanceResult{ 519 Instance: inst, 520 Hardware: hc, 521 }, nil 522 } 523 524 // createVirtualMachine creates a virtual machine and related resources. 525 // 526 // All resources created are tagged with the specified "vmTags", so if 527 // this function fails then all resources can be deleted by tag. 528 func createVirtualMachine( 529 resourceGroup, location, vmName string, 530 vmTags, envTags map[string]string, 531 instanceSpec *instances.InstanceSpec, 532 instanceConfig *instancecfg.InstanceConfig, 533 distributionGroupFunc func() ([]instance.Id, error), 534 instancesFunc func([]instance.Id) ([]instance.Instance, error), 535 apiPort *int, 536 internalNetworkSubnet *network.Subnet, 537 nsgID, storageEndpoint, storageAccountName string, 538 networkClient network.ManagementClient, 539 vmClient compute.VirtualMachinesClient, 540 availabilitySetClient compute.AvailabilitySetsClient, 541 vmExtensionClient compute.VirtualMachineExtensionsClient, 542 ) (compute.VirtualMachine, error) { 543 544 storageProfile, err := newStorageProfile( 545 vmName, instanceConfig.Series, 546 instanceSpec, storageEndpoint, storageAccountName, 547 ) 548 if err != nil { 549 return compute.VirtualMachine{}, errors.Annotate(err, "creating storage profile") 550 } 551 552 osProfile, seriesOS, err := newOSProfile(vmName, instanceConfig) 553 if err != nil { 554 return compute.VirtualMachine{}, errors.Annotate(err, "creating OS profile") 555 } 556 557 networkProfile, err := newNetworkProfile( 558 networkClient, vmName, apiPort, 559 internalNetworkSubnet, nsgID, 560 resourceGroup, location, vmTags, 561 ) 562 if err != nil { 563 return compute.VirtualMachine{}, errors.Annotate(err, "creating network profile") 564 } 565 566 availabilitySetId, err := createAvailabilitySet( 567 availabilitySetClient, 568 vmName, resourceGroup, location, 569 vmTags, envTags, 570 distributionGroupFunc, instancesFunc, 571 ) 572 if err != nil { 573 return compute.VirtualMachine{}, errors.Annotate(err, "creating availability set") 574 } 575 576 vmArgs := compute.VirtualMachine{ 577 Location: to.StringPtr(location), 578 Tags: toTagsPtr(vmTags), 579 Properties: &compute.VirtualMachineProperties{ 580 HardwareProfile: &compute.HardwareProfile{ 581 VMSize: compute.VirtualMachineSizeTypes( 582 instanceSpec.InstanceType.Name, 583 ), 584 }, 585 StorageProfile: storageProfile, 586 OsProfile: osProfile, 587 NetworkProfile: networkProfile, 588 AvailabilitySet: &compute.SubResource{ 589 ID: to.StringPtr(availabilitySetId), 590 }, 591 }, 592 } 593 vm, err := vmClient.CreateOrUpdate(resourceGroup, vmName, vmArgs) 594 if err != nil { 595 return compute.VirtualMachine{}, errors.Annotate(err, "creating virtual machine") 596 } 597 598 // On Windows and CentOS, we must add the CustomScript VM 599 // extension to run the CustomData script. 600 switch seriesOS { 601 case os.Windows, os.CentOS: 602 if err := createVMExtension( 603 vmExtensionClient, seriesOS, 604 resourceGroup, vmName, location, vmTags, 605 ); err != nil { 606 return compute.VirtualMachine{}, errors.Annotate( 607 err, "creating virtual machine extension", 608 ) 609 } 610 } 611 return vm, nil 612 } 613 614 // createAvailabilitySet creates the availability set for a machine to use 615 // if it doesn't already exist, and returns the availability set's ID. The 616 // algorithm used for choosing the availability set is: 617 // - if there is a distribution group, use the same availability set as 618 // the instances in that group. Instances in the group may be in 619 // different availability sets (when multiple services colocated on a 620 // machine), so we pick one arbitrarily 621 // - if there is no distribution group, create an availability name with 622 // a name based on the value of the tags.JujuUnitsDeployed tag in vmTags, 623 // if it exists 624 // - if there are no units assigned to the machine, then use the "juju" 625 // availability set 626 func createAvailabilitySet( 627 client compute.AvailabilitySetsClient, 628 vmName, resourceGroup, location string, 629 vmTags, envTags map[string]string, 630 distributionGroupFunc func() ([]instance.Id, error), 631 instancesFunc func([]instance.Id) ([]instance.Instance, error), 632 ) (string, error) { 633 logger.Debugf("selecting availability set for %q", vmName) 634 635 // First we check if there's a distribution group, and if so, 636 // use the availability set of the first instance we find in it. 637 var instanceIds []instance.Id 638 if distributionGroupFunc != nil { 639 var err error 640 instanceIds, err = distributionGroupFunc() 641 if err != nil { 642 return "", errors.Annotate( 643 err, "querying distribution group", 644 ) 645 } 646 } 647 instances, err := instancesFunc(instanceIds) 648 switch err { 649 case nil, environs.ErrPartialInstances, environs.ErrNoInstances: 650 default: 651 return "", errors.Annotate( 652 err, "querying distribution group instances", 653 ) 654 } 655 for _, instance := range instances { 656 if instance == nil { 657 continue 658 } 659 instance := instance.(*azureInstance) 660 availabilitySetSubResource := instance.Properties.AvailabilitySet 661 if availabilitySetSubResource == nil || availabilitySetSubResource.ID == nil { 662 continue 663 } 664 logger.Debugf("- selecting availability set of %q", instance.Name) 665 return to.String(availabilitySetSubResource.ID), nil 666 } 667 668 // We'll have to create an availability set. Use the name of one of the 669 // services assigned to the machine. 670 availabilitySetName := "juju" 671 if unitNames, ok := vmTags[tags.JujuUnitsDeployed]; ok { 672 for _, unitName := range strings.Fields(unitNames) { 673 if !names.IsValidUnit(unitName) { 674 continue 675 } 676 serviceName, err := names.UnitService(unitName) 677 if err != nil { 678 return "", errors.Annotate( 679 err, "getting service name", 680 ) 681 } 682 availabilitySetName = serviceName 683 break 684 } 685 } 686 687 logger.Debugf("- creating availability set %q", availabilitySetName) 688 availabilitySet, err := client.CreateOrUpdate( 689 resourceGroup, availabilitySetName, compute.AvailabilitySet{ 690 Location: to.StringPtr(location), 691 // NOTE(axw) we do *not* want to use vmTags here, 692 // because an availability set is shared by machines. 693 Tags: toTagsPtr(envTags), 694 }, 695 ) 696 if err != nil { 697 return "", errors.Annotatef( 698 err, "creating availability set %q", availabilitySetName, 699 ) 700 } 701 return to.String(availabilitySet.ID), nil 702 } 703 704 // newStorageProfile creates the storage profile for a virtual machine, 705 // based on the series and chosen instance spec. 706 func newStorageProfile( 707 vmName string, 708 series string, 709 instanceSpec *instances.InstanceSpec, 710 storageEndpoint, storageAccountName string, 711 ) (*compute.StorageProfile, error) { 712 logger.Debugf("creating storage profile for %q", vmName) 713 714 urnParts := strings.SplitN(instanceSpec.Image.Id, ":", 4) 715 if len(urnParts) != 4 { 716 return nil, errors.Errorf("invalid image ID %q", instanceSpec.Image.Id) 717 } 718 publisher := urnParts[0] 719 offer := urnParts[1] 720 sku := urnParts[2] 721 version := urnParts[3] 722 723 osDisksRoot := osDiskVhdRoot(storageEndpoint, storageAccountName) 724 osDiskName := vmName 725 osDisk := &compute.OSDisk{ 726 Name: to.StringPtr(osDiskName), 727 CreateOption: compute.FromImage, 728 Caching: compute.ReadWrite, 729 Vhd: &compute.VirtualHardDisk{ 730 URI: to.StringPtr( 731 osDisksRoot + osDiskName + vhdExtension, 732 ), 733 }, 734 } 735 return &compute.StorageProfile{ 736 ImageReference: &compute.ImageReference{ 737 Publisher: to.StringPtr(publisher), 738 Offer: to.StringPtr(offer), 739 Sku: to.StringPtr(sku), 740 Version: to.StringPtr(version), 741 }, 742 OsDisk: osDisk, 743 }, nil 744 } 745 746 func newOSProfile(vmName string, instanceConfig *instancecfg.InstanceConfig) (*compute.OSProfile, os.OSType, error) { 747 logger.Debugf("creating OS profile for %q", vmName) 748 749 customData, err := providerinit.ComposeUserData(instanceConfig, nil, AzureRenderer{}) 750 if err != nil { 751 return nil, os.Unknown, errors.Annotate(err, "composing user data") 752 } 753 754 osProfile := &compute.OSProfile{ 755 ComputerName: to.StringPtr(vmName), 756 CustomData: to.StringPtr(string(customData)), 757 } 758 759 seriesOS, err := jujuseries.GetOSFromSeries(instanceConfig.Series) 760 if err != nil { 761 return nil, os.Unknown, errors.Trace(err) 762 } 763 switch seriesOS { 764 case os.Ubuntu, os.CentOS, os.Arch: 765 // SSH keys are handled by custom data, but must also be 766 // specified in order to forego providing a password, and 767 // disable password authentication. 768 publicKeys := []compute.SSHPublicKey{{ 769 Path: to.StringPtr("/home/ubuntu/.ssh/authorized_keys"), 770 KeyData: to.StringPtr(instanceConfig.AuthorizedKeys), 771 }} 772 osProfile.AdminUsername = to.StringPtr("ubuntu") 773 osProfile.LinuxConfiguration = &compute.LinuxConfiguration{ 774 DisablePasswordAuthentication: to.BoolPtr(true), 775 SSH: &compute.SSHConfiguration{PublicKeys: &publicKeys}, 776 } 777 case os.Windows: 778 osProfile.AdminUsername = to.StringPtr("JujuAdministrator") 779 // A password is required by Azure, but we will never use it. 780 // We generate something sufficiently long and random that it 781 // should be infeasible to guess. 782 osProfile.AdminPassword = to.StringPtr(randomAdminPassword()) 783 osProfile.WindowsConfiguration = &compute.WindowsConfiguration{ 784 ProvisionVMAgent: to.BoolPtr(true), 785 EnableAutomaticUpdates: to.BoolPtr(true), 786 // TODO(?) add WinRM configuration here. 787 } 788 default: 789 return nil, os.Unknown, errors.NotSupportedf("%s", seriesOS) 790 } 791 return osProfile, seriesOS, nil 792 } 793 794 // StopInstances is specified in the InstanceBroker interface. 795 func (env *azureEnviron) StopInstances(ids ...instance.Id) error { 796 env.mu.Lock() 797 computeClient := env.compute 798 networkClient := env.network 799 env.mu.Unlock() 800 storageClient, err := env.getStorageClient() 801 if err != nil { 802 return errors.Trace(err) 803 } 804 805 // Query the instances, so we can inspect the VirtualMachines 806 // and delete related resources. 807 instances, err := env.Instances(ids) 808 switch err { 809 case environs.ErrNoInstances: 810 return nil 811 default: 812 return errors.Trace(err) 813 case nil, environs.ErrPartialInstances: 814 // handled below 815 break 816 } 817 818 for _, inst := range instances { 819 if inst == nil { 820 continue 821 } 822 if err := deleteInstance( 823 inst.(*azureInstance), computeClient, networkClient, storageClient, 824 ); err != nil { 825 return errors.Annotatef(err, "deleting instance %q", inst.Id()) 826 } 827 } 828 return nil 829 } 830 831 // deleteInstances deletes a virtual machine and all of the resources that 832 // it owns, and any corresponding network security rules. 833 func deleteInstance( 834 inst *azureInstance, 835 computeClient compute.ManagementClient, 836 networkClient network.ManagementClient, 837 storageClient internalazurestorage.Client, 838 ) error { 839 vmName := string(inst.Id()) 840 vmClient := compute.VirtualMachinesClient{computeClient} 841 nicClient := network.InterfacesClient{networkClient} 842 nsgClient := network.SecurityGroupsClient{networkClient} 843 securityRuleClient := network.SecurityRulesClient{networkClient} 844 publicIPClient := network.PublicIPAddressesClient{networkClient} 845 logger.Debugf("deleting instance %q", vmName) 846 847 logger.Debugf("- deleting virtual machine") 848 deleteResult, err := vmClient.Delete(inst.env.resourceGroup, vmName) 849 if err != nil { 850 if deleteResult.Response == nil || deleteResult.StatusCode != http.StatusNotFound { 851 return errors.Annotate(err, "deleting virtual machine") 852 } 853 } 854 855 // Delete the VM's OS disk VHD. 856 logger.Debugf("- deleting OS VHD") 857 blobClient := storageClient.GetBlobService() 858 if _, err := blobClient.DeleteBlobIfExists(osDiskVHDContainer, vmName); err != nil { 859 return errors.Annotate(err, "deleting OS VHD") 860 } 861 862 // Delete network security rules that refer to the VM. 863 logger.Debugf("- deleting security rules") 864 if err := deleteInstanceNetworkSecurityRules( 865 inst.env.resourceGroup, inst.Id(), nsgClient, securityRuleClient, 866 ); err != nil { 867 return errors.Annotate(err, "deleting network security rules") 868 } 869 870 // Detach public IPs from NICs. This must be done before public 871 // IPs can be deleted. In the future, VMs may not necessarily 872 // have a public IP, so we don't use the presence of a public 873 // IP to indicate the existence of an instance. 874 logger.Debugf("- detaching public IP addresses") 875 for _, nic := range inst.networkInterfaces { 876 if nic.Properties.IPConfigurations == nil { 877 continue 878 } 879 var detached bool 880 for i, ipConfiguration := range *nic.Properties.IPConfigurations { 881 if ipConfiguration.Properties.PublicIPAddress == nil { 882 continue 883 } 884 ipConfiguration.Properties.PublicIPAddress = nil 885 (*nic.Properties.IPConfigurations)[i] = ipConfiguration 886 detached = true 887 } 888 if detached { 889 if _, err := nicClient.CreateOrUpdate( 890 inst.env.resourceGroup, to.String(nic.Name), nic, 891 ); err != nil { 892 return errors.Annotate(err, "detaching public IP addresses") 893 } 894 } 895 } 896 897 // Delete public IPs. 898 logger.Debugf("- deleting public IPs") 899 for _, pip := range inst.publicIPAddresses { 900 pipName := to.String(pip.Name) 901 logger.Tracef("deleting public IP %q", pipName) 902 result, err := publicIPClient.Delete(inst.env.resourceGroup, pipName) 903 if err != nil { 904 if result.Response == nil || result.StatusCode != http.StatusNotFound { 905 return errors.Annotate(err, "deleting public IP") 906 } 907 } 908 } 909 910 // Delete NICs. 911 // 912 // NOTE(axw) this *must* be deleted last, or we risk leaking resources. 913 logger.Debugf("- deleting network interfaces") 914 for _, nic := range inst.networkInterfaces { 915 nicName := to.String(nic.Name) 916 logger.Tracef("deleting NIC %q", nicName) 917 result, err := nicClient.Delete(inst.env.resourceGroup, nicName) 918 if err != nil { 919 if result.Response == nil || result.StatusCode != http.StatusNotFound { 920 return errors.Annotate(err, "deleting NIC") 921 } 922 } 923 } 924 925 return nil 926 } 927 928 // Instances is specified in the Environ interface. 929 func (env *azureEnviron) Instances(ids []instance.Id) ([]instance.Instance, error) { 930 return env.instances(env.resourceGroup, ids, true /* refresh addresses */) 931 } 932 933 func (env *azureEnviron) instances( 934 resourceGroup string, 935 ids []instance.Id, 936 refreshAddresses bool, 937 ) ([]instance.Instance, error) { 938 if len(ids) == 0 { 939 return nil, nil 940 } 941 all, err := env.allInstances(resourceGroup, refreshAddresses) 942 if err != nil { 943 return nil, errors.Trace(err) 944 } 945 byId := make(map[instance.Id]instance.Instance) 946 for _, inst := range all { 947 byId[inst.Id()] = inst 948 } 949 var found int 950 matching := make([]instance.Instance, len(ids)) 951 for i, id := range ids { 952 inst, ok := byId[id] 953 if !ok { 954 continue 955 } 956 matching[i] = inst 957 found++ 958 } 959 if found == 0 { 960 return nil, environs.ErrNoInstances 961 } else if found < len(ids) { 962 return matching, environs.ErrPartialInstances 963 } 964 return matching, nil 965 } 966 967 // AllInstances is specified in the InstanceBroker interface. 968 func (env *azureEnviron) AllInstances() ([]instance.Instance, error) { 969 return env.allInstances(env.resourceGroup, true /* refresh addresses */) 970 } 971 972 // allInstances returns all of the instances in the given resource group, 973 // and optionally ensures that each instance's addresses are up-to-date. 974 func (env *azureEnviron) allInstances( 975 resourceGroup string, 976 refreshAddresses bool, 977 ) ([]instance.Instance, error) { 978 env.mu.Lock() 979 vmClient := compute.VirtualMachinesClient{env.compute} 980 nicClient := network.InterfacesClient{env.network} 981 pipClient := network.PublicIPAddressesClient{env.network} 982 env.mu.Unlock() 983 984 // Due to how deleting instances works, we have to get creative about 985 // listing instances. We list NICs and return an instance for each 986 // unique value of the jujuMachineNameTag tag. 987 // 988 // The machine provisioner will call AllInstances so it can delete 989 // unknown instances. StopInstances must delete VMs before NICs and 990 // public IPs, because a VM cannot have less than 1 NIC. Thus, we can 991 // potentially delete a VM but then fail to delete its NIC. 992 nicsResult, err := nicClient.List(resourceGroup) 993 if err != nil { 994 if nicsResult.Response.Response != nil && nicsResult.StatusCode == http.StatusNotFound { 995 // This will occur if the resource group does not 996 // exist, e.g. in a fresh hosted environment. 997 return nil, nil 998 } 999 return nil, errors.Trace(err) 1000 } 1001 if nicsResult.Value == nil || len(*nicsResult.Value) == 0 { 1002 return nil, nil 1003 } 1004 1005 // Create an azureInstance for each VM. 1006 result, err := vmClient.List(resourceGroup) 1007 if err != nil { 1008 return nil, errors.Annotate(err, "listing virtual machines") 1009 } 1010 vmNames := make(set.Strings) 1011 var azureInstances []*azureInstance 1012 if result.Value != nil { 1013 azureInstances = make([]*azureInstance, len(*result.Value)) 1014 for i, vm := range *result.Value { 1015 inst := &azureInstance{vm, env, nil, nil} 1016 azureInstances[i] = inst 1017 vmNames.Add(to.String(vm.Name)) 1018 } 1019 } 1020 1021 // Create additional azureInstances for NICs without machines. See 1022 // comments above for rationale. This needs to happen before calling 1023 // setInstanceAddresses, so we still associate the NICs/PIPs. 1024 for _, nic := range *nicsResult.Value { 1025 vmName, ok := toTags(nic.Tags)[jujuMachineNameTag] 1026 if !ok || vmNames.Contains(vmName) { 1027 continue 1028 } 1029 vm := compute.VirtualMachine{ 1030 Name: to.StringPtr(vmName), 1031 Properties: &compute.VirtualMachineProperties{ 1032 ProvisioningState: to.StringPtr("Partially Deleted"), 1033 }, 1034 } 1035 inst := &azureInstance{vm, env, nil, nil} 1036 azureInstances = append(azureInstances, inst) 1037 vmNames.Add(to.String(vm.Name)) 1038 } 1039 1040 if len(azureInstances) > 0 && refreshAddresses { 1041 if err := setInstanceAddresses( 1042 pipClient, resourceGroup, azureInstances, nicsResult, 1043 ); err != nil { 1044 return nil, errors.Trace(err) 1045 } 1046 } 1047 instances := make([]instance.Instance, len(azureInstances)) 1048 for i, inst := range azureInstances { 1049 instances[i] = inst 1050 } 1051 return instances, nil 1052 } 1053 1054 // Destroy is specified in the Environ interface. 1055 func (env *azureEnviron) Destroy() error { 1056 logger.Debugf("destroying model %q", env.envName) 1057 logger.Debugf("- deleting resource group") 1058 if err := env.deleteResourceGroup(); err != nil { 1059 return errors.Trace(err) 1060 } 1061 if env.resourceGroup == env.controllerResourceGroup { 1062 // This is the controller resource group; once it has been 1063 // deleted, there's nothing left. 1064 return nil 1065 } 1066 logger.Debugf("- deleting internal subnet") 1067 if err := env.deleteInternalSubnet(); err != nil { 1068 return errors.Trace(err) 1069 } 1070 return nil 1071 } 1072 1073 func (env *azureEnviron) deleteResourceGroup() error { 1074 client := resources.GroupsClient{env.resources} 1075 result, err := client.Delete(env.resourceGroup) 1076 if err != nil { 1077 if result.Response == nil || result.StatusCode != http.StatusNotFound { 1078 return errors.Annotatef(err, "deleting resource group %q", env.resourceGroup) 1079 } 1080 } 1081 return nil 1082 } 1083 1084 var errNoFwGlobal = errors.New("global firewall mode is not supported") 1085 1086 // OpenPorts is specified in the Environ interface. However, Azure does not 1087 // support the global firewall mode. 1088 func (env *azureEnviron) OpenPorts(ports []jujunetwork.PortRange) error { 1089 return errNoFwGlobal 1090 } 1091 1092 // ClosePorts is specified in the Environ interface. However, Azure does not 1093 // support the global firewall mode. 1094 func (env *azureEnviron) ClosePorts(ports []jujunetwork.PortRange) error { 1095 return errNoFwGlobal 1096 } 1097 1098 // Ports is specified in the Environ interface. 1099 func (env *azureEnviron) Ports() ([]jujunetwork.PortRange, error) { 1100 return nil, errNoFwGlobal 1101 } 1102 1103 // Provider is specified in the Environ interface. 1104 func (env *azureEnviron) Provider() environs.EnvironProvider { 1105 return env.provider 1106 } 1107 1108 // resourceGroupName returns the name of the environment's resource group. 1109 func resourceGroupName(modelTag names.ModelTag, modelName string) string { 1110 return fmt.Sprintf("juju-%s-%s", modelName, resourceName(modelTag)) 1111 } 1112 1113 // resourceName returns the string to use for a resource's Name tag, 1114 // to help users identify Juju-managed resources in the Azure portal. 1115 // 1116 // Since resources are grouped under resource groups, we just use the 1117 // tag. 1118 func resourceName(tag names.Tag) string { 1119 return tag.String() 1120 } 1121 1122 // getInstanceTypes gets the instance types available for the configured 1123 // location, keyed by name. 1124 func (env *azureEnviron) getInstanceTypes() (map[string]instances.InstanceType, error) { 1125 env.mu.Lock() 1126 defer env.mu.Unlock() 1127 instanceTypes, err := env.getInstanceTypesLocked() 1128 if err != nil { 1129 return nil, errors.Annotate(err, "getting instance types") 1130 } 1131 return instanceTypes, nil 1132 } 1133 1134 // getInstanceTypesLocked returns the instance types for Azure, by listing the 1135 // role sizes available to the subscription. 1136 func (env *azureEnviron) getInstanceTypesLocked() (map[string]instances.InstanceType, error) { 1137 if env.instanceTypes != nil { 1138 return env.instanceTypes, nil 1139 } 1140 1141 location := env.config.location 1142 client := compute.VirtualMachineSizesClient{env.compute} 1143 1144 result, err := client.List(location) 1145 if err != nil { 1146 return nil, errors.Trace(err) 1147 } 1148 instanceTypes := make(map[string]instances.InstanceType) 1149 if result.Value != nil { 1150 for _, size := range *result.Value { 1151 instanceType := newInstanceType(size) 1152 instanceTypes[instanceType.Name] = instanceType 1153 // Create aliases for standard role sizes. 1154 if strings.HasPrefix(instanceType.Name, "Standard_") { 1155 instanceTypes[instanceType.Name[len("Standard_"):]] = instanceType 1156 } 1157 } 1158 } 1159 env.instanceTypes = instanceTypes 1160 return instanceTypes, nil 1161 } 1162 1163 // getInternalSubnetLocked queries the internal subnet for the environment. 1164 func (env *azureEnviron) getInternalSubnetLocked() (*network.Subnet, error) { 1165 client := network.SubnetsClient{env.network} 1166 vnetName := internalNetworkName 1167 subnetName := env.resourceGroup 1168 subnet, err := client.Get(env.controllerResourceGroup, vnetName, subnetName) 1169 if err != nil { 1170 return nil, errors.Annotate(err, "getting internal subnet") 1171 } 1172 return &subnet, nil 1173 } 1174 1175 // getStorageClient queries the storage account key, and uses it to construct 1176 // a new storage client. 1177 func (env *azureEnviron) getStorageClient() (internalazurestorage.Client, error) { 1178 env.mu.Lock() 1179 defer env.mu.Unlock() 1180 client, err := getStorageClient(env.provider.config.NewStorageClient, env.config) 1181 if err != nil { 1182 return nil, errors.Annotate(err, "getting storage client") 1183 } 1184 return client, nil 1185 } 1186 1187 // AgentMirror is specified in the tools.HasAgentMirror interface. 1188 // 1189 // TODO(axw) 2016-04-11 #1568715 1190 // When we have image simplestreams, we should rename this to "Region", 1191 // to implement simplestreams.HasRegion. 1192 func (env *azureEnviron) AgentMirror() (simplestreams.CloudSpec, error) { 1193 env.mu.Lock() 1194 defer env.mu.Unlock() 1195 return simplestreams.CloudSpec{ 1196 Region: env.config.location, 1197 // The endpoints published in simplestreams 1198 // data are the storage endpoints. 1199 Endpoint: fmt.Sprintf("https://%s/", env.config.storageEndpoint), 1200 }, nil 1201 }