Testing if a jenkins container finished booting

When running Jenkins as a docker container for test purposes, it is necessary to verify the Jenkins master is fully functional before running the first test cases.
The http interface can be tested with a call to the API such as

curl --silent http://jenkins.host/api/json

It will first fail with Connection reset by peer, then with 503 Server Error: Service Unavailable and return a JSON output after a few seconds.
The Jenkins CLI can be tested by sending the help command.

$ wget -O /tmp/jenkins-cli.jar http://jenkins.host/jnlpJars/jenkins-cli.jar
$ java -jar /tmp/jenkins-cli.jar -s http://jenkins.host help

and it will use port 50000 to connect to the jenkins master. It will first fail with an error such as

SEVERE: I/O error in channel Chunked connection to http://jenkins.host/cli
java.io.StreamCorruptedException: invalid stream header: 0A0A0A0A

meaning the connection to port 50000 failed (the error message is misleading).
It will eventually succeed with

   add-job-to-view
    Adds jobs to view.
  build
...

If security is disabled (which is the default when running the container), a call to the CLI will succeed despite the following error message.

SEVERE: I/O error in channel CLI connection to http://jenkins.host
java.io.IOException: Unexpected termination of the channel

DNS spoofing with RPZ and bind9

When two web services reside on the same LAN, it may be convenient to spoof DNS entries to use the LAN IP instead of the public IP. It can be done using RPZ and bind9.
For instance workbench.dachary.org can be mapped to 10.0.2.21 with

$ cat /etc/bind/rpz.db
$TTL 60
@            IN    SOA  localhost. root.localhost.  (
                          2   ; serial
                          3H  ; refresh
                          1H  ; retry
                          1W  ; expiry
                          1H) ; minimum
                  IN    NS    localhost.

workbench.dachary.org        A    10.0.2.21

The zone is declared in

$ cat /etc/bind/named.conf.local
zone "rpz" {
      type master;
      file "/etc/bind/rpz.db";
      allow-query {none;};
};

and the response-policy is set in the options file with

$ cat /etc/bind/named.conf.options
...
	response-policy { zone "rpz"; };
};

When bind9 is restarted with /etc/init.d/bind9 restart, the mapping can be verified with

$ dig @127.0.0.1 workbench.dachary.org
workbench.dachary.org.	5	IN	A	10.0.2.21

If the bind9 server runs on a docker host, it can be used by docker containers with

docker run  ... --dns=172.17.42.1 ...

Mirror github pull requests locally

Each GitHub pull request is associated with a reference in the target repository. For instance the commit sent to pull request 3948 is the reference refs/pull/3948/head. If GitHub successfully merges the pull request in the target branch, another reference is associated with it, for instance refs/pull/3948/merge.
It is convenient to mirror pull requests in local branches, for instance to trigger GitLab CI or jenkins git-plugin because they only react to commits that belong to branches, not orphaned references. The tip of the branch can be named pull/XXX and its tip reset to the matching merge reference.
The tip of the branch could be set to the head reference but it would be less effective than using the merge because it is based on an older version of the target branch. Whatever test runs on the head, it may fail although it could succeed after the merge.
GitHub does not set the merge and the head reference atomically: the head can be set before GitHub even tries to merge. Similarly, when a pull request is rebased and forced push, there is a window of opportunity for the merge reference to still be about the previous head.
The following shell function takes care of all these border cases and keeps an up to date set of branches accurately reflecting all pull requests from a GitHub repository:

function import_pull_requests() {
    local remote=$1

    local ref
    local remote_head

    git fetch $remote +refs/pull/*:refs/remotes/$remote/pull/*

    git for-each-ref \
        --sort='-committerdate' \
        --format='%(refname) %(objectname)' \
        refs/remotes/$remote/pull/*/head | \
        while read ref remote_head ; do

        local pr=$(echo $ref | perl -pe 's:.*/pull/(.*)/head:$1:')

        # ignore pull requests that cannot merge
        local merge=$(git rev-parse --quiet --verify
            refs/remotes/$remote/pull/$pr/merge)
        test -z "$merge" && continue

        # ignore pull requests for which the merge does not match the
        # remote head, most likely because it has not been updated yet
        # after a rebase was pushed
        local merged_head=$(git rev-parse
            refs/remotes/$remote/pull/$pr/merge^2)
        test "$merged_head" != "$remote_head" && continue

        # nothing to do if the head did not change since we last saw
        # it
        local local_head=$(git rev-parse --quiet --verify
            refs/pull/$pr/head)
        test "$remote_head" = "$local_head" && continue

        # remember the head for the next round
        git update-ref refs/pull/$pr/head $remote_head

        # create/update a branch with the successfull merge of the
        # head
        git update-ref refs/heads/pull/$pr $merge
        echo branch pull/$pr
    done
}

Download the function and the associated tests

Using a cloud image with kvm

It would be convenient to have a virt-builder oneliner such as

$ virt-builder --arch i386 --ssh-inject ~/.ssh/id_rsa.pub fedora-21

to get an image suitable to run and login with

$ qemu-kvm -m 1024 -net user,hostfwd=tcp::2222-:22 \
  -drive file=fedora-21.qcow2 &
$ ssh -p 2222 localhost grep PRETTY /etc/os-release
PRETTY_NAME="Fedora 21 (Twenty One)"

Docker users have a simpler form because there is no need to ssh to enter the container:

$ docker run fedora:21 grep PRETTY /etc/os-release
PRETTY_NAME="Fedora 21 (Twenty One)"

It is not currently possible to use virt-builder as described above because

  • the set of images available by default is limited (no i386 architecture for instance)
  • the –inject-ssh option is only available in the development version

The libguestfs.org toolbox can however be used to implement a script modifying images prepared for the cloud (see ubuntu cloud images for instance):

  • wget the image
    wget -O my.img http://cloud-images.ubuntu.com/trusty/current/trusty-server-cloudimg-i386-disk1.img
    
  • create a config-drive for cloud-init to feed it the ssh public key.
    mkdir -p config-drive
    cat > config-drive/user-data <<EOF
    #cloud-config
    ssh_authorized_keys:
     - $(cat ~/.ssh/id_rsa.pub)
    chpasswd: { expire: False }
    EOF
    cat > config-drive/meta-data <<EOF
    instance-id: iid-123459
    local-hostname: testhost
    EOF
    ( cd config-drive ; LIBGUESTFS_BACKEND=direct virt-make-fs \
      --type=msdos --label=cidata .  ../config-drive.img )
    
  • launch the image with the config drive attached and it will be auto detected
    qemu-kvm -m 1024 -net user,hostfwd=tcp::2222-:22 \
      -drive file=my.img -drive config-drive.img
    

Continue reading “Using a cloud image with kvm”

Ceph make check in a ram disk

When running tests from the Ceph sources, the disk is used intensively and a ram disk can be used to reduce the latency. The kernel must be rebooted to set the ramdisk maximum size to 16GB. For instance on Ubuntu 14.04 in /etc/default/grub (the module name which could be rb or brd depending).

GRUB_CMDLINE_LINUX="brd.rd_size=16777216" # 16GB in KB

the grub configuration must then be updated with

sudo update-grub

After reboot the ram disk is formatted as an ext4 file system and mounted:

$ cat /sys/module/brd/parameters/rd_size
16777216
$ sudo mkfs -t ext4 /dev/ram1
$ sudo mount /dev/ram1 /srv
$ df -h /srv
Filesystem      Size  Used Avail Use% Mounted on
/dev/ram1        16G   44M   15G   1% /srv
$ free -g
             total       used       free     shared    buffers     cached
Mem:            31          0         31          0          0          0
-/+ buffers/cache:          0         31

Cloning ceph, compiling and running tests should now take less than 15 minutes with

$ git clone https://github.com/ceph/ceph
$ cd ceph
$ ./run-make-check.sh

When the ram disk is umounted, some of the memory used by the ram disk is still in use

$ free -g
             total       used       free     shared    buffers     cached
Mem:            31         27          4          0          0         17
-/+ buffers/cache:          9         22
$ sudo umount /srv
$ free -g
             total       used       free     shared    buffers     cached
Mem:            31         18         13          0          0          8
-/+ buffers/cache:          9         22

It can be flushed with

$ sudo blockdev --flushbufs /dev/ram1
$ free -g
             total       used       free     shared    buffers     cached
Mem:            31          9         22          0          0          8
-/+ buffers/cache:          0         31

Continue reading “Ceph make check in a ram disk”