github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/txns.go (about) 1 // Copyright 2012-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/bson" 10 "gopkg.in/mgo.v2/txn" 11 ) 12 13 // readTxnRevno is a convenience method delegating to the state's Database. 14 func (st *State) readTxnRevno(collectionName string, id interface{}) (int64, error) { 15 collection, closer := st.database.GetCollection(collectionName) 16 defer closer() 17 query := collection.FindId(id).Select(bson.D{{"txn-revno", 1}}) 18 var result struct { 19 TxnRevno int64 `bson:"txn-revno"` 20 } 21 err := query.One(&result) 22 return result.TxnRevno, errors.Trace(err) 23 } 24 25 // runTransaction is a convenience method delegating to the state's Database. 26 func (st *State) runTransaction(ops []txn.Op) error { 27 runner, closer := st.database.TransactionRunner() 28 defer closer() 29 return runner.RunTransaction(ops) 30 } 31 32 // runTransaction is a convenience method delegating to the state's Database 33 // for the model with the given modelUUID. 34 func (st *State) runTransactionFor(modelUUID string, ops []txn.Op) error { 35 database, dbcloser := st.database.CopyForModel(modelUUID) 36 defer dbcloser() 37 runner, closer := database.TransactionRunner() 38 defer closer() 39 return runner.RunTransaction(ops) 40 } 41 42 // runRawTransaction is a convenience method that will run a single 43 // transaction using a "raw" transaction runner that won't perform 44 // model filtering. 45 func (st *State) runRawTransaction(ops []txn.Op) error { 46 runner, closer := st.database.TransactionRunner() 47 defer closer() 48 if multiRunner, ok := runner.(*multiModelRunner); ok { 49 runner = multiRunner.rawRunner 50 } 51 return runner.RunTransaction(ops) 52 } 53 54 // run is a convenience method delegating to the state's Database. 55 func (st *State) run(transactions jujutxn.TransactionSource) error { 56 runner, closer := st.database.TransactionRunner() 57 defer closer() 58 return runner.Run(transactions) 59 } 60 61 // runForModel is a convenience method that delegates to a Database for a different 62 // modelUUID. 63 func (st *State) runForModel(modelUUID string, transactions jujutxn.TransactionSource) error { 64 database, dbcloser := st.database.CopyForModel(modelUUID) 65 defer dbcloser() 66 runner, closer := database.TransactionRunner() 67 defer closer() 68 return runner.Run(transactions) 69 } 70 71 // ResumeTransactions resumes all pending transactions. 72 func (st *State) ResumeTransactions() error { 73 runner, closer := st.database.TransactionRunner() 74 defer closer() 75 return runner.ResumeTransactions() 76 } 77 78 // MaybePruneTransactions removes data for completed transactions. 79 func (st *State) MaybePruneTransactions() error { 80 runner, closer := st.database.TransactionRunner() 81 defer closer() 82 // Prune txns only when txn count has doubled since last prune. 83 return runner.MaybePruneTransactions(2.0) 84 } 85 86 type multiModelRunner struct { 87 rawRunner jujutxn.Runner 88 schema collectionSchema 89 modelUUID string 90 } 91 92 // RunTransaction is part of the jujutxn.Runner interface. Operations 93 // that affect multi-model collections will be modified to 94 // ensure correct interaction with these collections. 95 func (r *multiModelRunner) RunTransaction(ops []txn.Op) error { 96 newOps, err := r.updateOps(ops) 97 if err != nil { 98 return errors.Trace(err) 99 } 100 return r.rawRunner.RunTransaction(newOps) 101 } 102 103 // Run is part of the jujutxn.Runner interface. Operations returned by 104 // the given "transactions" function that affect multi-model 105 // collections will be modified to ensure correct interaction with 106 // these collections. 107 func (r *multiModelRunner) Run(transactions jujutxn.TransactionSource) error { 108 return r.rawRunner.Run(func(attempt int) ([]txn.Op, error) { 109 ops, err := transactions(attempt) 110 if err != nil { 111 // Don't use Trace here as jujutxn doens't use juju/errors 112 // and won't deal correctly with some returned errors. 113 return nil, err 114 } 115 newOps, err := r.updateOps(ops) 116 if err != nil { 117 return nil, errors.Trace(err) 118 } 119 return newOps, nil 120 }) 121 } 122 123 // ResumeTransactions is part of the jujutxn.Runner interface. 124 func (r *multiModelRunner) ResumeTransactions() error { 125 return r.rawRunner.ResumeTransactions() 126 } 127 128 // MaybePruneTransactions is part of the jujutxn.Runner interface. 129 func (r *multiModelRunner) MaybePruneTransactions(pruneFactor float32) error { 130 return r.rawRunner.MaybePruneTransactions(pruneFactor) 131 } 132 133 // updateOps modifies the Insert and Update fields in a slice of 134 // txn.Ops to ensure they are multi-model safe where 135 // possible. The returned []txn.Op is a new copy of the input (with 136 // changes). 137 func (r *multiModelRunner) updateOps(ops []txn.Op) ([]txn.Op, error) { 138 var outOps []txn.Op 139 for _, op := range ops { 140 collInfo, found := r.schema[op.C] 141 if !found { 142 return nil, errors.Errorf("forbidden transaction: references unknown collection %q", op.C) 143 } 144 if collInfo.rawAccess { 145 return nil, errors.Errorf("forbidden transaction: references raw-access collection %q", op.C) 146 } 147 outOp := op 148 if !collInfo.global { 149 outOp.Id = ensureModelUUIDIfString(r.modelUUID, op.Id) 150 if op.Insert != nil { 151 newInsert, err := mungeDocForMultiEnv(op.Insert, r.modelUUID, modelUUIDRequired) 152 if err != nil { 153 return nil, errors.Annotatef(err, "cannot insert into %q", op.C) 154 } 155 outOp.Insert = newInsert 156 } 157 if op.Update != nil { 158 newUpdate, err := r.mungeUpdate(op.Update) 159 if err != nil { 160 return nil, errors.Annotatef(err, "cannot update %q", op.C) 161 } 162 outOp.Update = newUpdate 163 } 164 } 165 outOps = append(outOps, outOp) 166 } 167 logger.Tracef("rewrote transaction: %#v", outOps) 168 return outOps, nil 169 } 170 171 // mungeUpdate takes the value of an txn.Op Update field and modifies 172 // it to be multi-model safe, returning the modified document. 173 func (r *multiModelRunner) mungeUpdate(updateDoc interface{}) (interface{}, error) { 174 switch doc := updateDoc.(type) { 175 case bson.D: 176 return r.mungeBsonDUpdate(doc) 177 case bson.M: 178 return r.mungeBsonMUpdate(doc) 179 default: 180 return nil, errors.Errorf("don't know how to handle %T", updateDoc) 181 } 182 } 183 184 // mungeBsonDUpdate modifies a txn.Op's Update field values expressed 185 // as a bson.D and attempts to make it multi-model safe. 186 // 187 // Currently, only $set operations are munged. 188 func (r *multiModelRunner) mungeBsonDUpdate(updateDoc bson.D) (bson.D, error) { 189 outDoc := make(bson.D, 0, len(updateDoc)) 190 for _, elem := range updateDoc { 191 if elem.Name == "$set" { 192 newSetDoc, err := mungeDocForMultiEnv(elem.Value, r.modelUUID, 0) 193 if err != nil { 194 return nil, errors.Trace(err) 195 } 196 elem = bson.DocElem{elem.Name, newSetDoc} 197 } 198 outDoc = append(outDoc, elem) 199 } 200 return outDoc, nil 201 } 202 203 // mungeBsonMUpdate modifies a txn.Op's Update field values expressed 204 // as a bson.M and attempts to make it multi-model safe. 205 // 206 // Currently, only $set operations are munged. 207 func (r *multiModelRunner) mungeBsonMUpdate(updateDoc bson.M) (bson.M, error) { 208 outDoc := make(bson.M) 209 for name, elem := range updateDoc { 210 if name == "$set" { 211 var err error 212 elem, err = mungeDocForMultiEnv(elem, r.modelUUID, 0) 213 if err != nil { 214 return nil, errors.Trace(err) 215 } 216 } 217 outDoc[name] = elem 218 } 219 return outDoc, nil 220 }