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