hack/dind-cluster.sh
b2c2bf8d
 #!/bin/bash
 
9897da95
 # WARNING: The script modifies the host that docker is running on.  It
38798f2a
 # attempts to load the overlay and openvswitch modules. If this modification
9897da95
 # is undesirable consider running docker in a VM.
b2c2bf8d
 #
 # Overview
 # ========
 #
 # This script manages the lifecycle of an openshift dev cluster
 # deployed to docker-in-docker containers.  Once 'start' has been used
 # to successfully create a dind cluster, 'docker exec' can be used to
 # access the created containers (named
 # openshift-{master,node-1,node-2}) as if they were VMs.
 #
 # Dependencies
 # ------------
 #
9897da95
 # This script has been tested on Fedora 24, but should work on any
b2c2bf8d
 # release.  Docker is assumed to be installed.  At this time,
 # boot2docker is not supported.
 #
 # SELinux
 # -------
 #
 # Docker-in-docker's use of volumes is not compatible with selinux set
 # to enforcing mode.  Set selinux to permissive or disable it
 # entirely.
 #
b8a2ba83
 # OpenShift Configuration
 # -----------------------
 #
 # By default, a dind openshift cluster stores its configuration
9897da95
 # (openshift.local.*) in /tmp/openshift-dind-cluster/openshift.  It's
 # possible to run multiple dind clusters simultaneously by overriding
 # the instance prefix.  The following command would ensure
 # configuration was stored at /tmp/openshift-dind/cluster/my-cluster:
b8a2ba83
 #
9897da95
 #    OPENSHIFT_CLUSTER_ID=my-cluster hack/dind-cluster.sh [command]
b8a2ba83
 #
21e7e1d2
 # Suggested Workflow
 # ------------------
 #
 # When making changes to the deployment of a dind cluster or making
 # breaking golang changes, the 'restart' command will ensure that an
 # existing cluster is cleaned up before deploying a new cluster.
 #
b8a2ba83
 # Running Tests
 # -------------
 #
630e7d0c
 # The extended tests can be run against a dind cluster as follows:
b2c2bf8d
 #
630e7d0c
 #     OPENSHIFT_CONFIG_ROOT=dind test/extended/networking.sh
b2c2bf8d
 
 set -o errexit
 set -o nounset
 set -o pipefail
 
9897da95
 source "$(dirname "${BASH_SOURCE}")/lib/init.sh"
 source "${OS_ROOT}/images/dind/node/openshift-dind-lib.sh"
d24e93fc
 
b2c2bf8d
 function start() {
9897da95
   local origin_root=$1
   local config_root=$2
   local deployed_config_root=$3
   local cluster_id=$4
   local network_plugin=$5
   local wait_for_cluster=$6
   local node_count=$7
 
b2c2bf8d
   # docker-in-docker's use of volumes is not compatible with SELinux
   check-selinux
 
9897da95
   echo "Starting dind cluster '${cluster_id}' with plugin '${network_plugin}'"
b2c2bf8d
 
9897da95
   # Ensuring compatible host configuration
   #
   # Running in a container ensures that the docker host will be affected even
   # if docker is running remotely.  The openshift/dind-node image was chosen
   # due to its having sysctl installed.
   ${DOCKER_CMD} run --privileged --net=host --rm -v /lib/modules:/lib/modules \
                 openshift/dind-node bash -e -c \
38798f2a
                 '/usr/sbin/modprobe openvswitch;
                 /usr/sbin/modprobe overlay 2> /dev/null || true;'
9897da95
 
   # Initialize the cluster config path
   mkdir -p "${config_root}"
   echo "OPENSHIFT_NETWORK_PLUGIN=${network_plugin}" > "${config_root}/network-plugin"
   copy-runtime "${origin_root}" "${config_root}/"
 
   local volumes="-v ${config_root}:${deployed_config_root}"
   local run_cmd="${DOCKER_CMD} run -dt ${volumes}  --privileged"
 
   # Create containers
   ${run_cmd} --name="${MASTER_NAME}" --hostname="${MASTER_NAME}" "${MASTER_IMAGE}" > /dev/null
b2c2bf8d
   for name in "${NODE_NAMES[@]}"; do
9897da95
     ${run_cmd} --name="${name}" --hostname="${name}" "${NODE_IMAGE}" > /dev/null
b2c2bf8d
   done
de201098
 
9897da95
   local rc_file="dind-${cluster_id}.rc"
   local admin_config
   admin_config="$(get-admin-config "${CONFIG_ROOT}")"
   local bin_path
   bin_path="$(os::build::get-bin-output-path "${OS_ROOT}")"
a96ea3b4
   cat >"${rc_file}" <<EOF
 export KUBECONFIG=${admin_config}
 export PATH=\$PATH:${bin_path}
 EOF
0cb8c66e
 
9897da95
   if [[ -n "${wait_for_cluster}" ]]; then
     wait-for-cluster "${config_root}" "${node_count}"
0cb8c66e
   fi
26766a0d
 
6305a427
   if [[ "${KUBECONFIG:-}" != "${admin_config}"  ||
           ":${PATH}:" != *":${bin_path}:"* ]]; then
26766a0d
     echo ""
     echo "Before invoking the openshift cli, make sure to source the
 cluster's rc file to configure the bash environment:
 
   $ . ${rc_file}
   $ oc get nodes
 "
   fi
b2c2bf8d
 }
 
 function stop() {
9897da95
   local config_root=$1
   local cluster_id=$2
 
   echo "Stopping dind cluster '${cluster_id}'"
d331b4c5
 
9897da95
   local master_cid
   master_cid="$(${DOCKER_CMD} ps -qa --filter "name=${MASTER_NAME}")"
b2c2bf8d
   if [[ "${master_cid}" ]]; then
9897da95
     ${DOCKER_CMD} rm -f "${master_cid}" > /dev/null
b2c2bf8d
   fi
 
9897da95
   local node_cids
   node_cids="$(${DOCKER_CMD} ps -qa --filter "name=${NODE_PREFIX}")"
b2c2bf8d
   if [[ "${node_cids}" ]]; then
     node_cids=(${node_cids//\n/ })
     for cid in "${node_cids[@]}"; do
9897da95
       ${DOCKER_CMD} rm -f "${cid}" > /dev/null
b2c2bf8d
     done
   fi
 
9897da95
   # Cleaning up configuration to avoid conflict with a future cluster
cb3f10e0
   # The container will have created configuration as root
9897da95
   sudo rm -rf "${config_root}"/openshift.local.etcd
   sudo rm -rf "${config_root}"/openshift.local.config
cb3f10e0
 
b2c2bf8d
   # Cleanup orphaned volumes
   #
   # See: https://github.com/jpetazzo/dind#important-warning-about-disk-usage
   #
9897da95
   for volume in $( ${DOCKER_CMD} volume ls -qf dangling=true ); do
     ${DOCKER_CMD} volume rm "${volume}" > /dev/null
   done
 }
 
 function check-selinux() {
   if [[ "$(getenforce)" = "Enforcing" ]]; then
     >&2 echo "Error: This script is not compatible with SELinux enforcing mode."
     exit 1
adce7c75
   fi
b2c2bf8d
 }
 
9897da95
 function get-network-plugin() {
   local plugin=$1
21e7e1d2
 
9897da95
   local subnet_plugin="redhat/openshift-ovs-subnet"
   local multitenant_plugin="redhat/openshift-ovs-multitenant"
9810e7b8
   local default_plugin="${multitenant_plugin}"
21e7e1d2
 
9897da95
   if [[ "${plugin}" != "${subnet_plugin}" &&
           "${plugin}" != "${multitenant_plugin}" &&
           "${plugin}" != "cni" ]]; then
     if [[ -n "${plugin}" ]]; then
       >&2 echo "Invalid network plugin: ${plugin}"
     fi
     plugin="${default_plugin}"
21e7e1d2
   fi
9897da95
   echo "${plugin}"
 }
21e7e1d2
 
9897da95
 function get-docker-ip() {
   local cid=$1
 
   ${DOCKER_CMD} inspect --format '{{ .NetworkSettings.IPAddress }}' "${cid}"
 }
 
 function get-admin-config() {
   local config_root=$1
 
   echo "${config_root}/openshift.local.config/master/admin.kubeconfig"
 }
 
 function copy-runtime() {
   local origin_root=$1
   local target=$2
 
1b13c58e
   cp "$(os::util::find::built_binary openshift)" "${target}"
   cp "$(os::util::find::built_binary host-local)" "${target}"
   cp "$(os::util::find::built_binary loopback)" "${target}"
   cp "$(os::util::find::built_binary sdn-cni-plugin)" "${target}/openshift-sdn"
cf69a41b
   local osdn_plugin_path="${origin_root}/pkg/sdn/plugin"
   cp "${osdn_plugin_path}/bin/openshift-sdn-ovs" "${target}"
   cp "${osdn_plugin_path}/sdn-cni-plugin/80-openshift-sdn.conf" "${target}"
9897da95
 }
 
 function wait-for-cluster() {
   local config_root=$1
   local expected_node_count=$2
 
   # Increment the node count to ensure that the sdn node also reports readiness
   (( expected_node_count++ ))
 
   local kubeconfig
   kubeconfig="$(get-admin-config "${config_root}")"
   local oc
1b13c58e
   oc="$(os::util::find::built_binary oc)"
9897da95
 
6c6ec1ad
   # wait for healthz to report ok before trying to get nodes
   os::util::wait-for-condition "ok" "${oc} get --config=${kubeconfig} --raw=/healthz" "120"
 
9897da95
   local msg="${expected_node_count} nodes to report readiness"
   local condition="nodes-are-ready ${kubeconfig} ${oc} ${expected_node_count}"
ea684426
   local timeout=120
1218ef36
   os::util::wait-for-condition "${msg}" "${condition}" "${timeout}"
21e7e1d2
 }
 
2aa9ff85
 function nodes-are-ready() {
9897da95
   local kubeconfig=$1
   local oc=$2
   local expected_node_count=$3
 
   # TODO - do not count any node whose name matches the master node e.g. 'node-master'
6dad4aef
   read -d '' template <<'EOF'
 {{range $item := .items}}
9897da95
   {{range .status.conditions}}
     {{if eq .type "Ready"}}
       {{if eq .status "True"}}
         {{printf "%s\\n" $item.metadata.name}}
6dad4aef
       {{end}}
     {{end}}
   {{end}}
 {{end}}
 EOF
   # Remove formatting before use
   template="$(echo "${template}" | tr -d '\n' | sed -e 's/} \+/}/g')"
9897da95
   local count
   count="$("${oc}" --config="${kubeconfig}" get nodes \
                    --template "${template}" 2> /dev/null | \
                    wc -l)"
   test "${count}" -ge "${expected_node_count}"
2aa9ff85
 }
 
9897da95
 function build-images() {
   local origin_root=$1
 
   echo "Building container images"
   build-image "${origin_root}/images/dind/" "${BASE_IMAGE}"
   build-image "${origin_root}/images/dind/node" "${NODE_IMAGE}"
   build-image "${origin_root}/images/dind/master" "${MASTER_IMAGE}"
 }
 
 function build-image() {
   local build_root=$1
   local image_name=$2
 
   pushd "${build_root}" > /dev/null
     ${DOCKER_CMD} build -t "${image_name}" .
   popd > /dev/null
2aa9ff85
 }
 
9897da95
 DOCKER_CMD=${DOCKER_CMD:-"sudo docker"}
 
 CLUSTER_ID="${OPENSHIFT_CLUSTER_ID:-openshift}"
 
 TMPDIR="${TMPDIR:-"/tmp"}"
 CONFIG_ROOT="${OPENSHIFT_CONFIG_ROOT:-${TMPDIR}/openshift-dind-cluster/${CLUSTER_ID}}"
 DEPLOYED_CONFIG_ROOT="/data"
 
 MASTER_NAME="${CLUSTER_ID}-master"
 NODE_PREFIX="${CLUSTER_ID}-node-"
 NODE_COUNT=2
 NODE_NAMES=()
 for (( i=1; i<=NODE_COUNT; i++ )); do
   NODE_NAMES+=( "${NODE_PREFIX}${i}" )
 done
 
 BASE_IMAGE="openshift/dind"
 NODE_IMAGE="openshift/dind-node"
 MASTER_IMAGE="openshift/dind-master"
 
b2c2bf8d
 case "${1:-""}" in
   start)
9897da95
     BUILD=
     BUILD_IMAGES=
     WAIT_FOR_CLUSTER=1
     NETWORK_PLUGIN=
     REMOVE_EXISTING_CLUSTER=
     OPTIND=2
     while getopts ":bin:rs" opt; do
       case $opt in
         b)
           BUILD=1
           ;;
         i)
           BUILD_IMAGES=1
           ;;
         n)
           NETWORK_PLUGIN="${OPTARG}"
           ;;
         r)
           REMOVE_EXISTING_CLUSTER=1
           ;;
         s)
           WAIT_FOR_CLUSTER=
           ;;
         \?)
           echo "Invalid option: -${OPTARG}" >&2
           exit 1
           ;;
         :)
           echo "Option -${OPTARG} requires an argument." >&2
           exit 1
           ;;
       esac
     done
 
     if [[ -n "${REMOVE_EXISTING_CLUSTER}" ]]; then
       stop "${CONFIG_ROOT}" "${CLUSTER_ID}"
     fi
 
     # Build origin if requested or required
1b13c58e
     if [[ -n "${BUILD}" ]] || ! os::util::find::built_binary 'oc' >/dev/null 2>&1; then
9897da95
       "${OS_ROOT}/hack/build-go.sh"
     fi
 
     # Build images if requested or required
     if [[ -n "${BUILD_IMAGES}" ||
             -z "$(${DOCKER_CMD} images -q ${MASTER_IMAGE})" ]]; then
       build-images "${OS_ROOT}"
     fi
 
     NETWORK_PLUGIN="$(get-network-plugin "${NETWORK_PLUGIN}")"
     start "${OS_ROOT}" "${CONFIG_ROOT}" "${DEPLOYED_CONFIG_ROOT}" \
           "${CLUSTER_ID}" "${NETWORK_PLUGIN}" "${WAIT_FOR_CLUSTER}" \
           "${NODE_COUNT}" "${NODE_PREFIX}"
b2c2bf8d
     ;;
   stop)
9897da95
     stop "${CONFIG_ROOT}" "${CLUSTER_ID}"
21e7e1d2
     ;;
2aa9ff85
   wait-for-cluster)
9897da95
     wait-for-cluster "${CONFIG_ROOT}" "${NODE_COUNT}"
2aa9ff85
     ;;
388e0a19
   build-images)
9897da95
     build-images "${OS_ROOT}"
1f867ed2
     ;;
b2c2bf8d
   *)
9897da95
     >&2 echo "Usage: $0 {start|stop|wait-for-cluster|build-images}
 
 start accepts the following arguments:
 
  -n [net plugin]   the name of the network plugin to deploy
 
  -b                build origin before starting the cluster
 
  -i                build container images before starting the cluster
 
  -r                remove an existing cluster
 
  -s                skip waiting for nodes to become ready
 "
b2c2bf8d
     exit 2
 esac