#!/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 INITIAL_PLACEMENT_DB_VERSION=${INITIAL_DB_VERSION:-b4ed3a175331} 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 check_cli() { # Returns 0 if placement cli is installed and configured, # 1 if it is not installed, or 2 if the access to the # placement database fails # Usage: check_cli -> 0 placement-manage --version > /dev/null 2>&1 if [ $? -ne 0 ]; then # placement not installed return 1 fi placement-manage db version > /dev/null 2>&1 if [ $? -ne 0 ]; then # DB connection fails return 2 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 dump_flags="$3" local tmpdir=$(mktemp -d migrate-db.XXXXXXXX) local tmpfile="${tmpdir}/from-nova.sql" echo "Dumping from $source to $tmpfile" mysql_command $source mysqldump $dump_flags $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 " --skip-locks: don't use table locks for data migration" 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" echo " 6: Unable to execute placement's CLI commands" 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=$? check_cli placement_cli=$? # 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 elif [ $placement_cli -eq 1 ]; then echo "Unable to proceed without placement installed" exit 6 elif [ $placement_cli -eq 2 ]; then echo "The 'placement-manage db version' command fails" echo "Is placement.conf configured to access the new database?" exit 6 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 if getflag skip-locks $*; then migrate_data NOVA_API PLACEMENT "--skip-lock-tables --skip-add-locks" else migrate_data NOVA_API PLACEMENT fi placement-manage db stamp $INITIAL_PLACEMENT_DB_VERSION else echo "To actually migrate, run me with --migrate" fi rm -f $LAST_MYSQL_ERR