github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/backups/storage.go (about) 1 // Copyright 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package backups 5 6 import ( 7 "io" 8 "path" 9 "time" 10 11 "github.com/juju/errors" 12 "github.com/juju/juju/controller" 13 "github.com/juju/juju/environs/config" 14 "github.com/juju/juju/state" 15 jujutxn "github.com/juju/txn" 16 "github.com/juju/utils/filestorage" 17 "github.com/juju/version" 18 "gopkg.in/juju/blobstore.v2" 19 "gopkg.in/juju/names.v2" 20 "gopkg.in/mgo.v2" 21 "gopkg.in/mgo.v2/bson" 22 "gopkg.in/mgo.v2/txn" 23 ) 24 25 // backupIDTimstamp is used to format the timestamp from a backup 26 // metadata into a string. The result is used as the first half of the 27 // corresponding backup ID. 28 const backupIDTimestamp = "20060102-150405" 29 30 //--------------------------- 31 // Backup metadata document 32 33 // storageMetaDoc is a mirror of backups.Metadata, used just for DB storage. 34 type storageMetaDoc struct { 35 ID string `bson:"_id"` 36 37 // blob storage 38 39 Checksum string `bson:"checksum"` 40 ChecksumFormat string `bson:"checksumformat"` 41 Size int64 `bson:"size,minsize"` 42 Stored int64 `bson:"stored,minsize"` 43 44 // backup 45 46 Started int64 `bson:"started,minsize"` 47 Finished int64 `bson:"finished,minsize"` 48 Notes string `bson:"notes,omitempty"` 49 50 // origin 51 52 Model string `bson:"model"` 53 Machine string `bson:"machine"` 54 Hostname string `bson:"hostname"` 55 Version version.Number `bson:"version"` 56 Series string `bson:"series"` 57 } 58 59 func (doc *storageMetaDoc) isFileInfoComplete() bool { 60 if doc.Checksum == "" { 61 return false 62 } 63 if doc.ChecksumFormat == "" { 64 return false 65 } 66 if doc.Size == 0 { 67 return false 68 } 69 return true 70 } 71 72 func (doc *storageMetaDoc) validate() error { 73 if doc.ID == "" { 74 return errors.New("missing ID") 75 } 76 if doc.Started == 0 { 77 return errors.New("missing Started") 78 } 79 // We don't check doc.Finished because it doesn't have to be set. 80 if doc.Model == "" { 81 return errors.New("missing Model") 82 } 83 if doc.Machine == "" { 84 return errors.New("missing Machine") 85 } 86 if doc.Hostname == "" { 87 return errors.New("missing Hostname") 88 } 89 if doc.Version.Major == 0 { 90 return errors.New("missing Version") 91 } 92 93 // We don't check doc.Stored because it doesn't have to be set. 94 95 // Check the file-related fields. 96 if !doc.isFileInfoComplete() { 97 // Don't check the file-related fields. 98 return nil 99 } 100 if doc.Checksum == "" { 101 return errors.New("missing Checksum") 102 } 103 if doc.ChecksumFormat == "" { 104 return errors.New("missing ChecksumFormat") 105 } 106 if doc.Size == 0 { 107 return errors.New("missing Size") 108 } 109 110 return nil 111 } 112 113 func metadocUnixToTime(t int64) time.Time { 114 return time.Unix(t, 0).UTC() 115 } 116 117 func metadocTimeToUnix(t time.Time) int64 { 118 return t.UTC().Unix() 119 } 120 121 // docAsMetadata returns a new backups.Metadata based on the storageMetaDoc. 122 func docAsMetadata(doc *storageMetaDoc) *Metadata { 123 meta := NewMetadata() 124 meta.Started = metadocUnixToTime(doc.Started) 125 meta.Notes = doc.Notes 126 127 meta.Origin.Model = doc.Model 128 meta.Origin.Machine = doc.Machine 129 meta.Origin.Hostname = doc.Hostname 130 meta.Origin.Version = doc.Version 131 meta.Origin.Series = doc.Series 132 133 meta.SetID(doc.ID) 134 135 if doc.Finished != 0 { 136 finished := metadocUnixToTime(doc.Finished) 137 meta.Finished = &finished 138 } 139 140 if doc.isFileInfoComplete() { 141 // Set the file-related fields. 142 143 // The doc should have already been validated when stored. 144 meta.FileMetadata.Raw.Size = doc.Size 145 meta.FileMetadata.Raw.Checksum = doc.Checksum 146 meta.FileMetadata.Raw.ChecksumFormat = doc.ChecksumFormat 147 } 148 149 if doc.Stored != 0 { 150 stored := metadocUnixToTime(doc.Stored) 151 meta.SetStored(&stored) 152 } 153 154 return meta 155 } 156 157 // newStorageMetaDoc creates a storageMetaDoc using the corresponding 158 // values from the backup Metadata. 159 func newStorageMetaDoc(meta *Metadata) storageMetaDoc { 160 var doc storageMetaDoc 161 162 // Ignore metadata.ID. It will be set by storage later. 163 164 doc.Checksum = meta.Checksum() 165 doc.ChecksumFormat = meta.ChecksumFormat() 166 doc.Size = meta.Size() 167 if meta.Stored() != nil { 168 stored := meta.Stored() 169 doc.Stored = metadocTimeToUnix(*stored) 170 } 171 172 doc.Started = metadocTimeToUnix(meta.Started) 173 if meta.Finished != nil { 174 doc.Finished = metadocTimeToUnix(*meta.Finished) 175 } 176 doc.Notes = meta.Notes 177 178 doc.Model = meta.Origin.Model 179 doc.Machine = meta.Origin.Machine 180 doc.Hostname = meta.Origin.Hostname 181 doc.Version = meta.Origin.Version 182 doc.Series = meta.Origin.Series 183 184 return doc 185 } 186 187 //--------------------------- 188 // DB operations 189 190 // TODO(ericsnow) Merge storageDBWrapper with the storage implementation in 191 // state/storage.go (also see state/binarystorage). 192 193 // storageDBWrapper performs all state database operations needed for backups. 194 type storageDBWrapper struct { 195 session *mgo.Session 196 db *mgo.Database 197 metaColl *mgo.Collection 198 txnRunner jujutxn.Runner 199 modelUUID string 200 } 201 202 // newStorageDBWrapper returns a DB operator for the , with its own session. 203 func newStorageDBWrapper(db *mgo.Database, metaColl, modelUUID string) *storageDBWrapper { 204 session := db.Session.Copy() 205 db = db.With(session) 206 207 coll := db.C(metaColl) 208 txnRunner := jujutxn.NewRunner(jujutxn.RunnerParams{Database: db}) 209 dbWrap := storageDBWrapper{ 210 session: session, 211 db: db, 212 metaColl: coll, 213 txnRunner: txnRunner, 214 modelUUID: modelUUID, 215 } 216 return &dbWrap 217 } 218 219 // metadata populates doc with the document matching the ID. 220 func (b *storageDBWrapper) metadata(id string, doc interface{}) error { 221 err := b.metaColl.FindId(id).One(doc) 222 if err == mgo.ErrNotFound { 223 return errors.NotFoundf("backup metadata %q", id) 224 } 225 return errors.Trace(err) 226 } 227 228 // allMetadata populates docs with the list of documents in storage. 229 func (b *storageDBWrapper) allMetadata(docs interface{}) error { 230 err := b.metaColl.Find(nil).All(docs) 231 return errors.Trace(err) 232 } 233 234 // removeMetadataID removes the identified metadata from storage. 235 func (b *storageDBWrapper) removeMetadataID(id string) error { 236 err := b.metaColl.RemoveId(id) 237 return errors.Trace(err) 238 } 239 240 // txnOp returns a single transaction operation populated with the id 241 // and the metadata collection name. The caller should set other op 242 // values as needed. 243 func (b *storageDBWrapper) txnOpBase(id string) txn.Op { 244 op := txn.Op{ 245 C: b.metaColl.Name, 246 Id: id, 247 } 248 return op 249 } 250 251 // txnOpInsert returns a single transaction operation that will insert 252 // the document into storage. 253 func (b *storageDBWrapper) txnOpInsert(id string, doc interface{}) txn.Op { 254 op := b.txnOpBase(id) 255 op.Assert = txn.DocMissing 256 op.Insert = doc 257 return op 258 } 259 260 // txnOpInsert returns a single transaction operation that will update 261 // the already stored document. 262 func (b *storageDBWrapper) txnOpUpdate(id string, updates ...bson.DocElem) txn.Op { 263 op := b.txnOpBase(id) 264 op.Assert = txn.DocExists 265 op.Update = bson.D{{"$set", bson.D(updates)}} 266 return op 267 } 268 269 // runTransaction runs the DB operations within a single transaction. 270 func (b *storageDBWrapper) runTransaction(ops []txn.Op) error { 271 err := b.txnRunner.RunTransaction(ops) 272 return errors.Trace(err) 273 } 274 275 // blobStorage returns a ManagedStorage matching the env storage and the blobDB. 276 func (b *storageDBWrapper) blobStorage(blobDB string) blobstore.ManagedStorage { 277 dataStore := blobstore.NewGridFS(blobDB, blobDB, b.session) 278 return blobstore.NewManagedStorage(b.db, dataStore) 279 } 280 281 // Copy returns a copy of the operator. 282 func (b *storageDBWrapper) Copy() *storageDBWrapper { 283 session := b.session.Copy() 284 285 coll := b.metaColl.With(session) 286 db := coll.Database 287 txnRunner := jujutxn.NewRunner(jujutxn.RunnerParams{Database: db}) 288 dbWrap := storageDBWrapper{ 289 session: session, 290 db: db, 291 metaColl: coll, 292 txnRunner: txnRunner, 293 modelUUID: b.modelUUID, 294 } 295 return &dbWrap 296 } 297 298 // Close releases the DB connection resources. 299 func (b *storageDBWrapper) Close() error { 300 b.session.Close() 301 return nil 302 } 303 304 // getStorageMetadata returns the backup metadata associated with "id". 305 // If "id" does not match any stored records, an error satisfying 306 // juju/errors.IsNotFound() is returned. 307 func getStorageMetadata(dbWrap *storageDBWrapper, id string) (*storageMetaDoc, error) { 308 var doc storageMetaDoc 309 err := dbWrap.metadata(id, &doc) 310 if errors.IsNotFound(err) { 311 return nil, errors.Trace(err) 312 } else if err != nil { 313 return nil, errors.Annotate(err, "while getting metadata") 314 } 315 316 if err := doc.validate(); err != nil { 317 return nil, errors.Trace(err) 318 } 319 return &doc, nil 320 } 321 322 // newStorageID returns a new ID for a state backup. The format is the 323 // UTC timestamp from the metadata followed by the model ID: 324 // "YYYYMMDD-hhmmss.<model ID>". This makes the ID a little more human- 325 // consumable (in contrast to a plain UUID string). Ideally we would 326 // use some form of model name rather than the UUID, but for now 327 // the raw env ID is sufficient. 328 var newStorageID = func(doc *storageMetaDoc) string { 329 started := metadocUnixToTime(doc.Started) 330 timestamp := started.Format(backupIDTimestamp) 331 return timestamp + "." + doc.Model 332 } 333 334 // addStorageMetadata stores metadata for a backup where it can be 335 // accessed later. It returns a new ID that is associated with the 336 // backup. If the provided metadata already has an ID set, it is 337 // ignored. The new ID is set on the doc, even when there is an error. 338 func addStorageMetadata(dbWrap *storageDBWrapper, doc *storageMetaDoc) (string, error) { 339 // We use our own mongo _id value since the auto-generated one from 340 // mongo may contain sensitive data (see bson.ObjectID). 341 id := newStorageID(doc) 342 343 doc.ID = id 344 if err := doc.validate(); err != nil { 345 return "", errors.Trace(err) 346 } 347 348 op := dbWrap.txnOpInsert(id, doc) 349 350 if err := dbWrap.runTransaction([]txn.Op{op}); err != nil { 351 if errors.Cause(err) == txn.ErrAborted { 352 return "", errors.AlreadyExistsf("backup metadata %q", doc.ID) 353 } 354 return "", errors.Annotate(err, "while running transaction") 355 } 356 357 return id, nil 358 } 359 360 // setStorageStoredTime updates the backup metadata associated with "id" 361 // to indicate that a backup archive has been stored. If "id" does 362 // not match any stored records, an error satisfying 363 // juju/errors.IsNotFound() is returned. 364 func setStorageStoredTime(dbWrap *storageDBWrapper, id string, stored time.Time) error { 365 op := dbWrap.txnOpUpdate(id, bson.DocElem{"stored", metadocTimeToUnix(stored)}) 366 if err := dbWrap.runTransaction([]txn.Op{op}); err != nil { 367 if errors.Cause(err) == txn.ErrAborted { 368 return errors.NotFoundf("backup metadata %q", id) 369 } 370 return errors.Annotate(err, "while running transaction") 371 } 372 return nil 373 } 374 375 //--------------------------- 376 // metadata storage 377 378 type backupsDocStorage struct { 379 dbWrap *storageDBWrapper 380 } 381 382 type backupsMetadataStorage struct { 383 filestorage.MetadataDocStorage 384 db *mgo.Database 385 modelUUID string 386 } 387 388 func newMetadataStorage(dbWrap *storageDBWrapper) *backupsMetadataStorage { 389 dbWrap = dbWrap.Copy() 390 391 docStor := backupsDocStorage{dbWrap} 392 stor := backupsMetadataStorage{ 393 MetadataDocStorage: filestorage.MetadataDocStorage{&docStor}, 394 db: dbWrap.db, 395 modelUUID: dbWrap.modelUUID, 396 } 397 return &stor 398 } 399 400 // AddDoc adds the document to storage and returns the new ID. 401 func (s *backupsDocStorage) AddDoc(doc filestorage.Document) (string, error) { 402 metadata, ok := doc.(*Metadata) 403 if !ok { 404 return "", errors.Errorf("doc must be of type *backups.Metadata") 405 } 406 metaDoc := newStorageMetaDoc(metadata) 407 408 dbWrap := s.dbWrap.Copy() 409 defer dbWrap.Close() 410 411 id, err := addStorageMetadata(dbWrap, &metaDoc) 412 return id, errors.Trace(err) 413 } 414 415 // Doc returns the stored document associated with the given ID. 416 func (s *backupsDocStorage) Doc(id string) (filestorage.Document, error) { 417 dbWrap := s.dbWrap.Copy() 418 defer dbWrap.Close() 419 420 doc, err := getStorageMetadata(dbWrap, id) 421 if err != nil { 422 return nil, errors.Trace(err) 423 } 424 425 metadata := docAsMetadata(doc) 426 return metadata, nil 427 } 428 429 // ListDocs returns the list of all stored documents. 430 func (s *backupsDocStorage) ListDocs() ([]filestorage.Document, error) { 431 dbWrap := s.dbWrap.Copy() 432 defer dbWrap.Close() 433 434 var docs []storageMetaDoc 435 if err := dbWrap.allMetadata(&docs); err != nil { 436 return nil, errors.Trace(err) 437 } 438 439 list := make([]filestorage.Document, len(docs)) 440 for i, doc := range docs { 441 meta := docAsMetadata(&doc) 442 list[i] = meta 443 } 444 return list, nil 445 } 446 447 // RemoveDoc removes the identified document from storage. 448 func (s *backupsDocStorage) RemoveDoc(id string) error { 449 dbWrap := s.dbWrap.Copy() 450 defer dbWrap.Close() 451 452 return errors.Trace(dbWrap.removeMetadataID(id)) 453 } 454 455 // Close releases the DB resources. 456 func (s *backupsDocStorage) Close() error { 457 return s.dbWrap.Close() 458 } 459 460 // SetStored records in the metadata the fact that the file was stored. 461 func (s *backupsMetadataStorage) SetStored(id string) error { 462 dbWrap := newStorageDBWrapper(s.db, storageMetaName, s.modelUUID) 463 defer dbWrap.Close() 464 465 // TODO(perrito666) 2016-05-02 lp:1558657 466 err := setStorageStoredTime(dbWrap, id, time.Now()) 467 return errors.Trace(err) 468 } 469 470 //--------------------------- 471 // raw file storage 472 473 const backupStorageRoot = "backups" 474 475 type backupBlobStorage struct { 476 dbWrap *storageDBWrapper 477 478 modelUUID string 479 storeImpl blobstore.ManagedStorage 480 root string 481 } 482 483 func newFileStorage(dbWrap *storageDBWrapper, root string) filestorage.RawFileStorage { 484 dbWrap = dbWrap.Copy() 485 486 managed := dbWrap.blobStorage(dbWrap.db.Name) 487 stor := backupBlobStorage{ 488 dbWrap: dbWrap, 489 modelUUID: dbWrap.modelUUID, 490 storeImpl: managed, 491 root: root, 492 } 493 return &stor 494 } 495 496 func (s *backupBlobStorage) path(id string) string { 497 // Use of path.Join instead of filepath.Join is intentional - this 498 // is an model storage path not a filesystem path. 499 return path.Join(s.root, id) 500 } 501 502 // File returns the identified file from storage. 503 func (s *backupBlobStorage) File(id string) (io.ReadCloser, error) { 504 file, _, err := s.storeImpl.GetForBucket(s.modelUUID, s.path(id)) 505 return file, errors.Trace(err) 506 } 507 508 // AddFile adds the file to storage. 509 func (s *backupBlobStorage) AddFile(id string, file io.Reader, size int64) error { 510 return s.storeImpl.PutForBucket(s.modelUUID, s.path(id), file, size) 511 } 512 513 // RemoveFile removes the identified file from storage. 514 func (s *backupBlobStorage) RemoveFile(id string) error { 515 return s.storeImpl.RemoveForBucket(s.modelUUID, s.path(id)) 516 } 517 518 // Close closes the storage. 519 func (s *backupBlobStorage) Close() error { 520 return s.dbWrap.Close() 521 } 522 523 //--------------------------- 524 // backup storage 525 526 const ( 527 storageDBName = "backups" 528 storageMetaName = "metadata" 529 ) 530 531 // DB represents the set of methods required to perform a backup. 532 // It exists to break the strict dependency between state and this package, 533 // and those that depend on this package. 534 type DB interface { 535 // MongoSession returns the underlying mongodb session. 536 MongoSession() *mgo.Session 537 538 // ModelTag is the concrete model tag for this database. 539 ModelTag() names.ModelTag 540 541 // ModelConfig is the config of the model being backedup. 542 ModelConfig() (*config.Config, error) 543 544 // ControllerConfig is the config of the controller being backedup. 545 ControllerConfig() (controller.Config, error) 546 547 // StateServingInfo is the secrets of the controller. 548 StateServingInfo() (state.StateServingInfo, error) 549 } 550 551 // NewStorage returns a new FileStorage to use for storing backup 552 // archives (and metadata). 553 func NewStorage(st DB) filestorage.FileStorage { 554 modelUUID := st.ModelTag().Id() 555 db := st.MongoSession().DB(storageDBName) 556 dbWrap := newStorageDBWrapper(db, storageMetaName, modelUUID) 557 defer dbWrap.Close() 558 559 files := newFileStorage(dbWrap, backupStorageRoot) 560 docs := newMetadataStorage(dbWrap) 561 return filestorage.NewFileStorage(docs, files) 562 }