diff --git a/tests/antithesis/Makefile b/tests/antithesis/Makefile index c2a00b56e0d1..6a90f637b2a5 100644 --- a/tests/antithesis/Makefile +++ b/tests/antithesis/Makefile @@ -26,7 +26,8 @@ antithesis-build-etcd-image: --build-arg GO_IMAGE_TAG=$(shell go work edit -json | jq .Go) \ --build-arg REF=$(REF) \ --tag etcd-server:latest \ - $(ANTITHESIS_ROOT)/server/ + -f ./server/Dockerfile \ + $(ANTITHESIS_ROOT) .PHONY: antithesis-build-etcd-image-release-3.4 antithesis-build-etcd-image-release-3.4: REF=release-3.4 diff --git a/tests/antithesis/pkg/go.mod b/tests/antithesis/pkg/go.mod new file mode 100644 index 000000000000..1afe690ab837 --- /dev/null +++ b/tests/antithesis/pkg/go.mod @@ -0,0 +1,5 @@ +module go.etcd.io/etcd/tests/v3/pkg + +go 1.26.4 + +require github.com/antithesishq/antithesis-sdk-go v0.7.2 diff --git a/tests/antithesis/pkg/go.sum b/tests/antithesis/pkg/go.sum new file mode 100644 index 000000000000..55bc0df48c05 --- /dev/null +++ b/tests/antithesis/pkg/go.sum @@ -0,0 +1,2 @@ +github.com/antithesishq/antithesis-sdk-go v0.7.2 h1:oEEedg1Xgi8drRjqB0f9tfjhLoInE0IYZfZ6zAhQUbY= +github.com/antithesishq/antithesis-sdk-go v0.7.2/go.mod h1:FQyySiasQQM8735Ddel3MRojmy4dA1IqCeyJ5jmPMbI= diff --git a/tests/antithesis/pkg/rand/rand.go b/tests/antithesis/pkg/rand/rand.go new file mode 100644 index 000000000000..7daaae5dd23e --- /dev/null +++ b/tests/antithesis/pkg/rand/rand.go @@ -0,0 +1,89 @@ +// Copyright 2026 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 rand is a wrapper of https://antithesis.com/docs/generated/sdk/golang/random/ +// and provides the same functions as math/rand so it can be a drop-in replacement +// +// The usages of math/rand currently found in etcd (listing only the first occurence of each here) +// to find all, use `for f in $(rg -l 'math/rand"'); do echo $f; grep 'rand\.' $f; done` +// +// 1. out = append(out, rand.Int63n(max-min)+min) +// 2. r := rand.New(rand.NewSource(int64(time.Now().Nanosecond()))) +// 3. func shuffleEndpoints(r *rand.Rand, eps []string) []string { +// 4. n := rand.Intn(3) + 3 +// 5. key := fmt.Sprintf("foo%d", rand.Int()) +// 6. membersToChange := rand.Perm(len(clus.Procs))[:numberOfMembersToChange] +// 7. if _, err := rand.Read(v); err != nil { +// 8. multiplier := jitter * (rand.Float64()*2 - 1) +// 9. traceNum := rand.Int31() +// 10. randID = rand.Uint64() +// 11. rand.Shuffle(len(perm), func(i, j int) { +package rand + +import ( + mrand "math/rand" + + antithesis "github.com/antithesishq/antithesis-sdk-go/random" +) + +var ( + src = antithesis.Source() + r = mrand.New(src) +) + +type Rand = mrand.Rand + +func New(_ mrand.Source) *mrand.Rand { + return r +} + +func NewSource(_ int64) mrand.Source { + return src +} + +func Int63n(n int64) int64 { + return r.Int63n(n) +} + +func Uint64() uint64 { + return r.Uint64() +} + +func Intn(n int) int { + return r.Intn(n) +} + +func Int() int { + return r.Int() +} + +func Perm(n int) []int { + return r.Perm(n) +} + +func Read(p []byte) (n int, err error) { + return r.Read(p) +} + +func Float64() float64 { + return r.Float64() +} + +func Int31() int32 { + return r.Int31() +} + +func Shuffle(n int, swap func(i, j int)) { + r.Shuffle(n, swap) +} diff --git a/tests/antithesis/pkg/rand/v2/randv2.go b/tests/antithesis/pkg/rand/v2/randv2.go new file mode 100644 index 000000000000..7107ddf0a026 --- /dev/null +++ b/tests/antithesis/pkg/rand/v2/randv2.go @@ -0,0 +1,63 @@ +// Copyright 2026 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 rand is a wrapper of https://antithesis.com/docs/generated/sdk/golang/random/ +// and provides the same functions as math/rand/v2 +// +// The usages of math/rand/v2 currently found in etcd +// +// 1. server/etcdserver/txn/util_bench_test.go +// rng := rand.New(rand.NewPCG(1, 2)) +// 2. tests/robustness/validate/operations_test.go +// rand.Shuffle(len(historyCopy), func(i, j int) +// 3. tests/antithesis/test-template/robustness/traffic/main.go +// choice := rand.IntN(len(traffics)) +package rand + +import ( + mrandv1 "math/rand" + mrandv2 "math/rand/v2" + + antithesis "github.com/antithesishq/antithesis-sdk-go/random" +) + +type sourceV2Func func() uint64 + +func (f sourceV2Func) Uint64() uint64 { + return f() +} + +// antithesis doesn't provide a math/rand/v2 source, so this is +// needed for making a v1 source a v2. +var ( + v1Rand = mrandv1.New(antithesis.Source()) + v2Source = sourceV2Func(v1Rand.Uint64) + r = mrandv2.New(v2Source) +) + +func New(_ mrandv2.Source) *mrandv2.Rand { + return r +} + +func NewPCG(_, _ int) mrandv2.Source { + return v2Source +} + +func IntN(n int) int { + return r.IntN(n) +} + +func Shuffle(n int, swap func(i, j int)) { + r.Shuffle(n, swap) +} diff --git a/tests/antithesis/server/Dockerfile b/tests/antithesis/server/Dockerfile index f82714c50889..fb09bbc5169f 100644 --- a/tests/antithesis/server/Dockerfile +++ b/tests/antithesis/server/Dockerfile @@ -8,16 +8,24 @@ ARG REF=main RUN git clone --depth=1 https://github.com/etcd-io/etcd.git --branch=${REF} /etcd RUN go env -w GOTOOLCHAIN="go$(cat .go-version)" -# inject assertions in place of gofail +# Replacing math/rand with etcd's antithesis-sdk-go/random wrapper +# See https://antithesis.com/docs/configuration/the_antithesis_environment/?sid=8fce.35&sterm=random&marks=Random#random-devices +WORKDIR /etcd +COPY pkg /etcd/tests/antithesis/pkg +RUN find . -path "./tests/antithesis" -prune -type f -o -name 'go.mod' -type f -execdir go mod edit -replace=math/rand=$(git rev-parse --show-toplevel)/tests/antithesis/pkg/rand \; +RUN find . -path "./tests/antithesis" -prune -type f -o -name 'go.mod' -type f -execdir go mod edit -replace=math/rand/v2=$(git rev-parse --show-toplevel)/tests/antithesis/pkg/rand/v2 \; +RUN find . -name 'go.mod' -type f -execdir go mod tidy \; + +## inject assertions in place of gofail WORKDIR /etcd/server RUN go install golang.org/x/tools/cmd/goimports@latest -RUN go get github.com/antithesishq/antithesis-sdk-go@v0.4.4 +RUN go get github.com/antithesishq/antithesis-sdk-go@v0.7.2 RUN for file in $(grep -rl '// gofail'); do sed -i 's|\/\/ gofail.*var \([[:alnum:]]*\) .*|assert\.Reachable("\1", nil)|' $file; goimports -w $file; done RUN go mod tidy # replace verify with antithesis WORKDIR /etcd -COPY inject/* . +COPY server/inject/* . RUN if [ "${REF}" = "main" ]; then git apply *.patch; fi WORKDIR /etcd/client/pkg RUN go mod tidy