Our project uses Jenkins pipeline for the automation tests and packaging. The pipeline is defined in a Jenkinsfile script having multiple stages. Multiple jobs (triggered by the push events from different dev branches) might be running in parallel.
Now that we need to test a function against an external system which has limited resources.
Let's say, that system has 10 resource "slots" for tests: #1, #2, ..., #10. When each pipeline job wants to test that function, it needs to "reserve" a slot number (just an integer will suffice) and then the test program talks to the external system using the slot number (like a token or something). When the job finishes, it releases the number.
Is it possible in Jenkins? In other words, the Jenkins needs to maintain a small integer array for all parallel jobs. Whenever a job asks for a "slot" number, it finds a free number in the array and lock it up.
I Googled it and found a Jenkins plugin called "lockable resource plugin" (Jenkins - How to handle concurrent jobs that use a limited resource pool?), but it can only do "semaphore-like" resource management, not enough for my case. I not only need to know if the resource is used up or not, but also need to know which one is available at the moment.
Thank you guys!!
In my experience the "lockable resources" plugin is quite suitable for something like this. You would define one numbered resource per "slot" but give them all the same label. When you want to acquire a "slot", you pass that label to the pipeline lock step, which waits until a slot is available and gives you the name of that slot. Then it's just a simple matter of extracting the number from the resource name.
I'm using a similar scheme to acquire test VMs, where it works reliably.
Details:
Install the lockable resources plugin
Go to Manage Jenkins > Configure System > Scroll down to "Lockable Resources"
Add one lockable resource per slot. Name them like "slot-1", "slot-2" and so on (the dash makes it easy to extract the number later on). Assign the same label to all of them, e. g. "slots". This is how you logically group the resources.
Use the lock step in your pipeline code to acquire a slot. The key here is to use the quantity: 1 argument to acquire only a single slot (by default it would try to acquire all resources that match the given label). It will be released automatically when the code block of the lock step ends. When all slots are in use, the lock step waits until the next slot becomes available.
pipeline {
agent any
stages {
stage('Stage 1') {
steps {
script {
// Acquire a single slot
lock(label: 'slots', quantity: 1, variable: 'slotName') {
// Extract the slot number from the resource name
def slotNumber = slotName.split('-')[1] as int
// Code that uses the slot
echo "Acquired slot $slotNumber"
// Slot gets released automatically
}
}
}
}
}
}
Related
I am looking for way to speed up our Build-Pipeline.
The biggest impact would be to do certain things only if they have not been done.
So basically, I have already parameterized some optional stages which works fine, but there are some things I'd like to skip if there was a execution before which was successfull.
I have searched the docs, especially the section ref. when but the was nothing that did the job.
So the question is: Is there something I can do in a declarative pipeline to always skip a stage, except when
It has never been run successfully before
The last time it ran was not successful (eg. if I allow for its execution to be forced by passing a parameter)
I though about using the Build Number (eg only run it on the first execution of the pipeline), but this does't cut it for 2 reasons
I'm using milestones to prevent multiple executions of the pipe for a given branch/PR
If the first run fails, it would never be tried again
Oh, and I also thought about putting all of the logic in post -> regression and forcing the stage to fail on the first pipeline run, but this doesn't seem to be a good idea either.
One option ,although not ideal for large scale, can be to store the diffrent states as Global Environment Variables. (Manage Jenkins -> Configure System -> Global properties -> Environment variables).
These parameters are available for all jobs and can store parameters in a 'server wide' scope.
You can then update or set them via code using the following groovy method:
#NonCPS
def updateGlobalEnvVariable(String name, String value) {
def globalNodeProperties = Jenkins.getInstance().getGlobalNodeProperties()
def envVarsNodePropertyList = globalNodeProperties.getAll(hudson.slaves.EnvironmentVariablesNodeProperty.class)
if (envVarsNodePropertyList == null || envVarsNodePropertyList.size() == 0) {
def envVarsNodePropertyClass = this.class.classLoader.loadClass('hudson.slaves.EnvironmentVariablesNodeProperty')
globalNodeProperties.add(envVarsNodePropertyClass.newInstance())
}
envVarsNodePropertyList.get(0).getEnvVars().put(name, value)
}
This function will create the parameter if it does not exists or update its value in case it already exists. Also this function should better be placed in a Shared Library from which it will be available for all pipelines.
A nice advantage for this technique is that you can always 'reset' the different stages from the configuration page, However if you need to store multiple stages it can overflow the configuration page with a bit too much information.
Maybe you could use custom shared-workspace and then create/store some state file or something that could be used by your next executions like lastexecfailed.state and then try to locate the file on the shared workspace at the beginning of your execution.
I have several agents configured in Jenkins.
For one of my pipeline jobs execution, I wish to choose between two of my agents i.e. MYHOST11-ANSIBLE-SLAVE and MYHOST22-ANSIBLE-SLAVE which ever is available. Thus, if MYHOST11-ANSIBLE-SLAVE is unavailable my Jenkins pipeline job should switch to using MYHOST22-ANSIBLE-SLAVE
Can you please suggest what changes do I need in my below pipeline code ?
pipeline {
agent {
node {
label 'MYHOST11-ANSIBLE-SLAVE'
}
}
stages {
stage('Precheck') {
steps {
sh "echo Im from Jenkins>/tmp/jenkinsmoht.txt"
Note: I want my pipeline to choose only between the two agents I mentioned as only they have ansible with my pipeline invokes.
Other agents don't have ansible thus my pipeline would fail.
I think you have your nomenclature strategy a little skewed...
Let's say you have two nodes, named "HOST11" and "HOST22". Those two have ansible installed. Other nodes (eg "HOST33") do not. Those are the names of the Nodes, reflective of the Hosts the agents run on.
You want to configure your Nodes (/computer/<NodeName>/configure) with "labels" according to their characteristics, in this case "ansible", thus creating a "pool of nodes" of similar configuration.
You then use the label of the characteristic ("ansible") to assign the job to the pool of servers with that corresponding label (and characteristic). By assigning labels to nodes, you can specify the resources you want to use for specific jobs, and set up graceful queuing for your jobs.
eg:
agent {
node {
label 'ansible'
}
Jenkins will then pick the first available node with the matching label and run there, unless that node is not available. It will then try the next. If none are available, the job will remain queued.
If you choose to use a Host Name (which in truth is also just a "label", then you can only run on that one node).
Another distinction: "Available Node" in Jenkins implies on-line. If all executors are busy, it is still "available". Jenkins jobs are "sticky", it will wait until a node it has previously run on has an available executor. That can also result in the first node being overloaded. If that's a problem, look to install the "Least Load" plugin which will act as a load balancer using various criteria.
See this post to further constrain jobs using multiple labels.
ps: If your nodes are similar but not identical, you may be use the "Node Properties" or the "Slave Setup" plugin to make them transparently compatible to your job (eg: set VAR to different values/paths).
This is not a normal "Run this job on many slaves" question!
Cookie-cutter answers will not do.
Use Case
I am attempting to use the Jenkins Pipeline to instrument a test for distributed software that involves one "client" and many "servers" by allocating Jenkins slaves for those roles, running the components on the slaves and then tearing it all down. We can pretend that "servers" will run a web server and "client" runs "wget" against them.
Considerations
I'm using the scripted pipeline (not declarative). Essentially I need all the "servers" to be up when I run the client logic on the "client" node.
Obviously sequential node{} blocks won't work because I need all slaves to be up concurrently. Parallel may work, and I am open to this option, but it seems that it will be hard to debug.
My solution
So here is what I've come up with so far. This is a simplified example, there might be logic after each node closure (set up each server) and near the end of each node closure (clean up each server), or it can all be done by the client, doesn't really matter.
def allocatedServerList = []
// Allocate 3 "servers" and then 1 client. Keep servers allocated.
node {
allocatedServerList << env.NODE_NAME
node {
allocatedServerList << env.NODE_NAME
node {
allocatedServerList << env.NODE_NAME
node {
//this is the client
sh "run some client work against ${allocatedServerList}"
//eg: ssh to each server, start some service, pound it for a while, shut them down
}
}
}
}
Surprisingly, this works fine.
Can anyone suggest a better approach? The downside with nested code is that you can't change the number of nodes easily (without recursive methods, which make it unreadable)
to run on all nodes you can use something like this:
The example shows how to trigger jobs on all Jenkins nodes from Pipeline.
Summary: * The script uses NodeLabel Parameter plugin to pass the job name to the payload job. * Node list retrieval is being performed using Jenkins API, so it will require script approvals in the Sandbox mode
To see this example from jenkins.io please visit : Trigger Job On All Nodes
We are using Jenkins Pipeline Multibranch Plugin with Blue Ocean.
Through my reading, I believe it is quite common to tie your project's build number to the Jenkins run, as this allows traceability from an installed application through to the CI system, then to the change in source control, and then onto the issue that prompted the change.
The problem is that for each branch, the run number begins at 0. For a project with multiple branches, it seems impossible to guarantee a unique build number.
You can get the Git branch name from $GIT_BRANCH and add this to $BUILD_NUMBER to make an ID that's unique across branches (as long as your company doesn't do something like get themselves taken over by a large corporation that migrates you to another Jenkins server and resets all the build numbers: to protect against that, you might want to use $BUILD_URL).
Only snag is $GIT_BRANCH contains the / character, plus any characters you used when naming the branch, and these may or may not be permitted in all the places where you want an ID. ($BUILD_URL is also going to contain characters like : and /) If this is an issue, one workaround would be to delete unwanted characters with tr:
export MY_ID=$(echo $GIT_BRANCH-$BUILD_NUMBER | tr -dc [A-Za-z0-9-])
(-dc means delete the complement of these characters, so A-Z, a-z, 0-9 and - are the characters you want to keep.)
Maybe instead of a unique (global numeric) build number you might want to try a unique (global) build display name?
According to "pipeline syntax: global variables reference" currentBuild.displayName is a writable property. So you could e.g. add additional information to the build number (in order to make it globally unique) and use that string in subsequent artifact/application build steps (to incorporate that in the application's version output for your desired traceability), e.g. something like:
currentBuild.displayName = "${env.BRANCH_NAME}-${currentBuild.id}"
Using the build's schedule or start time formatted (currentBuild.timeInMillis) as a readable date, or using the SCM revision might be also useful, e.g. resulting in "20180119-091439-rev149923".
See also:
https://groups.google.com/forum/#!msg/jenkinsci-users/CDuWAYLz2zI/NLxwOku4AwAJ
https://support.cloudbees.com/hc/en-us/articles/220860347-How-to-set-build-name-in-Pipeline-job
One way is to have a Job that is being called from all branches and using it's build number. That job can be just a normal pipeline job with a dummy Jenkinsfile like echo "hello". Then just call it like this
def job = build job: 'build number generator', quietPeriod: 0, parameters: [
string(value: "${BRANCH_NAME}-${BUILD_NUMBER}", name: 'UID')
]
def BNUMBER = job.getNumber().toString()
currentBuild.displayName = "build #"+BNUMBER
echo BNUMBER
Not sure if that UID parameter is needed but it forces all calls into "build number generator" job to be unique so Jenkins wouldn't optimize builds that happen at same time to use same "build number generator" job.
You can use an external service to manage a unique build number for your project. It helps to get unique build numbers across branches and across CI servers too.
https://www.nextbuildnumber.net/
I have a pipeline script where I want to kick off parallel builds on two different build machines, and once it's all done, perform some post-run activity like unstashing and publishing test results, creating an archive from all of the binaries and libraries generated, etc.
It basically looks like this, where 'master' is a MacOS machine and we've got a separate machine for Windows builds:
// main run stuff
parallel (
"mac" : {
node ('master') {
for (job in macJobs) {
job.do()
}
}
},
"windows" : {
node ('windowsMachine') {
for (job in windowsJobs) {
job.do()
}
}
}
}
node('master') {
// post-run stuff
}
If I kick off a single build with this script then it completes no problem.
But, if a second build kicks off while the first is still working through the parallel block (i.e. its polling SCM and someone did a push while the first build is still going), then the post-run block doesn't get executed until the second job's parallel block completes.
There's obviously a priority queue based on who gets to request the node first, but I'd like for one complete script run to finish before Jenkins moves on to the next, so we don't end up with jobs piling up on the post-run block which normally only takes a couple of seconds to complete...
How do I modify the script to do this? I've tried wrapping it all in a single stage block, but no luck there.
I might guess that part of the problem lies around your post-run stuff sharing your master node with one of your parallel tasks. Especially if your master node only has a one or two executors, which would definitely put it at 100% load with more than one concurrent build.
If this sounds like it might be part of your problem, you can try giving your post-run stuff a dedicated node to guarantee availability independent of triggered builds. Or increase the executors available on your master node to guarantee that even if there are a couple concurrent builds, there are still executors available for those post-runs.
Jenkins doesn't really care about the origin of a block to execute. So if you have two jobs running at the same time, and each uses the master node in two separate blocks. There is a real chance the first block of each job will execute together before either of their second block is reached. If your executor only has two executors available, then you may even end up with a starved queue for that node, but at the very least, an executor must become available before either of those second blocks can begin.