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