github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/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(c, env, "juju-"+name+"-service1") 208 service2 := makeDeployment(c, env, "juju-"+name+"-service2") 209 service3 := makeDeployment(c, env, "notjuju-"+name+"-service3") 210 service4 := makeDeployment(c, 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(c, 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(c, 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(c, 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(c, 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(c, env, prefix+"myservice1") 507 service2 := makeDeployment(c, 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(c, env, prefix+"myservice1") 526 service2 := makeLegacyDeployment(c, 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(c, env, prefix+"service1") 547 service2 := makeDeployment(c, env, prefix+"service2") 548 service3 := makeLegacyDeployment(c, env, prefix+"service3") 549 service4 := makeDeployment(c, 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 // There is a minute chance that this tries the same name twice, and 711 // then this test will fail. If that happens, try seeding the 712 // randomizer with some fixed seed that doens't produce the problem. 713 attemptedNames := make(map[string]int) 714 for _, request := range *requests { 715 // Exit the loop if we hit the request to create the service, it comes 716 // after the check calls. 717 if request.Method == "POST" { 718 break 719 } 720 // Name is the last part of the URL from the GET requests that check 721 // availability. 722 _, name := path.Split(strings.TrimRight(request.URL, "/")) 723 attemptedNames[name] += 1 724 } 725 // The three attempts we just made all had different service names. 726 c.Check(attemptedNames, gc.HasLen, 3) 727 728 // Once newHostedService succeeds, we get a hosted service with the 729 // name returned from GetHostedServiceProperties. 730 c.Check(service.ServiceName, gc.Equals, "anything") 731 } 732 733 func (*environSuite) TestNewHostedServiceFailsIfUnableToFindUniqueName(c *gc.C) { 734 errorBody := makeNonAvailabilityResponse(c) 735 responses := []gwacl.DispatcherResponse{} 736 for counter := 0; counter < 100; counter++ { 737 responses = append(responses, gwacl.NewDispatcherResponse(errorBody, http.StatusOK, nil)) 738 } 739 gwacl.PatchManagementAPIResponses(responses) 740 azure, err := gwacl.NewManagementAPI("subscription", "", "West US") 741 c.Assert(err, jc.ErrorIsNil) 742 743 _, err = newHostedService(azure, "service", "affinity-group", "") 744 c.Assert(err, gc.NotNil) 745 c.Check(err, gc.ErrorMatches, "could not come up with a unique hosted service name.*") 746 } 747 748 func buildGetServicePropertiesResponses(c *gc.C, services ...*gwacl.HostedService) []gwacl.DispatcherResponse { 749 responses := make([]gwacl.DispatcherResponse, len(services)) 750 for i, service := range services { 751 serviceXML, err := service.Serialize() 752 c.Assert(err, jc.ErrorIsNil) 753 responses[i] = gwacl.NewDispatcherResponse([]byte(serviceXML), http.StatusOK, nil) 754 } 755 return responses 756 } 757 758 func buildStatusOKResponses(c *gc.C, n int) []gwacl.DispatcherResponse { 759 responses := make([]gwacl.DispatcherResponse, n) 760 for i := range responses { 761 responses[i] = gwacl.NewDispatcherResponse(nil, http.StatusOK, nil) 762 } 763 return responses 764 } 765 766 func makeAzureService(name string) *gwacl.HostedService { 767 return &gwacl.HostedService{ 768 HostedServiceDescriptor: gwacl.HostedServiceDescriptor{ServiceName: name}, 769 } 770 } 771 772 func makeRole(env *azureEnviron, c *gc.C) *gwacl.Role { 773 size := "Large" 774 vhd, err := env.newOSDisk("source-image-name", "trusty") 775 c.Assert(err, jc.ErrorIsNil) 776 777 userData := "example-user-data" 778 role, err := env.newRole(size, vhd, false, userData, "trusty", nil) 779 c.Assert(err, jc.ErrorIsNil) 780 return role 781 } 782 783 func makeLegacyDeployment(c *gc.C, env *azureEnviron, serviceName string) *gwacl.HostedService { 784 service := makeAzureService(serviceName) 785 service.Deployments = []gwacl.Deployment{{ 786 Name: serviceName, 787 RoleList: []gwacl.Role{*makeRole(env, c)}, 788 }} 789 return service 790 } 791 792 func makeDeployment(c *gc.C, env *azureEnviron, serviceName string) *gwacl.HostedService { 793 service := makeAzureService(serviceName) 794 service.Deployments = []gwacl.Deployment{{ 795 Name: serviceName + "-v2", 796 RoleList: []gwacl.Role{*makeRole(env, c), *makeRole(env, c)}, 797 }} 798 return service 799 } 800 801 func (s *environSuite) TestStopInstancesDestroysMachines(c *gc.C) { 802 env := makeEnviron(c) 803 prefix := env.getEnvPrefix() 804 service1Name := "service1" 805 service1 := makeLegacyDeployment(c, env, prefix+service1Name) 806 service2Name := "service2" 807 service2 := makeDeployment(c, env, prefix+service2Name) 808 809 inst1, err := env.getInstance(service1, "") 810 c.Assert(err, jc.ErrorIsNil) 811 role2Name := service2.Deployments[0].RoleList[0].RoleName 812 inst2, err := env.getInstance(service2, role2Name) 813 c.Assert(err, jc.ErrorIsNil) 814 role3Name := service2.Deployments[0].RoleList[1].RoleName 815 inst3, err := env.getInstance(service2, role3Name) 816 c.Assert(err, jc.ErrorIsNil) 817 818 responses := buildGetServicePropertiesResponses(c, service1) 819 responses = append(responses, buildStatusOKResponses(c, 1)...) // DeleteHostedService 820 responses = append(responses, buildGetServicePropertiesResponses(c, service2)...) 821 responses = append(responses, buildStatusOKResponses(c, 1)...) // DeleteHostedService 822 requests := gwacl.PatchManagementAPIResponses(responses) 823 err = env.StopInstances(inst1.Id(), inst2.Id(), inst3.Id()) 824 c.Check(err, jc.ErrorIsNil) 825 826 // One GET and DELETE per service 827 // (GetHostedServiceProperties and DeleteHostedService). 828 c.Check(len(*requests), gc.Equals, len(responses)) 829 assertOneRequestMatches(c, *requests, "GET", ".*"+service1Name+".") 830 assertOneRequestMatches(c, *requests, "GET", ".*"+service2Name+".*") 831 assertOneRequestMatches(c, *requests, "DELETE", ".*"+service1Name+".*") 832 assertOneRequestMatches(c, *requests, "DELETE", ".*"+service2Name+".*") 833 } 834 835 func (s *environSuite) TestStopInstancesServiceSubset(c *gc.C) { 836 env := makeEnviron(c) 837 service := makeDeployment(c, env, env.getEnvPrefix()+"service") 838 839 role1Name := service.Deployments[0].RoleList[0].RoleName 840 inst1, err := env.getInstance(service, role1Name) 841 c.Assert(err, jc.ErrorIsNil) 842 843 responses := buildGetServicePropertiesResponses(c, service) 844 responses = append(responses, buildStatusOKResponses(c, 1)...) // DeleteRole 845 requests := gwacl.PatchManagementAPIResponses(responses) 846 err = env.StopInstances(inst1.Id()) 847 c.Check(err, jc.ErrorIsNil) 848 849 // One GET for the service, and one DELETE for the role. 850 // The service isn't deleted because it has two roles, 851 // and only one is being deleted. 852 c.Check(len(*requests), gc.Equals, len(responses)) 853 assertOneRequestMatches(c, *requests, "GET", ".*"+service.ServiceName+".") 854 assertOneRequestMatches(c, *requests, "DELETE", ".*"+role1Name+".*") 855 } 856 857 func (s *environSuite) TestStopInstancesWhenStoppingMachinesFails(c *gc.C) { 858 env := makeEnviron(c) 859 prefix := env.getEnvPrefix() 860 service1 := makeDeployment(c, env, prefix+"service1") 861 service2 := makeDeployment(c, env, prefix+"service2") 862 service1Role1Name := service1.Deployments[0].RoleList[0].RoleName 863 inst1, err := env.getInstance(service1, service1Role1Name) 864 c.Assert(err, jc.ErrorIsNil) 865 service2Role1Name := service2.Deployments[0].RoleList[0].RoleName 866 inst2, err := env.getInstance(service2, service2Role1Name) 867 c.Assert(err, jc.ErrorIsNil) 868 869 responses := buildGetServicePropertiesResponses(c, service1) 870 // Failed to delete one of the services. This will cause StopInstances to stop 871 // immediately. 872 responses = append(responses, gwacl.NewDispatcherResponse(nil, http.StatusConflict, nil)) 873 requests := gwacl.PatchManagementAPIResponses(responses) 874 875 err = env.StopInstances(inst1.Id(), inst2.Id()) 876 c.Check(err, gc.ErrorMatches, ".*Conflict.*") 877 878 c.Check(len(*requests), gc.Equals, len(responses)) 879 assertOneRequestMatches(c, *requests, "GET", ".*"+service1.ServiceName+".*") 880 assertOneRequestMatches(c, *requests, "DELETE", service1.ServiceName) 881 } 882 883 func (s *environSuite) TestStopInstancesWithZeroInstance(c *gc.C) { 884 env := makeEnviron(c) 885 err := env.StopInstances() 886 c.Check(err, jc.ErrorIsNil) 887 } 888 889 // getVnetCleanupResponse returns the response 890 // that a fake http server should return when gwacl's 891 // RemoveVirtualNetworkSite() is called. 892 func getVnetCleanupResponse(c *gc.C) gwacl.DispatcherResponse { 893 existingConfig := &gwacl.NetworkConfiguration{ 894 XMLNS: gwacl.XMLNS_NC, 895 VirtualNetworkSites: nil, 896 } 897 body, err := existingConfig.Serialize() 898 c.Assert(err, jc.ErrorIsNil) 899 return gwacl.NewDispatcherResponse([]byte(body), http.StatusOK, nil) 900 } 901 902 func (s *environSuite) TestDestroyDoesNotCleanStorageIfError(c *gc.C) { 903 env := makeEnviron(c) 904 s.setDummyStorage(c, env) 905 906 // Populate storage. 907 err := env.Storage().Put("anything", strings.NewReader(""), 0) 908 c.Assert(err, jc.ErrorIsNil) 909 910 responses := []gwacl.DispatcherResponse{ 911 gwacl.NewDispatcherResponse(nil, http.StatusBadRequest, nil), 912 } 913 gwacl.PatchManagementAPIResponses(responses) 914 915 err = env.Destroy() 916 c.Check(err, gc.NotNil) 917 918 files, err := storage.List(env.Storage(), "") 919 c.Assert(err, jc.ErrorIsNil) 920 c.Check(files, gc.DeepEquals, []string{"anything"}) 921 } 922 923 func (s *environSuite) TestDestroyCleansUpStorage(c *gc.C) { 924 env := makeEnviron(c) 925 s.setDummyStorage(c, env) 926 // Populate storage. 927 err := env.Storage().Put("anything", strings.NewReader(""), 0) 928 c.Assert(err, jc.ErrorIsNil) 929 responses := getAzureServiceListResponse(c) 930 responses = append(responses, getVnetCleanupResponse(c)) 931 responses = append(responses, buildStatusOKResponses(c, 1)...) // DeleteAffinityGroup 932 gwacl.PatchManagementAPIResponses(responses) 933 934 err = env.Destroy() 935 c.Check(err, jc.ErrorIsNil) 936 937 files, err := storage.List(env.Storage(), "") 938 c.Assert(err, jc.ErrorIsNil) 939 c.Check(files, gc.HasLen, 0) 940 } 941 942 func (s *environSuite) TestDestroyDeletesVirtualNetworkAndAffinityGroup(c *gc.C) { 943 env := makeEnviron(c) 944 s.setDummyStorage(c, env) 945 responses := getAzureServiceListResponse(c) 946 // Prepare a configuration with a single virtual network. 947 existingConfig := &gwacl.NetworkConfiguration{ 948 XMLNS: gwacl.XMLNS_NC, 949 VirtualNetworkSites: &[]gwacl.VirtualNetworkSite{ 950 {Name: env.getVirtualNetworkName()}, 951 }, 952 } 953 body, err := existingConfig.Serialize() 954 c.Assert(err, jc.ErrorIsNil) 955 cleanupResponses := []gwacl.DispatcherResponse{ 956 // Return existing configuration. 957 gwacl.NewDispatcherResponse([]byte(body), http.StatusOK, nil), 958 // Accept upload of new configuration. 959 gwacl.NewDispatcherResponse(nil, http.StatusOK, nil), 960 // Accept deletion of affinity group. 961 gwacl.NewDispatcherResponse(nil, http.StatusOK, nil), 962 } 963 responses = append(responses, cleanupResponses...) 964 requests := gwacl.PatchManagementAPIResponses(responses) 965 966 err = env.Destroy() 967 c.Check(err, jc.ErrorIsNil) 968 969 c.Assert(*requests, gc.HasLen, 4) 970 // One request to get the network configuration. 971 getRequest := (*requests)[1] 972 c.Check(getRequest.Method, gc.Equals, "GET") 973 c.Check(strings.HasSuffix(getRequest.URL, "services/networking/media"), jc.IsTrue) 974 // One request to upload the new version of the network configuration. 975 putRequest := (*requests)[2] 976 c.Check(putRequest.Method, gc.Equals, "PUT") 977 c.Check(strings.HasSuffix(putRequest.URL, "services/networking/media"), jc.IsTrue) 978 // One request to delete the Affinity Group. 979 agRequest := (*requests)[3] 980 c.Check(strings.Contains(agRequest.URL, env.getAffinityGroupName()), jc.IsTrue) 981 c.Check(agRequest.Method, gc.Equals, "DELETE") 982 } 983 984 func (s *environSuite) TestDestroyDoesNotFailIfVirtualNetworkDeletionFails(c *gc.C) { 985 env := makeEnviron(c) 986 s.setDummyStorage(c, env) 987 responses := getAzureServiceListResponse(c) 988 cleanupResponses := []gwacl.DispatcherResponse{ 989 // Fail to get vnet for deletion 990 gwacl.NewDispatcherResponse(nil, http.StatusConflict, nil), 991 // Fail to delete affinity group 992 gwacl.NewDispatcherResponse(nil, http.StatusConflict, nil), 993 } 994 responses = append(responses, cleanupResponses...) 995 requests := gwacl.PatchManagementAPIResponses(responses) 996 997 err := env.Destroy() 998 c.Check(err, jc.ErrorIsNil) 999 c.Assert(*requests, gc.HasLen, 3) 1000 1001 getRequest := (*requests)[1] 1002 c.Check(getRequest.Method, gc.Equals, "GET") 1003 c.Check(strings.HasSuffix(getRequest.URL, "services/networking/media"), jc.IsTrue) 1004 1005 deleteRequest := (*requests)[2] 1006 c.Check(deleteRequest.Method, gc.Equals, "DELETE") 1007 c.Check(strings.Contains(deleteRequest.URL, env.getAffinityGroupName()), jc.IsTrue) 1008 } 1009 1010 func (s *environSuite) TestDestroyDoesNotFailIfAffinityGroupDeletionFails(c *gc.C) { 1011 env := makeEnviron(c) 1012 s.setDummyStorage(c, env) 1013 responses := getAzureServiceListResponse(c) 1014 // Prepare a configuration with a single virtual network. 1015 existingConfig := &gwacl.NetworkConfiguration{ 1016 XMLNS: gwacl.XMLNS_NC, 1017 VirtualNetworkSites: &[]gwacl.VirtualNetworkSite{ 1018 {Name: env.getVirtualNetworkName()}, 1019 }, 1020 } 1021 body, err := existingConfig.Serialize() 1022 c.Assert(err, jc.ErrorIsNil) 1023 cleanupResponses := []gwacl.DispatcherResponse{ 1024 // Return existing configuration. 1025 gwacl.NewDispatcherResponse([]byte(body), http.StatusOK, nil), 1026 // Accept upload of new configuration. 1027 gwacl.NewDispatcherResponse(nil, http.StatusOK, nil), 1028 // Fail to delete affinity group 1029 gwacl.NewDispatcherResponse(nil, http.StatusConflict, nil), 1030 } 1031 responses = append(responses, cleanupResponses...) 1032 requests := gwacl.PatchManagementAPIResponses(responses) 1033 1034 err = env.Destroy() 1035 c.Check(err, jc.ErrorIsNil) 1036 c.Assert(*requests, gc.HasLen, 4) 1037 1038 getRequest := (*requests)[1] 1039 c.Check(getRequest.Method, gc.Equals, "GET") 1040 c.Check(strings.HasSuffix(getRequest.URL, "services/networking/media"), jc.IsTrue) 1041 putRequest := (*requests)[2] 1042 c.Check(putRequest.Method, gc.Equals, "PUT") 1043 c.Check(strings.HasSuffix(putRequest.URL, "services/networking/media"), jc.IsTrue) 1044 } 1045 1046 var emptyListResponse = ` 1047 <?xml version="1.0" encoding="utf-8"?> 1048 <EnumerationResults ContainerName="http://myaccount.blob.core.windows.net/mycontainer"> 1049 <Prefix>prefix</Prefix> 1050 <Marker>marker</Marker> 1051 <MaxResults>maxresults</MaxResults> 1052 <Delimiter>delimiter</Delimiter> 1053 <Blobs></Blobs> 1054 <NextMarker /> 1055 </EnumerationResults>` 1056 1057 // assertOneRequestMatches asserts that at least one request in the given slice 1058 // contains a request with the given method and whose URL matches the given regexp. 1059 func assertOneRequestMatches(c *gc.C, requests []*gwacl.X509Request, method string, urlPattern string) { 1060 for _, request := range requests { 1061 matched, err := regexp.MatchString(urlPattern, request.URL) 1062 if err == nil && request.Method == method && matched { 1063 return 1064 } 1065 } 1066 c.Error(fmt.Sprintf("none of the requests matches: Method=%v, URL pattern=%v", method, urlPattern)) 1067 } 1068 1069 func (s *environSuite) TestDestroyStopsAllInstances(c *gc.C) { 1070 env := makeEnviron(c) 1071 s.setDummyStorage(c, env) 1072 name := env.Config().Name() 1073 service1 := makeDeployment(c, env, "juju-"+name+"-service1") 1074 service2 := makeDeployment(c, env, "juju-"+name+"-service2") 1075 service3 := makeDeployment(c, env, "juju-"+name+"-1-service3") 1076 1077 // The call to AllInstances() will return only one service (service1). 1078 responses := getAzureServiceListResponse( 1079 c, service1.HostedServiceDescriptor, service2.HostedServiceDescriptor, service3.HostedServiceDescriptor, 1080 ) 1081 responses = append(responses, buildStatusOKResponses(c, 2)...) // DeleteHostedService 1082 responses = append(responses, getVnetCleanupResponse(c)) 1083 responses = append(responses, buildStatusOKResponses(c, 1)...) // DeleteAffinityGroup 1084 requests := gwacl.PatchManagementAPIResponses(responses) 1085 1086 err := env.Destroy() 1087 c.Check(err, jc.ErrorIsNil) 1088 1089 // One request to get the list of all the environment's instances. 1090 // One delete request per destroyed service, and two additional 1091 // requests to delete the Virtual Network and the Affinity Group. 1092 c.Check((*requests), gc.HasLen, 5) 1093 c.Check((*requests)[0].Method, gc.Equals, "GET") 1094 assertOneRequestMatches(c, *requests, "DELETE", ".*"+service1.ServiceName+".*") 1095 assertOneRequestMatches(c, *requests, "DELETE", ".*"+service2.ServiceName+".*") 1096 } 1097 1098 func (s *environSuite) TestGetInstance(c *gc.C) { 1099 env := makeEnviron(c) 1100 service1 := makeLegacyDeployment(c, env, "service1") 1101 service2 := makeDeployment(c, env, "service1") 1102 1103 // azureEnviron.Instances will call getInstance with roleName=="" 1104 // for legacy instances. This will cause getInstance to get the 1105 // one and only role (or error if there is more than one). 1106 inst1, err := env.getInstance(service1, "") 1107 c.Assert(err, jc.ErrorIsNil) 1108 c.Check(inst1.Id(), gc.Equals, instance.Id("service1")) 1109 c.Assert(inst1, gc.FitsTypeOf, &azureInstance{}) 1110 c.Check(inst1.(*azureInstance).environ, gc.Equals, env) 1111 c.Check(inst1.(*azureInstance).roleName, gc.Equals, service1.Deployments[0].RoleList[0].RoleName) 1112 service1.Deployments[0].RoleList = service2.Deployments[0].RoleList 1113 inst1, err = env.getInstance(service1, "") 1114 c.Check(err, gc.ErrorMatches, `expected one role for "service1", got 2`) 1115 1116 inst2, err := env.getInstance(service2, service2.Deployments[0].RoleList[0].RoleName) 1117 c.Assert(err, jc.ErrorIsNil) 1118 c.Check(inst2.Id(), gc.Equals, instance.Id("service1-"+service2.Deployments[0].RoleList[0].RoleName)) 1119 } 1120 1121 func (s *environSuite) TestInitialPorts(c *gc.C) { 1122 env := makeEnviron(c) 1123 service1 := makeLegacyDeployment(c, env, "service1") 1124 service2 := makeDeployment(c, env, "service2") 1125 service3 := makeDeployment(c, env, "service3") 1126 service3.Label = base64.StdEncoding.EncodeToString([]byte(stateServerLabel)) 1127 1128 role1 := &service1.Deployments[0].RoleList[0] 1129 inst1, err := env.getInstance(service1, role1.RoleName) 1130 c.Assert(err, jc.ErrorIsNil) 1131 c.Assert(inst1.(*azureInstance).maskStateServerPorts, jc.IsTrue) 1132 role2 := &service2.Deployments[0].RoleList[0] 1133 inst2, err := env.getInstance(service2, role2.RoleName) 1134 c.Assert(err, jc.ErrorIsNil) 1135 role3 := &service3.Deployments[0].RoleList[0] 1136 inst3, err := env.getInstance(service3, role3.RoleName) 1137 c.Assert(err, jc.ErrorIsNil) 1138 1139 // Only role2 should report opened state server ports via the Ports method. 1140 dummyRole := *role1 1141 configSetNetwork(&dummyRole).InputEndpoints = &[]gwacl.InputEndpoint{{ 1142 LocalPort: env.Config().APIPort(), 1143 Protocol: "tcp", 1144 Name: "apiserver", 1145 Port: env.Config().APIPort(), 1146 }} 1147 reportsStateServerPorts := func(inst instance.Instance) bool { 1148 responses := preparePortChangeConversation(c, &dummyRole) 1149 gwacl.PatchManagementAPIResponses(responses) 1150 ports, err := inst.Ports("") 1151 c.Assert(err, jc.ErrorIsNil) 1152 portmap := make(map[network.PortRange]bool) 1153 for _, portRange := range ports { 1154 portmap[portRange] = true 1155 } 1156 apiPortRange := network.PortRange{ 1157 Protocol: "tcp", 1158 FromPort: env.Config().APIPort(), 1159 ToPort: env.Config().APIPort(), 1160 } 1161 return portmap[apiPortRange] 1162 } 1163 c.Check(inst1, gc.Not(jc.Satisfies), reportsStateServerPorts) 1164 c.Check(inst2, jc.Satisfies, reportsStateServerPorts) 1165 c.Check(inst3, gc.Not(jc.Satisfies), reportsStateServerPorts) 1166 } 1167 1168 func (*environSuite) TestNewOSVirtualDisk(c *gc.C) { 1169 env := makeEnviron(c) 1170 sourceImageName := "source-image-name" 1171 1172 tests := []struct { 1173 series string 1174 expected string 1175 }{ 1176 {"trusty", "Linux"}, 1177 {"win2012r2", "Windows"}, 1178 } 1179 for _, test := range tests { 1180 vhd, err := env.newOSDisk(sourceImageName, test.series) 1181 c.Assert(err, jc.ErrorIsNil) 1182 1183 mediaLinkUrl, err := url.Parse(vhd.MediaLink) 1184 c.Check(err, jc.ErrorIsNil) 1185 storageAccount := env.ecfg.storageAccountName() 1186 c.Check(mediaLinkUrl.Host, gc.Equals, fmt.Sprintf("%s.blob.core.windows.net", storageAccount)) 1187 c.Check(vhd.SourceImageName, gc.Equals, sourceImageName) 1188 c.Check(vhd.OS, gc.Equals, test.expected) 1189 } 1190 } 1191 1192 // mapInputEndpointsByPort takes a slice of input endpoints, and returns them 1193 // as a map keyed by their (external) ports. This makes it easier to query 1194 // individual endpoints from an array whose ordering you don't know. 1195 // Multiple input endpoints for the same port are treated as an error. 1196 func mapInputEndpointsByPort(c *gc.C, endpoints []gwacl.InputEndpoint) map[int]gwacl.InputEndpoint { 1197 mapping := make(map[int]gwacl.InputEndpoint) 1198 for _, endpoint := range endpoints { 1199 _, have := mapping[endpoint.Port] 1200 c.Assert(have, jc.IsFalse) 1201 mapping[endpoint.Port] = endpoint 1202 } 1203 return mapping 1204 } 1205 1206 func (s *environSuite) TestNewUnixRole(c *gc.C) { 1207 s.testNewUnixRole(c, false) 1208 } 1209 1210 func (s *environSuite) TestNewUnixRoleStateServer(c *gc.C) { 1211 s.testNewUnixRole(c, true) 1212 } 1213 1214 func (*environSuite) testNewUnixRole(c *gc.C, stateServer bool) { 1215 env := makeEnviron(c) 1216 size := "Large" 1217 vhd, err := env.newOSDisk("source-image-name", "trusty") 1218 c.Assert(err, jc.ErrorIsNil) 1219 userData := "example-user-data" 1220 1221 role, err := env.newRole(size, vhd, stateServer, userData, "trusty", nil) 1222 c.Assert(err, jc.ErrorIsNil) 1223 1224 configs := role.ConfigurationSets 1225 linuxConfig := configs[0] 1226 networkConfig := configs[1] 1227 c.Check(linuxConfig.ConfigurationSetType, gc.Equals, gwacl.CONFIG_SET_LINUX_PROVISIONING) 1228 c.Check(linuxConfig.CustomData, gc.Equals, userData) 1229 c.Check(linuxConfig.Hostname, gc.Equals, role.RoleName) 1230 c.Check(linuxConfig.Username, gc.Not(gc.Equals), "") 1231 c.Check(linuxConfig.Password, gc.Not(gc.Equals), "") 1232 c.Check(linuxConfig.DisableSSHPasswordAuthentication, gc.Equals, "true") 1233 c.Check(role.RoleSize, gc.Equals, size) 1234 c.Check(role.OSVirtualHardDisk, gc.DeepEquals, vhd) 1235 1236 endpoints := mapInputEndpointsByPort(c, *networkConfig.InputEndpoints) 1237 1238 // The network config contains an endpoint for ssh communication. 1239 sshEndpoint, ok := endpoints[22] 1240 c.Assert(ok, jc.IsTrue) 1241 c.Check(sshEndpoint.LocalPort, gc.Equals, 22) 1242 c.Check(sshEndpoint.Protocol, gc.Equals, "tcp") 1243 1244 if stateServer { 1245 // There should be an endpoint for the API port. 1246 apiEndpoint, ok := endpoints[env.Config().APIPort()] 1247 c.Assert(ok, jc.IsTrue) 1248 c.Check(apiEndpoint.LocalPort, gc.Equals, env.Config().APIPort()) 1249 c.Check(apiEndpoint.Protocol, gc.Equals, "tcp") 1250 } 1251 } 1252 1253 func (s *environSuite) TestNewWindowsRole(c *gc.C) { 1254 env := makeEnviron(c) 1255 s.setDummyStorage(c, env) 1256 1257 size := "Large" 1258 vhd, err := env.newOSDisk("source-img-name", "win2012r2") 1259 c.Assert(err, jc.ErrorIsNil) 1260 userData := "example-udata" 1261 1262 snap := env.getSnapshot() 1263 role, err := env.newRole(size, vhd, false, userData, "win2012r2", snap) 1264 c.Assert(err, jc.ErrorIsNil) 1265 1266 configs := role.ConfigurationSets 1267 winConfig := configs[0] 1268 netConfig := configs[1] 1269 c.Assert(winConfig.ConfigurationSetType, gc.Equals, gwacl.CONFIG_SET_WINDOWS_PROVISIONING) 1270 c.Assert(winConfig.CustomData, gc.Equals, userData) 1271 c.Assert(winConfig.ComputerName, gc.Equals, role.RoleName) 1272 c.Assert(winConfig.AdminUsername, gc.Not(gc.Equals), "") 1273 c.Assert(winConfig.AdminPassword, gc.Not(gc.Equals), "") 1274 c.Assert(winConfig.EnableAutomaticUpdates, gc.Equals, "true") 1275 c.Assert(winConfig.TimeZone, gc.Equals, "") 1276 c.Assert(winConfig.StoredCertificateSettings, gc.IsNil) 1277 c.Assert(winConfig.WinRMListeners, gc.IsNil) 1278 c.Assert(winConfig.AdditionalUnattendContent, gc.Equals, "") 1279 c.Assert(role.RoleSize, gc.Equals, size) 1280 c.Assert(role.OSVirtualHardDisk, gc.DeepEquals, vhd) 1281 1282 endpoints := mapInputEndpointsByPort(c, *netConfig.InputEndpoints) 1283 1284 // TODO(bogdanteleaga): make this support WinRM and not ssh 1285 // The network config contains an endpoint for ssh communication. 1286 sshEndpoint, ok := endpoints[22] 1287 c.Assert(ok, jc.IsTrue) 1288 c.Check(sshEndpoint.LocalPort, gc.Equals, 22) 1289 c.Check(sshEndpoint.Protocol, gc.Equals, "tcp") 1290 1291 c.Assert(role.ProvisionGuestAgent, gc.Equals, "true") 1292 resourceExtensions := *role.ResourceExtensionReferences 1293 resourceExtension := resourceExtensions[0] 1294 c.Assert(resourceExtension.ReferenceName, gc.Equals, "MyCustomScriptExtension") 1295 c.Assert(resourceExtension.Publisher, gc.Equals, "Microsoft.Compute") 1296 c.Assert(resourceExtension.Name, gc.Equals, "CustomScriptExtension") 1297 c.Assert(resourceExtension.Version, gc.Equals, "1.4") 1298 1299 resourceParameters := resourceExtension.ParameterValues 1300 publicParam := resourceParameters[0] 1301 1302 uri, err := snap.storage.URL(bootstrapUserdataScriptFilename) 1303 publicScript, err := makeUserdataResourceScripts(uri, bootstrapUserdataScriptFilename) 1304 1305 c.Assert(publicParam.Key, gc.Equals, "CustomScriptExtensionPublicConfigParameter") 1306 c.Assert(publicParam.Value, gc.Equals, publicScript) 1307 c.Assert(publicParam.Type, gc.Equals, gwacl.ResourceExtensionParameterTypePublic) 1308 } 1309 1310 func (*environSuite) TestProviderReturnsAzureEnvironProvider(c *gc.C) { 1311 prov := makeEnviron(c).Provider() 1312 c.Assert(prov, gc.NotNil) 1313 azprov, ok := prov.(azureEnvironProvider) 1314 c.Assert(ok, jc.IsTrue) 1315 c.Check(azprov, gc.NotNil) 1316 } 1317 1318 func (*environSuite) TestCreateVirtualNetwork(c *gc.C) { 1319 env := makeEnviron(c) 1320 responses := []gwacl.DispatcherResponse{ 1321 // No existing configuration found. 1322 gwacl.NewDispatcherResponse(nil, http.StatusNotFound, nil), 1323 // Accept upload of new configuration. 1324 gwacl.NewDispatcherResponse(nil, http.StatusOK, nil), 1325 } 1326 requests := gwacl.PatchManagementAPIResponses(responses) 1327 1328 env.createVirtualNetwork() 1329 1330 c.Assert(*requests, gc.HasLen, 2) 1331 request := (*requests)[1] 1332 body := gwacl.NetworkConfiguration{} 1333 err := xml.Unmarshal(request.Payload, &body) 1334 c.Assert(err, jc.ErrorIsNil) 1335 networkConf := (*body.VirtualNetworkSites)[0] 1336 c.Check(networkConf.Name, gc.Equals, env.getVirtualNetworkName()) 1337 c.Check(networkConf.AffinityGroup, gc.Equals, "") 1338 c.Check(networkConf.Location, gc.Equals, "location") 1339 } 1340 1341 func (*environSuite) TestDestroyVirtualNetwork(c *gc.C) { 1342 env := makeEnviron(c) 1343 // Prepare a configuration with a single virtual network. 1344 existingConfig := &gwacl.NetworkConfiguration{ 1345 XMLNS: gwacl.XMLNS_NC, 1346 VirtualNetworkSites: &[]gwacl.VirtualNetworkSite{ 1347 {Name: env.getVirtualNetworkName()}, 1348 }, 1349 } 1350 body, err := existingConfig.Serialize() 1351 c.Assert(err, jc.ErrorIsNil) 1352 responses := []gwacl.DispatcherResponse{ 1353 // Return existing configuration. 1354 gwacl.NewDispatcherResponse([]byte(body), http.StatusOK, nil), 1355 // Accept upload of new configuration. 1356 gwacl.NewDispatcherResponse(nil, http.StatusOK, nil), 1357 } 1358 requests := gwacl.PatchManagementAPIResponses(responses) 1359 1360 env.deleteVirtualNetwork() 1361 1362 c.Assert(*requests, gc.HasLen, 2) 1363 // One request to get the existing network configuration. 1364 getRequest := (*requests)[0] 1365 c.Check(getRequest.Method, gc.Equals, "GET") 1366 // One request to update the network configuration. 1367 putRequest := (*requests)[1] 1368 c.Check(putRequest.Method, gc.Equals, "PUT") 1369 newConfig := gwacl.NetworkConfiguration{} 1370 err = xml.Unmarshal(putRequest.Payload, &newConfig) 1371 c.Assert(err, jc.ErrorIsNil) 1372 // The new configuration has no VirtualNetworkSites. 1373 c.Check(newConfig.VirtualNetworkSites, gc.IsNil) 1374 } 1375 1376 func (*environSuite) TestGetVirtualNetworkNameContainsEnvName(c *gc.C) { 1377 env := makeEnviron(c) 1378 c.Check(strings.Contains(env.getVirtualNetworkName(), env.Config().Name()), jc.IsTrue) 1379 } 1380 1381 func (*environSuite) TestGetVirtualNetworkNameIsConstant(c *gc.C) { 1382 env := makeEnviron(c) 1383 c.Check(env.getVirtualNetworkName(), gc.Equals, env.getVirtualNetworkName()) 1384 } 1385 1386 func (*environSuite) TestCreateAffinityGroup(c *gc.C) { 1387 env := makeEnviron(c) 1388 responses := []gwacl.DispatcherResponse{ 1389 gwacl.NewDispatcherResponse(nil, http.StatusCreated, nil), 1390 } 1391 requests := gwacl.PatchManagementAPIResponses(responses) 1392 1393 env.createAffinityGroup() 1394 1395 c.Assert(*requests, gc.HasLen, 1) 1396 request := (*requests)[0] 1397 body := gwacl.CreateAffinityGroup{} 1398 err := xml.Unmarshal(request.Payload, &body) 1399 c.Assert(err, jc.ErrorIsNil) 1400 c.Check(body.Name, gc.Equals, env.getAffinityGroupName()) 1401 // This is a testing antipattern, the expected data comes from 1402 // config defaults. Fix it sometime. 1403 c.Check(body.Location, gc.Equals, "location") 1404 } 1405 1406 func (*environSuite) TestDestroyAffinityGroup(c *gc.C) { 1407 env := makeEnviron(c) 1408 responses := []gwacl.DispatcherResponse{ 1409 gwacl.NewDispatcherResponse(nil, http.StatusOK, nil), 1410 } 1411 requests := gwacl.PatchManagementAPIResponses(responses) 1412 1413 env.deleteAffinityGroup() 1414 1415 c.Assert(*requests, gc.HasLen, 1) 1416 request := (*requests)[0] 1417 c.Check(strings.Contains(request.URL, env.getAffinityGroupName()), jc.IsTrue) 1418 c.Check(request.Method, gc.Equals, "DELETE") 1419 } 1420 1421 func (*environSuite) TestGetAffinityGroupName(c *gc.C) { 1422 env := makeEnviron(c) 1423 c.Check(strings.Contains(env.getAffinityGroupName(), env.Config().Name()), jc.IsTrue) 1424 } 1425 1426 func (*environSuite) TestGetAffinityGroupNameIsConstant(c *gc.C) { 1427 env := makeEnviron(c) 1428 c.Check(env.getAffinityGroupName(), gc.Equals, env.getAffinityGroupName()) 1429 } 1430 1431 func (s *environSuite) TestSelectInstanceTypeAndImageUsesForcedImage(c *gc.C) { 1432 env := s.setupEnvWithDummyMetadata(c) 1433 forcedImage := "my-image" 1434 env.ecfg.attrs["force-image-name"] = forcedImage 1435 1436 aim := roleSizeByName("ExtraLarge") 1437 cons := constraints.Value{ 1438 CpuCores: &aim.CpuCores, 1439 Mem: &aim.Mem, 1440 } 1441 1442 instanceType, image, err := env.selectInstanceTypeAndImage(&instances.InstanceConstraint{ 1443 Region: "West US", 1444 Series: coretesting.FakeDefaultSeries, 1445 Constraints: cons, 1446 }) 1447 c.Assert(err, jc.ErrorIsNil) 1448 1449 c.Check(instanceType.Name, gc.Equals, aim.Name) 1450 c.Check(image, gc.Equals, forcedImage) 1451 } 1452 1453 func (s *baseEnvironSuite) setupEnvWithDummyMetadata(c *gc.C) *azureEnviron { 1454 envAttrs := makeAzureConfigMap(c) 1455 envAttrs["location"] = "North Europe" 1456 env := makeEnvironWithConfig(c, envAttrs) 1457 _, supported := environs.SupportsNetworking(env) 1458 c.Assert(supported, jc.IsFalse) 1459 s.setDummyStorage(c, env) 1460 images := []*imagemetadata.ImageMetadata{ 1461 { 1462 Id: "image-id", 1463 VirtType: "Hyper-V", 1464 Arch: "amd64", 1465 RegionName: "North Europe", 1466 Endpoint: "https://management.core.windows.net/", 1467 }, 1468 } 1469 s.makeTestMetadata(c, coretesting.FakeDefaultSeries, "North Europe", images) 1470 return env 1471 } 1472 1473 func (s *environSuite) TestSelectInstanceTypeAndImageUsesSimplestreamsByDefault(c *gc.C) { 1474 env := s.setupEnvWithDummyMetadata(c) 1475 // We'll tailor our constraints so as to get a specific instance type. 1476 aim := roleSizeByName("ExtraSmall") 1477 cons := constraints.Value{ 1478 CpuCores: &aim.CpuCores, 1479 Mem: &aim.Mem, 1480 } 1481 instanceType, image, err := env.selectInstanceTypeAndImage(&instances.InstanceConstraint{ 1482 Region: "North Europe", 1483 Series: coretesting.FakeDefaultSeries, 1484 Constraints: cons, 1485 }) 1486 c.Assert(err, jc.ErrorIsNil) 1487 c.Assert(instanceType.Name, gc.Equals, aim.Name) 1488 c.Assert(image, gc.Equals, "image-id") 1489 } 1490 1491 func (*environSuite) TestExtractStorageKeyPicksPrimaryKeyIfSet(c *gc.C) { 1492 keys := gwacl.StorageAccountKeys{ 1493 Primary: "mainkey", 1494 Secondary: "otherkey", 1495 } 1496 c.Check(extractStorageKey(&keys), gc.Equals, "mainkey") 1497 } 1498 1499 func (*environSuite) TestExtractStorageKeyFallsBackToSecondaryKey(c *gc.C) { 1500 keys := gwacl.StorageAccountKeys{ 1501 Secondary: "sparekey", 1502 } 1503 c.Check(extractStorageKey(&keys), gc.Equals, "sparekey") 1504 } 1505 1506 func (*environSuite) TestExtractStorageKeyReturnsBlankIfNoneSet(c *gc.C) { 1507 c.Check(extractStorageKey(&gwacl.StorageAccountKeys{}), gc.Equals, "") 1508 } 1509 1510 func assertSourceContents(c *gc.C, source simplestreams.DataSource, filename string, content []byte) { 1511 rc, _, err := source.Fetch(filename) 1512 c.Assert(err, jc.ErrorIsNil) 1513 defer rc.Close() 1514 retrieved, err := ioutil.ReadAll(rc) 1515 c.Assert(err, jc.ErrorIsNil) 1516 c.Assert(retrieved, gc.DeepEquals, content) 1517 } 1518 1519 func (s *environSuite) TestGetToolsMetadataSources(c *gc.C) { 1520 env := makeEnviron(c) 1521 s.setDummyStorage(c, env) 1522 sources, err := tools.GetMetadataSources(env) 1523 c.Assert(err, jc.ErrorIsNil) 1524 c.Assert(sources, gc.HasLen, 0) 1525 } 1526 1527 func (s *environSuite) TestCheckUnitAssignment(c *gc.C) { 1528 // If availability-sets-enabled is true, then placement is disabled. 1529 attrs := makeAzureConfigMap(c) 1530 attrs["availability-sets-enabled"] = true 1531 env := environs.Environ(makeEnvironWithConfig(c, attrs)) 1532 err := env.SupportsUnitPlacement() 1533 c.Assert(err, gc.ErrorMatches, "unit placement is not supported with availability-sets-enabled") 1534 1535 // If the user disables availability sets, they can do what they want. 1536 attrs["availability-sets-enabled"] = false 1537 env = environs.Environ(makeEnvironWithConfig(c, attrs)) 1538 err = env.SupportsUnitPlacement() 1539 c.Assert(err, jc.ErrorIsNil) 1540 } 1541 1542 type startInstanceSuite struct { 1543 baseEnvironSuite 1544 env *azureEnviron 1545 params environs.StartInstanceParams 1546 } 1547 1548 func (s *startInstanceSuite) SetUpTest(c *gc.C) { 1549 s.baseEnvironSuite.SetUpTest(c) 1550 s.env = s.setupEnvWithDummyMetadata(c) 1551 s.env.ecfg.attrs["force-image-name"] = "my-image" 1552 machineTag := names.NewMachineTag("1") 1553 stateInfo := &mongo.MongoInfo{ 1554 Info: mongo.Info{ 1555 CACert: coretesting.CACert, 1556 Addrs: []string{"localhost:123"}, 1557 }, 1558 Password: "password", 1559 Tag: machineTag, 1560 } 1561 apiInfo := &api.Info{ 1562 Addrs: []string{"localhost:124"}, 1563 CACert: coretesting.CACert, 1564 Password: "admin", 1565 Tag: machineTag, 1566 EnvironTag: coretesting.EnvironmentTag, 1567 } 1568 icfg, err := instancecfg.NewInstanceConfig("1", "yanonce", imagemetadata.ReleasedStream, "quantal", true, nil, stateInfo, apiInfo) 1569 c.Assert(err, jc.ErrorIsNil) 1570 s.params = environs.StartInstanceParams{ 1571 Tools: envtesting.AssertUploadFakeToolsVersions( 1572 c, s.env.storage, s.env.Config().AgentStream(), s.env.Config().AgentStream(), envtesting.V120p..., 1573 ), 1574 InstanceConfig: icfg, 1575 } 1576 } 1577 1578 func (s *startInstanceSuite) startInstance(c *gc.C) (serviceName string, stateServer bool) { 1579 var called bool 1580 var roleSize gwacl.RoleSize 1581 restore := testing.PatchValue(&createInstance, func(env *azureEnviron, azure *gwacl.ManagementAPI, role *gwacl.Role, serviceNameArg string, stateServerArg bool) (instance.Instance, error) { 1582 serviceName = serviceNameArg 1583 stateServer = stateServerArg 1584 for _, r := range gwacl.RoleSizes { 1585 if r.Name == role.RoleSize { 1586 roleSize = r 1587 break 1588 } 1589 } 1590 called = true 1591 return nil, nil 1592 }) 1593 defer restore() 1594 result, err := s.env.StartInstance(s.params) 1595 c.Assert(err, jc.ErrorIsNil) 1596 c.Assert(called, jc.IsTrue) 1597 c.Assert(result, gc.NotNil) 1598 c.Assert(result.Hardware, gc.NotNil) 1599 arch := "amd64" 1600 c.Assert(result.Hardware, gc.DeepEquals, &instance.HardwareCharacteristics{ 1601 Arch: &arch, 1602 Mem: &roleSize.Mem, 1603 RootDisk: &roleSize.OSDiskSpace, 1604 CpuCores: &roleSize.CpuCores, 1605 }) 1606 return serviceName, stateServer 1607 } 1608 1609 func (s *startInstanceSuite) TestStartInstanceDistributionGroupError(c *gc.C) { 1610 s.params.DistributionGroup = func() ([]instance.Id, error) { 1611 return nil, fmt.Errorf("DistributionGroupError") 1612 } 1613 s.env.ecfg.attrs["availability-sets-enabled"] = true 1614 _, err := s.env.StartInstance(s.params) 1615 c.Assert(err, gc.ErrorMatches, "DistributionGroupError") 1616 // DistributionGroup should not be called if availability-sets-enabled=false. 1617 s.env.ecfg.attrs["availability-sets-enabled"] = false 1618 s.startInstance(c) 1619 } 1620 1621 func (s *startInstanceSuite) TestStartInstanceDistributionGroupEmpty(c *gc.C) { 1622 // serviceName will be empty if DistributionGroup is nil or returns nothing. 1623 s.env.ecfg.attrs["availability-sets-enabled"] = true 1624 serviceName, _ := s.startInstance(c) 1625 c.Assert(serviceName, gc.Equals, "") 1626 s.params.DistributionGroup = func() ([]instance.Id, error) { return nil, nil } 1627 serviceName, _ = s.startInstance(c) 1628 c.Assert(serviceName, gc.Equals, "") 1629 } 1630 1631 func (s *startInstanceSuite) TestStartInstanceDistributionGroup(c *gc.C) { 1632 s.params.DistributionGroup = func() ([]instance.Id, error) { 1633 return []instance.Id{ 1634 instance.Id(s.env.getEnvPrefix() + "whatever-role0"), 1635 }, nil 1636 } 1637 // DistributionGroup will only have an effect if 1638 // availability-sets-enabled=true. 1639 s.env.ecfg.attrs["availability-sets-enabled"] = false 1640 serviceName, _ := s.startInstance(c) 1641 c.Assert(serviceName, gc.Equals, "") 1642 s.env.ecfg.attrs["availability-sets-enabled"] = true 1643 serviceName, _ = s.startInstance(c) 1644 c.Assert(serviceName, gc.Equals, "juju-testenv-whatever") 1645 } 1646 1647 func (s *startInstanceSuite) TestStartInstanceStateServerJobs(c *gc.C) { 1648 // If the machine has the JobManagesEnviron job, 1649 // we should see stateServer==true. 1650 s.params.InstanceConfig.Jobs = []multiwatcher.MachineJob{ 1651 multiwatcher.JobHostUnits, 1652 multiwatcher.JobManageNetworking, 1653 } 1654 _, stateServer := s.startInstance(c) 1655 c.Assert(stateServer, jc.IsFalse) 1656 s.params.InstanceConfig.Jobs = []multiwatcher.MachineJob{ 1657 multiwatcher.JobHostUnits, 1658 multiwatcher.JobManageEnviron, 1659 multiwatcher.JobManageNetworking, 1660 } 1661 _, stateServer = s.startInstance(c) 1662 c.Assert(stateServer, jc.IsTrue) 1663 } 1664 1665 func (s *environSuite) TestConstraintsValidator(c *gc.C) { 1666 env := s.setupEnvWithDummyMetadata(c) 1667 validator, err := env.ConstraintsValidator() 1668 c.Assert(err, jc.ErrorIsNil) 1669 cons := constraints.MustParse("arch=amd64 tags=bar cpu-power=10") 1670 unsupported, err := validator.Validate(cons) 1671 c.Assert(err, jc.ErrorIsNil) 1672 c.Assert(unsupported, jc.SameContents, []string{"cpu-power", "tags"}) 1673 } 1674 1675 func (s *environSuite) TestConstraintsValidatorVocab(c *gc.C) { 1676 env := s.setupEnvWithDummyMetadata(c) 1677 validator, err := env.ConstraintsValidator() 1678 c.Assert(err, jc.ErrorIsNil) 1679 cons := constraints.MustParse("arch=ppc64el") 1680 _, err = validator.Validate(cons) 1681 c.Assert(err, gc.ErrorMatches, "invalid constraint value: arch=ppc64el\nvalid values are:.*") 1682 cons = constraints.MustParse("instance-type=foo") 1683 _, err = validator.Validate(cons) 1684 c.Assert(err, gc.ErrorMatches, "invalid constraint value: instance-type=foo\nvalid values are:.*") 1685 } 1686 1687 func (s *environSuite) TestConstraintsMerge(c *gc.C) { 1688 env := s.setupEnvWithDummyMetadata(c) 1689 validator, err := env.ConstraintsValidator() 1690 c.Assert(err, jc.ErrorIsNil) 1691 consA := constraints.MustParse("arch=amd64 mem=1G root-disk=10G") 1692 consB := constraints.MustParse("instance-type=ExtraSmall") 1693 cons, err := validator.Merge(consA, consB) 1694 c.Assert(err, jc.ErrorIsNil) 1695 c.Assert(cons, gc.DeepEquals, constraints.MustParse("instance-type=ExtraSmall")) 1696 } 1697 1698 func (s *environSuite) TestBootstrapReusesAffinityGroupAndVNet(c *gc.C) { 1699 s.PatchValue(&version.Current.Number, coretesting.FakeVersionNumber) 1700 storageDir := c.MkDir() 1701 stor, err := filestorage.NewFileStorageWriter(storageDir) 1702 c.Assert(err, jc.ErrorIsNil) 1703 s.UploadFakeTools(c, stor, "released", "released") 1704 s.PatchValue(&tools.DefaultBaseURL, storageDir) 1705 1706 env := s.setupEnvWithDummyMetadata(c) 1707 var responses []gwacl.DispatcherResponse 1708 1709 // Fail to create affinity group because it already exists. 1710 responses = append(responses, gwacl.NewDispatcherResponse(nil, http.StatusConflict, nil)) 1711 1712 // Fail to create vnet because it already exists. 1713 sites := []gwacl.VirtualNetworkSite{{Name: env.getVirtualNetworkName()}} 1714 existingConfig := &gwacl.NetworkConfiguration{ 1715 XMLNS: gwacl.XMLNS_NC, 1716 VirtualNetworkSites: &sites, 1717 } 1718 body, err := existingConfig.Serialize() 1719 c.Assert(err, jc.ErrorIsNil) 1720 responses = append(responses, gwacl.NewDispatcherResponse([]byte(body), http.StatusOK, nil)) // GET network 1721 responses = append(responses, gwacl.NewDispatcherResponse(nil, http.StatusConflict, nil)) // conflict creating AG 1722 responses = append(responses, gwacl.NewDispatcherResponse(nil, http.StatusOK, nil)) // DELETE AG 1723 responses = append(responses, gwacl.NewDispatcherResponse(nil, http.StatusOK, nil)) // GET network (delete) 1724 responses = append(responses, gwacl.NewDispatcherResponse(nil, http.StatusOK, nil)) // PUT network (delete) 1725 gwacl.PatchManagementAPIResponses(responses) 1726 1727 s.PatchValue(&createInstance, func(*azureEnviron, *gwacl.ManagementAPI, *gwacl.Role, string, bool) (instance.Instance, error) { 1728 return nil, fmt.Errorf("no instance for you") 1729 }) 1730 err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 1731 c.Assert(err, gc.ErrorMatches, "cannot start bootstrap instance: no instance for you") 1732 } 1733 1734 func (s *environSuite) TestGetVirtualNetwork(c *gc.C) { 1735 env := makeEnviron(c) 1736 s.setDummyStorage(c, env) 1737 1738 networkConfig := &gwacl.NetworkConfiguration{ 1739 XMLNS: gwacl.XMLNS_NC, 1740 VirtualNetworkSites: &[]gwacl.VirtualNetworkSite{ 1741 {Name: env.getVirtualNetworkName()}, 1742 }, 1743 } 1744 body, err := networkConfig.Serialize() 1745 c.Assert(err, jc.ErrorIsNil) 1746 responses := []gwacl.DispatcherResponse{ 1747 // Return existing configuration. 1748 gwacl.NewDispatcherResponse([]byte(body), http.StatusOK, nil), 1749 } 1750 requests := gwacl.PatchManagementAPIResponses(responses) 1751 1752 for i := 0; i < 2; i++ { 1753 vnet, err := env.getVirtualNetwork() 1754 c.Assert(err, jc.ErrorIsNil) 1755 c.Assert(vnet, gc.NotNil) 1756 c.Assert(vnet.Name, gc.Equals, env.getVirtualNetworkName()) 1757 } 1758 1759 // getVirtualNetwork should cache: there should be only one request to get 1760 // the network configuration. 1761 c.Assert(*requests, gc.HasLen, 1) 1762 getRequest := (*requests)[0] 1763 c.Check(getRequest.Method, gc.Equals, "GET") 1764 c.Check(strings.HasSuffix(getRequest.URL, "services/networking/media"), jc.IsTrue) 1765 }