diff --git a/tools/mysql-migrate-db.sh b/tools/mysql-migrate-db.sh new file mode 100755 index 000000000..a8f9b3320 --- /dev/null +++ b/tools/mysql-migrate-db.sh @@ -0,0 +1,269 @@ +#!/bin/bash + +# This script will attempt to migrate your nova-api placement data to +# a new placement database. Run it with --help for usage, and --mkconfig +# to write a template config file to use. + +# Defaults we can guess +DEFAULT_MIGRATE_TABLES="allocations placement_aggregates consumers inventories projects " +DEFAULT_MIGRATE_TABLES+="resource_classes resource_provider_aggregates resource_provider_traits " +DEFAULT_MIGRATE_TABLES+="resource_providers traits users " +MIGRATE_TABLES=${MIGRATE_TABLES:-$DEFAULT_MIGRATE_TABLES} +PLACEMENT_DB_HOST=${PLACEMENT_DB_HOST:-localhost} +PLACEMENT_DB=${PLACEMENT_DB:-placement} +NOVA_API_DB_HOST=${NOVA_API_DB_HOST:-localhost} +NOVA_API_DB=${NOVA_API_DB:-nova_api} +TMPDIR=${TMPDIR:-/tmp} +LAST_MYSQL_ERR=${TMPDIR}/migrate-mysql-db.err +ME=$(basename "$0") + +declare -a ARGS +declare -a OPTS + +function getflag() { + # Return true if --$flag is present on the command line + # Usage: getflag help -> 0 + local flag="$1" + for opt in ${OPTS[*]}; do + if [ "$opt" == "--${flag}" ]; then + return 0 + fi + done + return 1 +} + +function parse_argv() { + # Parse command line arguments into positional arguments and + # option flags. Store each in $ARGS, $OPTS. + # Usage: parse_argv $* + for item in $*; do + if echo $item | grep -q -- '^--'; then + OPTS+=($item) + else + ARGS+=($item) + fi + done +} + +function db_var() { + # Return an attribute of database config based on the symbolic + # name + # Usage: db_var PLACEMENT USER -> $PLACEMENT_USER + local db="$1" + local var="$2" + + eval echo "\$${db}_${var}" +} + +function mysql_command() { + # Run a mysql command with the usual connection information taken + # from a symbolic configuration name + # Usage: mysql_command PLACEMENT [command] [args..] -> stdout + local whichdb="$1" + shift + local command=mysql + if [ "$2" ]; then + command=${1:-mysql} + shift + fi + local db=$(db_var $whichdb DB) + local host=$(db_var $whichdb DB_HOST) + local user=$(db_var $whichdb USER) + local pass=$(db_var $whichdb PASS) + + if [ "$command" = "mysql" ]; then + command="mysql --skip-column-names" + fi + + $command -h$host -u$user -p$pass $db $* 2>$LAST_MYSQL_ERR +} + +function show_error() { + # Prints the last error (if present) and removes the temporary + # file + if [ -f $LAST_MYSQL_ERR ]; then + cat $LAST_MYSQL_ERR + rm -f $LAST_MYSQL_ERR + fi +} + +function check_db() { + # Check a DB to see if it's missing, present, filled with data + # Returns 0 if it is present with data, 1 if present but no data + # or 2 if not present (or unable to connect) + # Usage: check_db PLACEMENT -> 0 + local whichdb="$1" + + local inv + local inv_count + + if ! echo "SELECT DATABASE()" | mysql_command $whichdb >/dev/null 2>&1; then + echo "Failed to connect to $whichdb database" + show_error + return 2 + fi + + inv=$(echo "SELECT COUNT(id) FROM inventories" | + mysql_command $whichdb) + if [ $? -ne 0 ]; then + # No schema + return 1 + fi + + inv_count=$(echo $inv | tail -n1) + if [ $inv_count -gt 0 ]; then + # Data found + return 0 + else + # No data found, but schema intact + return 1 + fi +} + +function migrate_data() { + # Actually migrate data from a source to destination symbolic + # database. Returns 1 if failure, 0 otherwise. + # Usage: migrate_data NOVA_API PLACEMENT -> 0 + local source="$1" + local dest="$2" + local tmpdir=$(mktemp -d migrate-db.XXXXXXXX) + local tmpfile="${tmpdir}/from-nova.sql" + + echo "Dumping from $source to $tmpfile" + mysql_command $source mysqldump $MIGRATE_TABLES > $tmpfile || { + echo 'Failed to dump source database:' + show_error + return 1 + } + echo "Loading to $dest from $tmpfile" + mysql_command $dest < $tmpfile || { + echo 'Failed to load destination database:' + show_error + return 1 + } +} + +function sanity_check_env() { + # Check that we have everything we need to examine the situation + # and potentially do the migration. Loads values from the rcfile, + # if present. Returns 1 if a config was not found, 2 if that + # config is incomplete or 0 if everything is good. + # Usage: sanity_check_env $rcfile -> 0 + + RCFILE="${1:-migrate-db.rc}" + if [ "$RCFILE" = '-' ]; then + # Don't require a file and assume everything is already + # set in the environment + true + elif [ ! -f "$RCFILE" ]; then + echo -n 'ERROR: Specify an RC file on the command line or create ' + echo 'migrate-db.rc in the current directory' + echo + show_help + else + source $RCFILE + fi + + required="NOVA_API_DB NOVA_API_USER NOVA_API_PASS PLACEMENT_DB PLACEMENT_USER PLACEMENT_PASS" + for var in $required; do + value=$(eval echo "\$$var") + if [ -z "$value" ]; then + echo "A value for $var was not provided but is required" + return 2 + fi + done +} + +function make_config() { + # Create or update a config file with defaults we know. Either use + # the default migrate-db.rc or the file specified on the command + # line. + RCFILE="${1:-migrate-db.rc}" + if [ -f "$RCFILE" ]; then + source $RCFILE + fi + + vars="NOVA_API_DB NOVA_API_USER NOVA_API_PASS NOVA_API_DB_HOST " + vars+="PLACEMENT_DB PLACEMENT_USER PLACEMENT_PASS PLACEMENT_DB_HOST " + vars+="MIGRATE_TABLES" + + (for var in $vars; do + val=$(eval echo "\$$var") + echo "${var}=\"$val\"" + done) > $RCFILE + + echo Wrote $(readlink -f $RCFILE) +} + +function show_help() { + echo "Usage: $ME [flags] [rcfile]" + echo + echo "Flags:" + echo " --help: this text" + echo " --migrate: actually do data migration" + echo " --mkconfig: write/update config to \$rcfile" + echo + echo "Pass '-' as \$rcfile if all config values are set in" + echo "the environment." + echo + echo "Exit codes:" + echo " 0: Success" + echo " 1: Usage error" + echo " 2: Configuration missing or incomplete" + echo " 3: Migration already completed" + echo " 4: No data to migrate from nova (new deployment)" + echo " 5: Unable to connect to one or both databases" + exit 0 +} + +parse_argv $* + +if getflag help; then + show_help +fi + +if getflag mkconfig; then + make_config $ARGS + exit 0 +fi + +# +# Actual migration logic starts here +# + +# Sanity check that we have what we need or bail +sanity_check_env $ARGS || exit $? + +# Check the state of each database we care about +check_db NOVA_API +nova_present=$? +check_db PLACEMENT +placement_present=$? + +# Try to come up with a good reason to refuse to migrate +if [ $nova_present -eq 0 -a $placement_present -eq 0 ]; then + echo "Migration has already completed. The placement database appears to have data." + exit 3 +elif [ $nova_present -eq 1 ]; then + echo "No data present in nova database - nothing to migrate (new deployment?)" + exit 4 +elif [ $nova_present -eq 2 ]; then + echo "Unable to proceed without connection to nova database" + exit 5 +elif [ $placement_present -eq 2 ]; then + echo "Unable to proceed without connection to placement database" + exit 5 +fi + +# If we get here, we expect to be able to migrate. Require them to opt into +# actual migration before we do anything. + +echo Nova database contains data, placement database does not. Okay to proceed with migration + +if getflag migrate $*; then + migrate_data NOVA_API PLACEMENT +else + echo "To actually migrate, run me with --migrate" +fi + +rm -f $LAST_MYSQL_ERR