~juju-qa/ubuntu/xenial/juju/xenial-2.0-beta3

« back to all changes in this revision

Viewing changes to src/gopkg.in/juju/charmstore.v5-unstable/internal/charmstore/addentity_test.go

  • Committer: Martin Packman
  • Date: 2016-03-30 19:31:08 UTC
  • mfrom: (1.1.41)
  • Revision ID: martin.packman@canonical.com-20160330193108-h9iz3ak334uk0z5r
Merge new upstream source 2.0~beta3

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2016 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package charmstore // import "gopkg.in/juju/charmstore.v5-unstable/internal/charmstore"
 
5
 
 
6
import (
 
7
        "archive/zip"
 
8
        "bytes"
 
9
        "fmt"
 
10
        "io"
 
11
        "io/ioutil"
 
12
        "regexp"
 
13
        "sort"
 
14
        "time"
 
15
 
 
16
        jc "github.com/juju/testing/checkers"
 
17
        gc "gopkg.in/check.v1"
 
18
        "gopkg.in/errgo.v1"
 
19
        "gopkg.in/juju/charm.v6-unstable"
 
20
        "gopkg.in/juju/charmrepo.v2-unstable/csclient/params"
 
21
 
 
22
        "gopkg.in/juju/charmstore.v5-unstable/internal/blobstore"
 
23
        "gopkg.in/juju/charmstore.v5-unstable/internal/mongodoc"
 
24
        "gopkg.in/juju/charmstore.v5-unstable/internal/router"
 
25
        "gopkg.in/juju/charmstore.v5-unstable/internal/storetesting"
 
26
)
 
27
 
 
28
type AddEntitySuite struct {
 
29
        commonSuite
 
30
}
 
31
 
 
32
var _ = gc.Suite(&AddEntitySuite{})
 
33
 
 
34
func (s *AddEntitySuite) TestAddCharmWithUser(c *gc.C) {
 
35
        store := s.newStore(c, false)
 
36
        defer store.Close()
 
37
 
 
38
        wordpress := storetesting.Charms.CharmDir("wordpress")
 
39
        url := router.MustNewResolvedURL("cs:~who/precise/wordpress-23", -1)
 
40
        err := store.AddCharmWithArchive(url, wordpress)
 
41
        c.Assert(err, gc.IsNil)
 
42
        assertBaseEntity(c, store, mongodoc.BaseURL(&url.URL), false)
 
43
}
 
44
 
 
45
func (s *AddEntitySuite) TestAddPromulgatedCharmDir(c *gc.C) {
 
46
        charmDir := storetesting.Charms.CharmDir("wordpress")
 
47
        s.checkAddCharm(c, charmDir, router.MustNewResolvedURL("~charmers/precise/wordpress-1", 1))
 
48
}
 
49
 
 
50
func (s *AddEntitySuite) TestAddPromulgatedCharmArchive(c *gc.C) {
 
51
        charmArchive := storetesting.Charms.CharmArchive(c.MkDir(), "wordpress")
 
52
        s.checkAddCharm(c, charmArchive, router.MustNewResolvedURL("~charmers/precise/wordpress-1", 1))
 
53
}
 
54
 
 
55
func (s *AddEntitySuite) TestAddUserOwnedCharmDir(c *gc.C) {
 
56
        charmDir := storetesting.Charms.CharmDir("wordpress")
 
57
        s.checkAddCharm(c, charmDir, router.MustNewResolvedURL("~charmers/precise/wordpress-1", -1))
 
58
}
 
59
 
 
60
func (s *AddEntitySuite) TestAddUserOwnedCharmArchive(c *gc.C) {
 
61
        charmArchive := storetesting.Charms.CharmArchive(c.MkDir(), "wordpress")
 
62
        s.checkAddCharm(c, charmArchive, router.MustNewResolvedURL("~charmers/precise/wordpress-1", -1))
 
63
}
 
64
 
 
65
func (s *AddEntitySuite) TestAddBundleDir(c *gc.C) {
 
66
        bundleDir := storetesting.Charms.BundleDir("wordpress-simple")
 
67
        s.checkAddBundle(c, bundleDir, router.MustNewResolvedURL("~charmers/bundle/wordpress-simple-2", 3))
 
68
}
 
69
 
 
70
func (s *AddEntitySuite) TestAddBundleArchive(c *gc.C) {
 
71
        bundleArchive, err := charm.ReadBundleArchive(
 
72
                storetesting.Charms.BundleArchivePath(c.MkDir(), "wordpress-simple"),
 
73
        )
 
74
        s.addRequiredCharms(c, bundleArchive)
 
75
        c.Assert(err, gc.IsNil)
 
76
        s.checkAddBundle(c, bundleArchive, router.MustNewResolvedURL("~charmers/bundle/wordpress-simple-2", 3))
 
77
}
 
78
 
 
79
func (s *AddEntitySuite) TestAddUserOwnedBundleDir(c *gc.C) {
 
80
        bundleDir := storetesting.Charms.BundleDir("wordpress-simple")
 
81
        s.checkAddBundle(c, bundleDir, router.MustNewResolvedURL("~charmers/bundle/wordpress-simple-1", -1))
 
82
}
 
83
 
 
84
func (s *AddEntitySuite) TestAddUserOwnedBundleArchive(c *gc.C) {
 
85
        bundleArchive, err := charm.ReadBundleArchive(
 
86
                storetesting.Charms.BundleArchivePath(c.MkDir(), "wordpress-simple"),
 
87
        )
 
88
        c.Assert(err, gc.IsNil)
 
89
        s.checkAddBundle(c, bundleArchive, router.MustNewResolvedURL("~charmers/bundle/wordpress-simple-1", -1))
 
90
}
 
91
 
 
92
func (s *AddEntitySuite) TestAddCharmWithBundleSeries(c *gc.C) {
 
93
        store := s.newStore(c, false)
 
94
        defer store.Close()
 
95
        ch := storetesting.Charms.CharmArchive(c.MkDir(), "wordpress")
 
96
        err := store.AddCharmWithArchive(router.MustNewResolvedURL("~charmers/bundle/wordpress-2", -1), ch)
 
97
        c.Assert(err, gc.ErrorMatches, `cannot read bundle archive: archive file "bundle.yaml" not found`)
 
98
}
 
99
 
 
100
func (s *AddEntitySuite) TestAddCharmWithMultiSeries(c *gc.C) {
 
101
        store := s.newStore(c, false)
 
102
        defer store.Close()
 
103
        ch := storetesting.Charms.CharmArchive(c.MkDir(), "multi-series")
 
104
        s.checkAddCharm(c, ch, router.MustNewResolvedURL("~charmers/multi-series-1", 1))
 
105
        // Make sure it can be accessed with a number of names
 
106
        e, err := store.FindBestEntity(charm.MustParseURL("~charmers/multi-series-1"), params.UnpublishedChannel, nil)
 
107
        c.Assert(err, gc.IsNil)
 
108
        c.Assert(e.URL.String(), gc.Equals, "cs:~charmers/multi-series-1")
 
109
        e, err = store.FindBestEntity(charm.MustParseURL("~charmers/trusty/multi-series-1"), params.UnpublishedChannel, nil)
 
110
        c.Assert(err, gc.IsNil)
 
111
        c.Assert(e.URL.String(), gc.Equals, "cs:~charmers/multi-series-1")
 
112
        e, err = store.FindBestEntity(charm.MustParseURL("~charmers/wily/multi-series-1"), params.UnpublishedChannel, nil)
 
113
        c.Assert(err, gc.IsNil)
 
114
        c.Assert(e.URL.String(), gc.Equals, "cs:~charmers/multi-series-1")
 
115
        _, err = store.FindBestEntity(charm.MustParseURL("~charmers/precise/multi-series-1"), params.UnpublishedChannel, nil)
 
116
        c.Assert(err, gc.ErrorMatches, "no matching charm or bundle for cs:~charmers/precise/multi-series-1")
 
117
        c.Assert(errgo.Cause(err), gc.Equals, params.ErrNotFound)
 
118
}
 
119
 
 
120
func (s *AddEntitySuite) TestAddCharmWithSeriesWhenThereIsAnExistingMultiSeriesVersion(c *gc.C) {
 
121
        store := s.newStore(c, false)
 
122
        defer store.Close()
 
123
        ch := storetesting.Charms.CharmArchive(c.MkDir(), "multi-series")
 
124
        err := store.AddCharmWithArchive(router.MustNewResolvedURL("~charmers/multi-series-1", -1), ch)
 
125
        c.Assert(err, gc.IsNil)
 
126
        ch = storetesting.Charms.CharmArchive(c.MkDir(), "wordpress")
 
127
        err = store.AddCharmWithArchive(router.MustNewResolvedURL("~charmers/trusty/multi-series-2", -1), ch)
 
128
        c.Assert(err, gc.ErrorMatches, `charm name duplicates multi-series charm name cs:~charmers/multi-series-1`)
 
129
}
 
130
 
 
131
func (s *AddEntitySuite) TestAddCharmWithMultiSeriesToES(c *gc.C) {
 
132
        store := s.newStore(c, true)
 
133
        defer store.Close()
 
134
        ch := storetesting.Charms.CharmArchive(c.MkDir(), "multi-series")
 
135
        s.checkAddCharm(c, ch, router.MustNewResolvedURL("~charmers/juju-gui-1", 1))
 
136
}
 
137
 
 
138
func (s *AddEntitySuite) TestAddBundleDuplicatingCharm(c *gc.C) {
 
139
        store := s.newStore(c, false)
 
140
        defer store.Close()
 
141
        ch := storetesting.Charms.CharmDir("wordpress")
 
142
        err := store.AddCharmWithArchive(router.MustNewResolvedURL("~tester/precise/wordpress-2", -1), ch)
 
143
        c.Assert(err, gc.IsNil)
 
144
 
 
145
        b := storetesting.Charms.BundleDir("wordpress-simple")
 
146
        s.addRequiredCharms(c, b)
 
147
        err = store.AddBundleWithArchive(router.MustNewResolvedURL("~tester/bundle/wordpress-5", -1), b)
 
148
        c.Assert(err, gc.ErrorMatches, "bundle name duplicates charm name cs:~tester/precise/wordpress-2")
 
149
}
 
150
 
 
151
func (s *AddEntitySuite) TestAddCharmDuplicatingBundle(c *gc.C) {
 
152
        store := s.newStore(c, false)
 
153
        defer store.Close()
 
154
 
 
155
        b := storetesting.Charms.BundleDir("wordpress-simple")
 
156
        s.addRequiredCharms(c, b)
 
157
        err := store.AddBundleWithArchive(router.MustNewResolvedURL("~charmers/bundle/wordpress-simple-2", -1), b)
 
158
        c.Assert(err, gc.IsNil)
 
159
 
 
160
        ch := storetesting.Charms.CharmDir("wordpress")
 
161
        err = store.AddCharmWithArchive(router.MustNewResolvedURL("~charmers/precise/wordpress-simple-5", -1), ch)
 
162
        c.Assert(err, gc.ErrorMatches, "charm name duplicates bundle name cs:~charmers/bundle/wordpress-simple-2")
 
163
}
 
164
 
 
165
var uploadEntityErrorsTests = []struct {
 
166
        about       string
 
167
        url         string
 
168
        upload      ArchiverTo
 
169
        blobHash    string
 
170
        blobSize    int64
 
171
        expectError string
 
172
        expectCause error
 
173
}{{
 
174
        about:       "revision not specified",
 
175
        url:         "~charmers/precise/wordpress",
 
176
        upload:      storetesting.NewCharm(nil),
 
177
        expectError: "entity id does not specify revision",
 
178
        expectCause: params.ErrEntityIdNotAllowed,
 
179
}, {
 
180
        about:       "user not specified",
 
181
        url:         "precise/wordpress-23",
 
182
        upload:      storetesting.NewCharm(nil),
 
183
        expectError: "entity id does not specify user",
 
184
        expectCause: params.ErrEntityIdNotAllowed,
 
185
}, {
 
186
        about:       "hash mismatch",
 
187
        url:         "~charmers/precise/wordpress-0",
 
188
        upload:      storetesting.NewCharm(nil),
 
189
        blobHash:    "blahblah",
 
190
        expectError: "cannot put archive blob: hash mismatch",
 
191
        // It would be nice if this was:
 
192
        // expectCause: params.ErrInvalidEntity,
 
193
}, {
 
194
        about:       "size mismatch",
 
195
        url:         "~charmers/precise/wordpress-0",
 
196
        upload:      storetesting.NewCharm(nil),
 
197
        blobSize:    99999,
 
198
        expectError: "cannot read charm archive: seek past end of file",
 
199
        // It would be nice if the above error was better and
 
200
        // the cause was:
 
201
        // expectCause: params.ErrInvalidEntity,
 
202
}, {
 
203
        about:       "charm uploaded to bundle URL",
 
204
        url:         "~charmers/bundle/foo-0",
 
205
        upload:      storetesting.NewCharm(nil),
 
206
        expectError: `cannot read bundle archive: archive file "bundle.yaml" not found`,
 
207
        // It would be nice if this was:
 
208
        // expectCause: params.ErrInvalidEntity,
 
209
}, {
 
210
        about: "bundle uploaded to charm URL",
 
211
        url:   "~charmers/precise/foo-0",
 
212
        upload: storetesting.NewBundle(&charm.BundleData{
 
213
                Services: map[string]*charm.ServiceSpec{
 
214
                        "foo": {
 
215
                                Charm: "foo",
 
216
                        },
 
217
                },
 
218
        }),
 
219
        expectError: `cannot read charm archive: archive file "metadata.yaml" not found`,
 
220
        // It would be nice if this was:
 
221
        // expectCause: params.ErrInvalidEntity,
 
222
}, {
 
223
        about:       "banned relation name",
 
224
        url:         "~charmers/precise/foo-0",
 
225
        upload:      storetesting.NewCharm(storetesting.RelationMeta("requires relation-name foo")),
 
226
        expectError: `relation relation-name has almost certainly not been changed from the template`,
 
227
        expectCause: params.ErrInvalidEntity,
 
228
}, {
 
229
        about:       "banned interface name",
 
230
        url:         "~charmers/precise/foo-0",
 
231
        upload:      storetesting.NewCharm(storetesting.RelationMeta("requires foo interface-name")),
 
232
        expectError: `interface interface-name in relation foo has almost certainly not been changed from the template`,
 
233
        expectCause: params.ErrInvalidEntity,
 
234
}, {
 
235
        about:       "unrecognized series",
 
236
        url:         "~charmers/precise/foo-0",
 
237
        upload:      storetesting.NewCharm(storetesting.MetaWithSupportedSeries(nil, "badseries")),
 
238
        expectError: `unrecognized series "badseries" in metadata`,
 
239
        expectCause: params.ErrInvalidEntity,
 
240
}, {
 
241
        about:       "inconsistent series",
 
242
        url:         "~charmers/trusty/foo-0",
 
243
        upload:      storetesting.NewCharm(storetesting.MetaWithSupportedSeries(nil, "trusty", "win10")),
 
244
        expectError: `cannot mix series from ubuntu and windows in single charm`,
 
245
        expectCause: params.ErrInvalidEntity,
 
246
}, {
 
247
        about:       "series not specified",
 
248
        url:         "~charmers/foo-0",
 
249
        upload:      storetesting.NewCharm(nil),
 
250
        expectError: `series not specified in url or charm metadata`,
 
251
        expectCause: params.ErrEntityIdNotAllowed,
 
252
}, {
 
253
        about:       "series not allowed by metadata",
 
254
        url:         "~charmers/precise/foo-0",
 
255
        upload:      storetesting.NewCharm(storetesting.MetaWithSupportedSeries(nil, "trusty")),
 
256
        expectError: `"precise" series not listed in charm metadata`,
 
257
        expectCause: params.ErrEntityIdNotAllowed,
 
258
}, {
 
259
        about: "bundle refers to non-existent charm",
 
260
        url:   "~charmers/bundle/foo-0",
 
261
        upload: storetesting.NewBundle(&charm.BundleData{
 
262
                Services: map[string]*charm.ServiceSpec{
 
263
                        "foo": {
 
264
                                Charm: "bad-charm",
 
265
                        },
 
266
                },
 
267
        }),
 
268
        expectError: regexp.QuoteMeta(`bundle verification failed: ["service \"foo\" refers to non-existent charm \"bad-charm\""]`),
 
269
        expectCause: params.ErrInvalidEntity,
 
270
}, {
 
271
        about:       "bundle verification fails",
 
272
        url:         "~charmers/bundle/foo-0",
 
273
        upload:      storetesting.NewBundle(&charm.BundleData{}),
 
274
        expectError: regexp.QuoteMeta(`bundle verification failed: ["at least one service must be specified"]`),
 
275
        expectCause: params.ErrInvalidEntity,
 
276
}, {
 
277
        about:       "invalid zip format",
 
278
        url:         "~charmers/foo-0",
 
279
        upload:      zipWithInvalidFormat(),
 
280
        expectError: `cannot read charm archive: zip: not a valid zip file`,
 
281
        expectCause: params.ErrInvalidEntity,
 
282
}, {
 
283
        about:       "invalid zip algorithm",
 
284
        url:         "~charmers/foo-0",
 
285
        upload:      zipWithInvalidAlgorithm(),
 
286
        expectError: `cannot read charm archive: zip: unsupported compression algorithm`,
 
287
        expectCause: params.ErrInvalidEntity,
 
288
}, {
 
289
        about:       "invalid zip checksum",
 
290
        url:         "~charmers/foo-0",
 
291
        upload:      zipWithInvalidChecksum(),
 
292
        expectError: `cannot read charm archive: zip: checksum error`,
 
293
        expectCause: params.ErrInvalidEntity,
 
294
}}
 
295
 
 
296
func (s *AddEntitySuite) TestUploadEntityErrors(c *gc.C) {
 
297
        store := s.newStore(c, true)
 
298
        defer store.Close()
 
299
        for i, test := range uploadEntityErrorsTests {
 
300
                c.Logf("test %d: %s", i, test.about)
 
301
                var buf bytes.Buffer
 
302
                err := test.upload.ArchiveTo(&buf)
 
303
                c.Assert(err, gc.IsNil)
 
304
                if test.blobHash == "" {
 
305
                        h := blobstore.NewHash()
 
306
                        h.Write(buf.Bytes())
 
307
                        test.blobHash = fmt.Sprintf("%x", h.Sum(nil))
 
308
                }
 
309
                if test.blobSize == 0 {
 
310
                        test.blobSize = int64(len(buf.Bytes()))
 
311
                }
 
312
                url := &router.ResolvedURL{
 
313
                        URL: *charm.MustParseURL(test.url),
 
314
                }
 
315
                err = store.UploadEntity(url, &buf, test.blobHash, test.blobSize, nil)
 
316
                c.Assert(err, gc.ErrorMatches, test.expectError)
 
317
                if test.expectCause != nil {
 
318
                        c.Assert(errgo.Cause(err), gc.Equals, test.expectCause)
 
319
                } else {
 
320
                        c.Assert(errgo.Cause(err), gc.Equals, err)
 
321
                }
 
322
        }
 
323
}
 
324
 
 
325
func (s *AddEntitySuite) checkAddCharm(c *gc.C, ch charm.Charm, url *router.ResolvedURL) {
 
326
        store := s.newStore(c, true)
 
327
        defer store.Close()
 
328
 
 
329
        // Add the charm to the store.
 
330
        beforeAdding := time.Now()
 
331
        err := store.AddCharmWithArchive(url, ch)
 
332
        c.Assert(err, gc.IsNil)
 
333
        afterAdding := time.Now()
 
334
 
 
335
        var doc *mongodoc.Entity
 
336
        err = store.DB.Entities().FindId(&url.URL).One(&doc)
 
337
        c.Assert(err, gc.IsNil)
 
338
 
 
339
        // The entity doc has been correctly added to the mongo collection.
 
340
        size, hash, hash256 := getSizeAndHashes(ch)
 
341
        sort.Strings(doc.CharmProvidedInterfaces)
 
342
        sort.Strings(doc.CharmRequiredInterfaces)
 
343
 
 
344
        // Check the upload time and then reset it to its zero value
 
345
        // so that we can test the deterministic parts later.
 
346
        c.Assert(doc.UploadTime, jc.TimeBetween(beforeAdding, afterAdding))
 
347
 
 
348
        doc.UploadTime = time.Time{}
 
349
 
 
350
        assertDoc := assertBlobFields(c, doc, url, hash, hash256, size)
 
351
        c.Assert(assertDoc, jc.DeepEquals, denormalizedEntity(&mongodoc.Entity{
 
352
                URL:                     &url.URL,
 
353
                BlobHash:                hash,
 
354
                BlobHash256:             hash256,
 
355
                Size:                    size,
 
356
                CharmMeta:               ch.Meta(),
 
357
                CharmActions:            ch.Actions(),
 
358
                CharmConfig:             ch.Config(),
 
359
                CharmProvidedInterfaces: []string{"http", "logging", "monitoring"},
 
360
                CharmRequiredInterfaces: []string{"mysql", "varnish"},
 
361
                PromulgatedURL:          url.PromulgatedURL(),
 
362
                SupportedSeries:         ch.Meta().Series,
 
363
        }))
 
364
 
 
365
        // The charm archive has been properly added to the blob store.
 
366
        r, obtainedSize, err := store.BlobStore.Open(doc.BlobName)
 
367
        c.Assert(err, gc.IsNil)
 
368
        defer r.Close()
 
369
        c.Assert(obtainedSize, gc.Equals, size)
 
370
        data, err := ioutil.ReadAll(r)
 
371
        c.Assert(err, gc.IsNil)
 
372
        charmArchive, err := charm.ReadCharmArchiveBytes(data)
 
373
        c.Assert(err, gc.IsNil)
 
374
        c.Assert(charmArchive.Meta(), jc.DeepEquals, ch.Meta())
 
375
        c.Assert(charmArchive.Config(), jc.DeepEquals, ch.Config())
 
376
        c.Assert(charmArchive.Actions(), jc.DeepEquals, ch.Actions())
 
377
        c.Assert(charmArchive.Revision(), jc.DeepEquals, ch.Revision())
 
378
 
 
379
        // Check that the base entity has been properly created.
 
380
        assertBaseEntity(c, store, mongodoc.BaseURL(&url.URL), url.PromulgatedRevision != -1)
 
381
 
 
382
        // Try inserting the charm again - it should fail because the charm is
 
383
        // already there.
 
384
        err = store.AddCharmWithArchive(url, ch)
 
385
        c.Assert(errgo.Cause(err), gc.Equals, params.ErrDuplicateUpload)
 
386
}
 
387
 
 
388
func (s *AddEntitySuite) checkAddBundle(c *gc.C, bundle charm.Bundle, url *router.ResolvedURL) {
 
389
        store := s.newStore(c, true)
 
390
        defer store.Close()
 
391
        // Add the bundle to the store.
 
392
        beforeAdding := time.Now()
 
393
        s.addRequiredCharms(c, bundle)
 
394
        err := store.AddBundleWithArchive(url, bundle)
 
395
        c.Assert(err, gc.IsNil)
 
396
        afterAdding := time.Now()
 
397
 
 
398
        var doc *mongodoc.Entity
 
399
        err = store.DB.Entities().FindId(&url.URL).One(&doc)
 
400
        c.Assert(err, gc.IsNil)
 
401
        sort.Sort(orderedURLs(doc.BundleCharms))
 
402
 
 
403
        // Check the upload time and then reset it to its zero value
 
404
        // so that we can test the deterministic parts later.
 
405
        c.Assert(doc.UploadTime, jc.TimeBetween(beforeAdding, afterAdding))
 
406
        doc.UploadTime = time.Time{}
 
407
 
 
408
        // The entity doc has been correctly added to the mongo collection.
 
409
        size, hash, hash256 := getSizeAndHashes(bundle)
 
410
 
 
411
        assertDoc := assertBlobFields(c, doc, url, hash, hash256, size)
 
412
        c.Assert(assertDoc, jc.DeepEquals, denormalizedEntity(&mongodoc.Entity{
 
413
                URL:          &url.URL,
 
414
                BlobHash:     hash,
 
415
                BlobHash256:  hash256,
 
416
                Size:         size,
 
417
                BundleData:   bundle.Data(),
 
418
                BundleReadMe: bundle.ReadMe(),
 
419
                BundleCharms: []*charm.URL{
 
420
                        charm.MustParseURL("mysql"),
 
421
                        charm.MustParseURL("wordpress"),
 
422
                },
 
423
                BundleMachineCount: newInt(2),
 
424
                BundleUnitCount:    newInt(2),
 
425
                PromulgatedURL:     url.PromulgatedURL(),
 
426
        }))
 
427
 
 
428
        // The bundle archive has been properly added to the blob store.
 
429
        r, obtainedSize, err := store.BlobStore.Open(doc.BlobName)
 
430
        c.Assert(err, gc.IsNil)
 
431
        defer r.Close()
 
432
        c.Assert(obtainedSize, gc.Equals, size)
 
433
        data, err := ioutil.ReadAll(r)
 
434
        c.Assert(err, gc.IsNil)
 
435
        bundleArchive, err := charm.ReadBundleArchiveBytes(data)
 
436
        c.Assert(err, gc.IsNil)
 
437
        c.Assert(bundleArchive.Data(), jc.DeepEquals, bundle.Data())
 
438
        c.Assert(bundleArchive.ReadMe(), jc.DeepEquals, bundle.ReadMe())
 
439
 
 
440
        // Check that the base entity has been properly created.
 
441
        assertBaseEntity(c, store, mongodoc.BaseURL(&url.URL), url.PromulgatedRevision != -1)
 
442
 
 
443
        // Try inserting the bundle again - it should fail because the bundle is
 
444
        // already there.
 
445
        err = store.AddBundleWithArchive(url, bundle)
 
446
        c.Assert(errgo.Cause(err), gc.Equals, params.ErrDuplicateUpload, gc.Commentf("error: %v", err))
 
447
}
 
448
 
 
449
// assertBlobFields asserts that the blob-related fields in doc are as expected.
 
450
// It returns a copy of doc with unpredictable fields zeroed out.
 
451
func assertBlobFields(c *gc.C, doc *mongodoc.Entity, url *router.ResolvedURL, hash, hash256 string, size int64) *mongodoc.Entity {
 
452
        doc1 := *doc
 
453
        doc = &doc1
 
454
 
 
455
        // The blob name is random, but we check that it's
 
456
        // in the correct format, and non-empty.
 
457
        blobName := doc.BlobName
 
458
        c.Assert(blobName, gc.Matches, "[0-9a-z]+")
 
459
        doc.BlobName = ""
 
460
        // The PreV5* fields are unpredictable, so zero them out
 
461
        // for the purposes of comparison.
 
462
        if doc.CharmMeta != nil && len(doc.CharmMeta.Series) > 0 {
 
463
                // It's a multi-series charm, so the PreV5* fields should be active.
 
464
                if doc.PreV5BlobSize <= doc.Size {
 
465
                        c.Fatalf("pre-v5 blobsize %d is unexpectedly less than original blob size %d", doc.PreV5BlobSize, doc.Size)
 
466
                }
 
467
                c.Assert(doc.PreV5BlobHash, gc.Not(gc.Equals), "")
 
468
                c.Assert(doc.PreV5BlobHash, gc.Not(gc.Equals), hash)
 
469
                c.Assert(doc.PreV5BlobHash256, gc.Not(gc.Equals), "")
 
470
                c.Assert(doc.PreV5BlobHash256, gc.Not(gc.Equals), hash256)
 
471
        } else {
 
472
                c.Assert(doc.PreV5BlobSize, gc.Equals, doc.Size)
 
473
                c.Assert(doc.PreV5BlobHash, gc.Equals, doc.BlobHash)
 
474
                c.Assert(doc.PreV5BlobHash256, gc.Equals, doc.BlobHash256)
 
475
        }
 
476
        doc.PreV5BlobSize = 0
 
477
        doc.PreV5BlobHash = ""
 
478
        doc.PreV5BlobHash256 = ""
 
479
        return doc
 
480
}
 
481
 
 
482
func assertBaseEntity(c *gc.C, store *Store, url *charm.URL, promulgated bool) {
 
483
        baseEntity, err := store.FindBaseEntity(url, nil)
 
484
        c.Assert(err, gc.IsNil)
 
485
        acls := mongodoc.ACL{
 
486
                Read:  []string{url.User},
 
487
                Write: []string{url.User},
 
488
        }
 
489
        expectACLs := map[params.Channel]mongodoc.ACL{
 
490
                params.StableChannel:      acls,
 
491
                params.DevelopmentChannel: acls,
 
492
                params.UnpublishedChannel: acls,
 
493
        }
 
494
        c.Assert(storetesting.NormalizeBaseEntity(baseEntity), jc.DeepEquals, storetesting.NormalizeBaseEntity(&mongodoc.BaseEntity{
 
495
                URL:         url,
 
496
                User:        url.User,
 
497
                Name:        url.Name,
 
498
                Promulgated: mongodoc.IntBool(promulgated),
 
499
                ChannelACLs: expectACLs,
 
500
        }))
 
501
}
 
502
 
 
503
type orderedURLs []*charm.URL
 
504
 
 
505
func (o orderedURLs) Less(i, j int) bool {
 
506
        return o[i].String() < o[j].String()
 
507
}
 
508
 
 
509
func (o orderedURLs) Swap(i, j int) {
 
510
        o[i], o[j] = o[j], o[i]
 
511
}
 
512
 
 
513
func (o orderedURLs) Len() int {
 
514
        return len(o)
 
515
}
 
516
 
 
517
type byteArchiver []byte
 
518
 
 
519
func (a byteArchiver) ArchiveTo(w io.Writer) error {
 
520
        _, err := w.Write(a)
 
521
        return err
 
522
}
 
523
 
 
524
func zipWithInvalidFormat() ArchiverTo {
 
525
        return byteArchiver(nil)
 
526
}
 
527
 
 
528
func zipWithInvalidChecksum() ArchiverTo {
 
529
        return byteArchiver(
 
530
                "PK\x03\x04\x14\x00\b\x00\x00\x00\x00\x00\x00" +
 
531
                        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
 
532
                        "\x00\x00\r\x00\x00\x00metadata.yamlielloPK\a\b" +
 
533
                        "\x86\xa6\x106\x05\x00\x00\x00\x05\x00\x00\x00PK" +
 
534
                        "\x01\x02\x14\x00\x14\x00\b\x00\x00\x00\x00\x00" +
 
535
                        "\x00\x00\x86\xa6\x106\x05\x00\x00\x00\x05\x00" +
 
536
                        "\x00\x00\r\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
 
537
                        "\x00\x00\x00\x00\x00\x00\x00\x00metadata.yamlPK" +
 
538
                        "\x05\x06\x00\x00\x00\x00\x01\x00\x01\x00;\x00" +
 
539
                        "\x00\x00@\x00\x00\x00\x00\x00",
 
540
        )
 
541
 
 
542
        data := storetesting.NewCharm(nil).Bytes()
 
543
        zr, err := zip.NewReader(bytes.NewReader(data), int64(len(data)))
 
544
        if err != nil {
 
545
                panic(err)
 
546
        }
 
547
        // Change the contents of a file so
 
548
        // that it won't (probably) fit the checksum
 
549
        // any more.
 
550
        off, err := zr.File[0].DataOffset()
 
551
        if err != nil {
 
552
                panic(err)
 
553
        }
 
554
        data[off] += 2
 
555
        return byteArchiver(data)
 
556
}
 
557
 
 
558
func zipWithInvalidAlgorithm() ArchiverTo {
 
559
        return byteArchiver(
 
560
                "PK\x03\x04\x14\x00\b\x00\t\x00\x00\x00" +
 
561
                        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
 
562
                        "\x00\x00\x00\x00\r\x00\x00\x00metadata.yamlhello" +
 
563
                        "PK\a\b\x86\xa6\x106\x05\x00\x00\x00\x05\x00" +
 
564
                        "\x00\x00PK\x01\x02\x14\x00\x14\x00\b\x00" +
 
565
                        "\t\x00\x00\x00\x00\x00\x86\xa6\x106\x05\x00" +
 
566
                        "\x00\x00\x05\x00\x00\x00\r\x00\x00\x00\x00" +
 
567
                        "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" +
 
568
                        "\x00\x00metadata.yamlPK\x05\x06\x00\x00\x00" +
 
569
                        "\x00\x01\x00\x01\x00;\x00\x00\x00@\x00\x00" +
 
570
                        "\x00\x00\x00",
 
571
        )
 
572
}