1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: AGPL-3.0
19 "git.arvados.org/arvados.git/sdk/go/arvados"
20 "github.com/sirupsen/logrus"
24 TestBlock = []byte("The quick brown fox jumps over the lazy dog.")
25 TestHash = "e4d909c290d0fb1ca068ffaddf22cbd0"
26 TestHashPutResp = "e4d909c290d0fb1ca068ffaddf22cbd0+44\n"
28 TestBlock2 = []byte("Pack my box with five dozen liquor jugs.")
29 TestHash2 = "f15ac516f788aec4f30932ffb6395c39"
31 TestBlock3 = []byte("Now is the time for all good men to come to the aid of their country.")
32 TestHash3 = "eed29bbffbc2dbe5e5ee0bb71888e61f"
34 // BadBlock is used to test collisions and corruption.
35 // It must not match any test hashes.
36 BadBlock = []byte("The magic words are squeamish ossifrage.")
38 EmptyHash = "d41d8cd98f00b204e9800998ecf8427e"
39 EmptyBlock = []byte("")
42 // A TestableVolume allows test suites to manipulate the state of an
43 // underlying Volume, in order to test behavior in cases that are
44 // impractical to achieve with a sequence of normal Volume operations.
45 type TestableVolume interface {
48 // [Over]write content for a locator with the given data,
49 // bypassing all constraints like readonly and serialize.
50 PutRaw(locator string, data []byte)
52 // Returns the strings that a driver uses to record read/write operations.
53 ReadWriteOperationLabelValues() (r, w string)
55 // Specify the value Mtime() should return, until the next
56 // call to Touch, TouchWithDate, or Put.
57 TouchWithDate(locator string, lastPut time.Time)
59 // Clean up, delete temporary files.
64 driver["mock"] = newMockVolume
67 // MockVolumes are test doubles for Volumes, used to test handlers.
68 type MockVolume struct {
69 Store map[string][]byte
70 Timestamps map[string]time.Time
72 // Bad volumes return an error for every operation.
76 // Touchable volumes' Touch() method succeeds for a locator
77 // that has been Put().
80 // Gate is a "starting gate", allowing test cases to pause
81 // volume operations long enough to inspect state. Every
82 // operation (except Status) starts by receiving from
83 // Gate. Sending one value unblocks one operation; closing the
84 // channel unblocks all operations. By default, Gate is a
85 // closed channel, so all operations proceed without
86 // blocking. See trash_worker_test.go for an example.
87 Gate chan struct{} `json:"-"`
89 cluster *arvados.Cluster
91 logger logrus.FieldLogger
92 metrics *volumeMetricsVecs
97 // newMockVolume returns a non-Bad, non-Readonly, Touchable mock
99 func newMockVolume(cluster *arvados.Cluster, volume arvados.Volume, logger logrus.FieldLogger, metrics *volumeMetricsVecs) (Volume, error) {
100 gate := make(chan struct{})
103 Store: make(map[string][]byte),
104 Timestamps: make(map[string]time.Time),
107 called: map[string]int{},
116 // CallCount returns how many times the named method has been called.
117 func (v *MockVolume) CallCount(method string) int {
119 defer v.mutex.Unlock()
120 c, ok := v.called[method]
127 func (v *MockVolume) gotCall(method string) {
129 defer v.mutex.Unlock()
130 if _, ok := v.called[method]; !ok {
137 func (v *MockVolume) Compare(ctx context.Context, loc string, buf []byte) error {
141 return v.BadVolumeError
142 } else if block, ok := v.Store[loc]; ok {
143 if fmt.Sprintf("%x", md5.Sum(block)) != loc {
146 if bytes.Compare(buf, block) != 0 {
147 return CollisionError
151 return os.ErrNotExist
155 func (v *MockVolume) Get(ctx context.Context, loc string, buf []byte) (int, error) {
159 return 0, v.BadVolumeError
160 } else if block, ok := v.Store[loc]; ok {
161 copy(buf[:len(block)], block)
162 return len(block), nil
164 return 0, os.ErrNotExist
167 func (v *MockVolume) Put(ctx context.Context, loc string, block []byte) error {
171 return v.BadVolumeError
173 if v.volume.ReadOnly {
174 return MethodDisabledError
180 func (v *MockVolume) Touch(loc string) error {
181 return v.TouchWithDate(loc, time.Now())
184 func (v *MockVolume) TouchWithDate(loc string, t time.Time) error {
187 if v.volume.ReadOnly {
188 return MethodDisabledError
190 if _, exists := v.Store[loc]; !exists {
191 return os.ErrNotExist
194 v.Timestamps[loc] = t
197 return errors.New("Touch failed")
200 func (v *MockVolume) Mtime(loc string) (time.Time, error) {
206 err = v.BadVolumeError
207 } else if t, ok := v.Timestamps[loc]; ok {
215 func (v *MockVolume) IndexTo(prefix string, w io.Writer) error {
218 for loc, block := range v.Store {
219 if !IsValidLocator(loc) || !strings.HasPrefix(loc, prefix) {
222 _, err := fmt.Fprintf(w, "%s+%d %d\n",
223 loc, len(block), 123456789)
231 func (v *MockVolume) Trash(loc string) error {
234 if v.volume.ReadOnly {
235 return MethodDisabledError
237 if _, ok := v.Store[loc]; ok {
238 if time.Since(v.Timestamps[loc]) < time.Duration(v.cluster.Collections.BlobSigningTTL) {
244 return os.ErrNotExist
247 func (v *MockVolume) GetDeviceID() string {
248 return "mock-device-id"
251 func (v *MockVolume) Untrash(loc string) error {
255 func (v *MockVolume) Status() *VolumeStatus {
257 for _, block := range v.Store {
258 used = used + uint64(len(block))
260 return &VolumeStatus{"/bogo", 123, 1000000 - used, used}
263 func (v *MockVolume) String() string {
264 return "[MockVolume]"
267 func (v *MockVolume) EmptyTrash() {
270 func (v *MockVolume) GetStorageClasses() []string {