github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/provider/openstack/local_test.go (about) 1 // Copyright 2012, 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package openstack_test 5 6 import ( 7 "bytes" 8 "errors" 9 "fmt" 10 "io/ioutil" 11 "net/http" 12 "net/http/httptest" 13 "net/url" 14 "os" 15 "path/filepath" 16 "regexp" 17 "strings" 18 19 jujuerrors "github.com/juju/errors" 20 gitjujutesting "github.com/juju/testing" 21 jc "github.com/juju/testing/checkers" 22 "github.com/juju/utils/arch" 23 "github.com/juju/utils/series" 24 "github.com/juju/utils/ssh" 25 "github.com/juju/version" 26 gc "gopkg.in/check.v1" 27 "gopkg.in/goose.v1/client" 28 "gopkg.in/goose.v1/identity" 29 "gopkg.in/goose.v1/nova" 30 "gopkg.in/goose.v1/testservices/hook" 31 "gopkg.in/goose.v1/testservices/identityservice" 32 "gopkg.in/goose.v1/testservices/novaservice" 33 "gopkg.in/goose.v1/testservices/openstackservice" 34 35 "github.com/juju/juju/cloud" 36 "github.com/juju/juju/cloudconfig/instancecfg" 37 "github.com/juju/juju/constraints" 38 "github.com/juju/juju/environs" 39 "github.com/juju/juju/environs/bootstrap" 40 "github.com/juju/juju/environs/config" 41 "github.com/juju/juju/environs/filestorage" 42 "github.com/juju/juju/environs/imagemetadata" 43 imagetesting "github.com/juju/juju/environs/imagemetadata/testing" 44 "github.com/juju/juju/environs/jujutest" 45 "github.com/juju/juju/environs/simplestreams" 46 sstesting "github.com/juju/juju/environs/simplestreams/testing" 47 "github.com/juju/juju/environs/storage" 48 envtesting "github.com/juju/juju/environs/testing" 49 "github.com/juju/juju/environs/tools" 50 "github.com/juju/juju/instance" 51 "github.com/juju/juju/juju" 52 "github.com/juju/juju/juju/testing" 53 "github.com/juju/juju/jujuclient/jujuclienttesting" 54 "github.com/juju/juju/network" 55 "github.com/juju/juju/provider/common" 56 "github.com/juju/juju/provider/openstack" 57 "github.com/juju/juju/status" 58 "github.com/juju/juju/storage/provider/registry" 59 coretesting "github.com/juju/juju/testing" 60 jujuversion "github.com/juju/juju/version" 61 ) 62 63 type ProviderSuite struct { 64 restoreTimeouts func() 65 } 66 67 var _ = gc.Suite(&ProviderSuite{}) 68 var _ = gc.Suite(&localHTTPSServerSuite{}) 69 var _ = gc.Suite(&noSwiftSuite{}) 70 71 func (s *ProviderSuite) SetUpTest(c *gc.C) { 72 s.restoreTimeouts = envtesting.PatchAttemptStrategies(openstack.ShortAttempt, openstack.StorageAttempt) 73 } 74 75 func (s *ProviderSuite) TearDownTest(c *gc.C) { 76 s.restoreTimeouts() 77 } 78 79 // Register tests to run against a test Openstack instance (service doubles). 80 func registerLocalTests() { 81 cred := &identity.Credentials{ 82 User: "fred", 83 Secrets: "secret", 84 Region: "some-region", 85 TenantName: "some tenant", 86 } 87 config := makeTestConfig(cred) 88 config["agent-version"] = coretesting.FakeVersionNumber.String() 89 config["authorized-keys"] = "fakekey" 90 gc.Suite(&localLiveSuite{ 91 LiveTests: LiveTests{ 92 cred: cred, 93 LiveTests: jujutest.LiveTests{ 94 TestConfig: config, 95 }, 96 }, 97 }) 98 gc.Suite(&localServerSuite{ 99 cred: cred, 100 Tests: jujutest.Tests{ 101 TestConfig: config, 102 }, 103 }) 104 } 105 106 // localServer is used to spin up a local Openstack service double. 107 type localServer struct { 108 Server *httptest.Server 109 Mux *http.ServeMux 110 oldHandler http.Handler 111 Nova *novaservice.Nova 112 restoreTimeouts func() 113 UseTLS bool 114 } 115 116 type newOpenstackFunc func(*http.ServeMux, *identity.Credentials, identity.AuthMode) *novaservice.Nova 117 118 func (s *localServer) start( 119 c *gc.C, cred *identity.Credentials, newOpenstackFunc newOpenstackFunc, 120 ) { 121 // Set up the HTTP server. 122 if s.UseTLS { 123 s.Server = httptest.NewTLSServer(nil) 124 } else { 125 s.Server = httptest.NewServer(nil) 126 } 127 c.Assert(s.Server, gc.NotNil) 128 s.oldHandler = s.Server.Config.Handler 129 s.Mux = http.NewServeMux() 130 s.Server.Config.Handler = s.Mux 131 cred.URL = s.Server.URL 132 c.Logf("Started service at: %v", s.Server.URL) 133 s.Nova = newOpenstackFunc(s.Mux, cred, identity.AuthUserPass) 134 s.restoreTimeouts = envtesting.PatchAttemptStrategies(openstack.ShortAttempt, openstack.StorageAttempt) 135 s.Nova.SetAvailabilityZones( 136 nova.AvailabilityZone{Name: "test-unavailable"}, 137 nova.AvailabilityZone{ 138 Name: "test-available", 139 State: nova.AvailabilityZoneState{ 140 Available: true, 141 }, 142 }, 143 ) 144 } 145 146 func (s *localServer) stop() { 147 s.Mux = nil 148 s.Server.Config.Handler = s.oldHandler 149 s.Server.Close() 150 s.restoreTimeouts() 151 } 152 153 // localLiveSuite runs tests from LiveTests using an Openstack service double. 154 type localLiveSuite struct { 155 coretesting.BaseSuite 156 LiveTests 157 srv localServer 158 } 159 160 func overrideCinderProvider(c *gc.C, s *gitjujutesting.CleanupSuite) { 161 // Override the cinder storage provider, since there is no test 162 // double for cinder. 163 old, err := registry.StorageProvider(openstack.CinderProviderType) 164 c.Assert(err, jc.ErrorIsNil) 165 registry.RegisterProvider(openstack.CinderProviderType, nil) 166 registry.RegisterProvider( 167 openstack.CinderProviderType, 168 openstack.NewCinderProvider(&mockAdapter{}), 169 ) 170 s.AddCleanup(func(*gc.C) { 171 registry.RegisterProvider(openstack.CinderProviderType, nil) 172 registry.RegisterProvider(openstack.CinderProviderType, old) 173 }) 174 } 175 176 func (s *localLiveSuite) SetUpSuite(c *gc.C) { 177 s.BaseSuite.SetUpSuite(c) 178 179 c.Logf("Running live tests using openstack service test double") 180 s.srv.start(c, s.cred, newFullOpenstackService) 181 182 // Set credentials to use when bootstrapping. Must be done after 183 // starting server to get the auth URL. 184 s.Credential = makeCredential(s.cred) 185 s.CloudEndpoint = s.cred.URL 186 s.CloudRegion = s.cred.Region 187 188 s.LiveTests.SetUpSuite(c) 189 openstack.UseTestImageData(openstack.ImageMetadataStorage(s.Env), s.cred) 190 restoreFinishBootstrap := envtesting.DisableFinishBootstrap() 191 s.AddCleanup(func(*gc.C) { restoreFinishBootstrap() }) 192 overrideCinderProvider(c, &s.CleanupSuite) 193 } 194 195 func (s *localLiveSuite) TearDownSuite(c *gc.C) { 196 openstack.RemoveTestImageData(openstack.ImageMetadataStorage(s.Env)) 197 s.LiveTests.TearDownSuite(c) 198 s.srv.stop() 199 s.BaseSuite.TearDownSuite(c) 200 } 201 202 func (s *localLiveSuite) SetUpTest(c *gc.C) { 203 s.BaseSuite.SetUpTest(c) 204 s.LiveTests.SetUpTest(c) 205 imagetesting.PatchOfficialDataSources(&s.CleanupSuite, "") 206 } 207 208 func (s *localLiveSuite) TearDownTest(c *gc.C) { 209 s.LiveTests.TearDownTest(c) 210 s.BaseSuite.TearDownTest(c) 211 } 212 213 // localServerSuite contains tests that run against an Openstack service double. 214 // These tests can test things that would be unreasonably slow or expensive 215 // to test on a live Openstack server. The service double is started and stopped for 216 // each test. 217 type localServerSuite struct { 218 coretesting.BaseSuite 219 jujutest.Tests 220 cred *identity.Credentials 221 srv localServer 222 env environs.Environ 223 toolsMetadataStorage storage.Storage 224 imageMetadataStorage storage.Storage 225 } 226 227 func (s *localServerSuite) SetUpSuite(c *gc.C) { 228 s.BaseSuite.SetUpSuite(c) 229 restoreFinishBootstrap := envtesting.DisableFinishBootstrap() 230 s.AddCleanup(func(*gc.C) { restoreFinishBootstrap() }) 231 overrideCinderProvider(c, &s.CleanupSuite) 232 c.Logf("Running local tests") 233 } 234 235 func (s *localServerSuite) SetUpTest(c *gc.C) { 236 s.BaseSuite.SetUpTest(c) 237 s.srv.start(c, s.cred, newFullOpenstackService) 238 239 // Set credentials to use when bootstrapping. Must be done after 240 // starting server to get the auth URL. 241 s.Credential = makeCredential(s.cred) 242 s.CloudEndpoint = s.cred.URL 243 s.CloudRegion = s.cred.Region 244 245 cl := client.NewClient(s.cred, identity.AuthUserPass, nil) 246 err := cl.Authenticate() 247 c.Assert(err, jc.ErrorIsNil) 248 containerURL, err := cl.MakeServiceURL("object-store", nil) 249 c.Assert(err, jc.ErrorIsNil) 250 s.TestConfig = s.TestConfig.Merge(coretesting.Attrs{ 251 "agent-metadata-url": containerURL + "/juju-dist-test/tools", 252 "image-metadata-url": containerURL + "/juju-dist-test", 253 "auth-url": s.cred.URL, 254 }) 255 s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber) 256 s.Tests.SetUpTest(c) 257 // For testing, we create a storage instance to which is uploaded tools and image metadata. 258 s.env = s.Prepare(c) 259 s.toolsMetadataStorage = openstack.MetadataStorage(s.env) 260 // Put some fake metadata in place so that tests that are simply 261 // starting instances without any need to check if those instances 262 // are running can find the metadata. 263 envtesting.UploadFakeTools(c, s.toolsMetadataStorage, s.env.Config().AgentStream(), s.env.Config().AgentStream()) 264 s.imageMetadataStorage = openstack.ImageMetadataStorage(s.env) 265 openstack.UseTestImageData(s.imageMetadataStorage, s.cred) 266 } 267 268 func (s *localServerSuite) TearDownTest(c *gc.C) { 269 if s.imageMetadataStorage != nil { 270 openstack.RemoveTestImageData(s.imageMetadataStorage) 271 } 272 if s.toolsMetadataStorage != nil { 273 envtesting.RemoveFakeToolsMetadata(c, s.toolsMetadataStorage) 274 } 275 s.Tests.TearDownTest(c) 276 s.srv.stop() 277 s.BaseSuite.TearDownTest(c) 278 } 279 280 func (s *localServerSuite) TestBootstrap(c *gc.C) { 281 // Tests uses Prepare, so destroy first. 282 err := environs.Destroy(s.env.Config().Name(), s.env, s.ControllerStore) 283 c.Assert(err, jc.ErrorIsNil) 284 s.Tests.TestBootstrap(c) 285 } 286 287 func (s *localServerSuite) TestStartStop(c *gc.C) { 288 // Tests uses Prepare, so destroy first. 289 err := environs.Destroy(s.env.Config().Name(), s.env, s.ControllerStore) 290 c.Assert(err, jc.ErrorIsNil) 291 s.Tests.TestStartStop(c) 292 } 293 294 // If the bootstrap node is configured to require a public IP address, 295 // bootstrapping fails if an address cannot be allocated. 296 func (s *localServerSuite) TestBootstrapFailsWhenPublicIPError(c *gc.C) { 297 coretesting.SkipIfPPC64EL(c, "lp:1425242") 298 299 cleanup := s.srv.Nova.RegisterControlPoint( 300 "addFloatingIP", 301 func(sc hook.ServiceControl, args ...interface{}) error { 302 return fmt.Errorf("failed on purpose") 303 }, 304 ) 305 defer cleanup() 306 307 err := environs.Destroy(s.env.Config().Name(), s.env, s.ControllerStore) 308 c.Assert(err, jc.ErrorIsNil) 309 310 // Create a config that matches s.TestConfig but with use-floating-ip set to true 311 cfg, err := config.New(config.NoDefaults, s.TestConfig.Merge(coretesting.Attrs{ 312 "use-floating-ip": true, 313 })) 314 c.Assert(err, jc.ErrorIsNil) 315 env, err := environs.New(cfg) 316 c.Assert(err, jc.ErrorIsNil) 317 err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 318 c.Assert(err, gc.ErrorMatches, "(.|\n)*cannot allocate a public IP as needed(.|\n)*") 319 } 320 321 func (s *localServerSuite) TestAddressesWithPublicIP(c *gc.C) { 322 // Floating IP address is 10.0.0.1 323 bootstrapFinished := false 324 s.PatchValue(&common.FinishBootstrap, func( 325 ctx environs.BootstrapContext, 326 client ssh.Client, 327 env environs.Environ, 328 inst instance.Instance, 329 instanceConfig *instancecfg.InstanceConfig, 330 ) error { 331 addr, err := inst.Addresses() 332 c.Assert(err, jc.ErrorIsNil) 333 c.Assert(addr, jc.SameContents, []network.Address{ 334 {Value: "10.0.0.1", Type: "ipv4", Scope: "public"}, 335 {Value: "127.0.0.1", Type: "ipv4", Scope: "local-machine"}, 336 {Value: "::face::000f", Type: "hostname", Scope: ""}, 337 {Value: "127.10.0.1", Type: "ipv4", Scope: "public"}, 338 {Value: "::dead:beef:f00d", Type: "ipv6", Scope: "public"}, 339 }) 340 bootstrapFinished = true 341 return nil 342 }) 343 344 // Create a config that matches s.TestConfig but with use-floating-ip set to true 345 cfg, err := config.New(config.NoDefaults, s.TestConfig.Merge(coretesting.Attrs{ 346 "use-floating-ip": true, 347 })) 348 c.Assert(err, jc.ErrorIsNil) 349 env, err := environs.New(cfg) 350 c.Assert(err, jc.ErrorIsNil) 351 err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 352 c.Assert(err, jc.ErrorIsNil) 353 c.Assert(bootstrapFinished, jc.IsTrue) 354 } 355 356 func (s *localServerSuite) TestAddressesWithoutPublicIP(c *gc.C) { 357 bootstrapFinished := false 358 s.PatchValue(&common.FinishBootstrap, func( 359 ctx environs.BootstrapContext, 360 client ssh.Client, 361 env environs.Environ, 362 inst instance.Instance, 363 instanceConfig *instancecfg.InstanceConfig, 364 ) error { 365 addr, err := inst.Addresses() 366 c.Assert(err, jc.ErrorIsNil) 367 c.Assert(addr, jc.SameContents, []network.Address{ 368 {Value: "127.0.0.1", Type: "ipv4", Scope: "local-machine"}, 369 {Value: "::face::000f", Type: "hostname", Scope: ""}, 370 {Value: "127.10.0.1", Type: "ipv4", Scope: "public"}, 371 {Value: "::dead:beef:f00d", Type: "ipv6", Scope: "public"}, 372 }) 373 bootstrapFinished = true 374 return nil 375 }) 376 377 cfg, err := config.New(config.NoDefaults, s.TestConfig.Merge(coretesting.Attrs{ 378 "use-floating-ip": false, 379 })) 380 c.Assert(err, jc.ErrorIsNil) 381 env, err := environs.New(cfg) 382 c.Assert(err, jc.ErrorIsNil) 383 err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 384 c.Assert(err, jc.ErrorIsNil) 385 c.Assert(bootstrapFinished, jc.IsTrue) 386 } 387 388 // If the environment is configured not to require a public IP address for nodes, 389 // bootstrapping and starting an instance should occur without any attempt to 390 // allocate a public address. 391 func (s *localServerSuite) TestStartInstanceWithoutPublicIP(c *gc.C) { 392 cleanup := s.srv.Nova.RegisterControlPoint( 393 "addFloatingIP", 394 func(sc hook.ServiceControl, args ...interface{}) error { 395 return fmt.Errorf("add floating IP should not have been called") 396 }, 397 ) 398 defer cleanup() 399 cleanup = s.srv.Nova.RegisterControlPoint( 400 "addServerFloatingIP", 401 func(sc hook.ServiceControl, args ...interface{}) error { 402 return fmt.Errorf("add server floating IP should not have been called") 403 }, 404 ) 405 defer cleanup() 406 407 err := environs.Destroy(s.env.Config().Name(), s.env, s.ControllerStore) 408 c.Assert(err, jc.ErrorIsNil) 409 410 attrs := s.TestConfig.Merge(coretesting.Attrs{"use-floating-ip": false}) 411 env, err := environs.Prepare( 412 envtesting.BootstrapContext(c), 413 s.ControllerStore, 414 prepareParams(attrs, s.cred), 415 ) 416 c.Assert(err, jc.ErrorIsNil) 417 err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 418 c.Assert(err, jc.ErrorIsNil) 419 inst, _ := testing.AssertStartInstance(c, env, "100") 420 err = env.StopInstances(inst.Id()) 421 c.Assert(err, jc.ErrorIsNil) 422 } 423 424 func (s *localServerSuite) TestStartInstanceHardwareCharacteristics(c *gc.C) { 425 // Ensure amd64 tools are available, to ensure an amd64 image. 426 amd64Version := version.Binary{ 427 Number: jujuversion.Current, 428 Arch: arch.AMD64, 429 } 430 for _, series := range series.SupportedSeries() { 431 amd64Version.Series = series 432 envtesting.AssertUploadFakeToolsVersions( 433 c, s.toolsMetadataStorage, s.env.Config().AgentStream(), s.env.Config().AgentStream(), amd64Version) 434 } 435 436 err := environs.Destroy(s.env.Config().Name(), s.env, s.ControllerStore) 437 c.Assert(err, jc.ErrorIsNil) 438 439 env := s.Prepare(c) 440 err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{}) 441 c.Assert(err, jc.ErrorIsNil) 442 _, hc := testing.AssertStartInstanceWithConstraints(c, env, "100", constraints.MustParse("mem=1024")) 443 c.Check(*hc.Arch, gc.Equals, "amd64") 444 c.Check(*hc.Mem, gc.Equals, uint64(2048)) 445 c.Check(*hc.CpuCores, gc.Equals, uint64(1)) 446 c.Assert(hc.CpuPower, gc.IsNil) 447 } 448 449 func (s *localServerSuite) TestStartInstanceNetwork(c *gc.C) { 450 cfg, err := config.New(config.NoDefaults, s.TestConfig.Merge(coretesting.Attrs{ 451 // A label that corresponds to a nova test service network 452 "network": "net", 453 })) 454 c.Assert(err, jc.ErrorIsNil) 455 env, err := environs.New(cfg) 456 c.Assert(err, jc.ErrorIsNil) 457 inst, _ := testing.AssertStartInstance(c, env, "100") 458 err = env.StopInstances(inst.Id()) 459 c.Assert(err, jc.ErrorIsNil) 460 } 461 462 func (s *localServerSuite) TestStartInstanceNetworkUnknownLabel(c *gc.C) { 463 cfg, err := config.New(config.NoDefaults, s.TestConfig.Merge(coretesting.Attrs{ 464 // A label that has no related network in the nova test service 465 "network": "no-network-with-this-label", 466 })) 467 c.Assert(err, jc.ErrorIsNil) 468 env, err := environs.New(cfg) 469 c.Assert(err, jc.ErrorIsNil) 470 inst, _, _, err := testing.StartInstance(env, "100") 471 c.Check(inst, gc.IsNil) 472 c.Assert(err, gc.ErrorMatches, "No networks exist with label .*") 473 } 474 475 func (s *localServerSuite) TestStartInstanceNetworkUnknownId(c *gc.C) { 476 cfg, err := config.New(config.NoDefaults, s.TestConfig.Merge(coretesting.Attrs{ 477 // A valid UUID but no related network in the nova test service 478 "network": "f81d4fae-7dec-11d0-a765-00a0c91e6bf6", 479 })) 480 c.Assert(err, jc.ErrorIsNil) 481 env, err := environs.New(cfg) 482 c.Assert(err, jc.ErrorIsNil) 483 inst, _, _, err := testing.StartInstance(env, "100") 484 c.Check(inst, gc.IsNil) 485 c.Assert(err, gc.ErrorMatches, "cannot run instance: (\\n|.)*"+ 486 "caused by: "+ 487 "request \\(.*/servers\\) returned unexpected status: "+ 488 "404; error info: .*itemNotFound.*") 489 } 490 491 func assertSecurityGroups(c *gc.C, env environs.Environ, expected []string) { 492 novaClient := openstack.GetNovaClient(env) 493 groups, err := novaClient.ListSecurityGroups() 494 c.Assert(err, jc.ErrorIsNil) 495 for _, name := range expected { 496 found := false 497 for _, group := range groups { 498 if group.Name == name { 499 found = true 500 break 501 } 502 } 503 if !found { 504 c.Errorf("expected security group %q not found", name) 505 } 506 } 507 for _, group := range groups { 508 found := false 509 for _, name := range expected { 510 if group.Name == name { 511 found = true 512 break 513 } 514 } 515 if !found { 516 c.Errorf("existing security group %q is not expected", group.Name) 517 } 518 } 519 } 520 521 func (s *localServerSuite) TestStopInstance(c *gc.C) { 522 cfg, err := config.New(config.NoDefaults, s.TestConfig.Merge(coretesting.Attrs{ 523 "firewall-mode": config.FwInstance})) 524 c.Assert(err, jc.ErrorIsNil) 525 env, err := environs.New(cfg) 526 c.Assert(err, jc.ErrorIsNil) 527 instanceName := "100" 528 inst, _ := testing.AssertStartInstance(c, env, instanceName) 529 // Openstack now has three security groups for the server, the default 530 // group, one group for the entire environment, and another for the 531 // new instance. 532 eUUID := env.Config().UUID() 533 assertSecurityGroups(c, env, []string{"default", fmt.Sprintf("juju-%v", eUUID), fmt.Sprintf("juju-%v-%v", eUUID, instanceName)}) 534 err = env.StopInstances(inst.Id()) 535 c.Assert(err, jc.ErrorIsNil) 536 // The security group for this instance is now removed. 537 assertSecurityGroups(c, env, []string{"default", fmt.Sprintf("juju-%v", eUUID)}) 538 } 539 540 // Due to bug #1300755 it can happen that the security group intended for 541 // an instance is also used as the common security group of another 542 // environment. If this is the case, the attempt to delete the instance's 543 // security group fails but StopInstance succeeds. 544 func (s *localServerSuite) TestStopInstanceSecurityGroupNotDeleted(c *gc.C) { 545 coretesting.SkipIfPPC64EL(c, "lp:1425242") 546 547 // Force an error when a security group is deleted. 548 cleanup := s.srv.Nova.RegisterControlPoint( 549 "removeSecurityGroup", 550 func(sc hook.ServiceControl, args ...interface{}) error { 551 return fmt.Errorf("failed on purpose") 552 }, 553 ) 554 defer cleanup() 555 cfg, err := config.New(config.NoDefaults, s.TestConfig.Merge(coretesting.Attrs{ 556 "firewall-mode": config.FwInstance})) 557 c.Assert(err, jc.ErrorIsNil) 558 env, err := environs.New(cfg) 559 c.Assert(err, jc.ErrorIsNil) 560 instanceName := "100" 561 inst, _ := testing.AssertStartInstance(c, env, instanceName) 562 eUUID := env.Config().UUID() 563 allSecurityGroups := []string{"default", fmt.Sprintf("juju-%v", eUUID), fmt.Sprintf("juju-%v-%v", eUUID, instanceName)} 564 assertSecurityGroups(c, env, allSecurityGroups) 565 err = env.StopInstances(inst.Id()) 566 c.Assert(err, jc.ErrorIsNil) 567 assertSecurityGroups(c, env, allSecurityGroups) 568 } 569 570 func (s *localServerSuite) TestDestroyEnvironmentDeletesSecurityGroupsFWModeInstance(c *gc.C) { 571 cfg, err := config.New(config.NoDefaults, s.TestConfig.Merge(coretesting.Attrs{ 572 "firewall-mode": config.FwInstance})) 573 c.Assert(err, jc.ErrorIsNil) 574 env, err := environs.New(cfg) 575 c.Assert(err, jc.ErrorIsNil) 576 instanceName := "100" 577 testing.AssertStartInstance(c, env, instanceName) 578 eUUID := env.Config().UUID() 579 allSecurityGroups := []string{"default", fmt.Sprintf("juju-%v", eUUID), fmt.Sprintf("juju-%v-%v", eUUID, instanceName)} 580 assertSecurityGroups(c, env, allSecurityGroups) 581 err = env.Destroy() 582 c.Check(err, jc.ErrorIsNil) 583 assertSecurityGroups(c, env, []string{"default"}) 584 } 585 586 func (s *localServerSuite) TestDestroyEnvironmentDeletesSecurityGroupsFWModeGlobal(c *gc.C) { 587 cfg, err := config.New(config.NoDefaults, s.TestConfig.Merge(coretesting.Attrs{ 588 "firewall-mode": config.FwGlobal})) 589 c.Assert(err, jc.ErrorIsNil) 590 env, err := environs.New(cfg) 591 c.Assert(err, jc.ErrorIsNil) 592 instanceName := "100" 593 testing.AssertStartInstance(c, env, instanceName) 594 eUUID := env.Config().UUID() 595 allSecurityGroups := []string{"default", fmt.Sprintf("juju-%v", eUUID), fmt.Sprintf("juju-%v-global", eUUID)} 596 assertSecurityGroups(c, env, allSecurityGroups) 597 err = env.Destroy() 598 c.Check(err, jc.ErrorIsNil) 599 assertSecurityGroups(c, env, []string{"default"}) 600 } 601 602 var instanceGathering = []struct { 603 ids []instance.Id 604 err error 605 }{ 606 {ids: []instance.Id{"id0"}}, 607 {ids: []instance.Id{"id0", "id0"}}, 608 {ids: []instance.Id{"id0", "id1"}}, 609 {ids: []instance.Id{"id1", "id0"}}, 610 {ids: []instance.Id{"id1", "id0", "id1"}}, 611 { 612 ids: []instance.Id{""}, 613 err: environs.ErrNoInstances, 614 }, 615 { 616 ids: []instance.Id{"", ""}, 617 err: environs.ErrNoInstances, 618 }, 619 { 620 ids: []instance.Id{"", "", ""}, 621 err: environs.ErrNoInstances, 622 }, 623 { 624 ids: []instance.Id{"id0", ""}, 625 err: environs.ErrPartialInstances, 626 }, 627 { 628 ids: []instance.Id{"", "id1"}, 629 err: environs.ErrPartialInstances, 630 }, 631 { 632 ids: []instance.Id{"id0", "id1", ""}, 633 err: environs.ErrPartialInstances, 634 }, 635 { 636 ids: []instance.Id{"id0", "", "id0"}, 637 err: environs.ErrPartialInstances, 638 }, 639 { 640 ids: []instance.Id{"id0", "id0", ""}, 641 err: environs.ErrPartialInstances, 642 }, 643 { 644 ids: []instance.Id{"", "id0", "id1"}, 645 err: environs.ErrPartialInstances, 646 }, 647 } 648 649 func (s *localServerSuite) TestInstanceStatus(c *gc.C) { 650 // goose's test service always returns ACTIVE state. 651 inst, _ := testing.AssertStartInstance(c, s.env, "100") 652 c.Assert(inst.Status().Status, gc.Equals, status.StatusRunning) 653 err := s.env.StopInstances(inst.Id()) 654 c.Assert(err, jc.ErrorIsNil) 655 } 656 657 func (s *localServerSuite) TestAllInstancesFloatingIP(c *gc.C) { 658 // Create a config that matches s.TestConfig but with use-floating-ip 659 cfg, err := config.New(config.NoDefaults, s.TestConfig.Merge(coretesting.Attrs{ 660 "use-floating-ip": true, 661 })) 662 c.Assert(err, jc.ErrorIsNil) 663 env, err := environs.New(cfg) 664 c.Assert(err, jc.ErrorIsNil) 665 666 inst0, _ := testing.AssertStartInstance(c, env, "100") 667 inst1, _ := testing.AssertStartInstance(c, env, "101") 668 defer func() { 669 err := env.StopInstances(inst0.Id(), inst1.Id()) 670 c.Assert(err, jc.ErrorIsNil) 671 }() 672 673 insts, err := env.AllInstances() 674 c.Assert(err, jc.ErrorIsNil) 675 for _, inst := range insts { 676 c.Assert(openstack.InstanceFloatingIP(inst).IP, gc.Equals, fmt.Sprintf("10.0.0.%v", inst.Id())) 677 } 678 } 679 680 func (s *localServerSuite) assertInstancesGathering(c *gc.C, withFloatingIP bool) { 681 // Create a config that matches s.TestConfig but with use-floating-ip 682 cfg, err := config.New(config.NoDefaults, s.TestConfig.Merge(coretesting.Attrs{ 683 "use-floating-ip": withFloatingIP, 684 })) 685 c.Assert(err, jc.ErrorIsNil) 686 env, err := environs.New(cfg) 687 c.Assert(err, jc.ErrorIsNil) 688 689 inst0, _ := testing.AssertStartInstance(c, env, "100") 690 id0 := inst0.Id() 691 inst1, _ := testing.AssertStartInstance(c, env, "101") 692 id1 := inst1.Id() 693 defer func() { 694 err := env.StopInstances(inst0.Id(), inst1.Id()) 695 c.Assert(err, jc.ErrorIsNil) 696 }() 697 698 for i, test := range instanceGathering { 699 c.Logf("test %d: find %v -> expect len %d, err: %v", i, test.ids, len(test.ids), test.err) 700 ids := make([]instance.Id, len(test.ids)) 701 for j, id := range test.ids { 702 switch id { 703 case "id0": 704 ids[j] = id0 705 case "id1": 706 ids[j] = id1 707 } 708 } 709 insts, err := env.Instances(ids) 710 c.Assert(err, gc.Equals, test.err) 711 if err == environs.ErrNoInstances { 712 c.Assert(insts, gc.HasLen, 0) 713 } else { 714 c.Assert(insts, gc.HasLen, len(test.ids)) 715 } 716 for j, inst := range insts { 717 if ids[j] != "" { 718 c.Assert(inst.Id(), gc.Equals, ids[j]) 719 if withFloatingIP { 720 c.Assert(openstack.InstanceFloatingIP(inst).IP, gc.Equals, fmt.Sprintf("10.0.0.%v", inst.Id())) 721 } else { 722 c.Assert(openstack.InstanceFloatingIP(inst), gc.IsNil) 723 } 724 } else { 725 c.Assert(inst, gc.IsNil) 726 } 727 } 728 } 729 } 730 731 func (s *localServerSuite) TestInstancesGathering(c *gc.C) { 732 s.assertInstancesGathering(c, false) 733 } 734 735 func (s *localServerSuite) TestInstancesGatheringWithFloatingIP(c *gc.C) { 736 s.assertInstancesGathering(c, true) 737 } 738 739 func (s *localServerSuite) TestInstancesBuildSpawning(c *gc.C) { 740 coretesting.SkipIfPPC64EL(c, "lp:1425242") 741 742 // HP servers are available once they are BUILD(spawning). 743 cleanup := s.srv.Nova.RegisterControlPoint( 744 "addServer", 745 func(sc hook.ServiceControl, args ...interface{}) error { 746 details := args[0].(*nova.ServerDetail) 747 details.Status = nova.StatusBuildSpawning 748 return nil 749 }, 750 ) 751 defer cleanup() 752 stateInst, _ := testing.AssertStartInstance(c, s.env, "100") 753 defer func() { 754 err := s.env.StopInstances(stateInst.Id()) 755 c.Assert(err, jc.ErrorIsNil) 756 }() 757 758 instances, err := s.env.Instances([]instance.Id{stateInst.Id()}) 759 760 c.Assert(err, jc.ErrorIsNil) 761 c.Assert(instances, gc.HasLen, 1) 762 c.Assert(instances[0].Status().Message, gc.Equals, nova.StatusBuildSpawning) 763 } 764 765 func (s *localServerSuite) TestInstancesShutoffSuspended(c *gc.C) { 766 coretesting.SkipIfPPC64EL(c, "lp:1425242") 767 768 cleanup := s.srv.Nova.RegisterControlPoint( 769 "addServer", 770 func(sc hook.ServiceControl, args ...interface{}) error { 771 details := args[0].(*nova.ServerDetail) 772 switch { 773 case strings.HasSuffix(details.Name, "-100"): 774 details.Status = nova.StatusShutoff 775 case strings.HasSuffix(details.Name, "-101"): 776 details.Status = nova.StatusSuspended 777 default: 778 c.Fatalf("unexpected instance details: %#v", details) 779 } 780 return nil 781 }, 782 ) 783 defer cleanup() 784 stateInst1, _ := testing.AssertStartInstance(c, s.env, "100") 785 stateInst2, _ := testing.AssertStartInstance(c, s.env, "101") 786 defer func() { 787 err := s.env.StopInstances(stateInst1.Id(), stateInst2.Id()) 788 c.Assert(err, jc.ErrorIsNil) 789 }() 790 791 instances, err := s.env.Instances([]instance.Id{stateInst1.Id(), stateInst2.Id()}) 792 793 c.Assert(err, jc.ErrorIsNil) 794 c.Assert(instances, gc.HasLen, 2) 795 c.Assert(instances[0].Status().Message, gc.Equals, nova.StatusShutoff) 796 c.Assert(instances[1].Status().Message, gc.Equals, nova.StatusSuspended) 797 } 798 799 func (s *localServerSuite) TestInstancesErrorResponse(c *gc.C) { 800 coretesting.SkipIfPPC64EL(c, "lp:1425242") 801 802 cleanup := s.srv.Nova.RegisterControlPoint( 803 "server", 804 func(sc hook.ServiceControl, args ...interface{}) error { 805 return fmt.Errorf("strange error not instance") 806 }, 807 ) 808 defer cleanup() 809 810 instances, err := s.env.Instances([]instance.Id{"1"}) 811 c.Check(instances, gc.IsNil) 812 c.Assert(err, gc.ErrorMatches, "(?s).*strange error not instance.*") 813 } 814 815 func (s *localServerSuite) TestInstancesMultiErrorResponse(c *gc.C) { 816 coretesting.SkipIfPPC64EL(c, "lp:1425242") 817 818 cleanup := s.srv.Nova.RegisterControlPoint( 819 "matchServers", 820 func(sc hook.ServiceControl, args ...interface{}) error { 821 return fmt.Errorf("strange error no instances") 822 }, 823 ) 824 defer cleanup() 825 826 instances, err := s.env.Instances([]instance.Id{"1", "2"}) 827 c.Check(instances, gc.IsNil) 828 c.Assert(err, gc.ErrorMatches, "(?s).*strange error no instances.*") 829 } 830 831 // TODO (wallyworld) - this test was copied from the ec2 provider. 832 // It should be moved to environs.jujutests.Tests. 833 func (s *localServerSuite) TestBootstrapInstanceUserDataAndState(c *gc.C) { 834 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), s.env, bootstrap.BootstrapParams{}) 835 c.Assert(err, jc.ErrorIsNil) 836 837 // Check that ControllerInstances returns the ID of the bootstrap machine. 838 ids, err := s.env.ControllerInstances() 839 c.Assert(err, jc.ErrorIsNil) 840 c.Assert(ids, gc.HasLen, 1) 841 842 insts, err := s.env.AllInstances() 843 c.Assert(err, jc.ErrorIsNil) 844 c.Assert(insts, gc.HasLen, 1) 845 c.Check(insts[0].Id(), gc.Equals, ids[0]) 846 847 addresses, err := insts[0].Addresses() 848 c.Assert(err, jc.ErrorIsNil) 849 c.Assert(addresses, gc.Not(gc.HasLen), 0) 850 851 // TODO(wallyworld) - 2013-03-01 bug=1137005 852 // The nova test double needs to be updated to support retrieving instance userData. 853 // Until then, we can't check the cloud init script was generated correctly. 854 // When we can, we should also check cloudinit for a non-manager node (as in the 855 // ec2 tests). 856 } 857 858 func (s *localServerSuite) assertGetImageMetadataSources(c *gc.C, stream, officialSourcePath string) { 859 // Create a config that matches s.TestConfig but with the specified stream. 860 envAttrs := s.TestConfig 861 if stream != "" { 862 envAttrs = envAttrs.Merge(coretesting.Attrs{"image-stream": stream}) 863 } 864 cfg, err := config.New(config.NoDefaults, envAttrs) 865 c.Assert(err, jc.ErrorIsNil) 866 env, err := environs.New(cfg) 867 c.Assert(err, jc.ErrorIsNil) 868 sources, err := environs.ImageMetadataSources(env) 869 c.Assert(err, jc.ErrorIsNil) 870 c.Assert(sources, gc.HasLen, 4) 871 var urls = make([]string, len(sources)) 872 for i, source := range sources { 873 url, err := source.URL("") 874 c.Assert(err, jc.ErrorIsNil) 875 urls[i] = url 876 } 877 // The image-metadata-url ends with "/juju-dist-test/". 878 c.Check(strings.HasSuffix(urls[0], "/juju-dist-test/"), jc.IsTrue) 879 // The product-streams URL ends with "/imagemetadata". 880 c.Check(strings.HasSuffix(urls[1], "/imagemetadata/"), jc.IsTrue) 881 c.Assert(urls[2], gc.Equals, fmt.Sprintf("https://streams.canonical.com/juju/images/%s/", officialSourcePath)) 882 c.Assert(urls[3], gc.Equals, fmt.Sprintf("http://cloud-images.ubuntu.com/%s/", officialSourcePath)) 883 } 884 885 func (s *localServerSuite) TestGetImageMetadataSources(c *gc.C) { 886 s.assertGetImageMetadataSources(c, "", "releases") 887 s.assertGetImageMetadataSources(c, "released", "releases") 888 s.assertGetImageMetadataSources(c, "daily", "daily") 889 } 890 891 func (s *localServerSuite) TestGetImageMetadataSourcesNoProductStreams(c *gc.C) { 892 s.PatchValue(openstack.MakeServiceURL, func(client.AuthenticatingClient, string, []string) (string, error) { 893 return "", errors.New("cannae do it captain") 894 }) 895 env := s.Open(c, s.env.Config()) 896 sources, err := environs.ImageMetadataSources(env) 897 c.Assert(err, jc.ErrorIsNil) 898 c.Assert(sources, gc.HasLen, 3) 899 900 // Check that data sources are in the right order 901 c.Check(sources[0].Description(), gc.Equals, "image-metadata-url") 902 c.Check(sources[1].Description(), gc.Equals, "default cloud images") 903 c.Check(sources[2].Description(), gc.Equals, "default ubuntu cloud images") 904 } 905 906 func (s *localServerSuite) TestGetToolsMetadataSources(c *gc.C) { 907 s.PatchValue(&tools.DefaultBaseURL, "") 908 909 env := s.Open(c, s.env.Config()) 910 sources, err := tools.GetMetadataSources(env) 911 c.Assert(err, jc.ErrorIsNil) 912 c.Assert(sources, gc.HasLen, 2) 913 var urls = make([]string, len(sources)) 914 for i, source := range sources { 915 url, err := source.URL("") 916 c.Assert(err, jc.ErrorIsNil) 917 urls[i] = url 918 } 919 // The agent-metadata-url ends with "/juju-dist-test/tools/". 920 c.Check(strings.HasSuffix(urls[0], "/juju-dist-test/tools/"), jc.IsTrue) 921 // Check that the URL from keystone parses. 922 _, err = url.Parse(urls[1]) 923 c.Assert(err, jc.ErrorIsNil) 924 } 925 926 func (s *localServerSuite) TestSupportedArchitectures(c *gc.C) { 927 env := s.Open(c, s.env.Config()) 928 a, err := env.SupportedArchitectures() 929 c.Assert(err, jc.ErrorIsNil) 930 c.Assert(a, jc.SameContents, []string{"amd64", "arm64", "ppc64el", "s390x"}) 931 } 932 933 func (s *localServerSuite) TestSupportsNetworking(c *gc.C) { 934 env := s.Open(c, s.env.Config()) 935 _, ok := environs.SupportsNetworking(env) 936 c.Assert(ok, jc.IsFalse) 937 } 938 939 func (s *localServerSuite) TestFindImageBadDefaultImage(c *gc.C) { 940 imagetesting.PatchOfficialDataSources(&s.CleanupSuite, "") 941 env := s.Open(c, s.env.Config()) 942 943 // An error occurs if no suitable image is found. 944 _, err := openstack.FindInstanceSpec(env, "saucy", "amd64", "mem=1G", nil) 945 c.Assert(err, gc.ErrorMatches, `no "saucy" images in some-region with arches \[amd64\]`) 946 } 947 948 func (s *localServerSuite) TestConstraintsValidator(c *gc.C) { 949 env := s.Open(c, s.env.Config()) 950 validator, err := env.ConstraintsValidator() 951 c.Assert(err, jc.ErrorIsNil) 952 cons := constraints.MustParse("arch=amd64 cpu-power=10 virt-type=lxd") 953 unsupported, err := validator.Validate(cons) 954 c.Assert(err, jc.ErrorIsNil) 955 c.Assert(unsupported, jc.SameContents, []string{"cpu-power"}) 956 } 957 958 func (s *localServerSuite) TestConstraintsValidatorVocab(c *gc.C) { 959 env := s.Open(c, s.env.Config()) 960 validator, err := env.ConstraintsValidator() 961 c.Assert(err, jc.ErrorIsNil) 962 963 // i386 is a valid arch, but is no longer supported. No image 964 // data was created for it for the test. 965 cons := constraints.MustParse("arch=i386") 966 _, err = validator.Validate(cons) 967 c.Assert(err, gc.ErrorMatches, "invalid constraint value: arch=i386\nvalid values are:.*") 968 cons = constraints.MustParse("instance-type=foo") 969 _, err = validator.Validate(cons) 970 c.Assert(err, gc.ErrorMatches, "invalid constraint value: instance-type=foo\nvalid values are:.*") 971 972 cons = constraints.MustParse("virt-type=foo") 973 _, err = validator.Validate(cons) 974 c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta("invalid constraint value: virt-type=foo\nvalid values are: [kvm lxd]")) 975 } 976 977 func (s *localServerSuite) TestConstraintsMerge(c *gc.C) { 978 env := s.Open(c, s.env.Config()) 979 validator, err := env.ConstraintsValidator() 980 c.Assert(err, jc.ErrorIsNil) 981 consA := constraints.MustParse("arch=amd64 mem=1G root-disk=10G") 982 consB := constraints.MustParse("instance-type=m1.small") 983 cons, err := validator.Merge(consA, consB) 984 c.Assert(err, jc.ErrorIsNil) 985 c.Assert(cons, gc.DeepEquals, constraints.MustParse("instance-type=m1.small")) 986 } 987 988 func (s *localServerSuite) TestFindImageInstanceConstraint(c *gc.C) { 989 env := s.Open(c, s.env.Config()) 990 imageMetadata := []*imagemetadata.ImageMetadata{{ 991 Id: "image-id", 992 Arch: "amd64", 993 }} 994 995 spec, err := openstack.FindInstanceSpec( 996 env, coretesting.FakeDefaultSeries, "amd64", "instance-type=m1.tiny", 997 imageMetadata, 998 ) 999 c.Assert(err, jc.ErrorIsNil) 1000 c.Assert(spec.InstanceType.Name, gc.Equals, "m1.tiny") 1001 } 1002 1003 func (s *localServerSuite) TestFindInstanceImageConstraintHypervisor(c *gc.C) { 1004 testVirtType := "qemu" 1005 env := s.Open(c, s.env.Config()) 1006 imageMetadata := []*imagemetadata.ImageMetadata{{ 1007 Id: "image-id", 1008 Arch: "amd64", 1009 VirtType: testVirtType, 1010 }} 1011 1012 spec, err := openstack.FindInstanceSpec( 1013 env, coretesting.FakeDefaultSeries, "amd64", "virt-type="+testVirtType, 1014 imageMetadata, 1015 ) 1016 c.Assert(err, jc.ErrorIsNil) 1017 c.Assert(spec.InstanceType.VirtType, gc.NotNil) 1018 c.Assert(*spec.InstanceType.VirtType, gc.Equals, testVirtType) 1019 c.Assert(spec.InstanceType.Name, gc.Equals, "m1.small") 1020 } 1021 1022 func (s *localServerSuite) TestFindInstanceImageWithHypervisorNoConstraint(c *gc.C) { 1023 testVirtType := "qemu" 1024 env := s.Open(c, s.env.Config()) 1025 imageMetadata := []*imagemetadata.ImageMetadata{{ 1026 Id: "image-id", 1027 Arch: "amd64", 1028 VirtType: testVirtType, 1029 }} 1030 1031 spec, err := openstack.FindInstanceSpec( 1032 env, coretesting.FakeDefaultSeries, "amd64", "", 1033 imageMetadata, 1034 ) 1035 c.Assert(err, jc.ErrorIsNil) 1036 c.Assert(spec.InstanceType.VirtType, gc.NotNil) 1037 c.Assert(*spec.InstanceType.VirtType, gc.Equals, testVirtType) 1038 c.Assert(spec.InstanceType.Name, gc.Equals, "m1.small") 1039 } 1040 1041 func (s *localServerSuite) TestFindInstanceNoConstraint(c *gc.C) { 1042 env := s.Open(c, s.env.Config()) 1043 imageMetadata := []*imagemetadata.ImageMetadata{{ 1044 Id: "image-id", 1045 Arch: "amd64", 1046 }} 1047 1048 spec, err := openstack.FindInstanceSpec( 1049 env, coretesting.FakeDefaultSeries, "amd64", "", 1050 imageMetadata, 1051 ) 1052 c.Assert(err, jc.ErrorIsNil) 1053 c.Assert(spec.InstanceType.VirtType, gc.IsNil) 1054 c.Assert(spec.InstanceType.Name, gc.Equals, "m1.small") 1055 } 1056 1057 func (s *localServerSuite) TestFindImageInvalidInstanceConstraint(c *gc.C) { 1058 env := s.Open(c, s.env.Config()) 1059 imageMetadata := []*imagemetadata.ImageMetadata{{ 1060 Id: "image-id", 1061 Arch: "amd64", 1062 }} 1063 _, err := openstack.FindInstanceSpec( 1064 env, coretesting.FakeDefaultSeries, "amd64", "instance-type=m1.large", 1065 imageMetadata, 1066 ) 1067 c.Assert(err, gc.ErrorMatches, `no instance types in some-region matching constraints "instance-type=m1.large"`) 1068 } 1069 1070 func (s *localServerSuite) TestPrecheckInstanceValidInstanceType(c *gc.C) { 1071 env := s.Open(c, s.env.Config()) 1072 cons := constraints.MustParse("instance-type=m1.small") 1073 placement := "" 1074 err := env.PrecheckInstance(coretesting.FakeDefaultSeries, cons, placement) 1075 c.Assert(err, jc.ErrorIsNil) 1076 } 1077 1078 func (s *localServerSuite) TestPrecheckInstanceInvalidInstanceType(c *gc.C) { 1079 env := s.Open(c, s.env.Config()) 1080 cons := constraints.MustParse("instance-type=m1.large") 1081 placement := "" 1082 err := env.PrecheckInstance(coretesting.FakeDefaultSeries, cons, placement) 1083 c.Assert(err, gc.ErrorMatches, `invalid Openstack flavour "m1.large" specified`) 1084 } 1085 1086 func (t *localServerSuite) TestPrecheckInstanceAvailZone(c *gc.C) { 1087 placement := "zone=test-available" 1088 err := t.env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, placement) 1089 c.Assert(err, jc.ErrorIsNil) 1090 } 1091 1092 func (t *localServerSuite) TestPrecheckInstanceAvailZoneUnavailable(c *gc.C) { 1093 placement := "zone=test-unavailable" 1094 err := t.env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, placement) 1095 c.Assert(err, jc.ErrorIsNil) 1096 } 1097 1098 func (t *localServerSuite) TestPrecheckInstanceAvailZoneUnknown(c *gc.C) { 1099 placement := "zone=test-unknown" 1100 err := t.env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, placement) 1101 c.Assert(err, gc.ErrorMatches, `invalid availability zone "test-unknown"`) 1102 } 1103 1104 func (t *localServerSuite) TestPrecheckInstanceAvailZonesUnsupported(c *gc.C) { 1105 t.srv.Nova.SetAvailabilityZones() // no availability zone support 1106 placement := "zone=test-unknown" 1107 err := t.env.PrecheckInstance(coretesting.FakeDefaultSeries, constraints.Value{}, placement) 1108 c.Assert(err, jc.Satisfies, jujuerrors.IsNotImplemented) 1109 } 1110 1111 func (s *localServerSuite) TestValidateImageMetadata(c *gc.C) { 1112 env := s.Open(c, s.env.Config()) 1113 params, err := env.(simplestreams.MetadataValidator).MetadataLookupParams("some-region") 1114 c.Assert(err, jc.ErrorIsNil) 1115 params.Sources, err = environs.ImageMetadataSources(env) 1116 c.Assert(err, jc.ErrorIsNil) 1117 params.Series = "raring" 1118 image_ids, _, err := imagemetadata.ValidateImageMetadata(params) 1119 c.Assert(err, jc.ErrorIsNil) 1120 c.Assert(image_ids, jc.SameContents, []string{"id-y"}) 1121 } 1122 1123 func (s *localServerSuite) TestImageMetadataSourceOrder(c *gc.C) { 1124 src := func(env environs.Environ) (simplestreams.DataSource, error) { 1125 return simplestreams.NewURLDataSource("my datasource", "bar", false, simplestreams.CUSTOM_CLOUD_DATA, false), nil 1126 } 1127 environs.RegisterUserImageDataSourceFunc("my func", src) 1128 env := s.Open(c, s.env.Config()) 1129 sources, err := environs.ImageMetadataSources(env) 1130 c.Assert(err, jc.ErrorIsNil) 1131 var sourceIds []string 1132 for _, s := range sources { 1133 sourceIds = append(sourceIds, s.Description()) 1134 } 1135 c.Assert(sourceIds, jc.DeepEquals, []string{ 1136 "image-metadata-url", "my datasource", "keystone catalog", "default cloud images", "default ubuntu cloud images"}) 1137 } 1138 1139 // TestEnsureGroup checks that when creating a duplicate security group, the existing group is 1140 // returned and the existing rules have been left as is. 1141 func (s *localServerSuite) TestEnsureGroup(c *gc.C) { 1142 rule := []nova.RuleInfo{ 1143 { 1144 IPProtocol: "tcp", 1145 FromPort: 22, 1146 ToPort: 22, 1147 }, 1148 } 1149 1150 assertRule := func(group nova.SecurityGroup) { 1151 c.Check(len(group.Rules), gc.Equals, 1) 1152 c.Check(*group.Rules[0].IPProtocol, gc.Equals, "tcp") 1153 c.Check(*group.Rules[0].FromPort, gc.Equals, 22) 1154 c.Check(*group.Rules[0].ToPort, gc.Equals, 22) 1155 } 1156 1157 group, err := openstack.EnsureGroup(s.env, "test group", rule) 1158 c.Assert(err, jc.ErrorIsNil) 1159 c.Assert(group.Name, gc.Equals, "test group") 1160 assertRule(group) 1161 id := group.Id 1162 // Do it again and check that the existing group is returned. 1163 anotherRule := []nova.RuleInfo{ 1164 { 1165 IPProtocol: "tcp", 1166 FromPort: 1, 1167 ToPort: 65535, 1168 }, 1169 } 1170 group, err = openstack.EnsureGroup(s.env, "test group", anotherRule) 1171 c.Assert(err, jc.ErrorIsNil) 1172 c.Check(group.Id, gc.Equals, id) 1173 c.Assert(group.Name, gc.Equals, "test group") 1174 assertRule(group) 1175 } 1176 1177 // localHTTPSServerSuite contains tests that run against an Openstack service 1178 // double connected on an HTTPS port with a self-signed certificate. This 1179 // service is set up and torn down for every test. This should only test 1180 // things that depend on the HTTPS connection, all other functional tests on a 1181 // local connection should be in localServerSuite 1182 type localHTTPSServerSuite struct { 1183 coretesting.BaseSuite 1184 attrs map[string]interface{} 1185 cred *identity.Credentials 1186 srv localServer 1187 env environs.Environ 1188 } 1189 1190 func (s *localHTTPSServerSuite) SetUpSuite(c *gc.C) { 1191 s.BaseSuite.SetUpSuite(c) 1192 overrideCinderProvider(c, &s.CleanupSuite) 1193 } 1194 1195 func (s *localHTTPSServerSuite) createConfigAttrs(c *gc.C) map[string]interface{} { 1196 attrs := makeTestConfig(s.cred) 1197 attrs["agent-version"] = coretesting.FakeVersionNumber.String() 1198 attrs["authorized-keys"] = "fakekey" 1199 // In order to set up and tear down the environment properly, we must 1200 // disable hostname verification 1201 attrs["ssl-hostname-verification"] = false 1202 attrs["auth-url"] = s.cred.URL 1203 // Now connect and set up test-local tools and image-metadata URLs 1204 cl := client.NewNonValidatingClient(s.cred, identity.AuthUserPass, nil) 1205 err := cl.Authenticate() 1206 c.Assert(err, jc.ErrorIsNil) 1207 containerURL, err := cl.MakeServiceURL("object-store", nil) 1208 c.Assert(err, jc.ErrorIsNil) 1209 c.Check(containerURL[:8], gc.Equals, "https://") 1210 attrs["agent-metadata-url"] = containerURL + "/juju-dist-test/tools" 1211 c.Logf("Set agent-metadata-url=%q", attrs["agent-metadata-url"]) 1212 attrs["image-metadata-url"] = containerURL + "/juju-dist-test" 1213 c.Logf("Set image-metadata-url=%q", attrs["image-metadata-url"]) 1214 return attrs 1215 } 1216 1217 func (s *localHTTPSServerSuite) SetUpTest(c *gc.C) { 1218 s.BaseSuite.SetUpTest(c) 1219 s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber) 1220 s.srv.UseTLS = true 1221 cred := &identity.Credentials{ 1222 User: "fred", 1223 Secrets: "secret", 1224 Region: "some-region", 1225 TenantName: "some tenant", 1226 } 1227 // Note: start() will change cred.URL to point to s.srv.Server.URL 1228 s.srv.start(c, cred, newFullOpenstackService) 1229 s.cred = cred 1230 attrs := s.createConfigAttrs(c) 1231 c.Assert(attrs["auth-url"].(string)[:8], gc.Equals, "https://") 1232 var err error 1233 s.env, err = environs.Prepare( 1234 envtesting.BootstrapContext(c), 1235 jujuclienttesting.NewMemStore(), 1236 prepareParams(attrs, s.cred), 1237 ) 1238 c.Assert(err, jc.ErrorIsNil) 1239 s.attrs = s.env.Config().AllAttrs() 1240 } 1241 1242 func (s *localHTTPSServerSuite) TearDownTest(c *gc.C) { 1243 if s.env != nil { 1244 err := s.env.Destroy() 1245 c.Check(err, jc.ErrorIsNil) 1246 s.env = nil 1247 } 1248 s.srv.stop() 1249 s.BaseSuite.TearDownTest(c) 1250 } 1251 1252 func (s *localHTTPSServerSuite) TestMustDisableSSLVerify(c *gc.C) { 1253 coretesting.SkipIfPPC64EL(c, "lp:1425242") 1254 1255 // If you don't have ssl-hostname-verification set to false, then we 1256 // fail to connect to the environment. Copy the attrs used by SetUp and 1257 // force hostname verification. 1258 newattrs := make(map[string]interface{}, len(s.attrs)) 1259 for k, v := range s.attrs { 1260 newattrs[k] = v 1261 } 1262 newattrs["ssl-hostname-verification"] = true 1263 cfg, err := config.New(config.NoDefaults, newattrs) 1264 c.Assert(err, jc.ErrorIsNil) 1265 env, err := environs.New(cfg) 1266 c.Assert(err, jc.ErrorIsNil) 1267 _, err = env.AllInstances() 1268 c.Assert(err, gc.ErrorMatches, "(.|\n)*x509: certificate signed by unknown authority") 1269 } 1270 1271 func (s *localHTTPSServerSuite) TestCanBootstrap(c *gc.C) { 1272 restoreFinishBootstrap := envtesting.DisableFinishBootstrap() 1273 defer restoreFinishBootstrap() 1274 1275 // For testing, we create a storage instance to which is uploaded tools and image metadata. 1276 metadataStorage := openstack.MetadataStorage(s.env) 1277 url, err := metadataStorage.URL("") 1278 c.Assert(err, jc.ErrorIsNil) 1279 c.Logf("Generating fake tools for: %v", url) 1280 envtesting.UploadFakeTools(c, metadataStorage, s.env.Config().AgentStream(), s.env.Config().AgentStream()) 1281 defer envtesting.RemoveFakeTools(c, metadataStorage, s.env.Config().AgentStream()) 1282 openstack.UseTestImageData(metadataStorage, s.cred) 1283 defer openstack.RemoveTestImageData(metadataStorage) 1284 1285 err = bootstrap.Bootstrap(envtesting.BootstrapContext(c), s.env, bootstrap.BootstrapParams{}) 1286 c.Assert(err, jc.ErrorIsNil) 1287 } 1288 1289 func (s *localHTTPSServerSuite) TestFetchFromImageMetadataSources(c *gc.C) { 1290 // Setup a custom URL for image metadata 1291 customStorage := openstack.CreateCustomStorage(s.env, "custom-metadata") 1292 customURL, err := customStorage.URL("") 1293 c.Assert(err, jc.ErrorIsNil) 1294 c.Check(customURL[:8], gc.Equals, "https://") 1295 1296 config, err := s.env.Config().Apply( 1297 map[string]interface{}{"image-metadata-url": customURL}, 1298 ) 1299 c.Assert(err, jc.ErrorIsNil) 1300 err = s.env.SetConfig(config) 1301 c.Assert(err, jc.ErrorIsNil) 1302 sources, err := environs.ImageMetadataSources(s.env) 1303 c.Assert(err, jc.ErrorIsNil) 1304 c.Assert(sources, gc.HasLen, 4) 1305 1306 // Make sure there is something to download from each location 1307 metadata := "metadata-content" 1308 metadataStorage := openstack.ImageMetadataStorage(s.env) 1309 err = metadataStorage.Put(metadata, bytes.NewBufferString(metadata), int64(len(metadata))) 1310 c.Assert(err, jc.ErrorIsNil) 1311 1312 custom := "custom-content" 1313 err = customStorage.Put(custom, bytes.NewBufferString(custom), int64(len(custom))) 1314 c.Assert(err, jc.ErrorIsNil) 1315 1316 // Produce map of data sources keyed on description 1317 mappedSources := make(map[string]simplestreams.DataSource, len(sources)) 1318 for i, s := range sources { 1319 c.Logf("datasource %d: %+v", i, s) 1320 mappedSources[s.Description()] = s 1321 } 1322 1323 // Read from the Config entry's image-metadata-url 1324 contentReader, url, err := mappedSources["image-metadata-url"].Fetch(custom) 1325 c.Assert(err, jc.ErrorIsNil) 1326 defer contentReader.Close() 1327 content, err := ioutil.ReadAll(contentReader) 1328 c.Assert(err, jc.ErrorIsNil) 1329 c.Assert(string(content), gc.Equals, custom) 1330 c.Check(url[:8], gc.Equals, "https://") 1331 1332 // Check the entry we got from keystone 1333 contentReader, url, err = mappedSources["keystone catalog"].Fetch(metadata) 1334 c.Assert(err, jc.ErrorIsNil) 1335 defer contentReader.Close() 1336 content, err = ioutil.ReadAll(contentReader) 1337 c.Assert(err, jc.ErrorIsNil) 1338 c.Assert(string(content), gc.Equals, metadata) 1339 c.Check(url[:8], gc.Equals, "https://") 1340 // Verify that we are pointing at exactly where metadataStorage thinks we are 1341 metaURL, err := metadataStorage.URL(metadata) 1342 c.Assert(err, jc.ErrorIsNil) 1343 c.Check(url, gc.Equals, metaURL) 1344 1345 } 1346 1347 func (s *localHTTPSServerSuite) TestFetchFromToolsMetadataSources(c *gc.C) { 1348 // Setup a custom URL for image metadata 1349 customStorage := openstack.CreateCustomStorage(s.env, "custom-tools-metadata") 1350 customURL, err := customStorage.URL("") 1351 c.Assert(err, jc.ErrorIsNil) 1352 c.Check(customURL[:8], gc.Equals, "https://") 1353 1354 config, err := s.env.Config().Apply( 1355 map[string]interface{}{"agent-metadata-url": customURL}, 1356 ) 1357 c.Assert(err, jc.ErrorIsNil) 1358 err = s.env.SetConfig(config) 1359 c.Assert(err, jc.ErrorIsNil) 1360 sources, err := tools.GetMetadataSources(s.env) 1361 c.Assert(err, jc.ErrorIsNil) 1362 c.Assert(sources, gc.HasLen, 3) 1363 1364 // Make sure there is something to download from each location 1365 1366 keystone := "keystone-tools-content" 1367 // The keystone entry just points at the root of the Swift storage, and 1368 // we have to create a container to upload any data. So we just point 1369 // into a subdirectory for the data we are downloading 1370 keystoneContainer := "tools-test" 1371 keystoneStorage := openstack.CreateCustomStorage(s.env, "tools-test") 1372 err = keystoneStorage.Put(keystone, bytes.NewBufferString(keystone), int64(len(keystone))) 1373 c.Assert(err, jc.ErrorIsNil) 1374 1375 custom := "custom-tools-content" 1376 err = customStorage.Put(custom, bytes.NewBufferString(custom), int64(len(custom))) 1377 c.Assert(err, jc.ErrorIsNil) 1378 1379 // Read from the Config entry's agent-metadata-url 1380 contentReader, url, err := sources[0].Fetch(custom) 1381 c.Assert(err, jc.ErrorIsNil) 1382 defer contentReader.Close() 1383 content, err := ioutil.ReadAll(contentReader) 1384 c.Assert(err, jc.ErrorIsNil) 1385 c.Assert(string(content), gc.Equals, custom) 1386 c.Check(url[:8], gc.Equals, "https://") 1387 1388 // Check the entry we got from keystone 1389 // Now fetch the data, and verify the contents. 1390 contentReader, url, err = sources[1].Fetch(keystoneContainer + "/" + keystone) 1391 c.Assert(err, jc.ErrorIsNil) 1392 defer contentReader.Close() 1393 content, err = ioutil.ReadAll(contentReader) 1394 c.Assert(err, jc.ErrorIsNil) 1395 c.Assert(string(content), gc.Equals, keystone) 1396 c.Check(url[:8], gc.Equals, "https://") 1397 keystoneURL, err := keystoneStorage.URL(keystone) 1398 c.Assert(err, jc.ErrorIsNil) 1399 c.Check(url, gc.Equals, keystoneURL) 1400 1401 // We *don't* test Fetch for sources[3] because it points to 1402 // streams.canonical.com 1403 } 1404 1405 func (s *localServerSuite) TestRemoveBlankContainer(c *gc.C) { 1406 storage := openstack.BlankContainerStorage() 1407 err := storage.Remove("some-file") 1408 c.Assert(err, gc.ErrorMatches, `cannot remove "some-file": swift container name is empty`) 1409 } 1410 1411 func (s *localServerSuite) TestAllInstancesIgnoresOtherMachines(c *gc.C) { 1412 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), s.env, bootstrap.BootstrapParams{}) 1413 c.Assert(err, jc.ErrorIsNil) 1414 1415 // Check that we see 1 instance in the environment 1416 insts, err := s.env.AllInstances() 1417 c.Assert(err, jc.ErrorIsNil) 1418 c.Check(insts, gc.HasLen, 1) 1419 1420 // Now start a machine 'manually' in the same account, with a similar 1421 // but not matching name, and ensure it isn't seen by AllInstances 1422 // See bug #1257481, for how similar names were causing them to get 1423 // listed (and thus destroyed) at the wrong time 1424 existingModelName := s.TestConfig["name"] 1425 newMachineName := fmt.Sprintf("juju-%s-2-machine-0", existingModelName) 1426 1427 // We grab the Nova client directly from the env, just to save time 1428 // looking all the stuff up 1429 novaClient := openstack.GetNovaClient(s.env) 1430 entity, err := novaClient.RunServer(nova.RunServerOpts{ 1431 Name: newMachineName, 1432 FlavorId: "1", // test service has 1,2,3 for flavor ids 1433 ImageId: "1", // UseTestImageData sets up images 1 and 2 1434 }) 1435 c.Assert(err, jc.ErrorIsNil) 1436 c.Assert(entity, gc.NotNil) 1437 1438 // List all servers with no filter, we should see both instances 1439 servers, err := novaClient.ListServersDetail(nova.NewFilter()) 1440 c.Assert(err, jc.ErrorIsNil) 1441 c.Assert(servers, gc.HasLen, 2) 1442 1443 insts, err = s.env.AllInstances() 1444 c.Assert(err, jc.ErrorIsNil) 1445 c.Check(insts, gc.HasLen, 1) 1446 } 1447 1448 func (s *localServerSuite) TestResolveNetworkUUID(c *gc.C) { 1449 var sampleUUID = "f81d4fae-7dec-11d0-a765-00a0c91e6bf6" 1450 networkId, err := openstack.ResolveNetwork(s.env, sampleUUID) 1451 c.Assert(err, jc.ErrorIsNil) 1452 c.Assert(networkId, gc.Equals, sampleUUID) 1453 } 1454 1455 func (s *localServerSuite) TestResolveNetworkLabel(c *gc.C) { 1456 // For now this test has to cheat and use knowledge of goose internals 1457 var networkLabel = "net" 1458 var expectNetworkId = "1" 1459 networkId, err := openstack.ResolveNetwork(s.env, networkLabel) 1460 c.Assert(err, jc.ErrorIsNil) 1461 c.Assert(networkId, gc.Equals, expectNetworkId) 1462 } 1463 1464 func (s *localServerSuite) TestResolveNetworkNotPresent(c *gc.C) { 1465 var notPresentNetwork = "no-network-with-this-label" 1466 networkId, err := openstack.ResolveNetwork(s.env, notPresentNetwork) 1467 c.Check(networkId, gc.Equals, "") 1468 c.Assert(err, gc.ErrorMatches, `No networks exist with label "no-network-with-this-label"`) 1469 } 1470 1471 // TODO(gz): TestResolveNetworkMultipleMatching when can inject new networks 1472 1473 func (t *localServerSuite) TestStartInstanceAvailZone(c *gc.C) { 1474 inst, err := t.testStartInstanceAvailZone(c, "test-available") 1475 c.Assert(err, jc.ErrorIsNil) 1476 c.Assert(openstack.InstanceServerDetail(inst).AvailabilityZone, gc.Equals, "test-available") 1477 } 1478 1479 func (t *localServerSuite) TestStartInstanceAvailZoneUnavailable(c *gc.C) { 1480 _, err := t.testStartInstanceAvailZone(c, "test-unavailable") 1481 c.Assert(err, gc.ErrorMatches, `availability zone "test-unavailable" is unavailable`) 1482 } 1483 1484 func (t *localServerSuite) TestStartInstanceAvailZoneUnknown(c *gc.C) { 1485 _, err := t.testStartInstanceAvailZone(c, "test-unknown") 1486 c.Assert(err, gc.ErrorMatches, `invalid availability zone "test-unknown"`) 1487 } 1488 1489 func (t *localServerSuite) testStartInstanceAvailZone(c *gc.C, zone string) (instance.Instance, error) { 1490 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), t.env, bootstrap.BootstrapParams{}) 1491 c.Assert(err, jc.ErrorIsNil) 1492 1493 params := environs.StartInstanceParams{Placement: "zone=" + zone} 1494 result, err := testing.StartInstanceWithParams(t.env, "1", params) 1495 if err != nil { 1496 return nil, err 1497 } 1498 return result.Instance, nil 1499 } 1500 1501 func (t *localServerSuite) TestGetAvailabilityZones(c *gc.C) { 1502 var resultZones []nova.AvailabilityZone 1503 var resultErr error 1504 t.PatchValue(openstack.NovaListAvailabilityZones, func(c *nova.Client) ([]nova.AvailabilityZone, error) { 1505 return append([]nova.AvailabilityZone{}, resultZones...), resultErr 1506 }) 1507 env := t.env.(common.ZonedEnviron) 1508 1509 resultErr = fmt.Errorf("failed to get availability zones") 1510 zones, err := env.AvailabilityZones() 1511 c.Assert(err, gc.Equals, resultErr) 1512 c.Assert(zones, gc.IsNil) 1513 1514 resultErr = nil 1515 resultZones = make([]nova.AvailabilityZone, 1) 1516 resultZones[0].Name = "whatever" 1517 zones, err = env.AvailabilityZones() 1518 c.Assert(err, jc.ErrorIsNil) 1519 c.Assert(zones, gc.HasLen, 1) 1520 c.Assert(zones[0].Name(), gc.Equals, "whatever") 1521 1522 // A successful result is cached, currently for the lifetime 1523 // of the Environ. This will change if/when we have long-lived 1524 // Environs to cut down repeated IaaS requests. 1525 resultErr = fmt.Errorf("failed to get availability zones") 1526 resultZones[0].Name = "andever" 1527 zones, err = env.AvailabilityZones() 1528 c.Assert(err, jc.ErrorIsNil) 1529 c.Assert(zones, gc.HasLen, 1) 1530 c.Assert(zones[0].Name(), gc.Equals, "whatever") 1531 } 1532 1533 func (t *localServerSuite) TestGetAvailabilityZonesCommon(c *gc.C) { 1534 var resultZones []nova.AvailabilityZone 1535 t.PatchValue(openstack.NovaListAvailabilityZones, func(c *nova.Client) ([]nova.AvailabilityZone, error) { 1536 return append([]nova.AvailabilityZone{}, resultZones...), nil 1537 }) 1538 env := t.env.(common.ZonedEnviron) 1539 resultZones = make([]nova.AvailabilityZone, 2) 1540 resultZones[0].Name = "az1" 1541 resultZones[1].Name = "az2" 1542 resultZones[0].State.Available = true 1543 resultZones[1].State.Available = false 1544 zones, err := env.AvailabilityZones() 1545 c.Assert(err, jc.ErrorIsNil) 1546 c.Assert(zones, gc.HasLen, 2) 1547 c.Assert(zones[0].Name(), gc.Equals, resultZones[0].Name) 1548 c.Assert(zones[1].Name(), gc.Equals, resultZones[1].Name) 1549 c.Assert(zones[0].Available(), jc.IsTrue) 1550 c.Assert(zones[1].Available(), jc.IsFalse) 1551 } 1552 1553 type mockAvailabilityZoneAllocations struct { 1554 group []instance.Id // input param 1555 result []common.AvailabilityZoneInstances 1556 err error 1557 } 1558 1559 func (t *mockAvailabilityZoneAllocations) AvailabilityZoneAllocations( 1560 e common.ZonedEnviron, group []instance.Id, 1561 ) ([]common.AvailabilityZoneInstances, error) { 1562 t.group = group 1563 return t.result, t.err 1564 } 1565 1566 func (t *localServerSuite) TestStartInstanceDistributionParams(c *gc.C) { 1567 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), t.env, bootstrap.BootstrapParams{}) 1568 c.Assert(err, jc.ErrorIsNil) 1569 1570 var mock mockAvailabilityZoneAllocations 1571 t.PatchValue(openstack.AvailabilityZoneAllocations, mock.AvailabilityZoneAllocations) 1572 1573 // no distribution group specified 1574 testing.AssertStartInstance(c, t.env, "1") 1575 c.Assert(mock.group, gc.HasLen, 0) 1576 1577 // distribution group specified: ensure it's passed through to AvailabilityZone. 1578 expectedInstances := []instance.Id{"i-0", "i-1"} 1579 params := environs.StartInstanceParams{ 1580 DistributionGroup: func() ([]instance.Id, error) { 1581 return expectedInstances, nil 1582 }, 1583 } 1584 _, err = testing.StartInstanceWithParams(t.env, "1", params) 1585 c.Assert(err, jc.ErrorIsNil) 1586 c.Assert(mock.group, gc.DeepEquals, expectedInstances) 1587 } 1588 1589 func (t *localServerSuite) TestStartInstanceDistributionErrors(c *gc.C) { 1590 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), t.env, bootstrap.BootstrapParams{}) 1591 c.Assert(err, jc.ErrorIsNil) 1592 1593 mock := mockAvailabilityZoneAllocations{ 1594 err: fmt.Errorf("AvailabilityZoneAllocations failed"), 1595 } 1596 t.PatchValue(openstack.AvailabilityZoneAllocations, mock.AvailabilityZoneAllocations) 1597 _, _, _, err = testing.StartInstance(t.env, "1") 1598 c.Assert(jujuerrors.Cause(err), gc.Equals, mock.err) 1599 1600 mock.err = nil 1601 dgErr := fmt.Errorf("DistributionGroup failed") 1602 params := environs.StartInstanceParams{ 1603 DistributionGroup: func() ([]instance.Id, error) { 1604 return nil, dgErr 1605 }, 1606 } 1607 _, err = testing.StartInstanceWithParams(t.env, "1", params) 1608 c.Assert(jujuerrors.Cause(err), gc.Equals, dgErr) 1609 } 1610 1611 func (t *localServerSuite) TestStartInstanceDistribution(c *gc.C) { 1612 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), t.env, bootstrap.BootstrapParams{}) 1613 c.Assert(err, jc.ErrorIsNil) 1614 1615 // test-available is the only available AZ, so AvailabilityZoneAllocations 1616 // is guaranteed to return that. 1617 inst, _ := testing.AssertStartInstance(c, t.env, "1") 1618 c.Assert(openstack.InstanceServerDetail(inst).AvailabilityZone, gc.Equals, "test-available") 1619 } 1620 1621 func (t *localServerSuite) TestStartInstancePicksValidZoneForHost(c *gc.C) { 1622 coretesting.SkipIfPPC64EL(c, "lp:1425242") 1623 1624 t.srv.Nova.SetAvailabilityZones( 1625 // bootstrap node will be on az1. 1626 nova.AvailabilityZone{ 1627 Name: "az1", 1628 State: nova.AvailabilityZoneState{ 1629 Available: true, 1630 }, 1631 }, 1632 // az2 will be made to return an error. 1633 nova.AvailabilityZone{ 1634 Name: "az2", 1635 State: nova.AvailabilityZoneState{ 1636 Available: true, 1637 }, 1638 }, 1639 // az3 will be valid to host an instance. 1640 nova.AvailabilityZone{ 1641 Name: "az3", 1642 State: nova.AvailabilityZoneState{ 1643 Available: true, 1644 }, 1645 }, 1646 ) 1647 1648 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), t.env, bootstrap.BootstrapParams{}) 1649 c.Assert(err, jc.ErrorIsNil) 1650 1651 cleanup := t.srv.Nova.RegisterControlPoint( 1652 "addServer", 1653 func(sc hook.ServiceControl, args ...interface{}) error { 1654 serverDetail := args[0].(*nova.ServerDetail) 1655 if serverDetail.AvailabilityZone == "az2" { 1656 return fmt.Errorf("No valid host was found") 1657 } 1658 return nil 1659 }, 1660 ) 1661 defer cleanup() 1662 inst, _ := testing.AssertStartInstance(c, t.env, "1") 1663 c.Assert(openstack.InstanceServerDetail(inst).AvailabilityZone, gc.Equals, "az3") 1664 } 1665 1666 func (t *localServerSuite) TestStartInstanceWithUnknownAZError(c *gc.C) { 1667 coretesting.SkipIfPPC64EL(c, "lp:1425242") 1668 1669 t.srv.Nova.SetAvailabilityZones( 1670 // bootstrap node will be on az1. 1671 nova.AvailabilityZone{ 1672 Name: "az1", 1673 State: nova.AvailabilityZoneState{ 1674 Available: true, 1675 }, 1676 }, 1677 // az2 will be made to return an unknown error. 1678 nova.AvailabilityZone{ 1679 Name: "az2", 1680 State: nova.AvailabilityZoneState{ 1681 Available: true, 1682 }, 1683 }, 1684 ) 1685 1686 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), t.env, bootstrap.BootstrapParams{}) 1687 c.Assert(err, jc.ErrorIsNil) 1688 1689 cleanup := t.srv.Nova.RegisterControlPoint( 1690 "addServer", 1691 func(sc hook.ServiceControl, args ...interface{}) error { 1692 serverDetail := args[0].(*nova.ServerDetail) 1693 if serverDetail.AvailabilityZone == "az2" { 1694 return fmt.Errorf("Some unknown error") 1695 } 1696 return nil 1697 }, 1698 ) 1699 defer cleanup() 1700 _, _, _, err = testing.StartInstance(t.env, "1") 1701 c.Assert(err, gc.ErrorMatches, "(?s).*Some unknown error.*") 1702 } 1703 1704 func (t *localServerSuite) TestStartInstanceDistributionAZNotImplemented(c *gc.C) { 1705 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), t.env, bootstrap.BootstrapParams{}) 1706 c.Assert(err, jc.ErrorIsNil) 1707 1708 mock := mockAvailabilityZoneAllocations{ 1709 err: jujuerrors.NotImplementedf("availability zones"), 1710 } 1711 t.PatchValue(openstack.AvailabilityZoneAllocations, mock.AvailabilityZoneAllocations) 1712 1713 // Instance will be created without an availability zone specified. 1714 inst, _ := testing.AssertStartInstance(c, t.env, "1") 1715 c.Assert(openstack.InstanceServerDetail(inst).AvailabilityZone, gc.Equals, "") 1716 } 1717 1718 func (t *localServerSuite) TestInstanceTags(c *gc.C) { 1719 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), t.env, bootstrap.BootstrapParams{}) 1720 c.Assert(err, jc.ErrorIsNil) 1721 1722 instances, err := t.env.AllInstances() 1723 c.Assert(err, jc.ErrorIsNil) 1724 c.Assert(instances, gc.HasLen, 1) 1725 1726 c.Assert( 1727 openstack.InstanceServerDetail(instances[0]).Metadata, 1728 jc.DeepEquals, 1729 map[string]string{ 1730 "juju-model-uuid": coretesting.ModelTag.Id(), 1731 "juju-controller-uuid": coretesting.ModelTag.Id(), 1732 "juju-is-controller": "true", 1733 }, 1734 ) 1735 } 1736 1737 func (t *localServerSuite) TestTagInstance(c *gc.C) { 1738 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), t.env, bootstrap.BootstrapParams{}) 1739 c.Assert(err, jc.ErrorIsNil) 1740 1741 assertMetadata := func(extraKey, extraValue string) { 1742 // Refresh instance 1743 instances, err := t.env.AllInstances() 1744 c.Assert(err, jc.ErrorIsNil) 1745 c.Assert(instances, gc.HasLen, 1) 1746 c.Assert( 1747 openstack.InstanceServerDetail(instances[0]).Metadata, 1748 jc.DeepEquals, 1749 map[string]string{ 1750 "juju-model-uuid": coretesting.ModelTag.Id(), 1751 "juju-controller-uuid": coretesting.ModelTag.Id(), 1752 "juju-is-controller": "true", 1753 extraKey: extraValue, 1754 }, 1755 ) 1756 } 1757 1758 instances, err := t.env.AllInstances() 1759 c.Assert(err, jc.ErrorIsNil) 1760 c.Assert(instances, gc.HasLen, 1) 1761 1762 extraKey := "extra-k" 1763 extraValue := "extra-v" 1764 err = t.env.(environs.InstanceTagger).TagInstance( 1765 instances[0].Id(), map[string]string{extraKey: extraValue}, 1766 ) 1767 c.Assert(err, jc.ErrorIsNil) 1768 assertMetadata(extraKey, extraValue) 1769 1770 // Ensure that a second call updates existing tags. 1771 extraValue = "extra-v2" 1772 err = t.env.(environs.InstanceTagger).TagInstance( 1773 instances[0].Id(), map[string]string{extraKey: extraValue}, 1774 ) 1775 c.Assert(err, jc.ErrorIsNil) 1776 assertMetadata(extraKey, extraValue) 1777 } 1778 1779 func prepareParams(attrs map[string]interface{}, cred *identity.Credentials) environs.PrepareParams { 1780 return environs.PrepareParams{ 1781 BaseConfig: attrs, 1782 ControllerName: attrs["name"].(string), 1783 Credential: makeCredential(cred), 1784 CloudName: "openstack", 1785 CloudEndpoint: cred.URL, 1786 CloudRegion: cred.Region, 1787 } 1788 } 1789 1790 func makeCredential(cred *identity.Credentials) cloud.Credential { 1791 return cloud.NewCredential( 1792 cloud.UserPassAuthType, 1793 map[string]string{ 1794 "username": cred.User, 1795 "password": cred.Secrets, 1796 "tenant-name": cred.TenantName, 1797 }, 1798 ) 1799 } 1800 1801 // noSwiftSuite contains tests that run against an OpenStack service double 1802 // that lacks Swift. 1803 type noSwiftSuite struct { 1804 coretesting.BaseSuite 1805 cred *identity.Credentials 1806 srv localServer 1807 env environs.Environ 1808 } 1809 1810 func (s *noSwiftSuite) SetUpSuite(c *gc.C) { 1811 s.BaseSuite.SetUpSuite(c) 1812 restoreFinishBootstrap := envtesting.DisableFinishBootstrap() 1813 s.AddCleanup(func(*gc.C) { restoreFinishBootstrap() }) 1814 1815 s.PatchValue(&imagemetadata.SimplestreamsImagesPublicKey, sstesting.SignedMetadataPublicKey) 1816 s.PatchValue(&juju.JujuPublicKey, sstesting.SignedMetadataPublicKey) 1817 } 1818 1819 func (s *noSwiftSuite) SetUpTest(c *gc.C) { 1820 s.BaseSuite.SetUpTest(c) 1821 s.cred = &identity.Credentials{ 1822 User: "fred", 1823 Secrets: "secret", 1824 Region: "some-region", 1825 TenantName: "some tenant", 1826 } 1827 s.srv.start(c, s.cred, newNovaOnlyOpenstackService) 1828 1829 attrs := coretesting.FakeConfig().Merge(coretesting.Attrs{ 1830 "name": "sample-no-swift", 1831 "type": "openstack", 1832 "auth-mode": "userpass", 1833 "agent-version": coretesting.FakeVersionNumber.String(), 1834 "authorized-keys": "fakekey", 1835 }) 1836 s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber) 1837 // Serve fake tools and image metadata using "filestorage", 1838 // rather than Swift as the rest of the tests do. 1839 storageDir := c.MkDir() 1840 imagesDir := filepath.Join(storageDir, "images") 1841 toolsDir := filepath.Join(storageDir, "tools") 1842 for _, dir := range []string{imagesDir, toolsDir} { 1843 err := os.MkdirAll(dir, 0755) 1844 c.Assert(err, jc.ErrorIsNil) 1845 } 1846 toolsStorage, err := filestorage.NewFileStorageWriter(storageDir) 1847 c.Assert(err, jc.ErrorIsNil) 1848 envtesting.UploadFakeTools(c, toolsStorage, "released", "released") 1849 s.PatchValue(&tools.DefaultBaseURL, storageDir) 1850 imageStorage, err := filestorage.NewFileStorageWriter(imagesDir) 1851 openstack.UseTestImageData(imageStorage, s.cred) 1852 imagetesting.PatchOfficialDataSources(&s.CleanupSuite, storageDir) 1853 1854 env, err := environs.Prepare( 1855 envtesting.BootstrapContext(c), 1856 jujuclienttesting.NewMemStore(), 1857 prepareParams(attrs, s.cred), 1858 ) 1859 c.Assert(err, jc.ErrorIsNil) 1860 s.env = env 1861 } 1862 1863 func (s *noSwiftSuite) TearDownTest(c *gc.C) { 1864 s.srv.stop() 1865 s.BaseSuite.TearDownTest(c) 1866 } 1867 1868 func (s *noSwiftSuite) TestBootstrap(c *gc.C) { 1869 err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), s.env, bootstrap.BootstrapParams{}) 1870 c.Assert(err, jc.ErrorIsNil) 1871 } 1872 1873 func newFullOpenstackService(mux *http.ServeMux, cred *identity.Credentials, auth identity.AuthMode) *novaservice.Nova { 1874 service := openstackservice.New(cred, auth) 1875 service.SetupHTTP(mux) 1876 return service.Nova 1877 } 1878 1879 func newNovaOnlyOpenstackService(mux *http.ServeMux, cred *identity.Credentials, auth identity.AuthMode) *novaservice.Nova { 1880 var identityService, fallbackService identityservice.IdentityService 1881 if auth == identity.AuthKeyPair { 1882 identityService = identityservice.NewKeyPair() 1883 } else { 1884 identityService = identityservice.NewUserPass() 1885 fallbackService = identityservice.NewV3UserPass() 1886 } 1887 userInfo := identityService.AddUser(cred.User, cred.Secrets, cred.TenantName) 1888 if cred.TenantName == "" { 1889 panic("Openstack service double requires a tenant to be specified.") 1890 } 1891 novaService := novaservice.New(cred.URL, "v2", userInfo.TenantId, cred.Region, identityService, fallbackService) 1892 identityService.SetupHTTP(mux) 1893 novaService.SetupHTTP(mux) 1894 return novaService 1895 }