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