ef9d8392f2
This patchset fixes a serious database restoration problem where the user is trying to restore a single database, but in the process of restoring the database, the script inadvertently also removes all tables from the other databases. The root cause was that the mysql "--one-database" restore option achieves the single database restoration, but somehow corrupts the other databases. The new approach taken in this patchset is to create a temporary database user which only has permission to restore the chosen database, and that will leave the other databases unharmed. This approach, which can be applied for restoring individual databases and even database tables, was recommended in (1). After the database is restored, the temporary user is deleted. (1) https://mariadb.com/kb/en/restoring-data-from-dump-files/ Also improved some of the error handling as well. Change-Id: I805c605ed2b424640ad6a0a379b1c0b9c0004e94
345 lines
9.1 KiB
Smarty
Executable File
345 lines
9.1 KiB
Smarty
Executable File
#!/bin/bash
|
|
|
|
# Copyright 2018 The Openstack-Helm Authors.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
|
# not use this file except in compliance with the License. You may obtain
|
|
# a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
|
# License for the specific language governing permissions and limitations
|
|
# under the License.
|
|
|
|
log_error() {
|
|
echo $1
|
|
exit 1
|
|
}
|
|
|
|
ARCHIVE_DIR=${MARIADB_BACKUP_BASE_DIR}/db/${MARIADB_POD_NAMESPACE}/mariadb/archive
|
|
RESTORE_DIR=${MARIADB_BACKUP_BASE_DIR}/db/${MARIADB_POD_NAMESPACE}/mariadb/restore
|
|
ARGS=("$@")
|
|
RESTORE_USER='restoreuser'
|
|
RESTORE_PW=$(pwgen 16 1)
|
|
RESTORE_LOG='/tmp/restore_error.log'
|
|
rm -f $RESTORE_LOG
|
|
|
|
#Create Restore Directory
|
|
mkdir -p $RESTORE_DIR
|
|
|
|
# This is for commands which require admin access
|
|
MYSQL="mysql \
|
|
--defaults-file=/etc/mysql/admin_user.cnf \
|
|
--host=$MARIADB_SERVER_SERVICE_HOST \
|
|
--connect-timeout 10"
|
|
|
|
# This is for commands which we want the temporary "restore" user
|
|
# to execute
|
|
RESTORE_CMD="mysql \
|
|
--user=${RESTORE_USER} \
|
|
--password=${RESTORE_PW} \
|
|
--host=$MARIADB_SERVER_SERVICE_HOST \
|
|
--connect-timeout 10"
|
|
|
|
#Delete file
|
|
delete_files() {
|
|
files_to_delete=("$@")
|
|
for f in "${files_to_delete[@]}"
|
|
do
|
|
if [ -f $f ]
|
|
then
|
|
rm -rf $f
|
|
fi
|
|
done
|
|
}
|
|
|
|
#Display all archives
|
|
list_archives() {
|
|
if [ -d ${ARCHIVE_DIR} ]
|
|
then
|
|
archives=$(find ${ARCHIVE_DIR}/ -iname "*.gz" -print)
|
|
echo "All Archives"
|
|
echo "=================================="
|
|
for archive in $archives
|
|
do
|
|
echo $archive | cut -d '/' -f 8
|
|
done
|
|
else
|
|
log_error "Archive directory is not available."
|
|
fi
|
|
}
|
|
|
|
#Return all database from an archive
|
|
get_databases() {
|
|
archive_file=$1
|
|
if [ -e ${ARCHIVE_DIR}/${archive_file} ]
|
|
then
|
|
files_to_purge=$(find $RESTORE_DIR/ -iname "*.sql" -print)
|
|
delete_files $files_to_purge
|
|
tar zxvf ${ARCHIVE_DIR}/${archive_file} -C ${RESTORE_DIR} 1>/dev/null
|
|
if [ -e ${RESTORE_DIR}/db.list ]
|
|
then
|
|
DBS=$(cat ${RESTORE_DIR}/db.list )
|
|
else
|
|
DBS=" "
|
|
fi
|
|
else
|
|
DBS=" "
|
|
fi
|
|
}
|
|
|
|
#Display all database from an archive
|
|
list_databases() {
|
|
archive_file=$1
|
|
get_databases $archive_file
|
|
#echo $DBS
|
|
if [ -n "$DBS" ]
|
|
then
|
|
echo " "
|
|
echo "Databases in the archive $archive_file"
|
|
echo "================================================================="
|
|
for db in $DBS
|
|
do
|
|
echo $db
|
|
done
|
|
fi
|
|
}
|
|
|
|
# Create temporary user for restoring specific databases.
|
|
create_restore_user() {
|
|
restore_db=$1
|
|
|
|
# Ensure any old restore user is removed first, if it exists.
|
|
# If it doesn't exist it may return error, so do not exit the
|
|
# script if that's the case.
|
|
delete_restore_user "dont_exit_on_error"
|
|
|
|
$MYSQL --execute="GRANT SELECT ON *.* TO ${RESTORE_USER}@'%' IDENTIFIED BY '${RESTORE_PW}';" 2>>$RESTORE_LOG
|
|
if [ "$?" -eq 0 ]
|
|
then
|
|
$MYSQL --execute="GRANT ALL ON ${restore_db}.* TO ${RESTORE_USER}@'%' IDENTIFIED BY '${RESTORE_PW}';" 2>>$RESTORE_LOG
|
|
if [ "$?" -ne 0 ]
|
|
then
|
|
cat $RESTORE_LOG
|
|
log_error "Failed to grant restore user ALL permissions on database ${restore_db}"
|
|
fi
|
|
else
|
|
cat $RESTORE_LOG
|
|
log_error "Failed to grant restore user select permissions on all databases"
|
|
fi
|
|
}
|
|
|
|
# Delete temporary restore user
|
|
delete_restore_user() {
|
|
error_handling=$1
|
|
|
|
$MYSQL --execute="DROP USER ${RESTORE_USER}@'%';" 2>>$RESTORE_LOG
|
|
if [ "$?" -ne 0 ]
|
|
then
|
|
if [ "$error_handling" == "exit_on_error" ]
|
|
then
|
|
cat $RESTORE_LOG
|
|
log_error "Failed to delete temporary restore user - needs attention to avoid a security hole"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
#Restore a single database
|
|
restore_single_db() {
|
|
single_db_name=$1
|
|
if [ -z "$single_db_name" ]
|
|
then
|
|
log_error "Restore single DB called but with wrong parameter."
|
|
fi
|
|
if [ -f ${ARCHIVE_DIR}/${archive_file} ]
|
|
then
|
|
files_to_purge=$(find $RESTORE_DIR/ -iname "*.sql" -print)
|
|
delete_files $files_to_purge
|
|
tar zxvf ${ARCHIVE_DIR}/${archive_file} -C ${RESTORE_DIR} 1>/dev/null
|
|
if [ -f ${RESTORE_DIR}/mariadb.all.sql ]
|
|
then
|
|
# Restoring a single database requires us to create a temporary user
|
|
# which has capability to only restore that ONE database. One gotcha
|
|
# is that the mysql command to restore the database is going to throw
|
|
# errors because of all the other databases that it cannot access. So
|
|
# because of this reason, the --force option is used to prevent the
|
|
# command from stopping on an error.
|
|
create_restore_user $single_db_name
|
|
$RESTORE_CMD --force < ${RESTORE_DIR}/mariadb.all.sql 2>>$RESTORE_LOG
|
|
if [ "$?" -eq 0 ]
|
|
then
|
|
echo "Database $single_db_name Restore successful."
|
|
else
|
|
cat $RESTORE_LOG
|
|
delete_restore_user "exit_on_error"
|
|
log_error "Database $single_db_name Restore failed."
|
|
fi
|
|
delete_restore_user "exit_on_error"
|
|
|
|
if [ -f ${RESTORE_DIR}/${single_db_name}_grant.sql ]
|
|
then
|
|
$MYSQL < ${RESTORE_DIR}/${single_db_name}_grant.sql 2>>$RESTORE_LOG
|
|
if [ "$?" -eq 0 ]
|
|
then
|
|
echo "Database $single_db_name Permission Restore successful."
|
|
else
|
|
cat $RESTORE_LOG
|
|
log_error "Database $single_db_name Permission Restore failed."
|
|
fi
|
|
else
|
|
log_error "There is no permission file available for $single_db_name"
|
|
fi
|
|
else
|
|
log_error "There is no database file available to restore from"
|
|
fi
|
|
else
|
|
log_error "Archive does not exist"
|
|
fi
|
|
}
|
|
|
|
#Restore all the databases
|
|
restore_all_dbs() {
|
|
if [ -f ${ARCHIVE_DIR}/${archive_file} ]
|
|
then
|
|
files_to_purge=$(find $RESTORE_DIR/ -iname "*.sql" -print)
|
|
delete_files $files_to_purge
|
|
tar zxvf ${ARCHIVE_DIR}/${archive_file} -C ${RESTORE_DIR} 1>/dev/null
|
|
if [ -f ${RESTORE_DIR}/mariadb.all.sql ]
|
|
then
|
|
$MYSQL < ${RESTORE_DIR}/mariadb.all.sql 2>$RESTORE_LOG
|
|
if [ "$?" -eq 0 ]
|
|
then
|
|
echo "Databases $( echo $DBS | tr -d '\n') Restore successful."
|
|
else
|
|
cat $RESTORE_LOG
|
|
log_error "Databases $( echo $DBS | tr -d '\n') Restore failed."
|
|
fi
|
|
if [ -n "$DBS" ]
|
|
then
|
|
for db in $DBS
|
|
do
|
|
if [ -f ${RESTORE_DIR}/${db}_grant.sql ]
|
|
then
|
|
$MYSQL < ${RESTORE_DIR}/${db}_grant.sql 2>>$RESTORE_LOG
|
|
if [ "$?" -eq 0 ]
|
|
then
|
|
echo "Database $db Permission Restore successful."
|
|
else
|
|
cat $RESTORE_LOG
|
|
log_error "Database $db Permission Restore failed."
|
|
fi
|
|
else
|
|
log_error "There is no permission file available for $db"
|
|
fi
|
|
done
|
|
else
|
|
log_error "There is no database file available to restore from"
|
|
fi
|
|
else
|
|
log_error "Archive does not exist"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
usage() {
|
|
ret_val=$1
|
|
echo "Usage:"
|
|
echo "Restore command options"
|
|
echo "============================="
|
|
echo "help"
|
|
echo "list_archives"
|
|
echo "list_databases <archive_filename>"
|
|
echo "restore <archive_filename> [<db_name> | ALL]"
|
|
exit $ret_val
|
|
}
|
|
|
|
is_Option() {
|
|
opts=$1
|
|
param=$2
|
|
find=0
|
|
for opt in $opts
|
|
do
|
|
if [ "$opt" == "$param" ]
|
|
then
|
|
find=1
|
|
fi
|
|
done
|
|
echo $find
|
|
}
|
|
|
|
#Main
|
|
if [ ${#ARGS[@]} -gt 3 ]
|
|
then
|
|
usage 1
|
|
elif [ ${#ARGS[@]} -eq 1 ]
|
|
then
|
|
if [ "${ARGS[0]}" == "list_archives" ]
|
|
then
|
|
list_archives
|
|
elif [ "${ARGS[0]}" == "help" ]
|
|
then
|
|
usage 0
|
|
else
|
|
usage 1
|
|
fi
|
|
elif [ ${#ARGS[@]} -eq 2 ]
|
|
then
|
|
if [ "${ARGS[0]}" == "list_databases" ]
|
|
then
|
|
list_databases ${ARGS[1]}
|
|
else
|
|
usage 1
|
|
fi
|
|
elif [ ${#ARGS[@]} -eq 3 ]
|
|
then
|
|
if [ "${ARGS[0]}" != "restore" ]
|
|
then
|
|
usage 1
|
|
else
|
|
if [ -f ${ARCHIVE_DIR}/${ARGS[1]} ]
|
|
then
|
|
#Get all the databases in that archive
|
|
get_databases ${ARGS[1]}
|
|
|
|
#check if the requested database is available in the archive
|
|
if [ $(is_Option "$DBS" ${ARGS[2]}) -eq 1 ]
|
|
then
|
|
echo "Creating database ${ARGS[2]} if it does not exist"
|
|
$MYSQL -e "CREATE DATABASE IF NOT EXISTS \`${ARGS[2]}\`" 2>>$RESTORE_LOG
|
|
if [ "$?" -ne 0 ]
|
|
then
|
|
cat $RESTORE_LOG
|
|
log_error "Database ${ARGS[2]} could not be created."
|
|
fi
|
|
echo "Restoring database ${ARGS[2]} and grants...this could take a few minutes."
|
|
restore_single_db ${ARGS[2]}
|
|
elif [ "$( echo ${ARGS[2]} | tr '[a-z]' '[A-Z]')" == "ALL" ]
|
|
then
|
|
echo "Creating databases if they do not exist"
|
|
for db in $DBS
|
|
do
|
|
$MYSQL -e "CREATE DATABASE IF NOT EXISTS \`$db\`"
|
|
if [ "$?" -ne 0 ]
|
|
then
|
|
cat $RESTORE_LOG
|
|
log_error "Database ${db} could not be created."
|
|
fi
|
|
done
|
|
echo "Restoring all databases and grants...this could take a few minutes."
|
|
restore_all_dbs
|
|
else
|
|
echo "Database ${ARGS[2]} does not exist."
|
|
fi
|
|
else
|
|
echo "Archive file not found"
|
|
fi
|
|
fi
|
|
else
|
|
usage 1
|
|
fi
|
|
|
|
exit 0
|