github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/state/modeloperation.go (about) 1 // Copyright 2017 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 "github.com/juju/mgo/v3/txn" 9 jujutxn "github.com/juju/txn/v3" 10 ) 11 12 // ModelOperation is a high-level model operation, 13 // encapsulating the logic required to apply a change 14 // to a model. 15 type ModelOperation interface { 16 // Build builds the low-level database transaction operations required 17 // to apply the change. If the transaction operations fail (e.g. due 18 // to concurrent changes), then Build may be called again. The attempt 19 // number, starting at zero, is passed in. 20 // 21 // Build is treated as a jujutxn.TransactionSource, so the errors 22 // in the jujutxn package may be returned by Build to influence 23 // transaction execution. 24 Build(attempt int) ([]txn.Op, error) 25 26 // Done is called after the operation is run, whether it succeeds or 27 // not. The result of running the operation is passed in, and the Done 28 // method may annotate the error; or run additional, non-transactional 29 // logic depending on the outcome. 30 Done(error) error 31 } 32 33 // modelOperationFunc is an adapter for composing a txn builder and done 34 // function/closure into a type that implements ModelOperation. 35 type modelOperationFunc struct { 36 buildFn func(attempt int) ([]txn.Op, error) 37 doneFn func(err error) error 38 } 39 40 // Build implements ModelOperation. 41 func (mof modelOperationFunc) Build(attempt int) ([]txn.Op, error) { 42 if mof.buildFn == nil { 43 return nil, nil 44 } 45 return mof.buildFn(attempt) 46 } 47 48 // Done implements ModelOperation. 49 func (mof modelOperationFunc) Done(err error) error { 50 if mof.doneFn == nil { 51 return err 52 } 53 return mof.doneFn(err) 54 } 55 56 // ComposeModelOperations returns a ModelOperation which composes multiple 57 // ModelOperations and executes them in a single transaction. If any of the 58 // provided ModelOperations are nil, they will be automatically ignored. 59 func ComposeModelOperations(modelOps ...ModelOperation) ModelOperation { 60 return modelOperationFunc{ 61 buildFn: func(attempt int) ([]txn.Op, error) { 62 var ops []txn.Op 63 for _, modelOp := range modelOps { 64 if modelOp == nil { 65 continue 66 } 67 childOps, err := modelOp.Build(attempt) 68 if err != nil && err != jujutxn.ErrNoOperations { 69 return nil, errors.Trace(err) 70 } 71 ops = append(ops, childOps...) 72 } 73 return ops, nil 74 }, 75 doneFn: func(err error) error { 76 // Unfortunately, we cannot detect the extact 77 // ModelOperation that caused the error. For now, just 78 // pass the error to each done method and ignore the 79 // return value. Then, return the original error back 80 // to the caller. 81 // 82 // A better approach would be to compare each Done 83 // method's return value to the original error and 84 // record it if different. Unfortunately, we don't have 85 // a multi-error type to represent a collection of 86 // errors. 87 for _, modelOp := range modelOps { 88 if modelOp == nil { 89 continue 90 } 91 _ = modelOp.Done(err) 92 } 93 94 return err 95 }, 96 } 97 } 98 99 // ApplyOperation applies a given ModelOperation to the model. 100 // 101 // NOTE(axw) when all model-specific types and methods are moved 102 // to Model, then this should move also. 103 func (st *State) ApplyOperation(op ModelOperation) error { 104 err := st.db().Run(op.Build) 105 return op.Done(err) 106 }