Merge lp:~wallyworld/goose/service-double-improvements into lp:~gophers/goose/trunk

Proposed by Ian Booth
Status: Merged
Merged at revision: 51
Proposed branch: lp:~wallyworld/goose/service-double-improvements
Merge into: lp:~gophers/goose/trunk
Diff against target: 2264 lines (+761/-637)
26 files modified
client/local_test.go (+15/-22)
identity/legacy_test.go (+3/-3)
identity/userpass_test.go (+5/-5)
nova/local_test.go (+12/-32)
swift/local_test.go (+12/-28)
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_test.go (+13/-29)
To merge this branch: bzr merge lp:~wallyworld/goose/service-double-improvements
Reviewer Review Type Date Requested Status
The Go Language Gophers Pending
Review via email: mp+144435@code.launchpad.net

Description of the change

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.

https://codereview.appspot.com/7194043/

To post a comment you must log in.
Revision history for this message
Ian Booth (wallyworld) wrote :
Download full text (3.4 KiB)

Reviewers: mp+144435_code.launchpad.net,

Message:
Please take a look.

Description:
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:

 openstack := openstack.New(s.Server.URL, s.cred.User, s.cred.Secrets,
s.LiveTests.cred.Region)
 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.

https://code.launchpad.net/~wallyworld/goose/service-double-improvements/+merge/144435

(do not edit description out of merge proposal)

Please review this at https://codereview.appspot.com/7194043/

Affected files:
   A [revision details]
   M client/local_test.go
   M identity/legacy_test.go
   M identity/userpass_test.go
   M nova/local_test.go
   M swift/local_test.go
   M testservices/identityservice/identityservice.go
   M testservices/identityservice/legacy.go
   M testservices/identityservice/legacy_test.go
   M testservices/identityservice/service_test.go
   M testservices/identityservice/userpass.go
   M testservices/identityservice/userpass_test.go
   A testservices/identityservice/users.go
   M testservices/identityservice/util.go
   M testservices/novaservice/service.go
   M testservices/novaservice/service_http.go
   M testservices/novaservice/service_http_test.go
   M testserv...

Read more...

Revision history for this message
Dimiter Naydenov (dimitern) wrote :

LGTM overall, with a couple of comments.

https://codereview.appspot.com/7194043/diff/1/client/local_test.go
File client/local_test.go (right):

https://codereview.appspot.com/7194043/diff/1/client/local_test.go#newcode42
client/local_test.go:42: // The openstack test service sets up userpass
authentication.
As on the other CL, think what happens if OS_TENANT_NAME is not set -
maybe pass it as an argument here?

https://codereview.appspot.com/7194043/diff/1/testservices/novaservice/service_http_test.go
File testservices/novaservice/service_http_test.go (right):

https://codereview.appspot.com/7194043/diff/1/testservices/novaservice/service_http_test.go#newcode120
testservices/novaservice/service_http_test.go:120: // SimpleTest defines
a simple request without a body and expected response.
Why a type, when it's only used here?

https://codereview.appspot.com/7194043/

51. By Ian Booth

Merge trunk

52. By Ian Booth

Make openstack constructor use credentials so tenant is no longer hard wired

53. By Ian Booth

Move generic code out of identityservice package

Revision history for this message
Ian Booth (wallyworld) wrote :

https://codereview.appspot.com/7194043/diff/1/client/local_test.go
File client/local_test.go (right):

https://codereview.appspot.com/7194043/diff/1/client/local_test.go#newcode42
client/local_test.go:42: // The openstack test service sets up userpass
authentication.
On 2013/01/23 12:28:08, dimitern wrote:
> As on the other CL, think what happens if OS_TENANT_NAME is not set -
maybe pass
> it as an argument here?

The tenant was hard wired, so no issue. However, I've tweaked it so that
the openstack service double is constructed using a credentials object
and the tenant to use comes from there.

https://codereview.appspot.com/7194043/diff/1/testservices/novaservice/service_http_test.go
File testservices/novaservice/service_http_test.go (right):

https://codereview.appspot.com/7194043/diff/1/testservices/novaservice/service_http_test.go#newcode120
testservices/novaservice/service_http_test.go:120: // SimpleTest defines
a simple request without a body and expected response.
On 2013/01/23 12:28:08, dimitern wrote:
> Why a type, when it's only used here?

The statically defined array was replaced with a function to allow the
token to be dynamically set in the struct values, since the token is no
longer hard wired. So the function return type and array declaration
inside the function needed to refer to the struct, and so using a type
became necessary.

https://codereview.appspot.com/7194043/

Revision history for this message
Ian Booth (wallyworld) wrote :

*** Submitted:

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

https://codereview.appspot.com/7194043/

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'client/local_test.go'
2--- client/local_test.go 2013-01-14 13:01:04 +0000
3+++ client/local_test.go 2013-01-24 03:17:22 +0000
4@@ -4,8 +4,9 @@
5 . "launchpad.net/gocheck"
6 "launchpad.net/goose/identity"
7 "launchpad.net/goose/testing/httpsuite"
8+ "launchpad.net/goose/testservices"
9 "launchpad.net/goose/testservices/identityservice"
10- "net/http"
11+ "launchpad.net/goose/testservices/openstack"
12 )
13
14 func registerLocalTests(authMethods []identity.AuthMethod) {
15@@ -24,38 +25,30 @@
16 LiveTests
17 // The following attributes are for using testing doubles.
18 httpsuite.HTTPSuite
19- identityDouble http.Handler
20+ service testservices.HttpService
21 }
22
23 func (s *localLiveSuite) SetUpSuite(c *C) {
24 c.Logf("Using identity service test double")
25 s.HTTPSuite.SetUpSuite(c)
26 s.cred = &identity.Credentials{
27- URL: s.Server.URL,
28- User: "fred",
29- Secrets: "secret",
30- Region: "some region"}
31+ URL: s.Server.URL,
32+ User: "fred",
33+ Secrets: "secret",
34+ Region: "some region",
35+ TenantName: "tenant",
36+ }
37 switch s.authMethod {
38 default:
39 panic("Invalid authentication method")
40 case identity.AuthUserPass:
41- s.identityDouble = identityservice.NewUserPass()
42- s.identityDouble.(*identityservice.UserPass).AddUser(s.cred.User, s.cred.Secrets)
43- ep := identityservice.Endpoint{
44- AdminURL: s.Server.URL,
45- InternalURL: s.Server.URL,
46- PublicURL: s.Server.URL,
47- Region: s.LiveTests.cred.Region,
48- }
49- service := identityservice.Service{"nova", "compute", []identityservice.Endpoint{ep}}
50- s.identityDouble.(*identityservice.UserPass).AddService(service)
51- service = identityservice.Service{"swift", "object-store", []identityservice.Endpoint{ep}}
52- s.identityDouble.(*identityservice.UserPass).AddService(service)
53+ // The openstack test service sets up userpass authentication.
54+ s.service = openstack.New(s.cred)
55 case identity.AuthLegacy:
56- s.identityDouble = identityservice.NewLegacy()
57- var legacy = s.identityDouble.(*identityservice.Legacy)
58- legacy.AddUser(s.cred.User, s.cred.Secrets)
59+ legacy := identityservice.NewLegacy()
60+ legacy.AddUser(s.cred.User, s.cred.Secrets, s.cred.TenantName)
61 legacy.SetManagementURL("http://management.test.invalid/url")
62+ s.service = legacy
63 }
64 s.LiveTests.SetUpSuite(c)
65 }
66@@ -67,7 +60,7 @@
67
68 func (s *localLiveSuite) SetUpTest(c *C) {
69 s.HTTPSuite.SetUpTest(c)
70- s.Mux.Handle("/", s.identityDouble)
71+ s.service.SetupHTTP(s.Mux)
72 s.LiveTests.SetUpTest(c)
73 }
74
75
76=== modified file 'identity/legacy_test.go'
77--- identity/legacy_test.go 2013-01-14 03:18:37 +0000
78+++ identity/legacy_test.go 2013-01-24 03:17:22 +0000
79@@ -15,13 +15,13 @@
80 func (s *LegacyTestSuite) TestAuthAgainstServer(c *C) {
81 service := identityservice.NewLegacy()
82 s.Mux.Handle("/", service)
83- token := service.AddUser("joe-user", "secrets")
84+ userInfo := service.AddUser("joe-user", "secrets", "tenant")
85 service.SetManagementURL("http://management.test.invalid/url")
86 var l Authenticator = &Legacy{}
87 creds := Credentials{User: "joe-user", URL: s.Server.URL, Secrets: "secrets"}
88 auth, err := l.Auth(&creds)
89 c.Assert(err, IsNil)
90- c.Assert(auth.Token, Equals, token)
91+ c.Assert(auth.Token, Equals, userInfo.Token)
92 c.Assert(
93 auth.ServiceURLs, DeepEquals,
94 map[string]string{"compute": "http://management.test.invalid/url/compute", "object-store": "http://management.test.invalid/url/object-store"})
95@@ -30,7 +30,7 @@
96 func (s *LegacyTestSuite) TestBadAuth(c *C) {
97 service := identityservice.NewLegacy()
98 s.Mux.Handle("/", service)
99- _ = service.AddUser("joe-user", "secrets")
100+ _ = service.AddUser("joe-user", "secrets", "tenant")
101 var l Authenticator = &Legacy{}
102 creds := Credentials{User: "joe-user", URL: s.Server.URL, Secrets: "bad-secrets"}
103 auth, err := l.Auth(&creds)
104
105=== modified file 'identity/userpass_test.go'
106--- identity/userpass_test.go 2013-01-14 03:18:37 +0000
107+++ identity/userpass_test.go 2013-01-24 03:17:22 +0000
108@@ -14,12 +14,12 @@
109
110 func (s *UserPassTestSuite) TestAuthAgainstServer(c *C) {
111 service := identityservice.NewUserPass()
112- s.Mux.Handle("/", service)
113- token := service.AddUser("joe-user", "secrets")
114+ service.SetupHTTP(s.Mux)
115+ userInfo := service.AddUser("joe-user", "secrets", "tenant")
116 var l Authenticator = &UserPass{}
117- creds := Credentials{User: "joe-user", URL: s.Server.URL, Secrets: "secrets"}
118+ creds := Credentials{User: "joe-user", URL: s.Server.URL + "/tokens", Secrets: "secrets"}
119 auth, err := l.Auth(&creds)
120 c.Assert(err, IsNil)
121- c.Assert(auth.Token, Equals, token)
122- // c.Assert(auth.ServiceURLs, DeepEquals, map[string]string{"compute": "http://management.test.invalid/url"})
123+ c.Assert(auth.Token, Equals, userInfo.Token)
124+ c.Assert(auth.TenantId, Equals, userInfo.TenantId)
125 }
126
127=== modified file 'nova/local_test.go'
128--- nova/local_test.go 2013-01-21 11:18:33 +0000
129+++ nova/local_test.go 2013-01-24 03:17:22 +0000
130@@ -7,8 +7,7 @@
131 "launchpad.net/goose/errors"
132 "launchpad.net/goose/identity"
133 "launchpad.net/goose/nova"
134- "launchpad.net/goose/testservices/identityservice"
135- "launchpad.net/goose/testservices/novaservice"
136+ "launchpad.net/goose/testservices/openstack"
137 "log"
138 "net/http"
139 "net/http/httptest"
140@@ -19,20 +18,14 @@
141 Suite(&localLiveSuite{})
142 }
143
144-const (
145- baseNovaURL = "/V1/1"
146-)
147-
148 // localLiveSuite runs tests from LiveTests using a fake
149 // nova server that runs within the test process itself.
150 type localLiveSuite struct {
151 LiveTests
152 // The following attributes are for using testing doubles.
153- Server *httptest.Server
154- Mux *http.ServeMux
155- oldHandler http.Handler
156- identityDouble *identityservice.UserPass
157- novaDouble *novaservice.Nova
158+ Server *httptest.Server
159+ Mux *http.ServeMux
160+ oldHandler http.Handler
161 }
162
163 func (s *localLiveSuite) SetUpSuite(c *C) {
164@@ -44,29 +37,16 @@
165 s.Mux = http.NewServeMux()
166 s.Server.Config.Handler = s.Mux
167
168+ // Set up an Openstack service.
169 s.cred = &identity.Credentials{
170- URL: s.Server.URL,
171- User: "fred",
172- Secrets: "secret",
173- Region: "some region"}
174- // Create an identity service and register a Nova endpoint.
175- s.identityDouble = identityservice.NewUserPass()
176- token := s.identityDouble.AddUser(s.cred.User, s.cred.Secrets)
177- ep := identityservice.Endpoint{
178- AdminURL: s.Server.URL + baseNovaURL,
179- InternalURL: s.Server.URL + baseNovaURL,
180- PublicURL: s.Server.URL + baseNovaURL,
181- Region: s.cred.Region,
182+ URL: s.Server.URL,
183+ User: "fred",
184+ Secrets: "secret",
185+ Region: "some region",
186+ TenantName: "tenant",
187 }
188- s.Mux.Handle("/tokens", s.identityDouble)
189-
190- service := identityservice.Service{"nova", "compute", []identityservice.Endpoint{ep}}
191- s.identityDouble.AddService(service)
192- // Create a nova service at the registered endpoint.
193- // TODO: identityservice.UserPass always uses tenantId="1", patch this
194- // when that changes.
195- s.novaDouble = novaservice.New("localhost", "V1", token, "1")
196- s.novaDouble.SetupHTTP(s.Mux)
197+ openstack := openstack.New(s.cred)
198+ openstack.SetupHTTP(s.Mux)
199
200 s.LiveTests.SetUpSuite(c)
201 }
202
203=== modified file 'swift/local_test.go'
204--- swift/local_test.go 2012-12-20 05:47:11 +0000
205+++ swift/local_test.go 2013-01-24 03:17:22 +0000
206@@ -4,19 +4,13 @@
207 . "launchpad.net/gocheck"
208 "launchpad.net/goose/identity"
209 "launchpad.net/goose/testing/httpsuite"
210- "launchpad.net/goose/testservices/identityservice"
211- "launchpad.net/goose/testservices/swiftservice"
212- "net/http"
213+ "launchpad.net/goose/testservices/openstack"
214 )
215
216 func registerLocalTests() {
217 Suite(&localLiveSuite{})
218 }
219
220-const (
221- baseURL = "/object-store"
222-)
223-
224 // localLiveSuite runs tests from LiveTests using a fake
225 // swift server that runs within the test process itself.
226 type localLiveSuite struct {
227@@ -24,32 +18,23 @@
228 LiveTestsPublicContainer
229 // The following attributes are for using testing doubles.
230 httpsuite.HTTPSuite
231- identityDouble http.Handler
232- swiftDouble http.Handler
233+ openstack *openstack.Openstack
234 }
235
236 func (s *localLiveSuite) SetUpSuite(c *C) {
237 c.Logf("Using identity and swift service test doubles")
238 s.HTTPSuite.SetUpSuite(c)
239+ // Set up an Openstack service.
240 s.LiveTests.cred = &identity.Credentials{
241- URL: s.Server.URL,
242- User: "fred",
243- Secrets: "secret",
244- Region: "some region"}
245+ URL: s.Server.URL,
246+ User: "fred",
247+ Secrets: "secret",
248+ Region: "some region",
249+ TenantName: "tenant",
250+ }
251 s.LiveTestsPublicContainer.cred = s.LiveTests.cred
252- // Create an identity service and register a Swift endpoint.
253- s.identityDouble = identityservice.NewUserPass()
254- token := s.identityDouble.(*identityservice.UserPass).AddUser(s.LiveTests.cred.User, s.LiveTests.cred.Secrets)
255- ep := identityservice.Endpoint{
256- s.Server.URL + baseURL, //admin
257- s.Server.URL + baseURL, //internal
258- s.Server.URL + baseURL, //public
259- s.LiveTests.cred.Region,
260- }
261- service := identityservice.Service{"swift", "object-store", []identityservice.Endpoint{ep}}
262- s.identityDouble.(*identityservice.UserPass).AddService(service)
263- // Create a swift service at the registered endpoint.
264- s.swiftDouble = swiftservice.New("localhost", baseURL+"/", token)
265+ s.openstack = openstack.New(s.LiveTests.cred)
266+
267 s.LiveTests.SetUpSuite(c)
268 s.LiveTestsPublicContainer.SetUpSuite(c)
269 }
270@@ -62,8 +47,7 @@
271
272 func (s *localLiveSuite) SetUpTest(c *C) {
273 s.HTTPSuite.SetUpTest(c)
274- s.Mux.Handle(baseURL+"/", s.swiftDouble)
275- s.Mux.Handle("/", s.identityDouble)
276+ s.openstack.SetupHTTP(s.Mux)
277 s.LiveTests.SetUpTest(c)
278 s.LiveTestsPublicContainer.SetUpTest(c)
279 }
280
281=== added directory 'testservices/cmd'
282=== renamed file 'testservices/main.go' => 'testservices/cmd/main.go'
283--- testservices/main.go 2012-11-11 11:13:52 +0000
284+++ testservices/cmd/main.go 2013-01-24 03:17:22 +0000
285@@ -61,9 +61,10 @@
286 if !ok {
287 log.Fatalf("No such provider: %s, pick one of: %v", provider, providers())
288 }
289- http.Handle("/", p)
290+ mux := http.NewServeMux()
291+ p.SetupHTTP(mux)
292 for _, u := range users.users {
293- p.AddUser(u.user, u.secret)
294+ p.AddUser(u.user, u.secret, "tenant")
295 }
296- log.Fatal(http.ListenAndServe(*serveAddr, nil))
297+ log.Fatal(http.ListenAndServe(*serveAddr, mux))
298 }
299
300=== modified file 'testservices/identityservice/identityservice.go'
301--- testservices/identityservice/identityservice.go 2012-11-11 11:13:52 +0000
302+++ testservices/identityservice/identityservice.go 2013-01-24 03:17:22 +0000
303@@ -1,10 +1,16 @@
304 package identityservice
305
306-import (
307- "net/http"
308-)
309+import "net/http"
310
311+// An IdentityService provides user authentication for an Openstack instance.
312 type IdentityService interface {
313- AddUser(user, secret string) (token string)
314- ServeHTTP(w http.ResponseWriter, r *http.Request)
315+ AddUser(user, secret, tenant string) *UserInfo
316+ FindUser(token string) (*UserInfo, error)
317+ RegisterServiceProvider(name, serviceType string, serviceProvider ServiceProvider)
318+ SetupHTTP(mux *http.ServeMux)
319+}
320+
321+// A ServiceProvider is an Openstack module which has service endpoints.
322+type ServiceProvider interface {
323+ Endpoints() []Endpoint
324 }
325
326=== modified file 'testservices/identityservice/legacy.go'
327--- testservices/identityservice/legacy.go 2012-12-21 05:07:39 +0000
328+++ testservices/identityservice/legacy.go 2013-01-24 03:17:22 +0000
329@@ -5,40 +5,48 @@
330 )
331
332 type Legacy struct {
333- tokens map[string]UserInfo
334+ Users
335 managementURL string
336 }
337
338 func NewLegacy() *Legacy {
339 service := &Legacy{}
340- service.tokens = make(map[string]UserInfo)
341+ service.users = make(map[string]UserInfo)
342+ service.tenants = make(map[string]string)
343 return service
344 }
345
346+func (lis *Legacy) RegisterServiceProvider(name, serviceType string, serviceProvider ServiceProvider) {
347+ // NOOP for legacy identity service.
348+}
349+
350 func (lis *Legacy) SetManagementURL(URL string) {
351 lis.managementURL = URL
352 }
353
354-func (lis *Legacy) AddUser(user, secret string) string {
355- token := randomHexToken()
356- lis.tokens[user] = UserInfo{secret: secret, token: token}
357- return token
358+// setupHTTP attaches all the needed handlers to provide the HTTP API.
359+func (lis *Legacy) SetupHTTP(mux *http.ServeMux) {
360+ mux.Handle("/", lis)
361 }
362
363 func (lis *Legacy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
364 username := r.Header.Get("X-Auth-User")
365- info, ok := lis.tokens[username]
366+ userInfo, ok := lis.users[username]
367 if !ok {
368 w.WriteHeader(http.StatusUnauthorized)
369 return
370 }
371 auth_key := r.Header.Get("X-Auth-Key")
372- if auth_key != info.secret {
373+ if auth_key != userInfo.secret {
374 w.WriteHeader(http.StatusUnauthorized)
375 return
376 }
377+ if userInfo.Token == "" {
378+ userInfo.Token = randomHexToken()
379+ lis.users[username] = userInfo
380+ }
381 header := w.Header()
382- header.Set("X-Auth-Token", info.token)
383+ header.Set("X-Auth-Token", userInfo.Token)
384 header.Set("X-Server-Management-Url", lis.managementURL+"/compute")
385 header.Set("X-Storage-Url", lis.managementURL+"/object-store")
386 w.WriteHeader(http.StatusNoContent)
387
388=== modified file 'testservices/identityservice/legacy_test.go'
389--- testservices/identityservice/legacy_test.go 2012-12-21 05:07:39 +0000
390+++ testservices/identityservice/legacy_test.go 2013-01-24 03:17:22 +0000
391@@ -19,9 +19,10 @@
392 // Ensure that it conforms to the interface
393 var _ IdentityService = identity
394 identity.SetManagementURL(managementURL)
395- s.Mux.Handle("/", identity)
396+ identity.SetupHTTP(s.Mux)
397 if user != "" {
398- token = identity.AddUser(user, secret)
399+ userInfo := identity.AddUser(user, secret, "tenant")
400+ token = userInfo.Token
401 }
402 return
403 }
404
405=== modified file 'testservices/identityservice/service_test.go'
406--- testservices/identityservice/service_test.go 2012-11-11 15:29:52 +0000
407+++ testservices/identityservice/service_test.go 2013-01-24 03:17:22 +0000
408@@ -17,7 +17,7 @@
409 var _ = Suite(&IdentityServiceSuite{service: NewLegacy()})
410
411 func (s *IdentityServiceSuite) TestAddUserGivesNewToken(c *C) {
412- token1 := s.service.AddUser("user-1", "password-1")
413- token2 := s.service.AddUser("user-2", "password-2")
414- c.Assert(token1, Not(Equals), token2)
415+ userInfo1 := s.service.AddUser("user-1", "password-1", "tenant")
416+ userInfo2 := s.service.AddUser("user-2", "password-2", "tenant")
417+ c.Assert(userInfo1.Token, Not(Equals), userInfo2.Token)
418 }
419
420=== modified file 'testservices/identityservice/userpass.go'
421--- testservices/identityservice/userpass.go 2012-12-21 04:10:14 +0000
422+++ testservices/identityservice/userpass.go 2013-01-24 03:17:22 +0000
423@@ -135,25 +135,25 @@
424 }`
425
426 type UserPass struct {
427- users map[string]UserInfo
428+ Users
429 services []Service
430 }
431
432 func NewUserPass() *UserPass {
433 userpass := &UserPass{
434- users: make(map[string]UserInfo),
435 services: make([]Service, 0),
436 }
437+ userpass.users = make(map[string]UserInfo)
438+ userpass.tenants = make(map[string]string)
439 return userpass
440 }
441
442-func (u *UserPass) AddUser(user, secret string) string {
443- token := randomHexToken()
444- u.users[user] = UserInfo{secret: secret, token: token}
445- return token
446+func (u *UserPass) RegisterServiceProvider(name, serviceType string, serviceProvider ServiceProvider) {
447+ service := Service{name, serviceType, serviceProvider.Endpoints()}
448+ u.addService(service)
449 }
450
451-func (u *UserPass) AddService(service Service) {
452+func (u *UserPass) addService(service Service) {
453 u.services = append(u.services, service)
454 }
455
456@@ -189,8 +189,6 @@
457 notJSON = ("Expecting to find application/json in Content-Type header." +
458 " The server could not comply with the request since it is either malformed" +
459 " or otherwise incorrect. The client is assumed to be in error.")
460- notAuthorized = "The request you have made requires authentication."
461- invalidUser = "Invalid user / password"
462 )
463
464 func (u *UserPass) ServeHTTP(w http.ResponseWriter, r *http.Request) {
465@@ -210,13 +208,9 @@
466 return
467 }
468 }
469- userInfo, ok := u.users[req.Auth.PasswordCredentials.Username]
470- if !ok {
471- u.ReturnFailure(w, http.StatusUnauthorized, notAuthorized)
472- return
473- }
474- if userInfo.secret != req.Auth.PasswordCredentials.Password {
475- u.ReturnFailure(w, http.StatusUnauthorized, invalidUser)
476+ userInfo, errmsg := u.authenticate(req.Auth.PasswordCredentials.Username, req.Auth.PasswordCredentials.Password)
477+ if errmsg != "" {
478+ u.ReturnFailure(w, http.StatusUnauthorized, errmsg)
479 return
480 }
481 res := AccessResponse{}
482@@ -228,7 +222,9 @@
483 return
484 }
485 res.Access.ServiceCatalog = u.services
486- res.Access.Token.Id = userInfo.token
487+ res.Access.Token.Id = userInfo.Token
488+ res.Access.Token.Tenant.Id = userInfo.TenantId
489+ res.Access.User.Id = userInfo.Id
490 if content, err := json.Marshal(res); err != nil {
491 u.ReturnFailure(w, http.StatusInternalServerError, err.Error())
492 return
493@@ -239,3 +235,8 @@
494 }
495 panic("All paths should have already returned")
496 }
497+
498+// setupHTTP attaches all the needed handlers to provide the HTTP API.
499+func (u *UserPass) SetupHTTP(mux *http.ServeMux) {
500+ mux.Handle("/tokens", u)
501+}
502
503=== modified file 'testservices/identityservice/userpass_test.go'
504--- testservices/identityservice/userpass_test.go 2012-11-16 16:39:16 +0000
505+++ testservices/identityservice/userpass_test.go 2013-01-24 03:17:22 +0000
506@@ -16,30 +16,30 @@
507
508 var _ = Suite(&UserPassSuite{})
509
510-func makeUserPass(user, secret string) (identity *UserPass, token string) {
511+func makeUserPass(user, secret string) (identity *UserPass) {
512 identity = NewUserPass()
513 // Ensure that it conforms to the interface
514 var _ IdentityService = identity
515 if user != "" {
516- token = identity.AddUser(user, secret)
517+ identity.AddUser(user, secret, "tenant")
518 }
519 return
520 }
521
522-func (s *UserPassSuite) setupUserPass(user, secret string) (token string) {
523+func (s *UserPassSuite) setupUserPass(user, secret string) {
524 var identity *UserPass
525- identity, token = makeUserPass(user, secret)
526- s.Mux.Handle("/", identity)
527+ identity = makeUserPass(user, secret)
528+ identity.SetupHTTP(s.Mux)
529 return
530 }
531
532-func (s *UserPassSuite) setupUserPassWithServices(user, secret string, services []Service) (token string) {
533+func (s *UserPassSuite) setupUserPassWithServices(user, secret string, services []Service) {
534 var identity *UserPass
535- identity, token = makeUserPass(user, secret)
536+ identity = makeUserPass(user, secret)
537 for _, service := range services {
538- identity.AddService(service)
539+ identity.addService(service)
540 }
541- s.Mux.Handle("/", identity)
542+ identity.SetupHTTP(s.Mux)
543 return
544 }
545
546@@ -56,7 +56,7 @@
547 func userPassAuthRequest(URL, user, key string) (*http.Response, error) {
548 client := &http.Client{}
549 body := strings.NewReader(fmt.Sprintf(authTemplate, user, key))
550- request, err := http.NewRequest("POST", URL, body)
551+ request, err := http.NewRequest("POST", URL+"/tokens", body)
552 request.Header.Set("Content-Type", "application/json")
553 if err != nil {
554 return nil, err
555@@ -81,11 +81,10 @@
556
557 func (s *UserPassSuite) TestNotJSON(c *C) {
558 // We do everything in userPassAuthRequest, except set the Content-Type
559- token := s.setupUserPass("user", "secret")
560- c.Assert(token, NotNil)
561+ s.setupUserPass("user", "secret")
562 client := &http.Client{}
563 body := strings.NewReader(fmt.Sprintf(authTemplate, "user", "secret"))
564- request, err := http.NewRequest("POST", s.Server.URL, body)
565+ request, err := http.NewRequest("POST", s.Server.URL+"/tokens", body)
566 c.Assert(err, IsNil)
567 res, err := client.Do(request)
568 defer res.Body.Close()
569@@ -95,8 +94,7 @@
570
571 func (s *UserPassSuite) TestBadJSON(c *C) {
572 // We do everything in userPassAuthRequest, except set the Content-Type
573- token := s.setupUserPass("user", "secret")
574- c.Assert(token, NotNil)
575+ s.setupUserPass("user", "secret")
576 res, err := userPassAuthRequest(s.Server.URL, "garbage\"in", "secret")
577 defer res.Body.Close()
578 c.Assert(err, IsNil)
579@@ -104,8 +102,7 @@
580 }
581
582 func (s *UserPassSuite) TestNoSuchUser(c *C) {
583- token := s.setupUserPass("user", "secret")
584- c.Assert(token, NotNil)
585+ s.setupUserPass("user", "secret")
586 res, err := userPassAuthRequest(s.Server.URL, "not-user", "secret")
587 defer res.Body.Close()
588 c.Assert(err, IsNil)
589@@ -113,8 +110,7 @@
590 }
591
592 func (s *UserPassSuite) TestBadPassword(c *C) {
593- token := s.setupUserPass("user", "secret")
594- c.Assert(token, NotNil)
595+ s.setupUserPass("user", "secret")
596 res, err := userPassAuthRequest(s.Server.URL, "user", "not-secret")
597 defer res.Body.Close()
598 c.Assert(err, IsNil)
599@@ -123,11 +119,10 @@
600
601 func (s *UserPassSuite) TestValidAuthorization(c *C) {
602 compute_url := "http://testing.invalid/compute"
603- token := s.setupUserPassWithServices("user", "secret", []Service{
604+ s.setupUserPassWithServices("user", "secret", []Service{
605 {"nova", "compute", []Endpoint{
606 {PublicURL: compute_url},
607 }}})
608- c.Assert(token, NotNil)
609 res, err := userPassAuthRequest(s.Server.URL, "user", "secret")
610 defer res.Body.Close()
611 c.Assert(err, IsNil)
612@@ -138,7 +133,7 @@
613 var response AccessResponse
614 err = json.Unmarshal(content, &response)
615 c.Assert(err, IsNil)
616- c.Check(response.Access.Token.Id, Equals, token)
617+ c.Check(response.Access.Token.Id, NotNil)
618 novaURL := ""
619 for _, service := range response.Access.ServiceCatalog {
620 if service.Type == "compute" {
621
622=== added file 'testservices/identityservice/users.go'
623--- testservices/identityservice/users.go 1970-01-01 00:00:00 +0000
624+++ testservices/identityservice/users.go 2013-01-24 03:17:22 +0000
625@@ -0,0 +1,63 @@
626+package identityservice
627+
628+import (
629+ "fmt"
630+ "strconv"
631+)
632+
633+type Users struct {
634+ nextUserId int
635+ nextTenantId int
636+ users map[string]UserInfo
637+ tenants map[string]string
638+}
639+
640+func (u *Users) addTenant(tenant string) string {
641+ for id, tenantName := range u.tenants {
642+ if tenant == tenantName {
643+ return id
644+ }
645+ }
646+ u.nextTenantId++
647+ id := strconv.Itoa(u.nextTenantId)
648+ u.tenants[id] = tenant
649+ return id
650+}
651+
652+func (u *Users) AddUser(user, secret, tenant string) *UserInfo {
653+ tenantId := u.addTenant(tenant)
654+ u.nextUserId++
655+ userInfo := &UserInfo{secret: secret, Id: strconv.Itoa(u.nextUserId), TenantId: tenantId}
656+ u.users[user] = *userInfo
657+ userInfo, _ = u.authenticate(user, secret)
658+ return userInfo
659+}
660+
661+func (u *Users) FindUser(token string) (*UserInfo, error) {
662+ for _, userInfo := range u.users {
663+ if userInfo.Token == token {
664+ return &userInfo, nil
665+ }
666+ }
667+ return nil, fmt.Errorf("No user with token %v exists", token)
668+}
669+
670+const (
671+ notAuthorized = "The request you have made requires authentication."
672+ invalidUser = "Invalid user / password"
673+)
674+
675+func (u *Users) authenticate(username, password string) (*UserInfo, string) {
676+ userInfo, ok := u.users[username]
677+ if !ok {
678+ return nil, notAuthorized
679+ }
680+ if userInfo.secret != password {
681+ return nil, invalidUser
682+ }
683+ if userInfo.Token == "" {
684+ userInfo.Token = randomHexToken()
685+ u.users[username] = userInfo
686+ }
687+ return &userInfo, ""
688+}
689
690=== modified file 'testservices/identityservice/util.go'
691--- testservices/identityservice/util.go 2012-11-23 03:05:16 +0000
692+++ testservices/identityservice/util.go 2013-01-24 03:17:22 +0000
693@@ -7,8 +7,10 @@
694 )
695
696 type UserInfo struct {
697- secret string
698- token string
699+ Id string
700+ TenantId string
701+ Token string
702+ secret string
703 }
704
705 // Generate a bit of random hex data for
706
707=== modified file 'testservices/novaservice/service.go'
708--- testservices/novaservice/service.go 2013-01-21 23:57:31 +0000
709+++ testservices/novaservice/service.go 2013-01-24 03:17:22 +0000
710@@ -5,12 +5,19 @@
711 import (
712 "fmt"
713 "launchpad.net/goose/nova"
714+ "launchpad.net/goose/testservices"
715+ "launchpad.net/goose/testservices/identityservice"
716+ "net/url"
717 "strings"
718 )
719
720+var _ testservices.HttpService = (*Nova)(nil)
721+var _ identityservice.ServiceProvider = (*Nova)(nil)
722+
723 // Nova implements a OpenStack Nova testing service and
724 // contains the service double's internal state.
725 type Nova struct {
726+ testservices.ServiceInstance
727 flavors map[string]nova.FlavorDetail
728 servers map[string]nova.ServerDetail
729 groups map[int]nova.SecurityGroup
730@@ -18,11 +25,6 @@
731 floatingIPs map[int]nova.FloatingIP
732 serverGroups map[string][]int
733 serverIPs map[string][]int
734- hostname string
735- versionPath string
736- token string
737- tenantId string
738- userId string
739 nextGroupId int
740 nextRuleId int
741 nextIPId int
742@@ -31,17 +33,35 @@
743
744 // endpoint returns either a versioned or non-versioned service
745 // endpoint URL from the given path.
746-func (n *Nova) endpoint(version bool, path string) string {
747- ep := "http://" + n.hostname
748+func (n *Nova) endpointURL(version bool, path string) string {
749+ ep := "http://" + n.Hostname
750 if version {
751- ep += n.versionPath + "/"
752- }
753- ep += n.tenantId + "/" + strings.TrimLeft(path, "/")
754+ ep += n.VersionPath + "/"
755+ }
756+ ep += n.TenantId
757+ if path != "" {
758+ ep += "/" + strings.TrimLeft(path, "/")
759+ }
760 return ep
761 }
762
763+func (n *Nova) Endpoints() []identityservice.Endpoint {
764+ ep := identityservice.Endpoint{
765+ AdminURL: n.endpointURL(true, ""),
766+ InternalURL: n.endpointURL(true, ""),
767+ PublicURL: n.endpointURL(true, ""),
768+ Region: n.Region,
769+ }
770+ return []identityservice.Endpoint{ep}
771+}
772+
773 // New creates an instance of the Nova object, given the parameters.
774-func New(hostname, versionPath, token, tenantId string) *Nova {
775+func New(hostURL, versionPath, tenantId, region string, identityService identityservice.IdentityService) *Nova {
776+ url, err := url.Parse(hostURL)
777+ if err != nil {
778+ panic(err)
779+ }
780+ hostname := url.Host
781 if !strings.HasSuffix(hostname, "/") {
782 hostname += "/"
783 }
784@@ -62,17 +82,19 @@
785 floatingIPs: make(map[int]nova.FloatingIP),
786 serverGroups: make(map[string][]int),
787 serverIPs: make(map[string][]int),
788- hostname: hostname,
789- versionPath: versionPath,
790- token: token,
791- tenantId: tenantId,
792- // TODO(wallyworld): Identity service double currently hard codes all user ids to "14". This should be fixed
793- // in the identity service but the fix will result in an API change which will break juju-core. So for now
794- // we will also hard code it here too.
795- userId: "14",
796 // The following attribute controls whether rate limit responses are sent back to the caller.
797 // This is switched off when we want to ensure the client eventually gets a proper response.
798 sendFakeRateLimitResponse: true,
799+ ServiceInstance: testservices.ServiceInstance{
800+ IdentityService: identityService,
801+ Hostname: hostname,
802+ VersionPath: versionPath,
803+ TenantId: tenantId,
804+ Region: region,
805+ },
806+ }
807+ if identityService != nil {
808+ identityService.RegisterServiceProvider("nova", "compute", nova)
809 }
810 for i, flavor := range defaultFlavors {
811 nova.buildFlavorLinks(&flavor)
812@@ -97,8 +119,8 @@
813 func (n *Nova) buildFlavorLinks(flavor *nova.FlavorDetail) {
814 url := "/flavors/" + flavor.Id
815 flavor.Links = []nova.Link{
816- nova.Link{Href: n.endpoint(true, url), Rel: "self"},
817- nova.Link{Href: n.endpoint(false, url), Rel: "bookmark"},
818+ nova.Link{Href: n.endpointURL(true, url), Rel: "self"},
819+ nova.Link{Href: n.endpointURL(false, url), Rel: "bookmark"},
820 }
821 }
822
823@@ -170,8 +192,8 @@
824 func (n *Nova) buildServerLinks(server *nova.ServerDetail) {
825 url := "/servers/" + server.Id
826 server.Links = []nova.Link{
827- nova.Link{Href: n.endpoint(true, url), Rel: "self"},
828- nova.Link{Href: n.endpoint(false, url), Rel: "bookmark"},
829+ nova.Link{Href: n.endpointURL(true, url), Rel: "self"},
830+ nova.Link{Href: n.endpointURL(false, url), Rel: "bookmark"},
831 }
832 }
833
834@@ -280,7 +302,7 @@
835 if _, err := n.securityGroup(group.Id); err == nil {
836 return fmt.Errorf("a security group with id %d already exists", group.Id)
837 }
838- group.TenantId = n.tenantId
839+ group.TenantId = n.TenantId
840 if group.Rules == nil {
841 group.Rules = []nova.SecurityGroupRule{}
842 }
843
844=== modified file 'testservices/novaservice/service_http.go'
845--- testservices/novaservice/service_http.go 2013-01-22 18:14:51 +0000
846+++ testservices/novaservice/service_http.go 2013-01-24 03:17:22 +0000
847@@ -9,6 +9,7 @@
848 "io"
849 "io/ioutil"
850 "launchpad.net/goose/nova"
851+ "launchpad.net/goose/testservices/identityservice"
852 "net/http"
853 "path"
854 "strconv"
855@@ -228,7 +229,7 @@
856 body := e.body
857 if body != "" {
858 if e.nova != nil {
859- body = strings.Replace(body, "$ENDPOINT$", e.nova.endpoint(true, "/"), -1)
860+ body = strings.Replace(body, "$ENDPOINT$", e.nova.endpointURL(true, "/"), -1)
861 }
862 body = strings.Replace(body, "$URL$", url, -1)
863 body = strings.Replace(body, "$ERROR$", e.Error(), -1)
864@@ -264,10 +265,15 @@
865 method func(n *Nova, w http.ResponseWriter, r *http.Request) error
866 }
867
868+func userInfo(i identityservice.IdentityService, r *http.Request) (*identityservice.UserInfo, error) {
869+ return i.FindUser(r.Header.Get(authToken))
870+}
871+
872 func (h *novaHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
873 path := r.URL.Path
874 // handle invalid X-Auth-Token header
875- if r.Header.Get(authToken) != h.n.token {
876+ _, err := userInfo(h.n.IdentityService, r)
877+ if err != nil {
878 errUnauthorized.ServeHTTP(w, r)
879 return
880 }
881@@ -276,7 +282,7 @@
882 errNotFound.ServeHTTP(w, r)
883 return
884 }
885- err := h.method(h.n, w, r)
886+ err = h.method(h.n, w, r)
887 if err == nil {
888 return
889 }
890@@ -541,7 +547,7 @@
891 continue
892 }
893 if sg, err := n.securityGroupByName(groupName); err != nil {
894- return noGroupError(groupName, n.tenantId)
895+ return noGroupError(groupName, n.TenantId)
896 } else {
897 groups = append(groups, sg.Id)
898 }
899@@ -553,11 +559,12 @@
900 flavorEnt := nova.Entity{Id: flavor.Id, Links: flavor.Links}
901 image := nova.Entity{Id: req.Server.ImageRef}
902 timestr := time.Now().Format(time.RFC3339)
903+ userInfo, _ := userInfo(n.IdentityService, r)
904 server := nova.ServerDetail{
905 Id: id,
906 Name: req.Server.Name,
907- TenantId: n.tenantId,
908- UserId: n.userId,
909+ TenantId: n.TenantId,
910+ UserId: userInfo.Id,
911 HostId: "1",
912 Image: image,
913 Flavor: flavorEnt,
914@@ -787,7 +794,7 @@
915 Id: nextId,
916 Name: req.Group.Name,
917 Description: req.Group.Description,
918- TenantId: n.tenantId,
919+ TenantId: n.TenantId,
920 })
921 if err != nil {
922 return err
923@@ -970,8 +977,8 @@
924 "/$v/$t/os-floating-ips": n.handler((*Nova).handleFloatingIPs),
925 }
926 for path, h := range handlers {
927- path = strings.Replace(path, "$v", n.versionPath, 1)
928- path = strings.Replace(path, "$t", n.tenantId, 1)
929+ path = strings.Replace(path, "$v", n.VersionPath, 1)
930+ path = strings.Replace(path, "$t", n.TenantId, 1)
931 if !strings.HasSuffix(path, "/") {
932 mux.Handle(path+"/", h)
933 }
934
935=== modified file 'testservices/novaservice/service_http_test.go'
936--- testservices/novaservice/service_http_test.go 2013-01-22 18:14:51 +0000
937+++ testservices/novaservice/service_http_test.go 2013-01-24 03:17:22 +0000
938@@ -10,8 +10,8 @@
939 . "launchpad.net/gocheck"
940 "launchpad.net/goose/nova"
941 "launchpad.net/goose/testing/httpsuite"
942+ "launchpad.net/goose/testservices/identityservice"
943 "net/http"
944- "net/url"
945 "strconv"
946 "strings"
947 )
948@@ -19,14 +19,17 @@
949 type NovaHTTPSuite struct {
950 httpsuite.HTTPSuite
951 service *Nova
952+ token string
953 }
954
955 var _ = Suite(&NovaHTTPSuite{})
956
957 func (s *NovaHTTPSuite) SetUpSuite(c *C) {
958 s.HTTPSuite.SetUpSuite(c)
959- url, _ := url.Parse(s.Server.URL)
960- s.service = New(url.Host, versionPath, token, tenantId)
961+ identityDouble := identityservice.NewUserPass()
962+ userInfo := identityDouble.AddUser("fred", "secret", "tenant")
963+ s.token = userInfo.Token
964+ s.service = New(s.Server.URL, versionPath, userInfo.TenantId, region, identityDouble)
965 }
966
967 func (s *NovaHTTPSuite) TearDownSuite(c *C) {
968@@ -68,8 +71,8 @@
969 // sendRequest constructs an HTTP request from the parameters and
970 // sends it, returning the response or an error.
971 func (s *NovaHTTPSuite) sendRequest(method, url string, body []byte, headers http.Header) (*http.Response, error) {
972- if !strings.HasPrefix(url, "http") { //s.service.hostname) {
973- url = "http://" + s.service.hostname + strings.TrimLeft(url, "/")
974+ if !strings.HasPrefix(url, "http") {
975+ url = "http://" + s.service.Hostname + strings.TrimLeft(url, "/")
976 }
977 req, err := http.NewRequest(method, url, bytes.NewReader(body))
978 if err != nil {
979@@ -91,8 +94,8 @@
980 if headers == nil {
981 headers = make(http.Header)
982 }
983- headers.Set(authToken, s.service.token)
984- url := s.service.endpoint(true, path)
985+ headers.Set(authToken, s.token)
986+ url := s.service.endpointURL(true, path)
987 return s.sendRequest(method, url, body, headers)
988 }
989
990@@ -114,339 +117,345 @@
991 return h
992 }
993
994-// simpleTests defines a simple request without a body and expected response.
995-var simpleTests = []struct {
996+// SimpleTest defines a simple request without a body and expected response.
997+type SimpleTest struct {
998 unauth bool
999 method string
1000 url string
1001 headers http.Header
1002 expect *errorResponse
1003-}{
1004- {
1005- unauth: true,
1006- method: "GET",
1007- url: "/any",
1008- headers: make(http.Header),
1009- expect: errUnauthorized,
1010- },
1011- {
1012- unauth: true,
1013- method: "POST",
1014- url: "/any",
1015- headers: setHeader(authToken, "phony"),
1016- expect: errUnauthorized,
1017- },
1018- {
1019- unauth: true,
1020- method: "GET",
1021- url: "/",
1022- headers: setHeader(authToken, token),
1023- expect: errNoVersion,
1024- },
1025- {
1026- unauth: true,
1027- method: "GET",
1028- url: "/any",
1029- headers: setHeader(authToken, token),
1030- expect: errMultipleChoices,
1031- },
1032- {
1033- unauth: true,
1034- method: "POST",
1035- url: "/any/unknown/one",
1036- headers: setHeader(authToken, token),
1037- expect: errMultipleChoices,
1038- },
1039- {
1040- method: "POST",
1041- url: "/any/unknown/one",
1042- expect: errNotFound,
1043- },
1044- {
1045- unauth: true,
1046- method: "GET",
1047- url: versionPath + "/phony_token",
1048- headers: setHeader(authToken, token),
1049- expect: errBadRequest,
1050- },
1051- {
1052- method: "GET",
1053- url: "/flavors/",
1054- expect: errNotFound,
1055- },
1056- {
1057- method: "GET",
1058- url: "/flavors/invalid",
1059- expect: errNotFound,
1060- },
1061- {
1062- method: "POST",
1063- url: "/flavors",
1064- expect: errBadRequest2,
1065- },
1066- {
1067- method: "POST",
1068- url: "/flavors/invalid",
1069- expect: errNotFound,
1070- },
1071- {
1072- method: "PUT",
1073- url: "/flavors",
1074- expect: errNotFound,
1075- },
1076- {
1077- method: "PUT",
1078- url: "/flavors/invalid",
1079- expect: errNotFoundJSON,
1080- },
1081- {
1082- method: "DELETE",
1083- url: "/flavors",
1084- expect: errNotFound,
1085- },
1086- {
1087- method: "DELETE",
1088- url: "/flavors/invalid",
1089- expect: errForbidden,
1090- },
1091- {
1092- method: "GET",
1093- url: "/flavors/detail/invalid",
1094- expect: errNotFound,
1095- },
1096- {
1097- method: "POST",
1098- url: "/flavors/detail",
1099- expect: errNotFound,
1100- },
1101- {
1102- method: "POST",
1103- url: "/flavors/detail/invalid",
1104- expect: errNotFound,
1105- },
1106- {
1107- method: "PUT",
1108- url: "/flavors/detail",
1109- expect: errNotFoundJSON,
1110- },
1111- {
1112- method: "PUT",
1113- url: "/flavors/detail/invalid",
1114- expect: errNotFound,
1115- },
1116- {
1117- method: "DELETE",
1118- url: "/flavors/detail",
1119- expect: errForbidden,
1120- },
1121- {
1122- method: "DELETE",
1123- url: "/flavors/detail/invalid",
1124- expect: errNotFound,
1125- },
1126- {
1127- method: "GET",
1128- url: "/servers/invalid",
1129- expect: errNotFoundJSON,
1130- },
1131- {
1132- method: "POST",
1133- url: "/servers",
1134- expect: errBadRequest2,
1135- },
1136- {
1137- method: "POST",
1138- url: "/servers/invalid",
1139- expect: errNotFound,
1140- },
1141- {
1142- method: "PUT",
1143- url: "/servers",
1144- expect: errNotFound,
1145- },
1146- {
1147- method: "PUT",
1148- url: "/servers/invalid",
1149- expect: errBadRequest2,
1150- },
1151- {
1152- method: "DELETE",
1153- url: "/servers",
1154- expect: errNotFound,
1155- },
1156- {
1157- method: "DELETE",
1158- url: "/servers/invalid",
1159- expect: errNotFoundJSON,
1160- },
1161- {
1162- method: "GET",
1163- url: "/servers/detail/invalid",
1164- expect: errNotFound,
1165- },
1166- {
1167- method: "POST",
1168- url: "/servers/detail",
1169- expect: errNotFound,
1170- },
1171- {
1172- method: "POST",
1173- url: "/servers/detail/invalid",
1174- expect: errNotFound,
1175- },
1176- {
1177- method: "PUT",
1178- url: "/servers/detail",
1179- expect: errBadRequest2,
1180- },
1181- {
1182- method: "PUT",
1183- url: "/servers/detail/invalid",
1184- expect: errNotFound,
1185- },
1186- {
1187- method: "DELETE",
1188- url: "/servers/detail",
1189- expect: errNotFoundJSON,
1190- },
1191- {
1192- method: "DELETE",
1193- url: "/servers/detail/invalid",
1194- expect: errNotFound,
1195- },
1196- {
1197- method: "GET",
1198- url: "/os-security-groups/invalid",
1199- expect: errBadRequestSG,
1200- },
1201- {
1202- method: "GET",
1203- url: "/os-security-groups/42",
1204- expect: errNotFoundJSONSG,
1205- },
1206- {
1207- method: "POST",
1208- url: "/os-security-groups",
1209- expect: errBadRequest2,
1210- },
1211- {
1212- method: "POST",
1213- url: "/os-security-groups/invalid",
1214- expect: errNotFound,
1215- },
1216- {
1217- method: "PUT",
1218- url: "/os-security-groups",
1219- expect: errNotFound,
1220- },
1221- {
1222- method: "PUT",
1223- url: "/os-security-groups/invalid",
1224- expect: errNotFoundJSON,
1225- },
1226- {
1227- method: "DELETE",
1228- url: "/os-security-groups",
1229- expect: errNotFound,
1230- },
1231- {
1232- method: "DELETE",
1233- url: "/os-security-groups/invalid",
1234- expect: errBadRequestSG,
1235- },
1236- {
1237- method: "DELETE",
1238- url: "/os-security-groups/42",
1239- expect: errNotFoundJSONSG,
1240- },
1241- {
1242- method: "GET",
1243- url: "/os-security-group-rules",
1244- expect: errNotFoundJSON,
1245- },
1246- {
1247- method: "GET",
1248- url: "/os-security-group-rules/invalid",
1249- expect: errNotFoundJSON,
1250- },
1251- {
1252- method: "GET",
1253- url: "/os-security-group-rules/42",
1254- expect: errNotFoundJSON,
1255- },
1256- {
1257- method: "POST",
1258- url: "/os-security-group-rules",
1259- expect: errBadRequest2,
1260- },
1261- {
1262- method: "POST",
1263- url: "/os-security-group-rules/invalid",
1264- expect: errNotFound,
1265- },
1266- {
1267- method: "PUT",
1268- url: "/os-security-group-rules",
1269- expect: errNotFound,
1270- },
1271- {
1272- method: "PUT",
1273- url: "/os-security-group-rules/invalid",
1274- expect: errNotFoundJSON,
1275- },
1276- {
1277- method: "DELETE",
1278- url: "/os-security-group-rules",
1279- expect: errNotFound,
1280- },
1281- {
1282- method: "DELETE",
1283- url: "/os-security-group-rules/invalid",
1284- expect: errBadRequestSG, // sic; should've been rule-specific
1285- },
1286- {
1287- method: "DELETE",
1288- url: "/os-security-group-rules/42",
1289- expect: errNotFoundJSONSGR,
1290- },
1291- {
1292- method: "GET",
1293- url: "/os-floating-ips/42",
1294- expect: errNotFoundJSON,
1295- },
1296- {
1297- method: "POST",
1298- url: "/os-floating-ips/invalid",
1299- expect: errNotFound,
1300- },
1301- {
1302- method: "PUT",
1303- url: "/os-floating-ips",
1304- expect: errNotFound,
1305- },
1306- {
1307- method: "PUT",
1308- url: "/os-floating-ips/invalid",
1309- expect: errNotFoundJSON,
1310- },
1311- {
1312- method: "DELETE",
1313- url: "/os-floating-ips",
1314- expect: errNotFound,
1315- },
1316- {
1317- method: "DELETE",
1318- url: "/os-floating-ips/invalid",
1319- expect: errNotFoundJSON,
1320- },
1321+}
1322+
1323+func (s *NovaHTTPSuite) simpleTests() []SimpleTest {
1324+ var simpleTests = []SimpleTest{
1325+ {
1326+ unauth: true,
1327+ method: "GET",
1328+ url: "/any",
1329+ headers: make(http.Header),
1330+ expect: errUnauthorized,
1331+ },
1332+ {
1333+ unauth: true,
1334+ method: "POST",
1335+ url: "/any",
1336+ headers: setHeader(authToken, "phony"),
1337+ expect: errUnauthorized,
1338+ },
1339+ {
1340+ unauth: true,
1341+ method: "GET",
1342+ url: "/",
1343+ headers: setHeader(authToken, s.token),
1344+ expect: errNoVersion,
1345+ },
1346+ {
1347+ unauth: true,
1348+ method: "GET",
1349+ url: "/any",
1350+ headers: setHeader(authToken, s.token),
1351+ expect: errMultipleChoices,
1352+ },
1353+ {
1354+ unauth: true,
1355+ method: "POST",
1356+ url: "/any/unknown/one",
1357+ headers: setHeader(authToken, s.token),
1358+ expect: errMultipleChoices,
1359+ },
1360+ {
1361+ method: "POST",
1362+ url: "/any/unknown/one",
1363+ expect: errNotFound,
1364+ },
1365+ {
1366+ unauth: true,
1367+ method: "GET",
1368+ url: versionPath + "/phony_token",
1369+ headers: setHeader(authToken, s.token),
1370+ expect: errBadRequest,
1371+ },
1372+ {
1373+ method: "GET",
1374+ url: "/flavors/",
1375+ expect: errNotFound,
1376+ },
1377+ {
1378+ method: "GET",
1379+ url: "/flavors/invalid",
1380+ expect: errNotFound,
1381+ },
1382+ {
1383+ method: "POST",
1384+ url: "/flavors",
1385+ expect: errBadRequest2,
1386+ },
1387+ {
1388+ method: "POST",
1389+ url: "/flavors/invalid",
1390+ expect: errNotFound,
1391+ },
1392+ {
1393+ method: "PUT",
1394+ url: "/flavors",
1395+ expect: errNotFound,
1396+ },
1397+ {
1398+ method: "PUT",
1399+ url: "/flavors/invalid",
1400+ expect: errNotFoundJSON,
1401+ },
1402+ {
1403+ method: "DELETE",
1404+ url: "/flavors",
1405+ expect: errNotFound,
1406+ },
1407+ {
1408+ method: "DELETE",
1409+ url: "/flavors/invalid",
1410+ expect: errForbidden,
1411+ },
1412+ {
1413+ method: "GET",
1414+ url: "/flavors/detail/invalid",
1415+ expect: errNotFound,
1416+ },
1417+ {
1418+ method: "POST",
1419+ url: "/flavors/detail",
1420+ expect: errNotFound,
1421+ },
1422+ {
1423+ method: "POST",
1424+ url: "/flavors/detail/invalid",
1425+ expect: errNotFound,
1426+ },
1427+ {
1428+ method: "PUT",
1429+ url: "/flavors/detail",
1430+ expect: errNotFoundJSON,
1431+ },
1432+ {
1433+ method: "PUT",
1434+ url: "/flavors/detail/invalid",
1435+ expect: errNotFound,
1436+ },
1437+ {
1438+ method: "DELETE",
1439+ url: "/flavors/detail",
1440+ expect: errForbidden,
1441+ },
1442+ {
1443+ method: "DELETE",
1444+ url: "/flavors/detail/invalid",
1445+ expect: errNotFound,
1446+ },
1447+ {
1448+ method: "GET",
1449+ url: "/servers/invalid",
1450+ expect: errNotFoundJSON,
1451+ },
1452+ {
1453+ method: "POST",
1454+ url: "/servers",
1455+ expect: errBadRequest2,
1456+ },
1457+ {
1458+ method: "POST",
1459+ url: "/servers/invalid",
1460+ expect: errNotFound,
1461+ },
1462+ {
1463+ method: "PUT",
1464+ url: "/servers",
1465+ expect: errNotFound,
1466+ },
1467+ {
1468+ method: "PUT",
1469+ url: "/servers/invalid",
1470+ expect: errBadRequest2,
1471+ },
1472+ {
1473+ method: "DELETE",
1474+ url: "/servers",
1475+ expect: errNotFound,
1476+ },
1477+ {
1478+ method: "DELETE",
1479+ url: "/servers/invalid",
1480+ expect: errNotFoundJSON,
1481+ },
1482+ {
1483+ method: "GET",
1484+ url: "/servers/detail/invalid",
1485+ expect: errNotFound,
1486+ },
1487+ {
1488+ method: "POST",
1489+ url: "/servers/detail",
1490+ expect: errNotFound,
1491+ },
1492+ {
1493+ method: "POST",
1494+ url: "/servers/detail/invalid",
1495+ expect: errNotFound,
1496+ },
1497+ {
1498+ method: "PUT",
1499+ url: "/servers/detail",
1500+ expect: errBadRequest2,
1501+ },
1502+ {
1503+ method: "PUT",
1504+ url: "/servers/detail/invalid",
1505+ expect: errNotFound,
1506+ },
1507+ {
1508+ method: "DELETE",
1509+ url: "/servers/detail",
1510+ expect: errNotFoundJSON,
1511+ },
1512+ {
1513+ method: "DELETE",
1514+ url: "/servers/detail/invalid",
1515+ expect: errNotFound,
1516+ },
1517+ {
1518+ method: "GET",
1519+ url: "/os-security-groups/invalid",
1520+ expect: errBadRequestSG,
1521+ },
1522+ {
1523+ method: "GET",
1524+ url: "/os-security-groups/42",
1525+ expect: errNotFoundJSONSG,
1526+ },
1527+ {
1528+ method: "POST",
1529+ url: "/os-security-groups",
1530+ expect: errBadRequest2,
1531+ },
1532+ {
1533+ method: "POST",
1534+ url: "/os-security-groups/invalid",
1535+ expect: errNotFound,
1536+ },
1537+ {
1538+ method: "PUT",
1539+ url: "/os-security-groups",
1540+ expect: errNotFound,
1541+ },
1542+ {
1543+ method: "PUT",
1544+ url: "/os-security-groups/invalid",
1545+ expect: errNotFoundJSON,
1546+ },
1547+ {
1548+ method: "DELETE",
1549+ url: "/os-security-groups",
1550+ expect: errNotFound,
1551+ },
1552+ {
1553+ method: "DELETE",
1554+ url: "/os-security-groups/invalid",
1555+ expect: errBadRequestSG,
1556+ },
1557+ {
1558+ method: "DELETE",
1559+ url: "/os-security-groups/42",
1560+ expect: errNotFoundJSONSG,
1561+ },
1562+ {
1563+ method: "GET",
1564+ url: "/os-security-group-rules",
1565+ expect: errNotFoundJSON,
1566+ },
1567+ {
1568+ method: "GET",
1569+ url: "/os-security-group-rules/invalid",
1570+ expect: errNotFoundJSON,
1571+ },
1572+ {
1573+ method: "GET",
1574+ url: "/os-security-group-rules/42",
1575+ expect: errNotFoundJSON,
1576+ },
1577+ {
1578+ method: "POST",
1579+ url: "/os-security-group-rules",
1580+ expect: errBadRequest2,
1581+ },
1582+ {
1583+ method: "POST",
1584+ url: "/os-security-group-rules/invalid",
1585+ expect: errNotFound,
1586+ },
1587+ {
1588+ method: "PUT",
1589+ url: "/os-security-group-rules",
1590+ expect: errNotFound,
1591+ },
1592+ {
1593+ method: "PUT",
1594+ url: "/os-security-group-rules/invalid",
1595+ expect: errNotFoundJSON,
1596+ },
1597+ {
1598+ method: "DELETE",
1599+ url: "/os-security-group-rules",
1600+ expect: errNotFound,
1601+ },
1602+ {
1603+ method: "DELETE",
1604+ url: "/os-security-group-rules/invalid",
1605+ expect: errBadRequestSG, // sic; should've been rule-specific
1606+ },
1607+ {
1608+ method: "DELETE",
1609+ url: "/os-security-group-rules/42",
1610+ expect: errNotFoundJSONSGR,
1611+ },
1612+ {
1613+ method: "GET",
1614+ url: "/os-floating-ips/42",
1615+ expect: errNotFoundJSON,
1616+ },
1617+ {
1618+ method: "POST",
1619+ url: "/os-floating-ips/invalid",
1620+ expect: errNotFound,
1621+ },
1622+ {
1623+ method: "PUT",
1624+ url: "/os-floating-ips",
1625+ expect: errNotFound,
1626+ },
1627+ {
1628+ method: "PUT",
1629+ url: "/os-floating-ips/invalid",
1630+ expect: errNotFoundJSON,
1631+ },
1632+ {
1633+ method: "DELETE",
1634+ url: "/os-floating-ips",
1635+ expect: errNotFound,
1636+ },
1637+ {
1638+ method: "DELETE",
1639+ url: "/os-floating-ips/invalid",
1640+ expect: errNotFoundJSON,
1641+ },
1642+ }
1643+ return simpleTests
1644 }
1645
1646 func (s *NovaHTTPSuite) TestSimpleRequestTests(c *C) {
1647+ simpleTests := s.simpleTests()
1648 for i, t := range simpleTests {
1649 c.Logf("#%d. %s %s -> %d", i, t.method, t.url, t.expect.code)
1650 if t.headers == nil {
1651 t.headers = make(http.Header)
1652- t.headers.Set(authToken, s.service.token)
1653+ t.headers.Set(authToken, s.token)
1654 }
1655 var (
1656 resp *http.Response
1657@@ -780,13 +789,13 @@
1658 {
1659 Id: 1,
1660 Name: "group 1",
1661- TenantId: s.service.tenantId,
1662+ TenantId: s.service.TenantId,
1663 Rules: []nova.SecurityGroupRule{},
1664 },
1665 {
1666 Id: 2,
1667 Name: "group 2",
1668- TenantId: s.service.tenantId,
1669+ TenantId: s.service.TenantId,
1670 Rules: []nova.SecurityGroupRule{},
1671 },
1672 }
1673@@ -816,7 +825,7 @@
1674 Id: 1,
1675 Name: "group 1",
1676 Description: "desc",
1677- TenantId: s.service.tenantId,
1678+ TenantId: s.service.TenantId,
1679 Rules: []nova.SecurityGroupRule{},
1680 }
1681 _, err := s.service.securityGroup(group.Id)
1682@@ -890,7 +899,7 @@
1683 ParentGroupId: group2.Id,
1684 Group: nova.SecurityGroupRef{
1685 Name: group1.Name,
1686- TenantId: s.service.tenantId,
1687+ TenantId: s.service.TenantId,
1688 },
1689 }
1690 ok := s.service.hasSecurityGroupRule(group1.Id, rule1.Id)
1691@@ -990,13 +999,13 @@
1692 {
1693 Id: 1,
1694 Name: "group1",
1695- TenantId: s.service.tenantId,
1696+ TenantId: s.service.TenantId,
1697 Rules: []nova.SecurityGroupRule{},
1698 },
1699 {
1700 Id: 2,
1701 Name: "group2",
1702- TenantId: s.service.tenantId,
1703+ TenantId: s.service.TenantId,
1704 Rules: []nova.SecurityGroupRule{},
1705 },
1706 }
1707
1708=== modified file 'testservices/novaservice/service_test.go'
1709--- testservices/novaservice/service_test.go 2013-01-21 11:18:33 +0000
1710+++ testservices/novaservice/service_test.go 2013-01-24 03:17:22 +0000
1711@@ -14,15 +14,14 @@
1712
1713 const (
1714 versionPath = "v2"
1715- token = "token"
1716- hostname = "example.com"
1717- tenantId = "tenant_id"
1718+ hostname = "http://example.com"
1719+ region = "region"
1720 )
1721
1722 var _ = Suite(&NovaSuite{})
1723
1724 func (s *NovaSuite) SetUpSuite(c *C) {
1725- s.service = New(hostname, versionPath, token, tenantId)
1726+ s.service = New(hostname, versionPath, "tenant", region, nil)
1727 }
1728
1729 func (s *NovaSuite) ensureNoFlavor(c *C, flavor nova.FlavorDetail) {
1730@@ -118,8 +117,8 @@
1731 fl, _ := s.service.flavor(flavor.Id)
1732 url := "/flavors/" + flavor.Id
1733 links := []nova.Link{
1734- nova.Link{Href: s.service.endpoint(true, url), Rel: "self"},
1735- nova.Link{Href: s.service.endpoint(false, url), Rel: "bookmark"},
1736+ nova.Link{Href: s.service.endpointURL(true, url), Rel: "self"},
1737+ nova.Link{Href: s.service.endpointURL(false, url), Rel: "bookmark"},
1738 }
1739 c.Assert(fl.Links, DeepEquals, links)
1740 }
1741@@ -214,8 +213,8 @@
1742 sr, _ := s.service.server(server.Id)
1743 url := "/servers/" + server.Id
1744 links := []nova.Link{
1745- {Href: s.service.endpoint(true, url), Rel: "self"},
1746- {Href: s.service.endpoint(false, url), Rel: "bookmark"},
1747+ {Href: s.service.endpointURL(true, url), Rel: "self"},
1748+ {Href: s.service.endpointURL(false, url), Rel: "bookmark"},
1749 }
1750 c.Assert(sr.Links, DeepEquals, links)
1751 }
1752@@ -456,7 +455,7 @@
1753 group := nova.SecurityGroup{
1754 Id: 1,
1755 Name: "test",
1756- TenantId: s.service.tenantId,
1757+ TenantId: s.service.TenantId,
1758 Rules: []nova.SecurityGroupRule{
1759 {Id: 10, ParentGroupId: 1},
1760 {Id: 20, ParentGroupId: 1},
1761@@ -492,13 +491,13 @@
1762 {
1763 Id: 1,
1764 Name: "one",
1765- TenantId: s.service.tenantId,
1766+ TenantId: s.service.TenantId,
1767 Rules: []nova.SecurityGroupRule{},
1768 },
1769 {
1770 Id: 2,
1771 Name: "two",
1772- TenantId: s.service.tenantId,
1773+ TenantId: s.service.TenantId,
1774 Rules: []nova.SecurityGroupRule{},
1775 },
1776 }
1777@@ -514,7 +513,7 @@
1778 func (s *NovaSuite) TestGetSecurityGroup(c *C) {
1779 group := nova.SecurityGroup{
1780 Id: 42,
1781- TenantId: s.service.tenantId,
1782+ TenantId: s.service.TenantId,
1783 Name: "group",
1784 Description: "desc",
1785 Rules: []nova.SecurityGroupRule{},
1786@@ -529,7 +528,7 @@
1787 group := nova.SecurityGroup{
1788 Id: 1,
1789 Name: "test",
1790- TenantId: s.service.tenantId,
1791+ TenantId: s.service.TenantId,
1792 Rules: []nova.SecurityGroupRule{},
1793 }
1794 s.ensureNoGroup(c, group)
1795@@ -600,7 +599,7 @@
1796 }
1797
1798 func (s *NovaSuite) TestAddGetGroupSecurityGroupRule(c *C) {
1799- srcGroup := nova.SecurityGroup{Id: 1, Name: "source", TenantId: s.service.tenantId}
1800+ srcGroup := nova.SecurityGroup{Id: 1, Name: "source", TenantId: s.service.TenantId}
1801 tgtGroup := nova.SecurityGroup{Id: 2, Name: "target"}
1802 s.createGroup(c, srcGroup)
1803 defer s.deleteGroup(c, srcGroup)
1804@@ -696,7 +695,7 @@
1805 group := nova.SecurityGroup{
1806 Id: 1,
1807 Name: "test",
1808- TenantId: s.service.tenantId,
1809+ TenantId: s.service.TenantId,
1810 }
1811 s.createGroup(c, group)
1812 defer s.deleteGroup(c, group)
1813@@ -800,13 +799,13 @@
1814 {
1815 Id: 1,
1816 Name: "gr1",
1817- TenantId: s.service.tenantId,
1818+ TenantId: s.service.TenantId,
1819 Rules: []nova.SecurityGroupRule{},
1820 },
1821 {
1822 Id: 2,
1823 Name: "gr2",
1824- TenantId: s.service.tenantId,
1825+ TenantId: s.service.TenantId,
1826 Rules: []nova.SecurityGroupRule{},
1827 },
1828 }
1829
1830=== added directory 'testservices/openstack'
1831=== added file 'testservices/openstack/openstack.go'
1832--- testservices/openstack/openstack.go 1970-01-01 00:00:00 +0000
1833+++ testservices/openstack/openstack.go 2013-01-24 03:17:22 +0000
1834@@ -0,0 +1,38 @@
1835+package openstack
1836+
1837+import (
1838+ "launchpad.net/goose/identity"
1839+ "launchpad.net/goose/testservices/identityservice"
1840+ "launchpad.net/goose/testservices/novaservice"
1841+ "launchpad.net/goose/testservices/swiftservice"
1842+ "net/http"
1843+)
1844+
1845+// Openstack provides an Openstack service double implementation.
1846+type Openstack struct {
1847+ identity identityservice.IdentityService
1848+ nova *novaservice.Nova
1849+ swift *swiftservice.Swift
1850+}
1851+
1852+// New creates an instance of a full Openstack service double.
1853+// An initial user with the specified credentials is registered with the identity service.
1854+func New(cred *identity.Credentials) *Openstack {
1855+ openstack := Openstack{
1856+ identity: identityservice.NewUserPass(),
1857+ }
1858+ userInfo := openstack.identity.AddUser(cred.User, cred.Secrets, cred.TenantName)
1859+ if cred.TenantName == "" {
1860+ panic("Openstack service double requires a tenant to be specified.")
1861+ }
1862+ openstack.nova = novaservice.New(cred.URL, "v2", userInfo.TenantId, cred.Region, openstack.identity)
1863+ openstack.swift = swiftservice.New(cred.URL, "v1", userInfo.TenantId, cred.Region, openstack.identity)
1864+ return &openstack
1865+}
1866+
1867+// setupHTTP attaches all the needed handlers to provide the HTTP API for the Openstack service..
1868+func (openstack *Openstack) SetupHTTP(mux *http.ServeMux) {
1869+ openstack.identity.SetupHTTP(mux)
1870+ openstack.nova.SetupHTTP(mux)
1871+ openstack.swift.SetupHTTP(mux)
1872+}
1873
1874=== added file 'testservices/service.go'
1875--- testservices/service.go 1970-01-01 00:00:00 +0000
1876+++ testservices/service.go 2013-01-24 03:17:22 +0000
1877@@ -0,0 +1,21 @@
1878+package testservices
1879+
1880+import (
1881+ "launchpad.net/goose/testservices/identityservice"
1882+ "net/http"
1883+)
1884+
1885+// An HttpService provides the HTTP API for a service double.
1886+type HttpService interface {
1887+ SetupHTTP(mux *http.ServeMux)
1888+}
1889+
1890+// A ServiceInstance is an Openstack module, one of nova, swift, glance.
1891+type ServiceInstance struct {
1892+ identityservice.ServiceProvider
1893+ IdentityService identityservice.IdentityService
1894+ Hostname string
1895+ VersionPath string
1896+ TenantId string
1897+ Region string
1898+}
1899
1900=== modified file 'testservices/swiftservice/service.go'
1901--- testservices/swiftservice/service.go 2012-12-17 01:30:20 +0000
1902+++ testservices/swiftservice/service.go 2013-01-24 03:17:22 +0000
1903@@ -5,29 +5,67 @@
1904 import (
1905 "fmt"
1906 "launchpad.net/goose/swift"
1907+ "launchpad.net/goose/testservices"
1908+ "launchpad.net/goose/testservices/identityservice"
1909+ "net/url"
1910+ "strings"
1911 "time"
1912 )
1913
1914 type object map[string][]byte
1915
1916+var _ testservices.HttpService = (*Swift)(nil)
1917+var _ identityservice.ServiceProvider = (*Swift)(nil)
1918+
1919 type Swift struct {
1920+ testservices.ServiceInstance
1921 containers map[string]object
1922- hostname string
1923- baseURL string
1924- token string
1925 }
1926
1927 // New creates an instance of the Swift object, given the parameters.
1928-func New(hostname, baseURL, token string) *Swift {
1929+func New(hostURL, versionPath, tenantId, region string, identityService identityservice.IdentityService) *Swift {
1930+ url, err := url.Parse(hostURL)
1931+ if err != nil {
1932+ panic(err)
1933+ }
1934+ hostname := url.Host
1935+ if !strings.HasSuffix(hostname, "/") {
1936+ hostname += "/"
1937+ }
1938 swift := &Swift{
1939 containers: make(map[string]object),
1940- hostname: hostname,
1941- baseURL: baseURL,
1942- token: token,
1943+ ServiceInstance: testservices.ServiceInstance{
1944+ IdentityService: identityService,
1945+ Hostname: hostname,
1946+ VersionPath: versionPath,
1947+ TenantId: tenantId,
1948+ Region: region,
1949+ },
1950+ }
1951+ if identityService != nil {
1952+ identityService.RegisterServiceProvider("swift", "object-store", swift)
1953 }
1954 return swift
1955 }
1956
1957+func (s *Swift) endpointURL(path string) string {
1958+ ep := "http://" + s.Hostname + s.VersionPath + "/" + s.TenantId
1959+ if path != "" {
1960+ ep += "/" + strings.TrimLeft(path, "/")
1961+ }
1962+ return ep
1963+}
1964+
1965+func (s *Swift) Endpoints() []identityservice.Endpoint {
1966+ ep := identityservice.Endpoint{
1967+ AdminURL: s.endpointURL(""),
1968+ InternalURL: s.endpointURL(""),
1969+ PublicURL: s.endpointURL(""),
1970+ Region: s.Region,
1971+ }
1972+ return []identityservice.Endpoint{ep}
1973+}
1974+
1975 // HasContainer verifies the given container exists or not.
1976 func (s *Swift) HasContainer(name string) bool {
1977 _, ok := s.containers[name]
1978@@ -119,5 +157,5 @@
1979 if _, err := s.GetObject(container, object); err != nil {
1980 return "", err
1981 }
1982- return fmt.Sprintf("%s%s%s/%s", s.hostname, s.baseURL, container, object), nil
1983+ return s.endpointURL(fmt.Sprintf("%s/%s", container, object)), nil
1984 }
1985
1986=== modified file 'testservices/swiftservice/service_http.go'
1987--- testservices/swiftservice/service_http.go 2013-01-21 11:18:33 +0000
1988+++ testservices/swiftservice/service_http.go 2013-01-24 03:17:22 +0000
1989@@ -4,6 +4,7 @@
1990
1991 import (
1992 "encoding/json"
1993+ "fmt"
1994 "io/ioutil"
1995 "net/http"
1996 "strings"
1997@@ -143,16 +144,15 @@
1998 // For public containers, the token is not required to access the files. For now, if the request
1999 // does not provide a token, we will let it through and assume a public container is being accessed.
2000 token := r.Header.Get("X-Auth-Token")
2001- if token != "" && token != s.token {
2002+ _, err := s.IdentityService.FindUser(token)
2003+ if token != "" && err != nil {
2004 w.WriteHeader(http.StatusUnauthorized)
2005 return
2006 }
2007- path := r.URL.Path
2008- if path[:len(s.baseURL)] == s.baseURL {
2009- path = path[len(s.baseURL):]
2010- }
2011- path = strings.TrimRight(path, "/")
2012- parts := strings.SplitN(path, "/", 2)
2013+ path := strings.TrimRight(r.URL.Path, "/")
2014+ path = strings.Trim(path, "/")
2015+ parts := strings.SplitN(path, "/", 4)
2016+ parts = parts[2:]
2017 if len(parts) == 1 {
2018 container := parts[0]
2019 s.handleContainers(container, w, r)
2020@@ -164,3 +164,9 @@
2021 panic("not implemented request: " + r.URL.Path)
2022 }
2023 }
2024+
2025+// setupHTTP attaches all the needed handlers to provide the HTTP API.
2026+func (s *Swift) SetupHTTP(mux *http.ServeMux) {
2027+ path := fmt.Sprintf("/%s/%s/", s.VersionPath, s.TenantId)
2028+ mux.Handle(path, s)
2029+}
2030
2031=== modified file 'testservices/swiftservice/service_http_test.go'
2032--- testservices/swiftservice/service_http_test.go 2012-12-20 05:47:11 +0000
2033+++ testservices/swiftservice/service_http_test.go 2013-01-24 03:17:22 +0000
2034@@ -9,24 +9,29 @@
2035 . "launchpad.net/gocheck"
2036 "launchpad.net/goose/swift"
2037 "launchpad.net/goose/testing/httpsuite"
2038+ "launchpad.net/goose/testservices/identityservice"
2039 "net/http"
2040 )
2041
2042 type SwiftHTTPSuite struct {
2043 httpsuite.HTTPSuite
2044- service SwiftService
2045+ service *Swift
2046+ token string
2047 }
2048
2049 var _ = Suite(&SwiftHTTPSuite{})
2050
2051 func (s *SwiftHTTPSuite) SetUpSuite(c *C) {
2052 s.HTTPSuite.SetUpSuite(c)
2053- s.service = New(s.Server.URL, baseURL, token)
2054+ identityDouble := identityservice.NewUserPass()
2055+ s.service = New(s.Server.URL, versionPath, tenantId, region, identityDouble)
2056+ userInfo := identityDouble.AddUser("fred", "secret", "tenant")
2057+ s.token = userInfo.Token
2058 }
2059
2060 func (s *SwiftHTTPSuite) SetUpTest(c *C) {
2061 s.HTTPSuite.SetUpTest(c)
2062- s.Mux.Handle(baseURL, s.service)
2063+ s.service.SetupHTTP(s.Mux)
2064 }
2065
2066 func (s *SwiftHTTPSuite) TearDownTest(c *C) {
2067@@ -41,15 +46,15 @@
2068 expectedStatusCode int) (resp *http.Response) {
2069 var req *http.Request
2070 var err error
2071- url := s.Server.URL + baseURL + path
2072+ url := s.service.endpointURL(path)
2073 if body != nil {
2074 req, err = http.NewRequest(method, url, bytes.NewBuffer(body))
2075 } else {
2076 req, err = http.NewRequest(method, url, nil)
2077 }
2078 c.Assert(err, IsNil)
2079- if token != "" {
2080- req.Header.Add("X-Auth-Token", token)
2081+ if s.token != "" {
2082+ req.Header.Add("X-Auth-Token", s.token)
2083 }
2084 client := &http.Client{}
2085 resp, err = client.Do(req)
2086@@ -250,16 +255,16 @@
2087 }
2088
2089 func (s *SwiftHTTPSuite) TestUnauthorizedFails(c *C) {
2090- oldtoken := token
2091+ oldtoken := s.token
2092 defer func() {
2093- token = oldtoken
2094+ s.token = oldtoken
2095 }()
2096 // TODO(wallyworld) - until ACLs are supported, empty tokens are assumed to be used when
2097 // we need to access a public container.
2098 // token = ""
2099 // s.sendRequest(c, "GET", "test", nil, http.StatusUnauthorized)
2100
2101- token = "invalid"
2102+ s.token = "invalid"
2103 s.sendRequest(c, "PUT", "test", nil, http.StatusUnauthorized)
2104
2105 s.sendRequest(c, "DELETE", "test", nil, http.StatusUnauthorized)
2106
2107=== modified file 'testservices/swiftservice/service_test.go'
2108--- testservices/swiftservice/service_test.go 2012-12-17 01:30:20 +0000
2109+++ testservices/swiftservice/service_test.go 2013-01-24 03:17:22 +0000
2110@@ -3,21 +3,23 @@
2111 package swiftservice
2112
2113 import (
2114+ "fmt"
2115 . "launchpad.net/gocheck"
2116 )
2117
2118 type SwiftServiceSuite struct {
2119- service SwiftService
2120+ service *Swift
2121 }
2122
2123-var baseURL = "/v1/AUTH_tenant/"
2124-var token = "token"
2125-var hostname = "localhost" // not really used here
2126+var region = "region" // not really used here
2127+var hostname = "http://localhost" // not really used here
2128+var versionPath = "v2" // not really used here
2129+var tenantId = "tenant" // not really used here
2130
2131 var _ = Suite(&SwiftServiceSuite{})
2132
2133 func (s *SwiftServiceSuite) SetUpSuite(c *C) {
2134- s.service = New(hostname, baseURL, token)
2135+ s.service = New(hostname, versionPath, tenantId, region, nil)
2136 }
2137
2138 func (s *SwiftServiceSuite) TestAddHasRemoveContainer(c *C) {
2139@@ -76,9 +78,8 @@
2140 err = s.service.AddObject("test", "obj", data)
2141 c.Assert(err, IsNil)
2142 url, err := s.service.GetURL("test", "obj")
2143- path := baseURL + "test/obj"
2144 c.Assert(err, IsNil)
2145- c.Assert(url, Equals, hostname+path)
2146+ c.Assert(url, Equals, fmt.Sprintf("%s/%s/%s/test/obj", hostname, versionPath, tenantId))
2147 err = s.service.RemoveContainer("test")
2148 c.Assert(err, IsNil)
2149 ok = s.service.HasContainer("test")
2150
2151=== removed file 'testservices/swiftservice/swiftservice.go'
2152--- testservices/swiftservice/swiftservice.go 2012-12-17 01:30:20 +0000
2153+++ testservices/swiftservice/swiftservice.go 1970-01-01 00:00:00 +0000
2154@@ -1,40 +0,0 @@
2155-// Swift double testing service - mimics OpenStack Swift object
2156-// storage service for testing goose against close-to-live API.
2157-
2158-package swiftservice
2159-
2160-import (
2161- "launchpad.net/goose/swift"
2162- "net/http"
2163-)
2164-
2165-// SwiftService presents an direct-API to manipulate the internal
2166-// state, as well as an HTTP API double for OpenStack Swift.
2167-type SwiftService interface {
2168- // AddContainer creates a new container with the given name.
2169- AddContainer(name string) error
2170-
2171- // AddObject creates a new named object in an existing container.
2172- AddObject(container, name string, data []byte) error
2173-
2174- // HasContainer verifies the given container exists or not.
2175- HasContainer(name string) bool
2176-
2177- // ListContainer lists the objects in the given container.
2178- ListContainer(name string) ([]swift.ContainerContents, error)
2179-
2180- // GetObject retrieves a given object's data from its container.
2181- GetObject(container, name string) ([]byte, error)
2182-
2183- // RemoveContainer deletes an existing named container.
2184- RemoveContainer(name string) error
2185-
2186- // RemoveObject deletes an existing named object, from its container.
2187- RemoveObject(container, name string) error
2188-
2189- // GetURL returns the named object's full public URL to get its data.
2190- GetURL(container, object string) (string, error)
2191-
2192- // ServeHTTP is the main entry point in the HTTP request processing.
2193- ServeHTTP(w http.ResponseWriter, r *http.Request)
2194-}
2195
2196=== modified file 'tools/secgroup-delete-all/main_test.go'
2197--- tools/secgroup-delete-all/main_test.go 2013-01-22 18:46:31 +0000
2198+++ tools/secgroup-delete-all/main_test.go 2013-01-24 03:17:22 +0000
2199@@ -7,8 +7,7 @@
2200 "launchpad.net/goose/identity"
2201 "launchpad.net/goose/nova"
2202 "launchpad.net/goose/testing/httpsuite"
2203- "launchpad.net/goose/testservices/identityservice"
2204- "launchpad.net/goose/testservices/novaservice"
2205+ "launchpad.net/goose/testservices/openstack"
2206 tool "launchpad.net/goose/tools/secgroup-delete-all"
2207 "testing"
2208 )
2209@@ -26,43 +25,28 @@
2210
2211 type ToolSuite struct {
2212 httpsuite.HTTPSuite
2213+ creds *identity.Credentials
2214 }
2215
2216 var _ = Suite(&ToolSuite{})
2217
2218 // GZ 2013-01-21: Should require EnvSuite for this, but clashes with HTTPSuite
2219-func createNovaClient(auth_url string) *nova.Client {
2220- creds := identity.Credentials{
2221- URL: auth_url,
2222+func createNovaClient(creds *identity.Credentials) *nova.Client {
2223+ osc := client.NewClient(creds, identity.AuthUserPass, nil)
2224+ return nova.New(osc)
2225+}
2226+
2227+func (s *ToolSuite) makeServices(c *C) *nova.Client {
2228+ creds := &identity.Credentials{
2229+ URL: s.Server.URL,
2230 User: username,
2231 Secrets: password,
2232 Region: region,
2233 TenantName: tenant,
2234 }
2235- osc := client.NewClient(&creds, identity.AuthUserPass, nil)
2236- return nova.New(osc)
2237-}
2238-
2239-func (s *ToolSuite) makeServices(c *C) *nova.Client {
2240- ident := identityservice.NewUserPass()
2241- token := ident.AddUser(username, password)
2242- // GZ 2013-01-21: Current novaservice double requires magic url like so
2243- computeurl := s.Server.URL + "/v2.0/" + tenant
2244- ident.AddService(identityservice.Service{
2245- "nova",
2246- "compute",
2247- []identityservice.Endpoint{
2248- {
2249- AdminURL: computeurl,
2250- InternalURL: computeurl,
2251- PublicURL: computeurl,
2252- Region: region,
2253- },
2254- }})
2255- s.Mux.Handle("/tokens", ident)
2256- comp := novaservice.New("unused.invalid", "v2.0", token, tenant)
2257- comp.SetupHTTP(s.Mux)
2258- return createNovaClient(s.Server.URL)
2259+ openstack := openstack.New(creds)
2260+ openstack.SetupHTTP(s.Mux)
2261+ return createNovaClient(creds)
2262 }
2263
2264 func (s *ToolSuite) TestNoGroups(c *C) {

Subscribers

People subscribed via source and target branches