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  }