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