github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/state/toolstorage/tools_test.go (about)

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