~nskaggs/+junk/xenial-test

« back to all changes in this revision

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

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2014 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package blobstore // import "gopkg.in/juju/charmstore.v5-unstable/internal/blobstore"
 
5
 
 
6
import (
 
7
        "crypto/sha512"
 
8
        "fmt"
 
9
        "hash"
 
10
        "io"
 
11
        "strconv"
 
12
 
 
13
        "github.com/juju/blobstore"
 
14
        "github.com/juju/errors"
 
15
        "gopkg.in/errgo.v1"
 
16
        "gopkg.in/mgo.v2"
 
17
)
 
18
 
 
19
type ReadSeekCloser interface {
 
20
        io.Reader
 
21
        io.Seeker
 
22
        io.Closer
 
23
}
 
24
 
 
25
// ContentChallengeError holds a proof-of-content
 
26
// challenge produced by a blobstore.
 
27
type ContentChallengeError struct {
 
28
        Req ContentChallenge
 
29
}
 
30
 
 
31
func (e *ContentChallengeError) Error() string {
 
32
        return "cannot upload because proof of content ownership is required"
 
33
}
 
34
 
 
35
// ContentChallenge holds a proof-of-content challenge
 
36
// produced by a blobstore. A client can satisfy the request
 
37
// by producing a ContentChallengeResponse containing
 
38
// the same request id and a hash of RangeLength bytes
 
39
// of the content starting at RangeStart.
 
40
type ContentChallenge struct {
 
41
        RequestId   string
 
42
        RangeStart  int64
 
43
        RangeLength int64
 
44
}
 
45
 
 
46
// ContentChallengeResponse holds a response to a ContentChallenge.
 
47
type ContentChallengeResponse struct {
 
48
        RequestId string
 
49
        Hash      string
 
50
}
 
51
 
 
52
// NewHash is used to calculate checksums for the blob store.
 
53
func NewHash() hash.Hash {
 
54
        return sha512.New384()
 
55
}
 
56
 
 
57
// NewContentChallengeResponse can be used by a client to respond to a content
 
58
// challenge. The returned value should be passed to BlobStorage.Put
 
59
// when the client retries the request.
 
60
func NewContentChallengeResponse(chal *ContentChallenge, r io.ReadSeeker) (*ContentChallengeResponse, error) {
 
61
        _, err := r.Seek(chal.RangeStart, 0)
 
62
        if err != nil {
 
63
                return nil, errgo.Mask(err)
 
64
        }
 
65
        hash := NewHash()
 
66
        nw, err := io.CopyN(hash, r, chal.RangeLength)
 
67
        if err != nil {
 
68
                return nil, errgo.Mask(err)
 
69
        }
 
70
        if nw != chal.RangeLength {
 
71
                return nil, errgo.Newf("content is not long enough")
 
72
        }
 
73
        return &ContentChallengeResponse{
 
74
                RequestId: chal.RequestId,
 
75
                Hash:      fmt.Sprintf("%x", hash.Sum(nil)),
 
76
        }, nil
 
77
}
 
78
 
 
79
// Store stores data blobs in mongodb, de-duplicating by
 
80
// blob hash.
 
81
type Store struct {
 
82
        mstore blobstore.ManagedStorage
 
83
}
 
84
 
 
85
// New returns a new blob store that writes to the given database,
 
86
// prefixing its collections with the given prefix.
 
87
func New(db *mgo.Database, prefix string) *Store {
 
88
        rs := blobstore.NewGridFS(db.Name, prefix, db.Session)
 
89
        return &Store{
 
90
                mstore: blobstore.NewManagedStorage(db, rs),
 
91
        }
 
92
}
 
93
 
 
94
func (s *Store) challengeResponse(resp *ContentChallengeResponse) error {
 
95
        id, err := strconv.ParseInt(resp.RequestId, 10, 64)
 
96
        if err != nil {
 
97
                return errgo.Newf("invalid request id %q", id)
 
98
        }
 
99
        return s.mstore.ProofOfAccessResponse(blobstore.NewPutResponse(id, resp.Hash))
 
100
}
 
101
 
 
102
// Put tries to stream the content from the given reader into blob
 
103
// storage, with the provided name. The content should have the given
 
104
// size and hash. If the content is already in the store, a
 
105
// ContentChallengeError is returned containing a challenge that must be
 
106
// satisfied by a client to prove that they have access to the content.
 
107
// If the proof has already been acquired, it should be passed in as the
 
108
// proof argument.
 
109
func (s *Store) Put(r io.Reader, name string, size int64, hash string, proof *ContentChallengeResponse) (*ContentChallenge, error) {
 
110
        if proof != nil {
 
111
                err := s.challengeResponse(proof)
 
112
                if err == nil {
 
113
                        return nil, nil
 
114
                }
 
115
                if err != blobstore.ErrResourceDeleted {
 
116
                        return nil, errgo.Mask(err)
 
117
                }
 
118
                // The blob has been deleted since the challenge
 
119
                // was created, so continue on with uploading
 
120
                // the content as if there was no previous challenge.
 
121
        }
 
122
        resp, err := s.mstore.PutForEnvironmentRequest("", name, hash)
 
123
        if err != nil {
 
124
                if errors.IsNotFound(err) {
 
125
                        if err := s.mstore.PutForEnvironmentAndCheckHash("", name, r, size, hash); err != nil {
 
126
                                return nil, errgo.Mask(err)
 
127
                        }
 
128
                        return nil, nil
 
129
                }
 
130
                return nil, err
 
131
        }
 
132
        return &ContentChallenge{
 
133
                RequestId:   fmt.Sprint(resp.RequestId),
 
134
                RangeStart:  resp.RangeStart,
 
135
                RangeLength: resp.RangeLength,
 
136
        }, nil
 
137
}
 
138
 
 
139
// PutUnchallenged stream the content from the given reader into blob
 
140
// storage, with the provided name. The content should have the given
 
141
// size and hash. In this case a challenge is never returned and a proof
 
142
// is not required.
 
143
func (s *Store) PutUnchallenged(r io.Reader, name string, size int64, hash string) error {
 
144
        return s.mstore.PutForEnvironmentAndCheckHash("", name, r, size, hash)
 
145
}
 
146
 
 
147
// Open opens the entry with the given name.
 
148
func (s *Store) Open(name string) (ReadSeekCloser, int64, error) {
 
149
        r, length, err := s.mstore.GetForEnvironment("", name)
 
150
        if err != nil {
 
151
                return nil, 0, errgo.Mask(err)
 
152
        }
 
153
        return r.(ReadSeekCloser), length, nil
 
154
}
 
155
 
 
156
// Remove the given name from the Store.
 
157
func (s *Store) Remove(name string) error {
 
158
        return s.mstore.RemoveForEnvironment("", name)
 
159
}