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