#!/usr/bin/sh
#
# ovirt-hosted-engine-setup -- ovirt hosted engine setup
# Copyright (C) 2013-2017 Red Hat, Inc.
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This library is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public
# License along with this library; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
#

HOSTED_ENGINE_CONF='/etc/ovirt-hosted-engine/hosted-engine.conf'
VIRSH_AUTH='/etc/ovirt-hosted-engine/virsh_auth.conf'

blankUUID='00000000-0000-0000-0000-000000000000'

if [ -r "${HOSTED_ENGINE_CONF}" ] ; then
    source "${HOSTED_ENGINE_CONF}"
fi

rc=1

exit_not_deployed() {
    echo "You must run deploy first"
    exit 1
}

exit_not_correctly_deployed() {
    echo It seems like a previous attempt to deploy hosted-engine failed \
         or it\'s still in progress. Please clean it up before trying again
    exit 2
}

check_vm_conf() {
    if [ ! -r "${conf}" ] ; then
        echo The hosted engine configuration has not been retrieved from shared storage yet,
        if ! systemctl is-active --quiet ovirt-ha-agent 1>/dev/null 2>&1; then
            echo please ensure that ovirt-ha-agent service is running.
        else
            echo for more details please check sanlock status.
        fi
        echo
        exit 1
    fi
}


usage() {
    if [ -n "$1" ] ; then
        cli_broker "$@" --help
        exit $rc
    fi
    cat << __EOF__
Usage: $0 [--help] <command> [<command-args>]
    --help
        show this help message.

    The available commands are:
        --deploy [options]
            run ovirt-hosted-engine deployment.
        --vm-start
            start VM on this host
        --vm-start-paused.
            start VM on this host with qemu paused.
        --vm-shutdown
            gracefully shutdown the VM on this host.
        --vm-poweroff
            forcefully poweroff the VM on this host.
        --vm-status [--json]
            VM status according to the HA agent. If --json is given, the
            output will be in machine-readable (JSON) format.
        --add-console-password [--password=<password>]
            Create a temporary password for vnc connection. If
            --password is given, the password will be set to the value
            provided. Otherwise, if it is set, the environment variable
            OVIRT_HOSTED_ENGINE_CONSOLE_PASSWORD will be used. As a last
            resort, the password will be read interactively.
        --config-append=<file>
            Load extra configuration files or answer file.
        --check-deployed
            Check whether the hosted engine has been deployed already.
        --check-liveliness
            Checks liveliness page of engine.
        --connect-storage
            Connect the hosted engine storage domain.
        --disconnect-storage
            Disconnect the hosted engine storage domain.
        --console
            Open the configured serial console.
        --set-maintenance --mode=<mode>
            Set maintenance status to the specified mode (global/local/none).
        --set-shared-config <key> <value> [--type=<type>]
            Set specified key to the specified value. If the key is duplicated
            in several files a type must be provided.
        --get-shared-config <key> [--type=<type>]
            Get specified key's value. If the key is duplicated in several
            files a type must be provided.
        --reinitialize-lockspace
            Make sure all hosted engine agents are down and reinitialize the
            sanlock lockspaces.
        --clean-metadata
            Remove the metadata for the current host's agent from the global
            status database. This makes all other hosts forget about this
            host.

__EOF__
    exit $rc
}

readpassword() {
        (
                cleanup() {
                        [ -n "${STTY_ORIG}" ] && stty "${STTY_ORIG}"
                }

                STTY_ORIG=
                trap cleanup 0
                [ -t 0 ] || die "Standard input is not a terminal"
                STTY_ORIG="$(stty -g)"
                stty -echo || die "Failed to disable terminal input echo"
                echo -n "Enter password: " >&2
                read pass
                echo >&2
                cat << __EOF__
${pass}
__EOF__
        )
}

cmd_deploy() {
    [ "$1" == "--help" ] && { cat << __EOF__
Usage: $0 --deploy [args]
    Run ovirt-hosted-engine deployment.

    --config-append=<file>
        Load extra configuration files.
    --generate-answer=<file>
        Generate answer file.
    --restore-from-file=<file>
        Restore an engine backup file during the deployment.
    --4
        Force IPv4 on dual stack env.
    --6
        Force IPv6 on dual stack env.
    --ansible-extra-vars=DATA
        Pass '--extra-vars=DATA' to all ansible calls.
        DATA can be anything that ansible can accept - var=value,
        @file, JSON/YAML.
        Please note: Using this option is supported only
        with specific values as documented elsewhere.
        Passing arbitrary values might conflict with existing
        variables.

__EOF__
return ;}

    exec /usr/share/ovirt-hosted-engine-setup/scripts/ovirt-hosted-engine-setup "$@"
}

cmd_vm_start() {
    [ "$1" == "--help" ] && { cat << __EOF__
Usage: $0 --vm-start
    Start the engine VM on this host.
    Available only after deployment has completed.

    --vm-conf=<file>
        Load an alternative vm.conf file as a recovery action.
__EOF__
return ;}

    # TODO: Check first the sanlock status, and if allows:
    if [ -n "${vmid}" ] ; then
        check_vm_conf
        local down=
        local status=
        status=$(${VDSMHELPER} checkVmStatus ${vmid})
        if [[ $status == *"Down"* || $status == *"Paused"* ]]; then
            echo "VM exists and is ${status}, cleaning up and restarting"
            ${VDSMHELPER} destroy "${vmid}"
        elif [ -n "${status}" ]; then
            echo "VM exists and its status is ${status}"
            exit 1
        fi
        if [[ "$1" == --vm-conf=* ]]; then
            vmconf="${1#*--vm-conf=}"
        else
            vmconf="${conf}"
        fi
        if ${VDSMHELPER} create "${vmconf}"; then
            echo "VM in WaitForLaunch"
        else
            echo "VM failed to launch"
            exit 1
        fi
    else
        exit_not_deployed
    fi
}

cmd_vm_start_paused() {
    [ "$1" == "--help" ] && { cat << __EOF__
Usage: $0 --vm-start-paused
    Start the engine VM in paused state on this host.
    Available only after deployment has completed.
__EOF__
return ;}

    # TODO: Check first the sanlock status, and if allows:
    if [ -n "${vmid}" ] ; then
        check_vm_conf
        local down=
        local status=
        status=$(${VDSMHELPER} checkVmStatus ${vmid})
        if [[ $status == *"Down"* ]]; then
            echo "VM exists and is down, cleaning up and restarting"
            ${VDSMHELPER} destroy "${vmid}"
        elif [ -n "${status}" ]; then
            echo "VM exists and its status is ${status}"
            exit 1
        fi
        temp_conf="$(mktemp)"
        cp "${conf}" "${temp_conf}"
        echo "launchPaused=true">>"${temp_conf}"
        if ${VDSMHELPER} create "${temp_conf}"; then
            rm -f "${temp_conf}"
            echo "VM in WaitForLaunch"
        else
            rm -f "${temp_conf}"
            echo "VM failed to launch"
            exit 1
        fi
    else
        exit_not_deployed
    fi
}

cmd_vm_shutdown() {
    [ "$1" == "--help" ] && { cat << __EOF__
Usage: $0 --vm-shutdown
    Gracefully shut down the engine VM on this host.
    Available only after deployment has completed.
__EOF__
return ;}

    if [ -n "${vmid}" ] ; then
        ${VDSMHELPER} shutdown "${vmid}" 120 "VM is shutting down!"
    else
        exit_not_deployed
    fi
}

cmd_vm_poweroff() {
    [ "$1" == "--help" ] && { cat << __EOF__
Usage: $0 --vm-poweroff
    Forcefully power off the engine VM on this host.
    Available only after deployment has completed.
__EOF__
return ;}

    if [ -n "${vmid}" ] ; then
        ${VDSMHELPER} destroy "${vmid}"
    else
        exit_not_deployed
    fi
}

cmd_vm_status() {
    [ "$1" == "--help" ] && { cat << __EOF__
Usage: $0 --vm-status [--json]
    Report the status of the engine VM according to the HA agent.
    Available only after deployment has completed.

    If --json is given, the output will be in machine-readable (JSON) format.
__EOF__
return ;}

    if [ -n "${vmid}" ] ; then
        check_vm_conf
        /usr/bin/python3 -m ovirt_hosted_engine_setup.vm_status $1
    else
        if virsh -r domstate HostedEngineLocal 1>/dev/null 2>&1; then
            exit_not_correctly_deployed
        fi
        exit_not_deployed
    fi
}

cmd_add_console_password() {
    [ "$1" == "--help" ] && { cat << __EOF__
Usage: $0 --add-console-password [--password=<password>]
    Create a temporary password for vnc connection.

    If --password is given, the password will be set to the value provided.
    Otherwise, if it is set, the environment variable
    OVIRT_HOSTED_ENGINE_CONSOLE_PASSWORD will be used. As a last resort, the
    password will be read interactively.
    Available only after deployment has completed.
__EOF__
return ;}

    if [ -z "${vmid}" ] ; then
        exit_not_deployed
    fi

    if [[ "$1" == --password=* ]]; then
        pass="${1#*=}"
    else
        pass="${OVIRT_HOSTED_ENGINE_CONSOLE_PASSWORD}"
        if [ -z "${pass}" ]; then
            pass="$(readpassword)" || exit 1
        fi
    fi
    ${VDSMHELPER} setVmTicket "${vmid}" "${pass}" 120
}

cmd_check_deployed() {
    [ "$1" == "--help" ] && { cat << __EOF__
Usage: $0 --check-deployed
    Report whether the engine has been deployed.
__EOF__
return ;}
    if [ -n "${vmid}" ] ; then
        echo "The hosted engine has been deployed"
        rc=0
    else
        echo "The hosted engine has not been deployed"
        rc=1
    fi
    exit $rc
}

cmd_check_liveliness() {
    [ "$1" == "--help" ] && { cat << __EOF__
Usage: $0 --check-liveliness
    Report status of the engine services by checking the liveliness page.
__EOF__
return ;}

    /usr/bin/python3 -m ovirt_hosted_engine_setup.check_liveliness
}

cmd_connect_storage() {
    [ "$1" == "--help" ] && { cat << __EOF__
Usage: $0 --connect-storage
    Connect the storage domain.
__EOF__
return ;}

    if [ -n "${vmid}" ] ; then
        /usr/bin/python3 -m ovirt_hosted_engine_setup.connect_storage_server "$@"
        rc=$?
    else
        exit_not_deployed
        rc=1
    fi
    exit $rc
}

cmd_disconnect_storage() {
    [ "$1" == "--help" ] && { cat << __EOF__
Usage: $0 --disconnect-storage
    Disconnect the storage domain.
__EOF__
return ;}

    if [ -n "${vmid}" ] ; then
        /usr/bin/python3 -m ovirt_hosted_engine_setup.disconnect_storage_server "$@"
        rc=$?
    else
        exit_not_deployed
        rc=1
    fi
    exit $rc
}

cmd_console() {
    [ "$1" == "--help" ] && { cat << __EOF__
Usage: $0 --console
    Open the configured serial console.
__EOF__
return ;}

    status=$(${VDSMHELPER} checkVmStatus ${vmid})
    if [ -n "${status}" ]; then
        if [[ $status == *"Up"* ]]; then
            echo "The engine VM is running on this host"
        else
            echo "The engine VM is on this host but its status is ${status}"
            echo "Trying anyway to connect to its console:"
            echo
        fi
        hasOvirtVmconsole=$(virsh -r dumpxml ${vmid} | grep -c "/var/run/ovirt-vmconsole-console")
        if [ $hasOvirtVmconsole -eq 0 ]; then
            exec /usr/bin/virsh \
                -c qemu:///system?authfile=${VIRSH_AUTH} \
                console \
                HostedEngine
        else
            echo "Escape character is ^]"
            exec /usr/bin/socat \
                UNIX-CONNECT:/var/run/ovirt-vmconsole-console/${vmid}.sock \
                STDIO,raw,echo=0,escape=29
        fi
    else
        echo "The engine VM is not on this host"
    fi
}

cmd_set_maintenance() {
    [ "$1" == "--help" ] && { cat << __EOF__
Usage: $0 --set-maintenance --mode=<mode>
    Set maintenance status to the specified mode. Valid values are:
    'global', 'local', and 'none'.
    Available only after deployment has completed.
__EOF__
return ;}

    if [[ "$1" == --mode=* ]]; then
        mode="${1#*--mode=}"
        case "$mode" in
            global|local|none) ;;
            *)
                echo "Invalid value '$mode' for --mode"
                exit 1
                ;;
        esac
    else
        echo "You must specify a maintenance mode with --mode"
        exit 1
    fi

    if [ -n "${vmid}" ] ; then
        /usr/bin/python3 -m ovirt_hosted_engine_setup.set_maintenance "${mode}"
    else
        exit_not_deployed
    fi
}

cmd_set_shared_config() {
    [ "$1" == "--help" ] && { cat << __EOF__
Usage: $0 --set-shared-config <key> <value> [--type=<type>]
    Set shared storage configuration.
    Valid types are: he_local, he_shared, ha, broker.
    Available only after deployment has completed.

    New values for he_shared (hosted-engine.conf source on the shared storage)
    will be used by all hosts (re)deployed after the configuration change.
    Currently running hosts will still use the old values.
    New values for he_local will be set in the local instance of
    he configuration file on the local host.
__EOF__
return ;}

    key=${1}
    value=${2}

    if [[ "$3" == --type=* ]]; then
            type="${3#*--type=}"
    fi

    if [ -z "$key" ] ; then
        echo "You must specify a key to set"
        exit 1
    fi
    if [ -z "$value" ] ; then
        echo "You must specify a new value to set"
        exit 1
    fi
    if [ -n "${vmid}" ] ; then
        /usr/bin/python3 -m ovirt_hosted_engine_setup.set_shared_config "${key}" "${value}" "${type}"
    else
        exit_not_deployed
    fi
}

cmd_get_shared_config() {
    [ "$1" == "--help" ] && { cat << __EOF__
Usage: $0 --get-shared-config <key> [--type=<type>]
    Get shared storage configuration.
    Valid types are: he_local, he_shared, ha, broker.
    Available only after deployment has completed.
__EOF__
return ;}

    key=${1}

    if [[ "$2" == --type=* ]]; then
            type="${2#*--type=}"
    fi

    if [ -z "$key" ] ; then
        echo "You must specify a key to get"
        exit 1
    fi

    if [ -n "${vmid}" ] ; then
        /usr/bin/python3 -m ovirt_hosted_engine_setup.get_shared_config "${key}" "${type}"
    else
        exit_not_deployed
    fi
}

cmd_reinitialize_lockspace() {
    [ "$1" == "--help" ] && { cat << __EOF__
Usage: $0 --reinitialize-lockspace [--force]
    Reinitialize the sanlock lockspace file. This WIPES all locks.
    Available only in properly deployed cluster in global maintenance mode
    with all HA agents shut down.

    --force  This option overrides the safety checks. Use at your own
             risk DANGEROUS.
__EOF__
return ;}

    if [ -n "${vmid}" ] ; then
        /usr/bin/python3 -m ovirt_hosted_engine_setup.reinitialize_lockspace "$@"
    else
        exit_not_deployed
    fi
}

cmd_clean_metadata() {
    [ "$1" == "--help" ] && { cat << __EOF__
Usage: $0 --clean_metadata [--force-cleanup] [--host-id=<id>]
    Remove host's metadata from the global status database.
    Available only in properly deployed cluster with properly stopped
    agent.

    --force-cleanup  This option overrides the safety checks. Use at your own
                     risk DANGEROUS.

    --host-id=<id>  Specify an explicit host id to clean
__EOF__
return ;}

    if [ -n "${vmid}" ] ; then
        check_vm_conf
        exec /usr/libexec/ovirt-hosted-engine-ha/ovirt-ha-agent --cleanup "$@"
    else
        exit_not_deployed
    fi
}

if [ -z "$1" ] ; then
    usage
fi

VDSMHELPER="/usr/bin/python3 -m ovirt_hosted_engine_setup.vdsm_helper"

cli_broker() {
    x="$1"
    shift
    case "${x}" in
        --deploy) cmd_deploy "$@" ;;
        --vm-start) cmd_vm_start "$@" ;;
        --vm-start-paused) cmd_vm_start_paused "$@" ;;
        --vm-shutdown) cmd_vm_shutdown "$@" ;;
        --vm-poweroff) cmd_vm_poweroff "$@" ;;
        --vm-status) cmd_vm_status "$@" ;;
        --add-console-password) cmd_add_console_password "$@" ;;
        --check-liveliness) cmd_check_liveliness "$@" ;;
        --check-deployed) cmd_check_deployed "$@" ;;
        --connect-storage) cmd_connect_storage "$@" ;;
        --disconnect-storage) cmd_disconnect_storage "$@" ;;
        --console) cmd_console "$@" ;;
        --set-maintenance) cmd_set_maintenance "$@" ;;
        --set-shared-config) cmd_set_shared_config "$@" ;;
        --get-shared-config) cmd_get_shared_config "$@" ;;
        --reinitialize-lockspace) cmd_reinitialize_lockspace "$@" ;;
        --clean-metadata) cmd_clean_metadata "$@" ;;
        --help)
            rc=0
            usage "$@"
        ;;
        *)
            rc=1
            echo "Invalid option '${x}'" >&2
            usage
        ;;
    esac
}

cli_broker "$@"
