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  }