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