github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/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  // PurgeTxn purges missing transation from restoreInfoC collection.
    88  // These can be caused because this collection is heavy use while backing
    89  // up and mongo 3.2 does not like this.
    90  func (info *RestoreInfo) PurgeTxn() error {
    91  	restoreInfo, closer := info.st.getRawCollection(restoreInfoC)
    92  	defer closer()
    93  	r := txn.NewRunner(restoreInfo)
    94  	return r.PurgeMissing(restoreInfoC)
    95  }
    96  
    97  // SetStatus sets the status of the current restore. Checks are made
    98  // to ensure that status changes are performed in the correct order.
    99  func (info *RestoreInfo) SetStatus(status RestoreStatus) error {
   100  	if err := status.Validate(); err != nil {
   101  		return errors.Trace(err)
   102  	}
   103  	buildTxn := func(_ int) ([]txn.Op, error) {
   104  		current, err := info.Status()
   105  		if err != nil {
   106  			return nil, errors.Annotate(err, "cannot read current status")
   107  		}
   108  		if current == status {
   109  			return nil, jujutxn.ErrNoOperations
   110  		}
   111  
   112  		ops, err := setRestoreStatusOps(current, status)
   113  		if err != nil {
   114  			return nil, errors.Trace(err)
   115  		}
   116  		return ops, nil
   117  	}
   118  	if err := info.st.run(buildTxn); err != nil {
   119  		return errors.Annotatef(err, "setting status %q", status)
   120  	}
   121  	return nil
   122  }
   123  
   124  // setRestoreStatusOps checks the validity of the supplied transition,
   125  // and returns either an error or a list of transaction operations that
   126  // will apply the transition.
   127  func setRestoreStatusOps(before, after RestoreStatus) ([]txn.Op, error) {
   128  	errInvalid := errors.Errorf("invalid restore transition: %s => %s", before, after)
   129  	switch after {
   130  	case RestorePending:
   131  		switch before {
   132  		case RestoreNotActive:
   133  			return createRestoreStatusPendingOps(), nil
   134  		case RestoreFailed, RestoreChecked:
   135  		default:
   136  			return nil, errInvalid
   137  		}
   138  	case RestoreFailed:
   139  		switch before {
   140  		case RestoreNotActive, RestoreChecked:
   141  			return nil, errInvalid
   142  		}
   143  	case RestoreInProgress:
   144  		if before != RestorePending {
   145  			return nil, errInvalid
   146  		}
   147  	case RestoreFinished:
   148  		// RestoreFinished is set after a restore so we cannot ensure
   149  		// what will be on the db state since it will deppend on
   150  		// what was set during backup.
   151  		switch before {
   152  		case RestoreNotActive:
   153  			return createRestoreStatusFinishedOps(), nil
   154  		case RestoreFailed:
   155  			// except for the case of Failed,this is most likely a race condition.
   156  			return nil, errInvalid
   157  		}
   158  
   159  	case RestoreChecked:
   160  		if before != RestoreFinished {
   161  			return nil, errInvalid
   162  		}
   163  	default:
   164  		return nil, errInvalid
   165  	}
   166  	return updateRestoreStatusChangeOps(before, after), nil
   167  }
   168  
   169  // createRestoreStatusFinishedOps is useful when setting finished on
   170  // a non initated restore document.
   171  func createRestoreStatusFinishedOps() []txn.Op {
   172  	return []txn.Op{{
   173  		C:      restoreInfoC,
   174  		Id:     currentRestoreId,
   175  		Assert: txn.DocMissing,
   176  		Insert: bson.D{{"status", RestoreFinished}},
   177  	}}
   178  }
   179  
   180  // createRestoreStatusPendingOps is the only valid way to create a
   181  // restore document.
   182  func createRestoreStatusPendingOps() []txn.Op {
   183  	return []txn.Op{{
   184  		C:      restoreInfoC,
   185  		Id:     currentRestoreId,
   186  		Assert: txn.DocMissing,
   187  		Insert: bson.D{{"status", RestorePending}},
   188  	}}
   189  }
   190  
   191  // updateRestoreStatusChangeOps will set the restore doc status to
   192  // after, so long as the doc's status is before.
   193  func updateRestoreStatusChangeOps(before, after RestoreStatus) []txn.Op {
   194  	return []txn.Op{{
   195  		C:      restoreInfoC,
   196  		Id:     currentRestoreId,
   197  		Assert: bson.D{{"status", before}},
   198  		Update: bson.D{{"$set", bson.D{{"status", after}}}},
   199  	}}
   200  }