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  }