Merge lp:~gophers/goose/trunk into lp:goose

Proposed by John A Meinel
Status: Merged
Merged at revision: 53
Proposed branch: lp:~gophers/goose/trunk
Merge into: lp:goose
Diff against target: 2525 lines (+1013/-609)
32 files modified
client/local_test.go (+15/-22)
identity/legacy_test.go (+3/-3)
identity/live_test.go (+40/-0)
identity/local_test.go (+63/-0)
identity/setup_test.go (+13/-1)
identity/userpass.go (+4/-0)
identity/userpass_test.go (+5/-5)
nova/local_test.go (+12/-32)
swift/local_test.go (+12/-28)
test.py (+1/-0)
testservices/cmd/main.go (+4/-3)
testservices/identityservice/identityservice.go (+11/-5)
testservices/identityservice/legacy.go (+17/-9)
testservices/identityservice/legacy_test.go (+3/-2)
testservices/identityservice/service_test.go (+3/-3)
testservices/identityservice/userpass.go (+18/-17)
testservices/identityservice/userpass_test.go (+17/-22)
testservices/identityservice/users.go (+63/-0)
testservices/identityservice/util.go (+4/-2)
testservices/novaservice/service.go (+46/-24)
testservices/novaservice/service_http.go (+16/-9)
testservices/novaservice/service_http_test.go (+343/-334)
testservices/novaservice/service_test.go (+16/-17)
testservices/openstack/openstack.go (+38/-0)
testservices/service.go (+21/-0)
testservices/swiftservice/service.go (+46/-8)
testservices/swiftservice/service_http.go (+13/-7)
testservices/swiftservice/service_http_test.go (+14/-9)
testservices/swiftservice/service_test.go (+8/-7)
testservices/swiftservice/swiftservice.go (+0/-40)
tools/secgroup-delete-all/main.go (+73/-0)
tools/secgroup-delete-all/main_test.go (+71/-0)
To merge this branch: bzr merge lp:~gophers/goose/trunk
Reviewer Review Type Date Requested Status
Go Bot Pending
Review via email: mp+144438@code.launchpad.net

Commit message

Merge trunk into goose-bot's trunk

Description of the change

Merge trunk into goose bot, to see if this works.

To post a comment you must log in.
Revision history for this message
Go Bot (go-bot) wrote :

The attempt to merge lp:goose into lp:~goose-bot/goose/trunk failed. Below is the output from the failed tests.

Setting GOPATH to: /home/tarmac/trees
Reconfiguring to use a shared repository
Traceback (most recent call last):
  File "test.py", line 172, in <module>
    main(sys.argv[1:])
  File "test.py", line 157, in main
    tarmac_setup(opts)
  File "test.py", line 82, in tarmac_setup
    create_tarmac_repository()
  File "test.py", line 58, in create_tarmac_repository
    reconfiguration.apply(False)
  File "/usr/lib/python2.7/dist-packages/bzrlib/reconfigure.py", line 351, in apply
    new_repo = up_bzrdir.find_repository()
  File "/usr/lib/python2.7/dist-packages/bzrlib/bzrdir.py", line 574, in find_repository
    raise errors.NoRepositoryPresent(self)
bzrlib.errors.NoRepositoryPresent: No repository present: "bzr+ssh://bazaar.launchpad.net/~goose-bot/goose/"

lp:~gophers/goose/trunk updated
50. By Martin Packman

Add simple tool for deleting security groups

Basic test of using the current api for doing some actual task. As the
live tests create lots of security groups without ever deleting them
it's also sort of useful to have around.

R=wallyworld, jameinel, rog, dimitern
CC=
https://codereview.appspot.com/6948051

51. By Ian Booth

Goose test infrastructure improvements

This branch improves the usabilty of the goose test infrastructure.

A full Openstack service test double is provided. A bunch of manual coding which was required in each test suite to set up various Openstack module test doubles is replaced by a few lines of code.

New code:

        cred := &identity.Credentials{...}
 openstack := openstack.New(cred)
 openstack.SetupHTTP(s.Mux)

Old code:

 // Create the identity service.
 s.identityDouble = identityservice.NewUserPass()
 token := s.identityDouble.AddUser(s.cred.User, s.cred.Secrets)
 s.Mux.Handle(baseIdentityURL, s.identityDouble)

 // Register Swift endpoints with identity service.
 ep := identityservice.Endpoint{
  AdminURL: s.Server.URL + baseSwiftURL,
  InternalURL: s.Server.URL + baseSwiftURL,
  PublicURL: s.Server.URL + baseSwiftURL,
  Region: s.cred.Region,
 }
 service := identityservice.Service{"swift", "object-store", []identityservice.Endpoint{ep}}
 s.identityDouble.AddService(service)
 s.swiftDouble = swiftservice.New("localhost", baseSwiftURL+"/", token)
 s.Mux.Handle(baseSwiftURL+"/", s.swiftDouble)

 // Register Nova endpoints with identity service.
 ep = identityservice.Endpoint{
  AdminURL: s.Server.URL + baseNovaURL,
  InternalURL: s.Server.URL + baseNovaURL,
  PublicURL: s.Server.URL + baseNovaURL,
  Region: s.cred.Region,
 }
 service = identityservice.Service{"nova", "compute", []identityservice.Endpoint{ep}}
 s.identityDouble.AddService(service)
 s.novaDouble = novaservice.New("localhost", "V1", token, "1")
 s.novaDouble.SetupHTTP(s.Mux)

Other changes include:

- fix the identity service double to remove the hard coded userId and tenantId.
- do not hard code a fixed token against a service double - each user is assigned their own token just like a real instance, allowing multi-user tests to be written.
- improvements to the Swift service double to make it use URLs which are compliant with how a real Swift instance would do it.
- factor out a common base class for the legacy and userpass double implementations.
- use a SetupHTTP() for all test doubles instead of requiring the coder to know the magic URL paths to use.

R=dimitern
CC=
https://codereview.appspot.com/7194043

52. By John A Meinel

Add live and local tests for identity code.

53. By John A Meinel

Include the fixes for no-repository present.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'client/local_test.go'
--- client/local_test.go 2013-01-14 13:01:04 +0000
+++ client/local_test.go 2013-01-28 18:22:27 +0000
@@ -4,8 +4,9 @@
4 . "launchpad.net/gocheck"4 . "launchpad.net/gocheck"
5 "launchpad.net/goose/identity"5 "launchpad.net/goose/identity"
6 "launchpad.net/goose/testing/httpsuite"6 "launchpad.net/goose/testing/httpsuite"
7 "launchpad.net/goose/testservices"
7 "launchpad.net/goose/testservices/identityservice"8 "launchpad.net/goose/testservices/identityservice"
8 "net/http"9 "launchpad.net/goose/testservices/openstack"
9)10)
1011
11func registerLocalTests(authMethods []identity.AuthMethod) {12func registerLocalTests(authMethods []identity.AuthMethod) {
@@ -24,38 +25,30 @@
24 LiveTests25 LiveTests
25 // The following attributes are for using testing doubles.26 // The following attributes are for using testing doubles.
26 httpsuite.HTTPSuite27 httpsuite.HTTPSuite
27 identityDouble http.Handler28 service testservices.HttpService
28}29}
2930
30func (s *localLiveSuite) SetUpSuite(c *C) {31func (s *localLiveSuite) SetUpSuite(c *C) {
31 c.Logf("Using identity service test double")32 c.Logf("Using identity service test double")
32 s.HTTPSuite.SetUpSuite(c)33 s.HTTPSuite.SetUpSuite(c)
33 s.cred = &identity.Credentials{34 s.cred = &identity.Credentials{
34 URL: s.Server.URL,35 URL: s.Server.URL,
35 User: "fred",36 User: "fred",
36 Secrets: "secret",37 Secrets: "secret",
37 Region: "some region"}38 Region: "some region",
39 TenantName: "tenant",
40 }
38 switch s.authMethod {41 switch s.authMethod {
39 default:42 default:
40 panic("Invalid authentication method")43 panic("Invalid authentication method")
41 case identity.AuthUserPass:44 case identity.AuthUserPass:
42 s.identityDouble = identityservice.NewUserPass()45 // The openstack test service sets up userpass authentication.
43 s.identityDouble.(*identityservice.UserPass).AddUser(s.cred.User, s.cred.Secrets)46 s.service = openstack.New(s.cred)
44 ep := identityservice.Endpoint{
45 AdminURL: s.Server.URL,
46 InternalURL: s.Server.URL,
47 PublicURL: s.Server.URL,
48 Region: s.LiveTests.cred.Region,
49 }
50 service := identityservice.Service{"nova", "compute", []identityservice.Endpoint{ep}}
51 s.identityDouble.(*identityservice.UserPass).AddService(service)
52 service = identityservice.Service{"swift", "object-store", []identityservice.Endpoint{ep}}
53 s.identityDouble.(*identityservice.UserPass).AddService(service)
54 case identity.AuthLegacy:47 case identity.AuthLegacy:
55 s.identityDouble = identityservice.NewLegacy()48 legacy := identityservice.NewLegacy()
56 var legacy = s.identityDouble.(*identityservice.Legacy)49 legacy.AddUser(s.cred.User, s.cred.Secrets, s.cred.TenantName)
57 legacy.AddUser(s.cred.User, s.cred.Secrets)
58 legacy.SetManagementURL("http://management.test.invalid/url")50 legacy.SetManagementURL("http://management.test.invalid/url")
51 s.service = legacy
59 }52 }
60 s.LiveTests.SetUpSuite(c)53 s.LiveTests.SetUpSuite(c)
61}54}
@@ -67,7 +60,7 @@
6760
68func (s *localLiveSuite) SetUpTest(c *C) {61func (s *localLiveSuite) SetUpTest(c *C) {
69 s.HTTPSuite.SetUpTest(c)62 s.HTTPSuite.SetUpTest(c)
70 s.Mux.Handle("/", s.identityDouble)63 s.service.SetupHTTP(s.Mux)
71 s.LiveTests.SetUpTest(c)64 s.LiveTests.SetUpTest(c)
72}65}
7366
7467
=== modified file 'identity/legacy_test.go'
--- identity/legacy_test.go 2013-01-14 03:18:37 +0000
+++ identity/legacy_test.go 2013-01-28 18:22:27 +0000
@@ -15,13 +15,13 @@
15func (s *LegacyTestSuite) TestAuthAgainstServer(c *C) {15func (s *LegacyTestSuite) TestAuthAgainstServer(c *C) {
16 service := identityservice.NewLegacy()16 service := identityservice.NewLegacy()
17 s.Mux.Handle("/", service)17 s.Mux.Handle("/", service)
18 token := service.AddUser("joe-user", "secrets")18 userInfo := service.AddUser("joe-user", "secrets", "tenant")
19 service.SetManagementURL("http://management.test.invalid/url")19 service.SetManagementURL("http://management.test.invalid/url")
20 var l Authenticator = &Legacy{}20 var l Authenticator = &Legacy{}
21 creds := Credentials{User: "joe-user", URL: s.Server.URL, Secrets: "secrets"}21 creds := Credentials{User: "joe-user", URL: s.Server.URL, Secrets: "secrets"}
22 auth, err := l.Auth(&creds)22 auth, err := l.Auth(&creds)
23 c.Assert(err, IsNil)23 c.Assert(err, IsNil)
24 c.Assert(auth.Token, Equals, token)24 c.Assert(auth.Token, Equals, userInfo.Token)
25 c.Assert(25 c.Assert(
26 auth.ServiceURLs, DeepEquals,26 auth.ServiceURLs, DeepEquals,
27 map[string]string{"compute": "http://management.test.invalid/url/compute", "object-store": "http://management.test.invalid/url/object-store"})27 map[string]string{"compute": "http://management.test.invalid/url/compute", "object-store": "http://management.test.invalid/url/object-store"})
@@ -30,7 +30,7 @@
30func (s *LegacyTestSuite) TestBadAuth(c *C) {30func (s *LegacyTestSuite) TestBadAuth(c *C) {
31 service := identityservice.NewLegacy()31 service := identityservice.NewLegacy()
32 s.Mux.Handle("/", service)32 s.Mux.Handle("/", service)
33 _ = service.AddUser("joe-user", "secrets")33 _ = service.AddUser("joe-user", "secrets", "tenant")
34 var l Authenticator = &Legacy{}34 var l Authenticator = &Legacy{}
35 creds := Credentials{User: "joe-user", URL: s.Server.URL, Secrets: "bad-secrets"}35 creds := Credentials{User: "joe-user", URL: s.Server.URL, Secrets: "bad-secrets"}
36 auth, err := l.Auth(&creds)36 auth, err := l.Auth(&creds)
3737
=== added file 'identity/live_test.go'
--- identity/live_test.go 1970-01-01 00:00:00 +0000
+++ identity/live_test.go 2013-01-28 18:22:27 +0000
@@ -0,0 +1,40 @@
1package identity_test
2
3import (
4 . "launchpad.net/gocheck"
5 "launchpad.net/goose/client"
6 "launchpad.net/goose/identity"
7)
8
9func registerOpenStackTests(cred *identity.Credentials) {
10 Suite(&LiveTests{
11 cred: cred,
12 })
13}
14
15type LiveTests struct {
16 cred *identity.Credentials
17 client client.AuthenticatingClient
18}
19
20func (s *LiveTests) SetUpSuite(c *C) {
21 s.client = client.NewClient(s.cred, identity.AuthUserPass, nil)
22}
23
24func (s *LiveTests) TearDownSuite(c *C) {
25}
26
27func (s *LiveTests) SetUpTest(c *C) {
28 // noop, called by local test suite.
29}
30
31func (s *LiveTests) TearDownTest(c *C) {
32 // noop, called by local test suite.
33}
34
35func (s *LiveTests) TestAuth(c *C) {
36 s.client.Authenticate()
37 url, err := s.client.MakeServiceURL("compute", []string{})
38 c.Assert(err, IsNil)
39 c.Assert(url[:len(s.cred.URL)], Equals, s.cred.URL)
40}
041
=== added file 'identity/local_test.go'
--- identity/local_test.go 1970-01-01 00:00:00 +0000
+++ identity/local_test.go 2013-01-28 18:22:27 +0000
@@ -0,0 +1,63 @@
1package identity_test
2
3import (
4 . "launchpad.net/gocheck"
5 "launchpad.net/goose/identity"
6 "launchpad.net/goose/testservices/openstack"
7 "net/http"
8 "net/http/httptest"
9)
10
11func registerLocalTests() {
12 Suite(&localLiveSuite{})
13}
14
15// localLiveSuite runs tests from LiveTests using a fake
16// nova server that runs within the test process itself.
17type localLiveSuite struct {
18 LiveTests
19 // The following attributes are for using testing doubles.
20 Server *httptest.Server
21 Mux *http.ServeMux
22 oldHandler http.Handler
23}
24
25func (s *localLiveSuite) SetUpSuite(c *C) {
26 c.Logf("Using identity and nova service test doubles")
27
28 // Set up the HTTP server.
29 s.Server = httptest.NewServer(nil)
30 s.oldHandler = s.Server.Config.Handler
31 s.Mux = http.NewServeMux()
32 s.Server.Config.Handler = s.Mux
33
34 // Set up an Openstack service.
35 s.cred = &identity.Credentials{
36 URL: s.Server.URL,
37 User: "fred",
38 Secrets: "secret",
39 Region: "some region",
40 TenantName: "tenant",
41 }
42 openstack := openstack.New(s.cred)
43 openstack.SetupHTTP(s.Mux)
44
45 s.LiveTests.SetUpSuite(c)
46}
47
48func (s *localLiveSuite) TearDownSuite(c *C) {
49 s.LiveTests.TearDownSuite(c)
50 s.Mux = nil
51 s.Server.Config.Handler = s.oldHandler
52 s.Server.Close()
53}
54
55func (s *localLiveSuite) SetUpTest(c *C) {
56 s.LiveTests.SetUpTest(c)
57}
58
59func (s *localLiveSuite) TearDownTest(c *C) {
60 s.LiveTests.TearDownTest(c)
61}
62
63// Additional tests to be run against the service double only go here.
064
=== modified file 'identity/setup_test.go'
--- identity/setup_test.go 2012-11-02 10:59:29 +0000
+++ identity/setup_test.go 2013-01-28 18:22:27 +0000
@@ -1,10 +1,22 @@
1package identity1package identity_test
22
3import (3import (
4 "flag"
4 . "launchpad.net/gocheck"5 . "launchpad.net/gocheck"
6 "launchpad.net/goose/identity"
5 "testing"7 "testing"
6)8)
79
10var live = flag.Bool("live", false, "Include live OpenStack (Canonistack) tests")
11
8func Test(t *testing.T) {12func Test(t *testing.T) {
13 if *live {
14 cred, err := identity.CompleteCredentialsFromEnv()
15 if err != nil {
16 t.Fatalf("Error setting up test suite: %s", err.Error())
17 }
18 registerOpenStackTests(cred)
19 }
20 registerLocalTests()
9 TestingT(t)21 TestingT(t)
10}22}
1123
=== modified file 'identity/userpass.go'
--- identity/userpass.go 2012-12-11 04:09:12 +0000
+++ identity/userpass.go 2013-01-28 18:22:27 +0000
@@ -4,6 +4,7 @@
4 "fmt"4 "fmt"
5 goosehttp "launchpad.net/goose/http"5 goosehttp "launchpad.net/goose/http"
6 "net/http"6 "net/http"
7 "os"
7)8)
89
9type passwordCredentials struct {10type passwordCredentials struct {
@@ -104,6 +105,9 @@
104 service.Endpoints = append(service.Endpoints[:i], service.Endpoints[i+1:]...)105 service.Endpoints = append(service.Endpoints[:i], service.Endpoints[i+1:]...)
105 }106 }
106 }107 }
108 if len(service.Endpoints) == 0 {
109 fmt.Fprintf(os.Stderr, "Found no endpoints for %v\n", service.Type)
110 }
107 details.ServiceURLs[service.Type] = service.Endpoints[0].PublicURL111 details.ServiceURLs[service.Type] = service.Endpoints[0].PublicURL
108 }112 }
109113
110114
=== modified file 'identity/userpass_test.go'
--- identity/userpass_test.go 2013-01-14 03:18:37 +0000
+++ identity/userpass_test.go 2013-01-28 18:22:27 +0000
@@ -14,12 +14,12 @@
1414
15func (s *UserPassTestSuite) TestAuthAgainstServer(c *C) {15func (s *UserPassTestSuite) TestAuthAgainstServer(c *C) {
16 service := identityservice.NewUserPass()16 service := identityservice.NewUserPass()
17 s.Mux.Handle("/", service)17 service.SetupHTTP(s.Mux)
18 token := service.AddUser("joe-user", "secrets")18 userInfo := service.AddUser("joe-user", "secrets", "tenant")
19 var l Authenticator = &UserPass{}19 var l Authenticator = &UserPass{}
20 creds := Credentials{User: "joe-user", URL: s.Server.URL, Secrets: "secrets"}20 creds := Credentials{User: "joe-user", URL: s.Server.URL + "/tokens", Secrets: "secrets"}
21 auth, err := l.Auth(&creds)21 auth, err := l.Auth(&creds)
22 c.Assert(err, IsNil)22 c.Assert(err, IsNil)
23 c.Assert(auth.Token, Equals, token)23 c.Assert(auth.Token, Equals, userInfo.Token)
24 // c.Assert(auth.ServiceURLs, DeepEquals, map[string]string{"compute": "http://management.test.invalid/url"})24 c.Assert(auth.TenantId, Equals, userInfo.TenantId)
25}25}
2626
=== modified file 'nova/local_test.go'
--- nova/local_test.go 2013-01-21 11:18:33 +0000
+++ nova/local_test.go 2013-01-28 18:22:27 +0000
@@ -7,8 +7,7 @@
7 "launchpad.net/goose/errors"7 "launchpad.net/goose/errors"
8 "launchpad.net/goose/identity"8 "launchpad.net/goose/identity"
9 "launchpad.net/goose/nova"9 "launchpad.net/goose/nova"
10 "launchpad.net/goose/testservices/identityservice"10 "launchpad.net/goose/testservices/openstack"
11 "launchpad.net/goose/testservices/novaservice"
12 "log"11 "log"
13 "net/http"12 "net/http"
14 "net/http/httptest"13 "net/http/httptest"
@@ -19,20 +18,14 @@
19 Suite(&localLiveSuite{})18 Suite(&localLiveSuite{})
20}19}
2120
22const (
23 baseNovaURL = "/V1/1"
24)
25
26// localLiveSuite runs tests from LiveTests using a fake21// localLiveSuite runs tests from LiveTests using a fake
27// nova server that runs within the test process itself.22// nova server that runs within the test process itself.
28type localLiveSuite struct {23type localLiveSuite struct {
29 LiveTests24 LiveTests
30 // The following attributes are for using testing doubles.25 // The following attributes are for using testing doubles.
31 Server *httptest.Server26 Server *httptest.Server
32 Mux *http.ServeMux27 Mux *http.ServeMux
33 oldHandler http.Handler28 oldHandler http.Handler
34 identityDouble *identityservice.UserPass
35 novaDouble *novaservice.Nova
36}29}
3730
38func (s *localLiveSuite) SetUpSuite(c *C) {31func (s *localLiveSuite) SetUpSuite(c *C) {
@@ -44,29 +37,16 @@
44 s.Mux = http.NewServeMux()37 s.Mux = http.NewServeMux()
45 s.Server.Config.Handler = s.Mux38 s.Server.Config.Handler = s.Mux
4639
40 // Set up an Openstack service.
47 s.cred = &identity.Credentials{41 s.cred = &identity.Credentials{
48 URL: s.Server.URL,42 URL: s.Server.URL,
49 User: "fred",43 User: "fred",
50 Secrets: "secret",44 Secrets: "secret",
51 Region: "some region"}45 Region: "some region",
52 // Create an identity service and register a Nova endpoint.46 TenantName: "tenant",
53 s.identityDouble = identityservice.NewUserPass()
54 token := s.identityDouble.AddUser(s.cred.User, s.cred.Secrets)
55 ep := identityservice.Endpoint{
56 AdminURL: s.Server.URL + baseNovaURL,
57 InternalURL: s.Server.URL + baseNovaURL,
58 PublicURL: s.Server.URL + baseNovaURL,
59 Region: s.cred.Region,
60 }47 }
61 s.Mux.Handle("/tokens", s.identityDouble)48 openstack := openstack.New(s.cred)
6249 openstack.SetupHTTP(s.Mux)
63 service := identityservice.Service{"nova", "compute", []identityservice.Endpoint{ep}}
64 s.identityDouble.AddService(service)
65 // Create a nova service at the registered endpoint.
66 // TODO: identityservice.UserPass always uses tenantId="1", patch this
67 // when that changes.
68 s.novaDouble = novaservice.New("localhost", "V1", token, "1")
69 s.novaDouble.SetupHTTP(s.Mux)
7050
71 s.LiveTests.SetUpSuite(c)51 s.LiveTests.SetUpSuite(c)
72}52}
7353
=== modified file 'swift/local_test.go'
--- swift/local_test.go 2012-12-20 05:47:11 +0000
+++ swift/local_test.go 2013-01-28 18:22:27 +0000
@@ -4,19 +4,13 @@
4 . "launchpad.net/gocheck"4 . "launchpad.net/gocheck"
5 "launchpad.net/goose/identity"5 "launchpad.net/goose/identity"
6 "launchpad.net/goose/testing/httpsuite"6 "launchpad.net/goose/testing/httpsuite"
7 "launchpad.net/goose/testservices/identityservice"7 "launchpad.net/goose/testservices/openstack"
8 "launchpad.net/goose/testservices/swiftservice"
9 "net/http"
10)8)
119
12func registerLocalTests() {10func registerLocalTests() {
13 Suite(&localLiveSuite{})11 Suite(&localLiveSuite{})
14}12}
1513
16const (
17 baseURL = "/object-store"
18)
19
20// localLiveSuite runs tests from LiveTests using a fake14// localLiveSuite runs tests from LiveTests using a fake
21// swift server that runs within the test process itself.15// swift server that runs within the test process itself.
22type localLiveSuite struct {16type localLiveSuite struct {
@@ -24,32 +18,23 @@
24 LiveTestsPublicContainer18 LiveTestsPublicContainer
25 // The following attributes are for using testing doubles.19 // The following attributes are for using testing doubles.
26 httpsuite.HTTPSuite20 httpsuite.HTTPSuite
27 identityDouble http.Handler21 openstack *openstack.Openstack
28 swiftDouble http.Handler
29}22}
3023
31func (s *localLiveSuite) SetUpSuite(c *C) {24func (s *localLiveSuite) SetUpSuite(c *C) {
32 c.Logf("Using identity and swift service test doubles")25 c.Logf("Using identity and swift service test doubles")
33 s.HTTPSuite.SetUpSuite(c)26 s.HTTPSuite.SetUpSuite(c)
27 // Set up an Openstack service.
34 s.LiveTests.cred = &identity.Credentials{28 s.LiveTests.cred = &identity.Credentials{
35 URL: s.Server.URL,29 URL: s.Server.URL,
36 User: "fred",30 User: "fred",
37 Secrets: "secret",31 Secrets: "secret",
38 Region: "some region"}32 Region: "some region",
33 TenantName: "tenant",
34 }
39 s.LiveTestsPublicContainer.cred = s.LiveTests.cred35 s.LiveTestsPublicContainer.cred = s.LiveTests.cred
40 // Create an identity service and register a Swift endpoint.36 s.openstack = openstack.New(s.LiveTests.cred)
41 s.identityDouble = identityservice.NewUserPass()37
42 token := s.identityDouble.(*identityservice.UserPass).AddUser(s.LiveTests.cred.User, s.LiveTests.cred.Secrets)
43 ep := identityservice.Endpoint{
44 s.Server.URL + baseURL, //admin
45 s.Server.URL + baseURL, //internal
46 s.Server.URL + baseURL, //public
47 s.LiveTests.cred.Region,
48 }
49 service := identityservice.Service{"swift", "object-store", []identityservice.Endpoint{ep}}
50 s.identityDouble.(*identityservice.UserPass).AddService(service)
51 // Create a swift service at the registered endpoint.
52 s.swiftDouble = swiftservice.New("localhost", baseURL+"/", token)
53 s.LiveTests.SetUpSuite(c)38 s.LiveTests.SetUpSuite(c)
54 s.LiveTestsPublicContainer.SetUpSuite(c)39 s.LiveTestsPublicContainer.SetUpSuite(c)
55}40}
@@ -62,8 +47,7 @@
6247
63func (s *localLiveSuite) SetUpTest(c *C) {48func (s *localLiveSuite) SetUpTest(c *C) {
64 s.HTTPSuite.SetUpTest(c)49 s.HTTPSuite.SetUpTest(c)
65 s.Mux.Handle(baseURL+"/", s.swiftDouble)50 s.openstack.SetupHTTP(s.Mux)
66 s.Mux.Handle("/", s.identityDouble)
67 s.LiveTests.SetUpTest(c)51 s.LiveTests.SetUpTest(c)
68 s.LiveTestsPublicContainer.SetUpTest(c)52 s.LiveTestsPublicContainer.SetUpTest(c)
69}53}
7054
=== modified file 'test.py'
--- test.py 2013-01-23 10:25:44 +0000
+++ test.py 2013-01-28 18:22:27 +0000
@@ -8,6 +8,7 @@
8KNOWN_LIVE_SUITES = [8KNOWN_LIVE_SUITES = [
9 'client',9 'client',
10 'glance',10 'glance',
11 'identity',
11 'nova',12 'nova',
12 'swift',13 'swift',
13]14]
1415
=== added directory 'testservices/cmd'
=== renamed file 'testservices/main.go' => 'testservices/cmd/main.go'
--- testservices/main.go 2012-11-11 11:13:52 +0000
+++ testservices/cmd/main.go 2013-01-28 18:22:27 +0000
@@ -61,9 +61,10 @@
61 if !ok {61 if !ok {
62 log.Fatalf("No such provider: %s, pick one of: %v", provider, providers())62 log.Fatalf("No such provider: %s, pick one of: %v", provider, providers())
63 }63 }
64 http.Handle("/", p)64 mux := http.NewServeMux()
65 p.SetupHTTP(mux)
65 for _, u := range users.users {66 for _, u := range users.users {
66 p.AddUser(u.user, u.secret)67 p.AddUser(u.user, u.secret, "tenant")
67 }68 }
68 log.Fatal(http.ListenAndServe(*serveAddr, nil))69 log.Fatal(http.ListenAndServe(*serveAddr, mux))
69}70}
7071
=== modified file 'testservices/identityservice/identityservice.go'
--- testservices/identityservice/identityservice.go 2012-11-11 11:13:52 +0000
+++ testservices/identityservice/identityservice.go 2013-01-28 18:22:27 +0000
@@ -1,10 +1,16 @@
1package identityservice1package identityservice
22
3import (3import "net/http"
4 "net/http"
5)
64
5// An IdentityService provides user authentication for an Openstack instance.
7type IdentityService interface {6type IdentityService interface {
8 AddUser(user, secret string) (token string)7 AddUser(user, secret, tenant string) *UserInfo
9 ServeHTTP(w http.ResponseWriter, r *http.Request)8 FindUser(token string) (*UserInfo, error)
9 RegisterServiceProvider(name, serviceType string, serviceProvider ServiceProvider)
10 SetupHTTP(mux *http.ServeMux)
11}
12
13// A ServiceProvider is an Openstack module which has service endpoints.
14type ServiceProvider interface {
15 Endpoints() []Endpoint
10}16}
1117
=== modified file 'testservices/identityservice/legacy.go'
--- testservices/identityservice/legacy.go 2012-12-21 05:07:39 +0000
+++ testservices/identityservice/legacy.go 2013-01-28 18:22:27 +0000
@@ -5,40 +5,48 @@
5)5)
66
7type Legacy struct {7type Legacy struct {
8 tokens map[string]UserInfo8 Users
9 managementURL string9 managementURL string
10}10}
1111
12func NewLegacy() *Legacy {12func NewLegacy() *Legacy {
13 service := &Legacy{}13 service := &Legacy{}
14 service.tokens = make(map[string]UserInfo)14 service.users = make(map[string]UserInfo)
15 service.tenants = make(map[string]string)
15 return service16 return service
16}17}
1718
19func (lis *Legacy) RegisterServiceProvider(name, serviceType string, serviceProvider ServiceProvider) {
20 // NOOP for legacy identity service.
21}
22
18func (lis *Legacy) SetManagementURL(URL string) {23func (lis *Legacy) SetManagementURL(URL string) {
19 lis.managementURL = URL24 lis.managementURL = URL
20}25}
2126
22func (lis *Legacy) AddUser(user, secret string) string {27// setupHTTP attaches all the needed handlers to provide the HTTP API.
23 token := randomHexToken()28func (lis *Legacy) SetupHTTP(mux *http.ServeMux) {
24 lis.tokens[user] = UserInfo{secret: secret, token: token}29 mux.Handle("/", lis)
25 return token
26}30}
2731
28func (lis *Legacy) ServeHTTP(w http.ResponseWriter, r *http.Request) {32func (lis *Legacy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
29 username := r.Header.Get("X-Auth-User")33 username := r.Header.Get("X-Auth-User")
30 info, ok := lis.tokens[username]34 userInfo, ok := lis.users[username]
31 if !ok {35 if !ok {
32 w.WriteHeader(http.StatusUnauthorized)36 w.WriteHeader(http.StatusUnauthorized)
33 return37 return
34 }38 }
35 auth_key := r.Header.Get("X-Auth-Key")39 auth_key := r.Header.Get("X-Auth-Key")
36 if auth_key != info.secret {40 if auth_key != userInfo.secret {
37 w.WriteHeader(http.StatusUnauthorized)41 w.WriteHeader(http.StatusUnauthorized)
38 return42 return
39 }43 }
44 if userInfo.Token == "" {
45 userInfo.Token = randomHexToken()
46 lis.users[username] = userInfo
47 }
40 header := w.Header()48 header := w.Header()
41 header.Set("X-Auth-Token", info.token)49 header.Set("X-Auth-Token", userInfo.Token)
42 header.Set("X-Server-Management-Url", lis.managementURL+"/compute")50 header.Set("X-Server-Management-Url", lis.managementURL+"/compute")
43 header.Set("X-Storage-Url", lis.managementURL+"/object-store")51 header.Set("X-Storage-Url", lis.managementURL+"/object-store")
44 w.WriteHeader(http.StatusNoContent)52 w.WriteHeader(http.StatusNoContent)
4553
=== modified file 'testservices/identityservice/legacy_test.go'
--- testservices/identityservice/legacy_test.go 2012-12-21 05:07:39 +0000
+++ testservices/identityservice/legacy_test.go 2013-01-28 18:22:27 +0000
@@ -19,9 +19,10 @@
19 // Ensure that it conforms to the interface19 // Ensure that it conforms to the interface
20 var _ IdentityService = identity20 var _ IdentityService = identity
21 identity.SetManagementURL(managementURL)21 identity.SetManagementURL(managementURL)
22 s.Mux.Handle("/", identity)22 identity.SetupHTTP(s.Mux)
23 if user != "" {23 if user != "" {
24 token = identity.AddUser(user, secret)24 userInfo := identity.AddUser(user, secret, "tenant")
25 token = userInfo.Token
25 }26 }
26 return27 return
27}28}
2829
=== modified file 'testservices/identityservice/service_test.go'
--- testservices/identityservice/service_test.go 2012-11-11 15:29:52 +0000
+++ testservices/identityservice/service_test.go 2013-01-28 18:22:27 +0000
@@ -17,7 +17,7 @@
17var _ = Suite(&IdentityServiceSuite{service: NewLegacy()})17var _ = Suite(&IdentityServiceSuite{service: NewLegacy()})
1818
19func (s *IdentityServiceSuite) TestAddUserGivesNewToken(c *C) {19func (s *IdentityServiceSuite) TestAddUserGivesNewToken(c *C) {
20 token1 := s.service.AddUser("user-1", "password-1")20 userInfo1 := s.service.AddUser("user-1", "password-1", "tenant")
21 token2 := s.service.AddUser("user-2", "password-2")21 userInfo2 := s.service.AddUser("user-2", "password-2", "tenant")
22 c.Assert(token1, Not(Equals), token2)22 c.Assert(userInfo1.Token, Not(Equals), userInfo2.Token)
23}23}
2424
=== modified file 'testservices/identityservice/userpass.go'
--- testservices/identityservice/userpass.go 2012-12-21 04:10:14 +0000
+++ testservices/identityservice/userpass.go 2013-01-28 18:22:27 +0000
@@ -135,25 +135,25 @@
135}`135}`
136136
137type UserPass struct {137type UserPass struct {
138 users map[string]UserInfo138 Users
139 services []Service139 services []Service
140}140}
141141
142func NewUserPass() *UserPass {142func NewUserPass() *UserPass {
143 userpass := &UserPass{143 userpass := &UserPass{
144 users: make(map[string]UserInfo),
145 services: make([]Service, 0),144 services: make([]Service, 0),
146 }145 }
146 userpass.users = make(map[string]UserInfo)
147 userpass.tenants = make(map[string]string)
147 return userpass148 return userpass
148}149}
149150
150func (u *UserPass) AddUser(user, secret string) string {151func (u *UserPass) RegisterServiceProvider(name, serviceType string, serviceProvider ServiceProvider) {
151 token := randomHexToken()152 service := Service{name, serviceType, serviceProvider.Endpoints()}
152 u.users[user] = UserInfo{secret: secret, token: token}153 u.addService(service)
153 return token
154}154}
155155
156func (u *UserPass) AddService(service Service) {156func (u *UserPass) addService(service Service) {
157 u.services = append(u.services, service)157 u.services = append(u.services, service)
158}158}
159159
@@ -189,8 +189,6 @@
189 notJSON = ("Expecting to find application/json in Content-Type header." +189 notJSON = ("Expecting to find application/json in Content-Type header." +
190 " The server could not comply with the request since it is either malformed" +190 " The server could not comply with the request since it is either malformed" +
191 " or otherwise incorrect. The client is assumed to be in error.")191 " or otherwise incorrect. The client is assumed to be in error.")
192 notAuthorized = "The request you have made requires authentication."
193 invalidUser = "Invalid user / password"
194)192)
195193
196func (u *UserPass) ServeHTTP(w http.ResponseWriter, r *http.Request) {194func (u *UserPass) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@@ -210,13 +208,9 @@
210 return208 return
211 }209 }
212 }210 }
213 userInfo, ok := u.users[req.Auth.PasswordCredentials.Username]211 userInfo, errmsg := u.authenticate(req.Auth.PasswordCredentials.Username, req.Auth.PasswordCredentials.Password)
214 if !ok {212 if errmsg != "" {
215 u.ReturnFailure(w, http.StatusUnauthorized, notAuthorized)213 u.ReturnFailure(w, http.StatusUnauthorized, errmsg)
216 return
217 }
218 if userInfo.secret != req.Auth.PasswordCredentials.Password {
219 u.ReturnFailure(w, http.StatusUnauthorized, invalidUser)
220 return214 return
221 }215 }
222 res := AccessResponse{}216 res := AccessResponse{}
@@ -228,7 +222,9 @@
228 return222 return
229 }223 }
230 res.Access.ServiceCatalog = u.services224 res.Access.ServiceCatalog = u.services
231 res.Access.Token.Id = userInfo.token225 res.Access.Token.Id = userInfo.Token
226 res.Access.Token.Tenant.Id = userInfo.TenantId
227 res.Access.User.Id = userInfo.Id
232 if content, err := json.Marshal(res); err != nil {228 if content, err := json.Marshal(res); err != nil {
233 u.ReturnFailure(w, http.StatusInternalServerError, err.Error())229 u.ReturnFailure(w, http.StatusInternalServerError, err.Error())
234 return230 return
@@ -239,3 +235,8 @@
239 }235 }
240 panic("All paths should have already returned")236 panic("All paths should have already returned")
241}237}
238
239// setupHTTP attaches all the needed handlers to provide the HTTP API.
240func (u *UserPass) SetupHTTP(mux *http.ServeMux) {
241 mux.Handle("/tokens", u)
242}
242243
=== modified file 'testservices/identityservice/userpass_test.go'
--- testservices/identityservice/userpass_test.go 2012-11-16 16:39:16 +0000
+++ testservices/identityservice/userpass_test.go 2013-01-28 18:22:27 +0000
@@ -16,30 +16,30 @@
1616
17var _ = Suite(&UserPassSuite{})17var _ = Suite(&UserPassSuite{})
1818
19func makeUserPass(user, secret string) (identity *UserPass, token string) {19func makeUserPass(user, secret string) (identity *UserPass) {
20 identity = NewUserPass()20 identity = NewUserPass()
21 // Ensure that it conforms to the interface21 // Ensure that it conforms to the interface
22 var _ IdentityService = identity22 var _ IdentityService = identity
23 if user != "" {23 if user != "" {
24 token = identity.AddUser(user, secret)24 identity.AddUser(user, secret, "tenant")
25 }25 }
26 return26 return
27}27}
2828
29func (s *UserPassSuite) setupUserPass(user, secret string) (token string) {29func (s *UserPassSuite) setupUserPass(user, secret string) {
30 var identity *UserPass30 var identity *UserPass
31 identity, token = makeUserPass(user, secret)31 identity = makeUserPass(user, secret)
32 s.Mux.Handle("/", identity)32 identity.SetupHTTP(s.Mux)
33 return33 return
34}34}
3535
36func (s *UserPassSuite) setupUserPassWithServices(user, secret string, services []Service) (token string) {36func (s *UserPassSuite) setupUserPassWithServices(user, secret string, services []Service) {
37 var identity *UserPass37 var identity *UserPass
38 identity, token = makeUserPass(user, secret)38 identity = makeUserPass(user, secret)
39 for _, service := range services {39 for _, service := range services {
40 identity.AddService(service)40 identity.addService(service)
41 }41 }
42 s.Mux.Handle("/", identity)42 identity.SetupHTTP(s.Mux)
43 return43 return
44}44}
4545
@@ -56,7 +56,7 @@
56func userPassAuthRequest(URL, user, key string) (*http.Response, error) {56func userPassAuthRequest(URL, user, key string) (*http.Response, error) {
57 client := &http.Client{}57 client := &http.Client{}
58 body := strings.NewReader(fmt.Sprintf(authTemplate, user, key))58 body := strings.NewReader(fmt.Sprintf(authTemplate, user, key))
59 request, err := http.NewRequest("POST", URL, body)59 request, err := http.NewRequest("POST", URL+"/tokens", body)
60 request.Header.Set("Content-Type", "application/json")60 request.Header.Set("Content-Type", "application/json")
61 if err != nil {61 if err != nil {
62 return nil, err62 return nil, err
@@ -81,11 +81,10 @@
8181
82func (s *UserPassSuite) TestNotJSON(c *C) {82func (s *UserPassSuite) TestNotJSON(c *C) {
83 // We do everything in userPassAuthRequest, except set the Content-Type83 // We do everything in userPassAuthRequest, except set the Content-Type
84 token := s.setupUserPass("user", "secret")84 s.setupUserPass("user", "secret")
85 c.Assert(token, NotNil)
86 client := &http.Client{}85 client := &http.Client{}
87 body := strings.NewReader(fmt.Sprintf(authTemplate, "user", "secret"))86 body := strings.NewReader(fmt.Sprintf(authTemplate, "user", "secret"))
88 request, err := http.NewRequest("POST", s.Server.URL, body)87 request, err := http.NewRequest("POST", s.Server.URL+"/tokens", body)
89 c.Assert(err, IsNil)88 c.Assert(err, IsNil)
90 res, err := client.Do(request)89 res, err := client.Do(request)
91 defer res.Body.Close()90 defer res.Body.Close()
@@ -95,8 +94,7 @@
9594
96func (s *UserPassSuite) TestBadJSON(c *C) {95func (s *UserPassSuite) TestBadJSON(c *C) {
97 // We do everything in userPassAuthRequest, except set the Content-Type96 // We do everything in userPassAuthRequest, except set the Content-Type
98 token := s.setupUserPass("user", "secret")97 s.setupUserPass("user", "secret")
99 c.Assert(token, NotNil)
100 res, err := userPassAuthRequest(s.Server.URL, "garbage\"in", "secret")98 res, err := userPassAuthRequest(s.Server.URL, "garbage\"in", "secret")
101 defer res.Body.Close()99 defer res.Body.Close()
102 c.Assert(err, IsNil)100 c.Assert(err, IsNil)
@@ -104,8 +102,7 @@
104}102}
105103
106func (s *UserPassSuite) TestNoSuchUser(c *C) {104func (s *UserPassSuite) TestNoSuchUser(c *C) {
107 token := s.setupUserPass("user", "secret")105 s.setupUserPass("user", "secret")
108 c.Assert(token, NotNil)
109 res, err := userPassAuthRequest(s.Server.URL, "not-user", "secret")106 res, err := userPassAuthRequest(s.Server.URL, "not-user", "secret")
110 defer res.Body.Close()107 defer res.Body.Close()
111 c.Assert(err, IsNil)108 c.Assert(err, IsNil)
@@ -113,8 +110,7 @@
113}110}
114111
115func (s *UserPassSuite) TestBadPassword(c *C) {112func (s *UserPassSuite) TestBadPassword(c *C) {
116 token := s.setupUserPass("user", "secret")113 s.setupUserPass("user", "secret")
117 c.Assert(token, NotNil)
118 res, err := userPassAuthRequest(s.Server.URL, "user", "not-secret")114 res, err := userPassAuthRequest(s.Server.URL, "user", "not-secret")
119 defer res.Body.Close()115 defer res.Body.Close()
120 c.Assert(err, IsNil)116 c.Assert(err, IsNil)
@@ -123,11 +119,10 @@
123119
124func (s *UserPassSuite) TestValidAuthorization(c *C) {120func (s *UserPassSuite) TestValidAuthorization(c *C) {
125 compute_url := "http://testing.invalid/compute"121 compute_url := "http://testing.invalid/compute"
126 token := s.setupUserPassWithServices("user", "secret", []Service{122 s.setupUserPassWithServices("user", "secret", []Service{
127 {"nova", "compute", []Endpoint{123 {"nova", "compute", []Endpoint{
128 {PublicURL: compute_url},124 {PublicURL: compute_url},
129 }}})125 }}})
130 c.Assert(token, NotNil)
131 res, err := userPassAuthRequest(s.Server.URL, "user", "secret")126 res, err := userPassAuthRequest(s.Server.URL, "user", "secret")
132 defer res.Body.Close()127 defer res.Body.Close()
133 c.Assert(err, IsNil)128 c.Assert(err, IsNil)
@@ -138,7 +133,7 @@
138 var response AccessResponse133 var response AccessResponse
139 err = json.Unmarshal(content, &response)134 err = json.Unmarshal(content, &response)
140 c.Assert(err, IsNil)135 c.Assert(err, IsNil)
141 c.Check(response.Access.Token.Id, Equals, token)136 c.Check(response.Access.Token.Id, NotNil)
142 novaURL := ""137 novaURL := ""
143 for _, service := range response.Access.ServiceCatalog {138 for _, service := range response.Access.ServiceCatalog {
144 if service.Type == "compute" {139 if service.Type == "compute" {
145140
=== added file 'testservices/identityservice/users.go'
--- testservices/identityservice/users.go 1970-01-01 00:00:00 +0000
+++ testservices/identityservice/users.go 2013-01-28 18:22:27 +0000
@@ -0,0 +1,63 @@
1package identityservice
2
3import (
4 "fmt"
5 "strconv"
6)
7
8type Users struct {
9 nextUserId int
10 nextTenantId int
11 users map[string]UserInfo
12 tenants map[string]string
13}
14
15func (u *Users) addTenant(tenant string) string {
16 for id, tenantName := range u.tenants {
17 if tenant == tenantName {
18 return id
19 }
20 }
21 u.nextTenantId++
22 id := strconv.Itoa(u.nextTenantId)
23 u.tenants[id] = tenant
24 return id
25}
26
27func (u *Users) AddUser(user, secret, tenant string) *UserInfo {
28 tenantId := u.addTenant(tenant)
29 u.nextUserId++
30 userInfo := &UserInfo{secret: secret, Id: strconv.Itoa(u.nextUserId), TenantId: tenantId}
31 u.users[user] = *userInfo
32 userInfo, _ = u.authenticate(user, secret)
33 return userInfo
34}
35
36func (u *Users) FindUser(token string) (*UserInfo, error) {
37 for _, userInfo := range u.users {
38 if userInfo.Token == token {
39 return &userInfo, nil
40 }
41 }
42 return nil, fmt.Errorf("No user with token %v exists", token)
43}
44
45const (
46 notAuthorized = "The request you have made requires authentication."
47 invalidUser = "Invalid user / password"
48)
49
50func (u *Users) authenticate(username, password string) (*UserInfo, string) {
51 userInfo, ok := u.users[username]
52 if !ok {
53 return nil, notAuthorized
54 }
55 if userInfo.secret != password {
56 return nil, invalidUser
57 }
58 if userInfo.Token == "" {
59 userInfo.Token = randomHexToken()
60 u.users[username] = userInfo
61 }
62 return &userInfo, ""
63}
064
=== modified file 'testservices/identityservice/util.go'
--- testservices/identityservice/util.go 2012-11-23 03:05:16 +0000
+++ testservices/identityservice/util.go 2013-01-28 18:22:27 +0000
@@ -7,8 +7,10 @@
7)7)
88
9type UserInfo struct {9type UserInfo struct {
10 secret string10 Id string
11 token string11 TenantId string
12 Token string
13 secret string
12}14}
1315
14// Generate a bit of random hex data for16// Generate a bit of random hex data for
1517
=== modified file 'testservices/novaservice/service.go'
--- testservices/novaservice/service.go 2013-01-21 23:57:31 +0000
+++ testservices/novaservice/service.go 2013-01-28 18:22:27 +0000
@@ -5,12 +5,19 @@
5import (5import (
6 "fmt"6 "fmt"
7 "launchpad.net/goose/nova"7 "launchpad.net/goose/nova"
8 "launchpad.net/goose/testservices"
9 "launchpad.net/goose/testservices/identityservice"
10 "net/url"
8 "strings"11 "strings"
9)12)
1013
14var _ testservices.HttpService = (*Nova)(nil)
15var _ identityservice.ServiceProvider = (*Nova)(nil)
16
11// Nova implements a OpenStack Nova testing service and17// Nova implements a OpenStack Nova testing service and
12// contains the service double's internal state.18// contains the service double's internal state.
13type Nova struct {19type Nova struct {
20 testservices.ServiceInstance
14 flavors map[string]nova.FlavorDetail21 flavors map[string]nova.FlavorDetail
15 servers map[string]nova.ServerDetail22 servers map[string]nova.ServerDetail
16 groups map[int]nova.SecurityGroup23 groups map[int]nova.SecurityGroup
@@ -18,11 +25,6 @@
18 floatingIPs map[int]nova.FloatingIP25 floatingIPs map[int]nova.FloatingIP
19 serverGroups map[string][]int26 serverGroups map[string][]int
20 serverIPs map[string][]int27 serverIPs map[string][]int
21 hostname string
22 versionPath string
23 token string
24 tenantId string
25 userId string
26 nextGroupId int28 nextGroupId int
27 nextRuleId int29 nextRuleId int
28 nextIPId int30 nextIPId int
@@ -31,17 +33,35 @@
3133
32// endpoint returns either a versioned or non-versioned service34// endpoint returns either a versioned or non-versioned service
33// endpoint URL from the given path.35// endpoint URL from the given path.
34func (n *Nova) endpoint(version bool, path string) string {36func (n *Nova) endpointURL(version bool, path string) string {
35 ep := "http://" + n.hostname37 ep := "http://" + n.Hostname
36 if version {38 if version {
37 ep += n.versionPath + "/"39 ep += n.VersionPath + "/"
38 }40 }
39 ep += n.tenantId + "/" + strings.TrimLeft(path, "/")41 ep += n.TenantId
42 if path != "" {
43 ep += "/" + strings.TrimLeft(path, "/")
44 }
40 return ep45 return ep
41}46}
4247
48func (n *Nova) Endpoints() []identityservice.Endpoint {
49 ep := identityservice.Endpoint{
50 AdminURL: n.endpointURL(true, ""),
51 InternalURL: n.endpointURL(true, ""),
52 PublicURL: n.endpointURL(true, ""),
53 Region: n.Region,
54 }
55 return []identityservice.Endpoint{ep}
56}
57
43// New creates an instance of the Nova object, given the parameters.58// New creates an instance of the Nova object, given the parameters.
44func New(hostname, versionPath, token, tenantId string) *Nova {59func New(hostURL, versionPath, tenantId, region string, identityService identityservice.IdentityService) *Nova {
60 url, err := url.Parse(hostURL)
61 if err != nil {
62 panic(err)
63 }
64 hostname := url.Host
45 if !strings.HasSuffix(hostname, "/") {65 if !strings.HasSuffix(hostname, "/") {
46 hostname += "/"66 hostname += "/"
47 }67 }
@@ -62,17 +82,19 @@
62 floatingIPs: make(map[int]nova.FloatingIP),82 floatingIPs: make(map[int]nova.FloatingIP),
63 serverGroups: make(map[string][]int),83 serverGroups: make(map[string][]int),
64 serverIPs: make(map[string][]int),84 serverIPs: make(map[string][]int),
65 hostname: hostname,
66 versionPath: versionPath,
67 token: token,
68 tenantId: tenantId,
69 // TODO(wallyworld): Identity service double currently hard codes all user ids to "14". This should be fixed
70 // in the identity service but the fix will result in an API change which will break juju-core. So for now
71 // we will also hard code it here too.
72 userId: "14",
73 // The following attribute controls whether rate limit responses are sent back to the caller.85 // The following attribute controls whether rate limit responses are sent back to the caller.
74 // This is switched off when we want to ensure the client eventually gets a proper response.86 // This is switched off when we want to ensure the client eventually gets a proper response.
75 sendFakeRateLimitResponse: true,87 sendFakeRateLimitResponse: true,
88 ServiceInstance: testservices.ServiceInstance{
89 IdentityService: identityService,
90 Hostname: hostname,
91 VersionPath: versionPath,
92 TenantId: tenantId,
93 Region: region,
94 },
95 }
96 if identityService != nil {
97 identityService.RegisterServiceProvider("nova", "compute", nova)
76 }98 }
77 for i, flavor := range defaultFlavors {99 for i, flavor := range defaultFlavors {
78 nova.buildFlavorLinks(&flavor)100 nova.buildFlavorLinks(&flavor)
@@ -97,8 +119,8 @@
97func (n *Nova) buildFlavorLinks(flavor *nova.FlavorDetail) {119func (n *Nova) buildFlavorLinks(flavor *nova.FlavorDetail) {
98 url := "/flavors/" + flavor.Id120 url := "/flavors/" + flavor.Id
99 flavor.Links = []nova.Link{121 flavor.Links = []nova.Link{
100 nova.Link{Href: n.endpoint(true, url), Rel: "self"},122 nova.Link{Href: n.endpointURL(true, url), Rel: "self"},
101 nova.Link{Href: n.endpoint(false, url), Rel: "bookmark"},123 nova.Link{Href: n.endpointURL(false, url), Rel: "bookmark"},
102 }124 }
103}125}
104126
@@ -170,8 +192,8 @@
170func (n *Nova) buildServerLinks(server *nova.ServerDetail) {192func (n *Nova) buildServerLinks(server *nova.ServerDetail) {
171 url := "/servers/" + server.Id193 url := "/servers/" + server.Id
172 server.Links = []nova.Link{194 server.Links = []nova.Link{
173 nova.Link{Href: n.endpoint(true, url), Rel: "self"},195 nova.Link{Href: n.endpointURL(true, url), Rel: "self"},
174 nova.Link{Href: n.endpoint(false, url), Rel: "bookmark"},196 nova.Link{Href: n.endpointURL(false, url), Rel: "bookmark"},
175 }197 }
176}198}
177199
@@ -280,7 +302,7 @@
280 if _, err := n.securityGroup(group.Id); err == nil {302 if _, err := n.securityGroup(group.Id); err == nil {
281 return fmt.Errorf("a security group with id %d already exists", group.Id)303 return fmt.Errorf("a security group with id %d already exists", group.Id)
282 }304 }
283 group.TenantId = n.tenantId305 group.TenantId = n.TenantId
284 if group.Rules == nil {306 if group.Rules == nil {
285 group.Rules = []nova.SecurityGroupRule{}307 group.Rules = []nova.SecurityGroupRule{}
286 }308 }
287309
=== modified file 'testservices/novaservice/service_http.go'
--- testservices/novaservice/service_http.go 2013-01-21 12:54:46 +0000
+++ testservices/novaservice/service_http.go 2013-01-28 18:22:27 +0000
@@ -9,6 +9,7 @@
9 "io"9 "io"
10 "io/ioutil"10 "io/ioutil"
11 "launchpad.net/goose/nova"11 "launchpad.net/goose/nova"
12 "launchpad.net/goose/testservices/identityservice"
12 "net/http"13 "net/http"
13 "path"14 "path"
14 "strconv"15 "strconv"
@@ -228,7 +229,7 @@
228 body := e.body229 body := e.body
229 if body != "" {230 if body != "" {
230 if e.nova != nil {231 if e.nova != nil {
231 body = strings.Replace(body, "$ENDPOINT$", e.nova.endpoint(true, "/"), -1)232 body = strings.Replace(body, "$ENDPOINT$", e.nova.endpointURL(true, "/"), -1)
232 }233 }
233 body = strings.Replace(body, "$URL$", url, -1)234 body = strings.Replace(body, "$URL$", url, -1)
234 body = strings.Replace(body, "$ERROR$", e.Error(), -1)235 body = strings.Replace(body, "$ERROR$", e.Error(), -1)
@@ -264,10 +265,15 @@
264 method func(n *Nova, w http.ResponseWriter, r *http.Request) error265 method func(n *Nova, w http.ResponseWriter, r *http.Request) error
265}266}
266267
268func userInfo(i identityservice.IdentityService, r *http.Request) (*identityservice.UserInfo, error) {
269 return i.FindUser(r.Header.Get(authToken))
270}
271
267func (h *novaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {272func (h *novaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
268 path := r.URL.Path273 path := r.URL.Path
269 // handle invalid X-Auth-Token header274 // handle invalid X-Auth-Token header
270 if r.Header.Get(authToken) != h.n.token {275 _, err := userInfo(h.n.IdentityService, r)
276 if err != nil {
271 errUnauthorized.ServeHTTP(w, r)277 errUnauthorized.ServeHTTP(w, r)
272 return278 return
273 }279 }
@@ -276,7 +282,7 @@
276 errNotFound.ServeHTTP(w, r)282 errNotFound.ServeHTTP(w, r)
277 return283 return
278 }284 }
279 err := h.method(h.n, w, r)285 err = h.method(h.n, w, r)
280 if err == nil {286 if err == nil {
281 return287 return
282 }288 }
@@ -541,7 +547,7 @@
541 continue547 continue
542 }548 }
543 if sg, err := n.securityGroupByName(groupName); err != nil {549 if sg, err := n.securityGroupByName(groupName); err != nil {
544 return noGroupError(groupName, n.tenantId)550 return noGroupError(groupName, n.TenantId)
545 } else {551 } else {
546 groups = append(groups, sg.Id)552 groups = append(groups, sg.Id)
547 }553 }
@@ -553,11 +559,12 @@
553 flavorEnt := nova.Entity{Id: flavor.Id, Links: flavor.Links}559 flavorEnt := nova.Entity{Id: flavor.Id, Links: flavor.Links}
554 image := nova.Entity{Id: req.Server.ImageRef}560 image := nova.Entity{Id: req.Server.ImageRef}
555 timestr := time.Now().Format(time.RFC3339)561 timestr := time.Now().Format(time.RFC3339)
562 userInfo, _ := userInfo(n.IdentityService, r)
556 server := nova.ServerDetail{563 server := nova.ServerDetail{
557 Id: id,564 Id: id,
558 Name: req.Server.Name,565 Name: req.Server.Name,
559 TenantId: n.tenantId,566 TenantId: n.TenantId,
560 UserId: n.userId,567 UserId: userInfo.Id,
561 HostId: "1",568 HostId: "1",
562 Image: image,569 Image: image,
563 Flavor: flavorEnt,570 Flavor: flavorEnt,
@@ -787,7 +794,7 @@
787 Id: nextId,794 Id: nextId,
788 Name: req.Group.Name,795 Name: req.Group.Name,
789 Description: req.Group.Description,796 Description: req.Group.Description,
790 TenantId: n.tenantId,797 TenantId: n.TenantId,
791 })798 })
792 if err != nil {799 if err != nil {
793 return err800 return err
@@ -970,8 +977,8 @@
970 "/$v/$t/os-floating-ips": n.handler((*Nova).handleFloatingIPs),977 "/$v/$t/os-floating-ips": n.handler((*Nova).handleFloatingIPs),
971 }978 }
972 for path, h := range handlers {979 for path, h := range handlers {
973 path = strings.Replace(path, "$v", n.versionPath, 1)980 path = strings.Replace(path, "$v", n.VersionPath, 1)
974 path = strings.Replace(path, "$t", n.tenantId, 1)981 path = strings.Replace(path, "$t", n.TenantId, 1)
975 if !strings.HasSuffix(path, "/") {982 if !strings.HasSuffix(path, "/") {
976 mux.Handle(path+"/", h)983 mux.Handle(path+"/", h)
977 }984 }
978985
=== modified file 'testservices/novaservice/service_http_test.go'
--- testservices/novaservice/service_http_test.go 2013-01-21 11:19:10 +0000
+++ testservices/novaservice/service_http_test.go 2013-01-28 18:22:27 +0000
@@ -10,8 +10,8 @@
10 . "launchpad.net/gocheck"10 . "launchpad.net/gocheck"
11 "launchpad.net/goose/nova"11 "launchpad.net/goose/nova"
12 "launchpad.net/goose/testing/httpsuite"12 "launchpad.net/goose/testing/httpsuite"
13 "launchpad.net/goose/testservices/identityservice"
13 "net/http"14 "net/http"
14 "net/url"
15 "strconv"15 "strconv"
16 "strings"16 "strings"
17)17)
@@ -19,14 +19,17 @@
19type NovaHTTPSuite struct {19type NovaHTTPSuite struct {
20 httpsuite.HTTPSuite20 httpsuite.HTTPSuite
21 service *Nova21 service *Nova
22 token string
22}23}
2324
24var _ = Suite(&NovaHTTPSuite{})25var _ = Suite(&NovaHTTPSuite{})
2526
26func (s *NovaHTTPSuite) SetUpSuite(c *C) {27func (s *NovaHTTPSuite) SetUpSuite(c *C) {
27 s.HTTPSuite.SetUpSuite(c)28 s.HTTPSuite.SetUpSuite(c)
28 url, _ := url.Parse(s.Server.URL)29 identityDouble := identityservice.NewUserPass()
29 s.service = New(url.Host, versionPath, token, tenantId)30 userInfo := identityDouble.AddUser("fred", "secret", "tenant")
31 s.token = userInfo.Token
32 s.service = New(s.Server.URL, versionPath, userInfo.TenantId, region, identityDouble)
30}33}
3134
32func (s *NovaHTTPSuite) TearDownSuite(c *C) {35func (s *NovaHTTPSuite) TearDownSuite(c *C) {
@@ -68,8 +71,8 @@
68// sendRequest constructs an HTTP request from the parameters and71// sendRequest constructs an HTTP request from the parameters and
69// sends it, returning the response or an error.72// sends it, returning the response or an error.
70func (s *NovaHTTPSuite) sendRequest(method, url string, body []byte, headers http.Header) (*http.Response, error) {73func (s *NovaHTTPSuite) sendRequest(method, url string, body []byte, headers http.Header) (*http.Response, error) {
71 if !strings.HasPrefix(url, "http") { //s.service.hostname) {74 if !strings.HasPrefix(url, "http") {
72 url = "http://" + s.service.hostname + strings.TrimLeft(url, "/")75 url = "http://" + s.service.Hostname + strings.TrimLeft(url, "/")
73 }76 }
74 req, err := http.NewRequest(method, url, bytes.NewReader(body))77 req, err := http.NewRequest(method, url, bytes.NewReader(body))
75 if err != nil {78 if err != nil {
@@ -91,8 +94,8 @@
91 if headers == nil {94 if headers == nil {
92 headers = make(http.Header)95 headers = make(http.Header)
93 }96 }
94 headers.Set(authToken, s.service.token)97 headers.Set(authToken, s.token)
95 url := s.service.endpoint(true, path)98 url := s.service.endpointURL(true, path)
96 return s.sendRequest(method, url, body, headers)99 return s.sendRequest(method, url, body, headers)
97}100}
98101
@@ -114,339 +117,345 @@
114 return h117 return h
115}118}
116119
117// simpleTests defines a simple request without a body and expected response.120// SimpleTest defines a simple request without a body and expected response.
118var simpleTests = []struct {121type SimpleTest struct {
119 unauth bool122 unauth bool
120 method string123 method string
121 url string124 url string
122 headers http.Header125 headers http.Header
123 expect *errorResponse126 expect *errorResponse
124}{127}
125 {128
126 unauth: true,129func (s *NovaHTTPSuite) simpleTests() []SimpleTest {
127 method: "GET",130 var simpleTests = []SimpleTest{
128 url: "/any",131 {
129 headers: make(http.Header),132 unauth: true,
130 expect: errUnauthorized,133 method: "GET",
131 },134 url: "/any",
132 {135 headers: make(http.Header),
133 unauth: true,136 expect: errUnauthorized,
134 method: "POST",137 },
135 url: "/any",138 {
136 headers: setHeader(authToken, "phony"),139 unauth: true,
137 expect: errUnauthorized,140 method: "POST",
138 },141 url: "/any",
139 {142 headers: setHeader(authToken, "phony"),
140 unauth: true,143 expect: errUnauthorized,
141 method: "GET",144 },
142 url: "/",145 {
143 headers: setHeader(authToken, token),146 unauth: true,
144 expect: errNoVersion,147 method: "GET",
145 },148 url: "/",
146 {149 headers: setHeader(authToken, s.token),
147 unauth: true,150 expect: errNoVersion,
148 method: "GET",151 },
149 url: "/any",152 {
150 headers: setHeader(authToken, token),153 unauth: true,
151 expect: errMultipleChoices,154 method: "GET",
152 },155 url: "/any",
153 {156 headers: setHeader(authToken, s.token),
154 unauth: true,157 expect: errMultipleChoices,
155 method: "POST",158 },
156 url: "/any/unknown/one",159 {
157 headers: setHeader(authToken, token),160 unauth: true,
158 expect: errMultipleChoices,161 method: "POST",
159 },162 url: "/any/unknown/one",
160 {163 headers: setHeader(authToken, s.token),
161 method: "POST",164 expect: errMultipleChoices,
162 url: "/any/unknown/one",165 },
163 expect: errNotFound,166 {
164 },167 method: "POST",
165 {168 url: "/any/unknown/one",
166 unauth: true,169 expect: errNotFound,
167 method: "GET",170 },
168 url: versionPath + "/phony_token",171 {
169 headers: setHeader(authToken, token),172 unauth: true,
170 expect: errBadRequest,173 method: "GET",
171 },174 url: versionPath + "/phony_token",
172 {175 headers: setHeader(authToken, s.token),
173 method: "GET",176 expect: errBadRequest,
174 url: "/flavors/",177 },
175 expect: errNotFound,178 {
176 },179 method: "GET",
177 {180 url: "/flavors/",
178 method: "GET",181 expect: errNotFound,
179 url: "/flavors/invalid",182 },
180 expect: errNotFound,183 {
181 },184 method: "GET",
182 {185 url: "/flavors/invalid",
183 method: "POST",186 expect: errNotFound,
184 url: "/flavors",187 },
185 expect: errBadRequest2,188 {
186 },189 method: "POST",
187 {190 url: "/flavors",
188 method: "POST",191 expect: errBadRequest2,
189 url: "/flavors/invalid",192 },
190 expect: errNotFound,193 {
191 },194 method: "POST",
192 {195 url: "/flavors/invalid",
193 method: "PUT",196 expect: errNotFound,
194 url: "/flavors",197 },
195 expect: errNotFound,198 {
196 },199 method: "PUT",
197 {200 url: "/flavors",
198 method: "PUT",201 expect: errNotFound,
199 url: "/flavors/invalid",202 },
200 expect: errNotFoundJSON,203 {
201 },204 method: "PUT",
202 {205 url: "/flavors/invalid",
203 method: "DELETE",206 expect: errNotFoundJSON,
204 url: "/flavors",207 },
205 expect: errNotFound,208 {
206 },209 method: "DELETE",
207 {210 url: "/flavors",
208 method: "DELETE",211 expect: errNotFound,
209 url: "/flavors/invalid",212 },
210 expect: errForbidden,213 {
211 },214 method: "DELETE",
212 {215 url: "/flavors/invalid",
213 method: "GET",216 expect: errForbidden,
214 url: "/flavors/detail/invalid",217 },
215 expect: errNotFound,218 {
216 },219 method: "GET",
217 {220 url: "/flavors/detail/invalid",
218 method: "POST",221 expect: errNotFound,
219 url: "/flavors/detail",222 },
220 expect: errNotFound,223 {
221 },224 method: "POST",
222 {225 url: "/flavors/detail",
223 method: "POST",226 expect: errNotFound,
224 url: "/flavors/detail/invalid",227 },
225 expect: errNotFound,228 {
226 },229 method: "POST",
227 {230 url: "/flavors/detail/invalid",
228 method: "PUT",231 expect: errNotFound,
229 url: "/flavors/detail",232 },
230 expect: errNotFoundJSON,233 {
231 },234 method: "PUT",
232 {235 url: "/flavors/detail",
233 method: "PUT",236 expect: errNotFoundJSON,
234 url: "/flavors/detail/invalid",237 },
235 expect: errNotFound,238 {
236 },239 method: "PUT",
237 {240 url: "/flavors/detail/invalid",
238 method: "DELETE",241 expect: errNotFound,
239 url: "/flavors/detail",242 },
240 expect: errForbidden,243 {
241 },244 method: "DELETE",
242 {245 url: "/flavors/detail",
243 method: "DELETE",246 expect: errForbidden,
244 url: "/flavors/detail/invalid",247 },
245 expect: errNotFound,248 {
246 },249 method: "DELETE",
247 {250 url: "/flavors/detail/invalid",
248 method: "GET",251 expect: errNotFound,
249 url: "/servers/invalid",252 },
250 expect: errNotFoundJSON,253 {
251 },254 method: "GET",
252 {255 url: "/servers/invalid",
253 method: "POST",256 expect: errNotFoundJSON,
254 url: "/servers",257 },
255 expect: errBadRequest2,258 {
256 },259 method: "POST",
257 {260 url: "/servers",
258 method: "POST",261 expect: errBadRequest2,
259 url: "/servers/invalid",262 },
260 expect: errNotFound,263 {
261 },264 method: "POST",
262 {265 url: "/servers/invalid",
263 method: "PUT",266 expect: errNotFound,
264 url: "/servers",267 },
265 expect: errNotFound,268 {
266 },269 method: "PUT",
267 {270 url: "/servers",
268 method: "PUT",271 expect: errNotFound,
269 url: "/servers/invalid",272 },
270 expect: errBadRequest2,273 {
271 },274 method: "PUT",
272 {275 url: "/servers/invalid",
273 method: "DELETE",276 expect: errBadRequest2,
274 url: "/servers",277 },
275 expect: errNotFound,278 {
276 },279 method: "DELETE",
277 {280 url: "/servers",
278 method: "DELETE",281 expect: errNotFound,
279 url: "/servers/invalid",282 },
280 expect: errNotFoundJSON,283 {
281 },284 method: "DELETE",
282 {285 url: "/servers/invalid",
283 method: "GET",286 expect: errNotFoundJSON,
284 url: "/servers/detail/invalid",287 },
285 expect: errNotFound,288 {
286 },289 method: "GET",
287 {290 url: "/servers/detail/invalid",
288 method: "POST",291 expect: errNotFound,
289 url: "/servers/detail",292 },
290 expect: errNotFound,293 {
291 },294 method: "POST",
292 {295 url: "/servers/detail",
293 method: "POST",296 expect: errNotFound,
294 url: "/servers/detail/invalid",297 },
295 expect: errNotFound,298 {
296 },299 method: "POST",
297 {300 url: "/servers/detail/invalid",
298 method: "PUT",301 expect: errNotFound,
299 url: "/servers/detail",302 },
300 expect: errBadRequest2,303 {
301 },304 method: "PUT",
302 {305 url: "/servers/detail",
303 method: "PUT",306 expect: errBadRequest2,
304 url: "/servers/detail/invalid",307 },
305 expect: errNotFound,308 {
306 },309 method: "PUT",
307 {310 url: "/servers/detail/invalid",
308 method: "DELETE",311 expect: errNotFound,
309 url: "/servers/detail",312 },
310 expect: errNotFoundJSON,313 {
311 },314 method: "DELETE",
312 {315 url: "/servers/detail",
313 method: "DELETE",316 expect: errNotFoundJSON,
314 url: "/servers/detail/invalid",317 },
315 expect: errNotFound,318 {
316 },319 method: "DELETE",
317 {320 url: "/servers/detail/invalid",
318 method: "GET",321 expect: errNotFound,
319 url: "/os-security-groups/invalid",322 },
320 expect: errBadRequestSG,323 {
321 },324 method: "GET",
322 {325 url: "/os-security-groups/invalid",
323 method: "GET",326 expect: errBadRequestSG,
324 url: "/os-security-groups/42",327 },
325 expect: errNotFoundJSONSG,328 {
326 },329 method: "GET",
327 {330 url: "/os-security-groups/42",
328 method: "POST",331 expect: errNotFoundJSONSG,
329 url: "/os-security-groups",332 },
330 expect: errBadRequest2,333 {
331 },334 method: "POST",
332 {335 url: "/os-security-groups",
333 method: "POST",336 expect: errBadRequest2,
334 url: "/os-security-groups/invalid",337 },
335 expect: errNotFound,338 {
336 },339 method: "POST",
337 {340 url: "/os-security-groups/invalid",
338 method: "PUT",341 expect: errNotFound,
339 url: "/os-security-groups",342 },
340 expect: errNotFound,343 {
341 },344 method: "PUT",
342 {345 url: "/os-security-groups",
343 method: "PUT",346 expect: errNotFound,
344 url: "/os-security-groups/invalid",347 },
345 expect: errNotFoundJSON,348 {
346 },349 method: "PUT",
347 {350 url: "/os-security-groups/invalid",
348 method: "DELETE",351 expect: errNotFoundJSON,
349 url: "/os-security-groups",352 },
350 expect: errNotFound,353 {
351 },354 method: "DELETE",
352 {355 url: "/os-security-groups",
353 method: "DELETE",356 expect: errNotFound,
354 url: "/os-security-groups/invalid",357 },
355 expect: errBadRequestSG,358 {
356 },359 method: "DELETE",
357 {360 url: "/os-security-groups/invalid",
358 method: "DELETE",361 expect: errBadRequestSG,
359 url: "/os-security-groups/42",362 },
360 expect: errNotFoundJSONSG,363 {
361 },364 method: "DELETE",
362 {365 url: "/os-security-groups/42",
363 method: "GET",366 expect: errNotFoundJSONSG,
364 url: "/os-security-group-rules",367 },
365 expect: errNotFoundJSON,368 {
366 },369 method: "GET",
367 {370 url: "/os-security-group-rules",
368 method: "GET",371 expect: errNotFoundJSON,
369 url: "/os-security-group-rules/invalid",372 },
370 expect: errNotFoundJSON,373 {
371 },374 method: "GET",
372 {375 url: "/os-security-group-rules/invalid",
373 method: "GET",376 expect: errNotFoundJSON,
374 url: "/os-security-group-rules/42",377 },
375 expect: errNotFoundJSON,378 {
376 },379 method: "GET",
377 {380 url: "/os-security-group-rules/42",
378 method: "POST",381 expect: errNotFoundJSON,
379 url: "/os-security-group-rules",382 },
380 expect: errBadRequest2,383 {
381 },384 method: "POST",
382 {385 url: "/os-security-group-rules",
383 method: "POST",386 expect: errBadRequest2,
384 url: "/os-security-group-rules/invalid",387 },
385 expect: errNotFound,388 {
386 },389 method: "POST",
387 {390 url: "/os-security-group-rules/invalid",
388 method: "PUT",391 expect: errNotFound,
389 url: "/os-security-group-rules",392 },
390 expect: errNotFound,393 {
391 },394 method: "PUT",
392 {395 url: "/os-security-group-rules",
393 method: "PUT",396 expect: errNotFound,
394 url: "/os-security-group-rules/invalid",397 },
395 expect: errNotFoundJSON,398 {
396 },399 method: "PUT",
397 {400 url: "/os-security-group-rules/invalid",
398 method: "DELETE",401 expect: errNotFoundJSON,
399 url: "/os-security-group-rules",402 },
400 expect: errNotFound,403 {
401 },404 method: "DELETE",
402 {405 url: "/os-security-group-rules",
403 method: "DELETE",406 expect: errNotFound,
404 url: "/os-security-group-rules/invalid",407 },
405 expect: errBadRequestSG, // sic; should've been rule-specific408 {
406 },409 method: "DELETE",
407 {410 url: "/os-security-group-rules/invalid",
408 method: "DELETE",411 expect: errBadRequestSG, // sic; should've been rule-specific
409 url: "/os-security-group-rules/42",412 },
410 expect: errNotFoundJSONSGR,413 {
411 },414 method: "DELETE",
412 {415 url: "/os-security-group-rules/42",
413 method: "GET",416 expect: errNotFoundJSONSGR,
414 url: "/os-floating-ips/42",417 },
415 expect: errNotFoundJSON,418 {
416 },419 method: "GET",
417 {420 url: "/os-floating-ips/42",
418 method: "POST",421 expect: errNotFoundJSON,
419 url: "/os-floating-ips/invalid",422 },
420 expect: errNotFound,423 {
421 },424 method: "POST",
422 {425 url: "/os-floating-ips/invalid",
423 method: "PUT",426 expect: errNotFound,
424 url: "/os-floating-ips",427 },
425 expect: errNotFound,428 {
426 },429 method: "PUT",
427 {430 url: "/os-floating-ips",
428 method: "PUT",431 expect: errNotFound,
429 url: "/os-floating-ips/invalid",432 },
430 expect: errNotFoundJSON,433 {
431 },434 method: "PUT",
432 {435 url: "/os-floating-ips/invalid",
433 method: "DELETE",436 expect: errNotFoundJSON,
434 url: "/os-floating-ips",437 },
435 expect: errNotFound,438 {
436 },439 method: "DELETE",
437 {440 url: "/os-floating-ips",
438 method: "DELETE",441 expect: errNotFound,
439 url: "/os-floating-ips/invalid",442 },
440 expect: errNotFoundJSON,443 {
441 },444 method: "DELETE",
445 url: "/os-floating-ips/invalid",
446 expect: errNotFoundJSON,
447 },
448 }
449 return simpleTests
442}450}
443451
444func (s *NovaHTTPSuite) TestSimpleRequestTests(c *C) {452func (s *NovaHTTPSuite) TestSimpleRequestTests(c *C) {
453 simpleTests := s.simpleTests()
445 for i, t := range simpleTests {454 for i, t := range simpleTests {
446 c.Logf("#%d. %s %s -> %d", i, t.method, t.url, t.expect.code)455 c.Logf("#%d. %s %s -> %d", i, t.method, t.url, t.expect.code)
447 if t.headers == nil {456 if t.headers == nil {
448 t.headers = make(http.Header)457 t.headers = make(http.Header)
449 t.headers.Set(authToken, s.service.token)458 t.headers.Set(authToken, s.token)
450 }459 }
451 var (460 var (
452 resp *http.Response461 resp *http.Response
@@ -780,13 +789,13 @@
780 {789 {
781 Id: 1,790 Id: 1,
782 Name: "group 1",791 Name: "group 1",
783 TenantId: s.service.tenantId,792 TenantId: s.service.TenantId,
784 Rules: []nova.SecurityGroupRule{},793 Rules: []nova.SecurityGroupRule{},
785 },794 },
786 {795 {
787 Id: 2,796 Id: 2,
788 Name: "group 2",797 Name: "group 2",
789 TenantId: s.service.tenantId,798 TenantId: s.service.TenantId,
790 Rules: []nova.SecurityGroupRule{},799 Rules: []nova.SecurityGroupRule{},
791 },800 },
792 }801 }
@@ -816,7 +825,7 @@
816 Id: 1,825 Id: 1,
817 Name: "group 1",826 Name: "group 1",
818 Description: "desc",827 Description: "desc",
819 TenantId: s.service.tenantId,828 TenantId: s.service.TenantId,
820 Rules: []nova.SecurityGroupRule{},829 Rules: []nova.SecurityGroupRule{},
821 }830 }
822 _, err := s.service.securityGroup(group.Id)831 _, err := s.service.securityGroup(group.Id)
@@ -890,7 +899,7 @@
890 ParentGroupId: group2.Id,899 ParentGroupId: group2.Id,
891 Group: nova.SecurityGroupRef{900 Group: nova.SecurityGroupRef{
892 Name: group1.Name,901 Name: group1.Name,
893 TenantId: s.service.tenantId,902 TenantId: s.service.TenantId,
894 },903 },
895 }904 }
896 ok := s.service.hasSecurityGroupRule(group1.Id, rule1.Id)905 ok := s.service.hasSecurityGroupRule(group1.Id, rule1.Id)
@@ -990,13 +999,13 @@
990 {999 {
991 Id: 1,1000 Id: 1,
992 Name: "group1",1001 Name: "group1",
993 TenantId: s.service.tenantId,1002 TenantId: s.service.TenantId,
994 Rules: []nova.SecurityGroupRule{},1003 Rules: []nova.SecurityGroupRule{},
995 },1004 },
996 {1005 {
997 Id: 2,1006 Id: 2,
998 Name: "group2",1007 Name: "group2",
999 TenantId: s.service.tenantId,1008 TenantId: s.service.TenantId,
1000 Rules: []nova.SecurityGroupRule{},1009 Rules: []nova.SecurityGroupRule{},
1001 },1010 },
1002 }1011 }
10031012
=== modified file 'testservices/novaservice/service_test.go'
--- testservices/novaservice/service_test.go 2013-01-21 11:18:33 +0000
+++ testservices/novaservice/service_test.go 2013-01-28 18:22:27 +0000
@@ -14,15 +14,14 @@
1414
15const (15const (
16 versionPath = "v2"16 versionPath = "v2"
17 token = "token"17 hostname = "http://example.com"
18 hostname = "example.com"18 region = "region"
19 tenantId = "tenant_id"
20)19)
2120
22var _ = Suite(&NovaSuite{})21var _ = Suite(&NovaSuite{})
2322
24func (s *NovaSuite) SetUpSuite(c *C) {23func (s *NovaSuite) SetUpSuite(c *C) {
25 s.service = New(hostname, versionPath, token, tenantId)24 s.service = New(hostname, versionPath, "tenant", region, nil)
26}25}
2726
28func (s *NovaSuite) ensureNoFlavor(c *C, flavor nova.FlavorDetail) {27func (s *NovaSuite) ensureNoFlavor(c *C, flavor nova.FlavorDetail) {
@@ -118,8 +117,8 @@
118 fl, _ := s.service.flavor(flavor.Id)117 fl, _ := s.service.flavor(flavor.Id)
119 url := "/flavors/" + flavor.Id118 url := "/flavors/" + flavor.Id
120 links := []nova.Link{119 links := []nova.Link{
121 nova.Link{Href: s.service.endpoint(true, url), Rel: "self"},120 nova.Link{Href: s.service.endpointURL(true, url), Rel: "self"},
122 nova.Link{Href: s.service.endpoint(false, url), Rel: "bookmark"},121 nova.Link{Href: s.service.endpointURL(false, url), Rel: "bookmark"},
123 }122 }
124 c.Assert(fl.Links, DeepEquals, links)123 c.Assert(fl.Links, DeepEquals, links)
125}124}
@@ -214,8 +213,8 @@
214 sr, _ := s.service.server(server.Id)213 sr, _ := s.service.server(server.Id)
215 url := "/servers/" + server.Id214 url := "/servers/" + server.Id
216 links := []nova.Link{215 links := []nova.Link{
217 {Href: s.service.endpoint(true, url), Rel: "self"},216 {Href: s.service.endpointURL(true, url), Rel: "self"},
218 {Href: s.service.endpoint(false, url), Rel: "bookmark"},217 {Href: s.service.endpointURL(false, url), Rel: "bookmark"},
219 }218 }
220 c.Assert(sr.Links, DeepEquals, links)219 c.Assert(sr.Links, DeepEquals, links)
221}220}
@@ -456,7 +455,7 @@
456 group := nova.SecurityGroup{455 group := nova.SecurityGroup{
457 Id: 1,456 Id: 1,
458 Name: "test",457 Name: "test",
459 TenantId: s.service.tenantId,458 TenantId: s.service.TenantId,
460 Rules: []nova.SecurityGroupRule{459 Rules: []nova.SecurityGroupRule{
461 {Id: 10, ParentGroupId: 1},460 {Id: 10, ParentGroupId: 1},
462 {Id: 20, ParentGroupId: 1},461 {Id: 20, ParentGroupId: 1},
@@ -492,13 +491,13 @@
492 {491 {
493 Id: 1,492 Id: 1,
494 Name: "one",493 Name: "one",
495 TenantId: s.service.tenantId,494 TenantId: s.service.TenantId,
496 Rules: []nova.SecurityGroupRule{},495 Rules: []nova.SecurityGroupRule{},
497 },496 },
498 {497 {
499 Id: 2,498 Id: 2,
500 Name: "two",499 Name: "two",
501 TenantId: s.service.tenantId,500 TenantId: s.service.TenantId,
502 Rules: []nova.SecurityGroupRule{},501 Rules: []nova.SecurityGroupRule{},
503 },502 },
504 }503 }
@@ -514,7 +513,7 @@
514func (s *NovaSuite) TestGetSecurityGroup(c *C) {513func (s *NovaSuite) TestGetSecurityGroup(c *C) {
515 group := nova.SecurityGroup{514 group := nova.SecurityGroup{
516 Id: 42,515 Id: 42,
517 TenantId: s.service.tenantId,516 TenantId: s.service.TenantId,
518 Name: "group",517 Name: "group",
519 Description: "desc",518 Description: "desc",
520 Rules: []nova.SecurityGroupRule{},519 Rules: []nova.SecurityGroupRule{},
@@ -529,7 +528,7 @@
529 group := nova.SecurityGroup{528 group := nova.SecurityGroup{
530 Id: 1,529 Id: 1,
531 Name: "test",530 Name: "test",
532 TenantId: s.service.tenantId,531 TenantId: s.service.TenantId,
533 Rules: []nova.SecurityGroupRule{},532 Rules: []nova.SecurityGroupRule{},
534 }533 }
535 s.ensureNoGroup(c, group)534 s.ensureNoGroup(c, group)
@@ -600,7 +599,7 @@
600}599}
601600
602func (s *NovaSuite) TestAddGetGroupSecurityGroupRule(c *C) {601func (s *NovaSuite) TestAddGetGroupSecurityGroupRule(c *C) {
603 srcGroup := nova.SecurityGroup{Id: 1, Name: "source", TenantId: s.service.tenantId}602 srcGroup := nova.SecurityGroup{Id: 1, Name: "source", TenantId: s.service.TenantId}
604 tgtGroup := nova.SecurityGroup{Id: 2, Name: "target"}603 tgtGroup := nova.SecurityGroup{Id: 2, Name: "target"}
605 s.createGroup(c, srcGroup)604 s.createGroup(c, srcGroup)
606 defer s.deleteGroup(c, srcGroup)605 defer s.deleteGroup(c, srcGroup)
@@ -696,7 +695,7 @@
696 group := nova.SecurityGroup{695 group := nova.SecurityGroup{
697 Id: 1,696 Id: 1,
698 Name: "test",697 Name: "test",
699 TenantId: s.service.tenantId,698 TenantId: s.service.TenantId,
700 }699 }
701 s.createGroup(c, group)700 s.createGroup(c, group)
702 defer s.deleteGroup(c, group)701 defer s.deleteGroup(c, group)
@@ -800,13 +799,13 @@
800 {799 {
801 Id: 1,800 Id: 1,
802 Name: "gr1",801 Name: "gr1",
803 TenantId: s.service.tenantId,802 TenantId: s.service.TenantId,
804 Rules: []nova.SecurityGroupRule{},803 Rules: []nova.SecurityGroupRule{},
805 },804 },
806 {805 {
807 Id: 2,806 Id: 2,
808 Name: "gr2",807 Name: "gr2",
809 TenantId: s.service.tenantId,808 TenantId: s.service.TenantId,
810 Rules: []nova.SecurityGroupRule{},809 Rules: []nova.SecurityGroupRule{},
811 },810 },
812 }811 }
813812
=== added directory 'testservices/openstack'
=== added file 'testservices/openstack/openstack.go'
--- testservices/openstack/openstack.go 1970-01-01 00:00:00 +0000
+++ testservices/openstack/openstack.go 2013-01-28 18:22:27 +0000
@@ -0,0 +1,38 @@
1package openstack
2
3import (
4 "launchpad.net/goose/identity"
5 "launchpad.net/goose/testservices/identityservice"
6 "launchpad.net/goose/testservices/novaservice"
7 "launchpad.net/goose/testservices/swiftservice"
8 "net/http"
9)
10
11// Openstack provides an Openstack service double implementation.
12type Openstack struct {
13 identity identityservice.IdentityService
14 nova *novaservice.Nova
15 swift *swiftservice.Swift
16}
17
18// New creates an instance of a full Openstack service double.
19// An initial user with the specified credentials is registered with the identity service.
20func New(cred *identity.Credentials) *Openstack {
21 openstack := Openstack{
22 identity: identityservice.NewUserPass(),
23 }
24 userInfo := openstack.identity.AddUser(cred.User, cred.Secrets, cred.TenantName)
25 if cred.TenantName == "" {
26 panic("Openstack service double requires a tenant to be specified.")
27 }
28 openstack.nova = novaservice.New(cred.URL, "v2", userInfo.TenantId, cred.Region, openstack.identity)
29 openstack.swift = swiftservice.New(cred.URL, "v1", userInfo.TenantId, cred.Region, openstack.identity)
30 return &openstack
31}
32
33// setupHTTP attaches all the needed handlers to provide the HTTP API for the Openstack service..
34func (openstack *Openstack) SetupHTTP(mux *http.ServeMux) {
35 openstack.identity.SetupHTTP(mux)
36 openstack.nova.SetupHTTP(mux)
37 openstack.swift.SetupHTTP(mux)
38}
039
=== added file 'testservices/service.go'
--- testservices/service.go 1970-01-01 00:00:00 +0000
+++ testservices/service.go 2013-01-28 18:22:27 +0000
@@ -0,0 +1,21 @@
1package testservices
2
3import (
4 "launchpad.net/goose/testservices/identityservice"
5 "net/http"
6)
7
8// An HttpService provides the HTTP API for a service double.
9type HttpService interface {
10 SetupHTTP(mux *http.ServeMux)
11}
12
13// A ServiceInstance is an Openstack module, one of nova, swift, glance.
14type ServiceInstance struct {
15 identityservice.ServiceProvider
16 IdentityService identityservice.IdentityService
17 Hostname string
18 VersionPath string
19 TenantId string
20 Region string
21}
022
=== modified file 'testservices/swiftservice/service.go'
--- testservices/swiftservice/service.go 2012-12-17 01:30:20 +0000
+++ testservices/swiftservice/service.go 2013-01-28 18:22:27 +0000
@@ -5,29 +5,67 @@
5import (5import (
6 "fmt"6 "fmt"
7 "launchpad.net/goose/swift"7 "launchpad.net/goose/swift"
8 "launchpad.net/goose/testservices"
9 "launchpad.net/goose/testservices/identityservice"
10 "net/url"
11 "strings"
8 "time"12 "time"
9)13)
1014
11type object map[string][]byte15type object map[string][]byte
1216
17var _ testservices.HttpService = (*Swift)(nil)
18var _ identityservice.ServiceProvider = (*Swift)(nil)
19
13type Swift struct {20type Swift struct {
21 testservices.ServiceInstance
14 containers map[string]object22 containers map[string]object
15 hostname string
16 baseURL string
17 token string
18}23}
1924
20// New creates an instance of the Swift object, given the parameters.25// New creates an instance of the Swift object, given the parameters.
21func New(hostname, baseURL, token string) *Swift {26func New(hostURL, versionPath, tenantId, region string, identityService identityservice.IdentityService) *Swift {
27 url, err := url.Parse(hostURL)
28 if err != nil {
29 panic(err)
30 }
31 hostname := url.Host
32 if !strings.HasSuffix(hostname, "/") {
33 hostname += "/"
34 }
22 swift := &Swift{35 swift := &Swift{
23 containers: make(map[string]object),36 containers: make(map[string]object),
24 hostname: hostname,37 ServiceInstance: testservices.ServiceInstance{
25 baseURL: baseURL,38 IdentityService: identityService,
26 token: token,39 Hostname: hostname,
40 VersionPath: versionPath,
41 TenantId: tenantId,
42 Region: region,
43 },
44 }
45 if identityService != nil {
46 identityService.RegisterServiceProvider("swift", "object-store", swift)
27 }47 }
28 return swift48 return swift
29}49}
3050
51func (s *Swift) endpointURL(path string) string {
52 ep := "http://" + s.Hostname + s.VersionPath + "/" + s.TenantId
53 if path != "" {
54 ep += "/" + strings.TrimLeft(path, "/")
55 }
56 return ep
57}
58
59func (s *Swift) Endpoints() []identityservice.Endpoint {
60 ep := identityservice.Endpoint{
61 AdminURL: s.endpointURL(""),
62 InternalURL: s.endpointURL(""),
63 PublicURL: s.endpointURL(""),
64 Region: s.Region,
65 }
66 return []identityservice.Endpoint{ep}
67}
68
31// HasContainer verifies the given container exists or not.69// HasContainer verifies the given container exists or not.
32func (s *Swift) HasContainer(name string) bool {70func (s *Swift) HasContainer(name string) bool {
33 _, ok := s.containers[name]71 _, ok := s.containers[name]
@@ -119,5 +157,5 @@
119 if _, err := s.GetObject(container, object); err != nil {157 if _, err := s.GetObject(container, object); err != nil {
120 return "", err158 return "", err
121 }159 }
122 return fmt.Sprintf("%s%s%s/%s", s.hostname, s.baseURL, container, object), nil160 return s.endpointURL(fmt.Sprintf("%s/%s", container, object)), nil
123}161}
124162
=== modified file 'testservices/swiftservice/service_http.go'
--- testservices/swiftservice/service_http.go 2013-01-21 11:18:33 +0000
+++ testservices/swiftservice/service_http.go 2013-01-28 18:22:27 +0000
@@ -4,6 +4,7 @@
44
5import (5import (
6 "encoding/json"6 "encoding/json"
7 "fmt"
7 "io/ioutil"8 "io/ioutil"
8 "net/http"9 "net/http"
9 "strings"10 "strings"
@@ -143,16 +144,15 @@
143 // For public containers, the token is not required to access the files. For now, if the request144 // For public containers, the token is not required to access the files. For now, if the request
144 // does not provide a token, we will let it through and assume a public container is being accessed.145 // does not provide a token, we will let it through and assume a public container is being accessed.
145 token := r.Header.Get("X-Auth-Token")146 token := r.Header.Get("X-Auth-Token")
146 if token != "" && token != s.token {147 _, err := s.IdentityService.FindUser(token)
148 if token != "" && err != nil {
147 w.WriteHeader(http.StatusUnauthorized)149 w.WriteHeader(http.StatusUnauthorized)
148 return150 return
149 }151 }
150 path := r.URL.Path152 path := strings.TrimRight(r.URL.Path, "/")
151 if path[:len(s.baseURL)] == s.baseURL {153 path = strings.Trim(path, "/")
152 path = path[len(s.baseURL):]154 parts := strings.SplitN(path, "/", 4)
153 }155 parts = parts[2:]
154 path = strings.TrimRight(path, "/")
155 parts := strings.SplitN(path, "/", 2)
156 if len(parts) == 1 {156 if len(parts) == 1 {
157 container := parts[0]157 container := parts[0]
158 s.handleContainers(container, w, r)158 s.handleContainers(container, w, r)
@@ -164,3 +164,9 @@
164 panic("not implemented request: " + r.URL.Path)164 panic("not implemented request: " + r.URL.Path)
165 }165 }
166}166}
167
168// setupHTTP attaches all the needed handlers to provide the HTTP API.
169func (s *Swift) SetupHTTP(mux *http.ServeMux) {
170 path := fmt.Sprintf("/%s/%s/", s.VersionPath, s.TenantId)
171 mux.Handle(path, s)
172}
167173
=== modified file 'testservices/swiftservice/service_http_test.go'
--- testservices/swiftservice/service_http_test.go 2012-12-20 05:47:11 +0000
+++ testservices/swiftservice/service_http_test.go 2013-01-28 18:22:27 +0000
@@ -9,24 +9,29 @@
9 . "launchpad.net/gocheck"9 . "launchpad.net/gocheck"
10 "launchpad.net/goose/swift"10 "launchpad.net/goose/swift"
11 "launchpad.net/goose/testing/httpsuite"11 "launchpad.net/goose/testing/httpsuite"
12 "launchpad.net/goose/testservices/identityservice"
12 "net/http"13 "net/http"
13)14)
1415
15type SwiftHTTPSuite struct {16type SwiftHTTPSuite struct {
16 httpsuite.HTTPSuite17 httpsuite.HTTPSuite
17 service SwiftService18 service *Swift
19 token string
18}20}
1921
20var _ = Suite(&SwiftHTTPSuite{})22var _ = Suite(&SwiftHTTPSuite{})
2123
22func (s *SwiftHTTPSuite) SetUpSuite(c *C) {24func (s *SwiftHTTPSuite) SetUpSuite(c *C) {
23 s.HTTPSuite.SetUpSuite(c)25 s.HTTPSuite.SetUpSuite(c)
24 s.service = New(s.Server.URL, baseURL, token)26 identityDouble := identityservice.NewUserPass()
27 s.service = New(s.Server.URL, versionPath, tenantId, region, identityDouble)
28 userInfo := identityDouble.AddUser("fred", "secret", "tenant")
29 s.token = userInfo.Token
25}30}
2631
27func (s *SwiftHTTPSuite) SetUpTest(c *C) {32func (s *SwiftHTTPSuite) SetUpTest(c *C) {
28 s.HTTPSuite.SetUpTest(c)33 s.HTTPSuite.SetUpTest(c)
29 s.Mux.Handle(baseURL, s.service)34 s.service.SetupHTTP(s.Mux)
30}35}
3136
32func (s *SwiftHTTPSuite) TearDownTest(c *C) {37func (s *SwiftHTTPSuite) TearDownTest(c *C) {
@@ -41,15 +46,15 @@
41 expectedStatusCode int) (resp *http.Response) {46 expectedStatusCode int) (resp *http.Response) {
42 var req *http.Request47 var req *http.Request
43 var err error48 var err error
44 url := s.Server.URL + baseURL + path49 url := s.service.endpointURL(path)
45 if body != nil {50 if body != nil {
46 req, err = http.NewRequest(method, url, bytes.NewBuffer(body))51 req, err = http.NewRequest(method, url, bytes.NewBuffer(body))
47 } else {52 } else {
48 req, err = http.NewRequest(method, url, nil)53 req, err = http.NewRequest(method, url, nil)
49 }54 }
50 c.Assert(err, IsNil)55 c.Assert(err, IsNil)
51 if token != "" {56 if s.token != "" {
52 req.Header.Add("X-Auth-Token", token)57 req.Header.Add("X-Auth-Token", s.token)
53 }58 }
54 client := &http.Client{}59 client := &http.Client{}
55 resp, err = client.Do(req)60 resp, err = client.Do(req)
@@ -250,16 +255,16 @@
250}255}
251256
252func (s *SwiftHTTPSuite) TestUnauthorizedFails(c *C) {257func (s *SwiftHTTPSuite) TestUnauthorizedFails(c *C) {
253 oldtoken := token258 oldtoken := s.token
254 defer func() {259 defer func() {
255 token = oldtoken260 s.token = oldtoken
256 }()261 }()
257 // TODO(wallyworld) - until ACLs are supported, empty tokens are assumed to be used when262 // TODO(wallyworld) - until ACLs are supported, empty tokens are assumed to be used when
258 // we need to access a public container.263 // we need to access a public container.
259 // token = ""264 // token = ""
260 // s.sendRequest(c, "GET", "test", nil, http.StatusUnauthorized)265 // s.sendRequest(c, "GET", "test", nil, http.StatusUnauthorized)
261266
262 token = "invalid"267 s.token = "invalid"
263 s.sendRequest(c, "PUT", "test", nil, http.StatusUnauthorized)268 s.sendRequest(c, "PUT", "test", nil, http.StatusUnauthorized)
264269
265 s.sendRequest(c, "DELETE", "test", nil, http.StatusUnauthorized)270 s.sendRequest(c, "DELETE", "test", nil, http.StatusUnauthorized)
266271
=== modified file 'testservices/swiftservice/service_test.go'
--- testservices/swiftservice/service_test.go 2012-12-17 01:30:20 +0000
+++ testservices/swiftservice/service_test.go 2013-01-28 18:22:27 +0000
@@ -3,21 +3,23 @@
3package swiftservice3package swiftservice
44
5import (5import (
6 "fmt"
6 . "launchpad.net/gocheck"7 . "launchpad.net/gocheck"
7)8)
89
9type SwiftServiceSuite struct {10type SwiftServiceSuite struct {
10 service SwiftService11 service *Swift
11}12}
1213
13var baseURL = "/v1/AUTH_tenant/"14var region = "region" // not really used here
14var token = "token"15var hostname = "http://localhost" // not really used here
15var hostname = "localhost" // not really used here16var versionPath = "v2" // not really used here
17var tenantId = "tenant" // not really used here
1618
17var _ = Suite(&SwiftServiceSuite{})19var _ = Suite(&SwiftServiceSuite{})
1820
19func (s *SwiftServiceSuite) SetUpSuite(c *C) {21func (s *SwiftServiceSuite) SetUpSuite(c *C) {
20 s.service = New(hostname, baseURL, token)22 s.service = New(hostname, versionPath, tenantId, region, nil)
21}23}
2224
23func (s *SwiftServiceSuite) TestAddHasRemoveContainer(c *C) {25func (s *SwiftServiceSuite) TestAddHasRemoveContainer(c *C) {
@@ -76,9 +78,8 @@
76 err = s.service.AddObject("test", "obj", data)78 err = s.service.AddObject("test", "obj", data)
77 c.Assert(err, IsNil)79 c.Assert(err, IsNil)
78 url, err := s.service.GetURL("test", "obj")80 url, err := s.service.GetURL("test", "obj")
79 path := baseURL + "test/obj"
80 c.Assert(err, IsNil)81 c.Assert(err, IsNil)
81 c.Assert(url, Equals, hostname+path)82 c.Assert(url, Equals, fmt.Sprintf("%s/%s/%s/test/obj", hostname, versionPath, tenantId))
82 err = s.service.RemoveContainer("test")83 err = s.service.RemoveContainer("test")
83 c.Assert(err, IsNil)84 c.Assert(err, IsNil)
84 ok = s.service.HasContainer("test")85 ok = s.service.HasContainer("test")
8586
=== removed file 'testservices/swiftservice/swiftservice.go'
--- testservices/swiftservice/swiftservice.go 2012-12-17 01:30:20 +0000
+++ testservices/swiftservice/swiftservice.go 1970-01-01 00:00:00 +0000
@@ -1,40 +0,0 @@
1// Swift double testing service - mimics OpenStack Swift object
2// storage service for testing goose against close-to-live API.
3
4package swiftservice
5
6import (
7 "launchpad.net/goose/swift"
8 "net/http"
9)
10
11// SwiftService presents an direct-API to manipulate the internal
12// state, as well as an HTTP API double for OpenStack Swift.
13type SwiftService interface {
14 // AddContainer creates a new container with the given name.
15 AddContainer(name string) error
16
17 // AddObject creates a new named object in an existing container.
18 AddObject(container, name string, data []byte) error
19
20 // HasContainer verifies the given container exists or not.
21 HasContainer(name string) bool
22
23 // ListContainer lists the objects in the given container.
24 ListContainer(name string) ([]swift.ContainerContents, error)
25
26 // GetObject retrieves a given object's data from its container.
27 GetObject(container, name string) ([]byte, error)
28
29 // RemoveContainer deletes an existing named container.
30 RemoveContainer(name string) error
31
32 // RemoveObject deletes an existing named object, from its container.
33 RemoveObject(container, name string) error
34
35 // GetURL returns the named object's full public URL to get its data.
36 GetURL(container, object string) (string, error)
37
38 // ServeHTTP is the main entry point in the HTTP request processing.
39 ServeHTTP(w http.ResponseWriter, r *http.Request)
40}
410
=== added directory 'tools'
=== added directory 'tools/secgroup-delete-all'
=== added file 'tools/secgroup-delete-all/main.go'
--- tools/secgroup-delete-all/main.go 1970-01-01 00:00:00 +0000
+++ tools/secgroup-delete-all/main.go 2013-01-28 18:22:27 +0000
@@ -0,0 +1,73 @@
1package main
2
3import (
4 "fmt"
5 "io"
6 "launchpad.net/gnuflag"
7 "launchpad.net/goose/client"
8 "launchpad.net/goose/identity"
9 "launchpad.net/goose/nova"
10 "os"
11)
12
13// DeleteAll destroys all security groups except the default
14func DeleteAll(w io.Writer, osn *nova.Client) (err error) {
15 groups, err := osn.ListSecurityGroups()
16 if err != nil {
17 return err
18 }
19 deleted := 0
20 failed := 0
21 for _, group := range groups {
22 if group.Name != "default" {
23 err := osn.DeleteSecurityGroup(group.Id)
24 if err != nil {
25 failed++
26 } else {
27 deleted++
28 }
29 }
30 }
31 if deleted != 0 {
32 fmt.Fprintf(w, "%d security groups deleted.\n", deleted)
33 } else if failed == 0 {
34 fmt.Fprint(w, "No security groups to delete.\n")
35 }
36 if failed != 0 {
37 fmt.Fprintf(w, "%d security groups could not be deleted.\n", failed)
38 }
39 return nil
40}
41
42func createNovaClient(authMode identity.AuthMethod) (osn *nova.Client, err error) {
43 creds, err := identity.CompleteCredentialsFromEnv()
44 if err != nil {
45 return nil, err
46 }
47 osc := client.NewClient(creds, authMode, nil)
48 return nova.New(osc), nil
49}
50
51var authModeFlag = gnuflag.String("auth-mode", "userpass", "type of authentication to use")
52
53var authModes = map[string]identity.AuthMethod{
54 "userpass": identity.AuthUserPass,
55 "legacy": identity.AuthLegacy,
56}
57
58func main() {
59 gnuflag.Parse(true)
60 mode, ok := authModes[*authModeFlag]
61 if !ok {
62 fmt.Fprintf(os.Stderr, "error: no such auth-mode %q\n", *authModeFlag)
63 os.Exit(1)
64 }
65 novaclient, err := createNovaClient(mode)
66 if err == nil {
67 err = DeleteAll(os.Stdout, novaclient)
68 }
69 if err != nil {
70 fmt.Fprintf(os.Stderr, "error: %v\n", err)
71 os.Exit(1)
72 }
73}
074
=== added file 'tools/secgroup-delete-all/main_test.go'
--- tools/secgroup-delete-all/main_test.go 1970-01-01 00:00:00 +0000
+++ tools/secgroup-delete-all/main_test.go 2013-01-28 18:22:27 +0000
@@ -0,0 +1,71 @@
1package main_test
2
3import (
4 "bytes"
5 . "launchpad.net/gocheck"
6 "launchpad.net/goose/client"
7 "launchpad.net/goose/identity"
8 "launchpad.net/goose/nova"
9 "launchpad.net/goose/testing/httpsuite"
10 "launchpad.net/goose/testservices/openstack"
11 tool "launchpad.net/goose/tools/secgroup-delete-all"
12 "testing"
13)
14
15func Test(t *testing.T) {
16 TestingT(t)
17}
18
19const (
20 username = "auser"
21 password = "apass"
22 region = "aregion"
23 tenant = "1"
24)
25
26type ToolSuite struct {
27 httpsuite.HTTPSuite
28 creds *identity.Credentials
29}
30
31var _ = Suite(&ToolSuite{})
32
33// GZ 2013-01-21: Should require EnvSuite for this, but clashes with HTTPSuite
34func createNovaClient(creds *identity.Credentials) *nova.Client {
35 osc := client.NewClient(creds, identity.AuthUserPass, nil)
36 return nova.New(osc)
37}
38
39func (s *ToolSuite) makeServices(c *C) *nova.Client {
40 creds := &identity.Credentials{
41 URL: s.Server.URL,
42 User: username,
43 Secrets: password,
44 Region: region,
45 TenantName: tenant,
46 }
47 openstack := openstack.New(creds)
48 openstack.SetupHTTP(s.Mux)
49 return createNovaClient(creds)
50}
51
52func (s *ToolSuite) TestNoGroups(c *C) {
53 nova := s.makeServices(c)
54 var buf bytes.Buffer
55 err := tool.DeleteAll(&buf, nova)
56 c.Assert(err, IsNil)
57 c.Assert(string(buf.Bytes()), Equals, "No security groups to delete.\n")
58}
59
60func (s *ToolSuite) TestTwoGroups(c *C) {
61 nova := s.makeServices(c)
62 nova.CreateSecurityGroup("group-a", "A group")
63 nova.CreateSecurityGroup("group-b", "Another group")
64 var buf bytes.Buffer
65 err := tool.DeleteAll(&buf, nova)
66 c.Assert(err, IsNil)
67 c.Assert(string(buf.Bytes()), Equals, "2 security groups deleted.\n")
68}
69
70// GZ 2013-01-21: Should also test undeleteable groups, but can't induce
71// novaservice errors currently.

Subscribers

People subscribed via source and target branches