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  }