Now Reading
Quicksort with Jenkins for Enjoyable and No Revenue

Quicksort with Jenkins for Enjoyable and No Revenue

2024-01-25 03:59:57

By Susam Pal on 25 Jan 2024

I first encountered Jenkins in 2007 whereas contributing to the Apache
Nutch challenge. It was known as Hudson again then. The nightly builds
for the challenge ran on Hudson at the moment. I bear in mind sifting
by my emails and reviewing construct end result notifications to maintain
a watch on the patches that acquired merged into the trunk on a regular basis. Sure,
patches and trunk! We had been nonetheless utilizing SVN again then. Hudson was
renamed to Jenkins in 2011.

Since model 2.0 (launched on 20 Apr 2016), Jenkins helps
pipeline scripts written in Groovy as a first-class entity. A
pipeline script successfully defines the construct job. It could possibly outline
construct properties, construct phases, construct steps, and many others. It could possibly even
invoke different construct jobs, together with itself.

Wait! If a pipeline can invoke itself, can we, maybe, clear up a
recursive drawback with it? Completely! That is exactly what we
are going to do on this submit. We’re going to implement quicksort
as a Jenkins pipeline for enjoyable and never a whit of revenue!

Run Jenkins

Before we get started, I need to tell you how to set up Jenkins just
enough to try the experiments presented later in this post on your
local system. This could be useful if you have never used Jenkins
before or if you do not have a Jenkins instance available with you
right now. If you are already well-versed in Jenkins and have an
instance at your disposal, feel free to skip ahead directly to
the
Quicksort part.

The steps under assume a Debian GNU/Linux system. Nonetheless, it
ought to be attainable to do that on any working system so long as you
can run Docker containers. Since software program evolves over time, let me
word down the variations of software program instruments I’m utilizing whereas writing
this submit. Right here they’re:

  • Debian GNU/Linux 12.4 (bookworm)
  • Docker model 20.10.24+dfsg1, construct 297e128
  • Docker picture tagged jenkins/jenkins:2.426.3-lts-jdk17
  • Jenkins 2.426.3

We shall be performing solely quick-and-dirty experiments on this submit,
so we don’t want a production-grade Jenkins occasion. We are going to run
Jenkins briefly in a container. The next steps present how one can
do that and how one can configure Jenkins for the upcoming experiments:

  1. Set up Docker if it isn’t already current on the system. For
    instance, on a Debian system, the next command installs
    Docker:

    sudo apt-get set up docker.io
  2. Now run the Jenkins container with this command:

    sudo docker run --rm -p 8080:8080 jenkins/jenkins:lts
  3. When the container begins, it prints a password in direction of the
    backside of the logs. Copy the password.

  4. Go to http://localhost:8080/
    in an online browser. When the Unlock Jenkins web page
    seems, paste the password and click on Proceed.

  5. On the Customise Jenkins web page, click on Set up
    instructed plugins
    . Alternatively, to keep away from putting in
    pointless plugins, click on Choose plugins to put in,
    deselect all the things besides Pipeline, and
    click on Set up. We’d like the pipeline plugin to carry out
    remainder of the experiment specified by this submit.

  6. On the Create First Admin Consumer web page, enter the small print
    to create a brand new consumer.

  7. On the Occasion Configuration web page, click on Save and
    End
    .

  8. The Jenkins is prepared! web page seems. Click on Begin
    utilizing Jenkins
    .

  9. Go to Construct Executor Standing > Constructed-In Node
    > Configure and alter Variety of executors
    from the default worth of 2 to 10.
    Click on Save.

Good day World

The following steps show how to run your first Jenkins pipeline:

  1. Go to Dashboard > New Item. Enter an item
    name, say, hello, select Pipeline, and
    click OK.

  2. On the next page, scroll down to the Pipeline section
    at the bottom and paste the following pipeline script and
    click Save.

    node {
        echo "hello, world"
    }
    
  3. Now click Build Now. A new build number appears at the
    bottom half of the left sidebar. Click on the build number,
    then click Console Output to see the output of the
    pipeline. The hello, world message should be
    present in the output.

To edit the pipeline script anytime, go to Dashboard, click
on the pipeline, then go to Configure, scroll down to
the Pipeline section, edit the script, and
click Save.

In real world software development, Jenkins is typically configured
to automatically pull some source code from a project repository
maintained under a version control system and then build it using
the pipeline script found in the file named Jenkinsfile
present at the top-level directory of the project. But since we
only intend to perform fun experiments in this post, we will just
paste our pipeline script directly into the pipeline configuration
page on Jenkins as explained above in order to keep things simple.
Jenkins also supports another way of writing pipelines using a
declarative style. They are known as declarative
pipelines
. In this post, however, we will be writing
only scripted pipelines so that we can write simple Groovy
code for our experiments without having to bother about too many
pipeline-specific notions like stages, steps, etc.

Factorial

Now let us write a simple pipeline that calculates the factorial of
a nonnegative integer. This will help us to demonstrate how a build
job can recursively call itself. We are not going to write
something like the following:

properties([
    parameters([
        string(
            name: 'INPUT',
            defaultValue: '0',
            description: 'A nonnegative integer'
        )
    ])
])

def factorial(n) {
    return n == 0 ? 1 : n * factorial(n - 1)
}

node {
    echo "${factorial(params.INPUT as int)}"
}

The code above is an example of a function that calls itself
recursively. However, we want the build job (not the
function) to call itself recursively. So we write the following
instead:

properties([
    parameters([
        string(
            name: 'INPUT',
            defaultValue: '0',
            description: 'A nonnegative integer'
        )
    ])
])

def MAX_INPUT = 10

node {
    echo "INPUT: ${params.INPUT}"
    currentBuild.description = "${params.INPUT} -> ..."

    def n = params.INPUT as int
    if (n > MAX_INPUT) {
        echo "ERROR: Input must not be greater than ${MAX_INPUT}"
    }

    env.RESULT = n == 0 ? 1 : n * (
        build(
            job: env.JOB_NAME,
            parameters: [string(name: 'INPUT', value: "${n - 1}")]
        ).getBuildVariables().RESULT as int
    )

    echo "RESULT: ${env.RESULT}"
    currentBuild.description = "${params.INPUT} -> ${env.RESULT}"
}

This code example demonstrates a few things worth noting:

To run the above pipeline, perform the following steps on the
Jenkins instance:

  1. Go to Dashboard > New Item. Enter an item
    name, say, factorial, select Pipeline, and
    click OK.

  2. On the next page, scroll down to the Pipeline section
    at the bottom and paste the pipeline script code presented
    above.

  3. Click Build Now. The first build sets
    the INPUT build parameter to 0 (the
    default value specified in the pipeline script). The
    result 1 shoud appear in the Console
    Output
    page.

  4. After the first build completes, the Build Now option on
    the left sidebar gets replaced with the Build with
    Parameters
    option. Click it, then enter a number,
    say, 5 and click Build. Now we should see
    Jenkins recursively triggering a total of 6 build jobs and each
    build job printing the factorial of the integer it receives as
    input. The top-level build job prints 120 as its
    result.

Here is a screenshot that shows what the build history looks like on
the left sidebar:

Screenshot of Jenkins build history that shows the builds that are triggered while computing the factorial of 0 and 5
The factorial of 0 computed in build 1 and the factorial of 5
computed in build 2

In the screenshot above, build number 2 is the build we triggered to
compute the factorial of 5. This build resulted in recursively
triggering five more builds which we see as build numbers 3 to 7.
The little input and output numbers displayed below each build
number comes from the currentBuild.description value we
set in the pipeline script.

If we click on build number 7, we find this on the build page:

Screenshot of Jenkins build page that shows build jobs triggered recursively
Build #7 page

This was a simple pipeline that demonstrates how a build job can
trigger itself, pass input to the triggered build and retrieve its
output. We did not do much error checking or handling. We have
kept the code as simple as reasonably possible. The focus here was
only on demonstrating the recursion.

Quicksort

Now we will implement quicksort in Jenkins. Sorting numbers using
the standard library is quite straightforward in Groovy. Here is an
example in the form of Jenkins pipeline:

properties([
    parameters([
        string(
            name: 'INPUT',
            defaultValue: '4, 3, 5, 4, 5, 8, 7, 9, 1',
            description: 'Comma-separated list of integers'
        )
    ])
])

node {
    def numbers = params.INPUT.split('s*,s*').collect {it as int}
    echo "${numbers.sort()}"
}

It can’t get simpler than this. However, we are not here to
demonstrate the standard library methods. We are here to
demonstrate recursion in Jenkins! We write the following pipeline
script instead:

properties([
    parameters([
        string(
            name: 'INPUT',
            defaultValue: '4, 3, 5, 4, 5, 8, 7, 9, 1',
            description: 'Comma-separated list of integers'
        )
    ])
])

def MAX_INPUT_SIZE = 10

node {
    echo "INPUT: ${params.INPUT}"
    currentBuild.description = "${params.INPUT} -> ..."

    def numbers = params.INPUT.split('s*,s*').collect {it as int}
    if (numbers.size() > MAX_INPUT_SIZE) {
        echo "ERROR: Input must not contain more than ${MAX_INPUT_SIZE} integers"
    }

    def pivot = numbers[0]
    def others = numbers.drop(1)
    def lo = others.findAll { it <= pivot }
    def hi = others.findAll { it > pivot }
    def builds = [:]
    def results = [lo: [], hi: []]

    if (lo) {
        builds.lo = {
            results.lo = build(
                job: env.JOB_NAME,
                parameters: [string(name: 'INPUT', value: lo.join(', '))
            ]).getBuildVariables().RESULT.split('s*,s*') as List
        }
    }
    if (hi) {
        builds.hi = {
            results.hi = build(
                job: env.JOB_NAME,
                parameters: [string(name: 'INPUT', value: hi.join(', '))
            ]).getBuildVariables().RESULT.split('s*,s*') as List
        }
    }
    parallel builds

    env.RESULT = (results.lo + [pivot] + results.hi).join(', ')
    echo "RESULT: ${env.RESULT}"
    currentBuild.description = "${params.INPUT} -> ${env.RESULT}"
}

Some of the code is similar to the one in the previous section. For
example, the properties step to set up the build
parameter, the build() call, setting the result
in env.RESULT, etc. should look familiar. Let us pay
attention to what is different.

Firstly, we have two build() calls instead of just one.
In fact, we have two closures with one build() call in
each closure. Then we use the parallel step to execute
both these closures in parallel. In each build job, we pick the
first integer in the input as the pivot, then compare all the
remaining integers with this pivot and separate them
into lo (low numbers) and hi (high
numbers). Then we call the build job recursively to repeat this
algorithm twice: once on the low numbers and again on the high
numbers.

Screenshot of Jenkins build history that shows the builds that are triggered while performing quicksort on a list of integers
Quicksort with recursive Jenkins builds

Unlike most textbook implementations of quicksort which lets the
recursion run all the way to the base case in which an empty list is
received and the recursive call returns without doing anything, the
above implementation is slightly optimised to avoid making recursive
builds when we find that the list of low numbers or the list of high
numbers is empty. We lose a little bit of simplicity by doing this
but it helps in avoiding wasteful build jobs that just receive an
empty list of numbers as input and exit without doing anything
meaningful.

I hope this was fun!

Source Link

What's Your Reaction?
Excited
0
Happy
0
In Love
0
Not Sure
0
Silly
0
View Comments (0)

Leave a Reply

Your email address will not be published.

2022 Blinking Robots.
WordPress by Doejo

Scroll To Top