github.com/altoros/juju-vmware@v0.0.0-20150312064031-f19ae857ccca/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 "fmt" 8 "reflect" 9 10 jujutxn "github.com/juju/txn" 11 "github.com/juju/utils/set" 12 "gopkg.in/mgo.v2" 13 "gopkg.in/mgo.v2/bson" 14 "gopkg.in/mgo.v2/txn" 15 ) 16 17 const ( 18 txnAssertEnvIsAlive = true 19 txnAssertEnvIsNotAlive = false 20 ) 21 22 // txnRunner returns a jujutxn.Runner instance. 23 // 24 // If st.transactionRunner is non-nil, then that will be 25 // returned and the session argument will be ignored. This 26 // is the case in tests only, when we want to test concurrent 27 // operations. 28 // 29 // If st.transactionRunner is nil, then we create a new 30 // transaction runner with the provided session and return 31 // that. 32 func (st *State) txnRunner(session *mgo.Session) jujutxn.Runner { 33 if st.transactionRunner != nil { 34 return st.transactionRunner 35 } 36 return newMultiEnvRunner(st.EnvironUUID(), st.db.With(session), txnAssertEnvIsAlive) 37 } 38 39 // txnRunnerNoEnvAliveAssert returns a jujutxn.Runner instance that does not 40 // add an assertion for a live environment to the transaction. It was 41 // introduced only to allow the initial environment to be created and should 42 // be used rarely. 43 func (st *State) txnRunnerNoEnvAliveAssert(session *mgo.Session) jujutxn.Runner { 44 if st.transactionRunner != nil { 45 return st.transactionRunner 46 } 47 return newMultiEnvRunner(st.EnvironUUID(), st.db.With(session), txnAssertEnvIsNotAlive) 48 } 49 50 // runTransactionNoEnvAliveAssert is a convenience method delegating to txnRunnerNoEnvAliveAssert. 51 func (st *State) runTransactionNoEnvAliveAssert(ops []txn.Op) error { 52 session := st.db.Session.Copy() 53 defer session.Close() 54 return st.txnRunnerNoEnvAliveAssert(session).RunTransaction(ops) 55 } 56 57 // runTransaction is a convenience method delegating to transactionRunner. 58 func (st *State) runTransaction(ops []txn.Op) error { 59 session := st.db.Session.Copy() 60 defer session.Close() 61 return st.txnRunner(session).RunTransaction(ops) 62 } 63 64 // run is a convenience method delegating to transactionRunner. 65 func (st *State) run(transactions jujutxn.TransactionSource) error { 66 session := st.db.Session.Copy() 67 defer session.Close() 68 return st.txnRunner(session).Run(transactions) 69 } 70 71 // ResumeTransactions resumes all pending transactions. 72 func (st *State) ResumeTransactions() error { 73 session := st.db.Session.Copy() 74 defer session.Close() 75 return st.txnRunner(session).ResumeTransactions() 76 } 77 78 func newMultiEnvRunner(envUUID string, db *mgo.Database, assertEnvAlive bool) jujutxn.Runner { 79 return &multiEnvRunner{ 80 rawRunner: jujutxn.NewRunner(jujutxn.RunnerParams{Database: db}), 81 envUUID: envUUID, 82 assertEnvAlive: assertEnvAlive, 83 } 84 } 85 86 type multiEnvRunner struct { 87 rawRunner jujutxn.Runner 88 envUUID string 89 assertEnvAlive bool 90 } 91 92 // RunTransaction is part of the jujutxn.Runner interface. Operations 93 // that affect multi-environment collections will be modified in-place 94 // to ensure correct interaction with these collections. 95 func (r *multiEnvRunner) RunTransaction(ops []txn.Op) error { 96 ops = r.updateOps(ops) 97 return r.rawRunner.RunTransaction(ops) 98 } 99 100 // Run is part of the jujutxn.Run interface. Operations returned by 101 // the given "transactions" function that affect multi-environment 102 // collections will be modified in-place to ensure correct interaction 103 // with these collections. 104 func (r *multiEnvRunner) Run(transactions jujutxn.TransactionSource) error { 105 return r.rawRunner.Run(func(attempt int) ([]txn.Op, error) { 106 ops, err := transactions(attempt) 107 if err != nil { 108 // Don't use Trace here as jujutxn doens't use juju/errors 109 // and won't deal correctly with some returned errors. 110 return nil, err 111 } 112 ops = r.updateOps(ops) 113 return ops, nil 114 }) 115 } 116 117 // Run is part of the jujutxn.Run interface. 118 func (r *multiEnvRunner) ResumeTransactions() error { 119 return r.rawRunner.ResumeTransactions() 120 } 121 122 func (r *multiEnvRunner) updateOps(ops []txn.Op) []txn.Op { 123 var opsNeedEnvAlive bool 124 for i, op := range ops { 125 if multiEnvCollections.Contains(op.C) { 126 var docID interface{} 127 if id, ok := op.Id.(string); ok { 128 docID = addEnvUUID(r.envUUID, id) 129 ops[i].Id = docID 130 } else { 131 docID = op.Id 132 } 133 134 if op.Insert != nil { 135 switch doc := op.Insert.(type) { 136 case bson.D: 137 ops[i].Insert = r.updateBsonD(doc, docID, op.C) 138 case bson.M: 139 r.updateBsonM(doc, docID, op.C) 140 default: 141 r.updateStruct(doc, docID, op.C) 142 } 143 144 if r.assertEnvAlive && !opsNeedEnvAlive && envAliveColls.Contains(op.C) { 145 opsNeedEnvAlive = true 146 } 147 } 148 } 149 } 150 if opsNeedEnvAlive { 151 ops = append(ops, assertEnvAliveOp(r.envUUID)) 152 } 153 return ops 154 } 155 156 func assertEnvAliveOp(envUUID string) txn.Op { 157 return txn.Op{ 158 C: environmentsC, 159 Id: envUUID, 160 Assert: isEnvAliveDoc, 161 } 162 } 163 164 var envAliveColls = newEnvAliveColls() 165 166 // newEnvAliveColls returns a copy of multiEnvCollections minus cleanupsC. 167 // This set is used to check if a txn needs to assert that there is a live 168 // environment be inserting docs. 169 func newEnvAliveColls() set.Strings { 170 e := set.NewStrings(multiEnvCollections.Values()...) 171 e.Remove(cleanupsC) 172 return e 173 } 174 175 func (r *multiEnvRunner) updateBsonD(doc bson.D, docID interface{}, collName string) bson.D { 176 idSeen := false 177 envUUIDSeen := false 178 for i, elem := range doc { 179 switch elem.Name { 180 case "_id": 181 idSeen = true 182 doc[i].Value = docID 183 case "env-uuid": 184 envUUIDSeen = true 185 if elem.Value != r.envUUID { 186 panic(fmt.Sprintf("environment UUID for document to insert into "+ 187 "%s does not match state", collName)) 188 } 189 } 190 } 191 if !idSeen { 192 doc = append(doc, bson.DocElem{"_id", docID}) 193 } 194 if !envUUIDSeen { 195 doc = append(doc, bson.DocElem{"env-uuid", r.envUUID}) 196 } 197 return doc 198 } 199 200 func (r *multiEnvRunner) updateBsonM(doc bson.M, docID interface{}, collName string) { 201 idSeen := false 202 envUUIDSeen := false 203 for key, value := range doc { 204 switch key { 205 case "_id": 206 idSeen = true 207 doc[key] = docID 208 case "env-uuid": 209 envUUIDSeen = true 210 if value != r.envUUID { 211 panic(fmt.Sprintf("environment UUID for document to insert into "+ 212 "%s does not match state", collName)) 213 } 214 } 215 } 216 if !idSeen { 217 doc["_id"] = docID 218 } 219 if !envUUIDSeen { 220 doc["env-uuid"] = r.envUUID 221 } 222 } 223 224 func (r *multiEnvRunner) updateStruct(doc, docID interface{}, collName string) { 225 v := reflect.ValueOf(doc) 226 t := v.Type() 227 228 if t.Kind() == reflect.Ptr { 229 v = v.Elem() 230 t = v.Type() 231 } 232 233 if t.Kind() == reflect.Struct { 234 envUUIDSeen := false 235 for i := 0; i < t.NumField(); i++ { 236 f := t.Field(i) 237 switch f.Tag.Get("bson") { 238 case "_id": 239 r.updateStructField(v, f.Name, docID, collName, overrideField) 240 case "env-uuid": 241 r.updateStructField(v, f.Name, r.envUUID, collName, fieldMustMatch) 242 envUUIDSeen = true 243 } 244 } 245 if !envUUIDSeen { 246 panic(fmt.Sprintf("struct for insert into %s is missing an env-uuid field", collName)) 247 } 248 } 249 250 } 251 252 const overrideField = "override" 253 const fieldMustMatch = "mustmatch" 254 255 func (r *multiEnvRunner) updateStructField(v reflect.Value, name string, newValue interface{}, collName, updateType string) { 256 fv := v.FieldByName(name) 257 if fv.Interface() != newValue { 258 if updateType == fieldMustMatch && fv.String() != "" { 259 panic(fmt.Sprintf("%s for insert into %s does not match expected value", 260 name, collName)) 261 } 262 if fv.CanSet() { 263 fv.Set(reflect.ValueOf(newValue)) 264 } else { 265 panic(fmt.Sprintf("struct for insert into %s requires "+ 266 "%s change but was passed by value", collName, name)) 267 } 268 } 269 } 270 271 // rawTxnRunner returns a transaction runner that won't perform 272 // automatic addition of environment UUIDs into transaction 273 // operations, even for collections that contain documents for 274 // multiple environments. It should be used rarely. 275 func (st *State) rawTxnRunner(session *mgo.Session) jujutxn.Runner { 276 if st.transactionRunner != nil { 277 return getRawRunner(st.transactionRunner) 278 } 279 return jujutxn.NewRunner(jujutxn.RunnerParams{ 280 Database: st.db.With(session), 281 }) 282 } 283 284 // runRawTransaction is a convenience method that will run a single 285 // transaction using a "raw" transaction runner, as returned by 286 // rawTxnRunner. 287 func (st *State) runRawTransaction(ops []txn.Op) error { 288 session := st.db.Session.Copy() 289 defer session.Close() 290 runner := st.rawTxnRunner(session) 291 return runner.RunTransaction(ops) 292 } 293 294 // getRawRunner returns the underlying "raw" transaction runner from 295 // the passed transaction runner. 296 func getRawRunner(runner jujutxn.Runner) jujutxn.Runner { 297 if runner, ok := runner.(*multiEnvRunner); ok { 298 return runner.rawRunner 299 } 300 return runner 301 }