# This file contains bash functions that may be used by guest systems (VMs).

# Sourcing this file calls functions fix_path_env and source_deploy.

source "$LIB_DIR/functions.sh"
source "$LIB_DIR/functions-common-devstack"

# Make devstack's operating system identification work with nounset
function init_os_ident {
    if [[ -z "${os_PACKAGE:-""}" ]]; then
        GetOSVersion
    fi
}

function source_deploy {
    if [ -n "${VM_SHELL_USER:-}" ]; then
        # Already sourced
        return 0
    fi
    if mountpoint -q /vagrant; then
        source "$CONFIG_DIR/deploy.vagrant"
    else
        source "$CONFIG_DIR/deploy.osbash"
    fi
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# If our sudo user's PATH is preserved (and does not contain sbin dirs),
# some commands won't be found. Observed with Vagrant shell provisioner
# scripts using sudo after "su - vagrant".
# Adding to the path seems preferable to messing with the vagrant user's
# sudoers environment (or working with a separate Vagrant user).

function fix_path_env {
    if is_root; then return 0; fi
    if echo 'echo $PATH'|sudo sh|grep -q '/sbin'; then return 0; fi
    export PATH=$PATH:/sbin:/usr/sbin:/usr/local/sbin
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
function zero_empty_space {
    echo "Filling empty disk space with zeros"
    sudo dd if=/dev/zero of=/filler bs=1M 2>/dev/null || true
    sudo rm /filler
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# For guest scripts to let osbash know they are running; used when osbashauto
# runs scripts inside of the VM (STATUS_DIR directory must be shared between
# host and VM).

function indicate_current_auto {
    if [ "${VM_SHELL_USER:-}" = "osbash" ]; then
        local scr_name=${1:-$(basename "$0")}
        local fpath=${2:-"/$STATUS_DIR/$scr_name.begin"}
        mkdir -p "$STATUS_DIR"
        touch "$fpath"
    fi
    log_point "script begin"
}
# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
# Debug function to make a script halt execution until a tmp file is removed

function wait_for_file {
    # If no argument is passed, use empty string (to pass nounset option)
    local msg=${1-""}
    local wait_file=remove_to_continue
    [ -n "$msg" ] && wait_file=${wait_file}_${msg}
    echo >&2 "DEBUG wait_for_file pause; remove /tmp/$wait_file to continue."
    touch "/tmp/$wait_file"
    while [ -e "/tmp/$wait_file" ]; do
        sleep 1
    done
}
#-------------------------------------------------------------------------------
# Copy stdin/stderr to log file
#-------------------------------------------------------------------------------

function exec_logpath {
    local log_path=$1

    # Append all stdin and stderr to log file
    exec > >(tee -a "$log_path") 2>&1
}

function exec_logfile {
    local log_dir=${1:-/home/$VM_SHELL_USER/log}

    # Default extension is log
    local ext=${2:-log}

    mkdir -p "$log_dir"

    # Log name based on name of running script
    local base_name=$(basename "$0" .sh)

    local prefix=$(get_next_prefix "$log_dir" "$ext")
    local log_name="${prefix}_$base_name.$ext"

    exec_logpath "$log_dir/$log_name"
}

#-------------------------------------------------------------------------------
# Functions that need to run as root
#-------------------------------------------------------------------------------

# /sbin/mount.vboxsf often ends up as a broken symlink, resulting in errors
# when trying to mount the share in osbashauto.
function as_root_fix_mount_vboxsf_link {
    local file=/sbin/mount.vboxsf
    if [ -L $file -a ! -e $file ]; then
        echo "$file is a broken symlink:"
        ls -l "$file"
        echo "Trying to fix it."

        sdir="/usr/lib/VBoxGuestAdditions"
        if [ -L "$sdir" -a ! -e "$sdir" ]; then
          # /usr/lib/VBoxGuestAdditions is a convenient link into a directory
          # under /opt that changes its name with VirtualBox versions.
          # In some cases, the link was missing but /sbin/mount.vboxsf
          # pointed there.
          echo "$sdir is a broken symlink:"
          ls -l "sdir"
          shopt -s nullglob
          local new=(/opt/VBoxGuestAdditions*/lib/VBoxGuestAdditions)
          if [ -n "$new" ]; then
              ln -sv "$new" "$sdir"
          else
              echo "as_root_fix_mount_vboxsf_link: no VGA dir, aborting."
              return 1
          fi
        fi

        if [ -L $file -a ! -e $file ]; then
          # In some cases, /sbin/mount.vboxsf gets the path in
          # /usr/lib/VBoxGuestAdditions. Try to fix the link.
          echo "Trying harder."
          new_target=$(find "$sdir/" -name "mount.vboxsf")
          if [ -z "$new_target" ]; then
              echo "as_root_fix_mount_vboxsf_link: no mount.vboxsf, aborting."
              return 1
          else
              echo "Found new target: $new_target"
          fi
          ln -svf "$new_target" "$file"
        fi
    fi
}

function as_root_inject_sudoer {
    if grep -q "${VM_SHELL_USER}" /etc/sudoers; then
        echo "${VM_SHELL_USER} already in /etc/sudoers"
    else
        echo "${VM_SHELL_USER} ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers
        echo "Defaults:${VM_SHELL_USER} !requiretty" >> /etc/sudoers
    fi
}

# Change to a regular user to execute a guest script (and log its output)

function as_root_exec_script {
    local script_path=$1
    local script_name="$(basename "$script_path" .sh)"

    echo "$(date) start $script_path"

    local prefix=$(get_next_prefix "$LOG_DIR" "auto")
    local log_path=$LOG_DIR/${prefix}_$script_name.auto

    su - "$VM_SHELL_USER" -c "bash $script_path" >"$log_path" 2>&1
    local rc=$?
    if [ $rc -ne 0 ]; then
        echo "$(date) ERROR: status $rc for $script_path" |
            tee >&2 -a "$LOG_DIR/error.log"
    else
        echo "$(date)  done"
    fi
    return $rc
}

#-------------------------------------------------------------------------------
# Root wrapper around devstack functions for manipulating config files
#-------------------------------------------------------------------------------

# Return predictable temporary path for configuration file editing.
# Used to simplify debugging.
function get_iniset_tmpfile {
    local file=$1

    # Set tmpdir="$LOG_DIR" if you want the temporary files to survive reboots.
    local tmpdir="/tmp"
    local ext="iniset"

    local prefix=$(get_next_prefix "$tmpdir" "$ext")

    # Typical tmpfile path: /tmp/000_etc_keystone_keystone.conf.iniset
    local tmpfile=$tmpdir/$prefix$(echo "$file" | tr '/' '_').$ext

    # Create file owned by regular user so it can be edited without privileges
    touch "$tmpfile"

    echo "$tmpfile"
}

# Set an option in an INI file
# iniset config-file section option value
function iniset_sudo {
    if (($# != 4)); then
        echo -n "ERROR: $# instead of 4 arguments for iniset: $*"
        exit 1
    fi

    local file=$1
    shift
    local tmpfile=$(get_iniset_tmpfile "$file")
    # Create a temporary copy, work on it, and copy it back into place
    sudo cp -fv "$file" "$tmpfile"
    echo >&2 iniset "$tmpfile" "$@"
    iniset "$tmpfile" "$@"
    cat "$tmpfile" | sudo tee "$file" >/dev/null
}

# Comment an option in an INI file
# inicomment config-file section option
function inicomment_sudo {
    local file=$1
    shift
    local tmpfile=$(get_iniset_tmpfile "$file")
    # Create a temporary copy, work on it, and copy it back into place
    sudo cp -fv "$file" "$tmpfile"
    echo >&2 inicomment "$tmpfile" "$@"
    inicomment "$tmpfile" "$@"
    cat "$tmpfile" | sudo tee "$file" >/dev/null
}

# Determinate is the given option present in the INI file
# ini_has_option config-file section option
function ini_has_option_sudo {
    local file=$1
    shift
    local tmpfile=$(get_iniset_tmpfile "$file")
    # Create a temporary copy, work on it
    sudo cp -fv "$file" "$tmpfile"
    echo >&2 ini_has_option "$tmpfile" "$@"
    ini_has_option "$tmpfile" "$@"
}

#-------------------------------------------------------------------------------
# Functions for manipulating config files without section
#-------------------------------------------------------------------------------

function iniset_sudo_no_section {
    local file=$1
    shift
    local tmpfile=$(get_iniset_tmpfile "$file")
    # Create a temporary copy, work on it, and copy it back into place
    sudo cp -fv "$file" "$tmpfile"
    iniset_no_section "$tmpfile" "$@"
    cat "$tmpfile" | sudo tee "$file" >/dev/null
}

# ini_has_option_no_section config-file option
function ini_has_option_no_section {
    local xtrace=$(set +o | grep xtrace)
    set +o xtrace
    local file=$1
    local option=$2
    local line
    line=$(sed -ne "/^$option[ \t]*=/ p;" "$file")
    $xtrace
    [ -n "$line" ]
}

# Set an option in an INI file
# iniset_no_section config-file option value
function iniset_no_section {
    local xtrace=$(set +o | grep xtrace)
    set +o xtrace
    local file=$1
    local option=$2
    local value=$3

    [[ -z $option ]] && return

    if ! ini_has_option_no_section "$file" "$option"; then
        # Add it
        sed -i -e "1 i\
$option = $value
" "$file"
    else
        local sep=$(echo -ne "\x01")
        # Replace it
        sed -i -e '/^'${option}'/ s'${sep}'^\('${option}'[ \t]*=[ \t]*\).*$'${sep}'\1'"${value}"${sep} "$file"
    fi
    $xtrace
}


#-------------------------------------------------------------------------------
# OpenStack helpers
#-------------------------------------------------------------------------------

function mysql_exe {
    local cmd="$1"
    echo "mysql cmd: $cmd."
    # XXX install-guide uses password, but distro install uses socket auth
    sudo mysql -u "root" -e "$cmd"
}

function setup_database {
    local service=$1
    local db_user=$2
    local db_password=$3

    echo -n "Waiting for database server to come up."
    until mysql_exe quit >/dev/null 2>&1; do
        sleep 1
        echo -n .
    done
    echo

    mysql_exe "CREATE DATABASE $service"
    mysql_exe "GRANT ALL ON ${service}.* TO '$db_user'@'%' IDENTIFIED BY '$db_password';"
    mysql_exe "GRANT ALL ON ${service}.* TO '$db_user'@'localhost' IDENTIFIED BY '$db_password';"
}

# Wait for neutron to come up. Due to a race during the operating system boot
# process, the neutron server sometimes fails to come up. We restart the
# neutron server if it does not reply for too long.
function wait_for_neutron {
    (
    source $CONFIG_DIR/demo-openstackrc.sh
    echo -n "Waiting for neutron to come up."
    local cnt=0
    until openstack network list >/dev/null 2>&1; do
        if [ "$cnt" -eq 10 ]; then
            echo
            echo "ERROR No response from neutron. Restarting neutron-server."
            node_ssh controller "sudo service neutron-server restart"
            echo -n "Waiting for neutron to come up."
        elif [ "$cnt" -eq 20 ]; then
            echo
            echo "ERROR neutron does not seem to come up. Aborting."
            exit 1
        fi
        echo -n .
        sleep 1
        cnt=$((cnt + 1))
    done
    echo
    )
}

# Wait for keystone to come up
function wait_for_keystone {
    (
    source "$CONFIG_DIR/admin-openstackrc.sh"
    echo -n "Waiting for keystone to come up."
    until openstack user list >/dev/null 2>&1; do
        echo -n .
        sleep 1
    done
    echo
    )
}

#-------------------------------------------------------------------------------
# Network configuration
#-------------------------------------------------------------------------------

# Return the nth network interface name (not counting loopback; 0 -> eth0)
function ifnum_to_ifname {
    local if_num=$1

    # Skip loopback and start counting with next interface
    local iface=${IF_NAMES[$((if_num + 1))]}

    echo >&2 "ifnum_to_ifname: interface $if_num is $iface"
    echo "$iface"
}

# Get all network interfaces (e.g. eth0, p2p1, ens0, enp0s3) into an array
function set_iface_list {
    unset IF_NAMES
    local iface
    for iface in $(ip -o link show|awk '/: / {print $2}'|tr -d ':'); do
        IF_NAMES+=($iface)
    done
    echo "Set IF_NAMES to ${IF_NAMES[*]}"
}

function hostname_to_ip {
    local host_name=$1
    getent hosts "$host_name"|awk '{print $1}'
}

function config_network {
    init_os_ident
    if is_ubuntu; then
        source "$LIB_DIR/functions.ubuntu.sh"
    else
        source "$LIB_DIR/functions.fedora.sh"
    fi

    netcfg_init

    # Get network interface configuration (NET_IF_?) for this node
    unset -v NET_IF_0 NET_IF_1 NET_IF_2 NET_IF_3
    get_node_netif_config "$(hostname)"

    local index
    local iftype
    for index in "${!NODE_IF_TYPE[@]}"; do
        iftype=${NODE_IF_TYPE[index]}
        config_netif "$iftype" "$index" "${NODE_IF_IP[index]}"
    done
}

#-------------------------------------------------------------------------------
# Log points
#------------------------------------------------------------------------------

# Record current size of log files of interest so we can later split them
# accordingly.
# Log points can be set anywhere in a client script simply by adding a
# line: log_point "log point name"

function log_point {
    local caller=$(basename "$0" .sh)
    local commit_msg=$1
    local logdir=${2:-/var/log}
    local ext=lsl
    local prefix=$(get_next_prefix "$logdir" "$ext")

    local fname
    fname=${prefix}_$(echo "${caller}_-_$commit_msg"|tr ' ' '_').$ext

    (
    cd "$logdir"
    sudo bash -c "shopt -s nullglob; ls -l auth.log* keystone/* upstart/*.log mysql/* neutron/*" | \
        sudo tee "$logdir/$fname" > /dev/null
    )
}

#-------------------------------------------------------------------------------
# ssh wrapper functions
#-------------------------------------------------------------------------------

function no_chk_ssh {
    echo >&2 "ssh $*"
    # Options set to disable strict host key checking and related messages.
    ssh \
        -o "UserKnownHostsFile /dev/null" \
        -o "StrictHostKeyChecking no" \
        -o LogLevel=error \
        "$@"
}

# ssh from one node VM to another node in the cluster
function node_ssh {
    no_chk_ssh -i "$HOME/.ssh/osbash_key" "$@"
}

#-------------------------------------------------------------------------------
fix_path_env
source_deploy
#-------------------------------------------------------------------------------

# vim: set ai ts=4 sw=4 et ft=sh:
