~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/apiserver/backup_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
// Copyright 2014 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package apiserver_test
 
5
 
 
6
import (
 
7
        "bytes"
 
8
        "encoding/base64"
 
9
        "encoding/json"
 
10
        "fmt"
 
11
        "io"
 
12
        "io/ioutil"
 
13
        "mime/multipart"
 
14
        "net/http"
 
15
        "net/textproto"
 
16
 
 
17
        "github.com/juju/errors"
 
18
        jc "github.com/juju/testing/checkers"
 
19
        "github.com/juju/utils"
 
20
        gc "gopkg.in/check.v1"
 
21
        "gopkg.in/macaroon-bakery.v1/httpbakery"
 
22
 
 
23
        "github.com/juju/juju/apiserver"
 
24
        apiserverbackups "github.com/juju/juju/apiserver/backups"
 
25
        "github.com/juju/juju/apiserver/params"
 
26
        "github.com/juju/juju/state"
 
27
        "github.com/juju/juju/state/backups"
 
28
        backupstesting "github.com/juju/juju/state/backups/testing"
 
29
)
 
30
 
 
31
type backupsCommonSuite struct {
 
32
        authHttpSuite
 
33
        fake *backupstesting.FakeBackups
 
34
}
 
35
 
 
36
func (s *backupsCommonSuite) SetUpTest(c *gc.C) {
 
37
        s.authHttpSuite.SetUpTest(c)
 
38
 
 
39
        s.fake = &backupstesting.FakeBackups{}
 
40
        s.PatchValue(apiserver.NewBackups,
 
41
                func(st *state.State) (backups.Backups, io.Closer) {
 
42
                        return s.fake, ioutil.NopCloser(nil)
 
43
                },
 
44
        )
 
45
}
 
46
 
 
47
func (s *backupsCommonSuite) backupURL(c *gc.C) string {
 
48
        environ, err := s.State.Model()
 
49
        c.Assert(err, jc.ErrorIsNil)
 
50
        uri := s.baseURL(c)
 
51
        uri.Path = fmt.Sprintf("/model/%s/backups", environ.UUID())
 
52
        return uri.String()
 
53
}
 
54
 
 
55
func (s *backupsCommonSuite) assertErrorResponse(c *gc.C, resp *http.Response, statusCode int, msg string) *params.Error {
 
56
        body, err := ioutil.ReadAll(resp.Body)
 
57
        c.Assert(err, jc.ErrorIsNil)
 
58
 
 
59
        c.Assert(resp.StatusCode, gc.Equals, statusCode, gc.Commentf("body: %s", body))
 
60
        c.Assert(resp.Header.Get("Content-Type"), gc.Equals, params.ContentTypeJSON, gc.Commentf("body: %q", body))
 
61
 
 
62
        var failure params.Error
 
63
        err = json.Unmarshal(body, &failure)
 
64
        c.Assert(err, jc.ErrorIsNil)
 
65
        c.Assert(&failure, gc.ErrorMatches, msg, gc.Commentf("body: %s", body))
 
66
        return &failure
 
67
}
 
68
 
 
69
type backupsSuite struct {
 
70
        backupsCommonSuite
 
71
}
 
72
 
 
73
var _ = gc.Suite(&backupsSuite{})
 
74
 
 
75
func (s *backupsSuite) TestRequiresAuth(c *gc.C) {
 
76
        resp := s.sendRequest(c, httpRequestParams{method: "GET", url: s.backupURL(c)})
 
77
        s.assertErrorResponse(c, resp, http.StatusUnauthorized, "no credentials provided")
 
78
}
 
79
 
 
80
func (s *backupsSuite) checkInvalidMethod(c *gc.C, method, url string) {
 
81
        resp := s.authRequest(c, httpRequestParams{method: method, url: url})
 
82
        s.assertErrorResponse(c, resp, http.StatusMethodNotAllowed, `unsupported method: "`+method+`"`)
 
83
}
 
84
 
 
85
func (s *backupsSuite) TestInvalidHTTPMethods(c *gc.C) {
 
86
        url := s.backupURL(c)
 
87
        for _, method := range []string{"POST", "DELETE", "OPTIONS"} {
 
88
                c.Log("testing HTTP method: " + method)
 
89
                s.checkInvalidMethod(c, method, url)
 
90
        }
 
91
}
 
92
 
 
93
func (s *backupsSuite) TestAuthRequiresClientNotMachine(c *gc.C) {
 
94
        // Add a machine and try to login.
 
95
        machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
 
96
        c.Assert(err, jc.ErrorIsNil)
 
97
        err = machine.SetProvisioned("foo", "fake_nonce", nil)
 
98
        c.Assert(err, jc.ErrorIsNil)
 
99
        password, err := utils.RandomPassword()
 
100
        c.Assert(err, jc.ErrorIsNil)
 
101
        err = machine.SetPassword(password)
 
102
        c.Assert(err, jc.ErrorIsNil)
 
103
 
 
104
        resp := s.sendRequest(c, httpRequestParams{
 
105
                tag:      machine.Tag().String(),
 
106
                password: password,
 
107
                method:   "GET",
 
108
                url:      s.backupURL(c),
 
109
                nonce:    "fake_nonce",
 
110
        })
 
111
        s.assertErrorResponse(c, resp, http.StatusInternalServerError, "tag kind machine not valid")
 
112
 
 
113
        // Now try a user login.
 
114
        resp = s.authRequest(c, httpRequestParams{method: "POST", url: s.backupURL(c)})
 
115
        s.assertErrorResponse(c, resp, http.StatusMethodNotAllowed, `unsupported method: "POST"`)
 
116
}
 
117
 
 
118
type backupsWithMacaroonsSuite struct {
 
119
        backupsCommonSuite
 
120
}
 
121
 
 
122
var _ = gc.Suite(&backupsWithMacaroonsSuite{})
 
123
 
 
124
func (s *backupsWithMacaroonsSuite) SetUpTest(c *gc.C) {
 
125
        s.macaroonAuthEnabled = true
 
126
        s.backupsCommonSuite.SetUpTest(c)
 
127
}
 
128
 
 
129
func (s *backupsWithMacaroonsSuite) TestWithNoBasicAuthReturnsDischargeRequiredError(c *gc.C) {
 
130
        resp := s.sendRequest(c, httpRequestParams{
 
131
                method:   "GET",
 
132
                jsonBody: &params.BackupsDownloadArgs{"bad-id"},
 
133
                url:      s.backupURL(c),
 
134
        })
 
135
 
 
136
        errResp := s.assertErrorResponse(c, resp, http.StatusUnauthorized, "verification failed: no macaroons")
 
137
        c.Assert(errResp.Code, gc.Equals, params.CodeDischargeRequired)
 
138
        c.Assert(errResp.Info, gc.NotNil)
 
139
        c.Assert(errResp.Info.Macaroon, gc.NotNil)
 
140
}
 
141
 
 
142
func (s *backupsWithMacaroonsSuite) TestCanGetWithDischargedMacaroon(c *gc.C) {
 
143
        checkCount := 0
 
144
        s.DischargerLogin = func() string {
 
145
                checkCount++
 
146
                return s.userTag.Id()
 
147
        }
 
148
        s.fake.Error = errors.New("failed!")
 
149
        resp := s.sendRequest(c, httpRequestParams{
 
150
                do:       s.doer(),
 
151
                method:   "GET",
 
152
                jsonBody: &params.BackupsDownloadArgs{"bad-id"},
 
153
                url:      s.backupURL(c),
 
154
        })
 
155
        s.assertErrorResponse(c, resp, http.StatusInternalServerError, "failed!")
 
156
        c.Assert(checkCount, gc.Equals, 1)
 
157
}
 
158
 
 
159
// doer returns a Do function that can make a bakery request
 
160
// appropriate for a backups endpoint.
 
161
func (s *backupsWithMacaroonsSuite) doer() func(*http.Request) (*http.Response, error) {
 
162
        return bakeryDo(nil, backupsBakeryGetError)
 
163
}
 
164
 
 
165
// backupsBakeryGetError implements a getError function
 
166
// appropriate for passing to httpbakery.Client.DoWithBodyAndCustomError
 
167
// for the backups endpoint.
 
168
func backupsBakeryGetError(resp *http.Response) error {
 
169
        if resp.StatusCode != http.StatusUnauthorized {
 
170
                return nil
 
171
        }
 
172
        data, err := ioutil.ReadAll(resp.Body)
 
173
        if err != nil {
 
174
                return errors.Annotatef(err, "cannot read body")
 
175
        }
 
176
        var errResp params.Error
 
177
        if err := json.Unmarshal(data, &errResp); err != nil {
 
178
                return errors.Annotatef(err, "cannot unmarshal body")
 
179
        }
 
180
        if errResp.Code != params.CodeDischargeRequired {
 
181
                return &errResp
 
182
        }
 
183
        if errResp.Info == nil {
 
184
                return errors.Annotatef(err, "no error info found in discharge-required response error")
 
185
        }
 
186
        // It's a discharge-required error, so make an appropriate httpbakery
 
187
        // error from it.
 
188
        return &httpbakery.Error{
 
189
                Message: errResp.Message,
 
190
                Code:    httpbakery.ErrDischargeRequired,
 
191
                Info: &httpbakery.ErrorInfo{
 
192
                        Macaroon:     errResp.Info.Macaroon,
 
193
                        MacaroonPath: errResp.Info.MacaroonPath,
 
194
                },
 
195
        }
 
196
}
 
197
 
 
198
type backupsDownloadSuite struct {
 
199
        backupsCommonSuite
 
200
}
 
201
 
 
202
var _ = gc.Suite(&backupsDownloadSuite{})
 
203
 
 
204
// sendValid sends a valid GET request to the backups endpoint
 
205
// and returns the response and the expected contents of the
 
206
// archive if the request succeeds.
 
207
func (s *backupsDownloadSuite) sendValidGet(c *gc.C) (resp *http.Response, archiveBytes []byte) {
 
208
        meta := backupstesting.NewMetadata()
 
209
        archive, err := backupstesting.NewArchiveBasic(meta)
 
210
        c.Assert(err, jc.ErrorIsNil)
 
211
        archiveBytes = archive.Bytes()
 
212
        s.fake.Meta = meta
 
213
        s.fake.Archive = ioutil.NopCloser(archive)
 
214
 
 
215
        return s.authRequest(c, httpRequestParams{
 
216
                method:      "GET",
 
217
                url:         s.backupURL(c),
 
218
                contentType: params.ContentTypeJSON,
 
219
                jsonBody: params.BackupsDownloadArgs{
 
220
                        ID: meta.ID(),
 
221
                },
 
222
        }), archiveBytes
 
223
}
 
224
 
 
225
func (s *backupsDownloadSuite) TestCalls(c *gc.C) {
 
226
        resp, _ := s.sendValidGet(c)
 
227
        defer resp.Body.Close()
 
228
 
 
229
        c.Check(s.fake.Calls, gc.DeepEquals, []string{"Get"})
 
230
        c.Check(s.fake.IDArg, gc.Equals, s.fake.Meta.ID())
 
231
}
 
232
 
 
233
func (s *backupsDownloadSuite) TestResponse(c *gc.C) {
 
234
        resp, _ := s.sendValidGet(c)
 
235
        defer resp.Body.Close()
 
236
        meta := s.fake.Meta
 
237
 
 
238
        c.Check(resp.StatusCode, gc.Equals, http.StatusOK)
 
239
        expectedChecksum := base64.StdEncoding.EncodeToString([]byte(meta.Checksum()))
 
240
        c.Check(resp.Header.Get("Digest"), gc.Equals, string(params.DigestSHA256)+"="+expectedChecksum)
 
241
        c.Check(resp.Header.Get("Content-Type"), gc.Equals, params.ContentTypeRaw)
 
242
}
 
243
 
 
244
func (s *backupsDownloadSuite) TestBody(c *gc.C) {
 
245
        resp, archiveBytes := s.sendValidGet(c)
 
246
        defer resp.Body.Close()
 
247
 
 
248
        body, err := ioutil.ReadAll(resp.Body)
 
249
        c.Assert(err, jc.ErrorIsNil)
 
250
        c.Check(body, jc.DeepEquals, archiveBytes)
 
251
}
 
252
 
 
253
func (s *backupsDownloadSuite) TestErrorWhenGetFails(c *gc.C) {
 
254
        s.fake.Error = errors.New("failed!")
 
255
        resp, _ := s.sendValidGet(c)
 
256
        defer resp.Body.Close()
 
257
 
 
258
        s.assertErrorResponse(c, resp, http.StatusInternalServerError, "failed!")
 
259
}
 
260
 
 
261
type backupsUploadSuite struct {
 
262
        backupsCommonSuite
 
263
        meta *backups.Metadata
 
264
}
 
265
 
 
266
var _ = gc.Suite(&backupsUploadSuite{})
 
267
 
 
268
func (s *backupsUploadSuite) sendValid(c *gc.C, id string) *http.Response {
 
269
        s.fake.Meta = backups.NewMetadata()
 
270
        s.fake.Meta.SetID("<a new backup ID>")
 
271
 
 
272
        var parts bytes.Buffer
 
273
        writer := multipart.NewWriter(&parts)
 
274
 
 
275
        // Set the metadata part.
 
276
        s.meta = backups.NewMetadata()
 
277
        metaResult := apiserverbackups.ResultFromMetadata(s.meta)
 
278
        header := make(textproto.MIMEHeader)
 
279
        header.Set("Content-Disposition", `form-data; name="metadata"`)
 
280
        header.Set("Content-Type", params.ContentTypeJSON)
 
281
        part, err := writer.CreatePart(header)
 
282
        c.Assert(err, jc.ErrorIsNil)
 
283
        err = json.NewEncoder(part).Encode(metaResult)
 
284
        c.Assert(err, jc.ErrorIsNil)
 
285
 
 
286
        // Set the attached part.
 
287
        archive := bytes.NewBufferString("<compressed data>")
 
288
        part, err = writer.CreateFormFile("attached", "juju-backup.tar.gz")
 
289
        c.Assert(err, jc.ErrorIsNil)
 
290
        _, err = io.Copy(part, archive)
 
291
        c.Assert(err, jc.ErrorIsNil)
 
292
 
 
293
        // Send the request.
 
294
        ctype := writer.FormDataContentType()
 
295
        return s.authRequest(c, httpRequestParams{method: "PUT", url: s.backupURL(c), contentType: ctype, body: &parts})
 
296
}
 
297
 
 
298
func (s *backupsUploadSuite) TestCalls(c *gc.C) {
 
299
        resp := s.sendValid(c, "<a new backup ID>")
 
300
        defer resp.Body.Close()
 
301
 
 
302
        c.Check(s.fake.Calls, gc.DeepEquals, []string{"Add"})
 
303
        c.Check(s.fake.ArchiveArg, gc.NotNil)
 
304
        c.Check(s.fake.MetaArg, jc.DeepEquals, s.meta)
 
305
}
 
306
 
 
307
func (s *backupsUploadSuite) TestResponse(c *gc.C) {
 
308
        resp := s.sendValid(c, "<a new backup ID>")
 
309
        defer resp.Body.Close()
 
310
 
 
311
        c.Check(resp.StatusCode, gc.Equals, http.StatusOK)
 
312
        c.Check(resp.Header.Get("Content-Type"), gc.Equals, params.ContentTypeJSON)
 
313
}
 
314
 
 
315
func (s *backupsUploadSuite) TestBody(c *gc.C) {
 
316
        resp := s.sendValid(c, "<a new backup ID>")
 
317
        defer resp.Body.Close()
 
318
        body, err := ioutil.ReadAll(resp.Body)
 
319
        c.Assert(err, jc.ErrorIsNil)
 
320
        var result params.BackupsUploadResult
 
321
        err = json.Unmarshal(body, &result)
 
322
        c.Assert(err, jc.ErrorIsNil)
 
323
 
 
324
        c.Check(result.ID, gc.Equals, "<a new backup ID>")
 
325
}
 
326
 
 
327
func (s *backupsUploadSuite) TestErrorWhenGetFails(c *gc.C) {
 
328
        s.fake.Error = errors.New("failed!")
 
329
        resp := s.sendValid(c, "<a new backup ID>")
 
330
        defer resp.Body.Close()
 
331
 
 
332
        s.assertErrorResponse(c, resp, http.StatusInternalServerError, "failed!")
 
333
}