github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/state/binarystorage/binarystorage_test.go (about)

     1  // Copyright 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package binarystorage_test
     5  
     6  import (
     7  	"bytes"
     8  	"fmt"
     9  	"io/ioutil"
    10  	"strings"
    11  	stdtesting "testing"
    12  
    13  	"github.com/juju/errors"
    14  	gitjujutesting "github.com/juju/testing"
    15  	jc "github.com/juju/testing/checkers"
    16  	jujutxn "github.com/juju/txn"
    17  	txntesting "github.com/juju/txn/testing"
    18  	"github.com/juju/version"
    19  	gc "gopkg.in/check.v1"
    20  	"gopkg.in/juju/blobstore.v2"
    21  	"gopkg.in/mgo.v2"
    22  
    23  	"github.com/juju/juju/mongo"
    24  	"github.com/juju/juju/state/binarystorage"
    25  	"github.com/juju/juju/testing"
    26  )
    27  
    28  const current = "2.0.42-trusty-amd64"
    29  
    30  func TestPackage(t *stdtesting.T) {
    31  	gc.TestingT(t)
    32  }
    33  
    34  type binaryStorageSuite struct {
    35  	testing.BaseSuite
    36  	mongo              *gitjujutesting.MgoInstance
    37  	session            *mgo.Session
    38  	storage            binarystorage.Storage
    39  	managedStorage     blobstore.ManagedStorage
    40  	metadataCollection mongo.Collection
    41  	txnRunner          jujutxn.Runner
    42  }
    43  
    44  var _ = gc.Suite(&binaryStorageSuite{})
    45  
    46  func (s *binaryStorageSuite) SetUpTest(c *gc.C) {
    47  	s.BaseSuite.SetUpTest(c)
    48  	s.mongo = &gitjujutesting.MgoInstance{}
    49  	s.mongo.Start(nil)
    50  
    51  	var err error
    52  	var closer func()
    53  	s.session, err = s.mongo.Dial()
    54  	c.Assert(err, jc.ErrorIsNil)
    55  	rs := blobstore.NewGridFS("blobstore", "blobstore", s.session)
    56  	catalogue := s.session.DB("catalogue")
    57  	s.managedStorage = blobstore.NewManagedStorage(catalogue, rs)
    58  	s.metadataCollection, closer = mongo.CollectionFromName(catalogue, "binarymetadata")
    59  	s.AddCleanup(func(*gc.C) { closer() })
    60  	s.txnRunner = jujutxn.NewRunner(jujutxn.RunnerParams{Database: catalogue})
    61  	s.storage = binarystorage.New("my-uuid", s.managedStorage, s.metadataCollection, s.txnRunner)
    62  }
    63  
    64  func (s *binaryStorageSuite) TearDownTest(c *gc.C) {
    65  	s.session.Close()
    66  	s.mongo.DestroyWithLog()
    67  	s.BaseSuite.TearDownTest(c)
    68  }
    69  
    70  func (s *binaryStorageSuite) TestAdd(c *gc.C) {
    71  	s.testAdd(c, "some-binary")
    72  }
    73  
    74  func (s *binaryStorageSuite) TestAddReplaces(c *gc.C) {
    75  	s.testAdd(c, "abc")
    76  	s.testAdd(c, "def")
    77  }
    78  
    79  func (s *binaryStorageSuite) testAdd(c *gc.C, content string) {
    80  	r := bytes.NewReader([]byte(content))
    81  	addedMetadata := binarystorage.Metadata{
    82  		Version: current,
    83  		Size:    int64(len(content)),
    84  		SHA256:  "hash(" + content + ")",
    85  	}
    86  	err := s.storage.Add(r, addedMetadata)
    87  	c.Assert(err, jc.ErrorIsNil)
    88  
    89  	metadata, rc, err := s.storage.Open(current)
    90  	c.Assert(err, jc.ErrorIsNil)
    91  	c.Assert(r, gc.NotNil)
    92  	defer rc.Close()
    93  	c.Assert(metadata, gc.Equals, addedMetadata)
    94  
    95  	data, err := ioutil.ReadAll(rc)
    96  	c.Assert(err, jc.ErrorIsNil)
    97  	c.Assert(string(data), gc.Equals, content)
    98  }
    99  
   100  func bumpVersion(v string) string {
   101  	vers := version.MustParseBinary(v)
   102  	vers.Build++
   103  	return vers.String()
   104  }
   105  
   106  func (s *binaryStorageSuite) TestAllMetadata(c *gc.C) {
   107  	metadata, err := s.storage.AllMetadata()
   108  	c.Assert(err, jc.ErrorIsNil)
   109  	c.Assert(metadata, gc.HasLen, 0)
   110  
   111  	s.addMetadataDoc(c, current, 3, "hash(abc)", "path")
   112  	metadata, err = s.storage.AllMetadata()
   113  	c.Assert(err, jc.ErrorIsNil)
   114  	c.Assert(metadata, gc.HasLen, 1)
   115  	expected := []binarystorage.Metadata{{
   116  		Version: current,
   117  		Size:    3,
   118  		SHA256:  "hash(abc)",
   119  	}}
   120  	c.Assert(metadata, jc.SameContents, expected)
   121  
   122  	alias := bumpVersion(current)
   123  	s.addMetadataDoc(c, alias, 3, "hash(abc)", "path")
   124  
   125  	metadata, err = s.storage.AllMetadata()
   126  	c.Assert(err, jc.ErrorIsNil)
   127  	c.Assert(metadata, gc.HasLen, 2)
   128  	expected = append(expected, binarystorage.Metadata{
   129  		Version: alias,
   130  		Size:    3,
   131  		SHA256:  "hash(abc)",
   132  	})
   133  	c.Assert(metadata, jc.SameContents, expected)
   134  }
   135  
   136  func (s *binaryStorageSuite) TestMetadata(c *gc.C) {
   137  	metadata, err := s.storage.Metadata(current)
   138  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   139  
   140  	s.addMetadataDoc(c, current, 3, "hash(abc)", "path")
   141  	metadata, err = s.storage.Metadata(current)
   142  	c.Assert(err, jc.ErrorIsNil)
   143  	c.Assert(metadata, gc.Equals, binarystorage.Metadata{
   144  		Version: current,
   145  		Size:    3,
   146  		SHA256:  "hash(abc)",
   147  	})
   148  }
   149  
   150  func (s *binaryStorageSuite) TestOpen(c *gc.C) {
   151  	_, _, err := s.storage.Open(current)
   152  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   153  	c.Assert(err, gc.ErrorMatches, `.* binary metadata not found`)
   154  
   155  	s.addMetadataDoc(c, current, 3, "hash(abc)", "path")
   156  	_, _, err = s.storage.Open(current)
   157  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   158  	c.Assert(err, gc.ErrorMatches, `resource at path "buckets/my-uuid/path" not found`)
   159  
   160  	err = s.managedStorage.PutForBucket("my-uuid", "path", strings.NewReader("blah"), 4)
   161  	c.Assert(err, jc.ErrorIsNil)
   162  
   163  	metadata, r, err := s.storage.Open(current)
   164  	c.Assert(err, jc.ErrorIsNil)
   165  	defer r.Close()
   166  	c.Assert(metadata, gc.Equals, binarystorage.Metadata{
   167  		Version: current,
   168  		Size:    3,
   169  		SHA256:  "hash(abc)",
   170  	})
   171  
   172  	data, err := ioutil.ReadAll(r)
   173  	c.Assert(err, jc.ErrorIsNil)
   174  	c.Assert(string(data), gc.Equals, "blah")
   175  }
   176  
   177  func (s *binaryStorageSuite) TestAddRemovesExisting(c *gc.C) {
   178  	// Add a metadata doc and a blob at a known path, then
   179  	// call Add and ensure the original blob is removed.
   180  	s.addMetadataDoc(c, current, 3, "hash(abc)", "path")
   181  	err := s.managedStorage.PutForBucket("my-uuid", "path", strings.NewReader("blah"), 4)
   182  	c.Assert(err, jc.ErrorIsNil)
   183  
   184  	addedMetadata := binarystorage.Metadata{
   185  		Version: current,
   186  		Size:    6,
   187  		SHA256:  "hash(xyzzzz)",
   188  	}
   189  	err = s.storage.Add(strings.NewReader("xyzzzz"), addedMetadata)
   190  	c.Assert(err, jc.ErrorIsNil)
   191  
   192  	// old blob should be gone
   193  	_, _, err = s.managedStorage.GetForBucket("my-uuid", "path")
   194  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   195  
   196  	s.assertMetadataAndContent(c, addedMetadata, "xyzzzz")
   197  }
   198  
   199  func (s *binaryStorageSuite) TestAddRemovesExistingRemoveFails(c *gc.C) {
   200  	// Add a metadata doc and a blob at a known path, then
   201  	// call Add and ensure that Add attempts to remove
   202  	// the original blob, but does not return an error if it
   203  	// fails.
   204  	s.addMetadataDoc(c, current, 3, "hash(abc)", "path")
   205  	err := s.managedStorage.PutForBucket("my-uuid", "path", strings.NewReader("blah"), 4)
   206  	c.Assert(err, jc.ErrorIsNil)
   207  
   208  	storage := binarystorage.New(
   209  		"my-uuid",
   210  		removeFailsManagedStorage{s.managedStorage},
   211  		s.metadataCollection,
   212  		s.txnRunner,
   213  	)
   214  	addedMetadata := binarystorage.Metadata{
   215  		Version: current,
   216  		Size:    6,
   217  		SHA256:  "hash(xyzzzz)",
   218  	}
   219  	err = storage.Add(strings.NewReader("xyzzzz"), addedMetadata)
   220  	c.Assert(err, jc.ErrorIsNil)
   221  
   222  	// old blob should still be there
   223  	r, _, err := s.managedStorage.GetForBucket("my-uuid", "path")
   224  	c.Assert(err, jc.ErrorIsNil)
   225  	r.Close()
   226  
   227  	s.assertMetadataAndContent(c, addedMetadata, "xyzzzz")
   228  }
   229  
   230  func (s *binaryStorageSuite) TestAddRemovesBlobOnFailure(c *gc.C) {
   231  	storage := binarystorage.New(
   232  		"my-uuid",
   233  		s.managedStorage,
   234  		s.metadataCollection,
   235  		errorTransactionRunner{s.txnRunner},
   236  	)
   237  	addedMetadata := binarystorage.Metadata{
   238  		Version: current,
   239  		Size:    6,
   240  		SHA256:  "hash",
   241  	}
   242  	err := storage.Add(strings.NewReader("xyzzzz"), addedMetadata)
   243  	c.Assert(err, gc.ErrorMatches, "cannot store binary metadata: Run fails")
   244  
   245  	path := fmt.Sprintf("tools/%s-%s", addedMetadata.Version, addedMetadata.SHA256)
   246  	_, _, err = s.managedStorage.GetForBucket("my-uuid", path)
   247  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   248  }
   249  
   250  func (s *binaryStorageSuite) TestAddRemovesBlobOnFailureRemoveFails(c *gc.C) {
   251  	storage := binarystorage.New(
   252  		"my-uuid",
   253  		removeFailsManagedStorage{s.managedStorage},
   254  		s.metadataCollection,
   255  		errorTransactionRunner{s.txnRunner},
   256  	)
   257  	addedMetadata := binarystorage.Metadata{
   258  		Version: current,
   259  		Size:    6,
   260  		SHA256:  "hash",
   261  	}
   262  	err := storage.Add(strings.NewReader("xyzzzz"), addedMetadata)
   263  	c.Assert(err, gc.ErrorMatches, "cannot store binary metadata: Run fails")
   264  
   265  	// blob should still be there, because the removal failed.
   266  	path := fmt.Sprintf("tools/%s-%s", addedMetadata.Version, addedMetadata.SHA256)
   267  	r, _, err := s.managedStorage.GetForBucket("my-uuid", path)
   268  	c.Assert(err, jc.ErrorIsNil)
   269  	r.Close()
   270  }
   271  
   272  func (s *binaryStorageSuite) TestAddSame(c *gc.C) {
   273  	metadata := binarystorage.Metadata{Version: current, Size: 1, SHA256: "0"}
   274  	for i := 0; i < 2; i++ {
   275  		err := s.storage.Add(strings.NewReader("0"), metadata)
   276  		c.Assert(err, jc.ErrorIsNil)
   277  		s.assertMetadataAndContent(c, metadata, "0")
   278  	}
   279  }
   280  
   281  func (s *binaryStorageSuite) TestAddConcurrent(c *gc.C) {
   282  	metadata0 := binarystorage.Metadata{Version: current, Size: 1, SHA256: "0"}
   283  	metadata1 := binarystorage.Metadata{Version: current, Size: 1, SHA256: "1"}
   284  
   285  	addMetadata := func() {
   286  		err := s.storage.Add(strings.NewReader("0"), metadata0)
   287  		c.Assert(err, jc.ErrorIsNil)
   288  		r, _, err := s.managedStorage.GetForBucket("my-uuid", fmt.Sprintf("tools/%s-0", current))
   289  		c.Assert(err, jc.ErrorIsNil)
   290  		r.Close()
   291  	}
   292  	defer txntesting.SetBeforeHooks(c, s.txnRunner, addMetadata).Check()
   293  
   294  	err := s.storage.Add(strings.NewReader("1"), metadata1)
   295  	c.Assert(err, jc.ErrorIsNil)
   296  
   297  	// Blob added in before-hook should be removed.
   298  	_, _, err = s.managedStorage.GetForBucket("my-uuid", fmt.Sprintf("tools/%s-0", current))
   299  	c.Assert(err, jc.Satisfies, errors.IsNotFound)
   300  
   301  	s.assertMetadataAndContent(c, metadata1, "1")
   302  }
   303  
   304  func (s *binaryStorageSuite) TestAddExcessiveContention(c *gc.C) {
   305  	metadata := []binarystorage.Metadata{
   306  		{Version: current, Size: 1, SHA256: "0"},
   307  		{Version: current, Size: 1, SHA256: "1"},
   308  		{Version: current, Size: 1, SHA256: "2"},
   309  		{Version: current, Size: 1, SHA256: "3"},
   310  	}
   311  
   312  	i := 1
   313  	addMetadata := func() {
   314  		err := s.storage.Add(strings.NewReader(metadata[i].SHA256), metadata[i])
   315  		c.Assert(err, jc.ErrorIsNil)
   316  		i++
   317  	}
   318  	defer txntesting.SetBeforeHooks(c, s.txnRunner, addMetadata, addMetadata, addMetadata).Check()
   319  
   320  	err := s.storage.Add(strings.NewReader(metadata[0].SHA256), metadata[0])
   321  	c.Assert(err, gc.ErrorMatches, "cannot store binary metadata: state changing too quickly; try again soon")
   322  
   323  	// There should be no blobs apart from the last one added by the before-hook.
   324  	for _, metadata := range metadata[:3] {
   325  		path := fmt.Sprintf("tools/%s-%s", metadata.Version, metadata.SHA256)
   326  		_, _, err = s.managedStorage.GetForBucket("my-uuid", path)
   327  		c.Assert(err, jc.Satisfies, errors.IsNotFound)
   328  	}
   329  
   330  	s.assertMetadataAndContent(c, metadata[3], "3")
   331  }
   332  
   333  func (s *binaryStorageSuite) addMetadataDoc(c *gc.C, v string, size int64, hash, path string) {
   334  	doc := struct {
   335  		Id      string `bson:"_id"`
   336  		Version string `bson:"version"`
   337  		Size    int64  `bson:"size"`
   338  		SHA256  string `bson:"sha256,omitempty"`
   339  		Path    string `bson:"path"`
   340  	}{
   341  		Id:      v,
   342  		Version: v,
   343  		Size:    size,
   344  		SHA256:  hash,
   345  		Path:    path,
   346  	}
   347  	err := s.metadataCollection.Writeable().Insert(&doc)
   348  	c.Assert(err, jc.ErrorIsNil)
   349  }
   350  
   351  func (s *binaryStorageSuite) assertMetadataAndContent(c *gc.C, expected binarystorage.Metadata, content string) {
   352  	metadata, r, err := s.storage.Open(expected.Version)
   353  	c.Assert(err, jc.ErrorIsNil)
   354  	defer r.Close()
   355  	c.Assert(metadata, gc.Equals, expected)
   356  
   357  	data, err := ioutil.ReadAll(r)
   358  	c.Assert(err, jc.ErrorIsNil)
   359  	c.Assert(string(data), gc.Equals, content)
   360  }
   361  
   362  type removeFailsManagedStorage struct {
   363  	blobstore.ManagedStorage
   364  }
   365  
   366  func (removeFailsManagedStorage) RemoveForBucket(uuid, path string) error {
   367  	return errors.Errorf("cannot remove %s:%s", uuid, path)
   368  }
   369  
   370  type errorTransactionRunner struct {
   371  	jujutxn.Runner
   372  }
   373  
   374  func (errorTransactionRunner) Run(transactions jujutxn.TransactionSource) error {
   375  	return errors.New("Run fails")
   376  }