github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/provider/azure/environ.go (about)

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