#!/bin/bash
# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; sh-indentation: 4; tab-width: 8 -*-

# base: contains the base functions and environment variables needed to use
#       the pbench-agent.

# pipeline status is set to the status of the last command that *failed*
# in the pipeline (or 0 if all succeed): this way "ssh foo | sed '...' "
# will catch any ssh failure
set -o pipefail

# very first thing to do is figure out which pbench we are
if [[ -z "${pbench_run}" ]]; then
    pbench_run=$(pbench-config pbench_run pbench-agent)
    if [[ -z "${pbench_run}" ]]; then
        pbench_run=/var/lib/pbench-agent
    fi
    if [[ ! -d ${pbench_run} ]]; then
        mkdir ${pbench_run}
        if [[ ${?} -ne 0 ]]; then
            echo "[ERROR] unable to create pbench_run directory, ${pbench_run}" >&2
            exit 1
        fi
    fi
    export pbench_run
else
    if [[ ! -d ${pbench_run} ]]; then
        echo "[ERROR] the provided pbench run directory, ${pbench_run}, does not exist" >&2
        exit 1
    fi
fi

# the pbench temporary directory is always relative to the $pbench_run
# directory
pbench_tmp="${pbench_run}/tmp"
if [[ ! -d ${pbench_tmp} ]]; then
    mkdir ${pbench_tmp}
    if [[ ${?} -ne 0 ]]; then
        echo "[ERROR] unable to create TMP directory, ${pbench_tmp}" >&2
        exit 1
    fi
fi
export pbench_tmp

# log file - N.B. not a directory
if [[ -z "${pbench_log}" ]]; then
    pbench_log=$(pbench-config pbench_log pbench-agent)
    if [[ -z "${pbench_log}" ]]; then
        pbench_log=${pbench_run}/pbench.log
    fi
    export pbench_log
fi

if [[ -z "${pbench_install_dir}" ]]; then
    pbench_install_dir=$(pbench-config install-dir pbench-agent)
    if [[ -z "${pbench_install_dir}" ]]; then
        pbench_install_dir=/opt/pbench-agent
    fi
    if [[ ! -d ${pbench_install_dir} ]]; then
        echo "[ERROR] pbench installation directory, ${pbench_install_dir}, does not exist" >&2
        exit 1
    fi
    export pbench_install_dir
fi
pbench_bspp_dir=${pbench_install_dir}/bench-scripts/postprocess
pbench_lib_dir=${pbench_install_dir}/lib
export pbench_bspp_dir pbench_lib_dir

TS_FORMAT="%FT%H:%M:%S"

function timestamp {
    # use ns in the timestamp
    echo "$(date --utc +"${TS_FORMAT}.%N")"
}

function log {
    echo "[info][$(timestamp)] $*" >> ${pbench_log}
}

function warn_log {
    local log_date=$(timestamp)
    echo "[warn][${log_date}] $*" >&2
    echo "[warn][${log_date}] $*" >> ${pbench_log}
}

function error_log {
    local log_date=$(timestamp)
    echo "[error][${log_date}] $*" >&2
    echo "[error][${log_date}] $*" >> ${pbench_log}
}

export _pbench_debug_mode=0

function debug_log {
    local log_date=$(timestamp)
    if [[ "${_pbench_debug_mode}" != "0" ]]; then
        echo "[debug][${log_date}] $*"
    fi
    echo "[debug][${log_date}] $*" >> ${pbench_log}
}

# date may be set "accidentally" so add a var with an unlikely name
# to check whether we need to set it.
if [[ -z "${date}" || -z "${_PBENCH_DATE_SET}" ]]; then
    # don't use ns in the date
    export date=$(date --utc +"${TS_FORMAT}")
    export _PBENCH_DATE_SET=1
fi
# don't use colons and dashes in the date suffix
export date_suffix=$(date --date ${date} --utc +"%Y.%m.%dT%H.%M.%S")

function validate_hostname {
    validate-hostname "${1}"
    if [[ ${?} -ne 0 ]]; then
        error_log "[${script_name}] Invalid ${2}: '${1}'"
        exit 1
    fi
}

if [[ -z "${_pbench_hostname}" ]]; then
    export _pbench_hostname=$(hostname --short)
fi
validate_hostname "${_pbench_hostname}" "_pbench_hostname"

if [[ -z "${_pbench_full_hostname}" ]]; then
    export _pbench_full_hostname=$(hostname --fqdn)
fi
validate_hostname "${_pbench_full_hostname}" "_pbench_full_hostname"

if [[ -z "${_pbench_hostname_ip}" ]]; then
    export _pbench_hostname_ip=$(hostname --all-ip-addresses)
fi
for _ip in ${_pbench_hostname_ip}; do
    validate-ipaddress "${_ip}"
    if [[ ${?} -ne 0 ]]; then
        error_log "[${script_name}] Invalid IP address: '${_ip}'"
        exit 1
    fi
done

if [[ -z "${ssh_opts}" ]]; then
    ssh_opts=$(pbench-config ssh_opts results)
    if [[ -z "${ssh_opts}" ]]; then
        ssh_opts='-o StrictHostKeyChecking=no'
    fi
    export ssh_opts
fi

if [[ -z "${scp_opts}" ]]; then
    scp_opts=$(pbench-config scp_opts results)
    if [[ -z "${scp_opts}" ]]; then
        scp_opts='-o StrictHostKeyChecking=no'
    fi
    export scp_opts
fi

if [[ -z "${PBENCH_RPM_REQUIREMENT_MODE}" ]]; then
    PBENCH_RPM_REQUIREMENT_MODE=$(pbench-config rpm_requirement_mode pbench-agent)
fi
if [[ "${PBENCH_RPM_REQUIREMENT_MODE}" != "strict" && "${PBENCH_RPM_REQUIREMENT_MODE}" != "relaxed" ]]; then
    error_log "[${script_name}] Invalid PBENCH_RPM_REQUIREMENT_MODE, '${PBENCH_RPM_REQUIREMENT_MODE}', expected 'strict' or 'relaxed'"
    exit 1
fi

# Constants
tools_group_prefix="tools-v1-"

function gen_tools_group_list() {
    pushd "${pbench_run}" >/dev/null
    if [[ ${?} -ne 0 ]]; then
        return 1
    fi
    local groups=$(/bin/ls -d ${tools_group_prefix}* 2> /dev/null | sed -e s/^${tools_group_prefix}//g)
    popd >/dev/null
    printf "${groups}"
    return 0
}

function gen_tools_group_dir {
    local group="${1}"
    if [[ -z "${group}" ]]; then
        return 1
    fi
    local tools_group_dir="${pbench_run}/${tools_group_prefix}${group}"
    printf -- "${tools_group_dir}"
    return 0
}

function verify_tool_group {
    # Ensure we have a tools group directory to work with
    local group="${1}"
    local param_name=${2:-"--group"}
    local tools_group_dir="$(gen_tools_group_dir ${group})"
    if [[ -z "${tools_group_dir}" ]]; then
        error_log "[base] INTERNAL ERROR - Empty argument passed to verify_tool_group()"
        exit 1
    fi
    if [[ ! -d "${tools_group_dir}" ]]; then
        printf -- "\t${script_name}: invalid ${param_name} option (\"${group}\"), directory not found: ${tools_group_dir}\n" >&2
        return 1
    fi
    if [[ -z "$(ls ${tools_group_dir})" ]]; then
        printf -- "${script_name}: WARNING: No tools are registered\n" >&2
    fi
    printf -- "${tools_group_dir}"
    return 0
}

function verify_collect_sysinfo {
    local sysinfo="${1}"
    pbench-verify-sysinfo-options "${sysinfo}"
    local ret=${?}
    if [[ ${ret} -ne 0 ]]; then
        printf -- "\t${script_name}: invalid --sysinfo option (\"${sysinfo}\")\n" >&2
    fi
    return ${ret}
}

function verify_common_bench_script_options {
    verify_tool_group "${1}" --tool-group > /dev/null
    local ret_tg=${?}
    verify_collect_sysinfo "${2}"
    local ret_cs=${?}
    if [[ ${ret_tg} -ne 0 || ${ret_cs} -ne 0 ]]; then
        printf -- "\n"
        usage >&2
        exit 1
    fi
}

# Generate inventory file with controller and remotes.
function generate_inventory {
    local component="${1}"
    local preamble=""
    preamble="[controller]\n%s\n\n[remote]\n"
    printf -- ${preamble} "${_pbench_full_hostname}"
    for dirent in $(/bin/ls -1 ${tool_group_dir}); do
        if [[ "${dirent}" == "__trigger__" ]]; then
            # Ignore trigger files
            continue
        elif [[ ! -d ${tool_group_dir}/${dirent} ]]; then
            # Ignore spurious files
            continue
        fi
        printf -- "%s\n" "${dirent}"
    done
}

function vercmp {
    # Compare two package versions via `rpmdev-vercmp`.
    local _package="${1}"
    local _match="${2}"
    local _exp_ver="${3}"
    local _ins_ver="${4}"

    # Note we declare `_stderr` local without an initializer because the return
    # code of rpmdev-vercmp would be overwritten by the return code of the
    # "local" declaration.
    local _stderr
    _stderr=$(rpmdev-vercmp "${_ins_ver}" "${_exp_ver}" 2>&1 > /dev/null)
    local _res=${?}
    if [[ ${_res} -ne 0 && ${_res} -ne 11 && ${_res} -ne 12 ]]; then
        error_log "rpmdev-vercmp - ${_stderr}"
        return 1
    fi
    if [[ "${_match}" == "equ" ]]; then
        [[ ${_res} -eq 0 ]]
    elif [[ "${_match}" == "gte" ]]; then
        [[ ${_res} -eq 0 || ${_res} -eq 11 ]]
    elif [[ "${_match}" == "gtr" ]]; then
        [[ ${_res} -eq 11 ]]
    else
        error_log "unrecognized 'match' argument, '${_match}'"
        return 1
    fi
    if [[ ${?} -ne 0 ]]; then
        error_log "wrong version installed: found ${_package}-${_ins_ver}, expected ${_package}-${_exp_ver} (${_match})"
        return 1
    fi

    debug_log "found ${_package}-${_ins_ver}"
    return 0
}

function interrupt() {
    # Capture the shell's signal status (e.g., 130 for SIGINT). Attempt to
    # gracefully terminate the Pbench Tool Meister session using the
    # --interrupt option, then exit the benchmark script with the saved
    # signal status.
    rc=${?}
    pbench-tool-meister-stop --interrupt "${tool_group}"
    if [[ ${?} != 0 ]]; then
        error_log "[${script_name}]: failed to stop the tool meisters on interrupt."
    fi
    exit ${rc}
}
