github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/database.go (about)

     1  // Copyright 2012-2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package state
     5  
     6  import (
     7  	"fmt"
     8  
     9  	"github.com/juju/errors"
    10  	jujutxn "github.com/juju/txn"
    11  	"gopkg.in/juju/names.v2"
    12  	"gopkg.in/mgo.v2"
    13  	"gopkg.in/mgo.v2/txn"
    14  
    15  	"github.com/juju/juju/mongo"
    16  )
    17  
    18  type SessionCloser func()
    19  
    20  func dontCloseAnything() {}
    21  
    22  // Database exposes the mongodb capabilities that most of state should see.
    23  type Database interface {
    24  
    25  	// Copy returns a matching Database with its own session, and a
    26  	// func that must be called when the Database is no longer needed.
    27  	//
    28  	// GetCollection and TransactionRunner results from the resulting Database
    29  	// will all share a session; this does not absolve you of responsibility
    30  	// for calling those collections' closers.
    31  	Copy() (Database, SessionCloser)
    32  
    33  	// CopyForModel returns a matching Database with its own session and
    34  	// its own modelUUID and a func that must be called when the Database is no
    35  	// longer needed.
    36  	//
    37  	// Same warnings apply for CopyForModel than for Copy.
    38  	CopyForModel(modelUUID string) (Database, SessionCloser)
    39  
    40  	// GetCollection returns the named Collection, and a func that must be
    41  	// called when the Collection is no longer needed. The returned Collection
    42  	// might or might not have its own session, depending on the Database; the
    43  	// closer must always be called regardless.
    44  	//
    45  	// If the schema specifies model-filtering for the named collection,
    46  	// the returned collection will automatically filter queries; for details,
    47  	// see modelStateCollection.
    48  	GetCollection(name string) (mongo.Collection, SessionCloser)
    49  
    50  	// TransactionRunner() returns a runner responsible for making changes to
    51  	// the database, and a func that must be called when the runner is no longer
    52  	// needed. The returned Runner might or might not have its own session,
    53  	// depending on the Database; the closer must always be called regardless.
    54  	//
    55  	// It will reject transactions that reference raw-access (or unknown)
    56  	// collections; it will automatically rewrite operations that reference
    57  	// non-global collections; and it will ensure that non-global documents can
    58  	// only be inserted while the corresponding model is still Alive.
    59  	TransactionRunner() (jujutxn.Runner, SessionCloser)
    60  
    61  	// Schema returns the schema used to load the database. The returned schema
    62  	// is not a copy and must not be modified.
    63  	Schema() collectionSchema
    64  }
    65  
    66  // Change represents any mgo/txn-representable change to a Database.
    67  type Change interface {
    68  
    69  	// Prepare ensures that db is in a valid base state for applying
    70  	// the change, and returns mgo/txn operations that will fail any
    71  	// enclosing transaction if the state has materially changed; or
    72  	// returns an error.
    73  	Prepare(db Database) ([]txn.Op, error)
    74  }
    75  
    76  // ErrChangeComplete can be returned from Prepare to finish an Apply
    77  // attempt and report success without taking any further action.
    78  var ErrChangeComplete = errors.New("change complete")
    79  
    80  // Apply runs the supplied Change against the supplied Database. If it
    81  // returns no error, the change succeeded.
    82  func Apply(db Database, change Change) error {
    83  	db, closer := db.Copy()
    84  	defer closer()
    85  
    86  	buildTxn := func(int) ([]txn.Op, error) {
    87  		ops, err := change.Prepare(db)
    88  		if errors.Cause(err) == ErrChangeComplete {
    89  			return nil, jujutxn.ErrNoOperations
    90  		}
    91  		if err != nil {
    92  			return nil, errors.Trace(err)
    93  		}
    94  		return ops, nil
    95  	}
    96  
    97  	runner, closer := db.TransactionRunner()
    98  	defer closer()
    99  	if err := runner.Run(buildTxn); err != nil {
   100  		return errors.Trace(err)
   101  	}
   102  	return nil
   103  }
   104  
   105  // collectionInfo describes important features of a collection.
   106  type collectionInfo struct {
   107  
   108  	// explicitCreate, if non-nil, will cause the collection to be explicitly
   109  	// Create~d (with the given value) before ensuring indexes.
   110  	explicitCreate *mgo.CollectionInfo
   111  
   112  	// indexes listed here will be EnsureIndex~ed before state is opened.
   113  	indexes []mgo.Index
   114  
   115  	// global collections will not have model filtering applied. Non-
   116  	// global collections will have both transactions and reads filtered by
   117  	// relevant model uuid.
   118  	global bool
   119  
   120  	// rawAccess collections can be safely accessed as a mongo.WriteCollection.
   121  	// Direct database access to txn-aware collections is strongly discouraged:
   122  	// merely writing directly to a field makes it impossible to use that field
   123  	// with mgo/txn; in the worst case, document deletion can destroy critical
   124  	// parts of the state distributed by mgo/txn, causing different runners to
   125  	// choose different global transaction orderings; this can in turn cause
   126  	// operations to be skipped.
   127  	//
   128  	// Short explanation follows: two different runners pick different -- but
   129  	// overlapping -- "next" transactions; they each pick the same txn-id; the
   130  	// first runner writes to an overlapping document and records the txn-id;
   131  	// and then the second runner inspects that document, sees that the chosen
   132  	// txn-id has already been applied, and <splat> skips that operation.
   133  	//
   134  	// Goodbye consistency. So please don't mix txn and non-txn writes without
   135  	// very careful analysis; and then, please, just don't do it anyway. If you
   136  	// need raw mgo, use a rawAccess collection.
   137  	rawAccess bool
   138  }
   139  
   140  // collectionSchema defines the set of collections used in juju.
   141  type collectionSchema map[string]collectionInfo
   142  
   143  // Load causes all recorded collections to be created and indexed as specified;
   144  // the returned Database will filter queries and transactions according to the
   145  // suppplied model UUID.
   146  func (schema collectionSchema) Load(db *mgo.Database, modelUUID string) (Database, error) {
   147  	if !names.IsValidModel(modelUUID) {
   148  		return nil, errors.New("invalid model UUID")
   149  	}
   150  	for name, info := range schema {
   151  		rawCollection := db.C(name)
   152  		if spec := info.explicitCreate; spec != nil {
   153  			if err := createCollection(rawCollection, spec); err != nil {
   154  				message := fmt.Sprintf("cannot create collection %q", name)
   155  				return nil, maybeUnauthorized(err, message)
   156  			}
   157  		}
   158  		for _, index := range info.indexes {
   159  			if err := rawCollection.EnsureIndex(index); err != nil {
   160  				return nil, maybeUnauthorized(err, "cannot create index")
   161  			}
   162  		}
   163  	}
   164  	return &database{
   165  		raw:       db,
   166  		schema:    schema,
   167  		modelUUID: modelUUID,
   168  	}, nil
   169  }
   170  
   171  // createCollection swallows collection-already-exists errors.
   172  func createCollection(raw *mgo.Collection, spec *mgo.CollectionInfo) error {
   173  	err := raw.Create(spec)
   174  	// The lack of error code for this error was reported upstream:
   175  	//     https://jira.mongodb.org/browse/SERVER-6992
   176  	if err == nil || err.Error() == "collection already exists" {
   177  		return nil
   178  	}
   179  	return err
   180  }
   181  
   182  // database implements Database.
   183  type database struct {
   184  
   185  	// raw is the underlying mgo Database.
   186  	raw *mgo.Database
   187  
   188  	// schema specifies how the various collections must be handled.
   189  	schema collectionSchema
   190  
   191  	// modelUUID is used to automatically filter queries and operations on
   192  	// certain collections (as defined in .schema).
   193  	modelUUID string
   194  
   195  	// runner exists for testing purposes; if non-nil, the result of
   196  	// TransactionRunner will always ultimately use this value to run
   197  	// all transactions. Setting it renders the database goroutine-unsafe.
   198  	runner jujutxn.Runner
   199  
   200  	// ownSession is used to avoid copying additional sessions in a database
   201  	// resulting from Copy.
   202  	ownSession bool
   203  }
   204  
   205  func (db *database) copySession(modelUUID string) (*database, SessionCloser) {
   206  	session := db.raw.Session.Copy()
   207  	return &database{
   208  		raw:        db.raw.With(session),
   209  		schema:     db.schema,
   210  		modelUUID:  modelUUID,
   211  		runner:     db.runner,
   212  		ownSession: true,
   213  	}, session.Close
   214  
   215  }
   216  
   217  // Copy is part of the Database interface.
   218  func (db *database) Copy() (Database, SessionCloser) {
   219  	return db.copySession(db.modelUUID)
   220  }
   221  
   222  // CopyForModel is part of the Database interface.
   223  func (db *database) CopyForModel(modelUUID string) (Database, SessionCloser) {
   224  	return db.copySession(modelUUID)
   225  }
   226  
   227  // GetCollection is part of the Database interface.
   228  func (db *database) GetCollection(name string) (collection mongo.Collection, closer SessionCloser) {
   229  	info, found := db.schema[name]
   230  	if !found {
   231  		logger.Errorf("using unknown collection %q", name)
   232  	}
   233  
   234  	// Copy session if necessary.
   235  	if db.ownSession {
   236  		collection = mongo.WrapCollection(db.raw.C(name))
   237  		closer = dontCloseAnything
   238  	} else {
   239  		collection, closer = mongo.CollectionFromName(db.raw, name)
   240  	}
   241  
   242  	// Apply model filtering.
   243  	if !info.global {
   244  		collection = &modelStateCollection{
   245  			WriteCollection: collection.Writeable(),
   246  			modelUUID:       db.modelUUID,
   247  		}
   248  	}
   249  
   250  	// Prevent layer-breaking.
   251  	if !info.rawAccess {
   252  		// TODO(fwereade): it would be nice to tweak the mongo.Collection
   253  		// interface a bit to drop Writeable in this situation, but it's
   254  		// not convenient yet.
   255  	}
   256  	return collection, closer
   257  }
   258  
   259  // TransactionRunner is part of the Database interface.
   260  func (db *database) TransactionRunner() (runner jujutxn.Runner, closer SessionCloser) {
   261  	runner = db.runner
   262  	closer = dontCloseAnything
   263  	if runner == nil {
   264  		raw := db.raw
   265  		if !db.ownSession {
   266  			session := raw.Session.Copy()
   267  			raw = raw.With(session)
   268  			closer = session.Close
   269  		}
   270  		params := jujutxn.RunnerParams{Database: raw}
   271  		runner = jujutxn.NewRunner(params)
   272  	}
   273  	return &multiModelRunner{
   274  		rawRunner: runner,
   275  		modelUUID: db.modelUUID,
   276  		schema:    db.schema,
   277  	}, closer
   278  }
   279  
   280  // Schema is part of the Database interface.
   281  func (db *database) Schema() collectionSchema {
   282  	return db.schema
   283  }