I have several (few hundreds) of files to run test on (each test takes few minutes).
Running sequentially is not acceptable and neither all together. So I am looking for something like a producer-consumer.
I tried pipeline jobs and parallel command the following way:
def files = findFiles glob: 'test_files/*'
def branches = [:]
files.each{
def test_command = "./test ${it}"
branches["${it}"] = { sh "${test_command} ${it}"}
}
stage name:'run', concurrency:2
parallel branches
Problem:
All the tasks are launch at the same time (OOM and all the fun)
Doesn't have the same introspection as the Jenkins parallel step, but since it seems not to support a fixed pool you can use xargs to achieve the same result:
def files = findFiles glob: 'test_files/*'
def branches = [:]
// there's probably a more efficient way to generate the list of commands
files.each{
sh "echo './test ${it}' >> tests.txt"
}
sh 'cat tests.txt | xargs -L 1 -I {} -P 2 bash -c "{}"'
The -P argument is the one that specifies a fixed number of 2 (or N) processes should always be running. Other tools like GNU Parallel offer even more tuning on how many processes should be used.
You can also try to use the lock step from the Lockable Resources plugin, the node step targeting a fixed number of executors. However this seems too much overhead to me unless your single tests are already taking tens of second each.
Related
In my Jenkinsfile I want to dynamically find the unity version using a python script like so:
environment {
UNITY_EDITOR = bat(script: "py $WORKSPACE/get_versions.py --unity", returnStdout: true).trim()
UNITY_BASE = "C:/Program Files/Unity/Hub/Editor/$UNITY_EDITOR/Editor/Unity.exe"
UNITY_WRAPPER = "UnityBatchWrapper -silent-crashes -no-dialogs -batchmode -quit -unityPath \"$UNITY_BASE\""
}
post {
always {
script {
echo "Returning license"
licenseReturnStatus = bat (
script: "$UNITY_WRAPPER -returnlicense",
returnStatus: true
) == 0
}
}
From other stackoverflow answers this seems like it should work, but instead my Jenkins job errors out during the post-build step because $UNITY_WRAPPER isn't defined:
groovy.lang.MissingPropertyException: No such property: UNITY_WRAPPER for class: groovy.lang.Binding
I'm thinking the batch step is what's failing, even though Jenkins doesn't complain about it. I've also tried using $env.WORKSPACE and %WORKSPACE% and that doesn't work either.
I'm beginning to think $WORKSPACE doesn't exist til after the environments step...
Turns out I didn't have Python installed since it was an ephemeral GCP builder and I hadn't updated the node label yet.
For anyone reading this that has trouble with bat commands - be sure to put an # sign in front of your command like "#py ..." or else the command will be echoed in the output. Also trim your output so it doesn't have CRLF in it.
We are using PACT (https://pact.io/) in our project. The can-i-deploy check, whether the deployment can be executed is done like this:
Jenkins Environment Variables (see <JENKINS_URL>/configure)
PACT_BROKER_URL
PACT_RUBY_STANDALONE_VERSION
(PACT_RUBY_STANDALONE_VERSION from https://github.com/pact-foundation/pact-ruby-standalone/releases)
Jenkinsfile:
environment {
SOURCE_BRANCH_NAME = sourceBranchName(env.BRANCH_NAME, env.CHANGE_BRANCH)
}
...
def sourceBranchName(String branchName, String changeBranchName) {
return changeBranchName == null ? branchName : changeBranchName
}
...
stage('can-i-deploy') {
steps {
withCredentials([usernamePassword(credentialsId: 'XXX', passwordVariable: 'PACT_BROKER_PASSWORD', usernameVariable: 'PACT_BROKER_USERNAME')]) {
sh "curl -LO https://github.com/pact-foundation/pact-ruby-standalone/releases/download/v${PACT_RUBY_STANDALONE_VERSION}/pact-${PACT_RUBY_STANDALONE_VERSION}-linux-x86_64.tar.gz"
sh "tar xzf pact-${PACT_RUBY_STANDALONE_VERSION}-linux-x86_64.tar.gz"
echo "Performing can-i-deploy check"
sh "pact/bin/./pact-broker can-i-deploy --broker-base-url=${PACT_BROKER_URL} --broker-username=${PACT_BROKER_USERNAME} --broker-password=${PACT_BROKER_PASSWORD} --pacticipant=project-frontend --latest=${env.SOURCE_BRANCH_NAME} --pacticipant=project-backend --latest=${env.SOURCE_BRANCH_NAME} --pacticipant=other-project-backend --latest=${env.SOURCE_BRANCH_NAME}"
}
}
}
Is there a more elegant way to do this?
I can't speak for Jenkins, but there are two things worth changing in the arguments sent to can-i-deploy:
It's no recommended to use the latest flag. You should use the --version to indicate the version of the application you are deploying and the --to flag to denote the target environment (latest runs the risk of race conditions with builds giving you false positives/negatives)
You don't need to specify compatible other projects, can-i-deploy will automatically detect all dependent components.
So it would look more like this
can-i-deploy --broker-base-url=${PACT_BROKER_URL} --broker-username=${PACT_BROKER_USERNAME} --broker-password=${PACT_BROKER_PASSWORD} --pacticipant=project-frontend --version some-sha-1234 --to prod
If you have access to docker, you might prefer to use our container.
P.S. if you simply export the following environment variables you can drop them off the argument list also:
PACT_BROKER_BASE_URL (please note the minor difference from what you're using)
PACT_BROKER_USERNAME
PACT_BROKER_PASSWORD
Trying to follow GNU Parallel as job queue with named pipes with GNU parallel 20201222, I run into issues of parallel not executing the last commands piped into it via tail -n+0 -f.
To demonstrate, I have 3 terminals open:
# terminal 1
true > jobqueue
tail -n+0 -f jobqueue | parallel
# terminal 2
tail -n+0 -f jobqueue | cat
Adding a single small test command to the queue:
# terminal 3
echo "echo test" >> jobqueue
Only terminal 2 prints "echo test", gnu parallel does not output anything.
# terminal 3
for i in `seq 10`; do echo "echo $i" >> jobqueue; done
Only terminal 2 prints "echo 1", ..., "echo 10" (one in each line), gnu parallel does not output anything.
# terminal 3
for i in `seq 100`; do echo "echo $i" >> jobqueue; done
Terminal 2 prints "echo 1", ..., "echo 100". Terminal 1 prints the lines "test", "1", ..., "10", "1", ..., "99", the last line "100" is missing.
Rerunning tail -n+0 -f jobqueue | parallel outputs all up to "99". Rerunning this with --resume --joblog log appended, outputs one more line ("100") but then also lags behind once new lines are added to joblog. For GNU parallel 20161222, the initial run only gets to line "84".
How can I force gnu parallel to flush its input queue on every line?
From man parallel:
There is a a small issue when using GNU parallel as queue system/batch manager: You have to submit JobSlot number of jobs before they will start, and after that you can submit one at a time, and job will start immediately if free slots are available. Output from the running or completed jobs are held back and will only be printed when JobSlots more jobs has been started (unless you use --ungroup or --line-buffer, in which case the output from the jobs are printed immediately). E.g. if you have 10 jobslots then the output from the first completed job will only be printed when job 11 has started, and the output of second completed job will only be printed when job 12 has started.
In other words: The jobs are running. Output is delayed. It is easier to see if you instead of using echo in your example use touch unique-file-name.
I have a Jenkins job for a db rollback script that uses a choice parameter for each environment (using NodeLabel Parameter Plugin).
I want the jobs to able to be run concurrently, but only for different environments.
"Execute concurrent builds if necessary" is enabled.
E.g. If the job is running for LIVE, allow someone to run the job again for TEST (this works). However, if LIVE is already running and someone runs the job for LIVE again, then do not run.
This plugin seems to suit my needs but is not shown on the list of available plugins in Manage Jenkins.
https://wiki.jenkins-ci.org/display/JENKINS/Concurrent+Run+Blocker+Plugin
Are there any other ways around this?
There's a solution with existing Jenkins plugins:
Create a Freestyle project named like Starter for concurrent builds exclusively on nodes.
☑ This build is parameterized
Node [NodeLabel Parameter Plugin]
Name: NODE
Choice Parameter
Name: JOB
Choices: ... the jobs' names you'd like to start with this ...
Build
Conditional step (single) [Conditional BuildStep Plugin]
Run?: Not
!: Execute Shell
Command:
#!/bin/bash +x -e
# Bash 4 needed for associative arrays
# From http://stackoverflow.com/questions/37678188/jenkins-stop-concurrent-job-with-same-parameter
echo ' Build --> Conditional step (single) --> Execute Shell'
echo " Checking whether job '$JOB' runs on node '$NODE'"
echo ' Creating array'
declare -A computers
# ------------------------------------------------------------------------
# Declare your nodes and their executors here as mentioned, for instance,
# in the API URI 'http://<jenkins>/computer/(master)/executors/0/api/xml':
computers=( # ^^^^^^ ^
[master]="0 1 2 3"
[slave]="0 1"
)
# Note: Executor indices DO NOT conform to the numbers in Jenkins'
# Build Executor Status UI.
# ------------------------------------------------------------------------
echo " Checking executors of node '$NODE'"
for computer in ${!computers[#]} ; do
for executorIdx in ${computers[$computer]} ; do
if [[ $computer == $NODE ]] ; then
if [[ "$computer" == "master" ]] ; then
node="(${computer})"
else
node=$computer
fi
url="${JENKINS_URL}/computer/${node}/executors/${executorIdx}/api/xml?tree=currentExecutable\[url\]"
echo " $url"
xml=$(curl -s $url)
#echo $computer, $executorIdx, $xml
if [[ "$xml" == *"/job/${JOB}"* ]] ; then
echo " Job '$JOB' is already building on '$computer' executor index '$executorIdx'"
echo ' Exiting with 1'
exit 1
fi
fi
done
done
echo ' Exiting with 0'
Builder: Set the build result
Result: Aborted
Conditional step (single)
Run?: Current build status
Builder: Trigger/call build on other projects
Build Triggers:
Projects to build: $JOB [ignore the error message]
Node Label parameter
Name: NODE [or how you call it in your downstream job(s)]
Node: $NODE
I would like to have a post-build hook or similar, so that I can have the same output as e. g. the IRC plugin, but give that to a script.
I was able to get all the info, except for the actual build status. This just doesn't work, neither as a "Post-build script", "Post-build task", "Parameterized Trigger" aso.
It is possible with some very ugly workarounds, but I wanted to ask, in case someone has a nicer option ... short of writing my own plugin.
It works as mentioned with the Groovy Post-Build Plugin, yet without any extra quoting within the string that gets executed. So I had to put the actual functionality into a shell script, that does a call to curl, which in turn needs quoting for the POST parameters aso.
def result = manager.build.result
def build_number = manager.build.number
def env = manager.build.getEnvironment(manager.listener)
def build_url = env['BUILD_URL']
def build_branch = env['SVN_BRANCH']
def short_branch = ( build_branch =~ /branches\//).replaceFirst("")
def host = env['NODE_NAME']
def svn_rev = env['SVN_REVISION']
def job_name = manager.build.project.getName()
"/usr/local/bin/skypeStagingNotify.sh Deployed ${short_branch} on ${host} - ${result} - ${build_url}".execute()
Use Groovy script in post-build step via Groovy Post-Build plugin. You can then access Jenkins internals via Jenkins Java API. The plugin provides the script with variable manager that can be used to access important parts of the API (see Usage section in the plugin documentation).
For example, here's how you can execute a simple external Python script on Windows and output its result (as well as the build result) to build console:
def command = """cmd /c python -c "for i in range(1,5): print i" """
manager.listener.logger.println command.execute().text
def result = manager.build.result
manager.listener.logger.println "And the result is: ${result}"
For this I really like the Conditional Build Step plugin. It's very flexible, and you can choose which actions to take based on build failure or success. For instance, here's a case where I use conditional build step to send a notification on build failure:
You can also use conditional build step to set an environment variable or write to a log file that you use in subsequent "execute shell" steps. So for instance, you might create a build with three steps: one step to compile code/run tests, another to set a STATUS="failed" environment variable, and then a third step which sends an email like The build finished with a status: ${STATUS}
Really easy solution, maybe not to elegant, but it works!
1: Catch all the build result you want to catch (in this case SUCCESS).
2: Inject an env variable valued with the job status
3: Do the Same for any kind of other status (in this case I catch from abort to unstable)
4: After you'll be able to use the value for whatever you wanna do.. in this case I'm passing it to an ANT script! (Or you can directly load it from ANT as Environment variable...)
Hope it can help!
Groovy script solution:-
Here I am using groovy script plugin to take the build status and setting it to the environmental variable, so the environmental variable can be used in post-build scripts using post-build task plugin.
Groovy script:-
import hudson.EnvVars
import hudson.model.Environment
def build = Thread.currentThread().executable
def result = manager.build.result.toString()
def vars = [BUILD_STATUS: result]
build.environments.add(0, Environment.create(new EnvVars(vars)))
Postscript:-
echo BUILD_STATUS="${BUILD_STATUS}"
Try Post Build Task plugin...
It lets you specify conditions based on the log output...
Basic solution (please don't laugh)
#!/bin/bash
STATUS='Not set'
if [ ! -z $UPSTREAM_BUILD_DIR ];then
ISFAIL=$(ls -l /var/lib/jenkins/jobs/$UPSTREAM_BUILD_DIR/builds | grep "lastFailedBuild\|lastUnsuccessfulBuild" | grep $UPSTREAM_BUILD_NR)
ISSUCCESS=$(ls -l /var/lib/jenkins/jobs/$UPSTREAM_BUILD_DIR/builds | grep "lastSuccessfulBuild\|lastStableBuild" | grep $UPSTREAM_BUILD_NR)
if [ ! -z "$ISFAIL" ];then
echo $ISFAIL
STATUS='FAIL'
elif [ ! -z "$ISSUCCESS" ]
then
STATUS='SUCCESS'
fi
fi
echo $STATUS
where
$UPSTREAM_BUILD_DIR=$JOB_NAME
$UPSTREAM_BUILD_NR=$BUILD_NUMBER
passed from upstream build
Of course "/var/lib/jenkins/jobs/" depends of your jenkins installation