370 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
			
		
		
	
	
			370 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Bash
		
	
	
	
	
	
#!/usr/bin/env bash
 | 
						|
 | 
						|
# Copyright 2016 Midokura SARL
 | 
						|
#
 | 
						|
# 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.
 | 
						|
 | 
						|
# Useful common functions are defined here.  Some of them were shamelessly
 | 
						|
# stolen from devstack.
 | 
						|
 | 
						|
# Save trace setting
 | 
						|
XTRACE=$(set +o | grep xtrace)
 | 
						|
set +o xtrace
 | 
						|
 | 
						|
# Control Functions
 | 
						|
# -----------------
 | 
						|
 | 
						|
# Prints line number and "message" in warning format
 | 
						|
# warn $LINENO "message"
 | 
						|
function warn {
 | 
						|
    local exitcode=$?
 | 
						|
    local xtrace=$(set +o | grep xtrace)
 | 
						|
    set +o xtrace
 | 
						|
    local msg="[WARNING] ${BASH_SOURCE[2]}:$1 $2"
 | 
						|
    echo $msg 1>&2;
 | 
						|
    if [[ -n ${LOGDIR} ]]; then
 | 
						|
        echo $msg >> "${LOGDIR}/error.log"
 | 
						|
    fi
 | 
						|
    $xtrace
 | 
						|
    return $exitcode
 | 
						|
}
 | 
						|
 | 
						|
# Prints backtrace info
 | 
						|
# filename:lineno:function
 | 
						|
# backtrace level
 | 
						|
function backtrace {
 | 
						|
    local level=$1
 | 
						|
    local deep=$((${#BASH_SOURCE[@]} - 1))
 | 
						|
    echo "[Call Trace]"
 | 
						|
    while [ $level -le $deep ]; do
 | 
						|
        echo "${BASH_SOURCE[$deep]}:${BASH_LINENO[$deep-1]}:${FUNCNAME[$deep-1]}"
 | 
						|
        deep=$((deep - 1))
 | 
						|
    done
 | 
						|
}
 | 
						|
 | 
						|
# Prints line number and "message" in error format
 | 
						|
# err $LINENO "message"
 | 
						|
function err {
 | 
						|
    local exitcode=$?
 | 
						|
    local xtrace=$(set +o | grep xtrace)
 | 
						|
    set +o xtrace
 | 
						|
    local msg="[ERROR] ${BASH_SOURCE[2]}:$1 $2"
 | 
						|
    echo $msg 1>&2;
 | 
						|
    if [[ -n ${LOGDIR} ]]; then
 | 
						|
        echo $msg >> "${LOGDIR}/error.log"
 | 
						|
    fi
 | 
						|
    $xtrace
 | 
						|
    return $exitcode
 | 
						|
}
 | 
						|
 | 
						|
# Prints line number and "message" then exits
 | 
						|
# die $LINENO "message"
 | 
						|
function die {
 | 
						|
    local exitcode=$?
 | 
						|
    set +o xtrace
 | 
						|
    local line=$1; shift
 | 
						|
    if [ $exitcode == 0 ]; then
 | 
						|
        exitcode=1
 | 
						|
    fi
 | 
						|
    backtrace 2
 | 
						|
    err $line "$*"
 | 
						|
    # Give buffers a second to flush
 | 
						|
    sleep 1
 | 
						|
    exit $exitcode
 | 
						|
}
 | 
						|
 | 
						|
# Checks an environment variable is not set or has length 0 OR if the
 | 
						|
# exit code is non-zero and prints "message" and exits
 | 
						|
# NOTE: env-var is the variable name without a '$'
 | 
						|
# die_if_not_set $LINENO env-var "message"
 | 
						|
function die_if_not_set {
 | 
						|
    local exitcode=$?
 | 
						|
    local xtrace=$(set +o | grep xtrace)
 | 
						|
    set +o xtrace
 | 
						|
    local line=$1; shift
 | 
						|
    local evar=$1; shift
 | 
						|
    if ! is_set $evar || [ $exitcode != 0 ]; then
 | 
						|
        die $line "$*"
 | 
						|
    fi
 | 
						|
    $xtrace
 | 
						|
}
 | 
						|
 | 
						|
# Test if the named environment variable is set and not zero length
 | 
						|
# is_set env-var
 | 
						|
function is_set {
 | 
						|
    local var=\$"$1"
 | 
						|
    eval "[ -n \"$var\" ]" # For ex.: sh -c "[ -n \"$var\" ]" would be better, but several exercises depends on this
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
# Package Functions
 | 
						|
# -----------------
 | 
						|
 | 
						|
# Wrapper for ``apt-get``
 | 
						|
# apt_get operation package [package ...]
 | 
						|
function apt_get {
 | 
						|
    sudo DEBIAN_FRONTEND=noninteractive \
 | 
						|
        apt-get --option "Dpkg::Options::=--force-confnew" --assume-yes "$@"
 | 
						|
}
 | 
						|
 | 
						|
# Update package repository
 | 
						|
# Uses globals ``NO_UPDATE_REPOS``, ``REPOS_UPDATED``, ``RETRY_UPDATE``
 | 
						|
# install_package package [package ...]
 | 
						|
function update_package_repo {
 | 
						|
    NO_UPDATE_REPOS=${NO_UPDATE_REPOS:-False}
 | 
						|
    REPOS_UPDATED=${REPOS_UPDATED:-False}
 | 
						|
 | 
						|
    if [[ "$NO_UPDATE_REPOS" = "True" ]]; then
 | 
						|
        return 0
 | 
						|
    fi
 | 
						|
 | 
						|
    local xtrace=$(set +o | grep xtrace)
 | 
						|
    set +o xtrace
 | 
						|
    if [[ "$REPOS_UPDATED" != "True" ]]; then
 | 
						|
        # if there are transient errors pulling the updates, that's fine.
 | 
						|
        # It may be secondary repositories that we don't really care about.
 | 
						|
        apt_get update  || /bin/true
 | 
						|
        REPOS_UPDATED=True
 | 
						|
    fi
 | 
						|
    $xtrace
 | 
						|
}
 | 
						|
 | 
						|
# Package installer
 | 
						|
# install_package package [package ...]
 | 
						|
function install_package {
 | 
						|
    update_package_repo
 | 
						|
    apt_get install --no-install-recommends "$@"
 | 
						|
}
 | 
						|
 | 
						|
# Function to tell if a package is installed
 | 
						|
# is_package_installed package [package ...]
 | 
						|
function is_package_installed {
 | 
						|
    dpkg -s "$@" > /dev/null 2> /dev/null
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
# System Functions
 | 
						|
# -----------------
 | 
						|
 | 
						|
# Service wrapper to stop services
 | 
						|
# stop_service service-name
 | 
						|
# Checks if kmod is loaded
 | 
						|
function is_kmod_loaded {
 | 
						|
    lsmod | grep -w $1 >& /dev/null
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
# Process Functions
 | 
						|
# -----------------
 | 
						|
 | 
						|
function is_screen_running {
 | 
						|
    type -p screen > /dev/null && screen -ls | egrep -q "[0-9]\.$1"
 | 
						|
}
 | 
						|
 | 
						|
function create_screen {
 | 
						|
    local name=$1
 | 
						|
    screen -d -m -S $name -t shell -s /bin/bash
 | 
						|
    sleep 1
 | 
						|
 | 
						|
    # Set a reasonable status bar
 | 
						|
    SCREEN_HARDSTATUS='%{= .} %-Lw%{= .}%> %n%f %t*%{= .}%+Lw%< %-=%{g}(%{d}%H/%l%{g})'
 | 
						|
    screen -r $name -X hardstatus alwayslastline "$SCREEN_HARDSTATUS"
 | 
						|
    screen -r $name -X setenv PROMPT_COMMAND /bin/true
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
# Helper to launch a process in a named screen
 | 
						|
# Uses globals ``CURRENT_LOG_TIME``, ``LOGDIR``,
 | 
						|
# ``SERVICE_DIR``
 | 
						|
# screen_process name "command-line"
 | 
						|
# Run a command in a shell in a screen window
 | 
						|
function screen_process {
 | 
						|
    local name=$1
 | 
						|
    local command="$2"
 | 
						|
 | 
						|
    SERVICE_DIR=${SERVICE_DIR:-/tmp/status}
 | 
						|
    mkdir -p ${SERVICE_DIR}/${SCREEN_NAME}
 | 
						|
 | 
						|
    # Append the process to the screen rc file
 | 
						|
    screen_rc ${SCREEN_NAME} "$name" "$command"
 | 
						|
 | 
						|
    screen -S ${SCREEN_NAME} -X screen -t $name
 | 
						|
 | 
						|
    if [[ -n ${LOGDIR} ]]; then
 | 
						|
        screen -S ${SCREEN_NAME} -p $name -X logfile ${LOGDIR}/${name}.log.${CURRENT_LOG_TIME}
 | 
						|
        screen -S ${SCREEN_NAME} -p $name -X log on
 | 
						|
        ln -sf ${LOGDIR}/${name}.log.${CURRENT_LOG_TIME} ${LOGDIR}/${name}.log
 | 
						|
    fi
 | 
						|
 | 
						|
    # sleep to allow bash to be ready to be send the command - we are
 | 
						|
    # creating a new window in screen and then sends characters, so if
 | 
						|
    # bash isn't running by the time we send the command, nothing happens
 | 
						|
    sleep 3
 | 
						|
 | 
						|
    NL=`echo -ne '\015'`
 | 
						|
    screen -S ${SCREEN_NAME} -p $name -X stuff "$command & echo \$! >$SERVICE_DIR/${SCREEN_NAME}/${name}.pid; fg || echo \"$name failed to start\" | tee \"$SERVICE_DIR/${SCREEN_NAME}/${name}.failure\"$NL"
 | 
						|
}
 | 
						|
 | 
						|
# _run_process() is designed to be backgrounded by run_process() to simulate a
 | 
						|
# fork.  It includes the dirty work of closing extra filehandles and preparing log
 | 
						|
# files to produce the same logs as screen_it().  The log filename is derived
 | 
						|
# from the service name.
 | 
						|
# _run_process service "command-line"
 | 
						|
function _run_process {
 | 
						|
    local service=$1
 | 
						|
    local command="$2"
 | 
						|
 | 
						|
    # Undo logging redirections and close the extra descriptors
 | 
						|
    exec 1>&3
 | 
						|
    exec 2>&3
 | 
						|
    exec 3>&-
 | 
						|
    exec 6>&-
 | 
						|
 | 
						|
    local real_logfile="${LOGDIR}/${service}.log.${CURRENT_LOG_TIME}"
 | 
						|
    if [[ -n ${LOGDIR} ]]; then
 | 
						|
        exec 1>&"$real_logfile" 2>&1
 | 
						|
        ln -sf "$real_logfile" ${LOGDIR}/${service}.log
 | 
						|
 | 
						|
        # Hack to get stdout from the Python interpreter for the logs.
 | 
						|
        export PYTHONUNBUFFERED=1
 | 
						|
    fi
 | 
						|
 | 
						|
    SERVICE_DIR=${SERVICE_DIR:-/tmp/status}
 | 
						|
    mkdir -p ${SERVICE_DIR}/${SCREEN_NAME}
 | 
						|
    setsid $command & echo $! > ${SERVICE_DIR}/${SCREEN_NAME}/${service}.pid
 | 
						|
 | 
						|
    # Just silently exit this process
 | 
						|
    exit 0
 | 
						|
}
 | 
						|
 | 
						|
# Run a single service under screen or directly
 | 
						|
# If the command includes shell metachatacters (;<>*) it must be run using a shell
 | 
						|
# run_process service "command-line"
 | 
						|
function run_process {
 | 
						|
    local service=$1
 | 
						|
    local command="$2"
 | 
						|
 | 
						|
    if [[ "$USE_SCREEN" = "True" ]]; then
 | 
						|
        screen_process "$service" "cd $TOP_DIR && $command"
 | 
						|
    else
 | 
						|
        # Spawn directly without screen
 | 
						|
        cd $TOP_DIR
 | 
						|
        _run_process "$service" "$command" &
 | 
						|
        cd -
 | 
						|
    fi
 | 
						|
}
 | 
						|
 | 
						|
# Screen rc file builder
 | 
						|
# Uses globals ``SCREENRC``
 | 
						|
# screen_rc service "command-line"
 | 
						|
function screen_rc {
 | 
						|
    local screen=$1
 | 
						|
    SCREENRC=$DEVMIDO_DIR/$screen-screenrc
 | 
						|
    if [[ ! -e $SCREENRC ]]; then
 | 
						|
        # Name the screen session
 | 
						|
        echo "sessionname $screen" > $SCREENRC
 | 
						|
        # Set a reasonable statusbar
 | 
						|
        echo "hardstatus alwayslastline '$SCREEN_HARDSTATUS'" >> $SCREENRC
 | 
						|
        # Some distributions override PROMPT_COMMAND for the screen terminal type - turn that off
 | 
						|
        echo "setenv PROMPT_COMMAND /bin/true" >> $SCREENRC
 | 
						|
        echo "screen -t shell bash" >> $SCREENRC
 | 
						|
    fi
 | 
						|
    # If this service doesn't already exist in the screenrc file
 | 
						|
    if ! grep $1 $SCREENRC 2>&1 > /dev/null; then
 | 
						|
        NL=`echo -ne '\015'`
 | 
						|
        echo "screen -t $1 bash" >> $SCREENRC
 | 
						|
        echo "stuff \"$2$NL\"" >> $SCREENRC
 | 
						|
 | 
						|
        if [[ -n ${LOGDIR} ]]; then
 | 
						|
            echo "logfile ${LOGDIR}/${1}.log.${CURRENT_LOG_TIME}" >>$SCREENRC
 | 
						|
            echo "log on" >>$SCREENRC
 | 
						|
        fi
 | 
						|
    fi
 | 
						|
}
 | 
						|
 | 
						|
# Set an option in an INI file
 | 
						|
# iniset config-file section option value
 | 
						|
function iniset {
 | 
						|
    local xtrace=$(set +o | grep xtrace)
 | 
						|
    set +o xtrace
 | 
						|
    local file=$1
 | 
						|
    local section=$2
 | 
						|
    local option=$3
 | 
						|
    local value=$4
 | 
						|
 | 
						|
    [[ -z $section || -z $option ]] && return
 | 
						|
 | 
						|
    if ! grep -q "^\[$section\]" "$file" 2>/dev/null; then
 | 
						|
        # Add section at the end
 | 
						|
        echo -e "\n[$section]" >>"$file"
 | 
						|
    fi
 | 
						|
    if ! ini_has_option "$file" "$section" "$option"; then
 | 
						|
        # Add it
 | 
						|
        sed -i -e "/^\[$section\]/ a\\
 | 
						|
$option = $value
 | 
						|
" "$file"
 | 
						|
    else
 | 
						|
        local sep=$(echo -ne "\x01")
 | 
						|
        # Replace it
 | 
						|
        sed -i -e '/^\['${section}'\]/,/^\[.*\]/ s'${sep}'^\('${option}'[ \t]*=[ \t]*\).*$'${sep}'\1'"${value}"${sep} "$file"
 | 
						|
    fi
 | 
						|
    $xtrace
 | 
						|
}
 | 
						|
 | 
						|
# Determinate is the given option present in the INI file
 | 
						|
# ini_has_option config-file section option
 | 
						|
function ini_has_option {
 | 
						|
    local xtrace=$(set +o | grep xtrace)
 | 
						|
    set +o xtrace
 | 
						|
    local file=$1
 | 
						|
    local section=$2
 | 
						|
    local option=$3
 | 
						|
    local line
 | 
						|
 | 
						|
    line=$(sed -ne "/^\[$section\]/,/^\[.*\]/ { /^$option[ \t]*=/ p; }" "$file")
 | 
						|
    $xtrace
 | 
						|
    [ -n "$line" ]
 | 
						|
}
 | 
						|
 | 
						|
function stop_process {
 | 
						|
    local service=$1
 | 
						|
 | 
						|
    # Kill via pid if we have one available
 | 
						|
    pkill -F $SERVICE_DIR/$SCREEN_NAME/$service.pid
 | 
						|
    rm $SERVICE_DIR/$SCREEN_NAME/$service.pid
 | 
						|
    screen -S $SCREEN_NAME -p $service -X kill
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
# MN Functions
 | 
						|
# ------------
 | 
						|
 | 
						|
# Wrapper for mn-conf command
 | 
						|
# Uses globals ``ZOOKEEPER_HOSTS``
 | 
						|
function configure_mn {
 | 
						|
    local value="$2"
 | 
						|
 | 
						|
    # quote with "" only when necessary.  we don't always quote because
 | 
						|
    # mn-conf complains for quoted booleans.  eg. "false"
 | 
						|
    if [[ "${value}" =~ ":" || "${value}" = "" ]]; then
 | 
						|
        value="\"${value}\""
 | 
						|
    fi
 | 
						|
 | 
						|
    # In some commands, mn-conf creates a local file, which requires root
 | 
						|
    # access.  For simplicity, always call mn-conf with root for now.
 | 
						|
    echo $1 : "${value}" | MIDO_ZOOKEEPER_HOSTS="$ZOOKEEPER_HOSTS" sudo mn-conf set
 | 
						|
}
 | 
						|
 | 
						|
# Restore xtrace
 | 
						|
$XTRACE
 |