4848// will be ignored when run on other environments.
4949// Note there are also differences when CI is being run pre- or post-commit.
5050//
51+ // When running CI with changes in this file, ensure the "Pipeline script from SCM" scm details match
52+ // the brances being tested. These details don't honour the per-build repository and branch parameterisation.
5153//
5254// Validate/lint this file using the following command
5355// `curl -X POST -F "jenkinsfile=<.jenkins/Jenkinsfile" https://ci-cassandra.apache.org/pipeline-model-converter/validate`
@@ -297,7 +299,7 @@ def isCanonical() {
297299}
298300
299301def isStageEnabled (stage ) {
300- return " jar" == stage || pipelineProfiles()[params. profile]. contains(stage) || (" custom" == params. profile && stage ==~ params. profile_custom_regexp)
302+ return " jar" == stage || pipelineProfiles()[params. profile]? . contains(stage) || (" custom" == params. profile && stage ==~ params. profile_custom_regexp)
301303}
302304
303305def isArchEnabled (arch ) {
@@ -322,22 +324,20 @@ def build(command, cell) {
322324 nodeExclusion = " &&!${ NODE_NAME} "
323325 withEnv(cell. collect { k , v -> " ${ k} =${ v} " }) {
324326 ws(" workspace/${ JOB_NAME} /${ BUILD_NUMBER} /${ cell.step} /${ cell.arch} /jdk-${ cell.jdk} " ) {
325- cleanAgent(cell. step)
326- cleanWs()
327327 fetchSource(cell. step, cell. arch, cell. jdk)
328328 sh """
329329 test -f .jenkins/Jenkinsfile || { echo "Invalid git fork/branch"; exit 1; }
330330 grep -q "Jenkins CI declaration" .jenkins/Jenkinsfile || { echo "Only Cassandra 5.0+ supported"; exit 1; }
331- """
331+ """
332+ fetchDockerImages([' almalinux-build' , ' bullseye-build' , ' centos7-build' ])
332333 def cell_suffix = " _jdk${ cell.jdk} _${ cell.arch} "
333- def logfile = " stage-logs/${ JOB_NAME} _${ BUILD_NUMBER} _${ cell.step}${ cell_suffix} _attempt${ attempt} .log"
334+ def logfile = " stage-logs/${ JOB_NAME} _${ BUILD_NUMBER} _${ cell.step}${ cell_suffix} _attempt${ attempt} .log.xz "
334335 def script_vars = " #!/bin/bash \n set -o pipefail ; " // pipe to tee needs pipefail
335336 script_vars = " ${ script_vars} m2_dir=\' ${ WORKSPACE} /build/m2\' "
336- def status = sh label : " RUNNING ${ cell.step} ..." , script : " ${ script_vars} ${ build_script} ${ cell.jdk} 2>&1 | tee build/${ logfile} " , returnStatus : true
337+ def status = sh label : " RUNNING ${ cell.step} ..." , script : " ${ script_vars} ${ build_script} ${ cell.jdk} 2>&1 | tee >( xz -c > build/${ logfile} ) " , returnStatus : true
337338 dir(" build" ) {
338- sh " xz -f *${ logfile} "
339- archiveArtifacts artifacts : " ${ logfile} .xz" , fingerprint : true
340- copyToNightlies(" ${ logfile} .xz" , " ${ cell.step} /jdk${ cell.jdk} /${ cell.arch} /" )
339+ archiveArtifacts artifacts : " ${ logfile} " , fingerprint : true
340+ copyToNightlies(" ${ logfile} " , " ${ cell.step} /jdk${ cell.jdk} /${ cell.arch} /" )
341341 }
342342 if (0 != status) { error(" Stage ${ cell.step}${ cell_suffix} failed with exit status ${ status} " ) }
343343 if (" jar" == cell. step) { // TODO only stash the project built files. all dependency libraries are restored from the local maven repo using `ant resolver-dist-lib`
@@ -364,11 +364,10 @@ def test(command, cell) {
364364 nodeExclusion = " &&!${ NODE_NAME} "
365365 withEnv(cell. collect { k , v -> " ${ k} =${ v} " }) {
366366 ws(" workspace/${ JOB_NAME} /${ BUILD_NUMBER} /${ cell.step} /${ cell.arch} /jdk-${ cell.jdk} /python-${ cell.python} " ) {
367- cleanAgent(cell. step)
368- cleanWs()
369367 fetchSource(cell. step, cell. arch, cell. jdk)
368+ fetchDockerImages([' ubuntu2004_test' ])
370369 def cell_suffix = " _jdk${ cell.jdk} _python_${ cell.python} _${ cell.cython} _${ cell.arch} _${ cell.split} _${ splits} "
371- def logfile = " stage-logs/${ JOB_NAME} _${ BUILD_NUMBER} _${ cell.step}${ cell_suffix} _attempt${ attempt} .log"
370+ def logfile = " stage-logs/${ JOB_NAME} _${ BUILD_NUMBER} _${ cell.step}${ cell_suffix} _attempt${ attempt} .log.xz "
372371 // pipe to tee needs pipefail
373372 def script_vars = " #!/bin/bash \n set -o pipefail ; "
374373 script_vars = " ${ script_vars} python_version=\' ${ cell.python} \' "
@@ -378,11 +377,10 @@ def test(command, cell) {
378377 }
379378 script_vars = fetchDTestsSource(command, script_vars)
380379 buildJVMDTestJars(cell, script_vars, logfile)
381- def status = sh label : " RUNNING TESTS ${ cell.step} ..." , script : " ${ script_vars} .build/docker/run-tests.sh ${ cell.step} '${ cell.split} /${ splits} ' ${ cell.jdk} 2>&1 | tee -a build/${ logfile} " , returnStatus : true
380+ def status = sh label : " RUNNING TESTS ${ cell.step} ..." , script : " ${ script_vars} .build/docker/run-tests.sh ${ cell.step} '${ cell.split} /${ splits} ' ${ cell.jdk} 2>&1 | tee >( xz -c > build/${ logfile} ) " , returnStatus : true
382381 dir(" build" ) {
383- sh " xz -f ${ logfile} "
384- archiveArtifacts artifacts : " ${ logfile} .xz" , fingerprint : true
385- copyToNightlies(" ${ logfile} .xz" , " ${ cell.step} /${ cell.arch} /jdk${ cell.jdk} /python${ cell.python} /cython_${ cell.cython} /" + " split_${ cell.split} _${ splits} " . replace(" /" , " _" ))
382+ archiveArtifacts artifacts : " ${ logfile} " , fingerprint : true
383+ copyToNightlies(" ${ logfile} " , " ${ cell.step} /${ cell.arch} /jdk${ cell.jdk} /python${ cell.python} /cython_${ cell.cython} /" + " split_${ cell.split} _${ splits} " . replace(" /" , " _" ))
386384 }
387385 if (0 != status) { error(" Stage ${ cell.step}${ cell_suffix} failed with exit status ${ status} " ) }
388386 dir(" build" ) {
@@ -405,12 +403,13 @@ def test(command, cell) {
405403}
406404
407405def fetchSource (stage , arch , jdk ) {
408- if (" jar" == stage) {
409- checkout changelog : false , scm : scmGit(branches : [[name : params. branch]], extensions : [cloneOption(depth : 1 , noTags : true , reference : ' ' , shallow : true )], userRemoteConfigs : [[url : params. repository]])
410- sh " mkdir -p build/stage-logs"
411- } else {
412- unstash name : " ${ arch} _${ jdk} "
413- }
406+ cleanAgent(stage)
407+ if (" jar" == stage) {
408+ checkout changelog : false , scm : scmGit(branches : [[name : params. branch]], extensions : [cloneOption(depth : 1 , noTags : true , reference : ' ' , shallow : true )], userRemoteConfigs : [[url : params. repository]])
409+ sh " mkdir -p build/stage-logs"
410+ } else {
411+ unstash name : " ${ arch} _${ jdk} "
412+ }
414413}
415414
416415def fetchDTestsSource (command , script_vars ) {
@@ -427,12 +426,26 @@ def buildJVMDTestJars(cell, script_vars, logfile) {
427426 try {
428427 unstash name : " jvm_dtests_${ cell.arch} _${ cell.jdk} "
429428 } catch (error) {
430- sh label : " RUNNING build_dtest_jars..." , script : " ${ script_vars} .build/docker/run-tests.sh build_dtest_jars ${ cell.jdk} 2>&1 | tee build/${ logfile} "
429+ sh label : " RUNNING build_dtest_jars..." , script : " ${ script_vars} .build/docker/run-tests.sh build_dtest_jars ${ cell.jdk} 2>&1 | tee >( xz -c > build/${ logfile} ) "
431430 stash name : " jvm_dtests_${ cell.arch} _${ cell.jdk} " , includes : ' **/dtest*.jar'
432431 }
433432 }
434433}
435434
435+ def fetchDockerImages (dockerfiles ) {
436+ // prefetch, from apache jfrog, reduces risking dockerhub pull rate limits
437+ for (dockerfile in dockerfiles) {
438+ sh """ #!/bin/bash
439+ image_tag="\$ (md5sum .build/docker/${ dockerfile} .docker | cut -d' ' -f1)"
440+ image_name="apache/cassandra-${ dockerfile} :\$ {image_tag}"
441+ if ! ( [[ "" != "\$ (docker images -q \$ {image_name} 2>/dev/null)" ]] ) ; then
442+ docker pull -q apache.jfrog.io/cassan-docker/\$ {image_name} &
443+ fi
444+ wait
445+ """
446+ }
447+ }
448+
436449def getNodeLabel (command , cell ) {
437450 echo " using node label: cassandra-${ cell.arch} -${ command.size} "
438451 return " cassandra-${ cell.arch} -${ command.size} "
@@ -445,44 +458,68 @@ def copyToNightlies(sourceFiles, remoteDirectory='') {
445458 retry(9 ) {
446459 if (attempt > 1 ) { sleep(60 * attempt) }
447460 sshPublisher(
448- continueOnError : true , failOnError : false ,
449- publishers : [
450- sshPublisherDesc(
451- configName : " Nightlies" ,
452- transfers : [ sshTransfer( sourceFiles : sourceFiles, remoteDirectory : remotePath) ]
453- )
454- ])
461+ continueOnError : true , failOnError : false ,
462+ publishers : [
463+ sshPublisherDesc(
464+ configName : " Nightlies" ,
465+ transfers : [ sshTransfer( sourceFiles : sourceFiles, remoteDirectory : remotePath) ]
466+ )
467+ ])
455468 }
456469 echo " archived to https://nightlies.apache.org/${ remotePath} "
457470 }
458471}
459472
460473def cleanAgent (job_name ) {
461474 sh " hostname"
475+ sh " test -f build.xml && git clean -qxdff -e build/test/jmh-result.json || true"
462476 if (isCanonical()) {
463- def maxJobHours = 12
464- echo " Cleaning project, and pruning docker for '${ job_name} ' on ${ NODE_NAME} …" ;
465- sh """
466- git clean -qxdff -e build/test/jmh-result.json || true;
467- if pgrep -xa docker || pgrep -af "build/docker" || pgrep -af "cassandra-builds/build-scripts" ; then docker system prune --all --force --filter "until=${ maxJobHours} h" || true ; else docker system prune --force --volumes || true ; fi;
468- """
477+ def agentScriptsUrl = " https://raw.githubusercontent.com/apache/cassandra-builds/trunk/jenkins-dsl/agent_scripts/"
478+ cleanAgentDocker(job_name, agentScriptsUrl)
479+ logAgentInfo(job_name, agentScriptsUrl)
480+ sh ' rm docker_agent_cleaner.sh docker_image_pruner.py agent_report.sh *-disk-usage-stats.txt'
469481 }
470482}
471483
484+ def cleanAgentDocker (job_name , agentScriptsUrl ) {
485+ // we don't expect any build to have been running for longer than maxBuildHours
486+ def maxBuildHours = 12
487+ echo " Pruning docker for '${ job_name} ' on ${ NODE_NAME} …" ;
488+ sh """ #!/bin/bash
489+ set +e
490+ wget -q ${ agentScriptsUrl} /docker_image_pruner.py
491+ wget -q ${ agentScriptsUrl} //docker_agent_cleaner.sh
492+ bash docker_agent_cleaner.sh ${ maxBuildHours}
493+ """
494+ }
495+
496+ def logAgentInfo (job_name , agentScriptsUrl ) {
497+ sh """ #!/bin/bash
498+ set +e -o pipefail
499+ wget -q ${ agentScriptsUrl} /agent_report.sh
500+ bash -x agent_report.sh | tee -a \$ (date +"%Y%m%d%H%M")-disk-usage-stats.txt
501+ """
502+ copyToNightlies(" *-disk-usage-stats.txt" , " cassandra/ci-cassandra.apache.org/agents/${ NODE_NAME} /disk-usage/" )
503+ }
504+
472505// ///////////////////////////////////////
473506// //// scripting support for summary ////
474507// ///////////////////////////////////////
475508
476509def generateTestReports () {
477510 node(" cassandra-medium" ) {
478- cleanWs( )
511+ cleanAgent( " generateTestReports " )
479512 checkout changelog : false , scm : scmGit(branches : [[name : params. branch]], extensions : [cloneOption(depth : 1 , noTags : true , reference : ' ' , shallow : true )], userRemoteConfigs : [[url : params. repository]])
513+ def logfile = " stage-logs/${ JOB_NAME} _${ BUILD_NUMBER} _generateTestReports.log.xz"
514+ sh " mkdir -p build/stage-logs"
515+ def teeSuffix = " 2>&1 | tee >( xz -c > build/${ logfile} )"
516+ def script_vars = " #!/bin/bash -x \n "
480517 if (isCanonical()) {
481518 // copyArtifacts takes >4hrs, hack with manual download
482- sh """
483- mkdir -p build/test
519+ sh """ ${ script_vars }
520+ ( mkdir -p build/test
484521 wget -q ${ BUILD_URL} /artifact/test/output/*zip*/output.zip
485- unzip -x -d build/test -q output.zip
522+ unzip -x -d build/test -q output.zip ) ${ teeSuffix }
486523 """
487524 } else {
488525 copyArtifacts filter : ' test/**/TEST-*.xml.xz,test/**/cqlshlib*.xml.xz,test/**/nosetests*.xml.xz' , fingerprintArtifacts : true , projectName : env. JOB_NAME , selector : specific(env. BUILD_NUMBER ), target : " build/" , optional : true
@@ -491,8 +528,8 @@ def generateTestReports() {
491528 // merge splits for each target's test report, other axes are kept separate
492529 // TODO parallelised for loop
493530 // TODO results_details.tar.xz needs to include all logs for failed tests
494- sh """
495- find build/test/output -name *.xml.xz -exec sh -c 'xz -f --decompress {} &' ';' ; wait
531+ sh """ ${ script_vars } (
532+ find build/test/output -name *.xml.xz -exec sh -c 'xz -f --decompress {} &' ';' ; wait
496533
497534 for target in \$ (ls build/test/output/) ; do
498535 if test -d build/test/output/\$ {target} ; then
@@ -506,12 +543,13 @@ def generateTestReports() {
506543
507544 .build/docker/_docker_run.sh bullseye-build.docker ci/generate-ci-summary.sh || echo "failed generate-ci-summary.sh"
508545
509- tar -cf build/results_details.tar -C build/test/ reports && xz -8f build/results_details.tar
546+ tar -cf build/results_details.tar -C build/test/ reports
547+ xz -8f build/results_details.tar ) ${ teeSuffix}
510548 """
511549
512550 dir(' build/' ) {
513- archiveArtifacts artifacts : " ci_summary.html,results_details.tar.xz" , fingerprint : true
514- copyToNightlies(' ci_summary.html,results_details.tar.xz' )
551+ archiveArtifacts artifacts : " ci_summary.html,results_details.tar.xz, ${ logfile } " , fingerprint : true
552+ copyToNightlies(' ci_summary.html,results_details.tar.xz,${logfile} ' )
515553 }
516554 }
517555 }
0 commit comments