master
go 246 lines 6.2 KB
Raw
1 package fsrepo
2
3 import (
4 "bytes"
5 "encoding/json"
6 "fmt"
7 "sort"
8
9 "github.com/ipfs/kubo/repo"
10
11 ds "github.com/ipfs/go-datastore"
12 "github.com/ipfs/go-datastore/mount"
13 dssync "github.com/ipfs/go-datastore/sync"
14 "github.com/ipfs/go-ds-measure"
15 )
16
17 // ConfigFromMap creates a new datastore config from a map.
18 type ConfigFromMap func(map[string]any) (DatastoreConfig, error)
19
20 // DatastoreConfig is an abstraction of a datastore config. A "spec" is first
21 // converted to a DatastoreConfig and then Create() is called to instantiate a
22 // new datastore.
23 type DatastoreConfig interface {
24 // DiskSpec returns a minimal configuration of the datastore representing
25 // what is stored on disk. Run time values are excluded.
26 DiskSpec() DiskSpec
27
28 // Create instantiates a new datastore from this config.
29 Create(path string) (repo.Datastore, error)
30 }
31
32 // DiskSpec is a minimal representation of the characteristic values of the
33 // datastore. If two diskspecs are the same, the loader assumes that they refer
34 // to exactly the same datastore. If they differ at all, it is assumed they are
35 // completely different datastores and a migration will be performed. Runtime
36 // values such as cache options or concurrency options should not be added
37 // here.
38 type DiskSpec map[string]any
39
40 // Bytes returns a minimal JSON encoding of the DiskSpec.
41 func (spec DiskSpec) Bytes() []byte {
42 b, err := json.Marshal(spec)
43 if err != nil {
44 // should not happen
45 panic(err)
46 }
47 return bytes.TrimSpace(b)
48 }
49
50 // String returns a minimal JSON encoding of the DiskSpec.
51 func (spec DiskSpec) String() string {
52 return string(spec.Bytes())
53 }
54
55 var datastores map[string]ConfigFromMap
56
57 func init() {
58 datastores = map[string]ConfigFromMap{
59 "mount": MountDatastoreConfig,
60 "mem": MemDatastoreConfig,
61 "log": LogDatastoreConfig,
62 "measure": MeasureDatastoreConfig,
63 }
64 }
65
66 func AddDatastoreConfigHandler(name string, dsc ConfigFromMap) error {
67 _, ok := datastores[name]
68 if ok {
69 return fmt.Errorf("already have a datastore named %q", name)
70 }
71
72 datastores[name] = dsc
73 return nil
74 }
75
76 // AnyDatastoreConfig returns a DatastoreConfig from a spec based on
77 // the "type" parameter.
78 func AnyDatastoreConfig(params map[string]any) (DatastoreConfig, error) {
79 which, ok := params["type"].(string)
80 if !ok {
81 return nil, fmt.Errorf("'type' field missing or not a string")
82 }
83 fun, ok := datastores[which]
84 if !ok {
85 return nil, fmt.Errorf("unknown datastore type: %s", which)
86 }
87 return fun(params)
88 }
89
90 type mountDatastoreConfig struct {
91 mounts []premount
92 }
93
94 type premount struct {
95 ds DatastoreConfig
96 prefix ds.Key
97 }
98
99 // MountDatastoreConfig returns a mount DatastoreConfig from a spec.
100 func MountDatastoreConfig(params map[string]any) (DatastoreConfig, error) {
101 var res mountDatastoreConfig
102 mounts, ok := params["mounts"].([]any)
103 if !ok {
104 return nil, fmt.Errorf("'mounts' field is missing or not an array")
105 }
106 for _, iface := range mounts {
107 cfg, ok := iface.(map[string]any)
108 if !ok {
109 return nil, fmt.Errorf("expected map for mountpoint")
110 }
111
112 child, err := AnyDatastoreConfig(cfg)
113 if err != nil {
114 return nil, err
115 }
116
117 prefix, found := cfg["mountpoint"]
118 if !found {
119 return nil, fmt.Errorf("no 'mountpoint' on mount")
120 }
121
122 res.mounts = append(res.mounts, premount{
123 ds: child,
124 prefix: ds.NewKey(prefix.(string)),
125 })
126 }
127 sort.Slice(res.mounts,
128 func(i, j int) bool {
129 return res.mounts[i].prefix.String() > res.mounts[j].prefix.String()
130 })
131
132 return &res, nil
133 }
134
135 func (c *mountDatastoreConfig) DiskSpec() DiskSpec {
136 cfg := map[string]any{"type": "mount"}
137 mounts := make([]any, len(c.mounts))
138 for i, m := range c.mounts {
139 c := m.ds.DiskSpec()
140 if c == nil {
141 c = make(map[string]any)
142 }
143 c["mountpoint"] = m.prefix.String()
144 mounts[i] = c
145 }
146 cfg["mounts"] = mounts
147 return cfg
148 }
149
150 func (c *mountDatastoreConfig) Create(path string) (repo.Datastore, error) {
151 mounts := make([]mount.Mount, len(c.mounts))
152 for i, m := range c.mounts {
153 ds, err := m.ds.Create(path)
154 if err != nil {
155 return nil, err
156 }
157 mounts[i].Datastore = ds
158 mounts[i].Prefix = m.prefix
159 }
160 return mount.New(mounts), nil
161 }
162
163 type memDatastoreConfig struct {
164 cfg map[string]any
165 }
166
167 // MemDatastoreConfig returns a memory DatastoreConfig from a spec.
168 func MemDatastoreConfig(params map[string]any) (DatastoreConfig, error) {
169 return &memDatastoreConfig{params}, nil
170 }
171
172 func (c *memDatastoreConfig) DiskSpec() DiskSpec {
173 return nil
174 }
175
176 func (c *memDatastoreConfig) Create(string) (repo.Datastore, error) {
177 return dssync.MutexWrap(ds.NewMapDatastore()), nil
178 }
179
180 type logDatastoreConfig struct {
181 child DatastoreConfig
182 name string
183 }
184
185 // LogDatastoreConfig returns a log DatastoreConfig from a spec.
186 func LogDatastoreConfig(params map[string]any) (DatastoreConfig, error) {
187 childField, ok := params["child"].(map[string]any)
188 if !ok {
189 return nil, fmt.Errorf("'child' field is missing or not a map")
190 }
191 child, err := AnyDatastoreConfig(childField)
192 if err != nil {
193 return nil, err
194 }
195 name, ok := params["name"].(string)
196 if !ok {
197 return nil, fmt.Errorf("'name' field was missing or not a string")
198 }
199 return &logDatastoreConfig{child, name}, nil
200 }
201
202 func (c *logDatastoreConfig) Create(path string) (repo.Datastore, error) {
203 child, err := c.child.Create(path)
204 if err != nil {
205 return nil, err
206 }
207 return ds.NewLogDatastore(child, c.name), nil
208 }
209
210 func (c *logDatastoreConfig) DiskSpec() DiskSpec {
211 return c.child.DiskSpec()
212 }
213
214 type measureDatastoreConfig struct {
215 child DatastoreConfig
216 prefix string
217 }
218
219 // MeasureDatastoreConfig returns a measure DatastoreConfig from a spec.
220 func MeasureDatastoreConfig(params map[string]any) (DatastoreConfig, error) {
221 childField, ok := params["child"].(map[string]any)
222 if !ok {
223 return nil, fmt.Errorf("'child' field is missing or not a map")
224 }
225 child, err := AnyDatastoreConfig(childField)
226 if err != nil {
227 return nil, err
228 }
229 prefix, ok := params["prefix"].(string)
230 if !ok {
231 return nil, fmt.Errorf("'prefix' field was missing or not a string")
232 }
233 return &measureDatastoreConfig{child, prefix}, nil
234 }
235
236 func (c *measureDatastoreConfig) DiskSpec() DiskSpec {
237 return c.child.DiskSpec()
238 }
239
240 func (c measureDatastoreConfig) Create(path string) (repo.Datastore, error) {
241 child, err := c.child.Create(path)
242 if err != nil {
243 return nil, err
244 }
245 return measure.New(c.prefix, child), nil
246 }