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 }