github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/provider/azure/environ.go (about)

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