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