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
|