github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/state/backups/metadata.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  	"bytes"
     8  	"crypto/sha1"
     9  	"encoding/base64"
    10  	"encoding/json"
    11  	"io"
    12  	"os"
    13  	"time"
    14  
    15  	"github.com/juju/errors"
    16  	jujuversion "github.com/juju/juju/version"
    17  	"github.com/juju/utils/filestorage"
    18  	"github.com/juju/version"
    19  )
    20  
    21  // checksumFormat identifies how to interpret the checksum for a backup
    22  // generated with this version of juju.
    23  const checksumFormat = "SHA-1, base64 encoded"
    24  
    25  // Origin identifies where a backup archive came from.  While it is
    26  // more about where and Metadata about what and when, that distinction
    27  // does not merit special consideration.  Instead, Origin exists
    28  // separately from Metadata due to its use as an argument when
    29  // requesting the creation of a new backup.
    30  type Origin struct {
    31  	Model    string
    32  	Machine  string
    33  	Hostname string
    34  	Version  version.Number
    35  	Series   string
    36  }
    37  
    38  // UnknownString is a marker value for string fields with unknown values.
    39  const UnknownString = "<unknown>"
    40  
    41  // UnknownVersion is a marker value for version fields with unknown values.
    42  var UnknownVersion = version.MustParse("9999.9999.9999")
    43  
    44  // UnknownOrigin returns a new backups origin with unknown values.
    45  func UnknownOrigin() Origin {
    46  	return Origin{
    47  		Model:    UnknownString,
    48  		Machine:  UnknownString,
    49  		Hostname: UnknownString,
    50  		Version:  UnknownVersion,
    51  	}
    52  }
    53  
    54  // Metadata contains the metadata for a single state backup archive.
    55  type Metadata struct {
    56  	*filestorage.FileMetadata
    57  
    58  	// Started records when the backup was started.
    59  	Started time.Time
    60  
    61  	// Finished records when the backup was complete.
    62  	Finished *time.Time
    63  
    64  	// Origin identifies where the backup was created.
    65  	Origin Origin
    66  
    67  	// Notes is an optional user-supplied annotation.
    68  	Notes string
    69  
    70  	// TODO(wallyworld) - remove these ASAP
    71  	// These are only used by the restore CLI when re-bootstrapping.
    72  	// We will use a better solution but the way restore currently
    73  	// works, we need them and they are no longer available via
    74  	// bootstrap config. We will need to ifx how re-bootstrap deals
    75  	// with these keys to address the issue.
    76  
    77  	// CACert is the controller CA certificate.
    78  	CACert string
    79  
    80  	// CAPrivateKey is the controller CA private key.
    81  	CAPrivateKey string
    82  }
    83  
    84  // NewMetadata returns a new Metadata for a state backup archive.  Only
    85  // the start time and the version are set.
    86  func NewMetadata() *Metadata {
    87  	return &Metadata{
    88  		FileMetadata: filestorage.NewMetadata(),
    89  		// TODO(fwereade): 2016-03-17 lp:1558657
    90  		Started: time.Now().UTC(),
    91  		Origin: Origin{
    92  			Version: jujuversion.Current,
    93  		},
    94  	}
    95  }
    96  
    97  // NewMetadataState composes a new backup metadata with its origin
    98  // values set.  The model UUID comes from state.  The hostname is
    99  // retrieved from the OS.
   100  func NewMetadataState(db DB, machine, series string) (*Metadata, error) {
   101  	// hostname could be derived from the model...
   102  	hostname, err := os.Hostname()
   103  	if err != nil {
   104  		// If os.Hostname() is not working, something is woefully wrong.
   105  		// Run for the hills.
   106  		return nil, errors.Annotate(err, "could not get hostname (system unstable?)")
   107  	}
   108  
   109  	meta := NewMetadata()
   110  	meta.Origin.Model = db.ModelTag().Id()
   111  	meta.Origin.Machine = machine
   112  	meta.Origin.Hostname = hostname
   113  	meta.Origin.Series = series
   114  
   115  	si, err := db.StateServingInfo()
   116  	if err != nil {
   117  		return nil, errors.Annotate(err, "could not get server secrets")
   118  	}
   119  	controllerCfg, err := db.ControllerConfig()
   120  	if err != nil {
   121  		return nil, errors.Annotate(err, "could not get controller config")
   122  	}
   123  	meta.CACert, _ = controllerCfg.CACert()
   124  	meta.CAPrivateKey = si.CAPrivateKey
   125  	return meta, nil
   126  }
   127  
   128  // MarkComplete populates the remaining metadata values.  The default
   129  // checksum format is used.
   130  func (m *Metadata) MarkComplete(size int64, checksum string) error {
   131  	if size == 0 {
   132  		return errors.New("missing size")
   133  	}
   134  	if checksum == "" {
   135  		return errors.New("missing checksum")
   136  	}
   137  	format := checksumFormat
   138  	// TODO(fwereade): 2016-03-17 lp:1558657
   139  	finished := time.Now().UTC()
   140  
   141  	if err := m.SetFileInfo(size, checksum, format); err != nil {
   142  		return errors.Annotate(err, "unexpected failure")
   143  	}
   144  	m.Finished = &finished
   145  
   146  	return nil
   147  }
   148  
   149  type flatMetadata struct {
   150  	ID string
   151  
   152  	// file storage
   153  
   154  	Checksum       string
   155  	ChecksumFormat string
   156  	Size           int64
   157  	Stored         time.Time
   158  
   159  	// backup
   160  
   161  	Started     time.Time
   162  	Finished    time.Time
   163  	Notes       string
   164  	Environment string
   165  	Machine     string
   166  	Hostname    string
   167  	Version     version.Number
   168  	Series      string
   169  
   170  	CACert       string
   171  	CAPrivateKey string
   172  }
   173  
   174  // TODO(ericsnow) Move AsJSONBuffer to filestorage.Metadata.
   175  
   176  // AsJSONBuffer returns a bytes.Buffer containing the JSON-ified metadata.
   177  func (m *Metadata) AsJSONBuffer() (io.Reader, error) {
   178  	flat := flatMetadata{
   179  		ID: m.ID(),
   180  
   181  		Checksum:       m.Checksum(),
   182  		ChecksumFormat: m.ChecksumFormat(),
   183  		Size:           m.Size(),
   184  
   185  		Started:      m.Started,
   186  		Notes:        m.Notes,
   187  		Environment:  m.Origin.Model,
   188  		Machine:      m.Origin.Machine,
   189  		Hostname:     m.Origin.Hostname,
   190  		Version:      m.Origin.Version,
   191  		Series:       m.Origin.Series,
   192  		CACert:       m.CACert,
   193  		CAPrivateKey: m.CAPrivateKey,
   194  	}
   195  
   196  	stored := m.Stored()
   197  	if stored != nil {
   198  		flat.Stored = *stored
   199  	}
   200  
   201  	if m.Finished != nil {
   202  		flat.Finished = *m.Finished
   203  	}
   204  
   205  	var outfile bytes.Buffer
   206  	if err := json.NewEncoder(&outfile).Encode(flat); err != nil {
   207  		return nil, errors.Trace(err)
   208  	}
   209  	return &outfile, nil
   210  }
   211  
   212  // NewMetadataJSONReader extracts a new metadata from the JSON file.
   213  func NewMetadataJSONReader(in io.Reader) (*Metadata, error) {
   214  	var flat flatMetadata
   215  	if err := json.NewDecoder(in).Decode(&flat); err != nil {
   216  		return nil, errors.Trace(err)
   217  	}
   218  
   219  	meta := NewMetadata()
   220  	meta.SetID(flat.ID)
   221  
   222  	err := meta.SetFileInfo(flat.Size, flat.Checksum, flat.ChecksumFormat)
   223  	if err != nil {
   224  		return nil, errors.Trace(err)
   225  	}
   226  
   227  	if !flat.Stored.IsZero() {
   228  		meta.SetStored(&flat.Stored)
   229  	}
   230  
   231  	meta.Started = flat.Started
   232  	if !flat.Finished.IsZero() {
   233  		meta.Finished = &flat.Finished
   234  	}
   235  	meta.Notes = flat.Notes
   236  	meta.Origin = Origin{
   237  		Model:    flat.Environment,
   238  		Machine:  flat.Machine,
   239  		Hostname: flat.Hostname,
   240  		Version:  flat.Version,
   241  		Series:   flat.Series,
   242  	}
   243  
   244  	// TODO(wallyworld) - put these in a separate file.
   245  	meta.CACert = flat.CACert
   246  	meta.CAPrivateKey = flat.CAPrivateKey
   247  
   248  	return meta, nil
   249  }
   250  
   251  func fileTimestamp(fi os.FileInfo) time.Time {
   252  	timestamp := creationTime(fi)
   253  	if !timestamp.IsZero() {
   254  		return timestamp
   255  	}
   256  	// Fall back to modification time.
   257  	return fi.ModTime()
   258  }
   259  
   260  // BuildMetadata generates the metadata for a backup archive file.
   261  func BuildMetadata(file *os.File) (*Metadata, error) {
   262  
   263  	// Extract the file size.
   264  	fi, err := file.Stat()
   265  	if err != nil {
   266  		return nil, errors.Trace(err)
   267  	}
   268  	size := fi.Size()
   269  
   270  	// Extract the timestamp.
   271  	timestamp := fileTimestamp(fi)
   272  
   273  	// Get the checksum.
   274  	hasher := sha1.New()
   275  	_, err = io.Copy(hasher, file)
   276  	if err != nil {
   277  		return nil, errors.Trace(err)
   278  	}
   279  	rawsum := hasher.Sum(nil)
   280  	checksum := base64.StdEncoding.EncodeToString(rawsum)
   281  
   282  	// Build the metadata.
   283  	meta := NewMetadata()
   284  	meta.Started = time.Time{}
   285  	meta.Origin = UnknownOrigin()
   286  	err = meta.MarkComplete(size, checksum)
   287  	if err != nil {
   288  		return nil, errors.Trace(err)
   289  	}
   290  	meta.Finished = &timestamp
   291  	return meta, nil
   292  }