diff --git a/tests/antithesis/Makefile b/tests/antithesis/Makefile index 8eb13cb6f5d..2c4dac36c90 100644 --- a/tests/antithesis/Makefile +++ b/tests/antithesis/Makefile @@ -5,13 +5,19 @@ ARCH ?= $(shell go env GOARCH) REF = main IMAGE_TAG = latest +# Node count for docker-compose file selection only CFG_NODE_COUNT ?= 3 +# Default environment variables (mainly for local development) +ETCD_ROBUSTNESS_ENDPOINTS ?= 127.0.0.1:12379,127.0.0.1:22379,127.0.0.1:32379 +ETCD_ROBUSTNESS_DATA_PATHS ?= /tmp/etcddata0,/tmp/etcddata1,/tmp/etcddata2 +ETCD_ROBUSTNESS_REPORT_PATH ?= ./report +ETCD_ROBUSTNESS_PATH_PREFIX ?= /tmp/etcddata + .PHONY: antithesis-build-client-docker-image -antithesis-build-client-docker-image: validate-node-count +antithesis-build-client-docker-image: 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 @@ -55,19 +61,31 @@ antithesis-run-container-validation: validate-node-count export USER_ID=$(USER_ID) && export GROUP_ID=$(GROUP_ID) && \ docker compose -f config/docker-compose-$(CFG_NODE_COUNT)-node.yml exec client /opt/antithesis/test/v1/robustness/finally_validation +LOCAL_ENV_VARS = export ETCD_ROBUSTNESS_ENDPOINTS=$(ETCD_ROBUSTNESS_ENDPOINTS) && \ + export ETCD_ROBUSTNESS_DATA_PATHS=$(ETCD_ROBUSTNESS_DATA_PATHS) && \ + export ETCD_ROBUSTNESS_REPORT_PATH=$(ETCD_ROBUSTNESS_REPORT_PATH) + .PHONY: antithesis-run-local-traffic antithesis-run-local-traffic: - go run -ldflags "-X main.NodeCount=$(CFG_NODE_COUNT)" --race ./test-template/robustness/traffic/main.go --local + $(LOCAL_ENV_VARS) && \ + go run --race ./test-template/robustness/traffic/main.go .PHONY: antithesis-run-local-validation antithesis-run-local-validation: - go run -ldflags "-X main.NodeCount=$(CFG_NODE_COUNT)" --race ./test-template/robustness/finally/main.go --local + $(LOCAL_ENV_VARS) && \ + go run --race ./test-template/robustness/finally/main.go .PHONY: antithesis-clean antithesis-clean: validate-node-count export USER_ID=$(USER_ID) && export GROUP_ID=$(GROUP_ID) && \ docker compose -f config/docker-compose-$(CFG_NODE_COUNT)-node.yml down --remove-orphans - rm -rf /tmp/etcddata0 /tmp/etcddata1 /tmp/etcddata2 /tmp/etcdreport + @echo "Cleaning data paths: $(ETCD_ROBUSTNESS_DATA_PATHS)" + @echo "Cleaning report path: $(ETCD_ROBUSTNESS_REPORT_PATH)" + @for path in $$(echo "$(ETCD_ROBUSTNESS_DATA_PATHS)" | tr ',' ' '); do \ + echo "Removing: $$path"; \ + rm -rf "$$path"; \ + done + rm -rf "$(ETCD_ROBUSTNESS_REPORT_PATH)" .PHONY: validate-node-count validate-node-count: diff --git a/tests/antithesis/config/docker-compose-1-node.yml b/tests/antithesis/config/docker-compose-1-node.yml index fcf2d97dbc3..a4e794a7386 100644 --- a/tests/antithesis/config/docker-compose-1-node.yml +++ b/tests/antithesis/config/docker-compose-1-node.yml @@ -9,7 +9,7 @@ services: group_add: - '${GROUP_ID:-root}' volumes: - - ${ETCD_ROBUSTNESS_DATA_PATH:-/tmp/etcddata}0:/var/etcddata0 + - ${ETCD_ROBUSTNESS_PATH_PREFIX:-/tmp/etcddata}0:/var/etcddata0 - ${ETCD_ROBUSTNESS_REPORT_PATH:-/tmp/etcdreport}:/var/report command: - /bin/sh @@ -43,16 +43,20 @@ services: ports: - 12379:2379 volumes: - - ${ETCD_ROBUSTNESS_DATA_PATH:-/tmp/etcddata}0:/var/etcd/data + - ${ETCD_ROBUSTNESS_PATH_PREFIX:-/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}" + environment: + ETCD_ROBUSTNESS_ENDPOINTS: "etcd0:2379" + ETCD_ROBUSTNESS_DATA_PATHS: "/var/etcddata0" + ETCD_ROBUSTNESS_REPORT_PATH: "/var/report" depends_on: etcd0: condition: service_started volumes: - - ${ETCD_ROBUSTNESS_DATA_PATH:-/tmp/etcddata}0:/var/etcddata0 + - ${ETCD_ROBUSTNESS_PATH_PREFIX:-/tmp/etcddata}0:/var/etcddata0 - ${ETCD_ROBUSTNESS_REPORT_PATH:-/tmp/etcdreport}:/var/report diff --git a/tests/antithesis/config/docker-compose-3-node.yml b/tests/antithesis/config/docker-compose-3-node.yml index 4a0bf7253db..008d8a97767 100644 --- a/tests/antithesis/config/docker-compose-3-node.yml +++ b/tests/antithesis/config/docker-compose-3-node.yml @@ -9,9 +9,9 @@ services: group_add: - '${GROUP_ID:-root}' volumes: - - ${ETCD_ROBUSTNESS_DATA_PATH:-/tmp/etcddata}0:/var/etcddata0 - - ${ETCD_ROBUSTNESS_DATA_PATH:-/tmp/etcddata}1:/var/etcddata1 - - ${ETCD_ROBUSTNESS_DATA_PATH:-/tmp/etcddata}2:/var/etcddata2 + - ${ETCD_ROBUSTNESS_PATH_PREFIX:-/tmp/etcddata}0:/var/etcddata0 + - ${ETCD_ROBUSTNESS_PATH_PREFIX:-/tmp/etcddata}1:/var/etcddata1 + - ${ETCD_ROBUSTNESS_PATH_PREFIX:-/tmp/etcddata}2:/var/etcddata2 - ${ETCD_ROBUSTNESS_REPORT_PATH:-/tmp/etcdreport}:/var/report command: - /bin/sh @@ -45,7 +45,7 @@ services: ports: - 12379:2379 volumes: - - ${ETCD_ROBUSTNESS_DATA_PATH:-/tmp/etcddata}0:/var/etcd/data + - ${ETCD_ROBUSTNESS_PATH_PREFIX:-/tmp/etcddata}0:/var/etcd/data etcd1: image: 'etcd-server:${IMAGE_TAG:-latest}' @@ -72,7 +72,7 @@ services: ports: - 22379:2379 volumes: - - ${ETCD_ROBUSTNESS_DATA_PATH:-/tmp/etcddata}1:/var/etcd/data + - ${ETCD_ROBUSTNESS_PATH_PREFIX:-/tmp/etcddata}1:/var/etcd/data etcd2: image: 'etcd-server:${IMAGE_TAG:-latest}' @@ -99,13 +99,17 @@ services: ports: - 32379:2379 volumes: - - ${ETCD_ROBUSTNESS_DATA_PATH:-/tmp/etcddata}2:/var/etcd/data + - ${ETCD_ROBUSTNESS_PATH_PREFIX:-/tmp/etcddata}2:/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}" + environment: + ETCD_ROBUSTNESS_ENDPOINTS: "etcd0:2379,etcd1:2379,etcd2:2379" + ETCD_ROBUSTNESS_DATA_PATHS: "/var/etcddata0,/var/etcddata1,/var/etcddata2" + ETCD_ROBUSTNESS_REPORT_PATH: "/var/report" depends_on: etcd0: condition: service_started @@ -114,7 +118,7 @@ services: etcd2: condition: service_started volumes: - - ${ETCD_ROBUSTNESS_DATA_PATH:-/tmp/etcddata}0:/var/etcddata0 - - ${ETCD_ROBUSTNESS_DATA_PATH:-/tmp/etcddata}1:/var/etcddata1 - - ${ETCD_ROBUSTNESS_DATA_PATH:-/tmp/etcddata}2:/var/etcddata2 + - ${ETCD_ROBUSTNESS_PATH_PREFIX:-/tmp/etcddata}0:/var/etcddata0 + - ${ETCD_ROBUSTNESS_PATH_PREFIX:-/tmp/etcddata}1:/var/etcddata1 + - ${ETCD_ROBUSTNESS_PATH_PREFIX:-/tmp/etcddata}2:/var/etcddata2 - ${ETCD_ROBUSTNESS_REPORT_PATH:-/tmp/etcdreport}:/var/report diff --git a/tests/antithesis/test-template/Dockerfile b/tests/antithesis/test-template/Dockerfile index b3370ef3fed..4699c1a04d1 100644 --- a/tests/antithesis/test-template/Dockerfile +++ b/tests/antithesis/test-template/Dockerfile @@ -2,14 +2,13 @@ ARG GO_VERSION=1.24.6 ARG ARCH=amd64 FROM golang:$GO_VERSION AS build -ARG CFG_NODE_COUNT=3 WORKDIR /build COPY . . WORKDIR /build/tests -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 +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 FROM ubuntu:24.04 COPY --from=build /opt/ /opt/ diff --git a/tests/antithesis/test-template/entrypoint/main.go b/tests/antithesis/test-template/entrypoint/main.go index ab9e365b3dd..de8315be8cf 100644 --- a/tests/antithesis/test-template/entrypoint/main.go +++ b/tests/antithesis/test-template/entrypoint/main.go @@ -30,22 +30,18 @@ import ( // Sleep duration const SLEEP = 10 -var NodeCount = "3" - // CheckHealth checks health of all etcd nodes func CheckHealth() bool { - cfg := common.MakeConfig(NodeCount) - - nodeOptions := []string{"etcd0", "etcd1", "etcd2"}[:cfg.NodeCount] + hosts, _, _ := common.GetPaths() // iterate over each node and check health - for _, node := range nodeOptions { + for _, host := range hosts { cli, err := clientv3.New(clientv3.Config{ - Endpoints: []string{fmt.Sprintf("http://%s:2379", node)}, + Endpoints: []string{fmt.Sprintf("http://%s", host)}, DialTimeout: 5 * time.Second, }) if err != nil { - fmt.Printf("Client [entrypoint]: connection failed with %s\n", node) + fmt.Printf("Client [entrypoint]: connection failed with %s\n", host) fmt.Printf("Client [entrypoint]: error: %v\n", err) return false } @@ -60,12 +56,12 @@ func CheckHealth() bool { // fetch the key setting-up to confirm that the node is available _, err = cli.Get(context.Background(), "setting-up") if err != nil { - fmt.Printf("Client [entrypoint]: connection failed with %s\n", node) + fmt.Printf("Client [entrypoint]: connection failed with %s\n", host) fmt.Printf("Client [entrypoint]: error: %v\n", err) return false } - fmt.Printf("Client [entrypoint]: connection successful with %s\n", node) + fmt.Printf("Client [entrypoint]: connection successful with %s\n", host) } return true diff --git a/tests/antithesis/test-template/robustness/common/config.go b/tests/antithesis/test-template/robustness/common/config.go deleted file mode 100644 index 97a8ef39637..00000000000 --- a/tests/antithesis/test-template/robustness/common/config.go +++ /dev/null @@ -1,29 +0,0 @@ -// 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 492da9ea2a8..68b912cbf50 100644 --- a/tests/antithesis/test-template/robustness/common/path.go +++ b/tests/antithesis/test-template/robustness/common/path.go @@ -19,48 +19,52 @@ package common import ( "fmt" "os" + "strings" ) const ( - defaultetcd0 = "etcd0:2379" - defaultetcd1 = "etcd1:2379" - defaultetcd2 = "etcd2:2379" - // mounted by the client in docker compose - defaultetcdDataPath = "/var/etcddata%d" - defaultReportPath = "/var/report/" - - localetcd0 = "127.0.0.1:12379" - localetcd1 = "127.0.0.1:22379" - localetcd2 = "127.0.0.1:32379" - // used by default when running the client locally - defaultetcdLocalDataPath = "/tmp/etcddata%d" - localetcdDataPathEnv = "ETCD_ROBUSTNESS_DATA_PATH" - localReportPath = "report" + EtcdEndpointsEnv = "ETCD_ROBUSTNESS_ENDPOINTS" + EtcdDataPathEnv = "ETCD_ROBUSTNESS_DATA_PATHS" + ReportPathEnv = "ETCD_ROBUSTNESS_REPORT_PATH" ) -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 GetPaths() ([]string, map[string]string, string) { + endpointsEnv := validateEnvVar(EtcdEndpointsEnv) + dataPathEnv := validateEnvVar(EtcdDataPathEnv) + reportPath := validateEnvVar(ReportPathEnv) + + etcdEndpoints := strings.Split(endpointsEnv, ",") + paths := strings.Split(dataPathEnv, ",") -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" + if len(paths) != len(etcdEndpoints) { + panic(fmt.Sprintf("number of etcd endpoints (%d) and data paths (%d) do not match", len(etcdEndpoints), len(paths))) + } + + for i, endpoint := range etcdEndpoints { + if strings.TrimSpace(endpoint) == "" { + panic(fmt.Sprintf("etcd endpoint at index %d is empty", i)) + } + if strings.TrimSpace(paths[i]) == "" { + panic(fmt.Sprintf("data path at index %d is empty", i)) + } + // Trim whitespace from endpoints and paths + etcdEndpoints[i] = strings.TrimSpace(endpoint) + paths[i] = strings.TrimSpace(paths[i]) } - dataPaths = etcdDataPaths(etcdDataPath, cfg.NodeCount) - return hosts, reportPath, dataPaths -} -func etcdDataPaths(dir string, amount int) map[string]string { + // Build the data paths map dataPaths := make(map[string]string) - for i := range amount { - dataPaths[fmt.Sprintf("etcd%d", i)] = fmt.Sprintf(dir, i) + for i, etcd := range etcdEndpoints { + dataPaths[etcd] = paths[i] + } + + return etcdEndpoints, dataPaths, reportPath +} + +func validateEnvVar(envName string) string { + value := os.Getenv(envName) + if value == "" { + panic(fmt.Sprintf("required environment variable %s is not set", envName)) } - return dataPaths + return value } diff --git a/tests/antithesis/test-template/robustness/finally/main.go b/tests/antithesis/test-template/robustness/finally/main.go index da9b87e591f..0ac4c525167 100644 --- a/tests/antithesis/test-template/robustness/finally/main.go +++ b/tests/antithesis/test-template/robustness/finally/main.go @@ -17,7 +17,6 @@ package main import ( - "flag" "maps" "os" "path/filepath" @@ -36,18 +35,8 @@ 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() - - cfg := common.MakeConfig(NodeCount) - - _, reportPath, dirs := common.DefaultPaths(cfg) - if *local { - _, reportPath, dirs = common.LocalPaths(cfg) - } + _, dataPaths, reportPath := common.GetPaths() lg, err := zap.NewProduction() if err != nil { @@ -60,7 +49,7 @@ func main() { panic(err) } - result := validateReports(lg, dirs, reports, tf) + result := validateReports(lg, dataPaths, reports, tf) if err := result.Linearization.Visualize(lg, filepath.Join(reportPath, reportFileName)); err != nil { panic(err) } diff --git a/tests/antithesis/test-template/robustness/traffic/main.go b/tests/antithesis/test-template/robustness/traffic/main.go index 75424b92b89..acc8523c375 100644 --- a/tests/antithesis/test-template/robustness/traffic/main.go +++ b/tests/antithesis/test-template/robustness/traffic/main.go @@ -18,7 +18,6 @@ package main import ( "context" - "flag" "math/rand/v2" "os" "slices" @@ -55,19 +54,10 @@ 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() - - cfg := common.MakeConfig(NodeCount) - - hosts, reportPath, etcdetcdDataPaths := common.DefaultPaths(cfg) - if *local { - hosts, reportPath, etcdetcdDataPaths = common.LocalPaths(cfg) - } + hosts, dataPaths, reportPath := common.GetPaths() ctx := context.Background() baseTime := time.Now() @@ -80,7 +70,7 @@ func main() { choice := rand.IntN(len(traffics)) tf := traffics[choice] lg.Info("Traffic", zap.String("Type", trafficNames[choice])) - r := report.TestReport{Logger: lg, ServersDataPath: etcdetcdDataPaths, Traffic: &report.TrafficDetail{ExpectUniqueRevision: tf.ExpectUniqueRevision()}} + r := report.TestReport{Logger: lg, ServersDataPath: dataPaths, Traffic: &report.TrafficDetail{ExpectUniqueRevision: tf.ExpectUniqueRevision()}} defer func() { if err = r.Report(reportPath); err != nil { lg.Error("Failed to save traffic generation report", zap.Error(err))