From 974655e02cc61c34056f79693b6e0b7fd6c43923 Mon Sep 17 00:00:00 2001 From: Marek Siarkowicz Date: Wed, 14 Jun 2023 13:24:06 +0200 Subject: [PATCH 1/2] tests/robustness: Rename operations const to separate from RequestType Signed-off-by: Marek Siarkowicz --- tests/robustness/model/describe.go | 12 ++++++------ tests/robustness/model/describe_test.go | 6 +++--- tests/robustness/model/deterministic.go | 20 ++++++++++++++------ tests/robustness/model/history.go | 16 ++++++++-------- tests/robustness/model/non_deterministic.go | 8 -------- tests/robustness/traffic/client.go | 4 ++-- tests/robustness/traffic/etcd.go | 16 ++++++++-------- tests/robustness/validate/patch_history.go | 6 +++--- 8 files changed, 44 insertions(+), 44 deletions(-) diff --git a/tests/robustness/model/describe.go b/tests/robustness/model/describe.go index e46c5276b..325ccfeff 100644 --- a/tests/robustness/model/describe.go +++ b/tests/robustness/model/describe.go @@ -100,7 +100,7 @@ func describeTxnResponse(request *TxnRequest, response *TxnResponse) string { func describeEtcdOperation(op EtcdOperation) string { switch op.Type { - case Range: + case RangeOperation: if op.WithPrefix { if op.Limit != 0 { return fmt.Sprintf("range(%q, limit=%d)", op.Key, op.Limit) @@ -108,12 +108,12 @@ func describeEtcdOperation(op EtcdOperation) string { return fmt.Sprintf("range(%q)", op.Key) } return fmt.Sprintf("get(%q)", op.Key) - case Put: + case PutOperation: if op.LeaseID != 0 { return fmt.Sprintf("put(%q, %s, %d)", op.Key, describeValueOrHash(op.Value), op.LeaseID) } return fmt.Sprintf("put(%q, %s)", op.Key, describeValueOrHash(op.Value)) - case Delete: + case DeleteOperation: return fmt.Sprintf("delete(%q)", op.Key) default: return fmt.Sprintf("", op.Type) @@ -122,7 +122,7 @@ func describeEtcdOperation(op EtcdOperation) string { func describeEtcdOperationResponse(req EtcdOperation, resp EtcdOperationResult) string { switch req.Type { - case Range: + case RangeOperation: if req.WithPrefix { kvs := make([]string, len(resp.KVs)) for i, kv := range resp.KVs { @@ -136,9 +136,9 @@ func describeEtcdOperationResponse(req EtcdOperation, resp EtcdOperationResult) return describeValueOrHash(resp.KVs[0].Value) } } - case Put: + case PutOperation: return fmt.Sprintf("ok") - case Delete: + case DeleteOperation: return fmt.Sprintf("deleted: %d", resp.Deleted) default: return fmt.Sprintf("", req.Type) diff --git a/tests/robustness/model/describe_test.go b/tests/robustness/model/describe_test.go index 17540e3ce..a0522e6a7 100644 --- a/tests/robustness/model/describe_test.go +++ b/tests/robustness/model/describe_test.go @@ -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: Put, Key: "key9b", Value: ValueOrHash{Value: "991"}}}, []EtcdOperation{{Type: Range, 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: Put, Key: "key9c", Value: ValueOrHash{Value: "992"}}}, []EtcdOperation{{Type: Range, 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{{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: Range, Key: "10"}, {Type: Put, Key: "11", Value: ValueOrHash{Value: "111"}}, {Type: Delete, 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{{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`, }, diff --git a/tests/robustness/model/deterministic.go b/tests/robustness/model/deterministic.go index 12c40ff57..0220a2fdc 100644 --- a/tests/robustness/model/deterministic.go +++ b/tests/robustness/model/deterministic.go @@ -83,19 +83,19 @@ func initState(request EtcdRequest, response EtcdResponse) etcdState { for i, op := range request.Txn.OperationsOnSuccess { opResp := response.Txn.Results[i] switch op.Type { - case Range: + case RangeOperation: for _, kv := range opResp.KVs { state.KeyValues[kv.Key] = ValueRevision{ Value: kv.Value, ModRevision: kv.ModRevision, } } - case Put: + case PutOperation: state.KeyValues[op.Key] = ValueRevision{ Value: op.Value, ModRevision: response.Revision, } - case Delete: + case DeleteOperation: default: panic("Unknown operation") } @@ -147,7 +147,7 @@ func (s etcdState) step(request EtcdRequest) (etcdState, EtcdResponse) { increaseRevision := false for i, op := range operations { switch op.Type { - case Range: + case RangeOperation: opResp[i] = EtcdOperationResult{ KVs: []KeyValue{}, } @@ -176,7 +176,7 @@ func (s etcdState) step(request EtcdRequest) (etcdState, EtcdResponse) { opResp[i].Count = 1 } } - case Put: + case PutOperation: _, leaseExists := s.Leases[op.LeaseID] if op.LeaseID != 0 && !leaseExists { break @@ -190,7 +190,7 @@ func (s etcdState) step(request EtcdRequest) (etcdState, EtcdResponse) { if leaseExists { s = attachToNewLease(s, op.LeaseID, op.Key) } - case Delete: + case DeleteOperation: if _, ok := s.KeyValues[op.Key]; ok { delete(s.KeyValues, op.Key) increaseRevision = true @@ -289,6 +289,14 @@ type EtcdOperation struct { LeaseID int64 } +type OperationType string + +const ( + RangeOperation OperationType = "range-operation" + PutOperation OperationType = "put-operation" + DeleteOperation OperationType = "delete-operation" +) + type LeaseGrantRequest struct { LeaseID int64 } diff --git a/tests/robustness/model/history.go b/tests/robustness/model/history.go index 843660121..0f7fb829e 100644 --- a/tests/robustness/model/history.go +++ b/tests/robustness/model/history.go @@ -243,11 +243,11 @@ func toEtcdOperation(op clientv3.Op) EtcdOperation { var opType OperationType switch { case op.IsGet(): - opType = Range + opType = RangeOperation case op.IsPut(): - opType = Put + opType = PutOperation case op.IsDelete(): - opType = Delete + opType = DeleteOperation default: panic("Unsupported operation") } @@ -339,7 +339,7 @@ func getRequest(key string) EtcdRequest { } func rangeRequest(key string, withPrefix bool, limit int64) EtcdRequest { - return EtcdRequest{Type: Txn, Txn: &TxnRequest{OperationsOnSuccess: []EtcdOperation{{Type: Range, Key: key, WithPrefix: withPrefix, Limit: limit}}}} + return EtcdRequest{Type: Txn, Txn: &TxnRequest{OperationsOnSuccess: []EtcdOperation{{Type: RangeOperation, Key: key, WithPrefix: withPrefix, Limit: limit}}}} } func emptyGetResponse(revision int64) EtcdNonDeterministicResponse { @@ -375,7 +375,7 @@ func unknownResponse(revision int64) EtcdNonDeterministicResponse { } func putRequest(key, value string) EtcdRequest { - return EtcdRequest{Type: Txn, Txn: &TxnRequest{OperationsOnSuccess: []EtcdOperation{{Type: Put, Key: key, Value: ToValueOrHash(value)}}}} + return EtcdRequest{Type: Txn, Txn: &TxnRequest{OperationsOnSuccess: []EtcdOperation{{Type: PutOperation, Key: key, Value: ToValueOrHash(value)}}}} } func putResponse(revision int64) EtcdNonDeterministicResponse { @@ -383,7 +383,7 @@ func putResponse(revision int64) EtcdNonDeterministicResponse { } func deleteRequest(key string) EtcdRequest { - return EtcdRequest{Type: Txn, Txn: &TxnRequest{OperationsOnSuccess: []EtcdOperation{{Type: Delete, Key: key}}}} + return EtcdRequest{Type: Txn, Txn: &TxnRequest{OperationsOnSuccess: []EtcdOperation{{Type: DeleteOperation, Key: key}}}} } func deleteResponse(deleted int64, revision int64) EtcdNonDeterministicResponse { @@ -406,7 +406,7 @@ func compareRevision(key string, expectedRevision int64) *EtcdCondition { } func putOperation(key, value string) *EtcdOperation { - return &EtcdOperation{Type: Put, Key: key, Value: ToValueOrHash(value)} + return &EtcdOperation{Type: PutOperation, Key: key, Value: ToValueOrHash(value)} } func txnRequestSingleOperation(cond *EtcdCondition, onSuccess, onFailure *EtcdOperation) EtcdRequest { @@ -442,7 +442,7 @@ func txnResponse(result []EtcdOperationResult, succeeded bool, revision int64) E } func putWithLeaseRequest(key, value string, leaseID int64) EtcdRequest { - return EtcdRequest{Type: Txn, Txn: &TxnRequest{OperationsOnSuccess: []EtcdOperation{{Type: Put, Key: key, 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 { diff --git a/tests/robustness/model/non_deterministic.go b/tests/robustness/model/non_deterministic.go index 163ea8db9..fe2ccdd6a 100644 --- a/tests/robustness/model/non_deterministic.go +++ b/tests/robustness/model/non_deterministic.go @@ -22,14 +22,6 @@ import ( "github.com/anishathalye/porcupine" ) -type OperationType string - -const ( - Range OperationType = "range" - Put OperationType = "put" - Delete OperationType = "delete" -) - // NonDeterministicModel extends DeterministicModel to handle requests that have unknown or error response. // Unknown/error response doesn't inform whether request was persisted or not, so model // considers both cases. This is represented as multiple equally possible deterministic states. diff --git a/tests/robustness/traffic/client.go b/tests/robustness/traffic/client.go index 52ffb0445..3e7f84aab 100644 --- a/tests/robustness/traffic/client.go +++ b/tests/robustness/traffic/client.go @@ -264,9 +264,9 @@ func toWatchEvent(event clientv3.Event) WatchEvent { var op model.OperationType switch event.Type { case mvccpb.PUT: - op = model.Put + op = model.PutOperation case mvccpb.DELETE: - op = model.Delete + op = model.DeleteOperation default: panic(fmt.Sprintf("Unexpected event type: %s", event.Type)) } diff --git a/tests/robustness/traffic/etcd.go b/tests/robustness/traffic/etcd.go index e0991fd2e..f9361a2ae 100644 --- a/tests/robustness/traffic/etcd.go +++ b/tests/robustness/traffic/etcd.go @@ -195,24 +195,24 @@ func (t etcdTraffic) pickMultiTxnOps(ids identity.Provider) (ops []clientv3.Op) atLeastOnePut := false for i := 0; i < MultiOpTxnOpCount; i++ { opTypes[i] = t.pickOperationType() - if opTypes[i] == model.Put { + if opTypes[i] == model.PutOperation { atLeastOnePut = true } } // Ensure at least one put to make operation unique if !atLeastOnePut { - opTypes[0] = model.Put + opTypes[0] = model.PutOperation } for i, opType := range opTypes { key := fmt.Sprintf("%d", keys[i]) switch opType { - case model.Range: + case model.RangeOperation: ops = append(ops, clientv3.OpGet(key)) - case model.Put: + case model.PutOperation: value := fmt.Sprintf("%d", ids.NewRequestId()) ops = append(ops, clientv3.OpPut(key, value)) - case model.Delete: + case model.DeleteOperation: ops = append(ops, clientv3.OpDelete(key)) default: panic("unsuported choice type") @@ -224,10 +224,10 @@ func (t etcdTraffic) pickMultiTxnOps(ids identity.Provider) (ops []clientv3.Op) func (t etcdTraffic) pickOperationType() model.OperationType { roll := rand.Int() % 100 if roll < 10 { - return model.Delete + return model.DeleteOperation } if roll < 50 { - return model.Range + return model.RangeOperation } - return model.Put + return model.PutOperation } diff --git a/tests/robustness/validate/patch_history.go b/tests/robustness/validate/patch_history.go index 13f8aaa4e..9d799f9db 100644 --- a/tests/robustness/validate/patch_history.go +++ b/tests/robustness/validate/patch_history.go @@ -73,7 +73,7 @@ func lastOperationObservedInWatch(operations []porcupine.Operation, watchEvents func matchWatchEvent(request *model.TxnRequest, watchEvents map[model.EtcdOperation]traffic.TimedWatchEvent) *traffic.TimedWatchEvent { for _, etcdOp := range append(request.OperationsOnSuccess, request.OperationsOnFailure...) { - if etcdOp.Type == model.Put { + if etcdOp.Type == model.PutOperation { // Remove LeaseID which is not exposed in watch. event, ok := watchEvents[model.EtcdOperation{ Type: etcdOp.Type, @@ -90,7 +90,7 @@ func matchWatchEvent(request *model.TxnRequest, watchEvents map[model.EtcdOperat func hasNonUniqueWriteOperation(request *model.TxnRequest) bool { for _, etcdOp := range request.OperationsOnSuccess { - if etcdOp.Type == model.Put || etcdOp.Type == model.Delete { + if etcdOp.Type == model.PutOperation || etcdOp.Type == model.DeleteOperation { return true } } @@ -99,7 +99,7 @@ func hasNonUniqueWriteOperation(request *model.TxnRequest) bool { func hasUniqueWriteOperation(request *model.TxnRequest) bool { for _, etcdOp := range request.OperationsOnSuccess { - if etcdOp.Type == model.Put { + if etcdOp.Type == model.PutOperation { return true } } From 69793181082a85d7d790fa98b18769ae7c12f6ca Mon Sep 17 00:00:00 2001 From: Marek Siarkowicz Date: Wed, 14 Jun 2023 13:43:55 +0200 Subject: [PATCH 2/2] tests/robustness: Make Range a proper request type to allow setting Range.Revision != 0 for stale reads Signed-off-by: Marek Siarkowicz --- tests/robustness/model/describe.go | 71 +++++++++----- tests/robustness/model/describe_test.go | 10 +- tests/robustness/model/deterministic.go | 107 ++++++++++++++------- tests/robustness/model/history.go | 25 ++--- tests/robustness/traffic/client.go | 6 +- tests/robustness/validate/patch_history.go | 8 +- 6 files changed, 145 insertions(+), 82 deletions(-) diff --git a/tests/robustness/model/describe.go b/tests/robustness/model/describe.go index 325ccfeff..2c2ecc3e9 100644 --- a/tests/robustness/model/describe.go +++ b/tests/robustness/model/describe.go @@ -30,17 +30,25 @@ func describeEtcdNonDeterministicResponse(request EtcdRequest, response EtcdNonD } func describeEtcdResponse(request EtcdRequest, response EtcdResponse) string { - if request.Type == Txn { + switch request.Type { + case Range: + return fmt.Sprintf("%s, rev: %d", describeRangeResponse(request.Range.RangeOptions, *response.Range), response.Revision) + case Txn: return fmt.Sprintf("%s, rev: %d", describeTxnResponse(request.Txn, response.Txn), response.Revision) + case LeaseGrant, LeaseRevoke, Defragment: + if response.Revision == 0 { + return "ok" + } + return fmt.Sprintf("ok, rev: %d", response.Revision) + default: + return fmt.Sprintf("", request.Type) } - if response.Revision == 0 { - return "ok" - } - return fmt.Sprintf("ok, rev: %d", response.Revision) } func describeEtcdRequest(request EtcdRequest) string { switch request.Type { + case Range: + return describeRangeRequest(request.Range.Key, request.Range.RangeOptions) case Txn: onSuccess := describeEtcdOperations(request.Txn.OperationsOnSuccess) if len(request.Txn.Conditions) != 0 { @@ -101,13 +109,7 @@ func describeTxnResponse(request *TxnRequest, response *TxnResponse) string { func describeEtcdOperation(op EtcdOperation) string { switch op.Type { case RangeOperation: - if op.WithPrefix { - if op.Limit != 0 { - return fmt.Sprintf("range(%q, limit=%d)", op.Key, op.Limit) - } - return fmt.Sprintf("range(%q)", op.Key) - } - return fmt.Sprintf("get(%q)", op.Key) + return describeRangeRequest(op.Key, op.RangeOptions) case PutOperation: if op.LeaseID != 0 { return fmt.Sprintf("put(%q, %s, %d)", op.Key, describeValueOrHash(op.Value), op.LeaseID) @@ -120,22 +122,25 @@ func describeEtcdOperation(op EtcdOperation) string { } } +func describeRangeRequest(key string, opts RangeOptions) string { + kwargs := []string{} + if opts.Limit != 0 { + kwargs = append(kwargs, fmt.Sprintf("limit=%d", opts.Limit)) + } + command := "get" + if opts.WithPrefix { + command = "range" + } + if len(kwargs) == 0 { + return fmt.Sprintf("%s(%q)", command, key) + } + return fmt.Sprintf("%s(%q, %s)", command, key, strings.Join(kwargs, ", ")) +} + func describeEtcdOperationResponse(req EtcdOperation, resp EtcdOperationResult) string { switch req.Type { case RangeOperation: - if req.WithPrefix { - kvs := make([]string, len(resp.KVs)) - for i, kv := range resp.KVs { - kvs[i] = describeValueOrHash(kv.Value) - } - return fmt.Sprintf("[%s], count: %d", strings.Join(kvs, ","), resp.Count) - } else { - if len(resp.KVs) == 0 { - return "nil" - } else { - return describeValueOrHash(resp.KVs[0].Value) - } - } + return describeRangeResponse(req.RangeOptions, resp.RangeResponse) case PutOperation: return fmt.Sprintf("ok") case DeleteOperation: @@ -145,6 +150,22 @@ func describeEtcdOperationResponse(req EtcdOperation, resp EtcdOperationResult) } } +func describeRangeResponse(opts RangeOptions, response RangeResponse) string { + if opts.WithPrefix { + kvs := make([]string, len(response.KVs)) + for i, kv := range response.KVs { + kvs[i] = describeValueOrHash(kv.Value) + } + return fmt.Sprintf("[%s], count: %d", strings.Join(kvs, ","), response.Count) + } else { + if len(response.KVs) == 0 { + return "nil" + } else { + return describeValueOrHash(response.KVs[0].Value) + } + } +} + func describeValueOrHash(value ValueOrHash) string { if value.Hash != 0 { return fmt.Sprintf("hash: %d", value.Hash) diff --git a/tests/robustness/model/describe_test.go b/tests/robustness/model/describe_test.go index a0522e6a7..1c6897459 100644 --- a/tests/robustness/model/describe_test.go +++ b/tests/robustness/model/describe_test.go @@ -95,18 +95,18 @@ 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", Value: ValueOrHash{Value: "991"}}}, []EtcdOperation{{Type: RangeOperation, Key: "key9b"}}), + req: txnRequest([]EtcdCondition{{Key: "key9b", ExpectedRevision: 9}}, []EtcdOperation{{Type: PutOperation, Key: "key9b", PutOptions: PutOptions{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", Value: ValueOrHash{Value: "992"}}}, []EtcdOperation{{Type: RangeOperation, Key: "key9c"}}), - resp: txnResponse([]EtcdOperationResult{{KVs: []KeyValue{{Key: "key9c", ValueRevision: ValueRevision{Value: ValueOrHash{Value: "993"}, ModRevision: 10}}}}}, false, 10), + req: txnRequest([]EtcdCondition{{Key: "key9c", ExpectedRevision: 9}}, []EtcdOperation{{Type: PutOperation, Key: "key9c", PutOptions: PutOptions{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", Value: ValueOrHash{Value: "111"}}, {Type: DeleteOperation, Key: "12"}}, nil), - resp: txnResponse([]EtcdOperationResult{{KVs: []KeyValue{{ValueRevision: ValueRevision{Value: ValueOrHash{Value: "110"}}}}}, {}, {Deleted: 1}}, true, 10), + req: txnRequest(nil, []EtcdOperation{{Type: RangeOperation, Key: "10"}, {Type: PutOperation, Key: "11", PutOptions: PutOptions{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`, }, { diff --git a/tests/robustness/model/deterministic.go b/tests/robustness/model/deterministic.go index 0220a2fdc..d846f45b4 100644 --- a/tests/robustness/model/deterministic.go +++ b/tests/robustness/model/deterministic.go @@ -73,6 +73,13 @@ func initState(request EtcdRequest, response EtcdResponse) etcdState { state := emptyState() state.Revision = response.Revision switch request.Type { + case Range: + for _, kv := range response.Range.KVs { + state.KeyValues[kv.Key] = ValueRevision{ + Value: kv.Value, + ModRevision: kv.ModRevision, + } + } case Txn: if response.Txn.Failure { return state @@ -131,6 +138,9 @@ func (s etcdState) step(request EtcdRequest) (etcdState, EtcdResponse) { } s.KeyValues = newKVs switch request.Type { + case Range: + resp := s.getRange(request.Range.Key, request.Range.RangeOptions) + return s, EtcdResponse{Range: &resp, Revision: s.Revision} case Txn: failure := false for _, cond := range request.Txn.Conditions { @@ -149,32 +159,7 @@ func (s etcdState) step(request EtcdRequest) (etcdState, EtcdResponse) { switch op.Type { case RangeOperation: opResp[i] = EtcdOperationResult{ - KVs: []KeyValue{}, - } - if op.WithPrefix { - var count int64 - for k, v := range s.KeyValues { - if strings.HasPrefix(k, op.Key) { - opResp[i].KVs = append(opResp[i].KVs, KeyValue{Key: k, ValueRevision: v}) - count += 1 - } - } - sort.Slice(opResp[i].KVs, func(j, k int) bool { - return opResp[i].KVs[j].Key < opResp[i].KVs[k].Key - }) - if op.Limit != 0 && count > op.Limit { - opResp[i].KVs = opResp[i].KVs[:op.Limit] - } - opResp[i].Count = count - } else { - value, ok := s.KeyValues[op.Key] - if ok { - opResp[i].KVs = append(opResp[i].KVs, KeyValue{ - Key: op.Key, - ValueRevision: value, - }) - opResp[i].Count = 1 - } + RangeResponse: s.getRange(op.Key, op.RangeOptions), } case PutOperation: _, leaseExists := s.Leases[op.LeaseID] @@ -238,6 +223,38 @@ func (s etcdState) step(request EtcdRequest) (etcdState, EtcdResponse) { } } +func (s etcdState) getRange(key string, options RangeOptions) RangeResponse { + response := RangeResponse{ + KVs: []KeyValue{}, + } + if options.WithPrefix { + var count int64 + for k, v := range s.KeyValues { + if strings.HasPrefix(k, key) { + response.KVs = append(response.KVs, KeyValue{Key: k, ValueRevision: v}) + count += 1 + } + } + sort.Slice(response.KVs, func(j, k int) bool { + return response.KVs[j].Key < response.KVs[k].Key + }) + if options.Limit != 0 && count > options.Limit { + response.KVs = response.KVs[:options.Limit] + } + response.Count = count + } else { + value, ok := s.KeyValues[key] + if ok { + response.KVs = append(response.KVs, KeyValue{ + Key: key, + ValueRevision: value, + }) + response.Count = 1 + } + } + return response +} + func detachFromOldLease(s etcdState, key string) etcdState { if oldLeaseId, ok := s.KeyLeases[key]; ok { delete(s.Leases[oldLeaseId].Keys, key) @@ -255,6 +272,7 @@ func attachToNewLease(s etcdState, leaseID int64, key string) etcdState { type RequestType string const ( + Range RequestType = "range" Txn RequestType = "txn" LeaseGrant RequestType = "leaseGrant" LeaseRevoke RequestType = "leaseRevoke" @@ -265,10 +283,28 @@ type EtcdRequest struct { Type RequestType LeaseGrant *LeaseGrantRequest LeaseRevoke *LeaseRevokeRequest + Range *RangeRequest Txn *TxnRequest Defragment *DefragmentRequest } +type RangeRequest struct { + Key string + RangeOptions + // TODO: Implement stale read using revision + revision int64 +} + +type RangeOptions struct { + WithPrefix bool + Limit int64 +} + +type PutOptions struct { + Value ValueOrHash + LeaseID int64 +} + type TxnRequest struct { Conditions []EtcdCondition OperationsOnSuccess []EtcdOperation @@ -281,12 +317,10 @@ type EtcdCondition struct { } type EtcdOperation struct { - Type OperationType - Key string - WithPrefix bool - Limit int64 - Value ValueOrHash - LeaseID int64 + Type OperationType + Key string + RangeOptions + PutOptions } type OperationType string @@ -308,6 +342,7 @@ type DefragmentRequest struct{} type EtcdResponse struct { Revision int64 Txn *TxnResponse + Range *RangeResponse LeaseGrant *LeaseGrantReponse LeaseRevoke *LeaseRevokeResponse Defragment *DefragmentResponse @@ -318,6 +353,11 @@ type TxnResponse struct { Results []EtcdOperationResult } +type RangeResponse struct { + KVs []KeyValue + Count int64 +} + type LeaseGrantReponse struct { LeaseID int64 } @@ -325,8 +365,7 @@ type LeaseRevokeResponse struct{} type DefragmentResponse struct{} type EtcdOperationResult struct { - KVs []KeyValue - Count int64 + RangeResponse Deleted int64 } diff --git a/tests/robustness/model/history.go b/tests/robustness/model/history.go index 0f7fb829e..b078b566c 100644 --- a/tests/robustness/model/history.go +++ b/tests/robustness/model/history.go @@ -252,9 +252,9 @@ func toEtcdOperation(op clientv3.Op) EtcdOperation { panic("Unsupported operation") } return EtcdOperation{ - Type: opType, - Key: string(op.KeyBytes()), - Value: ValueOrHash{Value: string(op.ValueBytes())}, + Type: opType, + Key: string(op.KeyBytes()), + PutOptions: PutOptions{Value: ValueOrHash{Value: string(op.ValueBytes())}}, } } @@ -273,8 +273,10 @@ func toEtcdOperationResult(resp *etcdserverpb.ResponseOp) EtcdOperationResult { } } return EtcdOperationResult{ - KVs: kvs, - Count: getResp.Count, + RangeResponse: RangeResponse{ + KVs: kvs, + Count: getResp.Count, + }, } case resp.GetResponsePut() != nil: return EtcdOperationResult{} @@ -339,7 +341,7 @@ func getRequest(key string) EtcdRequest { } func rangeRequest(key string, withPrefix bool, limit int64) EtcdRequest { - return EtcdRequest{Type: Txn, Txn: &TxnRequest{OperationsOnSuccess: []EtcdOperation{{Type: RangeOperation, Key: key, WithPrefix: withPrefix, Limit: limit}}}} + return EtcdRequest{Type: Range, Range: &RangeRequest{Key: key, RangeOptions: RangeOptions{WithPrefix: withPrefix, Limit: limit}}} } func emptyGetResponse(revision int64) EtcdNonDeterministicResponse { @@ -351,7 +353,7 @@ func getResponse(key, value string, modRevision, revision int64) EtcdNonDetermin } func rangeResponse(kvs []*mvccpb.KeyValue, count int64, revision int64) EtcdNonDeterministicResponse { - result := EtcdOperationResult{KVs: make([]KeyValue, len(kvs))} + result := RangeResponse{KVs: make([]KeyValue, len(kvs)), Count: count} for i, kv := range kvs { result.KVs[i] = KeyValue{ @@ -361,9 +363,8 @@ func rangeResponse(kvs []*mvccpb.KeyValue, count int64, revision int64) EtcdNonD ModRevision: kv.ModRevision, }, } - result.Count = count } - return EtcdNonDeterministicResponse{EtcdResponse: EtcdResponse{Txn: &TxnResponse{Results: []EtcdOperationResult{result}}, Revision: revision}} + return EtcdNonDeterministicResponse{EtcdResponse: EtcdResponse{Range: &result, Revision: revision}} } func failedResponse(err error) EtcdNonDeterministicResponse { @@ -375,7 +376,7 @@ func unknownResponse(revision int64) EtcdNonDeterministicResponse { } func putRequest(key, value string) EtcdRequest { - return EtcdRequest{Type: Txn, Txn: &TxnRequest{OperationsOnSuccess: []EtcdOperation{{Type: PutOperation, Key: key, Value: ToValueOrHash(value)}}}} + return EtcdRequest{Type: Txn, Txn: &TxnRequest{OperationsOnSuccess: []EtcdOperation{{Type: PutOperation, Key: key, PutOptions: PutOptions{Value: ToValueOrHash(value)}}}}} } func putResponse(revision int64) EtcdNonDeterministicResponse { @@ -406,7 +407,7 @@ func compareRevision(key string, expectedRevision int64) *EtcdCondition { } func putOperation(key, value string) *EtcdOperation { - return &EtcdOperation{Type: PutOperation, Key: key, Value: ToValueOrHash(value)} + return &EtcdOperation{Type: PutOperation, Key: key, PutOptions: PutOptions{Value: ToValueOrHash(value)}} } func txnRequestSingleOperation(cond *EtcdCondition, onSuccess, onFailure *EtcdOperation) EtcdRequest { @@ -442,7 +443,7 @@ func txnResponse(result []EtcdOperationResult, succeeded bool, revision int64) E } func putWithLeaseRequest(key, value string, leaseID int64) EtcdRequest { - return EtcdRequest{Type: Txn, Txn: &TxnRequest{OperationsOnSuccess: []EtcdOperation{{Type: PutOperation, Key: key, Value: ToValueOrHash(value), LeaseID: leaseID}}}} + return EtcdRequest{Type: Txn, Txn: &TxnRequest{OperationsOnSuccess: []EtcdOperation{{Type: PutOperation, Key: key, PutOptions: PutOptions{Value: ToValueOrHash(value), LeaseID: leaseID}}}}} } func leaseGrantRequest(leaseID int64) EtcdRequest { diff --git a/tests/robustness/traffic/client.go b/tests/robustness/traffic/client.go index 3e7f84aab..5462f749e 100644 --- a/tests/robustness/traffic/client.go +++ b/tests/robustness/traffic/client.go @@ -273,9 +273,9 @@ func toWatchEvent(event clientv3.Event) WatchEvent { return WatchEvent{ Revision: event.Kv.ModRevision, Op: model.EtcdOperation{ - Type: op, - Key: string(event.Kv.Key), - Value: model.ToValueOrHash(string(event.Kv.Value)), + Type: op, + Key: string(event.Kv.Key), + PutOptions: model.PutOptions{Value: model.ToValueOrHash(string(event.Kv.Value))}, }, } } diff --git a/tests/robustness/validate/patch_history.go b/tests/robustness/validate/patch_history.go index 9d799f9db..b672f8492 100644 --- a/tests/robustness/validate/patch_history.go +++ b/tests/robustness/validate/patch_history.go @@ -76,9 +76,11 @@ func matchWatchEvent(request *model.TxnRequest, watchEvents map[model.EtcdOperat if etcdOp.Type == model.PutOperation { // Remove LeaseID which is not exposed in watch. event, ok := watchEvents[model.EtcdOperation{ - Type: etcdOp.Type, - Key: etcdOp.Key, - Value: etcdOp.Value, + Type: etcdOp.Type, + Key: etcdOp.Key, + PutOptions: model.PutOptions{ + Value: etcdOp.Value, + }, }] if ok { return &event