-
Notifications
You must be signed in to change notification settings - Fork 11
/
bitcask.go
162 lines (142 loc) · 4.17 KB
/
bitcask.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
//go:build !windows
// +build !windows
package bitcask
import (
"context"
"path"
"path/filepath"
"strings"
"git.mills.io/prologic/bitcask"
"github.com/hashicorp/go-hclog"
"github.com/spf13/viper"
"github.com/netauth/netauth/internal/db"
"github.com/netauth/netauth/internal/startup"
)
func init() {
startup.RegisterCallback(cb)
}
func cb() {
db.RegisterKV("bitcask", New)
}
// BCStore is a store implementation based on the bitcask storage
// engine.
type BCStore struct {
s *bitcask.Bitcask
l hclog.Logger
eF func(db.Event)
}
// event is an enum for what type of event to fire and subsequently
// map to a DB event.
type eventType int
const (
eventUpdate eventType = iota
eventDelete
)
// New creates a new instance of the bitcask store.
func New(l hclog.Logger) (db.KVStore, error) {
p := filepath.Join(viper.GetString("core.home"), "bc")
x := &BCStore{}
x.l = l.Named("bitcask")
opts := []bitcask.Option{
bitcask.WithMaxKeySize(1024),
bitcask.WithMaxValueSize(1024 * 1000 * 5), // 5MiB
bitcask.WithSync(true),
}
b, err := bitcask.Open(p, opts...)
if err != nil {
return nil, err
}
x.s = b
return x, nil
}
// Put stores the bytes of v at a location identitified by the key k.
// If the operation fails an error will be returned explaining why.
func (bcs *BCStore) Put(_ context.Context, k string, v []byte) error {
if err := bcs.s.Put([]byte(k), v); err != nil {
return err
}
bcs.fireEventForKey(k, eventUpdate)
return nil
}
// Get returns the key at k or an error explaning why no data was
// returned.
func (bcs *BCStore) Get(_ context.Context, k string) ([]byte, error) {
v, err := bcs.s.Get([]byte(k))
switch err {
case nil:
return v, nil
default:
return nil, db.ErrNoValue
}
}
// Del removes any existing value at the location specified by the
// provided key.
func (bcs *BCStore) Del(_ context.Context, k string) error {
// I can come up with nothing that causes this delete call to
// fail, up to and including nuking the entire data directory.
// If you can write a way to check this error and its
// associated test, open a PR.
bcs.s.Delete([]byte(k))
bcs.fireEventForKey(k, eventDelete)
return nil
}
// Keys is a way to enumerate the keys in the key/value store and to
// optionally filter them based on a globbing expression. This cheats
// and uses superior knowledge that NetAuth uses only a single key
// namespace with a single layer of keys below it. Its technically
// possible to do something dumb with an entity or group name that
// includes a path seperator, but this should be filtered out at a
// higher level.
func (bcs *BCStore) Keys(_ context.Context, f string) ([]string, error) {
out := []string{}
for bk := range bcs.s.Keys() {
k := string(bk)
if m, _ := path.Match(f, k); m {
out = append(out, k)
}
}
return out, nil
}
// Close terminates the connection to the bitcask and flushes it to
// disk. The cask must not be used after Close() is called.
func (bcs *BCStore) Close() error {
return bcs.s.Close()
}
// Capabilities returns that this key/value store supports te mutable
// property, allowing it to be writeable to the higher level systems.
func (bcs *BCStore) Capabilities() []db.KVCapability {
return []db.KVCapability{db.KVMutable}
}
// fireEventForKey maps from a key to an entity or group and fires an
// appropriate event for the given key.
func (bcs *BCStore) fireEventForKey(k string, t eventType) {
switch {
case t == eventUpdate && strings.HasPrefix(k, "/entities/"):
bcs.eF(db.Event{
PK: filepath.Base(k),
Type: db.EventEntityUpdate,
})
case t == eventDelete && strings.HasPrefix(k, "/entities/"):
bcs.eF(db.Event{
PK: filepath.Base(k),
Type: db.EventEntityDestroy,
})
case t == eventUpdate && strings.HasPrefix(k, "/groups/"):
bcs.eF(db.Event{
PK: filepath.Base(k),
Type: db.EventGroupUpdate,
})
case t == eventDelete && strings.HasPrefix(k, "/groups/"):
bcs.eF(db.Event{
PK: filepath.Base(k),
Type: db.EventGroupDestroy,
})
default:
bcs.l.Warn("Event translation called with unknown key prefix", "type", t, "key", k)
}
}
// SetEventFunc sets up a function to call to fire events to
// subscribers.
func (bcs *BCStore) SetEventFunc(f func(db.Event)) {
bcs.eF = f
}