launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/provider/azure/environ_test.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package azure
     5  
     6  import (
     7  	"bytes"
     8  	"encoding/base64"
     9  	"encoding/xml"
    10  	"fmt"
    11  	"io/ioutil"
    12  	"net/http"
    13  	"net/url"
    14  	"path"
    15  	"regexp"
    16  	"strings"
    17  	"sync"
    18  
    19  	gc "launchpad.net/gocheck"
    20  	"launchpad.net/gwacl"
    21  
    22  	"launchpad.net/juju-core/constraints"
    23  	"launchpad.net/juju-core/environs"
    24  	"launchpad.net/juju-core/environs/bootstrap"
    25  	"launchpad.net/juju-core/environs/config"
    26  	"launchpad.net/juju-core/environs/imagemetadata"
    27  	"launchpad.net/juju-core/environs/simplestreams"
    28  	"launchpad.net/juju-core/environs/storage"
    29  	envtesting "launchpad.net/juju-core/environs/testing"
    30  	"launchpad.net/juju-core/environs/tools"
    31  	"launchpad.net/juju-core/instance"
    32  	"launchpad.net/juju-core/testing"
    33  	jc "launchpad.net/juju-core/testing/checkers"
    34  )
    35  
    36  type environSuite struct {
    37  	providerSuite
    38  }
    39  
    40  var _ = gc.Suite(&environSuite{})
    41  
    42  // makeEnviron creates a fake azureEnviron with arbitrary configuration.
    43  func makeEnviron(c *gc.C) *azureEnviron {
    44  	attrs := makeAzureConfigMap(c)
    45  	return makeEnvironWithConfig(c, attrs)
    46  }
    47  
    48  // makeEnviron creates a fake azureEnviron with the specified configuration.
    49  func makeEnvironWithConfig(c *gc.C, attrs map[string]interface{}) *azureEnviron {
    50  	cfg, err := config.New(config.NoDefaults, attrs)
    51  	c.Assert(err, gc.IsNil)
    52  	env, err := NewEnviron(cfg)
    53  	c.Assert(err, gc.IsNil)
    54  	// Prevent the test from trying to query for a storage-account key.
    55  	env.storageAccountKey = "fake-storage-account-key"
    56  	return env
    57  }
    58  
    59  // setDummyStorage injects the local provider's fake storage implementation
    60  // into the given environment, so that tests can manipulate storage as if it
    61  // were real.
    62  func (s *environSuite) setDummyStorage(c *gc.C, env *azureEnviron) {
    63  	closer, storage, _ := envtesting.CreateLocalTestStorage(c)
    64  	env.storage = storage
    65  	s.AddCleanup(func(c *gc.C) { closer.Close() })
    66  }
    67  
    68  func (*environSuite) TestGetSnapshot(c *gc.C) {
    69  	original := azureEnviron{name: "this-env", ecfg: new(azureEnvironConfig)}
    70  	snapshot := original.getSnapshot()
    71  
    72  	// The snapshot is identical to the original.
    73  	c.Check(*snapshot, gc.DeepEquals, original)
    74  
    75  	// However, they are distinct objects.
    76  	c.Check(snapshot, gc.Not(gc.Equals), &original)
    77  
    78  	// It's a shallow copy; they still share pointers.
    79  	c.Check(snapshot.ecfg, gc.Equals, original.ecfg)
    80  
    81  	// Neither object is locked at the end of the copy.
    82  	c.Check(original.Mutex, gc.Equals, sync.Mutex{})
    83  	c.Check(snapshot.Mutex, gc.Equals, sync.Mutex{})
    84  }
    85  
    86  func (*environSuite) TestGetSnapshotLocksEnviron(c *gc.C) {
    87  	original := azureEnviron{}
    88  	testing.TestLockingFunction(&original.Mutex, func() { original.getSnapshot() })
    89  }
    90  
    91  func (*environSuite) TestName(c *gc.C) {
    92  	env := azureEnviron{name: "foo"}
    93  	c.Check(env.Name(), gc.Equals, env.name)
    94  }
    95  
    96  func (*environSuite) TestPrecheck(c *gc.C) {
    97  	env := azureEnviron{name: "foo"}
    98  	var cons constraints.Value
    99  	err := env.PrecheckInstance("saucy", cons)
   100  	c.Check(err, gc.IsNil)
   101  	err = env.PrecheckContainer("saucy", instance.LXC)
   102  	c.Check(err, gc.ErrorMatches, "azure provider does not support containers")
   103  }
   104  
   105  func (*environSuite) TestConfigReturnsConfig(c *gc.C) {
   106  	cfg := new(config.Config)
   107  	ecfg := azureEnvironConfig{Config: cfg}
   108  	env := azureEnviron{ecfg: &ecfg}
   109  	c.Check(env.Config(), gc.Equals, cfg)
   110  }
   111  
   112  func (*environSuite) TestConfigLocksEnviron(c *gc.C) {
   113  	env := azureEnviron{name: "env", ecfg: new(azureEnvironConfig)}
   114  	testing.TestLockingFunction(&env.Mutex, func() { env.Config() })
   115  }
   116  
   117  func (*environSuite) TestGetManagementAPI(c *gc.C) {
   118  	env := makeEnviron(c)
   119  	context, err := env.getManagementAPI()
   120  	c.Assert(err, gc.IsNil)
   121  	defer env.releaseManagementAPI(context)
   122  	c.Check(context, gc.NotNil)
   123  	c.Check(context.ManagementAPI, gc.NotNil)
   124  	c.Check(context.certFile, gc.NotNil)
   125  	c.Check(context.GetRetryPolicy(), gc.DeepEquals, retryPolicy)
   126  }
   127  
   128  func (*environSuite) TestReleaseManagementAPIAcceptsNil(c *gc.C) {
   129  	env := makeEnviron(c)
   130  	env.releaseManagementAPI(nil)
   131  	// The real test is that this does not panic.
   132  }
   133  
   134  func (*environSuite) TestReleaseManagementAPIAcceptsIncompleteContext(c *gc.C) {
   135  	env := makeEnviron(c)
   136  	context := azureManagementContext{
   137  		ManagementAPI: nil,
   138  		certFile:      nil,
   139  	}
   140  	env.releaseManagementAPI(&context)
   141  	// The real test is that this does not panic.
   142  }
   143  
   144  func getAzureServiceListResponse(c *gc.C, services []gwacl.HostedServiceDescriptor) []gwacl.DispatcherResponse {
   145  	list := gwacl.HostedServiceDescriptorList{HostedServices: services}
   146  	listXML, err := list.Serialize()
   147  	c.Assert(err, gc.IsNil)
   148  	responses := []gwacl.DispatcherResponse{gwacl.NewDispatcherResponse(
   149  		[]byte(listXML),
   150  		http.StatusOK,
   151  		nil,
   152  	)}
   153  	return responses
   154  }
   155  
   156  // getAzureServiceResponses returns the slice of responses
   157  // (gwacl.DispatcherResponse) which correspond to the API requests used to
   158  // get the properties of a Service.
   159  func getAzureServiceResponses(c *gc.C, service gwacl.HostedService) []gwacl.DispatcherResponse {
   160  	serviceXML, err := service.Serialize()
   161  	c.Assert(err, gc.IsNil)
   162  	responses := []gwacl.DispatcherResponse{gwacl.NewDispatcherResponse(
   163  		[]byte(serviceXML),
   164  		http.StatusOK,
   165  		nil,
   166  	)}
   167  	return responses
   168  }
   169  
   170  func patchWithServiceListResponse(c *gc.C, services []gwacl.HostedServiceDescriptor) *[]*gwacl.X509Request {
   171  	responses := getAzureServiceListResponse(c, services)
   172  	return gwacl.PatchManagementAPIResponses(responses)
   173  }
   174  
   175  func (suite *environSuite) TestGetEnvPrefixContainsEnvName(c *gc.C) {
   176  	env := makeEnviron(c)
   177  	c.Check(strings.Contains(env.getEnvPrefix(), env.Name()), jc.IsTrue)
   178  }
   179  
   180  func (*environSuite) TestGetContainerName(c *gc.C) {
   181  	env := makeEnviron(c)
   182  	expected := env.getEnvPrefix() + "private"
   183  	c.Check(env.getContainerName(), gc.Equals, expected)
   184  }
   185  
   186  func (suite *environSuite) TestAllInstances(c *gc.C) {
   187  	env := makeEnviron(c)
   188  	prefix := env.getEnvPrefix()
   189  	services := []gwacl.HostedServiceDescriptor{{ServiceName: "deployment-in-another-env"}, {ServiceName: prefix + "deployment-1"}, {ServiceName: prefix + "deployment-2"}}
   190  	requests := patchWithServiceListResponse(c, services)
   191  	instances, err := env.AllInstances()
   192  	c.Assert(err, gc.IsNil)
   193  	c.Check(len(instances), gc.Equals, 2)
   194  	c.Check(instances[0].Id(), gc.Equals, instance.Id(prefix+"deployment-1"))
   195  	c.Check(instances[1].Id(), gc.Equals, instance.Id(prefix+"deployment-2"))
   196  	c.Check(len(*requests), gc.Equals, 1)
   197  }
   198  
   199  func (suite *environSuite) TestInstancesReturnsFilteredList(c *gc.C) {
   200  	services := []gwacl.HostedServiceDescriptor{{ServiceName: "deployment-1"}, {ServiceName: "deployment-2"}}
   201  	requests := patchWithServiceListResponse(c, services)
   202  	env := makeEnviron(c)
   203  	instances, err := env.Instances([]instance.Id{"deployment-1"})
   204  	c.Assert(err, gc.IsNil)
   205  	c.Check(len(instances), gc.Equals, 1)
   206  	c.Check(instances[0].Id(), gc.Equals, instance.Id("deployment-1"))
   207  	c.Check(len(*requests), gc.Equals, 1)
   208  }
   209  
   210  func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfNoInstancesRequested(c *gc.C) {
   211  	services := []gwacl.HostedServiceDescriptor{{ServiceName: "deployment-1"}, {ServiceName: "deployment-2"}}
   212  	patchWithServiceListResponse(c, services)
   213  	env := makeEnviron(c)
   214  	instances, err := env.Instances([]instance.Id{})
   215  	c.Check(err, gc.Equals, environs.ErrNoInstances)
   216  	c.Check(instances, gc.IsNil)
   217  }
   218  
   219  func (suite *environSuite) TestInstancesReturnsErrNoInstancesIfNoInstanceFound(c *gc.C) {
   220  	services := []gwacl.HostedServiceDescriptor{}
   221  	patchWithServiceListResponse(c, services)
   222  	env := makeEnviron(c)
   223  	instances, err := env.Instances([]instance.Id{"deploy-id"})
   224  	c.Check(err, gc.Equals, environs.ErrNoInstances)
   225  	c.Check(instances, gc.IsNil)
   226  }
   227  
   228  func (suite *environSuite) TestInstancesReturnsPartialInstancesIfSomeInstancesAreNotFound(c *gc.C) {
   229  	services := []gwacl.HostedServiceDescriptor{{ServiceName: "deployment-1"}, {ServiceName: "deployment-2"}}
   230  	requests := patchWithServiceListResponse(c, services)
   231  	env := makeEnviron(c)
   232  	instances, err := env.Instances([]instance.Id{"deployment-1", "unknown-deployment"})
   233  	c.Assert(err, gc.Equals, environs.ErrPartialInstances)
   234  	c.Check(len(instances), gc.Equals, 1)
   235  	c.Check(instances[0].Id(), gc.Equals, instance.Id("deployment-1"))
   236  	c.Check(len(*requests), gc.Equals, 1)
   237  }
   238  
   239  func (*environSuite) TestStorage(c *gc.C) {
   240  	env := makeEnviron(c)
   241  	baseStorage := env.Storage()
   242  	storage, ok := baseStorage.(*azureStorage)
   243  	c.Check(ok, gc.Equals, true)
   244  	c.Assert(storage, gc.NotNil)
   245  	c.Check(storage.storageContext.getContainer(), gc.Equals, env.getContainerName())
   246  	context, err := storage.getStorageContext()
   247  	c.Assert(err, gc.IsNil)
   248  	c.Check(context.Account, gc.Equals, env.ecfg.storageAccountName())
   249  	c.Check(context.RetryPolicy, gc.DeepEquals, retryPolicy)
   250  }
   251  
   252  func (*environSuite) TestQueryStorageAccountKeyGetsKey(c *gc.C) {
   253  	env := makeEnviron(c)
   254  	keysInAzure := gwacl.StorageAccountKeys{Primary: "a-key"}
   255  	azureResponse, err := xml.Marshal(keysInAzure)
   256  	c.Assert(err, gc.IsNil)
   257  	requests := gwacl.PatchManagementAPIResponses([]gwacl.DispatcherResponse{
   258  		gwacl.NewDispatcherResponse(azureResponse, http.StatusOK, nil),
   259  	})
   260  
   261  	returnedKey, err := env.queryStorageAccountKey()
   262  	c.Assert(err, gc.IsNil)
   263  
   264  	c.Check(returnedKey, gc.Equals, keysInAzure.Primary)
   265  	c.Assert(*requests, gc.HasLen, 1)
   266  	c.Check((*requests)[0].Method, gc.Equals, "GET")
   267  }
   268  
   269  func (*environSuite) TestGetStorageContextCreatesStorageContext(c *gc.C) {
   270  	env := makeEnviron(c)
   271  	stor, err := env.getStorageContext()
   272  	c.Assert(err, gc.IsNil)
   273  	c.Assert(stor, gc.NotNil)
   274  	c.Check(stor.Account, gc.Equals, env.ecfg.storageAccountName())
   275  	c.Check(stor.AzureEndpoint, gc.Equals, gwacl.GetEndpoint(env.ecfg.location()))
   276  }
   277  
   278  func (*environSuite) TestGetStorageContextUsesKnownStorageAccountKey(c *gc.C) {
   279  	env := makeEnviron(c)
   280  	env.storageAccountKey = "my-key"
   281  
   282  	stor, err := env.getStorageContext()
   283  	c.Assert(err, gc.IsNil)
   284  
   285  	c.Check(stor.Key, gc.Equals, "my-key")
   286  }
   287  
   288  func (*environSuite) TestGetStorageContextQueriesStorageAccountKeyIfNeeded(c *gc.C) {
   289  	env := makeEnviron(c)
   290  	env.storageAccountKey = ""
   291  	keysInAzure := gwacl.StorageAccountKeys{Primary: "my-key"}
   292  	azureResponse, err := xml.Marshal(keysInAzure)
   293  	c.Assert(err, gc.IsNil)
   294  	gwacl.PatchManagementAPIResponses([]gwacl.DispatcherResponse{
   295  		gwacl.NewDispatcherResponse(azureResponse, http.StatusOK, nil),
   296  	})
   297  
   298  	stor, err := env.getStorageContext()
   299  	c.Assert(err, gc.IsNil)
   300  
   301  	c.Check(stor.Key, gc.Equals, keysInAzure.Primary)
   302  	c.Check(env.storageAccountKey, gc.Equals, keysInAzure.Primary)
   303  }
   304  
   305  func (*environSuite) TestGetStorageContextFailsIfNoKeyAvailable(c *gc.C) {
   306  	env := makeEnviron(c)
   307  	env.storageAccountKey = ""
   308  	azureResponse, err := xml.Marshal(gwacl.StorageAccountKeys{})
   309  	c.Assert(err, gc.IsNil)
   310  	gwacl.PatchManagementAPIResponses([]gwacl.DispatcherResponse{
   311  		gwacl.NewDispatcherResponse(azureResponse, http.StatusOK, nil),
   312  	})
   313  
   314  	_, err = env.getStorageContext()
   315  	c.Assert(err, gc.NotNil)
   316  
   317  	c.Check(err, gc.ErrorMatches, "no keys available for storage account")
   318  }
   319  
   320  func (*environSuite) TestUpdateStorageAccountKeyGetsFreshKey(c *gc.C) {
   321  	env := makeEnviron(c)
   322  	keysInAzure := gwacl.StorageAccountKeys{Primary: "my-key"}
   323  	azureResponse, err := xml.Marshal(keysInAzure)
   324  	c.Assert(err, gc.IsNil)
   325  	gwacl.PatchManagementAPIResponses([]gwacl.DispatcherResponse{
   326  		gwacl.NewDispatcherResponse(azureResponse, http.StatusOK, nil),
   327  	})
   328  
   329  	key, err := env.updateStorageAccountKey(env.getSnapshot())
   330  	c.Assert(err, gc.IsNil)
   331  
   332  	c.Check(key, gc.Equals, keysInAzure.Primary)
   333  	c.Check(env.storageAccountKey, gc.Equals, keysInAzure.Primary)
   334  }
   335  
   336  func (*environSuite) TestUpdateStorageAccountKeyReturnsError(c *gc.C) {
   337  	env := makeEnviron(c)
   338  	env.storageAccountKey = ""
   339  	gwacl.PatchManagementAPIResponses([]gwacl.DispatcherResponse{
   340  		gwacl.NewDispatcherResponse(nil, http.StatusInternalServerError, nil),
   341  	})
   342  
   343  	_, err := env.updateStorageAccountKey(env.getSnapshot())
   344  	c.Assert(err, gc.NotNil)
   345  
   346  	c.Check(err, gc.ErrorMatches, "cannot obtain storage account keys: GET request failed.*Internal Server Error.*")
   347  	c.Check(env.storageAccountKey, gc.Equals, "")
   348  }
   349  
   350  func (*environSuite) TestUpdateStorageAccountKeyDetectsConcurrentUpdate(c *gc.C) {
   351  	env := makeEnviron(c)
   352  	env.storageAccountKey = ""
   353  	keysInAzure := gwacl.StorageAccountKeys{Primary: "my-key"}
   354  	azureResponse, err := xml.Marshal(keysInAzure)
   355  	c.Assert(err, gc.IsNil)
   356  	gwacl.PatchManagementAPIResponses([]gwacl.DispatcherResponse{
   357  		gwacl.NewDispatcherResponse(azureResponse, http.StatusOK, nil),
   358  	})
   359  
   360  	// Here we use a snapshot that's different from the environment, to
   361  	// simulate a concurrent change to the environment.
   362  	_, err = env.updateStorageAccountKey(makeEnviron(c))
   363  	c.Assert(err, gc.NotNil)
   364  
   365  	// updateStorageAccountKey detects the change, and refuses to write its
   366  	// outdated information into env.
   367  	c.Check(err, gc.ErrorMatches, "environment was reconfigured")
   368  	c.Check(env.storageAccountKey, gc.Equals, "")
   369  }
   370  
   371  func (*environSuite) TestSetConfigValidates(c *gc.C) {
   372  	env := makeEnviron(c)
   373  	originalCfg := env.ecfg
   374  	attrs := makeAzureConfigMap(c)
   375  	// This config is not valid.  It lacks essential information.
   376  	delete(attrs, "management-subscription-id")
   377  	badCfg, err := config.New(config.NoDefaults, attrs)
   378  	c.Assert(err, gc.IsNil)
   379  
   380  	err = env.SetConfig(badCfg)
   381  
   382  	// Since the config was not valid, SetConfig returns an error.  It
   383  	// does not update the environment's config either.
   384  	c.Check(err, gc.NotNil)
   385  	c.Check(
   386  		err,
   387  		gc.ErrorMatches,
   388  		"management-subscription-id: expected string, got nothing")
   389  	c.Check(env.ecfg, gc.Equals, originalCfg)
   390  }
   391  
   392  func (*environSuite) TestSetConfigUpdatesConfig(c *gc.C) {
   393  	env := makeEnviron(c)
   394  	// We're going to set a new config.  It can be recognized by its
   395  	// unusual default Ubuntu release series: 7.04 Feisty Fawn.
   396  	attrs := makeAzureConfigMap(c)
   397  	attrs["default-series"] = "feisty"
   398  	cfg, err := config.New(config.NoDefaults, attrs)
   399  	c.Assert(err, gc.IsNil)
   400  
   401  	err = env.SetConfig(cfg)
   402  	c.Assert(err, gc.IsNil)
   403  
   404  	c.Check(env.ecfg.Config.DefaultSeries(), gc.Equals, "feisty")
   405  }
   406  
   407  func (*environSuite) TestSetConfigLocksEnviron(c *gc.C) {
   408  	env := makeEnviron(c)
   409  	cfg, err := config.New(config.NoDefaults, makeAzureConfigMap(c))
   410  	c.Assert(err, gc.IsNil)
   411  
   412  	testing.TestLockingFunction(&env.Mutex, func() { env.SetConfig(cfg) })
   413  }
   414  
   415  func (*environSuite) TestSetConfigWillNotUpdateName(c *gc.C) {
   416  	// Once the environment's name has been set, it cannot be updated.
   417  	// Global validation rejects such a change.
   418  	// This matters because the attribute is not protected by a lock.
   419  	env := makeEnviron(c)
   420  	originalName := env.Name()
   421  	attrs := makeAzureConfigMap(c)
   422  	attrs["name"] = "new-name"
   423  	cfg, err := config.New(config.NoDefaults, attrs)
   424  	c.Assert(err, gc.IsNil)
   425  
   426  	err = env.SetConfig(cfg)
   427  
   428  	c.Assert(err, gc.NotNil)
   429  	c.Check(
   430  		err,
   431  		gc.ErrorMatches,
   432  		`cannot change name from ".*" to "new-name"`)
   433  	c.Check(env.Name(), gc.Equals, originalName)
   434  }
   435  
   436  func (*environSuite) TestSetConfigClearsStorageAccountKey(c *gc.C) {
   437  	env := makeEnviron(c)
   438  	env.storageAccountKey = "key-for-previous-config"
   439  	attrs := makeAzureConfigMap(c)
   440  	attrs["default-series"] = "other"
   441  	cfg, err := config.New(config.NoDefaults, attrs)
   442  	c.Assert(err, gc.IsNil)
   443  
   444  	err = env.SetConfig(cfg)
   445  	c.Assert(err, gc.IsNil)
   446  
   447  	c.Check(env.storageAccountKey, gc.Equals, "")
   448  }
   449  
   450  func (s *environSuite) TestStateInfoFailsIfNoStateInstances(c *gc.C) {
   451  	env := makeEnviron(c)
   452  	s.setDummyStorage(c, env)
   453  	_, _, err := env.StateInfo()
   454  	c.Check(err, gc.Equals, environs.ErrNotBootstrapped)
   455  }
   456  
   457  func (s *environSuite) TestStateInfo(c *gc.C) {
   458  	instanceID := "my-instance"
   459  	patchWithServiceListResponse(c, []gwacl.HostedServiceDescriptor{{
   460  		ServiceName: instanceID,
   461  	}})
   462  	env := makeEnviron(c)
   463  	s.setDummyStorage(c, env)
   464  	err := bootstrap.SaveState(
   465  		env.Storage(),
   466  		&bootstrap.BootstrapState{StateInstances: []instance.Id{instance.Id(instanceID)}})
   467  	c.Assert(err, gc.IsNil)
   468  
   469  	stateInfo, apiInfo, err := env.StateInfo()
   470  	c.Assert(err, gc.IsNil)
   471  
   472  	config := env.Config()
   473  	dnsName := "my-instance." + AZURE_DOMAIN_NAME
   474  	stateServerAddr := fmt.Sprintf("%s:%d", dnsName, config.StatePort())
   475  	apiServerAddr := fmt.Sprintf("%s:%d", dnsName, config.APIPort())
   476  	c.Check(stateInfo.Addrs, gc.DeepEquals, []string{stateServerAddr})
   477  	c.Check(apiInfo.Addrs, gc.DeepEquals, []string{apiServerAddr})
   478  }
   479  
   480  // parseCreateServiceRequest reconstructs the original CreateHostedService
   481  // request object passed to gwacl's AddHostedService method, based on the
   482  // X509Request which the method issues.
   483  func parseCreateServiceRequest(c *gc.C, request *gwacl.X509Request) *gwacl.CreateHostedService {
   484  	body := gwacl.CreateHostedService{}
   485  	err := xml.Unmarshal(request.Payload, &body)
   486  	c.Assert(err, gc.IsNil)
   487  	return &body
   488  }
   489  
   490  // makeNonAvailabilityResponse simulates a reply to the
   491  // CheckHostedServiceNameAvailability call saying that a name is not available.
   492  func makeNonAvailabilityResponse(c *gc.C) []byte {
   493  	errorBody, err := xml.Marshal(gwacl.AvailabilityResponse{
   494  		Result: "false",
   495  		Reason: "he's a very naughty boy"})
   496  	c.Assert(err, gc.IsNil)
   497  	return errorBody
   498  }
   499  
   500  // makeAvailabilityResponse simulates a reply to the
   501  // CheckHostedServiceNameAvailability call saying that a name is available.
   502  func makeAvailabilityResponse(c *gc.C) []byte {
   503  	errorBody, err := xml.Marshal(gwacl.AvailabilityResponse{
   504  		Result: "true"})
   505  	c.Assert(err, gc.IsNil)
   506  	return errorBody
   507  }
   508  
   509  func (*environSuite) TestAttemptCreateServiceCreatesService(c *gc.C) {
   510  	prefix := "myservice"
   511  	affinityGroup := "affinity-group"
   512  	location := "location"
   513  	responses := []gwacl.DispatcherResponse{
   514  		gwacl.NewDispatcherResponse(makeAvailabilityResponse(c), http.StatusOK, nil),
   515  		gwacl.NewDispatcherResponse(nil, http.StatusOK, nil),
   516  	}
   517  	requests := gwacl.PatchManagementAPIResponses(responses)
   518  	azure, err := gwacl.NewManagementAPI("subscription", "", "West US")
   519  	c.Assert(err, gc.IsNil)
   520  
   521  	service, err := attemptCreateService(azure, prefix, affinityGroup, location)
   522  	c.Assert(err, gc.IsNil)
   523  
   524  	c.Assert(*requests, gc.HasLen, 2)
   525  	body := parseCreateServiceRequest(c, (*requests)[1])
   526  	c.Check(body.ServiceName, gc.Equals, service.ServiceName)
   527  	c.Check(body.AffinityGroup, gc.Equals, affinityGroup)
   528  	c.Check(service.ServiceName, gc.Matches, prefix+".*")
   529  	c.Check(service.Location, gc.Equals, location)
   530  
   531  	label, err := base64.StdEncoding.DecodeString(service.Label)
   532  	c.Assert(err, gc.IsNil)
   533  	c.Check(string(label), gc.Equals, service.ServiceName)
   534  }
   535  
   536  func (*environSuite) TestAttemptCreateServiceReturnsNilIfNameNotUnique(c *gc.C) {
   537  	responses := []gwacl.DispatcherResponse{
   538  		gwacl.NewDispatcherResponse(makeNonAvailabilityResponse(c), http.StatusOK, nil),
   539  	}
   540  	gwacl.PatchManagementAPIResponses(responses)
   541  	azure, err := gwacl.NewManagementAPI("subscription", "", "West US")
   542  	c.Assert(err, gc.IsNil)
   543  
   544  	service, err := attemptCreateService(azure, "service", "affinity-group", "location")
   545  	c.Check(err, gc.IsNil)
   546  	c.Check(service, gc.IsNil)
   547  }
   548  
   549  func (*environSuite) TestAttemptCreateServicePropagatesOtherFailure(c *gc.C) {
   550  	responses := []gwacl.DispatcherResponse{
   551  		gwacl.NewDispatcherResponse(makeAvailabilityResponse(c), http.StatusOK, nil),
   552  		gwacl.NewDispatcherResponse(nil, http.StatusNotFound, nil),
   553  	}
   554  	gwacl.PatchManagementAPIResponses(responses)
   555  	azure, err := gwacl.NewManagementAPI("subscription", "", "West US")
   556  	c.Assert(err, gc.IsNil)
   557  
   558  	_, err = attemptCreateService(azure, "service", "affinity-group", "location")
   559  	c.Assert(err, gc.NotNil)
   560  	c.Check(err, gc.ErrorMatches, ".*Not Found.*")
   561  }
   562  
   563  func (*environSuite) TestNewHostedServiceCreatesService(c *gc.C) {
   564  	prefix := "myservice"
   565  	affinityGroup := "affinity-group"
   566  	location := "location"
   567  	responses := []gwacl.DispatcherResponse{
   568  		gwacl.NewDispatcherResponse(makeAvailabilityResponse(c), http.StatusOK, nil),
   569  		gwacl.NewDispatcherResponse(nil, http.StatusOK, nil),
   570  	}
   571  	requests := gwacl.PatchManagementAPIResponses(responses)
   572  	azure, err := gwacl.NewManagementAPI("subscription", "", "West US")
   573  	c.Assert(err, gc.IsNil)
   574  
   575  	service, err := newHostedService(azure, prefix, affinityGroup, location)
   576  	c.Assert(err, gc.IsNil)
   577  
   578  	c.Assert(*requests, gc.HasLen, 2)
   579  	body := parseCreateServiceRequest(c, (*requests)[1])
   580  	c.Check(body.ServiceName, gc.Equals, service.ServiceName)
   581  	c.Check(body.AffinityGroup, gc.Equals, affinityGroup)
   582  	c.Check(service.ServiceName, gc.Matches, prefix+".*")
   583  	c.Check(service.Location, gc.Equals, location)
   584  }
   585  
   586  func (*environSuite) TestNewHostedServiceRetriesIfNotUnique(c *gc.C) {
   587  	errorBody := makeNonAvailabilityResponse(c)
   588  	okBody := makeAvailabilityResponse(c)
   589  	// In this scenario, the first two names that we try are already
   590  	// taken.  The third one is unique though, so we succeed.
   591  	responses := []gwacl.DispatcherResponse{
   592  		gwacl.NewDispatcherResponse(errorBody, http.StatusOK, nil),
   593  		gwacl.NewDispatcherResponse(errorBody, http.StatusOK, nil),
   594  		gwacl.NewDispatcherResponse(okBody, http.StatusOK, nil),
   595  		gwacl.NewDispatcherResponse(nil, http.StatusOK, nil),
   596  	}
   597  	requests := gwacl.PatchManagementAPIResponses(responses)
   598  	azure, err := gwacl.NewManagementAPI("subscription", "", "West US")
   599  	c.Assert(err, gc.IsNil)
   600  
   601  	service, err := newHostedService(azure, "service", "affinity-group", "location")
   602  	c.Check(err, gc.IsNil)
   603  
   604  	c.Assert(*requests, gc.HasLen, 4)
   605  	// How many names have been attempted, and how often?
   606  	// There is a minute chance that this tries the same name twice, and
   607  	// then this test will fail.  If that happens, try seeding the
   608  	// randomizer with some fixed seed that doens't produce the problem.
   609  	attemptedNames := make(map[string]int)
   610  	for _, request := range *requests {
   611  		// Exit the loop if we hit the request to create the service, it comes
   612  		// after the check calls.
   613  		if request.Method == "POST" {
   614  			break
   615  		}
   616  		// Name is the last part of the URL from the GET requests that check
   617  		// availability.
   618  		_, name := path.Split(strings.TrimRight(request.URL, "/"))
   619  		attemptedNames[name] += 1
   620  	}
   621  	// The three attempts we just made all had different service names.
   622  	c.Check(attemptedNames, gc.HasLen, 3)
   623  
   624  	// Once newHostedService succeeds, we get a hosted service with the
   625  	// last requested name.
   626  	c.Check(
   627  		service.ServiceName,
   628  		gc.Equals,
   629  		parseCreateServiceRequest(c, (*requests)[3]).ServiceName)
   630  }
   631  
   632  func (*environSuite) TestNewHostedServiceFailsIfUnableToFindUniqueName(c *gc.C) {
   633  	errorBody := makeNonAvailabilityResponse(c)
   634  	responses := []gwacl.DispatcherResponse{}
   635  	for counter := 0; counter < 100; counter++ {
   636  		responses = append(responses, gwacl.NewDispatcherResponse(errorBody, http.StatusOK, nil))
   637  	}
   638  	gwacl.PatchManagementAPIResponses(responses)
   639  	azure, err := gwacl.NewManagementAPI("subscription", "", "West US")
   640  	c.Assert(err, gc.IsNil)
   641  
   642  	_, err = newHostedService(azure, "service", "affinity-group", "location")
   643  	c.Assert(err, gc.NotNil)
   644  	c.Check(err, gc.ErrorMatches, "could not come up with a unique hosted service name.*")
   645  }
   646  
   647  // buildDestroyAzureServiceResponses returns a slice containing the responses that a fake Azure server
   648  // can use to simulate the deletion of the given list of services.
   649  func buildDestroyAzureServiceResponses(c *gc.C, services []*gwacl.HostedService) []gwacl.DispatcherResponse {
   650  	responses := []gwacl.DispatcherResponse{}
   651  	for _, service := range services {
   652  		// When destroying a hosted service, gwacl first issues a Get request
   653  		// to fetch the properties of the services.  Then it destroys all the
   654  		// deployments found in this service (none in this case, we make sure
   655  		// the service does not contain deployments to keep the testing simple)
   656  		// And it finally deletes the service itself.
   657  		if len(service.Deployments) != 0 {
   658  			panic("buildDestroyAzureServiceResponses does not support services with deployments!")
   659  		}
   660  		serviceXML, err := service.Serialize()
   661  		c.Assert(err, gc.IsNil)
   662  		serviceGetResponse := gwacl.NewDispatcherResponse(
   663  			[]byte(serviceXML),
   664  			http.StatusOK,
   665  			nil,
   666  		)
   667  		responses = append(responses, serviceGetResponse)
   668  		serviceDeleteResponse := gwacl.NewDispatcherResponse(
   669  			nil,
   670  			http.StatusOK,
   671  			nil,
   672  		)
   673  		responses = append(responses, serviceDeleteResponse)
   674  	}
   675  	return responses
   676  }
   677  
   678  func makeAzureService(name string) (*gwacl.HostedService, *gwacl.HostedServiceDescriptor) {
   679  	service1Desc := &gwacl.HostedServiceDescriptor{ServiceName: name}
   680  	service1 := &gwacl.HostedService{HostedServiceDescriptor: *service1Desc}
   681  	return service1, service1Desc
   682  }
   683  
   684  func (s *environSuite) setServiceDeletionConcurrency(nbGoroutines int) {
   685  	s.PatchValue(&maxConcurrentDeletes, nbGoroutines)
   686  }
   687  
   688  func (s *environSuite) TestStopInstancesDestroysMachines(c *gc.C) {
   689  	s.setServiceDeletionConcurrency(3)
   690  	service1Name := "service1"
   691  	service1, service1Desc := makeAzureService(service1Name)
   692  	service2Name := "service2"
   693  	service2, service2Desc := makeAzureService(service2Name)
   694  	services := []*gwacl.HostedService{service1, service2}
   695  	responses := buildDestroyAzureServiceResponses(c, services)
   696  	requests := gwacl.PatchManagementAPIResponses(responses)
   697  	env := makeEnviron(c)
   698  	instances := convertToInstances(
   699  		[]gwacl.HostedServiceDescriptor{*service1Desc, *service2Desc},
   700  		env)
   701  
   702  	err := env.StopInstances(instances)
   703  	c.Check(err, gc.IsNil)
   704  
   705  	// It takes 2 API calls to delete each service:
   706  	// - one GET request to fetch the service's properties;
   707  	// - one DELETE request to delete the service.
   708  	c.Check(len(*requests), gc.Equals, len(services)*2)
   709  	assertOneRequestMatches(c, *requests, "GET", ".*"+service1Name+".*")
   710  	assertOneRequestMatches(c, *requests, "DELETE", ".*"+service1Name+".*")
   711  	assertOneRequestMatches(c, *requests, "GET", ".*"+service2Name+".")
   712  	assertOneRequestMatches(c, *requests, "DELETE", ".*"+service2Name+".*")
   713  }
   714  
   715  func (s *environSuite) TestStopInstancesWhenStoppingMachinesFails(c *gc.C) {
   716  	s.setServiceDeletionConcurrency(3)
   717  	responses := []gwacl.DispatcherResponse{
   718  		gwacl.NewDispatcherResponse(nil, http.StatusConflict, nil),
   719  	}
   720  	service1Name := "service1"
   721  	_, service1Desc := makeAzureService(service1Name)
   722  	service2Name := "service2"
   723  	service2, service2Desc := makeAzureService(service2Name)
   724  	services := []*gwacl.HostedService{service2}
   725  	destroyResponses := buildDestroyAzureServiceResponses(c, services)
   726  	responses = append(responses, destroyResponses...)
   727  	requests := gwacl.PatchManagementAPIResponses(responses)
   728  	env := makeEnviron(c)
   729  	instances := convertToInstances(
   730  		[]gwacl.HostedServiceDescriptor{*service1Desc, *service2Desc}, env)
   731  
   732  	err := env.StopInstances(instances)
   733  	c.Check(err, gc.ErrorMatches, ".*Conflict.*")
   734  
   735  	c.Check(len(*requests), gc.Equals, 3)
   736  	assertOneRequestMatches(c, *requests, "GET", ".*"+service1Name+".")
   737  	assertOneRequestMatches(c, *requests, "GET", ".*"+service2Name+".")
   738  	// Only one of the services was deleted.
   739  	assertOneRequestMatches(c, *requests, "DELETE", ".*")
   740  }
   741  
   742  func (s *environSuite) TestStopInstancesWithLimitedConcurrency(c *gc.C) {
   743  	s.setServiceDeletionConcurrency(3)
   744  	services := []*gwacl.HostedService{}
   745  	serviceDescs := []gwacl.HostedServiceDescriptor{}
   746  	for i := 0; i < 10; i++ {
   747  		serviceName := fmt.Sprintf("service%d", i)
   748  		service, serviceDesc := makeAzureService(serviceName)
   749  		services = append(services, service)
   750  		serviceDescs = append(serviceDescs, *serviceDesc)
   751  	}
   752  	responses := buildDestroyAzureServiceResponses(c, services)
   753  	requests := gwacl.PatchManagementAPIResponses(responses)
   754  	env := makeEnviron(c)
   755  	instances := convertToInstances(serviceDescs, env)
   756  
   757  	err := env.StopInstances(instances)
   758  	c.Check(err, gc.IsNil)
   759  	c.Check(len(*requests), gc.Equals, len(services)*2)
   760  }
   761  
   762  func (s *environSuite) TestStopInstancesWithZeroInstance(c *gc.C) {
   763  	s.setServiceDeletionConcurrency(3)
   764  	env := makeEnviron(c)
   765  	instances := []instance.Instance{}
   766  
   767  	err := env.StopInstances(instances)
   768  	c.Check(err, gc.IsNil)
   769  }
   770  
   771  // getVnetAndAffinityGroupCleanupResponses returns the responses
   772  // (gwacl.DispatcherResponse) that a fake http server should return
   773  // when gwacl's RemoveVirtualNetworkSite() and DeleteAffinityGroup()
   774  // are called.
   775  func getVnetAndAffinityGroupCleanupResponses(c *gc.C) []gwacl.DispatcherResponse {
   776  	existingConfig := &gwacl.NetworkConfiguration{
   777  		XMLNS:               gwacl.XMLNS_NC,
   778  		VirtualNetworkSites: nil,
   779  	}
   780  	body, err := existingConfig.Serialize()
   781  	c.Assert(err, gc.IsNil)
   782  	cleanupResponses := []gwacl.DispatcherResponse{
   783  		// Return empty net configuration.
   784  		gwacl.NewDispatcherResponse([]byte(body), http.StatusOK, nil),
   785  		// Accept deletion of affinity group.
   786  		gwacl.NewDispatcherResponse(nil, http.StatusOK, nil),
   787  	}
   788  	return cleanupResponses
   789  }
   790  
   791  func (s *environSuite) TestDestroyDoesNotCleanStorageIfError(c *gc.C) {
   792  	env := makeEnviron(c)
   793  	s.setDummyStorage(c, env)
   794  	// Populate storage.
   795  	err := bootstrap.SaveState(
   796  		env.Storage(),
   797  		&bootstrap.BootstrapState{StateInstances: []instance.Id{instance.Id("test-id")}})
   798  	c.Assert(err, gc.IsNil)
   799  	responses := []gwacl.DispatcherResponse{
   800  		gwacl.NewDispatcherResponse(nil, http.StatusBadRequest, nil),
   801  	}
   802  	gwacl.PatchManagementAPIResponses(responses)
   803  
   804  	err = env.Destroy()
   805  	c.Check(err, gc.NotNil)
   806  
   807  	files, err := storage.List(env.Storage(), "")
   808  	c.Assert(err, gc.IsNil)
   809  	c.Check(files, gc.HasLen, 1)
   810  }
   811  
   812  func (s *environSuite) TestDestroyCleansUpStorage(c *gc.C) {
   813  	env := makeEnviron(c)
   814  	s.setDummyStorage(c, env)
   815  	// Populate storage.
   816  	err := bootstrap.SaveState(
   817  		env.Storage(),
   818  		&bootstrap.BootstrapState{StateInstances: []instance.Id{instance.Id("test-id")}})
   819  	c.Assert(err, gc.IsNil)
   820  	services := []gwacl.HostedServiceDescriptor{}
   821  	responses := getAzureServiceListResponse(c, services)
   822  	cleanupResponses := getVnetAndAffinityGroupCleanupResponses(c)
   823  	responses = append(responses, cleanupResponses...)
   824  	gwacl.PatchManagementAPIResponses(responses)
   825  
   826  	err = env.Destroy()
   827  	c.Check(err, gc.IsNil)
   828  
   829  	files, err := storage.List(env.Storage(), "")
   830  	c.Assert(err, gc.IsNil)
   831  	c.Check(files, gc.HasLen, 0)
   832  }
   833  
   834  func (s *environSuite) TestDestroyDeletesVirtualNetworkAndAffinityGroup(c *gc.C) {
   835  	env := makeEnviron(c)
   836  	s.setDummyStorage(c, env)
   837  	services := []gwacl.HostedServiceDescriptor{}
   838  	responses := getAzureServiceListResponse(c, services)
   839  	// Prepare a configuration with a single virtual network.
   840  	existingConfig := &gwacl.NetworkConfiguration{
   841  		XMLNS: gwacl.XMLNS_NC,
   842  		VirtualNetworkSites: &[]gwacl.VirtualNetworkSite{
   843  			{Name: env.getVirtualNetworkName()},
   844  		},
   845  	}
   846  	body, err := existingConfig.Serialize()
   847  	c.Assert(err, gc.IsNil)
   848  	cleanupResponses := []gwacl.DispatcherResponse{
   849  		// Return existing configuration.
   850  		gwacl.NewDispatcherResponse([]byte(body), http.StatusOK, nil),
   851  		// Accept upload of new configuration.
   852  		gwacl.NewDispatcherResponse(nil, http.StatusOK, nil),
   853  		// Accept deletion of affinity group.
   854  		gwacl.NewDispatcherResponse(nil, http.StatusOK, nil),
   855  	}
   856  	responses = append(responses, cleanupResponses...)
   857  	requests := gwacl.PatchManagementAPIResponses(responses)
   858  
   859  	err = env.Destroy()
   860  	c.Check(err, gc.IsNil)
   861  
   862  	c.Assert(*requests, gc.HasLen, 4)
   863  	// One request to get the network configuration.
   864  	getRequest := (*requests)[1]
   865  	c.Check(getRequest.Method, gc.Equals, "GET")
   866  	c.Check(strings.HasSuffix(getRequest.URL, "services/networking/media"), gc.Equals, true)
   867  	// One request to upload the new version of the network configuration.
   868  	putRequest := (*requests)[2]
   869  	c.Check(putRequest.Method, gc.Equals, "PUT")
   870  	c.Check(strings.HasSuffix(putRequest.URL, "services/networking/media"), gc.Equals, true)
   871  	// One request to delete the Affinity Group.
   872  	agRequest := (*requests)[3]
   873  	c.Check(strings.Contains(agRequest.URL, env.getAffinityGroupName()), jc.IsTrue)
   874  	c.Check(agRequest.Method, gc.Equals, "DELETE")
   875  
   876  }
   877  
   878  var emptyListResponse = `
   879    <?xml version="1.0" encoding="utf-8"?>
   880    <EnumerationResults ContainerName="http://myaccount.blob.core.windows.net/mycontainer">
   881      <Prefix>prefix</Prefix>
   882      <Marker>marker</Marker>
   883      <MaxResults>maxresults</MaxResults>
   884      <Delimiter>delimiter</Delimiter>
   885      <Blobs></Blobs>
   886      <NextMarker />
   887    </EnumerationResults>`
   888  
   889  // assertOneRequestMatches asserts that at least one request in the given slice
   890  // contains a request with the given method and whose URL matches the given regexp.
   891  func assertOneRequestMatches(c *gc.C, requests []*gwacl.X509Request, method string, urlPattern string) {
   892  	for _, request := range requests {
   893  		matched, err := regexp.MatchString(urlPattern, request.URL)
   894  		if err == nil && request.Method == method && matched {
   895  			return
   896  		}
   897  	}
   898  	c.Error(fmt.Sprintf("none of the requests matches: Method=%v, URL pattern=%v", method, urlPattern))
   899  }
   900  
   901  func (s *environSuite) TestDestroyStopsAllInstances(c *gc.C) {
   902  	s.setServiceDeletionConcurrency(3)
   903  	env := makeEnviron(c)
   904  	s.setDummyStorage(c, env)
   905  
   906  	// Simulate 2 instances corresponding to two Azure services.
   907  	prefix := env.getEnvPrefix()
   908  	service1Name := prefix + "service1"
   909  	service1, service1Desc := makeAzureService(service1Name)
   910  	services := []*gwacl.HostedService{service1}
   911  	// The call to AllInstances() will return only one service (service1).
   912  	listInstancesResponses := getAzureServiceListResponse(c, []gwacl.HostedServiceDescriptor{*service1Desc})
   913  	destroyResponses := buildDestroyAzureServiceResponses(c, services)
   914  	responses := append(listInstancesResponses, destroyResponses...)
   915  	cleanupResponses := getVnetAndAffinityGroupCleanupResponses(c)
   916  	responses = append(responses, cleanupResponses...)
   917  	requests := gwacl.PatchManagementAPIResponses(responses)
   918  
   919  	err := env.Destroy()
   920  	c.Check(err, gc.IsNil)
   921  
   922  	// One request to get the list of all the environment's instances.
   923  	// Then two requests per destroyed machine (one to fetch the
   924  	// service's information, one to delete it) and two requests to delete
   925  	// the Virtual Network and the Affinity Group.
   926  	c.Check((*requests), gc.HasLen, 1+len(services)*2+2)
   927  	c.Check((*requests)[0].Method, gc.Equals, "GET")
   928  	assertOneRequestMatches(c, *requests, "GET", ".*"+service1Name+".*")
   929  	assertOneRequestMatches(c, *requests, "DELETE", ".*"+service1Name+".*")
   930  }
   931  
   932  func (*environSuite) TestGetInstance(c *gc.C) {
   933  	env := makeEnviron(c)
   934  	prefix := env.getEnvPrefix()
   935  	serviceName := prefix + "instance-name"
   936  	serviceDesc := gwacl.HostedServiceDescriptor{ServiceName: serviceName}
   937  	service := gwacl.HostedService{HostedServiceDescriptor: serviceDesc}
   938  	responses := getAzureServiceResponses(c, service)
   939  	gwacl.PatchManagementAPIResponses(responses)
   940  
   941  	instance, err := env.getInstance("serviceName")
   942  	c.Check(err, gc.IsNil)
   943  
   944  	c.Check(string(instance.Id()), gc.Equals, serviceName)
   945  	c.Check(instance, gc.FitsTypeOf, &azureInstance{})
   946  	azInstance := instance.(*azureInstance)
   947  	c.Check(azInstance.environ, gc.Equals, env)
   948  }
   949  
   950  func (*environSuite) TestNewOSVirtualDisk(c *gc.C) {
   951  	env := makeEnviron(c)
   952  	sourceImageName := "source-image-name"
   953  
   954  	vhd := env.newOSDisk(sourceImageName)
   955  
   956  	mediaLinkUrl, err := url.Parse(vhd.MediaLink)
   957  	c.Check(err, gc.IsNil)
   958  	storageAccount := env.ecfg.storageAccountName()
   959  	c.Check(mediaLinkUrl.Host, gc.Equals, fmt.Sprintf("%s.blob.core.windows.net", storageAccount))
   960  	c.Check(vhd.SourceImageName, gc.Equals, sourceImageName)
   961  }
   962  
   963  // mapInputEndpointsByPort takes a slice of input endpoints, and returns them
   964  // as a map keyed by their (external) ports.  This makes it easier to query
   965  // individual endpoints from an array whose ordering you don't know.
   966  // Multiple input endpoints for the same port are treated as an error.
   967  func mapInputEndpointsByPort(c *gc.C, endpoints []gwacl.InputEndpoint) map[int]gwacl.InputEndpoint {
   968  	mapping := make(map[int]gwacl.InputEndpoint)
   969  	for _, endpoint := range endpoints {
   970  		_, have := mapping[endpoint.Port]
   971  		c.Assert(have, gc.Equals, false)
   972  		mapping[endpoint.Port] = endpoint
   973  	}
   974  	return mapping
   975  }
   976  
   977  func (*environSuite) TestNewRole(c *gc.C) {
   978  	env := makeEnviron(c)
   979  	size := "Large"
   980  	vhd := env.newOSDisk("source-image-name")
   981  	userData := "example-user-data"
   982  	hostname := "hostname"
   983  
   984  	role := env.newRole(size, vhd, userData, hostname)
   985  
   986  	configs := role.ConfigurationSets
   987  	linuxConfig := configs[0]
   988  	networkConfig := configs[1]
   989  	c.Check(linuxConfig.CustomData, gc.Equals, userData)
   990  	c.Check(linuxConfig.Hostname, gc.Equals, hostname)
   991  	c.Check(linuxConfig.Username, gc.Not(gc.Equals), "")
   992  	c.Check(linuxConfig.Password, gc.Not(gc.Equals), "")
   993  	c.Check(linuxConfig.DisableSSHPasswordAuthentication, gc.Equals, "true")
   994  	c.Check(role.RoleSize, gc.Equals, size)
   995  	c.Check(role.OSVirtualHardDisk[0], gc.Equals, *vhd)
   996  
   997  	endpoints := mapInputEndpointsByPort(c, *networkConfig.InputEndpoints)
   998  
   999  	// The network config contains an endpoint for ssh communication.
  1000  	sshEndpoint, ok := endpoints[22]
  1001  	c.Assert(ok, gc.Equals, true)
  1002  	c.Check(sshEndpoint.LocalPort, gc.Equals, 22)
  1003  	c.Check(sshEndpoint.Protocol, gc.Equals, "tcp")
  1004  
  1005  	// There's also an endpoint for the state (mongodb) port.
  1006  	// TODO: Ought to have this only for state servers.
  1007  	stateEndpoint, ok := endpoints[env.Config().StatePort()]
  1008  	c.Assert(ok, gc.Equals, true)
  1009  	c.Check(stateEndpoint.LocalPort, gc.Equals, env.Config().StatePort())
  1010  	c.Check(stateEndpoint.Protocol, gc.Equals, "tcp")
  1011  
  1012  	// And one for the API port.
  1013  	// TODO: Ought to have this only for API servers.
  1014  	apiEndpoint, ok := endpoints[env.Config().APIPort()]
  1015  	c.Assert(ok, gc.Equals, true)
  1016  	c.Check(apiEndpoint.LocalPort, gc.Equals, env.Config().APIPort())
  1017  	c.Check(apiEndpoint.Protocol, gc.Equals, "tcp")
  1018  }
  1019  
  1020  func (*environSuite) TestNewDeployment(c *gc.C) {
  1021  	env := makeEnviron(c)
  1022  	deploymentName := "deployment-name"
  1023  	deploymentLabel := "deployment-label"
  1024  	virtualNetworkName := "virtual-network-name"
  1025  	vhd := env.newOSDisk("source-image-name")
  1026  	role := env.newRole("Small", vhd, "user-data", "hostname")
  1027  
  1028  	deployment := env.newDeployment(role, deploymentName, deploymentLabel, virtualNetworkName)
  1029  
  1030  	base64Label := base64.StdEncoding.EncodeToString([]byte(deploymentLabel))
  1031  	c.Check(deployment.Label, gc.Equals, base64Label)
  1032  	c.Check(deployment.Name, gc.Equals, deploymentName)
  1033  	c.Check(deployment.RoleList, gc.HasLen, 1)
  1034  }
  1035  
  1036  func (*environSuite) TestProviderReturnsAzureEnvironProvider(c *gc.C) {
  1037  	prov := makeEnviron(c).Provider()
  1038  	c.Assert(prov, gc.NotNil)
  1039  	azprov, ok := prov.(azureEnvironProvider)
  1040  	c.Assert(ok, gc.Equals, true)
  1041  	c.Check(azprov, gc.NotNil)
  1042  }
  1043  
  1044  func (*environSuite) TestCreateVirtualNetwork(c *gc.C) {
  1045  	env := makeEnviron(c)
  1046  	responses := []gwacl.DispatcherResponse{
  1047  		// No existing configuration found.
  1048  		gwacl.NewDispatcherResponse(nil, http.StatusNotFound, nil),
  1049  		// Accept upload of new configuration.
  1050  		gwacl.NewDispatcherResponse(nil, http.StatusOK, nil),
  1051  	}
  1052  	requests := gwacl.PatchManagementAPIResponses(responses)
  1053  
  1054  	env.createVirtualNetwork()
  1055  
  1056  	c.Assert(*requests, gc.HasLen, 2)
  1057  	request := (*requests)[1]
  1058  	body := gwacl.NetworkConfiguration{}
  1059  	err := xml.Unmarshal(request.Payload, &body)
  1060  	c.Assert(err, gc.IsNil)
  1061  	networkConf := (*body.VirtualNetworkSites)[0]
  1062  	c.Check(networkConf.Name, gc.Equals, env.getVirtualNetworkName())
  1063  	c.Check(networkConf.AffinityGroup, gc.Equals, env.getAffinityGroupName())
  1064  }
  1065  
  1066  func (*environSuite) TestDestroyVirtualNetwork(c *gc.C) {
  1067  	env := makeEnviron(c)
  1068  	// Prepare a configuration with a single virtual network.
  1069  	existingConfig := &gwacl.NetworkConfiguration{
  1070  		XMLNS: gwacl.XMLNS_NC,
  1071  		VirtualNetworkSites: &[]gwacl.VirtualNetworkSite{
  1072  			{Name: env.getVirtualNetworkName()},
  1073  		},
  1074  	}
  1075  	body, err := existingConfig.Serialize()
  1076  	c.Assert(err, gc.IsNil)
  1077  	responses := []gwacl.DispatcherResponse{
  1078  		// Return existing configuration.
  1079  		gwacl.NewDispatcherResponse([]byte(body), http.StatusOK, nil),
  1080  		// Accept upload of new configuration.
  1081  		gwacl.NewDispatcherResponse(nil, http.StatusOK, nil),
  1082  	}
  1083  	requests := gwacl.PatchManagementAPIResponses(responses)
  1084  
  1085  	env.deleteVirtualNetwork()
  1086  
  1087  	c.Assert(*requests, gc.HasLen, 2)
  1088  	// One request to get the existing network configuration.
  1089  	getRequest := (*requests)[0]
  1090  	c.Check(getRequest.Method, gc.Equals, "GET")
  1091  	// One request to update the network configuration.
  1092  	putRequest := (*requests)[1]
  1093  	c.Check(putRequest.Method, gc.Equals, "PUT")
  1094  	newConfig := gwacl.NetworkConfiguration{}
  1095  	err = xml.Unmarshal(putRequest.Payload, &newConfig)
  1096  	c.Assert(err, gc.IsNil)
  1097  	// The new configuration has no VirtualNetworkSites.
  1098  	c.Check(newConfig.VirtualNetworkSites, gc.IsNil)
  1099  }
  1100  
  1101  func (*environSuite) TestGetVirtualNetworkNameContainsEnvName(c *gc.C) {
  1102  	env := makeEnviron(c)
  1103  	c.Check(strings.Contains(env.getVirtualNetworkName(), env.Name()), jc.IsTrue)
  1104  }
  1105  
  1106  func (*environSuite) TestGetVirtualNetworkNameIsConstant(c *gc.C) {
  1107  	env := makeEnviron(c)
  1108  	c.Check(env.getVirtualNetworkName(), gc.Equals, env.getVirtualNetworkName())
  1109  }
  1110  
  1111  func (*environSuite) TestCreateAffinityGroup(c *gc.C) {
  1112  	env := makeEnviron(c)
  1113  	responses := []gwacl.DispatcherResponse{
  1114  		gwacl.NewDispatcherResponse(nil, http.StatusCreated, nil),
  1115  	}
  1116  	requests := gwacl.PatchManagementAPIResponses(responses)
  1117  
  1118  	env.createAffinityGroup()
  1119  
  1120  	c.Assert(*requests, gc.HasLen, 1)
  1121  	request := (*requests)[0]
  1122  	body := gwacl.CreateAffinityGroup{}
  1123  	err := xml.Unmarshal(request.Payload, &body)
  1124  	c.Assert(err, gc.IsNil)
  1125  	c.Check(body.Name, gc.Equals, env.getAffinityGroupName())
  1126  	// This is a testing antipattern, the expected data comes from
  1127  	// config defaults.  Fix it sometime.
  1128  	c.Check(body.Location, gc.Equals, "location")
  1129  }
  1130  
  1131  func (*environSuite) TestDestroyAffinityGroup(c *gc.C) {
  1132  	env := makeEnviron(c)
  1133  	responses := []gwacl.DispatcherResponse{
  1134  		gwacl.NewDispatcherResponse(nil, http.StatusOK, nil),
  1135  	}
  1136  	requests := gwacl.PatchManagementAPIResponses(responses)
  1137  
  1138  	env.deleteAffinityGroup()
  1139  
  1140  	c.Assert(*requests, gc.HasLen, 1)
  1141  	request := (*requests)[0]
  1142  	c.Check(strings.Contains(request.URL, env.getAffinityGroupName()), jc.IsTrue)
  1143  	c.Check(request.Method, gc.Equals, "DELETE")
  1144  }
  1145  
  1146  func (*environSuite) TestGetAffinityGroupName(c *gc.C) {
  1147  	env := makeEnviron(c)
  1148  	c.Check(strings.Contains(env.getAffinityGroupName(), env.Name()), jc.IsTrue)
  1149  }
  1150  
  1151  func (*environSuite) TestGetAffinityGroupNameIsConstant(c *gc.C) {
  1152  	env := makeEnviron(c)
  1153  	c.Check(env.getAffinityGroupName(), gc.Equals, env.getAffinityGroupName())
  1154  }
  1155  
  1156  func (*environSuite) TestGetImageMetadataSigningRequiredDefaultsToTrue(c *gc.C) {
  1157  	env := makeEnviron(c)
  1158  	// Hard-coded to true for now.  Once we support other base URLs, this
  1159  	// may have to become configurable.
  1160  	c.Check(env.getImageMetadataSigningRequired(), gc.Equals, true)
  1161  }
  1162  
  1163  func (*environSuite) TestSelectInstanceTypeAndImageUsesForcedImage(c *gc.C) {
  1164  	env := makeEnviron(c)
  1165  	forcedImage := "my-image"
  1166  	env.ecfg.attrs["force-image-name"] = forcedImage
  1167  
  1168  	aim := gwacl.RoleNameMap["ExtraLarge"]
  1169  	cons := constraints.Value{
  1170  		CpuCores: &aim.CpuCores,
  1171  		Mem:      &aim.Mem,
  1172  	}
  1173  
  1174  	instanceType, image, err := env.selectInstanceTypeAndImage(cons, "precise", "West US")
  1175  	c.Assert(err, gc.IsNil)
  1176  
  1177  	c.Check(instanceType, gc.Equals, aim.Name)
  1178  	c.Check(image, gc.Equals, forcedImage)
  1179  }
  1180  
  1181  func (*environSuite) TestSelectInstanceTypeAndImageUsesSimplestreamsByDefault(c *gc.C) {
  1182  	env := makeEnviron(c)
  1183  
  1184  	// We'll tailor our constraints so as to get a specific instance type.
  1185  	aim := gwacl.RoleNameMap["ExtraSmall"]
  1186  	cons := constraints.Value{
  1187  		CpuCores: &aim.CpuCores,
  1188  		Mem:      &aim.Mem,
  1189  	}
  1190  
  1191  	// We have one image available.
  1192  	images := []*imagemetadata.ImageMetadata{
  1193  		{
  1194  			Id:          "image",
  1195  			VType:       "Hyper-V",
  1196  			Arch:        "amd64",
  1197  			RegionAlias: "North Europe",
  1198  			RegionName:  "North Europe",
  1199  			Endpoint:    "http://localhost/",
  1200  		},
  1201  	}
  1202  	cleanup := patchFetchImageMetadata(images, nil)
  1203  	defer cleanup()
  1204  
  1205  	instanceType, image, err := env.selectInstanceTypeAndImage(cons, "precise", "West US")
  1206  	c.Assert(err, gc.IsNil)
  1207  
  1208  	c.Check(instanceType, gc.Equals, aim.Name)
  1209  	c.Check(image, gc.Equals, "image")
  1210  }
  1211  
  1212  func (*environSuite) TestConvertToInstances(c *gc.C) {
  1213  	services := []gwacl.HostedServiceDescriptor{
  1214  		{ServiceName: "foo"}, {ServiceName: "bar"},
  1215  	}
  1216  	env := makeEnviron(c)
  1217  	instances := convertToInstances(services, env)
  1218  	c.Check(instances, gc.DeepEquals, []instance.Instance{
  1219  		&azureInstance{services[0], env},
  1220  		&azureInstance{services[1], env},
  1221  	})
  1222  }
  1223  
  1224  func (*environSuite) TestExtractStorageKeyPicksPrimaryKeyIfSet(c *gc.C) {
  1225  	keys := gwacl.StorageAccountKeys{
  1226  		Primary:   "mainkey",
  1227  		Secondary: "otherkey",
  1228  	}
  1229  	c.Check(extractStorageKey(&keys), gc.Equals, "mainkey")
  1230  }
  1231  
  1232  func (*environSuite) TestExtractStorageKeyFallsBackToSecondaryKey(c *gc.C) {
  1233  	keys := gwacl.StorageAccountKeys{
  1234  		Secondary: "sparekey",
  1235  	}
  1236  	c.Check(extractStorageKey(&keys), gc.Equals, "sparekey")
  1237  }
  1238  
  1239  func (*environSuite) TestExtractStorageKeyReturnsBlankIfNoneSet(c *gc.C) {
  1240  	c.Check(extractStorageKey(&gwacl.StorageAccountKeys{}), gc.Equals, "")
  1241  }
  1242  
  1243  func assertSourceContents(c *gc.C, source simplestreams.DataSource, filename string, content []byte) {
  1244  	rc, _, err := source.Fetch(filename)
  1245  	c.Assert(err, gc.IsNil)
  1246  	defer rc.Close()
  1247  	retrieved, err := ioutil.ReadAll(rc)
  1248  	c.Assert(err, gc.IsNil)
  1249  	c.Assert(retrieved, gc.DeepEquals, content)
  1250  }
  1251  
  1252  func (s *environSuite) assertGetImageMetadataSources(c *gc.C, stream, officialSourcePath string) {
  1253  	envAttrs := makeAzureConfigMap(c)
  1254  	if stream != "" {
  1255  		envAttrs["image-stream"] = stream
  1256  	}
  1257  	env := makeEnvironWithConfig(c, envAttrs)
  1258  	s.setDummyStorage(c, env)
  1259  
  1260  	data := []byte{1, 2, 3, 4}
  1261  	env.Storage().Put("images/filename", bytes.NewReader(data), int64(len(data)))
  1262  
  1263  	sources, err := imagemetadata.GetMetadataSources(env)
  1264  	c.Assert(err, gc.IsNil)
  1265  	c.Assert(len(sources), gc.Equals, 2)
  1266  	assertSourceContents(c, sources[0], "filename", data)
  1267  	url, err := sources[1].URL("")
  1268  	c.Assert(err, gc.IsNil)
  1269  	c.Assert(url, gc.Equals, fmt.Sprintf("http://cloud-images.ubuntu.com/%s/", officialSourcePath))
  1270  }
  1271  
  1272  func (s *environSuite) TestGetImageMetadataSources(c *gc.C) {
  1273  	s.assertGetImageMetadataSources(c, "", "releases")
  1274  	s.assertGetImageMetadataSources(c, "released", "releases")
  1275  	s.assertGetImageMetadataSources(c, "daily", "daily")
  1276  }
  1277  
  1278  func (s *environSuite) TestGetToolsMetadataSources(c *gc.C) {
  1279  	env := makeEnviron(c)
  1280  	s.setDummyStorage(c, env)
  1281  
  1282  	data := []byte{1, 2, 3, 4}
  1283  	env.Storage().Put("tools/filename", bytes.NewReader(data), int64(len(data)))
  1284  
  1285  	sources, err := tools.GetMetadataSources(env)
  1286  	c.Assert(err, gc.IsNil)
  1287  	c.Assert(len(sources), gc.Equals, 1)
  1288  	assertSourceContents(c, sources[0], "filename", data)
  1289  }