#! /bin/bash
# vim: shiftwidth=4 tabstop=4 expandtab

# shellcheck source=/dev/null
source "$LIB_DIR"/stats

subcommand__help() {
    if [[ -z "$2" ]] || [[ "$2" == "stats" ]]; then
        echo "$COMMAND stats HYP [status] [--json]"
        if [[ "$1" == "details" ]] && [[ "$2" == "stats" ]]; then
            echo "  Show hypervisor stats"
            echo "    HYP              Hypervisor name (optional, default: local one)"
            echo "    status           Filter on VM status(es) (optional, default: active)"
            echo "                     Possible values:"
            echo "                     $( implode ", " "${HYP_STATS_STATUS[@]}" )"
            echo "    --json           JSON output"
        fi
    fi
    if [[ -z "$2" ]] || [[ "$2" == "candidates" ]]; then
        echo "$COMMAND candidates vCPU RAM [--best]"
        if [[ "$1" == "details" ]] && [[ "$2" == "candidates" ]]; then
            echo "  Find the candidate hypervisor(s) to deploy a new VM"
            echo "    [vCPU]             VM allocated vCPU (optional)"
            echo "    [RAM]              VM allocated memory (in Mo, optional)"
            echo "    --best             Only show the best candidate hypervisor"
            echo "  Note: use -F/--force to force identify a candidate even if no hypervisor"
            echo "  could normally host this VM."
        fi
    fi
    if [[ -z "$2" ]] || [[ "$2" == "check_allocated_resources" ]]; then
        echo "$COMMAND check_allocated_resources HYP [status]"
        if [[ "$1" == "details" ]] && [[ "$2" == "check_allocated_resources" ]]; then
            echo "  Check hypervisor allocated resources"
            echo "    HYP                            " \
                "Hypervisor name (optional, default: local one)"
            echo "    status                         " \
                "Filter on VM status(es) (optional, default: active, possible values:"
            echo "                                   " \
                "$( implode ", " "${HYP_STATS_STATUS[@]}" ))"
            echo "    -r|--hyp-reserved-memory       " \
                "Hypervisor reserved memory (in Mb, optional, default: ${HYP_RESERVED_MEMORY}Mb)"
            echo "    -w|--warning-cpu-threshold     " \
                "Warning CPU threshold (in %, optional, default: ${WARNING_CPU_THRESHOLD}%)"
            echo "    -c|--critical-cpu-threshold    " \
                "Critical CPU threshold (in %, optional, default: ${CRITICAL_CPU_THRESHOLD}%)"
        fi
    fi
}

subcommand__stats() {
    local hyp status json=0 stats
    local -a status_list status_args
    for opt in "${@:1}"; do
        case $opt in
            --json)
                json=1
                ;;
            *)
                if in_array "$opt" "${HYP_STATS_STATUS[@]}"; then
                    status_list+=( "$opt" )
                elif [[ -z "$hyp" ]]; then
                    hyp=$opt
                else
                    usage "Unknown parameter '$opt'"
                fi
                ;;
        esac
    done
    [[ -z "$hyp" ]] && hyp=$(hostname -s)
    check_hyp "$hyp" || usage "Invalid hypervisor '$hyp'"

    [[ "${#status_list[@]}" -eq 0 ]] && status_list+=( "active" )
    if ! in_array "all" "${status_list[@]}"; then
        for status in "${status_list[@]}"; do
            status_args+=( "--list-${status}" )
        done
    fi

    log DEBUG "Get hypervisor $hyp stats about ${status_list[*]} VMs..."
    stats=$( get_hyp_stats "$hyp" "${status_args[@]}" )
    if [[ $json -eq 1 ]]; then
        jq <<< "$stats"
        exit 0
    fi

    jq -r "$JQ_FORMAT_SIZE"'
        "Physical CPU core: \(.cpu)\n"
        + "vCPUs: "
            + "reserved=\(.vcpu_reserved) / "
            + "allocatable=\(.vcpu_allocatable) / "
            + "allocated=\(.vcpu_allocated) (\(.vcpu_allocated_percent)%) / "
            + "max allocated=\(.vcpu_max_allocated) (\(.vcpu_max_allocated_percent)%) / "
            + "available=\(.vcpu_available) (\(.vcpu_available_percent)%)\n"
        + "Physical memory: \(.memory | format_size) / "
            + "reserved=\(.memory_reserved | format_size) / "
            + "used=\(.memory_used | format_size) (\(.memory_used_percent)%) / "
            + "cached=\(.memory_cached | format_size) (\(.memory_cached_percent)%) / "
            + "buffers=\(.memory_buffers | format_size) / "
            + "free=\(.memory_free | format_size) (\(.memory_free_percent)%)\n"
        + "SWAP : \(.swap | format_size) / "
            + "used=\(.swap_used | format_size) (\(.swap_used_percent)%) / "
            + "cached=\(.swap_cached | format_size) (\(.swap_cached_percent)%) / "
            + "free=\(.swap_free | format_size) (\(.swap_free_percent)%)\n"
        + "Total memory (RAM+SWAP): \(.total_memory | format_size) / "
            + "used=\(.total_memory_used | format_size) (\(.total_memory_used_percent)%) / "
            + "cached=\(.total_memory_cached | format_size) (\(.total_memory_cached_percent)%) / "
            + "free=\(.total_memory_free | format_size) (\(.total_memory_free_percent)%)\n"
        + "VMs memory: "
            + "allocatable=\(.memory_allocatable | format_size) / "
            + "allocated=\(.memory_allocated | format_size) (\(.memory_allocated_percent)%) / "
            + "max allocated=\(.memory_max_allocated | format_size) (\(.memory_max_allocated_percent)%) / "
            + "used=\(.memory_allocated_used | format_size) (\(.memory_allocated_used_percent)%) / "
            + "available=\(.memory_available | format_size) (\(.memory_available_percent)%)\n"
    ' <<< "$stats"

    if [[ "$( jq -r '.vms | length ' <<< "$stats" )" -eq 0 ]]; then
        [[ "$status" != "all" ]] && echo "No $status VMs."
        [[ "$status" == "all" ]] && echo "No VMs."
        exit 0
    fi

    jq -r "$JQ_FORMAT_SIZE"'
        . as $root |
        .vms |
        to_entries |
        sort_by(.value.status, .value.vcpu.current, .value.balloon.current, .value.vcpu.maximum, .value.balloon.maximum) |
        (
            ["VM", "Status", "Current vCPUs", "Max vCPUs", "Current memory", "Used memory", "Max memory"],
            ["--", "------", "-------------", "---------", "--------------", "-----------", "----------"],
            (
                .[] |
                [
                    .key,
                    .value.status,
                    .value.vcpu.current,
                    .value.vcpu.maximum,
                    (.value.balloon.current | format_size),
                    (.value.balloon.used | format_size),
                    (.value.balloon.maximum | format_size)
                ]
            ),
            ["------"],
            [
                "",
                "Total:",
                $root.vcpu_allocated,
                $root.vcpu_max_allocated,
                ($root.memory_allocated | format_size),
                ($root.memory_allocated_used | format_size),
                ($root.memory_max_allocated | format_size)
            ]
        ) |
        @tsv
    ' <<< "$stats" | column -t -s $'\t'
}

subcommand__candidates() {
    check_generate_script_not_supported
    local vcpu memory best=0
    for opt in "${@:1}"; do
        case $opt in
            --best)
                best=1
                ;;
            *)
                if [[ "${vcpu:-null}" == "null" ]]; then
                    check_int "$opt" 1 || usage "Invalid vCPUs value." \
                        "Must be a positive integer."
                    vcpu=$opt
                elif [[ "${memory:-null}" == "null" ]]; then
                    check_int "$opt" 1 || usage "Invalid memory value." \
                        "Must be a positive integer."
                    (( memory=$opt*1024*1024 ))
                else
                    usage "Unknown parameter '$opt'"
                fi
                ;;
        esac
    done
    [[ "${vcpu:-null}" == "null" ]] && usage "You must provide VM vCPUs & memory"
    [[ "${memory:-null}" == "null" ]] && usage "You must provide VM memory as second parameter"
    log INFO "Looking for a hypervisor to host a VM with $vcpu vCPU(s) and $(format_size $memory) of memory"
    local candidates='[]'
    local hyp stats vcpu_allocatable memory_allocatable vcpu_available memory_available \
        available_ratio
    for hyp in $HYPS; do
        stats=$(get_hyp_stats "$hyp" --list-active)
        vcpu_available=$(jq -r .vcpu_available <<< "$stats" )
        if [[ -n "$vcpu" ]] && [[ $vcpu_available -lt $vcpu ]]; then
            debug "$hyp: no enough available vCPUs ($vcpu_available < $vcpu)"
            [[ $FORCE -eq 0 ]] && continue
            debug "Force mode: keep $hyp as candidate"
        fi
        memory_available=$(jq -r .memory_available <<< "$stats" )
        if [[ -n "$memory" ]] && [[ $memory_available -lt $memory ]]; then
            debug "$hyp: no enough available memory ($memory_available < $memory)"
            [[ $FORCE -eq 0 ]] && continue
            debug "Force mode: keep $hyp as candidate"
        fi
        available_ratio=$(
            jq '(
                (.vcpu_available * 100 / .vcpu_allocatable)
                + (.memory_available * 100 / .memory_allocatable)
            ) * 10 | round / 10' <<< "$stats"
        )
        log INFO "$hyp: could host it ($vcpu_available vCPUs / $(format_size $memory_available) " \
            "memory available, ratio = $available_ratio)"
        candidates=$(jq ". += [$(jo name="$hyp" available_ratio=$available_ratio)]" <<< "$candidates")
    done
    candidates_names=$(jq -r "sort_by(.available_ratio) | reverse | .[] | .name" <<< "$candidates")
    if [[ -z "$candidates_names" ]]; then
        echo "No candidate hypervisor found"
        exit 1
    fi
    if [[ $best -eq 1 ]]; then
        echo -e "$candidates_names"|head -n1
    else
        echo -e "$candidates_names"
    fi
}

function subcommand__check_allocated_resources() {
    check_generate_script_not_supported
    local hyp stats status idx=1
    local -a status_list status_args
    while [[ $idx -le $# ]]; do
        local opt=${!idx}
        case $opt in
            -r|--hyp-reserved-memory)
                ((idx++))
                HYP_RESERVED_MEMORY=${!idx}
                check_int "$HYP_RESERVED_MEMORY" || \
                    usage "Invalid hypervisor reserved memory specified"
            ;;
            -w|--warning-cpu-threshold)
                ((idx++))
                WARNING_CPU_THRESHOLD=${!idx}
                check_int "$WARNING_CPU_THRESHOLD" || \
                    usage "Invalid warning CPU threshold specified"
            ;;
            -c|--critical-cpu-threshold)
                ((idx++))
                CRITICAL_CPU_THRESHOLD=${!idx}
                check_int "$CRITICAL_CPU_THRESHOLD" || \
                    usage "Invalid critical CPU threshold specified"
            ;;
            *)
                if in_array "$opt" "${HYP_STATS_STATUS[@]}"; then
                    status_list+=( "$opt" )
                elif [[ -z "$hyp" ]]; then
                    hyp=$opt
                else
                    usage "Unknown parameter '$opt'"
                fi
        esac
        ((idx++))
    done

    [[ -z "$hyp" ]] && hyp=$(hostname -s)
    check_hyp "$hyp" || usage "Invalid hypervisor '$hyp'"

    [[ "${#status_list[@]}" -eq 0 ]] && status_list+=( "active" )
    if ! in_array "all" "${status_list[@]}"; then
        for status in "${status_list[@]}"; do
            status_args+=( "--list-${status}" )
        done
    fi

    log DEBUG "Get hypervisor $hyp stats about ${status_list[*]} VMs..."
    stats=$( get_hyp_stats "$hyp" "${status_args[@]}" )

    HYP_MEMORY=$( jq -r '.memory' <<< "$stats" )
    HYP_ALLOCABLE_MEMORY=$( jq -r '.memory_allocatable' <<< "$stats" )
    HYP_ALLOCATED_MEMORY=$( jq -r '.memory_allocated' <<< "$stats" )
    HYP_ALLOCATED_MEMORY_PERC=$( jq -r '.memory_allocated_percent' <<< "$stats" )

    HYP_CPU=$( jq -r '.cpu' <<< "$stats" )
    HYP_ALLOCATED_CPU=$( jq -r '.vcpu_allocated' <<< "$stats" )
    HYP_ALLOCATED_CPU_PERC=$( jq -r '.vcpu_allocated_percent' <<< "$stats" )

    EXIT_CODE=0
    EXIT_STATUS=OK
    if [[ "$HYP_ALLOCATED_MEMORY" -ge "$HYP_MEMORY" ]]; then
        EXIT_CODE=2
        EXIT_STATUS=CRITICAL
    elif [[ "$HYP_ALLOCATED_MEMORY" -ge "$HYP_ALLOCABLE_MEMORY" ]]; then
        EXIT_CODE=1
        EXIT_STATUS=WARNING
    fi

    (( HYP_WARNING_ALLOCATED_CPU_THRESHOLD=HYP_CPU*WARNING_CPU_THRESHOLD ))
    (( HYP_CRITICAL_ALLOCATED_CPU_THRESHOLD=HYP_CPU*CRITICAL_CPU_THRESHOLD ))
    debug "Hypervisor critical/warning allocated CPU thresholds:" \
        "$HYP_WARNING_ALLOCATED_CPU_THRESHOLD / $HYP_CRITICAL_ALLOCATED_CPU_THRESHOLD"

    if [[ "$HYP_ALLOCATED_CPU" -ge "$HYP_CRITICAL_ALLOCATED_CPU_THRESHOLD" ]]; then
        EXIT_CODE=2
            EXIT_STATUS=CRITICAL
    elif [[ $EXIT_CODE -le 1 ]] && \
        [[ "$HYP_ALLOCATED_CPU" -ge "$HYP_WARNING_ALLOCATED_CPU_THRESHOLD" ]]; then
            EXIT_CODE=1
            EXIT_STATUS=WARNING
    fi

    add_perfdata \
        allocated_memory \
        "${HYP_ALLOCATED_MEMORY}Kb" \
        "$HYP_ALLOCABLE_MEMORY" "$HYP_MEMORY" \
        0 "$HYP_MEMORY"

    add_perfdata \
        allocated_vcpu \
        "$HYP_ALLOCATED_CPU" \
        "$HYP_WARNING_ALLOCATED_CPU_THRESHOLD" "$HYP_CRITICAL_ALLOCATED_CPU_THRESHOLD" \
        0 "$HYP_CPU"

    echo "$EXIT_STATUS - $HYP_ALLOCATED_MEMORY_PERC% allocated memory" \
        "($( format_size "$HYP_ALLOCATED_MEMORY" ) /" \
        "$( format_size "$HYP_ALLOCABLE_MEMORY" ) allocable," \
        "$( format_size "$HYP_MEMORY" ) total)," \
        "$HYP_ALLOCATED_CPU allocated vCPU ($HYP_WARNING_ALLOCATED_CPU_THRESHOLD warning /" \
        "$HYP_CRITICAL_ALLOCATED_CPU_THRESHOLD critical) | $( format_perfdata )"

    exit $EXIT_CODE
}

run_subcommand "${COMMAND_ARGS[@]}"
