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  }