github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/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  	sendStatusAndJSON(resp, http.StatusOK, &params.BackupsUploadResult{ID: id})
   105  	return id, nil
   106  }
   107  
   108  func validateBackupMetadataResult(metaResult params.BackupsMetadataResult) error {
   109  	if metaResult.ID != "" {
   110  		return errors.New("got unexpected metadata ID")
   111  	}
   112  	if !metaResult.Stored.IsZero() {
   113  		return errors.New(`got unexpected metadata "Stored" value`)
   114  	}
   115  	return nil
   116  }
   117  
   118  func (h *backupHandler) read(req *http.Request, expectedType string) ([]byte, error) {
   119  	defer req.Body.Close()
   120  
   121  	ctype := req.Header.Get("Content-Type")
   122  	if ctype != expectedType {
   123  		return nil, errors.Errorf("expected Content-Type %q, got %q", expectedType, ctype)
   124  	}
   125  
   126  	body, err := ioutil.ReadAll(req.Body)
   127  	if err != nil {
   128  		return nil, errors.Annotate(err, "while reading request body")
   129  	}
   130  
   131  	return body, nil
   132  }
   133  
   134  func (h *backupHandler) parseGETArgs(req *http.Request) (*params.BackupsDownloadArgs, error) {
   135  	body, err := h.read(req, params.ContentTypeJSON)
   136  	if err != nil {
   137  		return nil, errors.Trace(err)
   138  	}
   139  
   140  	var args params.BackupsDownloadArgs
   141  	if err := json.Unmarshal(body, &args); err != nil {
   142  		return nil, errors.Annotate(err, "while de-serializing args")
   143  	}
   144  
   145  	return &args, nil
   146  }
   147  
   148  func (h *backupHandler) sendFile(file io.Reader, checksum string, resp http.ResponseWriter) error {
   149  	// We don't set the Content-Length header, leaving it at -1.
   150  	resp.Header().Set("Content-Type", params.ContentTypeRaw)
   151  	resp.Header().Set("Digest", params.EncodeChecksum(checksum))
   152  	resp.WriteHeader(http.StatusOK)
   153  	if _, err := io.Copy(resp, file); err != nil {
   154  		return errors.Annotate(err, "while streaming archive")
   155  	}
   156  	return nil
   157  }
   158  
   159  // sendError sends a JSON-encoded error response.
   160  // Note the difference from the error response sent by
   161  // the sendError function - the error is encoded directly
   162  // rather than in the Error field.
   163  func (h *backupHandler) sendError(w http.ResponseWriter, err error) {
   164  	err, status := common.ServerErrorAndStatus(err)
   165  
   166  	sendStatusAndJSON(w, status, err)
   167  }