diff --git a/tests/antithesis/Dockerfile.config b/tests/antithesis/Dockerfile.config index 39571f67111..9c796f6f3fc 100644 --- a/tests/antithesis/Dockerfile.config +++ b/tests/antithesis/Dockerfile.config @@ -4,7 +4,8 @@ FROM golang:$GO_VERSION AS build RUN go install github.com/a8m/envsubst/cmd/envsubst@v1.4.3 ARG IMAGE_TAG -COPY docker-compose.yml /docker-compose.yml.template +ARG NODE_COUNT +COPY docker-compose-${NODE_COUNT}-node.yml /docker-compose.yml.template RUN IMAGE_TAG=${IMAGE_TAG} cat /docker-compose.yml.template | envsubst > /docker-compose.yml FROM scratch diff --git a/tests/antithesis/Makefile b/tests/antithesis/Makefile index 23308872ae4..5b2ee7a452e 100644 --- a/tests/antithesis/Makefile +++ b/tests/antithesis/Makefile @@ -5,9 +5,14 @@ ARCH ?= $(shell go env GOARCH) REF = main IMAGE_TAG = latest +CFG_NODE_COUNT ?= 3 + .PHONY: antithesis-build-client-docker-image -antithesis-build-client-docker-image: - docker build --build-arg GO_VERSION=$(shell cat $(REPOSITORY_ROOT)/.go-version) -f $(REPOSITORY_ROOT)/tests/antithesis/test-template/Dockerfile $(REPOSITORY_ROOT) -t etcd-client:latest +antithesis-build-client-docker-image: validate-node-count + docker build \ + --build-arg GO_VERSION=$(shell cat $(REPOSITORY_ROOT)/.go-version) \ + --build-arg CFG_NODE_COUNT=$(CFG_NODE_COUNT) \ + -f $(REPOSITORY_ROOT)/tests/antithesis/test-template/Dockerfile $(REPOSITORY_ROOT) -t etcd-client:latest .PHONY: antithesis-build-etcd-image antithesis-build-etcd-image: @@ -30,30 +35,39 @@ antithesis-build-etcd-image-main: REF=main antithesis-build-etcd-image-main: antithesis-build-etcd-image .PHONY: antithesis-build-config-image -antithesis-build-config-image: - docker build -f ./Dockerfile.config . -t etcd-config:latest --build-arg IMAGE_TAG=$(IMAGE_TAG) +antithesis-build-config-image: validate-node-count + docker build -f ./Dockerfile.config . -t etcd-config:latest \ + --build-arg IMAGE_TAG=$(IMAGE_TAG) \ + --build-arg NODE_COUNT=$(CFG_NODE_COUNT) .PHONY: antithesis-docker-compose-up -antithesis-docker-compose-up: - export USER_ID=$(USER_ID) && export GROUP_ID=$(GROUP_ID) && docker-compose up +antithesis-docker-compose-up: validate-node-count + export USER_ID=$(USER_ID) && export GROUP_ID=$(GROUP_ID) && docker compose -f docker-compose-$(CFG_NODE_COUNT)-node.yml up .PHONY: antithesis-run-container-traffic -antithesis-run-container-traffic: - export USER_ID=$(USER_ID) && export GROUP_ID=$(GROUP_ID) && docker-compose exec client /opt/antithesis/test/v1/robustness/singleton_driver_traffic +antithesis-run-container-traffic: validate-node-count + export USER_ID=$(USER_ID) && export GROUP_ID=$(GROUP_ID) && docker compose -f docker-compose-$(CFG_NODE_COUNT)-node.yml exec client /opt/antithesis/test/v1/robustness/singleton_driver_traffic .PHONY: antithesis-run-container-validation -antithesis-run-container-validation: - export USER_ID=$(USER_ID) && export GROUP_ID=$(GROUP_ID) && docker-compose exec client /opt/antithesis/test/v1/robustness/finally_validation +antithesis-run-container-validation: validate-node-count + export USER_ID=$(USER_ID) && export GROUP_ID=$(GROUP_ID) && docker compose -f docker-compose-$(CFG_NODE_COUNT)-node.yml exec client /opt/antithesis/test/v1/robustness/finally_validation .PHONY: antithesis-run-local-traffic antithesis-run-local-traffic: - go run --race ./test-template/robustness/traffic/main.go --local + go run -ldflags "-X main.NodeCount=$(CFG_NODE_COUNT)" --race ./test-template/robustness/traffic/main.go --local .PHONY: antithesis-run-local-validation antithesis-run-local-validation: - go run --race ./test-template/robustness/finally/main.go --local + go run -ldflags "-X main.NodeCount=$(CFG_NODE_COUNT)" --race ./test-template/robustness/finally/main.go --local .PHONY: antithesis-clean -antithesis-clean: - export USER_ID=$(USER_ID) && export GROUP_ID=$(GROUP_ID) && docker-compose down +antithesis-clean: validate-node-count + export USER_ID=$(USER_ID) && export GROUP_ID=$(GROUP_ID) && docker compose -f docker-compose-$(CFG_NODE_COUNT)-node.yml down --remove-orphans rm -rf /tmp/etcddata0 /tmp/etcddata1 /tmp/etcddata2 /tmp/etcdreport + +.PHONY: validate-node-count +validate-node-count: + @if [ "$(CFG_NODE_COUNT)" != "1" ] && [ "$(CFG_NODE_COUNT)" != "3" ]; then \ + echo "CFG_NODE_COUNT must be either 1 or 3 (got $(CFG_NODE_COUNT))"; \ + exit 1; \ + fi diff --git a/tests/antithesis/docker-compose-1-node.yml b/tests/antithesis/docker-compose-1-node.yml new file mode 100644 index 00000000000..37f8fe9ce9e --- /dev/null +++ b/tests/antithesis/docker-compose-1-node.yml @@ -0,0 +1,54 @@ +--- +services: + # This is needed for creating non-root data folders on host. + # By default, if the folders don't exist when mounting, compose creates them with root as owner. + # With root owner, accessing the WAL files from local tests will fail due to an unauthorized access error. + init: + image: 'docker.io/library/ubuntu:latest' + user: root + group_add: + - '${GROUP_ID:-root}' + volumes: + - ${ETCD_ROBUSTNESS_DATA_PATH:-/tmp/etcddata}0:/var/etcddata0 + - ${ETCD_ROBUSTNESS_REPORT_PATH:-/tmp/etcdreport}:/var/report + command: + - /bin/sh + - -c + - | + rm -rf /var/etcddata0/* /var/report/* + chown -R ${USER_ID:-root}:${GROUP_ID:-root} /var/etcddata0 /var/report + + etcd0: + image: 'etcd-server:${IMAGE_TAG:-latest}' + container_name: etcd0 + hostname: etcd0 + environment: + ETCD_NAME: "etcd0" + ETCD_INITIAL_ADVERTISE_PEER_URLS: "http://etcd0:2380" + ETCD_LISTEN_PEER_URLS: "http://0.0.0.0:2380" + ETCD_LISTEN_CLIENT_URLS: "http://0.0.0.0:2379" + ETCD_ADVERTISE_CLIENT_URLS: "http://etcd0.etcd:2379" + ETCD_INITIAL_CLUSTER_TOKEN: "etcd-cluster-1" + ETCD_INITIAL_CLUSTER: "etcd0=http://etcd0:2380" + ETCD_INITIAL_CLUSTER_STATE: "new" + ETCD_DATA_DIR: "/var/etcd/data" + user: "${USER_ID:-root}:${GROUP_ID:-root}" + depends_on: + init: + condition: service_completed_successfully + ports: + - 12379:2379 + volumes: + - ${ETCD_ROBUSTNESS_DATA_PATH:-/tmp/etcddata}0:/var/etcd/data + + client: + image: 'etcd-client:${IMAGE_TAG:-latest}' + container_name: client + entrypoint: ["/opt/antithesis/entrypoint/entrypoint"] + user: "${USER_ID:-root}:${GROUP_ID:-root}" + depends_on: + etcd0: + condition: service_started + volumes: + - ${ETCD_ROBUSTNESS_DATA_PATH:-/tmp/etcddata}0:/var/etcddata0 + - ${ETCD_ROBUSTNESS_REPORT_PATH:-/tmp/etcdreport}:/var/report diff --git a/tests/antithesis/docker-compose.yml b/tests/antithesis/docker-compose-3-node.yml similarity index 100% rename from tests/antithesis/docker-compose.yml rename to tests/antithesis/docker-compose-3-node.yml diff --git a/tests/antithesis/test-template/Dockerfile b/tests/antithesis/test-template/Dockerfile index b4c9d0329a4..04f9ee66e37 100644 --- a/tests/antithesis/test-template/Dockerfile +++ b/tests/antithesis/test-template/Dockerfile @@ -2,10 +2,11 @@ ARG GO_VERSION=1.24.4 ARG ARCH=amd64 FROM golang:$GO_VERSION +ARG CFG_NODE_COUNT=3 WORKDIR /build COPY . . WORKDIR /build/tests -RUN go build -o /opt/antithesis/entrypoint/entrypoint -race ./antithesis/test-template/entrypoint/main.go -RUN go build -o /opt/antithesis/test/v1/robustness/singleton_driver_traffic -race ./antithesis/test-template/robustness/traffic/main.go -RUN go build -o /opt/antithesis/test/v1/robustness/finally_validation -race ./antithesis/test-template/robustness/finally/main.go +RUN go build -ldflags "-X main.NodeCount=$CFG_NODE_COUNT" -o /opt/antithesis/entrypoint/entrypoint -race ./antithesis/test-template/entrypoint/main.go +RUN go build -ldflags "-X main.NodeCount=$CFG_NODE_COUNT" -o /opt/antithesis/test/v1/robustness/singleton_driver_traffic -race ./antithesis/test-template/robustness/traffic/main.go +RUN go build -ldflags "-X main.NodeCount=$CFG_NODE_COUNT" -o /opt/antithesis/test/v1/robustness/finally_validation -race ./antithesis/test-template/robustness/finally/main.go diff --git a/tests/antithesis/test-template/entrypoint/main.go b/tests/antithesis/test-template/entrypoint/main.go index cf2f46f75f2..ab9e365b3dd 100644 --- a/tests/antithesis/test-template/entrypoint/main.go +++ b/tests/antithesis/test-template/entrypoint/main.go @@ -24,14 +24,19 @@ import ( "github.com/antithesishq/antithesis-sdk-go/lifecycle" clientv3 "go.etcd.io/etcd/client/v3" + "go.etcd.io/etcd/tests/v3/antithesis/test-template/robustness/common" ) // Sleep duration const SLEEP = 10 +var NodeCount = "3" + // CheckHealth checks health of all etcd nodes func CheckHealth() bool { - nodeOptions := []string{"etcd0", "etcd1", "etcd2"} + cfg := common.MakeConfig(NodeCount) + + nodeOptions := []string{"etcd0", "etcd1", "etcd2"}[:cfg.NodeCount] // iterate over each node and check health for _, node := range nodeOptions { diff --git a/tests/antithesis/test-template/robustness/common/config.go b/tests/antithesis/test-template/robustness/common/config.go new file mode 100644 index 00000000000..97a8ef39637 --- /dev/null +++ b/tests/antithesis/test-template/robustness/common/config.go @@ -0,0 +1,29 @@ +// Copyright 2025 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 common + +import "strconv" + +type Config struct { + NodeCount int +} + +func MakeConfig(nodeCount string) *Config { + cfg := &Config{} + + cfg.NodeCount, _ = strconv.Atoi(nodeCount) + + return cfg +} diff --git a/tests/antithesis/test-template/robustness/common/path.go b/tests/antithesis/test-template/robustness/common/path.go index 7c0e9e02a80..492da9ea2a8 100644 --- a/tests/antithesis/test-template/robustness/common/path.go +++ b/tests/antithesis/test-template/robustness/common/path.go @@ -38,23 +38,23 @@ const ( localReportPath = "report" ) -func DefaultPaths() (string, []string, map[string]string) { - hosts := []string{defaultetcd0, defaultetcd1, defaultetcd2} - reportPath := defaultReportPath - dataPaths := etcdDataPaths(defaultetcdDataPath, len(hosts)) - return reportPath, hosts, dataPaths +func DefaultPaths(cfg *Config) (hosts []string, reportPath string, dataPaths map[string]string) { + hosts = []string{defaultetcd0, defaultetcd1, defaultetcd2}[:cfg.NodeCount] + reportPath = defaultReportPath + dataPaths = etcdDataPaths(defaultetcdDataPath, cfg.NodeCount) + return hosts, reportPath, dataPaths } -func LocalPaths() (string, []string, map[string]string) { - hosts := []string{localetcd0, localetcd1, localetcd2} - reportPath := localReportPath +func LocalPaths(cfg *Config) (hosts []string, reportPath string, dataPaths map[string]string) { + hosts = []string{localetcd0, localetcd1, localetcd2}[:cfg.NodeCount] + reportPath = localReportPath etcdDataPath := defaultetcdLocalDataPath envPath := os.Getenv(localetcdDataPathEnv) if envPath != "" { etcdDataPath = envPath + "%d" } - dataPaths := etcdDataPaths(etcdDataPath, len(hosts)) - return reportPath, hosts, dataPaths + dataPaths = etcdDataPaths(etcdDataPath, cfg.NodeCount) + return hosts, reportPath, dataPaths } func etcdDataPaths(dir string, amount int) map[string]string { diff --git a/tests/antithesis/test-template/robustness/finally/main.go b/tests/antithesis/test-template/robustness/finally/main.go index 8f2bcac48f3..7eb2822344b 100644 --- a/tests/antithesis/test-template/robustness/finally/main.go +++ b/tests/antithesis/test-template/robustness/finally/main.go @@ -35,13 +35,17 @@ const ( reportFileName = "history.html" ) +var NodeCount = "3" + func main() { local := flag.Bool("local", false, "run finally locally and connect to etcd instances via localhost") flag.Parse() - reportPath, _, dirs := common.DefaultPaths() + cfg := common.MakeConfig(NodeCount) + + _, reportPath, dirs := common.DefaultPaths(cfg) if *local { - reportPath, _, dirs = common.LocalPaths() + _, reportPath, dirs = common.LocalPaths(cfg) } lg, err := zap.NewProduction() diff --git a/tests/antithesis/test-template/robustness/traffic/main.go b/tests/antithesis/test-template/robustness/traffic/main.go index de7f18de175..e4f372cdf25 100644 --- a/tests/antithesis/test-template/robustness/traffic/main.go +++ b/tests/antithesis/test-template/robustness/traffic/main.go @@ -54,15 +54,18 @@ var ( traffic.EtcdPutDeleteLease, traffic.Kubernetes, } + NodeCount = "3" ) func main() { local := flag.Bool("local", false, "run tests locally and connect to etcd instances via localhost") flag.Parse() - reportPath, hosts, etcdetcdDataPaths := common.DefaultPaths() + cfg := common.MakeConfig(NodeCount) + + hosts, reportPath, etcdetcdDataPaths := common.DefaultPaths(cfg) if *local { - reportPath, hosts, etcdetcdDataPaths = common.LocalPaths() + hosts, reportPath, etcdetcdDataPaths = common.LocalPaths(cfg) } ctx := context.Background()