github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/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(env, "juju-"+name+"-service1")
   208  	service2 := makeDeployment(env, "juju-"+name+"-service2")
   209  	service3 := makeDeployment(env, "notjuju-"+name+"-service3")
   210  	service4 := makeDeployment(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(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(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(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(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(env, prefix+"myservice1")
   507  	service2 := makeDeployment(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(env, prefix+"myservice1")
   526  	service2 := makeLegacyDeployment(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(env, prefix+"service1")
   547  	service2 := makeDeployment(env, prefix+"service2")
   548  	service3 := makeLegacyDeployment(env, prefix+"service3")
   549  	service4 := makeDeployment(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  	// How many names have been attempted, and how often?
   711  	// There is a minute chance that this tries the same name twice, and
   712  	// then this test will fail.  If that happens, try seeding the
   713  	// randomizer with some fixed seed that doens't produce the problem.
   714  	attemptedNames := make(map[string]int)
   715  	for _, request := range *requests {
   716  		// Exit the loop if we hit the request to create the service, it comes
   717  		// after the check calls.
   718  		if request.Method == "POST" {
   719  			break
   720  		}
   721  		// Name is the last part of the URL from the GET requests that check
   722  		// availability.
   723  		_, name := path.Split(strings.TrimRight(request.URL, "/"))
   724  		attemptedNames[name] += 1
   725  	}
   726  	// The three attempts we just made all had different service names.
   727  	c.Check(attemptedNames, gc.HasLen, 3)
   728  
   729  	// Once newHostedService succeeds, we get a hosted service with the
   730  	// name returned from GetHostedServiceProperties.
   731  	c.Check(service.ServiceName, gc.Equals, "anything")
   732  }
   733  
   734  func (*environSuite) TestNewHostedServiceFailsIfUnableToFindUniqueName(c *gc.C) {
   735  	errorBody := makeNonAvailabilityResponse(c)
   736  	responses := []gwacl.DispatcherResponse{}
   737  	for counter := 0; counter < 100; counter++ {
   738  		responses = append(responses, gwacl.NewDispatcherResponse(errorBody, http.StatusOK, nil))
   739  	}
   740  	gwacl.PatchManagementAPIResponses(responses)
   741  	azure, err := gwacl.NewManagementAPI("subscription", "", "West US")
   742  	c.Assert(err, jc.ErrorIsNil)
   743  
   744  	_, err = newHostedService(azure, "service", "affinity-group", "")
   745  	c.Assert(err, gc.NotNil)
   746  	c.Check(err, gc.ErrorMatches, "could not come up with a unique hosted service name.*")
   747  }
   748  
   749  func buildGetServicePropertiesResponses(c *gc.C, services ...*gwacl.HostedService) []gwacl.DispatcherResponse {
   750  	responses := make([]gwacl.DispatcherResponse, len(services))
   751  	for i, service := range services {
   752  		serviceXML, err := service.Serialize()
   753  		c.Assert(err, jc.ErrorIsNil)
   754  		responses[i] = gwacl.NewDispatcherResponse([]byte(serviceXML), http.StatusOK, nil)
   755  	}
   756  	return responses
   757  }
   758  
   759  func buildStatusOKResponses(c *gc.C, n int) []gwacl.DispatcherResponse {
   760  	responses := make([]gwacl.DispatcherResponse, n)
   761  	for i := range responses {
   762  		responses[i] = gwacl.NewDispatcherResponse(nil, http.StatusOK, nil)
   763  	}
   764  	return responses
   765  }
   766  
   767  func makeAzureService(name string) *gwacl.HostedService {
   768  	return &gwacl.HostedService{
   769  		HostedServiceDescriptor: gwacl.HostedServiceDescriptor{ServiceName: name},
   770  	}
   771  }
   772  
   773  func makeRole(env *azureEnviron) *gwacl.Role {
   774  	size := "Large"
   775  	vhd := env.newOSDisk("source-image-name")
   776  	userData := "example-user-data"
   777  	return env.newRole(size, vhd, userData, false)
   778  }
   779  
   780  func makeLegacyDeployment(env *azureEnviron, serviceName string) *gwacl.HostedService {
   781  	service := makeAzureService(serviceName)
   782  	service.Deployments = []gwacl.Deployment{{
   783  		Name:     serviceName,
   784  		RoleList: []gwacl.Role{*makeRole(env)},
   785  	}}
   786  	return service
   787  }
   788  
   789  func makeDeployment(env *azureEnviron, serviceName string) *gwacl.HostedService {
   790  	service := makeAzureService(serviceName)
   791  	service.Deployments = []gwacl.Deployment{{
   792  		Name:     serviceName + "-v2",
   793  		RoleList: []gwacl.Role{*makeRole(env), *makeRole(env)},
   794  	}}
   795  	return service
   796  }
   797  
   798  func (s *environSuite) TestStopInstancesDestroysMachines(c *gc.C) {
   799  	env := makeEnviron(c)
   800  	prefix := env.getEnvPrefix()
   801  	service1Name := "service1"
   802  	service1 := makeLegacyDeployment(env, prefix+service1Name)
   803  	service2Name := "service2"
   804  	service2 := makeDeployment(env, prefix+service2Name)
   805  
   806  	inst1, err := env.getInstance(service1, "")
   807  	c.Assert(err, jc.ErrorIsNil)
   808  	role2Name := service2.Deployments[0].RoleList[0].RoleName
   809  	inst2, err := env.getInstance(service2, role2Name)
   810  	c.Assert(err, jc.ErrorIsNil)
   811  	role3Name := service2.Deployments[0].RoleList[1].RoleName
   812  	inst3, err := env.getInstance(service2, role3Name)
   813  	c.Assert(err, jc.ErrorIsNil)
   814  
   815  	responses := buildGetServicePropertiesResponses(c, service1)
   816  	responses = append(responses, buildStatusOKResponses(c, 1)...) // DeleteHostedService
   817  	responses = append(responses, buildGetServicePropertiesResponses(c, service2)...)
   818  	responses = append(responses, buildStatusOKResponses(c, 1)...) // DeleteHostedService
   819  	requests := gwacl.PatchManagementAPIResponses(responses)
   820  	err = env.StopInstances(inst1.Id(), inst2.Id(), inst3.Id())
   821  	c.Check(err, jc.ErrorIsNil)
   822  
   823  	// One GET and DELETE per service
   824  	// (GetHostedServiceProperties and DeleteHostedService).
   825  	c.Check(len(*requests), gc.Equals, len(responses))
   826  	assertOneRequestMatches(c, *requests, "GET", ".*"+service1Name+".")
   827  	assertOneRequestMatches(c, *requests, "GET", ".*"+service2Name+".*")
   828  	assertOneRequestMatches(c, *requests, "DELETE", ".*"+service1Name+".*")
   829  	assertOneRequestMatches(c, *requests, "DELETE", ".*"+service2Name+".*")
   830  }
   831  
   832  func (s *environSuite) TestStopInstancesServiceSubset(c *gc.C) {
   833  	env := makeEnviron(c)
   834  	service := makeDeployment(env, env.getEnvPrefix()+"service")
   835  
   836  	role1Name := service.Deployments[0].RoleList[0].RoleName
   837  	inst1, err := env.getInstance(service, role1Name)
   838  	c.Assert(err, jc.ErrorIsNil)
   839  
   840  	responses := buildGetServicePropertiesResponses(c, service)
   841  	responses = append(responses, buildStatusOKResponses(c, 1)...) // DeleteRole
   842  	requests := gwacl.PatchManagementAPIResponses(responses)
   843  	err = env.StopInstances(inst1.Id())
   844  	c.Check(err, jc.ErrorIsNil)
   845  
   846  	// One GET for the service, and one DELETE for the role.
   847  	// The service isn't deleted because it has two roles,
   848  	// and only one is being deleted.
   849  	c.Check(len(*requests), gc.Equals, len(responses))
   850  	assertOneRequestMatches(c, *requests, "GET", ".*"+service.ServiceName+".")
   851  	assertOneRequestMatches(c, *requests, "DELETE", ".*"+role1Name+".*")
   852  }
   853  
   854  func (s *environSuite) TestStopInstancesWhenStoppingMachinesFails(c *gc.C) {
   855  	env := makeEnviron(c)
   856  	prefix := env.getEnvPrefix()
   857  	service1 := makeDeployment(env, prefix+"service1")
   858  	service2 := makeDeployment(env, prefix+"service2")
   859  	service1Role1Name := service1.Deployments[0].RoleList[0].RoleName
   860  	inst1, err := env.getInstance(service1, service1Role1Name)
   861  	c.Assert(err, jc.ErrorIsNil)
   862  	service2Role1Name := service2.Deployments[0].RoleList[0].RoleName
   863  	inst2, err := env.getInstance(service2, service2Role1Name)
   864  	c.Assert(err, jc.ErrorIsNil)
   865  
   866  	responses := buildGetServicePropertiesResponses(c, service1)
   867  	// Failed to delete one of the services. This will cause StopInstances to stop
   868  	// immediately.
   869  	responses = append(responses, gwacl.NewDispatcherResponse(nil, http.StatusConflict, nil))
   870  	requests := gwacl.PatchManagementAPIResponses(responses)
   871  
   872  	err = env.StopInstances(inst1.Id(), inst2.Id())
   873  	c.Check(err, gc.ErrorMatches, ".*Conflict.*")
   874  
   875  	c.Check(len(*requests), gc.Equals, len(responses))
   876  	assertOneRequestMatches(c, *requests, "GET", ".*"+service1.ServiceName+".*")
   877  	assertOneRequestMatches(c, *requests, "DELETE", service1.ServiceName)
   878  }
   879  
   880  func (s *environSuite) TestStopInstancesWithZeroInstance(c *gc.C) {
   881  	env := makeEnviron(c)
   882  	err := env.StopInstances()
   883  	c.Check(err, jc.ErrorIsNil)
   884  }
   885  
   886  // getVnetCleanupResponse returns the response
   887  // that a fake http server should return when gwacl's
   888  // RemoveVirtualNetworkSite() is called.
   889  func getVnetCleanupResponse(c *gc.C) gwacl.DispatcherResponse {
   890  	existingConfig := &gwacl.NetworkConfiguration{
   891  		XMLNS:               gwacl.XMLNS_NC,
   892  		VirtualNetworkSites: nil,
   893  	}
   894  	body, err := existingConfig.Serialize()
   895  	c.Assert(err, jc.ErrorIsNil)
   896  	return gwacl.NewDispatcherResponse([]byte(body), http.StatusOK, nil)
   897  }
   898  
   899  func (s *environSuite) TestDestroyDoesNotCleanStorageIfError(c *gc.C) {
   900  	env := makeEnviron(c)
   901  	s.setDummyStorage(c, env)
   902  
   903  	// Populate storage.
   904  	err := env.Storage().Put("anything", strings.NewReader(""), 0)
   905  	c.Assert(err, jc.ErrorIsNil)
   906  
   907  	responses := []gwacl.DispatcherResponse{
   908  		gwacl.NewDispatcherResponse(nil, http.StatusBadRequest, nil),
   909  	}
   910  	gwacl.PatchManagementAPIResponses(responses)
   911  
   912  	err = env.Destroy()
   913  	c.Check(err, gc.NotNil)
   914  
   915  	files, err := storage.List(env.Storage(), "")
   916  	c.Assert(err, jc.ErrorIsNil)
   917  	c.Check(files, gc.DeepEquals, []string{"anything"})
   918  }
   919  
   920  func (s *environSuite) TestDestroyCleansUpStorage(c *gc.C) {
   921  	env := makeEnviron(c)
   922  	s.setDummyStorage(c, env)
   923  	// Populate storage.
   924  	err := env.Storage().Put("anything", strings.NewReader(""), 0)
   925  	c.Assert(err, jc.ErrorIsNil)
   926  	responses := getAzureServiceListResponse(c)
   927  	responses = append(responses, getVnetCleanupResponse(c))
   928  	responses = append(responses, buildStatusOKResponses(c, 1)...) // DeleteAffinityGroup
   929  	gwacl.PatchManagementAPIResponses(responses)
   930  
   931  	err = env.Destroy()
   932  	c.Check(err, jc.ErrorIsNil)
   933  
   934  	files, err := storage.List(env.Storage(), "")
   935  	c.Assert(err, jc.ErrorIsNil)
   936  	c.Check(files, gc.HasLen, 0)
   937  }
   938  
   939  func (s *environSuite) TestDestroyDeletesVirtualNetworkAndAffinityGroup(c *gc.C) {
   940  	env := makeEnviron(c)
   941  	s.setDummyStorage(c, env)
   942  	responses := getAzureServiceListResponse(c)
   943  	// Prepare a configuration with a single virtual network.
   944  	existingConfig := &gwacl.NetworkConfiguration{
   945  		XMLNS: gwacl.XMLNS_NC,
   946  		VirtualNetworkSites: &[]gwacl.VirtualNetworkSite{
   947  			{Name: env.getVirtualNetworkName()},
   948  		},
   949  	}
   950  	body, err := existingConfig.Serialize()
   951  	c.Assert(err, jc.ErrorIsNil)
   952  	cleanupResponses := []gwacl.DispatcherResponse{
   953  		// Return existing configuration.
   954  		gwacl.NewDispatcherResponse([]byte(body), http.StatusOK, nil),
   955  		// Accept upload of new configuration.
   956  		gwacl.NewDispatcherResponse(nil, http.StatusOK, nil),
   957  		// Accept deletion of affinity group.
   958  		gwacl.NewDispatcherResponse(nil, http.StatusOK, nil),
   959  	}
   960  	responses = append(responses, cleanupResponses...)
   961  	requests := gwacl.PatchManagementAPIResponses(responses)
   962  
   963  	err = env.Destroy()
   964  	c.Check(err, jc.ErrorIsNil)
   965  
   966  	c.Assert(*requests, gc.HasLen, 4)
   967  	// One request to get the network configuration.
   968  	getRequest := (*requests)[1]
   969  	c.Check(getRequest.Method, gc.Equals, "GET")
   970  	c.Check(strings.HasSuffix(getRequest.URL, "services/networking/media"), jc.IsTrue)
   971  	// One request to upload the new version of the network configuration.
   972  	putRequest := (*requests)[2]
   973  	c.Check(putRequest.Method, gc.Equals, "PUT")
   974  	c.Check(strings.HasSuffix(putRequest.URL, "services/networking/media"), jc.IsTrue)
   975  	// One request to delete the Affinity Group.
   976  	agRequest := (*requests)[3]
   977  	c.Check(strings.Contains(agRequest.URL, env.getAffinityGroupName()), jc.IsTrue)
   978  	c.Check(agRequest.Method, gc.Equals, "DELETE")
   979  }
   980  
   981  func (s *environSuite) TestDestroyDoesNotFailIfVirtualNetworkDeletionFails(c *gc.C) {
   982  	env := makeEnviron(c)
   983  	s.setDummyStorage(c, env)
   984  	responses := getAzureServiceListResponse(c)
   985  	cleanupResponses := []gwacl.DispatcherResponse{
   986  		// Fail to get vnet for deletion
   987  		gwacl.NewDispatcherResponse(nil, http.StatusConflict, nil),
   988  		// Fail to delete affinity group
   989  		gwacl.NewDispatcherResponse(nil, http.StatusConflict, nil),
   990  	}
   991  	responses = append(responses, cleanupResponses...)
   992  	requests := gwacl.PatchManagementAPIResponses(responses)
   993  
   994  	err := env.Destroy()
   995  	c.Check(err, jc.ErrorIsNil)
   996  	c.Assert(*requests, gc.HasLen, 3)
   997  
   998  	getRequest := (*requests)[1]
   999  	c.Check(getRequest.Method, gc.Equals, "GET")
  1000  	c.Check(strings.HasSuffix(getRequest.URL, "services/networking/media"), jc.IsTrue)
  1001  
  1002  	deleteRequest := (*requests)[2]
  1003  	c.Check(deleteRequest.Method, gc.Equals, "DELETE")
  1004  	c.Check(strings.Contains(deleteRequest.URL, env.getAffinityGroupName()), jc.IsTrue)
  1005  }
  1006  
  1007  func (s *environSuite) TestDestroyDoesNotFailIfAffinityGroupDeletionFails(c *gc.C) {
  1008  	env := makeEnviron(c)
  1009  	s.setDummyStorage(c, env)
  1010  	responses := getAzureServiceListResponse(c)
  1011  	// Prepare a configuration with a single virtual network.
  1012  	existingConfig := &gwacl.NetworkConfiguration{
  1013  		XMLNS: gwacl.XMLNS_NC,
  1014  		VirtualNetworkSites: &[]gwacl.VirtualNetworkSite{
  1015  			{Name: env.getVirtualNetworkName()},
  1016  		},
  1017  	}
  1018  	body, err := existingConfig.Serialize()
  1019  	c.Assert(err, jc.ErrorIsNil)
  1020  	cleanupResponses := []gwacl.DispatcherResponse{
  1021  		// Return existing configuration.
  1022  		gwacl.NewDispatcherResponse([]byte(body), http.StatusOK, nil),
  1023  		// Accept upload of new configuration.
  1024  		gwacl.NewDispatcherResponse(nil, http.StatusOK, nil),
  1025  		// Fail to delete affinity group
  1026  		gwacl.NewDispatcherResponse(nil, http.StatusConflict, nil),
  1027  	}
  1028  	responses = append(responses, cleanupResponses...)
  1029  	requests := gwacl.PatchManagementAPIResponses(responses)
  1030  
  1031  	err = env.Destroy()
  1032  	c.Check(err, jc.ErrorIsNil)
  1033  	c.Assert(*requests, gc.HasLen, 4)
  1034  
  1035  	getRequest := (*requests)[1]
  1036  	c.Check(getRequest.Method, gc.Equals, "GET")
  1037  	c.Check(strings.HasSuffix(getRequest.URL, "services/networking/media"), jc.IsTrue)
  1038  	putRequest := (*requests)[2]
  1039  	c.Check(putRequest.Method, gc.Equals, "PUT")
  1040  	c.Check(strings.HasSuffix(putRequest.URL, "services/networking/media"), jc.IsTrue)
  1041  }
  1042  
  1043  var emptyListResponse = `
  1044    <?xml version="1.0" encoding="utf-8"?>
  1045    <EnumerationResults ContainerName="http://myaccount.blob.core.windows.net/mycontainer">
  1046      <Prefix>prefix</Prefix>
  1047      <Marker>marker</Marker>
  1048      <MaxResults>maxresults</MaxResults>
  1049      <Delimiter>delimiter</Delimiter>
  1050      <Blobs></Blobs>
  1051      <NextMarker />
  1052    </EnumerationResults>`
  1053  
  1054  // assertOneRequestMatches asserts that at least one request in the given slice
  1055  // contains a request with the given method and whose URL matches the given regexp.
  1056  func assertOneRequestMatches(c *gc.C, requests []*gwacl.X509Request, method string, urlPattern string) {
  1057  	for _, request := range requests {
  1058  		matched, err := regexp.MatchString(urlPattern, request.URL)
  1059  		if err == nil && request.Method == method && matched {
  1060  			return
  1061  		}
  1062  	}
  1063  	c.Error(fmt.Sprintf("none of the requests matches: Method=%v, URL pattern=%v", method, urlPattern))
  1064  }
  1065  
  1066  func (s *environSuite) TestDestroyStopsAllInstances(c *gc.C) {
  1067  	env := makeEnviron(c)
  1068  	s.setDummyStorage(c, env)
  1069  	name := env.Config().Name()
  1070  	service1 := makeDeployment(env, "juju-"+name+"-service1")
  1071  	service2 := makeDeployment(env, "juju-"+name+"-service2")
  1072  	service3 := makeDeployment(env, "juju-"+name+"-1-service3")
  1073  
  1074  	// The call to AllInstances() will return only one service (service1).
  1075  	responses := getAzureServiceListResponse(
  1076  		c, service1.HostedServiceDescriptor, service2.HostedServiceDescriptor, service3.HostedServiceDescriptor,
  1077  	)
  1078  	responses = append(responses, buildStatusOKResponses(c, 2)...) // DeleteHostedService
  1079  	responses = append(responses, getVnetCleanupResponse(c))
  1080  	responses = append(responses, buildStatusOKResponses(c, 1)...) // DeleteAffinityGroup
  1081  	requests := gwacl.PatchManagementAPIResponses(responses)
  1082  
  1083  	err := env.Destroy()
  1084  	c.Check(err, jc.ErrorIsNil)
  1085  
  1086  	// One request to get the list of all the environment's instances.
  1087  	// One delete request per destroyed service, and two additional
  1088  	// requests to delete the Virtual Network and the Affinity Group.
  1089  	c.Check((*requests), gc.HasLen, 5)
  1090  	c.Check((*requests)[0].Method, gc.Equals, "GET")
  1091  	assertOneRequestMatches(c, *requests, "DELETE", ".*"+service1.ServiceName+".*")
  1092  	assertOneRequestMatches(c, *requests, "DELETE", ".*"+service2.ServiceName+".*")
  1093  }
  1094  
  1095  func (s *environSuite) TestGetInstance(c *gc.C) {
  1096  	env := makeEnviron(c)
  1097  	service1 := makeLegacyDeployment(env, "service1")
  1098  	service2 := makeDeployment(env, "service1")
  1099  
  1100  	// azureEnviron.Instances will call getInstance with roleName==""
  1101  	// for legacy instances. This will cause getInstance to get the
  1102  	// one and only role (or error if there is more than one).
  1103  	inst1, err := env.getInstance(service1, "")
  1104  	c.Assert(err, jc.ErrorIsNil)
  1105  	c.Check(inst1.Id(), gc.Equals, instance.Id("service1"))
  1106  	c.Assert(inst1, gc.FitsTypeOf, &azureInstance{})
  1107  	c.Check(inst1.(*azureInstance).environ, gc.Equals, env)
  1108  	c.Check(inst1.(*azureInstance).roleName, gc.Equals, service1.Deployments[0].RoleList[0].RoleName)
  1109  	service1.Deployments[0].RoleList = service2.Deployments[0].RoleList
  1110  	inst1, err = env.getInstance(service1, "")
  1111  	c.Check(err, gc.ErrorMatches, `expected one role for "service1", got 2`)
  1112  
  1113  	inst2, err := env.getInstance(service2, service2.Deployments[0].RoleList[0].RoleName)
  1114  	c.Assert(err, jc.ErrorIsNil)
  1115  	c.Check(inst2.Id(), gc.Equals, instance.Id("service1-"+service2.Deployments[0].RoleList[0].RoleName))
  1116  }
  1117  
  1118  func (s *environSuite) TestInitialPorts(c *gc.C) {
  1119  	env := makeEnviron(c)
  1120  	service1 := makeLegacyDeployment(env, "service1")
  1121  	service2 := makeDeployment(env, "service2")
  1122  	service3 := makeDeployment(env, "service3")
  1123  	service3.Label = base64.StdEncoding.EncodeToString([]byte(stateServerLabel))
  1124  
  1125  	role1 := &service1.Deployments[0].RoleList[0]
  1126  	inst1, err := env.getInstance(service1, role1.RoleName)
  1127  	c.Assert(err, jc.ErrorIsNil)
  1128  	c.Assert(inst1.(*azureInstance).maskStateServerPorts, jc.IsTrue)
  1129  	role2 := &service2.Deployments[0].RoleList[0]
  1130  	inst2, err := env.getInstance(service2, role2.RoleName)
  1131  	c.Assert(err, jc.ErrorIsNil)
  1132  	role3 := &service3.Deployments[0].RoleList[0]
  1133  	inst3, err := env.getInstance(service3, role3.RoleName)
  1134  	c.Assert(err, jc.ErrorIsNil)
  1135  
  1136  	// Only role2 should report opened state server ports via the Ports method.
  1137  	dummyRole := *role1
  1138  	configSetNetwork(&dummyRole).InputEndpoints = &[]gwacl.InputEndpoint{{
  1139  		LocalPort: env.Config().APIPort(),
  1140  		Protocol:  "tcp",
  1141  		Name:      "apiserver",
  1142  		Port:      env.Config().APIPort(),
  1143  	}}
  1144  	reportsStateServerPorts := func(inst instance.Instance) bool {
  1145  		responses := preparePortChangeConversation(c, &dummyRole)
  1146  		gwacl.PatchManagementAPIResponses(responses)
  1147  		ports, err := inst.Ports("")
  1148  		c.Assert(err, jc.ErrorIsNil)
  1149  		portmap := make(map[network.PortRange]bool)
  1150  		for _, portRange := range ports {
  1151  			portmap[portRange] = true
  1152  		}
  1153  		apiPortRange := network.PortRange{
  1154  			Protocol: "tcp",
  1155  			FromPort: env.Config().APIPort(),
  1156  			ToPort:   env.Config().APIPort(),
  1157  		}
  1158  		return portmap[apiPortRange]
  1159  	}
  1160  	c.Check(inst1, gc.Not(jc.Satisfies), reportsStateServerPorts)
  1161  	c.Check(inst2, jc.Satisfies, reportsStateServerPorts)
  1162  	c.Check(inst3, gc.Not(jc.Satisfies), reportsStateServerPorts)
  1163  }
  1164  
  1165  func (*environSuite) TestNewOSVirtualDisk(c *gc.C) {
  1166  	env := makeEnviron(c)
  1167  	sourceImageName := "source-image-name"
  1168  
  1169  	vhd := env.newOSDisk(sourceImageName)
  1170  
  1171  	mediaLinkUrl, err := url.Parse(vhd.MediaLink)
  1172  	c.Check(err, jc.ErrorIsNil)
  1173  	storageAccount := env.ecfg.storageAccountName()
  1174  	c.Check(mediaLinkUrl.Host, gc.Equals, fmt.Sprintf("%s.blob.core.windows.net", storageAccount))
  1175  	c.Check(vhd.SourceImageName, gc.Equals, sourceImageName)
  1176  }
  1177  
  1178  // mapInputEndpointsByPort takes a slice of input endpoints, and returns them
  1179  // as a map keyed by their (external) ports.  This makes it easier to query
  1180  // individual endpoints from an array whose ordering you don't know.
  1181  // Multiple input endpoints for the same port are treated as an error.
  1182  func mapInputEndpointsByPort(c *gc.C, endpoints []gwacl.InputEndpoint) map[int]gwacl.InputEndpoint {
  1183  	mapping := make(map[int]gwacl.InputEndpoint)
  1184  	for _, endpoint := range endpoints {
  1185  		_, have := mapping[endpoint.Port]
  1186  		c.Assert(have, jc.IsFalse)
  1187  		mapping[endpoint.Port] = endpoint
  1188  	}
  1189  	return mapping
  1190  }
  1191  
  1192  func (s *environSuite) TestNewRole(c *gc.C) {
  1193  	s.testNewRole(c, false)
  1194  }
  1195  
  1196  func (s *environSuite) TestNewRoleStateServer(c *gc.C) {
  1197  	s.testNewRole(c, true)
  1198  }
  1199  
  1200  func (*environSuite) testNewRole(c *gc.C, stateServer bool) {
  1201  	env := makeEnviron(c)
  1202  	size := "Large"
  1203  	vhd := env.newOSDisk("source-image-name")
  1204  	userData := "example-user-data"
  1205  
  1206  	role := env.newRole(size, vhd, userData, stateServer)
  1207  
  1208  	configs := role.ConfigurationSets
  1209  	linuxConfig := configs[0]
  1210  	networkConfig := configs[1]
  1211  	c.Check(linuxConfig.CustomData, gc.Equals, userData)
  1212  	c.Check(linuxConfig.Hostname, gc.Equals, role.RoleName)
  1213  	c.Check(linuxConfig.Username, gc.Not(gc.Equals), "")
  1214  	c.Check(linuxConfig.Password, gc.Not(gc.Equals), "")
  1215  	c.Check(linuxConfig.DisableSSHPasswordAuthentication, gc.Equals, "true")
  1216  	c.Check(role.RoleSize, gc.Equals, size)
  1217  	c.Check(role.OSVirtualHardDisk, gc.DeepEquals, vhd)
  1218  
  1219  	endpoints := mapInputEndpointsByPort(c, *networkConfig.InputEndpoints)
  1220  
  1221  	// The network config contains an endpoint for ssh communication.
  1222  	sshEndpoint, ok := endpoints[22]
  1223  	c.Assert(ok, jc.IsTrue)
  1224  	c.Check(sshEndpoint.LocalPort, gc.Equals, 22)
  1225  	c.Check(sshEndpoint.Protocol, gc.Equals, "tcp")
  1226  
  1227  	if stateServer {
  1228  		// There should be an endpoint for the API port.
  1229  		apiEndpoint, ok := endpoints[env.Config().APIPort()]
  1230  		c.Assert(ok, jc.IsTrue)
  1231  		c.Check(apiEndpoint.LocalPort, gc.Equals, env.Config().APIPort())
  1232  		c.Check(apiEndpoint.Protocol, gc.Equals, "tcp")
  1233  	}
  1234  }
  1235  
  1236  func (*environSuite) TestProviderReturnsAzureEnvironProvider(c *gc.C) {
  1237  	prov := makeEnviron(c).Provider()
  1238  	c.Assert(prov, gc.NotNil)
  1239  	azprov, ok := prov.(azureEnvironProvider)
  1240  	c.Assert(ok, jc.IsTrue)
  1241  	c.Check(azprov, gc.NotNil)
  1242  }
  1243  
  1244  func (*environSuite) TestCreateVirtualNetwork(c *gc.C) {
  1245  	env := makeEnviron(c)
  1246  	responses := []gwacl.DispatcherResponse{
  1247  		// No existing configuration found.
  1248  		gwacl.NewDispatcherResponse(nil, http.StatusNotFound, nil),
  1249  		// Accept upload of new configuration.
  1250  		gwacl.NewDispatcherResponse(nil, http.StatusOK, nil),
  1251  	}
  1252  	requests := gwacl.PatchManagementAPIResponses(responses)
  1253  
  1254  	env.createVirtualNetwork()
  1255  
  1256  	c.Assert(*requests, gc.HasLen, 2)
  1257  	request := (*requests)[1]
  1258  	body := gwacl.NetworkConfiguration{}
  1259  	err := xml.Unmarshal(request.Payload, &body)
  1260  	c.Assert(err, jc.ErrorIsNil)
  1261  	networkConf := (*body.VirtualNetworkSites)[0]
  1262  	c.Check(networkConf.Name, gc.Equals, env.getVirtualNetworkName())
  1263  	c.Check(networkConf.AffinityGroup, gc.Equals, "")
  1264  	c.Check(networkConf.Location, gc.Equals, "location")
  1265  }
  1266  
  1267  func (*environSuite) TestDestroyVirtualNetwork(c *gc.C) {
  1268  	env := makeEnviron(c)
  1269  	// Prepare a configuration with a single virtual network.
  1270  	existingConfig := &gwacl.NetworkConfiguration{
  1271  		XMLNS: gwacl.XMLNS_NC,
  1272  		VirtualNetworkSites: &[]gwacl.VirtualNetworkSite{
  1273  			{Name: env.getVirtualNetworkName()},
  1274  		},
  1275  	}
  1276  	body, err := existingConfig.Serialize()
  1277  	c.Assert(err, jc.ErrorIsNil)
  1278  	responses := []gwacl.DispatcherResponse{
  1279  		// Return existing configuration.
  1280  		gwacl.NewDispatcherResponse([]byte(body), http.StatusOK, nil),
  1281  		// Accept upload of new configuration.
  1282  		gwacl.NewDispatcherResponse(nil, http.StatusOK, nil),
  1283  	}
  1284  	requests := gwacl.PatchManagementAPIResponses(responses)
  1285  
  1286  	env.deleteVirtualNetwork()
  1287  
  1288  	c.Assert(*requests, gc.HasLen, 2)
  1289  	// One request to get the existing network configuration.
  1290  	getRequest := (*requests)[0]
  1291  	c.Check(getRequest.Method, gc.Equals, "GET")
  1292  	// One request to update the network configuration.
  1293  	putRequest := (*requests)[1]
  1294  	c.Check(putRequest.Method, gc.Equals, "PUT")
  1295  	newConfig := gwacl.NetworkConfiguration{}
  1296  	err = xml.Unmarshal(putRequest.Payload, &newConfig)
  1297  	c.Assert(err, jc.ErrorIsNil)
  1298  	// The new configuration has no VirtualNetworkSites.
  1299  	c.Check(newConfig.VirtualNetworkSites, gc.IsNil)
  1300  }
  1301  
  1302  func (*environSuite) TestGetVirtualNetworkNameContainsEnvName(c *gc.C) {
  1303  	env := makeEnviron(c)
  1304  	c.Check(strings.Contains(env.getVirtualNetworkName(), env.Config().Name()), jc.IsTrue)
  1305  }
  1306  
  1307  func (*environSuite) TestGetVirtualNetworkNameIsConstant(c *gc.C) {
  1308  	env := makeEnviron(c)
  1309  	c.Check(env.getVirtualNetworkName(), gc.Equals, env.getVirtualNetworkName())
  1310  }
  1311  
  1312  func (*environSuite) TestCreateAffinityGroup(c *gc.C) {
  1313  	env := makeEnviron(c)
  1314  	responses := []gwacl.DispatcherResponse{
  1315  		gwacl.NewDispatcherResponse(nil, http.StatusCreated, nil),
  1316  	}
  1317  	requests := gwacl.PatchManagementAPIResponses(responses)
  1318  
  1319  	env.createAffinityGroup()
  1320  
  1321  	c.Assert(*requests, gc.HasLen, 1)
  1322  	request := (*requests)[0]
  1323  	body := gwacl.CreateAffinityGroup{}
  1324  	err := xml.Unmarshal(request.Payload, &body)
  1325  	c.Assert(err, jc.ErrorIsNil)
  1326  	c.Check(body.Name, gc.Equals, env.getAffinityGroupName())
  1327  	// This is a testing antipattern, the expected data comes from
  1328  	// config defaults.  Fix it sometime.
  1329  	c.Check(body.Location, gc.Equals, "location")
  1330  }
  1331  
  1332  func (*environSuite) TestDestroyAffinityGroup(c *gc.C) {
  1333  	env := makeEnviron(c)
  1334  	responses := []gwacl.DispatcherResponse{
  1335  		gwacl.NewDispatcherResponse(nil, http.StatusOK, nil),
  1336  	}
  1337  	requests := gwacl.PatchManagementAPIResponses(responses)
  1338  
  1339  	env.deleteAffinityGroup()
  1340  
  1341  	c.Assert(*requests, gc.HasLen, 1)
  1342  	request := (*requests)[0]
  1343  	c.Check(strings.Contains(request.URL, env.getAffinityGroupName()), jc.IsTrue)
  1344  	c.Check(request.Method, gc.Equals, "DELETE")
  1345  }
  1346  
  1347  func (*environSuite) TestGetAffinityGroupName(c *gc.C) {
  1348  	env := makeEnviron(c)
  1349  	c.Check(strings.Contains(env.getAffinityGroupName(), env.Config().Name()), jc.IsTrue)
  1350  }
  1351  
  1352  func (*environSuite) TestGetAffinityGroupNameIsConstant(c *gc.C) {
  1353  	env := makeEnviron(c)
  1354  	c.Check(env.getAffinityGroupName(), gc.Equals, env.getAffinityGroupName())
  1355  }
  1356  
  1357  func (s *environSuite) TestSelectInstanceTypeAndImageUsesForcedImage(c *gc.C) {
  1358  	env := s.setupEnvWithDummyMetadata(c)
  1359  	forcedImage := "my-image"
  1360  	env.ecfg.attrs["force-image-name"] = forcedImage
  1361  
  1362  	aim := roleSizeByName("ExtraLarge")
  1363  	cons := constraints.Value{
  1364  		CpuCores: &aim.CpuCores,
  1365  		Mem:      &aim.Mem,
  1366  	}
  1367  
  1368  	instanceType, image, err := env.selectInstanceTypeAndImage(&instances.InstanceConstraint{
  1369  		Region:      "West US",
  1370  		Series:      coretesting.FakeDefaultSeries,
  1371  		Constraints: cons,
  1372  	})
  1373  	c.Assert(err, jc.ErrorIsNil)
  1374  
  1375  	c.Check(instanceType.Name, gc.Equals, aim.Name)
  1376  	c.Check(image, gc.Equals, forcedImage)
  1377  }
  1378  
  1379  func (s *baseEnvironSuite) setupEnvWithDummyMetadata(c *gc.C) *azureEnviron {
  1380  	envAttrs := makeAzureConfigMap(c)
  1381  	envAttrs["location"] = "North Europe"
  1382  	env := makeEnvironWithConfig(c, envAttrs)
  1383  	_, supported := environs.SupportsNetworking(env)
  1384  	c.Assert(supported, jc.IsFalse)
  1385  	s.setDummyStorage(c, env)
  1386  	images := []*imagemetadata.ImageMetadata{
  1387  		{
  1388  			Id:         "image-id",
  1389  			VirtType:   "Hyper-V",
  1390  			Arch:       "amd64",
  1391  			RegionName: "North Europe",
  1392  			Endpoint:   "https://management.core.windows.net/",
  1393  		},
  1394  	}
  1395  	s.makeTestMetadata(c, coretesting.FakeDefaultSeries, "North Europe", images)
  1396  	return env
  1397  }
  1398  
  1399  func (s *environSuite) TestSelectInstanceTypeAndImageUsesSimplestreamsByDefault(c *gc.C) {
  1400  	env := s.setupEnvWithDummyMetadata(c)
  1401  	// We'll tailor our constraints so as to get a specific instance type.
  1402  	aim := roleSizeByName("ExtraSmall")
  1403  	cons := constraints.Value{
  1404  		CpuCores: &aim.CpuCores,
  1405  		Mem:      &aim.Mem,
  1406  	}
  1407  	instanceType, image, err := env.selectInstanceTypeAndImage(&instances.InstanceConstraint{
  1408  		Region:      "North Europe",
  1409  		Series:      coretesting.FakeDefaultSeries,
  1410  		Constraints: cons,
  1411  	})
  1412  	c.Assert(err, jc.ErrorIsNil)
  1413  	c.Assert(instanceType.Name, gc.Equals, aim.Name)
  1414  	c.Assert(image, gc.Equals, "image-id")
  1415  }
  1416  
  1417  func (*environSuite) TestExtractStorageKeyPicksPrimaryKeyIfSet(c *gc.C) {
  1418  	keys := gwacl.StorageAccountKeys{
  1419  		Primary:   "mainkey",
  1420  		Secondary: "otherkey",
  1421  	}
  1422  	c.Check(extractStorageKey(&keys), gc.Equals, "mainkey")
  1423  }
  1424  
  1425  func (*environSuite) TestExtractStorageKeyFallsBackToSecondaryKey(c *gc.C) {
  1426  	keys := gwacl.StorageAccountKeys{
  1427  		Secondary: "sparekey",
  1428  	}
  1429  	c.Check(extractStorageKey(&keys), gc.Equals, "sparekey")
  1430  }
  1431  
  1432  func (*environSuite) TestExtractStorageKeyReturnsBlankIfNoneSet(c *gc.C) {
  1433  	c.Check(extractStorageKey(&gwacl.StorageAccountKeys{}), gc.Equals, "")
  1434  }
  1435  
  1436  func assertSourceContents(c *gc.C, source simplestreams.DataSource, filename string, content []byte) {
  1437  	rc, _, err := source.Fetch(filename)
  1438  	c.Assert(err, jc.ErrorIsNil)
  1439  	defer rc.Close()
  1440  	retrieved, err := ioutil.ReadAll(rc)
  1441  	c.Assert(err, jc.ErrorIsNil)
  1442  	c.Assert(retrieved, gc.DeepEquals, content)
  1443  }
  1444  
  1445  func (s *environSuite) TestGetToolsMetadataSources(c *gc.C) {
  1446  	env := makeEnviron(c)
  1447  	s.setDummyStorage(c, env)
  1448  	sources, err := tools.GetMetadataSources(env)
  1449  	c.Assert(err, jc.ErrorIsNil)
  1450  	c.Assert(sources, gc.HasLen, 0)
  1451  }
  1452  
  1453  func (s *environSuite) TestCheckUnitAssignment(c *gc.C) {
  1454  	// If availability-sets-enabled is true, then placement is disabled.
  1455  	attrs := makeAzureConfigMap(c)
  1456  	attrs["availability-sets-enabled"] = true
  1457  	env := environs.Environ(makeEnvironWithConfig(c, attrs))
  1458  	err := env.SupportsUnitPlacement()
  1459  	c.Assert(err, gc.ErrorMatches, "unit placement is not supported with availability-sets-enabled")
  1460  
  1461  	// If the user disables availability sets, they can do what they want.
  1462  	attrs["availability-sets-enabled"] = false
  1463  	env = environs.Environ(makeEnvironWithConfig(c, attrs))
  1464  	err = env.SupportsUnitPlacement()
  1465  	c.Assert(err, jc.ErrorIsNil)
  1466  }
  1467  
  1468  type startInstanceSuite struct {
  1469  	baseEnvironSuite
  1470  	env    *azureEnviron
  1471  	params environs.StartInstanceParams
  1472  }
  1473  
  1474  func (s *startInstanceSuite) SetUpTest(c *gc.C) {
  1475  	s.baseEnvironSuite.SetUpTest(c)
  1476  	s.env = s.setupEnvWithDummyMetadata(c)
  1477  	s.env.ecfg.attrs["force-image-name"] = "my-image"
  1478  	machineTag := names.NewMachineTag("1")
  1479  	stateInfo := &mongo.MongoInfo{
  1480  		Info: mongo.Info{
  1481  			CACert: coretesting.CACert,
  1482  			Addrs:  []string{"localhost:123"},
  1483  		},
  1484  		Password: "password",
  1485  		Tag:      machineTag,
  1486  	}
  1487  	apiInfo := &api.Info{
  1488  		Addrs:      []string{"localhost:124"},
  1489  		CACert:     coretesting.CACert,
  1490  		Password:   "admin",
  1491  		Tag:        machineTag,
  1492  		EnvironTag: coretesting.EnvironmentTag,
  1493  	}
  1494  	icfg, err := instancecfg.NewInstanceConfig("1", "yanonce", imagemetadata.ReleasedStream, "quantal", true, nil, stateInfo, apiInfo)
  1495  	c.Assert(err, jc.ErrorIsNil)
  1496  	s.params = environs.StartInstanceParams{
  1497  		Tools: envtesting.AssertUploadFakeToolsVersions(
  1498  			c, s.env.storage, s.env.Config().AgentStream(), s.env.Config().AgentStream(), envtesting.V120p...,
  1499  		),
  1500  		InstanceConfig: icfg,
  1501  	}
  1502  }
  1503  
  1504  func (s *startInstanceSuite) startInstance(c *gc.C) (serviceName string, stateServer bool) {
  1505  	var called bool
  1506  	var roleSize gwacl.RoleSize
  1507  	restore := testing.PatchValue(&createInstance, func(env *azureEnviron, azure *gwacl.ManagementAPI, role *gwacl.Role, serviceNameArg string, stateServerArg bool) (instance.Instance, error) {
  1508  		serviceName = serviceNameArg
  1509  		stateServer = stateServerArg
  1510  		for _, r := range gwacl.RoleSizes {
  1511  			if r.Name == role.RoleSize {
  1512  				roleSize = r
  1513  				break
  1514  			}
  1515  		}
  1516  		called = true
  1517  		return nil, nil
  1518  	})
  1519  	defer restore()
  1520  	result, err := s.env.StartInstance(s.params)
  1521  	c.Assert(err, jc.ErrorIsNil)
  1522  	c.Assert(called, jc.IsTrue)
  1523  	c.Assert(result, gc.NotNil)
  1524  	c.Assert(result.Hardware, gc.NotNil)
  1525  	arch := "amd64"
  1526  	c.Assert(result.Hardware, gc.DeepEquals, &instance.HardwareCharacteristics{
  1527  		Arch:     &arch,
  1528  		Mem:      &roleSize.Mem,
  1529  		RootDisk: &roleSize.OSDiskSpace,
  1530  		CpuCores: &roleSize.CpuCores,
  1531  	})
  1532  	return serviceName, stateServer
  1533  }
  1534  
  1535  func (s *startInstanceSuite) TestStartInstanceDistributionGroupError(c *gc.C) {
  1536  	s.params.DistributionGroup = func() ([]instance.Id, error) {
  1537  		return nil, fmt.Errorf("DistributionGroupError")
  1538  	}
  1539  	s.env.ecfg.attrs["availability-sets-enabled"] = true
  1540  	_, err := s.env.StartInstance(s.params)
  1541  	c.Assert(err, gc.ErrorMatches, "DistributionGroupError")
  1542  	// DistributionGroup should not be called if availability-sets-enabled=false.
  1543  	s.env.ecfg.attrs["availability-sets-enabled"] = false
  1544  	s.startInstance(c)
  1545  }
  1546  
  1547  func (s *startInstanceSuite) TestStartInstanceDistributionGroupEmpty(c *gc.C) {
  1548  	// serviceName will be empty if DistributionGroup is nil or returns nothing.
  1549  	s.env.ecfg.attrs["availability-sets-enabled"] = true
  1550  	serviceName, _ := s.startInstance(c)
  1551  	c.Assert(serviceName, gc.Equals, "")
  1552  	s.params.DistributionGroup = func() ([]instance.Id, error) { return nil, nil }
  1553  	serviceName, _ = s.startInstance(c)
  1554  	c.Assert(serviceName, gc.Equals, "")
  1555  }
  1556  
  1557  func (s *startInstanceSuite) TestStartInstanceDistributionGroup(c *gc.C) {
  1558  	s.params.DistributionGroup = func() ([]instance.Id, error) {
  1559  		return []instance.Id{
  1560  			instance.Id(s.env.getEnvPrefix() + "whatever-role0"),
  1561  		}, nil
  1562  	}
  1563  	// DistributionGroup will only have an effect if
  1564  	// availability-sets-enabled=true.
  1565  	s.env.ecfg.attrs["availability-sets-enabled"] = false
  1566  	serviceName, _ := s.startInstance(c)
  1567  	c.Assert(serviceName, gc.Equals, "")
  1568  	s.env.ecfg.attrs["availability-sets-enabled"] = true
  1569  	serviceName, _ = s.startInstance(c)
  1570  	c.Assert(serviceName, gc.Equals, "juju-testenv-whatever")
  1571  }
  1572  
  1573  func (s *startInstanceSuite) TestStartInstanceStateServerJobs(c *gc.C) {
  1574  	// If the machine has the JobManagesEnviron job,
  1575  	// we should see stateServer==true.
  1576  	s.params.InstanceConfig.Jobs = []multiwatcher.MachineJob{
  1577  		multiwatcher.JobHostUnits,
  1578  		multiwatcher.JobManageNetworking,
  1579  	}
  1580  	_, stateServer := s.startInstance(c)
  1581  	c.Assert(stateServer, jc.IsFalse)
  1582  	s.params.InstanceConfig.Jobs = []multiwatcher.MachineJob{
  1583  		multiwatcher.JobHostUnits,
  1584  		multiwatcher.JobManageEnviron,
  1585  		multiwatcher.JobManageNetworking,
  1586  	}
  1587  	_, stateServer = s.startInstance(c)
  1588  	c.Assert(stateServer, jc.IsTrue)
  1589  }
  1590  
  1591  func (s *environSuite) TestConstraintsValidator(c *gc.C) {
  1592  	env := s.setupEnvWithDummyMetadata(c)
  1593  	validator, err := env.ConstraintsValidator()
  1594  	c.Assert(err, jc.ErrorIsNil)
  1595  	cons := constraints.MustParse("arch=amd64 tags=bar cpu-power=10")
  1596  	unsupported, err := validator.Validate(cons)
  1597  	c.Assert(err, jc.ErrorIsNil)
  1598  	c.Assert(unsupported, jc.SameContents, []string{"cpu-power", "tags"})
  1599  }
  1600  
  1601  func (s *environSuite) TestConstraintsValidatorVocab(c *gc.C) {
  1602  	env := s.setupEnvWithDummyMetadata(c)
  1603  	validator, err := env.ConstraintsValidator()
  1604  	c.Assert(err, jc.ErrorIsNil)
  1605  	cons := constraints.MustParse("arch=ppc64el")
  1606  	_, err = validator.Validate(cons)
  1607  	c.Assert(err, gc.ErrorMatches, "invalid constraint value: arch=ppc64el\nvalid values are:.*")
  1608  	cons = constraints.MustParse("instance-type=foo")
  1609  	_, err = validator.Validate(cons)
  1610  	c.Assert(err, gc.ErrorMatches, "invalid constraint value: instance-type=foo\nvalid values are:.*")
  1611  }
  1612  
  1613  func (s *environSuite) TestConstraintsMerge(c *gc.C) {
  1614  	env := s.setupEnvWithDummyMetadata(c)
  1615  	validator, err := env.ConstraintsValidator()
  1616  	c.Assert(err, jc.ErrorIsNil)
  1617  	consA := constraints.MustParse("arch=amd64 mem=1G root-disk=10G")
  1618  	consB := constraints.MustParse("instance-type=ExtraSmall")
  1619  	cons, err := validator.Merge(consA, consB)
  1620  	c.Assert(err, jc.ErrorIsNil)
  1621  	c.Assert(cons, gc.DeepEquals, constraints.MustParse("instance-type=ExtraSmall"))
  1622  }
  1623  
  1624  func (s *environSuite) TestBootstrapReusesAffinityGroupAndVNet(c *gc.C) {
  1625  	s.PatchValue(&version.Current.Number, coretesting.FakeVersionNumber)
  1626  	storageDir := c.MkDir()
  1627  	stor, err := filestorage.NewFileStorageWriter(storageDir)
  1628  	c.Assert(err, jc.ErrorIsNil)
  1629  	s.UploadFakeTools(c, stor, "released", "released")
  1630  	s.PatchValue(&tools.DefaultBaseURL, storageDir)
  1631  
  1632  	env := s.setupEnvWithDummyMetadata(c)
  1633  	var responses []gwacl.DispatcherResponse
  1634  
  1635  	// Fail to create affinity group because it already exists.
  1636  	responses = append(responses, gwacl.NewDispatcherResponse(nil, http.StatusConflict, nil))
  1637  
  1638  	// Fail to create vnet because it already exists.
  1639  	sites := []gwacl.VirtualNetworkSite{{Name: env.getVirtualNetworkName()}}
  1640  	existingConfig := &gwacl.NetworkConfiguration{
  1641  		XMLNS:               gwacl.XMLNS_NC,
  1642  		VirtualNetworkSites: &sites,
  1643  	}
  1644  	body, err := existingConfig.Serialize()
  1645  	c.Assert(err, jc.ErrorIsNil)
  1646  	responses = append(responses, gwacl.NewDispatcherResponse([]byte(body), http.StatusOK, nil)) // GET network
  1647  	responses = append(responses, gwacl.NewDispatcherResponse(nil, http.StatusConflict, nil))    // conflict creating AG
  1648  	responses = append(responses, gwacl.NewDispatcherResponse(nil, http.StatusOK, nil))          // DELETE AG
  1649  	responses = append(responses, gwacl.NewDispatcherResponse(nil, http.StatusOK, nil))          // GET network (delete)
  1650  	responses = append(responses, gwacl.NewDispatcherResponse(nil, http.StatusOK, nil))          // PUT network (delete)
  1651  	gwacl.PatchManagementAPIResponses(responses)
  1652  
  1653  	s.PatchValue(&createInstance, func(*azureEnviron, *gwacl.ManagementAPI, *gwacl.Role, string, bool) (instance.Instance, error) {
  1654  		return nil, fmt.Errorf("no instance for you")
  1655  	})
  1656  	err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{})
  1657  	c.Assert(err, gc.ErrorMatches, "cannot start bootstrap instance: no instance for you")
  1658  }
  1659  
  1660  func (s *environSuite) TestGetVirtualNetwork(c *gc.C) {
  1661  	env := makeEnviron(c)
  1662  	s.setDummyStorage(c, env)
  1663  
  1664  	networkConfig := &gwacl.NetworkConfiguration{
  1665  		XMLNS: gwacl.XMLNS_NC,
  1666  		VirtualNetworkSites: &[]gwacl.VirtualNetworkSite{
  1667  			{Name: env.getVirtualNetworkName()},
  1668  		},
  1669  	}
  1670  	body, err := networkConfig.Serialize()
  1671  	c.Assert(err, jc.ErrorIsNil)
  1672  	responses := []gwacl.DispatcherResponse{
  1673  		// Return existing configuration.
  1674  		gwacl.NewDispatcherResponse([]byte(body), http.StatusOK, nil),
  1675  	}
  1676  	requests := gwacl.PatchManagementAPIResponses(responses)
  1677  
  1678  	for i := 0; i < 2; i++ {
  1679  		vnet, err := env.getVirtualNetwork()
  1680  		c.Assert(err, jc.ErrorIsNil)
  1681  		c.Assert(vnet, gc.NotNil)
  1682  		c.Assert(vnet.Name, gc.Equals, env.getVirtualNetworkName())
  1683  	}
  1684  
  1685  	// getVirtualNetwork should cache: there should be only one request to get
  1686  	// the network configuration.
  1687  	c.Assert(*requests, gc.HasLen, 1)
  1688  	getRequest := (*requests)[0]
  1689  	c.Check(getRequest.Method, gc.Equals, "GET")
  1690  	c.Check(strings.HasSuffix(getRequest.URL, "services/networking/media"), jc.IsTrue)
  1691  }