#!/bin/sh # Copyright (C) 2014-2015 Internet Systems Consortium, Inc. ("ISC") # # Permission to use, copy, modify, and/or distribute this software for any # purpose with or without fee is hereby granted, provided that the above # copyright notice and this permission notice appear in all copies. # # THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH # REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY # AND FITNESS. IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT, # INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM # LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE # OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR # PERFORMANCE OF THIS SOFTWARE. # This is kea-admin script that conducts administrative tasks on the Kea # installation. Currently supported operations are: # # - lease database init # - lease database version check # - lease database version upgrade # Get the location of the kea-admin scripts prefix=@prefix@ SCRIPTS_DIR_DEFAULT=@datarootdir@/@PACKAGE@/scripts scripts_dir=${SCRIPTS_DIR_DEFAULT} # These are the default parameters. They will likely not work in any # specific deployment. db_user="keatest" db_password="keatest" db_name="keatest" # lease dump parameters dump_type=0 dump_file="" dump_qry="" # Include utilities. Use installed version if available and # use build version if it isn't. if [ -e @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh ]; then . @datarootdir@/@PACKAGE_NAME@/scripts/admin-utils.sh else . @abs_top_builddir@/src/bin/admin/admin-utils.sh fi # Prints out usage version. usage() { printf "kea-admin @PACKAGE_VERSION@\n" printf "\n" printf "This is a kea-admin script that conducts administrative tasks on\n" printf "the Kea installation.\n" printf "\n" printf "Usage: $0 COMMAND BACKEND [parameters]\n" printf "\n" printf "COMMAND: Currently supported operations are:\n" printf "\n" printf " - lease-init: Initalizes new lease database. Useful for first time installation.\n" printf " - lease-version: Checks version of the existing lease database scheme. Useful\n" printf " - for checking lease DB version when preparing for an upgrade.\n" printf " - lease-upgrade: Upgrades your lease database scheme\n" printf " - lease-dump: Dump current leases to a CSV file\n" printf "\n" printf "BACKEND - one of the supported backends: memfile|mysql|pgsql\n" printf "\n" printf "PARAMETERS: Parameters are optional in general, but may be required\n" printf " for specific operation.\n" printf " -u or --user name - specifies username when connecting to a database\n" printf " -p or --password pass - specifies a password when connecting to a database\n" printf " -n or --name database - specifies a database name to connect to\n" printf " -d or --directory - path to upgrade scripts (default: ${SCRIPTS_DIR_DEFAULT})\n" printf "\n" printf " Parameters specific to lease-dump:\n" printf " -4 to dump IPv4 leases to file\n" printf " -6 to dump IPv6 leases to file\n" printf " -o or --output - name of file to which leases will be dumped\n" } ### Logging functions ### # Logs message at the error level. # Takes one parameter that is printed as is. log_error() { printf "ERROR/kea-admin: ${1}\n" } # Logs message at the warning level. # Takes one parameter that is printed as is. log_warning() { printf "WARNING/kea-admin: ${1}\n" } # Logs message at the info level. # Takes one parameter that is printed as is. log_info() { printf "INFO/kea-admin: ${1}\n" } ### Convenience functions ### # Checks if the value is in the list. An example usage of this function # is to determine whether the kea-admin command belongs to the list of # supported commands. is_in_list() { local member=${1} # Value to be checked local list="${2}" # Comma separated list of items _inlist=0 # Return value: 0 if not in list, 1 otherwise. if [ -z ${member} ]; then log_error "missing ${class}" fi # Iterate over all items on the list and compare with the member. # If they match, return, otherwise log error and exit. for item in ${list} do if [ ${item} = ${member} ]; then _inlist=1 return fi done } ### Functions that implement database initialization commands memfile_init() { # @todo Implement this as part of #3601 log_error "NOT IMPLEMENTED" exit 1 } # Initializes a new, empty MySQL database. # It essentially calls scripts/mysql/dhcpdb_create.mysql script, with # some extra sanity checks. It will refuse to use it if there are any # existing tables. It's better safe than sorry. mysql_init() { printf "Checking if there is a database initialized already. Please ignore errors.\n" # Let's try to count the number of tables. Anything above 0 means that there # is some database in place. If there is anything, we abort. Note that # mysql may spit out connection or access errors to stderr, we ignore those. # We should not hide them as they may give hints to user what is wrong with # his setup. # RESULT=`mysql_execute "SHOW TABLES;"` ERRCODE=$? if [ $ERRCODE -ne 0 ] then log_error "mysql_init table query failed, mysql status = $ERRCODE" exit 1 fi COUNT=`echo $RESULT | wc -w` if [ $COUNT -gt 0 ]; then # Let't start with a new line. mysql could have printed something out. printf "\n" log_error "Expected empty database $db_name, but there are $COUNT tables: \n$_RESULT. Aborting." exit 1 fi printf "Initializing database using script %s\n" $scripts_dir/mysql/dhcpdb_create.mysql mysql -B --user=$db_user --password=$db_password $db_name < $scripts_dir/mysql/dhcpdb_create.mysql ERRCODE=$? printf "mysql returned status code $ERRCODE\n" if [ "$ERRCODE" -eq 0 ]; then printf "Lease DB version reported after initialization: " mysql_version printf "\n" fi exit $ERRCODE } pgsql_init() { printf "Checking if there is a database initialized already. Please ignore errors.\n" # Let's try to count the number of tables. Anything above 0 means that there # is some database in place. If there is anything, we abort. RESULT=`pgsql_execute "\d"` ERRCODE=$? if [ "$ERRCODE" -ne 0 ]; then log_error "pgsql_init: table query failed, status code: $ERRCODE?" exit 1 fi COUNT=`echo "$RESULT" | wc -w` if [ $COUNT -gt 0 ]; then printf "\n" log_error "Expected empty database $db_name, but the following tables are present \n$RESULT. Aborting." exit 2 fi init_script="$scripts_dir/pgsql/dhcpdb_create.pgsql" printf "Initializing database using script %s\n" $init_script RESULT=`pgsql_execute_script $init_script` ERRCODE=$? if [ "$ERRCODE" -ne 0 ]; then log_error "Database initialization failed, status code: $ERRCODE?" exit 1 fi version=`pgsql_version` printf "Lease DB version reported after initialization: $version\n" exit 0 } ### Functions that implement database version checking commands memfile_version() { # @todo Implement this as part of #3601 log_error "NOT IMPLEMENTED" exit 1 } ### Functions used for upgrade memfile_upgrade() { # @todo Implement this as part of #3601 log_error "NOT IMPLEMENTED" exit 1 } # Upgrades existing MySQL database installation. The idea is that # it will go over all upgrade scripts from (prefix)/share/kea/scripts/mysql # and run them one by one. They will be named properly, so they will # be run in order. # # This function prints version before and after upgrade. mysql_upgrade() { printf "Lease DB version reported before upgrade: " mysql_version printf "\n" # Check if the scripts directory exists at all. if [ ! -d ${scripts_dir}/mysql ]; then log_error "Invalid scripts directory: ${scripts_dir}/mysql" exit 1 fi # Check if there are any files in it num_files=$(find ${scripts_dir}/mysql/upgrade*.sh -type f | wc -l) if [ $num_files -eq 0 ]; then log_error "No scripts in ${scripts_dir}/mysql or the directory is not readable or does not have any upgrade* scripts." exit 1 fi for script in ${scripts_dir}/mysql/upgrade*.sh do echo "Processing $script file..." sh ${script} --user=${db_user} --password=${db_password} ${db_name} done printf "Lease DB version reported after upgrade: " mysql_version printf "\n" } pgsql_upgrade() { version=`pgsql_version` printf "Lease DB version reported before upgrade: $version\n" # Check if the scripts directory exists at all. if [ ! -d ${scripts_dir}/pgsql ]; then log_error "Invalid scripts directory: ${scripts_dir}/pgsql" exit 1 fi # Check if there are any files in it num_files=$(find ${scripts_dir}/pgsql/upgrade*.sh -type f | wc -l) if [ $num_files -eq 0 ]; then log_error "No scripts in ${scripts_dir}/pgsql or the directory is not readable or does not have any upgrade* scripts." exit 1 fi for script in ${scripts_dir}/pgsql/upgrade*.sh do echo "Processing $script file..." sh ${script} --user=${db_user} --password=${db_password} ${db_name} done version=`pgsql_version` printf "Lease DB version reported after upgrade: $version\n" exit 0 } # Utility function which tests if the given file exists and # if so notifies the user and provides them the opportunity # to abort the current command. check_file_overwrite () { local file=$1 if [ -e ${file} ] then echo "Output file, $file, exists and will be overwritten." echo "Do you wish to continue? (y/n)" read ans if [ ${ans} != "y" ] then echo "$command aborted by user." exit 1 fi fi } ### Functions used for dump # Sets the global variable, dump_qry, to the schema-version specific # SQL text needed to dump the lease data for the current backend # and protocol get_dump_query() { local version=$1 dump_qry="" dump_sql_file="$scripts_dir/${backend}/lease_dump_$version.sh" if [ ! -e $dump_sql_file ] then log_error "lease-dump: cannot access dump_sql_file: $dump_sql_file" exit 1; fi # source in the dump file which defines the sql text we'll need . $dump_sql_file if [ $? -ne 0 ] then log_error "lease-dump: error sourcing dump_sql_file: $dump_sql_file" exit 1 fi # Construct the SQL text to dump the leases based on protocol type case ${dump_type} in 4) dump_qry="$lease4_dump_sql"; ;; 6) dump_qry="$lease6_dump_sql"; ;; *) log_error "you must specify -4 or -6 for lease-dump" usage exit 1 ;; esac if [ "$dump_qry" = "" ] then log_error "lease-dump: dump query appears to be undefined" exit 1 fi } memfile_dump() { log_error "lease-dump is not supported for memfile" exit 1 } mysql_dump() { # get the correct dump query version=`mysql_version` retcode=$? if [ $retcode -ne 0 ] then log_error "lease-dump: mysql_version failed, exit code $retcode" exit 1; fi # Fetch the correct SQL text. Note this function will exit # if it fails. get_dump_query $version # Make sure they specified a file if [ "$dump_file" = "" ]; then log_error "you must specify an output file for lease-dump" usage exit 1 fi # If output file exists, notify user, allow them a chance to bail check_file_overwrite $dump_file # Check the temp file too tmp_file="$dump_file.tmp" check_file_overwrite $tmp_file # Run the sql to output tab-delimited lease data to a temp file. # By using a temp file we can check for MySQL errors before using # 'tr' to translate tabs to commas. We do not use MySQL's output # to file as that requires linux superuser privileges to execute # the select. mysql_execute "${dump_qry}" > $tmp_file retcode=$? if [ $retcode -ne 0 ]; then log_error "lease-dump: mysql_execute failed, exit code $retcode"; exit 1 fi # Now translate tabs to commas. cat $tmp_file | tr '\t' ',' >$dump_file if [ $? -ne 0 ]; then log_error "lease-dump: reformatting failed"; exit 1 fi # delete the tmp file on success rm $tmp_file echo lease$dump_type successfully dumped to $dump_file exit 0 } ### Functions used for dump pgsql_dump() { version=`pgsql_version` get_dump_query $version # Make sure they specified a file if [ "$dump_file" = "" ]; then log_error "you must specify an output file for lease-dump" usage exit 1 fi # If output file exists, notify user, allow them a chance to bail check_file_overwrite $dump_file # psql does not accept password as a parameter but will look in the environment export PGPASSWORD=$db_password # Call psql and redirect output to the dump file. We don't use psql "to csv" # as it can only be run as db superuser. echo "$dump_qry" | psql --set ON_ERROR_STOP=1 -t -q --user=$db_user --dbname=$db_name -w --no-align --field-separator=',' >$dump_file retcode=$? # Check for errors. if [ $retcode -ne 0 ]; then log_error "lease-dump: psql call failed, exit code: $retcode"; exit 1 fi echo lease$dump_type successfully dumped to $dump_file exit 0 } ### Script starts here ### # First, find what the command is command=${1} if [ -z ${command} ]; then log_error "missing command" usage exit 1 fi is_in_list "${command}" "lease-init lease-version lease-upgrade lease-dump" if [ ${_inlist} -eq 0 ]; then log_error "invalid command: ${command}" exit 1 fi shift # Second, check what's the backend backend=${1} if [ -z ${backend} ]; then log_error "missing backend" usage exit 1 fi is_in_list "${backend}" "memfile mysql pgsql" if [ ${_inlist} -eq 0 ]; then log_error "invalid backend: ${backend}" exit 1 fi shift # Ok, let's process parameters (if there are any) while [ ! -z "${1}" ] do option=${1} case ${option} in # Specify database user -u|--user) shift db_user=${1} if [ -z ${db_user} ]; then log_error "-u or --user requires a parameter" usage exit 1 fi ;; # Specify database password -p|--password) shift db_password=${1} if [ -z ${db_password} ]; then log_error "-p or --password requires a parameter" usage exit 1 fi ;; # Specify database name -n|--name) shift db_name=${1} if [ -z ${db_name} ]; then log_error "-n or --name requires a parameter" usage exit 1 fi ;; -d|--directory) shift scripts_dir=${1} if [ -z ${scripts_dir} ]; then log_error "-d or --directory requires a parameter" usage exit 1 fi ;; # specify DHCPv4 lease type -4) if [ $dump_type -eq 6 ]; then log_error "you may not specify both -4 and -6" usage exit 1 fi dump_type=4 ;; # specify DHCPv6 lease type -6) if [ $dump_type -eq 4 ]; then log_error "you may not specify both -4 and -6" usage exit 1 fi dump_type=6 ;; # specify output file, currently only used by lease dump -o|--output) shift dump_file=${1} if [ -z ${dump_file} ]; then log_error "-o or --output requires a parameter" usage exit 1 fi ;; *) log_error "invalid option: ${option}" usage exit 1 esac shift done case ${command} in # Initialize the database lease-init) case ${backend} in memfile) memfile_init ;; mysql) mysql_init ;; pgsql) pgsql_init ;; esac ;; lease-version) case ${backend} in memfile) memfile_version ;; mysql) mysql_version printf "\n" ;; pgsql) pgsql_version ;; esac ;; lease-upgrade) case ${backend} in memfile) memfile_upgrade ;; mysql) mysql_upgrade ;; pgsql) pgsql_upgrade ;; esac ;; lease-dump) case ${backend} in memfile) memfile_dump ;; mysql) mysql_dump ;; pgsql) pgsql_dump ;; esac ;; esac exit 0