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