Marek Siarkowicz 96e2a7fbd6 tests: Move linearizability model and identity to dedicated packages
Signed-off-by: Marek Siarkowicz <siarkowicz@google.com>
2023-01-16 19:16:27 +01:00

301 lines
8.9 KiB
Go

// 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 model
import (
"time"
"github.com/anishathalye/porcupine"
clientv3 "go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/tests/v3/linearizability/identity"
)
type AppendableHistory struct {
// id of the next write operation. If needed a new id might be requested from idProvider.
id int
idProvider identity.Provider
History
}
func NewAppendableHistory(ids identity.Provider) *AppendableHistory {
return &AppendableHistory{
id: ids.ClientId(),
idProvider: ids,
History: History{
successful: []porcupine.Operation{},
failed: []porcupine.Operation{},
},
}
}
func (h *AppendableHistory) AppendGet(key string, start, end time.Time, resp *clientv3.GetResponse) {
var readData string
if len(resp.Kvs) == 1 {
readData = string(resp.Kvs[0].Value)
}
var revision int64
if resp != nil && resp.Header != nil {
revision = resp.Header.Revision
}
h.successful = append(h.successful, porcupine.Operation{
ClientId: h.id,
Input: getRequest(key),
Call: start.UnixNano(),
Output: getResponse(readData, revision),
Return: end.UnixNano(),
})
}
func (h *AppendableHistory) AppendPut(key, value string, start, end time.Time, resp *clientv3.PutResponse, err error) {
request := putRequest(key, value)
if err != nil {
h.appendFailed(request, start, err)
return
}
var revision int64
if resp != nil && resp.Header != nil {
revision = resp.Header.Revision
}
h.successful = append(h.successful, porcupine.Operation{
ClientId: h.id,
Input: request,
Call: start.UnixNano(),
Output: putResponse(revision),
Return: end.UnixNano(),
})
}
func (h *AppendableHistory) AppendPutWithLease(key, value string, leaseID int64, start, end time.Time, resp *clientv3.PutResponse, err error) {
request := putWithLeaseRequest(key, value, leaseID)
if err != nil {
h.appendFailed(request, start, err)
return
}
var revision int64
if resp != nil && resp.Header != nil {
revision = resp.Header.Revision
}
h.successful = append(h.successful, porcupine.Operation{
ClientId: h.id,
Input: request,
Call: start.UnixNano(),
Output: putResponse(revision),
Return: end.UnixNano(),
})
}
func (h *AppendableHistory) AppendLeaseGrant(start, end time.Time, resp *clientv3.LeaseGrantResponse, err error) {
var leaseID int64
if resp != nil {
leaseID = int64(resp.ID)
}
request := leaseGrantRequest(leaseID)
if err != nil {
h.appendFailed(request, start, err)
return
}
var revision int64
if resp != nil && resp.ResponseHeader != nil {
revision = resp.ResponseHeader.Revision
}
h.successful = append(h.successful, porcupine.Operation{
ClientId: h.id,
Input: request,
Call: start.UnixNano(),
Output: leaseGrantResponse(revision),
Return: end.UnixNano(),
})
}
func (h *AppendableHistory) AppendLeaseRevoke(id int64, start time.Time, end time.Time, resp *clientv3.LeaseRevokeResponse, err error) {
request := leaseRevokeRequest(id)
if err != nil {
h.appendFailed(request, start, err)
return
}
var revision int64
if resp != nil && resp.Header != nil {
revision = resp.Header.Revision
}
h.successful = append(h.successful, porcupine.Operation{
ClientId: h.id,
Input: request,
Call: start.UnixNano(),
Output: leaseRevokeResponse(revision),
Return: end.UnixNano(),
})
}
func (h *AppendableHistory) AppendDelete(key string, start, end time.Time, resp *clientv3.DeleteResponse, err error) {
request := deleteRequest(key)
if err != nil {
h.appendFailed(request, start, err)
return
}
var revision int64
var deleted int64
if resp != nil && resp.Header != nil {
revision = resp.Header.Revision
deleted = resp.Deleted
}
h.successful = append(h.successful, porcupine.Operation{
ClientId: h.id,
Input: request,
Call: start.UnixNano(),
Output: deleteResponse(deleted, revision),
Return: end.UnixNano(),
})
}
func (h *AppendableHistory) AppendTxn(key, expectValue, newValue string, start, end time.Time, resp *clientv3.TxnResponse, err error) {
request := txnRequest(key, expectValue, newValue)
if err != nil {
h.appendFailed(request, start, err)
return
}
var revision int64
if resp != nil && resp.Header != nil {
revision = resp.Header.Revision
}
h.successful = append(h.successful, porcupine.Operation{
ClientId: h.id,
Input: request,
Call: start.UnixNano(),
Output: txnResponse(resp.Succeeded, revision),
Return: end.UnixNano(),
})
}
func (h *AppendableHistory) appendFailed(request EtcdRequest, start time.Time, err error) {
h.failed = append(h.failed, porcupine.Operation{
ClientId: h.id,
Input: request,
Call: start.UnixNano(),
Output: failedResponse(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()
}
func getRequest(key string) EtcdRequest {
return EtcdRequest{Ops: []EtcdOperation{{Type: Get, Key: key}}}
}
func getResponse(value string, revision int64) EtcdResponse {
return EtcdResponse{OpsResult: []EtcdOperationResult{{Value: value}}, Revision: revision}
}
func failedResponse(err error) EtcdResponse {
return EtcdResponse{Err: err}
}
func unknownResponse(revision int64) EtcdResponse {
return EtcdResponse{ResultUnknown: true, Revision: revision}
}
func putRequest(key, value string) EtcdRequest {
return EtcdRequest{Ops: []EtcdOperation{{Type: Put, Key: key, Value: value}}}
}
func putResponse(revision int64) EtcdResponse {
return EtcdResponse{OpsResult: []EtcdOperationResult{{}}, Revision: revision}
}
func deleteRequest(key string) EtcdRequest {
return EtcdRequest{Ops: []EtcdOperation{{Type: Delete, Key: key}}}
}
func deleteResponse(deleted int64, revision int64) EtcdResponse {
return EtcdResponse{OpsResult: []EtcdOperationResult{{Deleted: deleted}}, Revision: revision}
}
func txnRequest(key, expectValue, newValue string) EtcdRequest {
return EtcdRequest{Conds: []EtcdCondition{{Key: key, ExpectedValue: expectValue}}, Ops: []EtcdOperation{{Type: Put, Key: key, Value: newValue}}}
}
func txnResponse(succeeded bool, revision int64) EtcdResponse {
var result []EtcdOperationResult
if succeeded {
result = []EtcdOperationResult{{}}
}
return EtcdResponse{OpsResult: result, TxnResult: !succeeded, Revision: revision}
}
func putWithLeaseRequest(key, value string, leaseID int64) EtcdRequest {
return EtcdRequest{Ops: []EtcdOperation{{Type: PutWithLease, Key: key, Value: value, LeaseID: leaseID}}}
}
func leaseGrantRequest(leaseID int64) EtcdRequest {
return EtcdRequest{Ops: []EtcdOperation{{Type: LeaseGrant, LeaseID: leaseID}}}
}
func leaseGrantResponse(revision int64) EtcdResponse {
return EtcdResponse{OpsResult: []EtcdOperationResult{{}}, Revision: revision}
}
func leaseRevokeRequest(leaseID int64) EtcdRequest {
return EtcdRequest{Ops: []EtcdOperation{{Type: LeaseRevoke, LeaseID: leaseID}}}
}
func leaseRevokeResponse(revision int64) EtcdResponse {
return EtcdResponse{OpsResult: []EtcdOperationResult{{}}, Revision: revision}
}
type History struct {
successful []porcupine.Operation
// failed requests are kept separate as we don't know return time of failed operations.
// Based on https://github.com/anishathalye/porcupine/issues/10
failed []porcupine.Operation
}
func (h History) Merge(h2 History) History {
result := History{
successful: make([]porcupine.Operation, 0, len(h.successful)+len(h2.successful)),
failed: make([]porcupine.Operation, 0, len(h.failed)+len(h2.failed)),
}
result.successful = append(result.successful, h.successful...)
result.successful = append(result.successful, h2.successful...)
result.failed = append(result.failed, h.failed...)
result.failed = append(result.failed, h2.failed...)
return result
}
func (h History) Operations() []porcupine.Operation {
operations := make([]porcupine.Operation, 0, len(h.successful)+len(h.failed))
var maxTime int64
for _, op := range h.successful {
operations = append(operations, op)
if op.Return > maxTime {
maxTime = op.Return
}
}
for _, op := range h.failed {
if op.Call > maxTime {
maxTime = op.Call
}
}
// Failed requests don't have a known return time.
// Simulate Infinity by using last observed time.
for _, op := range h.failed {
op.Return = maxTime + 1
operations = append(operations, op)
}
return operations
}