#!/bin/sh

# Copyright (C) 2018-2021 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, 'local' is 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-2.0.2/src/bin/admin/admin-utils.sh"
fi

# Need a path for temporary files created during upgrade data migration
# 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-2.0.2/src/share/database/scripts/cql"
fi

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

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

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

-- Add a column holding leases for user context.
ALTER TABLE lease4 ADD user_context VARCHAR;
ALTER TABLE lease6 ADD user_context VARCHAR;

-- -----------------------------------------------------
-- Table logs (it is used by forensic logging hook library)
-- -----------------------------------------------------
CREATE TABLE IF NOT EXISTS logs (
    timeuuid TIMEUUID,  -- creation timeuuid, use dateOf() to get timestamp
    address VARCHAR,    -- address or prefix
    log VARCHAR,        -- the log itself
    PRIMARY KEY ((timeuuid))
);

-- Create search index for logs table
CREATE INDEX IF NOT EXISTS logsindex ON logs (address);

-- Add auth_key for storing keys for DHCPV6 reconfigure.
ALTER TABLE host_reservations ADD auth_key VARCHAR;

-- Cql requires primary keys in the WHERE here.
DELETE FROM schema_version WHERE version=2;
INSERT INTO schema_version (version, minor) VALUES(3, 0);

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

# Function to delete temporary migration files
clean_up() {
    # clean up the files
    if [ -e "${export_file}" ]
    then
        rm "${export_file}"
    fi

    if [ -e "${update_file}" ]
    then
        rm "${update_file}"
    fi
}

# Function to clean up and exit the script gracefully
#
# Called by migrate_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 Migration SUCCESS! $explanation"
    else
        echo "Data Migration FAILURE! $explanation"
    fi

    exit "${status}"
}

# Function adds a column to the global, $update_cols if needed
#
# Called by migrate_host_data() to determine if the given column
# value needs to be updated, and if so appends CQL update text
# to a global string accumulator, $update_cols.
#
# The accumlator text is of the form:
#
#  "<column_name>=<column value>{,<column_name>=<column_value>,..}"
#
# Parameters:
# val - current numerical value of the subnet ID column in question
# col - column name of the column in question
#
check_column() {
    local val="$1";shift
    local col="$1"
    local old_id="0"
    local new_id="-1"
    local comma=""

    # If the current value equals the value to be replaced
    # add it to the accumulator
    if [ "$val" = "$old_id" ]
    then
        # If the accumulator isn't empty, we need a comma
        if [ -n "${update_cols}" ]
        then
            comma=","
        fi

        update_cols="$update_cols$comma $col = $new_id"
    fi
}

# This function converts subnet ID columns in of existing host_reservations
# from 0 to 0xFFFFFFFF (SUBNET_ID_UNUSED). To do this it first creates an
# excerpt from host_reservations containing one row for each reservation
# with the following columns:
#
#  id (primary key)
#  host_ipv4_subnet_id
#  host_ipv6_subnet_id
#  option_subnet_id
#
# The excerpt is captured in a temporary file, the "export" file.
#
# Next, it iterates over the export file emitting a CQL update statement
# for each row that needs at least one of the columns updated. In other
# words, if at least one of the subnet ID columns in a row is 0, an
# update statement for that row will be emitted.  The update statements
# are captured in a second temporary file, the "update" file.
#
# After exhausting the export file, the update file is submitted to
# cqlsh for execution.
#
# No parameters.
migrate_host_data() {
    export_file="$temp_file_dir/cql_export.csv"
    update_file="$temp_file_dir/cql_update.cql"

    clean_up

    # Fetch host_reservation data so we have primary key, plus subnet id values
    echo "Exporting host_reservation data to $export_file ..."
    query="COPY host_reservations \
        (id, host_ipv4_subnet_id, host_ipv6_subnet_id, option_subnet_id)\
        TO '$export_file'"

    if ! cqlsh "${@}" -e "$query"
    then
        exit_now 1 "Cassandra export failed! Could not migrate data!"
    fi

    # Strip the carriage returns that CQL insists on adding.
    if [ -e "$export_file" ]
    then
        tr -d '\015' < "${export_file}" > "${export_file}.2"
        mv $export_file.2 $export_file
    else
        # Shouldn't happen but then again we're talking about CQL here
        exit_now 1 "Cassandra export file $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));
        update_cols=""
        xIFS="$IFS"
        # shellcheck disable=SC3003
        # SC3003: In POSIX sh, $'..' is undefined.
        IFS=$','

        i=1
        # Parse the column values
        for val in $line
        do
            case $i in
            1)
                host_id="$val"
                ;;
            2)
                check_column "$val" host_ipv4_subnet_id
                ;;
            3)
                check_column "$val" host_ipv6_subnet_id
                ;;
            4)
                check_column "$val" option_subnet_id
                ;;
            *)
                # 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" -ne 5 ]
        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 any of the current host's columns need to be replace, append an update for it
        if [ -n "$update_cols" ]
        then
            echo "update host_reservations set $update_cols where id = $host_id;" >> $update_file
            update_cnt=$((update_cnt + 1))
        fi

        IFS="$xIFS"
    done < "${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 updates were needed"
    fi

    # We have at least one update in the update file, so submit it # to cqlsh.
    echo "$update_cnt update statements written to $update_file"
    echo "Running the updates..."
    if ! cqlsh "${@}" -f "$update_file"
    then
        exit_now 1 "Cassandra updates failed"
    fi

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

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