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

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package binarystorage
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  
    10  	"github.com/juju/errors"
    11  	"github.com/juju/loggo"
    12  	jujutxn "github.com/juju/txn"
    13  	"gopkg.in/juju/blobstore.v2"
    14  	"gopkg.in/mgo.v2"
    15  	"gopkg.in/mgo.v2/bson"
    16  	"gopkg.in/mgo.v2/txn"
    17  
    18  	"github.com/juju/juju/mongo"
    19  )
    20  
    21  var logger = loggo.GetLogger("juju.state.binarystorage")
    22  
    23  type binaryStorage struct {
    24  	modelUUID          string
    25  	managedStorage     blobstore.ManagedStorage
    26  	metadataCollection mongo.Collection
    27  	txnRunner          jujutxn.Runner
    28  }
    29  
    30  var _ Storage = (*binaryStorage)(nil)
    31  
    32  // New constructs a new Storage that stores binary files in the provided
    33  // ManagedStorage, and metadata in the provided collection using the provided
    34  // transaction runner.
    35  func New(
    36  	modelUUID string,
    37  	managedStorage blobstore.ManagedStorage,
    38  	metadataCollection mongo.Collection,
    39  	runner jujutxn.Runner,
    40  ) Storage {
    41  	return &binaryStorage{
    42  		modelUUID:          modelUUID,
    43  		managedStorage:     managedStorage,
    44  		metadataCollection: metadataCollection,
    45  		txnRunner:          runner,
    46  	}
    47  }
    48  
    49  // Add implements Storage.Add.
    50  func (s *binaryStorage) Add(r io.Reader, metadata Metadata) (resultErr error) {
    51  	// Add the binary file to storage.
    52  	path := fmt.Sprintf("tools/%s-%s", metadata.Version, metadata.SHA256)
    53  	if err := s.managedStorage.PutForBucket(s.modelUUID, path, r, metadata.Size); err != nil {
    54  		return errors.Annotate(err, "cannot store binary file")
    55  	}
    56  	defer func() {
    57  		if resultErr == nil {
    58  			return
    59  		}
    60  		err := s.managedStorage.RemoveForBucket(s.modelUUID, path)
    61  		if err != nil {
    62  			logger.Errorf("failed to remove binary blob: %v", err)
    63  		}
    64  	}()
    65  
    66  	newDoc := metadataDoc{
    67  		Id:      metadata.Version,
    68  		Version: metadata.Version,
    69  		Size:    metadata.Size,
    70  		SHA256:  metadata.SHA256,
    71  		Path:    path,
    72  	}
    73  
    74  	// Add or replace metadata. If replacing, record the existing path so we
    75  	// can remove it later.
    76  	var oldPath string
    77  	buildTxn := func(attempt int) ([]txn.Op, error) {
    78  		op := txn.Op{
    79  			C:  s.metadataCollection.Name(),
    80  			Id: newDoc.Id,
    81  		}
    82  
    83  		// On the first attempt we assume we're adding new binary files.
    84  		// Subsequent attempts to add files will fetch the existing
    85  		// doc, record the old path, and attempt to update the
    86  		// size, path and hash fields.
    87  		if attempt == 0 {
    88  			op.Assert = txn.DocMissing
    89  			op.Insert = &newDoc
    90  		} else {
    91  			oldDoc, err := s.findMetadata(metadata.Version)
    92  			if err != nil {
    93  				return nil, err
    94  			}
    95  			oldPath = oldDoc.Path
    96  			op.Assert = bson.D{{"path", oldPath}}
    97  			if oldPath != path {
    98  				op.Update = bson.D{{
    99  					"$set", bson.D{
   100  						{"size", metadata.Size},
   101  						{"sha256", metadata.SHA256},
   102  						{"path", path},
   103  					},
   104  				}}
   105  			}
   106  		}
   107  		return []txn.Op{op}, nil
   108  	}
   109  	err := s.txnRunner.Run(buildTxn)
   110  	if err != nil {
   111  		return errors.Annotate(err, "cannot store binary metadata")
   112  	}
   113  
   114  	if oldPath != "" && oldPath != path {
   115  		// Attempt to remove the old path. Failure is non-fatal.
   116  		err := s.managedStorage.RemoveForBucket(s.modelUUID, oldPath)
   117  		if err != nil {
   118  			logger.Errorf("failed to remove old binary blob: %v", err)
   119  		} else {
   120  			logger.Debugf("removed old binary blob")
   121  		}
   122  	}
   123  	return nil
   124  }
   125  
   126  func (s *binaryStorage) Open(version string) (Metadata, io.ReadCloser, error) {
   127  	metadataDoc, err := s.findMetadata(version)
   128  	if err != nil {
   129  		return Metadata{}, nil, err
   130  	}
   131  	r, _, err := s.managedStorage.GetForBucket(s.modelUUID, metadataDoc.Path)
   132  	if err != nil {
   133  		return Metadata{}, nil, err
   134  	}
   135  	metadata := Metadata{
   136  		Version: metadataDoc.Version,
   137  		Size:    metadataDoc.Size,
   138  		SHA256:  metadataDoc.SHA256,
   139  	}
   140  	return metadata, r, nil
   141  }
   142  
   143  func (s *binaryStorage) Metadata(version string) (Metadata, error) {
   144  	metadataDoc, err := s.findMetadata(version)
   145  	if err != nil {
   146  		return Metadata{}, err
   147  	}
   148  	return Metadata{
   149  		Version: metadataDoc.Version,
   150  		Size:    metadataDoc.Size,
   151  		SHA256:  metadataDoc.SHA256,
   152  	}, nil
   153  }
   154  
   155  func (s *binaryStorage) AllMetadata() ([]Metadata, error) {
   156  	var docs []metadataDoc
   157  	if err := s.metadataCollection.Find(nil).All(&docs); err != nil {
   158  		return nil, err
   159  	}
   160  	list := make([]Metadata, len(docs))
   161  	for i, doc := range docs {
   162  		list[i] = Metadata{
   163  			Version: doc.Version,
   164  			Size:    doc.Size,
   165  			SHA256:  doc.SHA256,
   166  		}
   167  	}
   168  	return list, nil
   169  }
   170  
   171  type metadataDoc struct {
   172  	Id      string `bson:"_id"`
   173  	Version string `bson:"version"`
   174  	Size    int64  `bson:"size"`
   175  	SHA256  string `bson:"sha256,omitempty"`
   176  	Path    string `bson:"path"`
   177  }
   178  
   179  func (s *binaryStorage) findMetadata(version string) (metadataDoc, error) {
   180  	var doc metadataDoc
   181  	err := s.metadataCollection.FindId(version).One(&doc)
   182  	if err == mgo.ErrNotFound {
   183  		return doc, errors.NotFoundf("%v binary metadata", version)
   184  	}
   185  	return doc, err
   186  }