github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/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 "runtime/debug" 8 "strings" 9 10 "github.com/juju/errors" 11 "github.com/juju/mgo/v3/bson" 12 "github.com/juju/mgo/v3/txn" 13 jujutxn "github.com/juju/txn/v3" 14 ) 15 16 func readTxnRevno(db Database, collectionName string, id interface{}) (int64, error) { 17 collection, closer := db.GetCollection(collectionName) 18 defer closer() 19 query := collection.FindId(id).Select(bson.D{{"txn-revno", 1}}) 20 var result struct { 21 TxnRevno int64 `bson:"txn-revno"` 22 } 23 err := query.One(&result) 24 return result.TxnRevno, errors.Trace(err) 25 } 26 27 func (st *State) runRawTransaction(ops []txn.Op) error { 28 return st.database.RunRawTransaction(ops) 29 } 30 31 type multiModelRunner struct { 32 rawRunner jujutxn.Runner 33 schema CollectionSchema 34 modelUUID string 35 } 36 37 func shortStack() string { 38 var rv []string 39 for _, line := range strings.Split(string(debug.Stack()), "\n") { 40 if len(line) > 0 && line[0] == '\t' { 41 rv = append(rv, strings.Split(line[1:], " ")[0]) 42 } 43 } 44 // We definitely have at least 3 objects in rv - debug.Stack(), this function and the one that called it. 45 return strings.Join(rv[3:], " ") 46 } 47 48 var seenShortStacks = make(map[string]bool) 49 50 // RunTransaction is part of the jujutxn.Runner interface. Operations 51 // that affect multi-model collections will be modified to 52 // ensure correct interaction with these collections. 53 func (r *multiModelRunner) RunTransaction(tx *jujutxn.Transaction) error { 54 if len(tx.Ops) == 0 { 55 stack := shortStack() 56 // It is a warning that should be reported to us, but we definitely 57 // don't want to clutter up logs so we'll only write it once. 58 if !seenShortStacks[stack] { 59 seenShortStacks[stack] = true 60 logger.Warningf("Running no-op transaction - called by %s", stack) 61 } 62 } 63 newOps, err := r.updateOps(tx.Ops) 64 if err != nil { 65 return errors.Trace(err) 66 } 67 tx.Ops = newOps 68 return r.rawRunner.RunTransaction(tx) 69 } 70 71 // Run is part of the jujutxn.Runner interface. Operations returned by 72 // the given "transactions" function that affect multi-model 73 // collections will be modified to ensure correct interaction with 74 // these collections. 75 func (r *multiModelRunner) Run(transactions jujutxn.TransactionSource) error { 76 return r.rawRunner.Run(func(attempt int) ([]txn.Op, error) { 77 ops, err := transactions(attempt) 78 if err != nil { 79 // Don't use Trace here as jujutxn doens't use juju/errors 80 // and won't deal correctly with some returned errors. 81 return nil, err 82 } 83 newOps, err := r.updateOps(ops) 84 if err != nil { 85 return nil, errors.Trace(err) 86 } 87 return newOps, nil 88 }) 89 } 90 91 // ResumeTransactions is part of the jujutxn.Runner interface. 92 func (r *multiModelRunner) ResumeTransactions() error { 93 return errors.NotImplemented 94 } 95 96 // MaybePruneTransactions is part of the jujutxn.Runner interface. 97 func (r *multiModelRunner) MaybePruneTransactions(opts jujutxn.PruneOptions) error { 98 return errors.NotImplemented 99 } 100 101 // updateOps modifies the Insert and Update fields in a slice of 102 // txn.Ops to ensure they are multi-model safe where 103 // possible. The returned []txn.Op is a new copy of the input (with 104 // changes). 105 func (r *multiModelRunner) updateOps(ops []txn.Op) ([]txn.Op, error) { 106 var outOps []txn.Op 107 for _, op := range ops { 108 collInfo, found := r.schema[op.C] 109 if !found { 110 return nil, errors.Errorf("forbidden transaction: references unknown collection %q", op.C) 111 } 112 if collInfo.rawAccess { 113 return nil, errors.Errorf("forbidden transaction: references raw-access collection %q", op.C) 114 } 115 outOp := op 116 if !collInfo.global { 117 outOp.Id = ensureModelUUIDIfString(r.modelUUID, op.Id) 118 if op.Insert != nil { 119 newInsert, err := mungeDocForMultiModel(op.Insert, r.modelUUID, modelUUIDRequired) 120 if err != nil { 121 return nil, errors.Annotatef(err, "cannot insert into %q", op.C) 122 } 123 outOp.Insert = newInsert 124 } 125 if op.Update != nil { 126 newUpdate, err := r.mungeUpdate(op.Update) 127 if err != nil { 128 return nil, errors.Annotatef(err, "cannot update %q", op.C) 129 } 130 outOp.Update = newUpdate 131 } 132 } 133 outOps = append(outOps, outOp) 134 } 135 return outOps, nil 136 } 137 138 // mungeUpdate takes the value of an txn.Op Update field and modifies 139 // it to be multi-model safe, returning the modified document. 140 func (r *multiModelRunner) mungeUpdate(updateDoc interface{}) (interface{}, error) { 141 switch doc := updateDoc.(type) { 142 case bson.D: 143 return r.mungeBsonDUpdate(doc) 144 case bson.M: 145 return r.mungeBsonMUpdate(doc) 146 default: 147 return nil, errors.Errorf("don't know how to handle %T", updateDoc) 148 } 149 } 150 151 // mungeBsonDUpdate modifies a txn.Op's Update field values expressed 152 // as a bson.D and attempts to make it multi-model safe. 153 // 154 // Currently, only $set operations are munged. 155 func (r *multiModelRunner) mungeBsonDUpdate(updateDoc bson.D) (bson.D, error) { 156 outDoc := make(bson.D, 0, len(updateDoc)) 157 for _, elem := range updateDoc { 158 if elem.Name == "$set" { 159 newSetDoc, err := mungeDocForMultiModel(elem.Value, r.modelUUID, 0) 160 if err != nil { 161 return nil, errors.Trace(err) 162 } 163 elem = bson.DocElem{elem.Name, newSetDoc} 164 } 165 outDoc = append(outDoc, elem) 166 } 167 return outDoc, nil 168 } 169 170 // mungeBsonMUpdate modifies a txn.Op's Update field values expressed 171 // as a bson.M and attempts to make it multi-model safe. 172 // 173 // Currently, only $set operations are munged. 174 func (r *multiModelRunner) mungeBsonMUpdate(updateDoc bson.M) (bson.M, error) { 175 outDoc := make(bson.M) 176 for name, elem := range updateDoc { 177 if name == "$set" { 178 var err error 179 elem, err = mungeDocForMultiModel(elem, r.modelUUID, 0) 180 if err != nil { 181 return nil, errors.Trace(err) 182 } 183 } 184 outDoc[name] = elem 185 } 186 return outDoc, nil 187 }