github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/provider/azure/environ_test.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/xml"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"net/http"
    13  	"net/url"
    14  	"path"
    15  	"regexp"
    16  	"strings"
    17  	"sync"
    18  
    19  	"github.com/juju/testing"
    20  	jc "github.com/juju/testing/checkers"
    21  	gc "launchpad.net/gocheck"
    22  	"launchpad.net/gwacl"
    23  
    24  	"github.com/juju/juju/constraints"
    25  	"github.com/juju/juju/environs"
    26  	"github.com/juju/juju/environs/bootstrap"
    27  	"github.com/juju/juju/environs/config"
    28  	"github.com/juju/juju/environs/imagemetadata"
    29  	"github.com/juju/juju/environs/instances"
    30  	"github.com/juju/juju/environs/simplestreams"
    31  	"github.com/juju/juju/environs/storage"
    32  	envtesting "github.com/juju/juju/environs/testing"
    33  	"github.com/juju/juju/environs/tools"
    34  	"github.com/juju/juju/instance"
    35  	"github.com/juju/juju/mongo"
    36  	"github.com/juju/juju/state"
    37  	"github.com/juju/juju/state/api"
    38  	apiparams "github.com/juju/juju/state/api/params"
    39  	coretesting "github.com/juju/juju/testing"
    40  )
    41  
    42  type baseEnvironSuite struct {
    43  	providerSuite
    44  }
    45  
    46  type environSuite struct {
    47  	baseEnvironSuite
    48  }
    49  
    50  var _ = gc.Suite(&environSuite{})
    51  var _ = gc.Suite(&startInstanceSuite{})
    52  
    53  // makeEnviron creates a fake azureEnviron with arbitrary configuration.
    54  func makeEnviron(c *gc.C) *azureEnviron {
    55  	attrs := makeAzureConfigMap(c)
    56  	return makeEnvironWithConfig(c, attrs)
    57  }
    58  
    59  // makeEnvironWithConfig creates a fake azureEnviron with the specified configuration.
    60  func makeEnvironWithConfig(c *gc.C, attrs map[string]interface{}) *azureEnviron {
    61  	cfg, err := config.New(config.NoDefaults, attrs)
    62  	c.Assert(err, gc.IsNil)
    63  	env, err := NewEnviron(cfg)
    64  	c.Assert(err, gc.IsNil)
    65  	// Prevent the test from trying to query for a storage-account key.
    66  	env.storageAccountKey = "fake-storage-account-key"
    67  	return env
    68  }
    69  
    70  // setDummyStorage injects the local provider's fake storage implementation
    71  // into the given environment, so that tests can manipulate storage as if it
    72  // were real.
    73  func (s *baseEnvironSuite) setDummyStorage(c *gc.C, env *azureEnviron) {
    74  	closer, storage, _ := envtesting.CreateLocalTestStorage(c)
    75  	env.storage = storage
    76  	s.AddCleanup(func(c *gc.C) { closer.Close() })
    77  }
    78  
    79  func (*environSuite) TestGetEndpoint(c *gc.C) {
    80  	c.Check(
    81  		getEndpoint("West US"),
    82  		gc.Equals,
    83  		"https://management.core.windows.net/")
    84  	c.Check(
    85  		getEndpoint("China East"),
    86  		gc.Equals,
    87  		"https://management.core.chinacloudapi.cn/")
    88  }
    89  
    90  func (*environSuite) TestGetSnapshot(c *gc.C) {
    91  	original := azureEnviron{name: "this-env", ecfg: new(azureEnvironConfig)}
    92  	snapshot := original.getSnapshot()
    93  
    94  	// The snapshot is identical to the original.
    95  	c.Check(*snapshot, gc.DeepEquals, original)
    96  
    97  	// However, they are distinct objects.
    98  	c.Check(snapshot, gc.Not(gc.Equals), &original)
    99  
   100  	// It's a shallow copy; they still share pointers.
   101  	c.Check(snapshot.ecfg, gc.Equals, original.ecfg)
   102  
   103  	// Neither object is locked at the end of the copy.
   104  	c.Check(original.Mutex, gc.Equals, sync.Mutex{})
   105  	c.Check(snapshot.Mutex, gc.Equals, sync.Mutex{})
   106  }
   107  
   108  func (*environSuite) TestGetSnapshotLocksEnviron(c *gc.C) {
   109  	original := azureEnviron{}
   110  	coretesting.TestLockingFunction(&original.Mutex, func() { original.getSnapshot() })
   111  }
   112  
   113  func (*environSuite) TestName(c *gc.C) {
   114  	env := azureEnviron{name: "foo"}
   115  	c.Check(env.Name(), gc.Equals, env.name)
   116  }
   117  
   118  func (*environSuite) TestConfigReturnsConfig(c *gc.C) {
   119  	cfg := new(config.Config)
   120  	ecfg := azureEnvironConfig{Config: cfg}
   121  	env := azureEnviron{ecfg: &ecfg}
   122  	c.Check(env.Config(), gc.Equals, cfg)
   123  }
   124  
   125  func (*environSuite) TestConfigLocksEnviron(c *gc.C) {
   126  	env := azureEnviron{name: "env", ecfg: new(azureEnvironConfig)}
   127  	coretesting.TestLockingFunction(&env.Mutex, func() { env.Config() })
   128  }
   129  
   130  func (*environSuite) TestGetManagementAPI(c *gc.C) {
   131  	env := makeEnviron(c)
   132  	context, err := env.getManagementAPI()
   133  	c.Assert(err, gc.IsNil)
   134  	defer env.releaseManagementAPI(context)
   135  	c.Check(context, gc.NotNil)
   136  	c.Check(context.ManagementAPI, gc.NotNil)
   137  	c.Check(context.certFile, gc.NotNil)
   138  	c.Check(context.GetRetryPolicy(), gc.DeepEquals, retryPolicy)
   139  }
   140  
   141  func (*environSuite) TestReleaseManagementAPIAcceptsNil(c *gc.C) {
   142  	env := makeEnviron(c)
   143  	env.releaseManagementAPI(nil)
   144  	// The real test is that this does not panic.
   145  }
   146  
   147  func (*environSuite) TestReleaseManagementAPIAcceptsIncompleteContext(c *gc.C) {
   148  	env := makeEnviron(c)
   149  	context := azureManagementContext{
   150  		ManagementAPI: nil,
   151  		certFile:      nil,
   152  	}
   153  	env.releaseManagementAPI(&context)
   154  	// The real test is that this does not panic.
   155  }
   156  
   157  func getAzureServiceListResponse(c *gc.C, services ...gwacl.HostedServiceDescriptor) []gwacl.DispatcherResponse {
   158  	list := gwacl.HostedServiceDescriptorList{HostedServices: services}
   159  	listXML, err := list.Serialize()
   160  	c.Assert(err, gc.IsNil)
   161  	responses := []gwacl.DispatcherResponse{gwacl.NewDispatcherResponse(
   162  		[]byte(listXML),
   163  		http.StatusOK,
   164  		nil,
   165  	)}
   166  	return responses
   167  }
   168  
   169  // getAzureServiceResponse returns a gwacl.DispatcherResponse corresponding
   170  // to the API request used to get the properties of a Service.
   171  func getAzureServiceResponse(c *gc.C, service gwacl.HostedService) gwacl.DispatcherResponse {
   172  	serviceXML, err := service.Serialize()
   173  	c.Assert(err, gc.IsNil)
   174  	return gwacl.NewDispatcherResponse([]byte(serviceXML), http.StatusOK, nil)
   175  }
   176  
   177  func patchWithServiceListResponse(c *gc.C, services []gwacl.HostedServiceDescriptor) *[]*gwacl.X509Request {
   178  	responses := getAzureServiceListResponse(c, services...)
   179  	return gwacl.PatchManagementAPIResponses(responses)
   180  }
   181  
   182  func prepareInstancesResponses(c *gc.C, prefix string, services ...*gwacl.HostedService) []gwacl.DispatcherResponse {
   183  	descriptors := make([]gwacl.HostedServiceDescriptor, len(services))
   184  	for i, service := range services {
   185  		descriptors[i] = service.HostedServiceDescriptor
   186  	}
   187  	responses := getAzureServiceListResponse(c, descriptors...)
   188  	for _, service := range services {
   189  		if !strings.HasPrefix(service.ServiceName, prefix) {
   190  			continue
   191  		}
   192  		serviceXML, err := service.Serialize()
   193  		c.Assert(err, gc.IsNil)
   194  		serviceGetResponse := gwacl.NewDispatcherResponse([]byte(serviceXML), http.StatusOK, nil)
   195  		responses = append(responses, serviceGetResponse)
   196  	}
   197  	return responses
   198  }
   199  
   200  func patchInstancesResponses(c *gc.C, prefix string, services ...*gwacl.HostedService) *[]*gwacl.X509Request {
   201  	responses := prepareInstancesResponses(c, prefix, services...)
   202  	return gwacl.PatchManagementAPIResponses(responses)
   203  }
   204  
   205  func (s *environSuite) TestSupportedArchitectures(c *gc.C) {
   206  	env := s.setupEnvWithDummyMetadata(c)
   207  	a, err := env.SupportedArchitectures()
   208  	c.Assert(err, gc.IsNil)
   209  	c.Assert(a, gc.DeepEquals, []string{"amd64"})
   210  }
   211  
   212  func (s *environSuite) TestSupportNetworks(c *gc.C) {
   213  	env := s.setupEnvWithDummyMetadata(c)
   214  	c.Assert(env.SupportNetworks(), jc.IsFalse)
   215  }
   216  
   217  func (suite *environSuite) TestGetEnvPrefixContainsEnvName(c *gc.C) {
   218  	env := makeEnviron(c)
   219  	c.Check(strings.Contains(env.getEnvPrefix(), env.Name()), jc.IsTrue)
   220  }
   221  
   222  func (*environSuite) TestGetContainerName(c *gc.C) {
   223  	env := makeEnviron(c)
   224  	expected := env.getEnvPrefix() + "private"
   225  	c.Check(env.getContainerName(), gc.Equals, expected)
   226  }
   227  
   228  func (suite *environSuite) TestAllInstances(c *gc.C) {
   229  	env := makeEnviron(c)
   230  	prefix := env.getEnvPrefix()
   231  	service1 := makeLegacyDeployment(env, prefix+"service1")
   232  	service2 := makeDeployment(env, prefix+"service2")
   233  	service3 := makeDeployment(env, "not"+prefix+"service3")
   234  
   235  	requests := patchInstancesResponses(c, prefix, service1, service2, service3)
   236  	instances, err := env.AllInstances()
   237  	c.Assert(err, gc.IsNil)
   238  	c.Check(len(instances), gc.Equals, 3)
   239  	c.Check(instances[0].Id(), gc.Equals, instance.Id(prefix+"service1"))
   240  	service2Role1Name := service2.Deployments[0].RoleList[0].RoleName
   241  	service2Role2Name := service2.Deployments[0].RoleList[1].RoleName
   242  	c.Check(instances[1].Id(), gc.Equals, instance.Id(prefix+"service2-"+service2Role1Name))
   243  	c.Check(instances[2].Id(), gc.Equals, instance.Id(prefix+"service2-"+service2Role2Name))
   244  	c.Check(len(*requests), gc.Equals, 3)
   245  }
   246  
   247  func (suite *environSuite) TestInstancesReturnsFilteredList(c *gc.C) {
   248  	env := makeEnviron(c)
   249  	prefix := env.getEnvPrefix()
   250  	service := makeDeployment(env, prefix+"service")
   251  	requests := patchInstancesResponses(c, prefix, service)
   252  	role1Name := service.Deployments[0].RoleList[0].RoleName
   253  	instId := instance.Id(prefix + "service-" + role1Name)
   254  	instances, err := env.Instances([]instance.Id{instId})
   255  	c.Assert(err, gc.IsNil)
   256  	c.Check(len(instances), gc.Equals, 1)
   257  	c.Check(instances[0].Id(), gc.Equals, instId)
   258  	c.Check(len(*requests), gc.Equals, 2)
   259  }
   260  
   261  func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfNoInstancesRequested(c *gc.C) {
   262  	services := []gwacl.HostedServiceDescriptor{{ServiceName: "deployment-1"}, {ServiceName: "deployment-2"}}
   263  	patchWithServiceListResponse(c, services)
   264  	env := makeEnviron(c)
   265  	instances, err := env.Instances([]instance.Id{})
   266  	c.Check(err, gc.Equals, environs.ErrNoInstances)
   267  	c.Check(instances, gc.IsNil)
   268  }
   269  
   270  func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfNoInstanceFound(c *gc.C) {
   271  	services := []gwacl.HostedServiceDescriptor{}
   272  	patchWithServiceListResponse(c, services)
   273  	env := makeEnviron(c)
   274  	instances, err := env.Instances([]instance.Id{"deploy-id"})
   275  	c.Check(err, gc.Equals, environs.ErrNoInstances)
   276  	c.Check(instances, gc.IsNil)
   277  }
   278  
   279  func (suite *environSuite) TestInstancesReturnsPartialInstancesIfSomeInstancesAreNotFound(c *gc.C) {
   280  	env := makeEnviron(c)
   281  	prefix := env.getEnvPrefix()
   282  	service := makeDeployment(env, prefix+"service")
   283  
   284  	role1Name := service.Deployments[0].RoleList[0].RoleName
   285  	role2Name := service.Deployments[0].RoleList[1].RoleName
   286  	inst1Id := instance.Id(prefix + "service-" + role1Name)
   287  	inst2Id := instance.Id(prefix + "service-" + role2Name)
   288  	patchInstancesResponses(c, prefix, service)
   289  
   290  	instances, err := env.Instances([]instance.Id{inst1Id, "unknown", inst2Id})
   291  	c.Assert(err, gc.Equals, environs.ErrPartialInstances)
   292  	c.Check(len(instances), gc.Equals, 3)
   293  	c.Check(instances[0].Id(), gc.Equals, inst1Id)
   294  	c.Check(instances[1], gc.IsNil)
   295  	c.Check(instances[2].Id(), gc.Equals, inst2Id)
   296  }
   297  
   298  func (*environSuite) TestStorage(c *gc.C) {
   299  	env := makeEnviron(c)
   300  	baseStorage := env.Storage()
   301  	storage, ok := baseStorage.(*azureStorage)
   302  	c.Check(ok, gc.Equals, true)
   303  	c.Assert(storage, gc.NotNil)
   304  	c.Check(storage.storageContext.getContainer(), gc.Equals, env.getContainerName())
   305  	context, err := storage.getStorageContext()
   306  	c.Assert(err, gc.IsNil)
   307  	c.Check(context.Account, gc.Equals, env.ecfg.storageAccountName())
   308  	c.Check(context.RetryPolicy, gc.DeepEquals, retryPolicy)
   309  }
   310  
   311  func (*environSuite) TestQueryStorageAccountKeyGetsKey(c *gc.C) {
   312  	env := makeEnviron(c)
   313  	keysInAzure := gwacl.StorageAccountKeys{Primary: "a-key"}
   314  	azureResponse, err := xml.Marshal(keysInAzure)
   315  	c.Assert(err, gc.IsNil)
   316  	requests := gwacl.PatchManagementAPIResponses([]gwacl.DispatcherResponse{
   317  		gwacl.NewDispatcherResponse(azureResponse, http.StatusOK, nil),
   318  	})
   319  
   320  	returnedKey, err := env.queryStorageAccountKey()
   321  	c.Assert(err, gc.IsNil)
   322  
   323  	c.Check(returnedKey, gc.Equals, keysInAzure.Primary)
   324  	c.Assert(*requests, gc.HasLen, 1)
   325  	c.Check((*requests)[0].Method, gc.Equals, "GET")
   326  }
   327  
   328  func (*environSuite) TestGetStorageContextCreatesStorageContext(c *gc.C) {
   329  	env := makeEnviron(c)
   330  	stor, err := env.getStorageContext()
   331  	c.Assert(err, gc.IsNil)
   332  	c.Assert(stor, gc.NotNil)
   333  	c.Check(stor.Account, gc.Equals, env.ecfg.storageAccountName())
   334  	c.Check(stor.AzureEndpoint, gc.Equals, gwacl.GetEndpoint(env.ecfg.location()))
   335  }
   336  
   337  func (*environSuite) TestGetStorageContextUsesKnownStorageAccountKey(c *gc.C) {
   338  	env := makeEnviron(c)
   339  	env.storageAccountKey = "my-key"
   340  
   341  	stor, err := env.getStorageContext()
   342  	c.Assert(err, gc.IsNil)
   343  
   344  	c.Check(stor.Key, gc.Equals, "my-key")
   345  }
   346  
   347  func (*environSuite) TestGetStorageContextQueriesStorageAccountKeyIfNeeded(c *gc.C) {
   348  	env := makeEnviron(c)
   349  	env.storageAccountKey = ""
   350  	keysInAzure := gwacl.StorageAccountKeys{Primary: "my-key"}
   351  	azureResponse, err := xml.Marshal(keysInAzure)
   352  	c.Assert(err, gc.IsNil)
   353  	gwacl.PatchManagementAPIResponses([]gwacl.DispatcherResponse{
   354  		gwacl.NewDispatcherResponse(azureResponse, http.StatusOK, nil),
   355  	})
   356  
   357  	stor, err := env.getStorageContext()
   358  	c.Assert(err, gc.IsNil)
   359  
   360  	c.Check(stor.Key, gc.Equals, keysInAzure.Primary)
   361  	c.Check(env.storageAccountKey, gc.Equals, keysInAzure.Primary)
   362  }
   363  
   364  func (*environSuite) TestGetStorageContextFailsIfNoKeyAvailable(c *gc.C) {
   365  	env := makeEnviron(c)
   366  	env.storageAccountKey = ""
   367  	azureResponse, err := xml.Marshal(gwacl.StorageAccountKeys{})
   368  	c.Assert(err, gc.IsNil)
   369  	gwacl.PatchManagementAPIResponses([]gwacl.DispatcherResponse{
   370  		gwacl.NewDispatcherResponse(azureResponse, http.StatusOK, nil),
   371  	})
   372  
   373  	_, err = env.getStorageContext()
   374  	c.Assert(err, gc.NotNil)
   375  
   376  	c.Check(err, gc.ErrorMatches, "no keys available for storage account")
   377  }
   378  
   379  func (*environSuite) TestUpdateStorageAccountKeyGetsFreshKey(c *gc.C) {
   380  	env := makeEnviron(c)
   381  	keysInAzure := gwacl.StorageAccountKeys{Primary: "my-key"}
   382  	azureResponse, err := xml.Marshal(keysInAzure)
   383  	c.Assert(err, gc.IsNil)
   384  	gwacl.PatchManagementAPIResponses([]gwacl.DispatcherResponse{
   385  		gwacl.NewDispatcherResponse(azureResponse, http.StatusOK, nil),
   386  	})
   387  
   388  	key, err := env.updateStorageAccountKey(env.getSnapshot())
   389  	c.Assert(err, gc.IsNil)
   390  
   391  	c.Check(key, gc.Equals, keysInAzure.Primary)
   392  	c.Check(env.storageAccountKey, gc.Equals, keysInAzure.Primary)
   393  }
   394  
   395  func (*environSuite) TestUpdateStorageAccountKeyReturnsError(c *gc.C) {
   396  	env := makeEnviron(c)
   397  	env.storageAccountKey = ""
   398  	gwacl.PatchManagementAPIResponses([]gwacl.DispatcherResponse{
   399  		gwacl.NewDispatcherResponse(nil, http.StatusInternalServerError, nil),
   400  	})
   401  
   402  	_, err := env.updateStorageAccountKey(env.getSnapshot())
   403  	c.Assert(err, gc.NotNil)
   404  
   405  	c.Check(err, gc.ErrorMatches, "cannot obtain storage account keys: GET request failed.*Internal Server Error.*")
   406  	c.Check(env.storageAccountKey, gc.Equals, "")
   407  }
   408  
   409  func (*environSuite) TestUpdateStorageAccountKeyDetectsConcurrentUpdate(c *gc.C) {
   410  	env := makeEnviron(c)
   411  	env.storageAccountKey = ""
   412  	keysInAzure := gwacl.StorageAccountKeys{Primary: "my-key"}
   413  	azureResponse, err := xml.Marshal(keysInAzure)
   414  	c.Assert(err, gc.IsNil)
   415  	gwacl.PatchManagementAPIResponses([]gwacl.DispatcherResponse{
   416  		gwacl.NewDispatcherResponse(azureResponse, http.StatusOK, nil),
   417  	})
   418  
   419  	// Here we use a snapshot that's different from the environment, to
   420  	// simulate a concurrent change to the environment.
   421  	_, err = env.updateStorageAccountKey(makeEnviron(c))
   422  	c.Assert(err, gc.NotNil)
   423  
   424  	// updateStorageAccountKey detects the change, and refuses to write its
   425  	// outdated information into env.
   426  	c.Check(err, gc.ErrorMatches, "environment was reconfigured")
   427  	c.Check(env.storageAccountKey, gc.Equals, "")
   428  }
   429  
   430  func (*environSuite) TestSetConfigValidates(c *gc.C) {
   431  	env := makeEnviron(c)
   432  	originalCfg := env.ecfg
   433  	attrs := makeAzureConfigMap(c)
   434  	// This config is not valid.  It lacks essential information.
   435  	delete(attrs, "management-subscription-id")
   436  	badCfg, err := config.New(config.NoDefaults, attrs)
   437  	c.Assert(err, gc.IsNil)
   438  
   439  	err = env.SetConfig(badCfg)
   440  
   441  	// Since the config was not valid, SetConfig returns an error.  It
   442  	// does not update the environment's config either.
   443  	c.Check(err, gc.NotNil)
   444  	c.Check(
   445  		err,
   446  		gc.ErrorMatches,
   447  		"management-subscription-id: expected string, got nothing")
   448  	c.Check(env.ecfg, gc.Equals, originalCfg)
   449  }
   450  
   451  func (*environSuite) TestSetConfigUpdatesConfig(c *gc.C) {
   452  	env := makeEnviron(c)
   453  	// We're going to set a new config.  It can be recognized by its
   454  	// unusual default Ubuntu release series: 7.04 Feisty Fawn.
   455  	attrs := makeAzureConfigMap(c)
   456  	attrs["default-series"] = "feisty"
   457  	cfg, err := config.New(config.NoDefaults, attrs)
   458  	c.Assert(err, gc.IsNil)
   459  
   460  	err = env.SetConfig(cfg)
   461  	c.Assert(err, gc.IsNil)
   462  
   463  	c.Check(config.PreferredSeries(env.ecfg.Config), gc.Equals, "feisty")
   464  }
   465  
   466  func (*environSuite) TestSetConfigLocksEnviron(c *gc.C) {
   467  	env := makeEnviron(c)
   468  	cfg, err := config.New(config.NoDefaults, makeAzureConfigMap(c))
   469  	c.Assert(err, gc.IsNil)
   470  
   471  	coretesting.TestLockingFunction(&env.Mutex, func() { env.SetConfig(cfg) })
   472  }
   473  
   474  func (*environSuite) TestSetConfigWillNotUpdateName(c *gc.C) {
   475  	// Once the environment's name has been set, it cannot be updated.
   476  	// Global validation rejects such a change.
   477  	// This matters because the attribute is not protected by a lock.
   478  	env := makeEnviron(c)
   479  	originalName := env.Name()
   480  	attrs := makeAzureConfigMap(c)
   481  	attrs["name"] = "new-name"
   482  	cfg, err := config.New(config.NoDefaults, attrs)
   483  	c.Assert(err, gc.IsNil)
   484  
   485  	err = env.SetConfig(cfg)
   486  
   487  	c.Assert(err, gc.NotNil)
   488  	c.Check(
   489  		err,
   490  		gc.ErrorMatches,
   491  		`cannot change name from ".*" to "new-name"`)
   492  	c.Check(env.Name(), gc.Equals, originalName)
   493  }
   494  
   495  func (*environSuite) TestSetConfigClearsStorageAccountKey(c *gc.C) {
   496  	env := makeEnviron(c)
   497  	env.storageAccountKey = "key-for-previous-config"
   498  	attrs := makeAzureConfigMap(c)
   499  	attrs["default-series"] = "other"
   500  	cfg, err := config.New(config.NoDefaults, attrs)
   501  	c.Assert(err, gc.IsNil)
   502  
   503  	err = env.SetConfig(cfg)
   504  	c.Assert(err, gc.IsNil)
   505  
   506  	c.Check(env.storageAccountKey, gc.Equals, "")
   507  }
   508  
   509  func (s *environSuite) TestStateInfoFailsIfNoStateInstances(c *gc.C) {
   510  	env := makeEnviron(c)
   511  	s.setDummyStorage(c, env)
   512  	_, _, err := env.StateInfo()
   513  	c.Check(err, gc.Equals, environs.ErrNotBootstrapped)
   514  }
   515  
   516  func (s *environSuite) TestStateInfo(c *gc.C) {
   517  	env := makeEnviron(c)
   518  	s.setDummyStorage(c, env)
   519  	prefix := env.getEnvPrefix()
   520  
   521  	service := makeDeployment(env, prefix+"myservice")
   522  	instId := instance.Id(service.ServiceName + "-" + service.Deployments[0].RoleList[0].RoleName)
   523  	patchInstancesResponses(c, prefix, service)
   524  	err := bootstrap.SaveState(
   525  		env.Storage(),
   526  		&bootstrap.BootstrapState{StateInstances: []instance.Id{instId}},
   527  	)
   528  	c.Assert(err, gc.IsNil)
   529  
   530  	responses := prepareInstancesResponses(c, prefix, service)
   531  	responses = append(responses, prepareDeploymentInfoResponse(c, gwacl.Deployment{
   532  		RoleInstanceList: []gwacl.RoleInstance{{
   533  			RoleName:  service.Deployments[0].RoleList[0].RoleName,
   534  			IPAddress: "1.2.3.4",
   535  		}},
   536  		VirtualNetworkName: env.getVirtualNetworkName(),
   537  	})...)
   538  	gwacl.PatchManagementAPIResponses(responses)
   539  
   540  	stateInfo, apiInfo, err := env.StateInfo()
   541  	c.Assert(err, gc.IsNil)
   542  	config := env.Config()
   543  	dnsName := prefix + "myservice." + AzureDomainName
   544  	c.Check(stateInfo.Addrs, jc.SameContents, []string{
   545  		fmt.Sprintf("1.2.3.4:%d", config.StatePort()),
   546  		fmt.Sprintf("%s:%d", dnsName, config.StatePort()),
   547  	})
   548  	c.Check(apiInfo.Addrs, jc.DeepEquals, []string{
   549  		fmt.Sprintf("1.2.3.4:%d", config.APIPort()),
   550  		fmt.Sprintf("%s:%d", dnsName, config.APIPort()),
   551  	})
   552  }
   553  
   554  // parseCreateServiceRequest reconstructs the original CreateHostedService
   555  // request object passed to gwacl's AddHostedService method, based on the
   556  // X509Request which the method issues.
   557  func parseCreateServiceRequest(c *gc.C, request *gwacl.X509Request) *gwacl.CreateHostedService {
   558  	body := gwacl.CreateHostedService{}
   559  	err := xml.Unmarshal(request.Payload, &body)
   560  	c.Assert(err, gc.IsNil)
   561  	return &body
   562  }
   563  
   564  // getHostedServicePropertiesServiceName extracts the service name parameter
   565  // from the GetHostedServiceProperties request URL.
   566  func getHostedServicePropertiesServiceName(c *gc.C, request *gwacl.X509Request) string {
   567  	url, err := url.Parse(request.URL)
   568  	c.Assert(err, gc.IsNil)
   569  	return path.Base(url.Path)
   570  }
   571  
   572  // makeNonAvailabilityResponse simulates a reply to the
   573  // CheckHostedServiceNameAvailability call saying that a name is not available.
   574  func makeNonAvailabilityResponse(c *gc.C) []byte {
   575  	errorBody, err := xml.Marshal(gwacl.AvailabilityResponse{
   576  		Result: "false",
   577  		Reason: "he's a very naughty boy"})
   578  	c.Assert(err, gc.IsNil)
   579  	return errorBody
   580  }
   581  
   582  // makeAvailabilityResponse simulates a reply to the
   583  // CheckHostedServiceNameAvailability call saying that a name is available.
   584  func makeAvailabilityResponse(c *gc.C) []byte {
   585  	errorBody, err := xml.Marshal(gwacl.AvailabilityResponse{
   586  		Result: "true"})
   587  	c.Assert(err, gc.IsNil)
   588  	return errorBody
   589  }
   590  
   591  func (*environSuite) TestAttemptCreateServiceCreatesService(c *gc.C) {
   592  	prefix := "myservice"
   593  	affinityGroup := "affinity-group"
   594  
   595  	responses := []gwacl.DispatcherResponse{
   596  		gwacl.NewDispatcherResponse(makeAvailabilityResponse(c), http.StatusOK, nil),
   597  		gwacl.NewDispatcherResponse(nil, http.StatusOK, nil),
   598  	}
   599  	requests := gwacl.PatchManagementAPIResponses(responses)
   600  	azure, err := gwacl.NewManagementAPI("subscription", "", "West US")
   601  	c.Assert(err, gc.IsNil)
   602  
   603  	service, err := attemptCreateService(azure, prefix, affinityGroup, "")
   604  	c.Assert(err, gc.IsNil)
   605  
   606  	c.Assert(*requests, gc.HasLen, 2)
   607  	body := parseCreateServiceRequest(c, (*requests)[1])
   608  	c.Check(body.ServiceName, gc.Equals, service.ServiceName)
   609  	c.Check(body.AffinityGroup, gc.Equals, affinityGroup)
   610  	c.Check(service.ServiceName, gc.Matches, prefix+".*")
   611  	// We specify AffinityGroup, so Location should be empty.
   612  	c.Check(service.Location, gc.Equals, "")
   613  }
   614  
   615  func (*environSuite) TestAttemptCreateServiceReturnsNilIfNameNotUnique(c *gc.C) {
   616  	responses := []gwacl.DispatcherResponse{
   617  		gwacl.NewDispatcherResponse(makeNonAvailabilityResponse(c), http.StatusOK, nil),
   618  	}
   619  	gwacl.PatchManagementAPIResponses(responses)
   620  	azure, err := gwacl.NewManagementAPI("subscription", "", "West US")
   621  	c.Assert(err, gc.IsNil)
   622  
   623  	service, err := attemptCreateService(azure, "service", "affinity-group", "")
   624  	c.Check(err, gc.IsNil)
   625  	c.Check(service, gc.IsNil)
   626  }
   627  
   628  func (*environSuite) TestAttemptCreateServicePropagatesOtherFailure(c *gc.C) {
   629  	responses := []gwacl.DispatcherResponse{
   630  		gwacl.NewDispatcherResponse(makeAvailabilityResponse(c), http.StatusOK, nil),
   631  		gwacl.NewDispatcherResponse(nil, http.StatusNotFound, nil),
   632  	}
   633  	gwacl.PatchManagementAPIResponses(responses)
   634  	azure, err := gwacl.NewManagementAPI("subscription", "", "West US")
   635  	c.Assert(err, gc.IsNil)
   636  
   637  	_, err = attemptCreateService(azure, "service", "affinity-group", "")
   638  	c.Assert(err, gc.NotNil)
   639  	c.Check(err, gc.ErrorMatches, ".*Not Found.*")
   640  }
   641  
   642  func (*environSuite) TestNewHostedServiceCreatesService(c *gc.C) {
   643  	prefix := "myservice"
   644  	affinityGroup := "affinity-group"
   645  	responses := []gwacl.DispatcherResponse{
   646  		gwacl.NewDispatcherResponse(makeAvailabilityResponse(c), http.StatusOK, nil),
   647  		gwacl.NewDispatcherResponse(nil, http.StatusOK, nil),
   648  		getAzureServiceResponse(c, gwacl.HostedService{
   649  			HostedServiceDescriptor: gwacl.HostedServiceDescriptor{
   650  				ServiceName: "anything",
   651  			},
   652  		}),
   653  	}
   654  	requests := gwacl.PatchManagementAPIResponses(responses)
   655  	azure, err := gwacl.NewManagementAPI("subscription", "", "West US")
   656  	c.Assert(err, gc.IsNil)
   657  
   658  	service, err := newHostedService(azure, prefix, affinityGroup, "")
   659  	c.Assert(err, gc.IsNil)
   660  
   661  	c.Assert(*requests, gc.HasLen, 3)
   662  	body := parseCreateServiceRequest(c, (*requests)[1])
   663  	requestedServiceName := getHostedServicePropertiesServiceName(c, (*requests)[2])
   664  	c.Check(body.ServiceName, gc.Matches, prefix+".*")
   665  	c.Check(body.ServiceName, gc.Equals, requestedServiceName)
   666  	c.Check(body.AffinityGroup, gc.Equals, affinityGroup)
   667  	c.Check(service.ServiceName, gc.Equals, "anything")
   668  	c.Check(service.Location, gc.Equals, "")
   669  }
   670  
   671  func (*environSuite) TestNewHostedServiceRetriesIfNotUnique(c *gc.C) {
   672  	errorBody := makeNonAvailabilityResponse(c)
   673  	okBody := makeAvailabilityResponse(c)
   674  	// In this scenario, the first two names that we try are already
   675  	// taken.  The third one is unique though, so we succeed.
   676  	responses := []gwacl.DispatcherResponse{
   677  		gwacl.NewDispatcherResponse(errorBody, http.StatusOK, nil),
   678  		gwacl.NewDispatcherResponse(errorBody, http.StatusOK, nil),
   679  		gwacl.NewDispatcherResponse(okBody, http.StatusOK, nil), // name is unique
   680  		gwacl.NewDispatcherResponse(nil, http.StatusOK, nil),    // create service
   681  		getAzureServiceResponse(c, gwacl.HostedService{
   682  			HostedServiceDescriptor: gwacl.HostedServiceDescriptor{
   683  				ServiceName: "anything",
   684  			},
   685  		}),
   686  	}
   687  	requests := gwacl.PatchManagementAPIResponses(responses)
   688  	azure, err := gwacl.NewManagementAPI("subscription", "", "West US")
   689  	c.Assert(err, gc.IsNil)
   690  
   691  	service, err := newHostedService(azure, "service", "affinity-group", "")
   692  	c.Check(err, gc.IsNil)
   693  
   694  	c.Assert(*requests, gc.HasLen, 5)
   695  	// How many names have been attempted, and how often?
   696  	// There is a minute chance that this tries the same name twice, and
   697  	// then this test will fail.  If that happens, try seeding the
   698  	// randomizer with some fixed seed that doens't produce the problem.
   699  	attemptedNames := make(map[string]int)
   700  	for _, request := range *requests {
   701  		// Exit the loop if we hit the request to create the service, it comes
   702  		// after the check calls.
   703  		if request.Method == "POST" {
   704  			break
   705  		}
   706  		// Name is the last part of the URL from the GET requests that check
   707  		// availability.
   708  		_, name := path.Split(strings.TrimRight(request.URL, "/"))
   709  		attemptedNames[name] += 1
   710  	}
   711  	// The three attempts we just made all had different service names.
   712  	c.Check(attemptedNames, gc.HasLen, 3)
   713  
   714  	// Once newHostedService succeeds, we get a hosted service with the
   715  	// name returned from GetHostedServiceProperties.
   716  	c.Check(service.ServiceName, gc.Equals, "anything")
   717  }
   718  
   719  func (*environSuite) TestNewHostedServiceFailsIfUnableToFindUniqueName(c *gc.C) {
   720  	errorBody := makeNonAvailabilityResponse(c)
   721  	responses := []gwacl.DispatcherResponse{}
   722  	for counter := 0; counter < 100; counter++ {
   723  		responses = append(responses, gwacl.NewDispatcherResponse(errorBody, http.StatusOK, nil))
   724  	}
   725  	gwacl.PatchManagementAPIResponses(responses)
   726  	azure, err := gwacl.NewManagementAPI("subscription", "", "West US")
   727  	c.Assert(err, gc.IsNil)
   728  
   729  	_, err = newHostedService(azure, "service", "affinity-group", "")
   730  	c.Assert(err, gc.NotNil)
   731  	c.Check(err, gc.ErrorMatches, "could not come up with a unique hosted service name.*")
   732  }
   733  
   734  func buildGetServicePropertiesResponses(c *gc.C, services ...*gwacl.HostedService) []gwacl.DispatcherResponse {
   735  	responses := make([]gwacl.DispatcherResponse, len(services))
   736  	for i, service := range services {
   737  		serviceXML, err := service.Serialize()
   738  		c.Assert(err, gc.IsNil)
   739  		responses[i] = gwacl.NewDispatcherResponse([]byte(serviceXML), http.StatusOK, nil)
   740  	}
   741  	return responses
   742  }
   743  
   744  func buildStatusOKResponses(c *gc.C, n int) []gwacl.DispatcherResponse {
   745  	responses := make([]gwacl.DispatcherResponse, n)
   746  	for i := range responses {
   747  		responses[i] = gwacl.NewDispatcherResponse(nil, http.StatusOK, nil)
   748  	}
   749  	return responses
   750  }
   751  
   752  func makeAzureService(name string) *gwacl.HostedService {
   753  	return &gwacl.HostedService{
   754  		HostedServiceDescriptor: gwacl.HostedServiceDescriptor{ServiceName: name},
   755  	}
   756  }
   757  
   758  func makeRole(env *azureEnviron) *gwacl.Role {
   759  	size := "Large"
   760  	vhd := env.newOSDisk("source-image-name")
   761  	userData := "example-user-data"
   762  	return env.newRole(size, vhd, userData, false)
   763  }
   764  
   765  func makeLegacyDeployment(env *azureEnviron, serviceName string) *gwacl.HostedService {
   766  	service := makeAzureService(serviceName)
   767  	service.Deployments = []gwacl.Deployment{{
   768  		Name:     serviceName,
   769  		RoleList: []gwacl.Role{*makeRole(env)},
   770  	}}
   771  	return service
   772  }
   773  
   774  func makeDeployment(env *azureEnviron, serviceName string) *gwacl.HostedService {
   775  	service := makeAzureService(serviceName)
   776  	service.Deployments = []gwacl.Deployment{{
   777  		Name:     serviceName + "-v2",
   778  		RoleList: []gwacl.Role{*makeRole(env), *makeRole(env)},
   779  	}}
   780  	return service
   781  }
   782  
   783  func (s *environSuite) TestStopInstancesDestroysMachines(c *gc.C) {
   784  	env := makeEnviron(c)
   785  	prefix := env.getEnvPrefix()
   786  	service1Name := "service1"
   787  	service1 := makeLegacyDeployment(env, prefix+service1Name)
   788  	service2Name := "service2"
   789  	service2 := makeDeployment(env, prefix+service2Name)
   790  
   791  	inst1, err := env.getInstance(service1, "")
   792  	c.Assert(err, gc.IsNil)
   793  	role2Name := service2.Deployments[0].RoleList[0].RoleName
   794  	inst2, err := env.getInstance(service2, role2Name)
   795  	c.Assert(err, gc.IsNil)
   796  	role3Name := service2.Deployments[0].RoleList[1].RoleName
   797  	inst3, err := env.getInstance(service2, role3Name)
   798  	c.Assert(err, gc.IsNil)
   799  
   800  	responses := buildGetServicePropertiesResponses(c, service1)
   801  	responses = append(responses, buildStatusOKResponses(c, 1)...) // DeleteHostedService
   802  	responses = append(responses, buildGetServicePropertiesResponses(c, service2)...)
   803  	responses = append(responses, buildStatusOKResponses(c, 1)...) // DeleteHostedService
   804  	requests := gwacl.PatchManagementAPIResponses(responses)
   805  	err = env.StopInstances(inst1.Id(), inst2.Id(), inst3.Id())
   806  	c.Check(err, gc.IsNil)
   807  
   808  	// One GET and DELETE per service
   809  	// (GetHostedServiceProperties and DeleteHostedService).
   810  	c.Check(len(*requests), gc.Equals, len(responses))
   811  	assertOneRequestMatches(c, *requests, "GET", ".*"+service1Name+".")
   812  	assertOneRequestMatches(c, *requests, "GET", ".*"+service2Name+".*")
   813  	assertOneRequestMatches(c, *requests, "DELETE", ".*"+service1Name+".*")
   814  	assertOneRequestMatches(c, *requests, "DELETE", ".*"+service2Name+".*")
   815  }
   816  
   817  func (s *environSuite) TestStopInstancesServiceSubset(c *gc.C) {
   818  	env := makeEnviron(c)
   819  	service := makeDeployment(env, env.getEnvPrefix()+"service")
   820  
   821  	role1Name := service.Deployments[0].RoleList[0].RoleName
   822  	inst1, err := env.getInstance(service, role1Name)
   823  	c.Assert(err, gc.IsNil)
   824  
   825  	responses := buildGetServicePropertiesResponses(c, service)
   826  	responses = append(responses, buildStatusOKResponses(c, 1)...) // DeleteRole
   827  	requests := gwacl.PatchManagementAPIResponses(responses)
   828  	err = env.StopInstances(inst1.Id())
   829  	c.Check(err, gc.IsNil)
   830  
   831  	// One GET for the service, and one DELETE for the role.
   832  	// The service isn't deleted because it has two roles,
   833  	// and only one is being deleted.
   834  	c.Check(len(*requests), gc.Equals, len(responses))
   835  	assertOneRequestMatches(c, *requests, "GET", ".*"+service.ServiceName+".")
   836  	assertOneRequestMatches(c, *requests, "DELETE", ".*"+role1Name+".*")
   837  }
   838  
   839  func (s *environSuite) TestStopInstancesWhenStoppingMachinesFails(c *gc.C) {
   840  	env := makeEnviron(c)
   841  	prefix := env.getEnvPrefix()
   842  	service1 := makeDeployment(env, prefix+"service1")
   843  	service2 := makeDeployment(env, prefix+"service2")
   844  	service1Role1Name := service1.Deployments[0].RoleList[0].RoleName
   845  	inst1, err := env.getInstance(service1, service1Role1Name)
   846  	c.Assert(err, gc.IsNil)
   847  	service2Role1Name := service2.Deployments[0].RoleList[0].RoleName
   848  	inst2, err := env.getInstance(service2, service2Role1Name)
   849  	c.Assert(err, gc.IsNil)
   850  
   851  	responses := buildGetServicePropertiesResponses(c, service1)
   852  	// Failed to delete one of the services. This will cause StopInstances to stop
   853  	// immediately.
   854  	responses = append(responses, gwacl.NewDispatcherResponse(nil, http.StatusConflict, nil))
   855  	requests := gwacl.PatchManagementAPIResponses(responses)
   856  
   857  	err = env.StopInstances(inst1.Id(), inst2.Id())
   858  	c.Check(err, gc.ErrorMatches, ".*Conflict.*")
   859  
   860  	c.Check(len(*requests), gc.Equals, len(responses))
   861  	assertOneRequestMatches(c, *requests, "GET", ".*"+service1.ServiceName+".*")
   862  	assertOneRequestMatches(c, *requests, "DELETE", service1.ServiceName)
   863  }
   864  
   865  func (s *environSuite) TestStopInstancesWithZeroInstance(c *gc.C) {
   866  	env := makeEnviron(c)
   867  	err := env.StopInstances()
   868  	c.Check(err, gc.IsNil)
   869  }
   870  
   871  // getVnetAndAffinityGroupCleanupResponses returns the responses
   872  // (gwacl.DispatcherResponse) that a fake http server should return
   873  // when gwacl's RemoveVirtualNetworkSite() and DeleteAffinityGroup()
   874  // are called.
   875  func getVnetAndAffinityGroupCleanupResponses(c *gc.C) []gwacl.DispatcherResponse {
   876  	existingConfig := &gwacl.NetworkConfiguration{
   877  		XMLNS:               gwacl.XMLNS_NC,
   878  		VirtualNetworkSites: nil,
   879  	}
   880  	body, err := existingConfig.Serialize()
   881  	c.Assert(err, gc.IsNil)
   882  	cleanupResponses := []gwacl.DispatcherResponse{
   883  		// Return empty net configuration.
   884  		gwacl.NewDispatcherResponse([]byte(body), http.StatusOK, nil),
   885  		// Accept deletion of affinity group.
   886  		gwacl.NewDispatcherResponse(nil, http.StatusOK, nil),
   887  	}
   888  	return cleanupResponses
   889  }
   890  
   891  func (s *environSuite) TestDestroyDoesNotCleanStorageIfError(c *gc.C) {
   892  	env := makeEnviron(c)
   893  	s.setDummyStorage(c, env)
   894  	// Populate storage.
   895  	err := bootstrap.SaveState(
   896  		env.Storage(),
   897  		&bootstrap.BootstrapState{StateInstances: []instance.Id{instance.Id("test-id")}})
   898  	c.Assert(err, gc.IsNil)
   899  	responses := []gwacl.DispatcherResponse{
   900  		gwacl.NewDispatcherResponse(nil, http.StatusBadRequest, nil),
   901  	}
   902  	gwacl.PatchManagementAPIResponses(responses)
   903  
   904  	err = env.Destroy()
   905  	c.Check(err, gc.NotNil)
   906  
   907  	files, err := storage.List(env.Storage(), "")
   908  	c.Assert(err, gc.IsNil)
   909  	c.Check(files, gc.HasLen, 1)
   910  }
   911  
   912  func (s *environSuite) TestDestroyCleansUpStorage(c *gc.C) {
   913  	env := makeEnviron(c)
   914  	s.setDummyStorage(c, env)
   915  	// Populate storage.
   916  	err := bootstrap.SaveState(
   917  		env.Storage(),
   918  		&bootstrap.BootstrapState{StateInstances: []instance.Id{instance.Id("test-id")}})
   919  	c.Assert(err, gc.IsNil)
   920  	responses := getAzureServiceListResponse(c)
   921  	cleanupResponses := getVnetAndAffinityGroupCleanupResponses(c)
   922  	responses = append(responses, cleanupResponses...)
   923  	gwacl.PatchManagementAPIResponses(responses)
   924  
   925  	err = env.Destroy()
   926  	c.Check(err, gc.IsNil)
   927  
   928  	files, err := storage.List(env.Storage(), "")
   929  	c.Assert(err, gc.IsNil)
   930  	c.Check(files, gc.HasLen, 0)
   931  }
   932  
   933  func (s *environSuite) TestDestroyDeletesVirtualNetworkAndAffinityGroup(c *gc.C) {
   934  	env := makeEnviron(c)
   935  	s.setDummyStorage(c, env)
   936  	responses := getAzureServiceListResponse(c)
   937  	// Prepare a configuration with a single virtual network.
   938  	existingConfig := &gwacl.NetworkConfiguration{
   939  		XMLNS: gwacl.XMLNS_NC,
   940  		VirtualNetworkSites: &[]gwacl.VirtualNetworkSite{
   941  			{Name: env.getVirtualNetworkName()},
   942  		},
   943  	}
   944  	body, err := existingConfig.Serialize()
   945  	c.Assert(err, gc.IsNil)
   946  	cleanupResponses := []gwacl.DispatcherResponse{
   947  		// Return existing configuration.
   948  		gwacl.NewDispatcherResponse([]byte(body), http.StatusOK, nil),
   949  		// Accept upload of new configuration.
   950  		gwacl.NewDispatcherResponse(nil, http.StatusOK, nil),
   951  		// Accept deletion of affinity group.
   952  		gwacl.NewDispatcherResponse(nil, http.StatusOK, nil),
   953  	}
   954  	responses = append(responses, cleanupResponses...)
   955  	requests := gwacl.PatchManagementAPIResponses(responses)
   956  
   957  	err = env.Destroy()
   958  	c.Check(err, gc.IsNil)
   959  
   960  	c.Assert(*requests, gc.HasLen, 4)
   961  	// One request to get the network configuration.
   962  	getRequest := (*requests)[1]
   963  	c.Check(getRequest.Method, gc.Equals, "GET")
   964  	c.Check(strings.HasSuffix(getRequest.URL, "services/networking/media"), gc.Equals, true)
   965  	// One request to upload the new version of the network configuration.
   966  	putRequest := (*requests)[2]
   967  	c.Check(putRequest.Method, gc.Equals, "PUT")
   968  	c.Check(strings.HasSuffix(putRequest.URL, "services/networking/media"), gc.Equals, true)
   969  	// One request to delete the Affinity Group.
   970  	agRequest := (*requests)[3]
   971  	c.Check(strings.Contains(agRequest.URL, env.getAffinityGroupName()), jc.IsTrue)
   972  	c.Check(agRequest.Method, gc.Equals, "DELETE")
   973  
   974  }
   975  
   976  var emptyListResponse = `
   977    <?xml version="1.0" encoding="utf-8"?>
   978    <EnumerationResults ContainerName="http://myaccount.blob.core.windows.net/mycontainer">
   979      <Prefix>prefix</Prefix>
   980      <Marker>marker</Marker>
   981      <MaxResults>maxresults</MaxResults>
   982      <Delimiter>delimiter</Delimiter>
   983      <Blobs></Blobs>
   984      <NextMarker />
   985    </EnumerationResults>`
   986  
   987  // assertOneRequestMatches asserts that at least one request in the given slice
   988  // contains a request with the given method and whose URL matches the given regexp.
   989  func assertOneRequestMatches(c *gc.C, requests []*gwacl.X509Request, method string, urlPattern string) {
   990  	for _, request := range requests {
   991  		matched, err := regexp.MatchString(urlPattern, request.URL)
   992  		if err == nil && request.Method == method && matched {
   993  			return
   994  		}
   995  	}
   996  	c.Error(fmt.Sprintf("none of the requests matches: Method=%v, URL pattern=%v", method, urlPattern))
   997  }
   998  
   999  func (s *environSuite) TestDestroyStopsAllInstances(c *gc.C) {
  1000  	env := makeEnviron(c)
  1001  	s.setDummyStorage(c, env)
  1002  	prefix := env.getEnvPrefix()
  1003  	service1 := makeDeployment(env, prefix+"service1")
  1004  	service2 := makeDeployment(env, prefix+"service2")
  1005  
  1006  	// The call to AllInstances() will return only one service (service1).
  1007  	responses := getAzureServiceListResponse(c, service1.HostedServiceDescriptor, service2.HostedServiceDescriptor)
  1008  	responses = append(responses, buildStatusOKResponses(c, 2)...) // DeleteHostedService
  1009  	responses = append(responses, getVnetAndAffinityGroupCleanupResponses(c)...)
  1010  	requests := gwacl.PatchManagementAPIResponses(responses)
  1011  
  1012  	err := env.Destroy()
  1013  	c.Check(err, gc.IsNil)
  1014  
  1015  	// One request to get the list of all the environment's instances.
  1016  	// One delete request per destroyed service, and two additional
  1017  	// requests to delete the Virtual Network and the Affinity Group.
  1018  	c.Check((*requests), gc.HasLen, 5)
  1019  	c.Check((*requests)[0].Method, gc.Equals, "GET")
  1020  	assertOneRequestMatches(c, *requests, "DELETE", ".*"+service1.ServiceName+".*")
  1021  	assertOneRequestMatches(c, *requests, "DELETE", ".*"+service2.ServiceName+".*")
  1022  }
  1023  
  1024  func (s *environSuite) TestGetInstance(c *gc.C) {
  1025  	env := makeEnviron(c)
  1026  	service1 := makeLegacyDeployment(env, "service1")
  1027  	service2 := makeDeployment(env, "service1")
  1028  
  1029  	// azureEnviron.Instances will call getInstance with roleName==""
  1030  	// for legacy instances. This will cause getInstance to get the
  1031  	// one and only role (or error if there is more than one).
  1032  	inst1, err := env.getInstance(service1, "")
  1033  	c.Assert(err, gc.IsNil)
  1034  	c.Check(inst1.Id(), gc.Equals, instance.Id("service1"))
  1035  	c.Assert(inst1, gc.FitsTypeOf, &azureInstance{})
  1036  	c.Check(inst1.(*azureInstance).environ, gc.Equals, env)
  1037  	c.Check(inst1.(*azureInstance).roleName, gc.Equals, service1.Deployments[0].RoleList[0].RoleName)
  1038  	service1.Deployments[0].RoleList = service2.Deployments[0].RoleList
  1039  	inst1, err = env.getInstance(service1, "")
  1040  	c.Check(err, gc.ErrorMatches, `expected one role for "service1", got 2`)
  1041  
  1042  	inst2, err := env.getInstance(service2, service2.Deployments[0].RoleList[0].RoleName)
  1043  	c.Assert(err, gc.IsNil)
  1044  	c.Check(inst2.Id(), gc.Equals, instance.Id("service1-"+service2.Deployments[0].RoleList[0].RoleName))
  1045  }
  1046  
  1047  func (s *environSuite) TestInitialPorts(c *gc.C) {
  1048  	env := makeEnviron(c)
  1049  	service1 := makeLegacyDeployment(env, "service1")
  1050  	service2 := makeDeployment(env, "service2")
  1051  	service3 := makeDeployment(env, "service3")
  1052  	service3.Label = base64.StdEncoding.EncodeToString([]byte(stateServerLabel))
  1053  
  1054  	role1 := &service1.Deployments[0].RoleList[0]
  1055  	inst1, err := env.getInstance(service1, role1.RoleName)
  1056  	c.Assert(err, gc.IsNil)
  1057  	c.Assert(inst1.(*azureInstance).maskStateServerPorts, jc.IsTrue)
  1058  	role2 := &service2.Deployments[0].RoleList[0]
  1059  	inst2, err := env.getInstance(service2, role2.RoleName)
  1060  	c.Assert(err, gc.IsNil)
  1061  	role3 := &service3.Deployments[0].RoleList[0]
  1062  	inst3, err := env.getInstance(service3, role3.RoleName)
  1063  	c.Assert(err, gc.IsNil)
  1064  
  1065  	// Only role2 should report opened state server ports via the Ports method.
  1066  	dummyRole := *role1
  1067  	configSetNetwork(&dummyRole).InputEndpoints = &[]gwacl.InputEndpoint{{
  1068  		LocalPort: env.Config().StatePort(),
  1069  		Protocol:  "tcp",
  1070  		Name:      "stateserver",
  1071  		Port:      env.Config().StatePort(),
  1072  	}, {
  1073  		LocalPort: env.Config().APIPort(),
  1074  		Protocol:  "tcp",
  1075  		Name:      "apiserver",
  1076  		Port:      env.Config().APIPort(),
  1077  	}}
  1078  	reportsStateServerPorts := func(inst instance.Instance) bool {
  1079  		responses := preparePortChangeConversation(c, &dummyRole)
  1080  		gwacl.PatchManagementAPIResponses(responses)
  1081  		ports, err := inst.Ports("")
  1082  		c.Assert(err, gc.IsNil)
  1083  		portmap := make(map[int]bool)
  1084  		for _, port := range ports {
  1085  			portmap[port.Number] = true
  1086  		}
  1087  		return portmap[env.Config().StatePort()] && portmap[env.Config().APIPort()]
  1088  	}
  1089  	c.Check(inst1, gc.Not(jc.Satisfies), reportsStateServerPorts)
  1090  	c.Check(inst2, jc.Satisfies, reportsStateServerPorts)
  1091  	c.Check(inst3, gc.Not(jc.Satisfies), reportsStateServerPorts)
  1092  }
  1093  
  1094  func (*environSuite) TestNewOSVirtualDisk(c *gc.C) {
  1095  	env := makeEnviron(c)
  1096  	sourceImageName := "source-image-name"
  1097  
  1098  	vhd := env.newOSDisk(sourceImageName)
  1099  
  1100  	mediaLinkUrl, err := url.Parse(vhd.MediaLink)
  1101  	c.Check(err, gc.IsNil)
  1102  	storageAccount := env.ecfg.storageAccountName()
  1103  	c.Check(mediaLinkUrl.Host, gc.Equals, fmt.Sprintf("%s.blob.core.windows.net", storageAccount))
  1104  	c.Check(vhd.SourceImageName, gc.Equals, sourceImageName)
  1105  }
  1106  
  1107  // mapInputEndpointsByPort takes a slice of input endpoints, and returns them
  1108  // as a map keyed by their (external) ports.  This makes it easier to query
  1109  // individual endpoints from an array whose ordering you don't know.
  1110  // Multiple input endpoints for the same port are treated as an error.
  1111  func mapInputEndpointsByPort(c *gc.C, endpoints []gwacl.InputEndpoint) map[int]gwacl.InputEndpoint {
  1112  	mapping := make(map[int]gwacl.InputEndpoint)
  1113  	for _, endpoint := range endpoints {
  1114  		_, have := mapping[endpoint.Port]
  1115  		c.Assert(have, gc.Equals, false)
  1116  		mapping[endpoint.Port] = endpoint
  1117  	}
  1118  	return mapping
  1119  }
  1120  
  1121  func (s *environSuite) TestNewRole(c *gc.C) {
  1122  	s.testNewRole(c, false)
  1123  }
  1124  
  1125  func (s *environSuite) TestNewRoleStateServer(c *gc.C) {
  1126  	s.testNewRole(c, true)
  1127  }
  1128  
  1129  func (*environSuite) testNewRole(c *gc.C, stateServer bool) {
  1130  	env := makeEnviron(c)
  1131  	size := "Large"
  1132  	vhd := env.newOSDisk("source-image-name")
  1133  	userData := "example-user-data"
  1134  
  1135  	role := env.newRole(size, vhd, userData, stateServer)
  1136  
  1137  	configs := role.ConfigurationSets
  1138  	linuxConfig := configs[0]
  1139  	networkConfig := configs[1]
  1140  	c.Check(linuxConfig.CustomData, gc.Equals, userData)
  1141  	c.Check(linuxConfig.Hostname, gc.Equals, role.RoleName)
  1142  	c.Check(linuxConfig.Username, gc.Not(gc.Equals), "")
  1143  	c.Check(linuxConfig.Password, gc.Not(gc.Equals), "")
  1144  	c.Check(linuxConfig.DisableSSHPasswordAuthentication, gc.Equals, "true")
  1145  	c.Check(role.RoleSize, gc.Equals, size)
  1146  	c.Check(role.OSVirtualHardDisk, gc.DeepEquals, vhd)
  1147  
  1148  	endpoints := mapInputEndpointsByPort(c, *networkConfig.InputEndpoints)
  1149  
  1150  	// The network config contains an endpoint for ssh communication.
  1151  	sshEndpoint, ok := endpoints[22]
  1152  	c.Assert(ok, gc.Equals, true)
  1153  	c.Check(sshEndpoint.LocalPort, gc.Equals, 22)
  1154  	c.Check(sshEndpoint.Protocol, gc.Equals, "tcp")
  1155  
  1156  	if stateServer {
  1157  		// There's also an endpoint for the state (mongodb) port.
  1158  		stateEndpoint, ok := endpoints[env.Config().StatePort()]
  1159  		c.Assert(ok, gc.Equals, true)
  1160  		c.Check(stateEndpoint.LocalPort, gc.Equals, env.Config().StatePort())
  1161  		c.Check(stateEndpoint.Protocol, gc.Equals, "tcp")
  1162  
  1163  		// And one for the API port.
  1164  		apiEndpoint, ok := endpoints[env.Config().APIPort()]
  1165  		c.Assert(ok, gc.Equals, true)
  1166  		c.Check(apiEndpoint.LocalPort, gc.Equals, env.Config().APIPort())
  1167  		c.Check(apiEndpoint.Protocol, gc.Equals, "tcp")
  1168  	}
  1169  }
  1170  
  1171  func (*environSuite) TestProviderReturnsAzureEnvironProvider(c *gc.C) {
  1172  	prov := makeEnviron(c).Provider()
  1173  	c.Assert(prov, gc.NotNil)
  1174  	azprov, ok := prov.(azureEnvironProvider)
  1175  	c.Assert(ok, gc.Equals, true)
  1176  	c.Check(azprov, gc.NotNil)
  1177  }
  1178  
  1179  func (*environSuite) TestCreateVirtualNetwork(c *gc.C) {
  1180  	env := makeEnviron(c)
  1181  	responses := []gwacl.DispatcherResponse{
  1182  		// No existing configuration found.
  1183  		gwacl.NewDispatcherResponse(nil, http.StatusNotFound, nil),
  1184  		// Accept upload of new configuration.
  1185  		gwacl.NewDispatcherResponse(nil, http.StatusOK, nil),
  1186  	}
  1187  	requests := gwacl.PatchManagementAPIResponses(responses)
  1188  
  1189  	env.createVirtualNetwork()
  1190  
  1191  	c.Assert(*requests, gc.HasLen, 2)
  1192  	request := (*requests)[1]
  1193  	body := gwacl.NetworkConfiguration{}
  1194  	err := xml.Unmarshal(request.Payload, &body)
  1195  	c.Assert(err, gc.IsNil)
  1196  	networkConf := (*body.VirtualNetworkSites)[0]
  1197  	c.Check(networkConf.Name, gc.Equals, env.getVirtualNetworkName())
  1198  	c.Check(networkConf.AffinityGroup, gc.Equals, env.getAffinityGroupName())
  1199  }
  1200  
  1201  func (*environSuite) TestDestroyVirtualNetwork(c *gc.C) {
  1202  	env := makeEnviron(c)
  1203  	// Prepare a configuration with a single virtual network.
  1204  	existingConfig := &gwacl.NetworkConfiguration{
  1205  		XMLNS: gwacl.XMLNS_NC,
  1206  		VirtualNetworkSites: &[]gwacl.VirtualNetworkSite{
  1207  			{Name: env.getVirtualNetworkName()},
  1208  		},
  1209  	}
  1210  	body, err := existingConfig.Serialize()
  1211  	c.Assert(err, gc.IsNil)
  1212  	responses := []gwacl.DispatcherResponse{
  1213  		// Return existing configuration.
  1214  		gwacl.NewDispatcherResponse([]byte(body), http.StatusOK, nil),
  1215  		// Accept upload of new configuration.
  1216  		gwacl.NewDispatcherResponse(nil, http.StatusOK, nil),
  1217  	}
  1218  	requests := gwacl.PatchManagementAPIResponses(responses)
  1219  
  1220  	env.deleteVirtualNetwork()
  1221  
  1222  	c.Assert(*requests, gc.HasLen, 2)
  1223  	// One request to get the existing network configuration.
  1224  	getRequest := (*requests)[0]
  1225  	c.Check(getRequest.Method, gc.Equals, "GET")
  1226  	// One request to update the network configuration.
  1227  	putRequest := (*requests)[1]
  1228  	c.Check(putRequest.Method, gc.Equals, "PUT")
  1229  	newConfig := gwacl.NetworkConfiguration{}
  1230  	err = xml.Unmarshal(putRequest.Payload, &newConfig)
  1231  	c.Assert(err, gc.IsNil)
  1232  	// The new configuration has no VirtualNetworkSites.
  1233  	c.Check(newConfig.VirtualNetworkSites, gc.IsNil)
  1234  }
  1235  
  1236  func (*environSuite) TestGetVirtualNetworkNameContainsEnvName(c *gc.C) {
  1237  	env := makeEnviron(c)
  1238  	c.Check(strings.Contains(env.getVirtualNetworkName(), env.Name()), jc.IsTrue)
  1239  }
  1240  
  1241  func (*environSuite) TestGetVirtualNetworkNameIsConstant(c *gc.C) {
  1242  	env := makeEnviron(c)
  1243  	c.Check(env.getVirtualNetworkName(), gc.Equals, env.getVirtualNetworkName())
  1244  }
  1245  
  1246  func (*environSuite) TestCreateAffinityGroup(c *gc.C) {
  1247  	env := makeEnviron(c)
  1248  	responses := []gwacl.DispatcherResponse{
  1249  		gwacl.NewDispatcherResponse(nil, http.StatusCreated, nil),
  1250  	}
  1251  	requests := gwacl.PatchManagementAPIResponses(responses)
  1252  
  1253  	env.createAffinityGroup()
  1254  
  1255  	c.Assert(*requests, gc.HasLen, 1)
  1256  	request := (*requests)[0]
  1257  	body := gwacl.CreateAffinityGroup{}
  1258  	err := xml.Unmarshal(request.Payload, &body)
  1259  	c.Assert(err, gc.IsNil)
  1260  	c.Check(body.Name, gc.Equals, env.getAffinityGroupName())
  1261  	// This is a testing antipattern, the expected data comes from
  1262  	// config defaults.  Fix it sometime.
  1263  	c.Check(body.Location, gc.Equals, "location")
  1264  }
  1265  
  1266  func (*environSuite) TestDestroyAffinityGroup(c *gc.C) {
  1267  	env := makeEnviron(c)
  1268  	responses := []gwacl.DispatcherResponse{
  1269  		gwacl.NewDispatcherResponse(nil, http.StatusOK, nil),
  1270  	}
  1271  	requests := gwacl.PatchManagementAPIResponses(responses)
  1272  
  1273  	env.deleteAffinityGroup()
  1274  
  1275  	c.Assert(*requests, gc.HasLen, 1)
  1276  	request := (*requests)[0]
  1277  	c.Check(strings.Contains(request.URL, env.getAffinityGroupName()), jc.IsTrue)
  1278  	c.Check(request.Method, gc.Equals, "DELETE")
  1279  }
  1280  
  1281  func (*environSuite) TestGetAffinityGroupName(c *gc.C) {
  1282  	env := makeEnviron(c)
  1283  	c.Check(strings.Contains(env.getAffinityGroupName(), env.Name()), jc.IsTrue)
  1284  }
  1285  
  1286  func (*environSuite) TestGetAffinityGroupNameIsConstant(c *gc.C) {
  1287  	env := makeEnviron(c)
  1288  	c.Check(env.getAffinityGroupName(), gc.Equals, env.getAffinityGroupName())
  1289  }
  1290  
  1291  func (*environSuite) TestGetImageMetadataSigningRequiredDefaultsToTrue(c *gc.C) {
  1292  	env := makeEnviron(c)
  1293  	// Hard-coded to true for now.  Once we support other base URLs, this
  1294  	// may have to become configurable.
  1295  	c.Check(env.getImageMetadataSigningRequired(), gc.Equals, true)
  1296  }
  1297  
  1298  func (*environSuite) TestSelectInstanceTypeAndImageUsesForcedImage(c *gc.C) {
  1299  	env := makeEnviron(c)
  1300  	forcedImage := "my-image"
  1301  	env.ecfg.attrs["force-image-name"] = forcedImage
  1302  
  1303  	aim := gwacl.RoleNameMap["ExtraLarge"]
  1304  	cons := constraints.Value{
  1305  		CpuCores: &aim.CpuCores,
  1306  		Mem:      &aim.Mem,
  1307  	}
  1308  
  1309  	instanceType, image, err := env.selectInstanceTypeAndImage(&instances.InstanceConstraint{
  1310  		Region:      "West US",
  1311  		Series:      "precise",
  1312  		Constraints: cons,
  1313  	})
  1314  	c.Assert(err, gc.IsNil)
  1315  
  1316  	c.Check(instanceType, gc.Equals, aim.Name)
  1317  	c.Check(image, gc.Equals, forcedImage)
  1318  }
  1319  
  1320  func (s *environSuite) setupEnvWithDummyMetadata(c *gc.C) *azureEnviron {
  1321  	envAttrs := makeAzureConfigMap(c)
  1322  	envAttrs["location"] = "North Europe"
  1323  	env := makeEnvironWithConfig(c, envAttrs)
  1324  	s.setDummyStorage(c, env)
  1325  	s.PatchValue(&imagemetadata.DefaultBaseURL, "")
  1326  	s.PatchValue(&signedImageDataOnly, false)
  1327  	images := []*imagemetadata.ImageMetadata{
  1328  		{
  1329  			Id:         "image-id",
  1330  			VirtType:   "Hyper-V",
  1331  			Arch:       "amd64",
  1332  			RegionName: "North Europe",
  1333  			Endpoint:   "https://management.core.windows.net/",
  1334  		},
  1335  	}
  1336  	makeTestMetadata(c, env, "precise", "North Europe", images)
  1337  	return env
  1338  }
  1339  
  1340  func (s *environSuite) TestSelectInstanceTypeAndImageUsesSimplestreamsByDefault(c *gc.C) {
  1341  	env := s.setupEnvWithDummyMetadata(c)
  1342  	// We'll tailor our constraints so as to get a specific instance type.
  1343  	aim := gwacl.RoleNameMap["ExtraSmall"]
  1344  	cons := constraints.Value{
  1345  		CpuCores: &aim.CpuCores,
  1346  		Mem:      &aim.Mem,
  1347  	}
  1348  	instanceType, image, err := env.selectInstanceTypeAndImage(&instances.InstanceConstraint{
  1349  		Region:      "North Europe",
  1350  		Series:      "precise",
  1351  		Constraints: cons,
  1352  	})
  1353  	c.Assert(err, gc.IsNil)
  1354  	c.Assert(instanceType, gc.Equals, aim.Name)
  1355  	c.Assert(image, gc.Equals, "image-id")
  1356  }
  1357  
  1358  func (*environSuite) TestExtractStorageKeyPicksPrimaryKeyIfSet(c *gc.C) {
  1359  	keys := gwacl.StorageAccountKeys{
  1360  		Primary:   "mainkey",
  1361  		Secondary: "otherkey",
  1362  	}
  1363  	c.Check(extractStorageKey(&keys), gc.Equals, "mainkey")
  1364  }
  1365  
  1366  func (*environSuite) TestExtractStorageKeyFallsBackToSecondaryKey(c *gc.C) {
  1367  	keys := gwacl.StorageAccountKeys{
  1368  		Secondary: "sparekey",
  1369  	}
  1370  	c.Check(extractStorageKey(&keys), gc.Equals, "sparekey")
  1371  }
  1372  
  1373  func (*environSuite) TestExtractStorageKeyReturnsBlankIfNoneSet(c *gc.C) {
  1374  	c.Check(extractStorageKey(&gwacl.StorageAccountKeys{}), gc.Equals, "")
  1375  }
  1376  
  1377  func assertSourceContents(c *gc.C, source simplestreams.DataSource, filename string, content []byte) {
  1378  	rc, _, err := source.Fetch(filename)
  1379  	c.Assert(err, gc.IsNil)
  1380  	defer rc.Close()
  1381  	retrieved, err := ioutil.ReadAll(rc)
  1382  	c.Assert(err, gc.IsNil)
  1383  	c.Assert(retrieved, gc.DeepEquals, content)
  1384  }
  1385  
  1386  func (s *environSuite) assertGetImageMetadataSources(c *gc.C, stream, officialSourcePath string) {
  1387  	envAttrs := makeAzureConfigMap(c)
  1388  	if stream != "" {
  1389  		envAttrs["image-stream"] = stream
  1390  	}
  1391  	env := makeEnvironWithConfig(c, envAttrs)
  1392  	s.setDummyStorage(c, env)
  1393  
  1394  	data := []byte{1, 2, 3, 4}
  1395  	env.Storage().Put("images/filename", bytes.NewReader(data), int64(len(data)))
  1396  
  1397  	sources, err := imagemetadata.GetMetadataSources(env)
  1398  	c.Assert(err, gc.IsNil)
  1399  	c.Assert(len(sources), gc.Equals, 2)
  1400  	assertSourceContents(c, sources[0], "filename", data)
  1401  	url, err := sources[1].URL("")
  1402  	c.Assert(err, gc.IsNil)
  1403  	c.Assert(url, gc.Equals, fmt.Sprintf("http://cloud-images.ubuntu.com/%s/", officialSourcePath))
  1404  }
  1405  
  1406  func (s *environSuite) TestGetImageMetadataSources(c *gc.C) {
  1407  	s.assertGetImageMetadataSources(c, "", "releases")
  1408  	s.assertGetImageMetadataSources(c, "released", "releases")
  1409  	s.assertGetImageMetadataSources(c, "daily", "daily")
  1410  }
  1411  
  1412  func (s *environSuite) TestGetToolsMetadataSources(c *gc.C) {
  1413  	env := makeEnviron(c)
  1414  	s.setDummyStorage(c, env)
  1415  
  1416  	data := []byte{1, 2, 3, 4}
  1417  	env.Storage().Put("tools/filename", bytes.NewReader(data), int64(len(data)))
  1418  
  1419  	sources, err := tools.GetMetadataSources(env)
  1420  	c.Assert(err, gc.IsNil)
  1421  	c.Assert(len(sources), gc.Equals, 1)
  1422  	assertSourceContents(c, sources[0], "filename", data)
  1423  }
  1424  
  1425  func (s *environSuite) TestCheckUnitAssignment(c *gc.C) {
  1426  	// If availability-sets-enabled is true, then placement is disabled.
  1427  	attrs := makeAzureConfigMap(c)
  1428  	attrs["availability-sets-enabled"] = true
  1429  	env := environs.Environ(makeEnvironWithConfig(c, attrs))
  1430  	err := env.SupportsUnitPlacement()
  1431  	c.Assert(err, gc.ErrorMatches, "unit placement is not supported with availability-sets-enabled")
  1432  
  1433  	// If the user disables availability sets, they can do what they want.
  1434  	attrs["availability-sets-enabled"] = false
  1435  	env = environs.Environ(makeEnvironWithConfig(c, attrs))
  1436  	err = env.SupportsUnitPlacement()
  1437  	c.Assert(err, gc.IsNil)
  1438  }
  1439  
  1440  type startInstanceSuite struct {
  1441  	baseEnvironSuite
  1442  	env    *azureEnviron
  1443  	params environs.StartInstanceParams
  1444  }
  1445  
  1446  func (s *startInstanceSuite) SetUpTest(c *gc.C) {
  1447  	s.baseEnvironSuite.SetUpTest(c)
  1448  	s.env = makeEnviron(c)
  1449  	s.setDummyStorage(c, s.env)
  1450  	s.env.ecfg.attrs["force-image-name"] = "my-image"
  1451  	stateInfo := &state.Info{
  1452  		Info: mongo.Info{
  1453  			CACert: coretesting.CACert,
  1454  			Addrs:  []string{"localhost:123"},
  1455  		},
  1456  		Password: "password",
  1457  		Tag:      "machine-1",
  1458  	}
  1459  	apiInfo := &api.Info{
  1460  		Addrs:    []string{"localhost:124"},
  1461  		CACert:   coretesting.CACert,
  1462  		Password: "admin",
  1463  		Tag:      "machine-1",
  1464  	}
  1465  	s.params = environs.StartInstanceParams{
  1466  		Tools: envtesting.AssertUploadFakeToolsVersions(
  1467  			c, s.env.storage, envtesting.V120p...,
  1468  		),
  1469  		MachineConfig: environs.NewMachineConfig(
  1470  			"1", "yanonce", nil, stateInfo, apiInfo,
  1471  		),
  1472  	}
  1473  }
  1474  
  1475  func (s *startInstanceSuite) startInstance(c *gc.C) (serviceName string, stateServer bool) {
  1476  	var called bool
  1477  	restore := testing.PatchValue(&createInstance, func(env *azureEnviron, azure *gwacl.ManagementAPI, role *gwacl.Role, serviceNameArg string, stateServerArg bool) (instance.Instance, error) {
  1478  		serviceName = serviceNameArg
  1479  		stateServer = stateServerArg
  1480  		called = true
  1481  		return nil, nil
  1482  	})
  1483  	defer restore()
  1484  	_, _, _, err := s.env.StartInstance(s.params)
  1485  	c.Assert(err, gc.IsNil)
  1486  	c.Assert(called, jc.IsTrue)
  1487  	return serviceName, stateServer
  1488  }
  1489  
  1490  func (s *startInstanceSuite) TestStartInstanceDistributionGroupError(c *gc.C) {
  1491  	s.params.DistributionGroup = func() ([]instance.Id, error) {
  1492  		return nil, fmt.Errorf("DistributionGroupError")
  1493  	}
  1494  	s.env.ecfg.attrs["availability-sets-enabled"] = true
  1495  	_, _, _, err := s.env.StartInstance(s.params)
  1496  	c.Assert(err, gc.ErrorMatches, "DistributionGroupError")
  1497  	// DistributionGroup should not be called if availability-sets-enabled=false.
  1498  	s.env.ecfg.attrs["availability-sets-enabled"] = false
  1499  	s.startInstance(c)
  1500  }
  1501  
  1502  func (s *startInstanceSuite) TestStartInstanceDistributionGroupEmpty(c *gc.C) {
  1503  	// serviceName will be empty if DistributionGroup is nil or returns nothing.
  1504  	s.env.ecfg.attrs["availability-sets-enabled"] = true
  1505  	serviceName, _ := s.startInstance(c)
  1506  	c.Assert(serviceName, gc.Equals, "")
  1507  	s.params.DistributionGroup = func() ([]instance.Id, error) { return nil, nil }
  1508  	serviceName, _ = s.startInstance(c)
  1509  	c.Assert(serviceName, gc.Equals, "")
  1510  }
  1511  
  1512  func (s *startInstanceSuite) TestStartInstanceDistributionGroup(c *gc.C) {
  1513  	s.params.DistributionGroup = func() ([]instance.Id, error) {
  1514  		return []instance.Id{
  1515  			instance.Id(s.env.getEnvPrefix() + "whatever-role0"),
  1516  		}, nil
  1517  	}
  1518  	// DistributionGroup will only have an effect if
  1519  	// availability-sets-enabled=true.
  1520  	s.env.ecfg.attrs["availability-sets-enabled"] = false
  1521  	serviceName, _ := s.startInstance(c)
  1522  	c.Assert(serviceName, gc.Equals, "")
  1523  	s.env.ecfg.attrs["availability-sets-enabled"] = true
  1524  	serviceName, _ = s.startInstance(c)
  1525  	c.Assert(serviceName, gc.Equals, "juju-testenv-whatever")
  1526  }
  1527  
  1528  func (s *startInstanceSuite) TestStartInstanceStateServerJobs(c *gc.C) {
  1529  	// If the machine has the JobManagesEnviron job,
  1530  	// we should see stateServer==true.
  1531  	s.params.MachineConfig.Jobs = []apiparams.MachineJob{
  1532  		apiparams.JobHostUnits,
  1533  	}
  1534  	_, stateServer := s.startInstance(c)
  1535  	c.Assert(stateServer, jc.IsFalse)
  1536  	s.params.MachineConfig.Jobs = []apiparams.MachineJob{
  1537  		apiparams.JobHostUnits, apiparams.JobManageEnviron,
  1538  	}
  1539  	_, stateServer = s.startInstance(c)
  1540  	c.Assert(stateServer, jc.IsTrue)
  1541  }
  1542  
  1543  func (s *environSuite) TestConstraintsValidator(c *gc.C) {
  1544  	env := s.setupEnvWithDummyMetadata(c)
  1545  	validator, err := env.ConstraintsValidator()
  1546  	c.Assert(err, gc.IsNil)
  1547  	cons := constraints.MustParse("arch=amd64 tags=bar cpu-power=10")
  1548  	unsupported, err := validator.Validate(cons)
  1549  	c.Assert(err, gc.IsNil)
  1550  	c.Assert(unsupported, jc.SameContents, []string{"cpu-power", "tags"})
  1551  }
  1552  
  1553  func (s *environSuite) TestConstraintsValidatorVocab(c *gc.C) {
  1554  	env := s.setupEnvWithDummyMetadata(c)
  1555  	validator, err := env.ConstraintsValidator()
  1556  	c.Assert(err, gc.IsNil)
  1557  	cons := constraints.MustParse("arch=ppc64")
  1558  	_, err = validator.Validate(cons)
  1559  	c.Assert(err, gc.ErrorMatches, "invalid constraint value: arch=ppc64\nvalid values are:.*")
  1560  	cons = constraints.MustParse("instance-type=foo")
  1561  	_, err = validator.Validate(cons)
  1562  	c.Assert(err, gc.ErrorMatches, "invalid constraint value: instance-type=foo\nvalid values are:.*")
  1563  }