github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/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  	stdcontext "context"
     8  	"fmt"
     9  	"net/http"
    10  	"net/url"
    11  	"sort"
    12  	"strings"
    13  	"sync"
    14  	"time"
    15  
    16  	"github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2018-10-01/compute"
    17  	"github.com/Azure/azure-sdk-for-go/services/network/mgmt/2018-08-01/network"
    18  	"github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2018-05-01/resources"
    19  	"github.com/Azure/azure-sdk-for-go/services/storage/mgmt/2018-07-01/storage"
    20  	azurestorage "github.com/Azure/azure-sdk-for-go/storage"
    21  	"github.com/Azure/go-autorest/autorest"
    22  	"github.com/Azure/go-autorest/autorest/to"
    23  	"github.com/juju/errors"
    24  	"github.com/juju/loggo"
    25  	"github.com/juju/os"
    26  	jujuseries "github.com/juju/os/series"
    27  	"github.com/juju/retry"
    28  	"github.com/juju/utils/arch"
    29  	"github.com/juju/version"
    30  	"gopkg.in/juju/names.v2"
    31  
    32  	"github.com/juju/juju/cloudconfig/instancecfg"
    33  	"github.com/juju/juju/cloudconfig/providerinit"
    34  	"github.com/juju/juju/core/constraints"
    35  	"github.com/juju/juju/core/instance"
    36  	"github.com/juju/juju/environs"
    37  	"github.com/juju/juju/environs/config"
    38  	"github.com/juju/juju/environs/instances"
    39  	"github.com/juju/juju/environs/simplestreams"
    40  	"github.com/juju/juju/environs/tags"
    41  
    42  	"github.com/juju/juju/environs/context"
    43  	"github.com/juju/juju/provider/azure/internal/armtemplates"
    44  	internalazureresources "github.com/juju/juju/provider/azure/internal/azureresources"
    45  	internalazurestorage "github.com/juju/juju/provider/azure/internal/azurestorage"
    46  	"github.com/juju/juju/provider/azure/internal/errorutils"
    47  	"github.com/juju/juju/provider/azure/internal/tracing"
    48  	"github.com/juju/juju/provider/azure/internal/useragent"
    49  	"github.com/juju/juju/provider/common"
    50  	"github.com/juju/juju/tools"
    51  )
    52  
    53  const (
    54  	jujuMachineNameTag = tags.JujuTagPrefix + "machine-name"
    55  
    56  	// minRootDiskSize is the minimum root disk size Azure
    57  	// accepts for a VM's OS disk.
    58  	// It will be used if none is specified by the user.
    59  	minRootDiskSize = 30 * 1024 // 30 GiB
    60  
    61  	// serviceErrorCodeDeploymentCannotBeCancelled is the error code for
    62  	// service errors in response to an attempt to cancel a deployment
    63  	// that cannot be cancelled.
    64  	serviceErrorCodeDeploymentCannotBeCancelled = "DeploymentCannotBeCancelled"
    65  
    66  	// serviceErrorCodeResourceGroupBeingDeleted is the error code for
    67  	// service errors in response to an attempt to cancel a deployment
    68  	// that has already started to be deleted.
    69  	serviceErrorCodeResourceGroupBeingDeleted = "ResourceGroupBeingDeleted"
    70  
    71  	// controllerAvailabilitySet is the name of the availability set
    72  	// used for controller machines.
    73  	controllerAvailabilitySet = "juju-controller"
    74  
    75  	computeAPIVersion = "2018-10-01"
    76  	networkAPIVersion = "2018-08-01"
    77  	storageAPIVersion = "2018-07-01"
    78  )
    79  
    80  type azureEnviron struct {
    81  	// provider is the azureEnvironProvider used to open this environment.
    82  	provider *azureEnvironProvider
    83  
    84  	// cloud defines the cloud configuration for this environment.
    85  	cloud environs.CloudSpec
    86  
    87  	// location is the canonicalized location name. Use this instead
    88  	// of cloud.Region in API calls.
    89  	location string
    90  
    91  	// subscriptionId is the Azure account subscription ID.
    92  	subscriptionId string
    93  
    94  	// storageEndpoint is the Azure storage endpoint. This is the host
    95  	// portion of the storage endpoint URL only; use this instead of
    96  	// cloud.StorageEndpoint in API calls.
    97  	storageEndpoint string
    98  
    99  	// resourceGroup is the name of the Resource Group in the Azure
   100  	// subscription that corresponds to the environment.
   101  	resourceGroup string
   102  
   103  	// envName is the name of the environment.
   104  	envName string
   105  
   106  	// authorizer is the authorizer we use for Azure.
   107  	authorizer *cloudSpecAuth
   108  
   109  	compute            compute.BaseClient
   110  	disk               compute.BaseClient
   111  	resources          resources.BaseClient
   112  	storage            storage.BaseClient
   113  	network            network.BaseClient
   114  	storageClient      azurestorage.Client
   115  	storageAccountName string
   116  
   117  	mu                     sync.Mutex
   118  	config                 *azureModelConfig
   119  	instanceTypes          map[string]instances.InstanceType
   120  	storageAccount         **storage.Account
   121  	storageAccountKey      *storage.AccountKey
   122  	commonResourcesCreated bool
   123  }
   124  
   125  var _ environs.Environ = (*azureEnviron)(nil)
   126  
   127  // newEnviron creates a new azureEnviron.
   128  func newEnviron(
   129  	provider *azureEnvironProvider,
   130  	cloud environs.CloudSpec,
   131  	cfg *config.Config,
   132  ) (*azureEnviron, error) {
   133  
   134  	// The Azure storage code wants the endpoint host only, not the URL.
   135  	storageEndpointURL, err := url.Parse(cloud.StorageEndpoint)
   136  	if err != nil {
   137  		return nil, errors.Annotate(err, "parsing storage endpoint URL")
   138  	}
   139  
   140  	env := azureEnviron{
   141  		provider:        provider,
   142  		cloud:           cloud,
   143  		location:        canonicalLocation(cloud.Region),
   144  		storageEndpoint: storageEndpointURL.Host,
   145  	}
   146  	if err := env.initEnviron(); err != nil {
   147  		return nil, errors.Trace(err)
   148  	}
   149  
   150  	if err := env.SetConfig(cfg); err != nil {
   151  		return nil, errors.Trace(err)
   152  	}
   153  
   154  	modelTag := names.NewModelTag(cfg.UUID())
   155  	env.resourceGroup = resourceGroupName(modelTag, cfg.Name())
   156  	env.envName = cfg.Name()
   157  
   158  	// We need a deterministic storage account name, so that we can
   159  	// defer creation of the storage account to the VM deployment,
   160  	// and retain the ability to create multiple deployments in
   161  	// parallel.
   162  	//
   163  	// We use the last 20 non-hyphen hex characters of the model's
   164  	// UUID as the storage account name, prefixed with "juju". The
   165  	// probability of clashing with another storage account should
   166  	// be negligible.
   167  	uuidAlphaNumeric := strings.Replace(env.config.Config.UUID(), "-", "", -1)
   168  	env.storageAccountName = "juju" + uuidAlphaNumeric[len(uuidAlphaNumeric)-20:]
   169  
   170  	return &env, nil
   171  }
   172  
   173  func (env *azureEnviron) initEnviron() error {
   174  	credAttrs := env.cloud.Credential.Attributes()
   175  	env.subscriptionId = credAttrs[credAttrSubscriptionId]
   176  	env.authorizer = &cloudSpecAuth{
   177  		cloud:  env.cloud,
   178  		sender: env.provider.config.Sender,
   179  	}
   180  
   181  	env.compute = compute.NewWithBaseURI(env.cloud.Endpoint, env.subscriptionId)
   182  	env.disk = compute.NewWithBaseURI(env.cloud.Endpoint, env.subscriptionId)
   183  	env.resources = resources.NewWithBaseURI(env.cloud.Endpoint, env.subscriptionId)
   184  	env.storage = storage.NewWithBaseURI(env.cloud.Endpoint, env.subscriptionId)
   185  	env.network = network.NewWithBaseURI(env.cloud.Endpoint, env.subscriptionId)
   186  	clients := map[string]*autorest.Client{
   187  		"azure.compute":   &env.compute.Client,
   188  		"azure.disk":      &env.disk.Client,
   189  		"azure.resources": &env.resources.Client,
   190  		"azure.storage":   &env.storage.Client,
   191  		"azure.network":   &env.network.Client,
   192  	}
   193  	for id, client := range clients {
   194  		useragent.UpdateClient(client)
   195  		client.Authorizer = env.authorizer
   196  		logger := loggo.GetLogger(id)
   197  		if env.provider.config.Sender != nil {
   198  			client.Sender = env.provider.config.Sender
   199  		}
   200  		client.ResponseInspector = tracing.RespondDecorator(logger)
   201  		client.RequestInspector = tracing.PrepareDecorator(logger)
   202  		if env.provider.config.RequestInspector != nil {
   203  			tracer := client.RequestInspector
   204  			inspector := env.provider.config.RequestInspector
   205  			client.RequestInspector = func(p autorest.Preparer) autorest.Preparer {
   206  				p = tracer(p)
   207  				p = inspector(p)
   208  				return p
   209  			}
   210  		}
   211  	}
   212  	return nil
   213  }
   214  
   215  // PrepareForBootstrap is part of the Environ interface.
   216  func (env *azureEnviron) PrepareForBootstrap(ctx environs.BootstrapContext) error {
   217  	if ctx.ShouldVerifyCredentials() {
   218  		if err := verifyCredentials(env, nil); err != nil {
   219  			return errors.Trace(err)
   220  		}
   221  	}
   222  	return nil
   223  }
   224  
   225  // Create is part of the Environ interface.
   226  func (env *azureEnviron) Create(ctx context.ProviderCallContext, args environs.CreateParams) error {
   227  	if err := verifyCredentials(env, ctx); err != nil {
   228  		return errors.Trace(err)
   229  	}
   230  	return errors.Trace(env.initResourceGroup(ctx, args.ControllerUUID, false))
   231  }
   232  
   233  // Bootstrap is part of the Environ interface.
   234  func (env *azureEnviron) Bootstrap(
   235  	ctx environs.BootstrapContext,
   236  	callCtx context.ProviderCallContext,
   237  	args environs.BootstrapParams,
   238  ) (*environs.BootstrapResult, error) {
   239  	if err := env.initResourceGroup(callCtx, args.ControllerConfig.ControllerUUID(), true); err != nil {
   240  		return nil, errors.Annotate(err, "creating controller resource group")
   241  	}
   242  	result, err := common.Bootstrap(ctx, env, callCtx, args)
   243  	if err != nil {
   244  		logger.Errorf("bootstrap failed, destroying model: %v", err)
   245  		if err := env.Destroy(callCtx); err != nil {
   246  			logger.Errorf("failed to destroy model: %v", err)
   247  		}
   248  		return nil, errors.Trace(err)
   249  	}
   250  	return result, nil
   251  }
   252  
   253  // initResourceGroup creates a resource group for this environment.
   254  func (env *azureEnviron) initResourceGroup(ctx context.ProviderCallContext, controllerUUID string, controller bool) error {
   255  	resourceGroupsClient := resources.GroupsClient{env.resources}
   256  
   257  	env.mu.Lock()
   258  	tags := tags.ResourceTags(
   259  		names.NewModelTag(env.config.Config.UUID()),
   260  		names.NewControllerTag(controllerUUID),
   261  		env.config,
   262  	)
   263  	env.mu.Unlock()
   264  
   265  	logger.Debugf("creating resource group %q", env.resourceGroup)
   266  	sdkCtx := stdcontext.Background()
   267  	if _, err := resourceGroupsClient.CreateOrUpdate(sdkCtx, env.resourceGroup, resources.Group{
   268  		Location: to.StringPtr(env.location),
   269  		Tags:     *to.StringMapPtr(tags),
   270  	}); err != nil {
   271  		return errorutils.HandleCredentialError(errors.Annotate(err, "creating resource group"), ctx)
   272  	}
   273  
   274  	if !controller {
   275  		// When we create a resource group for a non-controller model,
   276  		// we must create the common resources up-front. This is so
   277  		// that parallel deployments do not affect dynamic changes,
   278  		// e.g. those made by the firewaller. For the controller model,
   279  		// we fold the creation of these resources into the bootstrap
   280  		// machine's deployment.
   281  		if err := env.createCommonResourceDeployment(ctx, tags, nil); err != nil {
   282  			return errors.Trace(err)
   283  		}
   284  	}
   285  
   286  	// New models are not given a storage account. Initialise the
   287  	// storage account pointer to a pointer to a nil pointer, so
   288  	// "getStorageAccount" avoids making an API call.
   289  	env.storageAccount = new(*storage.Account)
   290  
   291  	return nil
   292  }
   293  
   294  func (env *azureEnviron) createCommonResourceDeployment(
   295  	ctx context.ProviderCallContext,
   296  	tags map[string]string,
   297  	rules []network.SecurityRule,
   298  	commonResources ...armtemplates.Resource,
   299  ) error {
   300  	commonResources = append(commonResources, networkTemplateResources(
   301  		env.location, tags, nil, rules,
   302  	)...)
   303  
   304  	// We perform this deployment asynchronously, to avoid blocking
   305  	// the "juju add-model" command; Create is called synchronously.
   306  	// Eventually we should have Create called asynchronously, but
   307  	// until then we do this, and ensure that the deployment has
   308  	// completed before we schedule additional deployments.
   309  	deploymentsClient := resources.DeploymentsClient{env.resources}
   310  	deploymentsClient.ResponseInspector = asyncCreationRespondDecorator(
   311  		deploymentsClient.ResponseInspector,
   312  	)
   313  	template := armtemplates.Template{Resources: commonResources}
   314  	if err := createDeployment(
   315  		ctx,
   316  		deploymentsClient,
   317  		env.resourceGroup,
   318  		"common", // deployment name
   319  		template,
   320  	); err != nil {
   321  		return errors.Trace(err)
   322  	}
   323  	return nil
   324  }
   325  
   326  // ControllerInstances is specified in the Environ interface.
   327  func (env *azureEnviron) ControllerInstances(ctx context.ProviderCallContext, controllerUUID string) ([]instance.Id, error) {
   328  	instances, err := env.allInstances(ctx, env.resourceGroup, false, true)
   329  	if err != nil {
   330  		return nil, err
   331  	}
   332  	if len(instances) == 0 {
   333  		return nil, environs.ErrNoInstances
   334  	}
   335  	ids := make([]instance.Id, len(instances))
   336  	for i, inst := range instances {
   337  		ids[i] = inst.Id()
   338  	}
   339  	return ids, nil
   340  }
   341  
   342  // Config is specified in the Environ interface.
   343  func (env *azureEnviron) Config() *config.Config {
   344  	env.mu.Lock()
   345  	defer env.mu.Unlock()
   346  	return env.config.Config
   347  }
   348  
   349  // SetConfig is specified in the Environ interface.
   350  func (env *azureEnviron) SetConfig(cfg *config.Config) error {
   351  	env.mu.Lock()
   352  	defer env.mu.Unlock()
   353  
   354  	var old *config.Config
   355  	if env.config != nil {
   356  		old = env.config.Config
   357  	}
   358  	ecfg, err := validateConfig(cfg, old)
   359  	if err != nil {
   360  		return err
   361  	}
   362  	env.config = ecfg
   363  
   364  	return nil
   365  }
   366  
   367  // ConstraintsValidator is defined on the Environs interface.
   368  func (env *azureEnviron) ConstraintsValidator(ctx context.ProviderCallContext) (constraints.Validator, error) {
   369  	instanceTypes, err := env.getInstanceTypes(ctx)
   370  	if err != nil {
   371  		return nil, err
   372  	}
   373  	instTypeNames := make([]string, 0, len(instanceTypes))
   374  	for instTypeName := range instanceTypes {
   375  		instTypeNames = append(instTypeNames, instTypeName)
   376  	}
   377  	sort.Strings(instTypeNames)
   378  
   379  	validator := constraints.NewValidator()
   380  	validator.RegisterUnsupported([]string{
   381  		constraints.CpuPower,
   382  		constraints.Tags,
   383  		constraints.VirtType,
   384  	})
   385  	validator.RegisterVocabulary(
   386  		constraints.Arch,
   387  		[]string{arch.AMD64},
   388  	)
   389  	validator.RegisterVocabulary(
   390  		constraints.InstanceType,
   391  		instTypeNames,
   392  	)
   393  	validator.RegisterConflicts(
   394  		[]string{constraints.InstanceType},
   395  		[]string{
   396  			constraints.Mem,
   397  			constraints.Cores,
   398  			constraints.Arch,
   399  		},
   400  	)
   401  	return validator, nil
   402  }
   403  
   404  // PrecheckInstance is defined on the environs.InstancePrechecker interface.
   405  func (env *azureEnviron) PrecheckInstance(ctx context.ProviderCallContext, args environs.PrecheckInstanceParams) error {
   406  	if args.Placement != "" {
   407  		return fmt.Errorf("unknown placement directive: %s", args.Placement)
   408  	}
   409  	if !args.Constraints.HasInstanceType() {
   410  		return nil
   411  	}
   412  	// Constraint has an instance-type constraint so let's see if it is valid.
   413  	instanceTypes, err := env.getInstanceTypes(ctx)
   414  	if err != nil {
   415  		return err
   416  	}
   417  	for _, instanceType := range instanceTypes {
   418  		if instanceType.Name == *args.Constraints.InstanceType {
   419  			return nil
   420  		}
   421  	}
   422  	return fmt.Errorf("invalid instance type %q", *args.Constraints.InstanceType)
   423  }
   424  
   425  // MaintainInstance is specified in the InstanceBroker interface.
   426  func (*azureEnviron) MaintainInstance(ctx context.ProviderCallContext, args environs.StartInstanceParams) error {
   427  	return nil
   428  }
   429  
   430  // StartInstance is specified in the InstanceBroker interface.
   431  func (env *azureEnviron) StartInstance(ctx context.ProviderCallContext, args environs.StartInstanceParams) (*environs.StartInstanceResult, error) {
   432  	if args.ControllerUUID == "" {
   433  		return nil, errors.New("missing controller UUID")
   434  	}
   435  
   436  	// Get the required configuration and config-dependent information
   437  	// required to create the instance. We take the lock just once, to
   438  	// ensure we obtain all information based on the same configuration.
   439  	env.mu.Lock()
   440  	envTags := tags.ResourceTags(
   441  		names.NewModelTag(env.config.Config.UUID()),
   442  		names.NewControllerTag(args.ControllerUUID),
   443  		env.config,
   444  	)
   445  	storageAccountType := env.config.storageAccountType
   446  	imageStream := env.config.ImageStream()
   447  	instanceTypes, err := env.getInstanceTypesLocked(ctx)
   448  	if err != nil {
   449  		env.mu.Unlock()
   450  		return nil, errors.Trace(err)
   451  	}
   452  	env.mu.Unlock()
   453  
   454  	// If the user has not specified a root-disk size, then
   455  	// set a sensible default.
   456  	var rootDisk uint64
   457  	// Azure complains if we try and specify a root disk size less than the minimum.
   458  	// See http://pad.lv/1645408
   459  	if args.Constraints.RootDisk != nil && *args.Constraints.RootDisk > minRootDiskSize {
   460  		rootDisk = *args.Constraints.RootDisk
   461  	} else {
   462  		rootDisk = minRootDiskSize
   463  		args.Constraints.RootDisk = &rootDisk
   464  	}
   465  
   466  	// Identify the instance type and image to provision.
   467  	series := args.Tools.OneSeries()
   468  	instanceSpec, err := findInstanceSpec(
   469  		ctx,
   470  		compute.VirtualMachineImagesClient{env.compute},
   471  		instanceTypes,
   472  		&instances.InstanceConstraint{
   473  			Region:      env.location,
   474  			Series:      series,
   475  			Arches:      args.Tools.Arches(),
   476  			Constraints: args.Constraints,
   477  		},
   478  		imageStream,
   479  	)
   480  	if err != nil {
   481  		return nil, err
   482  	}
   483  	if rootDisk < instanceSpec.InstanceType.RootDisk {
   484  		// The InstanceType's RootDisk is set to the maximum
   485  		// OS disk size; override it with the user-specified
   486  		// or default root disk size.
   487  		instanceSpec.InstanceType.RootDisk = rootDisk
   488  	}
   489  
   490  	// Windows images are 127GiB, and cannot be made smaller.
   491  	const windowsMinRootDiskMB = 127 * 1024
   492  	seriesOS, err := jujuseries.GetOSFromSeries(series)
   493  	if err != nil {
   494  		return nil, errors.Trace(err)
   495  	}
   496  	if seriesOS == os.Windows {
   497  		if instanceSpec.InstanceType.RootDisk < windowsMinRootDiskMB {
   498  			instanceSpec.InstanceType.RootDisk = windowsMinRootDiskMB
   499  		}
   500  	}
   501  
   502  	// Pick tools by filtering the available tools down to the architecture of
   503  	// the image that will be provisioned.
   504  	selectedTools, err := args.Tools.Match(tools.Filter{
   505  		Arch: instanceSpec.Image.Arch,
   506  	})
   507  	if err != nil {
   508  		return nil, errors.Trace(err)
   509  	}
   510  	logger.Infof("picked agent binaries %q", selectedTools[0].Version)
   511  
   512  	// Finalize the instance config, which we'll render to CustomData below.
   513  	if err := args.InstanceConfig.SetTools(selectedTools); err != nil {
   514  		return nil, errors.Trace(err)
   515  	}
   516  	if err := instancecfg.FinishInstanceConfig(
   517  		args.InstanceConfig, env.Config(),
   518  	); err != nil {
   519  		return nil, err
   520  	}
   521  
   522  	machineTag := names.NewMachineTag(args.InstanceConfig.MachineId)
   523  	vmName := resourceName(machineTag)
   524  	vmTags := make(map[string]string)
   525  	for k, v := range args.InstanceConfig.Tags {
   526  		vmTags[k] = v
   527  	}
   528  	// jujuMachineNameTag identifies the VM name, in which is encoded
   529  	// the Juju machine name. We tag all resources related to the
   530  	// machine with this.
   531  	vmTags[jujuMachineNameTag] = vmName
   532  
   533  	if err := env.createVirtualMachine(
   534  		ctx, vmName, vmTags, envTags,
   535  		instanceSpec, args.InstanceConfig,
   536  		storageAccountType,
   537  	); err != nil {
   538  		logger.Errorf("creating instance failed, destroying: %v", err)
   539  		if err := env.StopInstances(ctx, instance.Id(vmName)); err != nil {
   540  			logger.Errorf("could not destroy failed virtual machine: %v", err)
   541  		}
   542  		return nil, errors.Annotatef(err, "creating virtual machine %q", vmName)
   543  	}
   544  
   545  	// Note: the instance is initialised without addresses to keep the
   546  	// API chatter down. We will refresh the instance if we need to know
   547  	// the addresses.
   548  	inst := &azureInstance{vmName, "Creating", env, nil, nil}
   549  	amd64 := arch.AMD64
   550  	hc := &instance.HardwareCharacteristics{
   551  		Arch:     &amd64,
   552  		Mem:      &instanceSpec.InstanceType.Mem,
   553  		RootDisk: &instanceSpec.InstanceType.RootDisk,
   554  		CpuCores: &instanceSpec.InstanceType.CpuCores,
   555  	}
   556  	return &environs.StartInstanceResult{
   557  		Instance: inst,
   558  		Hardware: hc,
   559  	}, nil
   560  }
   561  
   562  // createVirtualMachine creates a virtual machine and related resources.
   563  //
   564  // All resources created are tagged with the specified "vmTags", so if
   565  // this function fails then all resources can be deleted by tag.
   566  func (env *azureEnviron) createVirtualMachine(
   567  	ctx context.ProviderCallContext,
   568  	vmName string,
   569  	vmTags, envTags map[string]string,
   570  	instanceSpec *instances.InstanceSpec,
   571  	instanceConfig *instancecfg.InstanceConfig,
   572  	storageAccountType string,
   573  ) error {
   574  	deploymentsClient := resources.DeploymentsClient{
   575  		BaseClient: env.resources,
   576  	}
   577  	apiPorts := make([]int, 0, 2)
   578  	if instanceConfig.Controller != nil {
   579  		apiPorts = append(apiPorts, instanceConfig.Controller.Config.APIPort())
   580  		if instanceConfig.Controller.Config.AutocertDNSName() != "" {
   581  			// Open port 80 as well as it handles Let's Encrypt HTTP challenge.
   582  			apiPorts = append(apiPorts, 80)
   583  		}
   584  	} else {
   585  		ports := instanceConfig.APIInfo.Ports()
   586  		if len(ports) != 1 {
   587  			return errors.Errorf("expected one API port, found %v", ports)
   588  		}
   589  		apiPorts = append(apiPorts, ports[0])
   590  	}
   591  
   592  	var nicDependsOn, vmDependsOn []string
   593  	var resources []armtemplates.Resource
   594  	createCommonResources := instanceConfig.Bootstrap != nil
   595  	if createCommonResources {
   596  		// We're starting the bootstrap machine, so we will create the
   597  		// common resources in the same deployment.
   598  		resources = append(resources,
   599  			networkTemplateResources(env.location, envTags, apiPorts, nil)...,
   600  		)
   601  		nicDependsOn = append(nicDependsOn, fmt.Sprintf(
   602  			`[resourceId('Microsoft.Network/virtualNetworks', '%s')]`,
   603  			internalNetworkName,
   604  		))
   605  	} else {
   606  		// Wait for the common resource deployment to complete.
   607  		if err := env.waitCommonResourcesCreated(); err != nil {
   608  			return errors.Annotate(
   609  				err, "waiting for common resources to be created",
   610  			)
   611  		}
   612  	}
   613  
   614  	maybeStorageAccount, err := env.getStorageAccount()
   615  	if errors.IsNotFound(err) {
   616  		// Only models created prior to Juju 2.3 will have a storage
   617  		// account. Juju 2.3 onwards exclusively uses managed disks
   618  		// for all new models, and handles both managed and unmanaged
   619  		// disks for upgraded models.
   620  		maybeStorageAccount = nil
   621  	} else if err != nil {
   622  		return errors.Trace(err)
   623  	}
   624  
   625  	osProfile, seriesOS, err := newOSProfile(
   626  		vmName, instanceConfig,
   627  		env.provider.config.RandomWindowsAdminPassword,
   628  		env.provider.config.GenerateSSHKey,
   629  	)
   630  	if err != nil {
   631  		return errors.Annotate(err, "creating OS profile")
   632  	}
   633  	storageProfile, err := newStorageProfile(
   634  		vmName,
   635  		maybeStorageAccount,
   636  		storageAccountType,
   637  		instanceSpec,
   638  	)
   639  	if err != nil {
   640  		return errors.Annotate(err, "creating storage profile")
   641  	}
   642  
   643  	var availabilitySetSubResource *compute.SubResource
   644  	availabilitySetName, err := availabilitySetName(
   645  		vmName, vmTags, instanceConfig.Controller != nil,
   646  	)
   647  	if err != nil {
   648  		return errors.Annotate(err, "getting availability set name")
   649  	}
   650  	if availabilitySetName != "" {
   651  		availabilitySetId := fmt.Sprintf(
   652  			`[resourceId('Microsoft.Compute/availabilitySets','%s')]`,
   653  			availabilitySetName,
   654  		)
   655  		var (
   656  			availabilitySetProperties  interface{}
   657  			availabilityStorageOptions *storage.Sku
   658  		)
   659  		if maybeStorageAccount == nil {
   660  			// This model uses managed disks; we must create
   661  			// the availability set as "aligned" to support
   662  			// them.
   663  			availabilitySetProperties = &compute.AvailabilitySetProperties{
   664  				// Azure complains when the fault domain count
   665  				// is not specified, even though it is meant
   666  				// to be optional and default to the maximum.
   667  				// The maximum depends on the location, and
   668  				// there is no API to query it.
   669  				PlatformFaultDomainCount: to.Int32Ptr(maxFaultDomains(env.location)),
   670  			}
   671  			// Availability needs to be 'Aligned' to support managed disks.
   672  			availabilityStorageOptions = &storage.Sku{
   673  				Name: "Aligned",
   674  			}
   675  		}
   676  		resources = append(resources, armtemplates.Resource{
   677  			APIVersion: computeAPIVersion,
   678  			Type:       "Microsoft.Compute/availabilitySets",
   679  			Name:       availabilitySetName,
   680  			Location:   env.location,
   681  			Tags:       envTags,
   682  			Properties: availabilitySetProperties,
   683  			StorageSku: availabilityStorageOptions,
   684  		})
   685  		availabilitySetSubResource = &compute.SubResource{
   686  			ID: to.StringPtr(availabilitySetId),
   687  		}
   688  		vmDependsOn = append(vmDependsOn, availabilitySetId)
   689  	}
   690  
   691  	publicIPAddressName := vmName + "-public-ip"
   692  	publicIPAddressId := fmt.Sprintf(`[resourceId('Microsoft.Network/publicIPAddresses', '%s')]`, publicIPAddressName)
   693  	resources = append(resources, armtemplates.Resource{
   694  		APIVersion: networkAPIVersion,
   695  		Type:       "Microsoft.Network/publicIPAddresses",
   696  		Name:       publicIPAddressName,
   697  		Location:   env.location,
   698  		Tags:       vmTags,
   699  		StorageSku: &storage.Sku{Name: "Standard", Tier: "Regional"},
   700  		Properties: &network.PublicIPAddressPropertiesFormat{
   701  			PublicIPAddressVersion:   network.IPv4,
   702  			PublicIPAllocationMethod: network.Static,
   703  		},
   704  	})
   705  
   706  	// Controller and non-controller machines are assigned to separate
   707  	// subnets. This enables us to create controller-specific NSG rules
   708  	// just by targeting the controller subnet.
   709  	subnetName := internalSubnetName
   710  	subnetPrefix := internalSubnetPrefix
   711  	if instanceConfig.Controller != nil {
   712  		subnetName = controllerSubnetName
   713  		subnetPrefix = controllerSubnetPrefix
   714  	}
   715  	subnetId := fmt.Sprintf(
   716  		`[concat(resourceId('Microsoft.Network/virtualNetworks', '%s'), '/subnets/%s')]`,
   717  		internalNetworkName, subnetName,
   718  	)
   719  
   720  	privateIP, err := machineSubnetIP(subnetPrefix, instanceConfig.MachineId)
   721  	if err != nil {
   722  		return errors.Annotatef(err, "computing private IP address")
   723  	}
   724  	nicName := vmName + "-primary"
   725  	nicId := fmt.Sprintf(`[resourceId('Microsoft.Network/networkInterfaces', '%s')]`, nicName)
   726  	nicDependsOn = append(nicDependsOn, publicIPAddressId)
   727  	ipConfigurations := []network.InterfaceIPConfiguration{{
   728  		Name: to.StringPtr("primary"),
   729  		InterfaceIPConfigurationPropertiesFormat: &network.InterfaceIPConfigurationPropertiesFormat{
   730  			Primary:                   to.BoolPtr(true),
   731  			PrivateIPAddress:          to.StringPtr(privateIP.String()),
   732  			PrivateIPAllocationMethod: network.Static,
   733  			Subnet:                    &network.Subnet{ID: to.StringPtr(subnetId)},
   734  			PublicIPAddress: &network.PublicIPAddress{
   735  				ID: to.StringPtr(publicIPAddressId),
   736  			},
   737  		},
   738  	}}
   739  	resources = append(resources, armtemplates.Resource{
   740  		APIVersion: networkAPIVersion,
   741  		Type:       "Microsoft.Network/networkInterfaces",
   742  		Name:       nicName,
   743  		Location:   env.location,
   744  		Tags:       vmTags,
   745  		Properties: &network.InterfacePropertiesFormat{
   746  			IPConfigurations: &ipConfigurations,
   747  		},
   748  		DependsOn: nicDependsOn,
   749  	})
   750  
   751  	nics := []compute.NetworkInterfaceReference{{
   752  		ID: to.StringPtr(nicId),
   753  		NetworkInterfaceReferenceProperties: &compute.NetworkInterfaceReferenceProperties{
   754  			Primary: to.BoolPtr(true),
   755  		},
   756  	}}
   757  	vmDependsOn = append(vmDependsOn, nicId)
   758  	resources = append(resources, armtemplates.Resource{
   759  		APIVersion: computeAPIVersion,
   760  		Type:       "Microsoft.Compute/virtualMachines",
   761  		Name:       vmName,
   762  		Location:   env.location,
   763  		Tags:       vmTags,
   764  		Properties: &compute.VirtualMachineProperties{
   765  			HardwareProfile: &compute.HardwareProfile{
   766  				VMSize: compute.VirtualMachineSizeTypes(
   767  					instanceSpec.InstanceType.Name,
   768  				),
   769  			},
   770  			StorageProfile: storageProfile,
   771  			OsProfile:      osProfile,
   772  			NetworkProfile: &compute.NetworkProfile{
   773  				&nics,
   774  			},
   775  			AvailabilitySet: availabilitySetSubResource,
   776  		},
   777  		DependsOn: vmDependsOn,
   778  	})
   779  
   780  	// On Windows and CentOS, we must add the CustomScript VM
   781  	// extension to run the CustomData script.
   782  	switch seriesOS {
   783  	case os.Windows, os.CentOS:
   784  		properties, err := vmExtensionProperties(seriesOS)
   785  		if err != nil {
   786  			return errors.Annotate(
   787  				err, "creating virtual machine extension",
   788  			)
   789  		}
   790  		resources = append(resources, armtemplates.Resource{
   791  			APIVersion: computeAPIVersion,
   792  			Type:       "Microsoft.Compute/virtualMachines/extensions",
   793  			Name:       vmName + "/" + extensionName,
   794  			Location:   env.location,
   795  			Tags:       vmTags,
   796  			Properties: properties,
   797  			DependsOn:  []string{"Microsoft.Compute/virtualMachines/" + vmName},
   798  		})
   799  	}
   800  
   801  	logger.Debugf("- creating virtual machine deployment")
   802  	template := armtemplates.Template{Resources: resources}
   803  	// NOTE(axw) VMs take a long time to go to "Succeeded", so we do not
   804  	// block waiting for them to be fully provisioned. This means we won't
   805  	// return an error from StartInstance if the VM fails provisioning;
   806  	// we will instead report the error via the instance's status.
   807  	deploymentsClient.ResponseInspector = asyncCreationRespondDecorator(
   808  		deploymentsClient.ResponseInspector,
   809  	)
   810  	if err := createDeployment(
   811  		ctx,
   812  		deploymentsClient,
   813  		env.resourceGroup,
   814  		vmName, // deployment name
   815  		template,
   816  	); err != nil {
   817  		return errors.Trace(err)
   818  	}
   819  	return nil
   820  }
   821  
   822  // maxFaultDomains returns the maximum number of fault domains for the
   823  // given location/region. The numbers were taken from
   824  // https://docs.microsoft.com/en-au/azure/virtual-machines/windows/manage-availability,
   825  // as at 31 August 2017.
   826  func maxFaultDomains(location string) int32 {
   827  	// From the page linked in the doc comment:
   828  	// "The number of fault domains for managed availability sets varies
   829  	// by region - either two or three per region."
   830  	//
   831  	// We record those that at the time of writing have 3. Anything
   832  	// else has at least 2, so we just assume 2.
   833  	switch location {
   834  	case
   835  		"eastus",
   836  		"eastus2",
   837  		"westus",
   838  		"centralus",
   839  		"northcentralus",
   840  		"southcentralus",
   841  		"northeurope",
   842  		"westeurope":
   843  		return 3
   844  	}
   845  	return 2
   846  }
   847  
   848  // waitCommonResourcesCreated waits for the "common" deployment to complete.
   849  func (env *azureEnviron) waitCommonResourcesCreated() error {
   850  	env.mu.Lock()
   851  	defer env.mu.Unlock()
   852  	if env.commonResourcesCreated {
   853  		return nil
   854  	}
   855  	deployment, err := env.waitCommonResourcesCreatedLocked()
   856  	if err != nil {
   857  		return errors.Trace(err)
   858  	}
   859  	env.commonResourcesCreated = true
   860  	if deployment != nil {
   861  		// Check if the common deployment created
   862  		// a storage account. If it didn't, we can
   863  		// avoid a query for the storage account.
   864  		var hasStorageAccount bool
   865  		if deployment.Properties.Providers != nil {
   866  			for _, p := range *deployment.Properties.Providers {
   867  				if to.String(p.Namespace) != "Microsoft.Storage" {
   868  					continue
   869  				}
   870  				if p.ResourceTypes == nil {
   871  					continue
   872  				}
   873  				for _, rt := range *p.ResourceTypes {
   874  					if to.String(rt.ResourceType) != "storageAccounts" {
   875  						continue
   876  					}
   877  					hasStorageAccount = true
   878  					break
   879  				}
   880  				break
   881  			}
   882  		}
   883  		if !hasStorageAccount {
   884  			env.storageAccount = new(*storage.Account)
   885  		}
   886  	}
   887  	return nil
   888  }
   889  
   890  type deploymentIncompleteError struct {
   891  	error
   892  }
   893  
   894  func (env *azureEnviron) waitCommonResourcesCreatedLocked() (*resources.DeploymentExtended, error) {
   895  	deploymentsClient := resources.DeploymentsClient{env.resources}
   896  
   897  	// Release the lock while we're waiting, to avoid blocking others.
   898  	env.mu.Unlock()
   899  	defer env.mu.Lock()
   900  
   901  	// Wait for up to 5 minutes, with a 5 second polling interval,
   902  	// for the "common" deployment to be in one of the terminal
   903  	// states. The deployment typically takes only around 30 seconds,
   904  	// but we allow for a longer duration to be defensive.
   905  	var deployment *resources.DeploymentExtended
   906  	sdkCtx := stdcontext.Background()
   907  	waitDeployment := func() error {
   908  		result, err := deploymentsClient.Get(sdkCtx, env.resourceGroup, "common")
   909  		if err != nil {
   910  			if result.StatusCode == http.StatusNotFound {
   911  				// The controller model does not have a "common"
   912  				// deployment, as its common resources are created
   913  				// in the machine-0 deployment to keep bootstrap times
   914  				// optimal. Treat lack of a common deployment as an
   915  				// indication that the model is the controller model.
   916  				return nil
   917  			}
   918  			return errors.Annotate(err, "querying common deployment")
   919  		}
   920  		if result.Properties == nil {
   921  			return deploymentIncompleteError{errors.New("deployment incomplete")}
   922  		}
   923  
   924  		state := to.String(result.Properties.ProvisioningState)
   925  		if state == "Succeeded" {
   926  			// The deployment has succeeded, so the resources are
   927  			// ready for use.
   928  			deployment = &result
   929  			return nil
   930  		}
   931  		err = errors.Errorf("common resource deployment status is %q", state)
   932  		switch state {
   933  		case "Canceled", "Failed", "Deleted":
   934  		default:
   935  			err = deploymentIncompleteError{err}
   936  		}
   937  		return err
   938  	}
   939  	if err := retry.Call(retry.CallArgs{
   940  		Func: waitDeployment,
   941  		IsFatalError: func(err error) bool {
   942  			_, ok := err.(deploymentIncompleteError)
   943  			return !ok
   944  		},
   945  		Attempts:    -1,
   946  		Delay:       5 * time.Second,
   947  		MaxDuration: 5 * time.Minute,
   948  		Clock:       env.provider.config.RetryClock,
   949  	}); err != nil {
   950  		return nil, errors.Trace(err)
   951  	}
   952  	return deployment, nil
   953  }
   954  
   955  // createAvailabilitySet creates the availability set for a machine to use
   956  // if it doesn't already exist, and returns the availability set's ID. The
   957  // algorithm used for choosing the availability set is:
   958  //  - if the machine is a controller, use the availability set name
   959  //    "juju-controller";
   960  //  - if the machine has units assigned, create an availability
   961  //    name with a name based on the value of the tags.JujuUnitsDeployed tag
   962  //    in vmTags, if it exists;
   963  //  - otherwise, do not assign the machine to an availability set
   964  func availabilitySetName(
   965  	vmName string,
   966  	vmTags map[string]string,
   967  	controller bool,
   968  ) (string, error) {
   969  	logger.Debugf("selecting availability set for %q", vmName)
   970  	if controller {
   971  		return controllerAvailabilitySet, nil
   972  	}
   973  
   974  	// We'll have to create an availability set. Use the name of one of the
   975  	// services assigned to the machine.
   976  	var availabilitySetName string
   977  	if unitNames, ok := vmTags[tags.JujuUnitsDeployed]; ok {
   978  		for _, unitName := range strings.Fields(unitNames) {
   979  			if !names.IsValidUnit(unitName) {
   980  				continue
   981  			}
   982  			serviceName, err := names.UnitApplication(unitName)
   983  			if err != nil {
   984  				return "", errors.Annotate(err, "getting application name")
   985  			}
   986  			availabilitySetName = serviceName
   987  			break
   988  		}
   989  	}
   990  	return availabilitySetName, nil
   991  }
   992  
   993  // newStorageProfile creates the storage profile for a virtual machine,
   994  // based on the series and chosen instance spec.
   995  func newStorageProfile(
   996  	vmName string,
   997  	maybeStorageAccount *storage.Account,
   998  	storageAccountType string,
   999  	instanceSpec *instances.InstanceSpec,
  1000  ) (*compute.StorageProfile, error) {
  1001  	logger.Debugf("creating storage profile for %q", vmName)
  1002  
  1003  	urnParts := strings.SplitN(instanceSpec.Image.Id, ":", 4)
  1004  	if len(urnParts) != 4 {
  1005  		return nil, errors.Errorf("invalid image ID %q", instanceSpec.Image.Id)
  1006  	}
  1007  	publisher := urnParts[0]
  1008  	offer := urnParts[1]
  1009  	sku := urnParts[2]
  1010  	version := urnParts[3]
  1011  
  1012  	osDiskName := vmName
  1013  	osDiskSizeGB := mibToGB(instanceSpec.InstanceType.RootDisk)
  1014  	osDisk := &compute.OSDisk{
  1015  		Name:         to.StringPtr(osDiskName),
  1016  		CreateOption: compute.DiskCreateOptionTypesFromImage,
  1017  		Caching:      compute.CachingTypesReadWrite,
  1018  		DiskSizeGB:   to.Int32Ptr(int32(osDiskSizeGB)),
  1019  	}
  1020  
  1021  	if maybeStorageAccount == nil {
  1022  		// This model uses managed disks.
  1023  		osDisk.ManagedDisk = &compute.ManagedDiskParameters{
  1024  			StorageAccountType: compute.StorageAccountTypes(storageAccountType),
  1025  		}
  1026  	} else {
  1027  		// This model uses unmanaged disks.
  1028  		osDiskVhdRoot := blobContainerURL(maybeStorageAccount, osDiskVHDContainer)
  1029  		vhdURI := osDiskVhdRoot + osDiskName + vhdExtension
  1030  		osDisk.Vhd = &compute.VirtualHardDisk{to.StringPtr(vhdURI)}
  1031  	}
  1032  
  1033  	return &compute.StorageProfile{
  1034  		ImageReference: &compute.ImageReference{
  1035  			Publisher: to.StringPtr(publisher),
  1036  			Offer:     to.StringPtr(offer),
  1037  			Sku:       to.StringPtr(sku),
  1038  			Version:   to.StringPtr(version),
  1039  		},
  1040  		OsDisk: osDisk,
  1041  	}, nil
  1042  }
  1043  
  1044  func mibToGB(mib uint64) uint64 {
  1045  	b := float64(mib * 1024 * 1024)
  1046  	return uint64(b / (1000 * 1000 * 1000))
  1047  }
  1048  
  1049  func newOSProfile(
  1050  	vmName string,
  1051  	instanceConfig *instancecfg.InstanceConfig,
  1052  	randomAdminPassword func() string,
  1053  	generateSSHKey func(string) (string, string, error),
  1054  ) (*compute.OSProfile, os.OSType, error) {
  1055  	logger.Debugf("creating OS profile for %q", vmName)
  1056  
  1057  	customData, err := providerinit.ComposeUserData(instanceConfig, nil, AzureRenderer{})
  1058  	if err != nil {
  1059  		return nil, os.Unknown, errors.Annotate(err, "composing user data")
  1060  	}
  1061  
  1062  	osProfile := &compute.OSProfile{
  1063  		ComputerName: to.StringPtr(vmName),
  1064  		CustomData:   to.StringPtr(string(customData)),
  1065  	}
  1066  
  1067  	seriesOS, err := jujuseries.GetOSFromSeries(instanceConfig.Series)
  1068  	if err != nil {
  1069  		return nil, os.Unknown, errors.Trace(err)
  1070  	}
  1071  	switch seriesOS {
  1072  	case os.Ubuntu, os.CentOS:
  1073  		// SSH keys are handled by custom data, but must also be
  1074  		// specified in order to forego providing a password, and
  1075  		// disable password authentication.
  1076  		authorizedKeys := instanceConfig.AuthorizedKeys
  1077  		if len(authorizedKeys) == 0 {
  1078  			// Azure requires that machines be provisioned with
  1079  			// either a password or at least one SSH key. We
  1080  			// generate a key-pair to make Azure happy, but throw
  1081  			// away the private key so that nobody will be able
  1082  			// to log into the machine directly unless the keys
  1083  			// are updated with one that Juju tracks.
  1084  			_, public, err := generateSSHKey("")
  1085  			if err != nil {
  1086  				return nil, os.Unknown, errors.Trace(err)
  1087  			}
  1088  			authorizedKeys = public
  1089  		}
  1090  
  1091  		publicKeys := []compute.SSHPublicKey{{
  1092  			Path:    to.StringPtr("/home/ubuntu/.ssh/authorized_keys"),
  1093  			KeyData: to.StringPtr(authorizedKeys),
  1094  		}}
  1095  		osProfile.AdminUsername = to.StringPtr("ubuntu")
  1096  		osProfile.LinuxConfiguration = &compute.LinuxConfiguration{
  1097  			DisablePasswordAuthentication: to.BoolPtr(true),
  1098  			SSH:                           &compute.SSHConfiguration{PublicKeys: &publicKeys},
  1099  		}
  1100  	case os.Windows:
  1101  		osProfile.AdminUsername = to.StringPtr("JujuAdministrator")
  1102  		// A password is required by Azure, but we will never use it.
  1103  		// We generate something sufficiently long and random that it
  1104  		// should be infeasible to guess.
  1105  		osProfile.AdminPassword = to.StringPtr(randomAdminPassword())
  1106  		osProfile.WindowsConfiguration = &compute.WindowsConfiguration{
  1107  			ProvisionVMAgent:       to.BoolPtr(true),
  1108  			EnableAutomaticUpdates: to.BoolPtr(true),
  1109  			// TODO(?) add WinRM configuration here.
  1110  		}
  1111  	default:
  1112  		return nil, os.Unknown, errors.NotSupportedf("%s", seriesOS)
  1113  	}
  1114  	return osProfile, seriesOS, nil
  1115  }
  1116  
  1117  // StopInstances is specified in the InstanceBroker interface.
  1118  func (env *azureEnviron) StopInstances(ctx context.ProviderCallContext, ids ...instance.Id) error {
  1119  	if len(ids) == 0 {
  1120  		return nil
  1121  	}
  1122  
  1123  	// First up, cancel the deployments. Then we can identify the resources
  1124  	// that need to be deleted without racing with their creation.
  1125  	var wg sync.WaitGroup
  1126  	var existing int
  1127  	cancelResults := make([]error, len(ids))
  1128  	for i, id := range ids {
  1129  		logger.Debugf("canceling deployment for instance %q", id)
  1130  		wg.Add(1)
  1131  		go func(i int, id instance.Id) {
  1132  			defer wg.Done()
  1133  			sdkCtx := stdcontext.Background()
  1134  			cancelResults[i] = errors.Annotatef(
  1135  				env.cancelDeployment(ctx, sdkCtx, string(id)),
  1136  				"canceling deployment %q", id,
  1137  			)
  1138  		}(i, id)
  1139  	}
  1140  	wg.Wait()
  1141  	for _, err := range cancelResults {
  1142  		if err == nil {
  1143  			existing++
  1144  		} else if !errors.IsNotFound(err) {
  1145  			return err
  1146  		}
  1147  	}
  1148  	if existing == 0 {
  1149  		// None of the instances exist, so we can stop now.
  1150  		return nil
  1151  	}
  1152  
  1153  	maybeStorageClient, _, err := env.maybeGetStorageClient()
  1154  	if err != nil {
  1155  		return errors.Trace(err)
  1156  	}
  1157  
  1158  	// List network interfaces and public IP addresses.
  1159  	instanceNics, err := instanceNetworkInterfaces(
  1160  		ctx,
  1161  		env.resourceGroup,
  1162  		network.InterfacesClient{env.network},
  1163  	)
  1164  	if err != nil {
  1165  		return errors.Trace(err)
  1166  	}
  1167  	instancePips, err := instancePublicIPAddresses(
  1168  		ctx,
  1169  		env.resourceGroup,
  1170  		network.PublicIPAddressesClient{env.network},
  1171  	)
  1172  	if err != nil {
  1173  		return errors.Trace(err)
  1174  	}
  1175  
  1176  	// Delete the deployments, virtual machines, and related resources.
  1177  	deleteResults := make([]error, len(ids))
  1178  	for i, id := range ids {
  1179  		if errors.IsNotFound(cancelResults[i]) {
  1180  			continue
  1181  		}
  1182  		// The deployment does not exist, so there's nothing more to do.
  1183  		logger.Debugf("deleting instance %q", id)
  1184  		wg.Add(1)
  1185  		go func(i int, id instance.Id) {
  1186  			defer wg.Done()
  1187  			sdkCtx := stdcontext.Background()
  1188  			err := env.deleteVirtualMachine(
  1189  				ctx,
  1190  				sdkCtx,
  1191  				id,
  1192  				maybeStorageClient,
  1193  				instanceNics[id],
  1194  				instancePips[id],
  1195  			)
  1196  			deleteResults[i] = errors.Annotatef(
  1197  				err, "deleting instance %q", id,
  1198  			)
  1199  		}(i, id)
  1200  	}
  1201  	wg.Wait()
  1202  	for _, err := range deleteResults {
  1203  		if err != nil && !errors.IsNotFound(err) {
  1204  			return errors.Trace(err)
  1205  		}
  1206  	}
  1207  
  1208  	return nil
  1209  }
  1210  
  1211  // cancelDeployment cancels a template deployment.
  1212  func (env *azureEnviron) cancelDeployment(ctx context.ProviderCallContext, sdkCtx stdcontext.Context, name string) error {
  1213  	deploymentsClient := resources.DeploymentsClient{env.resources}
  1214  	logger.Debugf("- canceling deployment %q", name)
  1215  	cancelResult, err := deploymentsClient.Cancel(sdkCtx, env.resourceGroup, name)
  1216  	if err != nil {
  1217  		if cancelResult.Response != nil {
  1218  			switch cancelResult.StatusCode {
  1219  			case http.StatusNotFound:
  1220  				return errors.NewNotFound(err, fmt.Sprintf("deployment %q not found", name))
  1221  			case http.StatusConflict:
  1222  				if err, ok := errorutils.ServiceError(err); ok {
  1223  					if err.Code == serviceErrorCodeDeploymentCannotBeCancelled ||
  1224  						err.Code == serviceErrorCodeResourceGroupBeingDeleted {
  1225  						// Deployments can only canceled while they're running.
  1226  						return nil
  1227  					}
  1228  				}
  1229  			}
  1230  		}
  1231  		return errorutils.HandleCredentialError(errors.Annotatef(err, "canceling deployment %q", name), ctx)
  1232  	}
  1233  	return nil
  1234  }
  1235  
  1236  // deleteVirtualMachine deletes a virtual machine and all of the resources that
  1237  // it owns, and any corresponding network security rules.
  1238  func (env *azureEnviron) deleteVirtualMachine(
  1239  	ctx context.ProviderCallContext,
  1240  	sdkCtx stdcontext.Context,
  1241  	instId instance.Id,
  1242  	maybeStorageClient internalazurestorage.Client,
  1243  	networkInterfaces []network.Interface,
  1244  	publicIPAddresses []network.PublicIPAddress,
  1245  ) error {
  1246  	vmClient := compute.VirtualMachinesClient{env.compute}
  1247  	diskClient := compute.DisksClient{env.disk}
  1248  	nicClient := network.InterfacesClient{env.network}
  1249  	nsgClient := network.SecurityGroupsClient{env.network}
  1250  	securityRuleClient := network.SecurityRulesClient{env.network}
  1251  	pipClient := network.PublicIPAddressesClient{env.network}
  1252  	deploymentsClient := resources.DeploymentsClient{env.resources}
  1253  	vmName := string(instId)
  1254  
  1255  	// TODO(axw) delete resources concurrently.
  1256  
  1257  	// The VM must be deleted first, to release the lock on its resources.
  1258  	logger.Debugf("- deleting virtual machine (%s)", vmName)
  1259  	vmErrMsg := "deleting virtual machine"
  1260  	vmFuture, err := vmClient.Delete(sdkCtx, env.resourceGroup, vmName)
  1261  	if err != nil {
  1262  		if errorutils.MaybeInvalidateCredential(err, ctx) || !isNotFoundResponse(vmFuture.Response()) {
  1263  			return errors.Annotate(err, vmErrMsg)
  1264  		}
  1265  	} else {
  1266  		err = vmFuture.WaitForCompletionRef(sdkCtx, vmClient.Client)
  1267  		if err != nil {
  1268  			return errorutils.HandleCredentialError(errors.Annotate(err, vmErrMsg), ctx)
  1269  		}
  1270  		result, err := vmFuture.Result(vmClient)
  1271  		if err != nil {
  1272  			if errorutils.MaybeInvalidateCredential(err, ctx) || !isNotFoundResult(result) {
  1273  				return errors.Annotate(err, vmErrMsg)
  1274  			}
  1275  		}
  1276  	}
  1277  	if maybeStorageClient != nil {
  1278  		logger.Debugf("- deleting OS VHD (%s)", vmName)
  1279  		blobClient := maybeStorageClient.GetBlobService()
  1280  		vhdContainer := blobClient.GetContainerReference(osDiskVHDContainer)
  1281  		vhdBlob := vhdContainer.Blob(vmName)
  1282  		_, err := vhdBlob.DeleteIfExists(nil)
  1283  		return errorutils.HandleCredentialError(errors.Annotate(err, "deleting OS VHD"), ctx)
  1284  	}
  1285  
  1286  	// Delete the managed OS disk.
  1287  	logger.Debugf("- deleting OS disk (%s)", vmName)
  1288  	diskErrMsg := "deleting OS disk"
  1289  	diskFuture, err := diskClient.Delete(sdkCtx, env.resourceGroup, vmName)
  1290  	if err != nil {
  1291  		if errorutils.MaybeInvalidateCredential(err, ctx) || !isNotFoundResponse(diskFuture.Response()) {
  1292  			return errors.Annotate(err, diskErrMsg)
  1293  		}
  1294  	}
  1295  	if err == nil {
  1296  		err = diskFuture.WaitForCompletionRef(sdkCtx, diskClient.Client)
  1297  		if err != nil {
  1298  			return errorutils.HandleCredentialError(errors.Annotate(err, diskErrMsg), ctx)
  1299  		}
  1300  		result, err := diskFuture.Result(diskClient)
  1301  		if err != nil {
  1302  			if errorutils.MaybeInvalidateCredential(err, ctx) || !isNotFoundResult(result) {
  1303  				return errors.Annotate(err, diskErrMsg)
  1304  			}
  1305  		}
  1306  	}
  1307  	logger.Debugf("- deleting security rules (%s)", vmName)
  1308  	if err := deleteInstanceNetworkSecurityRules(
  1309  		ctx,
  1310  		env.resourceGroup, instId,
  1311  		nsgClient, securityRuleClient,
  1312  	); err != nil {
  1313  		return errors.Annotate(err, "deleting network security rules")
  1314  	}
  1315  
  1316  	logger.Debugf("- deleting network interfaces (%s)", vmName)
  1317  	networkErrMsg := "deleting NIC"
  1318  	for _, nic := range networkInterfaces {
  1319  		nicName := to.String(nic.Name)
  1320  		logger.Tracef("deleting NIC %q", nicName)
  1321  		nicFuture, err := nicClient.Delete(sdkCtx, env.resourceGroup, nicName)
  1322  		if err != nil {
  1323  			if errorutils.MaybeInvalidateCredential(err, ctx) || !isNotFoundResponse(nicFuture.Response()) {
  1324  				return errors.Annotate(err, networkErrMsg)
  1325  			}
  1326  		} else {
  1327  			err = nicFuture.WaitForCompletionRef(sdkCtx, nicClient.Client)
  1328  			if err != nil {
  1329  				return errorutils.HandleCredentialError(errors.Annotate(err, networkErrMsg), ctx)
  1330  			}
  1331  			result, err := nicFuture.Result(nicClient)
  1332  			if err != nil {
  1333  				if errorutils.MaybeInvalidateCredential(err, ctx) || !isNotFoundResult(result) {
  1334  					return errors.Annotate(err, networkErrMsg)
  1335  				}
  1336  			}
  1337  		}
  1338  	}
  1339  
  1340  	logger.Debugf("- deleting public IPs (%s)", vmName)
  1341  	ipErrMsg := "deleting public IP"
  1342  	for _, pip := range publicIPAddresses {
  1343  		pipName := to.String(pip.Name)
  1344  		logger.Tracef("deleting public IP %q", pipName)
  1345  		ipFuture, err := pipClient.Delete(sdkCtx, env.resourceGroup, pipName)
  1346  		if err != nil {
  1347  			if errorutils.MaybeInvalidateCredential(err, ctx) || !isNotFoundResponse(ipFuture.Response()) {
  1348  				return errors.Annotate(err, ipErrMsg)
  1349  			}
  1350  		} else {
  1351  			err = ipFuture.WaitForCompletionRef(sdkCtx, pipClient.Client)
  1352  			if err != nil {
  1353  				return errorutils.HandleCredentialError(errors.Annotate(err, ipErrMsg), ctx)
  1354  			}
  1355  			result, err := ipFuture.Result(pipClient)
  1356  			if err != nil {
  1357  				if errorutils.MaybeInvalidateCredential(err, ctx) || !isNotFoundResult(result) {
  1358  					return errors.Annotate(err, ipErrMsg)
  1359  				}
  1360  			}
  1361  		}
  1362  	}
  1363  
  1364  	// The deployment must be deleted last, or we risk leaking resources.
  1365  	logger.Debugf("- deleting deployment (%s)", vmName)
  1366  	deploymentFuture, err := deploymentsClient.Delete(sdkCtx, env.resourceGroup, vmName)
  1367  	deploymentErrMsg := "deleting deployment"
  1368  	if err != nil {
  1369  		if errorutils.MaybeInvalidateCredential(err, ctx) || !isNotFoundResponse(deploymentFuture.Response()) {
  1370  			return errors.Annotate(err, deploymentErrMsg)
  1371  		}
  1372  	} else {
  1373  		err = deploymentFuture.WaitForCompletionRef(sdkCtx, deploymentsClient.Client)
  1374  		if err != nil {
  1375  			return errorutils.HandleCredentialError(errors.Annotate(err, deploymentErrMsg), ctx)
  1376  		}
  1377  		deploymentResult, err := deploymentFuture.Result(deploymentsClient)
  1378  		if err != nil {
  1379  			if errorutils.MaybeInvalidateCredential(err, ctx) || !isNotFoundResult(deploymentResult) {
  1380  				return errors.Annotate(err, deploymentErrMsg)
  1381  			}
  1382  		}
  1383  	}
  1384  	return nil
  1385  }
  1386  
  1387  // Instances is specified in the Environ interface.
  1388  func (env *azureEnviron) Instances(ctx context.ProviderCallContext, ids []instance.Id) ([]instances.Instance, error) {
  1389  	return env.instances(ctx, env.resourceGroup, ids, true /* refresh addresses */)
  1390  }
  1391  
  1392  func (env *azureEnviron) instances(
  1393  	ctx context.ProviderCallContext,
  1394  	resourceGroup string,
  1395  	ids []instance.Id,
  1396  	refreshAddresses bool,
  1397  ) ([]instances.Instance, error) {
  1398  	if len(ids) == 0 {
  1399  		return nil, nil
  1400  	}
  1401  	all, err := env.allInstances(ctx, resourceGroup, refreshAddresses, false)
  1402  	if err != nil {
  1403  		return nil, errors.Trace(err)
  1404  	}
  1405  	byId := make(map[instance.Id]instances.Instance)
  1406  	for _, inst := range all {
  1407  		byId[inst.Id()] = inst
  1408  	}
  1409  	var found int
  1410  	matching := make([]instances.Instance, len(ids))
  1411  	for i, id := range ids {
  1412  		inst, ok := byId[id]
  1413  		if !ok {
  1414  			continue
  1415  		}
  1416  		matching[i] = inst
  1417  		found++
  1418  	}
  1419  	if found == 0 {
  1420  		return nil, environs.ErrNoInstances
  1421  	} else if found < len(ids) {
  1422  		return matching, environs.ErrPartialInstances
  1423  	}
  1424  	return matching, nil
  1425  }
  1426  
  1427  // AdoptResources is part of the Environ interface.
  1428  func (env *azureEnviron) AdoptResources(ctx context.ProviderCallContext, controllerUUID string, fromVersion version.Number) error {
  1429  	groupClient := resources.GroupsClient{env.resources}
  1430  
  1431  	err := env.updateGroupControllerTag(ctx, &groupClient, env.resourceGroup, controllerUUID)
  1432  	if err != nil {
  1433  		// If we can't update the group there's no point updating the
  1434  		// contained resources - the group will be killed if the
  1435  		// controller is destroyed, taking the other things with it.
  1436  		return errors.Trace(err)
  1437  	}
  1438  
  1439  	sdkCtx := stdcontext.Background()
  1440  	apiVersions, err := collectAPIVersions(ctx, sdkCtx, resources.ProvidersClient{env.resources})
  1441  	if err != nil {
  1442  		return errors.Trace(err)
  1443  	}
  1444  
  1445  	resourceClient := resources.Client{env.resources}
  1446  	res, err := resourceClient.ListByResourceGroupComplete(sdkCtx, env.resourceGroup, "", "", nil)
  1447  	if err != nil {
  1448  		return errorutils.HandleCredentialError(errors.Annotate(err, "listing resources"), ctx)
  1449  	}
  1450  	var failed []string
  1451  	for ; res.NotDone(); err = res.NextWithContext(sdkCtx) {
  1452  		if err != nil {
  1453  			return errors.Annotate(err, "listing resources")
  1454  		}
  1455  		resource := res.Value()
  1456  		apiVersion := apiVersions[to.String(resource.Type)]
  1457  		err := env.updateResourceControllerTag(
  1458  			ctx,
  1459  			sdkCtx,
  1460  			internalazureresources.ResourcesClient{&resourceClient},
  1461  			resource, controllerUUID, apiVersion,
  1462  		)
  1463  		if err != nil {
  1464  			name := to.String(resource.Name)
  1465  			logger.Errorf("error updating resource tags for %q: %v", name, err)
  1466  			failed = append(failed, name)
  1467  		}
  1468  	}
  1469  	if len(failed) > 0 {
  1470  		return errors.Errorf("failed to update controller for some resources: %v", failed)
  1471  	}
  1472  
  1473  	return nil
  1474  }
  1475  
  1476  func (env *azureEnviron) updateGroupControllerTag(ctx context.ProviderCallContext, client *resources.GroupsClient, groupName, controllerUUID string) error {
  1477  	sdkCtx := stdcontext.Background()
  1478  	group, err := client.Get(sdkCtx, groupName)
  1479  	if err != nil {
  1480  		return errorutils.HandleCredentialError(errors.Trace(err), ctx)
  1481  	}
  1482  
  1483  	logger.Debugf(
  1484  		"updating resource group %s juju controller uuid to %s",
  1485  		to.String(group.Name), controllerUUID,
  1486  	)
  1487  	group.Tags[tags.JujuController] = to.StringPtr(controllerUUID)
  1488  
  1489  	// The Azure API forbids specifying ProvisioningState on the update.
  1490  	if group.Properties != nil {
  1491  		(*group.Properties).ProvisioningState = nil
  1492  	}
  1493  
  1494  	_, err = client.CreateOrUpdate(sdkCtx, groupName, group)
  1495  	return errorutils.HandleCredentialError(errors.Annotatef(err, "updating controller for resource group %q", groupName), ctx)
  1496  }
  1497  
  1498  func (env *azureEnviron) updateResourceControllerTag(
  1499  	ctx context.ProviderCallContext,
  1500  	sdkCtx stdcontext.Context,
  1501  	client internalazureresources.ResourcesClient,
  1502  	stubResource resources.GenericResource,
  1503  	controllerUUID string,
  1504  	apiVersion string,
  1505  ) error {
  1506  	stubTags := to.StringMap(stubResource.Tags)
  1507  	if stubTags[tags.JujuController] == controllerUUID {
  1508  		// No update needed.
  1509  		return nil
  1510  	}
  1511  
  1512  	// Need to get the resource individually to ensure that the
  1513  	// properties are populated.
  1514  	resource, err := client.GetByID(sdkCtx, to.String(stubResource.ID), apiVersion)
  1515  	if err != nil {
  1516  		return errorutils.HandleCredentialError(errors.Annotatef(err, "getting full resource %q", to.String(stubResource.Name)), ctx)
  1517  	}
  1518  
  1519  	logger.Debugf("updating %s juju controller UUID to %s", to.String(stubResource.ID), controllerUUID)
  1520  	resource.Tags[tags.JujuController] = to.StringPtr(controllerUUID)
  1521  	_, err = client.CreateOrUpdateByID(
  1522  		sdkCtx,
  1523  		to.String(stubResource.ID),
  1524  		resource,
  1525  		apiVersion,
  1526  	)
  1527  	return errorutils.HandleCredentialError(errors.Annotatef(err, "updating controller for %q", to.String(resource.Name)), ctx)
  1528  }
  1529  
  1530  // AllInstances is specified in the InstanceBroker interface.
  1531  func (env *azureEnviron) AllInstances(ctx context.ProviderCallContext) ([]instances.Instance, error) {
  1532  	return env.allInstances(ctx, env.resourceGroup, true /* refresh addresses */, false /* all instances */)
  1533  }
  1534  
  1535  // allInstances returns all of the instances in the given resource group,
  1536  // and optionally ensures that each instance's addresses are up-to-date.
  1537  func (env *azureEnviron) allInstances(
  1538  	ctx context.ProviderCallContext,
  1539  	resourceGroup string,
  1540  	refreshAddresses bool,
  1541  	controllerOnly bool,
  1542  ) ([]instances.Instance, error) {
  1543  	deploymentsClient := resources.DeploymentsClient{env.resources}
  1544  	sdkCtx := stdcontext.Background()
  1545  	deploymentsResult, err := deploymentsClient.ListByResourceGroupComplete(sdkCtx, resourceGroup, "", nil)
  1546  	if err != nil {
  1547  		if isNotFoundResult(deploymentsResult.Response().Response) {
  1548  			// This will occur if the resource group does not
  1549  			// exist, e.g. in a fresh hosted environment.
  1550  			return nil, nil
  1551  		}
  1552  		return nil, errorutils.HandleCredentialError(errors.Trace(err), ctx)
  1553  	}
  1554  	if deploymentsResult.Response().IsEmpty() {
  1555  		return nil, nil
  1556  	}
  1557  
  1558  	var azureInstances []*azureInstance
  1559  	for ; deploymentsResult.NotDone(); err = deploymentsResult.NextWithContext(sdkCtx) {
  1560  		if err != nil {
  1561  			return nil, errors.Annotate(err, "listing resources")
  1562  		}
  1563  		deployment := deploymentsResult.Value()
  1564  		name := to.String(deployment.Name)
  1565  		if _, err := names.ParseMachineTag(name); err != nil {
  1566  			// Deployments we create for Juju machines are named
  1567  			// with the machine tag. We also create a "common"
  1568  			// deployment, so this will exclude that VM and any
  1569  			// other stray deployment resources.
  1570  			continue
  1571  		}
  1572  		if deployment.Properties == nil || deployment.Properties.Dependencies == nil {
  1573  			continue
  1574  		}
  1575  		if controllerOnly && !isControllerDeployment(deployment) {
  1576  			continue
  1577  		}
  1578  		provisioningState := to.String(deployment.Properties.ProvisioningState)
  1579  		inst := &azureInstance{name, provisioningState, env, nil, nil}
  1580  		azureInstances = append(azureInstances, inst)
  1581  	}
  1582  
  1583  	if len(azureInstances) > 0 && refreshAddresses {
  1584  		if err := setInstanceAddresses(
  1585  			ctx,
  1586  			resourceGroup,
  1587  			network.InterfacesClient{env.network},
  1588  			network.PublicIPAddressesClient{env.network},
  1589  			azureInstances,
  1590  		); err != nil {
  1591  			return nil, errors.Trace(err)
  1592  		}
  1593  	}
  1594  
  1595  	instances := make([]instances.Instance, len(azureInstances))
  1596  	for i, inst := range azureInstances {
  1597  		instances[i] = inst
  1598  	}
  1599  	return instances, nil
  1600  }
  1601  
  1602  func isControllerDeployment(deployment resources.DeploymentExtended) bool {
  1603  	for _, d := range *deployment.Properties.Dependencies {
  1604  		if d.DependsOn == nil {
  1605  			continue
  1606  		}
  1607  		if to.String(d.ResourceType) != "Microsoft.Compute/virtualMachines" {
  1608  			continue
  1609  		}
  1610  		for _, on := range *d.DependsOn {
  1611  			if to.String(on.ResourceType) != "Microsoft.Compute/availabilitySets" {
  1612  				continue
  1613  			}
  1614  			if to.String(on.ResourceName) == controllerAvailabilitySet {
  1615  				return true
  1616  			}
  1617  		}
  1618  	}
  1619  	return false
  1620  }
  1621  
  1622  // Destroy is specified in the Environ interface.
  1623  func (env *azureEnviron) Destroy(ctx context.ProviderCallContext) error {
  1624  	logger.Debugf("destroying model %q", env.envName)
  1625  	logger.Debugf("- deleting resource group %q", env.resourceGroup)
  1626  	sdkCtx := stdcontext.Background()
  1627  	if err := env.deleteResourceGroup(ctx, sdkCtx, env.resourceGroup); err != nil {
  1628  		return errors.Trace(err)
  1629  	}
  1630  	// Resource groups are self-contained and fully encompass
  1631  	// all environ resources. Once you delete the group, there
  1632  	// is nothing else to do.
  1633  	return nil
  1634  }
  1635  
  1636  // DestroyController is specified in the Environ interface.
  1637  func (env *azureEnviron) DestroyController(ctx context.ProviderCallContext, controllerUUID string) error {
  1638  	logger.Debugf("destroying model %q", env.envName)
  1639  	logger.Debugf("- deleting resource groups")
  1640  	if err := env.deleteControllerManagedResourceGroups(ctx, controllerUUID); err != nil {
  1641  		return errors.Trace(err)
  1642  	}
  1643  	// Resource groups are self-contained and fully encompass
  1644  	// all environ resources. Once you delete the group, there
  1645  	// is nothing else to do.
  1646  	return nil
  1647  }
  1648  
  1649  func (env *azureEnviron) deleteControllerManagedResourceGroups(ctx context.ProviderCallContext, controllerUUID string) error {
  1650  	filter := fmt.Sprintf(
  1651  		"tagname eq '%s' and tagvalue eq '%s'",
  1652  		tags.JujuController, controllerUUID,
  1653  	)
  1654  	client := resources.GroupsClient{env.resources}
  1655  	sdkCtx := stdcontext.Background()
  1656  	result, err := client.List(sdkCtx, filter, nil)
  1657  	if err != nil {
  1658  		return errorutils.HandleCredentialError(errors.Annotate(err, "listing resource groups"), ctx)
  1659  	}
  1660  	if result.Values() == nil {
  1661  		return nil
  1662  	}
  1663  
  1664  	// Walk all the pages of results so we can get a total list of groups to remove.
  1665  	var groupNames []*string
  1666  	for ; result.NotDone(); err = result.NextWithContext(sdkCtx) {
  1667  		for _, group := range result.Values() {
  1668  			groupNames = append(groupNames, group.Name)
  1669  		}
  1670  	}
  1671  	// Deleting groups can take a long time, so make sure they are
  1672  	// deleted in parallel.
  1673  	var wg sync.WaitGroup
  1674  	errs := make([]error, len(groupNames))
  1675  	for i, name := range groupNames {
  1676  		groupName := to.String(name)
  1677  		logger.Debugf("  - deleting resource group %q", groupName)
  1678  		wg.Add(1)
  1679  		go func(i int) {
  1680  			defer wg.Done()
  1681  			if err := env.deleteResourceGroup(ctx, sdkCtx, groupName); err != nil {
  1682  				errs[i] = errors.Annotatef(
  1683  					err, "deleting resource group %q", groupName,
  1684  				)
  1685  			}
  1686  		}(i)
  1687  	}
  1688  	wg.Wait()
  1689  
  1690  	// If there is just one error, return it. If there are multiple,
  1691  	// then combine their messages.
  1692  	var nonNilErrs []error
  1693  	for _, err := range errs {
  1694  		if err != nil {
  1695  			nonNilErrs = append(nonNilErrs, err)
  1696  		}
  1697  	}
  1698  	switch len(nonNilErrs) {
  1699  	case 0:
  1700  		return nil
  1701  	case 1:
  1702  		return nonNilErrs[0]
  1703  	}
  1704  	combined := make([]string, len(nonNilErrs))
  1705  	for i, err := range nonNilErrs {
  1706  		combined[i] = err.Error()
  1707  	}
  1708  	return errors.New(strings.Join(combined, "; "))
  1709  }
  1710  
  1711  func (env *azureEnviron) deleteResourceGroup(ctx context.ProviderCallContext, sdkCtx stdcontext.Context, resourceGroup string) error {
  1712  	client := resources.GroupsClient{env.resources}
  1713  	future, err := client.Delete(sdkCtx, resourceGroup)
  1714  	if err != nil {
  1715  		errorutils.HandleCredentialError(err, ctx)
  1716  		if !isNotFoundResponse(future.Response()) {
  1717  			return errors.Annotatef(err, "deleting resource group %q", resourceGroup)
  1718  		}
  1719  		return nil
  1720  	}
  1721  	err = future.WaitForCompletionRef(sdkCtx, client.Client)
  1722  	if err != nil {
  1723  		return errors.Annotatef(err, "deleting resource group %q", resourceGroup)
  1724  	}
  1725  	result, err := future.Result(client)
  1726  	if err != nil && !isNotFoundResult(result) {
  1727  		return errors.Annotatef(err, "deleting resource group %q", resourceGroup)
  1728  	}
  1729  	return nil
  1730  }
  1731  
  1732  // Provider is specified in the Environ interface.
  1733  func (env *azureEnviron) Provider() environs.EnvironProvider {
  1734  	return env.provider
  1735  }
  1736  
  1737  // resourceGroupName returns the name of the environment's resource group.
  1738  func resourceGroupName(modelTag names.ModelTag, modelName string) string {
  1739  	return fmt.Sprintf("juju-%s-%s", modelName, resourceName(modelTag))
  1740  }
  1741  
  1742  // resourceName returns the string to use for a resource's Name tag,
  1743  // to help users identify Juju-managed resources in the Azure portal.
  1744  //
  1745  // Since resources are grouped under resource groups, we just use the
  1746  // tag.
  1747  func resourceName(tag names.Tag) string {
  1748  	return tag.String()
  1749  }
  1750  
  1751  // getInstanceTypes gets the instance types available for the configured
  1752  // location, keyed by name.
  1753  func (env *azureEnviron) getInstanceTypes(ctx context.ProviderCallContext) (map[string]instances.InstanceType, error) {
  1754  	env.mu.Lock()
  1755  	defer env.mu.Unlock()
  1756  	instanceTypes, err := env.getInstanceTypesLocked(ctx)
  1757  	if err != nil {
  1758  		return nil, errors.Annotate(err, "getting instance types")
  1759  	}
  1760  	return instanceTypes, nil
  1761  }
  1762  
  1763  // getInstanceTypesLocked returns the instance types for Azure, by listing the
  1764  // role sizes available to the subscription.
  1765  func (env *azureEnviron) getInstanceTypesLocked(ctx context.ProviderCallContext) (map[string]instances.InstanceType, error) {
  1766  	if env.instanceTypes != nil {
  1767  		return env.instanceTypes, nil
  1768  	}
  1769  
  1770  	location := env.location
  1771  	client := compute.VirtualMachineSizesClient{env.compute}
  1772  
  1773  	result, err := client.List(stdcontext.Background(), location)
  1774  	if err != nil {
  1775  		return nil, errorutils.HandleCredentialError(errors.Annotate(err, "listing VM sizes"), ctx)
  1776  	}
  1777  	instanceTypes := make(map[string]instances.InstanceType)
  1778  	if result.Value != nil {
  1779  		for _, size := range *result.Value {
  1780  			instanceType := newInstanceType(size)
  1781  			instanceTypes[instanceType.Name] = instanceType
  1782  			// Create aliases for standard role sizes.
  1783  			if strings.HasPrefix(instanceType.Name, "Standard_") {
  1784  				instanceTypes[instanceType.Name[len("Standard_"):]] = instanceType
  1785  			}
  1786  		}
  1787  	}
  1788  	env.instanceTypes = instanceTypes
  1789  	return instanceTypes, nil
  1790  }
  1791  
  1792  // maybeGetStorageClient returns the environment's storage client if it
  1793  // has one, and nil if it does not.
  1794  func (env *azureEnviron) maybeGetStorageClient() (internalazurestorage.Client, *storage.Account, error) {
  1795  	storageClient, storageAccount, err := env.getStorageClient()
  1796  	if errors.IsNotFound(err) {
  1797  		// Only models created prior to Juju 2.3 will have a storage
  1798  		// account. Juju 2.3 onwards exclusively uses managed disks
  1799  		// for all new models, and handles both managed and unmanaged
  1800  		// disks for upgraded models.
  1801  		storageClient = nil
  1802  		storageAccount = nil
  1803  	} else if err != nil {
  1804  		return nil, nil, errors.Trace(err)
  1805  	}
  1806  	return storageClient, storageAccount, nil
  1807  }
  1808  
  1809  // getStorageClient queries the storage account key, and uses it to construct
  1810  // a new storage client.
  1811  func (env *azureEnviron) getStorageClient() (internalazurestorage.Client, *storage.Account, error) {
  1812  	env.mu.Lock()
  1813  	defer env.mu.Unlock()
  1814  	storageAccount, err := env.getStorageAccountLocked()
  1815  	if err != nil {
  1816  		return nil, nil, errors.Annotate(err, "getting storage account")
  1817  	}
  1818  	storageAccountKey, err := env.getStorageAccountKeyLocked(
  1819  		to.String(storageAccount.Name), false,
  1820  	)
  1821  	if err != nil {
  1822  		return nil, nil, errors.Annotate(err, "getting storage account key")
  1823  	}
  1824  	client, err := getStorageClient(
  1825  		env.provider.config.NewStorageClient,
  1826  		env.storageEndpoint,
  1827  		storageAccount,
  1828  		storageAccountKey,
  1829  	)
  1830  	if err != nil {
  1831  		return nil, nil, errors.Annotate(err, "getting storage client")
  1832  	}
  1833  	return client, storageAccount, nil
  1834  }
  1835  
  1836  // getStorageAccount returns the storage account for this environment's
  1837  // resource group.
  1838  func (env *azureEnviron) getStorageAccount() (*storage.Account, error) {
  1839  	env.mu.Lock()
  1840  	defer env.mu.Unlock()
  1841  	return env.getStorageAccountLocked()
  1842  }
  1843  
  1844  func (env *azureEnviron) getStorageAccountLocked() (*storage.Account, error) {
  1845  	if env.storageAccount != nil {
  1846  		if *env.storageAccount == nil {
  1847  			return nil, errors.NotFoundf("storage account")
  1848  		}
  1849  		return *env.storageAccount, nil
  1850  	}
  1851  	client := storage.AccountsClient{env.storage}
  1852  	account, err := client.GetProperties(stdcontext.Background(), env.resourceGroup, env.storageAccountName)
  1853  	if err != nil {
  1854  		if isNotFoundResult(account.Response) {
  1855  			// Remember that the account was not found
  1856  			// by storing a pointer to a nil pointer.
  1857  			env.storageAccount = new(*storage.Account)
  1858  			return nil, errors.NewNotFound(err, fmt.Sprintf("storage account not found"))
  1859  		}
  1860  		return nil, errors.Trace(err)
  1861  	}
  1862  	env.storageAccount = new(*storage.Account)
  1863  	*env.storageAccount = &account
  1864  	return &account, nil
  1865  }
  1866  
  1867  // getStorageAccountKeysLocked returns a storage account key for this
  1868  // environment's storage account. If refresh is true, any cached key
  1869  // will be refreshed. This method assumes that env.mu is held.
  1870  func (env *azureEnviron) getStorageAccountKeyLocked(accountName string, refresh bool) (*storage.AccountKey, error) {
  1871  	if !refresh && env.storageAccountKey != nil {
  1872  		return env.storageAccountKey, nil
  1873  	}
  1874  	client := storage.AccountsClient{env.storage}
  1875  	key, err := getStorageAccountKey(client, env.resourceGroup, accountName)
  1876  	if err != nil {
  1877  		return nil, errors.Trace(err)
  1878  	}
  1879  	env.storageAccountKey = key
  1880  	return key, nil
  1881  }
  1882  
  1883  // AgentMirror is specified in the tools.HasAgentMirror interface.
  1884  //
  1885  // TODO(axw) 2016-04-11 #1568715
  1886  // When we have image simplestreams, we should rename this to "Region",
  1887  // to implement simplestreams.HasRegion.
  1888  func (env *azureEnviron) AgentMirror() (simplestreams.CloudSpec, error) {
  1889  	return simplestreams.CloudSpec{
  1890  		Region: env.location,
  1891  		// The endpoints published in simplestreams
  1892  		// data are the storage endpoints.
  1893  		Endpoint: fmt.Sprintf("https://%s/", env.storageEndpoint),
  1894  	}, nil
  1895  }