From cd966a30446bf0246e0f6ad687cb24c3967cdd97 Mon Sep 17 00:00:00 2001 From: Stephan Behnke Date: Tue, 12 May 2026 12:15:17 -0700 Subject: [PATCH 1/5] Delegate mixedbrain devserver lifecycle to Omes Co-Authored-By: Claude Opus 4.7 (1M context) --- Makefile | 2 +- tests/mixedbrain/.gitignore | 2 + tests/mixedbrain/build_util.go | 91 +++++------- tests/mixedbrain/config_util.go | 212 --------------------------- tests/mixedbrain/go.mod | 42 ++++++ tests/mixedbrain/go.sum | 135 +++++++++++++++++ tests/mixedbrain/mixed_brain_test.go | 144 ++++++++++-------- tests/mixedbrain/server_util.go | 93 ++---------- 8 files changed, 314 insertions(+), 407 deletions(-) create mode 100644 tests/mixedbrain/.gitignore delete mode 100644 tests/mixedbrain/config_util.go create mode 100644 tests/mixedbrain/go.mod create mode 100644 tests/mixedbrain/go.sum diff --git a/Makefile b/Makefile index 7ea75e67c65..0c381d16e95 100644 --- a/Makefile +++ b/Makefile @@ -505,7 +505,7 @@ functional-with-fault-injection-test: clean-test-output mixed-brain-test: clean-test-output @printf $(COLOR) "Run mixed brain tests..." - @CGO_ENABLED=1 TEST_OUTPUT_ROOT=$(CURDIR)/$(TEST_OUTPUT_ROOT) go test -v $(MIXED_BRAIN_TEST_ROOT) $(COMPILED_TEST_ARGS) 2>&1 | tee -a test.log + @cd $(MIXED_BRAIN_TEST_ROOT) && CGO_ENABLED=1 TEST_OUTPUT_ROOT=$(CURDIR)/$(TEST_OUTPUT_ROOT) go test -v ./... $(COMPILED_TEST_ARGS) 2>&1 | tee -a $(CURDIR)/test.log @$(MAKE) verify-test-log verify-test-log: diff --git a/tests/mixedbrain/.gitignore b/tests/mixedbrain/.gitignore new file mode 100644 index 00000000000..c5d51fa8079 --- /dev/null +++ b/tests/mixedbrain/.gitignore @@ -0,0 +1,2 @@ +.devserver/ +.devserver.lock diff --git a/tests/mixedbrain/build_util.go b/tests/mixedbrain/build_util.go index eeded327b21..929a74c966b 100644 --- a/tests/mixedbrain/build_util.go +++ b/tests/mixedbrain/build_util.go @@ -17,9 +17,9 @@ import ( const ( retryTimeout = 30 * time.Second + omesModule = "github.com/temporalio/omes" + omesRepo = "https://" + omesModule + ".git" temporalRepo = "https://github.com/temporalio/temporal.git" - omesRepo = "https://github.com/temporalio/omes" - omesCommit = "8e4c1f54f3b0fb5e39d131f859c56fb2236395b1" ) func sourceRoot() string { @@ -27,31 +27,19 @@ func sourceRoot() string { return filepath.Join(filepath.Dir(filename), "..", "..") } -func cloneRepo(t *testing.T, url, destDir, ref string) { +// omesRef returns the sha of github.com/temporalio/omes as pinned in go.mod, +// queried via `go list -m` so go.mod stays the single source of truth. +// Pseudo-versions look like "v0.0.0-20260512170720-ab5a6ff22874"; we take the +// trailing 12-char sha. +func omesRef(t *testing.T) string { t.Helper() - t.Logf("Cloning %s at %s...", url, ref) - require.EventuallyWithT(t, func(collect *assert.CollectT) { - _ = os.RemoveAll(destDir) - out, err := exec.CommandContext(t.Context(), "git", "clone", "--filter=blob:none", url, destDir).CombinedOutput() - require.NoError(collect, err, "git clone failed:\n%s", out) - }, retryTimeout, 2*time.Second, "git clone "+filepath.Base(url)) - - out, err := exec.CommandContext(t.Context(), "git", "-C", destDir, "checkout", ref).CombinedOutput() - require.NoError(t, err, "git checkout %s failed:\n%s", ref, out) -} - -func buildServer(t *testing.T, srcDir, outputPath string) { - t.Helper() - t.Logf("Building server binary from %s...", srcDir) - cmd := exec.CommandContext(t.Context(), "go", - "build", - "-tags", "disable_grpc_modules", - "-o", outputPath, - "./cmd/server", - ) - cmd.Dir = srcDir - out, err := cmd.CombinedOutput() - require.NoError(t, err, "build server binary failed:\n%s", out) + out, err := exec.CommandContext(t.Context(), "go", "list", "-m", "-f", "{{.Version}}", omesModule).Output() + require.NoError(t, err, "go list -m %s", omesModule) + version := strings.TrimSpace(string(out)) + if i := strings.LastIndex(version, "-"); i >= 0 { + return version[i+1:] + } + return version } // resolveReleaseVersion returns the highest version for the previous minor. @@ -76,14 +64,10 @@ func resolveReleaseVersion(serverVersion string, tags []string) semver.Version { return best } -// downloadAndBuildReleaseServer finds the highest patch of the previous minor -// version, clones the repo at that tag, and builds the server binary. For -// example, if ServerVersion is 1.31.x, it will look for the highest 1.30.x -// tag. Falls back to pre-release tags (cloud versions) if no stable release -// exists yet. -func downloadAndBuildReleaseServer(t *testing.T, outputPath string) string { +// fetchPreviousMinorTag asks the temporal git remote for tags and resolves +// the latest patch of the previous minor relative to headers.ServerVersion. +func fetchPreviousMinorTag(t *testing.T) string { t.Helper() - t.Log("Resolving release tags...") var version semver.Version require.EventuallyWithT(t, func(collect *assert.CollectT) { @@ -101,30 +85,31 @@ func downloadAndBuildReleaseServer(t *testing.T, outputPath string) string { version = resolveReleaseVersion(headers.ServerVersion, tags) require.NotEqual(collect, semver.Version{}, version, "no tags found for previous minor") }, retryTimeout, 2*time.Second, "fetch release tags") - - tag := "v" + version.String() - repoDir := filepath.Join(filepath.Dir(outputPath), "temporal-release") - cloneRepo(t, temporalRepo, repoDir, tag) - - t.Log("Building release server binary...") - buildServer(t, repoDir, outputPath) - return tag + return "v" + version.String() } -func downloadAndBuildOmes(t *testing.T, workDir string) { +// downloadAndBuildOmes clones omes at omesRef into workDir/omes and builds its +// CLI into outputPath. We clone instead of going through the module cache so +// the build resolves omes's transitive deps independently of this module's +// go.sum (omes uses replace directives that block `go install`). +func downloadAndBuildOmes(t *testing.T, workDir, outputPath string) { t.Helper() repoDir := filepath.Join(workDir, "omes") - cloneRepo(t, omesRepo, repoDir, omesCommit) + ref := omesRef(t) + t.Logf("Cloning %s at %s...", omesRepo, ref) + require.EventuallyWithT(t, func(collect *assert.CollectT) { + _ = os.RemoveAll(repoDir) + out, err := exec.CommandContext(t.Context(), "git", "clone", "--filter=blob:none", omesRepo, repoDir).CombinedOutput() + require.NoError(collect, err, "git clone failed:\n%s", out) + }, retryTimeout, 2*time.Second, "git clone omes") + + out, err := exec.CommandContext(t.Context(), "git", "-C", repoDir, "checkout", ref).CombinedOutput() + require.NoError(t, err, "git checkout %s failed:\n%s", ref, out) - t.Log("Building Omes...") - omesBinary := filepath.Join(workDir, "omes-bin") - buildCmd := exec.CommandContext(t.Context(), "go", - "build", - "-o", omesBinary, - "./cmd", - ) - buildCmd.Dir = repoDir - out, err := buildCmd.CombinedOutput() - require.NoError(t, err, "build Omes failed:\n%s", out) + t.Logf("Building omes into %s...", outputPath) + cmd := exec.CommandContext(t.Context(), "go", "build", "-o", outputPath, "./cmd") + cmd.Dir = repoDir + out, err := cmd.CombinedOutput() + require.NoError(t, err, "build omes failed:\n%s", out) } diff --git a/tests/mixedbrain/config_util.go b/tests/mixedbrain/config_util.go deleted file mode 100644 index 2f05388afcf..00000000000 --- a/tests/mixedbrain/config_util.go +++ /dev/null @@ -1,212 +0,0 @@ -package mixedbrain - -import ( - "fmt" - "os" - "path/filepath" - "testing" - "time" - - "github.com/stretchr/testify/require" - "go.temporal.io/server/common/cluster" - "go.temporal.io/server/common/config" - "go.temporal.io/server/common/dynamicconfig" - "go.temporal.io/server/common/log" - "go.temporal.io/server/common/primitives" - "go.temporal.io/server/common/testing/freeport" - "gopkg.in/yaml.v3" -) - -var ( - // Fixed ports for CI to avoid exceeding the cluster_membership.rpc_port - // SMALLINT column (max 32767). Linux ephemeral ports start at 32768. - portSetA = newPortSet(7230) - portSetB = newPortSet(8240) -) - -type portSet struct { - FrontendGRPC int - FrontendMembership int - FrontendHTTP int - HistoryGRPC int - HistoryMembership int - MatchingGRPC int - MatchingMembership int - WorkerGRPC int - WorkerMembership int -} - -func newPortSet(base int) portSet { - return portSet{ - FrontendGRPC: base, - FrontendMembership: base + 1, - FrontendHTTP: base + 2, - HistoryGRPC: base + 3, - HistoryMembership: base + 4, - MatchingGRPC: base + 5, - MatchingMembership: base + 6, - WorkerGRPC: base + 7, - WorkerMembership: base + 8, - } -} - -func newRandPortSet() portSet { - return portSet{ - FrontendGRPC: freeport.MustGetFreePort(), - FrontendMembership: freeport.MustGetFreePort(), - FrontendHTTP: freeport.MustGetFreePort(), - HistoryGRPC: freeport.MustGetFreePort(), - HistoryMembership: freeport.MustGetFreePort(), - MatchingGRPC: freeport.MustGetFreePort(), - MatchingMembership: freeport.MustGetFreePort(), - WorkerGRPC: freeport.MustGetFreePort(), - WorkerMembership: freeport.MustGetFreePort(), - } -} - -func (p portSet) frontendAddr() string { - return fmt.Sprintf("127.0.0.1:%d", p.FrontendGRPC) -} - -func (p portSet) membershipPorts() []int { - return []int{ - p.FrontendMembership, - p.HistoryMembership, - p.MatchingMembership, - p.WorkerMembership, - } -} - -func generateConfig(t *testing.T, tmpDir string, ports portSet, activePorts portSet) string { - t.Helper() - - configDir := filepath.Join(tmpDir, fmt.Sprintf("config-%d", ports.FrontendGRPC)) - require.NoError(t, os.MkdirAll(configDir, 0755)) - - dynConfigPath := filepath.Join(sourceRoot(), "config", "dynamicconfig", "development-sql.yaml") - - driver := os.Getenv("PERSISTENCE_DRIVER") - if driver == "" { - driver = "sqlite" - } - - var dataStores map[string]config.DataStore - switch driver { - case "sqlite": - newStore := func(dbName string) config.DataStore { - return config.DataStore{ - SQL: &config.SQL{ - PluginName: "sqlite", - DatabaseName: filepath.Join(tmpDir, dbName), - ConnectAddr: "localhost", - ConnectProtocol: "tcp", - ConnectAttributes: map[string]string{ - "cache": "private", - "setup": "true", - "journal_mode": "wal", - "synchronous": "2", - "busy_timeout": "10000", - }, - MaxConns: 1, - MaxIdleConns: 1, - }, - } - } - dataStores = map[string]config.DataStore{ - "default": newStore("temporal_default.db"), - "visibility": newStore("temporal_visibility.db"), - } - case "postgres12", "postgres12_pgx": - newStore := func(dbName string) config.DataStore { - return config.DataStore{ - SQL: &config.SQL{ - PluginName: driver, - DatabaseName: dbName, - ConnectAddr: "127.0.0.1:5432", - ConnectProtocol: "tcp", - User: "temporal", - Password: "temporal", - MaxConns: 20, - MaxIdleConns: 20, - MaxConnLifetime: time.Hour, - }, - } - } - dataStores = map[string]config.DataStore{ - "default": newStore("temporal"), - "visibility": newStore("temporal_visibility"), - } - default: - t.Fatalf("unsupported persistence driver: %s", driver) - } - - cfg := config.Config{ - Log: log.Config{ - Stdout: true, - Level: "info", - }, - Persistence: config.Persistence{ - DefaultStore: "default", - VisibilityStore: "visibility", - NumHistoryShards: 4, - DataStores: dataStores, - }, - Global: config.Global{ - Membership: config.Membership{ - MaxJoinDuration: 30 * time.Second, - BroadcastAddress: "127.0.0.1", - }, - }, - Services: map[string]config.Service{ - string(primitives.FrontendService): { - RPC: config.RPC{ - GRPCPort: ports.FrontendGRPC, - MembershipPort: ports.FrontendMembership, - BindOnLocalHost: true, - HTTPPort: ports.FrontendHTTP, - }}, - string(primitives.MatchingService): { - RPC: config.RPC{ - GRPCPort: ports.MatchingGRPC, - MembershipPort: ports.MatchingMembership, - BindOnLocalHost: true, - }}, - string(primitives.HistoryService): { - RPC: config.RPC{ - GRPCPort: ports.HistoryGRPC, - MembershipPort: ports.HistoryMembership, - BindOnLocalHost: true, - }}, - string(primitives.WorkerService): { - RPC: config.RPC{ - GRPCPort: ports.WorkerGRPC, - MembershipPort: ports.WorkerMembership, - BindOnLocalHost: true, - }}, - }, - ClusterMetadata: &cluster.Config{ - FailoverVersionIncrement: 10, - MasterClusterName: "active", - CurrentClusterName: "active", - ClusterInformation: map[string]cluster.ClusterInformation{ - "active": { - Enabled: true, - InitialFailoverVersion: 1, - RPCAddress: fmt.Sprintf("localhost:%d", activePorts.FrontendGRPC), - HTTPAddress: fmt.Sprintf("localhost:%d", activePorts.FrontendHTTP), - }, - }, - }, - DynamicConfigClient: &dynamicconfig.FileBasedClientConfig{ - Filepath: dynConfigPath, - PollInterval: 10 * time.Second, - }, - } - - data, err := yaml.Marshal(cfg) - require.NoError(t, err) - - err = os.WriteFile(filepath.Join(configDir, "development.yaml"), data, 0644) - require.NoError(t, err) - return configDir -} diff --git a/tests/mixedbrain/go.mod b/tests/mixedbrain/go.mod new file mode 100644 index 00000000000..eca095b58db --- /dev/null +++ b/tests/mixedbrain/go.mod @@ -0,0 +1,42 @@ +module go.temporal.io/server/tests/mixedbrain + +go 1.26.2 + +require ( + github.com/blang/semver/v4 v4.0.0 + github.com/stretchr/testify v1.11.1 + github.com/temporalio/omes v0.0.0-20260512200559-fcd36ea5dfe3 + go.temporal.io/api v1.62.12-0.20260430203359-15c391664683 + go.temporal.io/server v0.0.0-00010101000000-000000000000 + go.uber.org/zap v1.27.1 + google.golang.org/grpc v1.80.0 + google.golang.org/protobuf v1.36.11 +) + +require ( + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/mock v1.6.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect + github.com/nexus-rpc/sdk-go v0.6.0 // indirect + github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect + github.com/robfig/cron v1.2.0 // indirect + github.com/stretchr/objx v0.5.3 // indirect + go.temporal.io/sdk v1.43.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/net v0.53.0 // indirect + golang.org/x/sync v0.20.0 // indirect + golang.org/x/sys v0.43.0 // indirect + golang.org/x/text v0.36.0 // indirect + golang.org/x/time v0.15.0 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260420184626-e10c466a9529 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +replace go.temporal.io/server => ../.. + +replace go.temporal.io/sdk => go.temporal.io/sdk v1.42.0 diff --git a/tests/mixedbrain/go.sum b/tests/mixedbrain/go.sum new file mode 100644 index 00000000000..b69ffbae248 --- /dev/null +++ b/tests/mixedbrain/go.sum @@ -0,0 +1,135 @@ +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw= +github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/nexus-rpc/sdk-go v0.6.0 h1:QRgnP2zTbxEbiyWG/aXH8uSC5LV/Mg1fqb19jb4DBlo= +github.com/nexus-rpc/sdk-go v0.6.0/go.mod h1:FHdPfVQwRuJFZFTF0Y2GOAxCrbIBNrcPna9slkGKPYk= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= +github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= +github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= +github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/temporalio/omes v0.0.0-20260512170720-ab5a6ff22874 h1:rXLS3GZTJg8MPlSOqxLtO6zfMrRxNKA74EbrbnZYmqY= +github.com/temporalio/omes v0.0.0-20260512170720-ab5a6ff22874/go.mod h1:5rwdMMhBm+IFEuy0XJWXztSHHYprkCvg7PycnsT7Opg= +github.com/temporalio/omes v0.0.0-20260512180205-53dab883083c h1:UYldhRVsV+mei34/xdhDfl9iISipAf2XY3Fpi17DXxw= +github.com/temporalio/omes v0.0.0-20260512180205-53dab883083c/go.mod h1:5rwdMMhBm+IFEuy0XJWXztSHHYprkCvg7PycnsT7Opg= +github.com/temporalio/omes v0.0.0-20260512194906-7aeffca4c8ce h1:qBuK7Nws1ITUY+iniq6J+d8sFul0w4HtSmmVKFYHntI= +github.com/temporalio/omes v0.0.0-20260512194906-7aeffca4c8ce/go.mod h1:6I67aD563CtAP+jpY/LQtlmNHpy6LTlfug7x0wS66pc= +github.com/temporalio/omes v0.0.0-20260512200559-fcd36ea5dfe3 h1:0+pN670Iz2V+LhiibQDUUyL3SAYVf86pkbb8Igdn+1M= +github.com/temporalio/omes v0.0.0-20260512200559-fcd36ea5dfe3/go.mod h1:6I67aD563CtAP+jpY/LQtlmNHpy6LTlfug7x0wS66pc= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= +go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= +go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= +go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= +go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= +go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= +go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= +go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= +go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= +go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= +go.temporal.io/api v1.62.12-0.20260430203359-15c391664683 h1:GtwQjX9hN0pRjuneBpl/xvcu9Xl9llAt4GjKrlpP0sg= +go.temporal.io/api v1.62.12-0.20260430203359-15c391664683/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= +go.temporal.io/sdk v1.42.0 h1:2Zyrj1PZFd1xQVrrXF6RlE1nHZzZRuWfSyC2TqT3ri8= +go.temporal.io/sdk v1.42.0/go.mod h1:Xp4TMHsie6kdw0lc0Ae4o8vktze5HZXBynF2DkiXcrQ= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= +golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= +golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/genproto/googleapis/api v0.0.0-20260420184626-e10c466a9529 h1:zUWMZsvo/IJcD1t6MNCPO/azZTwz0TvwCBqr5aifoVY= +google.golang.org/genproto/googleapis/api v0.0.0-20260420184626-e10c466a9529/go.mod h1:a5OGAgyRr4lqco7AG9hQM9Fwh0N2ZV4grR0eXFEsXQg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 h1:XF8+t6QQiS0o9ArVan/HW8Q7cycNPGsJf6GA2nXxYAg= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.80.0 h1:Xr6m2WmWZLETvUNvIUmeD5OAagMw3FiKmMlTdViWsHM= +google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07bwF4= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/tests/mixedbrain/mixed_brain_test.go b/tests/mixedbrain/mixed_brain_test.go index 1c5b5d8ab3e..b1ef95f3ccd 100644 --- a/tests/mixedbrain/mixed_brain_test.go +++ b/tests/mixedbrain/mixed_brain_test.go @@ -13,6 +13,9 @@ import ( "time" "github.com/stretchr/testify/require" + "github.com/temporalio/omes/devserver" + "go.temporal.io/server/common/headers" + "go.uber.org/zap" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" ) @@ -38,81 +41,87 @@ func logDir(t *testing.T) string { return dir } -// TestMixedBrain starts two servers in parallel, one using the current branch's binary -// and the other using the latest release binary. It then runs Omes throughput_stress -// to ensure that the mixed brain works correctly. -// Uses SQLite locally; and a dedicated database in CI for better concurrency. +// TestMixedBrain starts two servers in parallel — one built from the current +// branch's source tree and the other from the latest release tag of the +// previous minor — joined into a single logical cluster, and runs the Omes +// throughput_stress scenario through a round-robin TCP proxy to exercise both. +// Server lifecycle (clone + build + config + process) is delegated to +// github.com/temporalio/omes/devserver. func TestMixedBrain(t *testing.T) { tmpDir := t.TempDir() logRoot := logDir(t) - currentBinary := filepath.Join(tmpDir, "temporal-server-current") - releaseBinary := filepath.Join(tmpDir, "temporal-server-release") omesBinary := filepath.Join(tmpDir, "omes-bin") + var releaseTag string t.Run("setup", func(t *testing.T) { - t.Run("build current server", func(t *testing.T) { + t.Run("resolve release tag", func(t *testing.T) { t.Parallel() - buildServer(t, sourceRoot(), currentBinary) + releaseTag = fetchPreviousMinorTag(t) + t.Logf("Release tag: %s (current server version: %s)", releaseTag, headers.ServerVersion) }) - t.Run("download and build release server", func(t *testing.T) { + t.Run("build omes binary", func(t *testing.T) { t.Parallel() - downloadAndBuildReleaseServer(t, releaseBinary) - }) - t.Run("download and build Omes", func(t *testing.T) { - t.Parallel() - downloadAndBuildOmes(t, tmpDir) + downloadAndBuildOmes(t, tmpDir, omesBinary) }) }) if t.Failed() { return } - var portsCurrent, portsRelease portSet - if os.Getenv("CI") != "" { - portsCurrent = portSetA - portsRelease = portSetB - } else { - portsCurrent = newRandPortSet() - portsRelease = newRandPortSet() + persistence := persistenceFromEnv() + // postgres' cluster_membership.rpc_port is SMALLINT (max 32767); ephemeral + // ports overflow. Pin two non-overlapping low bases when not on sqlite. + currentBase, releaseBase := 0, 0 + if persistence.Driver != "" && persistence.Driver != "sqlite" { + currentBase, releaseBase = 7230, 7240 } - configCurrent := generateConfig(t, tmpDir, portsCurrent, portsCurrent) - configRelease := generateConfig(t, tmpDir, portsRelease, portsCurrent) - - var procCurrent, procRelease *serverProcess - var conn *grpc.ClientConn - var proxy *frontendProxy - runID := fmt.Sprintf("mixed-brain-%d", time.Now().Unix()) - nexusEndpoint := "mixed-brain-nexus" - - t.Run("start current server", func(st *testing.T) { - // Server processes use the parent t so their context survives this sub-test. - procCurrent = startServerProcess(t, "current", currentBinary, configCurrent, filepath.Join(logRoot, "mixedbrain_process-current.log")) - - var err error - conn, err = grpc.NewClient(portsCurrent.frontendAddr(), grpc.WithTransportCredentials(insecure.NewCredentials())) - require.NoError(st, err) - - // This ensures the current server is fully booted before starting the release server. - registerDefaultNamespace(st, conn) + // Start the current-source server first so the release server can target + // its frontend in cluster metadata. + currentLogger, currentLog := serverLogger(t, "current", logRoot) + defer currentLog.Close() + currentSrv, err := devserver.Start(t.Context(), devserver.Options{ + SourceDir: sourceRoot(), + PortBase: currentBase, + Persistence: persistence, + Stdout: currentLog, + Stderr: currentLog, + Logger: currentLogger, }) - if t.Failed() { - return - } - t.Cleanup(procCurrent.stop) + require.NoError(t, err, "start current server") + t.Cleanup(func() { _ = currentSrv.Stop() }) + + conn, err := grpc.NewClient(currentSrv.FrontendHostPort(), grpc.WithTransportCredentials(insecure.NewCredentials())) + require.NoError(t, err) defer func() { _ = conn.Close() }() - t.Run("start release server", func(_ *testing.T) { - procRelease = startServerProcess(t, "release", releaseBinary, configRelease, filepath.Join(logRoot, "mixedbrain_process-release.log")) + // devserver registers "default" itself, but the release server will need + // to see it too once it joins — the helper waits for AlreadyExists. + registerDefaultNamespace(t, conn) + + releaseLogger, releaseLog := serverLogger(t, "release", logRoot) + defer releaseLog.Close() + releaseSrv, err := devserver.Start(t.Context(), devserver.Options{ + Ref: releaseTag, + PortBase: releaseBase, + Persistence: persistence, + ClusterEndpoint: devserver.ClusterEndpoint{ + RPCAddress: currentSrv.FrontendHostPort(), + }, + Stdout: releaseLog, + Stderr: releaseLog, + Logger: releaseLogger, }) - if t.Failed() { - return - } - t.Cleanup(procRelease.stop) + require.NoError(t, err, "start release server") + t.Cleanup(func() { _ = releaseSrv.Stop() }) + + runID := fmt.Sprintf("mixed-brain-%d", time.Now().Unix()) + nexusEndpoint := "mixed-brain-nexus" t.Run("form cluster", func(st *testing.T) { - waitForClusterFormation(st, conn, 90*time.Second, portsCurrent, portsRelease) + // Two physical servers, each running frontend/history/matching/worker. + waitForClusterFormation(st, conn, 90*time.Second, 2) }) if t.Failed() { return @@ -121,18 +130,10 @@ func TestMixedBrain(t *testing.T) { t.Run("run omes", func(st *testing.T) { createNexusEndpoint(st, conn, nexusEndpoint, "default", "omes-"+runID) - proxy = startFrontendProxy(st, portsCurrent.frontendAddr(), portsRelease.frontendAddr()) + proxy := startFrontendProxy(st, currentSrv.FrontendHostPort(), releaseSrv.FrontendHostPort()) + st.Cleanup(proxy.stop) runOmes(st, omesBinary, proxy.addr(), filepath.Join(logRoot, "mixedbrain_omes.log"), testDuration(), runID, nexusEndpoint) - }) - if t.Failed() { - return - } - t.Cleanup(proxy.stop) - - t.Run("verify", func(st *testing.T) { - procCurrent.requireAlive(st) - procRelease.requireAlive(st) for i, backend := range []string{"current", "release"} { count := proxy.connCount[i].Load() @@ -185,3 +186,24 @@ func runOmes(t *testing.T, binary, serverAddr, logPath string, duration time.Dur return } } + +func persistenceFromEnv() devserver.PersistenceOptions { + driver := os.Getenv("PERSISTENCE_DRIVER") + if driver == "" || driver == "sqlite" { + return devserver.PersistenceOptions{} // default sqlite + } + return devserver.PersistenceOptions{ + Driver: driver, + ConnectAddr: "127.0.0.1:5432", + User: "temporal", + Password: "temporal", + } +} + +func serverLogger(t *testing.T, name, logRoot string) (*zap.SugaredLogger, *os.File) { + t.Helper() + logPath := filepath.Join(logRoot, fmt.Sprintf("mixedbrain_process-%s.log", name)) + f, err := os.Create(logPath) + require.NoError(t, err) + return zap.NewNop().Sugar().With("server", name), f +} diff --git a/tests/mixedbrain/server_util.go b/tests/mixedbrain/server_util.go index 02314c82926..a73e8b63f8e 100644 --- a/tests/mixedbrain/server_util.go +++ b/tests/mixedbrain/server_util.go @@ -1,12 +1,8 @@ package mixedbrain import ( - "context" "net" - "os" - "os/exec" "strconv" - "syscall" "testing" "time" @@ -21,66 +17,6 @@ import ( "google.golang.org/protobuf/types/known/durationpb" ) -type serverProcess struct { - name string - cmd *exec.Cmd - cancel context.CancelFunc - logFile *os.File - logPath string - done chan error -} - -func startServerProcess(t *testing.T, name, binary, configDir, logPath string) *serverProcess { - t.Helper() - t.Logf("Starting %s: %s", name, binary) - - logFile, err := os.Create(logPath) - require.NoError(t, err) - - ctx, cancel := context.WithCancel(t.Context()) - cmd := exec.CommandContext(ctx, binary, - "--root", "/", - "--config", configDir, - "--allow-no-auth", - "start", - ) - cmd.Stdout = logFile - cmd.Stderr = logFile - cmd.Dir = sourceRoot() - cmd.Cancel = func() error { return cmd.Process.Signal(syscall.SIGTERM) } - cmd.WaitDelay = 15 * time.Second - - require.NoError(t, cmd.Start()) - - done := make(chan error, 1) - go func() { done <- cmd.Wait() }() - - return &serverProcess{ - name: name, - cmd: cmd, - cancel: cancel, - logFile: logFile, - logPath: logPath, - done: done, - } -} - -func (p *serverProcess) stop() { - p.cancel() - <-p.done - _ = p.logFile.Close() -} - -func (p *serverProcess) requireAlive(t *testing.T) { - t.Helper() - select { - case err := <-p.done: - p.done <- err // put back so stop() doesn't deadlock - t.Fatalf("%s exited unexpectedly: %v (logs: %s)", p.name, err, p.logPath) - default: - } -} - func registerDefaultNamespace(t *testing.T, conn *grpc.ClientConn) { t.Helper() @@ -127,9 +63,10 @@ func createNexusEndpoint(t *testing.T, conn *grpc.ClientConn, endpointName, name } // waitForClusterFormation waits until the server's reachable members include -// all membership ports from all provided port sets, confirming the servers -// discovered each other. Reachable members use raw ringpop addresses (membership ports). -func waitForClusterFormation(t *testing.T, conn *grpc.ClientConn, timeout time.Duration, portSets ...portSet) { +// expected membership ports, confirming the servers discovered each other. +// We can't enumerate ports per service (devserver chooses them dynamically), +// so we just wait until both servers see at least minMembers reachable members. +func waitForClusterFormation(t *testing.T, conn *grpc.ClientConn, timeout time.Duration, minMembers int) { t.Helper() client := adminservice.NewAdminServiceClient(conn) @@ -144,26 +81,22 @@ func waitForClusterFormation(t *testing.T, conn *grpc.ClientConn, timeout time.D return false } - seen := map[int]bool{} + // Count unique reachable members by host:port to avoid duplicate + // service-level entries. + seen := map[string]bool{} for _, member := range membership.GetReachableMembers() { - _, portStr, err := net.SplitHostPort(member) + host, portStr, err := net.SplitHostPort(member) if err != nil { continue } - port, err := strconv.Atoi(portStr) - if err != nil { + if _, err := strconv.Atoi(portStr); err != nil { continue } - seen[port] = true + seen[host+":"+portStr] = true } - - for _, ps := range portSets { - for _, port := range ps.membershipPorts() { - if !seen[port] { - t.Logf("Waiting for cluster formation: port %d not yet visible", port) - return false - } - } + if len(seen) < minMembers { + t.Logf("Waiting for cluster formation: %d/%d members visible", len(seen), minMembers) + return false } return true }, timeout, time.Second, "cluster did not form within %v", timeout) From af5f3949d8fe50fe377ca93885c84c8580311b2e Mon Sep 17 00:00:00 2001 From: Stephan Behnke Date: Thu, 28 May 2026 13:18:52 -0700 Subject: [PATCH 2/5] Update mixedbrain to latest Omes devserver --- tests/mixedbrain/build_util.go | 2 +- tests/mixedbrain/go.mod | 19 ++----- tests/mixedbrain/go.sum | 76 +++------------------------- tests/mixedbrain/mixed_brain_test.go | 19 ++----- 4 files changed, 17 insertions(+), 99 deletions(-) diff --git a/tests/mixedbrain/build_util.go b/tests/mixedbrain/build_util.go index 929a74c966b..04d21379d43 100644 --- a/tests/mixedbrain/build_util.go +++ b/tests/mixedbrain/build_util.go @@ -110,6 +110,6 @@ func downloadAndBuildOmes(t *testing.T, workDir, outputPath string) { t.Logf("Building omes into %s...", outputPath) cmd := exec.CommandContext(t.Context(), "go", "build", "-o", outputPath, "./cmd") cmd.Dir = repoDir - out, err := cmd.CombinedOutput() + out, err = cmd.CombinedOutput() require.NoError(t, err, "build omes failed:\n%s", out) } diff --git a/tests/mixedbrain/go.mod b/tests/mixedbrain/go.mod index eca095b58db..3e9acd78970 100644 --- a/tests/mixedbrain/go.mod +++ b/tests/mixedbrain/go.mod @@ -1,12 +1,12 @@ module go.temporal.io/server/tests/mixedbrain -go 1.26.2 +go 1.26.3 require ( github.com/blang/semver/v4 v4.0.0 github.com/stretchr/testify v1.11.1 - github.com/temporalio/omes v0.0.0-20260512200559-fcd36ea5dfe3 - go.temporal.io/api v1.62.12-0.20260430203359-15c391664683 + github.com/temporalio/omes v0.0.0-20260526155557-883bc870ab0c + go.temporal.io/api v1.62.13-0.20260522153111-260c04482807 go.temporal.io/server v0.0.0-00010101000000-000000000000 go.uber.org/zap v1.27.1 google.golang.org/grpc v1.80.0 @@ -15,23 +15,14 @@ require ( require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/mock v1.6.0 // indirect - github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 // indirect + github.com/gofrs/flock v0.13.0 // indirect github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 // indirect - github.com/nexus-rpc/sdk-go v0.6.0 // indirect + github.com/nexus-rpc/nexus-proto-annotations v0.1.0 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/robfig/cron v1.2.0 // indirect - github.com/stretchr/objx v0.5.3 // indirect - go.temporal.io/sdk v1.43.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/net v0.53.0 // indirect - golang.org/x/sync v0.20.0 // indirect golang.org/x/sys v0.43.0 // indirect golang.org/x/text v0.36.0 // indirect - golang.org/x/time v0.15.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260420184626-e10c466a9529 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260420184626-e10c466a9529 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/tests/mixedbrain/go.sum b/tests/mixedbrain/go.sum index b69ffbae248..7f81b153533 100644 --- a/tests/mixedbrain/go.sum +++ b/tests/mixedbrain/go.sum @@ -4,55 +4,34 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a h1:yDWHCSQ40h88yih2JAcL6Ls/kVkSE8GFACTGVnMPruw= -github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a/go.mod h1:7Ga40egUymuWXxAe151lTNnCv97MddSOVsjpPPkityA= github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= +github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3 h1:B+8ClL/kCQkRiU82d9xajRPKYMrB7E0MbtzWVi1K4ns= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.3/go.mod h1:NbCUVmiS4foBGBHOYlCT25+YmGpJ32dZPi75pGEUpj4= github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0 h1:5VipnvEpbqr2gA2VbM+nYVbkIF28c5ZQfqCBQ5g2xfk= github.com/grpc-ecosystem/grpc-gateway/v2 v2.29.0/go.mod h1:Hyl3n6Twe1hvtd9XUXDec4pTvgMSEixRuQKPTMH2bNs= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/nexus-rpc/sdk-go v0.6.0 h1:QRgnP2zTbxEbiyWG/aXH8uSC5LV/Mg1fqb19jb4DBlo= -github.com/nexus-rpc/sdk-go v0.6.0/go.mod h1:FHdPfVQwRuJFZFTF0Y2GOAxCrbIBNrcPna9slkGKPYk= +github.com/nexus-rpc/nexus-proto-annotations v0.1.0 h1:2fELd+9sqUtNu6Fg//pw8YFsxOvp8vZ8hfP0nHhNI80= +github.com/nexus-rpc/nexus-proto-annotations v0.1.0/go.mod h1:n3UjF1bPCW8llR8tHvbxJ+27yPWrhpo8w/Yg1IOuY0Y= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ= -github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= -github.com/stretchr/objx v0.5.3 h1:jmXUvGomnU1o3W/V5h2VEradbpJDwGrzugQQvL0POH4= -github.com/stretchr/objx v0.5.3/go.mod h1:rDQraq+vQZU7Fde9LOZLr8Tax6zZvy4kuNKF+QYS+U0= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/temporalio/omes v0.0.0-20260512170720-ab5a6ff22874 h1:rXLS3GZTJg8MPlSOqxLtO6zfMrRxNKA74EbrbnZYmqY= -github.com/temporalio/omes v0.0.0-20260512170720-ab5a6ff22874/go.mod h1:5rwdMMhBm+IFEuy0XJWXztSHHYprkCvg7PycnsT7Opg= -github.com/temporalio/omes v0.0.0-20260512180205-53dab883083c h1:UYldhRVsV+mei34/xdhDfl9iISipAf2XY3Fpi17DXxw= -github.com/temporalio/omes v0.0.0-20260512180205-53dab883083c/go.mod h1:5rwdMMhBm+IFEuy0XJWXztSHHYprkCvg7PycnsT7Opg= -github.com/temporalio/omes v0.0.0-20260512194906-7aeffca4c8ce h1:qBuK7Nws1ITUY+iniq6J+d8sFul0w4HtSmmVKFYHntI= -github.com/temporalio/omes v0.0.0-20260512194906-7aeffca4c8ce/go.mod h1:6I67aD563CtAP+jpY/LQtlmNHpy6LTlfug7x0wS66pc= -github.com/temporalio/omes v0.0.0-20260512200559-fcd36ea5dfe3 h1:0+pN670Iz2V+LhiibQDUUyL3SAYVf86pkbb8Igdn+1M= -github.com/temporalio/omes v0.0.0-20260512200559-fcd36ea5dfe3/go.mod h1:6I67aD563CtAP+jpY/LQtlmNHpy6LTlfug7x0wS66pc= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/temporalio/omes v0.0.0-20260526155557-883bc870ab0c h1:4BLvxFccAC1QGBc3oO6cPBJ5hlXuTVGP/HnLsfkRKFA= +github.com/temporalio/omes v0.0.0-20260526155557-883bc870ab0c/go.mod h1:ruo0wxNH1LFUvJ5pcPzf2r3t56X9Joq9lW0/l0efDpk= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= @@ -65,59 +44,20 @@ go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfC go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= -go.temporal.io/api v1.62.12-0.20260430203359-15c391664683 h1:GtwQjX9hN0pRjuneBpl/xvcu9Xl9llAt4GjKrlpP0sg= -go.temporal.io/api v1.62.12-0.20260430203359-15c391664683/go.mod h1:iaxoP/9OXMJcQkETTECfwYq4cw/bj4nwov8b3ZLVnXM= -go.temporal.io/sdk v1.42.0 h1:2Zyrj1PZFd1xQVrrXF6RlE1nHZzZRuWfSyC2TqT3ri8= -go.temporal.io/sdk v1.42.0/go.mod h1:Xp4TMHsie6kdw0lc0Ae4o8vktze5HZXBynF2DkiXcrQ= +go.temporal.io/api v1.62.13-0.20260522153111-260c04482807 h1:/w/PvtpSFE7WcBOxHgC1MpZ8SC8GVMjsGK+pstKnYFA= +go.temporal.io/api v1.62.13-0.20260522153111-260c04482807/go.mod h1:0k75tRljEuELWGeXjEZZO7zYqBln4+1FrG6+IMOMy7Q= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4= -golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= -golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= -golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= google.golang.org/genproto/googleapis/api v0.0.0-20260420184626-e10c466a9529 h1:zUWMZsvo/IJcD1t6MNCPO/azZTwz0TvwCBqr5aifoVY= diff --git a/tests/mixedbrain/mixed_brain_test.go b/tests/mixedbrain/mixed_brain_test.go index b1ef95f3ccd..6aee95e5187 100644 --- a/tests/mixedbrain/mixed_brain_test.go +++ b/tests/mixedbrain/mixed_brain_test.go @@ -70,12 +70,6 @@ func TestMixedBrain(t *testing.T) { } persistence := persistenceFromEnv() - // postgres' cluster_membership.rpc_port is SMALLINT (max 32767); ephemeral - // ports overflow. Pin two non-overlapping low bases when not on sqlite. - currentBase, releaseBase := 0, 0 - if persistence.Driver != "" && persistence.Driver != "sqlite" { - currentBase, releaseBase = 7230, 7240 - } // Start the current-source server first so the release server can target // its frontend in cluster metadata. @@ -83,10 +77,8 @@ func TestMixedBrain(t *testing.T) { defer currentLog.Close() currentSrv, err := devserver.Start(t.Context(), devserver.Options{ SourceDir: sourceRoot(), - PortBase: currentBase, Persistence: persistence, - Stdout: currentLog, - Stderr: currentLog, + Output: currentLog, Logger: currentLogger, }) require.NoError(t, err, "start current server") @@ -104,13 +96,11 @@ func TestMixedBrain(t *testing.T) { defer releaseLog.Close() releaseSrv, err := devserver.Start(t.Context(), devserver.Options{ Ref: releaseTag, - PortBase: releaseBase, Persistence: persistence, ClusterEndpoint: devserver.ClusterEndpoint{ RPCAddress: currentSrv.FrontendHostPort(), }, - Stdout: releaseLog, - Stderr: releaseLog, + Output: releaseLog, Logger: releaseLogger, }) require.NoError(t, err, "start release server") @@ -193,10 +183,7 @@ func persistenceFromEnv() devserver.PersistenceOptions { return devserver.PersistenceOptions{} // default sqlite } return devserver.PersistenceOptions{ - Driver: driver, - ConnectAddr: "127.0.0.1:5432", - User: "temporal", - Password: "temporal", + Driver: driver, } } From 1fff53118a4b91d4ef195711a6b2e51459021600 Mon Sep 17 00:00:00 2001 From: Stephan Behnke Date: Thu, 28 May 2026 13:23:24 -0700 Subject: [PATCH 3/5] Inline mixedbrain persistence options --- tests/mixedbrain/mixed_brain_test.go | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/tests/mixedbrain/mixed_brain_test.go b/tests/mixedbrain/mixed_brain_test.go index 6aee95e5187..afdfbb159d8 100644 --- a/tests/mixedbrain/mixed_brain_test.go +++ b/tests/mixedbrain/mixed_brain_test.go @@ -69,7 +69,10 @@ func TestMixedBrain(t *testing.T) { return } - persistence := persistenceFromEnv() + persistence := devserver.PersistenceOptions{} + if driver := os.Getenv("PERSISTENCE_DRIVER"); driver != "" && driver != "sqlite" { + persistence.Driver = driver + } // Start the current-source server first so the release server can target // its frontend in cluster metadata. @@ -177,16 +180,6 @@ func runOmes(t *testing.T, binary, serverAddr, logPath string, duration time.Dur } } -func persistenceFromEnv() devserver.PersistenceOptions { - driver := os.Getenv("PERSISTENCE_DRIVER") - if driver == "" || driver == "sqlite" { - return devserver.PersistenceOptions{} // default sqlite - } - return devserver.PersistenceOptions{ - Driver: driver, - } -} - func serverLogger(t *testing.T, name, logRoot string) (*zap.SugaredLogger, *os.File) { t.Helper() logPath := filepath.Join(logRoot, fmt.Sprintf("mixedbrain_process-%s.log", name)) From 92e2d2f6c4278bb823e5aad180f514f30f19e38c Mon Sep 17 00:00:00 2001 From: Stephan Behnke Date: Thu, 28 May 2026 13:34:52 -0700 Subject: [PATCH 4/5] Use Omes devserver ports in mixedbrain --- tests/mixedbrain/go.mod | 2 +- tests/mixedbrain/go.sum | 4 ++-- tests/mixedbrain/mixed_brain_test.go | 3 +-- tests/mixedbrain/server_util.go | 33 +++++----------------------- 4 files changed, 10 insertions(+), 32 deletions(-) diff --git a/tests/mixedbrain/go.mod b/tests/mixedbrain/go.mod index 3e9acd78970..439262de356 100644 --- a/tests/mixedbrain/go.mod +++ b/tests/mixedbrain/go.mod @@ -5,7 +5,7 @@ go 1.26.3 require ( github.com/blang/semver/v4 v4.0.0 github.com/stretchr/testify v1.11.1 - github.com/temporalio/omes v0.0.0-20260526155557-883bc870ab0c + github.com/temporalio/omes v0.0.0-20260528212322-14460bcc246f go.temporal.io/api v1.62.13-0.20260522153111-260c04482807 go.temporal.io/server v0.0.0-00010101000000-000000000000 go.uber.org/zap v1.27.1 diff --git a/tests/mixedbrain/go.sum b/tests/mixedbrain/go.sum index 7f81b153533..f0d5103d0e7 100644 --- a/tests/mixedbrain/go.sum +++ b/tests/mixedbrain/go.sum @@ -30,8 +30,8 @@ github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDN github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/temporalio/omes v0.0.0-20260526155557-883bc870ab0c h1:4BLvxFccAC1QGBc3oO6cPBJ5hlXuTVGP/HnLsfkRKFA= -github.com/temporalio/omes v0.0.0-20260526155557-883bc870ab0c/go.mod h1:ruo0wxNH1LFUvJ5pcPzf2r3t56X9Joq9lW0/l0efDpk= +github.com/temporalio/omes v0.0.0-20260528212322-14460bcc246f h1:gLnuQgnw6SF4k0J4kMVAWY4h17tt/IhbzZr74D0Tbgc= +github.com/temporalio/omes v0.0.0-20260528212322-14460bcc246f/go.mod h1:ruo0wxNH1LFUvJ5pcPzf2r3t56X9Joq9lW0/l0efDpk= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= diff --git a/tests/mixedbrain/mixed_brain_test.go b/tests/mixedbrain/mixed_brain_test.go index afdfbb159d8..eb7ed3eb5ad 100644 --- a/tests/mixedbrain/mixed_brain_test.go +++ b/tests/mixedbrain/mixed_brain_test.go @@ -113,8 +113,7 @@ func TestMixedBrain(t *testing.T) { nexusEndpoint := "mixed-brain-nexus" t.Run("form cluster", func(st *testing.T) { - // Two physical servers, each running frontend/history/matching/worker. - waitForClusterFormation(st, conn, 90*time.Second, 2) + waitForClusterFormation(st, conn, 90*time.Second, releaseSrv.Ports()) }) if t.Failed() { return diff --git a/tests/mixedbrain/server_util.go b/tests/mixedbrain/server_util.go index a73e8b63f8e..07d2caf3b97 100644 --- a/tests/mixedbrain/server_util.go +++ b/tests/mixedbrain/server_util.go @@ -2,11 +2,13 @@ package mixedbrain import ( "net" + "slices" "strconv" "testing" "time" "github.com/stretchr/testify/require" + "github.com/temporalio/omes/devserver" nexuspb "go.temporal.io/api/nexus/v1" "go.temporal.io/api/operatorservice/v1" "go.temporal.io/api/workflowservice/v1" @@ -63,41 +65,18 @@ func createNexusEndpoint(t *testing.T, conn *grpc.ClientConn, endpointName, name } // waitForClusterFormation waits until the server's reachable members include -// expected membership ports, confirming the servers discovered each other. -// We can't enumerate ports per service (devserver chooses them dynamically), -// so we just wait until both servers see at least minMembers reachable members. -func waitForClusterFormation(t *testing.T, conn *grpc.ClientConn, timeout time.Duration, minMembers int) { +// another devserver, confirming the servers discovered each other. +func waitForClusterFormation(t *testing.T, conn *grpc.ClientConn, timeout time.Duration, expected devserver.Ports) { t.Helper() client := adminservice.NewAdminServiceClient(conn) + want := net.JoinHostPort(expected.Host, strconv.Itoa(expected.FrontendMembership)) require.Eventually(t, func() bool { resp, err := client.DescribeCluster(t.Context(), &adminservice.DescribeClusterRequest{}) if err != nil { return false } - membership := resp.GetMembershipInfo() - if membership == nil { - return false - } - - // Count unique reachable members by host:port to avoid duplicate - // service-level entries. - seen := map[string]bool{} - for _, member := range membership.GetReachableMembers() { - host, portStr, err := net.SplitHostPort(member) - if err != nil { - continue - } - if _, err := strconv.Atoi(portStr); err != nil { - continue - } - seen[host+":"+portStr] = true - } - if len(seen) < minMembers { - t.Logf("Waiting for cluster formation: %d/%d members visible", len(seen), minMembers) - return false - } - return true + return slices.Contains(resp.GetMembershipInfo().GetReachableMembers(), want) }, timeout, time.Second, "cluster did not form within %v", timeout) } From 5f556d061cfa83707cdbc097c66c636b94e3833a Mon Sep 17 00:00:00 2001 From: Stephan Behnke Date: Fri, 29 May 2026 14:51:38 -0700 Subject: [PATCH 5/5] Limit mixedbrain to PostgreSQL persistence --- tests/mixedbrain/mixed_brain_test.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/mixedbrain/mixed_brain_test.go b/tests/mixedbrain/mixed_brain_test.go index eb7ed3eb5ad..baaf8c1e862 100644 --- a/tests/mixedbrain/mixed_brain_test.go +++ b/tests/mixedbrain/mixed_brain_test.go @@ -69,10 +69,12 @@ func TestMixedBrain(t *testing.T) { return } - persistence := devserver.PersistenceOptions{} - if driver := os.Getenv("PERSISTENCE_DRIVER"); driver != "" && driver != "sqlite" { - persistence.Driver = driver + persistenceDriver := os.Getenv("PERSISTENCE_DRIVER") + if persistenceDriver == "" { + persistenceDriver = "postgres12" } + require.Contains(t, []string{"postgres12", "postgres12_pgx"}, persistenceDriver, "mixedbrain requires PostgreSQL because older release config templates do not support SQLite") + persistence := devserver.PersistenceOptions{Driver: persistenceDriver} // Start the current-source server first so the release server can target // its frontend in cluster metadata.