1
// Copyright 2014 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
4
package toolstorage_test
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/utils/arch"
19
"github.com/juju/utils/series"
20
gc "gopkg.in/check.v1"
21
"gopkg.in/juju/blobstore.v2"
24
"github.com/juju/juju/state/toolstorage"
25
"github.com/juju/juju/testing"
26
"github.com/juju/juju/version"
29
var _ = gc.Suite(&ToolsSuite{})
30
var current = version.Binary{
31
Number: version.Current,
32
Arch: arch.HostArch(),
33
Series: series.HostSeries(),
36
func TestPackage(t *stdtesting.T) {
40
type ToolsSuite struct {
42
mongo *gitjujutesting.MgoInstance
44
storage toolstorage.Storage
45
managedStorage blobstore.ManagedStorage
46
metadataCollection *mgo.Collection
47
txnRunner jujutxn.Runner
50
func (s *ToolsSuite) SetUpTest(c *gc.C) {
51
s.BaseSuite.SetUpTest(c)
52
s.mongo = &gitjujutesting.MgoInstance{}
56
s.session, err = s.mongo.Dial()
57
c.Assert(err, jc.ErrorIsNil)
58
rs := blobstore.NewGridFS("blobstore", "my-uuid", s.session)
59
catalogue := s.session.DB("catalogue")
60
s.managedStorage = blobstore.NewManagedStorage(catalogue, rs)
61
s.metadataCollection = catalogue.C("toolsmetadata")
62
s.txnRunner = jujutxn.NewRunner(jujutxn.RunnerParams{Database: catalogue})
63
s.storage = toolstorage.NewStorage("my-uuid", s.managedStorage, s.metadataCollection, s.txnRunner)
66
func (s *ToolsSuite) TearDownTest(c *gc.C) {
68
s.mongo.DestroyWithLog()
69
s.BaseSuite.TearDownTest(c)
72
func (s *ToolsSuite) TestAddTools(c *gc.C) {
73
s.testAddTools(c, "some-tools")
76
func (s *ToolsSuite) TestAddToolsReplaces(c *gc.C) {
77
s.testAddTools(c, "abc")
78
s.testAddTools(c, "def")
81
func (s *ToolsSuite) testAddTools(c *gc.C, content string) {
82
r := bytes.NewReader([]byte(content))
83
addedMetadata := toolstorage.Metadata{
85
Size: int64(len(content)),
86
SHA256: "hash(" + content + ")",
88
err := s.storage.AddTools(r, addedMetadata)
89
c.Assert(err, jc.ErrorIsNil)
91
metadata, rc, err := s.storage.Tools(current)
92
c.Assert(err, jc.ErrorIsNil)
93
c.Assert(r, gc.NotNil)
95
c.Assert(metadata, gc.Equals, addedMetadata)
97
data, err := ioutil.ReadAll(rc)
98
c.Assert(err, jc.ErrorIsNil)
99
c.Assert(string(data), gc.Equals, content)
102
func bumpVersion(v version.Binary) version.Binary {
107
func (s *ToolsSuite) TestAllMetadata(c *gc.C) {
108
metadata, err := s.storage.AllMetadata()
109
c.Assert(err, jc.ErrorIsNil)
110
c.Assert(metadata, gc.HasLen, 0)
112
s.addMetadataDoc(c, current, 3, "hash(abc)", "path")
113
metadata, err = s.storage.AllMetadata()
114
c.Assert(err, jc.ErrorIsNil)
115
c.Assert(metadata, gc.HasLen, 1)
116
expected := []toolstorage.Metadata{{
121
c.Assert(metadata, jc.SameContents, expected)
123
alias := bumpVersion(current)
124
s.addMetadataDoc(c, alias, 3, "hash(abc)", "path")
126
metadata, err = s.storage.AllMetadata()
127
c.Assert(err, jc.ErrorIsNil)
128
c.Assert(metadata, gc.HasLen, 2)
129
expected = append(expected, toolstorage.Metadata{
134
c.Assert(metadata, jc.SameContents, expected)
137
func (s *ToolsSuite) TestMetadata(c *gc.C) {
138
metadata, err := s.storage.Metadata(current)
139
c.Assert(err, jc.Satisfies, errors.IsNotFound)
141
s.addMetadataDoc(c, current, 3, "hash(abc)", "path")
142
metadata, err = s.storage.Metadata(current)
143
c.Assert(err, jc.ErrorIsNil)
144
c.Assert(metadata, gc.Equals, toolstorage.Metadata{
151
func (s *ToolsSuite) TestTools(c *gc.C) {
152
_, _, err := s.storage.Tools(current)
153
c.Assert(err, jc.Satisfies, errors.IsNotFound)
154
c.Assert(err, gc.ErrorMatches, `.* tools metadata not found`)
156
s.addMetadataDoc(c, current, 3, "hash(abc)", "path")
157
_, _, err = s.storage.Tools(current)
158
c.Assert(err, jc.Satisfies, errors.IsNotFound)
159
c.Assert(err, gc.ErrorMatches, `resource at path "buckets/my-uuid/path" not found`)
161
err = s.managedStorage.PutForBucket("my-uuid", "path", strings.NewReader("blah"), 4)
162
c.Assert(err, jc.ErrorIsNil)
164
metadata, r, err := s.storage.Tools(current)
165
c.Assert(err, jc.ErrorIsNil)
167
c.Assert(metadata, gc.Equals, toolstorage.Metadata{
173
data, err := ioutil.ReadAll(r)
174
c.Assert(err, jc.ErrorIsNil)
175
c.Assert(string(data), gc.Equals, "blah")
178
func (s *ToolsSuite) TestAddToolsRemovesExisting(c *gc.C) {
179
// Add a metadata doc and a blob at a known path, then
180
// call AddTools and ensure the original blob is removed.
181
s.addMetadataDoc(c, current, 3, "hash(abc)", "path")
182
err := s.managedStorage.PutForBucket("my-uuid", "path", strings.NewReader("blah"), 4)
183
c.Assert(err, jc.ErrorIsNil)
185
addedMetadata := toolstorage.Metadata{
188
SHA256: "hash(xyzzzz)",
190
err = s.storage.AddTools(strings.NewReader("xyzzzz"), addedMetadata)
191
c.Assert(err, jc.ErrorIsNil)
193
// old blob should be gone
194
_, _, err = s.managedStorage.GetForBucket("my-uuid", "path")
195
c.Assert(err, jc.Satisfies, errors.IsNotFound)
197
s.assertTools(c, addedMetadata, "xyzzzz")
200
func (s *ToolsSuite) TestAddToolsRemovesExistingRemoveFails(c *gc.C) {
201
// Add a metadata doc and a blob at a known path, then
202
// call AddTools and ensure that AddTools attempts to remove
203
// the original blob, but does not return an error if it
205
s.addMetadataDoc(c, current, 3, "hash(abc)", "path")
206
err := s.managedStorage.PutForBucket("my-uuid", "path", strings.NewReader("blah"), 4)
207
c.Assert(err, jc.ErrorIsNil)
209
storage := toolstorage.NewStorage(
211
removeFailsManagedStorage{s.managedStorage},
212
s.metadataCollection,
215
addedMetadata := toolstorage.Metadata{
218
SHA256: "hash(xyzzzz)",
220
err = storage.AddTools(strings.NewReader("xyzzzz"), addedMetadata)
221
c.Assert(err, jc.ErrorIsNil)
223
// old blob should still be there
224
r, _, err := s.managedStorage.GetForBucket("my-uuid", "path")
225
c.Assert(err, jc.ErrorIsNil)
228
s.assertTools(c, addedMetadata, "xyzzzz")
231
func (s *ToolsSuite) TestAddToolsRemovesBlobOnFailure(c *gc.C) {
232
storage := toolstorage.NewStorage(
235
s.metadataCollection,
236
errorTransactionRunner{s.txnRunner},
238
addedMetadata := toolstorage.Metadata{
243
err := storage.AddTools(strings.NewReader("xyzzzz"), addedMetadata)
244
c.Assert(err, gc.ErrorMatches, "cannot store tools metadata: Run fails")
246
path := fmt.Sprintf("tools/%s-%s", addedMetadata.Version, addedMetadata.SHA256)
247
_, _, err = s.managedStorage.GetForBucket("my-uuid", path)
248
c.Assert(err, jc.Satisfies, errors.IsNotFound)
251
func (s *ToolsSuite) TestAddToolsRemovesBlobOnFailureRemoveFails(c *gc.C) {
252
storage := toolstorage.NewStorage(
254
removeFailsManagedStorage{s.managedStorage},
255
s.metadataCollection,
256
errorTransactionRunner{s.txnRunner},
258
addedMetadata := toolstorage.Metadata{
263
err := storage.AddTools(strings.NewReader("xyzzzz"), addedMetadata)
264
c.Assert(err, gc.ErrorMatches, "cannot store tools metadata: Run fails")
266
// blob should still be there, because the removal failed.
267
path := fmt.Sprintf("tools/%s-%s", addedMetadata.Version, addedMetadata.SHA256)
268
r, _, err := s.managedStorage.GetForBucket("my-uuid", path)
269
c.Assert(err, jc.ErrorIsNil)
273
func (s *ToolsSuite) TestAddToolsSame(c *gc.C) {
274
metadata := toolstorage.Metadata{Version: current, Size: 1, SHA256: "0"}
275
for i := 0; i < 2; i++ {
276
err := s.storage.AddTools(strings.NewReader("0"), metadata)
277
c.Assert(err, jc.ErrorIsNil)
278
s.assertTools(c, metadata, "0")
282
func (s *ToolsSuite) TestAddToolsConcurrent(c *gc.C) {
283
metadata0 := toolstorage.Metadata{Version: current, Size: 1, SHA256: "0"}
284
metadata1 := toolstorage.Metadata{Version: current, Size: 1, SHA256: "1"}
286
addMetadata := func() {
287
err := s.storage.AddTools(strings.NewReader("0"), metadata0)
288
c.Assert(err, jc.ErrorIsNil)
289
r, _, err := s.managedStorage.GetForBucket("my-uuid", fmt.Sprintf("tools/%s-0", current))
290
c.Assert(err, jc.ErrorIsNil)
293
defer txntesting.SetBeforeHooks(c, s.txnRunner, addMetadata).Check()
295
err := s.storage.AddTools(strings.NewReader("1"), metadata1)
296
c.Assert(err, jc.ErrorIsNil)
298
// Blob added in before-hook should be removed.
299
_, _, err = s.managedStorage.GetForBucket("my-uuid", fmt.Sprintf("tools/%s-0", current))
300
c.Assert(err, jc.Satisfies, errors.IsNotFound)
302
s.assertTools(c, metadata1, "1")
305
func (s *ToolsSuite) TestAddToolsExcessiveContention(c *gc.C) {
306
metadata := []toolstorage.Metadata{
307
{Version: current, Size: 1, SHA256: "0"},
308
{Version: current, Size: 1, SHA256: "1"},
309
{Version: current, Size: 1, SHA256: "2"},
310
{Version: current, Size: 1, SHA256: "3"},
314
addMetadata := func() {
315
err := s.storage.AddTools(strings.NewReader(metadata[i].SHA256), metadata[i])
316
c.Assert(err, jc.ErrorIsNil)
319
defer txntesting.SetBeforeHooks(c, s.txnRunner, addMetadata, addMetadata, addMetadata).Check()
321
err := s.storage.AddTools(strings.NewReader(metadata[0].SHA256), metadata[0])
322
c.Assert(err, gc.ErrorMatches, "cannot store tools metadata: state changing too quickly; try again soon")
324
// There should be no blobs apart from the last one added by the before-hook.
325
for _, metadata := range metadata[:3] {
326
path := fmt.Sprintf("tools/%s-%s", metadata.Version, metadata.SHA256)
327
_, _, err = s.managedStorage.GetForBucket("my-uuid", path)
328
c.Assert(err, jc.Satisfies, errors.IsNotFound)
331
s.assertTools(c, metadata[3], "3")
334
func (s *ToolsSuite) addMetadataDoc(c *gc.C, v version.Binary, size int64, hash, path string) {
336
Id string `bson:"_id"`
337
Version version.Binary `bson:"version"`
338
Size int64 `bson:"size"`
339
SHA256 string `bson:"sha256,omitempty"`
340
Path string `bson:"path"`
348
err := s.metadataCollection.Insert(&doc)
349
c.Assert(err, jc.ErrorIsNil)
352
func (s *ToolsSuite) assertTools(c *gc.C, expected toolstorage.Metadata, content string) {
353
metadata, r, err := s.storage.Tools(expected.Version)
354
c.Assert(err, jc.ErrorIsNil)
356
c.Assert(metadata, gc.Equals, expected)
358
data, err := ioutil.ReadAll(r)
359
c.Assert(err, jc.ErrorIsNil)
360
c.Assert(string(data), gc.Equals, content)
363
type removeFailsManagedStorage struct {
364
blobstore.ManagedStorage
367
func (removeFailsManagedStorage) RemoveForBucket(uuid, path string) error {
368
return errors.Errorf("cannot remove %s:%s", uuid, path)
371
type errorTransactionRunner struct {
375
func (errorTransactionRunner) Run(transactions jujutxn.TransactionSource) error {
376
return errors.New("Run fails")