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