#!/bin/bash

set -eu
set -o pipefail

SCRIPT_NAME=$(basename $0)

OS_AUTH_URL=${OS_AUTH_URL:-""}
if [ -z "$OS_AUTH_URL" ]; then
    echo "You must source a stackrc file for the Undercloud."
    exit 1
fi

# NOTE(dtantsur): last working API version
export IRONIC_API_VERSION="1.6"

function show_options () {
    echo "Usage: $SCRIPT_NAME [options]"
    echo
    echo "Deploys instances via Ironic in preparation for an Overcloud deployment."
    echo
    echo "Options:"
    echo "      --register-nodes     -- register nodes from a nodes json file"
    echo "      --nodes-json         -- nodes json file containing node data"
    echo "                              for registration."
    echo "                              Default: nodes.json in the current directory"
    echo "      --discover-nodes     -- Perform discovery of registered nodes."
    echo "                              Powers on registered nodes to complete"
    echo "                              the discovery process."
    echo "      --configure-nodes    -- Configure BIOS and RAID volumes of "
    echo "                              registered nodes."
    echo "      --setup-flavors      -- Setup Nova flavors to match discovered"
    echo "                              profiles"
    echo "      --show-profile       -- Show matching profile of nodes"
    echo "      --deploy-nodes       -- Deploy nodes"
    echo "      --check-ssh          -- Check the root ssh connection to each"
    echo "                              deployed node."
    echo "      --delete-stack       -- Wait until the stack has deleted."
    echo "      --delete-nodes       -- Delete all nodes."
    echo "      -x                   -- enable tracing"
    echo "      --help, -h           -- Print this help message."
    echo
    exit $1
}

TEMP=$(getopt -o ,h -l,register-nodes,nodes-json:,discover-nodes,configure-nodes,deploy-nodes,help,setup-flavors,show-profile,check-ssh,delete-stack,delete-nodes,config-tools-provision -o,x,h -n $SCRIPT_NAME -- "$@")
if [ $? != 0 ] ; then echo "Terminating..." >&2 ; exit 1 ; fi

# Note the quotes around `$TEMP': they are essential!
eval set -- "$TEMP"

REGISTER_NODES=
NODES_JSON=
DISCOVER_NODES=
CONFIGURE_NODES=
DEPLOY_NODES=
SETUP_FLAVORS=
SHOW_PROFILE=
CHECK_SSH=
DELETE_STACK=
DELETE_NODES=
STDERR=/dev/null

# NOTE(bnemec): Can be removed once Ironic is updated to fix the
# novaclient.v1_1 deprecation warning
export PYTHONWARNINGS="ignore"

DEPLOY_NAME=${DEPLOY_NAME:-"ironic-discover"}
IRONIC=$(openstack endpoint show baremetal | grep publicurl | awk '{ print $4; }')
IRONIC=${IRONIC%/}

# Raise retry time to 60 seconds
export IRONIC_MAX_RETRIES=${IRONIC_MAX_RETRIES:-24}
export IRONIC_RETRY_INTERVAL=${IRONIC_RETRY_INTERVAL:-5}

while true ; do
    case "$1" in
        --register-nodes) REGISTER_NODES="1"; shift 1;;
        --nodes-json) NODES_JSON="$2"; shift 2;;
        --discover-nodes) DISCOVER_NODES="1"; shift 1;;
        --configure-nodes) CONFIGURE_NODES="1"; shift 1;;
        --show-profile) SHOW_PROFILE="1"; shift 1;;
        --deploy-nodes) DEPLOY_NODES="1"; shift 1;;
        --setup-flavors) SETUP_FLAVORS="1"; shift 1;;
        --check-ssh) CHECK_SSH="1"; shift 1;;
        --delete-stack) DELETE_STACK="1"; shift 1;;
        --delete-nodes) DELETE_NODES="1"; shift 1;;
        -x) set -x; STDERR=/dev/stderr; shift 1;;
        -h | --help) show_options 0;;
        --) shift ; break ;;
        *) echo "Error: unsupported option $1." ; exit 1 ;;
    esac
done

function register_nodes {
    NODES_JSON=${NODES_JSON:-"nodes.json"}
    NULL_STATS=${NULL_STATS:-0}
    tmp_json=$NODES_JSON
    if [ "$NULL_STATS" = "1" ]; then
        tmp_json=$(mktemp)
        jq '.nodes[].memory=null | .nodes[].disk=null | .nodes[].arch=null | .nodes[].cpu=null' $NODES_JSON > $tmp_json
    fi
    echo "  Registering nodes from $NODES_JSON"
    register-nodes --service-host undercloud --nodes <(jq '.nodes' $tmp_json) 1>/dev/null
    if [ "$NULL_STATS" = "1" ]; then
        rm -f $tmp_json
    fi

    deploy_kernel_id=$(glance image-show bm-deploy-kernel | awk ' / id / {print $4}')
    deploy_ramdisk_id=$(glance image-show bm-deploy-ramdisk | awk ' / id / {print $4}')

    # Add the local boot capability and deploy_{kernel, ramdisk} parameters
    node_list=$(ironic node-list --limit 0)
    node_ids=$(echo "$node_list" | tail -n +4 | head -n -1 | awk -F "| " '{print $2}')
    for node_id in $node_ids; do
        ironic node-update $node_id add properties/capabilities="boot_option:local" driver_info/deploy_ramdisk=$deploy_ramdisk_id driver_info/deploy_kernel=$deploy_kernel_id 1> /dev/null
    done

    echo "  Nodes registered."
    echo
    echo "$node_list"
    echo
}

function discover_nodes {
    echo "  Discovering nodes."
    node_ids=$(ironic node-list | tail -n +4 | head -n -1 | awk -F "| " '{print $2}')
    for node_id in $node_ids; do
        # NOTE(dtantsur): ||true is in case nodes are already MANAGEABLE
        ironic node-set-provision-state $node_id manage 2> /dev/null || true
        echo -n "    Sending node ID $node_id to discoverd for discovery ... "
        openstack baremetal introspection start $node_id
        # small sleep to avoid too many nodes DHCP'ing at once
        sleep 5
        echo "DONE."
    done

    echo "   Polling discoverd for discovery results ... "
    for node_id in $node_ids; do
        echo -n "       Result for node $node_id is ... "
        while true; do
            finished=$(openstack baremetal introspection status $node_id -f value -c finished)
            if [ "$finished" = "True" ]; then
                error=$(openstack baremetal introspection status $node_id -f value -c error)
                if [ "$error" = "None" ]; then
                    echo "DISCOVERED."
                else
                    echo "ERROR: $error"
                fi
                break
            fi
            sleep 15
        done
    done

    echo "   Setting node states to AVAILABLE... "
    for node_id in $node_ids; do
        ironic node-set-provision-state $node_id provide
    done
    echo
}

function configure_bios {
    echo "  Configuring BIOS."
    node_ids=$(ironic node-list | tail -n +4 | head -n -1 | awk -F "| " '{print $2}')

    for node_id in $node_ids; do
        driver=$(ironic node-show $node_id | grep 'driver[ \t]' | awk -F "|" '{print $3}' | xargs)
        if [ "$driver" == "pxe_drac" ]; then
            echo -n "    Configuring BIOS for node ID $node_id ... "
            ironic node-vendor-passthru $node_id --http_method POST configure_bios_settings
            echo "DONE."
        fi
    done
    # NOTE(ifarkas): wait until Ironic processes the request
    sleep 15
    echo
}

function configure_raid_volumes {
    create_root_volume=$1
    create_nonroot_volumes=$2

    echo "  Configuring RAID volumes."
    node_ids=$(ironic node-list | tail -n +4 | head -n -1 | awk -F "| " '{print $2}')
    for node_id in $node_ids; do

        driver=$(ironic node-show $node_id | grep 'driver[ \t]' | awk -F "|" '{print $3}' | xargs)

        if [ "$driver" == "pxe_drac" ]; then
            echo -n "    Configuring RAID volumes for node ID $node_id ... "
            ironic node-vendor-passthru $node_id --http_method POST create_raid_configuration \
                                        create_root_volume=$create_root_volume \
                                        create_nonroot_volumes=$create_nonroot_volumes
            echo "DONE."
        fi
    done
    # NOTE(ifarkas): wait until Ironic processes the request
    sleep 15
    echo
}

function wait_for_drac_config_jobs {
    echo "  Waiting for DRAC config jobs to finish ... "

    node_ids=$(ironic node-list | tail -n +4 | head -n -1 | awk -F "| " '{print $2}')
    for node_id in $node_ids; do

        driver=$(ironic node-show $node_id | grep 'driver[ \t]' | awk -F "|" '{print $3}' | xargs)

        if [ "$driver" == "pxe_drac" ]; then
            echo -n "    Waiting for node $node_id ... "

            while true; do
                jobs=$(ironic node-vendor-passthru $node_id --http_method GET list_unfinished_jobs)

                if [[ $jobs == *"'unfinished_jobs': []"* ]]; then
                    break
                fi

                sleep 30
            done
            echo "DONE."
        fi
    done
    echo
}

function change_power_state {
    requested_state=$1

    echo "  Changing power states."
    node_ids=$(ironic node-list | tail -n +4 | head -n -1 | awk -F "| " '{print $2}')
    for node_id in $node_ids; do
        echo -n "    Changing power state for node ID $node_id ... "
        ironic node-set-power-state $node_id $requested_state
        echo "DONE."
    done
    # NOTE(ifarkas): wait until Ironic processes the request
    sleep 15
    echo
}

function wait_for_ssh {
    echo "  Waiting for ssh as root to be enabled ... "
    echo
    ips=$(nova list | tail -n +4 | head -n -1 | awk '{print $12}' | cut -d= -f2)
    for ip in $ips; do
        echo -n "    checking $ip ... "
        tripleo wait_for 300 1 ssh -o "PasswordAuthentication=no" -o "StrictHostKeyChecking=no" root@$ip ls
        echo "DONE."
    done
    echo
}


function deploy_nodes {
    wait_for_hypervisor_stats
    DEPLOY_HEAT_TEMPLATE=${DEPLOY_HEAT_TEMPLATE:-"/usr/share/instack-undercloud/heat-templates/ironic-deployment.yaml"}
    CONTROL_COUNT=${CONTROL_COUNT:-"1"}
    COMPUTE_COUNT=${COMPUTE_COUNT:-"3"}
    echo "  Creating heat stack ... "
    heat stack-create $DEPLOY_NAME -f $DEPLOY_HEAT_TEMPLATE -P "control_count=$CONTROL_COUNT" -P "compute_count=$COMPUTE_COUNT"
    echo "  Created."
    echo
    echo -n "  Waiting for stack to finish ... "
    echo
    tripleo wait_for_stack_ready 60 10 $DEPLOY_NAME
    echo "DONE."
    heat stack-show $DEPLOY_NAME
    heat stack-list
    wait_for_ssh
    echo
}

function setup_flavors {
    if ! nova flavor-show baremetal 2>$STDERR 1>/dev/null; then
        echo "  Creating baremetal flavor ... "
        nova flavor-create baremetal auto 4096 40 1
    else
        echo "  baremetal flavor already exists."
    fi

    echo
    nova flavor-list
    echo

    echo "  Setting baremetal flavor keys ... "
    nova flavor-key baremetal set \
        "cpu_arch"="x86_64" \
        "capabilities:boot_option"="local"
    nova flavor-show baremetal
}

function show_profile {
    node_ids=$(ironic node-list | tail -n +4 | head -n -1 | awk -F "| " '{print $2}')
    token=$(openstack token issue | grep ' id ' | awk '{print $4}')
    echo "  Querying assigned profiles ... "
    echo
    for node_id in $node_ids; do
        echo "    $node_id"
        echo -n "      "
        curl -s -H "x-auth-token: $token" $IRONIC/v1/nodes/$node_id | jq '.properties.capabilities'
        echo
    done
    echo
    echo "  DONE."
    echo
}

function wait_for_hypervisor_stats {
    node_ids=$(ironic node-list | tail -n +4 | head -n -1 | awk -F "| " '{print $2}')
    expected_nodes=$(echo $node_ids | wc -w)
    expected_memory=0
    expected_vcpus=0

    token=$(openstack token issue | grep ' id ' | awk '{print $4}')
    echo -n "  Wating for nova hypervisor stats ... "
    for node_id in $node_ids; do
        mem=$(curl -s -H "x-auth-token: $token" $IRONIC/v1/nodes/$node_id | jq '.properties.memory_mb | tonumber')
        vcpu=$(curl -s -H "x-auth-token: $token" $IRONIC/v1/nodes/$node_id | jq '.properties.cpus | tonumber')
        expected_memory=$(($expected_memory + $mem))
        expected_vcpus=$(($expected_vcpus + $vcpu))
    done

    tripleo wait_for 180 1 wait_for_hypervisor_stats $expected_nodes $expected_memory  $expected_vcpus

    echo "DONE."
    echo
}

function delete_stack {
    heat stack-delete $DEPLOY_NAME
    tripleo wait_for 90 2 ! heat stack-show $DEPLOY_NAME
}

function delete_nodes {
    for n in $(ironic node-list | tail -n +4 | head -n -1 | awk '{print $2}'); do
        ironic node-delete $n;
    done
}

echo "Preparing for deployment..."

if [ "$REGISTER_NODES" = 1 ]; then
    register_nodes
fi

if [ "$DISCOVER_NODES" = 1 ]; then
    discover_nodes
fi

if [ "$CONFIGURE_NODES" = 1 ]; then
    configure_bios

    create_root_volume=true
    create_nonroot_volumes=false
    configure_raid_volumes $create_root_volume $create_nonroot_volumes

    change_power_state reboot
    wait_for_drac_config_jobs
    change_power_state off

    discover_nodes

    create_root_volume=false
    create_nonroot_volumes=true
    configure_raid_volumes $create_root_volume $create_nonroot_volumes

    change_power_state reboot
    wait_for_drac_config_jobs
    change_power_state off
fi

if [ "$SETUP_FLAVORS" = 1 ]; then
    setup_flavors
fi

if [ "$SHOW_PROFILE" = 1 ]; then
    show_profile
fi

if [ "$CHECK_SSH" = 1 ]; then
    wait_for_ssh
fi

if [ "$DELETE_STACK" = 1 ]; then
    delete_stack
fi

if [ "$DELETE_NODES" = 1 ]; then
    delete_nodes
fi

echo "Prepared."

if [ "$DEPLOY_NODES" = 1 ]; then
    echo "Deploying..."
    deploy_nodes
    echo "Deployed."
fi
