~8 minute read
This post explains how to configure Jenkins for the FreeBSD Hardware CI Lab. The work done here is based on earlier work using an Intel NUC and Rasberry PI [1][2] along with work pioneered by the main Continuous Integration server, https://ci.freebsd.org/.[3][4][5][6]
lang/python3
devel/jenkins
devel/py-jenkins-job-builder
devel/git
security/sudo
lang/groovy
(optional)- May be useful for creating/testing configurations used by Jenkins.
lang/expect
(optional)- Can be useful for testing expect scripts (or autogenerating scripts using autoexpect) but requires some TCL knowledge.
Setup rc.conf
to enable Jenkins and then start the Jenkins server.
sudo sysrc local_unbound_enable=YES
sudo sysrc jenkins_enable=YES
sudo sysrc jenkins_home=/usr/local/jenkins
sudo sysrc jenkins_args='--webroot=${jenkins_home}/war --httpPort=8180'
sudo service jenkins start
Then navigate to localhost:8180
or <hostname>:8180
where <hostname>
is the hostname of the machine running Jenkins. This will be referred to as <jenkins_url>
from here on.
You should then be prompted to input the initial admin password. Choose "Select plugins to install" and then click "None" at the top to deselect all plugins and continue (you can install all necessary plugins later). Continue to follow the prompts to create the initial admin account (in my case the user is called admin
) and setup Jenkins.
Plugins can be installed at <jenkins_url>/pluginManager/available
. And install the following plugins:
- Git
- Subversion
- Build Timeout
- Clang Scan-Build
- Timestamper
- Parameterized Trigger
- EnvInject
- Warnings Next Generation
- Copy Artifact Plugin
- PostBuildScript
- Conditional Buildstep
- Matrix-Based Security (optional - matrix based authentication for users)
- Embeddable Build Status (optional - get status buttons)
- Email Extension (optional - mailing after builds)
- Green Balls (optional - green for success)
- Blue Ocean (optional - prettier interface)
Create folders for Jenkins to work in.
sudo mkdir -p /jenkins/jails
sudo mkdir -p /jenkins/workspace
chown jenkins:jenkins /jenkins /jenkins/*
Create a sudoers file for jenkins /usr/local/etc/sudoers.d/jenkins
. This will allow jenkins to run the commands it needs to run in superuser without a password.
Cmnd_Alias CI_COMMANDS = /usr/sbin/jail, /usr/sbin/jexec, /sbin/mount, /sbin/umount, /sbin/devfs, /bin/chflags, /bin/rm, /usr/sbin/pkg, /usr/bin/tar, /sbin/ifconfig, /usr/bin/tee, /bin/mkdir, /sbin/mdconfig
Defaults:jenkins !env_reset
jenkins ALL=(root) NOPASSWD: CI_COMMANDS
Clone the the HW CI repository into an easily accessible location. You may want to fork this repository before checking it out.
git clone https://github.com/GeraldNDA/freebsd-ci
NOTE
This repository contains configurations and build scripts used on each build. By default, scripts are cloned from the repository any build that requires it. As a result, if you make any changes don't forget to push them (
git push
) after your commits.
default.yaml
defines default definitions for all jobsmacro.yaml
defines various custom build and post-build tasksdevice-template.yaml
defines templates for building FreeBSD for devices and testing themdevice-test.yaml
defines the branches/target architectures/devices that should be built and tested. This is where the majority of your changes will go.jail.yaml
defines macros related to working in a jailmail-notify.yaml
defines macros related to getting email notificationsscm.yaml
defines macros related to SCMs. This is where support for new repositories and branches should be added.
Job configurations are in the jjb
directory and managed by Jenkins Job Builder (JJB).
In order to modify any configurations, you should first make a jenkins_jobs.ini
file by copying the sample. You'll want to change the username to match the username used. As well you can create an API Token for yourself at <jenkins_url>/user/<username>/configure
and use that as JJB's configured password.
WARNING
While you could use your admin password instead of the API Token, it is not suggested to use a plaintext password with JJB for security reasons.
If you have forked the repository, you'll want to modify default.yaml
and change ci-scripts-git-path
to match the new repo.
NOTE
If the 'master' node is being used, you can also use a local path. This is done by modifying
device-template.yaml
and uncommenting the linescheckout-scripts-local
and commenting out thecheckout-scripts
.
To update Jenkins with the configurations, run the following:
jenkins-jobs --conf jenkins_jobs.ini update --delete-old .
Navigate to <jenkins_url>/configureSecurity/
and under the "Agents" section, enable "TCP port for JNLP agents". If you don't have a firewall, "Random" is a good option.
This will enable you to launch nodes via Java Web Start
Go to <jenkins_url>/computer/new
Ensure the following configurations are set:
Name
can be anything you wantLabels
should includejailer
if this is to be where jails are createdUsage
should beuse this node as much as possible
Launch method
should belaunch agent via Java Web Start
Environment Variables
should have the following values:-
BUILDER_0_IP4
should be some IP address in the same subnet as the IP address used to access the internet.WARNING
If it isn't defined correctly,
pkg
will fail when trying to build FreeBSD.NOTE
Also define
BUILDER_n_IP4
for everyn
th additional executor. UseBUILDER_n_IP6
if IP6 IP addresses are used instead. -
BUILDER_JFLAG
is the number of cores build jobs should use. Usesyctl kern.smp.cpus
to choose this number. -
BUILDER_MEMORY
is the amount of memory build jobs should use (e.g.8G
). This should be based on the amount of memory the node/computer has. -
BUILDER_NETIF
is the network interface the build jails should use. This should match the network interface to the internet (e.g.em0
) -
BUILDER_ZFS_PARENT
is the parent directory of jails. This should be/jenkins/jails
. The build scripts do not use ZFS.NOTE
If you are using ZFS you may want to use the jail scripts for: https://github.com/freebsd/freebsd-ci/blob/master/scripts/jail instead of the included ones.
-
The following setup is tested on the master machine (that does not use ZFS). If you are using a distributed machine that does use ZFS you may benefit from looking at the steps used in https://wiki.freebsd.org/Jenkins/Setup. These steps should also be applicable on a distributed machine (with Jenkins installed).
First, enable jails:
sudo sysrc jail_enable=YES
sudo service jail start
The following tasks should be run as the jenkins
user:
sudo su jenkins
Load the Jenkins scripts in the /jenkins
directory
cd /jenkins
git clone https://github.com/GeraldNDA/jenkins-agent-scripts
cd jenkins-agent-scripts
Copy agent.conf.sample
into agent.conf
. You'll need to update jenkins_url
, agentname
and secret
values to match the value what's given at <jenkins_url>/computer/${agentname}/
. At this url, you should see "Run from agent command line" which gives a command like this: java -jar agent.jar -jnlpUrl ${jenkins_url}/computer/${agentname}/agent-agent.jnlp -secret ${secret}
.
Configure the crontab to keep the instance running:
crontab crontab
To start the agent, run
daemon -cf keepalive.sh
You should see at <jenkins_url>/computer/${hostname}/
that the node is running successfully.
Given the following jobs
in FreeBSD-device-tests
(in device-test.yaml
)
- 'FreeBSD-{branch}-aarch64-device_tests':
disable_build: true
target: arm64
target_arch: aarch64
warnscanner: clang
device_name:
- pinea64
# Each 32-bit arm device has it's own CONF file so they have their own build job.
- 'FreeBSD-{branch}-beaglebone-device_tests':
target: arm
target_arch: armv7
warnscanner: clang
device_name: beaglebone
conf_name: BEAGLEBONE
Let's say you wanted to add another 32-bit device like the jetsontk1
and another aarch64 device like the rockpine64
and two amd devices (desktop1
and performancepc1
) (Assuming they are already configured for net booting). After doing so your configuration file would like so:
- 'FreeBSD-{branch}-aarch64-device_tests':
disable_build: true
target: arm64
target_arch: aarch64
warnscanner: clang
device_name:
- pinea64
- rockpine64
- 'FreeBSD-{branch}-amd-device_tests':
disable_build: true
target: amd
target_arch: amd
warnscanner: clang
device_name:
- desktop1
- performancepc1
- 'FreeBSD-{branch}-beaglebone-device_tests':
target: arm
target_arch: armv7
warnscanner: clang
device_name: beaglebone
conf_name: BEAGLEBONE
- 'FreeBSD-{branch}-jetsontk1-device_tests':
target: arm
target_arch: armv7
warnscanner: clang
device_name: beaglebone
conf_name: JETSON-TK1
Additionally, add the following build groups for each job
to add device/architecture specific builds as necessary.
- job-group:
name: 'FreeBSD-{branch}-amd-device_tests'
jobs:
- 'FreeBSD-{branch}-{target_arch}-build_devtest'
- 'FreeBSD-device-{branch}-{device_name}-test'
- job-group:
name: 'FreeBSD-{branch}-jetsontk1-device_tests'
jobs:
- 'FreeBSD-{branch}-{device_name}-build_devtest'
- 'FreeBSD-device-{branch}-{device_name}-test
Also, add these devices to the conserver.cf
file so that they can be accessed by the power control and serial scripts. A template conserver.cf
file is described here.
Additionally, you'll have to add jobs for each of these. At the time of writing the repository includes a sample device specific (beaglebone) and architecture specific (pinea64) build job and test job. Thus it should be sufficient to just copy them. And then follow the steps for modifying tests to ensure they will work. For example:
cp -r jobs/FreeBSD-device-head-beaglebone-test jobs/FreeBSD-device-head-jetsontk1-test
cp -r jobs/FreeBSD-device-head-beaglebone-build_devtest jobs/FreeBSD-device-head-jetsontk1-build_devtest
cp -r jobs/FreeBSD-device-head-pinea64-test jobs/FreeBSD-device-head-desktop1-test
cp -r jobs/FreeBSD-device-head-pinea64-test jobs/FreeBSD-device-head-performancepc1-test
cp -r jobs/FreeBSD-head-aarch64-build_devtest jobs/FreeBSD-device-head-amd-build_devtest
For architecture-specific builds, the defaults should work as expected. However, for device-specific builds you will need to update some files.
Each build job contains:
job/<jobname>/build.sh
- the script that runs the buildscripts/build/build-world-kernel-head.sh
- the script which performs the build and creates the artifacts used for installing the build (and NFS booting the devices)job/<jobname>/jail.conf
- File which contains configurations for jail environmentjob/<jobname>/src.conf
- File which contains additional build configurations. For device-specific builds, it should containKERNCONF=<DEVICE>-NFS
.GENERIC-NFS
used for architecture specific builds. Contains a Kernel configuration based onGENERIC
but adds NFS support<DEVICE>-NFS
used for device-specific builds. Contains a kernel configuration based on<DEVICE>
(or whichever kernel config is correct for this device).
For device tests, the default tests simply install the files and turn on the device. They expect FreeBSD to boot successfully and will then power off the device.
Each test job depends on the following files:
job/<jobname>/build.sh
- the script that runs the testsscripts/test/extract-artifacts.sh
- the script that extracts the built files and installs them in a directoryscripts/test/run-device-tests.sh
- runs the device scripts and loads any overrides to these scriptsscripts/test/device_tests/test_device.py
- a boilerplate test script that powers on the device and verifies that FreeBSD boots successfully. Will also source any additional tests if specified.job/<jobname>/device_tests/tests.py
- the sourced python file for specifying additional tests
Example job/<jobname>/device_tests/tests.py
:
# Either (or both) of these lists should be defined.
# The lists should be iterables of functions as specified below.
to_run = []
panic_actions = []
class TestError(Exception):
pass
def check_status(ch, msg):
res = ch.expect(["0", r"\d+"])
if res == 1:
raise TestFailure(f"Failed to do task '{msg}'")
ch.console.expect(ch.CMDLINE_RE)
def example_test(ch):
"""
`ch` is the console handler
- before running this function, the device has already been booted and logged into.
`ch.timeout` is the boot timeout
- default value is 5 minutes, otherwise every expect timeout is 1 minute.
`ch.console` is the Pexpect instance for running commands
- see Pexpect.spawn documentation for more info
`ch.device` is the device instance.
`ch.device.name` is the device name
`ch.device.turn_on/turn_off` are methods for controlling the power to the device.
`ch.CMDLINE_RE` is the regex that matches the command line PS1
"""
try:
ch.console.sendline("ls /")
ch.console.expect(ch.CMDLINE_RE)
check_status(ch, "Look for files at root")
ch.console.sendline("dmesg")
ch.console.expect(ch.CMDLINE_RE)
check_status(ch, "Check boot messages")
except TestFailure as e:
return (False, str(e))
# Can return (status, message) or nothing.
# return (True, "All's good!")
def bt_on_panic(ch):
# before running this function, the device panicked during boot
ch.console.sendline("bt")
# You can add multiple functions to either list. The will be run in the order of the list.
to_run.append(example_test)
panic_actions.append(bt_on_panic)
You can specify additional tests to run after logging in or after a panic occurs (to extract debug information).
NOTE
Patches to this issue have been submitted and it should be resolved soon.
In the <jenkins_home>
directory (i.e. /usr/local/jenkins
) remove any JNA files. You can use the following to do so.
sudo find war -name "*jna*" -exec sudo rm {} +
sudo find plugins -name "*jna*" -exec sudo rm {} +
Also delete any lingering workspaces (these may contain lock files which will prevent further checkouts)
sudo rm -rf /jenkins/workspace/*
And then restart jenkins
sudo service jenkins restart
Checking out from SVN should work properly now.
BUILDER_0_IP4
must be on the same subnet as the ip address used to access the interface. You can use a tool like this: http://meridianoutpost.com/resources/etools/network/two-ips-on-same-network.php to verify that your IP is on the same subnet i.e. if em0
uses ip address inet 192.168.32.164 netmask 0xffffff00 broadcast 192.168.32.255
then any address from 192.168.32.1
to 192.168.32.255
would be on the same subnet. Avoid using values at the highest range, because those are usually used as the "broadcast address" and avoid values too low as they are usually used as the "router address".
[1]: https://ci-dev.freebsd.org/job/rpi3-test/
[2]: https://github.com/lwhsu/testbed
[3]: https://wiki.freebsd.org/Jenkins
[4]: https://wiki.freebsd.org/Jenkins/Setup