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