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