0

I have a Jenkins pipeline that I'd like to run on either a params-specified agent or master. The pipeline code that implements this is:

pipeline {
  agent { label "${params.agent} || master" }
  ...
}

I've surmised, from the following posts, that the || operator needs to be inside the (double-)quotes:
Can I define multiple agent labels in a declarative Jenkins Pipeline?
https://serverfault.com/questions/1074089/how-to-apply-multiple-labels-to-jenkins-nodes
Jenkinsfile - agent matching multiple labels

When I run this job, it seems to always run on master.
When I switch the order of ${params.agent} and master in the agent statement, it seems to still always run on master.
If I remove " || master" from the agent statement, then the job runs on the params-specified agent.

Question: Is my observation that Jenkins "prefers" master a coincidence, or is there something wrong with the syntax that's making Jenkins default to master?
Is there some way to have Jenkins prefer not-master so that I can test validity of the agent statement?

StoneThrow
  • 5,314
  • 4
  • 44
  • 86
  • It seems you have an assumption that Jenkins will evaluate your `parameters {..}` clause first, and then your `agent {..}` clause later on. This assumption may be incorrect. The actual label that Jenkins is evaluating might be `"null || master"`, so a selection of `master` would be the only reasonable option. Check it by running `agent { label "${params.agent}" }` and see. – MaratC Mar 30 '22 at 08:59
  • @MaratC - agreed: and that's exactly the reason I was wanting to test this assumption. "Check it by running `agent {label "${params.agent}"}`" -- already tested this, as I noted in the OP: If I remove ` || master` from the agent statement, then the job runs on the params-specified agent. – StoneThrow Mar 30 '22 at 14:32
  • 1
    Ok, then it's a matter of Jenkins trying to stick to the same node. If you want to keep off master as much as possible, you need to implement the logic of choosing the node in the code. – MaratC Mar 30 '22 at 16:57

2 Answers2

1

h pipeline/job has a trusted agent list, more job succeed on the agent, the agent sit in the top on the list, pipeline/agent will pick top agent from list.

If your pipeline already run on master for several times and all succeed, even you give another agent to choose, pipeline always pick the most trusted agent firstly

yong
  • 13,357
  • 1
  • 16
  • 27
1

So, when Jenkins encounters the line

  agent { label "${params.agent} || master" }

it will do exactly one of the following:

  1. schedule your job on one of the nodes that match that label; or
  2. get stuck until there's a node that matches that label, or until aborted.

With regards to option 1, there's no guarantee that it will do a round-robin, a random choice, or prefer some nodes but not the others, etc. In practice, when several nodes match, Jenkins will prefer the node that ran your pipeline in the past. This is a reasonable behavior — if there's a workspace already on that node, some operations (like git checkout) may happen faster, saving time.

With regards to option 2, that's also a reasonable behavior. We actually use that to schedule a job on a non-existing label, while manipulating the labels to produce one that would match.

So, there's nothing wrong with your syntax, and Jenkins is behaving as designed.

If you want to implement some custom rule — like "always try a different node", or "try to use master as little as possible" — you have to code that.

Example pipeline (note I haven't checked it):

import hudson.model.Hudson

properties([
    parameters([
        string(name: 'DEPLOY_ON', defaultValue: 'node_name',
                description: 'try to run on this node, or master'),
    ])
])

resulting_node_name = ''

pipeline {
    agent { node { label 'master' } }
    stages {
        stage ('Do on master') {
            steps {
                script {
                    resulting_node_name = params.DEPLOY_ON
                    // note: this gets node by name, but you can get by label if you wish
                    def slave = Jenkins.instance.getNode(resulting_node_name)
                    
                    if (slave == null) {
                        currentBuild.result = 'FAILURE'
                    }

                    def computer = slave.computer
                    if (computer == null || computer.getChannel() == null || slave.name != params.DEPLOY_ON) {
                        println "Something wrong with the slave object, setting master"
                        resulting_node_name = 'master'
                    }

                    printSlaveInfo(slave)

                    computer = null
                    slave = null
                }
            }
        }

        stage('Do on actual node') {
            agent { node { label resulting_node_name } }
            steps {
                script {
                    println "Running on ${env.NODE_NAME}"
                }
            }

        }
    }
}

@NonCPS
def printSlaveInfo(slave) {
    // some info that you can use to choose the least-busy, best-equipped, etc.
    println('====================')
    println('Name: ' + slave.name)
    println('getLabelString: ' + slave.getLabelString())
    println('getNumExectutors: ' + slave.getNumExecutors())
    println('getRemoteFS: ' + slave.getRemoteFS())
    println('getMode: ' + slave.getMode())
    println('getRootPath: ' + slave.getRootPath())
    println('getDescriptor: ' + slave.getDescriptor())
    println('getComputer: ' + slave.getComputer())
    def computer = slave.computer
    println('\tcomputer.isAcceptingTasks: ' + computer.isAcceptingTasks())
    println('\tcomputer.isLaunchSupported: ' + computer.isLaunchSupported())
    println('\tcomputer.getConnectTime: ' + computer.getConnectTime())
    println('\tcomputer.getDemandStartMilliseconds: ' + computer.getDemandStartMilliseconds())
    println('\tcomputer.isOffline: ' + computer.isOffline())
    println('\tcomputer.countBusy: ' + computer.countBusy())
    println('\tcomputer.getLog: ' + computer.getLog())
    println('\tcomputer.getBuilds: ' + computer.getBuilds())
    println('====================')
}
MaratC
  • 6,418
  • 2
  • 20
  • 27