github.com/mwhudson/juju@v0.0.0-20160512215208-90ff01f3497f/state/restore.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"github.com/juju/errors"
     8  	jujutxn "github.com/juju/txn"
     9  	"gopkg.in/mgo.v2"
    10  	"gopkg.in/mgo.v2/bson"
    11  	"gopkg.in/mgo.v2/txn"
    12  )
    13  
    14  // RestoreStatus is the type of the statuses
    15  type RestoreStatus string
    16  
    17  // Validate returns an errors if status' value is not known.
    18  func (status RestoreStatus) Validate() error {
    19  	switch status {
    20  	case RestorePending, RestoreInProgress, RestoreFinished:
    21  	case RestoreChecked, RestoreFailed, RestoreNotActive:
    22  	default:
    23  		return errors.Errorf("unknown restore status: %v", status)
    24  	}
    25  	return nil
    26  }
    27  
    28  const (
    29  	currentRestoreId = "current"
    30  
    31  	// RestoreNotActive is not persisted in the database, and is
    32  	// used to indicate the absence of a current restore doc.
    33  	RestoreNotActive RestoreStatus = "NOT-RESTORING"
    34  
    35  	// RestorePending is a status to signal that a restore is about
    36  	// to start any change done in this status will be lost.
    37  	RestorePending RestoreStatus = "PENDING"
    38  
    39  	// RestoreInProgress indicates that a Restore is in progress.
    40  	RestoreInProgress RestoreStatus = "RESTORING"
    41  
    42  	// RestoreFinished it is set by restore upon a succesful run.
    43  	RestoreFinished RestoreStatus = "RESTORED"
    44  
    45  	// RestoreChecked is set when the server comes up after a
    46  	// succesful restore.
    47  	RestoreChecked RestoreStatus = "CHECKED"
    48  
    49  	// RestoreFailed indicates that the process failed in a
    50  	// recoverable step.
    51  	RestoreFailed RestoreStatus = "FAILED"
    52  )
    53  
    54  // RestoreInfo exposes restore status.
    55  func (st *State) RestoreInfo() *RestoreInfo {
    56  	return &RestoreInfo{st: st}
    57  }
    58  
    59  // RestoreInfo exposes restore status.
    60  type RestoreInfo struct {
    61  	st *State
    62  }
    63  
    64  // Status returns the current Restore doc status
    65  func (info *RestoreInfo) Status() (RestoreStatus, error) {
    66  	restoreInfo, closer := info.st.getCollection(restoreInfoC)
    67  	defer closer()
    68  
    69  	var doc struct {
    70  		Status RestoreStatus `bson:"status"`
    71  	}
    72  	err := restoreInfo.FindId(currentRestoreId).One(&doc)
    73  	switch errors.Cause(err) {
    74  	case nil:
    75  	case mgo.ErrNotFound:
    76  		return RestoreNotActive, nil
    77  	default:
    78  		return "", errors.Annotate(err, "cannot read restore info")
    79  	}
    80  
    81  	if err := doc.Status.Validate(); err != nil {
    82  		return "", errors.Trace(err)
    83  	}
    84  	return doc.Status, nil
    85  }
    86  
    87  // SetStatus sets the status of the current restore. Checks are made
    88  // to ensure that status changes are performed in the correct order.
    89  func (info *RestoreInfo) SetStatus(status RestoreStatus) error {
    90  	if err := status.Validate(); err != nil {
    91  		return errors.Trace(err)
    92  	}
    93  	buildTxn := func(_ int) ([]txn.Op, error) {
    94  		current, err := info.Status()
    95  		if err != nil {
    96  			return nil, errors.Annotate(err, "cannot read current status")
    97  		}
    98  		if current == status {
    99  			return nil, jujutxn.ErrNoOperations
   100  		}
   101  
   102  		ops, err := setRestoreStatusOps(current, status)
   103  		if err != nil {
   104  			return nil, errors.Trace(err)
   105  		}
   106  		return ops, nil
   107  	}
   108  	if err := info.st.run(buildTxn); err != nil {
   109  		return errors.Trace(err)
   110  	}
   111  	return nil
   112  }
   113  
   114  // setRestoreStatusOps checks the validity of the supplied transition,
   115  // and returns either an error or a list of transaction operations that
   116  // will apply the transition.
   117  func setRestoreStatusOps(before, after RestoreStatus) ([]txn.Op, error) {
   118  	errInvalid := errors.Errorf("invalid restore transition: %s => %s", before, after)
   119  	switch after {
   120  	case RestorePending:
   121  		switch before {
   122  		case RestoreNotActive:
   123  			return createRestoreStatusPendingOps(), nil
   124  		case RestoreFailed, RestoreChecked:
   125  		default:
   126  			return nil, errInvalid
   127  		}
   128  	case RestoreFailed:
   129  		switch before {
   130  		case RestoreNotActive, RestoreChecked:
   131  			return nil, errInvalid
   132  		}
   133  	case RestoreInProgress:
   134  		if before != RestorePending {
   135  			return nil, errInvalid
   136  		}
   137  	case RestoreFinished:
   138  		if before != RestoreInProgress {
   139  			return nil, errInvalid
   140  		}
   141  	case RestoreChecked:
   142  		if before != RestoreFinished {
   143  			return nil, errInvalid
   144  		}
   145  	default:
   146  		return nil, errInvalid
   147  	}
   148  	return updateRestoreStatusChangeOps(before, after), nil
   149  }
   150  
   151  // createRestoreStatusPendingOps is the only valid way to create a
   152  // restore document.
   153  func createRestoreStatusPendingOps() []txn.Op {
   154  	return []txn.Op{{
   155  		C:      restoreInfoC,
   156  		Id:     currentRestoreId,
   157  		Assert: txn.DocMissing,
   158  		Insert: bson.D{{"status", RestorePending}},
   159  	}}
   160  }
   161  
   162  // updateRestoreStatusChangeOps will set the restore doc status to
   163  // after, so long as the doc's status is before.
   164  func updateRestoreStatusChangeOps(before, after RestoreStatus) []txn.Op {
   165  	return []txn.Op{{
   166  		C:      restoreInfoC,
   167  		Id:     currentRestoreId,
   168  		Assert: bson.D{{"status", before}},
   169  		Update: bson.D{{"$set", bson.D{{"status", after}}}},
   170  	}}
   171  }