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