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 }