github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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  	"bytes"
     8  	"encoding/base64"
     9  	"encoding/json"
    10  	"fmt"
    11  	"net/http"
    12  	"regexp"
    13  	"strings"
    14  	"sync"
    15  	"time"
    16  
    17  	"github.com/juju/errors"
    18  	"github.com/juju/utils"
    19  	jujuos "github.com/juju/utils/os"
    20  	"github.com/juju/utils/series"
    21  	"github.com/juju/utils/set"
    22  	"launchpad.net/gwacl"
    23  
    24  	"github.com/juju/juju/cloudconfig/instancecfg"
    25  	"github.com/juju/juju/cloudconfig/providerinit"
    26  	"github.com/juju/juju/constraints"
    27  	"github.com/juju/juju/environs"
    28  	"github.com/juju/juju/environs/config"
    29  	"github.com/juju/juju/environs/imagemetadata"
    30  	"github.com/juju/juju/environs/instances"
    31  	"github.com/juju/juju/environs/simplestreams"
    32  	"github.com/juju/juju/environs/storage"
    33  	"github.com/juju/juju/instance"
    34  	"github.com/juju/juju/network"
    35  	"github.com/juju/juju/provider/common"
    36  	"github.com/juju/juju/state"
    37  	"github.com/juju/juju/state/multiwatcher"
    38  )
    39  
    40  const (
    41  	// deploymentSlot says in which slot to deploy instances.  Azure
    42  	// supports 'Production' or 'Staging'.
    43  	// This provider always deploys to Production.  Think twice about
    44  	// changing that: DNS names in the staging slot work differently from
    45  	// those in the production slot.  In Staging, Azure assigns an
    46  	// arbitrary hostname that we can then extract from the deployment's
    47  	// URL.  In Production, the hostname in the deployment URL does not
    48  	// actually seem to resolve; instead, the service name is used as the
    49  	// DNS name, with ".cloudapp.net" appended.
    50  	deploymentSlot = "Production"
    51  
    52  	// Address space of the virtual network used by the nodes in this
    53  	// environement, in CIDR notation. This is the network used for
    54  	// machine-to-machine communication.
    55  	networkDefinition = "10.0.0.0/8"
    56  
    57  	// stateServerLabel is the label applied to the cloud service created
    58  	// for state servers.
    59  	stateServerLabel = "juju-state-server"
    60  )
    61  
    62  // vars for testing purposes.
    63  var (
    64  	createInstance = (*azureEnviron).createInstance
    65  )
    66  
    67  type azureEnviron struct {
    68  	// Except where indicated otherwise, all fields in this object should
    69  	// only be accessed using a lock or a snapshot.
    70  	sync.Mutex
    71  
    72  	// archMutex gates access to supportedArchitectures
    73  	archMutex sync.Mutex
    74  	// supportedArchitectures caches the architectures
    75  	// for which images can be instantiated.
    76  	supportedArchitectures []string
    77  
    78  	// ecfg is the environment's Azure-specific configuration.
    79  	ecfg *azureEnvironConfig
    80  
    81  	// storage is this environ's own private storage.
    82  	storage storage.Storage
    83  
    84  	// storageAccountKey holds an access key to this environment's
    85  	// private storage.  This is automatically queried from Azure on
    86  	// startup.
    87  	storageAccountKey string
    88  
    89  	// api is a management API for Microsoft Azure.
    90  	api *gwacl.ManagementAPI
    91  
    92  	// vnet describes the configured virtual network.
    93  	vnet *gwacl.VirtualNetworkSite
    94  
    95  	// availableRoleSizes is the role sizes available in the configured
    96  	// location. This will be reset whenever the location configuration changes.
    97  	availableRoleSizes set.Strings
    98  }
    99  
   100  // azureEnviron implements Environ and HasRegion.
   101  var _ environs.Environ = (*azureEnviron)(nil)
   102  var _ simplestreams.HasRegion = (*azureEnviron)(nil)
   103  var _ state.Prechecker = (*azureEnviron)(nil)
   104  
   105  // NewEnviron creates a new azureEnviron.
   106  func NewEnviron(cfg *config.Config) (*azureEnviron, error) {
   107  	var env azureEnviron
   108  	err := env.SetConfig(cfg)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  
   113  	// Set up storage.
   114  	env.storage = &azureStorage{
   115  		storageContext: &environStorageContext{environ: &env},
   116  	}
   117  	return &env, nil
   118  }
   119  
   120  // extractStorageKey returns the primary account key from a gwacl
   121  // StorageAccountKeys struct, or if there is none, the secondary one.
   122  func extractStorageKey(keys *gwacl.StorageAccountKeys) string {
   123  	if keys.Primary != "" {
   124  		return keys.Primary
   125  	}
   126  	return keys.Secondary
   127  }
   128  
   129  // queryStorageAccountKey retrieves the storage account's key from Azure.
   130  func (env *azureEnviron) queryStorageAccountKey() (string, error) {
   131  	snap := env.getSnapshot()
   132  
   133  	accountName := snap.ecfg.storageAccountName()
   134  	keys, err := snap.api.GetStorageAccountKeys(accountName)
   135  	if err != nil {
   136  		return "", errors.Annotate(err, "cannot obtain storage account keys")
   137  	}
   138  
   139  	key := extractStorageKey(keys)
   140  	if key == "" {
   141  		return "", errors.New("no keys available for storage account")
   142  	}
   143  
   144  	return key, nil
   145  }
   146  
   147  // getSnapshot produces an atomic shallow copy of the environment object.
   148  // Whenever you need to access the environment object's fields without
   149  // modifying them, get a snapshot and read its fields instead.  You will
   150  // get a consistent view of the fields without any further locking.
   151  // If you do need to modify the environment's fields, do not get a snapshot
   152  // but lock the object throughout the critical section.
   153  func (env *azureEnviron) getSnapshot() *azureEnviron {
   154  	env.Lock()
   155  	defer env.Unlock()
   156  
   157  	// Copy the environment.  (Not the pointer, the environment itself.)
   158  	// This is a shallow copy.
   159  	snap := *env
   160  	// Reset the snapshot's mutex, because we just copied it while we
   161  	// were holding it.  The snapshot will have a "clean," unlocked mutex.
   162  	snap.Mutex = sync.Mutex{}
   163  	return &snap
   164  }
   165  
   166  // getAffinityGroupName returns the name of the affinity group used by all
   167  // the Services in this environment. The affinity group name is immutable,
   168  // so there is no need to use a configuration snapshot.
   169  func (env *azureEnviron) getAffinityGroupName() string {
   170  	return env.getEnvPrefix() + "ag"
   171  }
   172  
   173  // getLocation gets the configured Location for the environment.
   174  func (env *azureEnviron) getLocation() string {
   175  	snap := env.getSnapshot()
   176  	return snap.ecfg.location()
   177  }
   178  
   179  func (env *azureEnviron) createAffinityGroup() error {
   180  	snap := env.getSnapshot()
   181  	affinityGroupName := env.getAffinityGroupName()
   182  	location := snap.ecfg.location()
   183  	cag := gwacl.NewCreateAffinityGroup(affinityGroupName, affinityGroupName, affinityGroupName, location)
   184  	return snap.api.CreateAffinityGroup(&gwacl.CreateAffinityGroupRequest{
   185  		CreateAffinityGroup: cag,
   186  	})
   187  }
   188  
   189  func (env *azureEnviron) deleteAffinityGroup() error {
   190  	snap := env.getSnapshot()
   191  	affinityGroupName := env.getAffinityGroupName()
   192  	return snap.api.DeleteAffinityGroup(&gwacl.DeleteAffinityGroupRequest{
   193  		Name: affinityGroupName,
   194  	})
   195  }
   196  
   197  // getAvailableRoleSizes returns the role sizes available for the configured
   198  // location.
   199  func (env *azureEnviron) getAvailableRoleSizes() (_ set.Strings, err error) {
   200  	defer errors.DeferredAnnotatef(&err, "cannot get available role sizes")
   201  
   202  	snap := env.getSnapshot()
   203  	if snap.availableRoleSizes != nil {
   204  		return snap.availableRoleSizes, nil
   205  	}
   206  	locations, err := snap.api.ListLocations()
   207  	if err != nil {
   208  		return nil, errors.Annotate(err, "cannot list locations")
   209  	}
   210  	var available set.Strings
   211  	for _, location := range locations {
   212  		if location.Name != snap.ecfg.location() {
   213  			continue
   214  		}
   215  		if location.ComputeCapabilities == nil {
   216  			return nil, errors.Annotate(err, "cannot determine compute capabilities")
   217  		}
   218  		available = set.NewStrings(location.ComputeCapabilities.VirtualMachineRoleSizes...)
   219  		break
   220  	}
   221  	if available == nil {
   222  		return nil, errors.NotFoundf("location %q", snap.ecfg.location())
   223  	}
   224  	env.Lock()
   225  	env.availableRoleSizes = available
   226  	env.Unlock()
   227  	return available, nil
   228  }
   229  
   230  // getVirtualNetworkName returns the name of the virtual network used by all
   231  // the VMs in this environment. The virtual network name is immutable,
   232  // so there is no need to use a configuration snapshot.
   233  func (env *azureEnviron) getVirtualNetworkName() string {
   234  	return env.getEnvPrefix() + "vnet"
   235  }
   236  
   237  // getVirtualNetwork returns the virtual network used by all the VMs in this
   238  // environment.
   239  func (env *azureEnviron) getVirtualNetwork() (*gwacl.VirtualNetworkSite, error) {
   240  	snap := env.getSnapshot()
   241  	if snap.vnet != nil {
   242  		return snap.vnet, nil
   243  	}
   244  	cfg, err := env.api.GetNetworkConfiguration()
   245  	if err != nil {
   246  		return nil, errors.Annotate(err, "error getting network configuration")
   247  	}
   248  	var vnet *gwacl.VirtualNetworkSite
   249  	vnetName := env.getVirtualNetworkName()
   250  	if cfg != nil && cfg.VirtualNetworkSites != nil {
   251  		for _, site := range *cfg.VirtualNetworkSites {
   252  			if site.Name == vnetName {
   253  				vnet = &site
   254  				break
   255  			}
   256  		}
   257  	}
   258  	if vnet == nil {
   259  		return nil, errors.NotFoundf("virtual network %q", vnetName)
   260  	}
   261  	env.Lock()
   262  	env.vnet = vnet
   263  	env.Unlock()
   264  	return vnet, nil
   265  }
   266  
   267  // createVirtualNetwork creates a virtual network for the environment.
   268  func (env *azureEnviron) createVirtualNetwork() error {
   269  	snap := env.getSnapshot()
   270  	vnetName := env.getVirtualNetworkName()
   271  	virtualNetwork := gwacl.VirtualNetworkSite{
   272  		Name:     vnetName,
   273  		Location: snap.ecfg.location(),
   274  		AddressSpacePrefixes: []string{
   275  			networkDefinition,
   276  		},
   277  	}
   278  	if err := snap.api.AddVirtualNetworkSite(&virtualNetwork); err != nil {
   279  		return errors.Trace(err)
   280  	}
   281  	env.Lock()
   282  	env.vnet = &virtualNetwork
   283  	env.Unlock()
   284  	return nil
   285  }
   286  
   287  // deleteVnetAttempt is an AttemptyStrategy for use
   288  // when attempting delete a virtual network. This is
   289  // necessary as Azure apparently does not release all
   290  // references to the vnet even when all cloud services
   291  // are deleted.
   292  var deleteVnetAttempt = utils.AttemptStrategy{
   293  	Total: 30 * time.Second,
   294  	Delay: 1 * time.Second,
   295  }
   296  
   297  var networkInUse = regexp.MustCompile(".*The virtual network .* is currently in use.*")
   298  
   299  func (env *azureEnviron) deleteVirtualNetwork() error {
   300  	snap := env.getSnapshot()
   301  	vnetName := env.getVirtualNetworkName()
   302  	var err error
   303  	for a := deleteVnetAttempt.Start(); a.Next(); {
   304  		err = snap.api.RemoveVirtualNetworkSite(vnetName)
   305  		if err == nil {
   306  			return nil
   307  		}
   308  		if err, ok := err.(*gwacl.AzureError); ok {
   309  			if err.StatusCode() == 400 && networkInUse.MatchString(err.Message) {
   310  				// Retry on "virtual network XYZ is currently in use".
   311  				continue
   312  			}
   313  		}
   314  		// Any other error should be returned.
   315  		break
   316  	}
   317  	return err
   318  }
   319  
   320  // getContainerName returns the name of the private storage account container
   321  // that this environment is using. The container name is immutable,
   322  // so there is no need to use a configuration snapshot.
   323  func (env *azureEnviron) getContainerName() string {
   324  	return env.getEnvPrefix() + "private"
   325  }
   326  
   327  func isHTTPConflict(err error) bool {
   328  	if err, ok := err.(gwacl.HTTPError); ok {
   329  		return err.StatusCode() == http.StatusConflict
   330  	}
   331  	return false
   332  }
   333  
   334  func isVirtualNetworkExist(err error) bool {
   335  	// TODO(axw) 2014-06-16 #1330473
   336  	// Add an error type to gwacl for this.
   337  	s := err.Error()
   338  	const prefix = "could not add virtual network"
   339  	const suffix = "already exists"
   340  	return strings.HasPrefix(s, prefix) && strings.HasSuffix(s, suffix)
   341  }
   342  
   343  // Bootstrap is specified in the Environ interface.
   344  func (env *azureEnviron) Bootstrap(ctx environs.BootstrapContext, args environs.BootstrapParams) (arch, series string, _ environs.BootstrapFinalizer, err error) {
   345  	// The creation of the affinity group and the virtual network is specific to the Azure provider.
   346  	err = env.createAffinityGroup()
   347  	if err != nil && !isHTTPConflict(err) {
   348  		return "", "", nil, err
   349  	}
   350  	// If we fail after this point, clean up the affinity group.
   351  	defer func() {
   352  		if err != nil {
   353  			env.deleteAffinityGroup()
   354  		}
   355  	}()
   356  
   357  	err = env.createVirtualNetwork()
   358  	if err != nil && !isVirtualNetworkExist(err) {
   359  		return "", "", nil, err
   360  	}
   361  	// If we fail after this point, clean up the virtual network.
   362  	defer func() {
   363  		if err != nil {
   364  			env.deleteVirtualNetwork()
   365  		}
   366  	}()
   367  	return common.Bootstrap(ctx, env, args)
   368  }
   369  
   370  // isLegacyInstance reports whether the instance is a
   371  // legacy instance (i.e. one-to-one cloud service to instance).
   372  func isLegacyInstance(inst *azureInstance) (bool, error) {
   373  	snap := inst.environ.getSnapshot()
   374  	serviceName := inst.hostedService.ServiceName
   375  	service, err := snap.api.GetHostedServiceProperties(serviceName, true)
   376  	if err != nil {
   377  		return false, err
   378  	} else if len(service.Deployments) != 1 {
   379  		return false, nil
   380  	}
   381  	deploymentName := service.Deployments[0].Name
   382  	return deploymentName == deploymentNameV1(serviceName), nil
   383  }
   384  
   385  // StateServerInstances is specified in the Environ interface.
   386  func (env *azureEnviron) StateServerInstances() ([]instance.Id, error) {
   387  	// Locate the state-server cloud service, and get its addresses.
   388  	instances, err := env.AllInstances()
   389  	if err != nil {
   390  		return nil, err
   391  	}
   392  	var stateServerInstanceIds []instance.Id
   393  	var loadStateFile bool
   394  	for _, inst := range instances {
   395  		azureInstance := inst.(*azureInstance)
   396  		label := azureInstance.hostedService.Label
   397  		if decoded, err := base64.StdEncoding.DecodeString(label); err == nil {
   398  			if string(decoded) == stateServerLabel {
   399  				stateServerInstanceIds = append(stateServerInstanceIds, inst.Id())
   400  				continue
   401  			}
   402  		}
   403  		if !loadStateFile {
   404  			_, roleName := env.splitInstanceId(azureInstance.Id())
   405  			if roleName == "" {
   406  				loadStateFile = true
   407  			}
   408  		}
   409  	}
   410  	if loadStateFile {
   411  		// Some legacy instances were found, so we must load provider-state
   412  		// to find which instance was the original state server. If we find
   413  		// a legacy environment, then stateServerInstanceIds will not contain
   414  		// the original bootstrap instance, which is the only one that will
   415  		// be in provider-state.
   416  		instanceIds, err := common.ProviderStateInstances(env, env.Storage())
   417  		if err != nil {
   418  			return nil, err
   419  		}
   420  		stateServerInstanceIds = append(stateServerInstanceIds, instanceIds...)
   421  	}
   422  	if len(stateServerInstanceIds) == 0 {
   423  		return nil, environs.ErrNoInstances
   424  	}
   425  	return stateServerInstanceIds, nil
   426  }
   427  
   428  // Config is specified in the Environ interface.
   429  func (env *azureEnviron) Config() *config.Config {
   430  	snap := env.getSnapshot()
   431  	return snap.ecfg.Config
   432  }
   433  
   434  // SetConfig is specified in the Environ interface.
   435  func (env *azureEnviron) SetConfig(cfg *config.Config) error {
   436  	var oldLocation string
   437  	if snap := env.getSnapshot(); snap.ecfg != nil {
   438  		oldLocation = snap.ecfg.location()
   439  	}
   440  
   441  	ecfg, err := azureEnvironProvider{}.newConfig(cfg)
   442  	if err != nil {
   443  		return err
   444  	}
   445  
   446  	env.Lock()
   447  	defer env.Unlock()
   448  
   449  	if env.ecfg != nil {
   450  		_, err = azureEnvironProvider{}.Validate(cfg, env.ecfg.Config)
   451  		if err != nil {
   452  			return err
   453  		}
   454  	}
   455  
   456  	env.ecfg = ecfg
   457  
   458  	// Reset storage account key.  Even if we had one before, it may not
   459  	// be appropriate for the new config.
   460  	env.storageAccountKey = ""
   461  
   462  	subscription := ecfg.managementSubscriptionId()
   463  	certKeyPEM := []byte(ecfg.managementCertificate())
   464  	location := ecfg.location()
   465  	mgtAPI, err := gwacl.NewManagementAPICertDataWithRetryPolicy(subscription, certKeyPEM, certKeyPEM, location, retryPolicy)
   466  	if err != nil {
   467  		return errors.Annotate(err, "cannot acquire management API")
   468  	}
   469  	env.api = mgtAPI
   470  
   471  	// If the location changed, reset the available role sizes.
   472  	if location != oldLocation {
   473  		env.availableRoleSizes = nil
   474  	}
   475  
   476  	return nil
   477  }
   478  
   479  // attemptCreateService tries to create a new hosted service on Azure, with a
   480  // name it chooses (based on the given prefix), but recognizes that the name
   481  // may not be available.  If the name is not available, it does not treat that
   482  // as an error but just returns nil.
   483  func attemptCreateService(azure *gwacl.ManagementAPI, prefix, affinityGroupName, label string) (*gwacl.CreateHostedService, error) {
   484  	var err error
   485  	name := gwacl.MakeRandomHostedServiceName(prefix)
   486  	err = azure.CheckHostedServiceNameAvailability(name)
   487  	if err != nil {
   488  		// The calling function should retry.
   489  		return nil, nil
   490  	}
   491  	if label == "" {
   492  		label = name
   493  	}
   494  	req := gwacl.NewCreateHostedServiceWithLocation(name, label, "")
   495  	req.AffinityGroup = affinityGroupName
   496  	err = azure.AddHostedService(req)
   497  	if err != nil {
   498  		return nil, err
   499  	}
   500  	return req, nil
   501  }
   502  
   503  // newHostedService creates a hosted service.  It will make up a unique name,
   504  // starting with the given prefix.
   505  func newHostedService(azure *gwacl.ManagementAPI, prefix, affinityGroupName, label string) (*gwacl.HostedService, error) {
   506  	var err error
   507  	var createdService *gwacl.CreateHostedService
   508  	for tries := 10; tries > 0 && err == nil && createdService == nil; tries-- {
   509  		createdService, err = attemptCreateService(azure, prefix, affinityGroupName, label)
   510  	}
   511  	if err != nil {
   512  		return nil, errors.Annotate(err, "could not create hosted service")
   513  	}
   514  	if createdService == nil {
   515  		return nil, fmt.Errorf("could not come up with a unique hosted service name - is your randomizer initialized?")
   516  	}
   517  	return azure.GetHostedServiceProperties(createdService.ServiceName, true)
   518  }
   519  
   520  // SupportedArchitectures is specified on the EnvironCapability interface.
   521  func (env *azureEnviron) SupportedArchitectures() ([]string, error) {
   522  	env.archMutex.Lock()
   523  	defer env.archMutex.Unlock()
   524  	if env.supportedArchitectures != nil {
   525  		return env.supportedArchitectures, nil
   526  	}
   527  	// Create a filter to get all images from our region and for the correct stream.
   528  	ecfg := env.getSnapshot().ecfg
   529  	region := ecfg.location()
   530  	cloudSpec := simplestreams.CloudSpec{
   531  		Region:   region,
   532  		Endpoint: getEndpoint(region),
   533  	}
   534  	imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{
   535  		CloudSpec: cloudSpec,
   536  		Stream:    ecfg.ImageStream(),
   537  	})
   538  	var err error
   539  	env.supportedArchitectures, err = common.SupportedArchitectures(env, imageConstraint)
   540  	return env.supportedArchitectures, err
   541  }
   542  
   543  // selectInstanceTypeAndImage returns the appropriate instances.InstanceType and
   544  // the OS image name for launching a virtual machine with the given parameters.
   545  func (env *azureEnviron) selectInstanceTypeAndImage(constraint *instances.InstanceConstraint) (*instances.InstanceType, string, error) {
   546  	ecfg := env.getSnapshot().ecfg
   547  	sourceImageName := ecfg.forceImageName()
   548  	if sourceImageName != "" {
   549  		// Configuration forces us to use a specific image.  There may
   550  		// not be a suitable image in the simplestreams database.
   551  		// This means we can't use Juju's normal selection mechanism,
   552  		// because it combines instance-type and image selection: if
   553  		// there are no images we can use, it won't offer us an
   554  		// instance type either.
   555  		//
   556  		// Select the instance type using simple, Azure-specific code.
   557  		instanceType, err := selectMachineType(env, defaultToBaselineSpec(constraint.Constraints))
   558  		if err != nil {
   559  			return nil, "", err
   560  		}
   561  		return instanceType, sourceImageName, nil
   562  	}
   563  
   564  	// Choose the most suitable instance type and OS image, based on simplestreams information.
   565  	spec, err := findInstanceSpec(env, constraint)
   566  	if err != nil {
   567  		return nil, "", err
   568  	}
   569  	return &spec.InstanceType, spec.Image.Id, nil
   570  }
   571  
   572  var unsupportedConstraints = []string{
   573  	constraints.CpuPower,
   574  	constraints.Tags,
   575  }
   576  
   577  // ConstraintsValidator is defined on the Environs interface.
   578  func (env *azureEnviron) ConstraintsValidator() (constraints.Validator, error) {
   579  	validator := constraints.NewValidator()
   580  	validator.RegisterUnsupported(unsupportedConstraints)
   581  	supportedArches, err := env.SupportedArchitectures()
   582  	if err != nil {
   583  		return nil, err
   584  	}
   585  	validator.RegisterVocabulary(constraints.Arch, supportedArches)
   586  
   587  	instanceTypes, err := listInstanceTypes(env)
   588  	if err != nil {
   589  		return nil, err
   590  	}
   591  	instTypeNames := make([]string, len(instanceTypes))
   592  	for i, instanceType := range instanceTypes {
   593  		instTypeNames[i] = instanceType.Name
   594  	}
   595  	validator.RegisterVocabulary(constraints.InstanceType, instTypeNames)
   596  	validator.RegisterConflicts(
   597  		[]string{constraints.InstanceType},
   598  		[]string{constraints.Mem, constraints.CpuCores, constraints.Arch, constraints.RootDisk})
   599  
   600  	return validator, nil
   601  }
   602  
   603  // PrecheckInstance is defined on the state.Prechecker interface.
   604  func (env *azureEnviron) PrecheckInstance(series string, cons constraints.Value, placement string) error {
   605  	if placement != "" {
   606  		return fmt.Errorf("unknown placement directive: %s", placement)
   607  	}
   608  	if !cons.HasInstanceType() {
   609  		return nil
   610  	}
   611  	// Constraint has an instance-type constraint so let's see if it is valid.
   612  	instanceTypes, err := listInstanceTypes(env)
   613  	if err != nil {
   614  		return err
   615  	}
   616  	for _, instanceType := range instanceTypes {
   617  		if instanceType.Name == *cons.InstanceType {
   618  			return nil
   619  		}
   620  	}
   621  	return fmt.Errorf("invalid instance type %q", *cons.InstanceType)
   622  }
   623  
   624  // createInstance creates all of the Azure entities necessary for a
   625  // new instance. This includes Cloud Service, Deployment and Role.
   626  //
   627  // If serviceName is non-empty, then createInstance will assign to
   628  // the Cloud Service with that name. Otherwise, a new Cloud Service
   629  // will be created.
   630  func (env *azureEnviron) createInstance(azure *gwacl.ManagementAPI, role *gwacl.Role, serviceName string, stateServer bool) (resultInst instance.Instance, resultErr error) {
   631  	var inst instance.Instance
   632  	defer func() {
   633  		if inst != nil && resultErr != nil {
   634  			if err := env.StopInstances(inst.Id()); err != nil {
   635  				// Failure upon failure. Log it, but return the original error.
   636  				logger.Errorf("error releasing failed instance: %v", err)
   637  			}
   638  		}
   639  	}()
   640  	var err error
   641  	var service *gwacl.HostedService
   642  	if serviceName != "" {
   643  		logger.Debugf("creating instance in existing cloud service %q", serviceName)
   644  		service, err = azure.GetHostedServiceProperties(serviceName, true)
   645  	} else {
   646  		logger.Debugf("creating instance in new cloud service")
   647  		// If we're creating a cloud service for state servers,
   648  		// we will want to open additional ports. We need to
   649  		// record this against the cloud service, so we use a
   650  		// special label for the purpose.
   651  		var label string
   652  		if stateServer {
   653  			label = stateServerLabel
   654  		}
   655  		service, err = newHostedService(azure, env.getEnvPrefix(), env.getAffinityGroupName(), label)
   656  	}
   657  	if err != nil {
   658  		return nil, err
   659  	}
   660  	if len(service.Deployments) == 0 {
   661  		// This is a newly created cloud service, so we
   662  		// should destroy it if anything below fails.
   663  		defer func() {
   664  			if resultErr != nil {
   665  				azure.DeleteHostedService(service.ServiceName)
   666  				// Destroying the hosted service destroys the instance,
   667  				// so ensure StopInstances isn't called.
   668  				inst = nil
   669  			}
   670  		}()
   671  		// Create an initial deployment.
   672  		deployment := gwacl.NewDeploymentForCreateVMDeployment(
   673  			deploymentNameV2(service.ServiceName),
   674  			deploymentSlot,
   675  			deploymentNameV2(service.ServiceName),
   676  			[]gwacl.Role{*role},
   677  			env.getVirtualNetworkName(),
   678  		)
   679  		if err := azure.AddDeployment(deployment, service.ServiceName); err != nil {
   680  			return nil, errors.Annotate(err, "error creating VM deployment")
   681  		}
   682  		service.Deployments = append(service.Deployments, *deployment)
   683  	} else {
   684  		// Update the deployment.
   685  		deployment := &service.Deployments[0]
   686  		if err := azure.AddRole(&gwacl.AddRoleRequest{
   687  			ServiceName:      service.ServiceName,
   688  			DeploymentName:   deployment.Name,
   689  			PersistentVMRole: (*gwacl.PersistentVMRole)(role),
   690  		}); err != nil {
   691  			return nil, err
   692  		}
   693  		deployment.RoleList = append(deployment.RoleList, *role)
   694  	}
   695  	return env.getInstance(service, role.RoleName)
   696  }
   697  
   698  // deploymentNameV1 returns the deployment name used
   699  // in the original implementation of the Azure provider.
   700  func deploymentNameV1(serviceName string) string {
   701  	return serviceName
   702  }
   703  
   704  // deploymentNameV2 returns the deployment name used
   705  // in the current implementation of the Azure provider.
   706  func deploymentNameV2(serviceName string) string {
   707  	return serviceName + "-v2"
   708  }
   709  
   710  // MaintainInstance is specified in the InstanceBroker interface.
   711  func (*azureEnviron) MaintainInstance(args environs.StartInstanceParams) error {
   712  	return nil
   713  }
   714  
   715  // StartInstance is specified in the InstanceBroker interface.
   716  func (env *azureEnviron) StartInstance(args environs.StartInstanceParams) (*environs.StartInstanceResult, error) {
   717  	if args.InstanceConfig.HasNetworks() {
   718  		return nil, errors.New("starting instances with networks is not supported yet")
   719  	}
   720  
   721  	err := instancecfg.FinishInstanceConfig(args.InstanceConfig, env.Config())
   722  	if err != nil {
   723  		return nil, err
   724  	}
   725  
   726  	// Pick envtools.  Needed for the custom data (which is what we normally
   727  	// call userdata).
   728  	args.InstanceConfig.Tools = args.Tools[0]
   729  	logger.Infof("picked tools %q", args.InstanceConfig.Tools)
   730  
   731  	// Compose userdata.
   732  	userData, err := providerinit.ComposeUserData(args.InstanceConfig, nil, AzureRenderer{})
   733  	if err != nil {
   734  		return nil, errors.Annotate(err, "cannot compose user data")
   735  	}
   736  
   737  	snapshot := env.getSnapshot()
   738  	location := snapshot.ecfg.location()
   739  	instanceType, sourceImageName, err := env.selectInstanceTypeAndImage(&instances.InstanceConstraint{
   740  		Region:      location,
   741  		Series:      args.Tools.OneSeries(),
   742  		Arches:      args.Tools.Arches(),
   743  		Constraints: args.Constraints,
   744  	})
   745  	if err != nil {
   746  		return nil, err
   747  	}
   748  
   749  	// We use the cloud service label as a way to group instances with
   750  	// the same affinity, so that machines can be be allocated to the
   751  	// same availability set.
   752  	var cloudServiceName string
   753  	if args.DistributionGroup != nil && snapshot.ecfg.availabilitySetsEnabled() {
   754  		instanceIds, err := args.DistributionGroup()
   755  		if err != nil {
   756  			return nil, err
   757  		}
   758  		for _, id := range instanceIds {
   759  			cloudServiceName, _ = env.splitInstanceId(id)
   760  			if cloudServiceName != "" {
   761  				break
   762  			}
   763  		}
   764  	}
   765  
   766  	vhd, err := env.newOSDisk(sourceImageName, args.InstanceConfig.Series)
   767  	if err != nil {
   768  		return nil, errors.Trace(err)
   769  	}
   770  	// If we're creating machine-0, we'll want to expose port 22.
   771  	// All other machines get an auto-generated public port for SSH.
   772  	stateServer := multiwatcher.AnyJobNeedsState(args.InstanceConfig.Jobs...)
   773  	role, err := env.newRole(instanceType.Id, vhd, stateServer, string(userData), args.InstanceConfig.Series, snapshot)
   774  	if err != nil {
   775  		return nil, errors.Trace(err)
   776  	}
   777  	inst, err := createInstance(env, snapshot.api, role, cloudServiceName, stateServer)
   778  	if err != nil {
   779  		return nil, errors.Trace(err)
   780  	}
   781  	hc := &instance.HardwareCharacteristics{
   782  		Mem:      &instanceType.Mem,
   783  		RootDisk: &instanceType.RootDisk,
   784  		CpuCores: &instanceType.CpuCores,
   785  	}
   786  	if len(instanceType.Arches) == 1 {
   787  		hc.Arch = &instanceType.Arches[0]
   788  	}
   789  	return &environs.StartInstanceResult{
   790  		Instance: inst,
   791  		Hardware: hc,
   792  	}, nil
   793  }
   794  
   795  // getInstance returns an up-to-date version of the instance with the given
   796  // name.
   797  func (env *azureEnviron) getInstance(hostedService *gwacl.HostedService, roleName string) (instance.Instance, error) {
   798  	if n := len(hostedService.Deployments); n != 1 {
   799  		return nil, fmt.Errorf("expected one deployment for %q, got %d", hostedService.ServiceName, n)
   800  	}
   801  	deployment := &hostedService.Deployments[0]
   802  
   803  	var maskStateServerPorts bool
   804  	var instanceId instance.Id
   805  	switch deployment.Name {
   806  	case deploymentNameV1(hostedService.ServiceName):
   807  		// Old style instance.
   808  		instanceId = instance.Id(hostedService.ServiceName)
   809  		if n := len(deployment.RoleList); n != 1 {
   810  			return nil, fmt.Errorf("expected one role for %q, got %d", deployment.Name, n)
   811  		}
   812  		roleName = deployment.RoleList[0].RoleName
   813  		// In the old implementation of the Azure provider,
   814  		// all machines opened the state and API server ports.
   815  		maskStateServerPorts = true
   816  
   817  	case deploymentNameV2(hostedService.ServiceName):
   818  		instanceId = instance.Id(fmt.Sprintf("%s-%s", hostedService.ServiceName, roleName))
   819  		// Newly created state server machines are put into
   820  		// the cloud service with the stateServerLabel label.
   821  		if decoded, err := base64.StdEncoding.DecodeString(hostedService.Label); err == nil {
   822  			maskStateServerPorts = string(decoded) == stateServerLabel
   823  		}
   824  	}
   825  
   826  	var roleInstance *gwacl.RoleInstance
   827  	for _, role := range deployment.RoleInstanceList {
   828  		if role.RoleName == roleName {
   829  			roleInstance = &role
   830  			break
   831  		}
   832  	}
   833  
   834  	instance := &azureInstance{
   835  		environ:              env,
   836  		hostedService:        &hostedService.HostedServiceDescriptor,
   837  		instanceId:           instanceId,
   838  		deploymentName:       deployment.Name,
   839  		roleName:             roleName,
   840  		roleInstance:         roleInstance,
   841  		maskStateServerPorts: maskStateServerPorts,
   842  	}
   843  	return instance, nil
   844  }
   845  
   846  // newOSDisk creates a gwacl.OSVirtualHardDisk object suitable for an
   847  // Azure Virtual Machine.
   848  func (env *azureEnviron) newOSDisk(sourceImageName string, ser string) (*gwacl.OSVirtualHardDisk, error) {
   849  	vhdName := gwacl.MakeRandomDiskName("juju")
   850  	vhdPath := fmt.Sprintf("vhds/%s", vhdName)
   851  	snap := env.getSnapshot()
   852  	storageAccount := snap.ecfg.storageAccountName()
   853  	mediaLink := gwacl.CreateVirtualHardDiskMediaLink(storageAccount, vhdPath)
   854  	os, err := series.GetOSFromSeries(ser)
   855  	if err != nil {
   856  		return nil, errors.Trace(err)
   857  	}
   858  	var OSType string
   859  	switch os {
   860  	case jujuos.Windows:
   861  		OSType = "Windows"
   862  	default:
   863  		OSType = "Linux"
   864  	}
   865  	// The disk label is optional and the disk name can be omitted if
   866  	// mediaLink is provided.
   867  	return gwacl.NewOSVirtualHardDisk("", "", "", mediaLink, sourceImageName, OSType), nil
   868  }
   869  
   870  // getInitialEndpoints returns a slice of the endpoints every instance should have open
   871  // (ssh port, etc).
   872  func (env *azureEnviron) getInitialEndpoints(stateServer bool) []gwacl.InputEndpoint {
   873  	cfg := env.Config()
   874  	endpoints := []gwacl.InputEndpoint{{
   875  		LocalPort: 22,
   876  		Name:      "sshport",
   877  		Port:      22,
   878  		Protocol:  "tcp",
   879  	}}
   880  	if stateServer {
   881  		endpoints = append(endpoints, []gwacl.InputEndpoint{{
   882  			LocalPort: cfg.APIPort(),
   883  			Port:      cfg.APIPort(),
   884  			Protocol:  "tcp",
   885  			Name:      "apiport",
   886  		}}...)
   887  	}
   888  	for i, endpoint := range endpoints {
   889  		endpoint.LoadBalancedEndpointSetName = endpoint.Name
   890  		endpoint.LoadBalancerProbe = &gwacl.LoadBalancerProbe{
   891  			Port:     endpoint.Port,
   892  			Protocol: "TCP",
   893  		}
   894  		endpoints[i] = endpoint
   895  	}
   896  	return endpoints
   897  }
   898  
   899  // newRole creates a gwacl.Role object (an Azure Virtual Machine) which uses
   900  // the given Virtual Hard Drive.
   901  //
   902  // roleSize is the name of one of Azure's machine types, e.g. ExtraSmall,
   903  // Large, A6 etc.
   904  func (env *azureEnviron) newRole(roleSize string, vhd *gwacl.OSVirtualHardDisk, stateServer bool, userdata, ser string, snapshot *azureEnviron) (*gwacl.Role, error) {
   905  	// Do some common initialization
   906  	roleName := gwacl.MakeRandomRoleName("juju")
   907  	hostname := roleName
   908  	password := gwacl.MakeRandomPassword()
   909  
   910  	os, err := series.GetOSFromSeries(ser)
   911  	if err != nil {
   912  		return nil, errors.Trace(err)
   913  	}
   914  	// Generate a Network Configuration with the initially required ports open.
   915  	networkConfigurationSet := gwacl.NewNetworkConfigurationSet(env.getInitialEndpoints(stateServer), nil)
   916  
   917  	var role *gwacl.Role
   918  	switch os {
   919  	case jujuos.Windows:
   920  		role, err = makeWindowsRole(password, roleSize, roleName, userdata, vhd, networkConfigurationSet, snapshot)
   921  	default:
   922  		role, err = makeLinuxRole(hostname, password, roleSize, roleName, userdata, vhd, networkConfigurationSet)
   923  	}
   924  	if err != nil {
   925  		return nil, errors.Trace(err)
   926  	}
   927  	role.AvailabilitySetName = "juju"
   928  	return role, nil
   929  }
   930  
   931  // makeLinuxRole will create a gwacl.Role for a Linux VM.
   932  // The VM will have:
   933  // - an 'ubuntu' user defined with an unguessable (randomly generated) password
   934  // - its ssh port (TCP 22) open
   935  // (if a state server)
   936  // - its state port (TCP mongoDB) port open
   937  // - its API port (TCP) open
   938  // On Linux the userdata is sent as a base64 encoded string in the CustomData xml field of the role.
   939  func makeLinuxRole(
   940  	hostname, password, roleSize, roleName, userdata string,
   941  	vhd *gwacl.OSVirtualHardDisk, networkConfigSet *gwacl.ConfigurationSet) (*gwacl.Role, error) {
   942  	// Create a Linux Configuration with the username and the password
   943  	// empty and disable SSH with password authentication.
   944  	username := "ubuntu"
   945  	cfgSet := gwacl.NewLinuxProvisioningConfigurationSet(hostname, username, password, userdata, "true")
   946  	finalCfgSet := []gwacl.ConfigurationSet{*cfgSet, *networkConfigSet}
   947  	return gwacl.NewLinuxRole(roleSize, roleName, vhd, finalCfgSet), nil
   948  
   949  }
   950  
   951  // makeWindowsRole will create a gwacl.Role for a Windows VM.
   952  // The VM will have:
   953  // - an 'JujuAdministrator' user defined with an unguessable (randomly generated) password
   954  // TODO(bogdanteleaga): Open the winrm port and provide winrm access
   955  // - for now only port 22 is open(by default)
   956  // On Windows the userdata is uploaded to the storage and then a reference to it is sent
   957  // using CustomScriptExtension. For more details see makeUserdataResourceExtension
   958  func makeWindowsRole(
   959  	password, roleSize, roleName, userdata string, vhd *gwacl.OSVirtualHardDisk,
   960  	networkConfigSet *gwacl.ConfigurationSet, snapshot *azureEnviron) (*gwacl.Role, error) {
   961  	// Later we will add WinRM configuration here
   962  	// Windows does not accept hostnames over 15 characters.
   963  	roleName = roleName[:15]
   964  	hostname := roleName
   965  	username := "JujuAdministrator"
   966  	cfgSet := gwacl.NewWindowsProvisioningConfigurationSet(hostname, password, "true", "", nil, nil, username, "", userdata)
   967  	finalCfgSet := []gwacl.ConfigurationSet{*cfgSet, *networkConfigSet}
   968  	resourceExtension, err := makeUserdataResourceExtension(hostname, userdata, snapshot)
   969  	if err != nil {
   970  		return nil, errors.Trace(err)
   971  	}
   972  	return gwacl.NewWindowsRole(roleSize, roleName, vhd, finalCfgSet, &[]gwacl.ResourceExtensionReference{*resourceExtension}, "true"), nil
   973  
   974  }
   975  
   976  // makeUserdataResourceExtension will upload the userdata to storage and then fill in the proper xml
   977  // following the example here
   978  // https://msdn.microsoft.com/en-us/library/azure/dn781373.aspx
   979  func makeUserdataResourceExtension(nonce string, userData string, snapshot *azureEnviron) (*gwacl.ResourceExtensionReference, error) {
   980  	// The bootstrap userdata script is the same for all machines.
   981  	// So we first check if it's already uploaded and if it isn't we upload it
   982  	_, err := snapshot.storage.Get(bootstrapUserdataScriptFilename)
   983  	if errors.IsNotFound(err) {
   984  		err := snapshot.storage.Put(bootstrapUserdataScriptFilename, bytes.NewReader([]byte(bootstrapUserdataScript)), int64(len(bootstrapUserdataScript)))
   985  		if err != nil {
   986  			logger.Errorf(err.Error())
   987  			return nil, errors.Annotate(err, "cannot upload userdata to storage")
   988  		}
   989  	}
   990  
   991  	uri, err := snapshot.storage.URL(bootstrapUserdataScriptFilename)
   992  	if err != nil {
   993  		logger.Errorf(err.Error())
   994  		return nil, errors.Trace(err)
   995  	}
   996  
   997  	scriptPublicConfig, err := makeUserdataResourceScripts(uri, bootstrapUserdataScriptFilename)
   998  	if err != nil {
   999  		return nil, errors.Trace(err)
  1000  	}
  1001  	publicParam := gwacl.NewResourceExtensionParameter("CustomScriptExtensionPublicConfigParameter", scriptPublicConfig, gwacl.ResourceExtensionParameterTypePublic)
  1002  	return gwacl.NewResourceExtensionReference("MyCustomScriptExtension", "Microsoft.Compute", "CustomScriptExtension", "1.4", "", []gwacl.ResourceExtensionParameter{*publicParam}), nil
  1003  }
  1004  
  1005  func makeUserdataResourceScripts(uri, filename string) (publicParam string, err error) {
  1006  	type publicConfig struct {
  1007  		FileUris         []string `json:"fileUris"`
  1008  		CommandToExecute string   `json:"commandToExecute"`
  1009  	}
  1010  
  1011  	public := publicConfig{
  1012  		FileUris:         []string{uri},
  1013  		CommandToExecute: fmt.Sprintf("powershell -ExecutionPolicy Unrestricted -file %s", filename),
  1014  	}
  1015  
  1016  	publicConf, err := json.Marshal(public)
  1017  	if err != nil {
  1018  		return "", errors.Trace(err)
  1019  	}
  1020  
  1021  	scriptPublicConfig := base64.StdEncoding.EncodeToString(publicConf)
  1022  
  1023  	return scriptPublicConfig, nil
  1024  }
  1025  
  1026  // StopInstances is specified in the InstanceBroker interface.
  1027  func (env *azureEnviron) StopInstances(ids ...instance.Id) error {
  1028  	snap := env.getSnapshot()
  1029  
  1030  	// Map services to role names we want to delete.
  1031  	serviceInstances := make(map[string]map[string]bool)
  1032  	var serviceNames []string
  1033  	for _, id := range ids {
  1034  		serviceName, roleName := env.splitInstanceId(id)
  1035  		if roleName == "" {
  1036  			serviceInstances[serviceName] = nil
  1037  			serviceNames = append(serviceNames, serviceName)
  1038  		} else {
  1039  			deleteRoleNames, ok := serviceInstances[serviceName]
  1040  			if !ok {
  1041  				deleteRoleNames = make(map[string]bool)
  1042  				serviceInstances[serviceName] = deleteRoleNames
  1043  				serviceNames = append(serviceNames, serviceName)
  1044  			}
  1045  			deleteRoleNames[roleName] = true
  1046  		}
  1047  	}
  1048  
  1049  	// Load the properties of each service, so we know whether to
  1050  	// delete the entire service.
  1051  	//
  1052  	// Note: concurrent operations on Affinity Groups have been
  1053  	// found to cause conflict responses, so we do everything serially.
  1054  	for _, serviceName := range serviceNames {
  1055  		deleteRoleNames := serviceInstances[serviceName]
  1056  		service, err := snap.api.GetHostedServiceProperties(serviceName, true)
  1057  		if err != nil {
  1058  			return err
  1059  		} else if len(service.Deployments) != 1 {
  1060  			continue
  1061  		}
  1062  		// Filter the instances that have no corresponding role.
  1063  		roleNames := make(set.Strings)
  1064  		for _, role := range service.Deployments[0].RoleList {
  1065  			roleNames.Add(role.RoleName)
  1066  		}
  1067  		for roleName := range deleteRoleNames {
  1068  			if !roleNames.Contains(roleName) {
  1069  				delete(deleteRoleNames, roleName)
  1070  			}
  1071  		}
  1072  		// If we're deleting all the roles, we need to delete the
  1073  		// entire cloud service or we'll get an error. deleteRoleNames
  1074  		// is nil if we're dealing with a legacy deployment.
  1075  		if deleteRoleNames == nil || len(deleteRoleNames) == roleNames.Size() {
  1076  			if err := snap.api.DeleteHostedService(serviceName); err != nil {
  1077  				return err
  1078  			}
  1079  		} else {
  1080  			for roleName := range deleteRoleNames {
  1081  				if err := snap.api.DeleteRole(&gwacl.DeleteRoleRequest{
  1082  					ServiceName:    serviceName,
  1083  					DeploymentName: service.Deployments[0].Name,
  1084  					RoleName:       roleName,
  1085  					DeleteMedia:    true,
  1086  				}); err != nil {
  1087  					return err
  1088  				}
  1089  			}
  1090  		}
  1091  	}
  1092  	return nil
  1093  }
  1094  
  1095  // hostedServices returns all services for this environment.
  1096  func (env *azureEnviron) hostedServices() ([]gwacl.HostedServiceDescriptor, error) {
  1097  	snap := env.getSnapshot()
  1098  	services, err := snap.api.ListHostedServices()
  1099  	if err != nil {
  1100  		return nil, err
  1101  	}
  1102  
  1103  	var filteredServices []gwacl.HostedServiceDescriptor
  1104  	// Service names are prefixed with the environment name, followed by "-".
  1105  	// We must be careful not to include services where the environment name
  1106  	// is a substring of another name. ie we mustn't allow "azure" to match "azure-1".
  1107  	envPrefix := env.getEnvPrefix()
  1108  	// Just in case.
  1109  	filterPrefix := regexp.QuoteMeta(envPrefix)
  1110  
  1111  	// Now filter the services.
  1112  	prefixMatch := regexp.MustCompile("^" + filterPrefix + "[^-]*$")
  1113  	for _, service := range services {
  1114  		if prefixMatch.Match([]byte(service.ServiceName)) {
  1115  			filteredServices = append(filteredServices, service)
  1116  		}
  1117  	}
  1118  	return filteredServices, nil
  1119  }
  1120  
  1121  // destroyAllServices destroys all Cloud Services and deployments contained.
  1122  // This is needed to clean up broken environments, in which there are cloud
  1123  // services with no deployments.
  1124  func (env *azureEnviron) destroyAllServices() error {
  1125  	services, err := env.hostedServices()
  1126  	if err != nil {
  1127  		return err
  1128  	}
  1129  	snap := env.getSnapshot()
  1130  	for _, service := range services {
  1131  		if err := snap.api.DeleteHostedService(service.ServiceName); err != nil {
  1132  			return err
  1133  		}
  1134  	}
  1135  	return nil
  1136  }
  1137  
  1138  // splitInstanceId splits the specified instance.Id into its
  1139  // cloud-service and role parts. Both values will be empty
  1140  // if the instance-id is non-matching, and role will be empty
  1141  // for legacy instance-ids.
  1142  func (env *azureEnviron) splitInstanceId(id instance.Id) (service, role string) {
  1143  	prefix := env.getEnvPrefix()
  1144  	if !strings.HasPrefix(string(id), prefix) {
  1145  		return "", ""
  1146  	}
  1147  	fields := strings.Split(string(id)[len(prefix):], "-")
  1148  	service = prefix + fields[0]
  1149  	if len(fields) > 1 {
  1150  		role = fields[1]
  1151  	}
  1152  	return service, role
  1153  }
  1154  
  1155  // Instances is specified in the Environ interface.
  1156  func (env *azureEnviron) Instances(ids []instance.Id) ([]instance.Instance, error) {
  1157  	snap := env.getSnapshot()
  1158  
  1159  	type instanceId struct {
  1160  		serviceName, roleName string
  1161  	}
  1162  
  1163  	instancesIds := make([]instanceId, len(ids))
  1164  	serviceNames := make(set.Strings)
  1165  	for i, id := range ids {
  1166  		serviceName, roleName := env.splitInstanceId(id)
  1167  		if serviceName == "" {
  1168  			continue
  1169  		}
  1170  		instancesIds[i] = instanceId{
  1171  			serviceName: serviceName,
  1172  			roleName:    roleName,
  1173  		}
  1174  		serviceNames.Add(serviceName)
  1175  	}
  1176  
  1177  	// Map service names to gwacl.HostedServices.
  1178  	services, err := snap.api.ListSpecificHostedServices(&gwacl.ListSpecificHostedServicesRequest{
  1179  		ServiceNames: serviceNames.Values(),
  1180  	})
  1181  	if err != nil {
  1182  		return nil, err
  1183  	}
  1184  	if len(services) == 0 {
  1185  		return nil, environs.ErrNoInstances
  1186  	}
  1187  	hostedServices := make(map[string]*gwacl.HostedService)
  1188  	for _, s := range services {
  1189  		hostedService, err := snap.api.GetHostedServiceProperties(s.ServiceName, true)
  1190  		if err != nil {
  1191  			return nil, err
  1192  		}
  1193  		hostedServices[s.ServiceName] = hostedService
  1194  	}
  1195  
  1196  	var validInstances int
  1197  	instances := make([]instance.Instance, len(ids))
  1198  	for i, id := range instancesIds {
  1199  		if id.serviceName == "" {
  1200  			// Previously determined to be an invalid instance ID.
  1201  			continue
  1202  		}
  1203  		hostedService := hostedServices[id.serviceName]
  1204  		instance, err := snap.getInstance(hostedService, id.roleName)
  1205  		if err == nil {
  1206  			instances[i] = instance
  1207  			validInstances++
  1208  		} else {
  1209  			logger.Debugf("failed to get instance for role %q in service %q: %v", id.roleName, hostedService.ServiceName, err)
  1210  		}
  1211  	}
  1212  
  1213  	switch validInstances {
  1214  	case len(instances):
  1215  		err = nil
  1216  	case 0:
  1217  		instances = nil
  1218  		err = environs.ErrNoInstances
  1219  	default:
  1220  		err = environs.ErrPartialInstances
  1221  	}
  1222  	return instances, err
  1223  }
  1224  
  1225  // AllInstances is specified in the InstanceBroker interface.
  1226  func (env *azureEnviron) AllInstances() ([]instance.Instance, error) {
  1227  	// The instance list is built using the list of all the Azure
  1228  	// Services (instance==service).
  1229  	// Acquire management API object.
  1230  	snap := env.getSnapshot()
  1231  
  1232  	serviceDescriptors, err := env.hostedServices()
  1233  	if err != nil {
  1234  		return nil, err
  1235  	}
  1236  
  1237  	var instances []instance.Instance
  1238  	for _, sd := range serviceDescriptors {
  1239  		hostedService, err := snap.api.GetHostedServiceProperties(sd.ServiceName, true)
  1240  		if err != nil {
  1241  			return nil, err
  1242  		} else if len(hostedService.Deployments) != 1 {
  1243  			continue
  1244  		}
  1245  		deployment := &hostedService.Deployments[0]
  1246  		for _, role := range deployment.RoleList {
  1247  			instance, err := snap.getInstance(hostedService, role.RoleName)
  1248  			if err != nil {
  1249  				return nil, err
  1250  			}
  1251  			instances = append(instances, instance)
  1252  		}
  1253  	}
  1254  	return instances, nil
  1255  }
  1256  
  1257  // getEnvPrefix returns the prefix used to name the objects specific to this
  1258  // environment. The environment prefix name is immutable, so there is no need
  1259  // to use a configuration snapshot.
  1260  func (env *azureEnviron) getEnvPrefix() string {
  1261  	return fmt.Sprintf("juju-%s-", env.Config().Name())
  1262  }
  1263  
  1264  // Storage is specified in the Environ interface.
  1265  func (env *azureEnviron) Storage() storage.Storage {
  1266  	return env.getSnapshot().storage
  1267  }
  1268  
  1269  // Destroy is specified in the Environ interface.
  1270  func (env *azureEnviron) Destroy() error {
  1271  	logger.Debugf("destroying environment %q", env.Config().Name())
  1272  
  1273  	// Stop all instances.
  1274  	if err := env.destroyAllServices(); err != nil {
  1275  		return fmt.Errorf("cannot destroy instances: %v", err)
  1276  	}
  1277  
  1278  	// Delete vnet and affinity group. Deleting the virtual network
  1279  	// may fail for inexplicable reasons (cannot delete in the Azure
  1280  	// console either for some amount of time after deleting dependent
  1281  	// VMs), so we only treat this as a warning. There is no cost
  1282  	// associated with a vnet or affinity group.
  1283  	if err := env.deleteVirtualNetwork(); err != nil {
  1284  		logger.Warningf("cannot delete the environment's virtual network: %v", err)
  1285  	}
  1286  	if err := env.deleteAffinityGroup(); err != nil {
  1287  		logger.Warningf("cannot delete the environment's affinity group: %v", err)
  1288  	}
  1289  
  1290  	// Delete storage.
  1291  	// Deleting the storage is done last so that if something fails
  1292  	// half way through the Destroy() method, the storage won't be cleaned
  1293  	// up and thus an attempt to re-boostrap the environment will lead to
  1294  	// a "error: environment is already bootstrapped" error.
  1295  	if err := env.Storage().RemoveAll(); err != nil {
  1296  		return fmt.Errorf("cannot clean up storage: %v", err)
  1297  	}
  1298  	return nil
  1299  }
  1300  
  1301  // OpenPorts is specified in the Environ interface. However, Azure does not
  1302  // support the global firewall mode.
  1303  func (env *azureEnviron) OpenPorts(ports []network.PortRange) error {
  1304  	return nil
  1305  }
  1306  
  1307  // ClosePorts is specified in the Environ interface. However, Azure does not
  1308  // support the global firewall mode.
  1309  func (env *azureEnviron) ClosePorts(ports []network.PortRange) error {
  1310  	return nil
  1311  }
  1312  
  1313  // Ports is specified in the Environ interface.
  1314  func (env *azureEnviron) Ports() ([]network.PortRange, error) {
  1315  	// TODO: implement this.
  1316  	return []network.PortRange{}, nil
  1317  }
  1318  
  1319  // Provider is specified in the Environ interface.
  1320  func (env *azureEnviron) Provider() environs.EnvironProvider {
  1321  	return azureEnvironProvider{}
  1322  }
  1323  
  1324  var (
  1325  	retryPolicy = gwacl.RetryPolicy{
  1326  		NbRetries: 6,
  1327  		HttpStatusCodes: []int{
  1328  			http.StatusConflict,
  1329  			http.StatusRequestTimeout,
  1330  			http.StatusInternalServerError,
  1331  			http.StatusServiceUnavailable,
  1332  		},
  1333  		Delay: 10 * time.Second}
  1334  )
  1335  
  1336  // updateStorageAccountKey queries the storage account key, and updates the
  1337  // version cached in env.storageAccountKey.
  1338  //
  1339  // It takes a snapshot in order to preserve transactional integrity relative
  1340  // to the snapshot's starting state, without having to lock the environment
  1341  // for the duration.  If there is a conflicting change to env relative to the
  1342  // state recorded in the snapshot, this function will fail.
  1343  func (env *azureEnviron) updateStorageAccountKey(snapshot *azureEnviron) (string, error) {
  1344  	// This method follows an RCU pattern, an optimistic technique to
  1345  	// implement atomic read-update transactions: get a consistent snapshot
  1346  	// of state; process data; enter critical section; check for conflicts;
  1347  	// write back changes.  The advantage is that there are no long-held
  1348  	// locks, in particular while waiting for the request to Azure to
  1349  	// complete.
  1350  	// "Get a consistent snapshot of state" is the caller's responsibility.
  1351  	// The caller can use env.getSnapshot().
  1352  
  1353  	// Process data: get a current account key from Azure.
  1354  	key, err := env.queryStorageAccountKey()
  1355  	if err != nil {
  1356  		return "", err
  1357  	}
  1358  
  1359  	// Enter critical section.
  1360  	env.Lock()
  1361  	defer env.Unlock()
  1362  
  1363  	// Check for conflicts: is the config still what it was?
  1364  	if env.ecfg != snapshot.ecfg {
  1365  		// The environment has been reconfigured while we were
  1366  		// working on this, so the key we just get may not be
  1367  		// appropriate any longer.  So fail.
  1368  		// Whatever we were doing isn't likely to be right any more
  1369  		// anyway.  Otherwise, it might be worth returning the key
  1370  		// just in case it still works, and proceed without updating
  1371  		// env.storageAccountKey.
  1372  		return "", fmt.Errorf("environment was reconfigured")
  1373  	}
  1374  
  1375  	// Write back changes.
  1376  	env.storageAccountKey = key
  1377  	return key, nil
  1378  }
  1379  
  1380  // getStorageContext obtains a context object for interfacing with Azure's
  1381  // storage API.
  1382  // For now, each invocation just returns a separate object.  This is probably
  1383  // wasteful (each context gets its own SSL connection) and may need optimizing
  1384  // later.
  1385  func (env *azureEnviron) getStorageContext() (*gwacl.StorageContext, error) {
  1386  	snap := env.getSnapshot()
  1387  	key := snap.storageAccountKey
  1388  	if key == "" {
  1389  		// We don't know the storage-account key yet.  Request it.
  1390  		var err error
  1391  		key, err = env.updateStorageAccountKey(snap)
  1392  		if err != nil {
  1393  			return nil, err
  1394  		}
  1395  	}
  1396  	context := gwacl.StorageContext{
  1397  		Account:       snap.ecfg.storageAccountName(),
  1398  		Key:           key,
  1399  		AzureEndpoint: gwacl.GetEndpoint(snap.ecfg.location()),
  1400  		RetryPolicy:   retryPolicy,
  1401  	}
  1402  	return &context, nil
  1403  }
  1404  
  1405  // TODO(ericsnow) lp-1398055
  1406  // Implement the ZonedEnviron interface.
  1407  
  1408  // Region is specified in the HasRegion interface.
  1409  func (env *azureEnviron) Region() (simplestreams.CloudSpec, error) {
  1410  	ecfg := env.getSnapshot().ecfg
  1411  	return simplestreams.CloudSpec{
  1412  		Region:   ecfg.location(),
  1413  		Endpoint: string(gwacl.GetEndpoint(ecfg.location())),
  1414  	}, nil
  1415  }
  1416  
  1417  // SupportsUnitPlacement is specified in the state.EnvironCapability interface.
  1418  func (env *azureEnviron) SupportsUnitPlacement() error {
  1419  	if env.getSnapshot().ecfg.availabilitySetsEnabled() {
  1420  		return fmt.Errorf("unit placement is not supported with availability-sets-enabled")
  1421  	}
  1422  	return nil
  1423  }