@@ -228,7 +228,12 @@ def echoRemoteLogTail(def pipeline, Map remote, String remotePath, int lines = 2
228228// the locally-staged result XMLs when the same stageName is uploaded more than
229229// once in a build (e.g. SLURM infra-failure retries). First attempt passes "".
230230def uploadResults (def pipeline , SlurmCluster cluster , String clusterName , String nodeName , String stageName , Boolean stageIsInterrupted , String postTag = " " ) {
231- CloudManager . withSlurmSshCredentials(pipeline, clusterName, cluster) { remote ->
231+ CloudManager . withSlurmSshCredentialRemotes(pipeline, clusterName, cluster) { remotes ->
232+ // Pin one reachable frontend for the whole collect: every download targets
233+ // the same node workspace (/home/svc_tensorrt/bloom/scripts/${nodeName}),
234+ // so the find + scps must all hit the login node that holds those files.
235+ // No whole-closure failover here -- uploadArtifacts/junit must not re-run.
236+ def remote = CloudManager . selectReachableSlurmRemote(pipeline, remotes)
232237 def hasTimeoutTest = false
233238 def downloadResultSucceed = false
234239 def downloadPerfResultSucceed = false
@@ -599,7 +604,7 @@ def processShardTestList(llmSrc, testDBList, splitId, splits, perfMode=false, du
599604}
600605
601606def cleanUpSlurmResources (def pipeline , SlurmCluster cluster , String clusterName , String jobUID ){
602- CloudManager . withSlurmSshCredentials (pipeline, clusterName, cluster) { remote ->
607+ CloudManager . withSlurmFrontendFailover (pipeline, clusterName, cluster) { remote ->
603608 def jobWorkspace = " /home/svc_tensorrt/bloom/scripts/${ jobUID} "
604609
605610 Utils . exec(pipeline, script : " echo Sleeping to allow Slurm job completion; sleep 30" )
@@ -642,7 +647,7 @@ def cleanUpSlurmResources(def pipeline, SlurmCluster cluster, String clusterName
642647 pipeline,
643648 script : Utils . sshUserCmd(
644649 remote,
645- " \" ${ cleanupCommands} \" "
650+ Utils . bashWrappedRemoteCmd( cleanupCommands)
646651 )
647652 )
648653
@@ -658,7 +663,7 @@ def cleanUpNodeResources(def pipeline, SlurmCluster cluster, String clusterName,
658663
659664 Utils . exec(pipeline, script : " echo Sleeping to allow node destruction; sleep 30" )
660665
661- CloudManager . withSlurmSshCredentials (pipeline, clusterName, cluster) { remote ->
666+ CloudManager . withSlurmFrontendFailover (pipeline, clusterName, cluster) { remote ->
662667 Utils . exec(pipeline, script : " echo Slurm job ID: ${ slurmJobID} " )
663668
664669 Utils . exec(
@@ -683,7 +688,7 @@ def cleanUpNodeResources(def pipeline, SlurmCluster cluster, String clusterName,
683688 pipeline,
684689 script : Utils . sshUserCmd(
685690 remote,
686- " \" ${ cleanupCommands} \" "
691+ Utils . bashWrappedRemoteCmd( cleanupCommands)
687692 )
688693 )
689694
@@ -704,7 +709,7 @@ def querySlurmJobState(def pipeline, SlurmCluster cluster, String clusterName, S
704709 }
705710 String state = null
706711 try {
707- CloudManager . withSlurmSshCredentials (pipeline, clusterName, cluster) { remote ->
712+ CloudManager . withSlurmFrontendFailover (pipeline, clusterName, cluster) { remote ->
708713 // -X: allocation row only (skip .batch/.extern steps). -Pn:
709714 // parsable, no header. First line's first token is the job state;
710715 // SLURM renders cancellations as "CANCELLED by <uid>", so we keep
@@ -748,7 +753,7 @@ def runLLMTestlistWithAgent(pipeline, platform, testList, config=VANILLA_CONFIG,
748753
749754 try {
750755 // Run ssh command to start node in desired cluster via SLURM
751- CloudManager . withSlurmSshCredentials (pipeline, partition. clusterName, cluster) { remote ->
756+ CloudManager . withSlurmFrontendFailover (pipeline, partition. clusterName, cluster) { remote ->
752757 stage(' Request Node Via Slurm' ) {
753758 println (" Selected Cluster: ${ cluster.name} " )
754759
@@ -817,12 +822,14 @@ def runLLMTestlistWithAgent(pipeline, platform, testList, config=VANILLA_CONFIG,
817822 def jobRunningStartMs = null
818823
819824 stage(' Check If Node Is Online' ) {
820- CloudManager . withSlurmSshCredentials (pipeline, partition. clusterName, cluster) { remote ->
825+ CloudManager . withSlurmSshCredentialRemotes (pipeline, partition. clusterName, cluster) { remotes ->
821826 // Check the SLURM job once; if it is no longer active, raise a typed
822827 // InfraFailure(SLURM) so the retry layer routes it via instanceof (scope=SLURM).
823828 def checkSlurmJobActive = {
824829 try {
825- SlurmConfig . checkJobStatus(pipeline, cluster, slurmJobID, remote)
830+ CloudManager . withSlurmFrontendFailover(pipeline, remotes) { statusRemote ->
831+ SlurmConfig . checkJobStatus(pipeline, cluster, slurmJobID, statusRemote)
832+ }
826833 } catch (InterruptedException e) {
827834 throw e
828835 } catch (Exception e) {
@@ -845,8 +852,8 @@ def runLLMTestlistWithAgent(pipeline, platform, testList, config=VANILLA_CONFIG,
845852 // which overflowed the per-stage step cap). Release the held job every 10
846853 // iterations (~30 min). 300 iterations * 3 min = 15h budget.
847854 // Exit codes: 0 = job RUNNING, 3 = job no longer active, 4 = timed out.
848- def sacctStateCmd = Utils . sshUserCmd(remote , " \" sacct -j ${ slurmJobID} --format=State -Pn --allocations\" " )
849- def releaseCmd = Utils . sshUserCmd(remote , " \" scontrol release ${ slurmJobID} || true\" " )
855+ def sacctStateCmd = CloudManager . sshUserCmdWithSlurmFrontendFailover(remotes , " \" sacct -j ${ slurmJobID} --format=State -Pn --allocations\" " )
856+ def releaseCmd = CloudManager . sshUserCmdWithSlurmFrontendFailover(remotes , " \" scontrol release ${ slurmJobID} || true\" " )
850857 def waitRc = pipeline. sh(returnStatus : true , script : """
851858 set +e
852859 counter=0
@@ -967,7 +974,7 @@ def runLLMTestlistWithAgent(pipeline, platform, testList, config=VANILLA_CONFIG,
967974 def setupLogPath = " /home/svc_tensorrt/slurm-logs/slurm-${ slurmJobID} -${ nodeName} .out"
968975 def enrootLog = Utils . exec(
969976 pipeline,
970- script : Utils . sshUserCmd(remote, " \" grep '\\ [ENROOT\\ ]' ${ setupLogPath} 2>/dev/null || true\" " ),
977+ script : CloudManager . sshUserCmdWithSlurmFrontendFailover(remotes, Utils . bashWrappedRemoteCmd( " grep '\\ [ENROOT\\ ]' ${ setupLogPath} 2>/dev/null || true" ) ),
971978 returnStdout : true ,
972979 numRetries : 3
973980 ). trim()
@@ -1252,7 +1259,7 @@ def runLLMTestlistWithSbatch(pipeline, platform, testList, config=VANILLA_CONFIG
12521259 string(credentialsId : ' TRTLLM_HF_TOKEN' , variable : ' HF_TOKEN' ),
12531260 string(credentialsId : ' svc_tensorrt-swift-stack-key' , variable : ' S3_SECRET_KEY' ),
12541261 ]) {
1255- CloudManager . withSlurmSshCredentials (pipeline, partition. clusterName, cluster) { remote ->
1262+ CloudManager . withSlurmFrontendFailover (pipeline, partition. clusterName, cluster) { remote ->
12561263 def tarName = BUILD_CONFIGS [config][TARNAME ]
12571264 def llmTarfile = " https://urm.nvidia.com/artifactory/${ ARTIFACT_PATH} /${ tarName} "
12581265 def llmPath = sh (script : " realpath ." , returnStdout : true ). trim()
@@ -1323,11 +1330,20 @@ def runLLMTestlistWithSbatch(pipeline, platform, testList, config=VANILLA_CONFIG
13231330 def makoOptsJson = transformMakoArgsToJson([" Mako options:" ] + makoArgs)
13241331 String clusterNameForDurations = useClusterDurations ? partition. clusterName. replaceAll(' [^a-zA-Z0-9]' , ' _' ) : null
13251332 def testListPathLocal = renderTestDB(pipeline, testList, llmSrcLocal, stageName, makoOptsJson, clusterNameForDurations)
1333+ // Copy the test list atomically. A retry that reuses a still-active job
1334+ // re-copies over ${testListPathNode} while that job may be reading it via
1335+ // --test-list; scp truncates-then-streams, so a concurrent read could see a
1336+ // partial list and silently run a subset. Stage to a temp path and mv into
1337+ // place (same-dir rename is atomic) so a reader sees the whole old or new file.
13261338 Utils . copyFileToRemoteHost(
13271339 pipeline,
13281340 remote,
13291341 testListPathLocal,
1330- testListPathNode
1342+ " ${ testListPathNode} .tmp"
1343+ )
1344+ Utils . exec(
1345+ pipeline,
1346+ script : Utils . sshUserCmd(remote, " \" mv -f ${ testListPathNode} .tmp ${ testListPathNode} \" " )
13311347 )
13321348
13331349 // Download and Merge waives.txt
@@ -1671,13 +1687,26 @@ def runLLMTestlistWithSbatch(pipeline, platform, testList, config=VANILLA_CONFIG
16711687 set -xEeuo pipefail
16721688 trap 'rc=\$ ?; echo "Error in file \$ {BASH_SOURCE[0]} on line \$ LINENO: \$ BASH_COMMAND (exit \$ rc)"; exit \$ rc' ERR
16731689
1674- # Clean up previous job intermediate files so that retry can work
1690+ # Reuse an already-active job after an ambiguous frontend disconnect.
16751691 if [ -f "${ jobWorkspace} /slurm_job_id.txt" ]; then
16761692 previous_job_id=\$ (cat "${ jobWorkspace} /slurm_job_id.txt")
16771693 echo "Found previous Slurm job ID: \$ {previous_job_id}"
1678- scancel "\$ {previous_job_id}" || true
1679- # Wait for 120 seconds to ensure the previous job is canceled
1680- sleep 120
1694+ previous_state=\$ (sacct -j "\$ {previous_job_id}" --format=State -Pn --allocations 2>/dev/null | head -1 | cut -d'|' -f1 | awk '{print \$ 1}' || true)
1695+ if [ -z "\$ {previous_state}" ]; then
1696+ previous_state=\$ (scontrol show job "\$ {previous_job_id}" 2>/dev/null | tr ' ' '\\ n' | sed -n 's/^JobState=//p' | head -1 || true)
1697+ fi
1698+ case "\$ {previous_state}" in
1699+ RUNNING|PENDING|CONFIGURING|COMPLETING|REQUEUED|RESIZING|SUSPENDED|SIGNALING|STOPPED)
1700+ echo "Reusing active Slurm job \$ {previous_job_id} in state \$ {previous_state}"
1701+ exit 0
1702+ ;;
1703+ *)
1704+ echo "Previous Slurm job \$ {previous_job_id} is not active (state='\$ {previous_state:-UNKNOWN}'). Cleaning it up before resubmission."
1705+ scancel "\$ {previous_job_id}" || true
1706+ # Wait for 120 seconds to ensure the previous job is canceled
1707+ sleep 120
1708+ ;;
1709+ esac
16811710 fi
16821711
16831712 # Clean up workspace: remove all files/dirs not in the keep list
@@ -1706,14 +1735,15 @@ def runLLMTestlistWithSbatch(pipeline, platform, testList, config=VANILLA_CONFIG
17061735 }
17071736
17081737 stage(" [${ stageName} ] Run Pytest" ) {
1709- // Submit the Slurm job
1738+ // Submit the Slurm job. Submit/metadata/track all run on the one
1739+ // frontend the enclosing withSlurmFrontendFailover pinned, so they
1740+ // share the job workspace (slurm_job_id.txt, scripts) on that login
1741+ // node; a frontend disconnect fails the whole closure over to a fresh
1742+ // frontend as a unit (the submit script reuses an active job).
17101743 Utils . exec(
17111744 pipeline,
17121745 timeout : false ,
1713- script : Utils . sshUserCmd(
1714- remote,
1715- scriptSubmitPathNode
1716- ),
1746+ script : Utils . sshUserCmd(remote, scriptSubmitPathNode),
17171747 numRetries : 3
17181748 )
17191749
@@ -1722,10 +1752,7 @@ def runLLMTestlistWithSbatch(pipeline, platform, testList, config=VANILLA_CONFIG
17221752 if (! slurmJobId) {
17231753 slurmJobId = Utils . exec(
17241754 pipeline,
1725- script : Utils . sshUserCmd(
1726- remote,
1727- " \" cat ${ jobWorkspace} /slurm_job_id.txt\" "
1728- ),
1755+ script : Utils . sshUserCmd(remote, " \" cat ${ jobWorkspace} /slurm_job_id.txt\" " ),
17291756 returnStdout : true ,
17301757 numRetries : 3
17311758 ). trim()
@@ -1816,15 +1843,12 @@ def runLLMTestlistWithSbatch(pipeline, platform, testList, config=VANILLA_CONFIG
18161843 true
18171844 )
18181845
1819- // Track the Slurm job
1846+ // Track the Slurm job on the same pinned frontend.
18201847 try {
18211848 Utils . exec(
18221849 pipeline,
18231850 timeout : false ,
1824- script : Utils . sshUserCmd(
1825- remote,
1826- scriptTrackPathNode
1827- ),
1851+ script : Utils . sshUserCmd(remote, scriptTrackPathNode),
18281852 numRetries : 3
18291853 )
18301854 } catch (InterruptedException e) {
@@ -1853,7 +1877,7 @@ def runLLMTestlistWithSbatch(pipeline, platform, testList, config=VANILLA_CONFIG
18531877 }
18541878 }
18551879 echo " Finished test stage execution."
1856- } // end CloudManager.withSlurmSshCredentials
1880+ } // end CloudManager.withSlurmFrontendFailover
18571881 } // end withCredentials
18581882 } catch (InterruptedException e) {
18591883 stageIsInterrupted = true
@@ -2245,7 +2269,7 @@ def readSlurmWorkspaceFile(def pipeline, Map remote, String path, String stageNa
22452269 pipeline,
22462270 script : Utils . sshUserCmd(
22472271 remote,
2248- " \" cat ${ path} 2>/dev/null || true\" "
2272+ Utils . bashWrappedRemoteCmd( " cat ${ path} 2>/dev/null || true" )
22492273 ),
22502274 returnStdout : true ,
22512275 numRetries : numRetries
@@ -2254,6 +2278,13 @@ def readSlurmWorkspaceFile(def pipeline, Map remote, String path, String stageNa
22542278 } catch (InterruptedException e) {
22552279 throw e
22562280 } catch (Exception e) {
2281+ // A dead frontend must propagate so the enclosing withSlurmFrontendFailover
2282+ // fails over to another remote; swallowing it as "" would strand the stage on
2283+ // the unreachable frontend. Any other read failure (missing file, transient)
2284+ // is non-fatal -- the metadata is best-effort, so return "" and carry on.
2285+ if (CloudManager . isSlurmFrontendConnectionFailure(e)) {
2286+ throw e
2287+ }
22572288 echo " [INFRA-RETRY] ${ stageName} : unable to read SLURM metadata file ${ path} : ${ e.toString()} "
22582289 return " "
22592290 }
@@ -2308,7 +2339,7 @@ def captureSlurmJobNodeList(def pipeline, SlurmCluster cluster, String clusterNa
23082339 def capturedJobID = slurmJobID
23092340 def nodeList = null
23102341 try {
2311- CloudManager . withSlurmSshCredentials (pipeline, clusterName, cluster) { remote ->
2342+ CloudManager . withSlurmFrontendFailover (pipeline, clusterName, cluster) { remote ->
23122343 def metadata = captureSlurmWorkspaceMetadata(pipeline, remote, jobWorkspace, placementContext, stageName)
23132344 capturedJobID = capturedJobID ?: metadata. slurmJobId
23142345 nodeList = metadata. nodeList
0 commit comments