Skip to content
This repository was archived by the owner on Mar 30, 2023. It is now read-only.

Commit 6e41214

Browse files
authored
Merge pull request #42 from RSE-Cambridge/fix-watching
Fix dacd watching
2 parents 90b482f + 27ec3bc commit 6e41214

22 files changed

Lines changed: 609 additions & 119 deletions

File tree

Makefile

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
# limitations under the License.
1212

1313

14-
all: deps buildlocal test format
14+
all: deps buildlocal format test
1515

1616
buildlocal:
1717
mkdir -p `pwd`/bin
@@ -24,8 +24,8 @@ format:
2424
test:
2525
mkdir -p `pwd`/bin
2626
./build/rebuild_mocks.sh
27-
go test -cover -race -coverprofile=./bin/coverage.txt ./...
2827
go vet ./...
28+
go test -cover -race -coverprofile=./bin/coverage.txt ./...
2929

3030
test-func:
3131
./build/func_test.sh

cmd/dac-func-test/pool.go

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -54,28 +54,6 @@ func testGetBricks(poolRegistry registry.PoolRegistry) {
5454
}
5555
}
5656

57-
func testAllocateBricks(poolRegistry registry.PoolRegistry) {
58-
poolRegistry.WatchHostBrickAllocations("foo", func(old *registry.BrickAllocation,
59-
new *registry.BrickAllocation) {
60-
log.Printf("**Allocation update. Old: %+v New: %+v", old, new)
61-
if new.DeallocateRequested {
62-
log.Printf("requested clean of: %d:%s", new.AllocatedIndex, new.Device)
63-
}
64-
})
65-
//allocations := []registry.BrickAllocation{
66-
// {Hostname: "foo", Device: "vbdb1", AllocatedVolume: "vol1"},
67-
// {Hostname: "foo", Device: "nvme3n1", AllocatedVolume: "vol1"},
68-
//}
69-
// TODO: create a volume to get the bricks allocated from?
70-
//if err := poolRegistry.AllocateBricks(allocations); err != nil {
71-
// log.Fatal(err)
72-
//}
73-
if err := poolRegistry.DeallocateBricks("vol1"); err != nil {
74-
log.Fatal(err)
75-
}
76-
log.Println("asdf")
77-
}
78-
7957
func testGetAllocations(poolRegistry registry.PoolRegistry) {
8058
allocations, err := poolRegistry.GetAllocationsForHost("foo")
8159
if err != nil {
@@ -132,7 +110,6 @@ func TestKeystorePoolRegistry(keystore keystoreregistry.Keystore) {
132110
poolRegistry := keystoreregistry.NewPoolRegistry(keystore)
133111
testUpdateHost(poolRegistry)
134112
testGetBricks(poolRegistry)
135-
testAllocateBricks(poolRegistry)
136113
testGetAllocations(poolRegistry)
137114
testDeleteAllocations(poolRegistry)
138115
testKeepHostAlive(poolRegistry)

cmd/dac-func-test/volume.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,7 @@ func TestKeystoreVolumeRegistry(keystore keystoreregistry.Keystore) {
1919
}
2020

2121
func testVolumeCRUD(volRegistry registry.VolumeRegistry) {
22-
volRegistry.WatchVolumeChanges("asdf", func(old *registry.Volume, new *registry.Volume) bool {
23-
log.Printf("Volume update detected. old: %s new: %s", old.State, new.State)
24-
return false
25-
})
22+
// TODO: test get volume changes?
2623

2724
volume := registry.Volume{Name: "asdf", State: registry.Registered, JobName: "foo", SizeBricks: 2, SizeGB: 200}
2825
volume2 := registry.Volume{Name: "asdf2", JobName: "foo", SizeBricks: 3, SizeGB: 300}

cmd/dacctl/main_test.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@ func (*stubKeystore) KeepAliveKey(key string) error {
175175
func (*stubKeystore) NewMutex(lockKey string) (keystoreregistry.Mutex, error) {
176176
panic("implement me")
177177
}
178+
func (*stubKeystore) Watch(ctxt context.Context, key string, withPrefix bool) keystoreregistry.KeyValueUpdateChan {
179+
panic("implement me")
180+
}
178181

179182
type stubDacctlActions struct{}
180183

internal/pkg/etcdregistry/keystore.go

Lines changed: 78 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,11 +65,19 @@ func newEtcdClient() *clientv3.Client {
6565

6666
func NewKeystore() keystoreregistry.Keystore {
6767
cli := newEtcdClient()
68-
return &etcKeystore{cli}
68+
return &etcKeystore{
69+
Watcher: cli.Watcher,
70+
KV: cli.KV,
71+
Lease: cli.Lease,
72+
Client: cli,
73+
}
6974
}
7075

7176
type etcKeystore struct {
72-
*clientv3.Client
77+
Watcher clientv3.Watcher
78+
KV clientv3.KV
79+
Lease clientv3.Lease
80+
Client *clientv3.Client
7381
}
7482

7583
func (client *etcKeystore) NewMutex(lockKey string) (keystoreregistry.Mutex, error) {
@@ -97,6 +105,10 @@ func handleError(err error) {
97105
}
98106
}
99107

108+
func (client *etcKeystore) Close() error {
109+
return client.Client.Close()
110+
}
111+
100112
func (client *etcKeystore) runTransaction(ifOps []clientv3.Cmp, thenOps []clientv3.Op) error {
101113
kvc := clientv3.NewKV(client.Client)
102114
kvc.Txn(context.Background())
@@ -195,7 +207,7 @@ func (client *etcKeystore) Get(key string) (keystoreregistry.KeyValueVersion, er
195207

196208
func (client *etcKeystore) WatchPrefix(prefix string,
197209
onUpdate func(old *keystoreregistry.KeyValueVersion, new *keystoreregistry.KeyValueVersion)) {
198-
rch := client.Watch(context.Background(), prefix, clientv3.WithPrefix(), clientv3.WithPrevKV())
210+
rch := client.Client.Watch(context.Background(), prefix, clientv3.WithPrefix(), clientv3.WithPrevKV())
199211
go func() {
200212
for wresp := range rch {
201213
for _, ev := range wresp.Events {
@@ -213,7 +225,7 @@ func (client *etcKeystore) WatchPrefix(prefix string,
213225

214226
func (client *etcKeystore) WatchKey(ctxt context.Context, key string,
215227
onUpdate func(old *keystoreregistry.KeyValueVersion, new *keystoreregistry.KeyValueVersion)) {
216-
rch := client.Watch(ctxt, key, clientv3.WithPrevKV())
228+
rch := client.Client.Watch(ctxt, key, clientv3.WithPrevKV())
217229
go func() {
218230
for watchResponse := range rch {
219231
for _, ev := range watchResponse.Events {
@@ -232,6 +244,66 @@ func (client *etcKeystore) WatchKey(ctxt context.Context, key string,
232244
}()
233245
}
234246

247+
// TODO: this needs fixing up
248+
func (client *etcKeystore) WatchForCondition(ctxt context.Context, key string, fromRevision int64,
249+
check func(update keystoreregistry.KeyValueUpdate) bool) (bool, error) {
250+
251+
// check key is present and find revision of the last update
252+
initialValue, err := client.Get(key)
253+
if err != nil {
254+
return false, err
255+
}
256+
if fromRevision < initialValue.CreateRevision {
257+
return false, errors.New("incorrect fromRevision")
258+
}
259+
260+
// no deadline set, so add default timeout of 10 mins
261+
var cancelFunc context.CancelFunc
262+
_, ok := ctxt.Deadline()
263+
if !ok {
264+
ctxt, cancelFunc = context.WithTimeout(ctxt, time.Minute*10)
265+
}
266+
267+
// open channel with etcd, starting with the last revision of the key from above
268+
rch := client.Client.Watch(ctxt, key, clientv3.WithPrefix(), clientv3.WithRev(fromRevision))
269+
if rch == nil {
270+
cancelFunc()
271+
return false, errors.New("no watcher returned from etcd")
272+
}
273+
274+
conditionMet := false
275+
go func() {
276+
for watchResponse := range rch {
277+
// TODO: this should instead use Watch from above!
278+
for _, ev := range watchResponse.Events {
279+
update := keystoreregistry.KeyValueUpdate{
280+
New: getKeyValueVersion(ev.Kv),
281+
Old: getKeyValueVersion(ev.PrevKv),
282+
}
283+
284+
// show deleted by returning nil for new
285+
isKeyDeleted := false
286+
if ev.Type == clientv3.EventTypeDelete {
287+
update.New = nil
288+
isKeyDeleted = true
289+
}
290+
291+
conditionMet := check(update)
292+
293+
// stop watching if the condition passed or key was deleted
294+
if conditionMet || isKeyDeleted {
295+
cancelFunc()
296+
return
297+
}
298+
}
299+
}
300+
// Assuming we get here when the context is cancelled or hits its timeout
301+
// i.e. there are no more events, so we close the channel
302+
}()
303+
304+
return conditionMet, nil
305+
}
306+
235307
func (client *etcKeystore) KeepAliveKey(key string) error {
236308
kvc := clientv3.NewKV(client.Client)
237309

@@ -243,7 +315,7 @@ func (client *etcKeystore) KeepAliveKey(key string) error {
243315

244316
// TODO what about configure timeout and ttl?
245317
var ttl int64 = 10
246-
grantResponse, err := client.Grant(context.Background(), ttl)
318+
grantResponse, err := client.Client.Grant(context.Background(), ttl)
247319
if err != nil {
248320
log.Fatal(err)
249321
}
@@ -258,7 +330,7 @@ func (client *etcKeystore) KeepAliveKey(key string) error {
258330
return fmt.Errorf("unable to create keep-alive key: %s", key)
259331
}
260332

261-
ch, err := client.KeepAlive(context.Background(), leaseID)
333+
ch, err := client.Client.KeepAlive(context.Background(), leaseID)
262334
if err != nil {
263335
log.Fatal(err)
264336
}

internal/pkg/etcdregistry/watch.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package etcdregistry
2+
3+
import (
4+
"context"
5+
"github.com/RSE-Cambridge/data-acc/internal/pkg/keystoreregistry"
6+
"github.com/coreos/etcd/clientv3"
7+
)
8+
9+
func (client *etcKeystore) Watch(ctxt context.Context, key string, withPrefix bool) keystoreregistry.KeyValueUpdateChan {
10+
options := []clientv3.OpOption{clientv3.WithPrevKV()}
11+
if withPrefix {
12+
options = append(options, clientv3.WithPrefix())
13+
}
14+
rch := client.Watcher.Watch(ctxt, key, options...)
15+
16+
c := make(chan keystoreregistry.KeyValueUpdate)
17+
18+
go processWatchEvents(rch, c)
19+
20+
return c
21+
}
22+
23+
func processWatchEvents(watchChan clientv3.WatchChan, c chan keystoreregistry.KeyValueUpdate) {
24+
for watchResponse := range watchChan {
25+
// if error, send empty update with an error
26+
err := watchResponse.Err()
27+
if err != nil {
28+
c <- keystoreregistry.KeyValueUpdate{Err: err}
29+
}
30+
31+
// send all events in this watch response
32+
for _, ev := range watchResponse.Events {
33+
update := keystoreregistry.KeyValueUpdate{
34+
IsCreate: ev.IsCreate(),
35+
IsModify: ev.IsModify(),
36+
IsDelete: ev.Type == clientv3.EventTypeDelete,
37+
}
38+
if update.IsCreate || update.IsModify {
39+
update.New = getKeyValueVersion(ev.Kv)
40+
}
41+
if update.IsDelete || update.IsModify {
42+
update.Old = getKeyValueVersion(ev.PrevKv)
43+
}
44+
45+
c <- update
46+
}
47+
}
48+
49+
// Assuming we get here when the context is cancelled or hits its timeout
50+
// i.e. there are no more events, so we close the channel
51+
close(c)
52+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package etcdregistry
2+
3+
import (
4+
"context"
5+
"github.com/coreos/etcd/clientv3"
6+
"github.com/coreos/etcd/mvcc/mvccpb"
7+
"github.com/stretchr/testify/assert"
8+
"testing"
9+
)
10+
11+
type fakeWatcher struct {
12+
t *testing.T
13+
ch clientv3.WatchChan
14+
opts []clientv3.OpOption
15+
}
16+
17+
func (fw fakeWatcher) Watch(ctx context.Context, key string, opts ...clientv3.OpOption) clientv3.WatchChan {
18+
assert.Equal(fw.t, "key", key)
19+
assert.EqualValues(fw.t, len(fw.opts), len(opts)) // TODO: how to assert this properly?
20+
return fw.ch
21+
}
22+
func (fakeWatcher) Close() error {
23+
panic("implement me")
24+
}
25+
26+
func TestEtcKeystore_Watch_Nil(t *testing.T) {
27+
keystore := etcKeystore{
28+
Watcher: fakeWatcher{
29+
t: t, ch: nil,
30+
opts: []clientv3.OpOption{clientv3.WithPrevKV()},
31+
},
32+
}
33+
34+
response := keystore.Watch(context.TODO(), "key", false)
35+
36+
assert.Empty(t, response)
37+
}
38+
39+
func TestEtcKeystore_Watch(t *testing.T) {
40+
ch := make(chan clientv3.WatchResponse)
41+
42+
keystore := etcKeystore{
43+
Watcher: fakeWatcher{
44+
t: t, ch: ch,
45+
opts: []clientv3.OpOption{clientv3.WithPrefix(), clientv3.WithPrevKV()},
46+
},
47+
}
48+
49+
go func() {
50+
ch <- clientv3.WatchResponse{
51+
Events: []*clientv3.Event{
52+
{Type: clientv3.EventTypePut, Kv: &mvccpb.KeyValue{Key: []byte("key1")}},
53+
{Type: clientv3.EventTypePut, Kv: &mvccpb.KeyValue{Key: []byte("key2")}},
54+
}}
55+
ch <- clientv3.WatchResponse{
56+
Events: []*clientv3.Event{
57+
{
58+
Type: clientv3.EventTypePut,
59+
Kv: &mvccpb.KeyValue{ModRevision: 1, Key: []byte("key2")},
60+
PrevKv: &mvccpb.KeyValue{ModRevision: 1, Key: []byte("key2")},
61+
},
62+
}}
63+
ch <- clientv3.WatchResponse{
64+
Events: []*clientv3.Event{
65+
{Type: clientv3.EventTypeDelete, PrevKv: &mvccpb.KeyValue{Key: []byte("key2")}},
66+
{Type: clientv3.EventTypeDelete, PrevKv: &mvccpb.KeyValue{Key: []byte("key1")}},
67+
}}
68+
ch <- clientv3.WatchResponse{Canceled: true}
69+
close(ch)
70+
}()
71+
72+
response := keystore.Watch(context.TODO(), "key", true)
73+
74+
ev1 := <-response
75+
assert.True(t, ev1.IsCreate)
76+
assert.False(t, ev1.IsModify)
77+
assert.False(t, ev1.IsDelete)
78+
assert.Nil(t, ev1.Old)
79+
assert.EqualValues(t, "key1", ev1.New.Key)
80+
81+
ev2 := <-response
82+
assert.True(t, ev2.IsCreate)
83+
assert.False(t, ev2.IsModify)
84+
assert.False(t, ev2.IsDelete)
85+
assert.Nil(t, ev2.Old)
86+
assert.EqualValues(t, "key2", ev2.New.Key)
87+
88+
ev3 := <-response
89+
assert.False(t, ev3.IsCreate)
90+
assert.True(t, ev3.IsModify)
91+
assert.False(t, ev3.IsDelete)
92+
assert.EqualValues(t, "key2", ev3.New.Key)
93+
assert.EqualValues(t, "key2", ev3.Old.Key)
94+
95+
ev4 := <-response
96+
assert.False(t, ev4.IsCreate)
97+
assert.False(t, ev4.IsModify)
98+
assert.True(t, ev4.IsDelete)
99+
assert.Nil(t, ev4.New)
100+
assert.EqualValues(t, "key2", ev4.Old.Key)
101+
102+
ev5 := <-response
103+
assert.False(t, ev5.IsCreate)
104+
assert.False(t, ev5.IsModify)
105+
assert.True(t, ev5.IsDelete)
106+
assert.Nil(t, ev5.New)
107+
assert.EqualValues(t, "key1", ev5.Old.Key)
108+
109+
ev6 := <-response
110+
assert.Equal(t,
111+
"etcdserver: mvcc: required revision is a future revision",
112+
ev6.Err.Error())
113+
114+
// Check channels are closed
115+
_, ok := <-response
116+
assert.False(t, ok)
117+
_, ok = <-ch
118+
assert.False(t, ok)
119+
}

0 commit comments

Comments
 (0)