diff --git a/src/FSLibrary.Tests/Tests.fs b/src/FSLibrary.Tests/Tests.fs index e4b6f5c9..7a3c4f73 100644 --- a/src/FSLibrary.Tests/Tests.fs +++ b/src/FSLibrary.Tests/Tests.fs @@ -278,6 +278,41 @@ type Tests(output: ITestOutputHelper) = Assert.Null(spec.Affinity) Assert.Equal(nCfgPlain.Nonce, nCfgPlain.PodLabels().[CfgVal.runNonceLabelKey]) + [] + member __.``HTTP proxy pod inherits mission node affinity and tolerations``() = + // Regression: the HTTP proxy Deployment must carry the mission's node + // placement (self.Affinity()/self.Tolerations()) like the core pods it + // fronts -- otherwise it cannot schedule onto a tainted/dedicated pool + // and stays Pending on a busy cluster. + let nCfg = + MakeNetworkCfg + { ctx with + requireNodeLabels = [ ("purpose", Some "largetests") ] + tolerateNodeTaints = [ ("largetests", None) ] + installNetworkDelay = Some false } + [ coreSet ] + passOpt + + let spec = nCfg.ToHttpProxyDeployment().Spec.Template.Spec + + // Node affinity requires the mission node label. + Assert.NotNull(spec.Affinity) + Assert.NotNull(spec.Affinity.NodeAffinity) + + let term = + Seq.exactlyOne spec.Affinity.NodeAffinity.RequiredDuringSchedulingIgnoredDuringExecution.NodeSelectorTerms + + let expr = Seq.exactlyOne term.MatchExpressions + Assert.Equal("purpose", expr.Key) + Assert.Equal("In", expr.OperatorProperty) + Assert.Equal("largetests", Seq.exactlyOne expr.Values) + + // Tolerates the mission node taint. + Assert.True( + spec.Tolerations + |> Seq.exists (fun t -> t.Key = "largetests" && t.OperatorProperty = "Exists") + ) + [] member __.``Core init commands look reasonable``() = let nCfgWithoutSimulateApply = diff --git a/src/FSLibrary/StellarKubeSpecs.fs b/src/FSLibrary/StellarKubeSpecs.fs index 64c35b1f..2e16857a 100644 --- a/src/FSLibrary/StellarKubeSpecs.fs +++ b/src/FSLibrary/StellarKubeSpecs.fs @@ -974,7 +974,16 @@ type NetworkCfg with configMap = V1ConfigMapVolumeSource(name = self.HttpProxyConfigMapName) ) - let podSpec = V1PodSpec(containers = [| container |], volumes = [| volume |]) + // Mirror the core pods' node placement so the proxy schedules onto the + // same (often tainted/dedicated) node pool it fronts -- otherwise it is + // confined to untainted capacity and can stay Pending on a busy cluster. + let podSpec = + V1PodSpec( + containers = [| container |], + volumes = [| volume |], + ?affinity = self.Affinity(), + tolerations = self.Tolerations() + ) let podTemplate = V1PodTemplateSpec(metadata = V1ObjectMeta(labels = self.HttpProxyLabels), spec = podSpec)