github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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  	"encoding/base64"
     8  	"fmt"
     9  	"net/http"
    10  	"regexp"
    11  	"strings"
    12  	"sync"
    13  	"time"
    14  
    15  	"github.com/juju/errors"
    16  	"github.com/juju/utils"
    17  	"github.com/juju/utils/set"
    18  	"launchpad.net/gwacl"
    19  
    20  	"github.com/juju/juju/constraints"
    21  	"github.com/juju/juju/environs"
    22  	"github.com/juju/juju/environs/config"
    23  	"github.com/juju/juju/environs/imagemetadata"
    24  	"github.com/juju/juju/environs/instances"
    25  	"github.com/juju/juju/environs/network"
    26  	"github.com/juju/juju/environs/simplestreams"
    27  	"github.com/juju/juju/environs/storage"
    28  	envtools "github.com/juju/juju/environs/tools"
    29  	"github.com/juju/juju/instance"
    30  	"github.com/juju/juju/provider/common"
    31  	"github.com/juju/juju/state"
    32  	"github.com/juju/juju/state/api"
    33  	"github.com/juju/juju/state/api/params"
    34  )
    35  
    36  const (
    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  	// stateServerLabel is the label applied to the cloud service created
    54  	// for state servers.
    55  	stateServerLabel = "juju-state-server"
    56  )
    57  
    58  // vars for testing purposes.
    59  var (
    60  	createInstance = (*azureEnviron).createInstance
    61  )
    62  
    63  type azureEnviron struct {
    64  	// Except where indicated otherwise, all fields in this object should
    65  	// only be accessed using a lock or a snapshot.
    66  	sync.Mutex
    67  
    68  	// name is immutable; it does not need locking.
    69  	name string
    70  
    71  	// archMutex gates access to supportedArchitectures
    72  	archMutex sync.Mutex
    73  	// supportedArchitectures caches the architectures
    74  	// for which images can be instantiated.
    75  	supportedArchitectures []string
    76  
    77  	// ecfg is the environment's Azure-specific configuration.
    78  	ecfg *azureEnvironConfig
    79  
    80  	// storage is this environ's own private storage.
    81  	storage storage.Storage
    82  
    83  	// storageAccountKey holds an access key to this environment's
    84  	// private storage.  This is automatically queried from Azure on
    85  	// startup.
    86  	storageAccountKey string
    87  }
    88  
    89  // azureEnviron implements Environ and HasRegion.
    90  var _ environs.Environ = (*azureEnviron)(nil)
    91  var _ simplestreams.HasRegion = (*azureEnviron)(nil)
    92  var _ imagemetadata.SupportsCustomSources = (*azureEnviron)(nil)
    93  var _ envtools.SupportsCustomSources = (*azureEnviron)(nil)
    94  var _ state.Prechecker = (*azureEnviron)(nil)
    95  
    96  // NewEnviron creates a new azureEnviron.
    97  func NewEnviron(cfg *config.Config) (*azureEnviron, error) {
    98  	env := azureEnviron{name: cfg.Name()}
    99  	err := env.SetConfig(cfg)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  
   104  	// Set up storage.
   105  	env.storage = &azureStorage{
   106  		storageContext: &environStorageContext{environ: &env},
   107  	}
   108  	return &env, nil
   109  }
   110  
   111  // extractStorageKey returns the primary account key from a gwacl
   112  // StorageAccountKeys struct, or if there is none, the secondary one.
   113  func extractStorageKey(keys *gwacl.StorageAccountKeys) string {
   114  	if keys.Primary != "" {
   115  		return keys.Primary
   116  	}
   117  	return keys.Secondary
   118  }
   119  
   120  // queryStorageAccountKey retrieves the storage account's key from Azure.
   121  func (env *azureEnviron) queryStorageAccountKey() (string, error) {
   122  	azure, err := env.getManagementAPI()
   123  	if err != nil {
   124  		return "", err
   125  	}
   126  	defer env.releaseManagementAPI(azure)
   127  
   128  	accountName := env.getSnapshot().ecfg.storageAccountName()
   129  	keys, err := azure.GetStorageAccountKeys(accountName)
   130  	if err != nil {
   131  		return "", fmt.Errorf("cannot obtain storage account keys: %v", err)
   132  	}
   133  
   134  	key := extractStorageKey(keys)
   135  	if key == "" {
   136  		return "", fmt.Errorf("no keys available for storage account")
   137  	}
   138  
   139  	return key, nil
   140  }
   141  
   142  // Name is specified in the Environ interface.
   143  func (env *azureEnviron) Name() string {
   144  	return env.name
   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.
   168  func (env *azureEnviron) getAffinityGroupName() string {
   169  	return env.getEnvPrefix() + "ag"
   170  }
   171  
   172  func (env *azureEnviron) createAffinityGroup() error {
   173  	affinityGroupName := env.getAffinityGroupName()
   174  	azure, err := env.getManagementAPI()
   175  	if err != nil {
   176  		return err
   177  	}
   178  	defer env.releaseManagementAPI(azure)
   179  	snap := env.getSnapshot()
   180  	location := snap.ecfg.location()
   181  	cag := gwacl.NewCreateAffinityGroup(affinityGroupName, affinityGroupName, affinityGroupName, location)
   182  	return azure.CreateAffinityGroup(&gwacl.CreateAffinityGroupRequest{
   183  		CreateAffinityGroup: cag})
   184  }
   185  
   186  func (env *azureEnviron) deleteAffinityGroup() error {
   187  	affinityGroupName := env.getAffinityGroupName()
   188  	azure, err := env.getManagementAPI()
   189  	if err != nil {
   190  		return err
   191  	}
   192  	defer env.releaseManagementAPI(azure)
   193  	return azure.DeleteAffinityGroup(&gwacl.DeleteAffinityGroupRequest{
   194  		Name: affinityGroupName})
   195  }
   196  
   197  // getVirtualNetworkName returns the name of the virtual network used by all
   198  // the VMs in this environment.
   199  func (env *azureEnviron) getVirtualNetworkName() string {
   200  	return env.getEnvPrefix() + "vnet"
   201  }
   202  
   203  func (env *azureEnviron) createVirtualNetwork() error {
   204  	vnetName := env.getVirtualNetworkName()
   205  	affinityGroupName := env.getAffinityGroupName()
   206  	azure, err := env.getManagementAPI()
   207  	if err != nil {
   208  		return err
   209  	}
   210  	defer env.releaseManagementAPI(azure)
   211  	virtualNetwork := gwacl.VirtualNetworkSite{
   212  		Name:          vnetName,
   213  		AffinityGroup: affinityGroupName,
   214  		AddressSpacePrefixes: []string{
   215  			networkDefinition,
   216  		},
   217  	}
   218  	return azure.AddVirtualNetworkSite(&virtualNetwork)
   219  }
   220  
   221  // deleteVnetAttempt is an AttemptyStrategy for use
   222  // when attempting delete a virtual network. This is
   223  // necessary as Azure apparently does not release all
   224  // references to the vnet even when all cloud services
   225  // are deleted.
   226  var deleteVnetAttempt = utils.AttemptStrategy{
   227  	Total: 30 * time.Second,
   228  	Delay: 1 * time.Second,
   229  }
   230  
   231  var networkInUse = regexp.MustCompile(".*The virtual network .* is currently in use.*")
   232  
   233  func (env *azureEnviron) deleteVirtualNetwork() error {
   234  	azure, err := env.getManagementAPI()
   235  	if err != nil {
   236  		return err
   237  	}
   238  	defer env.releaseManagementAPI(azure)
   239  	for a := deleteVnetAttempt.Start(); a.Next(); {
   240  		vnetName := env.getVirtualNetworkName()
   241  		err = azure.RemoveVirtualNetworkSite(vnetName)
   242  		if err == nil {
   243  			return nil
   244  		}
   245  		if err, ok := err.(*gwacl.AzureError); ok {
   246  			if err.StatusCode() == 400 && networkInUse.MatchString(err.Message) {
   247  				// Retry on "virtual network XYZ is currently in use".
   248  				continue
   249  			}
   250  		}
   251  		// Any other error should be returned.
   252  		break
   253  	}
   254  	return err
   255  }
   256  
   257  // getContainerName returns the name of the private storage account container
   258  // that this environment is using.
   259  func (env *azureEnviron) getContainerName() string {
   260  	return env.getEnvPrefix() + "private"
   261  }
   262  
   263  // Bootstrap is specified in the Environ interface.
   264  func (env *azureEnviron) Bootstrap(ctx environs.BootstrapContext, args environs.BootstrapParams) (err error) {
   265  	// The creation of the affinity group and the virtual network is specific to the Azure provider.
   266  	err = env.createAffinityGroup()
   267  	if err != nil {
   268  		return err
   269  	}
   270  	// If we fail after this point, clean up the affinity group.
   271  	defer func() {
   272  		if err != nil {
   273  			env.deleteAffinityGroup()
   274  		}
   275  	}()
   276  	err = env.createVirtualNetwork()
   277  	if err != nil {
   278  		return err
   279  	}
   280  	// If we fail after this point, clean up the virtual network.
   281  	defer func() {
   282  		if err != nil {
   283  			env.deleteVirtualNetwork()
   284  		}
   285  	}()
   286  	err = common.Bootstrap(ctx, env, args)
   287  	return err
   288  }
   289  
   290  // StateInfo is specified in the Environ interface.
   291  func (env *azureEnviron) StateInfo() (*state.Info, *api.Info, error) {
   292  	return common.StateInfo(env)
   293  }
   294  
   295  // Config is specified in the Environ interface.
   296  func (env *azureEnviron) Config() *config.Config {
   297  	snap := env.getSnapshot()
   298  	return snap.ecfg.Config
   299  }
   300  
   301  // SetConfig is specified in the Environ interface.
   302  func (env *azureEnviron) SetConfig(cfg *config.Config) error {
   303  	ecfg, err := azureEnvironProvider{}.newConfig(cfg)
   304  	if err != nil {
   305  		return err
   306  	}
   307  
   308  	env.Lock()
   309  	defer env.Unlock()
   310  
   311  	if env.ecfg != nil {
   312  		_, err = azureEnvironProvider{}.Validate(cfg, env.ecfg.Config)
   313  		if err != nil {
   314  			return err
   315  		}
   316  	}
   317  
   318  	env.ecfg = ecfg
   319  
   320  	// Reset storage account key.  Even if we had one before, it may not
   321  	// be appropriate for the new config.
   322  	env.storageAccountKey = ""
   323  
   324  	return nil
   325  }
   326  
   327  // attemptCreateService tries to create a new hosted service on Azure, with a
   328  // name it chooses (based on the given prefix), but recognizes that the name
   329  // may not be available.  If the name is not available, it does not treat that
   330  // as an error but just returns nil.
   331  func attemptCreateService(azure *gwacl.ManagementAPI, prefix, affinityGroupName, label string) (*gwacl.CreateHostedService, error) {
   332  	var err error
   333  	name := gwacl.MakeRandomHostedServiceName(prefix)
   334  	err = azure.CheckHostedServiceNameAvailability(name)
   335  	if err != nil {
   336  		// The calling function should retry.
   337  		return nil, nil
   338  	}
   339  	if label == "" {
   340  		label = name
   341  	}
   342  	req := gwacl.NewCreateHostedServiceWithLocation(name, label, "")
   343  	req.AffinityGroup = affinityGroupName
   344  	err = azure.AddHostedService(req)
   345  	if err != nil {
   346  		return nil, err
   347  	}
   348  	return req, nil
   349  }
   350  
   351  // newHostedService creates a hosted service.  It will make up a unique name,
   352  // starting with the given prefix.
   353  func newHostedService(azure *gwacl.ManagementAPI, prefix, affinityGroupName, label string) (*gwacl.HostedService, error) {
   354  	var err error
   355  	var createdService *gwacl.CreateHostedService
   356  	for tries := 10; tries > 0 && err == nil && createdService == nil; tries-- {
   357  		createdService, err = attemptCreateService(azure, prefix, affinityGroupName, label)
   358  	}
   359  	if err != nil {
   360  		return nil, fmt.Errorf("could not create hosted service: %v", err)
   361  	}
   362  	if createdService == nil {
   363  		return nil, fmt.Errorf("could not come up with a unique hosted service name - is your randomizer initialized?")
   364  	}
   365  	return azure.GetHostedServiceProperties(createdService.ServiceName, true)
   366  }
   367  
   368  // SupportedArchitectures is specified on the EnvironCapability interface.
   369  func (env *azureEnviron) SupportedArchitectures() ([]string, error) {
   370  	env.archMutex.Lock()
   371  	defer env.archMutex.Unlock()
   372  	if env.supportedArchitectures != nil {
   373  		return env.supportedArchitectures, nil
   374  	}
   375  	// Create a filter to get all images from our region and for the correct stream.
   376  	ecfg := env.getSnapshot().ecfg
   377  	region := ecfg.location()
   378  	cloudSpec := simplestreams.CloudSpec{
   379  		Region:   region,
   380  		Endpoint: getEndpoint(region),
   381  	}
   382  	imageConstraint := imagemetadata.NewImageConstraint(simplestreams.LookupParams{
   383  		CloudSpec: cloudSpec,
   384  		Stream:    ecfg.ImageStream(),
   385  	})
   386  	var err error
   387  	env.supportedArchitectures, err = common.SupportedArchitectures(env, imageConstraint)
   388  	return env.supportedArchitectures, err
   389  }
   390  
   391  // SupportNetworks is specified on the EnvironCapability interface.
   392  func (env *azureEnviron) SupportNetworks() bool {
   393  	return false
   394  }
   395  
   396  // selectInstanceTypeAndImage returns the appropriate instance-type name and
   397  // the OS image name for launching a virtual machine with the given parameters.
   398  func (env *azureEnviron) selectInstanceTypeAndImage(constraint *instances.InstanceConstraint) (string, string, error) {
   399  	ecfg := env.getSnapshot().ecfg
   400  	sourceImageName := ecfg.forceImageName()
   401  	if sourceImageName != "" {
   402  		// Configuration forces us to use a specific image.  There may
   403  		// not be a suitable image in the simplestreams database.
   404  		// This means we can't use Juju's normal selection mechanism,
   405  		// because it combines instance-type and image selection: if
   406  		// there are no images we can use, it won't offer us an
   407  		// instance type either.
   408  		//
   409  		// Select the instance type using simple, Azure-specific code.
   410  		machineType, err := selectMachineType(gwacl.RoleSizes, defaultToBaselineSpec(constraint.Constraints))
   411  		if err != nil {
   412  			return "", "", err
   413  		}
   414  		return machineType.Name, sourceImageName, nil
   415  	}
   416  
   417  	// Choose the most suitable instance type and OS image, based on simplestreams information.
   418  	spec, err := findInstanceSpec(env, constraint)
   419  	if err != nil {
   420  		return "", "", err
   421  	}
   422  	return spec.InstanceType.Id, spec.Image.Id, nil
   423  }
   424  
   425  var unsupportedConstraints = []string{
   426  	constraints.CpuPower,
   427  	constraints.Tags,
   428  }
   429  
   430  // ConstraintsValidator is defined on the Environs interface.
   431  func (env *azureEnviron) ConstraintsValidator() (constraints.Validator, error) {
   432  	validator := constraints.NewValidator()
   433  	validator.RegisterUnsupported(unsupportedConstraints)
   434  	supportedArches, err := env.SupportedArchitectures()
   435  	if err != nil {
   436  		return nil, err
   437  	}
   438  	validator.RegisterVocabulary(constraints.Arch, supportedArches)
   439  	instTypeNames := make([]string, len(gwacl.RoleSizes))
   440  	for i, role := range gwacl.RoleSizes {
   441  		instTypeNames[i] = role.Name
   442  	}
   443  	validator.RegisterVocabulary(constraints.InstanceType, instTypeNames)
   444  	return validator, nil
   445  }
   446  
   447  // PrecheckInstance is defined on the state.Prechecker interface.
   448  func (env *azureEnviron) PrecheckInstance(series string, cons constraints.Value, placement string) error {
   449  	if placement != "" {
   450  		return fmt.Errorf("unknown placement directive: %s", placement)
   451  	}
   452  	if !cons.HasInstanceType() {
   453  		return nil
   454  	}
   455  	// Constraint has an instance-type constraint so let's see if it is valid.
   456  	instanceTypes, err := listInstanceTypes(env, gwacl.RoleSizes)
   457  	if err != nil {
   458  		return err
   459  	}
   460  	for _, instanceType := range instanceTypes {
   461  		if instanceType.Name == *cons.InstanceType {
   462  			return nil
   463  		}
   464  	}
   465  	return fmt.Errorf("invalid Azure instance %q specified", *cons.InstanceType)
   466  }
   467  
   468  // createInstance creates all of the Azure entities necessary for a
   469  // new instance. This includes Cloud Service, Deployment and Role.
   470  //
   471  // If serviceName is non-empty, then createInstance will assign to
   472  // the Cloud Service with that name. Otherwise, a new Cloud Service
   473  // will be created.
   474  func (env *azureEnviron) createInstance(azure *gwacl.ManagementAPI, role *gwacl.Role, serviceName string, stateServer bool) (resultInst instance.Instance, resultErr error) {
   475  	var inst instance.Instance
   476  	defer func() {
   477  		if inst != nil && resultErr != nil {
   478  			if err := env.StopInstances(inst.Id()); err != nil {
   479  				// Failure upon failure. Log it, but return the original error.
   480  				logger.Errorf("error releasing failed instance: %v", err)
   481  			}
   482  		}
   483  	}()
   484  	var err error
   485  	var service *gwacl.HostedService
   486  	if serviceName != "" {
   487  		logger.Debugf("creating instance in existing cloud service %q", serviceName)
   488  		service, err = azure.GetHostedServiceProperties(serviceName, true)
   489  	} else {
   490  		logger.Debugf("creating instance in new cloud service")
   491  		// If we're creating a cloud service for state servers,
   492  		// we will want to open additional ports. We need to
   493  		// record this against the cloud service, so we use a
   494  		// special label for the purpose.
   495  		var label string
   496  		if stateServer {
   497  			label = stateServerLabel
   498  		}
   499  		service, err = newHostedService(azure, env.getEnvPrefix(), env.getAffinityGroupName(), label)
   500  	}
   501  	if err != nil {
   502  		return nil, err
   503  	}
   504  	if len(service.Deployments) == 0 {
   505  		// This is a newly created cloud service, so we
   506  		// should destroy it if anything below fails.
   507  		defer func() {
   508  			if resultErr != nil {
   509  				azure.DeleteHostedService(service.ServiceName)
   510  				// Destroying the hosted service destroys the instance,
   511  				// so ensure StopInstances isn't called.
   512  				inst = nil
   513  			}
   514  		}()
   515  		// Create an initial deployment.
   516  		deployment := gwacl.NewDeploymentForCreateVMDeployment(
   517  			deploymentNameV2(service.ServiceName),
   518  			deploymentSlot,
   519  			deploymentNameV2(service.ServiceName),
   520  			[]gwacl.Role{*role},
   521  			env.getVirtualNetworkName(),
   522  		)
   523  		if err := azure.AddDeployment(deployment, service.ServiceName); err != nil {
   524  			return nil, err
   525  		}
   526  		service.Deployments = append(service.Deployments, *deployment)
   527  	} else {
   528  		// Update the deployment.
   529  		deployment := &service.Deployments[0]
   530  		if err := azure.AddRole(&gwacl.AddRoleRequest{
   531  			ServiceName:      service.ServiceName,
   532  			DeploymentName:   deployment.Name,
   533  			PersistentVMRole: (*gwacl.PersistentVMRole)(role),
   534  		}); err != nil {
   535  			return nil, err
   536  		}
   537  		deployment.RoleList = append(deployment.RoleList, *role)
   538  	}
   539  	return env.getInstance(service, role.RoleName)
   540  }
   541  
   542  // deploymentNameV1 returns the deployment name used
   543  // in the original implementation of the Azure provider.
   544  func deploymentNameV1(serviceName string) string {
   545  	return serviceName
   546  }
   547  
   548  // deploymentNameV2 returns the deployment name used
   549  // in the current implementation of the Azure provider.
   550  func deploymentNameV2(serviceName string) string {
   551  	return serviceName + "-v2"
   552  }
   553  
   554  // StartInstance is specified in the InstanceBroker interface.
   555  func (env *azureEnviron) StartInstance(args environs.StartInstanceParams) (_ instance.Instance, _ *instance.HardwareCharacteristics, _ []network.Info, err error) {
   556  	if args.MachineConfig.HasNetworks() {
   557  		return nil, nil, nil, fmt.Errorf("starting instances with networks is not supported yet.")
   558  	}
   559  
   560  	err = environs.FinishMachineConfig(args.MachineConfig, env.Config(), args.Constraints)
   561  	if err != nil {
   562  		return nil, nil, nil, err
   563  	}
   564  
   565  	// Pick envtools.  Needed for the custom data (which is what we normally
   566  	// call userdata).
   567  	args.MachineConfig.Tools = args.Tools[0]
   568  	logger.Infof("picked tools %q", args.MachineConfig.Tools)
   569  
   570  	// Compose userdata.
   571  	userData, err := makeCustomData(args.MachineConfig)
   572  	if err != nil {
   573  		return nil, nil, nil, fmt.Errorf("custom data: %v", err)
   574  	}
   575  
   576  	azure, err := env.getManagementAPI()
   577  	if err != nil {
   578  		return nil, nil, nil, err
   579  	}
   580  	defer env.releaseManagementAPI(azure)
   581  
   582  	snapshot := env.getSnapshot()
   583  	location := snapshot.ecfg.location()
   584  	instanceType, sourceImageName, err := env.selectInstanceTypeAndImage(&instances.InstanceConstraint{
   585  		Region:      location,
   586  		Series:      args.Tools.OneSeries(),
   587  		Arches:      args.Tools.Arches(),
   588  		Constraints: args.Constraints,
   589  	})
   590  	if err != nil {
   591  		return nil, nil, nil, err
   592  	}
   593  
   594  	// We use the cloud service label as a way to group instances with
   595  	// the same affinity, so that machines can be be allocated to the
   596  	// same availability set.
   597  	var cloudServiceName string
   598  	if args.DistributionGroup != nil && snapshot.ecfg.availabilitySetsEnabled() {
   599  		instanceIds, err := args.DistributionGroup()
   600  		if err != nil {
   601  			return nil, nil, nil, err
   602  		}
   603  		for _, id := range instanceIds {
   604  			cloudServiceName, _ = env.splitInstanceId(id)
   605  			if cloudServiceName != "" {
   606  				break
   607  			}
   608  		}
   609  	}
   610  
   611  	vhd := env.newOSDisk(sourceImageName)
   612  	// If we're creating machine-0, we'll want to expose port 22.
   613  	// All other machines get an auto-generated public port for SSH.
   614  	stateServer := false
   615  	for _, job := range args.MachineConfig.Jobs {
   616  		if job == params.JobManageEnviron {
   617  			stateServer = true
   618  			break
   619  		}
   620  	}
   621  	role := env.newRole(instanceType, vhd, userData, stateServer)
   622  	inst, err := createInstance(env, azure.ManagementAPI, role, cloudServiceName, stateServer)
   623  	if err != nil {
   624  		return nil, nil, nil, err
   625  	}
   626  	// TODO(bug 1193998) - return instance hardware characteristics as well
   627  	return inst, &instance.HardwareCharacteristics{}, nil, nil
   628  }
   629  
   630  // getInstance returns an up-to-date version of the instance with the given
   631  // name.
   632  func (env *azureEnviron) getInstance(hostedService *gwacl.HostedService, roleName string) (instance.Instance, error) {
   633  	if n := len(hostedService.Deployments); n != 1 {
   634  		return nil, fmt.Errorf("expected one deployment for %q, got %d", hostedService.ServiceName, n)
   635  	}
   636  	deployment := &hostedService.Deployments[0]
   637  
   638  	var maskStateServerPorts bool
   639  	var instanceId instance.Id
   640  	switch deployment.Name {
   641  	case deploymentNameV1(hostedService.ServiceName):
   642  		// Old style instance.
   643  		instanceId = instance.Id(hostedService.ServiceName)
   644  		if n := len(deployment.RoleList); n != 1 {
   645  			return nil, fmt.Errorf("expected one role for %q, got %d", deployment.Name, n)
   646  		}
   647  		roleName = deployment.RoleList[0].RoleName
   648  		// In the old implementation of the Azure provider,
   649  		// all machines opened the state and API server ports.
   650  		maskStateServerPorts = true
   651  
   652  	case deploymentNameV2(hostedService.ServiceName):
   653  		instanceId = instance.Id(fmt.Sprintf("%s-%s", hostedService.ServiceName, roleName))
   654  		// Newly created state server machines are put into
   655  		// the cloud service with the stateServerLabel label.
   656  		if decoded, err := base64.StdEncoding.DecodeString(hostedService.Label); err == nil {
   657  			maskStateServerPorts = string(decoded) == stateServerLabel
   658  		}
   659  	}
   660  
   661  	var roleInstance *gwacl.RoleInstance
   662  	for _, role := range deployment.RoleInstanceList {
   663  		if role.RoleName == roleName {
   664  			roleInstance = &role
   665  			break
   666  		}
   667  	}
   668  
   669  	instance := &azureInstance{
   670  		environ:              env,
   671  		hostedService:        &hostedService.HostedServiceDescriptor,
   672  		instanceId:           instanceId,
   673  		deploymentName:       deployment.Name,
   674  		roleName:             roleName,
   675  		roleInstance:         roleInstance,
   676  		maskStateServerPorts: maskStateServerPorts,
   677  	}
   678  	return instance, nil
   679  }
   680  
   681  // newOSDisk creates a gwacl.OSVirtualHardDisk object suitable for an
   682  // Azure Virtual Machine.
   683  func (env *azureEnviron) newOSDisk(sourceImageName string) *gwacl.OSVirtualHardDisk {
   684  	vhdName := gwacl.MakeRandomDiskName("juju")
   685  	vhdPath := fmt.Sprintf("vhds/%s", vhdName)
   686  	snap := env.getSnapshot()
   687  	storageAccount := snap.ecfg.storageAccountName()
   688  	mediaLink := gwacl.CreateVirtualHardDiskMediaLink(storageAccount, vhdPath)
   689  	// The disk label is optional and the disk name can be omitted if
   690  	// mediaLink is provided.
   691  	return gwacl.NewOSVirtualHardDisk("", "", "", mediaLink, sourceImageName, "Linux")
   692  }
   693  
   694  // getInitialEndpoints returns a slice of the endpoints every instance should have open
   695  // (ssh port, etc).
   696  func (env *azureEnviron) getInitialEndpoints(stateServer bool) []gwacl.InputEndpoint {
   697  	// TODO(axw) either proxy ssh traffic through one of the
   698  	// randomly chosen VMs to the internal address, or otherwise
   699  	// don't load balance SSH and provide a way of getting the
   700  	// local port.
   701  	cfg := env.Config()
   702  	endpoints := []gwacl.InputEndpoint{{
   703  		LocalPort: 22,
   704  		Name:      "sshport",
   705  		Port:      22,
   706  		Protocol:  "tcp",
   707  	}}
   708  	if stateServer {
   709  		endpoints = append(endpoints, []gwacl.InputEndpoint{{
   710  			LocalPort: cfg.StatePort(),
   711  			Port:      cfg.StatePort(),
   712  			Protocol:  "tcp",
   713  			Name:      "stateport",
   714  		}, {
   715  			LocalPort: cfg.APIPort(),
   716  			Port:      cfg.APIPort(),
   717  			Protocol:  "tcp",
   718  			Name:      "apiport",
   719  		}}...)
   720  	}
   721  	for i, endpoint := range endpoints {
   722  		endpoint.LoadBalancedEndpointSetName = endpoint.Name
   723  		endpoint.LoadBalancerProbe = &gwacl.LoadBalancerProbe{
   724  			Port:     endpoint.Port,
   725  			Protocol: "TCP",
   726  		}
   727  		endpoints[i] = endpoint
   728  	}
   729  	return endpoints
   730  }
   731  
   732  // newRole creates a gwacl.Role object (an Azure Virtual Machine) which uses
   733  // the given Virtual Hard Drive.
   734  //
   735  // The VM will have:
   736  // - an 'ubuntu' user defined with an unguessable (randomly generated) password
   737  // - its ssh port (TCP 22) open
   738  // (if a state server)
   739  // - its state port (TCP mongoDB) port open
   740  // - its API port (TCP) open
   741  //
   742  // roleSize is the name of one of Azure's machine types, e.g. ExtraSmall,
   743  // Large, A6 etc.
   744  func (env *azureEnviron) newRole(roleSize string, vhd *gwacl.OSVirtualHardDisk, userData string, stateServer bool) *gwacl.Role {
   745  	roleName := gwacl.MakeRandomRoleName("juju")
   746  	// Create a Linux Configuration with the username and the password
   747  	// empty and disable SSH with password authentication.
   748  	hostname := roleName
   749  	username := "ubuntu"
   750  	password := gwacl.MakeRandomPassword()
   751  	linuxConfigurationSet := gwacl.NewLinuxProvisioningConfigurationSet(hostname, username, password, userData, "true")
   752  	// Generate a Network Configuration with the initially required ports open.
   753  	networkConfigurationSet := gwacl.NewNetworkConfigurationSet(env.getInitialEndpoints(stateServer), nil)
   754  	role := gwacl.NewRole(
   755  		roleSize, roleName, vhd,
   756  		[]gwacl.ConfigurationSet{*linuxConfigurationSet, *networkConfigurationSet},
   757  	)
   758  	role.AvailabilitySetName = "juju"
   759  	return role
   760  }
   761  
   762  // StopInstances is specified in the InstanceBroker interface.
   763  func (env *azureEnviron) StopInstances(ids ...instance.Id) error {
   764  	context, err := env.getManagementAPI()
   765  	if err != nil {
   766  		return err
   767  	}
   768  	defer env.releaseManagementAPI(context)
   769  
   770  	// Map services to role names we want to delete.
   771  	serviceInstances := make(map[string]map[string]bool)
   772  	var serviceNames []string
   773  	for _, id := range ids {
   774  		serviceName, roleName := env.splitInstanceId(id)
   775  		if roleName == "" {
   776  			serviceInstances[serviceName] = nil
   777  			serviceNames = append(serviceNames, serviceName)
   778  		} else {
   779  			deleteRoleNames, ok := serviceInstances[serviceName]
   780  			if !ok {
   781  				deleteRoleNames = make(map[string]bool)
   782  				serviceInstances[serviceName] = deleteRoleNames
   783  				serviceNames = append(serviceNames, serviceName)
   784  			}
   785  			deleteRoleNames[roleName] = true
   786  		}
   787  	}
   788  
   789  	// Load the properties of each service, so we know whether to
   790  	// delete the entire service.
   791  	//
   792  	// Note: concurrent operations on Affinity Groups have been
   793  	// found to cause conflict responses, so we do everything serially.
   794  	for _, serviceName := range serviceNames {
   795  		deleteRoleNames := serviceInstances[serviceName]
   796  		service, err := context.GetHostedServiceProperties(serviceName, true)
   797  		if err != nil {
   798  			return err
   799  		} else if len(service.Deployments) != 1 {
   800  			continue
   801  		}
   802  		// Filter the instances that have no corresponding role.
   803  		var roleNames set.Strings
   804  		for _, role := range service.Deployments[0].RoleList {
   805  			roleNames.Add(role.RoleName)
   806  		}
   807  		for roleName := range deleteRoleNames {
   808  			if !roleNames.Contains(roleName) {
   809  				delete(deleteRoleNames, roleName)
   810  			}
   811  		}
   812  		// If we're deleting all the roles, we need to delete the
   813  		// entire cloud service or we'll get an error. deleteRoleNames
   814  		// is nil if we're dealing with a legacy deployment.
   815  		if deleteRoleNames == nil || len(deleteRoleNames) == roleNames.Size() {
   816  			if err := context.DeleteHostedService(serviceName); err != nil {
   817  				return err
   818  			}
   819  		} else {
   820  			for roleName := range deleteRoleNames {
   821  				if err := context.DeleteRole(&gwacl.DeleteRoleRequest{
   822  					ServiceName:    serviceName,
   823  					DeploymentName: service.Deployments[0].Name,
   824  					RoleName:       roleName,
   825  					DeleteMedia:    true,
   826  				}); err != nil {
   827  					return err
   828  				}
   829  			}
   830  		}
   831  	}
   832  	return nil
   833  }
   834  
   835  // destroyAllServices destroys all Cloud Services and deployments contained.
   836  // This is needed to clean up broken environments, in which there are cloud
   837  // services with no deployments.
   838  func (env *azureEnviron) destroyAllServices() error {
   839  	context, err := env.getManagementAPI()
   840  	if err != nil {
   841  		return err
   842  	}
   843  	defer env.releaseManagementAPI(context)
   844  
   845  	request := &gwacl.ListPrefixedHostedServicesRequest{ServiceNamePrefix: env.getEnvPrefix()}
   846  	services, err := context.ListPrefixedHostedServices(request)
   847  	if err != nil {
   848  		return err
   849  	}
   850  	for _, service := range services {
   851  		if err := context.DeleteHostedService(service.ServiceName); err != nil {
   852  			return err
   853  		}
   854  	}
   855  	return nil
   856  }
   857  
   858  // splitInstanceId splits the specified instance.Id into its
   859  // cloud-service and role parts. Both values will be empty
   860  // if the instance-id is non-matching, and role will be empty
   861  // for legacy instance-ids.
   862  func (env *azureEnviron) splitInstanceId(id instance.Id) (service, role string) {
   863  	prefix := env.getEnvPrefix()
   864  	if !strings.HasPrefix(string(id), prefix) {
   865  		return "", ""
   866  	}
   867  	fields := strings.Split(string(id)[len(prefix):], "-")
   868  	service = prefix + fields[0]
   869  	if len(fields) > 1 {
   870  		role = fields[1]
   871  	}
   872  	return service, role
   873  }
   874  
   875  // Instances is specified in the Environ interface.
   876  func (env *azureEnviron) Instances(ids []instance.Id) ([]instance.Instance, error) {
   877  	context, err := env.getManagementAPI()
   878  	if err != nil {
   879  		return nil, err
   880  	}
   881  	defer env.releaseManagementAPI(context)
   882  
   883  	type instanceId struct {
   884  		serviceName, roleName string
   885  	}
   886  
   887  	instancesIds := make([]instanceId, len(ids))
   888  	var serviceNames set.Strings
   889  	for i, id := range ids {
   890  		serviceName, roleName := env.splitInstanceId(id)
   891  		if serviceName == "" {
   892  			continue
   893  		}
   894  		instancesIds[i] = instanceId{
   895  			serviceName: serviceName,
   896  			roleName:    roleName,
   897  		}
   898  		serviceNames.Add(serviceName)
   899  	}
   900  
   901  	// Map service names to gwacl.HostedServices.
   902  	services, err := context.ListSpecificHostedServices(&gwacl.ListSpecificHostedServicesRequest{
   903  		ServiceNames: serviceNames.Values(),
   904  	})
   905  	if err != nil {
   906  		return nil, err
   907  	}
   908  	if len(services) == 0 {
   909  		return nil, environs.ErrNoInstances
   910  	}
   911  	hostedServices := make(map[string]*gwacl.HostedService)
   912  	for _, s := range services {
   913  		hostedService, err := context.GetHostedServiceProperties(s.ServiceName, true)
   914  		if err != nil {
   915  			return nil, err
   916  		}
   917  		hostedServices[s.ServiceName] = hostedService
   918  	}
   919  
   920  	err = nil
   921  	instances := make([]instance.Instance, len(ids))
   922  	for i, id := range instancesIds {
   923  		if id.serviceName == "" {
   924  			// Previously determined to be an invalid instance ID.
   925  			continue
   926  		}
   927  		hostedService := hostedServices[id.serviceName]
   928  		instance, err := env.getInstance(hostedService, id.roleName)
   929  		if err == nil {
   930  			instances[i] = instance
   931  		} else {
   932  			logger.Debugf("failed to get instance for role %q in service %q: %v", id.roleName, hostedService.ServiceName, err)
   933  		}
   934  	}
   935  	for _, instance := range instances {
   936  		if instance == nil {
   937  			err = environs.ErrPartialInstances
   938  		}
   939  	}
   940  	return instances, err
   941  }
   942  
   943  // AllocateAddress requests a new address to be allocated for the
   944  // given instance on the given network. This is not implemented on the
   945  // Azure provider yet.
   946  func (*azureEnviron) AllocateAddress(_ instance.Id, _ network.Id) (instance.Address, error) {
   947  	return instance.Address{}, errors.NotImplementedf("AllocateAddress")
   948  }
   949  
   950  // AllInstances is specified in the InstanceBroker interface.
   951  func (env *azureEnviron) AllInstances() ([]instance.Instance, error) {
   952  	// The instance list is built using the list of all the Azure
   953  	// Services (instance==service).
   954  	// Acquire management API object.
   955  	context, err := env.getManagementAPI()
   956  	if err != nil {
   957  		return nil, err
   958  	}
   959  	defer env.releaseManagementAPI(context)
   960  
   961  	request := &gwacl.ListPrefixedHostedServicesRequest{ServiceNamePrefix: env.getEnvPrefix()}
   962  	serviceDescriptors, err := context.ListPrefixedHostedServices(request)
   963  	if err != nil {
   964  		return nil, err
   965  	}
   966  
   967  	var instances []instance.Instance
   968  	for _, sd := range serviceDescriptors {
   969  		hostedService, err := context.GetHostedServiceProperties(sd.ServiceName, true)
   970  		if err != nil {
   971  			return nil, err
   972  		} else if len(hostedService.Deployments) != 1 {
   973  			continue
   974  		}
   975  		deployment := &hostedService.Deployments[0]
   976  		for _, role := range deployment.RoleList {
   977  			instance, err := env.getInstance(hostedService, role.RoleName)
   978  			if err != nil {
   979  				return nil, err
   980  			}
   981  			instances = append(instances, instance)
   982  		}
   983  	}
   984  	return instances, nil
   985  }
   986  
   987  // getEnvPrefix returns the prefix used to name the objects specific to this
   988  // environment.
   989  func (env *azureEnviron) getEnvPrefix() string {
   990  	return fmt.Sprintf("juju-%s-", env.Name())
   991  }
   992  
   993  // Storage is specified in the Environ interface.
   994  func (env *azureEnviron) Storage() storage.Storage {
   995  	return env.getSnapshot().storage
   996  }
   997  
   998  // Destroy is specified in the Environ interface.
   999  func (env *azureEnviron) Destroy() error {
  1000  	logger.Debugf("destroying environment %q", env.name)
  1001  
  1002  	// Stop all instances.
  1003  	if err := env.destroyAllServices(); err != nil {
  1004  		return fmt.Errorf("cannot destroy instances: %v", err)
  1005  	}
  1006  
  1007  	// Delete vnet and affinity group.
  1008  	if err := env.deleteVirtualNetwork(); err != nil {
  1009  		return fmt.Errorf("cannot delete the environment's virtual network: %v", err)
  1010  	}
  1011  	if err := env.deleteAffinityGroup(); err != nil {
  1012  		return fmt.Errorf("cannot delete the environment's affinity group: %v", err)
  1013  	}
  1014  
  1015  	// Delete storage.
  1016  	// Deleting the storage is done last so that if something fails
  1017  	// half way through the Destroy() method, the storage won't be cleaned
  1018  	// up and thus an attempt to re-boostrap the environment will lead to
  1019  	// a "error: environment is already bootstrapped" error.
  1020  	if err := env.Storage().RemoveAll(); err != nil {
  1021  		return fmt.Errorf("cannot clean up storage: %v", err)
  1022  	}
  1023  	return nil
  1024  }
  1025  
  1026  // OpenPorts is specified in the Environ interface. However, Azure does not
  1027  // support the global firewall mode.
  1028  func (env *azureEnviron) OpenPorts(ports []instance.Port) error {
  1029  	return nil
  1030  }
  1031  
  1032  // ClosePorts is specified in the Environ interface. However, Azure does not
  1033  // support the global firewall mode.
  1034  func (env *azureEnviron) ClosePorts(ports []instance.Port) error {
  1035  	return nil
  1036  }
  1037  
  1038  // Ports is specified in the Environ interface.
  1039  func (env *azureEnviron) Ports() ([]instance.Port, error) {
  1040  	// TODO: implement this.
  1041  	return []instance.Port{}, nil
  1042  }
  1043  
  1044  // Provider is specified in the Environ interface.
  1045  func (env *azureEnviron) Provider() environs.EnvironProvider {
  1046  	return azureEnvironProvider{}
  1047  }
  1048  
  1049  // azureManagementContext wraps two things: a gwacl.ManagementAPI (effectively
  1050  // a session on the Azure management API) and a tempCertFile, which keeps track
  1051  // of the temporary certificate file that needs to be deleted once we're done
  1052  // with this particular session.
  1053  // Since it embeds *gwacl.ManagementAPI, you can use it much as if it were a
  1054  // pointer to a ManagementAPI object.  Just don't forget to release it after
  1055  // use.
  1056  type azureManagementContext struct {
  1057  	*gwacl.ManagementAPI
  1058  	certFile *tempCertFile
  1059  }
  1060  
  1061  var (
  1062  	retryPolicy = gwacl.RetryPolicy{
  1063  		NbRetries: 6,
  1064  		HttpStatusCodes: []int{
  1065  			http.StatusConflict,
  1066  			http.StatusRequestTimeout,
  1067  			http.StatusInternalServerError,
  1068  			http.StatusServiceUnavailable,
  1069  		},
  1070  		Delay: 10 * time.Second}
  1071  )
  1072  
  1073  // getManagementAPI obtains a context object for interfacing with Azure's
  1074  // management API.
  1075  // For now, each invocation just returns a separate object.  This is probably
  1076  // wasteful (each context gets its own SSL connection) and may need optimizing
  1077  // later.
  1078  func (env *azureEnviron) getManagementAPI() (*azureManagementContext, error) {
  1079  	snap := env.getSnapshot()
  1080  	subscription := snap.ecfg.managementSubscriptionId()
  1081  	certData := snap.ecfg.managementCertificate()
  1082  	certFile, err := newTempCertFile([]byte(certData))
  1083  	if err != nil {
  1084  		return nil, err
  1085  	}
  1086  	// After this point, if we need to leave prematurely, we should clean
  1087  	// up that certificate file.
  1088  	location := snap.ecfg.location()
  1089  	mgtAPI, err := gwacl.NewManagementAPIWithRetryPolicy(subscription, certFile.Path(), location, retryPolicy)
  1090  	if err != nil {
  1091  		certFile.Delete()
  1092  		return nil, err
  1093  	}
  1094  	context := azureManagementContext{
  1095  		ManagementAPI: mgtAPI,
  1096  		certFile:      certFile,
  1097  	}
  1098  	return &context, nil
  1099  }
  1100  
  1101  // releaseManagementAPI frees up a context object obtained through
  1102  // getManagementAPI.
  1103  func (env *azureEnviron) releaseManagementAPI(context *azureManagementContext) {
  1104  	// Be tolerant to incomplete context objects, in case we ever get
  1105  	// called during cleanup of a failed attempt to create one.
  1106  	if context == nil || context.certFile == nil {
  1107  		return
  1108  	}
  1109  	// For now, all that needs doing is to delete the temporary certificate
  1110  	// file.  We may do cleverer things later, such as connection pooling
  1111  	// where this method returns a context to the pool.
  1112  	context.certFile.Delete()
  1113  }
  1114  
  1115  // updateStorageAccountKey queries the storage account key, and updates the
  1116  // version cached in env.storageAccountKey.
  1117  //
  1118  // It takes a snapshot in order to preserve transactional integrity relative
  1119  // to the snapshot's starting state, without having to lock the environment
  1120  // for the duration.  If there is a conflicting change to env relative to the
  1121  // state recorded in the snapshot, this function will fail.
  1122  func (env *azureEnviron) updateStorageAccountKey(snapshot *azureEnviron) (string, error) {
  1123  	// This method follows an RCU pattern, an optimistic technique to
  1124  	// implement atomic read-update transactions: get a consistent snapshot
  1125  	// of state; process data; enter critical section; check for conflicts;
  1126  	// write back changes.  The advantage is that there are no long-held
  1127  	// locks, in particular while waiting for the request to Azure to
  1128  	// complete.
  1129  	// "Get a consistent snapshot of state" is the caller's responsibility.
  1130  	// The caller can use env.getSnapshot().
  1131  
  1132  	// Process data: get a current account key from Azure.
  1133  	key, err := env.queryStorageAccountKey()
  1134  	if err != nil {
  1135  		return "", err
  1136  	}
  1137  
  1138  	// Enter critical section.
  1139  	env.Lock()
  1140  	defer env.Unlock()
  1141  
  1142  	// Check for conflicts: is the config still what it was?
  1143  	if env.ecfg != snapshot.ecfg {
  1144  		// The environment has been reconfigured while we were
  1145  		// working on this, so the key we just get may not be
  1146  		// appropriate any longer.  So fail.
  1147  		// Whatever we were doing isn't likely to be right any more
  1148  		// anyway.  Otherwise, it might be worth returning the key
  1149  		// just in case it still works, and proceed without updating
  1150  		// env.storageAccountKey.
  1151  		return "", fmt.Errorf("environment was reconfigured")
  1152  	}
  1153  
  1154  	// Write back changes.
  1155  	env.storageAccountKey = key
  1156  	return key, nil
  1157  }
  1158  
  1159  // getStorageContext obtains a context object for interfacing with Azure's
  1160  // storage API.
  1161  // For now, each invocation just returns a separate object.  This is probably
  1162  // wasteful (each context gets its own SSL connection) and may need optimizing
  1163  // later.
  1164  func (env *azureEnviron) getStorageContext() (*gwacl.StorageContext, error) {
  1165  	snap := env.getSnapshot()
  1166  	key := snap.storageAccountKey
  1167  	if key == "" {
  1168  		// We don't know the storage-account key yet.  Request it.
  1169  		var err error
  1170  		key, err = env.updateStorageAccountKey(snap)
  1171  		if err != nil {
  1172  			return nil, err
  1173  		}
  1174  	}
  1175  	context := gwacl.StorageContext{
  1176  		Account:       snap.ecfg.storageAccountName(),
  1177  		Key:           key,
  1178  		AzureEndpoint: gwacl.GetEndpoint(snap.ecfg.location()),
  1179  		RetryPolicy:   retryPolicy,
  1180  	}
  1181  	return &context, nil
  1182  }
  1183  
  1184  // baseURLs specifies an Azure specific location where we look for simplestreams information.
  1185  // It contains the central databases for the released and daily streams, but this may
  1186  // become more configurable.  This variable is here as a placeholder, but also
  1187  // as an injection point for tests.
  1188  var baseURLs = []string{}
  1189  
  1190  // GetImageSources returns a list of sources which are used to search for simplestreams image metadata.
  1191  func (env *azureEnviron) GetImageSources() ([]simplestreams.DataSource, error) {
  1192  	sources := make([]simplestreams.DataSource, 1+len(baseURLs))
  1193  	sources[0] = storage.NewStorageSimpleStreamsDataSource("cloud storage", env.Storage(), storage.BaseImagesPath)
  1194  	for i, url := range baseURLs {
  1195  		sources[i+1] = simplestreams.NewURLDataSource("Azure base URL", url, utils.VerifySSLHostnames)
  1196  	}
  1197  	return sources, nil
  1198  }
  1199  
  1200  // GetToolsSources returns a list of sources which are used to search for simplestreams tools metadata.
  1201  func (env *azureEnviron) GetToolsSources() ([]simplestreams.DataSource, error) {
  1202  	// Add the simplestreams source off the control bucket.
  1203  	sources := []simplestreams.DataSource{
  1204  		storage.NewStorageSimpleStreamsDataSource("cloud storage", env.Storage(), storage.BaseToolsPath)}
  1205  	return sources, nil
  1206  }
  1207  
  1208  // getImageMetadataSigningRequired returns whether this environment requires
  1209  // image metadata from Simplestreams to be signed.
  1210  func (env *azureEnviron) getImageMetadataSigningRequired() bool {
  1211  	// Hard-coded to true for now.  Once we support custom base URLs,
  1212  	// this may have to change.
  1213  	return true
  1214  }
  1215  
  1216  // Region is specified in the HasRegion interface.
  1217  func (env *azureEnviron) Region() (simplestreams.CloudSpec, error) {
  1218  	ecfg := env.getSnapshot().ecfg
  1219  	return simplestreams.CloudSpec{
  1220  		Region:   ecfg.location(),
  1221  		Endpoint: string(gwacl.GetEndpoint(ecfg.location())),
  1222  	}, nil
  1223  }
  1224  
  1225  // SupportsUnitPlacement is specified in the state.EnvironCapability interface.
  1226  func (env *azureEnviron) SupportsUnitPlacement() error {
  1227  	if env.getSnapshot().ecfg.availabilitySetsEnabled() {
  1228  		return fmt.Errorf("unit placement is not supported with availability-sets-enabled")
  1229  	}
  1230  	return nil
  1231  }