daf003e1956a6dd1d951a8b17394ae7ec792ee71
[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. When
61         // overwriting existing data, it must never leave the storage
62         // device in an inconsistent state: a subsequent call to Get
63         // must return either the entire old block, the entire new
64         // block, or an error. (An implementation that cannot peform
65         // atomic updates must leave the old data alone and return an
66         // error.)
67         //
68         // Put also sets the timestamp for the given locator to the
69         // current time.
70         //
71         // Put must return a non-nil error unless it can guarantee
72         // that the entire block has been written and flushed to
73         // persistent storage, and that its timestamp is current. Of
74         // course, this guarantee is only as good as the underlying
75         // storage device, but it is Put's responsibility to at least
76         // get whatever guarantee is offered by the storage device.
77         //
78         // Put should not verify that loc==hash(block): this is the
79         // caller's responsibility.
80         Put(loc string, block []byte) error
81
82         // Touch sets the timestamp for the given locator to the
83         // current time.
84         //
85         // loc is as described in Get.
86         //
87         // If invoked at time t0, Touch must guarantee that a
88         // subsequent call to Mtime will return a timestamp no older
89         // than {t0 minus one second}. For example, if Touch is called
90         // at 2015-07-07T01:23:45.67890123Z, it is acceptable for a
91         // subsequent Mtime to return any of the following:
92         //
93         //   - 2015-07-07T01:23:45.00000000Z
94         //   - 2015-07-07T01:23:45.67890123Z
95         //   - 2015-07-07T01:23:46.67890123Z
96         //   - 2015-07-08T00:00:00.00000000Z
97         //
98         // It is not acceptable for a subsequente Mtime to return
99         // either of the following:
100         //
101         //   - 2015-07-07T00:00:00.00000000Z -- ERROR
102         //   - 2015-07-07T01:23:44.00000000Z -- ERROR
103         //
104         // Touch must return a non-nil error if the timestamp cannot
105         // be updated.
106         Touch(loc string) error
107
108         // Mtime returns the stored timestamp for the given locator.
109         //
110         // loc is as described in Get.
111         //
112         // Mtime must return a non-nil error if the given block is not
113         // found or the timestamp could not be retrieved.
114         Mtime(loc string) (time.Time, error)
115
116         // IndexTo writes a complete list of locators with the given
117         // prefix for which Get() can retrieve data.
118         //
119         // prefix consists of zero or more lowercase hexadecimal
120         // digits.
121         //
122         // Each locator must be written to the given writer using the
123         // following format:
124         //
125         //   loc "+" size " " timestamp "\n"
126         //
127         // where:
128         //
129         //   - size is the number of bytes of content, given as a
130         //     decimal number with one or more digits
131         //
132         //   - timestamp is the timestamp stored for the locator,
133         //     given as a decimal number of seconds after January 1,
134         //     1970 UTC.
135         //
136         // IndexTo must not write any other data to writer: for
137         // example, it must not write any blank lines.
138         //
139         // If an error makes it impossible to provide a complete
140         // index, IndexTo must return a non-nil error. It is
141         // acceptable to return a non-nil error after writing a
142         // partial index to writer.
143         //
144         // The resulting index is not expected to be sorted in any
145         // particular order.
146         IndexTo(prefix string, writer io.Writer) error
147
148         // Delete deletes the block data from the underlying storage
149         // device.
150         //
151         // loc is as described in Get.
152         //
153         // If the timestamp for the given locator is newer than
154         // blob_signature_ttl, Delete must not delete the data.
155         //
156         // If a Delete operation overlaps with any Touch or Put
157         // operations on the same locator, the implementation must
158         // ensure one of the following outcomes:
159         //
160         //   - Touch and Put return a non-nil error, or
161         //   - Delete does not delete the block, or
162         //   - Both of the above.
163         //
164         // If it is possible for the storage device to be accessed by
165         // a different process or host, the synchronization mechanism
166         // should also guard against races with other processes and
167         // hosts. If such a mechanism is not available, there must be
168         // a mechanism for detecting unsafe configurations, alerting
169         // the operator, and aborting or falling back to a read-only
170         // state. In other words, running multiple keepstore processes
171         // with the same underlying storage device must either work
172         // reliably or fail outright.
173         //
174         // Corollary: A successful Touch or Put guarantees a block
175         // will not be deleted for at least blob_signature_ttl
176         // seconds.
177         Delete(loc string) error
178
179         // Status returns a *VolumeStatus representing the current
180         // in-use and available storage capacity and an
181         // implementation-specific volume identifier (e.g., "mount
182         // point" for a UnixVolume).
183         Status() *VolumeStatus
184
185         // String returns an identifying label for this volume,
186         // suitable for including in log messages. It should contain
187         // enough information to uniquely identify the underlying
188         // storage device, but should not contain any credentials or
189         // secrets.
190         String() string
191
192         // Writable returns false if all future Put, Mtime, and Delete
193         // calls are expected to fail.
194         //
195         // If the volume is only temporarily unwritable -- or if Put
196         // will fail because it is full, but Mtime or Delete can
197         // succeed -- then Writable should return false.
198         Writable() bool
199 }
200
201 // A VolumeManager tells callers which volumes can read, which volumes
202 // can write, and on which volume the next write should be attempted.
203 type VolumeManager interface {
204         // AllReadable returns all volumes.
205         AllReadable() []Volume
206
207         // AllWritable returns all volumes that aren't known to be in
208         // a read-only state. (There is no guarantee that a write to
209         // one will succeed, though.)
210         AllWritable() []Volume
211
212         // NextWritable returns the volume where the next new block
213         // should be written. A VolumeManager can select a volume in
214         // order to distribute activity across spindles, fill up disks
215         // with more free space, etc.
216         NextWritable() Volume
217
218         // Close shuts down the volume manager cleanly.
219         Close()
220 }
221
222 // RRVolumeManager is a round-robin VolumeManager: the Nth call to
223 // NextWritable returns the (N % len(writables))th writable Volume
224 // (where writables are all Volumes v where v.Writable()==true).
225 type RRVolumeManager struct {
226         readables []Volume
227         writables []Volume
228         counter   uint32
229 }
230
231 func MakeRRVolumeManager(volumes []Volume) *RRVolumeManager {
232         vm := &RRVolumeManager{}
233         for _, v := range volumes {
234                 vm.readables = append(vm.readables, v)
235                 if v.Writable() {
236                         vm.writables = append(vm.writables, v)
237                 }
238         }
239         return vm
240 }
241
242 func (vm *RRVolumeManager) AllReadable() []Volume {
243         return vm.readables
244 }
245
246 func (vm *RRVolumeManager) AllWritable() []Volume {
247         return vm.writables
248 }
249
250 func (vm *RRVolumeManager) NextWritable() Volume {
251         if len(vm.writables) == 0 {
252                 return nil
253         }
254         i := atomic.AddUint32(&vm.counter, 1)
255         return vm.writables[i%uint32(len(vm.writables))]
256 }
257
258 func (vm *RRVolumeManager) Close() {
259 }
260
261 // VolumeStatus
262 //   * mount_point
263 //   * device_num (an integer identifying the underlying storage system)
264 //   * bytes_free
265 //   * bytes_used
266 type VolumeStatus struct {
267         MountPoint string `json:"mount_point"`
268         DeviceNum  uint64 `json:"device_num"`
269         BytesFree  uint64 `json:"bytes_free"`
270         BytesUsed  uint64 `json:"bytes_used"`
271 }