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