diff --git a/tests/linearizability/client.go b/tests/linearizability/client.go index d1b04106a434..09dd6cff6e26 100644 --- a/tests/linearizability/client.go +++ b/tests/linearizability/client.go @@ -18,20 +18,13 @@ import ( "context" "time" - "github.com/anishathalye/porcupine" clientv3 "go.etcd.io/etcd/client/v3" "go.uber.org/zap" ) type recordingClient struct { - client clientv3.Client - - // id of the next write operation. If needed a new id might be requested from idProvider. - id int - idProvider idProvider - - operations []porcupine.Operation - failed []porcupine.Operation + client clientv3.Client + history *history } func NewClient(endpoints []string, ids idProvider) (*recordingClient, error) { @@ -45,11 +38,8 @@ func NewClient(endpoints []string, ids idProvider) (*recordingClient, error) { return nil, err } return &recordingClient{ - client: *cc, - id: ids.ClientId(), - idProvider: ids, - operations: []porcupine.Operation{}, - failed: []porcupine.Operation{}, + client: *cc, + history: NewHistory(ids), }, nil } @@ -64,17 +54,7 @@ func (c *recordingClient) Get(ctx context.Context, key string) error { if err != nil { return err } - var readData string - if len(resp.Kvs) == 1 { - readData = string(resp.Kvs[0].Value) - } - c.operations = append(c.operations, porcupine.Operation{ - ClientId: c.id, - Input: EtcdRequest{Op: Get, Key: key}, - Call: callTime.UnixNano(), - Output: EtcdResponse{GetData: readData, Revision: resp.Header.Revision}, - Return: returnTime.UnixNano(), - }) + c.history.AppendGet(key, callTime, returnTime, resp) return nil } @@ -82,48 +62,6 @@ func (c *recordingClient) Put(ctx context.Context, key, value string) error { callTime := time.Now() resp, err := c.client.Put(ctx, key, value) returnTime := time.Now() - if err != nil { - c.failed = append(c.failed, porcupine.Operation{ - ClientId: c.id, - Input: EtcdRequest{Op: Put, Key: key, PutData: value}, - Call: callTime.UnixNano(), - Output: EtcdResponse{Err: err}, - Return: 0, // For failed writes we don't know when request has really finished. - }) - // Operations of single client needs to be sequential. - // As we don't know return time of failed operations, all new writes need to be done with new client id. - c.id = c.idProvider.ClientId() - return err - } - var revision int64 - if resp != nil && resp.Header != nil { - revision = resp.Header.Revision - } - c.operations = append(c.operations, porcupine.Operation{ - ClientId: c.id, - Input: EtcdRequest{Op: Put, Key: key, PutData: value}, - Call: callTime.UnixNano(), - Output: EtcdResponse{Err: err, Revision: revision}, - Return: returnTime.UnixNano(), - }) - return nil -} - -func (c *recordingClient) Operations() []porcupine.Operation { - operations := make([]porcupine.Operation, 0, len(c.operations)+len(c.failed)) - var maxTime int64 - for _, op := range c.operations { - operations = append(operations, op) - if op.Return > maxTime { - maxTime = op.Return - } - } - for _, op := range c.failed { - if op.Call > maxTime { - continue - } - op.Return = maxTime + 1 - operations = append(operations, op) - } - return operations + c.history.AppendPut(key, value, callTime, returnTime, resp, err) + return err } diff --git a/tests/linearizability/history.go b/tests/linearizability/history.go new file mode 100644 index 000000000000..7b78f313dfb1 --- /dev/null +++ b/tests/linearizability/history.go @@ -0,0 +1,99 @@ +// Copyright 2022 The etcd Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package linearizability + +import ( + "time" + + "github.com/anishathalye/porcupine" + clientv3 "go.etcd.io/etcd/client/v3" +) + +type history struct { + // id of the next write operation. If needed a new id might be requested from idProvider. + id int + idProvider idProvider + + operations []porcupine.Operation + failed []porcupine.Operation +} + +func NewHistory(ids idProvider) *history { + return &history{ + id: ids.ClientId(), + idProvider: ids, + operations: []porcupine.Operation{}, + failed: []porcupine.Operation{}, + } +} + +func (h *history) AppendGet(key string, start, end time.Time, resp *clientv3.GetResponse) { + var readData string + if len(resp.Kvs) == 1 { + readData = string(resp.Kvs[0].Value) + } + h.operations = append(h.operations, porcupine.Operation{ + ClientId: h.id, + Input: EtcdRequest{Op: Get, Key: key}, + Call: start.UnixNano(), + Output: EtcdResponse{GetData: readData, Revision: resp.Header.Revision}, + Return: end.UnixNano(), + }) +} + +func (h *history) AppendPut(key, value string, start, end time.Time, resp *clientv3.PutResponse, err error) { + if err != nil { + h.failed = append(h.failed, porcupine.Operation{ + ClientId: h.id, + Input: EtcdRequest{Op: Put, Key: key, PutData: value}, + Call: start.UnixNano(), + Output: EtcdResponse{Err: err}, + Return: 0, // For failed writes we don't know when request has really finished. + }) + // Operations of single client needs to be sequential. + // As we don't know return time of failed operations, all new writes need to be done with new client id. + h.id = h.idProvider.ClientId() + } + var revision int64 + if resp != nil && resp.Header != nil { + revision = resp.Header.Revision + } + h.operations = append(h.operations, porcupine.Operation{ + ClientId: h.id, + Input: EtcdRequest{Op: Put, Key: key, PutData: value}, + Call: start.UnixNano(), + Output: EtcdResponse{Err: err, Revision: revision}, + Return: end.UnixNano(), + }) +} + +func (h *history) Operations() []porcupine.Operation { + operations := make([]porcupine.Operation, 0, len(h.operations)+len(h.failed)) + var maxTime int64 + for _, op := range h.operations { + operations = append(operations, op) + if op.Return > maxTime { + maxTime = op.Return + } + } + for _, op := range h.failed { + if op.Call > maxTime { + continue + } + op.Return = maxTime + 1 + operations = append(operations, op) + } + return operations +} diff --git a/tests/linearizability/linearizability_test.go b/tests/linearizability/linearizability_test.go index 570cb881cb8b..0e74f4e49475 100644 --- a/tests/linearizability/linearizability_test.go +++ b/tests/linearizability/linearizability_test.go @@ -162,7 +162,7 @@ func simulateTraffic(ctx context.Context, t *testing.T, clus *e2e.EtcdProcessClu config.traffic.Run(ctx, c, limiter, ids) mux.Lock() - operations = append(operations, c.Operations()...) + operations = append(operations, c.history.Operations()...) mux.Unlock() }(c) }