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