~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/gopkg.in/goose.v1/client/local_test.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

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
        "net/url"
 
7
        "runtime"
 
8
        "sync"
 
9
        "time"
 
10
 
 
11
        gc "gopkg.in/check.v1"
 
12
 
 
13
        "gopkg.in/goose.v1/client"
 
14
        "gopkg.in/goose.v1/errors"
 
15
        "gopkg.in/goose.v1/identity"
 
16
        "gopkg.in/goose.v1/swift"
 
17
        "gopkg.in/goose.v1/testing/httpsuite"
 
18
        "gopkg.in/goose.v1/testservices"
 
19
        "gopkg.in/goose.v1/testservices/identityservice"
 
20
        "gopkg.in/goose.v1/testservices/openstackservice"
 
21
)
 
22
 
 
23
func registerLocalTests(authModes []identity.AuthMode) {
 
24
        for _, authMode := range authModes {
 
25
                gc.Suite(&localLiveSuite{
 
26
                        LiveTests: LiveTests{
 
27
                                authMode: authMode,
 
28
                        },
 
29
                })
 
30
        }
 
31
        gc.Suite(&localHTTPSSuite{HTTPSuite: httpsuite.HTTPSuite{UseTLS: true}})
 
32
}
 
33
 
 
34
// localLiveSuite runs tests from LiveTests using a fake
 
35
// identity server that runs within the test process itself.
 
36
type localLiveSuite struct {
 
37
        LiveTests
 
38
        // The following attributes are for using testing doubles.
 
39
        httpsuite.HTTPSuite
 
40
        service testservices.HttpService
 
41
}
 
42
 
 
43
func (s *localLiveSuite) SetUpSuite(c *gc.C) {
 
44
        c.Logf("Using identity service test double")
 
45
        s.HTTPSuite.SetUpSuite(c)
 
46
        s.cred = &identity.Credentials{
 
47
                URL:        s.Server.URL,
 
48
                User:       "fred",
 
49
                Secrets:    "secret",
 
50
                Region:     "zone1.some region",
 
51
                TenantName: "tenant",
 
52
        }
 
53
        switch s.authMode {
 
54
        default:
 
55
                panic("Invalid authentication method")
 
56
        case identity.AuthKeyPair:
 
57
                // The openstack test service sets up keypair authentication.
 
58
                s.service = openstackservice.New(s.cred, identity.AuthKeyPair)
 
59
                // Add an additional endpoint so region filtering can be properly tested.
 
60
                serviceDef := identityservice.Service{
 
61
                        V2: identityservice.V2Service{
 
62
                                Name: "nova",
 
63
                                Type: "compute",
 
64
                                Endpoints: []identityservice.Endpoint{
 
65
                                        {PublicURL: "http://nova2", Region: "zone2.RegionOne"},
 
66
                                },
 
67
                        }}
 
68
                s.service.(*openstackservice.Openstack).Identity.AddService(serviceDef)
 
69
        case identity.AuthUserPass:
 
70
                // The openstack test service sets up userpass authentication.
 
71
                s.service = openstackservice.New(s.cred, identity.AuthUserPass)
 
72
                // Add an additional endpoint so region filtering can be properly tested.
 
73
                serviceDef := identityservice.Service{
 
74
                        V2: identityservice.V2Service{
 
75
                                Name: "nova",
 
76
                                Type: "compute",
 
77
                                Endpoints: []identityservice.Endpoint{
 
78
                                        {PublicURL: "http://nova2", Region: "zone2.RegionOne"},
 
79
                                },
 
80
                        }}
 
81
                s.service.(*openstackservice.Openstack).Identity.AddService(serviceDef)
 
82
        case identity.AuthUserPassV3:
 
83
                // The openstack test service sets up userpass authentication.
 
84
                s.service = openstackservice.New(s.cred, identity.AuthUserPass)
 
85
                // Add an additional endpoint so region filtering can be properly tested.
 
86
                serviceDef := identityservice.Service{
 
87
                        V3: identityservice.V3Service{
 
88
                                Name:      "nova",
 
89
                                Type:      "compute",
 
90
                                Endpoints: identityservice.NewV3Endpoints("", "", "http://nova2", "zone2.RegionOne"),
 
91
                        }}
 
92
                s.service.(*openstackservice.Openstack).Identity.AddService(serviceDef)
 
93
 
 
94
        case identity.AuthLegacy:
 
95
                legacy := identityservice.NewLegacy()
 
96
                legacy.AddUser(s.cred.User, s.cred.Secrets, s.cred.TenantName)
 
97
                legacy.SetManagementURL("http://management.test.invalid/url")
 
98
                s.service = legacy
 
99
        }
 
100
        s.LiveTests.SetUpSuite(c)
 
101
}
 
102
 
 
103
func (s *localLiveSuite) TearDownSuite(c *gc.C) {
 
104
        s.LiveTests.TearDownSuite(c)
 
105
        s.HTTPSuite.TearDownSuite(c)
 
106
}
 
107
 
 
108
func (s *localLiveSuite) SetUpTest(c *gc.C) {
 
109
        s.HTTPSuite.SetUpTest(c)
 
110
        s.service.SetupHTTP(s.Mux)
 
111
        s.LiveTests.SetUpTest(c)
 
112
}
 
113
 
 
114
func (s *localLiveSuite) TearDownTest(c *gc.C) {
 
115
        s.LiveTests.TearDownTest(c)
 
116
        s.HTTPSuite.TearDownTest(c)
 
117
}
 
118
 
 
119
// Additional tests to be run against the service double only go here.
 
120
 
 
121
func (s *localLiveSuite) TestInvalidRegion(c *gc.C) {
 
122
        if s.authMode == identity.AuthLegacy {
 
123
                c.Skip("legacy authentication doesn't use regions")
 
124
        }
 
125
        creds := &identity.Credentials{
 
126
                User:    "fred",
 
127
                URL:     s.Server.URL,
 
128
                Secrets: "secret",
 
129
                Region:  "invalid",
 
130
        }
 
131
        cl := client.NewClient(creds, s.authMode, nil)
 
132
        err := cl.Authenticate()
 
133
        c.Assert(err.Error(), gc.Matches, "(.|\n)*invalid region(.|\n)*")
 
134
}
 
135
 
 
136
// Test service lookup with inexact region matching.
 
137
func (s *localLiveSuite) TestInexactRegionMatch(c *gc.C) {
 
138
        if s.authMode == identity.AuthLegacy {
 
139
                c.Skip("legacy authentication doesn't use regions")
 
140
        }
 
141
        cl := client.NewClient(s.cred, s.authMode, nil)
 
142
        err := cl.Authenticate()
 
143
        serviceURL, err := cl.MakeServiceURL("compute", []string{})
 
144
        c.Assert(err, gc.IsNil)
 
145
        _, err = url.Parse(serviceURL)
 
146
        c.Assert(err, gc.IsNil)
 
147
        serviceURL, err = cl.MakeServiceURL("object-store", []string{})
 
148
        c.Assert(err, gc.IsNil)
 
149
        _, err = url.Parse(serviceURL)
 
150
        c.Assert(err, gc.IsNil)
 
151
}
 
152
 
 
153
type fakeAuthenticator struct {
 
154
        mu        sync.Mutex
 
155
        nrCallers int
 
156
        // authStart is used as a gate to signal the fake authenticator that it can start.
 
157
        authStart chan struct{}
 
158
}
 
159
 
 
160
func newAuthenticator(bufsize int) *fakeAuthenticator {
 
161
        return &fakeAuthenticator{
 
162
                authStart: make(chan struct{}, bufsize),
 
163
        }
 
164
}
 
165
 
 
166
func (auth *fakeAuthenticator) Auth(creds *identity.Credentials) (*identity.AuthDetails, error) {
 
167
        auth.mu.Lock()
 
168
        auth.nrCallers++
 
169
        auth.mu.Unlock()
 
170
        // Wait till the test says the authenticator can proceed.
 
171
        <-auth.authStart
 
172
        runtime.Gosched()
 
173
        defer func() {
 
174
                auth.mu.Lock()
 
175
                auth.nrCallers--
 
176
                auth.mu.Unlock()
 
177
        }()
 
178
        auth.mu.Lock()
 
179
        tooManyCallers := auth.nrCallers > 1
 
180
        auth.mu.Unlock()
 
181
        if tooManyCallers {
 
182
                return nil, fmt.Errorf("Too many callers of Auth function")
 
183
        }
 
184
        URLs := make(map[string]identity.ServiceURLs)
 
185
        endpoints := make(map[string]string)
 
186
        endpoints["compute"] = "http://localhost"
 
187
        URLs[creds.Region] = endpoints
 
188
        return &identity.AuthDetails{
 
189
                Token:             "token",
 
190
                TenantId:          "tenant",
 
191
                UserId:            "1",
 
192
                RegionServiceURLs: URLs,
 
193
        }, nil
 
194
}
 
195
 
 
196
func (s *localLiveSuite) TestAuthenticationTimeout(c *gc.C) {
 
197
        cl := client.NewClient(s.cred, s.authMode, nil)
 
198
        defer client.SetAuthenticationTimeout(1 * time.Millisecond)()
 
199
        auth := newAuthenticator(0)
 
200
        client.SetAuthenticator(cl, auth)
 
201
 
 
202
        var err error
 
203
        err = cl.Authenticate()
 
204
        // Wake up the authenticator after we have timed out.
 
205
        auth.authStart <- struct{}{}
 
206
        c.Assert(errors.IsTimeout(err), gc.Equals, true)
 
207
}
 
208
 
 
209
func (s *localLiveSuite) assertAuthenticationSuccess(c *gc.C) client.Client {
 
210
        cl := client.NewClient(s.cred, s.authMode, nil)
 
211
        cl.SetRequiredServiceTypes([]string{"compute"})
 
212
        defer client.SetAuthenticationTimeout(1 * time.Millisecond)()
 
213
        auth := newAuthenticator(1)
 
214
        client.SetAuthenticator(cl, auth)
 
215
 
 
216
        // Signal that the authenticator can proceed immediately.
 
217
        auth.authStart <- struct{}{}
 
218
        err := cl.Authenticate()
 
219
        c.Assert(err, gc.IsNil)
 
220
        // It completed with no error but check it also ran correctly.
 
221
        c.Assert(cl.IsAuthenticated(), gc.Equals, true)
 
222
        return cl
 
223
}
 
224
 
 
225
func (s *localLiveSuite) TestAuthenticationSuccess(c *gc.C) {
 
226
        cl := s.assertAuthenticationSuccess(c)
 
227
        URL, err := cl.MakeServiceURL("compute", nil)
 
228
        c.Assert(err, gc.IsNil)
 
229
        c.Assert(URL, gc.Equals, "http://localhost")
 
230
}
 
231
 
 
232
func (s *localLiveSuite) TestMakeServiceURL(c *gc.C) {
 
233
        cl := s.assertAuthenticationSuccess(c)
 
234
        URL, err := cl.MakeServiceURL("compute", []string{"foo"})
 
235
        c.Assert(err, gc.IsNil)
 
236
        c.Assert(URL, gc.Equals, "http://localhost/foo")
 
237
}
 
238
 
 
239
func (s *localLiveSuite) TestMakeServiceURLRetainsTrailingSlash(c *gc.C) {
 
240
        cl := s.assertAuthenticationSuccess(c)
 
241
        URL, err := cl.MakeServiceURL("compute", []string{"foo", "bar/"})
 
242
        c.Assert(err, gc.IsNil)
 
243
        c.Assert(URL, gc.Equals, "http://localhost/foo/bar/")
 
244
}
 
245
 
 
246
func checkAuthentication(cl client.AuthenticatingClient) error {
 
247
        err := cl.Authenticate()
 
248
        if err != nil {
 
249
                return err
 
250
        }
 
251
        URL, err := cl.MakeServiceURL("compute", nil)
 
252
        if err != nil {
 
253
                return err
 
254
        }
 
255
        if URL != "http://localhost" {
 
256
                return fmt.Errorf("Unexpected URL: %s", URL)
 
257
        }
 
258
        return nil
 
259
}
 
260
 
 
261
func (s *localLiveSuite) TestAuthenticationForbidsMultipleCallers(c *gc.C) {
 
262
        if s.authMode == identity.AuthLegacy {
 
263
                c.Skip("legacy authentication")
 
264
        }
 
265
        cl := client.NewClient(s.cred, s.authMode, nil)
 
266
        cl.SetRequiredServiceTypes([]string{"compute"})
 
267
        auth := newAuthenticator(2)
 
268
        client.SetAuthenticator(cl, auth)
 
269
 
 
270
        // Signal that the authenticator can proceed immediately.
 
271
        auth.authStart <- struct{}{}
 
272
        auth.authStart <- struct{}{}
 
273
        var allDone sync.WaitGroup
 
274
        allDone.Add(2)
 
275
        var err1, err2 error
 
276
        go func() {
 
277
                err1 = checkAuthentication(cl)
 
278
                allDone.Done()
 
279
        }()
 
280
        go func() {
 
281
                err2 = checkAuthentication(cl)
 
282
                allDone.Done()
 
283
        }()
 
284
        allDone.Wait()
 
285
        c.Assert(err1, gc.IsNil)
 
286
        c.Assert(err2, gc.IsNil)
 
287
}
 
288
 
 
289
type configurableAuth struct {
 
290
        regionsURLs map[string]identity.ServiceURLs
 
291
}
 
292
 
 
293
func NewConfigurableAuth(regionsURLData string) *configurableAuth {
 
294
        auth := &configurableAuth{}
 
295
        err := json.Unmarshal([]byte(regionsURLData), &auth.regionsURLs)
 
296
        if err != nil {
 
297
                panic(err)
 
298
        }
 
299
        return auth
 
300
}
 
301
 
 
302
func (auth *configurableAuth) Auth(creds *identity.Credentials) (*identity.AuthDetails, error) {
 
303
        return &identity.AuthDetails{
 
304
                Token:             "token",
 
305
                TenantId:          "tenant",
 
306
                UserId:            "1",
 
307
                RegionServiceURLs: auth.regionsURLs,
 
308
        }, nil
 
309
}
 
310
 
 
311
type authRegionTest struct {
 
312
        region        string
 
313
        regionURLInfo string
 
314
        errorMsg      string
 
315
}
 
316
 
 
317
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"
 
318
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"
 
319
var invalidRegionMsgf = "(.|\n)*invalid region %q"
 
320
 
 
321
var authRegionTests = []authRegionTest{
 
322
        {
 
323
                "a.region.1",
 
324
                `{"a.region.1":{"compute":"http://foo"}}`,
 
325
                fmt.Sprintf(missingEndpointMsgf, "a.region.1", "compute, object-store", "object-store"),
 
326
        },
 
327
        {
 
328
                "b.region.1",
 
329
                `{"a.region.1":{"compute":"http://foo"}}`,
 
330
                fmt.Sprintf(invalidRegionMsgf, "b.region.1"),
 
331
        },
 
332
        {
 
333
                "b.region.1",
 
334
                `{"a.region.1":{"compute":"http://foo"}, "region.1":{"object-store":"http://foobar"}}`,
 
335
                fmt.Sprintf(missingEndpointSuggestRegionMsgf, "b.region.1", "compute, object-store", "compute", "a.region.1"),
 
336
        },
 
337
        {
 
338
                "region.1",
 
339
                `{"a.region.1":{"compute":"http://foo"}, "region.1":{"object-store":"http://foobar"}}`,
 
340
                fmt.Sprintf(missingEndpointSuggestRegionMsgf, "region.1", "compute, object-store", "compute", "a.region.1"),
 
341
        },
 
342
}
 
343
 
 
344
func (s *localLiveSuite) TestNonAccessibleServiceType(c *gc.C) {
 
345
        if s.authMode == identity.AuthLegacy {
 
346
                c.Skip("legacy authentication")
 
347
        }
 
348
        for _, at := range authRegionTests {
 
349
                s.cred.Region = at.region
 
350
                cl := client.NewClient(s.cred, s.authMode, nil)
 
351
                auth := NewConfigurableAuth(at.regionURLInfo)
 
352
                client.SetAuthenticator(cl, auth)
 
353
                err := cl.Authenticate()
 
354
                c.Assert(err, gc.ErrorMatches, at.errorMsg)
 
355
        }
 
356
}
 
357
 
 
358
type localHTTPSSuite struct {
 
359
        // The following attributes are for using testing doubles.
 
360
        httpsuite.HTTPSuite
 
361
        service testservices.HttpService
 
362
        cred    *identity.Credentials
 
363
}
 
364
 
 
365
func (s *localHTTPSSuite) SetUpSuite(c *gc.C) {
 
366
        c.Logf("Using identity service test double")
 
367
        s.HTTPSuite.SetUpSuite(c)
 
368
        c.Assert(s.Server.URL[:8], gc.Equals, "https://")
 
369
        s.cred = &identity.Credentials{
 
370
                URL:        s.Server.URL,
 
371
                User:       "fred",
 
372
                Secrets:    "secret",
 
373
                Region:     "zone1.some region",
 
374
                TenantName: "tenant",
 
375
        }
 
376
        // The openstack test service sets up userpass authentication.
 
377
        s.service = openstackservice.New(s.cred, identity.AuthUserPass)
 
378
        // Add an additional endpoint so region filtering can be properly tested.
 
379
        serviceDef := identityservice.Service{
 
380
                V2: identityservice.V2Service{
 
381
                        Name: "nova",
 
382
                        Type: "compute",
 
383
                        Endpoints: []identityservice.Endpoint{
 
384
                                {PublicURL: "https://nova2", Region: "zone2.RegionOne"},
 
385
                        },
 
386
                }}
 
387
        s.service.(*openstackservice.Openstack).Identity.AddService(serviceDef)
 
388
}
 
389
 
 
390
func (s *localHTTPSSuite) TearDownSuite(c *gc.C) {
 
391
        s.HTTPSuite.TearDownSuite(c)
 
392
}
 
393
 
 
394
func (s *localHTTPSSuite) SetUpTest(c *gc.C) {
 
395
        s.HTTPSuite.SetUpTest(c)
 
396
        s.service.SetupHTTP(s.Mux)
 
397
}
 
398
 
 
399
func (s *localHTTPSSuite) TearDownTest(c *gc.C) {
 
400
        s.HTTPSuite.TearDownTest(c)
 
401
}
 
402
 
 
403
func (s *localHTTPSSuite) TestDefaultClientRefusesSelfSigned(c *gc.C) {
 
404
        cl := client.NewClient(s.cred, identity.AuthUserPass, nil)
 
405
        err := cl.Authenticate()
 
406
        c.Assert(err, gc.ErrorMatches, "(.|\n)*x509: certificate signed by unknown authority")
 
407
}
 
408
 
 
409
func (s *localHTTPSSuite) TestNonValidatingClientAcceptsSelfSigned(c *gc.C) {
 
410
        cl := client.NewNonValidatingClient(s.cred, identity.AuthUserPass, nil)
 
411
        err := cl.Authenticate()
 
412
        c.Assert(err, gc.IsNil)
 
413
 
 
414
        // Requests into this client should be https:// URLs
 
415
        swiftURL, err := cl.MakeServiceURL("object-store", []string{"test_container"})
 
416
        c.Assert(err, gc.IsNil)
 
417
        c.Assert(swiftURL[:8], gc.Equals, "https://")
 
418
        // We use swiftClient.CreateContainer to test a Binary request
 
419
        swiftClient := swift.New(cl)
 
420
        c.Assert(swiftClient.CreateContainer("test_container", swift.Private), gc.IsNil)
 
421
 
 
422
        // And we use List to test the JsonRequest
 
423
        contents, err := swiftClient.List("test_container", "", "", "", 0)
 
424
        c.Assert(err, gc.IsNil)
 
425
        c.Check(contents, gc.DeepEquals, []swift.ContainerContents{})
 
426
}
 
427
 
 
428
func (s *localHTTPSSuite) setupPublicContainer(c *gc.C) string {
 
429
        // First set up a container that can be read publically
 
430
        authClient := client.NewNonValidatingClient(s.cred, identity.AuthUserPass, nil)
 
431
        authSwift := swift.New(authClient)
 
432
        err := authSwift.CreateContainer("test_container", swift.PublicRead)
 
433
        c.Assert(err, gc.IsNil)
 
434
 
 
435
        baseURL, err := authClient.MakeServiceURL("object-store", nil)
 
436
        c.Assert(err, gc.IsNil)
 
437
        c.Assert(baseURL[:8], gc.Equals, "https://")
 
438
        return baseURL
 
439
}
 
440
 
 
441
func (s *localHTTPSSuite) TestDefaultPublicClientRefusesSelfSigned(c *gc.C) {
 
442
        baseURL := s.setupPublicContainer(c)
 
443
        swiftClient := swift.New(client.NewPublicClient(baseURL, nil))
 
444
        contents, err := swiftClient.List("test_container", "", "", "", 0)
 
445
        c.Assert(err, gc.ErrorMatches, "(.|\n)*x509: certificate signed by unknown authority")
 
446
        c.Assert(contents, gc.DeepEquals, []swift.ContainerContents(nil))
 
447
}
 
448
 
 
449
func (s *localHTTPSSuite) TestNonValidatingPublicClientAcceptsSelfSigned(c *gc.C) {
 
450
        baseURL := s.setupPublicContainer(c)
 
451
        swiftClient := swift.New(client.NewNonValidatingPublicClient(baseURL, nil))
 
452
        contents, err := swiftClient.List("test_container", "", "", "", 0)
 
453
        c.Assert(err, gc.IsNil)
 
454
        c.Assert(contents, gc.DeepEquals, []swift.ContainerContents{})
 
455
}
 
456
 
 
457
func (s *localHTTPSSuite) TestAuthDiscover(c *gc.C) {
 
458
        cl := client.NewNonValidatingClient(s.cred, identity.AuthUserPass, nil)
 
459
        options, err := cl.IdentityAuthOptions()
 
460
        c.Assert(err, gc.IsNil)
 
461
        c.Assert(options, gc.DeepEquals, identity.AuthOptions{identity.AuthOption{Mode: 3, Endpoint: s.cred.URL + "/v3/"}, identity.AuthOption{Mode: 1, Endpoint: s.cred.URL + "/v2.0/"}})
 
462
}