如何通过管道在多个节点上触发Jenkins作业(仅执行一个作业)

时间:2019-05-20 15:31:02

标签: jenkins groovy jenkins-pipeline

我有一个Jenkins工作,配置为脚本化Jenkins管道

  1. 从GitHub检出代码
  2. 合并开发人员变更
  3. 构建调试映像

然后应该将其拆分为3个单独的并行进程-其中一个构建代码的发布版本并对其进行单元测试。 其他两个过程应该是相同的,调试映像会闪现到目标上并运行各种测试。

目标在詹金斯中被标识为 slave_1 slave_2 ,并且都分配了标签 131_ci_targets

我正在使用“并行”来触发发布版本以及测试作业的多个实例。我将在下面发布我的脚本管道的副本(略作编辑)以供参考,但是对于这个问题,我尝试了以下所有三个选项。

  1. 使用将buildLabelParamaterValue设置为allNodesMatchingLabel的单个true呼叫。在此TEST_TARGETS是标签 131_ci_targets
parallel_steps = [:]
parallel_steps["release"] = { // Release build and test steps
}

parallel_steps["${TEST_TARGETS}"] = {
    stage("${TEST_TARGETS}") {
        build job: 'Trial_Test_Pipe',
              parameters: [string(name: 'TARGET_BRANCH', value: "${TARGET_BRANCH}"),
                           string(name: 'FRAMEWORK_VERSION', value: "${FRAMEWORK_VERSION}"),
                           [$class: 'LabelParameterValue',
                            name: 'RUN_NODE', label: "${TEST_TARGETS}",
                            allNodesMatchingLabel: true,
                            nodeEligibility: [$class: 'AllNodeEligibility']]]
    }
} // ${TEST_TARGETS}

stage('Parallel'){
    parallel parallel_steps
} // Parallel
  1. 使用带有build的单个NodeParamaterValue调用和所有节点的列表。在此TEST_TARGETS中,它还是标签,而test_nodes是2个字符串的列表:[slave_1, slave_2]
parallel_steps = [:]
parallel_steps["release"] = { // Release build and test steps
}

test_nodes = hostNames("${TEST_TARGETS}")

parallel_steps["${TEST_TARGETS}"] = {
    stage("${TEST_TARGETS}") {
        echo "test_nodes: ${test_nodes}"
        build job: 'Trial_Test_Pipe',
              parameters: [string(name: 'TARGET_BRANCH', value: "${TARGET_BRANCH}"),
                           string(name: 'FRAMEWORK_VERSION', value: "${FRAMEWORK_VERSION}"),
                           [$class: 'NodeParameterValue',
                            name: 'RUN_NODE', labels: test_nodes,
                            nodeEligibility: [$class: 'AllNodeEligibility']]]
    }
} // ${TEST_TARGETS}

stage('Parallel'){
    parallel parallel_steps
} // Parallel

3:使用多个阶段,每个阶段带有一个build的单个NodeParamaterValue调用和一个仅包含1个从属ID的列表。 test_nodes是字符串列表:[slave_1, slave_2],而第一个调用通过slave_1,第二个调用通过slave_2

        for ( tn in test_nodes ) {
            parallel_steps["${tn}"] = {
                stage("${tn}") {
                    echo "test_nodes: ${test_nodes}"
                    build job: 'Trial_Test_Pipe',
                          parameters: [string(name: 'TARGET_BRANCH', value: "${TARGET_BRANCH}"),
                                       string(name: 'FRAMEWORK_VERSION', value: "${FRAMEWORK_VERSION}"),
                                       [$class: 'NodeParameterValue',
                                        name: 'RUN_NODE', labels: [tn],
                                        nodeEligibility: [$class: 'IgnoreOfflineNodeEligibility']]],
                          wait: false
                }
            } // ${tn}
        }

假设所有slave_2slave_1均已定义,在线且具有可用的执行程序,则上述所有操作仅会触发slave_2上的“ Trial_Test_Pipe”运行一次。

Trial_Test_Pipe作业是另一个Jenkins Pipeline作业,并且未选中“不允许并发构建”复选框。

任何想法:

  • 为什么这项工作只会触发一次运行,而不会触发两次?
  • 什么是正确的解决方案?

现在供参考:这是我的完整的詹金斯脚本工作:

import hudson.model.*
import hudson.EnvVars
import groovy.json.JsonSlurperClassic
import groovy.json.JsonBuilder
import groovy.json.JsonOutput
import java.net.URL

def BUILD_SLAVE=""

// clean the workspace before starting the build process
def clean_before_build() {
    bat label:'',
        script: '''cd %GITHUB_REPO_PATH%
                   git status
                   git clean -x -d -f
                   '''
}

// Routine to build the firmware
// Can build Debug or Release depending on the environment variables
def build_the_firmware() {
    return
    def batch_script = """
        REM *** Build script here
        echo "... Build script here ..."
        """

    bat label:'',
        script: batch_script
}

// Copy the hex files out of the Build folder and into the Jenkins workspace
def copy_hex_files_to_workspace() {
    return
    def batch_script = """
        REM *** Copy HEX file to workspace:
        echo "... Copy HEX file to workspace ..."
        """

    bat label:'',
        script: batch_script
}

// Updated from stackOverflow answer: https://stackoverflow.com/a/54145233/1589770
@NonCPS
def hostNames(label) {
    nodes = []
    jenkins.model.Jenkins.instance.computers.each { c ->
        if ( c.isOnline() ){
            labels = c.node.labelString
            labels.split(' ').each { l ->
                if (l == label) {
                    nodes.add(c.node.selfLabel.name)
                }
            }
        }
    }
    return nodes
}

try {
    node('Build_Slave') {
        BUILD_SLAVE = "${env.NODE_NAME}"
        echo "build_slave=${BUILD_SLAVE}"

        stage('Checkout Repo') {
            // Set a desription on the build history to make for easy identification
            currentBuild.setDescription("Pull Request: ${PULL_REQUEST_NUMBER} \n${TARGET_BRANCH}")

            echo "... checking out dev code from our repo ..."
        } // Checkout Repo

        stage ('Merge PR') {
            // Merge the base branch into the target for test
            echo "... Merge the base branch into the target for test ..."
        } // Merge PR

        stage('Build Debug') {
            withEnv(['LIB_MODE=Debug', 'IMG_MODE=Debug', 'OUT_FOLDER=Debug']){
                clean_before_build()
                build_the_firmware()
                copy_hex_files_to_workspace()

                archiveArtifacts "${LIB_MODE}\\*.hex, ${LIB_MODE}\\*.map"
            }
        } // Build Debug

        stage('Post Build') {
            if (currentBuild.resultIsWorseOrEqualTo("UNSTABLE")) {
                echo "... Send a mail to the Admins and the Devs ..."
            }
        } // Post Merge

    } // node

    parallel_steps = [:]
    parallel_steps["release"] = {
        node("${BUILD_SLAVE}") {
            stage('Build Release') {
                withEnv(['LIB_MODE=Release', 'IMG_MODE=Release', 'OUT_FOLDER=build\\Release']){
                    clean_before_build()
                    build_the_firmware()
                    copy_hex_files_to_workspace()

                    archiveArtifacts "${LIB_MODE}\\*.hex, ${LIB_MODE}\\*.map"
                }
            } // Build Release
            stage('Unit Tests') {
                echo "... do Unit Tests here ..."
            }
        }
    } // release

    test_nodes = hostNames("${TEST_TARGETS}")

    if (true) {
        parallel_steps["${TEST_TARGETS}"] = {
            stage("${TEST_TARGETS}") {
                echo "test_nodes: ${test_nodes}"
                build job: 'Trial_Test_Pipe',
                      parameters: [string(name: 'TARGET_BRANCH', value: "${TARGET_BRANCH}"),
                                   string(name: 'FRAMEWORK_VERSION', value: "${FRAMEWORK_VERSION}"),
                                   [$class: 'LabelParameterValue',
                                    name: 'RUN_NODE', label: "${TEST_TARGETS}",
                                    allNodesMatchingLabel: true,
                                    nodeEligibility: [$class: 'AllNodeEligibility']]]
            }
        } // ${TEST_TARGETS}
    } else if ( false ) {
        parallel_steps["${TEST_TARGETS}"] = {
            stage("${TEST_TARGETS}") {
                echo "test_nodes: ${test_nodes}"
                build job: 'Trial_Test_Pipe',
                      parameters: [string(name: 'TARGET_BRANCH', value: "${TARGET_BRANCH}"),
                                   string(name: 'FRAMEWORK_VERSION', value: "${FRAMEWORK_VERSION}"),
                                   [$class: 'NodeParameterValue',
                                    name: 'RUN_NODE', labels: test_nodes,
                                    nodeEligibility: [$class: 'AllNodeEligibility']]]
            }
        } // ${TEST_TARGETS}
    } else {
        for ( tn in test_nodes ) {
            parallel_steps["${tn}"] = {
                stage("${tn}") {
                    echo "test_nodes: ${test_nodes}"
                    build job: 'Trial_Test_Pipe',
                          parameters: [string(name: 'TARGET_BRANCH', value: "${TARGET_BRANCH}"),
                                       string(name: 'FRAMEWORK_VERSION', value: "${FRAMEWORK_VERSION}"),
                                       [$class: 'NodeParameterValue',
                                        name: 'RUN_NODE', labels: [tn],
                                        nodeEligibility: [$class: 'IgnoreOfflineNodeEligibility']]],
                          wait: false
                }
            } // ${tn}
        }
    }

    stage('Parallel'){
        parallel parallel_steps
    } // Parallel
} // try
catch (Exception ex) {
    if ( manager.logContains(".*Merge conflict in .*") ) {
        manager.addWarningBadge("Pull Request ${PULL_REQUEST_NUMBER} Experienced Git Merge Conflicts.")
        manager.createSummary("warning.gif").appendText("<h2>Experienced Git Merge Conflicts!</h2>", false, false, false, "red")
    }

    echo "... Send a mail to the Admins and the Devs ..."

    throw ex
}

1 个答案:

答案 0 :(得分:0)

所以……我对此有一个解决方案……例如,我知道该怎么做,以及上述解决方案之一为何不起作用。

获胜者是 选项3 ... ...无效的原因是外壳(stage部分)中的代码是“在阶段实际运行之前评估t。结果,字符串直到那时才展开,并且由于tn到那时固定在slave_2上,因此这是两个并行流上使用的值。

在此处的Jenkins示例中……[https://jenkins.io/doc/pipeline/examples/#parallel-from-grep] ...附件是从函数transformIntoStep返回的,通过这样做,我能够对字符串进行早期评估,因此可以并行执行在两个从站上运行的步骤。

如果您在这里寻找答案,希望对您有所帮助。如果确实如此,请随时给我提意见。干杯:)

我最终的脚本化jenkinsfile看起来像这样:

import hudson.model.*
import hudson.EnvVars
import groovy.json.JsonSlurperClassic
import groovy.json.JsonBuilder
import groovy.json.JsonOutput
import java.net.URL

BUILD_SLAVE=""
parallel_steps = [:]

// clean the workspace before starting the build process
def clean_before_build() {
    bat label:'',
        script: '''cd %GITHUB_REPO_PATH%
                   git status
                   git clean -x -d -f
                   '''
}

// Routine to build the firmware
// Can build Debug or Release depending on the environment variables
def build_the_firmware() {
    def batch_script = """
        REM *** Build script here
        echo "... Build script here ..."
        """

    bat label:'',
        script: batch_script
}

// Copy the hex files out of the Build folder and into the Jenkins workspace
def copy_hex_files_to_workspace() {
    def batch_script = """
        REM *** Copy HEX file to workspace:
        echo "... Copy HEX file to workspace ..."
        """

    bat label:'',
        script: batch_script
}

// Updated from stackOverflow answer: https://stackoverflow.com/a/54145233/1589770
@NonCPS
def hostNames(label) {
    nodes = []
    jenkins.model.Jenkins.instance.computers.each { c ->
        if ( c.isOnline() ){
            labels = c.node.labelString
            labels.split(' ').each { l ->
                if (l == label) {
                    nodes.add(c.node.selfLabel.name)
                }
            }
        }
    }
    return nodes
}

def transformTestStep(nodeId) {
    return {
        stage(nodeId) {
            build job: 'Trial_Test_Pipe',
                  parameters: [string(name: 'TARGET_BRANCH', value: TARGET_BRANCH),
                               string(name: 'FRAMEWORK_VERSION', value: FRAMEWORK_VERSION),
                               [$class: 'NodeParameterValue',
                                name: 'RUN_NODE', labels: [nodeId],
                                nodeEligibility: [$class: 'IgnoreOfflineNodeEligibility']]],
                  wait: false
        }
    }
}

def transformReleaseStep(build_slave) {
    return {
        node(build_slave) {
            stage('Build Release') {
                withEnv(['LIB_MODE=Release', 'IMG_MODE=Release', 'OUT_FOLDER=build\\Release']){
                    clean_before_build()
                    build_the_firmware()
                    copy_hex_files_to_workspace()

                    archiveArtifacts "${LIB_MODE}\\*.hex, ${LIB_MODE}\\*.map"
                }
            } // Build Release
            stage('Unit Tests') {
                echo "... do Unit Tests here ..."
            }
        }
    }
}


try {
    node('Build_Slave') {
        BUILD_SLAVE = "${env.NODE_NAME}"
        echo "build_slave=${BUILD_SLAVE}"

        parallel_steps["release"] = transformReleaseStep(BUILD_SLAVE)

        test_nodes = hostNames("${TEST_TARGETS}")
        for ( tn in test_nodes ) {
            parallel_steps[tn] = transformTestStep(tn)
        }

        stage('Checkout Repo') {
            // Set a desription on the build history to make for easy identification
            currentBuild.setDescription("Pull Request: ${PULL_REQUEST_NUMBER} \n${TARGET_BRANCH}")

            echo "... checking out dev code from our repo ..."
        } // Checkout Repo

        stage ('Merge PR') {
            // Merge the base branch into the target for test
            echo "... Merge the base branch into the target for test ..."
        } // Merge PR

        stage('Build Debug') {
            withEnv(['LIB_MODE=Debug', 'IMG_MODE=Debug', 'OUT_FOLDER=Debug']){
                clean_before_build()
                build_the_firmware()
                copy_hex_files_to_workspace()

                archiveArtifacts "${LIB_MODE}\\*.hex, ${LIB_MODE}\\*.map"
            }
        } // Build Debug

        stage('Post Build') {
            if (currentBuild.resultIsWorseOrEqualTo("UNSTABLE")) {
                echo "... Send a mail to the Admins and the Devs ..."
            }
        } // Post Merge

    } // node

    stage('Parallel'){
        parallel parallel_steps
    } // Parallel

} // try
catch (Exception ex) {
    if ( manager.logContains(".*Merge conflict in .*") ) {
        manager.addWarningBadge("Pull Request ${PULL_REQUEST_NUMBER} Experienced Git Merge Conflicts.")
        manager.createSummary("warning.gif").appendText("<h2>Experienced Git Merge Conflicts!</h2>", false, false, false, "red")
    }

    echo "... Send a mail to the Admins and the Devs ..."

    throw ex
}