diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000000..c85cc49527 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,183 @@ +def MACHINE = 'none' +def machine = 'none' +def HOME = 'none' + +pipeline { + agent { label 'built-in' } + + options { + disableConcurrentBuilds(abortPrevious: true) + skipDefaultCheckout(true) + buildDiscarder(logRotator(numToKeepStr: '3')) + } + + stages { + + stage('Get Machine') { + agent { label 'built-in' } + steps { + script { + MACHINE = 'none' + for (label in pullRequest.labels) { + echo "Label: ${label}" + if ((label.matches("CI-Hera-Ready"))) { + MACHINE = 'hera' + } else if ((label.matches("CI-Orion-Ready"))) { + MACHINE = 'orion' + } else if ((label.matches("CI-Hercules-Ready"))) { + MACHINE = 'hercules' + } + } + machine = MACHINE[0].toUpperCase() + MACHINE.substring(1) + } + } + } + + stage('Get Common Workspace') { + agent { label "${MACHINE}-emc" } + steps ( timeout(time: 1, unit: 'HOURS') ) { + script { + HOME = "${WORKSPACE}/TESTDIR" + pullRequest.addLabel("CI-${machine}-Building") + if (pullRequest.labels.contains("CI-${machine}-Ready")) { + pullRequest.removeLabel("CI-${machine}-Ready") + } + } + } + } + + stage('Build') { + matrix { + agent { label "${MACHINE}-emc" } + axes { + axis { + name "system" + values "gfs", "gefs" + } + } + stages { + stage("build system") { + steps { + script { + def HOMEgfs = "${HOME}/${system}" + properties([parameters([[$class: 'NodeParameterDefinition', allowedSlaves: ['built-in','Hera-EMC','Orion-EMC'], defaultSlaves: ['built-in'], name: '', nodeEligibility: [$class: 'AllNodeEligibility'], triggerIfResult: 'allCases']])]) + sh( script: "mkdir -p ${HOMEgfs}", returnStatus: true) + dir(HOMEgfs) { + checkout scm + env.MACHINE_ID = MACHINE + if (fileExists("sorc/BUILT_semaphor")) { + sh( script: "cat sorc/BUILT_semaphor", returnStdout: true).trim() + pullRequest.comment("Cloned PR already built (or build skipped) on ${machine} in directory ${HOMEgfs}") + } else { + sh( script: "git submodule update --init --recursive", returnStatus: true) + if (system == "gfs") { + dir("${HOMEgfs}/sorc") { + sh( script: "./build_all.sh -gu -j 4", returnStatus: false) + sh( script: "./link_workflow.sh", returnStatus: false) + sh( script: "echo ${HOMEgfs} > BUILT_semaphor", returnStatus: true) + } + } else if (system == "gefs") { + // TODO: need to add gefs build arguments from a yaml file + dir("${HOMEgfs}/sorc") { + sh( script: "./build_all.sh -gu -j 4", returnStatus: false) + sh( script: "./link_workflow.sh", returnStatus: false) + sh( script: "echo ${HOMEgfs} > BUILT_semaphor", returnStatus: true) + } + } + } + } + } + } + } + } + } + } + + stage('Setup RUNTESTS') { + agent { label "${MACHINE}-emc" } + steps { + script { + sh( script: "mkdir -p ${HOME}/RUNTESTS", returnStatus: true) + //TODO cannot get pullRequest.labels.contains("CI-${machine}-Building") to work + pullRequest.removeLabel("CI-${machine}-Building") + pullRequest.addLabel("CI-${machine}-Running") + } + } + + } + + stage('Run Tests') { + matrix { + agent { label "${MACHINE}-emc" } + axes { + axis { + name "Case" + values "C48_ATM", "C48_S2SWA_gefs", "C48_S2SW", "C96_atm3DVar" + } + } + stages { + stage('Create Experiment') { + steps { + script { + env.RUNTESTS = "${HOME}/RUNTESTS" + def HOMEgfs = "${HOME}/gfs" + env.HOME = HOMEgfs + sh( script: "rm -Rf ${RUNTESTS}/EXPDIR/${Case}_*" ) + sh( script: "rm -Rf ${RUNTESTS}/COMROOT/${Case}_*" ) + sh( script: "${HOMEgfs}/ci/scripts/utils/ci_utils_wrapper.sh create_experiment ${HOMEgfs}/ci/cases/pr/${Case}.yaml", returnStatus: true) + } + } + } + stage('Run Experiments') { + steps { + script { + def HOMEgfs = "${HOME}/gfs" + pslot = sh( script: "${HOMEgfs}/ci/scripts/utils/ci_utils_wrapper.sh get_pslot ${HOME}/RUNTESTS ${Case}", returnStdout: true ).trim() + pullRequest.comment("Running experiments: ${Case} with pslot ${pslot} on ${machine}") + try { + sh( script: "${HOMEgfs}/ci/scripts/run-check_ci.sh ${HOME} ${pslot}", returnStatus: false) + pullRequest.comment("SUCCESS running experiments: ${Case} on ${machine}") + } catch (Exception e) { + pullRequest.comment("FAILURE running experiments: ${Case} on ${machine}") + error("Failed to run experiments ${Case} on ${machine}") + } + } + } + } + } + } + } + + } + + post { + success { + script { + //if ( pullRequest.labels.contains( "CI-${machine}-Running" ) ) { + // pullRequest.removeLabel("CI-${machine}-Running") + //} + // TODO: contains mehthod does not work + pullRequest.removeLabel("CI-${machine}-Running") + pullRequest.addLabel("CI-${machine}-Passed") + def timestamp = new Date().format("MM dd HH:mm:ss", TimeZone.getTimeZone('America/New_York')) + pullRequest.comment("SUCCESSFULLY ran all CI Cases on ${machine} at ${timestamp}") + } + cleanWs() + } + failure { + script { + pullRequest.removeLabel("CI-${machine}-Running") + pullRequest.addLabel("CI-${machine}-Failed") + def timestamp = new Date().format("MM dd HH:mm:ss", TimeZone.getTimeZone('America/New_York')) + pullRequest.comment("CI FAILED ${machine} at ${timestamp}\n\nBuilt and ran in directory ${HOME}") + if (fileExists('${HOME}/RUNTESTS/ci.log')) { + def fileContent = readFile '${HOME}/RUNTESTS/ci.log' + fileContent.eachLine { line -> + archiveArtifacts artifacts: "${line}", fingerprint: true + } + } + } + } + } + +} diff --git a/ci/scripts/run-check_ci.sh b/ci/scripts/run-check_ci.sh index 5a909c1c64..f98f434462 100755 --- a/ci/scripts/run-check_ci.sh +++ b/ci/scripts/run-check_ci.sh @@ -21,7 +21,9 @@ pslot=${2:-${pslot:-?}} # Name of the experiment being tested by this scr # │   └── ${pslot} # └── EXPDIR # └── ${pslot} -HOMEgfs="${TEST_DIR}/HOMEgfs" +# Two system build directories created at build time gfs, and gdas +# TODO: Make this configurable (for now all scripts run from gfs for CI at runtime) +HOMEgfs="${TEST_DIR}/gfs" RUNTESTS="${TEST_DIR}/RUNTESTS" # Source modules and setup logging diff --git a/ci/scripts/run-check_ci_stub.sh b/ci/scripts/run-check_ci_stub.sh new file mode 100755 index 0000000000..23d3da8376 --- /dev/null +++ b/ci/scripts/run-check_ci_stub.sh @@ -0,0 +1,11 @@ +#!/bin/bash + +set -eu + +HOMEgfs=$1 +pslot=$2 + +echo -e "HOMEgfs: ${HOMEgfs}\npslpot: ${pslot}\n in ${PWD}" >> "${HOMEgfs}/ci/scripts/run-check_ci_stub.log" +if [[ ${pslot} =~ "C48_S2SW" ]]; then + exit 1 +fi diff --git a/ci/scripts/utils/ci_utils.sh b/ci/scripts/utils/ci_utils.sh index 737a3e5a86..6b93122579 100755 --- a/ci/scripts/utils/ci_utils.sh +++ b/ci/scripts/utils/ci_utils.sh @@ -1,24 +1,102 @@ #!/bin/env bash -function cancel_slurm_jobs() { - - # Usage: cancel_slurm_jobs - # Example: cancel_slurm_jobs "C48_ATM_3c4e7f74" +function cancel_batch_jobs() { + # Usage: cancel_batch_jobs + # Example: cancel_batch_jobs "C48_ATM_3c4e7f74" # - # Cancel all Slurm jobs that have the given substring in their name + # Cancel all batch jobs that have the given substring in their name # So like in the example all jobs with "C48_ATM_3c4e7f74" # in their name will be canceled local substring=$1 local job_ids - job_ids=$(squeue -u "${USER}" -h -o "%i") - - for job_id in ${job_ids}; do - job_name=$(sacct -j "${job_id}" --format=JobName%100 | head -3 | tail -1 | sed -r 's/\s+//g') || true - if [[ "${job_name}" =~ ${substring} ]]; then - echo "Canceling Slurm Job ${job_name} with: scancel ${job_id}" - scancel "${job_id}" - continue - fi - done + + # cancel pbs jobs + if [[ ${MACHINE_ID} == "wcoss2" ]]; then + job_ids=$(qstat -u "${USER}" | awk '{print $1}') || true + + for job_id in ${job_ids}; do + job_name=$(qstat -f "${job_id}" | grep Job_Name | awk '{print $3}') || true + if [[ "${job_name}" =~ ${substring} ]]; then + echo "Canceling PBS Job ${job_name} with: qdel ${job_id}" + qdel "${job_id}" + continue + fi + done + # cancel slurm jobs + else + job_ids=$(squeue -u "${USER}" -h -o "%i") + + for job_id in ${job_ids}; do + job_name=$(sacct -j "${job_id}" --format=JobName%100 | head -3 | tail -1 | sed -r 's/\s+//g') || true + if [[ "${job_name}" =~ ${substring} ]]; then + echo "Canceling Slurm Job ${job_name} with: scancel ${job_id}" + scancel "${job_id}" + continue + fi + done + fi +} + + +function get_pr_case_list () { + + ############################################################# + # loop over every yaml file in the PR's ci/cases + # and create an run directory for each one for this PR loop + ############################################################# + for yaml_config in "${HOMEgfs}/ci/cases/pr/"*.yaml; do + case=$(basename "${yaml_config}" .yaml) || true + echo "${case}" + done +} + +function get_pslot_list () { + + local RUNTESTS="${1}" + + ############################################################# + # loop over expdir directories in RUNTESTS + # and create list of the directory names (pslot) with the hash tag + ############################################################# + for pslot_dir in "${RUNTESTS}/EXPDIR/"*; do + pslot=$(basename "${pslot_dir}") || true + echo "${pslot}" + done + +} + +function get_pslot () { + + local RUNTESTS="${1}" + local case="${2}" + + ############################################################# + # loop over expdir directories in RUNTESTS + # and return the name of the pslot with its tag that matches the case + ############################################################# + for pslot_dir in "${RUNTESTS}/EXPDIR/"*; do + pslot=$(basename "${pslot_dir}") + check_case=$(echo "${pslot}" | rev | cut -d"_" -f2- | rev) || true + if [[ "${check_case}" == "${case}" ]]; then + echo "${pslot}" + break + fi + done + +} + +function create_experiment () { + + local yaml_config="${1}" + cd "${HOMEgfs}" || exit 1 + pr_sha=$(git rev-parse --short HEAD) + case=$(basename "${yaml_config}" .yaml) || true + export pslot=${case}_${pr_sha} + + source "${HOMEgfs}/ci/platforms/config.${MACHINE_ID}" + source "${HOMEgfs}/workflow/gw_setup.sh" + + "${HOME}/workflow/create_experiment.py" --yaml "${yaml_config}" + } diff --git a/ci/scripts/utils/ci_utils_wrapper.sh b/ci/scripts/utils/ci_utils_wrapper.sh new file mode 100755 index 0000000000..51c392fb99 --- /dev/null +++ b/ci/scripts/utils/ci_utils_wrapper.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +HOMEgfs="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." >/dev/null 2>&1 && pwd )" +source "${HOMEgfs}/ush/detect_machine.sh" + +utitilty_function="${1}" + +source "${HOMEgfs}/ci/scripts/utils/ci_utils.sh" +${utitilty_function} "${@:2}" diff --git a/sorc/build_all_stub.sh b/sorc/build_all_stub.sh new file mode 100755 index 0000000000..6221b6bb52 --- /dev/null +++ b/sorc/build_all_stub.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +set -eu + +echo "Ran build all stub in ${PWD}" > "BUILD_STUB_RAN_$(date +%d%H%M)"