github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/state/backups/backups.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  /*
     5  Package backups contains all the stand-alone backup-related
     6  functionality for juju state. That functionality is encapsulated by
     7  the backups.Backups type. The package also exposes a few key helpers
     8  and components.
     9  
    10  Backups are not a part of juju state nor of normal state operations.
    11  However, they certainly are tightly coupled with state (the very
    12  subject of backups). This puts backups in an odd position, particularly
    13  with regard to the storage of backup metadata and archives.
    14  
    15  As noted above backups are about state but not a part of state. So
    16  exposing backup-related methods on State would imply the wrong thing.
    17  Thus most of the functionality here is defined at a high level without
    18  relation to state. A few low-level parts or helpers are exposed as
    19  functions to which you pass a state value. Those are kept to a minimum.
    20  
    21  Note that state (and juju as a whole) currently does not have a
    22  persistence layer abstraction to facilitate separating different
    23  persistence needs and implementations. As a consequence, state's
    24  data, whether about how an model should look or about existing
    25  resources within an model, is dumped essentially straight into
    26  State's mongo connection. The code in the state package does not
    27  make any distinction between the two (nor does the package clearly
    28  distinguish between state-related abstractions and state-related
    29  data).
    30  
    31  Backups add yet another category, merely taking advantage of State's
    32  mongo for storage. In the interest of making the distinction clear,
    33  among other reasons, backups uses its own database under state's mongo
    34  connection.
    35  */
    36  package backups
    37  
    38  import (
    39  	"io"
    40  	"os"
    41  	"path"
    42  	"strings"
    43  	"time"
    44  
    45  	"github.com/juju/errors"
    46  	"github.com/juju/loggo"
    47  	"github.com/juju/utils/filestorage"
    48  	"gopkg.in/juju/names.v2"
    49  )
    50  
    51  const (
    52  	// FilenamePrefix is the prefix used for backup archive files.
    53  	FilenamePrefix = "juju-backup-"
    54  
    55  	// FilenameTemplate is used with time.Time.Format to generate a filename.
    56  	FilenameTemplate = FilenamePrefix + "20060102-150405.tar.gz"
    57  )
    58  
    59  var logger = loggo.GetLogger("juju.state.backups")
    60  
    61  var (
    62  	getFilesToBackUp = GetFilesToBackUp
    63  	getDBDumper      = NewDBDumper
    64  	runCreate        = create
    65  	finishMeta       = func(meta *Metadata, result *createResult) error {
    66  		return meta.MarkComplete(result.size, result.checksum)
    67  	}
    68  	storeArchive = StoreArchive
    69  )
    70  
    71  // StoreArchive sends the backup archive and its metadata to storage.
    72  // It also sets the metadata's ID and Stored values.
    73  func StoreArchive(stor filestorage.FileStorage, meta *Metadata, file io.Reader) error {
    74  	id, err := stor.Add(meta, file)
    75  	if err != nil {
    76  		return errors.Trace(err)
    77  	}
    78  	meta.SetID(id)
    79  	stored, err := stor.Metadata(id)
    80  	if err != nil {
    81  		return errors.Trace(err)
    82  	}
    83  	meta.SetStored(stored.Stored())
    84  	return nil
    85  }
    86  
    87  // Backups is an abstraction around all juju backup-related functionality.
    88  type Backups interface {
    89  	// Create creates a new juju backup archive. It updates
    90  	// the provided metadata.
    91  	Create(meta *Metadata, paths *Paths, dbInfo *DBInfo, keepCopy, noDownload bool) (string, error)
    92  
    93  	// Add stores the backup archive and returns its new ID.
    94  	Add(archive io.Reader, meta *Metadata) (string, error)
    95  
    96  	// Get returns the metadata and archive file associated with the ID.
    97  	Get(id string) (*Metadata, io.ReadCloser, error)
    98  
    99  	// List returns the metadata for all stored backups.
   100  	List() ([]*Metadata, error)
   101  
   102  	// Remove deletes the backup from storage.
   103  	Remove(id string) error
   104  
   105  	// Restore updates juju's state to the contents of the backup archive,
   106  	// it returns the tag string for the machine where the backup originated
   107  	// or error if the process fails.
   108  	Restore(backupId string, args RestoreArgs) (names.Tag, error)
   109  }
   110  
   111  type backups struct {
   112  	storage filestorage.FileStorage
   113  }
   114  
   115  // NewBackups creates a new Backups value using the FileStorage provided.
   116  func NewBackups(stor filestorage.FileStorage) Backups {
   117  	b := backups{
   118  		storage: stor,
   119  	}
   120  	return &b
   121  }
   122  
   123  // Create creates and stores a new juju backup archive (based on arguments)
   124  // and updates the provided metadata.  A filename to download the backup is provided.
   125  func (b *backups) Create(meta *Metadata, paths *Paths, dbInfo *DBInfo, keepCopy, noDownload bool) (string, error) {
   126  	// TODO(fwereade): 2016-03-17 lp:1558657
   127  	meta.Started = time.Now().UTC()
   128  
   129  	// The metadata file will not contain the ID or the "finished" data.
   130  	// However, that information is not as critical. The alternatives
   131  	// are either adding the metadata file to the archive after the fact
   132  	// or adding placeholders here for the finished data and filling
   133  	// them in afterward.  Neither is particularly trivial.
   134  	metadataFile, err := meta.AsJSONBuffer()
   135  	if err != nil {
   136  		return "", errors.Annotate(err, "while preparing the metadata")
   137  	}
   138  
   139  	// Create the archive.
   140  	filesToBackUp, err := getFilesToBackUp("", paths, meta.Origin.Machine)
   141  	if err != nil {
   142  		return "", errors.Annotate(err, "while listing files to back up")
   143  	}
   144  
   145  	dumper, err := getDBDumper(dbInfo)
   146  	if err != nil {
   147  		return "", errors.Annotate(err, "while preparing for DB dump")
   148  	}
   149  
   150  	args := createArgs{paths.BackupDir, filesToBackUp, dumper, metadataFile, noDownload}
   151  	result, err := runCreate(&args)
   152  	if err != nil {
   153  		return "", errors.Annotate(err, "while creating backup archive")
   154  	}
   155  	defer result.archiveFile.Close()
   156  
   157  	// Finalize the metadata.
   158  	err = finishMeta(meta, result)
   159  	if err != nil {
   160  		return "", errors.Annotate(err, "while updating metadata")
   161  	}
   162  
   163  	// Store the archive if asked by user
   164  	if keepCopy {
   165  		err = storeArchive(b.storage, meta, result.archiveFile)
   166  		if err != nil {
   167  			return "", errors.Annotate(err, "while storing backup archive")
   168  		}
   169  	}
   170  
   171  	return result.filename, nil
   172  }
   173  
   174  // Add stores the backup archive and returns its new ID.
   175  func (b *backups) Add(archive io.Reader, meta *Metadata) (string, error) {
   176  	// Store the archive.
   177  	err := storeArchive(b.storage, meta, archive)
   178  	if err != nil {
   179  		return "", errors.Annotate(err, "while storing backup archive")
   180  	}
   181  
   182  	return meta.ID(), nil
   183  }
   184  
   185  // Get retrieves the associated metadata and archive file from model storage.
   186  // There are two cases, the archive file can be in the juju database or
   187  // a file on the machine.
   188  func (b *backups) Get(id string) (*Metadata, io.ReadCloser, error) {
   189  	if strings.Contains(id, TempFilename) {
   190  		return b.getArchiveFromFilename(id)
   191  	}
   192  	rawmeta, archiveFile, err := b.storage.Get(id)
   193  	if err != nil {
   194  		return nil, nil, errors.Trace(err)
   195  	}
   196  
   197  	meta, ok := rawmeta.(*Metadata)
   198  	if !ok {
   199  		return nil, nil, errors.New("did not get a backups.Metadata value from storage")
   200  	}
   201  
   202  	return meta, archiveFile, nil
   203  }
   204  
   205  func (b *backups) getArchiveFromFilename(name string) (_ *Metadata, _ io.ReadCloser, err error) {
   206  	dir, _ := path.Split(name)
   207  	build := builder{rootDir: dir}
   208  	defer func() {
   209  		if err2 := build.removeRootDir(); err2 != nil {
   210  			logger.Errorf(err2.Error())
   211  			if err != nil {
   212  				err = errors.Annotatef(err2, "getArchiveFromFilename(%s)", name)
   213  			}
   214  		}
   215  	}()
   216  
   217  	readCloser, err := os.Open(name)
   218  	if err != nil {
   219  		return nil, nil, errors.Annotate(err, "while opening archive file for download")
   220  	}
   221  
   222  	meta, err := BuildMetadata(readCloser)
   223  	if err != nil {
   224  		return nil, nil, errors.Annotate(err, "while creating metadata for archive file to download")
   225  	}
   226  
   227  	// BuildMetadata copied readCloser, so reset handle to beginning of the file
   228  	_, err = readCloser.Seek(0, io.SeekStart)
   229  	if err != nil {
   230  		return nil, nil, errors.Annotate(err, "while resetting archive file to download")
   231  	}
   232  
   233  	return meta, readCloser, nil
   234  }
   235  
   236  // List returns the metadata for all stored backups.
   237  func (b *backups) List() ([]*Metadata, error) {
   238  	metaList, err := b.storage.List()
   239  	if err != nil {
   240  		return nil, errors.Trace(err)
   241  	}
   242  	result := make([]*Metadata, len(metaList))
   243  	for i, meta := range metaList {
   244  		m, ok := meta.(*Metadata)
   245  		if !ok {
   246  			msg := "expected backups.Metadata value from storage for %q, got %T"
   247  			return nil, errors.Errorf(msg, meta.ID(), meta)
   248  		}
   249  		result[i] = m
   250  	}
   251  	return result, nil
   252  }
   253  
   254  // Remove deletes the backup from storage.
   255  func (b *backups) Remove(id string) error {
   256  	return errors.Trace(b.storage.Remove(id))
   257  }