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