#!/bin/sh

# Copyright (C) 2019-2020 Internet Systems Consortium, Inc. ("ISC")
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

# shellcheck disable=SC1091
# SC1091: Not following: ... was not specified as input (see shellcheck -x).

# shellcheck disable=SC2039
# SC2039: In POSIX sh, arrays are undefined.

# Exit with error if commands exit with non-zero and if undefined variables are
# used.
set -eu

# shellcheck disable=SC2034
# SC2034: ... appears unused. Verify use (or export if used externally).
prefix="/usr"

# Include utilities. Use installed version if available and
# use build version if it isn't.
if [ -e "${prefix}/share/kea/scripts/admin-utils.sh" ]; then
    . "${prefix}/share/kea/scripts/admin-utils.sh"
else
    . "/build/kea/src/kea-1.9.8/src/bin/admin/admin-utils.sh"
fi

# Need a path for temporary files created during data update
# Use the state directory in the install path directory if it exists, otherwise
# use the build tree
if [ -e "/var/lib/kea" ]; then
    temp_file_dir="/var/lib/kea"
else
    temp_file_dir="/build/kea/src/kea-1.9.8/src/share/database/scripts/cql"
fi

# Ensures the current schema version is 4.0. If not it exits.
check_version() {
    version=$(cql_version "${@}")

    if [ "${version}" != "4.0" ]; then
        printf "This script upgrades 4.0 to 5.0. Reported version is %s. Skipping upgrade.\n" "${version}"
        exit 0
    fi
}

# Peforms the schema changes from 4.0 to 5.0
update_schema() {
    cqlsh "${@}" <<EOF
-- This line starts database upgrade to version 5.0

-- Add the lower case hostname column to reservations.
ALTER TABLE hosts ADD lower_case_hostname VARCHAR;

-- Make the lower case hostname an index.
CREATE INDEX IF NOT EXISTS hostsindex8 ON hosts (lower_case_hostname);

-- Create a new hostname index on lease4.
CREATE INDEX IF NOT EXISTS lease4index6 ON lease4 (hostname);

-- Create a new hostname index on lease6.
CREATE INDEX IF NOT EXISTS lease6index7 ON lease6 (hostname);

DELETE FROM schema_version WHERE version=4;
INSERT INTO schema_version (version, minor) VALUES(5, 0);

-- This line concludes database upgrade to version 5.0
EOF
}

# Functions to delete temporary update files
clean_up() {
    # clean up the files
    if [ -e "$host_export_file" ]
    then
        rm "$host_export_file"
    fi

    if [ -e "$host_update_file" ]
    then
        rm "$host_update_file"
    fi

    if [ -e "$lease4_export_file" ]
    then
        rm "$lease4_export_file"
    fi

    if [ -e "$lease4_update_file" ]
    then
        rm "$lease4_update_file"
    fi

    if [ -e "$lease6_export_file" ]
    then
        rm "$lease6_export_file"
    fi

    if [ -e "$lease6_update_file" ]
    then
        rm "$lease6_update_file"
    fi
}

# Function to clean up and exit the script gracefully
#
# Called by update_host_data()
#
# Parameters:
# status - integer value to pass to sh:exit
# explanation - "quoted" text message to emit to stdout
exit_now() {
    status=$1;shift
    explanation=$1

    clean_up
    if [ "$status" -eq 0 ]
    then
        echo "Data Update SUCCESS! $explanation"
    else
        echo "Data Update FAILURE! $explanation"
    fi

    exit "$status"
}

# This function adds host 'lower_case_hostname' column.
#
# After exhausting the export file, the update file is submitted to
# cqlsh for execution.
#
# No parameters.
update_host_data() {
    clean_up

    # Fetch hosts data so we have primary key components and hostname.
    echo "Exporting hostnames to $host_export_file ..."
    query="COPY hosts (key, id, hostname) TO '$host_export_file'"


    if ! cqlsh "${@}" -e "$query"
    then
        exit_now 1 "Cassandra get hostname failed! Could not update host!"
    fi

    # Strip the carriage returns that CQL insists on adding.
    if [ -e "$host_export_file" ]
    then
        tr -d '\015' < "$host_export_file" > "$host_export_file.2"
        mv "$host_export_file.2" "$host_export_file"
    else
        # Shouldn't happen but then again we're talking about CQL here
        exit_now 1 "Cassandra export file $host_export_file is missing?"
    fi

    # Iterate through the exported data, accumulating update statements,
    # one for each reservation that needs updating.  We should have one
    # host per line.
    line_cnt=0
    update_cnt=0

    while read -r line
    do
        line_cnt=$((line_cnt + 1))
        xIFS="$IFS"
        IFS=','

        i=1
        hostname=
        # Parse the column values
        for val in $line
        do
            case $i in
            1)
                key="$val"
                ;;
            2)
                host_id="$val"
                ;;
            3)
                hostname="$val"
                ;;
            *)
                # We're going to assume that since any error is fatal
                exit_now 1 "Line# $line_cnt, too many values, wrong or corrupt file"
                ;;
            esac
            i=$((i + 1))
        done

        if [ "$i" -lt 3 ]
        then
            # We're going to assume that since any error is fatal
            exit_now 1 "Line# $line_cnt, too few values, wrong or corrupt file"
        fi

        # If the hostname was not null set the lower case value
        if [ "$hostname" != "" ]
        then
            lower=$(echo "${hostname}" | tr '[:upper:]' '[:lower:]')
            echo "update hosts set lower_case_hostname = '$lower' where key = $key and id = $host_id;" >> "$host_update_file"
            update_cnt=$((update_cnt + 1))
        fi

        IFS="$xIFS"
    done < "$host_export_file"

    # If we didn't record any updates, then hey, we're good to go!
    if [ "$update_cnt" -eq 0 ]
    then
        exit_now 0 "Completed successfully: No host updates were needed"
    fi

    # We have at least one update in the update file, so submit it # to cqlsh.
    echo "$update_cnt host update statements written to $host_update_file"
    echo "Running the updates..."

    if ! cqlsh "${@}" -f "$host_update_file"
    then
        exit_now 1 "Cassandra host updates failed"
    fi

    exit_now 0 "Updated $update_cnt of $line_cnt host records"
}

# This function moves lease4 hostnames to lower case.
#
# After exhausting the export file, the update file is submitted to
# cqlsh for execution.
#
# No parameters.
update_lease4_data() {
    clean_up

    # Fetch lease4 data so we have primary address and hostname.
    echo "Exporting hostnames to $lease4_export_file ..."
    query="COPY lease4 (address, hostname) TO '$lease4_export_file'"


    if ! cqlsh "${@}" -e "$query"
    then
        exit_now 1 "Cassandra get hostname failed! Could not update lease4!"
    fi

    # Strip the carriage returns that CQL insists on adding.
    if [ -e "$lease4_export_file" ]
    then
        tr -d '\015' < "$lease4_export_file" > "$lease4_export_file.2"
        mv "$lease4_export_file.2" "$lease4_export_file"
    else
        # Shouldn't happen but then again we're talking about CQL here
        exit_now 1 "Cassandra export file $lease4_export_file is missing?"
    fi

    # Iterate through the exported data, accumulating update statements,
    # one for each lease that needs updating.  We should have one lease4
    # per line.
    line_cnt=0
    update_cnt=0

    while read -r line
    do
        line_cnt=$((line_cnt + 1))
        xIFS="$IFS"
        IFS=','

        i=1
        hostname=
        # Parse the column values
        for val in $line
        do
            case $i in
            1)
                address="$val"
                ;;
            2)
                hostname="$val"
                ;;
            *)
                # We're going to assume that since any error is fatal
                exit_now 1 "Line# $line_cnt, too many values, wrong or corrupt file"
                ;;
            esac
            i=$((i + 1))
        done

        if [ "$i" -lt 2 ]
        then
            # We're going to assume that since any error is fatal
            exit_now 1 "Line# $line_cnt, too few values, wrong or corrupt file"
        fi

        # If the hostname was not null set the lower case value
        if [ "$hostname" != "" ]
        then
            lower=$(echo "${hostname}" | tr '[:upper:]' '[:lower:]')
            echo "update lease4 set hostname = '$lower' where address = $address;" >> "$lease4_update_file"
            update_cnt=$((update_cnt + 1))
        fi

        IFS="$xIFS"
    done < "$lease4_export_file"

    # If we didn't record any updates, then hey, we're good to go!
    if [ "$update_cnt" -eq 0 ]
    then
        exit_now 0 "Completed successfully: No lease4 updates were needed"
    fi

    # We have at least one update in the update file, so submit it # to cqlsh.
    echo "$update_cnt lease4 update statements written to $lease4_update_file"
    echo "Running the updates..."

    if ! cqlsh "${@}" -f "$lease4_update_file"
    then
        exit_now 1 "Cassandra lease4 updates failed"
    fi

    exit_now 0 "Updated $update_cnt of $line_cnt lease4 records"
}

# This function moves lease6 hostnames to lower case.
#
# After exhausting the export file, the update file is submitted to
# cqlsh for execution.
#
# No parameters.
update_lease6_data() {
    clean_up

    # Fetch lease6 data so we have primary address and hostname.
    echo "Exporting hostnames to $lease6_export_file ..."
    query="COPY lease6 (address, hostname) TO '$lease6_export_file'"

    if ! cqlsh "${@}" -e "$query"
    then
        exit_now 1 "Cassandra get hostname failed! Could not update lease6!"
    fi

    # Strip the carriage returns that CQL insists on adding.
    if [ -e "$lease6_export_file" ]
    then
        tr -d '\015' < "$lease6_export_file" > "$lease6_export_file.2"
        mv "$lease6_export_file.2" "$lease6_export_file"
    else
        # Shouldn't happen but then again we're talking about CQL here
        exit_now 1 "Cassandra export file $lease6_export_file is missing?"
    fi

    # Iterate through the exported data, accumulating update statements,
    # one for each lease that needs updating.  We should have one lease6
    # per line.
    line_cnt=0
    update_cnt=0

    while read -r line
    do
        line_cnt=$((line_cnt + 1))
        xIFS="$IFS"
        IFS=','

        i=1
        hostname=
        # Parse the column values
        for val in $line
        do
            case $i in
            1)
                address="$val"
                ;;
            2)
                hostname="$val"
                ;;
            *)
                # We're going to assume that since any error is fatal
                exit_now 1 "Line# $line_cnt, too many values, wrong or corrupt file"
                ;;
            esac
            i=$((i + 1))
        done

        if [ "$i" -lt 2 ]
        then
            # We're going to assume that since any error is fatal
            exit_now 1 "Line# $line_cnt, too few values, wrong or corrupt file"
        fi

        # If the hostname was not null set the lower case value
        if [ "$hostname" != "" ]
        then
            lower=$(echo "${hostname}" | tr '[:upper:]' '[:lower:]')
            echo "update lease6 set hostname = '$lower' where address = $address;" >> "$lease6_update_file"
            update_cnt=$((update_cnt + 1))
        fi

        IFS="$xIFS"
    done < "$lease6_export_file"

    # If we didn't record any updates, then hey, we're good to go!
    if [ "$update_cnt" -eq 0 ]
    then
        exit_now 0 "Completed successfully: No lease6 updates were needed"
    fi

    # We have at least one update in the update file, so submit it # to cqlsh.
    echo "$update_cnt lease6 update statements written to $lease6_update_file"
    echo "Running the updates..."

    if ! cqlsh "${@}" -f "$lease6_update_file"
    then
        exit_now 1 "Cassandra lease6 updates failed"
    fi

    exit_now 0 "Updated $update_cnt of $line_cnt lease6 records"
}

host_export_file="$temp_file_dir/cql_host_export.csv"
host_update_file="$temp_file_dir/cql_host_update.cql"
lease4_export_file="$temp_file_dir/cql_lease4_export.csv"
lease4_update_file="$temp_file_dir/cql_lease4_update.cql"
lease6_export_file="$temp_file_dir/cql_lease6_export.csv"
lease6_update_file="$temp_file_dir/cql_lease6_update.cql"

check_version "${@}"
if ! update_schema "${@}"; then
    printf 'Schema update FAILED!\n'
    exit 1
fi
update_host_data "${@}"
update_lease4_data "${@}"
update_lease6_data "${@}"
