tests/robustness: Make watch event independent from etcd operation

Signed-off-by: Marek Siarkowicz <siarkowicz@google.com>
This commit is contained in:
Marek Siarkowicz 2023-06-22 15:14:04 +02:00
parent 04d24c2128
commit 3f09a51c60
8 changed files with 50 additions and 56 deletions

View File

@ -95,17 +95,17 @@ func TestModelDescribe(t *testing.T) {
expectDescribe: `if(mod_rev(key9)==9).then(put("key9", "99")) -> err: "failed"`,
},
{
req: txnRequest([]EtcdCondition{{Key: "key9b", ExpectedRevision: 9}}, []EtcdOperation{{Type: PutOperation, Key: "key9b", PutOptions: PutOptions{Value: ValueOrHash{Value: "991"}}}}, []EtcdOperation{{Type: RangeOperation, Key: "key9b"}}),
req: txnRequest([]EtcdCondition{{Key: "key9b", ExpectedRevision: 9}}, []EtcdOperation{{Type: PutOperation, Key: "key9b", Value: ValueOrHash{Value: "991"}}}, []EtcdOperation{{Type: RangeOperation, Key: "key9b"}}),
resp: txnResponse([]EtcdOperationResult{{}}, true, 10),
expectDescribe: `if(mod_rev(key9b)==9).then(put("key9b", "991")).else(get("key9b")) -> success(ok), rev: 10`,
},
{
req: txnRequest([]EtcdCondition{{Key: "key9c", ExpectedRevision: 9}}, []EtcdOperation{{Type: PutOperation, Key: "key9c", PutOptions: PutOptions{Value: ValueOrHash{Value: "992"}}}}, []EtcdOperation{{Type: RangeOperation, Key: "key9c"}}),
req: txnRequest([]EtcdCondition{{Key: "key9c", ExpectedRevision: 9}}, []EtcdOperation{{Type: PutOperation, Key: "key9c", Value: ValueOrHash{Value: "992"}}}, []EtcdOperation{{Type: RangeOperation, Key: "key9c"}}),
resp: txnResponse([]EtcdOperationResult{{RangeResponse: RangeResponse{KVs: []KeyValue{{Key: "key9c", ValueRevision: ValueRevision{Value: ValueOrHash{Value: "993"}, ModRevision: 10}}}}}}, false, 10),
expectDescribe: `if(mod_rev(key9c)==9).then(put("key9c", "992")).else(get("key9c")) -> failure("993"), rev: 10`,
},
{
req: txnRequest(nil, []EtcdOperation{{Type: RangeOperation, Key: "10"}, {Type: PutOperation, Key: "11", PutOptions: PutOptions{Value: ValueOrHash{Value: "111"}}}, {Type: DeleteOperation, Key: "12"}}, nil),
req: txnRequest(nil, []EtcdOperation{{Type: RangeOperation, Key: "10"}, {Type: PutOperation, Key: "11", Value: ValueOrHash{Value: "111"}}, {Type: DeleteOperation, Key: "12"}}, nil),
resp: txnResponse([]EtcdOperationResult{{RangeResponse: RangeResponse{KVs: []KeyValue{{ValueRevision: ValueRevision{Value: ValueOrHash{Value: "110"}}}}}}, {}, {Deleted: 1}}, true, 10),
expectDescribe: `get("10"), put("11", "111"), delete("12") -> "110", ok, deleted: 1, rev: 10`,
},

View File

@ -261,11 +261,6 @@ type RangeOptions struct {
Limit int64
}
type PutOptions struct {
Value ValueOrHash
LeaseID int64
}
type TxnRequest struct {
Conditions []EtcdCondition
OperationsOnSuccess []EtcdOperation
@ -281,7 +276,8 @@ type EtcdOperation struct {
Type OperationType
Key string
RangeOptions
PutOptions
Value ValueOrHash
LeaseID int64
}
type OperationType string

View File

@ -239,23 +239,20 @@ func toEtcdCondition(cmp clientv3.Cmp) (cond EtcdCondition) {
return cond
}
func toEtcdOperation(op clientv3.Op) EtcdOperation {
var opType OperationType
func toEtcdOperation(option clientv3.Op) (op EtcdOperation) {
op.Key = string(option.KeyBytes())
switch {
case op.IsGet():
opType = RangeOperation
case op.IsPut():
opType = PutOperation
case op.IsDelete():
opType = DeleteOperation
case option.IsGet():
op.Type = RangeOperation
case option.IsPut():
op.Type = PutOperation
op.Value = ValueOrHash{Value: string(option.ValueBytes())}
case option.IsDelete():
op.Type = DeleteOperation
default:
panic("Unsupported operation")
}
return EtcdOperation{
Type: opType,
Key: string(op.KeyBytes()),
PutOptions: PutOptions{Value: ValueOrHash{Value: string(op.ValueBytes())}},
}
return op
}
func toEtcdOperationResult(resp *etcdserverpb.ResponseOp) EtcdOperationResult {
@ -384,7 +381,7 @@ func partialResponse(revision int64) MaybeEtcdResponse {
}
func putRequest(key, value string) EtcdRequest {
return EtcdRequest{Type: Txn, Txn: &TxnRequest{OperationsOnSuccess: []EtcdOperation{{Type: PutOperation, Key: key, PutOptions: PutOptions{Value: ToValueOrHash(value)}}}}}
return EtcdRequest{Type: Txn, Txn: &TxnRequest{OperationsOnSuccess: []EtcdOperation{{Type: PutOperation, Key: key, Value: ToValueOrHash(value)}}}}
}
func putResponse(revision int64) MaybeEtcdResponse {
@ -415,7 +412,7 @@ func compareRevision(key string, expectedRevision int64) *EtcdCondition {
}
func putOperation(key, value string) *EtcdOperation {
return &EtcdOperation{Type: PutOperation, Key: key, PutOptions: PutOptions{Value: ToValueOrHash(value)}}
return &EtcdOperation{Type: PutOperation, Key: key, Value: ToValueOrHash(value)}
}
func txnRequestSingleOperation(cond *EtcdCondition, onSuccess, onFailure *EtcdOperation) EtcdRequest {
@ -451,7 +448,7 @@ func txnResponse(result []EtcdOperationResult, succeeded bool, revision int64) M
}
func putWithLeaseRequest(key, value string, leaseID int64) EtcdRequest {
return EtcdRequest{Type: Txn, Txn: &TxnRequest{OperationsOnSuccess: []EtcdOperation{{Type: PutOperation, Key: key, PutOptions: PutOptions{Value: ToValueOrHash(value), LeaseID: leaseID}}}}}
return EtcdRequest{Type: Txn, Txn: &TxnRequest{OperationsOnSuccess: []EtcdOperation{{Type: PutOperation, Key: key, Value: ToValueOrHash(value), LeaseID: leaseID}}}}
}
func leaseGrantRequest(leaseID int64) EtcdRequest {

View File

@ -73,7 +73,12 @@ func (r *EtcdReplay) next() (request EtcdRequest, revision int64, index int) {
index = r.eventHistoryIndex
operations := []EtcdOperation{}
for r.eventHistory[index].Revision == revision {
operations = append(operations, r.eventHistory[index].Op)
event := r.eventHistory[index]
operations = append(operations, EtcdOperation{
Type: event.Type,
Key: event.Key,
Value: event.Value,
})
index++
}
return EtcdRequest{
@ -84,16 +89,13 @@ func (r *EtcdReplay) next() (request EtcdRequest, revision int64, index int) {
}, revision, index
}
func operationToRequest(op EtcdOperation) EtcdRequest {
return EtcdRequest{
Type: Txn,
Txn: &TxnRequest{
OperationsOnSuccess: []EtcdOperation{op},
},
}
}
type WatchEvent struct {
Op EtcdOperation
Event
Revision int64
}
type Event struct {
Type OperationType
Key string
Value ValueOrHash
}

View File

@ -266,10 +266,10 @@ func toWatchEvent(event clientv3.Event) model.WatchEvent {
}
return model.WatchEvent{
Revision: event.Kv.ModRevision,
Op: model.EtcdOperation{
Type: op,
Key: string(event.Kv.Key),
PutOptions: model.PutOptions{Value: model.ToValueOrHash(string(event.Kv.Value))},
Event: model.Event{
Type: op,
Key: string(event.Kv.Key),
Value: model.ToValueOrHash(string(event.Kv.Value)),
},
}
}

View File

@ -21,7 +21,7 @@ import (
"go.etcd.io/etcd/tests/v3/robustness/traffic"
)
func patchOperationsWithWatchEvents(operations []porcupine.Operation, watchEvents map[model.EtcdOperation]traffic.TimedWatchEvent) []porcupine.Operation {
func patchOperationsWithWatchEvents(operations []porcupine.Operation, watchEvents map[model.Event]traffic.TimedWatchEvent) []porcupine.Operation {
newOperations := make([]porcupine.Operation, 0, len(operations))
lastObservedOperation := lastOperationObservedInWatch(operations, watchEvents)
@ -51,7 +51,7 @@ func patchOperationsWithWatchEvents(operations []porcupine.Operation, watchEvent
return newOperations
}
func lastOperationObservedInWatch(operations []porcupine.Operation, watchEvents map[model.EtcdOperation]traffic.TimedWatchEvent) porcupine.Operation {
func lastOperationObservedInWatch(operations []porcupine.Operation, watchEvents map[model.Event]traffic.TimedWatchEvent) porcupine.Operation {
var maxCallTime int64
var lastOperation porcupine.Operation
for _, op := range operations {
@ -68,16 +68,13 @@ func lastOperationObservedInWatch(operations []porcupine.Operation, watchEvents
return lastOperation
}
func matchWatchEvent(request *model.TxnRequest, watchEvents map[model.EtcdOperation]traffic.TimedWatchEvent) *traffic.TimedWatchEvent {
func matchWatchEvent(request *model.TxnRequest, watchEvents map[model.Event]traffic.TimedWatchEvent) *traffic.TimedWatchEvent {
for _, etcdOp := range append(request.OperationsOnSuccess, request.OperationsOnFailure...) {
if etcdOp.Type == model.PutOperation {
// Remove LeaseID which is not exposed in watch.
event, ok := watchEvents[model.EtcdOperation{
Type: etcdOp.Type,
Key: etcdOp.Key,
PutOptions: model.PutOptions{
Value: etcdOp.Value,
},
event, ok := watchEvents[model.Event{
Type: etcdOp.Type,
Key: etcdOp.Key,
Value: etcdOp.Value,
}]
if ok {
return &event

View File

@ -41,12 +41,12 @@ func operations(reports []traffic.ClientReport) []porcupine.Operation {
return ops
}
func uniqueWatchEvents(reports []traffic.ClientReport) map[model.EtcdOperation]traffic.TimedWatchEvent {
persisted := map[model.EtcdOperation]traffic.TimedWatchEvent{}
func uniqueWatchEvents(reports []traffic.ClientReport) map[model.Event]traffic.TimedWatchEvent {
persisted := map[model.Event]traffic.TimedWatchEvent{}
for _, r := range reports {
for _, resp := range r.Watch {
for _, event := range resp.Events {
persisted[event.Op] = traffic.TimedWatchEvent{Time: resp.Time, WatchEvent: event}
persisted[event.Event] = traffic.TimedWatchEvent{Time: resp.Time, WatchEvent: event}
}
}
}

View File

@ -79,11 +79,12 @@ func validateUnique(t *testing.T, expectUniqueRevision bool, report traffic.Clie
key = struct {
revision int64
key string
}{event.Revision, event.Op.Key}
}{event.Revision, event.Key}
}
if _, found := uniqueOperations[key]; found {
t.Errorf("Broke watch guarantee: Unique - an event will never appear on a watch twice, key: %q, revision: %d, client: %d", event.Op.Key, event.Revision, report.ClientId)
t.Errorf("Broke watch guarantee: Unique - an event will never appear on a watch twice, key: %q, revision: %d, client: %d", event.Key, event.Revision, report.ClientId)
}
uniqueOperations[key] = struct{}{}
}
@ -137,7 +138,8 @@ func validateEventsMatch(t *testing.T, reports []traffic.ClientReport) {
for _, r := range reports {
for _, resp := range r.Watch {
for _, event := range resp.Events {
rk := revisionKey{key: event.Op.Key, revision: event.Revision}
rk := revisionKey{key: event.Key, revision: event.Revision}
if prev, found := revisionKeyToEvent[rk]; found {
if prev.WatchEvent != event {
t.Errorf("Events between clients %d and %d don't match, key: %q, revision: %d, diff: %s", prev.ClientId, r.ClientId, rk.key, rk.revision, cmp.Diff(prev, event))