-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbackup.go
186 lines (157 loc) · 4.96 KB
/
backup.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
// Copyright 2014 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package apiserver
import (
"encoding/json"
"io"
"io/ioutil"
"net/http"
"github.com/juju/errors"
"github.com/juju/juju/apiserver/common"
apiserverbackups "github.com/juju/juju/apiserver/facades/client/backups"
"github.com/juju/juju/apiserver/httpattachment"
"github.com/juju/juju/apiserver/params"
"github.com/juju/juju/state"
"github.com/juju/juju/state/backups"
)
var newBackups = func(st *state.State, m *state.Model) (backups.Backups, io.Closer) {
backend := struct {
*state.State
*state.Model
}{st, m}
stor := backups.NewStorage(backend)
return backups.NewBackups(stor), stor
}
// backupHandler handles backup requests.
type backupHandler struct {
ctxt httpContext
}
func (h *backupHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
// Validate before authenticate because the authentication is dependent
// on the state connection that is determined during the validation.
st, err := h.ctxt.stateForRequestAuthenticatedUser(req)
if err != nil {
h.sendError(resp, err)
return
}
defer st.Release()
if !st.IsController() {
h.sendError(resp, errors.New("requested model is not the controller model"))
return
}
m, err := st.Model()
if err != nil {
h.sendError(resp, err)
return
}
backups, closer := newBackups(st.State, m)
defer closer.Close()
switch req.Method {
case "GET":
logger.Infof("handling backups download request")
id, err := h.download(backups, resp, req)
if err != nil {
h.sendError(resp, err)
return
}
logger.Infof("backups download request successful for %q", id)
case "PUT":
logger.Infof("handling backups upload request")
id, err := h.upload(backups, resp, req)
if err != nil {
h.sendError(resp, err)
return
}
logger.Infof("backups upload request successful for %q", id)
default:
h.sendError(resp, errors.MethodNotAllowedf("unsupported method: %q", req.Method))
}
}
func (h *backupHandler) download(backups backups.Backups, resp http.ResponseWriter, req *http.Request) (string, error) {
args, err := h.parseGETArgs(req)
if err != nil {
return "", err
}
logger.Infof("backups download request for %q", args.ID)
meta, archive, err := backups.Get(args.ID)
if err != nil {
return "", err
}
defer archive.Close()
err = h.sendFile(archive, meta.Checksum(), resp)
return args.ID, err
}
func (h *backupHandler) upload(backups backups.Backups, resp http.ResponseWriter, req *http.Request) (string, error) {
// Since we want to stream the archive in we cannot simply use
// mime/multipart directly.
defer req.Body.Close()
var metaResult params.BackupsMetadataResult
archive, err := httpattachment.Get(req, &metaResult)
if err != nil {
return "", err
}
if err := validateBackupMetadataResult(metaResult); err != nil {
return "", err
}
meta := apiserverbackups.MetadataFromResult(metaResult)
id, err := backups.Add(archive, meta)
if err != nil {
return "", err
}
if err := sendStatusAndJSON(resp, http.StatusOK, ¶ms.BackupsUploadResult{ID: id}); err != nil {
return "", errors.Trace(err)
}
return id, nil
}
func validateBackupMetadataResult(metaResult params.BackupsMetadataResult) error {
if metaResult.ID != "" {
return errors.New("got unexpected metadata ID")
}
if !metaResult.Stored.IsZero() {
return errors.New(`got unexpected metadata "Stored" value`)
}
return nil
}
func (h *backupHandler) read(req *http.Request, expectedType string) ([]byte, error) {
defer req.Body.Close()
ctype := req.Header.Get("Content-Type")
if ctype != expectedType {
return nil, errors.Errorf("expected Content-Type %q, got %q", expectedType, ctype)
}
body, err := ioutil.ReadAll(req.Body)
if err != nil {
return nil, errors.Annotate(err, "while reading request body")
}
return body, nil
}
func (h *backupHandler) parseGETArgs(req *http.Request) (*params.BackupsDownloadArgs, error) {
body, err := h.read(req, params.ContentTypeJSON)
if err != nil {
return nil, errors.Trace(err)
}
var args params.BackupsDownloadArgs
if err := json.Unmarshal(body, &args); err != nil {
return nil, errors.Annotate(err, "while de-serializing args")
}
return &args, nil
}
func (h *backupHandler) sendFile(file io.Reader, checksum string, resp http.ResponseWriter) error {
// We don't set the Content-Length header, leaving it at -1.
resp.Header().Set("Content-Type", params.ContentTypeRaw)
resp.Header().Set("Digest", params.EncodeChecksum(checksum))
resp.WriteHeader(http.StatusOK)
if _, err := io.Copy(resp, file); err != nil {
return errors.Annotate(err, "while streaming archive")
}
return nil
}
// sendError sends a JSON-encoded error response.
// Note the difference from the error response sent by
// the sendError function - the error is encoded directly
// rather than in the Error field.
func (h *backupHandler) sendError(w http.ResponseWriter, err error) {
err, status := common.ServerErrorAndStatus(err)
if err := sendStatusAndJSON(w, status, err); err != nil {
logger.Errorf("%v", err)
}
}