Merge branch '5748-keepstore-leak' refs #5748
[arvados.git] / services / keepstore / volume_test.go
1 package main
2
3 import (
4         "errors"
5         "fmt"
6         "io"
7         "os"
8         "strings"
9         "sync"
10         "time"
11 )
12
13 // MockVolumes are test doubles for Volumes, used to test handlers.
14 type MockVolume struct {
15         Store      map[string][]byte
16         Timestamps map[string]time.Time
17         // Bad volumes return an error for every operation.
18         Bad bool
19         // Touchable volumes' Touch() method succeeds for a locator
20         // that has been Put().
21         Touchable bool
22         // Readonly volumes return an error for Put, Delete, and
23         // Touch.
24         Readonly bool
25         called   map[string]int
26         mutex    sync.Mutex
27 }
28
29 // CreateMockVolume returns a non-Bad, non-Readonly, Touchable mock
30 // volume.
31 func CreateMockVolume() *MockVolume {
32         return &MockVolume{
33                 Store:      make(map[string][]byte),
34                 Timestamps: make(map[string]time.Time),
35                 Bad:        false,
36                 Touchable:  true,
37                 Readonly:   false,
38                 called:     map[string]int{},
39         }
40 }
41
42 // CallCount returns how many times the named method has been called.
43 func (v *MockVolume) CallCount(method string) int {
44         v.mutex.Lock()
45         defer v.mutex.Unlock()
46         if c, ok := v.called[method]; !ok {
47                 return 0
48         } else {
49                 return c
50         }
51 }
52
53 func (v *MockVolume) gotCall(method string) {
54         v.mutex.Lock()
55         defer v.mutex.Unlock()
56         if _, ok := v.called[method]; !ok {
57                 v.called[method] = 1
58         } else {
59                 v.called[method]++
60         }
61 }
62
63 func (v *MockVolume) Get(loc string) ([]byte, error) {
64         v.gotCall("Get")
65         if v.Bad {
66                 return nil, errors.New("Bad volume")
67         } else if block, ok := v.Store[loc]; ok {
68                 return block, nil
69         }
70         return nil, os.ErrNotExist
71 }
72
73 func (v *MockVolume) Put(loc string, block []byte) error {
74         v.gotCall("Put")
75         if v.Bad {
76                 return errors.New("Bad volume")
77         }
78         if v.Readonly {
79                 return MethodDisabledError
80         }
81         v.Store[loc] = block
82         return v.Touch(loc)
83 }
84
85 func (v *MockVolume) Touch(loc string) error {
86         v.gotCall("Touch")
87         if v.Readonly {
88                 return MethodDisabledError
89         }
90         if v.Touchable {
91                 v.Timestamps[loc] = time.Now()
92                 return nil
93         }
94         return errors.New("Touch failed")
95 }
96
97 func (v *MockVolume) Mtime(loc string) (time.Time, error) {
98         v.gotCall("Mtime")
99         var mtime time.Time
100         var err error
101         if v.Bad {
102                 err = errors.New("Bad volume")
103         } else if t, ok := v.Timestamps[loc]; ok {
104                 mtime = t
105         } else {
106                 err = os.ErrNotExist
107         }
108         return mtime, err
109 }
110
111 func (v *MockVolume) IndexTo(prefix string, w io.Writer) error {
112         v.gotCall("IndexTo")
113         for loc, block := range v.Store {
114                 if !IsValidLocator(loc) || !strings.HasPrefix(loc, prefix) {
115                         continue
116                 }
117                 _, err := fmt.Fprintf(w, "%s+%d %d\n",
118                         loc, len(block), 123456789)
119                 if err != nil {
120                         return err
121                 }
122         }
123         return nil
124 }
125
126 func (v *MockVolume) Delete(loc string) error {
127         v.gotCall("Delete")
128         if v.Readonly {
129                 return MethodDisabledError
130         }
131         if _, ok := v.Store[loc]; ok {
132                 if time.Since(v.Timestamps[loc]) < blob_signature_ttl {
133                         return nil
134                 }
135                 delete(v.Store, loc)
136                 return nil
137         }
138         return os.ErrNotExist
139 }
140
141 func (v *MockVolume) Status() *VolumeStatus {
142         var used uint64
143         for _, block := range v.Store {
144                 used = used + uint64(len(block))
145         }
146         return &VolumeStatus{"/bogo", 123, 1000000 - used, used}
147 }
148
149 func (v *MockVolume) String() string {
150         return "[MockVolume]"
151 }
152
153 func (v *MockVolume) Writable() bool {
154         return !v.Readonly
155 }