github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/state/toolstorage/tools.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package toolstorage
     5  
     6  import (
     7  	"fmt"
     8  	"io"
     9  
    10  	"github.com/juju/blobstore"
    11  	"github.com/juju/errors"
    12  	"github.com/juju/loggo"
    13  	jujutxn "github.com/juju/txn"
    14  	"gopkg.in/mgo.v2"
    15  	"gopkg.in/mgo.v2/bson"
    16  	"gopkg.in/mgo.v2/txn"
    17  
    18  	"github.com/juju/juju/version"
    19  )
    20  
    21  var logger = loggo.GetLogger("juju.state.toolstorage")
    22  
    23  type toolsStorage struct {
    24  	envUUID            string
    25  	managedStorage     blobstore.ManagedStorage
    26  	metadataCollection *mgo.Collection
    27  	txnRunner          jujutxn.Runner
    28  }
    29  
    30  var _ Storage = (*toolsStorage)(nil)
    31  
    32  // NewStorage constructs a new Storage that stores tools tarballs
    33  // in the provided ManagedStorage, and tools metadata in the provided
    34  // collection using the provided transaction runner.
    35  func NewStorage(
    36  	envUUID string,
    37  	managedStorage blobstore.ManagedStorage,
    38  	metadataCollection *mgo.Collection,
    39  	runner jujutxn.Runner,
    40  ) Storage {
    41  	return &toolsStorage{
    42  		envUUID:            envUUID,
    43  		managedStorage:     managedStorage,
    44  		metadataCollection: metadataCollection,
    45  		txnRunner:          runner,
    46  	}
    47  }
    48  
    49  func (s *toolsStorage) AddTools(r io.Reader, metadata Metadata) (resultErr error) {
    50  	// Add the tools tarball to storage.
    51  	path := toolsPath(metadata.Version, metadata.SHA256)
    52  	if err := s.managedStorage.PutForEnvironment(s.envUUID, path, r, metadata.Size); err != nil {
    53  		return errors.Annotate(err, "cannot store tools tarball")
    54  	}
    55  	defer func() {
    56  		if resultErr == nil {
    57  			return
    58  		}
    59  		err := s.managedStorage.RemoveForEnvironment(s.envUUID, path)
    60  		if err != nil {
    61  			logger.Errorf("failed to remove tools blob: %v", err)
    62  		}
    63  	}()
    64  
    65  	newDoc := toolsMetadataDoc{
    66  		Id:      metadata.Version.String(),
    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
    74  	// existing path so we 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 tools.
    83  		// Subsequent attempts to add tools 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.toolsMetadata(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 tools metadata")
   111  	}
   112  
   113  	if oldPath != "" && oldPath != path {
   114  		// Attempt to remove the old path. Failure is non-fatal.
   115  		err := s.managedStorage.RemoveForEnvironment(s.envUUID, oldPath)
   116  		if err != nil {
   117  			logger.Errorf("failed to remove old tools blob: %v", err)
   118  		} else {
   119  			logger.Debugf("removed old tools blob")
   120  		}
   121  	}
   122  	return nil
   123  }
   124  
   125  func (s *toolsStorage) Tools(v version.Binary) (Metadata, io.ReadCloser, error) {
   126  	metadataDoc, err := s.toolsMetadata(v)
   127  	if err != nil {
   128  		return Metadata{}, nil, err
   129  	}
   130  	tools, err := s.toolsTarball(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, tools, nil
   140  }
   141  
   142  func (s *toolsStorage) Metadata(v version.Binary) (Metadata, error) {
   143  	metadataDoc, err := s.toolsMetadata(v)
   144  	if err != nil {
   145  		return Metadata{}, err
   146  	}
   147  	metadata := Metadata{
   148  		Version: metadataDoc.Version,
   149  		Size:    metadataDoc.Size,
   150  		SHA256:  metadataDoc.SHA256,
   151  	}
   152  	return metadata, nil
   153  }
   154  
   155  func (s *toolsStorage) AllMetadata() ([]Metadata, error) {
   156  	var docs []toolsMetadataDoc
   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  		metadata := Metadata{
   163  			Version: doc.Version,
   164  			Size:    doc.Size,
   165  			SHA256:  doc.SHA256,
   166  		}
   167  		list[i] = metadata
   168  	}
   169  	return list, nil
   170  }
   171  
   172  type toolsMetadataDoc struct {
   173  	Id      string         `bson:"_id"`
   174  	Version version.Binary `bson:"version"`
   175  	Size    int64          `bson:"size"`
   176  	SHA256  string         `bson:"sha256,omitempty"`
   177  	Path    string         `bson:"path"`
   178  }
   179  
   180  func (s *toolsStorage) toolsMetadata(v version.Binary) (toolsMetadataDoc, error) {
   181  	var doc toolsMetadataDoc
   182  	err := s.metadataCollection.Find(bson.D{{"_id", v.String()}}).One(&doc)
   183  	if err == mgo.ErrNotFound {
   184  		return doc, errors.NotFoundf("%v tools metadata", v)
   185  	} else if err != nil {
   186  		return doc, err
   187  	}
   188  	return doc, nil
   189  }
   190  
   191  func (s *toolsStorage) toolsTarball(path string) (io.ReadCloser, error) {
   192  	r, _, err := s.managedStorage.GetForEnvironment(s.envUUID, path)
   193  	return r, err
   194  }
   195  
   196  // toolsPath returns the storage path for the specified tools.
   197  func toolsPath(v version.Binary, hash string) string {
   198  	return fmt.Sprintf("tools/%s-%s", v, hash)
   199  }