diff --git a/build-system/pr-validation.yaml b/build-system/pr-validation.yaml index ddca94fe407..d4f94937991 100644 --- a/build-system/pr-validation.yaml +++ b/build-system/pr-validation.yaml @@ -112,6 +112,10 @@ jobs: artifactName: "net_mntr_windows-$(Build.BuildId)" mntrFailuresDir: 'TestResults\\multinode' mntrFailuresArtifactName: "net_7_mntr_FAILED_windows-$(Build.BuildId)" + env: + # 2-vCPU hosted agents can't comfortably run StressSpec's 10-node default; 7 is the + # smallest count StressSpecConfig.BuildConfig can still fit (see its doc comment). + MNTR_STRESSSPEC_NODECOUNT: "7" # First-ever Linux MNTR lane: same suite, same incrementalist scoping and # command as "net_mntr_windows" above, on ubuntu-latest (matching the Linux @@ -129,6 +133,10 @@ jobs: mntrFailuresDir: 'TestResults/multinode' mntrFailuresArtifactName: "net_mntr_FAILED_linux-$(Build.BuildId)" continueOnError: true + env: + # 2-vCPU hosted agents can't comfortably run StressSpec's 10-node default; 7 is the + # smallest count StressSpecConfig.BuildConfig can still fit (see its doc comment). + MNTR_STRESSSPEC_NODECOUNT: "7" # Independent, parallel, NON-BLOCKING Artery MNTR stage. Runs the exact same # incrementally-scoped multi-node suite as "net_mntr_windows" above, but @@ -167,6 +175,9 @@ jobs: continueOnError: true env: AKKA_MNTR_TRANSPORT: artery + # 2-vCPU hosted agents can't comfortably run StressSpec's 10-node default; 7 is the + # smallest count StressSpecConfig.BuildConfig can still fit (see its doc comment). + MNTR_STRESSSPEC_NODECOUNT: "7" # Fourth lane, completing the classic/Artery x Windows/Linux MNTR matrix # (only classic Windows is blocking). Same rationale and known/accepted @@ -184,6 +195,9 @@ jobs: continueOnError: true env: AKKA_MNTR_TRANSPORT: artery + # 2-vCPU hosted agents can't comfortably run StressSpec's 10-node default; 7 is the + # smallest count StressSpecConfig.BuildConfig can still fit (see its doc comment). + MNTR_STRESSSPEC_NODECOUNT: "7" - template: azure-pipeline.template.yaml parameters: diff --git a/src/core/Akka.Cluster.Tests.MultiNode/StressSpec.cs b/src/core/Akka.Cluster.Tests.MultiNode/StressSpec.cs index 28be9a4fedc..58f85f531c8 100644 --- a/src/core/Akka.Cluster.Tests.MultiNode/StressSpec.cs +++ b/src/core/Akka.Cluster.Tests.MultiNode/StressSpec.cs @@ -47,7 +47,35 @@ public StressSpecConfig() foreach (var i in Enumerable.Range(1, TotalNumberOfNodes)) Role("node-" + i); - CommonConfig = ConfigurationFactory.ParseString(@" + CommonConfig = ConfigurationFactory.ParseString(BuildConfig(TotalNumberOfNodes)); + + TestTransport = true; + } + + /// + /// Builds the `akka.test.cluster-stress-spec` (plus supporting actor/remote) config for a run + /// of nodes. + /// + /// The reference node count (matching Pekko's stress spec) is 10, and with the full 10-node + /// `nr-of-nodes-*` defaults below, 10 also happens to be the *smallest* count that fits every + /// phase -- see the arithmetic in 's constructor. The joining phases need + /// >= 7 nodes on their own (3 seed nodes + 4 singleton join phases), and the leaving/shutdown + /// phases together remove 7 nodes' worth of `nr-of-nodes-*`, which requires + /// `totalNumberOfNodes - 3 >= 7`, i.e. `totalNumberOfNodes >= 10`. So on the 2-vCPU hosted CI + /// agents, lowering `MNTR_STRESSSPEC_NODECOUNT` below 10 by itself is not enough -- + /// throws unless the phase counts shrink too. Below 10 nodes, this method drops the two + /// "-large" one-by-one leave/shutdown phases (each mostly redundant with the "-small" one-by-one + /// phase that already covers that code path, just at a different point in the cluster's + /// lifecycle) and halves the simultaneous "shutdown" phase from 2 to 1, freeing exactly the 3 + /// nodes needed to fit a 7-node run. + /// + internal static string BuildConfig(int totalNumberOfNodes) + { + var leavingOneByOneLarge = totalNumberOfNodes < 10 ? 0 : 1; + var shutdownOneByOneLarge = totalNumberOfNodes < 10 ? 0 : 1; + var shutdown = totalNumberOfNodes < 10 ? 1 : 2; + + return @" akka.test.cluster-stress-spec { infolog = on # scale the nr-of-nodes* settings with this factor @@ -59,12 +87,12 @@ public StressSpecConfig() nr-of-nodes-joining-one-by-one-large = 1 nr-of-nodes-joining-to-one = 1 nr-of-nodes-leaving-one-by-one-small = 1 - nr-of-nodes-leaving-one-by-one-large = 1 + nr-of-nodes-leaving-one-by-one-large = " + leavingOneByOneLarge + @" nr-of-nodes-leaving = 1 nr-of-nodes-shutdown-one-by-one-small = 1 - nr-of-nodes-shutdown-one-by-one-large = 1 + nr-of-nodes-shutdown-one-by-one-large = " + shutdownOneByOneLarge + @" nr-of-nodes-partition = 2 - nr-of-nodes-shutdown = 2 + nr-of-nodes-shutdown = " + shutdown + @" nr-of-nodes-join-remove = 2 # not scaled # scale the *-duration settings with this factor @@ -76,7 +104,7 @@ public StressSpecConfig() convergence-within-factor = 1.0 } akka.actor.provider = cluster - + akka.cluster { # akka.test.timefactor does NOT reach cluster settings. TestKitBase.Dilated scales TestKit # waits only; ClusterSettings reads this value raw. So a lane that declares ""this box is 3x @@ -134,9 +162,7 @@ public StressSpecConfig() parallelism-factor = 0.5 parallelism-max = 16 } -}"); - - TestTransport = true; +}"; } public class Settings diff --git a/src/core/Akka.Cluster.Tests.MultiNode/StressSpecConfigSpec.cs b/src/core/Akka.Cluster.Tests.MultiNode/StressSpecConfigSpec.cs new file mode 100644 index 00000000000..6109f3a6be4 --- /dev/null +++ b/src/core/Akka.Cluster.Tests.MultiNode/StressSpecConfigSpec.cs @@ -0,0 +1,86 @@ +//----------------------------------------------------------------------- +// +// Copyright (C) 2009-2022 Lightbend Inc. +// Copyright (C) 2013-2026 .NET Foundation +// +//----------------------------------------------------------------------- + +using System; +using Akka.Configuration; +using Xunit; + +namespace Akka.Cluster.Tests.MultiNode; + +/// +/// Plain (non multi-node) unit tests for 's per-phase node-count +/// arithmetic -- in particular, that automatically +/// shrinks the `nr-of-nodes-*` phase counts so a reduced `MNTR_STRESSSPEC_NODECOUNT` (used on the +/// 2-vCPU hosted CI agents) still produces a consistent +/// instead of throwing. +/// +/// These tests do not spin up an actor system or a multi-node run -- +/// only reads plain HOCON values. +/// +public class StressSpecConfigSpec +{ + private static StressSpecConfig.Settings BuildSettings(int totalNumberOfNodes) + { + var config = ConfigurationFactory.ParseString(StressSpecConfig.BuildConfig(totalNumberOfNodes)); + return new StressSpecConfig.Settings(config, totalNumberOfNodes); + } + + [Fact] + public void Settings_at_the_10_node_reference_count_uses_the_full_defaults() + { + var settings = BuildSettings(10); + + Assert.Equal(1, settings.NumberOfNodesLeavingOneByOneLarge); + Assert.Equal(1, settings.NumberOfNodesShutdownOneByOneLarge); + Assert.Equal(2, settings.NumberOfNodesShutdown); + Assert.Equal(3, settings.NumberOfNodesJoiningToSeedNodes); + } + + [Theory] + [InlineData(7)] + [InlineData(8)] + [InlineData(9)] + public void Settings_below_10_nodes_does_not_throw(int totalNumberOfNodes) + { + var settings = BuildSettings(totalNumberOfNodes); + Assert.Equal(totalNumberOfNodes, settings.TotalNumberOfNodes); + } + + [Fact] + public void Settings_at_7_nodes_shrinks_the_leaving_and_shutdown_phases_to_exactly_fit() + { + var settings = BuildSettings(7); + + // the two "-large" one-by-one phases are dropped, and the simultaneous "shutdown" phase + // is halved from 2 to 1, freeing exactly the 3 nodes a 7-node run needs + Assert.Equal(0, settings.NumberOfNodesLeavingOneByOneLarge); + Assert.Equal(0, settings.NumberOfNodesShutdownOneByOneLarge); + Assert.Equal(1, settings.NumberOfNodesShutdown); + + // the joining phases exactly consume all 7 nodes (3 seed + 4 singleton joins), so + // there are none left over to join to the seed nodes as a batch + Assert.Equal(0, settings.NumberOfNodesJoiningToSeedNodes); + + // the leaving/shutdown phases must still leave the 3 master-hosting nodes alone + var leavingAndShutdown = settings.NumberOfNodesLeavingOneByOneSmall + + settings.NumberOfNodesLeavingOneByOneLarge + + settings.NumberOfNodesLeaving + + settings.NumberOfNodesShutdownOneByOneSmall + + settings.NumberOfNodesShutdownOneByOneLarge + + settings.NumberOfNodesShutdown; + Assert.True(leavingAndShutdown <= settings.TotalNumberOfNodes - 3); + } + + [Fact] + public void Settings_below_7_nodes_still_throws_because_the_joining_phases_alone_need_7() + { + // 3 seed nodes + 4 singleton join phases (joining-to-seed-initially, one-by-one-small, + // one-by-one-large, joining-to-one) need >= 7 nodes regardless of how far the + // leaving/shutdown phases are shrunk, so 7 is the practical floor. + Assert.Throws(() => BuildSettings(6)); + } +}