#!/bin/bash # $@: Jenkins labels # Prints nodes corresponding to jenkins labels. print_nodes_in_labels () { ( set -euf -o pipefail local labels="$@" local label for label in $labels; do wget --retry-connrefused --waitretry=1 -O - https://ci.linaro.org/label/$label/api/json?pretty=true 2>/dev/null | grep nodeName | cut -d: -f 2 | sed -e 's/"//g' done ) } # $@: Jenkins labels, typically tcwg-t[kx]1_{32/64}-test # Returns node from one of the labels with least number of running containers. print_node_with_least_containers () { ( set -euf -o pipefail local tester_labels="$@" local tester local testers local load_value local tester_min_load_name="" local tester_min_load_value="999" local ret # Re. --random-sort below: shuffle node list to mitigate races # when starting multiple containers at the same time testers=$(print_nodes_in_labels $tester_labels | sort --random-sort) for tester in $testers; do ret=0 load_value=$(timeout 10s ssh ${tester}.tcwglab docker -H :2375 ps | wc -l) || ret=$? if [ $ret -eq 0 ]; then if [ "$load_value" -lt "$tester_min_load_value" ]; then tester_min_load_name=$tester tester_min_load_value=$load_value fi fi done echo $tester_min_load_name ) } # $1: Jenkins tcwg-*-build label # Prints out architecture for container image print_arch_for_label () { ( set -euf -o pipefail local label="$1" case $label in tcwg-x86_64-*) echo amd64 ;; tcwg-x86_32-*) echo i386 ;; tcwg-tx1_64-*|tcwg-apm_64-*) echo arm64 ;; tcwg-tk1_32-*|tcwg-tx1_32-*|tcwg-apm_32-*) echo armhf ;; *) echo "ERROR: Unsupported label: $label" >&2; exit 1 ;; esac ) } # $1: Jenkins tcwg-*-build label # Prints out host type print_type_for_label () { ( set -euf -o pipefail echo "$1" | sed -e "s/^tcwg-\(.*\)-build\$/\1/" ) } # $1: Jenkins tcwg node # Prints out hardware type, which is second to last field print_hw_id_for_node () { ( set -euf -o pipefail local hw_id hw_id=$(echo "$1" | sed -e "s/^tcwg.*-\([^-]\+\)-[0-9]\+/\1/") if [ x"$hw_id" = x"$1" ]; then echo "ERROR: Bad node name: $1" >&2 exit 1 fi echo "$hw_id" ) } # $1: Jenkins $NODE_NAME # Prints DNS hostname print_host_for_node () { ( set -euf -o pipefail local host="$1.tcwglab" if ! host "$host" >& /dev/null; then echo "Error: no DNS entry for $host" >&2 exit 1 fi echo "$host" ) } # $1: Host name or "localhost". # Prints docker-friendly arch of host print_arch_for_host () { ( set -euf -o pipefail local host="$1" local arch case "$host" in "localhost"*) arch=$(uname -m) case "$arch" in "aarch64") arch="arm64" ;; "arm"*) arch="armhf" ;; "x86_64") arch="amd64" ;; *) echo "ERROR: Unknown uname -m arch: $arch" >&2; exit 1 ;; esac echo "$arch" ;; *) # While not strictly correct, print_arch_for_label is relaxed # enough to handle this. print_arch_for_label "$host" ;; esac ) } # $1: target triplet # Prints tester label for remote cross-testing print_tester_label_for_target () { ( set -euf -o pipefail local target="$1" case "$target" in aarch64-linux-gnu_ilp32) # We test ILP32 using QEMU KVM, and TX1s run 3.10 kernel that # doesn't support KVM. Test on APM builders for now. echo "tcwg-apm_64-build" ;; aarch64-linux*) echo "tcwg-tx1_64-test" ;; armv8l-linux*) echo "tcwg-tx1_32-test" ;; arm-linux*) echo "tcwg-tk1_32-test" ;; esac ) } # Run command on remote machine in given directory via ssh on a given port # "$1" -- [:[:[:]]] # "$2, $3, etc" -- command and its arguments # E.g., remote_exec dev-01.tcwglab::/tmp find -name "my file.bak" remote_exec () { ( set -euf -o pipefail local host="$(echo $1 | cut -d: -f 1)" local port="$(echo $1 | cut -s -d: -f 2)" local dir="$(echo $1 | cut -s -d: -f 3)" local opts="$(echo $1 | cut -s -d: -f 4)" shift local -a cmd cmd=() # Add quotes to every parameter for i in "$@"; do cmd+=($(printf '%q' "$i")); done ssh $opts ${port:+-p$port} $host "${dir:+cd "$(printf '%q' "$dir")" &&} exec ${cmd[@]}" ) } # Resolve git ref to sha1 # $1 -- repo directory # $2 -- branch, tag or refspec # $3 -- (optional) remote name git_rev_parse () { ( set -euf -o pipefail local dir="$1" local ref="$2" local remote="origin" local ret=0 if [ $# -ge 3 ]; then remote="$3" fi ( cd "$dir" # Convert git branch/tag names into SHA1 local sha1 try_ref case "$ref" in "refs/"*) try_ref="$ref";; *) try_ref="refs/remotes/$remote/$ref" ;; esac sha1=$(git rev-parse --short "$try_ref") || ret=$? if [ $ret -ne 0 ]; then # Assume that $ref is already a SHA1 sha1=$(git rev-parse --short "$ref") if [ x"$sha1" = x"" ]; then echo "ERROR: Cannot parse $ref in repo $dir" >&2 exit 1 fi fi echo "$sha1" ) ) } # Clone or update a git repo # $1 -- repo directory # $2 -- branch, tag or refspec # $3 -- master git repo # $4 -- reference git repo (to speedup initial cloning) clone_or_update_repo () { ( set -euf -o pipefail local dir="$1" local ref="$2" local url="$3" local refopt="" if [ $# -ge 4 ]; then refopt="--reference $4" fi if ! [ -d "$dir/.git" ]; then rm -rf "$dir" git clone $refopt "$url" "$dir" fi ( cd "$dir" # Update from URL. git remote set-url origin "$url" git remote update -p git fetch -q origin "refs/changes/*:refs/changes/*" # Convert git branch/tag names into SHA1 local sha1 sha1=$(git_rev_parse "$dir" "$ref") # Checkout git reset --hard git clean -dfx git checkout --detach "$sha1" ) ) } # Wget files from URL that may have wildcards; only the last "basename" # part of URL is allowed to contain wildcards. Safe to use on normal URLs. # Return N-1 of files downloaded, or 127 if no files were downloaded. # $1 -- URL # $2,... -- additional parameters to wget wget_wildcard_url () { ( set -eu -o pipefail local url="$1" shift local url_basename url_basename="$(basename "$url")" local tmpdir tmpdir="$(mktemp -d)" wget_opts="" case "$(echo "$url" | cut -d/ -f3)" in *".tcwglab") wget_opts="$wget_opts --no-check-certificate" ;; esac wget --progress=dot:giga -r --no-parent --no-directories --level 1 "--directory-prefix=$tmpdir" -A "$url_basename" $wget_opts "$@" "$(dirname "$url")/" local count=-1 for i in "$tmpdir"/$url_basename; do mv "$i" . count=$((count+1)) done rm -rf "$tmpdir" return $count ) } # Fetch a tarball using wget_wildcard_url and untar it into a directory # named after the tarball. # $1 -- URL # $2 -- base directory to untar to # $3 -- extra tar options, e.g. "--strip-components 1" untar_url () { ( set -eu -o pipefail local url="$1" local basedir="$2" local taropts="$3" local tarball local dirname wget_wildcard_url "$url" > /dev/null 2>&1 # shellcheck disable=SC2046 tarball="$(ls $(basename "$url"))" dirname="$basedir/${tarball%.tar*}" mkdir "$dirname" tar xf "$tarball" --directory "$dirname" $taropts echo "$dirname" ) } # Wait until the ssh server is ready to accept connexions # $1: host # $2: port # $3: retry count (optional) # Returns 0 on success, 1 in case of error wait_for_ssh_server () { ( set -euf -o pipefail local session_host="$1" local session_port="$2" local count="${3:-20}" while [ $count -gt 0 ] do ssh -p $session_port $session_host true && break echo "SSH server not ready, waiting....." sleep 5 count=$((count - 1)) done if [ $count -eq 0 ]; then echo "ERROR: SSH server did not respond ($session_host:$session_port)" return 1 fi return 0 ) } # Print CPU share allocation for $task and $weight. # $1: task # $2: weight print_cpu_shares () { ( set -euf -o pipefail local task="$1" local weight="$2" local cpus cpus=$(( $weight * 1000 )) # 1000 cpu shares per executor echo "$cpus" ) } # Print memory allocation for $task and $weight. # $1: task # $2: weight print_memory_limit () { ( set -euf -o pipefail local task="$1" local weight="$2" local memory case "$task" in build) memory=$(( $weight * 10000 )) ;; # 10GB per executor test) memory=$(( $weight * 750 )) ;; # 0.75GB per session esac echo "$memory" ) } # Print PID allocation for $task and $weight. # $1: task # $2: weight print_pids_limit () { ( set -euf -o pipefail local task="$1" local weight="$2" local pids pids=$(( $weight * 5000 )) # 5000 processes per executor echo "$pids" ) } # Print default bind mounts for $task # $1: task print_bind_mounts () { ( set -euf -o pipefail local task="$1" local -a bind_mounts case $task in build) if [ x"$WORKSPACE" = x ]; then echo "WORKSPACE is not defined. Are you executing this from Jenkins?" >&2 exit 1 fi snapshots_top="/home/tcwg-buildslave" workspace_src="$WORKSPACE" if [ -f "/.dockerenv" ] && mount | grep -q "/run/docker.sock "; then # If inside "host" container (with proxied docker and /home from host-home volume), # convert paths to refer to volume's path on bare-metal. snapshots_top=$(echo "$snapshots_top" | sed -e "s#^/home/#/var/lib/docker/volumes/host-home/_data/#") workspace_src=$(echo "$workspace_src" | sed -e "s#^/home/#/var/lib/docker/volumes/host-home/_data/#") fi bind_mounts=( $snapshots_top/snapshots-ref:/home/tcwg-buildslave/snapshots-ref:ro $snapshots_top/llvm-reference:/home/tcwg-buildslave/llvm-reference:ro $workspace_src:$WORKSPACE ) esac echo "${bind_mounts[@]:+${bind_mounts[@]}}" ) } # Return zero if bash array is defined. # $1: Name of bash array test_array() { local var var="$1[@]" if [ x"${!var+set}" = x"set" ]; then return 0 else return 1 fi } # Process "--var value" and "++arr elem" arguments and define corresponding # variables and arrays. # "--var value" defines shell variable "$var" to "value". # "++arr elem" defines shell array "$arr[@]" and adds "elem" to it. # Shell array $CONVERTED_ARGS is set to the arguments processed. # Shell variable $SHIFT_CONVERTED_ARGS is set to number of arguments processed. # $@: Pairs of def/val arguments, stops at "--" marker. convert_args_to_variables () { local name local num="0" eval "CONVERTED_ARGS=(--)" while [ $# -gt 0 ]; do case "$1" in "--") # Finish marker num=$(($num+1)) shift 1 break ;; "--"*) name="${1#--}" eval "$name=\"$2\"" num=$(($num+2)) ;; "++"*) name="${1#++}" if ! test_array $name; then eval "$name=()" fi eval "$name+=(\"$2\")" num=$(($num+2)) ;; *) echo "ERROR: option does not start with '--' or '++': $1" exit 1 ;; esac eval "CONVERTED_ARGS+=(\"$1\" \"$2\")" shift 2 done eval "SHIFT_CONVERTED_ARGS=$num" }