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 }