Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Sample parallel pipeline with limits #114

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions pipeline-examples/parallel-with-limits/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Synopsis
Demonstrate an example of a Jenkinsfile to execute a large number of jobs in parallel, while
limiting the total number of jobs that can run concurrently

# Background

The requirement was to execute over 200 long running free style jobs with a limited number of resources. With 40 threads, it took 13 hours to complete the execution of all jobs
- Multiples of this pipeline needed to run during the day on different branches
- It was necessary to prevent a single pipeline from consuming all available threads at one time
- It was required that users be allowed stop the pipeline and all child jobs easily
- It was required to dynamically build the list of jobs to execute. Jobs are added and removed frequently
- It was desired to keep to a minumum the number jobs added to the Jenkins job queue at one time
- All jobs run in a shared pool of VMs used by different teams. It is not possible to manually assign and maintain labels for each team. VMs must be able to be added and removed from the shared pool as needed.
- The Throttle plugin was used for many years, but the performance of the plugin became an issue as the number of nodes grew past 50 and the number of jobs exceded several hundreds


Note: other locking/limiting mechanisms were explored, but the Lockable Resources plugin ended up being the most elegant solution.
If the Lockable Resources plugin allowed the creation of multiple ephemeral resources on the fly, the sample pipeline would be even simpler. At the moment this does not work: lock(label: "mylabel", quantity: 5){}
66 changes: 66 additions & 0 deletions pipeline-examples/parallel-with-limits/parallelWithLimits.groovy
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
This pipeline will execute all jobs in the folder that contains it.
This pipeline uses the LockableResources plugin to control how many parallel jobs run at the same time.
The maximum number of jobs that can run at the same time is specified by the optional RESOURCES parameter.
The optional LABEL parameter can be used to customize how resources are grouped.
If this job is stopped, all child jobs started by this job will be stopped by Jenkins.
*/

import org.jenkins.plugins.lockableresources.LockableResourcesManager

node {
// the label of all resources used by this pipeline
def label = params.getOrDefault('LABEL', currentBuild.rawBuild.project.parent.name)
println "LABEL: $label"

// number of resources to be used by this pipeline
def resources = Integer.valueOf(params.getOrDefault('EXECUTORS', 5))
println "RESOURCES: $resources"

// the list of stages that will be executed in parallel
def parallelStages = [:]

stage('Prepare') {
// setup lockable resources needed for this pipeline
setupLockableResources(label, resources)

// list of jobs to be executed
def jobs = currentBuild.rawBuild.project.parent.items.findAll({
project -> project.name != currentBuild.rawBuild.project.name && !project.isDisabled()
}).collect { job -> return job.name }

println "Total jobs to run: ${jobs.size()}"

jobs.each() { job ->
parallelStages[job] = {
stage(job) {
// Lock one of the resources assigned the specified label
lock(label: label, quantity: 1) {
build(job: job, wait: true, propagate: false, quietPeriod: 10)
}
}
}
}
}

stage('Run') {
// run all stages in parallel
parallel parallelStages
}
}

// Setup lockable ephemeral resources that will be deleted after the pipeline completes.
// Ephemeral resources are not configurable via the Global Configuration screen,
// this is an advantage when working with large number of resources.
@NonCPS
def setupLockableResources(label, executors) {
println "Setting up lockable resources for this build"
def lrm = LockableResourcesManager.get()
for (i = 0; i < executors; i++) {
lrm.createResourceWithLabel("${label}-$i", label)
// Get lockable resource by name
def lockableResource = lrm.fromName("${label}-$i")
// Make resource ephemeral, resources will be automatically deleted when this pipeline ends
lockableResource.setEphemeral(true)
}
}