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