launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/provider/azure/environ.go (about)

     1  // Copyright 2013 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  	"sync"
    10  	"time"
    11  
    12  	"launchpad.net/gwacl"
    13  
    14  	"launchpad.net/juju-core/constraints"
    15  	"launchpad.net/juju-core/environs"
    16  	"launchpad.net/juju-core/environs/cloudinit"
    17  	"launchpad.net/juju-core/environs/config"
    18  	"launchpad.net/juju-core/environs/imagemetadata"
    19  	"launchpad.net/juju-core/environs/instances"
    20  	"launchpad.net/juju-core/environs/simplestreams"
    21  	"launchpad.net/juju-core/environs/storage"
    22  	envtools "launchpad.net/juju-core/environs/tools"
    23  	"launchpad.net/juju-core/instance"
    24  	"launchpad.net/juju-core/provider/common"
    25  	"launchpad.net/juju-core/state"
    26  	"launchpad.net/juju-core/state/api"
    27  	"launchpad.net/juju-core/tools"
    28  	"launchpad.net/juju-core/utils/parallel"
    29  )
    30  
    31  const (
    32  	// In our initial implementation, each instance gets its own hosted
    33  	// service, deployment and role in Azure.  The role always gets this
    34  	// hostname (instance==service).
    35  	roleHostname = "default"
    36  
    37  	// deploymentSlot says in which slot to deploy instances.  Azure
    38  	// supports 'Production' or 'Staging'.
    39  	// This provider always deploys to Production.  Think twice about
    40  	// changing that: DNS names in the staging slot work differently from
    41  	// those in the production slot.  In Staging, Azure assigns an
    42  	// arbitrary hostname that we can then extract from the deployment's
    43  	// URL.  In Production, the hostname in the deployment URL does not
    44  	// actually seem to resolve; instead, the service name is used as the
    45  	// DNS name, with ".cloudapp.net" appended.
    46  	deploymentSlot = "Production"
    47  
    48  	// Address space of the virtual network used by the nodes in this
    49  	// environement, in CIDR notation. This is the network used for
    50  	// machine-to-machine communication.
    51  	networkDefinition = "10.0.0.0/8"
    52  )
    53  
    54  type azureEnviron struct {
    55  	// Except where indicated otherwise, all fields in this object should
    56  	// only be accessed using a lock or a snapshot.
    57  	sync.Mutex
    58  
    59  	// name is immutable; it does not need locking.
    60  	name string
    61  
    62  	// ecfg is the environment's Azure-specific configuration.
    63  	ecfg *azureEnvironConfig
    64  
    65  	// storage is this environ's own private storage.
    66  	storage storage.Storage
    67  
    68  	// storageAccountKey holds an access key to this environment's
    69  	// private storage.  This is automatically queried from Azure on
    70  	// startup.
    71  	storageAccountKey string
    72  }
    73  
    74  // azureEnviron implements Environ and HasRegion.
    75  var _ environs.Environ = (*azureEnviron)(nil)
    76  var _ simplestreams.HasRegion = (*azureEnviron)(nil)
    77  var _ imagemetadata.SupportsCustomSources = (*azureEnviron)(nil)
    78  var _ envtools.SupportsCustomSources = (*azureEnviron)(nil)
    79  
    80  // NewEnviron creates a new azureEnviron.
    81  func NewEnviron(cfg *config.Config) (*azureEnviron, error) {
    82  	env := azureEnviron{name: cfg.Name()}
    83  	err := env.SetConfig(cfg)
    84  	if err != nil {
    85  		return nil, err
    86  	}
    87  
    88  	// Set up storage.
    89  	env.storage = &azureStorage{
    90  		storageContext: &environStorageContext{environ: &env},
    91  	}
    92  	return &env, nil
    93  }
    94  
    95  // extractStorageKey returns the primary account key from a gwacl
    96  // StorageAccountKeys struct, or if there is none, the secondary one.
    97  func extractStorageKey(keys *gwacl.StorageAccountKeys) string {
    98  	if keys.Primary != "" {
    99  		return keys.Primary
   100  	}
   101  	return keys.Secondary
   102  }
   103  
   104  // queryStorageAccountKey retrieves the storage account's key from Azure.
   105  func (env *azureEnviron) queryStorageAccountKey() (string, error) {
   106  	azure, err := env.getManagementAPI()
   107  	if err != nil {
   108  		return "", err
   109  	}
   110  	defer env.releaseManagementAPI(azure)
   111  
   112  	accountName := env.getSnapshot().ecfg.storageAccountName()
   113  	keys, err := azure.GetStorageAccountKeys(accountName)
   114  	if err != nil {
   115  		return "", fmt.Errorf("cannot obtain storage account keys: %v", err)
   116  	}
   117  
   118  	key := extractStorageKey(keys)
   119  	if key == "" {
   120  		return "", fmt.Errorf("no keys available for storage account")
   121  	}
   122  
   123  	return key, nil
   124  }
   125  
   126  // PrecheckInstance is specified in the environs.Prechecker interface.
   127  func (*azureEnviron) PrecheckInstance(series string, cons constraints.Value) error {
   128  	return nil
   129  }
   130  
   131  // PrecheckContainer is specified in the environs.Prechecker interface.
   132  func (*azureEnviron) PrecheckContainer(series string, kind instance.ContainerType) error {
   133  	// This check can either go away or be relaxed when the azure
   134  	// provider manages container addressibility.
   135  	return environs.NewContainersUnsupported("azure provider does not support containers")
   136  }
   137  
   138  // Name is specified in the Environ interface.
   139  func (env *azureEnviron) Name() string {
   140  	return env.name
   141  }
   142  
   143  // getSnapshot produces an atomic shallow copy of the environment object.
   144  // Whenever you need to access the environment object's fields without
   145  // modifying them, get a snapshot and read its fields instead.  You will
   146  // get a consistent view of the fields without any further locking.
   147  // If you do need to modify the environment's fields, do not get a snapshot
   148  // but lock the object throughout the critical section.
   149  func (env *azureEnviron) getSnapshot() *azureEnviron {
   150  	env.Lock()
   151  	defer env.Unlock()
   152  
   153  	// Copy the environment.  (Not the pointer, the environment itself.)
   154  	// This is a shallow copy.
   155  	snap := *env
   156  	// Reset the snapshot's mutex, because we just copied it while we
   157  	// were holding it.  The snapshot will have a "clean," unlocked mutex.
   158  	snap.Mutex = sync.Mutex{}
   159  	return &snap
   160  }
   161  
   162  // getAffinityGroupName returns the name of the affinity group used by all
   163  // the Services in this environment.
   164  func (env *azureEnviron) getAffinityGroupName() string {
   165  	return env.getEnvPrefix() + "ag"
   166  }
   167  
   168  func (env *azureEnviron) createAffinityGroup() error {
   169  	affinityGroupName := env.getAffinityGroupName()
   170  	azure, err := env.getManagementAPI()
   171  	if err != nil {
   172  		return err
   173  	}
   174  	defer env.releaseManagementAPI(azure)
   175  	snap := env.getSnapshot()
   176  	location := snap.ecfg.location()
   177  	cag := gwacl.NewCreateAffinityGroup(affinityGroupName, affinityGroupName, affinityGroupName, location)
   178  	return azure.CreateAffinityGroup(&gwacl.CreateAffinityGroupRequest{
   179  		CreateAffinityGroup: cag})
   180  }
   181  
   182  func (env *azureEnviron) deleteAffinityGroup() error {
   183  	affinityGroupName := env.getAffinityGroupName()
   184  	azure, err := env.getManagementAPI()
   185  	if err != nil {
   186  		return err
   187  	}
   188  	defer env.releaseManagementAPI(azure)
   189  	return azure.DeleteAffinityGroup(&gwacl.DeleteAffinityGroupRequest{
   190  		Name: affinityGroupName})
   191  }
   192  
   193  // getVirtualNetworkName returns the name of the virtual network used by all
   194  // the VMs in this environment.
   195  func (env *azureEnviron) getVirtualNetworkName() string {
   196  	return env.getEnvPrefix() + "vnet"
   197  }
   198  
   199  func (env *azureEnviron) createVirtualNetwork() error {
   200  	vnetName := env.getVirtualNetworkName()
   201  	affinityGroupName := env.getAffinityGroupName()
   202  	azure, err := env.getManagementAPI()
   203  	if err != nil {
   204  		return err
   205  	}
   206  	defer env.releaseManagementAPI(azure)
   207  	virtualNetwork := gwacl.VirtualNetworkSite{
   208  		Name:          vnetName,
   209  		AffinityGroup: affinityGroupName,
   210  		AddressSpacePrefixes: []string{
   211  			networkDefinition,
   212  		},
   213  	}
   214  	return azure.AddVirtualNetworkSite(&virtualNetwork)
   215  }
   216  
   217  func (env *azureEnviron) deleteVirtualNetwork() error {
   218  	azure, err := env.getManagementAPI()
   219  	if err != nil {
   220  		return err
   221  	}
   222  	defer env.releaseManagementAPI(azure)
   223  	vnetName := env.getVirtualNetworkName()
   224  	return azure.RemoveVirtualNetworkSite(vnetName)
   225  }
   226  
   227  // getContainerName returns the name of the private storage account container
   228  // that this environment is using.
   229  func (env *azureEnviron) getContainerName() string {
   230  	return env.getEnvPrefix() + "private"
   231  }
   232  
   233  // Bootstrap is specified in the Environ interface.
   234  func (env *azureEnviron) Bootstrap(ctx environs.BootstrapContext, cons constraints.Value) (err error) {
   235  	// The creation of the affinity group and the virtual network is specific to the Azure provider.
   236  	err = env.createAffinityGroup()
   237  	if err != nil {
   238  		return err
   239  	}
   240  	// If we fail after this point, clean up the affinity group.
   241  	defer func() {
   242  		if err != nil {
   243  			env.deleteAffinityGroup()
   244  		}
   245  	}()
   246  	err = env.createVirtualNetwork()
   247  	if err != nil {
   248  		return err
   249  	}
   250  	// If we fail after this point, clean up the virtual network.
   251  	defer func() {
   252  		if err != nil {
   253  			env.deleteVirtualNetwork()
   254  		}
   255  	}()
   256  	err = common.Bootstrap(ctx, env, cons)
   257  	return err
   258  }
   259  
   260  // StateInfo is specified in the Environ interface.
   261  func (env *azureEnviron) StateInfo() (*state.Info, *api.Info, error) {
   262  	return common.StateInfo(env)
   263  }
   264  
   265  // Config is specified in the Environ interface.
   266  func (env *azureEnviron) Config() *config.Config {
   267  	snap := env.getSnapshot()
   268  	return snap.ecfg.Config
   269  }
   270  
   271  // SetConfig is specified in the Environ interface.
   272  func (env *azureEnviron) SetConfig(cfg *config.Config) error {
   273  	ecfg, err := azureEnvironProvider{}.newConfig(cfg)
   274  	if err != nil {
   275  		return err
   276  	}
   277  
   278  	env.Lock()
   279  	defer env.Unlock()
   280  
   281  	if env.ecfg != nil {
   282  		_, err = azureEnvironProvider{}.Validate(cfg, env.ecfg.Config)
   283  		if err != nil {
   284  			return err
   285  		}
   286  	}
   287  
   288  	env.ecfg = ecfg
   289  
   290  	// Reset storage account key.  Even if we had one before, it may not
   291  	// be appropriate for the new config.
   292  	env.storageAccountKey = ""
   293  
   294  	return nil
   295  }
   296  
   297  // attemptCreateService tries to create a new hosted service on Azure, with a
   298  // name it chooses (based on the given prefix), but recognizes that the name
   299  // may not be available.  If the name is not available, it does not treat that
   300  // as an error but just returns nil.
   301  func attemptCreateService(azure *gwacl.ManagementAPI, prefix string, affinityGroupName string, location string) (*gwacl.CreateHostedService, error) {
   302  	var err error
   303  	name := gwacl.MakeRandomHostedServiceName(prefix)
   304  	err = azure.CheckHostedServiceNameAvailability(name)
   305  	if err != nil {
   306  		// The calling function should retry.
   307  		return nil, nil
   308  	}
   309  	req := gwacl.NewCreateHostedServiceWithLocation(name, name, location)
   310  	req.AffinityGroup = affinityGroupName
   311  	err = azure.AddHostedService(req)
   312  	if err != nil {
   313  		return nil, err
   314  	}
   315  	return req, nil
   316  }
   317  
   318  // architectures lists the CPU architectures supported by Azure.
   319  var architectures = []string{"amd64", "i386"}
   320  
   321  // newHostedService creates a hosted service.  It will make up a unique name,
   322  // starting with the given prefix.
   323  func newHostedService(azure *gwacl.ManagementAPI, prefix string, affinityGroupName string, location string) (*gwacl.CreateHostedService, error) {
   324  	var err error
   325  	var svc *gwacl.CreateHostedService
   326  	for tries := 10; tries > 0 && err == nil && svc == nil; tries-- {
   327  		svc, err = attemptCreateService(azure, prefix, affinityGroupName, location)
   328  	}
   329  	if err != nil {
   330  		return nil, fmt.Errorf("could not create hosted service: %v", err)
   331  	}
   332  	if svc == nil {
   333  		return nil, fmt.Errorf("could not come up with a unique hosted service name - is your randomizer initialized?")
   334  	}
   335  	return svc, nil
   336  }
   337  
   338  // selectInstanceTypeAndImage returns the appropriate instance-type name and
   339  // the OS image name for launching a virtual machine with the given parameters.
   340  func (env *azureEnviron) selectInstanceTypeAndImage(cons constraints.Value, series, location string) (string, string, error) {
   341  	ecfg := env.getSnapshot().ecfg
   342  	sourceImageName := ecfg.forceImageName()
   343  	if sourceImageName != "" {
   344  		// Configuration forces us to use a specific image.  There may
   345  		// not be a suitable image in the simplestreams database.
   346  		// This means we can't use Juju's normal selection mechanism,
   347  		// because it combines instance-type and image selection: if
   348  		// there are no images we can use, it won't offer us an
   349  		// instance type either.
   350  		//
   351  		// Select the instance type using simple, Azure-specific code.
   352  		machineType, err := selectMachineType(gwacl.RoleSizes, defaultToBaselineSpec(cons))
   353  		if err != nil {
   354  			return "", "", err
   355  		}
   356  		return machineType.Name, sourceImageName, nil
   357  	}
   358  
   359  	// Choose the most suitable instance type and OS image, based on
   360  	// simplestreams information.
   361  	//
   362  	// This should be the normal execution path.  The user is not expected
   363  	// to configure a source image name in normal use.
   364  	constraint := instances.InstanceConstraint{
   365  		Region:      location,
   366  		Series:      series,
   367  		Arches:      architectures,
   368  		Constraints: cons,
   369  	}
   370  	spec, err := findInstanceSpec(env, constraint)
   371  	if err != nil {
   372  		return "", "", err
   373  	}
   374  	return spec.InstanceType.Id, spec.Image.Id, nil
   375  }
   376  
   377  // StartInstance is specified in the InstanceBroker interface.
   378  func (env *azureEnviron) StartInstance(cons constraints.Value, possibleTools tools.List,
   379  	machineConfig *cloudinit.MachineConfig) (_ instance.Instance, _ *instance.HardwareCharacteristics, err error) {
   380  
   381  	// Declaring "err" in the function signature so that we can "defer"
   382  	// any cleanup that needs to run during error returns.
   383  
   384  	err = environs.FinishMachineConfig(machineConfig, env.Config(), cons)
   385  	if err != nil {
   386  		return nil, nil, err
   387  	}
   388  
   389  	// Pick envtools.  Needed for the custom data (which is what we normally
   390  	// call userdata).
   391  	machineConfig.Tools = possibleTools[0]
   392  	logger.Infof("picked tools %q", machineConfig.Tools)
   393  
   394  	// Compose userdata.
   395  	userData, err := makeCustomData(machineConfig)
   396  	if err != nil {
   397  		return nil, nil, fmt.Errorf("custom data: %v", err)
   398  	}
   399  
   400  	azure, err := env.getManagementAPI()
   401  	if err != nil {
   402  		return nil, nil, err
   403  	}
   404  	defer env.releaseManagementAPI(azure)
   405  
   406  	snap := env.getSnapshot()
   407  	location := snap.ecfg.location()
   408  	service, err := newHostedService(azure.ManagementAPI, env.getEnvPrefix(), env.getAffinityGroupName(), location)
   409  	if err != nil {
   410  		return nil, nil, err
   411  	}
   412  	serviceName := service.ServiceName
   413  
   414  	// If we fail after this point, clean up the hosted service.
   415  	defer func() {
   416  		if err != nil {
   417  			azure.DestroyHostedService(
   418  				&gwacl.DestroyHostedServiceRequest{
   419  					ServiceName: serviceName,
   420  				})
   421  		}
   422  	}()
   423  
   424  	series := possibleTools.OneSeries()
   425  	instanceType, sourceImageName, err := env.selectInstanceTypeAndImage(cons, series, location)
   426  	if err != nil {
   427  		return nil, nil, err
   428  	}
   429  
   430  	// virtualNetworkName is the virtual network to which all the
   431  	// deployments in this environment belong.
   432  	virtualNetworkName := env.getVirtualNetworkName()
   433  
   434  	// 1. Create an OS Disk.
   435  	vhd := env.newOSDisk(sourceImageName)
   436  
   437  	// 2. Create a Role for a Linux machine.
   438  	role := env.newRole(instanceType, vhd, userData, roleHostname)
   439  
   440  	// 3. Create the Deployment object.
   441  	deployment := env.newDeployment(role, serviceName, serviceName, virtualNetworkName)
   442  
   443  	err = azure.AddDeployment(deployment, serviceName)
   444  	if err != nil {
   445  		return nil, nil, err
   446  	}
   447  
   448  	var inst instance.Instance
   449  
   450  	// From here on, remember to shut down the instance before returning
   451  	// any error.
   452  	defer func() {
   453  		if err != nil && inst != nil {
   454  			err2 := env.StopInstances([]instance.Instance{inst})
   455  			if err2 != nil {
   456  				// Failure upon failure.  Log it, but return
   457  				// the original error.
   458  				logger.Errorf("error releasing failed instance: %v", err)
   459  			}
   460  		}
   461  	}()
   462  
   463  	// Assign the returned instance to 'inst' so that the deferred method
   464  	// above can perform its check.
   465  	inst, err = env.getInstance(serviceName)
   466  	if err != nil {
   467  		return nil, nil, err
   468  	}
   469  	// TODO(bug 1193998) - return instance hardware characteristics as well
   470  	return inst, &instance.HardwareCharacteristics{}, nil
   471  }
   472  
   473  // getInstance returns an up-to-date version of the instance with the given
   474  // name.
   475  func (env *azureEnviron) getInstance(instanceName string) (instance.Instance, error) {
   476  	context, err := env.getManagementAPI()
   477  	if err != nil {
   478  		return nil, err
   479  	}
   480  	defer env.releaseManagementAPI(context)
   481  	service, err := context.GetHostedServiceProperties(instanceName, false)
   482  	if err != nil {
   483  		return nil, fmt.Errorf("could not get instance %q: %v", instanceName, err)
   484  	}
   485  	instance := &azureInstance{service.HostedServiceDescriptor, env}
   486  	return instance, nil
   487  }
   488  
   489  // newOSDisk creates a gwacl.OSVirtualHardDisk object suitable for an
   490  // Azure Virtual Machine.
   491  func (env *azureEnviron) newOSDisk(sourceImageName string) *gwacl.OSVirtualHardDisk {
   492  	vhdName := gwacl.MakeRandomDiskName("juju")
   493  	vhdPath := fmt.Sprintf("vhds/%s", vhdName)
   494  	snap := env.getSnapshot()
   495  	storageAccount := snap.ecfg.storageAccountName()
   496  	mediaLink := gwacl.CreateVirtualHardDiskMediaLink(storageAccount, vhdPath)
   497  	// The disk label is optional and the disk name can be omitted if
   498  	// mediaLink is provided.
   499  	return gwacl.NewOSVirtualHardDisk("", "", "", mediaLink, sourceImageName, "Linux")
   500  }
   501  
   502  // getInitialEndpoints returns a slice of the endpoints every instance should have open
   503  // (ssh port, etc).
   504  func (env *azureEnviron) getInitialEndpoints() []gwacl.InputEndpoint {
   505  	cfg := env.Config()
   506  	return []gwacl.InputEndpoint{
   507  		{
   508  			LocalPort: 22,
   509  			Name:      "sshport",
   510  			Port:      22,
   511  			Protocol:  "tcp",
   512  		},
   513  		// TODO: Ought to have this only for state servers.
   514  		{
   515  			LocalPort: cfg.StatePort(),
   516  			Name:      "stateport",
   517  			Port:      cfg.StatePort(),
   518  			Protocol:  "tcp",
   519  		},
   520  		// TODO: Ought to have this only for API servers.
   521  		{
   522  			LocalPort: cfg.APIPort(),
   523  			Name:      "apiport",
   524  			Port:      cfg.APIPort(),
   525  			Protocol:  "tcp",
   526  		}}
   527  }
   528  
   529  // newRole creates a gwacl.Role object (an Azure Virtual Machine) which uses
   530  // the given Virtual Hard Drive.
   531  //
   532  // The VM will have:
   533  // - an 'ubuntu' user defined with an unguessable (randomly generated) password
   534  // - its ssh port (TCP 22) open
   535  // - its state port (TCP mongoDB) port open
   536  // - its API port (TCP) open
   537  //
   538  // roleSize is the name of one of Azure's machine types, e.g. ExtraSmall,
   539  // Large, A6 etc.
   540  func (env *azureEnviron) newRole(roleSize string, vhd *gwacl.OSVirtualHardDisk, userData string, roleHostname string) *gwacl.Role {
   541  	// Create a Linux Configuration with the username and the password
   542  	// empty and disable SSH with password authentication.
   543  	hostname := roleHostname
   544  	username := "ubuntu"
   545  	password := gwacl.MakeRandomPassword()
   546  	linuxConfigurationSet := gwacl.NewLinuxProvisioningConfigurationSet(hostname, username, password, userData, "true")
   547  	// Generate a Network Configuration with the initially required ports
   548  	// open.
   549  	networkConfigurationSet := gwacl.NewNetworkConfigurationSet(env.getInitialEndpoints(), nil)
   550  	roleName := gwacl.MakeRandomRoleName("juju")
   551  	// The ordering of these configuration sets is significant for the tests.
   552  	return gwacl.NewRole(
   553  		roleSize, roleName,
   554  		[]gwacl.ConfigurationSet{*linuxConfigurationSet, *networkConfigurationSet},
   555  		[]gwacl.OSVirtualHardDisk{*vhd})
   556  }
   557  
   558  // newDeployment creates and returns a gwacl Deployment object.
   559  func (env *azureEnviron) newDeployment(role *gwacl.Role, deploymentName string, deploymentLabel string, virtualNetworkName string) *gwacl.Deployment {
   560  	// Use the service name as the label for the deployment.
   561  	return gwacl.NewDeploymentForCreateVMDeployment(deploymentName, deploymentSlot, deploymentLabel, []gwacl.Role{*role}, virtualNetworkName)
   562  }
   563  
   564  // Spawn this many goroutines to issue requests for destroying services.
   565  // TODO: this is currently set to 1 because of a problem in Azure:
   566  // removing Services in the same affinity group concurrently causes a conflict.
   567  // This conflict is wrongly reported by Azure as a BadRequest (400).
   568  // This has been reported to Windows Azure.
   569  var maxConcurrentDeletes = 1
   570  
   571  // StartInstance is specified in the InstanceBroker interface.
   572  func (env *azureEnviron) StopInstances(instances []instance.Instance) error {
   573  	// Each Juju instance is an Azure Service (instance==service), destroy
   574  	// all the Azure services.
   575  	// Acquire management API object.
   576  	context, err := env.getManagementAPI()
   577  	if err != nil {
   578  		return err
   579  	}
   580  	defer env.releaseManagementAPI(context)
   581  
   582  	// Destroy all the services in parallel.
   583  	run := parallel.NewRun(maxConcurrentDeletes)
   584  	for _, instance := range instances {
   585  		serviceName := string(instance.Id())
   586  		run.Do(func() error {
   587  			request := &gwacl.DestroyHostedServiceRequest{ServiceName: serviceName}
   588  			return context.DestroyHostedService(request)
   589  		})
   590  	}
   591  	return run.Wait()
   592  }
   593  
   594  // Instances is specified in the Environ interface.
   595  func (env *azureEnviron) Instances(ids []instance.Id) ([]instance.Instance, error) {
   596  	// The instance list is built using the list of all the relevant
   597  	// Azure Services (instance==service).
   598  	// Acquire management API object.
   599  	context, err := env.getManagementAPI()
   600  	if err != nil {
   601  		return nil, err
   602  	}
   603  	defer env.releaseManagementAPI(context)
   604  
   605  	// Prepare gwacl request object.
   606  	serviceNames := make([]string, len(ids))
   607  	for i, id := range ids {
   608  		serviceNames[i] = string(id)
   609  	}
   610  	request := &gwacl.ListSpecificHostedServicesRequest{ServiceNames: serviceNames}
   611  
   612  	// Issue 'ListSpecificHostedServices' request with gwacl.
   613  	services, err := context.ListSpecificHostedServices(request)
   614  	if err != nil {
   615  		return nil, err
   616  	}
   617  
   618  	// If no instances were found, return ErrNoInstances.
   619  	if len(services) == 0 {
   620  		return nil, environs.ErrNoInstances
   621  	}
   622  
   623  	instances := convertToInstances(services, env)
   624  
   625  	// Check if we got a partial result.
   626  	if len(ids) != len(instances) {
   627  		return instances, environs.ErrPartialInstances
   628  	}
   629  	return instances, nil
   630  }
   631  
   632  // AllInstances is specified in the InstanceBroker interface.
   633  func (env *azureEnviron) AllInstances() ([]instance.Instance, error) {
   634  	// The instance list is built using the list of all the Azure
   635  	// Services (instance==service).
   636  	// Acquire management API object.
   637  	context, err := env.getManagementAPI()
   638  	if err != nil {
   639  		return nil, err
   640  	}
   641  	defer env.releaseManagementAPI(context)
   642  
   643  	request := &gwacl.ListPrefixedHostedServicesRequest{ServiceNamePrefix: env.getEnvPrefix()}
   644  	services, err := context.ListPrefixedHostedServices(request)
   645  	if err != nil {
   646  		return nil, err
   647  	}
   648  	return convertToInstances(services, env), nil
   649  }
   650  
   651  // getEnvPrefix returns the prefix used to name the objects specific to this
   652  // environment.
   653  func (env *azureEnviron) getEnvPrefix() string {
   654  	return fmt.Sprintf("juju-%s-", env.Name())
   655  }
   656  
   657  // convertToInstances converts a slice of gwacl.HostedServiceDescriptor objects
   658  // into a slice of instance.Instance objects.
   659  func convertToInstances(services []gwacl.HostedServiceDescriptor, env *azureEnviron) []instance.Instance {
   660  	instances := make([]instance.Instance, len(services))
   661  	for i, service := range services {
   662  		instances[i] = &azureInstance{service, env}
   663  	}
   664  	return instances
   665  }
   666  
   667  // Storage is specified in the Environ interface.
   668  func (env *azureEnviron) Storage() storage.Storage {
   669  	return env.getSnapshot().storage
   670  }
   671  
   672  // Destroy is specified in the Environ interface.
   673  func (env *azureEnviron) Destroy() error {
   674  	logger.Debugf("destroying environment %q", env.name)
   675  
   676  	// Stop all instances.
   677  	insts, err := env.AllInstances()
   678  	if err != nil {
   679  		return fmt.Errorf("cannot get instances: %v", err)
   680  	}
   681  	err = env.StopInstances(insts)
   682  	if err != nil {
   683  		return fmt.Errorf("cannot stop instances: %v", err)
   684  	}
   685  
   686  	// Delete vnet and affinity group.
   687  	err = env.deleteVirtualNetwork()
   688  	if err != nil {
   689  		return fmt.Errorf("cannot delete the environment's virtual network: %v", err)
   690  	}
   691  	err = env.deleteAffinityGroup()
   692  	if err != nil {
   693  		return fmt.Errorf("cannot delete the environment's affinity group: %v", err)
   694  	}
   695  
   696  	// Delete storage.
   697  	// Deleting the storage is done last so that if something fails
   698  	// half way through the Destroy() method, the storage won't be cleaned
   699  	// up and thus an attempt to re-boostrap the environment will lead to
   700  	// a "error: environment is already bootstrapped" error.
   701  	err = env.Storage().RemoveAll()
   702  	if err != nil {
   703  		return fmt.Errorf("cannot clean up storage: %v", err)
   704  	}
   705  	return nil
   706  }
   707  
   708  // OpenPorts is specified in the Environ interface. However, Azure does not
   709  // support the global firewall mode.
   710  func (env *azureEnviron) OpenPorts(ports []instance.Port) error {
   711  	return nil
   712  }
   713  
   714  // ClosePorts is specified in the Environ interface. However, Azure does not
   715  // support the global firewall mode.
   716  func (env *azureEnviron) ClosePorts(ports []instance.Port) error {
   717  	return nil
   718  }
   719  
   720  // Ports is specified in the Environ interface.
   721  func (env *azureEnviron) Ports() ([]instance.Port, error) {
   722  	// TODO: implement this.
   723  	return []instance.Port{}, nil
   724  }
   725  
   726  // Provider is specified in the Environ interface.
   727  func (env *azureEnviron) Provider() environs.EnvironProvider {
   728  	return azureEnvironProvider{}
   729  }
   730  
   731  // azureManagementContext wraps two things: a gwacl.ManagementAPI (effectively
   732  // a session on the Azure management API) and a tempCertFile, which keeps track
   733  // of the temporary certificate file that needs to be deleted once we're done
   734  // with this particular session.
   735  // Since it embeds *gwacl.ManagementAPI, you can use it much as if it were a
   736  // pointer to a ManagementAPI object.  Just don't forget to release it after
   737  // use.
   738  type azureManagementContext struct {
   739  	*gwacl.ManagementAPI
   740  	certFile *tempCertFile
   741  }
   742  
   743  var (
   744  	retryPolicy = gwacl.RetryPolicy{
   745  		NbRetries: 6,
   746  		HttpStatusCodes: []int{
   747  			http.StatusConflict,
   748  			http.StatusRequestTimeout,
   749  			http.StatusInternalServerError,
   750  			http.StatusServiceUnavailable,
   751  		},
   752  		Delay: 10 * time.Second}
   753  )
   754  
   755  // getManagementAPI obtains a context object for interfacing with Azure's
   756  // management API.
   757  // For now, each invocation just returns a separate object.  This is probably
   758  // wasteful (each context gets its own SSL connection) and may need optimizing
   759  // later.
   760  func (env *azureEnviron) getManagementAPI() (*azureManagementContext, error) {
   761  	snap := env.getSnapshot()
   762  	subscription := snap.ecfg.managementSubscriptionId()
   763  	certData := snap.ecfg.managementCertificate()
   764  	certFile, err := newTempCertFile([]byte(certData))
   765  	if err != nil {
   766  		return nil, err
   767  	}
   768  	// After this point, if we need to leave prematurely, we should clean
   769  	// up that certificate file.
   770  	location := snap.ecfg.location()
   771  	mgtAPI, err := gwacl.NewManagementAPIWithRetryPolicy(subscription, certFile.Path(), location, retryPolicy)
   772  	if err != nil {
   773  		certFile.Delete()
   774  		return nil, err
   775  	}
   776  	context := azureManagementContext{
   777  		ManagementAPI: mgtAPI,
   778  		certFile:      certFile,
   779  	}
   780  	return &context, nil
   781  }
   782  
   783  // releaseManagementAPI frees up a context object obtained through
   784  // getManagementAPI.
   785  func (env *azureEnviron) releaseManagementAPI(context *azureManagementContext) {
   786  	// Be tolerant to incomplete context objects, in case we ever get
   787  	// called during cleanup of a failed attempt to create one.
   788  	if context == nil || context.certFile == nil {
   789  		return
   790  	}
   791  	// For now, all that needs doing is to delete the temporary certificate
   792  	// file.  We may do cleverer things later, such as connection pooling
   793  	// where this method returns a context to the pool.
   794  	context.certFile.Delete()
   795  }
   796  
   797  // updateStorageAccountKey queries the storage account key, and updates the
   798  // version cached in env.storageAccountKey.
   799  //
   800  // It takes a snapshot in order to preserve transactional integrity relative
   801  // to the snapshot's starting state, without having to lock the environment
   802  // for the duration.  If there is a conflicting change to env relative to the
   803  // state recorded in the snapshot, this function will fail.
   804  func (env *azureEnviron) updateStorageAccountKey(snapshot *azureEnviron) (string, error) {
   805  	// This method follows an RCU pattern, an optimistic technique to
   806  	// implement atomic read-update transactions: get a consistent snapshot
   807  	// of state; process data; enter critical section; check for conflicts;
   808  	// write back changes.  The advantage is that there are no long-held
   809  	// locks, in particular while waiting for the request to Azure to
   810  	// complete.
   811  	// "Get a consistent snapshot of state" is the caller's responsibility.
   812  	// The caller can use env.getSnapshot().
   813  
   814  	// Process data: get a current account key from Azure.
   815  	key, err := env.queryStorageAccountKey()
   816  	if err != nil {
   817  		return "", err
   818  	}
   819  
   820  	// Enter critical section.
   821  	env.Lock()
   822  	defer env.Unlock()
   823  
   824  	// Check for conflicts: is the config still what it was?
   825  	if env.ecfg != snapshot.ecfg {
   826  		// The environment has been reconfigured while we were
   827  		// working on this, so the key we just get may not be
   828  		// appropriate any longer.  So fail.
   829  		// Whatever we were doing isn't likely to be right any more
   830  		// anyway.  Otherwise, it might be worth returning the key
   831  		// just in case it still works, and proceed without updating
   832  		// env.storageAccountKey.
   833  		return "", fmt.Errorf("environment was reconfigured")
   834  	}
   835  
   836  	// Write back changes.
   837  	env.storageAccountKey = key
   838  	return key, nil
   839  }
   840  
   841  // getStorageContext obtains a context object for interfacing with Azure's
   842  // storage API.
   843  // For now, each invocation just returns a separate object.  This is probably
   844  // wasteful (each context gets its own SSL connection) and may need optimizing
   845  // later.
   846  func (env *azureEnviron) getStorageContext() (*gwacl.StorageContext, error) {
   847  	snap := env.getSnapshot()
   848  	key := snap.storageAccountKey
   849  	if key == "" {
   850  		// We don't know the storage-account key yet.  Request it.
   851  		var err error
   852  		key, err = env.updateStorageAccountKey(snap)
   853  		if err != nil {
   854  			return nil, err
   855  		}
   856  	}
   857  	context := gwacl.StorageContext{
   858  		Account:       snap.ecfg.storageAccountName(),
   859  		Key:           key,
   860  		AzureEndpoint: gwacl.GetEndpoint(snap.ecfg.location()),
   861  		RetryPolicy:   retryPolicy,
   862  	}
   863  	return &context, nil
   864  }
   865  
   866  // baseURLs specifies an Azure specific location where we look for simplestreams information.
   867  // It contains the central databases for the released and daily streams, but this may
   868  // become more configurable.  This variable is here as a placeholder, but also
   869  // as an injection point for tests.
   870  var baseURLs = []string{}
   871  
   872  // GetImageSources returns a list of sources which are used to search for simplestreams image metadata.
   873  func (env *azureEnviron) GetImageSources() ([]simplestreams.DataSource, error) {
   874  	sources := make([]simplestreams.DataSource, 1+len(baseURLs))
   875  	sources[0] = storage.NewStorageSimpleStreamsDataSource(env.Storage(), storage.BaseImagesPath)
   876  	for i, url := range baseURLs {
   877  		sources[i+1] = simplestreams.NewURLDataSource(url, simplestreams.VerifySSLHostnames)
   878  	}
   879  	return sources, nil
   880  }
   881  
   882  // GetToolsSources returns a list of sources which are used to search for simplestreams tools metadata.
   883  func (env *azureEnviron) GetToolsSources() ([]simplestreams.DataSource, error) {
   884  	// Add the simplestreams source off the control bucket.
   885  	sources := []simplestreams.DataSource{
   886  		storage.NewStorageSimpleStreamsDataSource(env.Storage(), storage.BaseToolsPath)}
   887  	return sources, nil
   888  }
   889  
   890  // getImageMetadataSigningRequired returns whether this environment requires
   891  // image metadata from Simplestreams to be signed.
   892  func (env *azureEnviron) getImageMetadataSigningRequired() bool {
   893  	// Hard-coded to true for now.  Once we support custom base URLs,
   894  	// this may have to change.
   895  	return true
   896  }
   897  
   898  // Region is specified in the HasRegion interface.
   899  func (env *azureEnviron) Region() (simplestreams.CloudSpec, error) {
   900  	ecfg := env.getSnapshot().ecfg
   901  	return simplestreams.CloudSpec{
   902  		Region:   ecfg.location(),
   903  		Endpoint: string(gwacl.GetEndpoint(ecfg.location())),
   904  	}, nil
   905  }