~juju-qa/ubuntu/yakkety/juju/2.0-rc3-again

« back to all changes in this revision

Viewing changes to src/launchpad.net/goose/client/local_test.go

  • Committer: Package Import Robot
  • Author(s): James Page
  • Date: 2013-04-24 22:34:47 UTC
  • Revision ID: package-import@ubuntu.com-20130424223447-f0qdji7ubnyo0s71
Tags: upstream-1.10.0.1
ImportĀ upstreamĀ versionĀ 1.10.0.1

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
package client_test
 
2
 
 
3
import (
 
4
        "encoding/json"
 
5
        "fmt"
 
6
        . "launchpad.net/gocheck"
 
7
        "launchpad.net/goose/client"
 
8
        "launchpad.net/goose/errors"
 
9
        "launchpad.net/goose/identity"
 
10
        "launchpad.net/goose/testing/httpsuite"
 
11
        "launchpad.net/goose/testservices"
 
12
        "launchpad.net/goose/testservices/identityservice"
 
13
        "launchpad.net/goose/testservices/openstackservice"
 
14
        "net/url"
 
15
        "runtime"
 
16
        "sync"
 
17
        "time"
 
18
)
 
19
 
 
20
func registerLocalTests(authModes []identity.AuthMode) {
 
21
        for _, authMode := range authModes {
 
22
                Suite(&localLiveSuite{
 
23
                        LiveTests: LiveTests{
 
24
                                authMode: authMode,
 
25
                        },
 
26
                })
 
27
        }
 
28
}
 
29
 
 
30
// localLiveSuite runs tests from LiveTests using a fake
 
31
// identity server that runs within the test process itself.
 
32
type localLiveSuite struct {
 
33
        LiveTests
 
34
        // The following attributes are for using testing doubles.
 
35
        httpsuite.HTTPSuite
 
36
        service testservices.HttpService
 
37
}
 
38
 
 
39
func (s *localLiveSuite) SetUpSuite(c *C) {
 
40
        c.Logf("Using identity service test double")
 
41
        s.HTTPSuite.SetUpSuite(c)
 
42
        s.cred = &identity.Credentials{
 
43
                URL:        s.Server.URL,
 
44
                User:       "fred",
 
45
                Secrets:    "secret",
 
46
                Region:     "zone1.some region",
 
47
                TenantName: "tenant",
 
48
        }
 
49
        switch s.authMode {
 
50
        default:
 
51
                panic("Invalid authentication method")
 
52
        case identity.AuthUserPass:
 
53
                // The openstack test service sets up userpass authentication.
 
54
                s.service = openstackservice.New(s.cred)
 
55
                // Add an additional endpoint so region filtering can be properly tested.
 
56
                serviceDef := identityservice.Service{"nova", "compute", []identityservice.Endpoint{
 
57
                        identityservice.Endpoint{PublicURL: "http://nova2", Region: "zone2.RegionOne"},
 
58
                }}
 
59
                s.service.(*openstackservice.Openstack).Identity.(*identityservice.UserPass).AddService(serviceDef)
 
60
 
 
61
        case identity.AuthLegacy:
 
62
                legacy := identityservice.NewLegacy()
 
63
                legacy.AddUser(s.cred.User, s.cred.Secrets, s.cred.TenantName)
 
64
                legacy.SetManagementURL("http://management.test.invalid/url")
 
65
                s.service = legacy
 
66
        }
 
67
        s.LiveTests.SetUpSuite(c)
 
68
}
 
69
 
 
70
func (s *localLiveSuite) TearDownSuite(c *C) {
 
71
        s.LiveTests.TearDownSuite(c)
 
72
        s.HTTPSuite.TearDownSuite(c)
 
73
}
 
74
 
 
75
func (s *localLiveSuite) SetUpTest(c *C) {
 
76
        s.HTTPSuite.SetUpTest(c)
 
77
        s.service.SetupHTTP(s.Mux)
 
78
        s.LiveTests.SetUpTest(c)
 
79
}
 
80
 
 
81
func (s *localLiveSuite) TearDownTest(c *C) {
 
82
        s.LiveTests.TearDownTest(c)
 
83
        s.HTTPSuite.TearDownTest(c)
 
84
}
 
85
 
 
86
// Additional tests to be run against the service double only go here.
 
87
 
 
88
func (s *localLiveSuite) TestInvalidRegion(c *C) {
 
89
        if s.authMode == identity.AuthLegacy {
 
90
                c.Skip("legacy authentication doesn't use regions")
 
91
        }
 
92
        creds := &identity.Credentials{
 
93
                User:    "fred",
 
94
                URL:     s.Server.URL,
 
95
                Secrets: "secret",
 
96
                Region:  "invalid",
 
97
        }
 
98
        cl := client.NewClient(creds, s.authMode, nil)
 
99
        err := cl.Authenticate()
 
100
        c.Assert(err.Error(), Matches, "(.|\n)*invalid region(.|\n)*")
 
101
}
 
102
 
 
103
// Test service lookup with inexact region matching.
 
104
func (s *localLiveSuite) TestInexactRegionMatch(c *C) {
 
105
        if s.authMode == identity.AuthLegacy {
 
106
                c.Skip("legacy authentication doesn't use regions")
 
107
        }
 
108
        cl := client.NewClient(s.cred, s.authMode, nil)
 
109
        err := cl.Authenticate()
 
110
        serviceURL, err := cl.MakeServiceURL("compute", []string{})
 
111
        c.Assert(err, IsNil)
 
112
        _, err = url.Parse(serviceURL)
 
113
        c.Assert(err, IsNil)
 
114
        serviceURL, err = cl.MakeServiceURL("object-store", []string{})
 
115
        c.Assert(err, IsNil)
 
116
        _, err = url.Parse(serviceURL)
 
117
        c.Assert(err, IsNil)
 
118
}
 
119
 
 
120
type fakeAuthenticator struct {
 
121
        mu        sync.Mutex
 
122
        nrCallers int
 
123
        // authStart is used as a gate to signal the fake authenticator that it can start.
 
124
        authStart chan struct{}
 
125
}
 
126
 
 
127
func newAuthenticator(bufsize int) *fakeAuthenticator {
 
128
        return &fakeAuthenticator{
 
129
                authStart: make(chan struct{}, bufsize),
 
130
        }
 
131
}
 
132
 
 
133
func (auth *fakeAuthenticator) Auth(creds *identity.Credentials) (*identity.AuthDetails, error) {
 
134
        auth.mu.Lock()
 
135
        auth.nrCallers++
 
136
        auth.mu.Unlock()
 
137
        // Wait till the test says the authenticator can proceed.
 
138
        <-auth.authStart
 
139
        runtime.Gosched()
 
140
        defer func() {
 
141
                auth.mu.Lock()
 
142
                auth.nrCallers--
 
143
                auth.mu.Unlock()
 
144
        }()
 
145
        auth.mu.Lock()
 
146
        tooManyCallers := auth.nrCallers > 1
 
147
        auth.mu.Unlock()
 
148
        if tooManyCallers {
 
149
                return nil, fmt.Errorf("Too many callers of Auth function")
 
150
        }
 
151
        URLs := make(map[string]identity.ServiceURLs)
 
152
        endpoints := make(map[string]string)
 
153
        endpoints["compute"] = "http://localhost"
 
154
        URLs[creds.Region] = endpoints
 
155
        return &identity.AuthDetails{
 
156
                Token:             "token",
 
157
                TenantId:          "tenant",
 
158
                UserId:            "1",
 
159
                RegionServiceURLs: URLs,
 
160
        }, nil
 
161
}
 
162
 
 
163
func (s *localLiveSuite) TestAuthenticationTimeout(c *C) {
 
164
        cl := client.NewClient(s.cred, s.authMode, nil)
 
165
        defer client.SetAuthenticationTimeout(1 * time.Millisecond)()
 
166
        auth := newAuthenticator(0)
 
167
        client.SetAuthenticator(cl, auth)
 
168
 
 
169
        var err error
 
170
        err = cl.Authenticate()
 
171
        // Wake up the authenticator after we have timed out.
 
172
        auth.authStart <- struct{}{}
 
173
        c.Assert(errors.IsTimeout(err), Equals, true)
 
174
}
 
175
 
 
176
func (s *localLiveSuite) assertAuthenticationSuccess(c *C) client.Client {
 
177
        cl := client.NewClient(s.cred, s.authMode, nil)
 
178
        cl.SetRequiredServiceTypes([]string{"compute"})
 
179
        defer client.SetAuthenticationTimeout(1 * time.Millisecond)()
 
180
        auth := newAuthenticator(1)
 
181
        client.SetAuthenticator(cl, auth)
 
182
 
 
183
        // Signal that the authenticator can proceed immediately.
 
184
        auth.authStart <- struct{}{}
 
185
        err := cl.Authenticate()
 
186
        c.Assert(err, IsNil)
 
187
        // It completed with no error but check it also ran correctly.
 
188
        c.Assert(cl.IsAuthenticated(), Equals, true)
 
189
        return cl
 
190
}
 
191
 
 
192
func (s *localLiveSuite) TestAuthenticationSuccess(c *C) {
 
193
        cl := s.assertAuthenticationSuccess(c)
 
194
        URL, err := cl.MakeServiceURL("compute", nil)
 
195
        c.Assert(err, IsNil)
 
196
        c.Assert(URL, Equals, "http://localhost")
 
197
}
 
198
 
 
199
func (s *localLiveSuite) TestMakeServiceURL(c *C) {
 
200
        cl := s.assertAuthenticationSuccess(c)
 
201
        URL, err := cl.MakeServiceURL("compute", []string{"foo"})
 
202
        c.Assert(err, IsNil)
 
203
        c.Assert(URL, Equals, "http://localhost/foo")
 
204
}
 
205
 
 
206
func (s *localLiveSuite) TestMakeServiceURLRetainsTrailingSlash(c *C) {
 
207
        cl := s.assertAuthenticationSuccess(c)
 
208
        URL, err := cl.MakeServiceURL("compute", []string{"foo", "bar/"})
 
209
        c.Assert(err, IsNil)
 
210
        c.Assert(URL, Equals, "http://localhost/foo/bar/")
 
211
}
 
212
 
 
213
func checkAuthentication(cl client.AuthenticatingClient) error {
 
214
        err := cl.Authenticate()
 
215
        if err != nil {
 
216
                return err
 
217
        }
 
218
        URL, err := cl.MakeServiceURL("compute", nil)
 
219
        if err != nil {
 
220
                return err
 
221
        }
 
222
        if URL != "http://localhost" {
 
223
                return fmt.Errorf("Unexpected URL: %s", URL)
 
224
        }
 
225
        return nil
 
226
}
 
227
 
 
228
func (s *localLiveSuite) TestAuthenticationForbidsMultipleCallers(c *C) {
 
229
        if s.authMode == identity.AuthLegacy {
 
230
                c.Skip("legacy authentication")
 
231
        }
 
232
        cl := client.NewClient(s.cred, s.authMode, nil)
 
233
        cl.SetRequiredServiceTypes([]string{"compute"})
 
234
        auth := newAuthenticator(2)
 
235
        client.SetAuthenticator(cl, auth)
 
236
 
 
237
        // Signal that the authenticator can proceed immediately.
 
238
        auth.authStart <- struct{}{}
 
239
        auth.authStart <- struct{}{}
 
240
        var allDone sync.WaitGroup
 
241
        allDone.Add(2)
 
242
        var err1, err2 error
 
243
        go func() {
 
244
                err1 = checkAuthentication(cl)
 
245
                allDone.Done()
 
246
        }()
 
247
        go func() {
 
248
                err2 = checkAuthentication(cl)
 
249
                allDone.Done()
 
250
        }()
 
251
        allDone.Wait()
 
252
        c.Assert(err1, IsNil)
 
253
        c.Assert(err2, IsNil)
 
254
}
 
255
 
 
256
type configurableAuth struct {
 
257
        regionsURLs map[string]identity.ServiceURLs
 
258
}
 
259
 
 
260
func NewConfigurableAuth(regionsURLData string) *configurableAuth {
 
261
        auth := &configurableAuth{}
 
262
        err := json.Unmarshal([]byte(regionsURLData), &auth.regionsURLs)
 
263
        if err != nil {
 
264
                panic(err)
 
265
        }
 
266
        return auth
 
267
}
 
268
 
 
269
func (auth *configurableAuth) Auth(creds *identity.Credentials) (*identity.AuthDetails, error) {
 
270
        return &identity.AuthDetails{
 
271
                Token:             "token",
 
272
                TenantId:          "tenant",
 
273
                UserId:            "1",
 
274
                RegionServiceURLs: auth.regionsURLs,
 
275
        }, nil
 
276
}
 
277
 
 
278
type authRegionTest struct {
 
279
        region        string
 
280
        regionURLInfo string
 
281
        errorMsg      string
 
282
}
 
283
 
 
284
var missingEndpointMsgf = "(.|\n)*the configured region %q does not allow access to all required services, namely: %s(.|\n)*access to these services is missing: %s"
 
285
var missingEndpointSuggestRegionMsgf = "(.|\n)*the configured region %q does not allow access to all required services, namely: %s(.|\n)*access to these services is missing: %s(.|\n)*one of these regions may be suitable instead: %s"
 
286
var invalidRegionMsgf = "(.|\n)*invalid region %q"
 
287
 
 
288
var authRegionTests = []authRegionTest{
 
289
        authRegionTest{
 
290
                "a.region.1",
 
291
                `{"a.region.1":{"compute":"http://foo"}}`,
 
292
                fmt.Sprintf(missingEndpointMsgf, "a.region.1", "compute, object-store", "object-store"),
 
293
        },
 
294
        authRegionTest{
 
295
                "b.region.1",
 
296
                `{"a.region.1":{"compute":"http://foo"}}`,
 
297
                fmt.Sprintf(invalidRegionMsgf, "b.region.1"),
 
298
        },
 
299
        authRegionTest{
 
300
                "b.region.1",
 
301
                `{"a.region.1":{"compute":"http://foo"}, "region.1":{"object-store":"http://foobar"}}`,
 
302
                fmt.Sprintf(missingEndpointSuggestRegionMsgf, "b.region.1", "compute, object-store", "compute", "a.region.1"),
 
303
        },
 
304
        authRegionTest{
 
305
                "region.1",
 
306
                `{"a.region.1":{"compute":"http://foo"}, "region.1":{"object-store":"http://foobar"}}`,
 
307
                fmt.Sprintf(missingEndpointSuggestRegionMsgf, "region.1", "compute, object-store", "compute", "a.region.1"),
 
308
        },
 
309
}
 
310
 
 
311
func (s *localLiveSuite) TestNonAccessibleServiceType(c *C) {
 
312
        if s.authMode == identity.AuthLegacy {
 
313
                c.Skip("legacy authentication")
 
314
        }
 
315
        for _, at := range authRegionTests {
 
316
                s.cred.Region = at.region
 
317
                cl := client.NewClient(s.cred, s.authMode, nil)
 
318
                auth := NewConfigurableAuth(at.regionURLInfo)
 
319
                client.SetAuthenticator(cl, auth)
 
320
                err := cl.Authenticate()
 
321
                c.Assert(err, ErrorMatches, at.errorMsg)
 
322
        }
 
323
}