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, &params.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  }