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