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