Как запустить задание Jenkins на нескольких узлах из конвейера (выполняется только одно задание) - PullRequest
0 голосов
/ 20 мая 2019

У меня есть задание Jenkins, настроенное как Scripted Jenkins Pipeline , которое:

  1. Проверяет код из GitHub
  2. объединяет изменения разработчика
  3. создает отладочный образ

затем предполагается разделить на 3 отдельных параллельных процесса, один из которых создает релиз-версию кода и выполняет его модульное тестирование. Предполагается, что два других процесса идентичны: образ отладки переносится на цель и выполняются различные тесты.

Цели определены в Дженкинсе как slave_1 и slave_2 и им обоим присвоена метка 131_ci_targets

Я использую «параллель» для запуска сборки выпуска и нескольких экземпляров тестового задания. Я опубликую (слегка отредактированную) копию моего сценария конвейера ниже для полной справки, но для вопроса я перепробовал все 3 из следующих вариантов.

  1. Использование одного build вызова с LabelParamaterValue и 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 идентификатор ведомого. 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}
        }

Все вышеперечисленное будет запускать только один запуск Trial_Test_Pipe на slave_2, при условии, что оба slave_1 и slave_2 определены онлайн и имеют доступных исполнителей.

Задание Trial_Test_Pipe - это еще одно задание Jenkins Pipeline, и флажок «Не разрешать одновременные сборки» снят.

Есть мысли о:

  • Почему задание будет запускать только один из прогонов, а не оба?
  • Какое может быть правильное решение?

Для справки: вот моя полная (ish) сценарий работы Дженкинса:

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 голосов
/ 21 мая 2019

Итак ... У меня есть решение для этого ... как, я понимаю, что делать, и почему одно из вышеупомянутых решений не работает.

Победитель Вариант 3 ... причина, по которой он не работал, заключается в том, что код внутри корпуса (часть stage) не проверяется до тех пор, пока этап фактически не будет запущен.В результате строки не расширяются до тех пор, и, поскольку 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
}
...