Skip to content

Commit 5b913af

Browse files
committed
Extract methods for accessing EtcdState fields
Signed-off-by: Marek Siarkowicz <siarkowicz@google.com>
1 parent 93f54ae commit 5b913af

5 files changed

Lines changed: 120 additions & 50 deletions

File tree

tests/robustness/model/describe.go

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,6 @@ package model
1616

1717
import (
1818
"fmt"
19-
"maps"
20-
"slices"
21-
"sort"
2219
"strings"
2320

2421
"go.etcd.io/etcd/client/pkg/v3/types"
@@ -91,19 +88,18 @@ func describeEtcdState(state EtcdState) string {
9188
if len(state.KeyValues) > 0 {
9289
descHTML = append(descHTML, "keys: <ul style=\"margin: 0.25em 0;\">")
9390

94-
keys := slices.Collect(maps.Keys(state.KeyValues))
95-
sort.Strings(keys)
96-
for _, key := range keys {
97-
descHTML = append(descHTML, fmt.Sprintf("<li style=\"margin: 0.25em 0;\"><strong>%s</strong> - ", key))
91+
keys, values, leases := state.KeysValueLeases()
92+
for i := range keys {
93+
descHTML = append(descHTML, fmt.Sprintf("<li style=\"margin: 0.25em 0;\"><strong>%s</strong> - ", keys[i]))
9894

99-
value := state.KeyValues[key]
95+
value := values[i]
10096
if value.Value.Value != "" {
10197
descHTML = append(descHTML, fmt.Sprintf("val: %q, ", value.Value.Value))
10298
}
10399
if value.Value.Hash != 0 {
104100
descHTML = append(descHTML, fmt.Sprintf("hash: %d, ", value.Value.Hash))
105101
}
106-
lease := state.KeyLeases[key]
102+
lease := leases[i]
107103
if lease != 0 {
108104
descHTML = append(descHTML, fmt.Sprintf("lease: %d, ", lease))
109105
}
@@ -114,10 +110,9 @@ func describeEtcdState(state EtcdState) string {
114110
descHTML = append(descHTML, "</ul>")
115111
}
116112

117-
if len(state.Leases) > 0 {
113+
leases := state.leases()
114+
if len(leases) > 0 {
118115
descHTML = append(descHTML, "leases: <ul style=\"margin: 0.25em 0;\">")
119-
leases := slices.Collect(maps.Keys(state.Leases))
120-
slices.Sort(leases)
121116
for _, lease := range leases {
122117
descHTML = append(descHTML, fmt.Sprintf("<li style=\"margin: 0.25em 0;\"><strong>%d</strong></li>", lease))
123118
}

tests/robustness/model/deterministic.go

Lines changed: 67 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"html"
2121
"maps"
2222
"reflect"
23+
"slices"
2324
"sort"
2425

2526
"github.com/anishathalye/porcupine"
@@ -163,7 +164,10 @@ func (s EtcdState) stepTxn(request EtcdRequest) (EtcdState, MaybeEtcdResponse) {
163164
newState := s.DeepCopy()
164165
failure := false
165166
for _, cond := range request.Txn.Conditions {
166-
val := newState.KeyValues[cond.Key]
167+
val, ok := newState.GetValue(cond.Key)
168+
if !ok {
169+
val = &ValueRevision{}
170+
}
167171
if cond.ExpectedVersion > 0 {
168172
if val.Version != cond.ExpectedVersion {
169173
failure = true
@@ -187,29 +191,29 @@ func (s EtcdState) stepTxn(request EtcdRequest) (EtcdState, MaybeEtcdResponse) {
187191
RangeResponse: newState.getRange(op.Range),
188192
}
189193
case PutOperation:
190-
_, leaseExists := newState.Leases[op.Put.LeaseID]
191-
if op.Put.LeaseID != 0 && !leaseExists {
192-
break
194+
var leaseID *int64
195+
if op.Put.LeaseID != 0 {
196+
if !newState.leaseExists(op.Put.LeaseID) {
197+
break
198+
}
199+
leaseID = &op.Put.LeaseID
193200
}
194201
ver := int64(1)
195-
if val, exists := newState.KeyValues[op.Put.Key]; exists && val.Version > 0 {
196-
ver = val.Version + 1
202+
valPtr, exists := newState.GetValue(op.Put.Key)
203+
if exists && valPtr.Version > 0 {
204+
ver = valPtr.Version + 1
197205
}
198-
newState.KeyValues[op.Put.Key] = ValueRevision{
206+
val := ValueRevision{
199207
Value: op.Put.Value,
200208
ModRevision: newState.Revision + 1,
201209
Version: ver,
202210
}
211+
newState.setValueLease(op.Put.Key, val, leaseID)
203212
increaseRevision = true
204-
newState = detachFromOldLease(newState, op.Put.Key)
205-
if leaseExists {
206-
newState = attachToNewLease(newState, op.Put.LeaseID, op.Put.Key)
207-
}
208213
case DeleteOperation:
209-
if _, ok := newState.KeyValues[op.Delete.Key]; ok {
210-
delete(newState.KeyValues, op.Delete.Key)
214+
if _, ok := newState.GetValue(op.Delete.Key); ok {
215+
newState.deleteKey(op.Delete.Key)
211216
increaseRevision = true
212-
newState = detachFromOldLease(newState, op.Delete.Key)
213217
opResp[i].Deleted = 1
214218
}
215219
default:
@@ -294,28 +298,69 @@ func (s EtcdState) getRange(options RangeOptions) RangeResponse {
294298
}
295299
response.Count = count
296300
} else {
297-
value, ok := s.KeyValues[options.Start]
301+
valPtr, ok := s.GetValue(options.Start)
298302
if ok {
299303
response.KVs = append(response.KVs, KeyValue{
300304
Key: options.Start,
301-
ValueRevision: value,
305+
ValueRevision: *valPtr,
302306
})
303307
response.Count = 1
304308
}
305309
}
306310
return response
307311
}
308312

309-
func detachFromOldLease(s EtcdState, key string) EtcdState {
313+
func (s EtcdState) KeysValueLeases() (keys []string, values []ValueRevision, leases []int64) {
314+
keys = make([]string, 0, len(s.KeyValues))
315+
values = make([]ValueRevision, 0, len(s.KeyValues))
316+
leases = make([]int64, 0, len(s.KeyLeases))
317+
318+
for k, v := range s.KeyValues {
319+
keys = append(keys, k)
320+
values = append(values, v)
321+
leases = append(leases, s.KeyLeases[k])
322+
}
323+
return keys, values, leases
324+
}
325+
326+
func (s EtcdState) leases() []int64 {
327+
return slices.Collect(maps.Keys(s.Leases))
328+
}
329+
330+
func (s EtcdState) GetValue(key string) (*ValueRevision, bool) {
331+
val, ok := s.KeyValues[key]
332+
if !ok {
333+
return nil, false
334+
}
335+
return &val, true
336+
}
337+
338+
func (s EtcdState) setValueLease(key string, val ValueRevision, lease *int64) {
339+
s.KeyValues[key] = val
310340
if oldLeaseID, ok := s.KeyLeases[key]; ok {
311341
delete(s.Leases[oldLeaseID].Keys, key)
342+
}
343+
if lease != nil {
344+
s.KeyLeases[key] = *lease
345+
s.Leases[*lease].Keys[key] = leased
346+
} else {
312347
delete(s.KeyLeases, key)
313348
}
314-
return s
315349
}
316350

317-
func attachToNewLease(s EtcdState, leaseID int64, key string) EtcdState {
318-
s.KeyLeases[key] = leaseID
319-
s.Leases[leaseID].Keys[key] = leased
320-
return s
351+
func (s EtcdState) leaseExists(lease int64) bool {
352+
_, ok := s.Leases[lease]
353+
return ok
354+
}
355+
356+
func (s EtcdState) deleteKey(key string) {
357+
delete(s.KeyValues, key)
358+
if oldLeaseID, ok := s.KeyLeases[key]; ok {
359+
delete(s.Leases[oldLeaseID].Keys, key)
360+
}
361+
delete(s.KeyLeases, key)
362+
}
363+
364+
func (s EtcdState) leaseKeys(leaseID int64) []string {
365+
return slices.Sorted(maps.Keys(s.Leases[leaseID].Keys))
321366
}

tests/robustness/model/replay.go

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ package model
1616

1717
import (
1818
"fmt"
19-
"sort"
2019
"strings"
2120

2221
"github.com/anishathalye/porcupine"
@@ -99,7 +98,7 @@ func toWatchEvents(prevState *EtcdState, request EtcdRequest, response MaybeEtcd
9998
events = append(events, e)
10099
}
101100
case PutOperation:
102-
_, leaseExists := prevState.Leases[op.Put.LeaseID]
101+
leaseExists := prevState.leaseExists(op.Put.LeaseID)
103102
if op.Put.LeaseID != 0 && !leaseExists {
104103
break
105104
}
@@ -112,7 +111,7 @@ func toWatchEvents(prevState *EtcdState, request EtcdRequest, response MaybeEtcd
112111
},
113112
Revision: response.Revision,
114113
}
115-
if _, ok := prevState.KeyValues[op.Put.Key]; !ok {
114+
if _, ok := prevState.GetValue(op.Put.Key); !ok {
116115
e.IsCreate = true
117116
}
118117
events = append(events, e)
@@ -121,15 +120,7 @@ func toWatchEvents(prevState *EtcdState, request EtcdRequest, response MaybeEtcd
121120
}
122121
}
123122
case LeaseRevoke:
124-
deletedKeys := []string{}
125-
for key := range prevState.Leases[request.LeaseRevoke.LeaseID].Keys {
126-
if _, ok := prevState.KeyValues[key]; ok {
127-
deletedKeys = append(deletedKeys, key)
128-
}
129-
}
130-
131-
sort.Strings(deletedKeys)
132-
for _, key := range deletedKeys {
123+
for _, key := range prevState.leaseKeys(request.LeaseRevoke.LeaseID) {
133124
e := PersistedEvent{
134125
Event: Event{
135126
Type: DeleteOperation,

tests/robustness/validate/validate_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,39 @@ func TestValidateWatch(t *testing.T) {
226226
putRequest("b", "2"),
227227
},
228228
},
229+
{
230+
name: "Reliable - Put with non-existent lease doesn't generate watch event - pass",
231+
reports: []report.ClientReport{
232+
{
233+
Watch: []model.WatchOperation{
234+
{
235+
Request: model.WatchRequest{
236+
Key: "a",
237+
},
238+
Responses: []model.WatchResponse{},
239+
},
240+
},
241+
},
242+
},
243+
persistedRequests: []model.EtcdRequest{
244+
{
245+
Type: model.Txn,
246+
Txn: &model.TxnRequest{
247+
OperationsOnSuccess: []model.EtcdOperation{
248+
{
249+
Type: model.PutOperation,
250+
Put: model.PutOptions{
251+
Key: "a",
252+
Value: model.ToValueOrHash("1"),
253+
LeaseID: 1,
254+
},
255+
},
256+
},
257+
},
258+
},
259+
},
260+
expectError: "",
261+
},
229262
{
230263
name: "Ordered, Unique - unique ordered events in separate response - pass",
231264
reports: []report.ClientReport{

tests/robustness/validate/watch.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -278,9 +278,15 @@ func validatePrevKV(lg *zap.Logger, replay *model.EtcdReplay, report report.Clie
278278

279279
// We allow PrevValue to be nil since in the face of compaction, etcd does not
280280
// guarantee its presence.
281-
if event.PrevValue != nil && *event.PrevValue != state.KeyValues[event.Key] {
282-
lg.Error("Incorrect event prevValue field", zap.Int("client", report.ClientID), zap.Any("event", event), zap.Any("previousValue", state.KeyValues[event.Key]))
283-
err = errBrokePrevKV
281+
if event.PrevValue != nil {
282+
val, ok := state.GetValue(event.Key)
283+
if !ok {
284+
lg.Error("Incorrect event prevValue field", zap.Int("client", report.ClientID), zap.Any("event", event), zap.Any("previousValue", nil))
285+
err = errBrokePrevKV
286+
} else if *event.PrevValue != *val {
287+
lg.Error("Incorrect event prevValue field", zap.Int("client", report.ClientID), zap.Any("event", event), zap.Any("previousValue", *val))
288+
err = errBrokePrevKV
289+
}
284290
}
285291
}
286292
}
@@ -299,7 +305,7 @@ func validateIsCreate(lg *zap.Logger, replay *model.EtcdReplay, report report.Cl
299305
}
300306
// A create event will not have an entry in our history and a non-create
301307
// event *should* have an entry in our history.
302-
if _, prevKeyExists := state.KeyValues[event.Key]; event.IsCreate == prevKeyExists {
308+
if _, prevKeyExists := state.GetValue(event.Key); event.IsCreate == prevKeyExists {
303309
lg.Error("Incorrect event IsCreate field", zap.Int("client", report.ClientID), zap.Any("event", event))
304310
err = errBrokeIsCreate
305311
}

0 commit comments

Comments
 (0)