Skip to content

Commit

Permalink
tests: Validate etcd linearizability
Browse files Browse the repository at this point in the history
Signed-off-by: Marek Siarkowicz <[email protected]>
  • Loading branch information
serathius committed Oct 17, 2022
1 parent da538d0 commit 5779393
Show file tree
Hide file tree
Showing 14 changed files with 380 additions and 0 deletions.
11 changes: 11 additions & 0 deletions .github/workflows/linearizability.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
name: Linearizability
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: "1.19.1"
- run: make test-linearizability
4 changes: 4 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ test-e2e: build
test-e2e-release: build
PASSES="release e2e" ./scripts/test.sh

.PHONY: test-linearizability
test-linearizability: build
PASSES="linearizability" ./scripts/test.sh

# Static analysis

verify: verify-gofmt verify-bom verify-lint verify-dep verify-shellcheck verify-goword verify-govet verify-license-header verify-receiver-name verify-mod-tidy verify-shellcheck verify-shellws verify-proto-annotations
Expand Down
9 changes: 9 additions & 0 deletions bill-of-materials.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@
}
]
},
{
"project": "github.com/anishathalye/porcupine",
"licenses": [
{
"type": "MIT License",
"confidence": 1
}
]
},
{
"project": "github.com/benbjohnson/clock",
"licenses": [
Expand Down
1 change: 1 addition & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/anishathalye/porcupine v0.1.2/go.mod h1:/X9OQYnVb7DzfKCQVO4tI1Aq+o56UJW+RvN/5U4EuZA=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
Expand Down
5 changes: 5 additions & 0 deletions scripts/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,11 @@ function e2e_pass {
run_for_module "tests" go_test "./common/..." "keep_going" : --tags=e2e -timeout="${TIMEOUT:-30m}" "${RUN_ARG[@]}" "$@"
}

function linearizability_pass {
# e2e tests are running pre-build binary. Settings like --race,-cover,-cpu does not have any impact.
run_for_module "tests" go_test "./linearizability/..." "keep_going" : -timeout="${TIMEOUT:-30m}" "${RUN_ARG[@]}" "$@"
}

function integration_e2e_pass {
run_pass "integration" "${@}"
run_pass "e2e" "${@}"
Expand Down
4 changes: 4 additions & 0 deletions tests/framework/e2e.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,10 @@ func (c *e2eCluster) Client(cfg clientv3.AuthConfig) (Client, error) {
return e2eClient{etcdctl}, nil
}

func (c *e2eCluster) Endpoints() []string {
return c.EndpointsV3()
}

func (c *e2eCluster) Members() (ms []Member) {
for _, proc := range c.EtcdProcessCluster.Procs {
ms = append(ms, e2eMember{EtcdProcess: proc, Cfg: c.Cfg})
Expand Down
4 changes: 4 additions & 0 deletions tests/framework/e2e/cluster_proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ func (p *proxyEtcdProcess) Logs() LogsExpect {
return p.etcdProc.Logs()
}

func (p *proxyEtcdProcess) Kill() error {
return p.etcdProc.Kill()
}

type proxyProc struct {
lg *zap.Logger
name string
Expand Down
6 changes: 6 additions & 0 deletions tests/framework/e2e/etcd_process.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"fmt"
"net/url"
"os"
"syscall"
"testing"
"time"

Expand Down Expand Up @@ -47,6 +48,7 @@ type EtcdProcess interface {
Close() error
Config() *EtcdServerProcessConfig
Logs() LogsExpect
Kill() error
}

type LogsExpect interface {
Expand Down Expand Up @@ -177,6 +179,10 @@ func (ep *EtcdServerProcess) Logs() LogsExpect {
return ep.proc
}

func (ep *EtcdServerProcess) Kill() error {
return ep.proc.Signal(syscall.SIGKILL)
}

func AssertProcessLogs(t *testing.T, ep EtcdProcess, expectLog string) {
t.Helper()
var err error
Expand Down
1 change: 1 addition & 0 deletions tests/framework/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ type Cluster interface {
Client(cfg clientv3.AuthConfig) (Client, error)
WaitLeader(t testing.TB) int
Close() error
Endpoints() []string
}

type Member interface {
Expand Down
1 change: 1 addition & 0 deletions tests/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ replace (
)

require (
github.com/anishathalye/porcupine v0.1.2
github.com/coreos/go-semver v0.3.0
github.com/dustin/go-humanize v1.0.0
github.com/gogo/protobuf v1.3.2
Expand Down
2 changes: 2 additions & 0 deletions tests/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
github.com/anishathalye/porcupine v0.1.2 h1:eqWNeLcnTzXt6usipDJ4RFn6XOWqY5wEqBYVG3yFLSE=
github.com/anishathalye/porcupine v0.1.2/go.mod h1:/X9OQYnVb7DzfKCQVO4tI1Aq+o56UJW+RvN/5U4EuZA=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
Expand Down
27 changes: 27 additions & 0 deletions tests/linearizability/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// 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 (
"testing"

"go.etcd.io/etcd/tests/v3/framework"
)

var testRunner = framework.E2eTestRunner

func TestMain(m *testing.M) {
testRunner.TestMain(m)
}
112 changes: 112 additions & 0 deletions tests/linearizability/model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// 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 (
"encoding/json"
"fmt"

"github.com/anishathalye/porcupine"
)

type Operation int8

const Read Operation = 0
const Put Operation = 1

type etcdRequest struct {
op Operation
writeData string
}

type etcdResponse struct {
readData string
err error
}

type EtcdState struct {
Value string
FailedWrites []string
}

var etcdModel = porcupine.Model{
Init: func() interface{} { return "{}" },
Step: func(st interface{}, in interface{}, out interface{}) (bool, interface{}) {
stateString := st.(string)
var state EtcdState
err := json.Unmarshal([]byte(stateString), &state)
if err != nil {
panic(err)
}
request := in.(etcdRequest)
response := out.(etcdResponse)
ok, state := step(state, request, response)
data, err := json.Marshal(state)
if err != nil {
panic(err)
}
return ok, string(data)
},
DescribeOperation: func(in, out interface{}) string {
request := in.(etcdRequest)
response := out.(etcdResponse)
var call, args, resp string
switch request.op {
case Read:
call = "read"
if response.err != nil {
resp = response.err.Error()
} else {
resp = response.readData
}
case Put:
call = "write"
args = request.writeData
if response.err != nil {
resp = response.err.Error()
} else {
resp = "ok"
}
default:
return "<invalid>"
}
return fmt.Sprintf("%s(%q) -> %s", call, args, resp)
},
}

func step(state EtcdState, request etcdRequest, response etcdResponse) (bool, EtcdState) {
var ok bool
switch request.op {
case Read:
ok = state.Value == response.readData
if !ok {
for i, write := range state.FailedWrites {
if write == response.readData {
ok = true
state = EtcdState{Value: write, FailedWrites: append(state.FailedWrites[:i], state.FailedWrites[i+1:]...)}
break
}
}
}
case Put:
if response.err == nil {
state.Value = request.writeData
} else {
state.FailedWrites = append(state.FailedWrites, request.writeData)
}
ok = true
}
return ok, state
}
Loading

0 comments on commit 5779393

Please sign in to comment.