github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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 stdcontext "context" 9 "encoding/pem" 10 "fmt" 11 "io" 12 "net/http" 13 "net/http/httptest" 14 "net/url" 15 "os" 16 "path/filepath" 17 "regexp" 18 "sort" 19 "strings" 20 "time" 21 22 "github.com/go-goose/goose/v5/cinder" 23 "github.com/go-goose/goose/v5/client" 24 "github.com/go-goose/goose/v5/identity" 25 "github.com/go-goose/goose/v5/neutron" 26 "github.com/go-goose/goose/v5/nova" 27 "github.com/go-goose/goose/v5/testservices/hook" 28 "github.com/go-goose/goose/v5/testservices/identityservice" 29 "github.com/go-goose/goose/v5/testservices/neutronmodel" 30 "github.com/go-goose/goose/v5/testservices/neutronservice" 31 "github.com/go-goose/goose/v5/testservices/novaservice" 32 "github.com/go-goose/goose/v5/testservices/openstackservice" 33 "github.com/juju/clock/testclock" 34 "github.com/juju/collections/set" 35 "github.com/juju/errors" 36 "github.com/juju/names/v5" 37 gitjujutesting "github.com/juju/testing" 38 jc "github.com/juju/testing/checkers" 39 "github.com/juju/utils/v3" 40 "github.com/juju/utils/v3/ssh" 41 "github.com/juju/version/v2" 42 gc "gopkg.in/check.v1" 43 44 "github.com/juju/juju/cloud" 45 "github.com/juju/juju/cloudconfig/instancecfg" 46 "github.com/juju/juju/core/arch" 47 corebase "github.com/juju/juju/core/base" 48 "github.com/juju/juju/core/constraints" 49 "github.com/juju/juju/core/instance" 50 "github.com/juju/juju/core/network" 51 corenetwork "github.com/juju/juju/core/network" 52 "github.com/juju/juju/core/network/firewall" 53 "github.com/juju/juju/core/status" 54 "github.com/juju/juju/environs" 55 "github.com/juju/juju/environs/bootstrap" 56 environscloudspec "github.com/juju/juju/environs/cloudspec" 57 "github.com/juju/juju/environs/config" 58 "github.com/juju/juju/environs/context" 59 "github.com/juju/juju/environs/filestorage" 60 "github.com/juju/juju/environs/imagemetadata" 61 imagetesting "github.com/juju/juju/environs/imagemetadata/testing" 62 "github.com/juju/juju/environs/instances" 63 "github.com/juju/juju/environs/jujutest" 64 "github.com/juju/juju/environs/simplestreams" 65 sstesting "github.com/juju/juju/environs/simplestreams/testing" 66 envstorage "github.com/juju/juju/environs/storage" 67 "github.com/juju/juju/environs/tags" 68 envtesting "github.com/juju/juju/environs/testing" 69 "github.com/juju/juju/environs/tools" 70 "github.com/juju/juju/juju/keys" 71 "github.com/juju/juju/juju/testing" 72 "github.com/juju/juju/jujuclient" 73 "github.com/juju/juju/provider/common" 74 "github.com/juju/juju/provider/openstack" 75 "github.com/juju/juju/storage" 76 coretesting "github.com/juju/juju/testing" 77 jujuversion "github.com/juju/juju/version" 78 ) 79 80 type ProviderSuite struct { 81 restoreTimeouts func() 82 } 83 84 var _ = gc.Suite(&ProviderSuite{}) 85 86 func (s *ProviderSuite) SetUpTest(c *gc.C) { 87 s.restoreTimeouts = envtesting.PatchAttemptStrategies(openstack.ShortAttempt, openstack.StorageAttempt) 88 } 89 90 func (s *ProviderSuite) TearDownTest(c *gc.C) { 91 s.restoreTimeouts() 92 } 93 94 // Register tests to run against a test Openstack instance (service doubles). 95 func registerLocalTests() { 96 cred := &identity.Credentials{ 97 User: "fred", 98 Secrets: "secret", 99 Region: "some-region", 100 TenantName: "some tenant", 101 } 102 testConfig := makeTestConfig(cred) 103 testConfig["agent-version"] = coretesting.FakeVersionNumber.String() 104 testConfig["authorized-keys"] = "fakekey" 105 testConfig["network"] = "private_999" 106 gc.Suite(&localLiveSuite{ 107 LiveTests: LiveTests{ 108 cred: cred, 109 LiveTests: jujutest.LiveTests{ 110 TestConfig: testConfig, 111 }, 112 }, 113 }) 114 gc.Suite(&localServerSuite{ 115 cred: cred, 116 Tests: jujutest.Tests{ 117 TestConfig: testConfig, 118 }, 119 }) 120 gc.Suite(&noNeutronSuite{ 121 cred: cred, 122 }) 123 } 124 125 // localServer is used to spin up a local Openstack service double. 126 type localServer struct { 127 OpenstackSvc *openstackservice.Openstack 128 Nova *novaservice.Nova 129 Neutron *neutronservice.Neutron 130 restoreTimeouts func() 131 UseTLS bool 132 } 133 134 type newOpenstackFunc func(*identity.Credentials, identity.AuthMode, bool) (*openstackservice.Openstack, []string) 135 136 func (s *localServer) start( 137 c *gc.C, cred *identity.Credentials, newOpenstackFunc newOpenstackFunc, 138 ) { 139 var logMsg []string 140 s.OpenstackSvc, logMsg = newOpenstackFunc(cred, identity.AuthUserPass, s.UseTLS) 141 s.Nova = s.OpenstackSvc.Nova 142 s.Neutron = s.OpenstackSvc.Neutron 143 for _, msg := range logMsg { 144 c.Logf("%v", msg) 145 } 146 s.restoreTimeouts = envtesting.PatchAttemptStrategies(openstack.ShortAttempt, openstack.StorageAttempt) 147 s.Nova.SetAvailabilityZones( 148 nova.AvailabilityZone{Name: "test-unavailable"}, 149 nova.AvailabilityZone{ 150 Name: "test-available", 151 State: nova.AvailabilityZoneState{ 152 Available: true, 153 }, 154 }, 155 ) 156 } 157 158 func (s *localServer) stop() { 159 if s.OpenstackSvc != nil { 160 s.OpenstackSvc.Stop() 161 } else if s.Nova != nil { 162 s.Nova.Stop() 163 } 164 s.restoreTimeouts() 165 } 166 167 func (s *localServer) openstackCertificate(c *gc.C) ([]string, error) { 168 certificate, err := s.OpenstackSvc.Certificate(openstackservice.Identity) 169 if err != nil { 170 return []string{}, err 171 } 172 if certificate == nil { 173 return []string{}, errors.New("No certificate returned from openstack test double") 174 } 175 buf := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certificate.Raw}) 176 return []string{string(buf)}, nil 177 } 178 179 func (s *localHTTPSServerSuite) envUsingCertificate(c *gc.C) environs.Environ { 180 newattrs := make(map[string]interface{}, len(s.attrs)) 181 for k, v := range s.attrs { 182 newattrs[k] = v 183 } 184 newattrs["ssl-hostname-verification"] = true 185 cfg, err := config.New(config.NoDefaults, newattrs) 186 c.Assert(err, jc.ErrorIsNil) 187 188 cloudSpec := makeCloudSpec(s.cred) 189 cloudSpec.CACertificates, err = s.srv.openstackCertificate(c) 190 c.Assert(err, jc.ErrorIsNil) 191 192 env, err := environs.New(stdcontext.TODO(), environs.OpenParams{ 193 Cloud: cloudSpec, 194 Config: cfg, 195 }) 196 c.Assert(err, jc.ErrorIsNil) 197 return env 198 } 199 200 // localLiveSuite runs tests from LiveTests using an Openstack service double. 201 type localLiveSuite struct { 202 coretesting.BaseSuite 203 LiveTests 204 srv localServer 205 } 206 207 func makeMockAdapter() *mockAdapter { 208 volumes := make(map[string]*cinder.Volume) 209 return &mockAdapter{ 210 createVolume: func(args cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) { 211 metadata := args.Metadata.(map[string]string) 212 volume := cinder.Volume{ 213 ID: args.Name, 214 Metadata: metadata, 215 Status: "cool", 216 AvailabilityZone: args.AvailabilityZone, 217 } 218 volumes[volume.ID] = &volume 219 return &volume, nil 220 }, 221 getVolumesDetail: func() ([]cinder.Volume, error) { 222 var result []cinder.Volume 223 for _, volume := range volumes { 224 result = append(result, *volume) 225 } 226 return result, nil 227 }, 228 getVolume: func(volumeId string) (*cinder.Volume, error) { 229 if volume, ok := volumes[volumeId]; ok { 230 return volume, nil 231 } 232 return nil, errors.New("not found") 233 }, 234 setVolumeMetadata: func(volumeId string, metadata map[string]string) (map[string]string, error) { 235 if volume, ok := volumes[volumeId]; ok { 236 for k, v := range metadata { 237 volume.Metadata[k] = v 238 } 239 return volume.Metadata, nil 240 } 241 return nil, errors.New("not found") 242 }, 243 } 244 } 245 246 func overrideCinderProvider(s *gitjujutesting.CleanupSuite, adapter *mockAdapter) { 247 s.PatchValue(openstack.NewOpenstackStorage, func(*openstack.Environ) (openstack.OpenstackStorage, error) { 248 return adapter, nil 249 }) 250 } 251 252 func (s *localLiveSuite) SetUpSuite(c *gc.C) { 253 s.BaseSuite.SetUpSuite(c) 254 255 c.Logf("Running live tests using openstack service test double") 256 s.srv.start(c, s.cred, newFullOpenstackService) 257 258 // Set credentials to use when bootstrapping. Must be done after 259 // starting server to get the auth URL. 260 s.Credential = makeCredential(s.cred) 261 s.CloudEndpoint = s.cred.URL 262 s.CloudRegion = s.cred.Region 263 264 s.LiveTests.SetUpSuite(c) 265 openstack.UseTestImageData(openstack.ImageMetadataStorage(s.Env), s.cred) 266 restoreFinishBootstrap := envtesting.DisableFinishBootstrap() 267 s.AddCleanup(func(*gc.C) { restoreFinishBootstrap() }) 268 overrideCinderProvider(&s.CleanupSuite, &mockAdapter{}) 269 } 270 271 func (s *localLiveSuite) TearDownSuite(c *gc.C) { 272 openstack.RemoveTestImageData(openstack.ImageMetadataStorage(s.Env)) 273 s.LiveTests.TearDownSuite(c) 274 s.srv.stop() 275 s.BaseSuite.TearDownSuite(c) 276 } 277 278 func (s *localLiveSuite) SetUpTest(c *gc.C) { 279 s.BaseSuite.SetUpTest(c) 280 s.LiveTests.SetUpTest(c) 281 imagetesting.PatchOfficialDataSources(&s.CleanupSuite, "") 282 } 283 284 func (s *localLiveSuite) TearDownTest(c *gc.C) { 285 s.LiveTests.TearDownTest(c) 286 s.BaseSuite.TearDownTest(c) 287 } 288 289 // localServerSuite contains tests that run against an Openstack service double. 290 // These tests can test things that would be unreasonably slow or expensive 291 // to test on a live Openstack server. The service double is started and stopped for 292 // each test. 293 type localServerSuite struct { 294 coretesting.BaseSuite 295 jujutest.Tests 296 cred *identity.Credentials 297 srv localServer 298 env environs.Environ 299 toolsMetadataStorage envstorage.Storage 300 imageMetadataStorage envstorage.Storage 301 storageAdapter *mockAdapter 302 callCtx context.ProviderCallContext 303 } 304 305 func (s *localServerSuite) SetUpSuite(c *gc.C) { 306 s.BaseSuite.SetUpSuite(c) 307 restoreFinishBootstrap := envtesting.DisableFinishBootstrap() 308 s.AddCleanup(func(*gc.C) { restoreFinishBootstrap() }) 309 310 server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 311 w.WriteHeader(404) 312 })) 313 s.AddCleanup(func(c *gc.C) { 314 server.Close() 315 }) 316 s.PatchValue(&imagemetadata.DefaultUbuntuBaseURL, server.URL) 317 318 c.Logf("Running local tests") 319 } 320 321 func (s *localServerSuite) SetUpTest(c *gc.C) { 322 s.BaseSuite.SetUpTest(c) 323 s.srv.start(c, s.cred, newFullOpenstackService) 324 325 // Set credentials to use when bootstrapping. Must be done after 326 // starting server to get the auth URL. 327 s.Credential = makeCredential(s.cred) 328 s.CloudEndpoint = s.cred.URL 329 s.CloudRegion = s.cred.Region 330 cl := client.NewClient(s.cred, identity.AuthUserPass, nil) 331 err := cl.Authenticate() 332 c.Assert(err, jc.ErrorIsNil) 333 containerURL, err := cl.MakeServiceURL("object-store", "", nil) 334 c.Assert(err, jc.ErrorIsNil) 335 s.TestConfig = s.TestConfig.Merge(coretesting.Attrs{ 336 "agent-metadata-url": containerURL + "/juju-dist-test/tools", 337 "image-metadata-url": containerURL + "/juju-dist-test", 338 "auth-url": s.cred.URL, 339 }) 340 s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber) 341 s.Tests.SetUpTest(c) 342 // For testing, we create a storage instance to which is uploaded tools and image metadata. 343 s.env = s.Prepare(c) 344 s.toolsMetadataStorage = openstack.MetadataStorage(s.env) 345 // Put some fake metadata in place so that tests that are simply 346 // starting instances without any need to check if those instances 347 // are running can find the metadata. 348 envtesting.UploadFakeTools(c, s.toolsMetadataStorage, s.env.Config().AgentStream(), s.env.Config().AgentStream()) 349 s.imageMetadataStorage = openstack.ImageMetadataStorage(s.env) 350 openstack.UseTestImageData(s.imageMetadataStorage, s.cred) 351 s.storageAdapter = makeMockAdapter() 352 overrideCinderProvider(&s.CleanupSuite, s.storageAdapter) 353 s.callCtx = context.NewEmptyCloudCallContext() 354 } 355 356 func (s *localServerSuite) TearDownTest(c *gc.C) { 357 if s.imageMetadataStorage != nil { 358 openstack.RemoveTestImageData(s.imageMetadataStorage) 359 } 360 if s.toolsMetadataStorage != nil { 361 envtesting.RemoveFakeToolsMetadata(c, s.toolsMetadataStorage) 362 } 363 s.Tests.TearDownTest(c) 364 s.srv.stop() 365 s.BaseSuite.TearDownTest(c) 366 } 367 368 func (s *localServerSuite) openEnviron(c *gc.C, attrs coretesting.Attrs) environs.Environ { 369 cfg, err := config.New(config.NoDefaults, s.TestConfig.Merge(attrs)) 370 c.Assert(err, jc.ErrorIsNil) 371 env, err := environs.New(stdcontext.TODO(), environs.OpenParams{ 372 Cloud: s.CloudSpec(), 373 Config: cfg, 374 }) 375 c.Assert(err, jc.ErrorIsNil) 376 return env 377 } 378 379 func (s *localServerSuite) TestBootstrap(c *gc.C) { 380 // Tests uses Prepare, so destroy first. 381 err := environs.Destroy(s.env.Config().Name(), s.env, s.callCtx, s.ControllerStore) 382 c.Assert(err, jc.ErrorIsNil) 383 s.Tests.TestBootstrap(c) 384 } 385 386 func (s *localServerSuite) TestStartStop(c *gc.C) { 387 // Tests uses Prepare, so destroy first. 388 err := environs.Destroy(s.env.Config().Name(), s.env, s.callCtx, s.ControllerStore) 389 c.Assert(err, jc.ErrorIsNil) 390 s.Tests.TestStartStop(c) 391 } 392 393 // If the bootstrap node is configured to require a public IP address, 394 // bootstrapping fails if an address cannot be allocated. 395 func (s *localServerSuite) TestBootstrapFailsWhenPublicIPError(c *gc.C) { 396 coretesting.SkipIfPPC64EL(c, "lp:1425242") 397 398 cleanup := s.srv.Neutron.RegisterControlPoint( 399 "addFloatingIP", 400 func(sc hook.ServiceControl, args ...interface{}) error { 401 return fmt.Errorf("failed on purpose") 402 }, 403 ) 404 defer cleanup() 405 406 err := environs.Destroy(s.env.Config().Name(), s.env, s.callCtx, s.ControllerStore) 407 c.Assert(err, jc.ErrorIsNil) 408 409 env := s.openEnviron(c, coretesting.Attrs{}) 410 cons := constraints.MustParse("allocate-public-ip=true") 411 err = bootstrapEnvWithConstraints(c, env, cons) 412 c.Assert(err, gc.ErrorMatches, "(.|\n)*cannot allocate a public IP as needed(.|\n)*") 413 } 414 415 func (s *localServerSuite) TestAddressesWithPublicIPConstraints(c *gc.C) { 416 // Floating IP address is 10.0.0.1 417 bootstrapFinished := false 418 s.PatchValue(&common.FinishBootstrap, func( 419 ctx environs.BootstrapContext, 420 client ssh.Client, 421 env environs.Environ, 422 callCtx context.ProviderCallContext, 423 inst instances.Instance, 424 instanceConfig *instancecfg.InstanceConfig, 425 _ environs.BootstrapDialOpts, 426 ) error { 427 addr, err := inst.Addresses(s.callCtx) 428 c.Assert(err, jc.ErrorIsNil) 429 c.Assert(addr, jc.SameContents, network.ProviderAddresses{ 430 network.NewMachineAddress("10.0.0.1", corenetwork.WithScope(corenetwork.ScopePublic)).AsProviderAddress(), 431 network.NewMachineAddress("127.0.0.1", corenetwork.WithScope(corenetwork.ScopeMachineLocal)).AsProviderAddress(), 432 network.NewMachineAddress("::face::000f").AsProviderAddress(), 433 network.NewMachineAddress("127.10.0.1", corenetwork.WithScope(corenetwork.ScopePublic)).AsProviderAddress(), 434 network.NewMachineAddress("::dead:beef:f00d", corenetwork.WithScope(corenetwork.ScopePublic)).AsProviderAddress(), 435 }) 436 bootstrapFinished = true 437 return nil 438 }) 439 440 env := s.openEnviron(c, coretesting.Attrs{ 441 "network": "private_999", 442 }) 443 cons := constraints.MustParse("allocate-public-ip=true") 444 err := bootstrapEnvWithConstraints(c, env, cons) 445 c.Assert(err, jc.ErrorIsNil) 446 c.Assert(bootstrapFinished, jc.IsTrue) 447 } 448 449 func (s *localServerSuite) TestAddressesWithoutPublicIPConstraints(c *gc.C) { 450 bootstrapFinished := false 451 s.PatchValue(&common.FinishBootstrap, func( 452 ctx environs.BootstrapContext, 453 client ssh.Client, 454 env environs.Environ, 455 callCtx context.ProviderCallContext, 456 inst instances.Instance, 457 instanceConfig *instancecfg.InstanceConfig, 458 _ environs.BootstrapDialOpts, 459 ) error { 460 addr, err := inst.Addresses(s.callCtx) 461 c.Assert(err, jc.ErrorIsNil) 462 c.Assert(addr, jc.SameContents, network.ProviderAddresses{ 463 network.NewMachineAddress("127.0.0.1", corenetwork.WithScope(corenetwork.ScopeMachineLocal)).AsProviderAddress(), 464 network.NewMachineAddress("::face::000f").AsProviderAddress(), 465 network.NewMachineAddress("127.10.0.1", corenetwork.WithScope(corenetwork.ScopePublic)).AsProviderAddress(), 466 network.NewMachineAddress("::dead:beef:f00d", corenetwork.WithScope(corenetwork.ScopePublic)).AsProviderAddress(), 467 }) 468 bootstrapFinished = true 469 return nil 470 }) 471 472 env := s.openEnviron(c, coretesting.Attrs{}) 473 cons := constraints.MustParse("allocate-public-ip=false") 474 err := bootstrapEnvWithConstraints(c, env, cons) 475 c.Assert(err, jc.ErrorIsNil) 476 c.Assert(bootstrapFinished, jc.IsTrue) 477 } 478 479 // If the environment is configured not to require a public IP address for nodes, 480 // bootstrapping and starting an instance should occur without any attempt to 481 // allocate a public address. 482 func (s *localServerSuite) TestStartInstanceWithoutPublicIP(c *gc.C) { 483 cleanup := s.srv.Neutron.RegisterControlPoint( 484 "addFloatingIP", 485 func(sc hook.ServiceControl, args ...interface{}) error { 486 return fmt.Errorf("add floating IP should not have been called") 487 }, 488 ) 489 defer cleanup() 490 cleanup = s.srv.Nova.RegisterControlPoint( 491 "addServerFloatingIP", 492 func(sc hook.ServiceControl, args ...interface{}) error { 493 return fmt.Errorf("add server floating IP should not have been called") 494 }, 495 ) 496 defer cleanup() 497 498 err := environs.Destroy(s.env.Config().Name(), s.env, s.callCtx, s.ControllerStore) 499 c.Assert(err, jc.ErrorIsNil) 500 501 env := s.Prepare(c) 502 err = bootstrapEnv(c, env) 503 c.Assert(err, jc.ErrorIsNil) 504 inst, _ := testing.AssertStartInstance(c, env, s.callCtx, s.ControllerUUID, "100") 505 err = env.StopInstances(s.callCtx, inst.Id()) 506 c.Assert(err, jc.ErrorIsNil) 507 } 508 509 // If we fail to allocate a floating IP when starting an instance, the nova instance 510 // should be terminated. 511 func (s *localServerSuite) TestStartInstanceWhenPublicIPError(c *gc.C) { 512 var ( 513 addServerID string 514 removeServerID string 515 removeServerCalled bool 516 ) 517 518 cleanup := s.srv.Neutron.RegisterControlPoint( 519 "addFloatingIP", 520 func(sc hook.ServiceControl, args ...interface{}) error { 521 return fmt.Errorf("fail on purpose") 522 }, 523 ) 524 defer cleanup() 525 cleanup = s.srv.Nova.RegisterControlPoint( 526 "addServer", 527 func(sc hook.ServiceControl, args ...interface{}) error { 528 addServerID = args[0].(*nova.ServerDetail).Id 529 return nil 530 }, 531 ) 532 defer cleanup() 533 cleanup = s.srv.Nova.RegisterControlPoint( 534 "removeServer", 535 func(sc hook.ServiceControl, args ...interface{}) error { 536 removeServerCalled = true 537 removeServerID = args[0].(string) 538 return nil 539 }, 540 ) 541 defer cleanup() 542 _, _, _, err := testing.StartInstanceWithConstraints(s.env, s.callCtx, s.ControllerUUID, "100", constraints.MustParse("allocate-public-ip=true")) 543 c.Assert(err, gc.ErrorMatches, "(.|\n)*cannot allocate a public IP as needed(.|\n)*") 544 c.Assert(removeServerCalled, jc.IsTrue) 545 c.Assert(removeServerID, gc.Equals, addServerID) 546 } 547 548 func (s *localServerSuite) TestStartInstanceHardwareCharacteristics(c *gc.C) { 549 // Ensure amd64 tools are available, to ensure an amd64 image. 550 env := s.ensureAMDImages(c) 551 err := bootstrapEnv(c, env) 552 c.Assert(err, jc.ErrorIsNil) 553 _, hc := testing.AssertStartInstanceWithConstraints(c, env, s.callCtx, s.ControllerUUID, "100", constraints.MustParse("mem=1024 arch=amd64")) 554 c.Check(*hc.Arch, gc.Equals, "amd64") 555 c.Check(*hc.Mem, gc.Equals, uint64(2048)) 556 c.Check(*hc.CpuCores, gc.Equals, uint64(1)) 557 c.Assert(hc.CpuPower, gc.IsNil) 558 } 559 560 func (s *localServerSuite) TestInstanceName(c *gc.C) { 561 inst, _ := testing.AssertStartInstance(c, s.env, s.callCtx, s.ControllerUUID, "100") 562 serverDetail := openstack.InstanceServerDetail(inst) 563 envName := s.env.Config().Name() 564 c.Assert(serverDetail.Name, gc.Matches, "juju-06f00d-"+envName+"-100") 565 } 566 567 func (s *localServerSuite) TestStartInstanceNetwork(c *gc.C) { 568 cfg, err := s.env.Config().Apply(coretesting.Attrs{ 569 // A label that corresponds to a neutron test service network 570 "network": "net", 571 }) 572 c.Assert(err, jc.ErrorIsNil) 573 err = s.env.SetConfig(cfg) 574 c.Assert(err, jc.ErrorIsNil) 575 576 inst, _ := testing.AssertStartInstance(c, s.env, s.callCtx, s.ControllerUUID, "100") 577 err = s.env.StopInstances(s.callCtx, inst.Id()) 578 c.Assert(err, jc.ErrorIsNil) 579 } 580 581 func (s *localServerSuite) TestStartInstanceMultiNetworkFound(c *gc.C) { 582 cfg, err := s.env.Config().Apply(coretesting.Attrs{ 583 "network": "", 584 }) 585 c.Assert(err, jc.ErrorIsNil) 586 587 err = s.env.SetConfig(cfg) 588 c.Assert(err, jc.ErrorIsNil) 589 590 inst, _, _, err := testing.StartInstance(s.env, s.callCtx, s.ControllerUUID, "100") 591 c.Assert(err, jc.ErrorIsNil) 592 c.Check(inst, gc.NotNil) 593 } 594 595 func (s *localServerSuite) TestStartInstanceExternalNetwork(c *gc.C) { 596 cfg, err := s.env.Config().Apply(coretesting.Attrs{ 597 // A label that corresponds to a neutron test service external network 598 "external-network": "ext-net", 599 }) 600 c.Assert(err, jc.ErrorIsNil) 601 err = s.env.SetConfig(cfg) 602 c.Assert(err, jc.ErrorIsNil) 603 604 cons := constraints.MustParse("allocate-public-ip=true") 605 inst, _ := testing.AssertStartInstanceWithConstraints(c, s.env, s.callCtx, s.ControllerUUID, "100", cons) 606 err = s.env.StopInstances(s.callCtx, inst.Id()) 607 c.Assert(err, jc.ErrorIsNil) 608 } 609 610 func (s *localServerSuite) TestStartInstanceNetworkUnknownLabel(c *gc.C) { 611 cfg, err := s.env.Config().Apply(coretesting.Attrs{ 612 // A label that has no related network in the neutron test service 613 "network": "no-network-with-this-label", 614 }) 615 c.Assert(err, jc.ErrorIsNil) 616 err = s.env.SetConfig(cfg) 617 c.Assert(err, jc.ErrorIsNil) 618 619 inst, _, _, err := testing.StartInstance(s.env, s.callCtx, s.ControllerUUID, "100") 620 c.Check(inst, gc.IsNil) 621 c.Assert(err, gc.ErrorMatches, `unable to determine networks for configured list: \[no-network-with-this-label\]`) 622 } 623 624 func (s *localServerSuite) TestStartInstanceExternalNetworkUnknownLabel(c *gc.C) { 625 cfg, err := s.env.Config().Apply(coretesting.Attrs{ 626 // A label that has no related network in the neutron test service 627 "external-network": "no-network-with-this-label", 628 }) 629 c.Assert(err, jc.ErrorIsNil) 630 err = s.env.SetConfig(cfg) 631 c.Assert(err, jc.ErrorIsNil) 632 633 cons := constraints.MustParse("allocate-public-ip=true") 634 inst, _, _, err := testing.StartInstanceWithConstraints(s.env, s.callCtx, s.ControllerUUID, "100", cons) 635 c.Assert(err, jc.ErrorIsNil) 636 err = s.env.StopInstances(s.callCtx, inst.Id()) 637 c.Assert(err, jc.ErrorIsNil) 638 } 639 640 func (s *localServerSuite) TestStartInstanceNetworkUnknownID(c *gc.C) { 641 cfg, err := s.env.Config().Apply(coretesting.Attrs{ 642 // A valid UUID but no related network in the nova test service 643 "network": "f81d4fae-7dec-11d0-a765-00a0c91e6bf6", 644 }) 645 c.Assert(err, jc.ErrorIsNil) 646 err = s.env.SetConfig(cfg) 647 c.Assert(err, jc.ErrorIsNil) 648 649 inst, _, _, err := testing.StartInstance(s.env, s.callCtx, s.ControllerUUID, "100") 650 c.Check(inst, gc.IsNil) 651 c.Assert(err, gc.ErrorMatches, 652 `unable to determine networks for configured list: \[f81d4fae-7dec-11d0-a765-00a0c91e6bf6\]`) 653 } 654 655 func (s *localServerSuite) TestStartInstanceNoNetworksNetworkNotSetNoError(c *gc.C) { 656 // Modify the Openstack service that is created by default, 657 // to clear the networks. 658 model := neutronmodel.New() 659 for _, net := range model.AllNetworks() { 660 _ = model.RemoveNetwork(net.Id) 661 } 662 s.srv.OpenstackSvc.Neutron.AddNeutronModel(model) 663 s.srv.OpenstackSvc.Nova.AddNeutronModel(model) 664 665 cfg, err := s.env.Config().Apply(coretesting.Attrs{ 666 "network": "", 667 }) 668 c.Assert(err, jc.ErrorIsNil) 669 err = s.env.SetConfig(cfg) 670 c.Assert(err, jc.ErrorIsNil) 671 672 inst, _, _, err := testing.StartInstance(s.env, s.callCtx, s.ControllerUUID, "100") 673 c.Check(inst, gc.NotNil) 674 c.Assert(err, jc.ErrorIsNil) 675 } 676 677 func (s *localServerSuite) TestStartInstanceOneNetworkNetworkNotSetNoError(c *gc.C) { 678 // Modify the Openstack service that is created by default, 679 // to remove all but 1 internal network. 680 model := neutronmodel.New() 681 var foundOne bool 682 for _, net := range model.AllNetworks() { 683 if !net.External { 684 if !foundOne { 685 foundOne = true 686 continue 687 } 688 err := model.RemoveNetwork(net.Id) 689 c.Assert(err, jc.ErrorIsNil) 690 } 691 } 692 s.srv.OpenstackSvc.Neutron.AddNeutronModel(model) 693 s.srv.OpenstackSvc.Nova.AddNeutronModel(model) 694 695 cfg, err := s.env.Config().Apply(coretesting.Attrs{ 696 "network": "", 697 }) 698 c.Assert(err, jc.ErrorIsNil) 699 err = s.env.SetConfig(cfg) 700 c.Assert(err, jc.ErrorIsNil) 701 702 inst, _, _, err := testing.StartInstance(s.env, s.callCtx, s.ControllerUUID, "100") 703 c.Check(inst, gc.NotNil) 704 c.Assert(err, jc.ErrorIsNil) 705 } 706 707 func (s *localServerSuite) TestStartInstanceNetworksDifferentAZ(c *gc.C) { 708 // If both the network and external-network config values are 709 // specified, there is not check for them being on different 710 // network availability zones with allocate-public-ip constraint. 711 cfg, err := s.env.Config().Apply(coretesting.Attrs{ 712 "network": "net", // az = nova 713 "external-network": "ext-net", // az = test-available 714 }) 715 c.Assert(err, jc.ErrorIsNil) 716 err = s.env.SetConfig(cfg) 717 c.Assert(err, jc.ErrorIsNil) 718 719 cons := constraints.MustParse("allocate-public-ip=true") 720 inst, _, _, err := testing.StartInstanceWithConstraints(s.env, s.callCtx, s.ControllerUUID, "100", cons) 721 c.Assert(err, jc.ErrorIsNil) 722 err = s.env.StopInstances(s.callCtx, inst.Id()) 723 c.Assert(err, jc.ErrorIsNil) 724 } 725 726 func (s *localServerSuite) TestStartInstanceNetworksEmptyAZ(c *gc.C) { 727 // Modify the Openstack service that is created by default, 728 // to clear the networks. 729 model := neutronmodel.New() 730 for _, net := range model.AllNetworks() { 731 _ = model.RemoveNetwork(net.Id) 732 } 733 734 // Add 2 networks to the Openstack service, one private, 735 // one external without availability zones. LP: 1891227. 736 err := model.AddNetwork(neutron.NetworkV2{ 737 Id: "1", 738 Name: "no-az-net", 739 SubnetIds: []string{"sub-net"}, 740 External: false, 741 }) 742 c.Assert(err, jc.ErrorIsNil) 743 err = model.AddNetwork(neutron.NetworkV2{ 744 Id: "2", 745 Name: "ext-no-az-net", 746 SubnetIds: []string{"ext-sub-net"}, 747 External: true, 748 }) 749 c.Assert(err, jc.ErrorIsNil) 750 s.srv.OpenstackSvc.Neutron.AddNeutronModel(model) 751 s.srv.OpenstackSvc.Nova.AddNeutronModel(model) 752 753 // Set floating ip to ensure we try to find the external 754 // network. 755 cfg, err := s.env.Config().Apply(coretesting.Attrs{ 756 "network": "no-az-net", // az = nova 757 }) 758 c.Assert(err, jc.ErrorIsNil) 759 err = s.env.SetConfig(cfg) 760 c.Assert(err, jc.ErrorIsNil) 761 762 cons := constraints.MustParse("allocate-public-ip=true") 763 inst, _, _, err := testing.StartInstanceWithConstraints(s.env, s.callCtx, s.ControllerUUID, "100", cons) 764 c.Assert(err, jc.ErrorIsNil) 765 err = s.env.StopInstances(s.callCtx, inst.Id()) 766 c.Assert(err, jc.ErrorIsNil) 767 } 768 769 func (s *localServerSuite) TestStartInstanceNetworkNoExternalNetInAZ(c *gc.C) { 770 cfg, err := s.env.Config().Apply(coretesting.Attrs{ 771 "network": "net", // az = nova 772 }) 773 c.Assert(err, jc.ErrorIsNil) 774 err = s.env.SetConfig(cfg) 775 c.Assert(err, jc.ErrorIsNil) 776 777 cons := constraints.MustParse("allocate-public-ip=true") 778 _, _, _, err = testing.StartInstanceWithConstraints(s.env, s.callCtx, s.ControllerUUID, "100", cons) 779 c.Assert(err, gc.ErrorMatches, "cannot allocate a public IP as needed: could not find an external network in availability zone.*") 780 } 781 782 func (s *localServerSuite) TestStartInstancePortSecurityEnabled(c *gc.C) { 783 cfg, err := s.env.Config().Apply(coretesting.Attrs{ 784 "network": "net", 785 }) 786 c.Assert(err, jc.ErrorIsNil) 787 err = s.env.SetConfig(cfg) 788 c.Assert(err, jc.ErrorIsNil) 789 790 inst, _, _, err := testing.StartInstance(s.env, s.callCtx, s.ControllerUUID, "100") 791 c.Assert(err, jc.ErrorIsNil) 792 novaClient := openstack.GetNovaClient(s.env) 793 detail, err := novaClient.GetServer(string(inst.Id())) 794 c.Assert(err, jc.ErrorIsNil) 795 c.Assert(detail.Groups, gc.NotNil) 796 } 797 798 func (s *localServerSuite) TestStartInstancePortSecurityDisabled(c *gc.C) { 799 cfg, err := s.env.Config().Apply(coretesting.Attrs{ 800 "network": "net-disabled", 801 }) 802 c.Assert(err, jc.ErrorIsNil) 803 err = s.env.SetConfig(cfg) 804 c.Assert(err, jc.ErrorIsNil) 805 806 inst, _, _, err := testing.StartInstance(s.env, s.callCtx, s.ControllerUUID, "100") 807 c.Assert(err, jc.ErrorIsNil) 808 novaClient := openstack.GetNovaClient(s.env) 809 detail, err := novaClient.GetServer(string(inst.Id())) 810 c.Assert(err, jc.ErrorIsNil) 811 c.Assert(detail.Groups, gc.IsNil) 812 } 813 814 func (s *localServerSuite) TestStartInstanceGetServerFail(c *gc.C) { 815 // Force an error in waitForActiveServerDetails 816 cleanup := s.srv.Nova.RegisterControlPoint( 817 "server", 818 func(sc hook.ServiceControl, args ...interface{}) error { 819 return fmt.Errorf("GetServer failed on purpose") 820 }, 821 ) 822 defer cleanup() 823 inst, _, _, err := testing.StartInstance(s.env, s.callCtx, s.ControllerUUID, "100") 824 c.Check(inst, gc.IsNil) 825 c.Assert(err, gc.ErrorMatches, "cannot run instance: "+ 826 "request \\(.*/servers\\) returned unexpected status: "+ 827 "500; error info: .*GetServer failed on purpose") 828 c.Assert(errors.Is(err, environs.ErrAvailabilityZoneIndependent), jc.IsTrue) 829 } 830 831 func (s *localServerSuite) TestStartInstanceWaitForActiveDetails(c *gc.C) { 832 env := s.openEnviron(c, coretesting.Attrs{"firewall-mode": config.FwInstance}) 833 834 s.srv.Nova.SetServerStatus(nova.StatusBuild) 835 defer s.srv.Nova.SetServerStatus("") 836 837 // Make time advance in zero time 838 clk := testclock.NewClock(time.Time{}) 839 clock := testclock.AutoAdvancingClock{Clock: clk, Advance: clk.Advance} 840 env.(*openstack.Environ).SetClock(&clock) 841 842 inst, _, _, err := testing.StartInstance(env, s.callCtx, s.ControllerUUID, "100") 843 c.Check(inst, gc.IsNil) 844 c.Assert(err, gc.ErrorMatches, "cannot run instance: max duration exceeded: instance .* has status BUILD") 845 846 // Ensure that the started instance got terminated. 847 insts, err := env.AllInstances(s.callCtx) 848 c.Assert(err, jc.ErrorIsNil) 849 c.Assert(insts, gc.HasLen, 0, gc.Commentf("expected launched instance to be terminated if stuck in BUILD state")) 850 } 851 852 func (s *localServerSuite) TestStartInstanceDeletesSecurityGroupsOnInstanceCreateFailure(c *gc.C) { 853 env := s.openEnviron(c, coretesting.Attrs{"firewall-mode": config.FwInstance}) 854 855 // Force an error in waitForActiveServerDetails 856 cleanup := s.srv.Nova.RegisterControlPoint( 857 "server", 858 func(sc hook.ServiceControl, args ...interface{}) error { 859 return fmt.Errorf("GetServer failed on purpose") 860 }, 861 ) 862 defer cleanup() 863 inst, _, _, err := testing.StartInstance(env, s.callCtx, s.ControllerUUID, "100") 864 c.Check(inst, gc.IsNil) 865 c.Assert(err, gc.NotNil) 866 867 assertSecurityGroups(c, env, []string{"default"}) 868 } 869 870 func (s *localServerSuite) TestStartInstanceDeletesSecurityGroupsOnFailure(c *gc.C) { 871 env := s.openEnviron(c, coretesting.Attrs{"firewall-mode": config.FwInstance}) 872 873 s.srv.Nova.SetServerStatus(nova.StatusBuild) 874 defer s.srv.Nova.SetServerStatus("") 875 876 // Make time advance in zero time 877 clk := testclock.NewClock(time.Time{}) 878 clock := testclock.AutoAdvancingClock{Clock: clk, Advance: clk.Advance} 879 env.(*openstack.Environ).SetClock(&clock) 880 881 _, _, _, err := testing.StartInstance(env, s.callCtx, s.ControllerUUID, "100") 882 c.Assert(err, gc.NotNil) 883 884 assertSecurityGroups(c, env, []string{"default"}) 885 } 886 887 func assertSecurityGroups(c *gc.C, env environs.Environ, expected []string) { 888 neutronClient := openstack.GetNeutronClient(env) 889 groups, err := neutronClient.ListSecurityGroupsV2() 890 c.Assert(err, jc.ErrorIsNil) 891 for _, name := range expected { 892 found := false 893 for _, group := range groups { 894 if group.Name == name { 895 found = true 896 break 897 } 898 } 899 if !found { 900 c.Errorf("expected security group %q not found", name) 901 } 902 } 903 for _, group := range groups { 904 found := false 905 for _, name := range expected { 906 if group.Name == name { 907 found = true 908 break 909 } 910 } 911 if !found { 912 c.Errorf("existing security group %q is not expected", group.Name) 913 } 914 } 915 } 916 917 type portAssertion struct { 918 SubnetIDs []string 919 NamePrefix string 920 } 921 922 func assertPorts(c *gc.C, env environs.Environ, expected []portAssertion) { 923 neutronClient := openstack.GetNeutronClient(env) 924 ports, err := neutronClient.ListPortsV2() 925 c.Assert(err, jc.ErrorIsNil) 926 c.Assert(ports, gc.HasLen, len(expected)) 927 for k, port := range ports { 928 c.Assert(port.Name, jc.HasPrefix, expected[k].NamePrefix) 929 c.Assert(port.FixedIPs, gc.HasLen, len(expected[k].SubnetIDs)) 930 for i, ip := range port.FixedIPs { 931 c.Assert(ip.SubnetID, gc.Equals, expected[k].SubnetIDs[i]) 932 } 933 } 934 } 935 936 func assertInstanceIds(c *gc.C, env environs.Environ, callCtx context.ProviderCallContext, expected ...instance.Id) { 937 allInstances, err := env.AllRunningInstances(callCtx) 938 c.Assert(err, jc.ErrorIsNil) 939 instIds := make([]instance.Id, len(allInstances)) 940 for i, inst := range allInstances { 941 instIds[i] = inst.Id() 942 } 943 c.Assert(instIds, jc.SameContents, expected) 944 } 945 946 func (s *localServerSuite) TestStopInstance(c *gc.C) { 947 env := s.openEnviron(c, coretesting.Attrs{"firewall-mode": config.FwInstance}) 948 instanceName := "100" 949 inst, _ := testing.AssertStartInstance(c, env, s.callCtx, s.ControllerUUID, instanceName) 950 // Openstack now has three security groups for the server, the default 951 // group, one group for the entire environment, and another for the 952 // new instance. 953 modelUUID := env.Config().UUID() 954 allSecurityGroups := []string{ 955 "default", fmt.Sprintf("juju-%v-%v", s.ControllerUUID, modelUUID), 956 fmt.Sprintf("juju-%v-%v-%v", s.ControllerUUID, modelUUID, instanceName), 957 } 958 assertSecurityGroups(c, env, allSecurityGroups) 959 err := env.StopInstances(s.callCtx, inst.Id()) 960 c.Assert(err, jc.ErrorIsNil) 961 // The security group for this instance is now removed. 962 assertSecurityGroups(c, env, []string{ 963 "default", fmt.Sprintf("juju-%v-%v", s.ControllerUUID, modelUUID), 964 }) 965 } 966 967 // Due to bug #1300755 it can happen that the security group intended for 968 // an instance is also used as the common security group of another 969 // environment. If this is the case, the attempt to delete the instance's 970 // security group fails but StopInstance succeeds. 971 func (s *localServerSuite) TestStopInstanceSecurityGroupNotDeleted(c *gc.C) { 972 coretesting.SkipIfPPC64EL(c, "lp:1425242") 973 974 // Force an error when a security group is deleted. 975 cleanup := s.srv.Neutron.RegisterControlPoint( 976 "removeSecurityGroup", 977 func(sc hook.ServiceControl, args ...interface{}) error { 978 return fmt.Errorf("failed on purpose") 979 }, 980 ) 981 defer cleanup() 982 env := s.openEnviron(c, coretesting.Attrs{"firewall-mode": config.FwInstance}) 983 instanceName := "100" 984 inst, _ := testing.AssertStartInstance(c, env, s.callCtx, s.ControllerUUID, instanceName) 985 modelUUID := env.Config().UUID() 986 allSecurityGroups := []string{ 987 "default", fmt.Sprintf("juju-%v-%v", s.ControllerUUID, modelUUID), 988 fmt.Sprintf("juju-%v-%v-%v", s.ControllerUUID, modelUUID, instanceName), 989 } 990 assertSecurityGroups(c, env, allSecurityGroups) 991 992 // Make time advance in zero time 993 clk := testclock.NewClock(time.Time{}) 994 clock := testclock.AutoAdvancingClock{Clock: clk, Advance: clk.Advance} 995 env.(*openstack.Environ).SetClock(&clock) 996 997 err := env.StopInstances(s.callCtx, inst.Id()) 998 c.Assert(err, jc.ErrorIsNil) 999 assertSecurityGroups(c, env, allSecurityGroups) 1000 } 1001 1002 func (s *localServerSuite) TestDestroyEnvironmentDeletesSecurityGroupsFWModeInstance(c *gc.C) { 1003 env := s.openEnviron(c, coretesting.Attrs{"firewall-mode": config.FwInstance}) 1004 instanceName := "100" 1005 testing.AssertStartInstance(c, env, s.callCtx, s.ControllerUUID, instanceName) 1006 modelUUID := env.Config().UUID() 1007 allSecurityGroups := []string{ 1008 "default", fmt.Sprintf("juju-%v-%v", s.ControllerUUID, modelUUID), 1009 fmt.Sprintf("juju-%v-%v-%v", s.ControllerUUID, modelUUID, instanceName), 1010 } 1011 assertSecurityGroups(c, env, allSecurityGroups) 1012 err := env.Destroy(s.callCtx) 1013 c.Check(err, jc.ErrorIsNil) 1014 assertSecurityGroups(c, env, []string{"default"}) 1015 } 1016 1017 func (s *localServerSuite) TestDestroyEnvironmentDeletesSecurityGroupsFWModeGlobal(c *gc.C) { 1018 env := s.openEnviron(c, coretesting.Attrs{"firewall-mode": config.FwGlobal}) 1019 instanceName := "100" 1020 testing.AssertStartInstance(c, env, s.callCtx, s.ControllerUUID, instanceName) 1021 modelUUID := env.Config().UUID() 1022 allSecurityGroups := []string{ 1023 "default", fmt.Sprintf("juju-%v-%v", s.ControllerUUID, modelUUID), 1024 fmt.Sprintf("juju-%v-%v-global", s.ControllerUUID, modelUUID), 1025 } 1026 assertSecurityGroups(c, env, allSecurityGroups) 1027 err := env.Destroy(s.callCtx) 1028 c.Check(err, jc.ErrorIsNil) 1029 assertSecurityGroups(c, env, []string{"default"}) 1030 } 1031 1032 func (s *localServerSuite) TestDestroyController(c *gc.C) { 1033 env := s.openEnviron(c, coretesting.Attrs{"uuid": utils.MustNewUUID().String()}) 1034 controllerEnv := s.env 1035 1036 controllerInstanceName := "100" 1037 testing.AssertStartInstance(c, controllerEnv, s.callCtx, s.ControllerUUID, controllerInstanceName) 1038 hostedModelInstanceName := "200" 1039 testing.AssertStartInstance(c, env, s.callCtx, s.ControllerUUID, hostedModelInstanceName) 1040 modelUUID := env.Config().UUID() 1041 allControllerSecurityGroups := []string{ 1042 "default", fmt.Sprintf("juju-%v-%v", s.ControllerUUID, controllerEnv.Config().UUID()), 1043 fmt.Sprintf("juju-%v-%v-%v", s.ControllerUUID, controllerEnv.Config().UUID(), controllerInstanceName), 1044 } 1045 allHostedModelSecurityGroups := []string{ 1046 "default", fmt.Sprintf("juju-%v-%v", s.ControllerUUID, modelUUID), 1047 fmt.Sprintf("juju-%v-%v-%v", s.ControllerUUID, modelUUID, hostedModelInstanceName), 1048 } 1049 assertSecurityGroups(c, controllerEnv, append( 1050 allControllerSecurityGroups, allHostedModelSecurityGroups..., 1051 )) 1052 1053 err := controllerEnv.DestroyController(s.callCtx, s.ControllerUUID) 1054 c.Check(err, jc.ErrorIsNil) 1055 assertSecurityGroups(c, controllerEnv, []string{"default"}) 1056 assertInstanceIds(c, env, s.callCtx) 1057 assertInstanceIds(c, controllerEnv, s.callCtx) 1058 } 1059 1060 func (s *localServerSuite) TestDestroyHostedModel(c *gc.C) { 1061 env := s.openEnviron(c, coretesting.Attrs{"uuid": utils.MustNewUUID().String()}) 1062 controllerEnv := s.env 1063 1064 controllerInstanceName := "100" 1065 controllerInstance, _ := testing.AssertStartInstance(c, controllerEnv, s.callCtx, s.ControllerUUID, controllerInstanceName) 1066 hostedModelInstanceName := "200" 1067 testing.AssertStartInstance(c, env, s.callCtx, s.ControllerUUID, hostedModelInstanceName) 1068 modelUUID := env.Config().UUID() 1069 allControllerSecurityGroups := []string{ 1070 "default", fmt.Sprintf("juju-%v-%v", s.ControllerUUID, controllerEnv.Config().UUID()), 1071 fmt.Sprintf("juju-%v-%v-%v", s.ControllerUUID, controllerEnv.Config().UUID(), controllerInstanceName), 1072 } 1073 allHostedModelSecurityGroups := []string{ 1074 "default", fmt.Sprintf("juju-%v-%v", s.ControllerUUID, modelUUID), 1075 fmt.Sprintf("juju-%v-%v-%v", s.ControllerUUID, modelUUID, hostedModelInstanceName), 1076 } 1077 assertSecurityGroups(c, controllerEnv, append( 1078 allControllerSecurityGroups, allHostedModelSecurityGroups..., 1079 )) 1080 1081 err := env.Destroy(s.callCtx) 1082 c.Check(err, jc.ErrorIsNil) 1083 assertSecurityGroups(c, controllerEnv, allControllerSecurityGroups) 1084 assertInstanceIds(c, env, s.callCtx) 1085 assertInstanceIds(c, controllerEnv, s.callCtx, controllerInstance.Id()) 1086 } 1087 1088 func (s *localServerSuite) TestDestroyControllerSpaceConstraints(c *gc.C) { 1089 uuid := utils.MustNewUUID().String() 1090 env := s.openEnviron(c, coretesting.Attrs{"uuid": uuid}) 1091 controllerEnv := s.env 1092 1093 s.srv.Nova.SetAvailabilityZones( 1094 nova.AvailabilityZone{ 1095 Name: "zone-0", 1096 State: nova.AvailabilityZoneState{ 1097 Available: true, 1098 }, 1099 }, 1100 ) 1101 1102 controllerInstanceName := "100" 1103 params := environs.StartInstanceParams{ 1104 ControllerUUID: s.ControllerUUID, 1105 AvailabilityZone: "zone-0", 1106 Constraints: constraints.MustParse("spaces=space-1 zones=zone-0"), 1107 SubnetsToZones: []map[corenetwork.Id][]string{ 1108 { 1109 "999-01": {"zone-0"}, 1110 }, 1111 }, 1112 } 1113 _, err := testing.StartInstanceWithParams(env, s.callCtx, controllerInstanceName, params) 1114 c.Assert(err, jc.ErrorIsNil) 1115 assertPorts(c, env, []portAssertion{ 1116 {NamePrefix: fmt.Sprintf("juju-%s-", uuid), SubnetIDs: []string{"999-01"}}, 1117 }) 1118 1119 // The openstack runtime would assign a device_id to a port when it's 1120 // assigned to an instance. To ensure that all ports are correctly removed 1121 // when destroying and so we can exercise all the code paths we have to 1122 // replicate that piece of code. 1123 // When moving to mocking of providers, this shouldn't be need or required. 1124 s.assignDeviceIdToPort(c, "1", "1") 1125 1126 err = controllerEnv.DestroyController(s.callCtx, s.ControllerUUID) 1127 c.Check(err, jc.ErrorIsNil) 1128 assertSecurityGroups(c, controllerEnv, []string{"default"}) 1129 assertInstanceIds(c, env, s.callCtx) 1130 assertInstanceIds(c, controllerEnv, s.callCtx) 1131 assertPorts(c, env, []portAssertion{}) 1132 } 1133 1134 func (s *localServerSuite) assignDeviceIdToPort(c *gc.C, portId, deviceId string) { 1135 err := s.srv.Nova.AddOSInterface(deviceId, nova.OSInterface{ 1136 FixedIPs: []nova.PortFixedIP{ 1137 { 1138 IPAddress: "10.0.0.1", 1139 }, 1140 }, 1141 IPAddress: "10.0.0.1", 1142 }) 1143 c.Assert(err, jc.ErrorIsNil) 1144 1145 model := s.srv.Neutron.NeutronModel() 1146 port, err := model.Port("1") 1147 c.Assert(err, jc.ErrorIsNil) 1148 err = model.RemovePort("1") 1149 c.Assert(err, jc.ErrorIsNil) 1150 port.DeviceId = "1" 1151 err = model.AddPort(*port) 1152 c.Assert(err, jc.ErrorIsNil) 1153 } 1154 1155 var instanceGathering = []struct { 1156 ids []instance.Id 1157 err error 1158 }{ 1159 {ids: []instance.Id{"id0"}}, 1160 {ids: []instance.Id{"id0", "id0"}}, 1161 {ids: []instance.Id{"id0", "id1"}}, 1162 {ids: []instance.Id{"id1", "id0"}}, 1163 {ids: []instance.Id{"id1", "id0", "id1"}}, 1164 { 1165 ids: []instance.Id{""}, 1166 err: environs.ErrNoInstances, 1167 }, 1168 { 1169 ids: []instance.Id{"", ""}, 1170 err: environs.ErrNoInstances, 1171 }, 1172 { 1173 ids: []instance.Id{"", "", ""}, 1174 err: environs.ErrNoInstances, 1175 }, 1176 { 1177 ids: []instance.Id{"id0", ""}, 1178 err: environs.ErrPartialInstances, 1179 }, 1180 { 1181 ids: []instance.Id{"", "id1"}, 1182 err: environs.ErrPartialInstances, 1183 }, 1184 { 1185 ids: []instance.Id{"id0", "id1", ""}, 1186 err: environs.ErrPartialInstances, 1187 }, 1188 { 1189 ids: []instance.Id{"id0", "", "id0"}, 1190 err: environs.ErrPartialInstances, 1191 }, 1192 { 1193 ids: []instance.Id{"id0", "id0", ""}, 1194 err: environs.ErrPartialInstances, 1195 }, 1196 { 1197 ids: []instance.Id{"", "id0", "id1"}, 1198 err: environs.ErrPartialInstances, 1199 }, 1200 } 1201 1202 func (s *localServerSuite) TestInstanceStatus(c *gc.C) { 1203 // goose's test service always returns ACTIVE state. 1204 inst, _ := testing.AssertStartInstance(c, s.env, s.callCtx, s.ControllerUUID, "100") 1205 c.Assert(inst.Status(s.callCtx).Status, gc.Equals, status.Running) 1206 err := s.env.StopInstances(s.callCtx, inst.Id()) 1207 c.Assert(err, jc.ErrorIsNil) 1208 } 1209 1210 func (s *localServerSuite) TestAllRunningInstancesFloatingIP(c *gc.C) { 1211 env := s.openEnviron(c, coretesting.Attrs{ 1212 "network": "private_999", 1213 }) 1214 cons := constraints.MustParse("allocate-public-ip=true") 1215 inst0, _ := testing.AssertStartInstanceWithConstraints(c, env, s.callCtx, s.ControllerUUID, "100", cons) 1216 inst1, _ := testing.AssertStartInstanceWithConstraints(c, env, s.callCtx, s.ControllerUUID, "101", cons) 1217 defer func() { 1218 err := env.StopInstances(s.callCtx, inst0.Id(), inst1.Id()) 1219 c.Assert(err, jc.ErrorIsNil) 1220 }() 1221 1222 allInstances, err := env.AllRunningInstances(s.callCtx) 1223 c.Assert(err, jc.ErrorIsNil) 1224 for _, inst := range allInstances { 1225 c.Assert(*openstack.InstanceFloatingIP(inst), gc.Equals, fmt.Sprintf("10.0.0.%v", inst.Id())) 1226 } 1227 } 1228 1229 func (s *localServerSuite) assertInstancesGathering(c *gc.C, withFloatingIP bool) { 1230 env := s.openEnviron(c, coretesting.Attrs{}) 1231 1232 var cons constraints.Value 1233 if withFloatingIP { 1234 cons = constraints.MustParse("allocate-public-ip=true") 1235 } 1236 inst0, _ := testing.AssertStartInstanceWithConstraints(c, env, s.callCtx, s.ControllerUUID, "100", cons) 1237 id0 := inst0.Id() 1238 inst1, _ := testing.AssertStartInstanceWithConstraints(c, env, s.callCtx, s.ControllerUUID, "101", cons) 1239 id1 := inst1.Id() 1240 defer func() { 1241 err := env.StopInstances(s.callCtx, inst0.Id(), inst1.Id()) 1242 c.Assert(err, jc.ErrorIsNil) 1243 }() 1244 1245 for i, test := range instanceGathering { 1246 c.Logf("test %d: find %v -> expect len %d, err: %v", i, test.ids, len(test.ids), test.err) 1247 ids := make([]instance.Id, len(test.ids)) 1248 for j, id := range test.ids { 1249 switch id { 1250 case "id0": 1251 ids[j] = id0 1252 case "id1": 1253 ids[j] = id1 1254 } 1255 } 1256 insts, err := env.Instances(s.callCtx, ids) 1257 c.Assert(err, gc.Equals, test.err) 1258 if err == environs.ErrNoInstances { 1259 c.Assert(insts, gc.HasLen, 0) 1260 } else { 1261 c.Assert(insts, gc.HasLen, len(test.ids)) 1262 } 1263 for j, inst := range insts { 1264 if ids[j] != "" { 1265 c.Assert(inst.Id(), gc.Equals, ids[j]) 1266 if withFloatingIP { 1267 c.Assert(*openstack.InstanceFloatingIP(inst), gc.Equals, fmt.Sprintf("10.0.0.%v", inst.Id())) 1268 } else { 1269 c.Assert(openstack.InstanceFloatingIP(inst), gc.IsNil) 1270 } 1271 } else { 1272 c.Assert(inst, gc.IsNil) 1273 } 1274 } 1275 } 1276 } 1277 1278 func (s *localServerSuite) TestInstancesGathering(c *gc.C) { 1279 s.assertInstancesGathering(c, false) 1280 } 1281 1282 func (s *localServerSuite) TestInstancesGatheringWithFloatingIP(c *gc.C) { 1283 s.assertInstancesGathering(c, true) 1284 } 1285 1286 func (s *localServerSuite) TestInstancesShutoffSuspended(c *gc.C) { 1287 coretesting.SkipIfPPC64EL(c, "lp:1425242") 1288 1289 cleanup := s.srv.Nova.RegisterControlPoint( 1290 "addServer", 1291 func(sc hook.ServiceControl, args ...interface{}) error { 1292 details := args[0].(*nova.ServerDetail) 1293 switch { 1294 case strings.HasSuffix(details.Name, "-100"): 1295 details.Status = nova.StatusShutoff 1296 case strings.HasSuffix(details.Name, "-101"): 1297 details.Status = nova.StatusSuspended 1298 default: 1299 c.Fatalf("unexpected instance details: %#v", details) 1300 } 1301 return nil 1302 }, 1303 ) 1304 defer cleanup() 1305 stateInst1, _ := testing.AssertStartInstance(c, s.env, s.callCtx, s.ControllerUUID, "100") 1306 stateInst2, _ := testing.AssertStartInstance(c, s.env, s.callCtx, s.ControllerUUID, "101") 1307 defer func() { 1308 err := s.env.StopInstances(s.callCtx, stateInst1.Id(), stateInst2.Id()) 1309 c.Assert(err, jc.ErrorIsNil) 1310 }() 1311 1312 twoInstances, err := s.env.Instances(s.callCtx, []instance.Id{stateInst1.Id(), stateInst2.Id()}) 1313 1314 c.Assert(err, jc.ErrorIsNil) 1315 c.Assert(twoInstances, gc.HasLen, 2) 1316 c.Assert(twoInstances[0].Status(s.callCtx).Message, gc.Equals, nova.StatusShutoff) 1317 c.Assert(twoInstances[1].Status(s.callCtx).Message, gc.Equals, nova.StatusSuspended) 1318 } 1319 1320 func (s *localServerSuite) TestInstancesErrorResponse(c *gc.C) { 1321 coretesting.SkipIfPPC64EL(c, "lp:1425242") 1322 1323 cleanup := s.srv.Nova.RegisterControlPoint( 1324 "server", 1325 func(sc hook.ServiceControl, args ...interface{}) error { 1326 return fmt.Errorf("strange error not instance") 1327 }, 1328 ) 1329 defer cleanup() 1330 1331 oneInstance, err := s.env.Instances(s.callCtx, []instance.Id{"1"}) 1332 c.Check(oneInstance, gc.IsNil) 1333 c.Assert(err, gc.ErrorMatches, "(?s).*strange error not instance.*") 1334 } 1335 1336 func (s *localServerSuite) TestInstancesMultiErrorResponse(c *gc.C) { 1337 coretesting.SkipIfPPC64EL(c, "lp:1425242") 1338 1339 cleanup := s.srv.Nova.RegisterControlPoint( 1340 "matchServers", 1341 func(sc hook.ServiceControl, args ...interface{}) error { 1342 return fmt.Errorf("strange error no instances") 1343 }, 1344 ) 1345 defer cleanup() 1346 1347 twoInstances, err := s.env.Instances(s.callCtx, []instance.Id{"1", "2"}) 1348 c.Check(twoInstances, gc.IsNil) 1349 c.Assert(err, gc.ErrorMatches, "(?s).*strange error no instances.*") 1350 } 1351 1352 // TODO (wallyworld) - this test was copied from the ec2 provider. 1353 // It should be moved to environs.jujutests.Tests. 1354 func (s *localServerSuite) TestBootstrapInstanceUserDataAndState(c *gc.C) { 1355 err := bootstrapEnv(c, s.env) 1356 c.Assert(err, jc.ErrorIsNil) 1357 1358 // Check that ControllerInstances returns the ID of the bootstrap machine. 1359 ids, err := s.env.ControllerInstances(s.callCtx, s.ControllerUUID) 1360 c.Assert(err, jc.ErrorIsNil) 1361 c.Assert(ids, gc.HasLen, 1) 1362 1363 allInstances, err := s.env.AllRunningInstances(s.callCtx) 1364 c.Assert(err, jc.ErrorIsNil) 1365 c.Assert(allInstances, gc.HasLen, 1) 1366 c.Check(allInstances[0].Id(), gc.Equals, ids[0]) 1367 1368 addresses, err := allInstances[0].Addresses(s.callCtx) 1369 c.Assert(err, jc.ErrorIsNil) 1370 c.Assert(addresses, gc.Not(gc.HasLen), 0) 1371 1372 // TODO(wallyworld) - 2013-03-01 bug=1137005 1373 // The nova test double needs to be updated to support retrieving instance userData. 1374 // Until then, we can't check the cloud init script was generated correctly. 1375 // When we can, we should also check cloudinit for a non-manager node (as in the 1376 // ec2 tests). 1377 } 1378 1379 func (s *localServerSuite) TestGetImageMetadataSources(c *gc.C) { 1380 ss := simplestreams.NewSimpleStreams(sstesting.TestDataSourceFactory()) 1381 // Create a config that matches s.TestConfig but with the specified stream. 1382 attrs := coretesting.Attrs{} 1383 env := s.openEnviron(c, attrs) 1384 1385 sources, err := environs.ImageMetadataSources(env, ss) 1386 c.Assert(err, jc.ErrorIsNil) 1387 c.Assert(sources, gc.HasLen, 3) 1388 var urls = make([]string, len(sources)) 1389 for i, source := range sources { 1390 imageURL, err := source.URL("") 1391 c.Assert(err, jc.ErrorIsNil) 1392 urls[i] = imageURL 1393 } 1394 // The image-metadata-url ends with "/juju-dist-test/". 1395 c.Check(strings.HasSuffix(urls[0], "/juju-dist-test/"), jc.IsTrue) 1396 // The product-streams URL ends with "/imagemetadata". 1397 c.Check(strings.HasSuffix(urls[1], "/imagemetadata/"), jc.IsTrue) 1398 c.Assert(urls[2], jc.HasPrefix, imagemetadata.DefaultUbuntuBaseURL) 1399 } 1400 1401 func (s *localServerSuite) TestGetImageMetadataSourcesNoProductStreams(c *gc.C) { 1402 ss := simplestreams.NewSimpleStreams(sstesting.TestDataSourceFactory()) 1403 s.PatchValue(openstack.MakeServiceURL, func(client.AuthenticatingClient, string, string, []string) (string, error) { 1404 return "", errors.New("cannae do it captain") 1405 }) 1406 env := s.Open(c, stdcontext.TODO(), s.env.Config()) 1407 sources, err := environs.ImageMetadataSources(env, ss) 1408 c.Assert(err, jc.ErrorIsNil) 1409 c.Assert(sources, gc.HasLen, 2) 1410 1411 // Check that data sources are in the right order 1412 c.Check(sources[0].Description(), gc.Equals, "image-metadata-url") 1413 c.Check(sources[1].Description(), gc.Equals, "default ubuntu cloud images") 1414 } 1415 1416 func (s *localServerSuite) TestGetToolsMetadataSources(c *gc.C) { 1417 ss := simplestreams.NewSimpleStreams(sstesting.TestDataSourceFactory()) 1418 s.PatchValue(&tools.DefaultBaseURL, "") 1419 1420 env := s.Open(c, stdcontext.TODO(), s.env.Config()) 1421 sources, err := tools.GetMetadataSources(env, ss) 1422 c.Assert(err, jc.ErrorIsNil) 1423 c.Assert(sources, gc.HasLen, 2) 1424 var urls = make([]string, len(sources)) 1425 for i, source := range sources { 1426 metadataURL, err := source.URL("") 1427 c.Assert(err, jc.ErrorIsNil) 1428 urls[i] = metadataURL 1429 } 1430 // The agent-metadata-url ends with "/juju-dist-test/tools/". 1431 c.Check(strings.HasSuffix(urls[0], "/juju-dist-test/tools/"), jc.IsTrue) 1432 // Check that the URL from keystone parses. 1433 _, err = url.Parse(urls[1]) 1434 c.Assert(err, jc.ErrorIsNil) 1435 } 1436 1437 func (s *localServerSuite) TestSupportsNetworking(c *gc.C) { 1438 env := s.Open(c, stdcontext.TODO(), s.env.Config()) 1439 _, ok := environs.SupportsNetworking(env) 1440 c.Assert(ok, jc.IsTrue) 1441 } 1442 1443 func (s *localServerSuite) prepareNetworkingEnviron(c *gc.C, cfg *config.Config) environs.NetworkingEnviron { 1444 env := s.Open(c, stdcontext.TODO(), cfg) 1445 netenv, supported := environs.SupportsNetworking(env) 1446 c.Assert(supported, jc.IsTrue) 1447 return netenv 1448 } 1449 1450 func (s *localServerSuite) TestSubnetsFindAll(c *gc.C) { 1451 env := s.prepareNetworkingEnviron(c, s.env.Config()) 1452 // the environ is opened with network:"private_999" which maps to network id "999" 1453 obtainedSubnets, err := env.Subnets(s.callCtx, "", []network.Id{}) 1454 c.Assert(err, jc.ErrorIsNil) 1455 neutronClient := openstack.GetNeutronClient(s.env) 1456 openstackSubnets, err := neutronClient.ListSubnetsV2() 1457 c.Assert(err, jc.ErrorIsNil) 1458 1459 obtainedSubnetMap := make(map[network.Id]network.SubnetInfo) 1460 for _, sub := range obtainedSubnets { 1461 obtainedSubnetMap[sub.ProviderId] = sub 1462 } 1463 1464 expectedSubnetMap := make(map[network.Id]network.SubnetInfo) 1465 for _, subnet := range openstackSubnets { 1466 if subnet.NetworkId != "999" { 1467 continue 1468 } 1469 net, err := neutronClient.GetNetworkV2(subnet.NetworkId) 1470 c.Assert(err, jc.ErrorIsNil) 1471 expectedSubnetMap[network.Id(subnet.Id)] = network.SubnetInfo{ 1472 CIDR: subnet.Cidr, 1473 ProviderId: network.Id(subnet.Id), 1474 ProviderNetworkId: network.Id(subnet.NetworkId), 1475 VLANTag: 0, 1476 AvailabilityZones: net.AvailabilityZones, 1477 ProviderSpaceId: "", 1478 } 1479 } 1480 1481 c.Check(obtainedSubnetMap, jc.DeepEquals, expectedSubnetMap) 1482 } 1483 1484 func (s *localServerSuite) TestSubnetsFindAllWithExternal(c *gc.C) { 1485 cfg := s.env.Config() 1486 cfg, err := cfg.Apply(map[string]interface{}{"external-network": "ext-net"}) 1487 c.Assert(err, jc.ErrorIsNil) 1488 env := s.prepareNetworkingEnviron(c, cfg) 1489 // private_999 is the internal network, 998 is the external network 1490 // the environ is opened with network:"private_999" which maps to network id "999" 1491 obtainedSubnets, err := env.Subnets(s.callCtx, "", []network.Id{}) 1492 c.Assert(err, jc.ErrorIsNil) 1493 neutronClient := openstack.GetNeutronClient(s.env) 1494 openstackSubnets, err := neutronClient.ListSubnetsV2() 1495 c.Assert(err, jc.ErrorIsNil) 1496 1497 obtainedSubnetMap := make(map[network.Id]network.SubnetInfo) 1498 for _, sub := range obtainedSubnets { 1499 obtainedSubnetMap[sub.ProviderId] = sub 1500 } 1501 1502 expectedSubnetMap := make(map[network.Id]network.SubnetInfo) 1503 for _, subnets := range openstackSubnets { 1504 if subnets.NetworkId != "999" && subnets.NetworkId != "998" { 1505 continue 1506 } 1507 net, err := neutronClient.GetNetworkV2(subnets.NetworkId) 1508 c.Assert(err, jc.ErrorIsNil) 1509 expectedSubnetMap[network.Id(subnets.Id)] = network.SubnetInfo{ 1510 CIDR: subnets.Cidr, 1511 ProviderId: network.Id(subnets.Id), 1512 ProviderNetworkId: network.Id(subnets.NetworkId), 1513 VLANTag: 0, 1514 AvailabilityZones: net.AvailabilityZones, 1515 ProviderSpaceId: "", 1516 } 1517 } 1518 1519 c.Check(obtainedSubnetMap, jc.DeepEquals, expectedSubnetMap) 1520 } 1521 1522 func (s *localServerSuite) TestFindNetworksInternal(c *gc.C) { 1523 s.testFindNetworks(c, true) 1524 } 1525 1526 func (s *localServerSuite) TestFindNetworksExternal(c *gc.C) { 1527 s.testFindNetworks(c, false) 1528 } 1529 1530 func (s *localServerSuite) testFindNetworks(c *gc.C, internal bool) { 1531 env := s.prepareNetworkingEnviron(c, s.env.Config()) 1532 obtainedNetworks, err := openstack.FindNetworks(env, internal) 1533 c.Assert(err, jc.ErrorIsNil) 1534 neutronClient := openstack.GetNeutronClient(s.env) 1535 openstackNetworks, err := neutronClient.ListNetworksV2() 1536 c.Assert(err, jc.ErrorIsNil) 1537 1538 expectedNetworks := set.NewStrings() 1539 for _, oNet := range openstackNetworks { 1540 if oNet.External == internal { 1541 continue 1542 } 1543 expectedNetworks.Add(oNet.Name) 1544 } 1545 1546 c.Check(obtainedNetworks.Values(), jc.SameContents, expectedNetworks.Values()) 1547 1548 } 1549 1550 func (s *localServerSuite) TestSubnetsWithMissingSubnet(c *gc.C) { 1551 env := s.prepareNetworkingEnviron(c, s.env.Config()) 1552 subnets, err := env.Subnets(s.callCtx, "", []network.Id{"missing"}) 1553 c.Assert(err, gc.ErrorMatches, `failed to find the following subnet ids: \[missing\]`) 1554 c.Assert(subnets, gc.HasLen, 0) 1555 } 1556 1557 func (s *localServerSuite) TestSuperSubnets(c *gc.C) { 1558 env := s.prepareNetworkingEnviron(c, s.env.Config()) 1559 obtainedSubnets, err := env.SuperSubnets(s.callCtx) 1560 c.Assert(err, jc.ErrorIsNil) 1561 neutronClient := openstack.GetNeutronClient(s.env) 1562 openstackSubnets, err := neutronClient.ListSubnetsV2() 1563 c.Assert(err, jc.ErrorIsNil) 1564 1565 expectedSubnets := make([]string, 0, len(openstackSubnets)) 1566 for _, subnets := range openstackSubnets { 1567 if subnets.NetworkId != "999" { 1568 continue 1569 } 1570 expectedSubnets = append(expectedSubnets, subnets.Cidr) 1571 } 1572 sort.Strings(obtainedSubnets) 1573 sort.Strings(expectedSubnets) 1574 c.Check(obtainedSubnets, jc.DeepEquals, expectedSubnets) 1575 } 1576 1577 func (s *localServerSuite) TestFindImageBadDefaultImage(c *gc.C) { 1578 imagetesting.PatchOfficialDataSources(&s.CleanupSuite, "") 1579 env := s.Open(c, stdcontext.TODO(), s.env.Config()) 1580 1581 // An error occurs if no suitable image is found. 1582 _, err := openstack.FindInstanceSpec(env, corebase.MakeDefaultBase("ubuntu", "15.04"), "amd64", "mem=1G", nil) 1583 c.Assert(err, gc.ErrorMatches, `no metadata for "ubuntu@15.04" images in some-region with arch amd64`) 1584 } 1585 1586 func (s *localServerSuite) TestConstraintsValidator(c *gc.C) { 1587 env := s.Open(c, stdcontext.TODO(), s.env.Config()) 1588 validator, err := env.ConstraintsValidator(s.callCtx) 1589 c.Assert(err, jc.ErrorIsNil) 1590 cons := constraints.MustParse("arch=amd64 cpu-power=10 virt-type=lxd") 1591 unsupported, err := validator.Validate(cons) 1592 c.Assert(err, jc.ErrorIsNil) 1593 c.Assert(unsupported, jc.SameContents, []string{"cpu-power"}) 1594 } 1595 1596 func (s *localServerSuite) TestConstraintsValidatorVocab(c *gc.C) { 1597 env := s.Open(c, stdcontext.TODO(), s.env.Config()) 1598 validator, err := env.ConstraintsValidator(s.callCtx) 1599 c.Assert(err, jc.ErrorIsNil) 1600 1601 cons := constraints.MustParse("instance-type=foo") 1602 _, err = validator.Validate(cons) 1603 c.Assert(err, gc.ErrorMatches, "invalid constraint value: instance-type=foo\nvalid values are:.*") 1604 1605 cons = constraints.MustParse("virt-type=foo") 1606 _, err = validator.Validate(cons) 1607 c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta("invalid constraint value: virt-type=foo\nvalid values are: [kvm lxd]")) 1608 } 1609 1610 func (s *localServerSuite) TestConstraintsMerge(c *gc.C) { 1611 env := s.Open(c, stdcontext.TODO(), s.env.Config()) 1612 validator, err := env.ConstraintsValidator(s.callCtx) 1613 c.Assert(err, jc.ErrorIsNil) 1614 consA := constraints.MustParse("arch=amd64 mem=1G root-disk=10G") 1615 consB := constraints.MustParse("instance-type=m1.small") 1616 cons, err := validator.Merge(consA, consB) 1617 c.Assert(err, jc.ErrorIsNil) 1618 // NOTE: root-disk and instance-type constraints are checked by PrecheckInstance. 1619 c.Assert(cons, gc.DeepEquals, constraints.MustParse("arch=amd64 instance-type=m1.small root-disk=10G")) 1620 } 1621 1622 func (s *localServerSuite) TestFindImageInstanceConstraint(c *gc.C) { 1623 env := s.Open(c, stdcontext.TODO(), s.env.Config()) 1624 imageMetadata := []*imagemetadata.ImageMetadata{{ 1625 Id: "image-id", 1626 Arch: "amd64", 1627 }} 1628 1629 spec, err := openstack.FindInstanceSpec( 1630 env, jujuversion.DefaultSupportedLTSBase(), "amd64", "instance-type=m1.tiny", 1631 imageMetadata, 1632 ) 1633 c.Assert(err, jc.ErrorIsNil) 1634 c.Assert(spec.InstanceType.Name, gc.Equals, "m1.tiny") 1635 } 1636 1637 func (s *localServerSuite) TestFindInstanceImageConstraintHypervisor(c *gc.C) { 1638 testVirtType := "qemu" 1639 env := s.Open(c, stdcontext.TODO(), s.env.Config()) 1640 imageMetadata := []*imagemetadata.ImageMetadata{{ 1641 Id: "image-id", 1642 Arch: "amd64", 1643 VirtType: testVirtType, 1644 }} 1645 1646 spec, err := openstack.FindInstanceSpec( 1647 env, jujuversion.DefaultSupportedLTSBase(), "amd64", "virt-type="+testVirtType, 1648 imageMetadata, 1649 ) 1650 c.Assert(err, jc.ErrorIsNil) 1651 c.Assert(spec.InstanceType.VirtType, gc.NotNil) 1652 c.Assert(*spec.InstanceType.VirtType, gc.Equals, testVirtType) 1653 c.Assert(spec.InstanceType.Name, gc.Equals, "m1.small") 1654 } 1655 1656 func (s *localServerSuite) TestFindInstanceImageWithHypervisorNoConstraint(c *gc.C) { 1657 testVirtType := "qemu" 1658 env := s.Open(c, stdcontext.TODO(), s.env.Config()) 1659 imageMetadata := []*imagemetadata.ImageMetadata{{ 1660 Id: "image-id", 1661 Arch: "amd64", 1662 VirtType: testVirtType, 1663 }} 1664 1665 spec, err := openstack.FindInstanceSpec( 1666 env, jujuversion.DefaultSupportedLTSBase(), "amd64", "", 1667 imageMetadata, 1668 ) 1669 c.Assert(err, jc.ErrorIsNil) 1670 c.Assert(spec.InstanceType.VirtType, gc.NotNil) 1671 c.Assert(*spec.InstanceType.VirtType, gc.Equals, testVirtType) 1672 c.Assert(spec.InstanceType.Name, gc.Equals, "m1.small") 1673 } 1674 1675 func (s *localServerSuite) TestFindInstanceNoConstraint(c *gc.C) { 1676 env := s.Open(c, stdcontext.TODO(), s.env.Config()) 1677 imageMetadata := []*imagemetadata.ImageMetadata{{ 1678 Id: "image-id", 1679 Arch: "amd64", 1680 }} 1681 1682 spec, err := openstack.FindInstanceSpec( 1683 env, jujuversion.DefaultSupportedLTSBase(), "amd64", "", 1684 imageMetadata, 1685 ) 1686 c.Assert(err, jc.ErrorIsNil) 1687 c.Assert(spec.InstanceType.VirtType, gc.IsNil) 1688 c.Assert(spec.InstanceType.Name, gc.Equals, "m1.small") 1689 } 1690 1691 func (s *localServerSuite) TestFindImageInvalidInstanceConstraint(c *gc.C) { 1692 env := s.Open(c, stdcontext.TODO(), s.env.Config()) 1693 imageMetadata := []*imagemetadata.ImageMetadata{{ 1694 Id: "image-id", 1695 Arch: "amd64", 1696 }} 1697 _, err := openstack.FindInstanceSpec( 1698 env, jujuversion.DefaultSupportedLTSBase(), "amd64", "instance-type=m1.large", 1699 imageMetadata, 1700 ) 1701 c.Assert(err, gc.ErrorMatches, `no instance types in some-region matching constraints "arch=amd64 instance-type=m1.large"`) 1702 } 1703 1704 func (s *localServerSuite) TestPrecheckInstanceValidInstanceType(c *gc.C) { 1705 env := s.Open(c, stdcontext.TODO(), s.env.Config()) 1706 cons := constraints.MustParse("instance-type=m1.small") 1707 err := env.PrecheckInstance(s.callCtx, environs.PrecheckInstanceParams{Base: jujuversion.DefaultSupportedLTSBase(), Constraints: cons}) 1708 c.Assert(err, jc.ErrorIsNil) 1709 } 1710 1711 func (s *localServerSuite) TestPrecheckInstanceInvalidInstanceType(c *gc.C) { 1712 env := s.Open(c, stdcontext.TODO(), s.env.Config()) 1713 cons := constraints.MustParse("instance-type=m1.large") 1714 err := env.PrecheckInstance(s.callCtx, environs.PrecheckInstanceParams{Base: jujuversion.DefaultSupportedLTSBase(), Constraints: cons}) 1715 c.Assert(err, gc.ErrorMatches, `invalid Openstack flavour "m1.large" specified`) 1716 } 1717 1718 func (s *localServerSuite) TestPrecheckInstanceInvalidRootDiskConstraint(c *gc.C) { 1719 env := s.Open(c, stdcontext.TODO(), s.env.Config()) 1720 cons := constraints.MustParse("instance-type=m1.small root-disk=10G") 1721 err := env.PrecheckInstance(s.callCtx, environs.PrecheckInstanceParams{Base: jujuversion.DefaultSupportedLTSBase(), Constraints: cons}) 1722 c.Assert(err, gc.ErrorMatches, `constraint root-disk cannot be specified with instance-type unless constraint root-disk-source=volume`) 1723 } 1724 1725 func (s *localServerSuite) TestPrecheckInstanceAvailZone(c *gc.C) { 1726 placement := "zone=test-available" 1727 err := s.env.PrecheckInstance(s.callCtx, environs.PrecheckInstanceParams{Base: jujuversion.DefaultSupportedLTSBase(), Placement: placement}) 1728 c.Assert(err, jc.ErrorIsNil) 1729 } 1730 1731 func (s *localServerSuite) TestPrecheckInstanceAvailZoneUnavailable(c *gc.C) { 1732 placement := "zone=test-unavailable" 1733 err := s.env.PrecheckInstance(s.callCtx, environs.PrecheckInstanceParams{Base: jujuversion.DefaultSupportedLTSBase(), Placement: placement}) 1734 c.Assert(err, gc.ErrorMatches, `zone "test-unavailable" is unavailable`) 1735 } 1736 1737 func (s *localServerSuite) TestPrecheckInstanceAvailZoneUnknown(c *gc.C) { 1738 placement := "zone=test-unknown" 1739 err := s.env.PrecheckInstance(s.callCtx, environs.PrecheckInstanceParams{Base: jujuversion.DefaultSupportedLTSBase(), Placement: placement}) 1740 c.Assert(err, gc.ErrorMatches, `availability zone "test-unknown" not valid`) 1741 } 1742 1743 func (s *localServerSuite) TestPrecheckInstanceAvailZonesUnsupported(c *gc.C) { 1744 s.srv.Nova.SetAvailabilityZones() // no availability zone support 1745 placement := "zone=test-unknown" 1746 err := s.env.PrecheckInstance(s.callCtx, environs.PrecheckInstanceParams{Base: jujuversion.DefaultSupportedLTSBase(), Placement: placement}) 1747 c.Assert(err, jc.Satisfies, errors.IsNotImplemented) 1748 } 1749 1750 func (s *localServerSuite) TestPrecheckInstanceVolumeAvailZonesNoPlacement(c *gc.C) { 1751 s.testPrecheckInstanceVolumeAvailZones(c, "") 1752 } 1753 1754 func (s *localServerSuite) TestPrecheckInstanceVolumeAvailZonesSameZonePlacement(c *gc.C) { 1755 s.testPrecheckInstanceVolumeAvailZones(c, "zone=az1") 1756 } 1757 1758 func (s *localServerSuite) testPrecheckInstanceVolumeAvailZones(c *gc.C, placement string) { 1759 s.srv.Nova.SetAvailabilityZones( 1760 nova.AvailabilityZone{ 1761 Name: "az1", 1762 State: nova.AvailabilityZoneState{ 1763 Available: true, 1764 }, 1765 }, 1766 ) 1767 1768 _, err := s.storageAdapter.CreateVolume(cinder.CreateVolumeVolumeParams{ 1769 Size: 123, 1770 Name: "foo", 1771 AvailabilityZone: "az1", 1772 Metadata: map[string]string{ 1773 "juju-model-uuid": coretesting.ModelTag.Id(), 1774 "juju-controller-uuid": coretesting.ControllerTag.Id(), 1775 }, 1776 }) 1777 c.Assert(err, jc.ErrorIsNil) 1778 1779 err = s.env.PrecheckInstance(s.callCtx, environs.PrecheckInstanceParams{ 1780 Base: jujuversion.DefaultSupportedLTSBase(), 1781 Placement: placement, 1782 VolumeAttachments: []storage.VolumeAttachmentParams{{VolumeId: "foo"}}, 1783 }) 1784 c.Assert(err, jc.ErrorIsNil) 1785 } 1786 1787 func (s *localServerSuite) TestPrecheckInstanceAvailZonesConflictsVolume(c *gc.C) { 1788 s.srv.Nova.SetAvailabilityZones( 1789 nova.AvailabilityZone{ 1790 Name: "az1", 1791 State: nova.AvailabilityZoneState{ 1792 Available: true, 1793 }, 1794 }, 1795 nova.AvailabilityZone{ 1796 Name: "az2", 1797 State: nova.AvailabilityZoneState{ 1798 Available: true, 1799 }, 1800 }, 1801 ) 1802 1803 _, err := s.storageAdapter.CreateVolume(cinder.CreateVolumeVolumeParams{ 1804 Size: 123, 1805 Name: "foo", 1806 AvailabilityZone: "az1", 1807 Metadata: map[string]string{ 1808 "juju-model-uuid": coretesting.ModelTag.Id(), 1809 "juju-controller-uuid": coretesting.ControllerTag.Id(), 1810 }, 1811 }) 1812 c.Assert(err, jc.ErrorIsNil) 1813 1814 err = s.env.PrecheckInstance(s.callCtx, environs.PrecheckInstanceParams{ 1815 Base: jujuversion.DefaultSupportedLTSBase(), 1816 Placement: "zone=az2", 1817 VolumeAttachments: []storage.VolumeAttachmentParams{{VolumeId: "foo"}}, 1818 }) 1819 c.Assert(err, gc.ErrorMatches, `cannot create instance with placement "zone=az2": cannot create instance in zone "az2", as this will prevent attaching the requested disks in zone "az1"`) 1820 } 1821 1822 func (s *localServerSuite) TestDeriveAvailabilityZones(c *gc.C) { 1823 placement := "zone=test-available" 1824 env := s.env.(common.ZonedEnviron) 1825 zones, err := env.DeriveAvailabilityZones( 1826 s.callCtx, 1827 environs.StartInstanceParams{ 1828 Placement: placement, 1829 }) 1830 c.Assert(err, jc.ErrorIsNil) 1831 c.Assert(zones, gc.DeepEquals, []string{"test-available"}) 1832 } 1833 1834 func (s *localServerSuite) TestDeriveAvailabilityZonesUnavailable(c *gc.C) { 1835 placement := "zone=test-unavailable" 1836 env := s.env.(common.ZonedEnviron) 1837 zones, err := env.DeriveAvailabilityZones( 1838 s.callCtx, 1839 environs.StartInstanceParams{ 1840 Placement: placement, 1841 }) 1842 c.Assert(err, gc.ErrorMatches, `zone "test-unavailable" is unavailable`) 1843 c.Assert(zones, gc.HasLen, 0) 1844 } 1845 1846 func (s *localServerSuite) TestDeriveAvailabilityZonesUnknown(c *gc.C) { 1847 placement := "zone=test-unknown" 1848 env := s.env.(common.ZonedEnviron) 1849 zones, err := env.DeriveAvailabilityZones( 1850 s.callCtx, 1851 environs.StartInstanceParams{ 1852 Placement: placement, 1853 }) 1854 c.Assert(err, gc.ErrorMatches, `availability zone "test-unknown" not valid`) 1855 c.Assert(zones, gc.HasLen, 0) 1856 } 1857 1858 func (s *localServerSuite) TestDeriveAvailabilityZonesVolumeNoPlacement(c *gc.C) { 1859 s.srv.Nova.SetAvailabilityZones( 1860 nova.AvailabilityZone{ 1861 Name: "az1", 1862 State: nova.AvailabilityZoneState{ 1863 Available: false, 1864 }, 1865 }, 1866 nova.AvailabilityZone{ 1867 Name: "az2", 1868 State: nova.AvailabilityZoneState{ 1869 Available: true, 1870 }, 1871 }, 1872 ) 1873 1874 _, err := s.storageAdapter.CreateVolume(cinder.CreateVolumeVolumeParams{ 1875 Size: 123, 1876 Name: "foo", 1877 AvailabilityZone: "az2", 1878 Metadata: map[string]string{ 1879 "juju-model-uuid": coretesting.ModelTag.Id(), 1880 "juju-controller-uuid": coretesting.ControllerTag.Id(), 1881 }, 1882 }) 1883 c.Assert(err, jc.ErrorIsNil) 1884 1885 env := s.env.(common.ZonedEnviron) 1886 zones, err := env.DeriveAvailabilityZones( 1887 s.callCtx, 1888 environs.StartInstanceParams{ 1889 VolumeAttachments: []storage.VolumeAttachmentParams{{VolumeId: "foo"}}, 1890 }) 1891 c.Assert(err, jc.ErrorIsNil) 1892 c.Assert(zones, gc.DeepEquals, []string{"az2"}) 1893 } 1894 1895 func (s *localServerSuite) TestDeriveAvailabilityZonesConflictsVolume(c *gc.C) { 1896 s.srv.Nova.SetAvailabilityZones( 1897 nova.AvailabilityZone{ 1898 Name: "az1", 1899 State: nova.AvailabilityZoneState{ 1900 Available: true, 1901 }, 1902 }, 1903 nova.AvailabilityZone{ 1904 Name: "az2", 1905 State: nova.AvailabilityZoneState{ 1906 Available: true, 1907 }, 1908 }, 1909 ) 1910 1911 _, err := s.storageAdapter.CreateVolume(cinder.CreateVolumeVolumeParams{ 1912 Size: 123, 1913 Name: "foo", 1914 AvailabilityZone: "az1", 1915 Metadata: map[string]string{ 1916 "juju-model-uuid": coretesting.ModelTag.Id(), 1917 "juju-controller-uuid": coretesting.ControllerTag.Id(), 1918 }, 1919 }) 1920 c.Assert(err, jc.ErrorIsNil) 1921 1922 env := s.env.(common.ZonedEnviron) 1923 zones, err := env.DeriveAvailabilityZones( 1924 s.callCtx, 1925 environs.StartInstanceParams{ 1926 Placement: "zone=az2", 1927 VolumeAttachments: []storage.VolumeAttachmentParams{{VolumeId: "foo"}}, 1928 }) 1929 c.Assert(err, gc.ErrorMatches, `cannot create instance with placement "zone=az2": cannot create instance in zone "az2", as this will prevent attaching the requested disks in zone "az1"`) 1930 c.Assert(zones, gc.HasLen, 0) 1931 } 1932 1933 func (s *localServerSuite) TestValidateImageMetadata(c *gc.C) { 1934 ss := simplestreams.NewSimpleStreams(sstesting.TestDataSourceFactory()) 1935 env := s.Open(c, stdcontext.TODO(), s.env.Config()) 1936 params, err := env.(simplestreams.ImageMetadataValidator).ImageMetadataLookupParams("some-region") 1937 c.Assert(err, jc.ErrorIsNil) 1938 params.Sources, err = environs.ImageMetadataSources(env, ss) 1939 c.Assert(err, jc.ErrorIsNil) 1940 params.Release = "13.04" 1941 imageIDs, _, err := imagemetadata.ValidateImageMetadata(ss, params) 1942 c.Assert(err, jc.ErrorIsNil) 1943 c.Assert(imageIDs, jc.SameContents, []string{"id-y"}) 1944 } 1945 1946 func (s *localServerSuite) TestImageMetadataSourceOrder(c *gc.C) { 1947 ss := simplestreams.NewSimpleStreams(sstesting.TestDataSourceFactory()) 1948 src := func(env environs.Environ) (simplestreams.DataSource, error) { 1949 return ss.NewDataSource(simplestreams.Config{ 1950 Description: "my datasource", 1951 BaseURL: "file:///bar", 1952 HostnameVerification: false, 1953 Priority: simplestreams.CUSTOM_CLOUD_DATA}), nil 1954 } 1955 environs.RegisterUserImageDataSourceFunc("my func", src) 1956 defer environs.UnregisterImageDataSourceFunc("my func") 1957 env := s.Open(c, stdcontext.TODO(), s.env.Config()) 1958 sources, err := environs.ImageMetadataSources(env, ss) 1959 c.Assert(err, jc.ErrorIsNil) 1960 var sourceIds []string 1961 for _, s := range sources { 1962 sourceIds = append(sourceIds, s.Description()) 1963 } 1964 c.Assert(sourceIds, jc.DeepEquals, []string{ 1965 "image-metadata-url", "my datasource", "keystone catalog", "default ubuntu cloud images"}) 1966 } 1967 1968 // TestEnsureGroup checks that when creating a duplicate security group, the existing group is 1969 // returned. 1970 func (s *localServerSuite) TestEnsureGroup(c *gc.C) { 1971 group, err := openstack.EnsureGroup(s.env, s.callCtx, "test group", false) 1972 c.Assert(err, jc.ErrorIsNil) 1973 c.Assert(group.Name, gc.Equals, "test group") 1974 id := group.Id 1975 1976 // Do it again and check that the existing group is returned 1977 group, err = openstack.EnsureGroup(s.env, s.callCtx, "test group", false) 1978 c.Assert(err, jc.ErrorIsNil) 1979 c.Check(group.Id, gc.Equals, id) 1980 c.Assert(group.Name, gc.Equals, "test group") 1981 } 1982 1983 // TestEnsureModelGroup checks that when creating a model security group, the 1984 // group is created with the correct ingress rules 1985 func (s *localServerSuite) TestEnsureModelGroup(c *gc.C) { 1986 group, err := openstack.EnsureGroup(s.env, s.callCtx, "test model group", true) 1987 c.Assert(err, jc.ErrorIsNil) 1988 c.Assert(group.Name, gc.Equals, "test model group") 1989 1990 stringRules := make([]string, 0, len(group.Rules)) 1991 for _, rule := range group.Rules { 1992 // Skip the default Security Group Rules created by Neutron 1993 if rule.Direction == "egress" { 1994 continue 1995 } 1996 var minInt int 1997 if rule.PortRangeMin != nil { 1998 minInt = *rule.PortRangeMin 1999 } 2000 var maxInt int 2001 if rule.PortRangeMax != nil { 2002 maxInt = *rule.PortRangeMax 2003 } 2004 ruleStr := fmt.Sprintf("%s %s %d %d %s %s %s", 2005 rule.Direction, 2006 *rule.IPProtocol, 2007 minInt, maxInt, 2008 rule.RemoteIPPrefix, 2009 rule.EthernetType, 2010 rule.ParentGroupId, 2011 ) 2012 stringRules = append(stringRules, ruleStr) 2013 } 2014 // We don't care about the ordering, so we sort the result, and compare it. 2015 expectedRules := []string{ 2016 fmt.Sprintf(`ingress tcp 1 65535 IPv6 %s`, group.Id), 2017 fmt.Sprintf(`ingress tcp 1 65535 IPv4 %s`, group.Id), 2018 fmt.Sprintf(`ingress udp 1 65535 IPv6 %s`, group.Id), 2019 fmt.Sprintf(`ingress udp 1 65535 IPv4 %s`, group.Id), 2020 fmt.Sprintf(`ingress icmp 0 0 IPv6 %s`, group.Id), 2021 fmt.Sprintf(`ingress icmp 0 0 IPv4 %s`, group.Id), 2022 } 2023 sort.Strings(stringRules) 2024 sort.Strings(expectedRules) 2025 c.Check(stringRules, gc.DeepEquals, expectedRules) 2026 } 2027 2028 // TestMatchingGroup checks that you receive the group you expected. matchingGroup() 2029 // is used by the firewaller when opening and closing ports. Unit test in response to bug 1675799. 2030 func (s *localServerSuite) TestMatchingGroup(c *gc.C) { 2031 err := bootstrapEnv(c, s.env) 2032 c.Assert(err, jc.ErrorIsNil) 2033 group1, err := openstack.EnsureGroup(s.env, s.callCtx, 2034 openstack.MachineGroupName(s.env, s.ControllerUUID, "1"), false) 2035 c.Assert(err, jc.ErrorIsNil) 2036 group2, err := openstack.EnsureGroup(s.env, s.callCtx, 2037 openstack.MachineGroupName(s.env, s.ControllerUUID, "2"), false) 2038 c.Assert(err, jc.ErrorIsNil) 2039 _, err = openstack.EnsureGroup(s.env, s.callCtx, openstack.MachineGroupName(s.env, s.ControllerUUID, "11"), false) 2040 c.Assert(err, jc.ErrorIsNil) 2041 _, err = openstack.EnsureGroup(s.env, s.callCtx, openstack.MachineGroupName(s.env, s.ControllerUUID, "12"), false) 2042 c.Assert(err, jc.ErrorIsNil) 2043 2044 machineNameRegexp := openstack.MachineGroupRegexp(s.env, "1") 2045 groupMatched, err := openstack.MatchingGroup(s.env, s.callCtx, machineNameRegexp) 2046 c.Assert(err, jc.ErrorIsNil) 2047 c.Assert(group1.Id, gc.Equals, groupMatched.Id) 2048 2049 machineNameRegexp = openstack.MachineGroupRegexp(s.env, "2") 2050 groupMatched, err = openstack.MatchingGroup(s.env, s.callCtx, machineNameRegexp) 2051 c.Assert(err, jc.ErrorIsNil) 2052 c.Assert(group2.Id, gc.Equals, groupMatched.Id) 2053 } 2054 2055 // localHTTPSServerSuite contains tests that run against an Openstack service 2056 // double connected on an HTTPS port with a self-signed certificate. This 2057 // service is set up and torn down for every test. This should only test 2058 // things that depend on the HTTPS connection, all other functional tests on a 2059 // local connection should be in localServerSuite 2060 type localHTTPSServerSuite struct { 2061 coretesting.BaseSuite 2062 attrs map[string]interface{} 2063 cred *identity.Credentials 2064 srv localServer 2065 env environs.Environ 2066 callCtx context.ProviderCallContext 2067 } 2068 2069 var _ = gc.Suite(&localHTTPSServerSuite{}) 2070 2071 func (s *localHTTPSServerSuite) SetUpSuite(c *gc.C) { 2072 s.BaseSuite.SetUpSuite(c) 2073 overrideCinderProvider(&s.CleanupSuite, &mockAdapter{}) 2074 } 2075 2076 func (s *localHTTPSServerSuite) createConfigAttrs(c *gc.C) map[string]interface{} { 2077 attrs := makeTestConfig(s.cred) 2078 attrs["agent-version"] = coretesting.FakeVersionNumber.String() 2079 attrs["authorized-keys"] = "fakekey" 2080 attrs["network"] = "net" 2081 // In order to set up and tear down the environment properly, we must 2082 // disable hostname verification 2083 attrs["ssl-hostname-verification"] = false 2084 attrs["auth-url"] = s.cred.URL 2085 // Now connect and set up test-local tools and image-metadata URLs 2086 cl := client.NewNonValidatingClient(s.cred, identity.AuthUserPass, nil) 2087 err := cl.Authenticate() 2088 c.Assert(err, jc.ErrorIsNil) 2089 containerURL, err := cl.MakeServiceURL("object-store", "", nil) 2090 c.Assert(err, jc.ErrorIsNil) 2091 c.Check(containerURL[:8], gc.Equals, "https://") 2092 attrs["agent-metadata-url"] = containerURL + "/juju-dist-test/tools" 2093 c.Logf("Set agent-metadata-url=%q", attrs["agent-metadata-url"]) 2094 attrs["image-metadata-url"] = containerURL + "/juju-dist-test" 2095 c.Logf("Set image-metadata-url=%q", attrs["image-metadata-url"]) 2096 return attrs 2097 } 2098 2099 func (s *localHTTPSServerSuite) SetUpTest(c *gc.C) { 2100 s.BaseSuite.SetUpTest(c) 2101 s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber) 2102 s.srv.UseTLS = true 2103 cred := &identity.Credentials{ 2104 User: "fred", 2105 Secrets: "secret", 2106 Region: "some-region", 2107 TenantName: "some tenant", 2108 } 2109 // Note: start() will change cred.URL to point to s.srv.Server.URL 2110 s.srv.start(c, cred, newFullOpenstackService) 2111 s.cred = cred 2112 attrs := s.createConfigAttrs(c) 2113 c.Assert(attrs["auth-url"].(string)[:8], gc.Equals, "https://") 2114 env, err := bootstrap.PrepareController( 2115 false, 2116 envtesting.BootstrapTODOContext(c), 2117 jujuclient.NewMemStore(), 2118 prepareParams(attrs, s.cred), 2119 ) 2120 c.Assert(err, jc.ErrorIsNil) 2121 s.env = env.(environs.Environ) 2122 s.attrs = s.env.Config().AllAttrs() 2123 s.callCtx = context.NewEmptyCloudCallContext() 2124 } 2125 2126 func (s *localHTTPSServerSuite) TearDownTest(c *gc.C) { 2127 if s.env != nil { 2128 err := s.env.Destroy(s.callCtx) 2129 c.Check(err, jc.ErrorIsNil) 2130 s.env = nil 2131 } 2132 s.srv.stop() 2133 s.BaseSuite.TearDownTest(c) 2134 } 2135 2136 func (s *localHTTPSServerSuite) TestSSLVerify(c *gc.C) { 2137 // If you don't have ssl-hostname-verification set to false, and do have 2138 // a CA Certificate, then we can connect to the environment. Copy the attrs 2139 // used by SetUp and force hostname verification. 2140 env := s.envUsingCertificate(c) 2141 _, err := env.AllRunningInstances(s.callCtx) 2142 c.Assert(err, gc.IsNil) 2143 } 2144 2145 func (s *localHTTPSServerSuite) TestMustDisableSSLVerify(c *gc.C) { 2146 coretesting.SkipIfPPC64EL(c, "lp:1425242") 2147 2148 // If you don't have ssl-hostname-verification set to false, then we 2149 // fail to connect to the environment. Copy the attrs used by SetUp and 2150 // force hostname verification. 2151 newattrs := make(map[string]interface{}, len(s.attrs)) 2152 for k, v := range s.attrs { 2153 newattrs[k] = v 2154 } 2155 newattrs["ssl-hostname-verification"] = true 2156 cfg, err := config.New(config.NoDefaults, newattrs) 2157 c.Assert(err, jc.ErrorIsNil) 2158 _, err = environs.New(stdcontext.TODO(), environs.OpenParams{ 2159 Cloud: makeCloudSpec(s.cred), 2160 Config: cfg, 2161 }) 2162 c.Assert(err, gc.ErrorMatches, "(.|\n)*x509: certificate signed by unknown authority") 2163 } 2164 2165 func (s *localHTTPSServerSuite) TestCanBootstrap(c *gc.C) { 2166 restoreFinishBootstrap := envtesting.DisableFinishBootstrap() 2167 defer restoreFinishBootstrap() 2168 2169 // For testing, we create a storage instance to which is uploaded tools and image metadata. 2170 toolsMetadataStorage := openstack.MetadataStorage(s.env) 2171 agentURL, err := toolsMetadataStorage.URL("") 2172 c.Assert(err, jc.ErrorIsNil) 2173 c.Logf("Generating fake tools for: %v", agentURL) 2174 envtesting.UploadFakeTools(c, toolsMetadataStorage, s.env.Config().AgentStream(), s.env.Config().AgentStream()) 2175 defer envtesting.RemoveFakeTools(c, toolsMetadataStorage, s.env.Config().AgentStream()) 2176 2177 imageMetadataStorage := openstack.ImageMetadataStorage(s.env) 2178 c.Logf("Generating fake images") 2179 openstack.UseTestImageData(imageMetadataStorage, s.cred) 2180 defer openstack.RemoveTestImageData(imageMetadataStorage) 2181 2182 err = bootstrapEnv(c, s.env) 2183 c.Assert(err, jc.ErrorIsNil) 2184 } 2185 2186 func (s *localHTTPSServerSuite) TestFetchFromImageMetadataSources(c *gc.C) { 2187 ss := simplestreams.NewSimpleStreams(sstesting.TestSkipVerifyDataSourceFactory()) 2188 // Setup a custom URL for image metadata 2189 customStorage := openstack.CreateCustomStorage(s.env, "custom-metadata") 2190 customURL, err := customStorage.URL("") 2191 c.Assert(err, jc.ErrorIsNil) 2192 c.Check(customURL[:8], gc.Equals, "https://") 2193 2194 envConfig, err := s.env.Config().Apply( 2195 map[string]interface{}{"image-metadata-url": customURL}, 2196 ) 2197 c.Assert(err, jc.ErrorIsNil) 2198 err = s.env.SetConfig(envConfig) 2199 c.Assert(err, jc.ErrorIsNil) 2200 sources, err := environs.ImageMetadataSources(s.env, ss) 2201 c.Assert(err, jc.ErrorIsNil) 2202 c.Assert(sources, gc.HasLen, 3) 2203 2204 // Make sure there is something to download from each location 2205 metadata := "metadata-content" 2206 metadataStorage := openstack.ImageMetadataStorage(s.env) 2207 err = metadataStorage.Put(metadata, bytes.NewBufferString(metadata), int64(len(metadata))) 2208 c.Assert(err, jc.ErrorIsNil) 2209 2210 custom := "custom-content" 2211 err = customStorage.Put(custom, bytes.NewBufferString(custom), int64(len(custom))) 2212 c.Assert(err, jc.ErrorIsNil) 2213 2214 // Produce map of data sources keyed on description 2215 mappedSources := make(map[string]simplestreams.DataSource, len(sources)) 2216 for i, s := range sources { 2217 c.Logf("datasource %d: %+v", i, s) 2218 mappedSources[s.Description()] = s 2219 } 2220 2221 // Read from the Config entry's image-metadata-url 2222 contentReader, imageURL, err := mappedSources["image-metadata-url"].Fetch(custom) 2223 c.Assert(err, jc.ErrorIsNil) 2224 defer func() { _ = contentReader.Close() }() 2225 content, err := io.ReadAll(contentReader) 2226 c.Assert(err, jc.ErrorIsNil) 2227 c.Assert(string(content), gc.Equals, custom) 2228 c.Check(imageURL[:8], gc.Equals, "https://") 2229 2230 // Check the entry we got from keystone 2231 contentReader, imageURL, err = mappedSources["keystone catalog"].Fetch(metadata) 2232 c.Assert(err, jc.ErrorIsNil) 2233 defer func() { _ = contentReader.Close() }() 2234 content, err = io.ReadAll(contentReader) 2235 c.Assert(err, jc.ErrorIsNil) 2236 c.Assert(string(content), gc.Equals, metadata) 2237 c.Check(imageURL[:8], gc.Equals, "https://") 2238 // Verify that we are pointing at exactly where metadataStorage thinks we are 2239 metaURL, err := metadataStorage.URL(metadata) 2240 c.Assert(err, jc.ErrorIsNil) 2241 c.Check(imageURL, gc.Equals, metaURL) 2242 } 2243 2244 func (s *localHTTPSServerSuite) TestFetchFromImageMetadataSourcesWithCertificate(c *gc.C) { 2245 ss := simplestreams.NewSimpleStreams(sstesting.TestDataSourceFactory()) 2246 env := s.envUsingCertificate(c) 2247 2248 // Setup a custom URL for image metadata 2249 customStorage := openstack.CreateCustomStorage(env, "custom-metadata") 2250 customURL, err := customStorage.URL("") 2251 c.Assert(err, jc.ErrorIsNil) 2252 c.Check(customURL[:8], gc.Equals, "https://") 2253 2254 envConfig, err := env.Config().Apply( 2255 map[string]interface{}{"image-metadata-url": customURL}, 2256 ) 2257 c.Assert(err, jc.ErrorIsNil) 2258 err = env.SetConfig(envConfig) 2259 c.Assert(err, jc.ErrorIsNil) 2260 sources, err := environs.ImageMetadataSources(env, ss) 2261 c.Assert(err, jc.ErrorIsNil) 2262 c.Assert(sources, gc.HasLen, 3) 2263 2264 // Make sure there is something to download from each location 2265 metadata := "metadata-content" 2266 metadataStorage := openstack.ImageMetadataStorage(env) 2267 err = metadataStorage.Put(metadata, bytes.NewBufferString(metadata), int64(len(metadata))) 2268 c.Assert(err, jc.ErrorIsNil) 2269 2270 custom := "custom-content" 2271 err = customStorage.Put(custom, bytes.NewBufferString(custom), int64(len(custom))) 2272 c.Assert(err, jc.ErrorIsNil) 2273 2274 // Produce map of data sources keyed on description 2275 mappedSources := make(map[string]simplestreams.DataSource, len(sources)) 2276 for i, s := range sources { 2277 c.Logf("datasource %d: %+v", i, s) 2278 mappedSources[s.Description()] = s 2279 } 2280 2281 // Check the entry we got from keystone 2282 contentReader, imageURL, err := mappedSources["keystone catalog"].Fetch(metadata) 2283 c.Assert(err, jc.ErrorIsNil) 2284 defer func() { _ = contentReader.Close() }() 2285 content, err := io.ReadAll(contentReader) 2286 c.Assert(err, jc.ErrorIsNil) 2287 c.Assert(string(content), gc.Equals, metadata) 2288 c.Check(imageURL[:8], gc.Equals, "https://") 2289 2290 // Verify that we are pointing at exactly where metadataStorage thinks we are 2291 metaURL, err := metadataStorage.URL(metadata) 2292 c.Assert(err, jc.ErrorIsNil) 2293 c.Check(imageURL, gc.Equals, metaURL) 2294 } 2295 2296 func (s *localHTTPSServerSuite) TestFetchFromToolsMetadataSources(c *gc.C) { 2297 ss := simplestreams.NewSimpleStreams(sstesting.TestSkipVerifyDataSourceFactory()) 2298 // Setup a custom URL for image metadata 2299 customStorage := openstack.CreateCustomStorage(s.env, "custom-tools-metadata") 2300 customURL, err := customStorage.URL("") 2301 c.Assert(err, jc.ErrorIsNil) 2302 c.Check(customURL[:8], gc.Equals, "https://") 2303 2304 envConfig, err := s.env.Config().Apply( 2305 map[string]interface{}{"agent-metadata-url": customURL}, 2306 ) 2307 c.Assert(err, jc.ErrorIsNil) 2308 err = s.env.SetConfig(envConfig) 2309 c.Assert(err, jc.ErrorIsNil) 2310 sources, err := tools.GetMetadataSources(s.env, ss) 2311 c.Assert(err, jc.ErrorIsNil) 2312 c.Assert(sources, gc.HasLen, 3) 2313 2314 // Make sure there is something to download from each location 2315 2316 keystone := "keystone-tools-content" 2317 // The keystone entry just points at the root of the Swift storage, and 2318 // we have to create a container to upload any data. So we just point 2319 // into a subdirectory for the data we are downloading 2320 keystoneContainer := "tools-test" 2321 keystoneStorage := openstack.CreateCustomStorage(s.env, "tools-test") 2322 err = keystoneStorage.Put(keystone, bytes.NewBufferString(keystone), int64(len(keystone))) 2323 c.Assert(err, jc.ErrorIsNil) 2324 2325 custom := "custom-tools-content" 2326 err = customStorage.Put(custom, bytes.NewBufferString(custom), int64(len(custom))) 2327 c.Assert(err, jc.ErrorIsNil) 2328 2329 // Read from the Config entry's agent-metadata-url 2330 contentReader, metadataURL, err := sources[0].Fetch(custom) 2331 c.Assert(err, jc.ErrorIsNil) 2332 defer func() { _ = contentReader.Close() }() 2333 content, err := io.ReadAll(contentReader) 2334 c.Assert(err, jc.ErrorIsNil) 2335 c.Assert(string(content), gc.Equals, custom) 2336 c.Check(metadataURL[:8], gc.Equals, "https://") 2337 2338 // Check the entry we got from keystone 2339 // Now fetch the data, and verify the contents. 2340 contentReader, metadataURL, err = sources[1].Fetch(keystoneContainer + "/" + keystone) 2341 c.Assert(err, jc.ErrorIsNil) 2342 defer func() { _ = contentReader.Close() }() 2343 content, err = io.ReadAll(contentReader) 2344 c.Assert(err, jc.ErrorIsNil) 2345 c.Assert(string(content), gc.Equals, keystone) 2346 c.Check(metadataURL[:8], gc.Equals, "https://") 2347 keystoneURL, err := keystoneStorage.URL(keystone) 2348 c.Assert(err, jc.ErrorIsNil) 2349 c.Check(metadataURL, gc.Equals, keystoneURL) 2350 2351 // We *don't* test Fetch for sources[3] because it points to 2352 // streams.canonical.com 2353 } 2354 2355 func (s *localServerSuite) TestRemoveBlankContainer(c *gc.C) { 2356 containerStorage := openstack.BlankContainerStorage() 2357 err := containerStorage.Remove("some-file") 2358 c.Assert(err, gc.ErrorMatches, `cannot remove "some-file": swift container name is empty`) 2359 } 2360 2361 func (s *localServerSuite) TestAllRunningInstancesIgnoresOtherMachines(c *gc.C) { 2362 err := bootstrapEnv(c, s.env) 2363 c.Assert(err, jc.ErrorIsNil) 2364 2365 // Check that we see 1 instance in the environment 2366 insts, err := s.env.AllRunningInstances(s.callCtx) 2367 c.Assert(err, jc.ErrorIsNil) 2368 c.Check(insts, gc.HasLen, 1) 2369 2370 // Now start a machine 'manually' in the same account, with a similar 2371 // but not matching name, and ensure it isn't seen by AllRunningInstances 2372 // See bug #1257481, for how similar names were causing them to get 2373 // listed (and thus destroyed) at the wrong time 2374 existingModelName := s.TestConfig["name"] 2375 newMachineName := fmt.Sprintf("juju-%s-2-machine-0", existingModelName) 2376 2377 // We grab the Nova client directly from the env, just to save time 2378 // looking all the stuff up 2379 novaClient := openstack.GetNovaClient(s.env) 2380 entity, err := novaClient.RunServer(nova.RunServerOpts{ 2381 Name: newMachineName, 2382 FlavorId: "1", // test service has 1,2,3 for flavor ids 2383 ImageId: "1", // UseTestImageData sets up images 1 and 2 2384 Networks: []nova.ServerNetworks{{NetworkId: "1"}}, 2385 }) 2386 c.Assert(err, jc.ErrorIsNil) 2387 c.Assert(entity, gc.NotNil) 2388 2389 // List all servers with no filter, we should see both instances 2390 servers, err := novaClient.ListServersDetail(nova.NewFilter()) 2391 c.Assert(err, jc.ErrorIsNil) 2392 c.Assert(servers, gc.HasLen, 2) 2393 2394 insts, err = s.env.AllRunningInstances(s.callCtx) 2395 c.Assert(err, jc.ErrorIsNil) 2396 c.Check(insts, gc.HasLen, 1) 2397 } 2398 2399 func (s *localServerSuite) TestResolveNetworkUUID(c *gc.C) { 2400 var sampleUUID = "f81d4fae-7dec-11d0-a765-00a0c91e6bf6" 2401 2402 err := s.srv.Neutron.NeutronModel().AddNetwork(neutron.NetworkV2{Id: sampleUUID}) 2403 c.Assert(err, jc.ErrorIsNil) 2404 2405 networkIDs, err := openstack.ResolveNetworkIDs(s.env, sampleUUID, false) 2406 c.Assert(err, jc.ErrorIsNil) 2407 c.Assert(networkIDs, gc.DeepEquals, []string{sampleUUID}) 2408 } 2409 2410 func (s *localServerSuite) TestResolveNetworkLabel(c *gc.C) { 2411 // For now this test has to cheat and use knowledge of goose internals 2412 var networkLabel = "net" 2413 var expectNetworkIDs = []string{"1"} 2414 networkIDs, err := openstack.ResolveNetworkIDs(s.env, networkLabel, false) 2415 c.Assert(err, jc.ErrorIsNil) 2416 c.Assert(networkIDs, gc.DeepEquals, expectNetworkIDs) 2417 } 2418 2419 func (s *localServerSuite) TestResolveNetworkLabelMultiple(c *gc.C) { 2420 var networkLabel = "multi" 2421 2422 err := s.srv.Neutron.NeutronModel().AddNetwork(neutron.NetworkV2{ 2423 Id: "multi-666", 2424 Name: networkLabel, 2425 }) 2426 c.Assert(err, jc.ErrorIsNil) 2427 2428 err = s.srv.Neutron.NeutronModel().AddNetwork(neutron.NetworkV2{ 2429 Id: "multi-999", 2430 Name: networkLabel, 2431 }) 2432 c.Assert(err, jc.ErrorIsNil) 2433 2434 var expectNetworkIDs = []string{"multi-666", "multi-999"} 2435 networkIDs, err := openstack.ResolveNetworkIDs(s.env, networkLabel, false) 2436 c.Assert(err, jc.ErrorIsNil) 2437 c.Assert(networkIDs, jc.SameContents, expectNetworkIDs) 2438 } 2439 2440 func (s *localServerSuite) TestResolveNetworkNotPresent(c *gc.C) { 2441 networkIDs, err := openstack.ResolveNetworkIDs(s.env, "no-network-with-this-label", false) 2442 c.Assert(err, jc.ErrorIsNil) 2443 c.Check(networkIDs, gc.HasLen, 0) 2444 } 2445 2446 func (s *localServerSuite) TestStartInstanceAvailZone(c *gc.C) { 2447 inst, err := s.testStartInstanceAvailZone(c, "test-available") 2448 c.Assert(err, jc.ErrorIsNil) 2449 c.Assert(openstack.InstanceServerDetail(inst).AvailabilityZone, gc.Equals, "test-available") 2450 } 2451 2452 func (s *localServerSuite) TestStartInstanceAvailZoneUnavailable(c *gc.C) { 2453 _, err := s.testStartInstanceAvailZone(c, "test-unavailable") 2454 c.Assert(errors.Is(err, environs.ErrAvailabilityZoneIndependent), jc.IsFalse) 2455 } 2456 2457 func (s *localServerSuite) TestStartInstanceAvailZoneUnknown(c *gc.C) { 2458 _, err := s.testStartInstanceAvailZone(c, "test-unknown") 2459 c.Assert(errors.Is(err, environs.ErrAvailabilityZoneIndependent), jc.IsFalse) 2460 } 2461 2462 func (s *localServerSuite) testStartInstanceAvailZone(c *gc.C, zone string) (instances.Instance, error) { 2463 err := bootstrapEnv(c, s.env) 2464 c.Assert(err, jc.ErrorIsNil) 2465 2466 params := environs.StartInstanceParams{ 2467 ControllerUUID: s.ControllerUUID, 2468 AvailabilityZone: zone, 2469 } 2470 result, err := testing.StartInstanceWithParams(s.env, s.callCtx, "1", params) 2471 if err != nil { 2472 return nil, err 2473 } 2474 return result.Instance, nil 2475 } 2476 2477 func (s *localServerSuite) TestGetAvailabilityZones(c *gc.C) { 2478 var resultZones []nova.AvailabilityZone 2479 var resultErr error 2480 s.PatchValue(openstack.NovaListAvailabilityZones, func(c *nova.Client) ([]nova.AvailabilityZone, error) { 2481 return append([]nova.AvailabilityZone{}, resultZones...), resultErr 2482 }) 2483 env := s.env.(common.ZonedEnviron) 2484 2485 resultErr = fmt.Errorf("failed to get availability zones") 2486 zones, err := env.AvailabilityZones(s.callCtx) 2487 c.Assert(err, gc.Equals, resultErr) 2488 c.Assert(zones, gc.IsNil) 2489 2490 resultErr = nil 2491 resultZones = make([]nova.AvailabilityZone, 1) 2492 resultZones[0].Name = "whatever" 2493 zones, err = env.AvailabilityZones(s.callCtx) 2494 c.Assert(err, jc.ErrorIsNil) 2495 c.Assert(zones, gc.HasLen, 1) 2496 c.Assert(zones[0].Name(), gc.Equals, "whatever") 2497 } 2498 2499 func (s *localServerSuite) TestGetAvailabilityZonesCommon(c *gc.C) { 2500 var resultZones []nova.AvailabilityZone 2501 s.PatchValue(openstack.NovaListAvailabilityZones, func(c *nova.Client) ([]nova.AvailabilityZone, error) { 2502 return append([]nova.AvailabilityZone{}, resultZones...), nil 2503 }) 2504 env := s.env.(common.ZonedEnviron) 2505 resultZones = make([]nova.AvailabilityZone, 2) 2506 resultZones[0].Name = "az1" 2507 resultZones[1].Name = "az2" 2508 resultZones[0].State.Available = true 2509 resultZones[1].State.Available = false 2510 zones, err := env.AvailabilityZones(s.callCtx) 2511 c.Assert(err, jc.ErrorIsNil) 2512 c.Assert(zones, gc.HasLen, 2) 2513 c.Assert(zones[0].Name(), gc.Equals, resultZones[0].Name) 2514 c.Assert(zones[1].Name(), gc.Equals, resultZones[1].Name) 2515 c.Assert(zones[0].Available(), jc.IsTrue) 2516 c.Assert(zones[1].Available(), jc.IsFalse) 2517 } 2518 2519 func (s *localServerSuite) TestStartInstanceWithUnknownAZError(c *gc.C) { 2520 coretesting.SkipIfPPC64EL(c, "lp:1425242") 2521 2522 s.srv.Nova.SetAvailabilityZones( 2523 // bootstrap node will be on az1. 2524 nova.AvailabilityZone{ 2525 Name: "az1", 2526 State: nova.AvailabilityZoneState{ 2527 Available: true, 2528 }, 2529 }, 2530 // az2 will be made to return an unknown error. 2531 nova.AvailabilityZone{ 2532 Name: "az2", 2533 State: nova.AvailabilityZoneState{ 2534 Available: true, 2535 }, 2536 }, 2537 ) 2538 2539 err := bootstrapEnv(c, s.env) 2540 c.Assert(err, jc.ErrorIsNil) 2541 2542 cleanup := s.srv.Nova.RegisterControlPoint( 2543 "addServer", 2544 func(sc hook.ServiceControl, args ...interface{}) error { 2545 serverDetail := args[0].(*nova.ServerDetail) 2546 if serverDetail.AvailabilityZone == "az2" { 2547 return fmt.Errorf("some unknown error") 2548 } 2549 return nil 2550 }, 2551 ) 2552 defer cleanup() 2553 _, err = testing.StartInstanceWithParams(s.env, s.callCtx, "1", environs.StartInstanceParams{ 2554 ControllerUUID: s.ControllerUUID, 2555 AvailabilityZone: "az2", 2556 }) 2557 c.Assert(err, gc.ErrorMatches, "(?s).*some unknown error.*") 2558 } 2559 2560 func (s *localServerSuite) testStartInstanceWithParamsDeriveAZ( 2561 machineId string, 2562 params environs.StartInstanceParams, 2563 ) (*environs.StartInstanceResult, error) { 2564 zonedEnv := s.env.(common.ZonedEnviron) 2565 zones, err := zonedEnv.DeriveAvailabilityZones(s.callCtx, params) 2566 if err != nil { 2567 return nil, err 2568 } 2569 if len(zones) < 1 { 2570 return nil, errors.New("no zones found") 2571 } 2572 params.AvailabilityZone = zones[0] 2573 return testing.StartInstanceWithParams(s.env, s.callCtx, "1", params) 2574 } 2575 2576 func (s *localServerSuite) TestStartInstanceVolumeAttachmentsAvailZone(c *gc.C) { 2577 s.srv.Nova.SetAvailabilityZones( 2578 nova.AvailabilityZone{ 2579 Name: "az1", 2580 State: nova.AvailabilityZoneState{ 2581 Available: true, 2582 }, 2583 }, 2584 nova.AvailabilityZone{ 2585 Name: "az2", 2586 State: nova.AvailabilityZoneState{ 2587 Available: true, 2588 }, 2589 }, 2590 nova.AvailabilityZone{ 2591 Name: "test-available", 2592 State: nova.AvailabilityZoneState{ 2593 Available: true, 2594 }, 2595 }, 2596 ) 2597 err := bootstrapEnv(c, s.env) 2598 c.Assert(err, jc.ErrorIsNil) 2599 2600 _, err = s.storageAdapter.CreateVolume(cinder.CreateVolumeVolumeParams{ 2601 Size: 123, 2602 Name: "foo", 2603 AvailabilityZone: "az2", 2604 Metadata: map[string]string{ 2605 "juju-model-uuid": coretesting.ModelTag.Id(), 2606 "juju-controller-uuid": coretesting.ControllerTag.Id(), 2607 }, 2608 }) 2609 c.Assert(err, jc.ErrorIsNil) 2610 result, err := s.testStartInstanceWithParamsDeriveAZ("1", environs.StartInstanceParams{ 2611 ControllerUUID: s.ControllerUUID, 2612 VolumeAttachments: []storage.VolumeAttachmentParams{ 2613 {VolumeId: "foo"}, 2614 }, 2615 }) 2616 c.Assert(err, jc.ErrorIsNil) 2617 c.Assert(openstack.InstanceServerDetail(result.Instance).AvailabilityZone, gc.Equals, "az2") 2618 } 2619 2620 func (s *localServerSuite) TestStartInstanceVolumeAttachmentsMultipleAvailZones(c *gc.C) { 2621 err := bootstrapEnv(c, s.env) 2622 c.Assert(err, jc.ErrorIsNil) 2623 2624 for _, az := range []string{"az1", "az2"} { 2625 _, err := s.storageAdapter.CreateVolume(cinder.CreateVolumeVolumeParams{ 2626 Size: 123, 2627 Name: "vol-" + az, 2628 AvailabilityZone: az, 2629 Metadata: map[string]string{ 2630 "juju-model-uuid": coretesting.ModelTag.Id(), 2631 "juju-controller-uuid": coretesting.ControllerTag.Id(), 2632 }, 2633 }) 2634 c.Assert(err, jc.ErrorIsNil) 2635 } 2636 2637 _, err = s.testStartInstanceWithParamsDeriveAZ("1", environs.StartInstanceParams{ 2638 ControllerUUID: s.ControllerUUID, 2639 VolumeAttachments: []storage.VolumeAttachmentParams{ 2640 {VolumeId: "vol-az1"}, 2641 {VolumeId: "vol-az2"}, 2642 }, 2643 }) 2644 c.Assert(err, gc.ErrorMatches, `cannot attach volumes from multiple availability zones: vol-az1 is in az1, vol-az2 is in az2`) 2645 } 2646 2647 func (s *localServerSuite) TestStartInstanceVolumeAttachmentsAvailZoneConflictsPlacement(c *gc.C) { 2648 err := bootstrapEnv(c, s.env) 2649 c.Assert(err, jc.ErrorIsNil) 2650 2651 s.srv.Nova.SetAvailabilityZones( 2652 nova.AvailabilityZone{ 2653 Name: "az1", 2654 State: nova.AvailabilityZoneState{ 2655 Available: true, 2656 }, 2657 }, 2658 nova.AvailabilityZone{ 2659 Name: "az2", 2660 State: nova.AvailabilityZoneState{ 2661 Available: true, 2662 }, 2663 }, 2664 ) 2665 _, err = s.storageAdapter.CreateVolume(cinder.CreateVolumeVolumeParams{ 2666 Size: 123, 2667 Name: "foo", 2668 AvailabilityZone: "az1", 2669 Metadata: map[string]string{ 2670 "juju-model-uuid": coretesting.ModelTag.Id(), 2671 "juju-controller-uuid": coretesting.ControllerTag.Id(), 2672 }, 2673 }) 2674 c.Assert(err, jc.ErrorIsNil) 2675 2676 _, err = testing.StartInstanceWithParams(s.env, s.callCtx, "1", environs.StartInstanceParams{ 2677 ControllerUUID: s.ControllerUUID, 2678 VolumeAttachments: []storage.VolumeAttachmentParams{{VolumeId: "foo"}}, 2679 AvailabilityZone: "az2", 2680 }) 2681 c.Assert(err, gc.ErrorMatches, `cannot create instance in zone "az2", as this will prevent attaching the requested disks in zone "az1"`) 2682 } 2683 2684 // novaInstaceStartedWithOpts exposes run server options used to start an instance. 2685 type novaInstaceStartedWithOpts interface { 2686 NovaInstanceStartedWithOpts() *nova.RunServerOpts 2687 } 2688 2689 func (s *localServerSuite) TestStartInstanceWithImageIDConstraint(c *gc.C) { 2690 env := s.ensureAMDImages(c) 2691 2692 err := bootstrapEnv(c, env) 2693 c.Assert(err, jc.ErrorIsNil) 2694 2695 cons, err := constraints.Parse("image-id=ubuntu-bf2") 2696 c.Assert(err, jc.ErrorIsNil) 2697 2698 res, err := testing.StartInstanceWithParams(env, s.callCtx, "1", environs.StartInstanceParams{ 2699 ControllerUUID: s.ControllerUUID, 2700 Constraints: cons, 2701 }) 2702 c.Assert(err, jc.ErrorIsNil) 2703 c.Assert(res, gc.NotNil) 2704 2705 runOpts := res.Instance.(novaInstaceStartedWithOpts).NovaInstanceStartedWithOpts() 2706 c.Assert(runOpts, gc.NotNil) 2707 c.Assert(runOpts.ImageId, gc.NotNil) 2708 c.Assert(runOpts.ImageId, gc.Equals, "ubuntu-bf2") 2709 } 2710 2711 func (s *localServerSuite) TestStartInstanceVolumeRootBlockDevice(c *gc.C) { 2712 // diskSizeGiB should be equal to the openstack.defaultRootDiskSize 2713 diskSizeGiB := 30 2714 env := s.ensureAMDImages(c) 2715 2716 err := bootstrapEnv(c, env) 2717 c.Assert(err, jc.ErrorIsNil) 2718 2719 cons, err := constraints.Parse("root-disk-source=volume arch=amd64") 2720 c.Assert(err, jc.ErrorIsNil) 2721 2722 res, err := testing.StartInstanceWithParams(env, s.callCtx, "1", environs.StartInstanceParams{ 2723 ControllerUUID: s.ControllerUUID, 2724 Constraints: cons, 2725 }) 2726 c.Assert(err, jc.ErrorIsNil) 2727 c.Assert(res, gc.NotNil) 2728 2729 runOpts := res.Instance.(novaInstaceStartedWithOpts).NovaInstanceStartedWithOpts() 2730 c.Assert(runOpts, gc.NotNil) 2731 c.Assert(runOpts.BlockDeviceMappings, gc.NotNil) 2732 deviceMapping := runOpts.BlockDeviceMappings[0] 2733 c.Assert(deviceMapping, jc.DeepEquals, nova.BlockDeviceMapping{ 2734 BootIndex: 0, 2735 UUID: "1", 2736 SourceType: "image", 2737 DestinationType: "volume", 2738 DeleteOnTermination: true, 2739 VolumeSize: diskSizeGiB, 2740 }) 2741 } 2742 2743 func (s *localServerSuite) TestStartInstanceVolumeRootBlockDeviceSized(c *gc.C) { 2744 env := s.ensureAMDImages(c) 2745 2746 diskSizeGiB := 10 2747 2748 err := bootstrapEnv(c, env) 2749 c.Assert(err, jc.ErrorIsNil) 2750 2751 cons, err := constraints.Parse("root-disk-source=volume root-disk=10G arch=amd64") 2752 c.Assert(err, jc.ErrorIsNil) 2753 2754 res, err := testing.StartInstanceWithParams(env, s.callCtx, "1", environs.StartInstanceParams{ 2755 ControllerUUID: s.ControllerUUID, 2756 Constraints: cons, 2757 }) 2758 c.Assert(err, jc.ErrorIsNil) 2759 c.Assert(res, gc.NotNil) 2760 2761 c.Assert(res.Hardware.RootDisk, gc.NotNil) 2762 c.Assert(*res.Hardware.RootDisk, gc.Equals, uint64(diskSizeGiB*1024)) 2763 2764 runOpts := res.Instance.(novaInstaceStartedWithOpts).NovaInstanceStartedWithOpts() 2765 c.Assert(runOpts, gc.NotNil) 2766 c.Assert(runOpts.BlockDeviceMappings, gc.NotNil) 2767 deviceMapping := runOpts.BlockDeviceMappings[0] 2768 c.Assert(deviceMapping, jc.DeepEquals, nova.BlockDeviceMapping{ 2769 BootIndex: 0, 2770 UUID: "1", 2771 SourceType: "image", 2772 DestinationType: "volume", 2773 DeleteOnTermination: true, 2774 VolumeSize: diskSizeGiB, 2775 }) 2776 } 2777 2778 func (s *localServerSuite) TestStartInstanceLocalRootBlockDeviceConstraint(c *gc.C) { 2779 env := s.ensureAMDImages(c) 2780 2781 err := bootstrapEnv(c, env) 2782 c.Assert(err, jc.ErrorIsNil) 2783 2784 cons, err := constraints.Parse("root-disk-source=local root-disk=1G arch=amd64") 2785 c.Assert(err, jc.ErrorIsNil) 2786 c.Assert(cons.HasRootDisk(), jc.IsTrue) 2787 c.Assert(*cons.RootDisk, gc.Equals, uint64(1024)) 2788 2789 res, err := testing.StartInstanceWithParams(env, s.callCtx, "1", environs.StartInstanceParams{ 2790 ControllerUUID: s.ControllerUUID, 2791 Constraints: cons, 2792 }) 2793 c.Assert(err, jc.ErrorIsNil) 2794 c.Assert(res, gc.NotNil) 2795 2796 c.Assert(res.Hardware.RootDisk, gc.NotNil) 2797 // Check local disk requirements are met. 2798 c.Assert(*res.Hardware.RootDisk, jc.GreaterThan, uint64(1024-1)) 2799 2800 runOpts := res.Instance.(novaInstaceStartedWithOpts).NovaInstanceStartedWithOpts() 2801 c.Assert(runOpts, gc.NotNil) 2802 c.Assert(runOpts.BlockDeviceMappings, gc.NotNil) 2803 deviceMapping := runOpts.BlockDeviceMappings[0] 2804 c.Assert(deviceMapping, jc.DeepEquals, nova.BlockDeviceMapping{ 2805 BootIndex: 0, 2806 UUID: "1", 2807 SourceType: "image", 2808 DestinationType: "local", 2809 DeleteOnTermination: true, 2810 // VolumeSize is 0 when a local disk is used. 2811 VolumeSize: 0, 2812 }) 2813 } 2814 2815 func (s *localServerSuite) TestStartInstanceLocalRootBlockDevice(c *gc.C) { 2816 env := s.ensureAMDImages(c) 2817 2818 err := bootstrapEnv(c, env) 2819 c.Assert(err, jc.ErrorIsNil) 2820 2821 cons, err := constraints.Parse("root-disk=1G arch=amd64") 2822 c.Assert(err, jc.ErrorIsNil) 2823 c.Assert(cons.HasRootDisk(), jc.IsTrue) 2824 c.Assert(*cons.RootDisk, gc.Equals, uint64(1024)) 2825 2826 res, err := testing.StartInstanceWithParams(env, s.callCtx, "1", environs.StartInstanceParams{ 2827 ControllerUUID: s.ControllerUUID, 2828 Constraints: cons, 2829 }) 2830 c.Assert(err, jc.ErrorIsNil) 2831 c.Assert(res, gc.NotNil) 2832 2833 c.Assert(res.Hardware.RootDisk, gc.NotNil) 2834 // Check local disk requirements are met. 2835 c.Assert(*res.Hardware.RootDisk, jc.GreaterThan, uint64(1024-1)) 2836 2837 runOpts := res.Instance.(novaInstaceStartedWithOpts).NovaInstanceStartedWithOpts() 2838 c.Assert(runOpts, gc.NotNil) 2839 c.Assert(runOpts.BlockDeviceMappings, gc.NotNil) 2840 deviceMapping := runOpts.BlockDeviceMappings[0] 2841 c.Assert(deviceMapping, jc.DeepEquals, nova.BlockDeviceMapping{ 2842 BootIndex: 0, 2843 UUID: "1", 2844 SourceType: "image", 2845 DestinationType: "local", 2846 DeleteOnTermination: true, 2847 // VolumeSize is 0 when a local disk is used. 2848 VolumeSize: 0, 2849 }) 2850 } 2851 2852 func (s *localServerSuite) TestInstanceTags(c *gc.C) { 2853 err := bootstrapEnv(c, s.env) 2854 c.Assert(err, jc.ErrorIsNil) 2855 2856 allInstances, err := s.env.AllRunningInstances(s.callCtx) 2857 c.Assert(err, jc.ErrorIsNil) 2858 c.Assert(allInstances, gc.HasLen, 1) 2859 2860 c.Assert( 2861 openstack.InstanceServerDetail(allInstances[0]).Metadata, 2862 jc.DeepEquals, 2863 map[string]string{ 2864 "juju-model-uuid": coretesting.ModelTag.Id(), 2865 "juju-controller-uuid": coretesting.ControllerTag.Id(), 2866 "juju-is-controller": "true", 2867 }, 2868 ) 2869 } 2870 2871 func (s *localServerSuite) TestTagInstance(c *gc.C) { 2872 err := bootstrapEnv(c, s.env) 2873 c.Assert(err, jc.ErrorIsNil) 2874 2875 assertMetadata := func(extraKey, extraValue string) { 2876 // Refresh instance 2877 allInstances, err := s.env.AllRunningInstances(s.callCtx) 2878 c.Assert(err, jc.ErrorIsNil) 2879 c.Assert(allInstances, gc.HasLen, 1) 2880 c.Assert( 2881 openstack.InstanceServerDetail(allInstances[0]).Metadata, 2882 jc.DeepEquals, 2883 map[string]string{ 2884 "juju-model-uuid": coretesting.ModelTag.Id(), 2885 "juju-controller-uuid": coretesting.ControllerTag.Id(), 2886 "juju-is-controller": "true", 2887 extraKey: extraValue, 2888 }, 2889 ) 2890 } 2891 2892 allInstances, err := s.env.AllRunningInstances(s.callCtx) 2893 c.Assert(err, jc.ErrorIsNil) 2894 c.Assert(allInstances, gc.HasLen, 1) 2895 2896 extraKey := "extra-k" 2897 extraValue := "extra-v" 2898 err = s.env.(environs.InstanceTagger).TagInstance( 2899 s.callCtx, 2900 allInstances[0].Id(), 2901 map[string]string{extraKey: extraValue}, 2902 ) 2903 c.Assert(err, jc.ErrorIsNil) 2904 assertMetadata(extraKey, extraValue) 2905 2906 // Ensure that a second call updates existing tags. 2907 extraValue = "extra-v2" 2908 err = s.env.(environs.InstanceTagger).TagInstance( 2909 s.callCtx, 2910 allInstances[0].Id(), 2911 map[string]string{extraKey: extraValue}, 2912 ) 2913 c.Assert(err, jc.ErrorIsNil) 2914 assertMetadata(extraKey, extraValue) 2915 } 2916 2917 func (s *localServerSuite) TestAdoptResources(c *gc.C) { 2918 err := bootstrapEnv(c, s.env) 2919 c.Assert(err, jc.ErrorIsNil) 2920 2921 hostedModelUUID := "7e386e08-cba7-44a4-a76e-7c1633584210" 2922 cfg, err := s.env.Config().Apply(map[string]interface{}{ 2923 "uuid": hostedModelUUID, 2924 }) 2925 c.Assert(err, jc.ErrorIsNil) 2926 env, err := environs.New(stdcontext.TODO(), environs.OpenParams{ 2927 Cloud: makeCloudSpec(s.cred), 2928 Config: cfg, 2929 }) 2930 c.Assert(err, jc.ErrorIsNil) 2931 originalController := coretesting.ControllerTag.Id() 2932 _, _, _, err = testing.StartInstance(env, s.callCtx, originalController, "0") 2933 c.Assert(err, jc.ErrorIsNil) 2934 2935 addVolume(c, s.env, s.callCtx, originalController, "99/9") 2936 addVolume(c, env, s.callCtx, originalController, "23/9") 2937 2938 s.checkInstanceTags(c, s.env, originalController) 2939 s.checkInstanceTags(c, env, originalController) 2940 s.checkVolumeTags(c, s.env, originalController) 2941 s.checkVolumeTags(c, env, originalController) 2942 s.checkGroupController(c, s.env, originalController) 2943 s.checkGroupController(c, env, originalController) 2944 2945 // Needs to be a correctly formatted uuid so we can get it out of 2946 // group names. 2947 newController := "aaaaaaaa-bbbb-cccc-dddd-0123456789ab" 2948 err = env.AdoptResources(s.callCtx, newController, version.MustParse("1.2.3")) 2949 c.Assert(err, jc.ErrorIsNil) 2950 2951 s.checkInstanceTags(c, s.env, originalController) 2952 s.checkInstanceTags(c, env, newController) 2953 s.checkVolumeTags(c, s.env, originalController) 2954 s.checkVolumeTags(c, env, newController) 2955 s.checkGroupController(c, s.env, originalController) 2956 s.checkGroupController(c, env, newController) 2957 } 2958 2959 func (s *localServerSuite) TestAdoptResourcesNoStorage(c *gc.C) { 2960 // Nova-lxd doesn't support storage. lp:1677225 2961 s.PatchValue(openstack.NewOpenstackStorage, func(*openstack.Environ) (openstack.OpenstackStorage, error) { 2962 return nil, errors.NotSupportedf("volumes") 2963 }) 2964 err := bootstrapEnv(c, s.env) 2965 c.Assert(err, jc.ErrorIsNil) 2966 2967 hostedModelUUID := "7e386e08-cba7-44a4-a76e-7c1633584210" 2968 cfg, err := s.env.Config().Apply(map[string]interface{}{ 2969 "uuid": hostedModelUUID, 2970 }) 2971 c.Assert(err, jc.ErrorIsNil) 2972 env, err := environs.New(stdcontext.TODO(), environs.OpenParams{ 2973 Cloud: makeCloudSpec(s.cred), 2974 Config: cfg, 2975 }) 2976 c.Assert(err, jc.ErrorIsNil) 2977 originalController := coretesting.ControllerTag.Id() 2978 _, _, _, err = testing.StartInstance(env, s.callCtx, originalController, "0") 2979 c.Assert(err, jc.ErrorIsNil) 2980 2981 s.checkInstanceTags(c, s.env, originalController) 2982 s.checkInstanceTags(c, env, originalController) 2983 s.checkGroupController(c, s.env, originalController) 2984 s.checkGroupController(c, env, originalController) 2985 2986 // Needs to be a correctly formatted uuid so we can get it out of 2987 // group names. 2988 newController := "aaaaaaaa-bbbb-cccc-dddd-0123456789ab" 2989 err = env.AdoptResources(s.callCtx, newController, version.MustParse("1.2.3")) 2990 c.Assert(err, jc.ErrorIsNil) 2991 2992 s.checkInstanceTags(c, s.env, originalController) 2993 s.checkInstanceTags(c, env, newController) 2994 s.checkGroupController(c, s.env, originalController) 2995 s.checkGroupController(c, env, newController) 2996 } 2997 2998 func addVolume( 2999 c *gc.C, env environs.Environ, callCtx context.ProviderCallContext, controllerUUID, name string, 3000 ) *storage.Volume { 3001 storageAdapter, err := (*openstack.NewOpenstackStorage)(env.(*openstack.Environ)) 3002 c.Assert(err, jc.ErrorIsNil) 3003 modelUUID := env.Config().UUID() 3004 source := openstack.NewCinderVolumeSourceForModel(storageAdapter, modelUUID, env.(common.ZonedEnviron)) 3005 result, err := source.CreateVolumes(callCtx, []storage.VolumeParams{{ 3006 Tag: names.NewVolumeTag(name), 3007 ResourceTags: tags.ResourceTags( 3008 names.NewModelTag(modelUUID), 3009 names.NewControllerTag(controllerUUID), 3010 ), 3011 }}) 3012 c.Assert(err, jc.ErrorIsNil) 3013 c.Assert(result, gc.HasLen, 1) 3014 c.Assert(result[0].Error, jc.ErrorIsNil) 3015 return result[0].Volume 3016 } 3017 3018 func (s *localServerSuite) checkInstanceTags(c *gc.C, env environs.Environ, expectedController string) { 3019 allInstances, err := env.AllRunningInstances(s.callCtx) 3020 c.Assert(err, jc.ErrorIsNil) 3021 c.Assert(allInstances, gc.Not(gc.HasLen), 0) 3022 for _, inst := range allInstances { 3023 server := openstack.InstanceServerDetail(inst) 3024 c.Logf(string(inst.Id())) 3025 c.Check(server.Metadata[tags.JujuController], gc.Equals, expectedController) 3026 } 3027 } 3028 3029 func (s *localServerSuite) checkVolumeTags(c *gc.C, env environs.Environ, expectedController string) { 3030 stor, err := (*openstack.NewOpenstackStorage)(env.(*openstack.Environ)) 3031 c.Assert(err, jc.ErrorIsNil) 3032 source := openstack.NewCinderVolumeSourceForModel(stor, env.Config().UUID(), s.env.(common.ZonedEnviron)) 3033 volumeIds, err := source.ListVolumes(s.callCtx) 3034 c.Assert(err, jc.ErrorIsNil) 3035 c.Assert(volumeIds, gc.Not(gc.HasLen), 0) 3036 for _, volumeId := range volumeIds { 3037 c.Logf(volumeId) 3038 volume, err := stor.GetVolume(volumeId) 3039 c.Assert(err, jc.ErrorIsNil) 3040 c.Check(volume.Metadata[tags.JujuController], gc.Equals, expectedController) 3041 } 3042 } 3043 3044 func (s *localServerSuite) checkGroupController(c *gc.C, env environs.Environ, expectedController string) { 3045 groupNames, err := openstack.GetModelGroupNames(env) 3046 c.Assert(err, jc.ErrorIsNil) 3047 c.Assert(groupNames, gc.Not(gc.HasLen), 0) 3048 extractControllerRe, err := regexp.Compile(openstack.GroupControllerPattern) 3049 c.Assert(err, jc.ErrorIsNil) 3050 for _, group := range groupNames { 3051 c.Logf(group) 3052 controller := extractControllerRe.ReplaceAllString(group, "$controllerUUID") 3053 c.Check(controller, gc.Equals, expectedController) 3054 } 3055 } 3056 3057 func (s *localServerSuite) TestUpdateGroupController(c *gc.C) { 3058 err := bootstrapEnv(c, s.env) 3059 c.Assert(err, jc.ErrorIsNil) 3060 3061 groupNames, err := openstack.GetModelGroupNames(s.env) 3062 c.Assert(err, jc.ErrorIsNil) 3063 groupNamesBefore := set.NewStrings(groupNames...) 3064 c.Assert(groupNamesBefore, gc.DeepEquals, set.NewStrings( 3065 "juju-deadbeef-1bad-500d-9000-4b1d0d06f00d-deadbeef-0bad-400d-8000-4b1d0d06f00d", 3066 "juju-deadbeef-1bad-500d-9000-4b1d0d06f00d-deadbeef-0bad-400d-8000-4b1d0d06f00d-0", 3067 )) 3068 3069 firewaller := openstack.GetFirewaller(s.env) 3070 err = firewaller.UpdateGroupController(s.callCtx, "aabbccdd-eeee-ffff-0000-0123456789ab") 3071 c.Assert(err, jc.ErrorIsNil) 3072 3073 groupNames, err = openstack.GetModelGroupNames(s.env) 3074 c.Assert(err, jc.ErrorIsNil) 3075 groupNamesAfter := set.NewStrings(groupNames...) 3076 c.Assert(groupNamesAfter, gc.DeepEquals, set.NewStrings( 3077 "juju-aabbccdd-eeee-ffff-0000-0123456789ab-deadbeef-0bad-400d-8000-4b1d0d06f00d", 3078 "juju-aabbccdd-eeee-ffff-0000-0123456789ab-deadbeef-0bad-400d-8000-4b1d0d06f00d-0", 3079 )) 3080 } 3081 3082 func (s *localServerSuite) TestICMPFirewallRules(c *gc.C) { 3083 err := bootstrapEnv(c, s.env) 3084 c.Assert(err, jc.ErrorIsNil) 3085 3086 inst, _ := testing.AssertStartInstance(c, s.env, s.callCtx, s.ControllerUUID, "100") 3087 firewaller := openstack.GetFirewaller(s.env) 3088 err = firewaller.OpenInstancePorts(s.callCtx, inst, "100", firewall.IngressRules{ 3089 { 3090 PortRange: network.PortRange{ 3091 FromPort: -1, 3092 ToPort: -1, 3093 Protocol: "icmp", 3094 }, 3095 SourceCIDRs: set.NewStrings("0.0.0.0/0"), 3096 }, 3097 { 3098 PortRange: network.PortRange{ 3099 FromPort: -1, 3100 ToPort: -1, 3101 Protocol: "ipv6-icmp", 3102 }, 3103 SourceCIDRs: set.NewStrings("::/0"), 3104 }, 3105 }) 3106 c.Assert(err, jc.ErrorIsNil) 3107 3108 rules, err := firewaller.InstanceIngressRules(s.callCtx, inst, "100") 3109 c.Assert(err, jc.ErrorIsNil) 3110 3111 c.Assert(len(rules), gc.Equals, 1) 3112 c.Assert(rules[0].PortRange.FromPort, gc.Equals, -1) 3113 c.Assert(rules[0].PortRange.ToPort, gc.Equals, -1) 3114 c.Assert(rules[0].PortRange.Protocol, gc.Equals, "icmp") 3115 c.Assert(rules[0].SourceCIDRs.Size(), gc.Equals, 2) 3116 c.Assert(rules[0].SourceCIDRs.Contains("0.0.0.0/0"), jc.IsTrue) 3117 c.Assert(rules[0].SourceCIDRs.Contains("::/0"), jc.IsTrue) 3118 } 3119 3120 // TestIPv6RuleCreationForEmptyCIDR is a regression test for lp1709312 3121 func (s *localServerSuite) TestIPv6RuleCreationForEmptyCIDR(c *gc.C) { 3122 err := bootstrapEnv(c, s.env) 3123 c.Assert(err, jc.ErrorIsNil) 3124 3125 inst, _ := testing.AssertStartInstance(c, s.env, s.callCtx, s.ControllerUUID, "100") 3126 firewaller := openstack.GetFirewaller(s.env) 3127 err = firewaller.OpenInstancePorts(s.callCtx, inst, "100", firewall.IngressRules{ 3128 { 3129 PortRange: network.PortRange{ 3130 FromPort: 443, 3131 ToPort: 443, 3132 Protocol: "tcp", 3133 }, 3134 SourceCIDRs: set.NewStrings(), 3135 }, 3136 }) 3137 c.Assert(err, jc.ErrorIsNil) 3138 3139 rules, err := firewaller.InstanceIngressRules(s.callCtx, inst, "100") 3140 c.Assert(err, jc.ErrorIsNil) 3141 3142 c.Assert(len(rules), gc.Equals, 1) 3143 c.Assert(rules[0].PortRange.FromPort, gc.Equals, 443) 3144 c.Assert(rules[0].PortRange.ToPort, gc.Equals, 443) 3145 c.Assert(rules[0].PortRange.Protocol, gc.Equals, "tcp") 3146 c.Assert(rules[0].SourceCIDRs.Size(), gc.Equals, 2) 3147 c.Assert(rules[0].SourceCIDRs.Contains("0.0.0.0/0"), jc.IsTrue) 3148 c.Assert(rules[0].SourceCIDRs.Contains("::/0"), jc.IsTrue) 3149 } 3150 3151 func (s *localServerSuite) ensureAMDImages(c *gc.C) environs.Environ { 3152 // Ensure amd64 tools are available, to ensure an amd64 image. 3153 amd64Version := version.Binary{ 3154 Number: jujuversion.Current, 3155 Arch: arch.AMD64, 3156 } 3157 workloadOSList, err := corebase.AllWorkloadOSTypes() 3158 c.Assert(err, jc.ErrorIsNil) 3159 for _, workloadOS := range workloadOSList.Values() { 3160 amd64Version.Release = workloadOS 3161 envtesting.AssertUploadFakeToolsVersions( 3162 c, s.toolsMetadataStorage, s.env.Config().AgentStream(), s.env.Config().AgentStream(), amd64Version) 3163 } 3164 3165 // Destroy the old Environ 3166 err = environs.Destroy(s.env.Config().Name(), s.env, s.callCtx, s.ControllerStore) 3167 c.Assert(err, jc.ErrorIsNil) 3168 3169 // Prepare a new Environ 3170 return s.Prepare(c) 3171 } 3172 3173 // noNeutronSuite is a clone of localServerSuite which hacks the local 3174 // openstack to remove the neutron service from the auth response - 3175 // this causes the client to switch to nova networking. 3176 type noNeutronSuite struct { 3177 coretesting.BaseSuite 3178 cred *identity.Credentials 3179 srv localServer 3180 } 3181 3182 func (s *noNeutronSuite) SetUpSuite(c *gc.C) { 3183 s.BaseSuite.SetUpSuite(c) 3184 c.Logf("Running local tests") 3185 } 3186 3187 func (s *noNeutronSuite) SetUpTest(c *gc.C) { 3188 s.BaseSuite.SetUpTest(c) 3189 s.srv.start(c, s.cred, newNovaNetworkingOpenstackService) 3190 3191 userPass, ok := s.srv.OpenstackSvc.Identity.(*identityservice.UserPass) 3192 c.Assert(ok, jc.IsTrue) 3193 // Ensure that there's nothing returned with a type of "network", 3194 // so that we switch over to nova networking. 3195 cleanup := userPass.RegisterControlPoint("authorisation", func(sc hook.ServiceControl, args ...interface{}) error { 3196 res, ok := args[0].(*identityservice.AccessResponse) 3197 c.Assert(ok, jc.IsTrue) 3198 var filtered []identityservice.V2Service 3199 for _, service := range res.Access.ServiceCatalog { 3200 if service.Type != "network" { 3201 filtered = append(filtered, service) 3202 } 3203 } 3204 res.Access.ServiceCatalog = filtered 3205 return nil 3206 }) 3207 s.AddCleanup(func(c *gc.C) { cleanup() }) 3208 } 3209 3210 func (s *noNeutronSuite) TearDownTest(c *gc.C) { 3211 s.srv.stop() 3212 s.BaseSuite.TearDownTest(c) 3213 } 3214 3215 func (s *noNeutronSuite) TestSupport(c *gc.C) { 3216 cl := client.NewClient(s.cred, identity.AuthUserPass, nil) 3217 err := cl.Authenticate() 3218 c.Assert(err, jc.ErrorIsNil) 3219 containerURL, err := cl.MakeServiceURL("object-store", "", nil) 3220 c.Assert(err, jc.ErrorIsNil) 3221 attrs := coretesting.FakeConfig().Merge(coretesting.Attrs{ 3222 "name": "sample-no-neutron", 3223 "type": "openstack", 3224 "auth-mode": "userpass", 3225 "agent-version": coretesting.FakeVersionNumber.String(), 3226 "agent-metadata-url": containerURL + "/juju-dist-test/tools", 3227 "image-metadata-url": containerURL + "/juju-dist-test", 3228 "authorized-keys": "fakekey", 3229 }) 3230 s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber) 3231 // For testing, we create a storage instance to which is uploaded tools and image metadata. 3232 _, err = bootstrap.PrepareController( 3233 false, 3234 envtesting.BootstrapTODOContext(c), 3235 jujuclient.NewMemStore(), 3236 prepareParams(attrs, s.cred), 3237 ) 3238 c.Check(err, jc.Satisfies, errors.IsNotFound) 3239 c.Assert(err, gc.ErrorMatches, `OpenStack Neutron service`) 3240 } 3241 3242 func prepareParams(attrs map[string]interface{}, cred *identity.Credentials) bootstrap.PrepareParams { 3243 return bootstrap.PrepareParams{ 3244 ControllerConfig: coretesting.FakeControllerConfig(), 3245 ModelConfig: attrs, 3246 ControllerName: attrs["name"].(string), 3247 Cloud: makeCloudSpec(cred), 3248 AdminSecret: testing.AdminSecret, 3249 } 3250 } 3251 3252 func makeCloudSpec(cred *identity.Credentials) environscloudspec.CloudSpec { 3253 credential := makeCredential(cred) 3254 return environscloudspec.CloudSpec{ 3255 Type: "openstack", 3256 Name: "openstack", 3257 Endpoint: cred.URL, 3258 Region: cred.Region, 3259 Credential: &credential, 3260 } 3261 } 3262 3263 func makeCredential(cred *identity.Credentials) cloud.Credential { 3264 return cloud.NewCredential( 3265 cloud.UserPassAuthType, 3266 map[string]string{ 3267 "username": cred.User, 3268 "password": cred.Secrets, 3269 "tenant-name": cred.TenantName, 3270 }, 3271 ) 3272 } 3273 3274 // noSwiftSuite contains tests that run against an OpenStack service double 3275 // that lacks Swift. 3276 type noSwiftSuite struct { 3277 coretesting.BaseSuite 3278 cred *identity.Credentials 3279 srv localServer 3280 env environs.Environ 3281 } 3282 3283 var _ = gc.Suite(&noSwiftSuite{}) 3284 3285 func (s *noSwiftSuite) SetUpSuite(c *gc.C) { 3286 s.BaseSuite.SetUpSuite(c) 3287 restoreFinishBootstrap := envtesting.DisableFinishBootstrap() 3288 s.AddCleanup(func(*gc.C) { restoreFinishBootstrap() }) 3289 3290 s.PatchValue(&imagemetadata.SimplestreamsImagesPublicKey, sstesting.SignedMetadataPublicKey) 3291 s.PatchValue(&keys.JujuPublicKey, sstesting.SignedMetadataPublicKey) 3292 } 3293 3294 func (s *noSwiftSuite) SetUpTest(c *gc.C) { 3295 s.BaseSuite.SetUpTest(c) 3296 s.cred = &identity.Credentials{ 3297 Version: 2, 3298 User: "fred", 3299 Secrets: "secret", 3300 Region: "some-region", 3301 TenantName: "some tenant", 3302 } 3303 s.srv.start(c, s.cred, newNovaOnlyOpenstackService) 3304 3305 attrs := coretesting.FakeConfig().Merge(coretesting.Attrs{ 3306 "name": "sample-no-swift", 3307 "type": "openstack", 3308 "auth-mode": "userpass", 3309 "agent-version": coretesting.FakeVersionNumber.String(), 3310 "authorized-keys": "fakekey", 3311 }) 3312 s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber) 3313 // Serve fake tools and image metadata using "filestorage", 3314 // rather than Swift as the rest of the tests do. 3315 storageDir := c.MkDir() 3316 imagesDir := filepath.Join(storageDir, "images") 3317 toolsDir := filepath.Join(storageDir, "tools") 3318 for _, dir := range []string{imagesDir, toolsDir} { 3319 err := os.MkdirAll(dir, 0755) 3320 c.Assert(err, jc.ErrorIsNil) 3321 } 3322 toolsStorage, err := filestorage.NewFileStorageWriter(storageDir) 3323 c.Assert(err, jc.ErrorIsNil) 3324 envtesting.UploadFakeTools(c, toolsStorage, "released", "released") 3325 s.PatchValue(&tools.DefaultBaseURL, storageDir) 3326 imageStorage, err := filestorage.NewFileStorageWriter(imagesDir) 3327 c.Assert(err, jc.ErrorIsNil) 3328 openstack.UseTestImageData(imageStorage, s.cred) 3329 imagetesting.PatchOfficialDataSources(&s.CleanupSuite, storageDir) 3330 3331 env, err := bootstrap.PrepareController( 3332 false, 3333 envtesting.BootstrapTODOContext(c), 3334 jujuclient.NewMemStore(), 3335 prepareParams(attrs, s.cred), 3336 ) 3337 c.Assert(err, jc.ErrorIsNil) 3338 s.env = env.(environs.Environ) 3339 } 3340 3341 func (s *noSwiftSuite) TearDownTest(c *gc.C) { 3342 s.srv.stop() 3343 s.BaseSuite.TearDownTest(c) 3344 } 3345 3346 func (s *noSwiftSuite) TestBootstrap(c *gc.C) { 3347 cfg, err := s.env.Config().Apply(coretesting.Attrs{ 3348 // A label that corresponds to a neutron test service network 3349 "network": "net", 3350 }) 3351 c.Assert(err, jc.ErrorIsNil) 3352 3353 err = s.env.SetConfig(cfg) 3354 c.Assert(err, jc.ErrorIsNil) 3355 3356 c.Assert(bootstrapEnv(c, s.env), jc.ErrorIsNil) 3357 } 3358 3359 func newFullOpenstackService(cred *identity.Credentials, auth identity.AuthMode, useTSL bool) ( 3360 *openstackservice.Openstack, []string, 3361 ) { 3362 service, logMsg := openstackservice.New(cred, auth, useTSL) 3363 service.UseNeutronNetworking() 3364 service.SetupHTTP(nil) 3365 return service, logMsg 3366 } 3367 3368 func newNovaOnlyOpenstackService(cred *identity.Credentials, auth identity.AuthMode, useTSL bool) (*openstackservice.Openstack, []string) { 3369 service, logMsg := openstackservice.NewNoSwift(cred, auth, useTSL) 3370 service.UseNeutronNetworking() 3371 service.SetupHTTP(nil) 3372 return service, logMsg 3373 } 3374 3375 func newNovaNetworkingOpenstackService(cred *identity.Credentials, auth identity.AuthMode, useTSL bool) (*openstackservice.Openstack, []string) { 3376 service, logMsg := openstackservice.New(cred, auth, useTSL) 3377 service.SetupHTTP(nil) 3378 return service, logMsg 3379 } 3380 func bootstrapEnv(c *gc.C, env environs.Environ) error { 3381 return bootstrapEnvWithConstraints(c, env, constraints.Value{}) 3382 } 3383 3384 func bootstrapEnvWithConstraints(c *gc.C, env environs.Environ, cons constraints.Value) error { 3385 return bootstrap.Bootstrap(envtesting.BootstrapTODOContext(c), env, 3386 context.NewEmptyCloudCallContext(), 3387 bootstrap.BootstrapParams{ 3388 ControllerConfig: coretesting.FakeControllerConfig(), 3389 AdminSecret: testing.AdminSecret, 3390 CAPrivateKey: coretesting.CAKey, 3391 SupportedBootstrapBases: coretesting.FakeSupportedJujuBases, 3392 BootstrapConstraints: cons, 3393 }) 3394 }