blob: acebabe102a10c13c2ae21fb94976ccb332cb0c2 [file] [log] [blame]
#!/bin/bash
#
# Copyright 2015 The Bazel Authors. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Script to easily re-image the various vms on GCE
set -eu
# List of executor nodes in the following format:
# GCE-VM-NAME GCE-BASE-IMAGE JENKINS-NODE LOCATION STARTUP-METADATA SETUP-SCRIPTS
# Where
# GCE-VM-NAME is the VM name on GCE
# GCE-BASE-IMAGE is the name of the base image in GCE
# (see `gcloud compute images list`)
# JENKINS-NODE is the name of the node in Jenkins
# LOCATION is the location in GCE (e.g. europe-west1-d)
# NETWORK is the GCE network the instance has to be created on.
# STARTUP-METADATA is the metadata argument to gcloud to launch the right
# startup script.
# SETUP-SCRIPTS is a list of shell scripts to adapt the executor node. It should
# create a ci user with its home in /home/ci
# and ends with writing to /home/ci/node_name the name
# of the jenkins node.
# Executor nodes for ci.bazel.io
SLAVES=(
"ubuntu-14-04-slave-1 ubuntu-1404-lts ubuntu_14.04-x86_64-1 europe-west1-d default startup-script=jenkins-slave.sh ubuntu-14-04-slave.sh bootstrap-bazel.sh linux-android.sh cleanup-install.sh"
"ubuntu-14-04-slave-2 ubuntu-1404-lts ubuntu_14.04-x86_64-2 europe-west1-d default startup-script=jenkins-slave.sh ubuntu-14-04-slave.sh bootstrap-bazel.sh linux-android.sh cleanup-install.sh"
"ubuntu-14-04-slave-3 ubuntu-1404-lts ubuntu_14.04-x86_64-3 europe-west1-d default startup-script=jenkins-slave.sh ubuntu-14-04-slave.sh bootstrap-bazel.sh linux-android.sh cleanup-install.sh"
"ubuntu-14-04-slave-4 ubuntu-1404-lts ubuntu_14.04-x86_64-4 europe-west1-d default startup-script=jenkins-slave.sh ubuntu-14-04-slave.sh bootstrap-bazel.sh linux-android.sh cleanup-install.sh"
"ubuntu-14-04-slave-5 ubuntu-1404-lts ubuntu_14.04-x86_64-5 europe-west1-d default startup-script=jenkins-slave.sh ubuntu-14-04-slave.sh bootstrap-bazel.sh linux-android.sh cleanup-install.sh"
"ubuntu-14-04-slave-6 ubuntu-1404-lts ubuntu_14.04-x86_64-6 europe-west1-d default startup-script=jenkins-slave.sh ubuntu-14-04-slave.sh bootstrap-bazel.sh linux-android.sh cleanup-install.sh"
"ubuntu-14-04-slave-7 ubuntu-1404-lts ubuntu_14.04-x86_64-7 europe-west1-d default startup-script=jenkins-slave.sh ubuntu-14-04-slave.sh bootstrap-bazel.sh linux-android.sh cleanup-install.sh"
"ubuntu-14-04-slave-8 ubuntu-1404-lts ubuntu_14.04-x86_64-8 europe-west1-d default startup-script=jenkins-slave.sh ubuntu-14-04-slave.sh bootstrap-bazel.sh linux-android.sh cleanup-install.sh"
"ubuntu-16-04-slave-1 ubuntu-1604-lts ubuntu_16.04-x86_64-1 europe-west1-d default startup-script=jenkins-slave.sh ubuntu-16-04-slave.sh bootstrap-bazel.sh linux-android.sh cleanup-install.sh"
"ubuntu-16-04-slave-2 ubuntu-1604-lts ubuntu_16.04-x86_64-2 europe-west1-d default startup-script=jenkins-slave.sh ubuntu-16-04-slave.sh bootstrap-bazel.sh linux-android.sh cleanup-install.sh"
"ubuntu-16-04-slave-3 ubuntu-1604-lts ubuntu_16.04-x86_64-3 europe-west1-d default startup-script=jenkins-slave.sh ubuntu-16-04-slave.sh bootstrap-bazel.sh linux-android.sh cleanup-install.sh"
"ubuntu-16-04-slave-4 ubuntu-1604-lts ubuntu_16.04-x86_64-4 europe-west1-d default startup-script=jenkins-slave.sh ubuntu-16-04-slave.sh bootstrap-bazel.sh linux-android.sh cleanup-install.sh"
"ubuntu-16-04-slave-5 ubuntu-1604-lts ubuntu_16.04-x86_64-5 europe-west1-d default startup-script=jenkins-slave.sh ubuntu-16-04-slave.sh bootstrap-bazel.sh linux-android.sh cleanup-install.sh"
"ubuntu-16-04-slave-6 ubuntu-1604-lts ubuntu_16.04-x86_64-6 europe-west1-d default startup-script=jenkins-slave.sh ubuntu-16-04-slave.sh bootstrap-bazel.sh linux-android.sh cleanup-install.sh"
"ubuntu-16-04-slave-7 ubuntu-1604-lts ubuntu_16.04-x86_64-7 europe-west1-d default startup-script=jenkins-slave.sh ubuntu-16-04-slave.sh bootstrap-bazel.sh linux-android.sh cleanup-install.sh"
"ubuntu-16-04-slave-8 ubuntu-1604-lts ubuntu_16.04-x86_64-8 europe-west1-d default startup-script=jenkins-slave.sh ubuntu-16-04-slave.sh bootstrap-bazel.sh linux-android.sh cleanup-install.sh"
"ubuntu-docker-slave-1 https://www.googleapis.com/compute/v1/projects/ubuntu-os-cloud/global/images/ubuntu-1510-wily-v20151026 ubuntu_15.10-x86_64-docker-1 europe-west1-d default startup-script=jenkins-slave.sh ubuntu-15-10-slave.sh ubuntu-15-10-docker.sh bootstrap-bazel.sh linux-android.sh cleanup-install.sh"
"ubuntu-docker-slave-2 https://www.googleapis.com/compute/v1/projects/ubuntu-os-cloud/global/images/ubuntu-1510-wily-v20151026 ubuntu_15.10-x86_64-docker-2 europe-west1-d default startup-script=jenkins-slave.sh ubuntu-15-10-slave.sh ubuntu-15-10-docker.sh bootstrap-bazel.sh linux-android.sh cleanup-install.sh"
"ubuntu-docker-slave-3 https://www.googleapis.com/compute/v1/projects/ubuntu-os-cloud/global/images/ubuntu-1510-wily-v20151026 ubuntu_15.10-x86_64-docker-3 europe-west1-d default startup-script=jenkins-slave.sh ubuntu-15-10-slave.sh ubuntu-15-10-docker.sh bootstrap-bazel.sh linux-android.sh cleanup-install.sh"
"ubuntu-docker-slave-4 https://www.googleapis.com/compute/v1/projects/ubuntu-os-cloud/global/images/ubuntu-1510-wily-v20151026 ubuntu_15.10-x86_64-docker-4 europe-west1-d default startup-script=jenkins-slave.sh ubuntu-15-10-slave.sh ubuntu-15-10-docker.sh bootstrap-bazel.sh linux-android.sh cleanup-install.sh"
"freebsd-11-slave-1 https://www.googleapis.com/compute/v1/projects/freebsd-org-cloud-dev/global/images/freebsd-11-0-stable-amd64-2017-01-06 freebsd-11-1 europe-west1-d default startup-script=jenkins-slave.sh freebsd-slave.sh freebsd-ci-homedir.sh"
"freebsd-11-slave-2 https://www.googleapis.com/compute/v1/projects/freebsd-org-cloud-dev/global/images/freebsd-11-0-stable-amd64-2017-01-06 freebsd-11-2 europe-west1-d default startup-script=jenkins-slave.sh freebsd-slave.sh freebsd-ci-homedir.sh"
"freebsd-11-slave-3 https://www.googleapis.com/compute/v1/projects/freebsd-org-cloud-dev/global/images/freebsd-11-0-stable-amd64-2017-01-06 freebsd-11-3 europe-west1-d default startup-script=jenkins-slave.sh freebsd-slave.sh freebsd-ci-homedir.sh"
"freebsd-11-slave-4 https://www.googleapis.com/compute/v1/projects/freebsd-org-cloud-dev/global/images/freebsd-11-0-stable-amd64-2017-01-06 freebsd-11-4 europe-west1-d default startup-script=jenkins-slave.sh freebsd-slave.sh freebsd-ci-homedir.sh"
"freebsd-12-slave-1 https://www.googleapis.com/compute/v1/projects/freebsd-org-cloud-dev/global/images/freebsd-12-0-current-amd64-2017-07-04 freebsd-12-1 europe-west1-d default startup-script=jenkins-slave.sh freebsd-slave.sh freebsd-ci-homedir.sh"
"freebsd-12-slave-2 https://www.googleapis.com/compute/v1/projects/freebsd-org-cloud-dev/global/images/freebsd-12-0-current-amd64-2017-07-04 freebsd-12-2 europe-west1-d default startup-script=jenkins-slave.sh freebsd-slave.sh freebsd-ci-homedir.sh"
"freebsd-12-slave-3 https://www.googleapis.com/compute/v1/projects/freebsd-org-cloud-dev/global/images/freebsd-12-0-current-amd64-2017-07-04 freebsd-12-3 europe-west1-d default startup-script=jenkins-slave.sh freebsd-slave.sh freebsd-ci-homedir.sh"
"freebsd-12-slave-4 https://www.googleapis.com/compute/v1/projects/freebsd-org-cloud-dev/global/images/freebsd-12-0-current-amd64-2017-07-04 freebsd-12-4 europe-west1-d default startup-script=jenkins-slave.sh freebsd-slave.sh freebsd-ci-homedir.sh"
# Fow Windows, we use a custom image with pre-installed MSVC.
"windows-slave-1 windows-server-2012-r2-dc-v20160112-vs2015-cpp-python-msys windows-x86_64-1 europe-west1-d default windows-startup-script-ps1=jenkins-slave-windows.ps1"
"windows-slave-2 windows-server-2012-r2-dc-v20160112-vs2015-cpp-python-msys windows-x86_64-2 europe-west1-d default windows-startup-script-ps1=jenkins-slave-windows.ps1"
"windows-slave-3 windows-server-2012-r2-dc-v20160112-vs2015-cpp-python-msys windows-x86_64-3 europe-west1-d default windows-startup-script-ps1=jenkins-slave-windows.ps1"
"windows-slave-4 windows-server-2012-r2-dc-v20160112-vs2015-cpp-python-msys windows-x86_64-4 europe-west1-d default windows-startup-script-ps1=jenkins-slave-windows.ps1"
# For benchmark
"ubuntu-16-04-benchmark-slave ubuntu-1604-lts ubuntu_16.04-x86_64-benchmark-1 europe-west1-d default startup-script=jenkins-slave.sh ubuntu-16-04-slave.sh bootstrap-bazel.sh linux-android.sh cleanup-install.sh"
)
# Jenkins controller for ci.bazel.io
MASTER=(
# VM name
"jenkins"
# Zone
"us-central1-a"
# Metadata specification
"google-container-manifest=jenkins.yml,startup-script=mount-volumes.sh"
# Disk specification
"name=jenkins-volumes,device-name=volumes"
# Address name
"ci"
# Network name
"default"
)
# Executor nodes for ci-staging.bazel.io
STAGING_SLAVES=(
"ubuntu-14-04-slave-staging ubuntu-1404-lts ubuntu_14.04-x86_64-staging europe-west1-d staging startup-script=jenkins-slave.sh ubuntu-14-04-slave.sh bootstrap-bazel.sh linux-android.sh cleanup-install.sh"
"ubuntu-16-04-slave-staging ubuntu-1604-lts ubuntu_16.04-x86_64-staging europe-west1-d staging startup-script=jenkins-slave.sh ubuntu-16-04-slave.sh bootstrap-bazel.sh linux-android.sh cleanup-install.sh"
"ubuntu-docker-slave-staging https://www.googleapis.com/compute/v1/projects/ubuntu-os-cloud/global/images/ubuntu-1510-wily-v20151026 ubuntu_15.10-x86_64-docker-staging europe-west1-d staging startup-script=jenkins-slave.sh ubuntu-15-10-slave.sh ubuntu-15-10-docker.sh bootstrap-bazel.sh linux-android.sh cleanup-install.sh"
"freebsd-11-slave-staging https://www.googleapis.com/compute/v1/projects/freebsd-org-cloud-dev/global/images/freebsd-11-0-stable-amd64-2017-01-06 freebsd-11-staging europe-west1-d staging startup-script=jenkins-slave.sh freebsd-slave.sh freebsd-ci-homedir.sh"
"freebsd-12-slave-staging https://www.googleapis.com/compute/v1/projects/freebsd-org-cloud-dev/global/images/freebsd-12-0-current-amd64-2017-07-04 freebsd-12-staging europe-west1-d staging startup-script=jenkins-slave.sh freebsd-slave.sh freebsd-ci-homedir.sh"
# Fow Windows, we use a custom image with pre-installed MSVC.
"windows-slave-staging windows-server-2012-r2-dc-v20160112-vs2015-cpp-python-msys windows-x86_64-staging europe-west1-d staging windows-startup-script-ps1=jenkins-slave-windows.ps1"
# Remote Cache
"remote-cache-staging ubuntu-1604-lts remote-cache-staging europe-west1-d staging startup-script=start-remote-cache.sh setup-remote-cache.sh"
)
STAGING_MASTER=(
# VM name
"jenkins-staging"
# Zone
"europe-west1-d"
# Metadata specification
"google-container-manifest=jenkins-staging.yml,startup-script=mount-volumes.sh"
# Disk specification
"name=jenkins-volumes-staging,device-name=volumes"
# Address name
"ci-staging"
# Network name
"staging"
)
cd "$(dirname "${BASH_SOURCE[0]}")"
source utils/commands.sh
# Create the container engine VM containing the jenkins instance.
function create_master() {
local flavour="${1:-}"
local name="$1"
local location="$2"
local metadata="$3"
local disk="$4"
local address="$5"
local network="$6"
gcloud compute instances create "$name" --tags jenkins \
--zone "$location" --machine-type n1-standard-4 \
--image container-vm \
--metadata-from-file "$metadata" \
--boot-disk-type pd-ssd --boot-disk-size 40GB \
--network "$network" \
--address "$address" --disk "$disk"
}
# Create a node named $1 whose image is $2 (see `gcloud compute image list`)
# and whose jenkins node name is $3. The other arguments are a list of setup
# scripts to run as root on instance creation. The `jenkins-slave.sh` script
# will be used as the startup script for the instance.
function create_slave() {
local TAG="$1"
local IMAGE="$2"
local JENKINS_NODE="$3"
local LOCATION="$4"
local NETWORK="$5"
local STARTUP_METADATA="$6"
shift 6
if [[ $IMAGE == ubuntu-* ]]; then
IMAGE_FLAG="--image-project=ubuntu-os-cloud --image-family=$IMAGE"
else
IMAGE_FLAG="--image $IMAGE"
fi
gcloud compute instances create "$TAG" \
--zone "$LOCATION" --machine-type n1-standard-16 \
--network "$NETWORK" \
$IMAGE_FLAG \
--metadata jenkins_node="$JENKINS_NODE" \
--boot-disk-type pd-ssd --boot-disk-size 500GB
case "$TAG" in
windows-*) # Windows
;;
freebsd*) # FreeBSD
# Wait a bit for the VM to fully start
wait_vm "$TAG" "$LOCATION" 120 /usr/bin/true
# Create the jenkins user, run additional set-up scripts and mark
# the install process as finished.
ssh_command "$TAG" "$LOCATION" \
"pw useradd -n ci -s /usr/local/bin/bash -d /home/ci -m -w no" \
"echo \"echo -n '$JENKINS_NODE' >/home/ci/node_name\" | su -m ci" \
"$@"
;;
*) # Linux
wait_vm "$TAG" "$LOCATION" # Wait a bit for the VM to fully start
# Create the jenkins user, run additional set-up scripts and mark
# the install process as finished.
ssh_command "$TAG" "$LOCATION" \
"sudo adduser --system --home /home/ci ci" \
"su ci -s /bin/bash -c \"echo -n '$JENKINS_NODE' >/home/ci/node_name\"" \
"$@"
;;
esac
gcloud compute instances stop "$TAG" \
--zone "$LOCATION"
gcloud compute instances add-metadata "$TAG" \
--zone "$LOCATION" \
--metadata-from-file "$STARTUP_METADATA"
gcloud compute instances start "$TAG" \
--zone "$LOCATION"
}
# Updates the --metadata and --metadata-from-file values of an existing VM.
#
# Primary purpose is to propagate changes to the startup scripts (e.g.
# mount-volumes.sh for the jenkins controller, jenkins-slave.sh for Ubuntu nodes,
# etc.) without recreating the VM. The update needs a VM reboot to take effect.
#
# The gcloud command takes a few moments to complete so it is started as a
# background job. Wait on its PID or job number (or %?gcloud) before exiting
# this script.
function update_metadata() {
local tag="${MASTER[0]}"
local metadata_flag=""
local location="${MASTER[1]}"
local startup_metadata="${MASTER[2]}"
if [ "$1" = "jenkins-staging" ]; then
tag="${STAGING_MASTER[0]}"
metadata_flag=""
location="${STAGING_MASTER[1]}"
startup_metadata="${STAGING_MASTER[2]}"
elif [ ! "$1" = jenkins ]; then
local args="$(get_slave_by_name "$1")"
[ -n "$args" ] || (echo "Unknown vm $1" >&2; exit 1)
tag="$(echo $args | cut -d' ' -f1)"
metadata_flag="--metadata jenkins_node=$(echo $args | cut -d' ' -f3)"
location="$(echo $args | cut -d' ' -f4)"
startup_metadata="$(echo $args | cut -d' ' -f6)"
fi
gcloud compute instances add-metadata "$tag" \
--zone "$location" \
$metadata_flag \
--metadata-from-file "$startup_metadata" &
}
function get_slave_by_name() {
for i in "${SLAVES[@]}" "${STAGING_SLAVES[@]}"; do
if [[ "$i" =~ ^"$1 " ]]; then
echo "$i"
fi
done
}
function create_vm() {
if [ "$1" = "jenkins" ]; then
create_master "${MASTER[@]}"
elif [ "$1" = "jenkins-staging" ]; then
create_master "${STAGING_MASTER[@]}"
else
local args="$(get_slave_by_name "$1")"
[ -n "$args" ] || (echo "Unknown vm $1" >&2; exit 1)
create_slave $args
fi
}
function action() {
local action=$1
shift
if (( $# == 0 )); then
$action jenkins
$action jenkins-staging
for i in "${SLAVES[@]}" "${STAGING_SLAVES[@]}"; do
$action "${i%% *}"
done
elif (( $# == 1 )) && [ "$1" = "prod" ]; then
$action jenkins
for i in "${SLAVES[@]}"; do
$action "${i%% *}"
done
elif (( $# == 1 )) && [ "$1" = "staging" ]; then
$action jenkins-staging
for i in "${STAGING_SLAVES[@]}"; do
$action "${i%% *}"
done
else
for i in "$@"; do
$action "$i"
done
fi
wait %?gcloud 2>/dev/null || true # wait fails if the job already finished.
}
function test_slave() {
local TAG=$1
if test_vm $TAG; then
if [ "$TAG" = "${MASTER[0]}" ]; then
echo "${MASTER[1]}"
elif [ "$TAG" = "${STAGING_MASTER[0]}" ]; then
echo "${STAGING_MASTER[1]}"
else
get_slave_by_name "$TAG" | cut -d " " -f 4
fi
fi
}
function vm_command() {
local command=$1
local TAG=$2
local location="$(test_slave "$TAG")"
shift 2
if [ -n "$location" ]; then
gcloud compute instances $command --quiet --zone=$location $TAG "$@"
fi
}
function delete_vm() {
vm_command delete "$@"
}
function stop_vm() {
vm_command stop "$@"
}
function start_vm() {
vm_command start "$@"
}
function do_ssh_command() {
tag=$1
shift
location="$(test_slave "${tag}")"
if [ -z "${location}" ]; then
echo "Slave ${tag} was not found or is not running" >&2
else
ssh_command "$tag" "$location" "$@"
fi
}
command="${1-}"
shift || true
case "${command}" in
"stop")
action stop_vm "$@"
;;
"start")
action start_vm "$@"
;;
"create")
action create_vm "$@"
;;
"delete")
action delete_vm "$@"
;;
"reimage")
action delete_vm "$@"
action create_vm "$@"
;;
"update_metadata")
action update_metadata "$@"
;;
"ssh_command")
do_ssh_command "$@"
;;
"ssh")
tag=$1
location="$(test_slave "${tag}")"
if [ -z "${location}" ]; then
echo "Slave ${tag} was not found or is not running" >&2
else
gcloud compute ssh "$tag" --zone "$location"
fi
;;
"kill_container")
tag="$1"
do_ssh_command "$1" 'sudo docker stop $(sudo docker ps -q -f status=running -f ancestor='"$2"')'
;;
*)
echo "Usage: $0 <command> ([<vm> ... <vm>]|staging|prod)" >&2
echo " $0 ssh_command <vm> <arg0> [<arg1>..<argN>]" >&2
echo " $0 ssh <vm>" >&2
echo " $0 kill_container <vm> <image>" >&2
echo " - command can be:" >&2
echo " create: create the VM, fails if the VM already exists." >&2
echo " delete: delete the VM, fails if the VM does not exists." >&2
echo " reimage: reimage the VM, equivalent to delete followed by create." >&2
echo " update_metadata: update the VM metadata, the VM must already exists." >&2
echo " start: start the VM, the VM must exists." >&2
echo " stop: stop the VM, the VM must exists." >&2
echo " - ssh_command executes a command via SSH on the specified VM." >&2
echo " - ssh launch a secure shell on the specified VM." >&2
echo " - kill_container kills container that runs the specified image on the" >&2
echo " specified VM." >&2
echo "Special value 'staging' and 'prod' point to all VM in, respectively," >&2
echo "ci-staging.bazel.io and ci.bazel.io." >&2
exit 1
;;
esac