Merge branch 'master' into 7179-generic-volume-tests
[arvados.git] / services / keepstore / volume.go
1 // A Volume is an interface representing a Keep back-end storage unit:
2 // for example, a single mounted disk, a RAID array, an Amazon S3 volume,
3 // etc.
4
5 package main
6
7 import (
8         "io"
9         "sync/atomic"
10         "time"
11 )
12
13 type Volume interface {
14         // Get a block. IFF the returned error is nil, the caller must
15         // put the returned slice back into the buffer pool when it's
16         // finished with it. (Otherwise, the buffer pool will be
17         // depleted and eventually -- when all available buffers are
18         // used and not returned -- operations will reach deadlock.)
19         //
20         // loc is guaranteed to consist of 32 or more lowercase hex
21         // digits.
22         //
23         // Get should not verify the integrity of the returned data:
24         // it should just return whatever was found in its backing
25         // store. (Integrity checking is the caller's responsibility.)
26         //
27         // If an error is encountered that prevents it from
28         // retrieving the data, that error should be returned so the
29         // caller can log (and send to the client) a more useful
30         // message.
31         //
32         // If the error is "not found", and there's no particular
33         // reason to expect the block to be found (other than that a
34         // caller is asking for it), the returned error should satisfy
35         // os.IsNotExist(err): this is a normal condition and will not
36         // be logged as an error (except that a 404 will appear in the
37         // access log if the block is not found on any other volumes
38         // either).
39         //
40         // If the data in the backing store is bigger than BLOCKSIZE,
41         // Get is permitted to return an error without reading any of
42         // the data.
43         Get(loc string) ([]byte, error)
44
45         // Compare the given data with the stored data (i.e., what Get
46         // would return). If equal, return nil. If not, return
47         // CollisionError or DiskHashError (depending on whether the
48         // data on disk matches the expected hash), or whatever error
49         // was encountered opening/reading the stored data.
50         Compare(loc string, data []byte) error
51
52         // Put writes a block to an underlying storage device.
53         //
54         // loc is as described in Get.
55         //
56         // len(block) is guaranteed to be between 0 and BLOCKSIZE.
57         //
58         // If a block is already stored under the same name (loc) with
59         // different content, Put must either overwrite the existing
60         // data with the new data or return a non-nil error.
61         //
62         // Put also sets the timestamp for the given locator to the
63         // current time.
64         //
65         // Put must return a non-nil error unless it can guarantee
66         // that the entire block has been written and flushed to
67         // persistent storage, and that its timestamp is current. Of
68         // course, this guarantee is only as good as the underlying
69         // storage device, but it is Put's responsibility to at least
70         // get whatever guarantee is offered by the storage device.
71         //
72         // Put should not verify that loc==hash(block): this is the
73         // caller's responsibility.
74         Put(loc string, block []byte) error
75
76         // Touch sets the timestamp for the given locator to the
77         // current time.
78         //
79         // loc is as described in Get.
80         //
81         // If invoked at time t0, Touch must guarantee that a
82         // subsequent call to Mtime will return a timestamp no older
83         // than {t0 minus one second}. For example, if Touch is called
84         // at 2015-07-07T01:23:45.67890123Z, it is acceptable for a
85         // subsequent Mtime to return any of the following:
86         //
87         //   - 2015-07-07T01:23:45.00000000Z
88         //   - 2015-07-07T01:23:45.67890123Z
89         //   - 2015-07-07T01:23:46.67890123Z
90         //   - 2015-07-08T00:00:00.00000000Z
91         //
92         // It is not acceptable for a subsequente Mtime to return
93         // either of the following:
94         //
95         //   - 2015-07-07T00:00:00.00000000Z -- ERROR
96         //   - 2015-07-07T01:23:44.00000000Z -- ERROR
97         //
98         // Touch must return a non-nil error if the timestamp cannot
99         // be updated.
100         Touch(loc string) error
101
102         // Mtime returns the stored timestamp for the given locator.
103         //
104         // loc is as described in Get.
105         //
106         // Mtime must return a non-nil error if the given block is not
107         // found or the timestamp could not be retrieved.
108         Mtime(loc string) (time.Time, error)
109
110         // IndexTo writes a complete list of locators with the given
111         // prefix for which Get() can retrieve data.
112         //
113         // prefix consists of zero or more lowercase hexadecimal
114         // digits.
115         //
116         // Each locator must be written to the given writer using the
117         // following format:
118         //
119         //   loc "+" size " " timestamp "\n"
120         //
121         // where:
122         //
123         //   - size is the number of bytes of content, given as a
124         //     decimal number with one or more digits
125         //
126         //   - timestamp is the timestamp stored for the locator,
127         //     given as a decimal number of seconds after January 1,
128         //     1970 UTC.
129         //
130         // IndexTo must not write any other data to writer: for
131         // example, it must not write any blank lines.
132         //
133         // If an error makes it impossible to provide a complete
134         // index, IndexTo must return a non-nil error. It is
135         // acceptable to return a non-nil error after writing a
136         // partial index to writer.
137         //
138         // The resulting index is not expected to be sorted in any
139         // particular order.
140         IndexTo(prefix string, writer io.Writer) error
141
142         // Delete deletes the block data from the underlying storage
143         // device.
144         //
145         // loc is as described in Get.
146         //
147         // If the timestamp for the given locator is newer than
148         // blob_signature_ttl, Delete must not delete the data.
149         //
150         // If a Delete operation overlaps with any Touch or Put
151         // operations on the same locator, the implementation must
152         // ensure one of the following outcomes:
153         //
154         //   - Touch and Put return a non-nil error, or
155         //   - Delete does not delete the block, or
156         //   - Both of the above.
157         //
158         // If it is possible for the storage device to be accessed by
159         // a different process or host, the synchronization mechanism
160         // should also guard against races with other processes and
161         // hosts. If such a mechanism is not available, there must be
162         // a mechanism for detecting unsafe configurations, alerting
163         // the operator, and aborting or falling back to a read-only
164         // state. In other words, running multiple keepstore processes
165         // with the same underlying storage device must either work
166         // reliably or fail outright.
167         //
168         // Corollary: A successful Touch or Put guarantees a block
169         // will not be deleted for at least blob_signature_ttl
170         // seconds.
171         Delete(loc string) error
172
173         // Status returns a *VolumeStatus representing the current
174         // in-use and available storage capacity and an
175         // implementation-specific volume identifier (e.g., "mount
176         // point" for a UnixVolume).
177         Status() *VolumeStatus
178
179         // String returns an identifying label for this volume,
180         // suitable for including in log messages. It should contain
181         // enough information to uniquely identify the underlying
182         // storage device, but should not contain any credentials or
183         // secrets.
184         String() string
185
186         // Writable returns false if all future Put, Mtime, and Delete
187         // calls are expected to fail.
188         //
189         // If the volume is only temporarily unwritable -- or if Put
190         // will fail because it is full, but Mtime or Delete can
191         // succeed -- then Writable should return false.
192         Writable() bool
193 }
194
195 // A VolumeManager tells callers which volumes can read, which volumes
196 // can write, and on which volume the next write should be attempted.
197 type VolumeManager interface {
198         // AllReadable returns all volumes.
199         AllReadable() []Volume
200
201         // AllWritable returns all volumes that aren't known to be in
202         // a read-only state. (There is no guarantee that a write to
203         // one will succeed, though.)
204         AllWritable() []Volume
205
206         // NextWritable returns the volume where the next new block
207         // should be written. A VolumeManager can select a volume in
208         // order to distribute activity across spindles, fill up disks
209         // with more free space, etc.
210         NextWritable() Volume
211
212         // Close shuts down the volume manager cleanly.
213         Close()
214 }
215
216 // RRVolumeManager is a round-robin VolumeManager: the Nth call to
217 // NextWritable returns the (N % len(writables))th writable Volume
218 // (where writables are all Volumes v where v.Writable()==true).
219 type RRVolumeManager struct {
220         readables []Volume
221         writables []Volume
222         counter   uint32
223 }
224
225 func MakeRRVolumeManager(volumes []Volume) *RRVolumeManager {
226         vm := &RRVolumeManager{}
227         for _, v := range volumes {
228                 vm.readables = append(vm.readables, v)
229                 if v.Writable() {
230                         vm.writables = append(vm.writables, v)
231                 }
232         }
233         return vm
234 }
235
236 func (vm *RRVolumeManager) AllReadable() []Volume {
237         return vm.readables
238 }
239
240 func (vm *RRVolumeManager) AllWritable() []Volume {
241         return vm.writables
242 }
243
244 func (vm *RRVolumeManager) NextWritable() Volume {
245         if len(vm.writables) == 0 {
246                 return nil
247         }
248         i := atomic.AddUint32(&vm.counter, 1)
249         return vm.writables[i%uint32(len(vm.writables))]
250 }
251
252 func (vm *RRVolumeManager) Close() {
253 }
254
255 // VolumeStatus
256 //   * mount_point
257 //   * device_num (an integer identifying the underlying storage system)
258 //   * bytes_free
259 //   * bytes_used
260 type VolumeStatus struct {
261         MountPoint string `json:"mount_point"`
262         DeviceNum  uint64 `json:"device_num"`
263         BytesFree  uint64 `json:"bytes_free"`
264         BytesUsed  uint64 `json:"bytes_used"`
265 }