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