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