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