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