7179: Add Volume interface specs.
[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.
17         //
18         // loc is guaranteed to consist of 32 or more lowercase hex
19         // digits.
20         //
21         // Get should not verify the integrity of the returned data:
22         // it should just return whatever was found in its backing
23         // store.
24         //
25         // If an error is encountered that prevents it from
26         // retrieving the data, that error should be returned so the
27         // caller can log (and send to the client) a more useful
28         // message.
29         //
30         // If the error is "not found", and there's no particular
31         // reason to expect the block to be found (other than that a
32         // caller is asking for it), the returned error should satisfy
33         // os.IsNotExist(err): this is a normal condition and will not
34         // be logged as an error (except that a 404 will appear in the
35         // access log if the block is not found on any other volumes
36         // either).
37         //
38         // If the data in the backing store is bigger than BLOCKSIZE,
39         // Get is permitted to return an error without reading any of
40         // the data.
41         Get(loc string) ([]byte, error)
42
43         // Put writes a block to an underlying storage device.
44         //
45         // loc is as described in Get.
46         //
47         // len(block) is guaranteed to be between 0 and BLOCKSIZE.
48         //
49         // If a block is already stored under the same name (loc) with
50         // different content, Put must either overwrite the existing
51         // data with the new data or return a non-nil error.
52         //
53         // Put must return a non-nil error unless it can guarantee
54         // that the entire block has been written and flushed to
55         // persistent storage. Of course, this guarantee is only as
56         // good as the underlying storage device, but it is Put's
57         // responsibility to at least get whatever guarantee is
58         // offered by the storage device.
59         //
60         // Put should not verify that loc==hash(block): this is the
61         // caller's responsibility.
62         Put(loc string, block []byte) error
63
64         // Touch sets the timestamp for the given locator to the
65         // current time.
66         //
67         // loc is as described in Get.
68         //
69         // Touch must return a non-nil error unless it can guarantee
70         // that a future call to Mtime() will return a timestamp newer
71         // than {now minus one second}.
72         Touch(loc string) error
73
74         // Mtime returns the stored timestamp for the given locator.
75         //
76         // loc is as described in Get.
77         //
78         // Mtime must return a non-nil error if the given block is not
79         // found or the timestamp could not be retrieved.
80         Mtime(loc string) (time.Time, error)
81
82         // IndexTo writes a complete list of locators with the given
83         // prefix for which Get() can retrieve data.
84         //
85         // prefix consists of zero or more lowercase hexadecimal
86         // digits.
87         //
88         // Each locator must be written to the given writer using the
89         // following format:
90         //
91         //   loc "+" size " " timestamp "\n"
92         //
93         // where:
94         //
95         //   - size is the number of bytes of content, given as a
96         //     decimal number with one or more digits
97         //     
98         //   - timestamp is the timestamp stored for the locator,
99         //     given as a decimal number of seconds after January 1,
100         //     1970 UTC.
101         //
102         // IndexTo must not write any other data to writer: for
103         // example, it must not write any blank lines.
104         //
105         // If an error makes it impossible to provide a complete
106         // index, IndexTo must return a non-nil error. It is
107         // acceptable to return a non-nil error after writing a
108         // partial index to writer.
109         //
110         // The resulting index is not expected to be sorted in any
111         // particular order.
112         IndexTo(prefix string, writer io.Writer) error
113
114         // Delete deletes the block data from the underlying storage
115         // device.
116         //
117         // loc is as described in Get.
118         //
119         // If the timestamp for the given locator is newer than
120         // blob_signature_ttl, Delete must not delete the data.
121         //
122         // If callers in different goroutines invoke overlapping
123         // Delete() and Touch() operations on the same locator, the
124         // implementation must guarantee that Touch() returns a
125         // non-nil error, or Delete() does not delete the block, or
126         // both.
127         Delete(loc string) error
128
129         // Status() returns a *VolumeStatus representing the current
130         // in-use and available storage capacity and an
131         // implementation-specific volume identifier (e.g., "mount
132         // point" for a UnixVolume).
133         Status() *VolumeStatus
134
135         // String() returns an identifying label for this volume,
136         // suitable for including in log messages. It should contain
137         // enough information to uniquely identify the underlying
138         // storage device, but should not contain any credentials or
139         // secrets.
140         String() string
141
142         // Writable() returns false if all future Put(), Mtime(), and
143         // Delete() calls are expected to fail.
144         //
145         // If the volume is only temporarily unwritable -- or if Put()
146         // will fail because it is full, but Mtime() or Delete() can
147         // succeed -- then Writable() should return false.
148         Writable() bool
149 }
150
151 // A VolumeManager tells callers which volumes can read, which volumes
152 // can write, and on which volume the next write should be attempted.
153 type VolumeManager interface {
154         // AllReadable returns all volumes.
155         AllReadable() []Volume
156         // AllWritable returns all volumes that aren't known to be in
157         // a read-only state. (There is no guarantee that a write to
158         // one will succeed, though.)
159         AllWritable() []Volume
160         // NextWritable returns the volume where the next new block
161         // should be written. A VolumeManager can select a volume in
162         // order to distribute activity across spindles, fill up disks
163         // with more free space, etc.
164         NextWritable() Volume
165         // Close shuts down the volume manager cleanly.
166         Close()
167 }
168
169 type RRVolumeManager struct {
170         readables []Volume
171         writables []Volume
172         counter   uint32
173 }
174
175 func MakeRRVolumeManager(volumes []Volume) *RRVolumeManager {
176         vm := &RRVolumeManager{}
177         for _, v := range volumes {
178                 vm.readables = append(vm.readables, v)
179                 if v.Writable() {
180                         vm.writables = append(vm.writables, v)
181                 }
182         }
183         return vm
184 }
185
186 func (vm *RRVolumeManager) AllReadable() []Volume {
187         return vm.readables
188 }
189
190 func (vm *RRVolumeManager) AllWritable() []Volume {
191         return vm.writables
192 }
193
194 func (vm *RRVolumeManager) NextWritable() Volume {
195         if len(vm.writables) == 0 {
196                 return nil
197         }
198         i := atomic.AddUint32(&vm.counter, 1)
199         return vm.writables[i%uint32(len(vm.writables))]
200 }
201
202 func (vm *RRVolumeManager) Close() {
203 }