Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| // Copyright 2012, 2013 Canonical Ltd. | |
| // Licensed under the AGPLv3, see LICENCE file for details. | |
| package openstack_test | |
| import ( | |
| "bytes" | |
| "errors" | |
| "fmt" | |
| "io/ioutil" | |
| "net/url" | |
| "os" | |
| "path/filepath" | |
| "regexp" | |
| "strings" | |
| "time" | |
| jujuerrors "github.com/juju/errors" | |
| gitjujutesting "github.com/juju/testing" | |
| jc "github.com/juju/testing/checkers" | |
| "github.com/juju/utils" | |
| "github.com/juju/utils/arch" | |
| "github.com/juju/utils/series" | |
| "github.com/juju/utils/set" | |
| "github.com/juju/utils/ssh" | |
| "github.com/juju/version" | |
| gc "gopkg.in/check.v1" | |
| "gopkg.in/goose.v1/cinder" | |
| "gopkg.in/goose.v1/client" | |
| "gopkg.in/goose.v1/identity" | |
| "gopkg.in/goose.v1/neutron" | |
| "gopkg.in/goose.v1/nova" | |
| "gopkg.in/goose.v1/testservices/hook" | |
| "gopkg.in/goose.v1/testservices/neutronservice" | |
| "gopkg.in/goose.v1/testservices/novaservice" | |
| "gopkg.in/goose.v1/testservices/openstackservice" | |
| "gopkg.in/juju/names.v2" | |
| "github.com/juju/juju/cloud" | |
| "github.com/juju/juju/cloudconfig/instancecfg" | |
| "github.com/juju/juju/constraints" | |
| "github.com/juju/juju/environs" | |
| "github.com/juju/juju/environs/bootstrap" | |
| "github.com/juju/juju/environs/config" | |
| "github.com/juju/juju/environs/filestorage" | |
| "github.com/juju/juju/environs/imagemetadata" | |
| imagetesting "github.com/juju/juju/environs/imagemetadata/testing" | |
| "github.com/juju/juju/environs/jujutest" | |
| "github.com/juju/juju/environs/simplestreams" | |
| sstesting "github.com/juju/juju/environs/simplestreams/testing" | |
| envstorage "github.com/juju/juju/environs/storage" | |
| "github.com/juju/juju/environs/tags" | |
| envtesting "github.com/juju/juju/environs/testing" | |
| "github.com/juju/juju/environs/tools" | |
| "github.com/juju/juju/instance" | |
| "github.com/juju/juju/juju/keys" | |
| "github.com/juju/juju/juju/testing" | |
| "github.com/juju/juju/jujuclient/jujuclienttesting" | |
| "github.com/juju/juju/network" | |
| "github.com/juju/juju/provider/common" | |
| "github.com/juju/juju/provider/openstack" | |
| "github.com/juju/juju/status" | |
| "github.com/juju/juju/storage" | |
| coretesting "github.com/juju/juju/testing" | |
| jujuversion "github.com/juju/juju/version" | |
| ) | |
| type ProviderSuite struct { | |
| restoreTimeouts func() | |
| } | |
| var _ = gc.Suite(&ProviderSuite{}) | |
| var _ = gc.Suite(&localHTTPSServerSuite{}) | |
| var _ = gc.Suite(&noSwiftSuite{}) | |
| func (s *ProviderSuite) SetUpTest(c *gc.C) { | |
| s.restoreTimeouts = envtesting.PatchAttemptStrategies(openstack.ShortAttempt, openstack.StorageAttempt) | |
| } | |
| func (s *ProviderSuite) TearDownTest(c *gc.C) { | |
| s.restoreTimeouts() | |
| } | |
| // Register tests to run against a test Openstack instance (service doubles). | |
| func registerLocalTests() { | |
| cred := &identity.Credentials{ | |
| User: "fred", | |
| Secrets: "secret", | |
| Region: "some-region", | |
| TenantName: "some tenant", | |
| } | |
| config := makeTestConfig(cred) | |
| config["agent-version"] = coretesting.FakeVersionNumber.String() | |
| config["authorized-keys"] = "fakekey" | |
| gc.Suite(&localLiveSuite{ | |
| LiveTests: LiveTests{ | |
| cred: cred, | |
| LiveTests: jujutest.LiveTests{ | |
| TestConfig: config, | |
| }, | |
| }, | |
| }) | |
| gc.Suite(&localServerSuite{ | |
| cred: cred, | |
| Tests: jujutest.Tests{ | |
| TestConfig: config, | |
| }, | |
| }) | |
| } | |
| // localServer is used to spin up a local Openstack service double. | |
| type localServer struct { | |
| Openstack *openstackservice.Openstack | |
| Nova *novaservice.Nova | |
| Neutron *neutronservice.Neutron | |
| restoreTimeouts func() | |
| UseTLS bool | |
| } | |
| type newOpenstackFunc func(*identity.Credentials, identity.AuthMode, bool) (*novaservice.Nova, *neutronservice.Neutron, []string) | |
| func (s *localServer) start( | |
| c *gc.C, cred *identity.Credentials, newOpenstackFunc newOpenstackFunc, | |
| ) { | |
| var logMsg []string | |
| s.Nova, s.Neutron, logMsg = newOpenstackFunc(cred, identity.AuthUserPass, s.UseTLS) | |
| for _, msg := range logMsg { | |
| c.Logf("%v", msg) | |
| } | |
| s.restoreTimeouts = envtesting.PatchAttemptStrategies(openstack.ShortAttempt, openstack.StorageAttempt) | |
| s.Nova.SetAvailabilityZones( | |
| nova.AvailabilityZone{Name: "test-unavailable"}, | |
| nova.AvailabilityZone{ | |
| Name: "test-available", | |
| State: nova.AvailabilityZoneState{ | |
| Available: true, | |
| }, | |
| }, | |
| ) | |
| } | |
| func (s *localServer) stop() { | |
| if s.Openstack != nil { | |
| s.Openstack.Stop() | |
| } else if s.Nova != nil { | |
| s.Nova.Stop() | |
| } | |
| s.restoreTimeouts() | |
| } | |
| // localLiveSuite runs tests from LiveTests using an Openstack service double. | |
| type localLiveSuite struct { | |
| coretesting.BaseSuite | |
| LiveTests | |
| srv localServer | |
| } | |
| func makeMockAdapter() *mockAdapter { | |
| volumes := make(map[string]*cinder.Volume) | |
| return &mockAdapter{ | |
| createVolume: func(args cinder.CreateVolumeVolumeParams) (*cinder.Volume, error) { | |
| metadata := args.Metadata.(map[string]string) | |
| volume := cinder.Volume{ | |
| ID: args.Name, | |
| Metadata: metadata, | |
| Status: "cool", | |
| } | |
| volumes[volume.ID] = &volume | |
| return &volume, nil | |
| }, | |
| getVolumesDetail: func() ([]cinder.Volume, error) { | |
| var result []cinder.Volume | |
| for _, volume := range volumes { | |
| result = append(result, *volume) | |
| } | |
| return result, nil | |
| }, | |
| getVolume: func(volumeId string) (*cinder.Volume, error) { | |
| if volume, ok := volumes[volumeId]; ok { | |
| return volume, nil | |
| } | |
| return nil, errors.New("not found") | |
| }, | |
| setVolumeMetadata: func(volumeId string, metadata map[string]string) (map[string]string, error) { | |
| if volume, ok := volumes[volumeId]; ok { | |
| for k, v := range metadata { | |
| volume.Metadata[k] = v | |
| } | |
| return volume.Metadata, nil | |
| } | |
| return nil, errors.New("not found") | |
| }, | |
| } | |
| } | |
| func overrideCinderProvider(c *gc.C, s *gitjujutesting.CleanupSuite, adapter *mockAdapter) { | |
| s.PatchValue(openstack.NewOpenstackStorage, func(*openstack.Environ) (openstack.OpenstackStorage, error) { | |
| return adapter, nil | |
| }) | |
| } | |
| func (s *localLiveSuite) SetUpSuite(c *gc.C) { | |
| s.BaseSuite.SetUpSuite(c) | |
| c.Logf("Running live tests using openstack service test double") | |
| s.srv.start(c, s.cred, newFullOpenstackService) | |
| // Set credentials to use when bootstrapping. Must be done after | |
| // starting server to get the auth URL. | |
| s.Credential = makeCredential(s.cred) | |
| s.CloudEndpoint = s.cred.URL | |
| s.CloudRegion = s.cred.Region | |
| s.LiveTests.SetUpSuite(c) | |
| openstack.UseTestImageData(openstack.ImageMetadataStorage(s.Env), s.cred) | |
| restoreFinishBootstrap := envtesting.DisableFinishBootstrap() | |
| s.AddCleanup(func(*gc.C) { restoreFinishBootstrap() }) | |
| overrideCinderProvider(c, &s.CleanupSuite, &mockAdapter{}) | |
| } | |
| func (s *localLiveSuite) TearDownSuite(c *gc.C) { | |
| openstack.RemoveTestImageData(openstack.ImageMetadataStorage(s.Env)) | |
| s.LiveTests.TearDownSuite(c) | |
| s.srv.stop() | |
| s.BaseSuite.TearDownSuite(c) | |
| } | |
| func (s *localLiveSuite) SetUpTest(c *gc.C) { | |
| s.BaseSuite.SetUpTest(c) | |
| s.LiveTests.SetUpTest(c) | |
| imagetesting.PatchOfficialDataSources(&s.CleanupSuite, "") | |
| } | |
| func (s *localLiveSuite) TearDownTest(c *gc.C) { | |
| s.LiveTests.TearDownTest(c) | |
| s.BaseSuite.TearDownTest(c) | |
| } | |
| // localServerSuite contains tests that run against an Openstack service double. | |
| // These tests can test things that would be unreasonably slow or expensive | |
| // to test on a live Openstack server. The service double is started and stopped for | |
| // each test. | |
| type localServerSuite struct { | |
| coretesting.BaseSuite | |
| jujutest.Tests | |
| cred *identity.Credentials | |
| srv localServer | |
| env environs.Environ | |
| toolsMetadataStorage envstorage.Storage | |
| imageMetadataStorage envstorage.Storage | |
| storageAdapter *mockAdapter | |
| } | |
| func (s *localServerSuite) SetUpSuite(c *gc.C) { | |
| s.BaseSuite.SetUpSuite(c) | |
| restoreFinishBootstrap := envtesting.DisableFinishBootstrap() | |
| s.AddCleanup(func(*gc.C) { restoreFinishBootstrap() }) | |
| c.Logf("Running local tests") | |
| } | |
| func (s *localServerSuite) SetUpTest(c *gc.C) { | |
| s.BaseSuite.SetUpTest(c) | |
| s.srv.start(c, s.cred, newFullOpenstackService) | |
| // Set credentials to use when bootstrapping. Must be done after | |
| // starting server to get the auth URL. | |
| s.Credential = makeCredential(s.cred) | |
| s.CloudEndpoint = s.cred.URL | |
| s.CloudRegion = s.cred.Region | |
| cl := client.NewClient(s.cred, identity.AuthUserPass, nil) | |
| err := cl.Authenticate() | |
| c.Assert(err, jc.ErrorIsNil) | |
| containerURL, err := cl.MakeServiceURL("object-store", "", nil) | |
| c.Assert(err, jc.ErrorIsNil) | |
| s.TestConfig = s.TestConfig.Merge(coretesting.Attrs{ | |
| "agent-metadata-url": containerURL + "/juju-dist-test/tools", | |
| "image-metadata-url": containerURL + "/juju-dist-test", | |
| "auth-url": s.cred.URL, | |
| }) | |
| s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber) | |
| s.Tests.SetUpTest(c) | |
| // For testing, we create a storage instance to which is uploaded tools and image metadata. | |
| s.env = s.Prepare(c) | |
| s.toolsMetadataStorage = openstack.MetadataStorage(s.env) | |
| // Put some fake metadata in place so that tests that are simply | |
| // starting instances without any need to check if those instances | |
| // are running can find the metadata. | |
| envtesting.UploadFakeTools(c, s.toolsMetadataStorage, s.env.Config().AgentStream(), s.env.Config().AgentStream()) | |
| s.imageMetadataStorage = openstack.ImageMetadataStorage(s.env) | |
| openstack.UseTestImageData(s.imageMetadataStorage, s.cred) | |
| s.storageAdapter = makeMockAdapter() | |
| overrideCinderProvider(c, &s.CleanupSuite, s.storageAdapter) | |
| } | |
| func (s *localServerSuite) TearDownTest(c *gc.C) { | |
| if s.imageMetadataStorage != nil { | |
| openstack.RemoveTestImageData(s.imageMetadataStorage) | |
| } | |
| if s.toolsMetadataStorage != nil { | |
| envtesting.RemoveFakeToolsMetadata(c, s.toolsMetadataStorage) | |
| } | |
| s.Tests.TearDownTest(c) | |
| s.srv.stop() | |
| s.BaseSuite.TearDownTest(c) | |
| } | |
| func (s *localServerSuite) openEnviron(c *gc.C, attrs coretesting.Attrs) environs.Environ { | |
| cfg, err := config.New(config.NoDefaults, s.TestConfig.Merge(attrs)) | |
| c.Assert(err, jc.ErrorIsNil) | |
| env, err := environs.New(environs.OpenParams{ | |
| Cloud: s.CloudSpec(), | |
| Config: cfg, | |
| }) | |
| c.Assert(err, jc.ErrorIsNil) | |
| return env | |
| } | |
| func (s *localServerSuite) TestBootstrap(c *gc.C) { | |
| // Tests uses Prepare, so destroy first. | |
| err := environs.Destroy(s.env.Config().Name(), s.env, s.ControllerStore) | |
| c.Assert(err, jc.ErrorIsNil) | |
| s.Tests.TestBootstrap(c) | |
| } | |
| func (s *localServerSuite) TestStartStop(c *gc.C) { | |
| // Tests uses Prepare, so destroy first. | |
| err := environs.Destroy(s.env.Config().Name(), s.env, s.ControllerStore) | |
| c.Assert(err, jc.ErrorIsNil) | |
| s.Tests.TestStartStop(c) | |
| } | |
| // If the bootstrap node is configured to require a public IP address, | |
| // bootstrapping fails if an address cannot be allocated. | |
| func (s *localServerSuite) TestBootstrapFailsWhenPublicIPError(c *gc.C) { | |
| coretesting.SkipIfPPC64EL(c, "lp:1425242") | |
| cleanup := s.srv.Neutron.RegisterControlPoint( | |
| "addFloatingIP", | |
| func(sc hook.ServiceControl, args ...interface{}) error { | |
| return fmt.Errorf("failed on purpose") | |
| }, | |
| ) | |
| defer cleanup() | |
| err := environs.Destroy(s.env.Config().Name(), s.env, s.ControllerStore) | |
| c.Assert(err, jc.ErrorIsNil) | |
| env := s.openEnviron(c, coretesting.Attrs{"use-floating-ip": true}) | |
| err = bootstrapEnv(c, env) | |
| c.Assert(err, gc.ErrorMatches, "(.|\n)*cannot allocate a public IP as needed(.|\n)*") | |
| } | |
| func (s *localServerSuite) TestAddressesWithPublicIP(c *gc.C) { | |
| // Floating IP address is 10.0.0.1 | |
| bootstrapFinished := false | |
| s.PatchValue(&common.FinishBootstrap, func( | |
| ctx environs.BootstrapContext, | |
| client ssh.Client, | |
| env environs.Environ, | |
| inst instance.Instance, | |
| instanceConfig *instancecfg.InstanceConfig, | |
| _ environs.BootstrapDialOpts, | |
| ) error { | |
| addr, err := inst.Addresses() | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(addr, jc.SameContents, []network.Address{ | |
| {Value: "10.0.0.1", Type: "ipv4", Scope: "public"}, | |
| {Value: "127.0.0.1", Type: "ipv4", Scope: "local-machine"}, | |
| {Value: "::face::000f", Type: "hostname", Scope: ""}, | |
| {Value: "127.10.0.1", Type: "ipv4", Scope: "public"}, | |
| {Value: "::dead:beef:f00d", Type: "ipv6", Scope: "public"}, | |
| }) | |
| bootstrapFinished = true | |
| return nil | |
| }) | |
| env := s.openEnviron(c, coretesting.Attrs{"use-floating-ip": true}) | |
| err := bootstrapEnv(c, env) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(bootstrapFinished, jc.IsTrue) | |
| } | |
| func (s *localServerSuite) TestAddressesWithoutPublicIP(c *gc.C) { | |
| bootstrapFinished := false | |
| s.PatchValue(&common.FinishBootstrap, func( | |
| ctx environs.BootstrapContext, | |
| client ssh.Client, | |
| env environs.Environ, | |
| inst instance.Instance, | |
| instanceConfig *instancecfg.InstanceConfig, | |
| _ environs.BootstrapDialOpts, | |
| ) error { | |
| addr, err := inst.Addresses() | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(addr, jc.SameContents, []network.Address{ | |
| {Value: "127.0.0.1", Type: "ipv4", Scope: "local-machine"}, | |
| {Value: "::face::000f", Type: "hostname", Scope: ""}, | |
| {Value: "127.10.0.1", Type: "ipv4", Scope: "public"}, | |
| {Value: "::dead:beef:f00d", Type: "ipv6", Scope: "public"}, | |
| }) | |
| bootstrapFinished = true | |
| return nil | |
| }) | |
| env := s.openEnviron(c, coretesting.Attrs{"use-floating-ip": false}) | |
| err := bootstrapEnv(c, env) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(bootstrapFinished, jc.IsTrue) | |
| } | |
| // If the environment is configured not to require a public IP address for nodes, | |
| // bootstrapping and starting an instance should occur without any attempt to | |
| // allocate a public address. | |
| func (s *localServerSuite) TestStartInstanceWithoutPublicIP(c *gc.C) { | |
| cleanup := s.srv.Neutron.RegisterControlPoint( | |
| "addFloatingIP", | |
| func(sc hook.ServiceControl, args ...interface{}) error { | |
| return fmt.Errorf("add floating IP should not have been called") | |
| }, | |
| ) | |
| defer cleanup() | |
| cleanup = s.srv.Nova.RegisterControlPoint( | |
| "addServerFloatingIP", | |
| func(sc hook.ServiceControl, args ...interface{}) error { | |
| return fmt.Errorf("add server floating IP should not have been called") | |
| }, | |
| ) | |
| defer cleanup() | |
| err := environs.Destroy(s.env.Config().Name(), s.env, s.ControllerStore) | |
| c.Assert(err, jc.ErrorIsNil) | |
| s.TestConfig["use-floating-ip"] = false | |
| env := s.Prepare(c) | |
| err = bootstrapEnv(c, env) | |
| c.Assert(err, jc.ErrorIsNil) | |
| inst, _ := testing.AssertStartInstance(c, env, s.ControllerUUID, "100") | |
| err = env.StopInstances(inst.Id()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| } | |
| func (s *localServerSuite) TestStartInstanceHardwareCharacteristics(c *gc.C) { | |
| // Ensure amd64 tools are available, to ensure an amd64 image. | |
| amd64Version := version.Binary{ | |
| Number: jujuversion.Current, | |
| Arch: arch.AMD64, | |
| } | |
| for _, series := range series.SupportedSeries() { | |
| amd64Version.Series = series | |
| envtesting.AssertUploadFakeToolsVersions( | |
| c, s.toolsMetadataStorage, s.env.Config().AgentStream(), s.env.Config().AgentStream(), amd64Version) | |
| } | |
| err := environs.Destroy(s.env.Config().Name(), s.env, s.ControllerStore) | |
| c.Assert(err, jc.ErrorIsNil) | |
| env := s.Prepare(c) | |
| err = bootstrapEnv(c, env) | |
| c.Assert(err, jc.ErrorIsNil) | |
| _, hc := testing.AssertStartInstanceWithConstraints(c, env, s.ControllerUUID, "100", constraints.MustParse("mem=1024")) | |
| c.Check(*hc.Arch, gc.Equals, "amd64") | |
| c.Check(*hc.Mem, gc.Equals, uint64(2048)) | |
| c.Check(*hc.CpuCores, gc.Equals, uint64(1)) | |
| c.Assert(hc.CpuPower, gc.IsNil) | |
| } | |
| func (s *localServerSuite) TestInstanceName(c *gc.C) { | |
| inst, _ := testing.AssertStartInstance(c, s.env, s.ControllerUUID, "100") | |
| serverDetail := openstack.InstanceServerDetail(inst) | |
| envName := s.env.Config().Name() | |
| c.Assert(serverDetail.Name, gc.Matches, "juju-06f00d-"+envName+"-100") | |
| } | |
| func (s *localServerSuite) TestStartInstanceNetwork(c *gc.C) { | |
| cfg, err := s.env.Config().Apply(coretesting.Attrs{ | |
| // A label that corresponds to a neutron test service network | |
| "network": "net", | |
| }) | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = s.env.SetConfig(cfg) | |
| c.Assert(err, jc.ErrorIsNil) | |
| inst, _ := testing.AssertStartInstance(c, s.env, s.ControllerUUID, "100") | |
| err = s.env.StopInstances(inst.Id()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| } | |
| func (s *localServerSuite) TestStartInstanceExternalNetwork(c *gc.C) { | |
| cfg, err := s.env.Config().Apply(coretesting.Attrs{ | |
| // A label that corresponds to a neutron test service external network | |
| "external-network": "ext-net", | |
| "use-floating-ip": true, | |
| }) | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = s.env.SetConfig(cfg) | |
| c.Assert(err, jc.ErrorIsNil) | |
| inst, _ := testing.AssertStartInstance(c, s.env, s.ControllerUUID, "100") | |
| err = s.env.StopInstances(inst.Id()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| } | |
| func (s *localServerSuite) TestStartInstanceNetworkUnknownLabel(c *gc.C) { | |
| cfg, err := s.env.Config().Apply(coretesting.Attrs{ | |
| // A label that has no related network in the neutron test service | |
| "network": "no-network-with-this-label", | |
| }) | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = s.env.SetConfig(cfg) | |
| c.Assert(err, jc.ErrorIsNil) | |
| inst, _, _, err := testing.StartInstance(s.env, s.ControllerUUID, "100") | |
| c.Check(inst, gc.IsNil) | |
| c.Assert(err, gc.ErrorMatches, "no networks exist with label .*") | |
| } | |
| func (s *localServerSuite) TestStartInstanceExternalNetworkUnknownLabel(c *gc.C) { | |
| cfg, err := s.env.Config().Apply(coretesting.Attrs{ | |
| // A label that has no related network in the neutron test service | |
| "external-network": "no-network-with-this-label", | |
| "use-floating-ip": true, | |
| }) | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = s.env.SetConfig(cfg) | |
| c.Assert(err, jc.ErrorIsNil) | |
| inst, _, _, err := testing.StartInstance(s.env, s.ControllerUUID, "100") | |
| err = s.env.StopInstances(inst.Id()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| } | |
| func (s *localServerSuite) TestStartInstanceNetworkUnknownId(c *gc.C) { | |
| cfg, err := s.env.Config().Apply(coretesting.Attrs{ | |
| // A valid UUID but no related network in the nova test service | |
| "network": "f81d4fae-7dec-11d0-a765-00a0c91e6bf6", | |
| }) | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = s.env.SetConfig(cfg) | |
| c.Assert(err, jc.ErrorIsNil) | |
| inst, _, _, err := testing.StartInstance(s.env, s.ControllerUUID, "100") | |
| c.Check(inst, gc.IsNil) | |
| c.Assert(err, gc.ErrorMatches, "cannot run instance: (\\n|.)*"+ | |
| "caused by: "+ | |
| "request \\(.*/servers\\) returned unexpected status: "+ | |
| "404; error info: .*itemNotFound.*") | |
| } | |
| func assertSecurityGroups(c *gc.C, env environs.Environ, expected []string) { | |
| neutronClient := openstack.GetNeutronClient(env) | |
| groups, err := neutronClient.ListSecurityGroupsV2() | |
| c.Assert(err, jc.ErrorIsNil) | |
| for _, name := range expected { | |
| found := false | |
| for _, group := range groups { | |
| if group.Name == name { | |
| found = true | |
| break | |
| } | |
| } | |
| if !found { | |
| c.Errorf("expected security group %q not found", name) | |
| } | |
| } | |
| for _, group := range groups { | |
| found := false | |
| for _, name := range expected { | |
| if group.Name == name { | |
| found = true | |
| break | |
| } | |
| } | |
| if !found { | |
| c.Errorf("existing security group %q is not expected", group.Name) | |
| } | |
| } | |
| } | |
| func assertInstanceIds(c *gc.C, env environs.Environ, expected ...instance.Id) { | |
| insts, err := env.AllInstances() | |
| c.Assert(err, jc.ErrorIsNil) | |
| instIds := make([]instance.Id, len(insts)) | |
| for i, inst := range insts { | |
| instIds[i] = inst.Id() | |
| } | |
| c.Assert(instIds, jc.SameContents, expected) | |
| } | |
| func (s *localServerSuite) TestStopInstance(c *gc.C) { | |
| env := s.openEnviron(c, coretesting.Attrs{"firewall-mode": config.FwInstance}) | |
| instanceName := "100" | |
| inst, _ := testing.AssertStartInstance(c, env, s.ControllerUUID, instanceName) | |
| // Openstack now has three security groups for the server, the default | |
| // group, one group for the entire environment, and another for the | |
| // new instance. | |
| modelUUID := env.Config().UUID() | |
| allSecurityGroups := []string{ | |
| "default", fmt.Sprintf("juju-%v-%v", s.ControllerUUID, modelUUID), | |
| fmt.Sprintf("juju-%v-%v-%v", s.ControllerUUID, modelUUID, instanceName), | |
| } | |
| assertSecurityGroups(c, env, allSecurityGroups) | |
| err := env.StopInstances(inst.Id()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| // The security group for this instance is now removed. | |
| assertSecurityGroups(c, env, []string{ | |
| "default", fmt.Sprintf("juju-%v-%v", s.ControllerUUID, modelUUID), | |
| }) | |
| } | |
| // Due to bug #1300755 it can happen that the security group intended for | |
| // an instance is also used as the common security group of another | |
| // environment. If this is the case, the attempt to delete the instance's | |
| // security group fails but StopInstance succeeds. | |
| func (s *localServerSuite) TestStopInstanceSecurityGroupNotDeleted(c *gc.C) { | |
| coretesting.SkipIfPPC64EL(c, "lp:1425242") | |
| // Force an error when a security group is deleted. | |
| cleanup := s.srv.Neutron.RegisterControlPoint( | |
| "removeSecurityGroup", | |
| func(sc hook.ServiceControl, args ...interface{}) error { | |
| return fmt.Errorf("failed on purpose") | |
| }, | |
| ) | |
| defer cleanup() | |
| env := s.openEnviron(c, coretesting.Attrs{"firewall-mode": config.FwInstance}) | |
| instanceName := "100" | |
| inst, _ := testing.AssertStartInstance(c, env, s.ControllerUUID, instanceName) | |
| modelUUID := env.Config().UUID() | |
| allSecurityGroups := []string{ | |
| "default", fmt.Sprintf("juju-%v-%v", s.ControllerUUID, modelUUID), | |
| fmt.Sprintf("juju-%v-%v-%v", s.ControllerUUID, modelUUID, instanceName), | |
| } | |
| assertSecurityGroups(c, env, allSecurityGroups) | |
| // Make time advance in zero time | |
| clk := gitjujutesting.NewClock(time.Time{}) | |
| clock := gitjujutesting.AutoAdvancingClock{clk, clk.Advance} | |
| env.(*openstack.Environ).SetClock(&clock) | |
| err := env.StopInstances(inst.Id()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| assertSecurityGroups(c, env, allSecurityGroups) | |
| } | |
| func (s *localServerSuite) TestDestroyEnvironmentDeletesSecurityGroupsFWModeInstance(c *gc.C) { | |
| env := s.openEnviron(c, coretesting.Attrs{"firewall-mode": config.FwInstance}) | |
| instanceName := "100" | |
| testing.AssertStartInstance(c, env, s.ControllerUUID, instanceName) | |
| modelUUID := env.Config().UUID() | |
| allSecurityGroups := []string{ | |
| "default", fmt.Sprintf("juju-%v-%v", s.ControllerUUID, modelUUID), | |
| fmt.Sprintf("juju-%v-%v-%v", s.ControllerUUID, modelUUID, instanceName), | |
| } | |
| assertSecurityGroups(c, env, allSecurityGroups) | |
| err := env.Destroy() | |
| c.Check(err, jc.ErrorIsNil) | |
| assertSecurityGroups(c, env, []string{"default"}) | |
| } | |
| func (s *localServerSuite) TestDestroyEnvironmentDeletesSecurityGroupsFWModeGlobal(c *gc.C) { | |
| env := s.openEnviron(c, coretesting.Attrs{"firewall-mode": config.FwGlobal}) | |
| instanceName := "100" | |
| testing.AssertStartInstance(c, env, s.ControllerUUID, instanceName) | |
| modelUUID := env.Config().UUID() | |
| allSecurityGroups := []string{ | |
| "default", fmt.Sprintf("juju-%v-%v", s.ControllerUUID, modelUUID), | |
| fmt.Sprintf("juju-%v-%v-global", s.ControllerUUID, modelUUID), | |
| } | |
| assertSecurityGroups(c, env, allSecurityGroups) | |
| err := env.Destroy() | |
| c.Check(err, jc.ErrorIsNil) | |
| assertSecurityGroups(c, env, []string{"default"}) | |
| } | |
| func (s *localServerSuite) TestDestroyController(c *gc.C) { | |
| env := s.openEnviron(c, coretesting.Attrs{"uuid": utils.MustNewUUID().String()}) | |
| controllerEnv := s.env | |
| controllerInstanceName := "100" | |
| testing.AssertStartInstance(c, controllerEnv, s.ControllerUUID, controllerInstanceName) | |
| hostedModelInstanceName := "200" | |
| testing.AssertStartInstance(c, env, s.ControllerUUID, hostedModelInstanceName) | |
| modelUUID := env.Config().UUID() | |
| allControllerSecurityGroups := []string{ | |
| "default", fmt.Sprintf("juju-%v-%v", s.ControllerUUID, controllerEnv.Config().UUID()), | |
| fmt.Sprintf("juju-%v-%v-%v", s.ControllerUUID, controllerEnv.Config().UUID(), controllerInstanceName), | |
| } | |
| allHostedModelSecurityGroups := []string{ | |
| "default", fmt.Sprintf("juju-%v-%v", s.ControllerUUID, modelUUID), | |
| fmt.Sprintf("juju-%v-%v-%v", s.ControllerUUID, modelUUID, hostedModelInstanceName), | |
| } | |
| assertSecurityGroups(c, controllerEnv, append( | |
| allControllerSecurityGroups, allHostedModelSecurityGroups..., | |
| )) | |
| err := controllerEnv.DestroyController(s.ControllerUUID) | |
| c.Check(err, jc.ErrorIsNil) | |
| assertSecurityGroups(c, controllerEnv, []string{"default"}) | |
| assertInstanceIds(c, env) | |
| assertInstanceIds(c, controllerEnv) | |
| } | |
| func (s *localServerSuite) TestDestroyHostedModel(c *gc.C) { | |
| env := s.openEnviron(c, coretesting.Attrs{"uuid": utils.MustNewUUID().String()}) | |
| controllerEnv := s.env | |
| controllerInstanceName := "100" | |
| controllerInstance, _ := testing.AssertStartInstance(c, controllerEnv, s.ControllerUUID, controllerInstanceName) | |
| hostedModelInstanceName := "200" | |
| testing.AssertStartInstance(c, env, s.ControllerUUID, hostedModelInstanceName) | |
| modelUUID := env.Config().UUID() | |
| allControllerSecurityGroups := []string{ | |
| "default", fmt.Sprintf("juju-%v-%v", s.ControllerUUID, controllerEnv.Config().UUID()), | |
| fmt.Sprintf("juju-%v-%v-%v", s.ControllerUUID, controllerEnv.Config().UUID(), controllerInstanceName), | |
| } | |
| allHostedModelSecurityGroups := []string{ | |
| "default", fmt.Sprintf("juju-%v-%v", s.ControllerUUID, modelUUID), | |
| fmt.Sprintf("juju-%v-%v-%v", s.ControllerUUID, modelUUID, hostedModelInstanceName), | |
| } | |
| assertSecurityGroups(c, controllerEnv, append( | |
| allControllerSecurityGroups, allHostedModelSecurityGroups..., | |
| )) | |
| err := env.Destroy() | |
| c.Check(err, jc.ErrorIsNil) | |
| assertSecurityGroups(c, controllerEnv, allControllerSecurityGroups) | |
| assertInstanceIds(c, env) | |
| assertInstanceIds(c, controllerEnv, controllerInstance.Id()) | |
| } | |
| var instanceGathering = []struct { | |
| ids []instance.Id | |
| err error | |
| }{ | |
| {ids: []instance.Id{"id0"}}, | |
| {ids: []instance.Id{"id0", "id0"}}, | |
| {ids: []instance.Id{"id0", "id1"}}, | |
| {ids: []instance.Id{"id1", "id0"}}, | |
| {ids: []instance.Id{"id1", "id0", "id1"}}, | |
| { | |
| ids: []instance.Id{""}, | |
| err: environs.ErrNoInstances, | |
| }, | |
| { | |
| ids: []instance.Id{"", ""}, | |
| err: environs.ErrNoInstances, | |
| }, | |
| { | |
| ids: []instance.Id{"", "", ""}, | |
| err: environs.ErrNoInstances, | |
| }, | |
| { | |
| ids: []instance.Id{"id0", ""}, | |
| err: environs.ErrPartialInstances, | |
| }, | |
| { | |
| ids: []instance.Id{"", "id1"}, | |
| err: environs.ErrPartialInstances, | |
| }, | |
| { | |
| ids: []instance.Id{"id0", "id1", ""}, | |
| err: environs.ErrPartialInstances, | |
| }, | |
| { | |
| ids: []instance.Id{"id0", "", "id0"}, | |
| err: environs.ErrPartialInstances, | |
| }, | |
| { | |
| ids: []instance.Id{"id0", "id0", ""}, | |
| err: environs.ErrPartialInstances, | |
| }, | |
| { | |
| ids: []instance.Id{"", "id0", "id1"}, | |
| err: environs.ErrPartialInstances, | |
| }, | |
| } | |
| func (s *localServerSuite) TestInstanceStatus(c *gc.C) { | |
| // goose's test service always returns ACTIVE state. | |
| inst, _ := testing.AssertStartInstance(c, s.env, s.ControllerUUID, "100") | |
| c.Assert(inst.Status().Status, gc.Equals, status.Running) | |
| err := s.env.StopInstances(inst.Id()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| } | |
| func (s *localServerSuite) TestAllInstancesFloatingIP(c *gc.C) { | |
| env := s.openEnviron(c, coretesting.Attrs{"use-floating-ip": true}) | |
| inst0, _ := testing.AssertStartInstance(c, env, s.ControllerUUID, "100") | |
| inst1, _ := testing.AssertStartInstance(c, env, s.ControllerUUID, "101") | |
| defer func() { | |
| err := env.StopInstances(inst0.Id(), inst1.Id()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| }() | |
| insts, err := env.AllInstances() | |
| c.Assert(err, jc.ErrorIsNil) | |
| for _, inst := range insts { | |
| c.Assert(*openstack.InstanceFloatingIP(inst), gc.Equals, fmt.Sprintf("10.0.0.%v", inst.Id())) | |
| } | |
| } | |
| func (s *localServerSuite) assertInstancesGathering(c *gc.C, withFloatingIP bool) { | |
| env := s.openEnviron(c, coretesting.Attrs{"use-floating-ip": withFloatingIP}) | |
| inst0, _ := testing.AssertStartInstance(c, env, s.ControllerUUID, "100") | |
| id0 := inst0.Id() | |
| inst1, _ := testing.AssertStartInstance(c, env, s.ControllerUUID, "101") | |
| id1 := inst1.Id() | |
| defer func() { | |
| err := env.StopInstances(inst0.Id(), inst1.Id()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| }() | |
| for i, test := range instanceGathering { | |
| c.Logf("test %d: find %v -> expect len %d, err: %v", i, test.ids, len(test.ids), test.err) | |
| ids := make([]instance.Id, len(test.ids)) | |
| for j, id := range test.ids { | |
| switch id { | |
| case "id0": | |
| ids[j] = id0 | |
| case "id1": | |
| ids[j] = id1 | |
| } | |
| } | |
| insts, err := env.Instances(ids) | |
| c.Assert(err, gc.Equals, test.err) | |
| if err == environs.ErrNoInstances { | |
| c.Assert(insts, gc.HasLen, 0) | |
| } else { | |
| c.Assert(insts, gc.HasLen, len(test.ids)) | |
| } | |
| for j, inst := range insts { | |
| if ids[j] != "" { | |
| c.Assert(inst.Id(), gc.Equals, ids[j]) | |
| if withFloatingIP { | |
| c.Assert(*openstack.InstanceFloatingIP(inst), gc.Equals, fmt.Sprintf("10.0.0.%v", inst.Id())) | |
| } else { | |
| c.Assert(openstack.InstanceFloatingIP(inst), gc.IsNil) | |
| } | |
| } else { | |
| c.Assert(inst, gc.IsNil) | |
| } | |
| } | |
| } | |
| } | |
| func (s *localServerSuite) TestInstancesGathering(c *gc.C) { | |
| s.assertInstancesGathering(c, false) | |
| } | |
| func (s *localServerSuite) TestInstancesGatheringWithFloatingIP(c *gc.C) { | |
| s.assertInstancesGathering(c, true) | |
| } | |
| func (s *localServerSuite) TestInstancesBuildSpawning(c *gc.C) { | |
| coretesting.SkipIfPPC64EL(c, "lp:1425242") | |
| cleanup := s.srv.Nova.RegisterControlPoint( | |
| "addServer", | |
| func(sc hook.ServiceControl, args ...interface{}) error { | |
| details := args[0].(*nova.ServerDetail) | |
| details.Status = nova.StatusBuildSpawning | |
| return nil | |
| }, | |
| ) | |
| defer cleanup() | |
| stateInst, _ := testing.AssertStartInstance(c, s.env, s.ControllerUUID, "100") | |
| defer func() { | |
| err := s.env.StopInstances(stateInst.Id()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| }() | |
| instances, err := s.env.Instances([]instance.Id{stateInst.Id()}) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(instances, gc.HasLen, 1) | |
| c.Assert(instances[0].Status().Message, gc.Equals, nova.StatusBuildSpawning) | |
| } | |
| func (s *localServerSuite) TestInstancesShutoffSuspended(c *gc.C) { | |
| coretesting.SkipIfPPC64EL(c, "lp:1425242") | |
| cleanup := s.srv.Nova.RegisterControlPoint( | |
| "addServer", | |
| func(sc hook.ServiceControl, args ...interface{}) error { | |
| details := args[0].(*nova.ServerDetail) | |
| switch { | |
| case strings.HasSuffix(details.Name, "-100"): | |
| details.Status = nova.StatusShutoff | |
| case strings.HasSuffix(details.Name, "-101"): | |
| details.Status = nova.StatusSuspended | |
| default: | |
| c.Fatalf("unexpected instance details: %#v", details) | |
| } | |
| return nil | |
| }, | |
| ) | |
| defer cleanup() | |
| stateInst1, _ := testing.AssertStartInstance(c, s.env, s.ControllerUUID, "100") | |
| stateInst2, _ := testing.AssertStartInstance(c, s.env, s.ControllerUUID, "101") | |
| defer func() { | |
| err := s.env.StopInstances(stateInst1.Id(), stateInst2.Id()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| }() | |
| instances, err := s.env.Instances([]instance.Id{stateInst1.Id(), stateInst2.Id()}) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(instances, gc.HasLen, 2) | |
| c.Assert(instances[0].Status().Message, gc.Equals, nova.StatusShutoff) | |
| c.Assert(instances[1].Status().Message, gc.Equals, nova.StatusSuspended) | |
| } | |
| func (s *localServerSuite) TestInstancesErrorResponse(c *gc.C) { | |
| coretesting.SkipIfPPC64EL(c, "lp:1425242") | |
| cleanup := s.srv.Nova.RegisterControlPoint( | |
| "server", | |
| func(sc hook.ServiceControl, args ...interface{}) error { | |
| return fmt.Errorf("strange error not instance") | |
| }, | |
| ) | |
| defer cleanup() | |
| instances, err := s.env.Instances([]instance.Id{"1"}) | |
| c.Check(instances, gc.IsNil) | |
| c.Assert(err, gc.ErrorMatches, "(?s).*strange error not instance.*") | |
| } | |
| func (s *localServerSuite) TestInstancesMultiErrorResponse(c *gc.C) { | |
| coretesting.SkipIfPPC64EL(c, "lp:1425242") | |
| cleanup := s.srv.Nova.RegisterControlPoint( | |
| "matchServers", | |
| func(sc hook.ServiceControl, args ...interface{}) error { | |
| return fmt.Errorf("strange error no instances") | |
| }, | |
| ) | |
| defer cleanup() | |
| instances, err := s.env.Instances([]instance.Id{"1", "2"}) | |
| c.Check(instances, gc.IsNil) | |
| c.Assert(err, gc.ErrorMatches, "(?s).*strange error no instances.*") | |
| } | |
| // TODO (wallyworld) - this test was copied from the ec2 provider. | |
| // It should be moved to environs.jujutests.Tests. | |
| func (s *localServerSuite) TestBootstrapInstanceUserDataAndState(c *gc.C) { | |
| err := bootstrapEnv(c, s.env) | |
| c.Assert(err, jc.ErrorIsNil) | |
| // Check that ControllerInstances returns the ID of the bootstrap machine. | |
| ids, err := s.env.ControllerInstances(s.ControllerUUID) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(ids, gc.HasLen, 1) | |
| insts, err := s.env.AllInstances() | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(insts, gc.HasLen, 1) | |
| c.Check(insts[0].Id(), gc.Equals, ids[0]) | |
| addresses, err := insts[0].Addresses() | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(addresses, gc.Not(gc.HasLen), 0) | |
| // TODO(wallyworld) - 2013-03-01 bug=1137005 | |
| // The nova test double needs to be updated to support retrieving instance userData. | |
| // Until then, we can't check the cloud init script was generated correctly. | |
| // When we can, we should also check cloudinit for a non-manager node (as in the | |
| // ec2 tests). | |
| } | |
| func (s *localServerSuite) assertGetImageMetadataSources(c *gc.C, stream, officialSourcePath string) { | |
| // Create a config that matches s.TestConfig but with the specified stream. | |
| attrs := coretesting.Attrs{} | |
| if stream != "" { | |
| attrs = coretesting.Attrs{"image-stream": stream} | |
| } | |
| env := s.openEnviron(c, attrs) | |
| sources, err := environs.ImageMetadataSources(env) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(sources, gc.HasLen, 4) | |
| var urls = make([]string, len(sources)) | |
| for i, source := range sources { | |
| url, err := source.URL("") | |
| c.Assert(err, jc.ErrorIsNil) | |
| urls[i] = url | |
| } | |
| // The image-metadata-url ends with "/juju-dist-test/". | |
| c.Check(strings.HasSuffix(urls[0], "/juju-dist-test/"), jc.IsTrue) | |
| // The product-streams URL ends with "/imagemetadata". | |
| c.Check(strings.HasSuffix(urls[1], "/imagemetadata/"), jc.IsTrue) | |
| c.Assert(urls[2], gc.Equals, fmt.Sprintf("https://streams.canonical.com/juju/images/%s/", officialSourcePath)) | |
| c.Assert(urls[3], gc.Equals, fmt.Sprintf("http://cloud-images.ubuntu.com/%s/", officialSourcePath)) | |
| } | |
| func (s *localServerSuite) TestGetImageMetadataSources(c *gc.C) { | |
| s.assertGetImageMetadataSources(c, "", "releases") | |
| s.assertGetImageMetadataSources(c, "released", "releases") | |
| s.assertGetImageMetadataSources(c, "daily", "daily") | |
| } | |
| func (s *localServerSuite) TestGetImageMetadataSourcesNoProductStreams(c *gc.C) { | |
| s.PatchValue(openstack.MakeServiceURL, func(client.AuthenticatingClient, string, string, []string) (string, error) { | |
| return "", errors.New("cannae do it captain") | |
| }) | |
| env := s.Open(c, s.env.Config()) | |
| sources, err := environs.ImageMetadataSources(env) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(sources, gc.HasLen, 3) | |
| // Check that data sources are in the right order | |
| c.Check(sources[0].Description(), gc.Equals, "image-metadata-url") | |
| c.Check(sources[1].Description(), gc.Equals, "default cloud images") | |
| c.Check(sources[2].Description(), gc.Equals, "default ubuntu cloud images") | |
| } | |
| func (s *localServerSuite) TestGetToolsMetadataSources(c *gc.C) { | |
| s.PatchValue(&tools.DefaultBaseURL, "") | |
| env := s.Open(c, s.env.Config()) | |
| sources, err := tools.GetMetadataSources(env) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(sources, gc.HasLen, 2) | |
| var urls = make([]string, len(sources)) | |
| for i, source := range sources { | |
| url, err := source.URL("") | |
| c.Assert(err, jc.ErrorIsNil) | |
| urls[i] = url | |
| } | |
| // The agent-metadata-url ends with "/juju-dist-test/tools/". | |
| c.Check(strings.HasSuffix(urls[0], "/juju-dist-test/tools/"), jc.IsTrue) | |
| // Check that the URL from keystone parses. | |
| _, err = url.Parse(urls[1]) | |
| c.Assert(err, jc.ErrorIsNil) | |
| } | |
| func (s *localServerSuite) TestSupportsNetworking(c *gc.C) { | |
| env := s.Open(c, s.env.Config()) | |
| _, ok := environs.SupportsNetworking(env) | |
| c.Assert(ok, jc.IsFalse) | |
| } | |
| func (s *localServerSuite) TestFindImageBadDefaultImage(c *gc.C) { | |
| imagetesting.PatchOfficialDataSources(&s.CleanupSuite, "") | |
| env := s.Open(c, s.env.Config()) | |
| // An error occurs if no suitable image is found. | |
| _, err := openstack.FindInstanceSpec(env, "saucy", "amd64", "mem=1G", nil) | |
| c.Assert(err, gc.ErrorMatches, `no "saucy" images in some-region with arches \[amd64\]`) | |
| } | |
| func (s *localServerSuite) TestConstraintsValidator(c *gc.C) { | |
| env := s.Open(c, s.env.Config()) | |
| validator, err := env.ConstraintsValidator() | |
| c.Assert(err, jc.ErrorIsNil) | |
| cons := constraints.MustParse("arch=amd64 cpu-power=10 virt-type=lxd") | |
| unsupported, err := validator.Validate(cons) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(unsupported, jc.SameContents, []string{"cpu-power"}) | |
| } | |
| func (s *localServerSuite) TestConstraintsValidatorVocab(c *gc.C) { | |
| env := s.Open(c, s.env.Config()) | |
| validator, err := env.ConstraintsValidator() | |
| c.Assert(err, jc.ErrorIsNil) | |
| cons := constraints.MustParse("instance-type=foo") | |
| _, err = validator.Validate(cons) | |
| c.Assert(err, gc.ErrorMatches, "invalid constraint value: instance-type=foo\nvalid values are:.*") | |
| cons = constraints.MustParse("virt-type=foo") | |
| _, err = validator.Validate(cons) | |
| c.Assert(err, gc.ErrorMatches, regexp.QuoteMeta("invalid constraint value: virt-type=foo\nvalid values are: [kvm lxd]")) | |
| } | |
| func (s *localServerSuite) TestConstraintsMerge(c *gc.C) { | |
| env := s.Open(c, s.env.Config()) | |
| validator, err := env.ConstraintsValidator() | |
| c.Assert(err, jc.ErrorIsNil) | |
| consA := constraints.MustParse("arch=amd64 mem=1G root-disk=10G") | |
| consB := constraints.MustParse("instance-type=m1.small") | |
| cons, err := validator.Merge(consA, consB) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(cons, gc.DeepEquals, constraints.MustParse("arch=amd64 instance-type=m1.small")) | |
| } | |
| func (s *localServerSuite) TestFindImageInstanceConstraint(c *gc.C) { | |
| env := s.Open(c, s.env.Config()) | |
| imageMetadata := []*imagemetadata.ImageMetadata{{ | |
| Id: "image-id", | |
| Arch: "amd64", | |
| }} | |
| spec, err := openstack.FindInstanceSpec( | |
| env, series.LatestLts(), "amd64", "instance-type=m1.tiny", | |
| imageMetadata, | |
| ) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(spec.InstanceType.Name, gc.Equals, "m1.tiny") | |
| } | |
| func (s *localServerSuite) TestFindInstanceImageConstraintHypervisor(c *gc.C) { | |
| testVirtType := "qemu" | |
| env := s.Open(c, s.env.Config()) | |
| imageMetadata := []*imagemetadata.ImageMetadata{{ | |
| Id: "image-id", | |
| Arch: "amd64", | |
| VirtType: testVirtType, | |
| }} | |
| spec, err := openstack.FindInstanceSpec( | |
| env, series.LatestLts(), "amd64", "virt-type="+testVirtType, | |
| imageMetadata, | |
| ) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(spec.InstanceType.VirtType, gc.NotNil) | |
| c.Assert(*spec.InstanceType.VirtType, gc.Equals, testVirtType) | |
| c.Assert(spec.InstanceType.Name, gc.Equals, "m1.small") | |
| } | |
| func (s *localServerSuite) TestFindInstanceImageWithHypervisorNoConstraint(c *gc.C) { | |
| testVirtType := "qemu" | |
| env := s.Open(c, s.env.Config()) | |
| imageMetadata := []*imagemetadata.ImageMetadata{{ | |
| Id: "image-id", | |
| Arch: "amd64", | |
| VirtType: testVirtType, | |
| }} | |
| spec, err := openstack.FindInstanceSpec( | |
| env, series.LatestLts(), "amd64", "", | |
| imageMetadata, | |
| ) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(spec.InstanceType.VirtType, gc.NotNil) | |
| c.Assert(*spec.InstanceType.VirtType, gc.Equals, testVirtType) | |
| c.Assert(spec.InstanceType.Name, gc.Equals, "m1.small") | |
| } | |
| func (s *localServerSuite) TestFindInstanceNoConstraint(c *gc.C) { | |
| env := s.Open(c, s.env.Config()) | |
| imageMetadata := []*imagemetadata.ImageMetadata{{ | |
| Id: "image-id", | |
| Arch: "amd64", | |
| }} | |
| spec, err := openstack.FindInstanceSpec( | |
| env, series.LatestLts(), "amd64", "", | |
| imageMetadata, | |
| ) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(spec.InstanceType.VirtType, gc.IsNil) | |
| c.Assert(spec.InstanceType.Name, gc.Equals, "m1.small") | |
| } | |
| func (s *localServerSuite) TestFindImageInvalidInstanceConstraint(c *gc.C) { | |
| env := s.Open(c, s.env.Config()) | |
| imageMetadata := []*imagemetadata.ImageMetadata{{ | |
| Id: "image-id", | |
| Arch: "amd64", | |
| }} | |
| _, err := openstack.FindInstanceSpec( | |
| env, series.LatestLts(), "amd64", "instance-type=m1.large", | |
| imageMetadata, | |
| ) | |
| c.Assert(err, gc.ErrorMatches, `no instance types in some-region matching constraints "instance-type=m1.large"`) | |
| } | |
| func (s *localServerSuite) TestPrecheckInstanceValidInstanceType(c *gc.C) { | |
| env := s.Open(c, s.env.Config()) | |
| cons := constraints.MustParse("instance-type=m1.small") | |
| placement := "" | |
| err := env.PrecheckInstance(series.LatestLts(), cons, placement) | |
| c.Assert(err, jc.ErrorIsNil) | |
| } | |
| func (s *localServerSuite) TestPrecheckInstanceInvalidInstanceType(c *gc.C) { | |
| env := s.Open(c, s.env.Config()) | |
| cons := constraints.MustParse("instance-type=m1.large") | |
| placement := "" | |
| err := env.PrecheckInstance(series.LatestLts(), cons, placement) | |
| c.Assert(err, gc.ErrorMatches, `invalid Openstack flavour "m1.large" specified`) | |
| } | |
| func (t *localServerSuite) TestPrecheckInstanceAvailZone(c *gc.C) { | |
| placement := "zone=test-available" | |
| err := t.env.PrecheckInstance(series.LatestLts(), constraints.Value{}, placement) | |
| c.Assert(err, jc.ErrorIsNil) | |
| } | |
| func (t *localServerSuite) TestPrecheckInstanceAvailZoneUnavailable(c *gc.C) { | |
| placement := "zone=test-unavailable" | |
| err := t.env.PrecheckInstance(series.LatestLts(), constraints.Value{}, placement) | |
| c.Assert(err, jc.ErrorIsNil) | |
| } | |
| func (t *localServerSuite) TestPrecheckInstanceAvailZoneUnknown(c *gc.C) { | |
| placement := "zone=test-unknown" | |
| err := t.env.PrecheckInstance(series.LatestLts(), constraints.Value{}, placement) | |
| c.Assert(err, gc.ErrorMatches, `invalid availability zone "test-unknown"`) | |
| } | |
| func (t *localServerSuite) TestPrecheckInstanceAvailZonesUnsupported(c *gc.C) { | |
| t.srv.Nova.SetAvailabilityZones() // no availability zone support | |
| placement := "zone=test-unknown" | |
| err := t.env.PrecheckInstance(series.LatestLts(), constraints.Value{}, placement) | |
| c.Assert(err, jc.Satisfies, jujuerrors.IsNotImplemented) | |
| } | |
| func (s *localServerSuite) TestValidateImageMetadata(c *gc.C) { | |
| env := s.Open(c, s.env.Config()) | |
| params, err := env.(simplestreams.MetadataValidator).MetadataLookupParams("some-region") | |
| c.Assert(err, jc.ErrorIsNil) | |
| params.Sources, err = environs.ImageMetadataSources(env) | |
| c.Assert(err, jc.ErrorIsNil) | |
| params.Series = "raring" | |
| image_ids, _, err := imagemetadata.ValidateImageMetadata(params) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(image_ids, jc.SameContents, []string{"id-y"}) | |
| } | |
| func (s *localServerSuite) TestImageMetadataSourceOrder(c *gc.C) { | |
| src := func(env environs.Environ) (simplestreams.DataSource, error) { | |
| return simplestreams.NewURLDataSource("my datasource", "bar", false, simplestreams.CUSTOM_CLOUD_DATA, false), nil | |
| } | |
| environs.RegisterUserImageDataSourceFunc("my func", src) | |
| env := s.Open(c, s.env.Config()) | |
| sources, err := environs.ImageMetadataSources(env) | |
| c.Assert(err, jc.ErrorIsNil) | |
| var sourceIds []string | |
| for _, s := range sources { | |
| sourceIds = append(sourceIds, s.Description()) | |
| } | |
| c.Assert(sourceIds, jc.DeepEquals, []string{ | |
| "image-metadata-url", "my datasource", "keystone catalog", "default cloud images", "default ubuntu cloud images"}) | |
| } | |
| // TestEnsureGroup checks that when creating a duplicate security group, the existing group is | |
| // returned and the existing rules have been left as is. | |
| func (s *localServerSuite) TestEnsureGroup(c *gc.C) { | |
| rule := []neutron.RuleInfoV2{ | |
| { | |
| Direction: "ingress", | |
| IPProtocol: "tcp", | |
| PortRangeMin: 22, | |
| PortRangeMax: 22, | |
| }, | |
| } | |
| assertRule := func(group neutron.SecurityGroupV2) { | |
| c.Check(len(group.Rules), gc.Equals, 3) | |
| for _, r := range group.Rules { | |
| // Ignore the 2 default egress rules for each new | |
| // security group created by Neutron | |
| if r.Direction == "egress" { | |
| continue | |
| } | |
| c.Check(r.Direction, gc.Equals, "ingress") | |
| c.Check(*r.IPProtocol, gc.Equals, "tcp") | |
| c.Check(*r.PortRangeMin, gc.Equals, 22) | |
| c.Check(*r.PortRangeMax, gc.Equals, 22) | |
| } | |
| } | |
| group, err := openstack.EnsureGroup(s.env, "test group", rule) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(group.Name, gc.Equals, "test group") | |
| assertRule(group) | |
| id := group.Id | |
| // Do it again and check that the existing group is returned. | |
| group, err = openstack.EnsureGroup(s.env, "test group", rule) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Check(group.Id, gc.Equals, id) | |
| c.Assert(group.Name, gc.Equals, "test group") | |
| assertRule(group) | |
| } | |
| // localHTTPSServerSuite contains tests that run against an Openstack service | |
| // double connected on an HTTPS port with a self-signed certificate. This | |
| // service is set up and torn down for every test. This should only test | |
| // things that depend on the HTTPS connection, all other functional tests on a | |
| // local connection should be in localServerSuite | |
| type localHTTPSServerSuite struct { | |
| coretesting.BaseSuite | |
| attrs map[string]interface{} | |
| cred *identity.Credentials | |
| srv localServer | |
| env environs.Environ | |
| } | |
| func (s *localHTTPSServerSuite) SetUpSuite(c *gc.C) { | |
| s.BaseSuite.SetUpSuite(c) | |
| overrideCinderProvider(c, &s.CleanupSuite, &mockAdapter{}) | |
| } | |
| func (s *localHTTPSServerSuite) createConfigAttrs(c *gc.C) map[string]interface{} { | |
| attrs := makeTestConfig(s.cred) | |
| attrs["agent-version"] = coretesting.FakeVersionNumber.String() | |
| attrs["authorized-keys"] = "fakekey" | |
| // In order to set up and tear down the environment properly, we must | |
| // disable hostname verification | |
| attrs["ssl-hostname-verification"] = false | |
| attrs["auth-url"] = s.cred.URL | |
| // Now connect and set up test-local tools and image-metadata URLs | |
| cl := client.NewNonValidatingClient(s.cred, identity.AuthUserPass, nil) | |
| err := cl.Authenticate() | |
| c.Assert(err, jc.ErrorIsNil) | |
| containerURL, err := cl.MakeServiceURL("object-store", "", nil) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Check(containerURL[:8], gc.Equals, "https://") | |
| attrs["agent-metadata-url"] = containerURL + "/juju-dist-test/tools" | |
| c.Logf("Set agent-metadata-url=%q", attrs["agent-metadata-url"]) | |
| attrs["image-metadata-url"] = containerURL + "/juju-dist-test" | |
| c.Logf("Set image-metadata-url=%q", attrs["image-metadata-url"]) | |
| return attrs | |
| } | |
| func (s *localHTTPSServerSuite) SetUpTest(c *gc.C) { | |
| s.BaseSuite.SetUpTest(c) | |
| s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber) | |
| s.srv.UseTLS = true | |
| cred := &identity.Credentials{ | |
| User: "fred", | |
| Secrets: "secret", | |
| Region: "some-region", | |
| TenantName: "some tenant", | |
| } | |
| // Note: start() will change cred.URL to point to s.srv.Server.URL | |
| s.srv.start(c, cred, newFullOpenstackService) | |
| s.cred = cred | |
| attrs := s.createConfigAttrs(c) | |
| c.Assert(attrs["auth-url"].(string)[:8], gc.Equals, "https://") | |
| var err error | |
| s.env, err = bootstrap.Prepare( | |
| envtesting.BootstrapContext(c), | |
| jujuclienttesting.NewMemStore(), | |
| prepareParams(attrs, s.cred), | |
| ) | |
| c.Assert(err, jc.ErrorIsNil) | |
| s.attrs = s.env.Config().AllAttrs() | |
| } | |
| func (s *localHTTPSServerSuite) TearDownTest(c *gc.C) { | |
| if s.env != nil { | |
| err := s.env.Destroy() | |
| c.Check(err, jc.ErrorIsNil) | |
| s.env = nil | |
| } | |
| s.srv.stop() | |
| s.BaseSuite.TearDownTest(c) | |
| } | |
| func (s *localHTTPSServerSuite) TestMustDisableSSLVerify(c *gc.C) { | |
| coretesting.SkipIfPPC64EL(c, "lp:1425242") | |
| // If you don't have ssl-hostname-verification set to false, then we | |
| // fail to connect to the environment. Copy the attrs used by SetUp and | |
| // force hostname verification. | |
| newattrs := make(map[string]interface{}, len(s.attrs)) | |
| for k, v := range s.attrs { | |
| newattrs[k] = v | |
| } | |
| newattrs["ssl-hostname-verification"] = true | |
| cfg, err := config.New(config.NoDefaults, newattrs) | |
| c.Assert(err, jc.ErrorIsNil) | |
| env, err := environs.New(environs.OpenParams{ | |
| Cloud: makeCloudSpec(s.cred), | |
| Config: cfg, | |
| }) | |
| c.Assert(err, jc.ErrorIsNil) | |
| _, err = env.AllInstances() | |
| c.Assert(err, gc.ErrorMatches, "(.|\n)*x509: certificate signed by unknown authority") | |
| } | |
| func (s *localHTTPSServerSuite) TestCanBootstrap(c *gc.C) { | |
| restoreFinishBootstrap := envtesting.DisableFinishBootstrap() | |
| defer restoreFinishBootstrap() | |
| // For testing, we create a storage instance to which is uploaded tools and image metadata. | |
| metadataStorage := openstack.MetadataStorage(s.env) | |
| url, err := metadataStorage.URL("") | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Logf("Generating fake tools for: %v", url) | |
| envtesting.UploadFakeTools(c, metadataStorage, s.env.Config().AgentStream(), s.env.Config().AgentStream()) | |
| defer envtesting.RemoveFakeTools(c, metadataStorage, s.env.Config().AgentStream()) | |
| openstack.UseTestImageData(metadataStorage, s.cred) | |
| defer openstack.RemoveTestImageData(metadataStorage) | |
| err = bootstrapEnv(c, s.env) | |
| c.Assert(err, jc.ErrorIsNil) | |
| } | |
| func (s *localHTTPSServerSuite) TestFetchFromImageMetadataSources(c *gc.C) { | |
| // Setup a custom URL for image metadata | |
| customStorage := openstack.CreateCustomStorage(s.env, "custom-metadata") | |
| customURL, err := customStorage.URL("") | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Check(customURL[:8], gc.Equals, "https://") | |
| config, err := s.env.Config().Apply( | |
| map[string]interface{}{"image-metadata-url": customURL}, | |
| ) | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = s.env.SetConfig(config) | |
| c.Assert(err, jc.ErrorIsNil) | |
| sources, err := environs.ImageMetadataSources(s.env) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(sources, gc.HasLen, 4) | |
| // Make sure there is something to download from each location | |
| metadata := "metadata-content" | |
| metadataStorage := openstack.ImageMetadataStorage(s.env) | |
| err = metadataStorage.Put(metadata, bytes.NewBufferString(metadata), int64(len(metadata))) | |
| c.Assert(err, jc.ErrorIsNil) | |
| custom := "custom-content" | |
| err = customStorage.Put(custom, bytes.NewBufferString(custom), int64(len(custom))) | |
| c.Assert(err, jc.ErrorIsNil) | |
| // Produce map of data sources keyed on description | |
| mappedSources := make(map[string]simplestreams.DataSource, len(sources)) | |
| for i, s := range sources { | |
| c.Logf("datasource %d: %+v", i, s) | |
| mappedSources[s.Description()] = s | |
| } | |
| // Read from the Config entry's image-metadata-url | |
| contentReader, url, err := mappedSources["image-metadata-url"].Fetch(custom) | |
| c.Assert(err, jc.ErrorIsNil) | |
| defer contentReader.Close() | |
| content, err := ioutil.ReadAll(contentReader) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(string(content), gc.Equals, custom) | |
| c.Check(url[:8], gc.Equals, "https://") | |
| // Check the entry we got from keystone | |
| contentReader, url, err = mappedSources["keystone catalog"].Fetch(metadata) | |
| c.Assert(err, jc.ErrorIsNil) | |
| defer contentReader.Close() | |
| content, err = ioutil.ReadAll(contentReader) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(string(content), gc.Equals, metadata) | |
| c.Check(url[:8], gc.Equals, "https://") | |
| // Verify that we are pointing at exactly where metadataStorage thinks we are | |
| metaURL, err := metadataStorage.URL(metadata) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Check(url, gc.Equals, metaURL) | |
| } | |
| func (s *localHTTPSServerSuite) TestFetchFromToolsMetadataSources(c *gc.C) { | |
| // Setup a custom URL for image metadata | |
| customStorage := openstack.CreateCustomStorage(s.env, "custom-tools-metadata") | |
| customURL, err := customStorage.URL("") | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Check(customURL[:8], gc.Equals, "https://") | |
| config, err := s.env.Config().Apply( | |
| map[string]interface{}{"agent-metadata-url": customURL}, | |
| ) | |
| c.Assert(err, jc.ErrorIsNil) | |
| err = s.env.SetConfig(config) | |
| c.Assert(err, jc.ErrorIsNil) | |
| sources, err := tools.GetMetadataSources(s.env) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(sources, gc.HasLen, 3) | |
| // Make sure there is something to download from each location | |
| keystone := "keystone-tools-content" | |
| // The keystone entry just points at the root of the Swift storage, and | |
| // we have to create a container to upload any data. So we just point | |
| // into a subdirectory for the data we are downloading | |
| keystoneContainer := "tools-test" | |
| keystoneStorage := openstack.CreateCustomStorage(s.env, "tools-test") | |
| err = keystoneStorage.Put(keystone, bytes.NewBufferString(keystone), int64(len(keystone))) | |
| c.Assert(err, jc.ErrorIsNil) | |
| custom := "custom-tools-content" | |
| err = customStorage.Put(custom, bytes.NewBufferString(custom), int64(len(custom))) | |
| c.Assert(err, jc.ErrorIsNil) | |
| // Read from the Config entry's agent-metadata-url | |
| contentReader, url, err := sources[0].Fetch(custom) | |
| c.Assert(err, jc.ErrorIsNil) | |
| defer contentReader.Close() | |
| content, err := ioutil.ReadAll(contentReader) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(string(content), gc.Equals, custom) | |
| c.Check(url[:8], gc.Equals, "https://") | |
| // Check the entry we got from keystone | |
| // Now fetch the data, and verify the contents. | |
| contentReader, url, err = sources[1].Fetch(keystoneContainer + "/" + keystone) | |
| c.Assert(err, jc.ErrorIsNil) | |
| defer contentReader.Close() | |
| content, err = ioutil.ReadAll(contentReader) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(string(content), gc.Equals, keystone) | |
| c.Check(url[:8], gc.Equals, "https://") | |
| keystoneURL, err := keystoneStorage.URL(keystone) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Check(url, gc.Equals, keystoneURL) | |
| // We *don't* test Fetch for sources[3] because it points to | |
| // streams.canonical.com | |
| } | |
| func (s *localServerSuite) TestRemoveBlankContainer(c *gc.C) { | |
| storage := openstack.BlankContainerStorage() | |
| err := storage.Remove("some-file") | |
| c.Assert(err, gc.ErrorMatches, `cannot remove "some-file": swift container name is empty`) | |
| } | |
| func (s *localServerSuite) TestAllInstancesIgnoresOtherMachines(c *gc.C) { | |
| err := bootstrapEnv(c, s.env) | |
| c.Assert(err, jc.ErrorIsNil) | |
| // Check that we see 1 instance in the environment | |
| insts, err := s.env.AllInstances() | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Check(insts, gc.HasLen, 1) | |
| // Now start a machine 'manually' in the same account, with a similar | |
| // but not matching name, and ensure it isn't seen by AllInstances | |
| // See bug #1257481, for how similar names were causing them to get | |
| // listed (and thus destroyed) at the wrong time | |
| existingModelName := s.TestConfig["name"] | |
| newMachineName := fmt.Sprintf("juju-%s-2-machine-0", existingModelName) | |
| // We grab the Nova client directly from the env, just to save time | |
| // looking all the stuff up | |
| novaClient := openstack.GetNovaClient(s.env) | |
| entity, err := novaClient.RunServer(nova.RunServerOpts{ | |
| Name: newMachineName, | |
| FlavorId: "1", // test service has 1,2,3 for flavor ids | |
| ImageId: "1", // UseTestImageData sets up images 1 and 2 | |
| }) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(entity, gc.NotNil) | |
| // List all servers with no filter, we should see both instances | |
| servers, err := novaClient.ListServersDetail(nova.NewFilter()) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(servers, gc.HasLen, 2) | |
| insts, err = s.env.AllInstances() | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Check(insts, gc.HasLen, 1) | |
| } | |
| func (s *localServerSuite) TestResolveNetworkUUID(c *gc.C) { | |
| var sampleUUID = "f81d4fae-7dec-11d0-a765-00a0c91e6bf6" | |
| networkId, err := openstack.ResolveNetwork(s.env, sampleUUID) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(networkId, gc.Equals, sampleUUID) | |
| } | |
| func (s *localServerSuite) TestResolveNetworkLabel(c *gc.C) { | |
| // For now this test has to cheat and use knowledge of goose internals | |
| var networkLabel = "net" | |
| var expectNetworkId = "1" | |
| networkId, err := openstack.ResolveNetwork(s.env, networkLabel) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(networkId, gc.Equals, expectNetworkId) | |
| } | |
| func (s *localServerSuite) TestResolveNetworkNotPresent(c *gc.C) { | |
| var notPresentNetwork = "no-network-with-this-label" | |
| networkId, err := openstack.ResolveNetwork(s.env, notPresentNetwork) | |
| c.Check(networkId, gc.Equals, "") | |
| c.Assert(err, gc.ErrorMatches, `no networks exist with label "no-network-with-this-label"`) | |
| } | |
| // TODO(gz): TestResolveNetworkMultipleMatching when can inject new networks | |
| func (t *localServerSuite) TestStartInstanceAvailZone(c *gc.C) { | |
| inst, err := t.testStartInstanceAvailZone(c, "test-available") | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(openstack.InstanceServerDetail(inst).AvailabilityZone, gc.Equals, "test-available") | |
| } | |
| func (t *localServerSuite) TestStartInstanceAvailZoneUnavailable(c *gc.C) { | |
| _, err := t.testStartInstanceAvailZone(c, "test-unavailable") | |
| c.Assert(err, gc.ErrorMatches, `availability zone "test-unavailable" is unavailable`) | |
| } | |
| func (t *localServerSuite) TestStartInstanceAvailZoneUnknown(c *gc.C) { | |
| _, err := t.testStartInstanceAvailZone(c, "test-unknown") | |
| c.Assert(err, gc.ErrorMatches, `invalid availability zone "test-unknown"`) | |
| } | |
| func (t *localServerSuite) testStartInstanceAvailZone(c *gc.C, zone string) (instance.Instance, error) { | |
| err := bootstrapEnv(c, t.env) | |
| c.Assert(err, jc.ErrorIsNil) | |
| params := environs.StartInstanceParams{ | |
| ControllerUUID: t.ControllerUUID, | |
| Placement: "zone=" + zone, | |
| } | |
| result, err := testing.StartInstanceWithParams(t.env, "1", params) | |
| if err != nil { | |
| return nil, err | |
| } | |
| return result.Instance, nil | |
| } | |
| func (t *localServerSuite) TestGetAvailabilityZones(c *gc.C) { | |
| var resultZones []nova.AvailabilityZone | |
| var resultErr error | |
| t.PatchValue(openstack.NovaListAvailabilityZones, func(c *nova.Client) ([]nova.AvailabilityZone, error) { | |
| return append([]nova.AvailabilityZone{}, resultZones...), resultErr | |
| }) | |
| env := t.env.(common.ZonedEnviron) | |
| resultErr = fmt.Errorf("failed to get availability zones") | |
| zones, err := env.AvailabilityZones() | |
| c.Assert(err, gc.Equals, resultErr) | |
| c.Assert(zones, gc.IsNil) | |
| resultErr = nil | |
| resultZones = make([]nova.AvailabilityZone, 1) | |
| resultZones[0].Name = "whatever" | |
| zones, err = env.AvailabilityZones() | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(zones, gc.HasLen, 1) | |
| c.Assert(zones[0].Name(), gc.Equals, "whatever") | |
| // A successful result is cached, currently for the lifetime | |
| // of the Environ. This will change if/when we have long-lived | |
| // Environs to cut down repeated IaaS requests. | |
| resultErr = fmt.Errorf("failed to get availability zones") | |
| resultZones[0].Name = "andever" | |
| zones, err = env.AvailabilityZones() | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(zones, gc.HasLen, 1) | |
| c.Assert(zones[0].Name(), gc.Equals, "whatever") | |
| } | |
| func (t *localServerSuite) TestGetAvailabilityZonesCommon(c *gc.C) { | |
| var resultZones []nova.AvailabilityZone | |
| t.PatchValue(openstack.NovaListAvailabilityZones, func(c *nova.Client) ([]nova.AvailabilityZone, error) { | |
| return append([]nova.AvailabilityZone{}, resultZones...), nil | |
| }) | |
| env := t.env.(common.ZonedEnviron) | |
| resultZones = make([]nova.AvailabilityZone, 2) | |
| resultZones[0].Name = "az1" | |
| resultZones[1].Name = "az2" | |
| resultZones[0].State.Available = true | |
| resultZones[1].State.Available = false | |
| zones, err := env.AvailabilityZones() | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(zones, gc.HasLen, 2) | |
| c.Assert(zones[0].Name(), gc.Equals, resultZones[0].Name) | |
| c.Assert(zones[1].Name(), gc.Equals, resultZones[1].Name) | |
| c.Assert(zones[0].Available(), jc.IsTrue) | |
| c.Assert(zones[1].Available(), jc.IsFalse) | |
| } | |
| type mockAvailabilityZoneAllocations struct { | |
| group []instance.Id // input param | |
| result []common.AvailabilityZoneInstances | |
| err error | |
| } | |
| func (t *mockAvailabilityZoneAllocations) AvailabilityZoneAllocations( | |
| e common.ZonedEnviron, group []instance.Id, | |
| ) ([]common.AvailabilityZoneInstances, error) { | |
| t.group = group | |
| return t.result, t.err | |
| } | |
| func (t *localServerSuite) TestStartInstanceDistributionParams(c *gc.C) { | |
| err := bootstrapEnv(c, t.env) | |
| c.Assert(err, jc.ErrorIsNil) | |
| var mock mockAvailabilityZoneAllocations | |
| t.PatchValue(openstack.AvailabilityZoneAllocations, mock.AvailabilityZoneAllocations) | |
| // no distribution group specified | |
| testing.AssertStartInstance(c, t.env, t.ControllerUUID, "1") | |
| c.Assert(mock.group, gc.HasLen, 0) | |
| // distribution group specified: ensure it's passed through to AvailabilityZone. | |
| expectedInstances := []instance.Id{"i-0", "i-1"} | |
| params := environs.StartInstanceParams{ | |
| ControllerUUID: t.ControllerUUID, | |
| DistributionGroup: func() ([]instance.Id, error) { | |
| return expectedInstances, nil | |
| }, | |
| } | |
| _, err = testing.StartInstanceWithParams(t.env, "1", params) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(mock.group, gc.DeepEquals, expectedInstances) | |
| } | |
| func (t *localServerSuite) TestStartInstanceDistributionErrors(c *gc.C) { | |
| err := bootstrapEnv(c, t.env) | |
| c.Assert(err, jc.ErrorIsNil) | |
| mock := mockAvailabilityZoneAllocations{ | |
| err: fmt.Errorf("AvailabilityZoneAllocations failed"), | |
| } | |
| t.PatchValue(openstack.AvailabilityZoneAllocations, mock.AvailabilityZoneAllocations) | |
| _, _, _, err = testing.StartInstance(t.env, t.ControllerUUID, "1") | |
| c.Assert(jujuerrors.Cause(err), gc.Equals, mock.err) | |
| mock.err = nil | |
| dgErr := fmt.Errorf("DistributionGroup failed") | |
| params := environs.StartInstanceParams{ | |
| ControllerUUID: t.ControllerUUID, | |
| DistributionGroup: func() ([]instance.Id, error) { | |
| return nil, dgErr | |
| }, | |
| } | |
| _, err = testing.StartInstanceWithParams(t.env, "1", params) | |
| c.Assert(jujuerrors.Cause(err), gc.Equals, dgErr) | |
| } | |
| func (t *localServerSuite) TestStartInstanceDistribution(c *gc.C) { | |
| err := bootstrapEnv(c, t.env) | |
| c.Assert(err, jc.ErrorIsNil) | |
| // test-available is the only available AZ, so AvailabilityZoneAllocations | |
| // is guaranteed to return that. | |
| inst, _ := testing.AssertStartInstance(c, t.env, t.ControllerUUID, "1") | |
| c.Assert(openstack.InstanceServerDetail(inst).AvailabilityZone, gc.Equals, "test-available") | |
| } | |
| func (t *localServerSuite) TestStartInstancePicksValidZoneForHost(c *gc.C) { | |
| coretesting.SkipIfPPC64EL(c, "lp:1425242") | |
| t.srv.Nova.SetAvailabilityZones( | |
| // bootstrap node will be on az1. | |
| nova.AvailabilityZone{ | |
| Name: "az1", | |
| State: nova.AvailabilityZoneState{ | |
| Available: true, | |
| }, | |
| }, | |
| // az2 will be made to return an error. | |
| nova.AvailabilityZone{ | |
| Name: "az2", | |
| State: nova.AvailabilityZoneState{ | |
| Available: true, | |
| }, | |
| }, | |
| // az3 will be valid to host an instance. | |
| nova.AvailabilityZone{ | |
| Name: "az3", | |
| State: nova.AvailabilityZoneState{ | |
| Available: true, | |
| }, | |
| }, | |
| ) | |
| err := bootstrapEnv(c, t.env) | |
| c.Assert(err, jc.ErrorIsNil) | |
| cleanup := t.srv.Nova.RegisterControlPoint( | |
| "addServer", | |
| func(sc hook.ServiceControl, args ...interface{}) error { | |
| serverDetail := args[0].(*nova.ServerDetail) | |
| if serverDetail.AvailabilityZone == "az2" { | |
| return fmt.Errorf("No valid host was found") | |
| } | |
| return nil | |
| }, | |
| ) | |
| defer cleanup() | |
| inst, _ := testing.AssertStartInstance(c, t.env, t.ControllerUUID, "1") | |
| c.Assert(openstack.InstanceServerDetail(inst).AvailabilityZone, gc.Equals, "az3") | |
| } | |
| func (t *localServerSuite) TestStartInstanceWithUnknownAZError(c *gc.C) { | |
| coretesting.SkipIfPPC64EL(c, "lp:1425242") | |
| t.srv.Nova.SetAvailabilityZones( | |
| // bootstrap node will be on az1. | |
| nova.AvailabilityZone{ | |
| Name: "az1", | |
| State: nova.AvailabilityZoneState{ | |
| Available: true, | |
| }, | |
| }, | |
| // az2 will be made to return an unknown error. | |
| nova.AvailabilityZone{ | |
| Name: "az2", | |
| State: nova.AvailabilityZoneState{ | |
| Available: true, | |
| }, | |
| }, | |
| ) | |
| err := bootstrapEnv(c, t.env) | |
| c.Assert(err, jc.ErrorIsNil) | |
| cleanup := t.srv.Nova.RegisterControlPoint( | |
| "addServer", | |
| func(sc hook.ServiceControl, args ...interface{}) error { | |
| serverDetail := args[0].(*nova.ServerDetail) | |
| if serverDetail.AvailabilityZone == "az2" { | |
| return fmt.Errorf("Some unknown error") | |
| } | |
| return nil | |
| }, | |
| ) | |
| defer cleanup() | |
| _, _, _, err = testing.StartInstance(t.env, t.ControllerUUID, "1") | |
| c.Assert(err, gc.ErrorMatches, "(?s).*Some unknown error.*") | |
| } | |
| func (t *localServerSuite) TestStartInstanceDistributionAZNotImplemented(c *gc.C) { | |
| err := bootstrapEnv(c, t.env) | |
| c.Assert(err, jc.ErrorIsNil) | |
| mock := mockAvailabilityZoneAllocations{ | |
| err: jujuerrors.NotImplementedf("availability zones"), | |
| } | |
| t.PatchValue(openstack.AvailabilityZoneAllocations, mock.AvailabilityZoneAllocations) | |
| // Instance will be created without an availability zone specified. | |
| inst, _ := testing.AssertStartInstance(c, t.env, t.ControllerUUID, "1") | |
| c.Assert(openstack.InstanceServerDetail(inst).AvailabilityZone, gc.Equals, "") | |
| } | |
| func (t *localServerSuite) TestInstanceTags(c *gc.C) { | |
| err := bootstrapEnv(c, t.env) | |
| c.Assert(err, jc.ErrorIsNil) | |
| instances, err := t.env.AllInstances() | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(instances, gc.HasLen, 1) | |
| c.Assert( | |
| openstack.InstanceServerDetail(instances[0]).Metadata, | |
| jc.DeepEquals, | |
| map[string]string{ | |
| "juju-model-uuid": coretesting.ModelTag.Id(), | |
| "juju-controller-uuid": coretesting.ControllerTag.Id(), | |
| "juju-is-controller": "true", | |
| }, | |
| ) | |
| } | |
| func (t *localServerSuite) TestTagInstance(c *gc.C) { | |
| err := bootstrapEnv(c, t.env) | |
| c.Assert(err, jc.ErrorIsNil) | |
| assertMetadata := func(extraKey, extraValue string) { | |
| // Refresh instance | |
| instances, err := t.env.AllInstances() | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(instances, gc.HasLen, 1) | |
| c.Assert( | |
| openstack.InstanceServerDetail(instances[0]).Metadata, | |
| jc.DeepEquals, | |
| map[string]string{ | |
| "juju-model-uuid": coretesting.ModelTag.Id(), | |
| "juju-controller-uuid": coretesting.ControllerTag.Id(), | |
| "juju-is-controller": "true", | |
| extraKey: extraValue, | |
| }, | |
| ) | |
| } | |
| instances, err := t.env.AllInstances() | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(instances, gc.HasLen, 1) | |
| extraKey := "extra-k" | |
| extraValue := "extra-v" | |
| err = t.env.(environs.InstanceTagger).TagInstance( | |
| instances[0].Id(), map[string]string{extraKey: extraValue}, | |
| ) | |
| c.Assert(err, jc.ErrorIsNil) | |
| assertMetadata(extraKey, extraValue) | |
| // Ensure that a second call updates existing tags. | |
| extraValue = "extra-v2" | |
| err = t.env.(environs.InstanceTagger).TagInstance( | |
| instances[0].Id(), map[string]string{extraKey: extraValue}, | |
| ) | |
| c.Assert(err, jc.ErrorIsNil) | |
| assertMetadata(extraKey, extraValue) | |
| } | |
| func (s *localServerSuite) TestAdoptResources(c *gc.C) { | |
| err := bootstrapEnv(c, s.env) | |
| c.Assert(err, jc.ErrorIsNil) | |
| hostedModelUUID := "7e386e08-cba7-44a4-a76e-7c1633584210" | |
| cfg, err := s.env.Config().Apply(map[string]interface{}{ | |
| "uuid": hostedModelUUID, | |
| }) | |
| c.Assert(err, jc.ErrorIsNil) | |
| env, err := environs.New(environs.OpenParams{ | |
| Cloud: makeCloudSpec(s.cred), | |
| Config: cfg, | |
| }) | |
| c.Assert(err, jc.ErrorIsNil) | |
| originalController := coretesting.ControllerTag.Id() | |
| _, _, _, err = testing.StartInstance(env, originalController, "0") | |
| c.Assert(err, jc.ErrorIsNil) | |
| addVolume(c, s.env, originalController, "99/9") | |
| addVolume(c, env, originalController, "23/9") | |
| s.checkInstanceTags(c, s.env, originalController) | |
| s.checkInstanceTags(c, env, originalController) | |
| s.checkVolumeTags(c, s.env, originalController) | |
| s.checkVolumeTags(c, env, originalController) | |
| s.checkGroupController(c, s.env, originalController) | |
| s.checkGroupController(c, env, originalController) | |
| // Needs to be a correctly formatted uuid so we can get it out of | |
| // group names. | |
| newController := "aaaaaaaa-bbbb-cccc-dddd-0123456789ab" | |
| err = env.AdoptResources(newController, version.MustParse("1.2.3")) | |
| c.Assert(err, jc.ErrorIsNil) | |
| s.checkInstanceTags(c, s.env, originalController) | |
| s.checkInstanceTags(c, env, newController) | |
| s.checkVolumeTags(c, s.env, originalController) | |
| s.checkVolumeTags(c, env, newController) | |
| s.checkGroupController(c, s.env, originalController) | |
| s.checkGroupController(c, env, newController) | |
| } | |
| func addVolume(c *gc.C, env environs.Environ, controllerUUID, name string) { | |
| storageAdapter, err := (*openstack.NewOpenstackStorage)(env.(*openstack.Environ)) | |
| c.Assert(err, jc.ErrorIsNil) | |
| modelUUID := env.Config().UUID() | |
| source := openstack.NewCinderVolumeSourceForModel(storageAdapter, modelUUID) | |
| result, err := source.CreateVolumes([]storage.VolumeParams{{ | |
| Tag: names.NewVolumeTag(name), | |
| ResourceTags: tags.ResourceTags( | |
| names.NewModelTag(modelUUID), | |
| names.NewControllerTag(controllerUUID), | |
| ), | |
| }}) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(result, gc.HasLen, 1) | |
| c.Assert(result[0].Error, jc.ErrorIsNil) | |
| } | |
| func (s *localServerSuite) checkInstanceTags(c *gc.C, env environs.Environ, expectedController string) { | |
| instances, err := env.AllInstances() | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(instances, gc.Not(gc.HasLen), 0) | |
| for _, instance := range instances { | |
| server := openstack.InstanceServerDetail(instance) | |
| c.Logf(string(instance.Id())) | |
| c.Check(server.Metadata[tags.JujuController], gc.Equals, expectedController) | |
| } | |
| } | |
| func (s *localServerSuite) checkVolumeTags(c *gc.C, env environs.Environ, expectedController string) { | |
| storage, err := (*openstack.NewOpenstackStorage)(env.(*openstack.Environ)) | |
| c.Assert(err, jc.ErrorIsNil) | |
| source := openstack.NewCinderVolumeSourceForModel(storage, env.Config().UUID()) | |
| volumeIds, err := source.ListVolumes() | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(volumeIds, gc.Not(gc.HasLen), 0) | |
| for _, volumeId := range volumeIds { | |
| c.Logf(volumeId) | |
| volume, err := storage.GetVolume(volumeId) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Check(volume.Metadata[tags.JujuController], gc.Equals, expectedController) | |
| } | |
| } | |
| func (s *localServerSuite) checkGroupController(c *gc.C, env environs.Environ, expectedController string) { | |
| groupNames, err := openstack.GetModelGroupNames(env) | |
| c.Assert(err, jc.ErrorIsNil) | |
| c.Assert(groupNames, gc.Not(gc.HasLen), 0) | |
| extractControllerRe, err := regexp.Compile(openstack.GroupControllerPattern) | |
| c.Assert(err, jc.ErrorIsNil) | |
| for _, group := range groupNames { | |
| c.Logf(group) | |
| controller := extractControllerRe.ReplaceAllString(group, "$controllerUUID") | |
| c.Check(controller, gc.Equals, expectedController) | |
| } | |
| } | |
| func (s *localServerSuite) TestUpdateGroupController(c *gc.C) { | |
| err := bootstrapEnv(c, s.env) | |
| c.Assert(err, jc.ErrorIsNil) | |
| groupNames, err := openstack.GetModelGroupNames(s.env) | |
| c.Assert(err, jc.ErrorIsNil) | |
| groupNamesBefore := set.NewStrings(groupNames...) | |
| c.Assert(groupNamesBefore, gc.DeepEquals, set.NewStrings( | |
| "juju-deadbeef-1bad-500d-9000-4b1d0d06f00d-deadbeef-0bad-400d-8000-4b1d0d06f00d", | |
| "juju-deadbeef-1bad-500d-9000-4b1d0d06f00d-deadbeef-0bad-400d-8000-4b1d0d06f00d-0", | |
| )) | |
| firewaller := openstack.GetFirewaller(s.env) | |
| err = firewaller.UpdateGroupController("aabbccdd-eeee-ffff-0000-0123456789ab") | |
| c.Assert(err, jc.ErrorIsNil) | |
| groupNames, err = openstack.GetModelGroupNames(s.env) | |
| c.Assert(err, jc.ErrorIsNil) | |
| groupNamesAfter := set.NewStrings(groupNames...) | |
| c.Assert(groupNamesAfter, gc.DeepEquals, set.NewStrings( | |
| "juju-aabbccdd-eeee-ffff-0000-0123456789ab-deadbeef-0bad-400d-8000-4b1d0d06f00d", | |
| "juju-aabbccdd-eeee-ffff-0000-0123456789ab-deadbeef-0bad-400d-8000-4b1d0d06f00d-0", | |
| )) | |
| } | |
| func prepareParams(attrs map[string]interface{}, cred *identity.Credentials) bootstrap.PrepareParams { | |
| return bootstrap.PrepareParams{ | |
| ControllerConfig: coretesting.FakeControllerConfig(), | |
| ModelConfig: attrs, | |
| ControllerName: attrs["name"].(string), | |
| Cloud: makeCloudSpec(cred), | |
| AdminSecret: testing.AdminSecret, | |
| } | |
| } | |
| func makeCloudSpec(cred *identity.Credentials) environs.CloudSpec { | |
| credential := makeCredential(cred) | |
| return environs.CloudSpec{ | |
| Type: "openstack", | |
| Name: "openstack", | |
| Endpoint: cred.URL, | |
| Region: cred.Region, | |
| Credential: &credential, | |
| } | |
| } | |
| func makeCredential(cred *identity.Credentials) cloud.Credential { | |
| return cloud.NewCredential( | |
| cloud.UserPassAuthType, | |
| map[string]string{ | |
| "username": cred.User, | |
| "password": cred.Secrets, | |
| "tenant-name": cred.TenantName, | |
| }, | |
| ) | |
| } | |
| // noSwiftSuite contains tests that run against an OpenStack service double | |
| // that lacks Swift. | |
| type noSwiftSuite struct { | |
| coretesting.BaseSuite | |
| cred *identity.Credentials | |
| srv localServer | |
| env environs.Environ | |
| } | |
| func (s *noSwiftSuite) SetUpSuite(c *gc.C) { | |
| s.BaseSuite.SetUpSuite(c) | |
| restoreFinishBootstrap := envtesting.DisableFinishBootstrap() | |
| s.AddCleanup(func(*gc.C) { restoreFinishBootstrap() }) | |
| s.PatchValue(&imagemetadata.SimplestreamsImagesPublicKey, sstesting.SignedMetadataPublicKey) | |
| s.PatchValue(&keys.JujuPublicKey, sstesting.SignedMetadataPublicKey) | |
| } | |
| func (s *noSwiftSuite) SetUpTest(c *gc.C) { | |
| s.BaseSuite.SetUpTest(c) | |
| s.cred = &identity.Credentials{ | |
| User: "fred", | |
| Secrets: "secret", | |
| Region: "some-region", | |
| TenantName: "some tenant", | |
| } | |
| s.srv.start(c, s.cred, newNovaOnlyOpenstackService) | |
| attrs := coretesting.FakeConfig().Merge(coretesting.Attrs{ | |
| "name": "sample-no-swift", | |
| "type": "openstack", | |
| "auth-mode": "userpass", | |
| "agent-version": coretesting.FakeVersionNumber.String(), | |
| "authorized-keys": "fakekey", | |
| }) | |
| s.PatchValue(&jujuversion.Current, coretesting.FakeVersionNumber) | |
| // Serve fake tools and image metadata using "filestorage", | |
| // rather than Swift as the rest of the tests do. | |
| storageDir := c.MkDir() | |
| imagesDir := filepath.Join(storageDir, "images") | |
| toolsDir := filepath.Join(storageDir, "tools") | |
| for _, dir := range []string{imagesDir, toolsDir} { | |
| err := os.MkdirAll(dir, 0755) | |
| c.Assert(err, jc.ErrorIsNil) | |
| } | |
| toolsStorage, err := filestorage.NewFileStorageWriter(storageDir) | |
| c.Assert(err, jc.ErrorIsNil) | |
| envtesting.UploadFakeTools(c, toolsStorage, "released", "released") | |
| s.PatchValue(&tools.DefaultBaseURL, storageDir) | |
| imageStorage, err := filestorage.NewFileStorageWriter(imagesDir) | |
| openstack.UseTestImageData(imageStorage, s.cred) | |
| imagetesting.PatchOfficialDataSources(&s.CleanupSuite, storageDir) | |
| env, err := bootstrap.Prepare( | |
| envtesting.BootstrapContext(c), | |
| jujuclienttesting.NewMemStore(), | |
| prepareParams(attrs, s.cred), | |
| ) | |
| c.Assert(err, jc.ErrorIsNil) | |
| s.env = env | |
| } | |
| func (s *noSwiftSuite) TearDownTest(c *gc.C) { | |
| s.srv.stop() | |
| s.BaseSuite.TearDownTest(c) | |
| } | |
| func (s *noSwiftSuite) TestBootstrap(c *gc.C) { | |
| err := bootstrap.Bootstrap(envtesting.BootstrapContext(c), s.env, bootstrap.BootstrapParams{ | |
| ControllerConfig: coretesting.FakeControllerConfig(), | |
| AdminSecret: testing.AdminSecret, | |
| CAPrivateKey: coretesting.CAKey, | |
| }) | |
| c.Assert(err, jc.ErrorIsNil) | |
| } | |
| func newFullOpenstackService(cred *identity.Credentials, auth identity.AuthMode, useTSL bool) (*novaservice.Nova, *neutronservice.Neutron, []string) { | |
| service, logMsg := openstackservice.New(cred, auth, useTSL) | |
| service.UseNeutronNetworking() | |
| service.SetupHTTP(nil) | |
| return service.Nova, service.Neutron, logMsg | |
| } | |
| func newNovaOnlyOpenstackService(cred *identity.Credentials, auth identity.AuthMode, useTSL bool) (*novaservice.Nova, *neutronservice.Neutron, []string) { | |
| service, logMsg := openstackservice.NewNoSwift(cred, auth, useTSL) | |
| service.UseNeutronNetworking() | |
| service.SetupHTTP(nil) | |
| return service.Nova, service.Neutron, logMsg | |
| } | |
| func bootstrapEnv(c *gc.C, env environs.Environ) error { | |
| return bootstrap.Bootstrap(envtesting.BootstrapContext(c), env, bootstrap.BootstrapParams{ | |
| ControllerConfig: coretesting.FakeControllerConfig(), | |
| AdminSecret: testing.AdminSecret, | |
| CAPrivateKey: coretesting.CAKey, | |
| }) | |
| } |