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

Subscribers

People subscribed via source and target branches