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