/* Copyright (c) 2003, 2019, Oracle and/or its affiliates. All rights reserved. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, as published by the Free Software Foundation. This program is also distributed with certain software (including but not limited to OpenSSL) that is licensed under separate terms, as designated in a particular file or component or in included license documentation. The authors of MySQL hereby grant you an additional permission to link the program and your derivative works with the separately licensed software that they have included with MySQL. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0, for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include "../src/ndbapi/NdbDictionaryImpl.hpp" #include "consumer_printer.hpp" #include "consumer_restore.hpp" #include "my_alloc.h" #include #include #define TMP_TABLE_PREFIX "#sql" #define TMP_TABLE_PREFIX_LEN 4 extern FilteredNdbOut err; extern FilteredNdbOut info; extern FilteredNdbOut debug; extern RestoreLogger restoreLogger; static Uint32 g_tableCompabilityMask = 0; static int ga_nodeId = 0; static int ga_nParallelism = 128; static int ga_backupId = 0; bool ga_dont_ignore_systab_0 = false; static bool ga_no_upgrade = false; static bool ga_promote_attributes = false; static bool ga_demote_attributes = false; static const Uint32 BF_UNKNOWN = 0; static const Uint32 BF_SINGLE = 1; static const Uint32 BF_MULTI_PART = 2; static bool g_restoring_in_parallel = true; static const int g_max_parts = 128; static int ga_backup_format = BF_UNKNOWN; static int ga_part_count = 1; static int ga_error_thread = 0; static const char* default_backupPath = "." DIR_SEPARATOR; static const char* ga_backupPath = default_backupPath; static const char *opt_nodegroup_map_str= 0; static unsigned opt_nodegroup_map_len= 0; static NODE_GROUP_MAP opt_nodegroup_map[MAX_NODE_GROUP_MAPS]; #define OPT_NDB_NODEGROUP_MAP 'z' const char *opt_ndb_database= NULL; const char *opt_ndb_table= NULL; unsigned int opt_verbose; unsigned int opt_hex_format; bool opt_show_part_id = true; unsigned int opt_progress_frequency; NDB_TICKS g_report_prev; Vector g_databases; Vector g_tables; Vector g_include_tables, g_exclude_tables; Vector g_include_databases, g_exclude_databases; Properties g_rewrite_databases; NdbRecordPrintFormat g_ndbrecord_print_format; unsigned int opt_no_binlog; Ndb_cluster_connection *g_cluster_connection = NULL; class RestoreOption { public: virtual ~RestoreOption() { } int optid; BaseString argument; }; Vector g_include_exclude; static void save_include_exclude(int optid, char * argument); static inline void parse_rewrite_database(char * argument); static void exitHandler(int code); static void free_include_excludes_vector(); /** * print and restore flags */ static bool ga_restore_epoch = false; static bool ga_restore = false; static bool ga_print = false; static bool ga_skip_table_check = false; static bool ga_exclude_missing_columns = false; static bool ga_exclude_missing_tables = false; static bool opt_exclude_intermediate_sql_tables = true; #ifdef ERROR_INSERT static unsigned int _error_insert = 0; #endif static int _print = 0; static int _print_meta = 0; static int _print_data = 0; static int _print_log = 0; static int _print_sql_log = 0; static int _restore_data = 0; static int _restore_meta = 0; static int _no_restore_disk = 0; static bool _preserve_trailing_spaces = false; static bool ga_disable_indexes = false; static bool ga_rebuild_indexes = false; bool ga_skip_unknown_objects = false; bool ga_skip_broken_objects = false; BaseString g_options("ndb_restore"); const char *load_default_groups[]= { "mysql_cluster","ndb_restore",0 }; enum ndb_restore_options { OPT_VERBOSE = NDB_STD_OPTIONS_LAST, OPT_INCLUDE_TABLES, OPT_EXCLUDE_TABLES, OPT_INCLUDE_DATABASES, OPT_EXCLUDE_DATABASES, OPT_REWRITE_DATABASE #ifdef ERROR_INSERT ,OPT_ERROR_INSERT #endif }; static const char *opt_fields_enclosed_by= NULL; static const char *opt_fields_terminated_by= NULL; static const char *opt_fields_optionally_enclosed_by= NULL; static const char *opt_lines_terminated_by= NULL; static const char *tab_path= NULL; static int opt_append; static const char *opt_exclude_tables= NULL; static const char *opt_include_tables= NULL; static const char *opt_exclude_databases= NULL; static const char *opt_include_databases= NULL; static const char *opt_rewrite_database= NULL; static bool opt_restore_privilege_tables = false; static struct my_option my_long_options[] = { NDB_STD_OPTS("ndb_restore"), { "connect", 'c', "same as --connect-string", (uchar**) &opt_ndb_connectstring, (uchar**) &opt_ndb_connectstring, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, { "nodeid", 'n', "Backup files from node with id", (uchar**) &ga_nodeId, (uchar**) &ga_nodeId, 0, GET_INT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, { "backupid", 'b', "Backup id", (uchar**) &ga_backupId, (uchar**) &ga_backupId, 0, GET_INT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, { "restore_data", 'r', "Restore table data/logs into NDB Cluster using NDBAPI", (uchar**) &_restore_data, (uchar**) &_restore_data, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "restore_meta", 'm', "Restore meta data into NDB Cluster using NDBAPI", (uchar**) &_restore_meta, (uchar**) &_restore_meta, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "no-upgrade", 'u', "Don't upgrade array type for var attributes, which don't resize VAR data and don't change column attributes", (uchar**) &ga_no_upgrade, (uchar**) &ga_no_upgrade, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "promote-attributes", 'A', "Allow attributes to be promoted when restoring data from backup", (uchar**) &ga_promote_attributes, (uchar**) &ga_promote_attributes, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "lossy-conversions", 'L', "Allow lossy conversions for attributes (type demotions or integral" " signed/unsigned type changes) when restoring data from backup", (uchar**) &ga_demote_attributes, (uchar**) &ga_demote_attributes, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "preserve-trailing-spaces", 'P', "Allow to preserve the tailing spaces (including paddings) When char->varchar or binary->varbinary is promoted", (uchar**) &_preserve_trailing_spaces, (uchar**)_preserve_trailing_spaces , 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "no-restore-disk-objects", 'd', "Dont restore disk objects (tablespace/logfilegroups etc)", (uchar**) &_no_restore_disk, (uchar**) &_no_restore_disk, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "restore_epoch", 'e', "Restore epoch info into the status table. Convenient on a MySQL Cluster " "replication slave, for starting replication. The row in " NDB_REP_DB "." NDB_APPLY_TABLE " with id 0 will be updated/inserted.", (uchar**) &ga_restore_epoch, (uchar**) &ga_restore_epoch, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "skip-table-check", 's', "Skip table structure check during restore of data", (uchar**) &ga_skip_table_check, (uchar**) &ga_skip_table_check, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "parallelism", 'p', "No of parallel transactions during restore of data." "(parallelism can be 1 to 1024)", (uchar**) &ga_nParallelism, (uchar**) &ga_nParallelism, 0, GET_INT, REQUIRED_ARG, 128, 1, 1024, 0, 1, 0 }, { "print", NDB_OPT_NOSHORT, "Print metadata, data and log to stdout", (uchar**) &_print, (uchar**) &_print, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "print_data", NDB_OPT_NOSHORT, "Print data to stdout", (uchar**) &_print_data, (uchar**) &_print_data, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "print_meta", NDB_OPT_NOSHORT, "Print meta data to stdout", (uchar**) &_print_meta, (uchar**) &_print_meta, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "print_log", NDB_OPT_NOSHORT, "Print log to stdout", (uchar**) &_print_log, (uchar**) &_print_log, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "print_sql_log", NDB_OPT_NOSHORT, "Print SQL log to stdout", (uchar**) &_print_sql_log, (uchar**) &_print_sql_log, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "backup_path", NDB_OPT_NOSHORT, "Path to backup files", (uchar**) &ga_backupPath, (uchar**) &ga_backupPath, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, { "dont_ignore_systab_0", 'f', "Do not ignore system table during --print-data.", (uchar**) &ga_dont_ignore_systab_0, (uchar**) &ga_dont_ignore_systab_0, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "ndb-nodegroup-map", OPT_NDB_NODEGROUP_MAP, "Nodegroup map for ndbcluster. Syntax: list of (source_ng, dest_ng)", (uchar**) &opt_nodegroup_map_str, (uchar**) &opt_nodegroup_map_str, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, { "fields-enclosed-by", NDB_OPT_NOSHORT, "Fields are enclosed by ...", (uchar**) &opt_fields_enclosed_by, (uchar**) &opt_fields_enclosed_by, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, { "fields-terminated-by", NDB_OPT_NOSHORT, "Fields are terminated by ...", (uchar**) &opt_fields_terminated_by, (uchar**) &opt_fields_terminated_by, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, { "fields-optionally-enclosed-by", NDB_OPT_NOSHORT, "Fields are optionally enclosed by ...", (uchar**) &opt_fields_optionally_enclosed_by, (uchar**) &opt_fields_optionally_enclosed_by, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, { "hex", NDB_OPT_NOSHORT, "print binary types in hex format", (uchar**) &opt_hex_format, (uchar**) &opt_hex_format, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "tab", 'T', "Creates tab separated textfile for each table to " "given path. (creates .txt files)", (uchar**) &tab_path, (uchar**) &tab_path, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0}, { "append", NDB_OPT_NOSHORT, "for --tab append data to file", (uchar**) &opt_append, (uchar**) &opt_append, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "lines-terminated-by", NDB_OPT_NOSHORT, "", (uchar**) &opt_lines_terminated_by, (uchar**) &opt_lines_terminated_by, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, { "progress-frequency", NDB_OPT_NOSHORT, "Print status uf restore periodically in given seconds", (uchar**) &opt_progress_frequency, (uchar**) &opt_progress_frequency, 0, GET_INT, REQUIRED_ARG, 0, 0, 65535, 0, 0, 0 }, { "no-binlog", NDB_OPT_NOSHORT, "If a mysqld is connected and has binary log, do not log the restored data", (uchar**) &opt_no_binlog, (uchar**) &opt_no_binlog, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "verbose", OPT_VERBOSE, "verbosity", (uchar**) &opt_verbose, (uchar**) &opt_verbose, 0, GET_INT, REQUIRED_ARG, 1, 0, 255, 0, 0, 0 }, { "include-databases", OPT_INCLUDE_DATABASES, "Comma separated list of databases to restore. Example: db1,db3", (uchar**) &opt_include_databases, (uchar**) &opt_include_databases, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, { "exclude-databases", OPT_EXCLUDE_DATABASES, "Comma separated list of databases to not restore. Example: db1,db3", (uchar**) &opt_exclude_databases, (uchar**) &opt_exclude_databases, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, { "rewrite-database", OPT_REWRITE_DATABASE, "A pair 'source,dest' of database names from/into which to restore. " "Example: --rewrite-database=oldDb,newDb", (uchar**) &opt_rewrite_database, (uchar**) &opt_rewrite_database, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, { "include-tables", OPT_INCLUDE_TABLES, "Comma separated list of tables to " "restore. Table name should include database name. Example: db1.t1,db3.t1", (uchar**) &opt_include_tables, (uchar**) &opt_include_tables, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, { "exclude-tables", OPT_EXCLUDE_TABLES, "Comma separated list of tables to " "not restore. Table name should include database name. " "Example: db1.t1,db3.t1", (uchar**) &opt_exclude_tables, (uchar**) &opt_exclude_tables, 0, GET_STR, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, { "restore-privilege-tables", NDB_OPT_NOSHORT, "Restore privilege tables (after they have been moved to ndb)", (uchar**) &opt_restore_privilege_tables, (uchar**) &opt_restore_privilege_tables, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "exclude-missing-columns", NDB_OPT_NOSHORT, "Ignore columns present in backup but not in database", (uchar**) &ga_exclude_missing_columns, (uchar**) &ga_exclude_missing_columns, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "exclude-missing-tables", NDB_OPT_NOSHORT, "Ignore tables present in backup but not in database", (uchar**) &ga_exclude_missing_tables, (uchar**) &ga_exclude_missing_tables, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "exclude-intermediate-sql-tables", NDB_OPT_NOSHORT, "Do not restore intermediate tables with #sql-prefixed names", (uchar**) &opt_exclude_intermediate_sql_tables, (uchar**) &opt_exclude_intermediate_sql_tables, 0, GET_BOOL, NO_ARG, 1, 0, 0, 0, 0, 0 }, { "disable-indexes", NDB_OPT_NOSHORT, "Disable indexes and foreign keys", (uchar**) &ga_disable_indexes, (uchar**) &ga_disable_indexes, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "rebuild-indexes", NDB_OPT_NOSHORT, "Rebuild indexes", (uchar**) &ga_rebuild_indexes, (uchar**) &ga_rebuild_indexes, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "skip-unknown-objects", 256, "Skip unknown object when parsing backup", (uchar**) &ga_skip_unknown_objects, (uchar**) &ga_skip_unknown_objects, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "skip-broken-objects", 256, "Skip broken object when parsing backup", (uchar**) &ga_skip_broken_objects, (uchar**) &ga_skip_broken_objects, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, { "show-part-id", 256, "Prefix log messages with backup part ID", (uchar**) &opt_show_part_id, (uchar**) &opt_show_part_id, 0, GET_BOOL, NO_ARG, 0, 0, 0, 0, 0, 0 }, #ifdef ERROR_INSERT { "error-insert", OPT_ERROR_INSERT, "Insert errors (testing option)", (uchar **)&_error_insert, (uchar **)&_error_insert, 0, GET_INT, REQUIRED_ARG, 0, 0, 0, 0, 0, 0 }, #endif { 0, 0, 0, 0, 0, 0, GET_NO_ARG, NO_ARG, 0, 0, 0, 0, 0, 0} }; static char* analyse_one_map(char *map_str, uint16 *source, uint16 *dest) { char *end_ptr; int number; DBUG_ENTER("analyse_one_map"); /* Search for pattern ( source_ng , dest_ng ) */ while (isspace(*map_str)) map_str++; if (*map_str != '(') { DBUG_RETURN(NULL); } map_str++; while (isspace(*map_str)) map_str++; number= strtol(map_str, &end_ptr, 10); if (!end_ptr || number < 0 || number >= MAX_NODE_GROUP_MAPS) { DBUG_RETURN(NULL); } *source= (uint16)number; map_str= end_ptr; while (isspace(*map_str)) map_str++; if (*map_str != ',') { DBUG_RETURN(NULL); } map_str++; number= strtol(map_str, &end_ptr, 10); if (!end_ptr || number < 0 || number >= NDB_UNDEF_NODEGROUP) { DBUG_RETURN(NULL); } *dest= (uint16)number; map_str= end_ptr; if (*map_str != ')') { DBUG_RETURN(NULL); } map_str++; while (isspace(*map_str)) map_str++; DBUG_RETURN(map_str); } static bool insert_ng_map(NODE_GROUP_MAP *ng_map, uint16 source_ng, uint16 dest_ng) { uint index= source_ng; uint ng_index= ng_map[index].no_maps; opt_nodegroup_map_len++; if (ng_index >= MAX_MAPS_PER_NODE_GROUP) return true; ng_map[index].no_maps++; ng_map[index].map_array[ng_index]= dest_ng; return false; } static void init_nodegroup_map() { uint i,j; NODE_GROUP_MAP *ng_map = &opt_nodegroup_map[0]; for (i = 0; i < MAX_NODE_GROUP_MAPS; i++) { ng_map[i].no_maps= 0; for (j= 0; j < MAX_MAPS_PER_NODE_GROUP; j++) ng_map[i].map_array[j]= NDB_UNDEF_NODEGROUP; } } static bool analyse_nodegroup_map(const char *ng_map_str, NODE_GROUP_MAP *ng_map) { uint16 source_ng, dest_ng; char *local_str= (char*)ng_map_str; DBUG_ENTER("analyse_nodegroup_map"); do { if (!local_str) { DBUG_RETURN(TRUE); } local_str= analyse_one_map(local_str, &source_ng, &dest_ng); if (!local_str) { DBUG_RETURN(TRUE); } if (insert_ng_map(ng_map, source_ng, dest_ng)) { DBUG_RETURN(TRUE); } if (!(*local_str)) break; } while (TRUE); DBUG_RETURN(FALSE); } static void short_usage_sub(void) { ndb_short_usage_sub("[]"); } static bool get_one_option(int optid, const struct my_option *opt MY_ATTRIBUTE((unused)), char *argument) { #ifndef DBUG_OFF opt_debug= "d:t:O,/tmp/ndb_restore.trace"; #endif ndb_std_get_one_option(optid, opt, argument); switch (optid) { case OPT_VERBOSE: info.setThreshold(255-opt_verbose); break; case 'n': if (ga_nodeId == 0) { err << "Error in --nodeid,-n setting, see --help"; exitHandler(NdbToolsProgramExitCode::WRONG_ARGS); } info.setLevel(254); info << "Nodeid = " << ga_nodeId << endl; break; case 'b': if (ga_backupId == 0) { err << "Error in --backupid,-b setting, see --help"; exitHandler(NdbToolsProgramExitCode::WRONG_ARGS); } info.setLevel(254); info << "Backup Id = " << ga_backupId << endl; break; case OPT_NDB_NODEGROUP_MAP: /* This option is used to set a map from nodegroup in original cluster to nodegroup in new cluster. */ opt_nodegroup_map_len= 0; info.setLevel(254); info << "Analyse node group map" << endl; if (analyse_nodegroup_map(opt_nodegroup_map_str, &opt_nodegroup_map[0])) { exitHandler(NdbToolsProgramExitCode::WRONG_ARGS); } break; case OPT_INCLUDE_DATABASES: case OPT_EXCLUDE_DATABASES: case OPT_INCLUDE_TABLES: case OPT_EXCLUDE_TABLES: save_include_exclude(optid, argument); break; case OPT_REWRITE_DATABASE: parse_rewrite_database(argument); break; } return 0; } static const char* SCHEMA_NAME="/def/"; static const int SCHEMA_NAME_SIZE= 5; int makeInternalTableName(const BaseString &externalName, BaseString& internalName) { // Make dbname.table1 into dbname/def/table1 Vector parts; // Must contain a dot if (externalName.indexOf('.') == -1) return -1; externalName.split(parts,"."); // .. and only 1 dot if (parts.size() != 2) return -1; internalName.clear(); internalName.append(parts[0]); // db name internalName.append(SCHEMA_NAME); // /def/ internalName.append(parts[1]); // table name return 0; } void processTableList(const char* str, Vector &lst) { // Process tables list like db1.t1,db2.t1 and exits when // it finds problems. Vector tmp; unsigned int i; /* Split passed string on comma into 2 BaseStrings in the vector */ BaseString(str).split(tmp,","); for (i=0; i < tmp.size(); i++) { BaseString internalName; if (makeInternalTableName(tmp[i], internalName)) { info << "`" << tmp[i] << "` is not a valid tablename!" << endl; exitHandler(NdbToolsProgramExitCode::WRONG_ARGS); } lst.push_back(internalName); } } BaseString makeExternalTableName(const BaseString &internalName) { // Make dbname/def/table1 into dbname.table1 BaseString externalName; ssize_t idx = internalName.indexOf('/'); externalName = internalName.substr(0,idx); externalName.append("."); externalName.append(internalName.substr(idx + SCHEMA_NAME_SIZE, internalName.length())); return externalName; } // Exclude the legacy list of six privilege tables from Cluster 7.x #include "storage/ndb/plugin/ndb_dist_priv_util.h" void exclude_privilege_tables() { const char* table_name; Ndb_dist_priv_util dist_priv; while((table_name= dist_priv.iter_next_table())) { BaseString priv_tab; priv_tab.assfmt("%s.%s", dist_priv.database(), table_name); g_exclude_tables.push_back(priv_tab); save_include_exclude(OPT_EXCLUDE_TABLES, (char *)priv_tab.c_str()); } } bool readArguments(Ndb_opts & opts, char*** pargv) { Uint32 i; BaseString tmp; debug << "Load defaults" << endl; init_nodegroup_map(); debug << "handle_options" << endl; opts.set_usage_funcs(short_usage_sub); if (opts.handle_options(get_one_option)) { exitHandler(NdbToolsProgramExitCode::WRONG_ARGS); } if (ga_nodeId == 0) { err << "Backup file node ID not specified, please provide --nodeid" << endl; exitHandler(NdbToolsProgramExitCode::WRONG_ARGS); } if (ga_backupId == 0) { err << "Backup ID not specified, please provide --backupid" << endl; exitHandler(NdbToolsProgramExitCode::WRONG_ARGS); } for (i = 0; i < MAX_NODE_GROUP_MAPS; i++) opt_nodegroup_map[i].curr_index = 0; #if 0 /* Test code written t{ o verify nodegroup mapping */ printf("Handled options successfully\n"); Uint16 map_ng[16]; Uint32 j; for (j = 0; j < 4; j++) { for (i = 0; i < 4 ; i++) map_ng[i] = i; map_nodegroups(&map_ng[0], (Uint32)4); for (i = 0; i < 4 ; i++) printf("NG %u mapped to %u \n", i, map_ng[i]); } for (j = 0; j < 4; j++) { for (i = 0; i < 8 ; i++) map_ng[i] = i >> 1; map_nodegroups(&map_ng[0], (Uint32)8); for (i = 0; i < 8 ; i++) printf("NG %u mapped to %u \n", i >> 1, map_ng[i]); } exit(NdbToolsProgramExitCode::WRONG_ARGS); #endif for (;;) { int i= 0; if (ga_backupPath == default_backupPath) { // Set backup file path if ((*pargv)[i] == NULL) break; ga_backupPath = (*pargv)[i++]; } if ((*pargv)[i] == NULL) break; g_databases.push_back((*pargv)[i++]); while ((*pargv)[i] != NULL) { g_tables.push_back((*pargv)[i++]); } break; } info.setLevel(254); info << "backup path = " << ga_backupPath << endl; if (g_databases.size() > 0) { info << "WARNING! Using deprecated syntax for selective object restoration." << endl; info << "Please use --include-*/--exclude-* options in future." << endl; info << "Restoring only from database " << g_databases[0].c_str() << endl; if (g_tables.size() > 0) { info << "Restoring tables:"; } for (unsigned i= 0; i < g_tables.size(); i++) { info << " " << g_tables[i].c_str(); } if (g_tables.size() > 0) info << endl; } if (ga_restore) { // Exclude privilege tables unless explicitely included if (!opt_restore_privilege_tables) exclude_privilege_tables(); // Move over old style arguments to include/exclude lists if (g_databases.size() > 0) { BaseString tab_prefix, tab; tab_prefix.append(g_databases[0].c_str()); tab_prefix.append("."); if (g_tables.size() == 0) { g_include_databases.push_back(g_databases[0]); save_include_exclude(OPT_INCLUDE_DATABASES, (char *)g_databases[0].c_str()); } for (unsigned i= 0; i < g_tables.size(); i++) { tab.assign(tab_prefix); tab.append(g_tables[i]); g_include_tables.push_back(tab); save_include_exclude(OPT_INCLUDE_TABLES, (char *)tab.c_str()); } } } if (opt_include_databases) { tmp = BaseString(opt_include_databases); tmp.split(g_include_databases,","); info << "Including Databases: "; for (i= 0; i < g_include_databases.size(); i++) { info << g_include_databases[i] << " "; } info << endl; } if (opt_exclude_databases) { tmp = BaseString(opt_exclude_databases); tmp.split(g_exclude_databases,","); info << "Excluding databases: "; for (i= 0; i < g_exclude_databases.size(); i++) { info << g_exclude_databases[i] << " "; } info << endl; } if (opt_rewrite_database) { info << "Rewriting databases:"; Properties::Iterator it(&g_rewrite_databases); const char * src; for (src = it.first(); src != NULL; src = it.next()) { const char * dst = NULL; bool r = g_rewrite_databases.get(src, &dst); require(r && (dst != NULL)); info << " (" << src << "->" << dst << ")"; } info << endl; } if (opt_include_tables) { processTableList(opt_include_tables, g_include_tables); info << "Including tables: "; for (i= 0; i < g_include_tables.size(); i++) { info << makeExternalTableName(g_include_tables[i]).c_str() << " "; } info << endl; } if (opt_exclude_tables) { processTableList(opt_exclude_tables, g_exclude_tables); info << "Excluding tables: "; for (i= 0; i < g_exclude_tables.size(); i++) { info << makeExternalTableName(g_exclude_tables[i]).c_str() << " "; } info << endl; } /* the below formatting follows the formatting from mysqldump do not change unless to adopt to changes in mysqldump */ g_ndbrecord_print_format.fields_enclosed_by= opt_fields_enclosed_by ? opt_fields_enclosed_by : ""; g_ndbrecord_print_format.fields_terminated_by= opt_fields_terminated_by ? opt_fields_terminated_by : "\t"; g_ndbrecord_print_format.fields_optionally_enclosed_by= opt_fields_optionally_enclosed_by ? opt_fields_optionally_enclosed_by : ""; g_ndbrecord_print_format.lines_terminated_by= opt_lines_terminated_by ? opt_lines_terminated_by : "\n"; if (g_ndbrecord_print_format.fields_optionally_enclosed_by[0] == '\0') g_ndbrecord_print_format.null_string= "\\N"; else g_ndbrecord_print_format.null_string= ""; g_ndbrecord_print_format.hex_prefix= ""; g_ndbrecord_print_format.hex_format= opt_hex_format; if (ga_skip_table_check) { g_tableCompabilityMask = ~(Uint32)0; ga_skip_unknown_objects = true; } if (ga_promote_attributes) { g_tableCompabilityMask |= TCM_ATTRIBUTE_PROMOTION; } if (ga_demote_attributes) { g_tableCompabilityMask |= TCM_ATTRIBUTE_DEMOTION; } if (ga_exclude_missing_columns) { g_tableCompabilityMask |= TCM_EXCLUDE_MISSING_COLUMNS; } return true; } bool create_consumers(RestoreThreadData *data) { BackupPrinter *printer = new BackupPrinter(opt_nodegroup_map, opt_nodegroup_map_len); if (printer == NULL) return false; if (g_restoring_in_parallel && (ga_nParallelism > ga_part_count)) ga_nParallelism /= ga_part_count; char threadname[20]; BaseString::snprintf(threadname, sizeof(threadname), "%d-%u", ga_nodeId, data->m_part_id); BackupRestore* restore = new BackupRestore(g_cluster_connection, opt_nodegroup_map, opt_nodegroup_map_len, threadname, ga_nParallelism); if (restore == NULL) { delete printer; printer = NULL; return false; } if (_print) { ga_print = true; ga_restore = true; printer->m_print = true; } if (_print_meta) { ga_print = true; printer->m_print_meta = true; } if (_print_data) { ga_print = true; printer->m_print_data = true; } if (_print_log) { ga_print = true; printer->m_print_log = true; } if (_print_sql_log) { ga_print = true; printer->m_print_sql_log = true; } if (_restore_data) { ga_restore = true; restore->m_restore = true; } if (_restore_meta) { // ndb_restore has been requested to perform some metadata work // like restore-meta or disable-indexes. To avoid 'object already exists' // errors, only restore-thread 1 will do the actual metadata-restore work. // So flags like restore_meta, restore_epoch and disable_indexes are set // only on thread 1 to indicate that it must perform this restore work. // While restoring metadata, some init work is done, like creating an Ndb // object, setting up callbacks, and loading info about all the tables into // the BackupConsumer. // The remaining threads also need this init work to be done, since later // phases of ndb_restore rely upon it, e.g. --restore-data needs the table // info. So an additional flag m_metadata_work_requested is set for all // the restore-threads to indicate that the init work must be done. If // m_metadata_work_requested = 1 and m_restore_meta = 0, the thread will // do only the init work, and skip the ndbapi function calls to create or // delete the metadata objects. restore->m_metadata_work_requested = true; if (data->m_part_id == 1) { // restore-thread 1 must perform actual work of restoring metadata restore->m_restore_meta = true; // use thread-specific flag saved in RestoreThreadData to determine // whether the thread should restore metadata objects data->m_restore_meta = true; } if(ga_exclude_missing_tables) { //conflict in options err << "Conflicting arguments found : " << "Cannot use `restore-meta` and " << "`exclude-missing-tables` together. Exiting..." << endl; return false; } } if (_no_restore_disk) { restore->m_no_restore_disk = true; } if (ga_no_upgrade) { restore->m_no_upgrade = true; } if (_preserve_trailing_spaces) { restore->m_preserve_trailing_spaces = true; } if (ga_restore_epoch) { restore->m_restore_epoch_requested = true; if (data->m_part_id == 1) restore->m_restore_epoch = true; } if (ga_disable_indexes) { restore->m_metadata_work_requested = true; if (data->m_part_id == 1) restore->m_disable_indexes = true; } if (ga_rebuild_indexes) { restore->m_metadata_work_requested = true; if (data->m_part_id == 1) restore->m_rebuild_indexes = true; } { BackupConsumer * c = printer; data->m_consumers.push_back(c); } { BackupConsumer * c = restore; data->m_consumers.push_back(c); } return true; } void clear_consumers(RestoreThreadData *data) { for(Uint32 i= 0; im_consumers.size(); i++) delete data->m_consumers[i]; data->m_consumers.clear(); } static inline bool checkSysTable(const TableS* table) { return ! table->getSysTable(); } static inline bool checkSysTable(const RestoreMetaData& metaData, uint i) { assert(i < metaData.getNoOfTables()); return checkSysTable(metaData[i]); } static inline bool isBlobTable(const TableS* table) { return table->getMainTable() != NULL; } static inline bool isIndex(const TableS* table) { const NdbTableImpl & tmptab = NdbTableImpl::getImpl(* table->m_dictTable); return (int) tmptab.m_indexType != (int) NdbDictionary::Index::Undefined; } static inline bool isSYSTAB_0(const TableS* table) { return table->isSYSTAB_0(); } const char* getTableName(const TableS* table) { const char *table_name; if (isBlobTable(table)) table_name= table->getMainTable()->getTableName(); else if (isIndex(table)) table_name= NdbTableImpl::getImpl(*table->m_dictTable).m_primaryTable.c_str(); else table_name= table->getTableName(); return table_name; } static void parse_rewrite_database(char * argument) { const BaseString arg(argument); Vector args; unsigned int n = arg.split(args, ","); if ((n == 2) && (args[0].length() > 0) && (args[1].length() > 0)) { const BaseString src = args[0]; const BaseString dst = args[1]; const bool replace = true; bool r = g_rewrite_databases.put(src.c_str(), dst.c_str(), replace); require(r); return; // ok } info << "argument `" << arg.c_str() << "` is not a pair 'a,b' of non-empty names." << endl; exitHandler(NdbToolsProgramExitCode::WRONG_ARGS); } static void save_include_exclude(int optid, char * argument) { BaseString arg = argument; Vector args; arg.split(args, ","); for (uint i = 0; i < args.size(); i++) { RestoreOption * option = new RestoreOption(); BaseString arg; option->optid = optid; switch (optid) { case OPT_INCLUDE_TABLES: case OPT_EXCLUDE_TABLES: if (makeInternalTableName(args[i], arg)) { info << "`" << args[i] << "` is not a valid tablename!" << endl; exitHandler(NdbToolsProgramExitCode::WRONG_ARGS); } break; default: arg = args[i]; break; } option->argument = arg; g_include_exclude.push_back(option); } } static bool check_include_exclude(BaseString database, BaseString table) { const char * db = database.c_str(); const char * tbl = table.c_str(); bool do_include = true; if (g_include_databases.size() != 0 || g_include_tables.size() != 0) { /* User has explicitly specified what databases and/or tables should be restored. If no match is found then DON'T restore table. */ do_include = false; } if (do_include && (g_exclude_databases.size() != 0 || g_exclude_tables.size() != 0)) { /* User has not explicitly specified what databases and/or tables should be restored. User has explicitly specified what databases and/or tables should NOT be restored. If no match is found then DO restore table. */ do_include = true; } if (g_include_exclude.size() != 0) { /* Scan include exclude arguments in reverse. First matching include causes table to be restored. first matching exclude causes table NOT to be restored. */ for(uint i = g_include_exclude.size(); i > 0; i--) { RestoreOption *option = g_include_exclude[i-1]; switch (option->optid) { case OPT_INCLUDE_TABLES: if (strcmp(tbl, option->argument.c_str()) == 0) return true; // do include break; case OPT_EXCLUDE_TABLES: if (strcmp(tbl, option->argument.c_str()) == 0) return false; // don't include break; case OPT_INCLUDE_DATABASES: if (strcmp(db, option->argument.c_str()) == 0) return true; // do include break; case OPT_EXCLUDE_DATABASES: if (strcmp(db, option->argument.c_str()) == 0) return false; // don't include break; default: continue; } } } return do_include; } static bool check_intermediate_sql_table(const char *table_name) { BaseString tbl(table_name); Vector fields; tbl.split(fields, "/"); if((fields.size() == 3) && !fields[2].empty() && strncmp(fields[2].c_str(), TMP_TABLE_PREFIX, TMP_TABLE_PREFIX_LEN) == 0) return true; return false; } static inline bool checkDoRestore(const TableS* table) { bool ret = true; BaseString db, tbl; tbl.assign(getTableName(table)); ssize_t idx = tbl.indexOf('/'); db = tbl.substr(0, idx); /* Include/exclude flags are evaluated right to left, and first match overrides any other matches. Non-overlapping arguments are accumulative. If no include flags are specified this means all databases/tables except any excluded are restored. If include flags are specified than only those databases or tables specified are restored. */ ret = check_include_exclude(db, tbl); return ret; } static inline bool checkDbAndTableName(const TableS* table) { if (table->isBroken()) return false; const char *table_name = getTableName(table); if(opt_exclude_intermediate_sql_tables && (check_intermediate_sql_table(table_name) == true)) { return false; } // If --exclude lists are given, check them if ((g_exclude_tables.size() || opt_exclude_databases) && ! checkDoRestore(table)) return false; // If --include lists are given, ignore the old-style table list if (opt_include_tables || opt_include_databases ) { return (checkDoRestore(table)); } if (g_tables.size() == 0 && g_databases.size() == 0) return true; if (g_databases.size() == 0) g_databases.push_back("TEST_DB"); // Filter on the main table name for indexes and blobs unsigned i; for (i= 0; i < g_databases.size(); i++) { if (strncmp(table_name, g_databases[i].c_str(), g_databases[i].length()) == 0 && table_name[g_databases[i].length()] == '/') { // we have a match if (g_databases.size() > 1 || g_tables.size() == 0) return true; break; } } if (i == g_databases.size()) return false; // no match found while (*table_name != '/') table_name++; table_name++; while (*table_name != '/') table_name++; table_name++; // Check if table should be restored for (i= 0; i < g_tables.size(); i++) { if (strcmp(table_name, g_tables[i].c_str()) == 0) return true; } return false; } static void exclude_missing_tables(const RestoreMetaData& metaData, const Vector g_consumers) { Uint32 i, j; bool isMissing; Vector missingTables; for(i = 0; i < metaData.getNoOfTables(); i++) { const TableS *table= metaData[i]; isMissing = false; for(j = 0; j < g_consumers.size(); j++) isMissing |= g_consumers[j]->isMissingTable(*table); if( isMissing ) { /* add missing tables to exclude list */ g_exclude_tables.push_back(table->getTableName()); BaseString tableName = makeExternalTableName(table->getTableName()); save_include_exclude(OPT_EXCLUDE_TABLES, (char*)tableName.c_str()); missingTables.push_back(tableName); } } if(missingTables.size() > 0){ info << "Excluded Missing tables: "; for (i=0 ; i < missingTables.size(); i++) { info << missingTables[i] << " "; } info << endl; } } static void free_data_callback(void *ctx) { // RestoreThreadData is passed as context object to in RestoreDataIterator // ctor. RestoreDataIterator calls callback function with context object // as parameter, so that callback can extract thread info from it. RestoreThreadData *data = (RestoreThreadData*)ctx; for(Uint32 i= 0; i < data->m_consumers.size(); i++) data->m_consumers[i]->tuple_free(); } static void free_include_excludes_vector() { for (unsigned i = 0; i < g_include_exclude.size(); i++) { delete g_include_exclude[i]; } g_include_exclude.clear(); } static void exitHandler(int code) { free_include_excludes_vector(); if (opt_core) abort(); else exit(code); } static void init_restore() { if (_restore_meta || _restore_data || ga_restore_epoch || ga_disable_indexes || ga_rebuild_indexes) { // create one Ndb_cluster_connection to be shared by all threads g_cluster_connection = new Ndb_cluster_connection(opt_ndb_connectstring, opt_ndb_nodeid); if (g_cluster_connection == NULL) { err << "Failed to create cluster connection!!" << endl; exitHandler(NdbToolsProgramExitCode::FAILED); } g_cluster_connection->set_name(g_options.c_str()); if (g_cluster_connection->connect(opt_connect_retries - 1, opt_connect_retry_delay, 1) != 0) { delete g_cluster_connection; g_cluster_connection = NULL; exitHandler(NdbToolsProgramExitCode::FAILED); } } } static void cleanup_restore() { delete g_cluster_connection; g_cluster_connection = 0; free_include_excludes_vector(); } static void init_progress() { g_report_prev = NdbTick_getCurrentTicks(); } static int check_progress() { if (!opt_progress_frequency) return 0; const NDB_TICKS now = NdbTick_getCurrentTicks(); if (NdbTick_Elapsed(g_report_prev, now).seconds() >= opt_progress_frequency) { g_report_prev = now; return 1; } return 0; } static void report_progress(const char *prefix, const BackupFile &f) { info.setLevel(255); if (f.get_file_size()) restoreLogger.log_info("%s %llupercent(%llu bytes)\n", prefix, (f.get_file_pos() * 100 + f.get_file_size()-1) / f.get_file_size(), f.get_file_pos()); else restoreLogger.log_info("%s %llu bytes\n", prefix, f.get_file_pos()); } /** * Reports, clears information on columns where data truncation was detected. */ static void check_data_truncations(const TableS * table) { assert(table); const char * tname = table->getTableName(); const int n = table->getNoOfAttributes(); for (int i = 0; i < n; i++) { AttributeDesc * desc = table->getAttributeDesc(i); if (desc->truncation_detected) { const char * cname = desc->m_column->getName(); info.setLevel(254); restoreLogger.log_info("Data truncation(s) detected for attribute: %s.%s", tname, cname); desc->truncation_detected = false; } } } int do_restore(RestoreThreadData *thrdata) { init_progress(); char timestamp[64]; Vector &g_consumers = thrdata->m_consumers; char threadName[15] = ""; if (opt_show_part_id) BaseString::snprintf(threadName, sizeof(threadName), "[part %u] ", thrdata->m_part_id); restoreLogger.setThreadPrefix(threadName); /** * we must always load meta data, even if we will only print it to stdout */ restoreLogger.log_debug("Start restoring meta data"); RestoreMetaData metaData(ga_backupPath, ga_nodeId, ga_backupId, thrdata->m_part_id, ga_part_count); #ifdef ERROR_INSERT if(_error_insert > 0) { metaData.error_insert(_error_insert); } #endif Logger::format_timestamp(time(NULL), timestamp, sizeof(timestamp)); restoreLogger.log_info("%s [restore_metadata] Read meta data file header", timestamp); if (!metaData.readHeader()) { restoreLogger.log_error("Failed to read %s", metaData.getFilename()); return NdbToolsProgramExitCode::FAILED; } const BackupFormat::FileHeader & tmp = metaData.getFileHeader(); const Uint32 version = tmp.BackupVersion; char buf[NDB_VERSION_STRING_BUF_SZ]; char new_buf[NDB_VERSION_STRING_BUF_SZ]; info.setLevel(254); if (version >= NDBD_RAW_LCP) { restoreLogger.log_info("Backup version in files: %s ndb version: %s", ndbGetVersionString(version, 0, ndbd_drop6(version) ? "-drop6" : 0, buf, sizeof(buf)), ndbGetVersionString(tmp.NdbVersion, tmp.MySQLVersion, 0, buf, sizeof(buf))); } else { restoreLogger.log_info("Backup version in files: %s", ndbGetVersionString(version, 0, ndbd_drop6(version) ? "-drop6" : 0, buf, sizeof(buf))); } /** * check wheater we can restore the backup (right version). */ // in these versions there was an error in how replica info was // stored on disk if (version >= MAKE_VERSION(5,1,3) && version <= MAKE_VERSION(5,1,9)) { restoreLogger.log_error("Restore program incompatible with backup versions between %s and %s" ,ndbGetVersionString(MAKE_VERSION(5,1,3), 0, 0, buf, sizeof(buf)) ,ndbGetVersionString(MAKE_VERSION(5,1,9), 0, 0, new_buf, sizeof(new_buf)) ); return NdbToolsProgramExitCode::FAILED; } if (version > NDB_VERSION) { restoreLogger.log_error("Restore program older than backup version. Not supported. Use new restore program"); return NdbToolsProgramExitCode::FAILED; } restoreLogger.log_debug("Load content"); Logger::format_timestamp(time(NULL), timestamp, sizeof(timestamp)); restoreLogger.log_info("%s [restore_metadata] Load content", timestamp); int res = metaData.loadContent(); restoreLogger.log_info("Stop GCP of Backup: %u", metaData.getStopGCP()); restoreLogger.log_info("Start GCP of Backup: %u", metaData.getStartGCP()); if (res == 0) { restoreLogger.log_error("Restore: Failed to load content"); return NdbToolsProgramExitCode::FAILED; } restoreLogger.log_debug("Get number of Tables"); Logger::format_timestamp(time(NULL), timestamp, sizeof(timestamp)); restoreLogger.log_info("%s [restore_metadata] Get number of Tables", timestamp); if (metaData.getNoOfTables() == 0) { restoreLogger.log_error("The backup contains no tables"); return NdbToolsProgramExitCode::FAILED; } if (_print_sql_log && _print_log) { restoreLogger.log_debug("Check to ensure that both print-sql-log and print-log options are not passed"); restoreLogger.log_error("Both print-sql-log and print-log options passed. Exiting..."); return NdbToolsProgramExitCode::FAILED; } if (_print_sql_log) { restoreLogger.log_debug("Check for tables with hidden PKs or column of type blob when print-sql-log option is passed"); for(Uint32 i = 0; i < metaData.getNoOfTables(); i++) { const TableS *table = metaData[i]; if (!(checkSysTable(table) && checkDbAndTableName(table))) continue; /* Blobs are stored as separate tables with names prefixed * with NDB$BLOB. This can be used to check if there are * any columns of type blob in the tables being restored */ BaseString tableName(table->getTableName()); Vector tableNameParts; tableName.split(tableNameParts, "/"); if (tableNameParts[2].starts_with("NDB$BLOB")) { restoreLogger.log_error("Found column of type blob with print-sql-log option set. Exiting..." ); return NdbToolsProgramExitCode::FAILED; } /* Hidden PKs are stored with the name $PK */ int noOfPK = table->m_dictTable->getNoOfPrimaryKeys(); for (int j = 0; j < noOfPK; j++) { const char* pkName = table->m_dictTable->getPrimaryKey(j); if (strcmp(pkName, "$PK") == 0) { restoreLogger.log_error("Found hidden primary key with print-sql-log option set. Exiting..."); return NdbToolsProgramExitCode::FAILED; } } } } restoreLogger.log_debug("Validate Footer"); Logger::format_timestamp(time(NULL), timestamp, sizeof(timestamp)); restoreLogger.log_info("%s [restore_metadata] Validate Footer", timestamp); if (!metaData.validateFooter()) { restoreLogger.log_error("Restore: Failed to validate footer."); return NdbToolsProgramExitCode::FAILED; } restoreLogger.log_debug("Init Backup objects"); Uint32 i; for (i = 0; i < g_consumers.size(); i++) { if (!g_consumers[i]->init(g_tableCompabilityMask)) { restoreLogger.log_error("Failed to initialize consumers"); return NdbToolsProgramExitCode::FAILED; } } if(ga_exclude_missing_tables) exclude_missing_tables(metaData, thrdata->m_consumers); /* report to clusterlog if applicable */ for (i = 0; i < g_consumers.size(); i++) g_consumers[i]->report_started(ga_backupId, ga_nodeId); /* before syncing on m_barrier, check if any threads have already exited */ if (ga_error_thread > 0) { return NdbToolsProgramExitCode::FAILED; } if (!thrdata->m_restore_meta) { /** * Only thread 1 is allowed to restore metadata objects. restore_meta * flag is set to true on thread 1, which causes consumer-restore to * actually restore the metadata objects, * e.g. g_consumer->object(tablespace) restores the tablespace * * Remaining threads have restore_meta = false, which causes * consumer-restore to query metadata objects and save metadata for * reference by later phases of restore * e.g. g_consumer->object(tablespace) queries+saves tablespace metadata * * So thread 1 must finish restoring all metadata objects before any other * thread is allowed to start metadata restore. Use CyclicBarrier to allow * all threads except thread-1 to arrive at barrier. Barrier will not be * opened until all threads arrive at it, so all threads will wait till * thread 1 arrives at barrier. When thread 1 completes metadata restore, * it arrives at barrier, opening barrier and allowing all threads to * proceed to next restore-phase. */ if (!thrdata->m_barrier->wait()) { ga_error_thread = thrdata->m_part_id; return NdbToolsProgramExitCode::FAILED; } } restoreLogger.log_debug("Restore objects (tablespaces, ..)"); Logger::format_timestamp(time(NULL), timestamp, sizeof(timestamp)); restoreLogger.log_info("%s [restore_metadata] Restore objects (tablespaces, ..)", timestamp); for (i = 0; i < metaData.getNoOfObjects(); i++) { for (Uint32 j = 0; j < g_consumers.size(); j++) if (!g_consumers[j]->object(metaData.getObjType(i), metaData.getObjPtr(i))) { restoreLogger.log_error( "Restore: Failed to restore table: %s ... Exiting", metaData[i]->getTableName()); return NdbToolsProgramExitCode::FAILED; } if (check_progress()) { info.setLevel(255); restoreLogger.log_info(" Object create progress: %u objects out of %u", i+1, metaData.getNoOfObjects()); } } Vector table_output(metaData.getNoOfTables()); restoreLogger.log_debug("Restoring tables"); Logger::format_timestamp(time(NULL), timestamp, sizeof(timestamp)); restoreLogger.log_info("%s [restore_metadata] Restoring tables", timestamp); for(i = 0; igetTableName(); while (*table_name != '/') table_name++; table_name++; while (*table_name != '/') table_name++; table_name++; convert_dirname(tmp_path, tab_path, NullS); res= my_fopen(fn_format(filename, table_name, tmp_path, ".txt", 4), opt_append ? O_WRONLY|O_APPEND|O_CREAT : O_WRONLY|O_TRUNC|O_CREAT, MYF(MY_WME)); if (res == 0) { return NdbToolsProgramExitCode::FAILED; } FileOutputStream *f= new FileOutputStream(res); table_output[i]= f; } for (Uint32 j = 0; j < g_consumers.size(); j++) { if (!g_consumers[j]->table(* table)) { restoreLogger.log_error( "Restore: Failed to restore table: `%s` ... Exiting ", table->getTableName()); return NdbToolsProgramExitCode::FAILED; } } } else { for (Uint32 j = 0; j < g_consumers.size(); j++) { if (!g_consumers[j]->createSystable(* table)) { restoreLogger.log_error( "Restore: Failed to restore system table: `%s` ... Exiting", table->getTableName()); return NdbToolsProgramExitCode::FAILED; } } } if (check_progress()) { info.setLevel(255); restoreLogger.log_info("Table create progress: %u tables out of %u", i+1, metaData.getNoOfTables()); } } restoreLogger.log_debug("Save foreign key info"); Logger::format_timestamp(time(NULL), timestamp, sizeof(timestamp)); restoreLogger.log_info("%s [restore_metadata] Save foreign key info", timestamp); for (i = 0; i < metaData.getNoOfObjects(); i++) { for (Uint32 j = 0; j < g_consumers.size(); j++) { if (!g_consumers[j]->fk(metaData.getObjType(i), metaData.getObjPtr(i))) { return NdbToolsProgramExitCode::FAILED; } } } restoreLogger.log_debug("Close tables" ); for (i = 0; i < g_consumers.size(); i++) { if (!g_consumers[i]->endOfTables()) { restoreLogger.log_error("Restore: Failed while closing tables"); return NdbToolsProgramExitCode::FAILED; } if (!ga_disable_indexes && !ga_rebuild_indexes) { if (!g_consumers[i]->endOfTablesFK()) { restoreLogger.log_error("Restore: Failed while closing tables FKs"); return NdbToolsProgramExitCode::FAILED; } } } /* before syncing on m_barrier, check if any threads have already exited */ if (ga_error_thread > 0) { return NdbToolsProgramExitCode::FAILED; } if (thrdata->m_restore_meta) { // thread 1 arrives at barrier -> barrier opens -> all threads continue if (!thrdata->m_barrier->wait()) { ga_error_thread = thrdata->m_part_id; return NdbToolsProgramExitCode::FAILED; } } /* report to clusterlog if applicable */ for(i= 0; i < g_consumers.size(); i++) { g_consumers[i]->report_meta_data(ga_backupId, ga_nodeId); } restoreLogger.log_debug("Iterate over data"); Logger::format_timestamp(time(NULL), timestamp, sizeof(timestamp)); restoreLogger.log_info("%s [restore_data] Start restoring table data", timestamp); if (ga_restore || ga_print) { if(_restore_data || _print_data) { // Check table compatibility for (i=0; i < metaData.getNoOfTables(); i++){ if (checkSysTable(metaData, i) && checkDbAndTableName(metaData[i])) { TableS & tableS = *metaData[i]; // not const for(Uint32 j = 0; j < g_consumers.size(); j++) { if (!g_consumers[j]->table_compatible_check(tableS)) { restoreLogger.log_error("Restore: Failed to restore data, %s table structure incompatible with backup's ... Exiting ", tableS.getTableName()); return NdbToolsProgramExitCode::FAILED; } if (tableS.m_staging && !g_consumers[j]->prepare_staging(tableS)) { restoreLogger.log_error("Restore: Failed to restore data, %s failed to prepare staging table for data conversion ... Exiting", tableS.getTableName()); return NdbToolsProgramExitCode::FAILED; } } } } for (i=0; i < metaData.getNoOfTables(); i++) { if (checkSysTable(metaData, i) && checkDbAndTableName(metaData[i])) { // blob table checks use data which is populated by table compatibility checks TableS & tableS = *metaData[i]; if (isBlobTable(&tableS)) { for (Uint32 j = 0; j < g_consumers.size(); j++) { if (!g_consumers[j]->check_blobs(tableS)) { restoreLogger.log_error( "Restore: Failed to restore data, " "%s table's blobs incompatible with backup's ... Exiting ", tableS.getTableName()); return NdbToolsProgramExitCode::FAILED; } } } } } RestoreDataIterator dataIter(metaData, &free_data_callback, (void*)thrdata); if (!dataIter.validateBackupFile()) { restoreLogger.log_error( "Unable to allocate memory for BackupFile constructor"); return NdbToolsProgramExitCode::FAILED; } if (!dataIter.validateRestoreDataIterator()) { restoreLogger.log_error("Unable to allocate memory for RestoreDataIterator constructor"); return NdbToolsProgramExitCode::FAILED; } Logger::format_timestamp(time(NULL), timestamp, sizeof(timestamp)); restoreLogger.log_info("%s [restore_data] Read data file header", timestamp); // Read data file header if (!dataIter.readHeader()) { restoreLogger.log_error( "Failed to read header of data file. Exiting..."); return NdbToolsProgramExitCode::FAILED; } Logger::format_timestamp(time(NULL), timestamp, sizeof(timestamp)); restoreLogger.log_info("%s [restore_data] Restore fragments", timestamp); Uint32 fragmentId; while (dataIter.readFragmentHeader(res= 0, &fragmentId)) { const TupleS* tuple; while ((tuple = dataIter.getNextTuple(res= 1)) != 0) { const TableS* table = tuple->getTable(); OutputStream *output = table_output[table->getLocalId()]; if (!output) continue; OutputStream *tmp = ndbout.m_out; ndbout.m_out = output; for(Uint32 j= 0; j < g_consumers.size(); j++) g_consumers[j]->tuple(* tuple, fragmentId); ndbout.m_out = tmp; if (check_progress()) report_progress("Data file progress: ", dataIter); } // while (tuple != NULL); if (res < 0) { restoreLogger.log_error( "Restore: An error occurred while restoring data. Exiting..."); return NdbToolsProgramExitCode::FAILED; } if (!dataIter.validateFragmentFooter()) { restoreLogger.log_error( "Restore: Error validating fragment footer. Exiting..."); return NdbToolsProgramExitCode::FAILED; } } // while (dataIter.readFragmentHeader(res)) if (res < 0) { restoreLogger.log_error( "Restore: An error occurred while restoring data." "Exiting... res = %u", res); return NdbToolsProgramExitCode::FAILED; } dataIter.validateFooter(); //not implemented for (i= 0; i < g_consumers.size(); i++) g_consumers[i]->endOfTuples(); /* report to clusterlog if applicable */ for(i= 0; i < g_consumers.size(); i++) { g_consumers[i]->report_data(ga_backupId, ga_nodeId); } } if(_restore_data || _print_log || _print_sql_log) { RestoreLogIterator logIter(metaData); Logger::format_timestamp(time(NULL), timestamp, sizeof(timestamp)); restoreLogger.log_info("%s [restore_log] Read log file header", timestamp); if (!logIter.readHeader()) { restoreLogger.log_error( "Failed to read header of data file. Exiting..."); return NdbToolsProgramExitCode::FAILED; } const LogEntry * logEntry = 0; restoreLogger.log_info("%s [restore_log] Restore log entries", timestamp); while ((logEntry = logIter.getNextLogEntry(res= 0)) != 0) { const TableS* table = logEntry->m_table; OutputStream *output = table_output[table->getLocalId()]; if (!output) continue; for(Uint32 j= 0; j < g_consumers.size(); j++) g_consumers[j]->logEntry(* logEntry); if (check_progress()) report_progress("Log file progress: ", logIter); } if (res < 0) { restoreLogger.log_error( "Restore: An restoring the data log. Exiting... res = %u", res); return NdbToolsProgramExitCode::FAILED; } logIter.validateFooter(); //not implemented for (i= 0; i < g_consumers.size(); i++) g_consumers[i]->endOfLogEntrys(); /* report to clusterlog if applicable */ for(i= 0; i < g_consumers.size(); i++) { g_consumers[i]->report_log(ga_backupId, ga_nodeId); } } /* move data from staging table to real table */ if(_restore_data) { for (i = 0; i < metaData.getNoOfTables(); i++) { const TableS* table = metaData[i]; if (table->m_staging) { for (Uint32 j = 0; j < g_consumers.size(); j++) { if (!g_consumers[j]->finalize_staging(*table)) { restoreLogger.log_error( "Restore: Failed staging data to table: %s. Exiting...", table->getTableName()); return NdbToolsProgramExitCode::FAILED; } } } } } if(_restore_data) { for(i = 0; i < metaData.getNoOfTables(); i++) { const TableS* table = metaData[i]; check_data_truncations(table); OutputStream *output = table_output[table->getLocalId()]; if (!output) { continue; } for (Uint32 j = 0; j < g_consumers.size(); j++) { if (!g_consumers[j]->finalize_table(*table)) { restoreLogger.log_error( "Restore: Failed to finalize restore table: %s. Exiting...", metaData[i]->getTableName()); return NdbToolsProgramExitCode::FAILED; } } } } } if (ga_error_thread > 0) { restoreLogger.log_error("Thread %u exits on error", thrdata->m_part_id); // thread 1 failed to restore metadata, exiting return NdbToolsProgramExitCode::FAILED; } if (ga_restore_epoch) { Logger::format_timestamp(time(NULL), timestamp, sizeof(timestamp)); restoreLogger.log_info("%s [restore_epoch] Restoring epoch", timestamp); RestoreLogIterator logIter(metaData); if (!logIter.readHeader()) { err << "Failed to read snapshot info from log file. Exiting..." << endl; return NdbToolsProgramExitCode::FAILED; } bool snapshotstart = logIter.isSnapshotstartBackup(); for (i= 0; i < g_consumers.size(); i++) if (!g_consumers[i]->update_apply_status(metaData, snapshotstart)) { restoreLogger.log_error("Restore: Failed to restore epoch"); return NdbToolsProgramExitCode::FAILED; } } if (ga_error_thread > 0) { restoreLogger.log_error("Thread %u exits on error", thrdata->m_part_id); // thread 1 failed to restore metadata, exiting return NdbToolsProgramExitCode::FAILED; } unsigned j; for(j= 0; j < g_consumers.size(); j++) { if (g_consumers[j]->has_temp_error()) { ndbout_c("\nRestore successful, but encountered temporary error, " "please look at configuration."); } } if (ga_rebuild_indexes) { /** * Index rebuild should not be allowed to start until all threads have * finished restoring data. Wait until all threads have arrived at * barrier, then allow all threads to continue. Thread 1 will then rebuild * indices, while all other threads do nothing. */ if (!thrdata->m_barrier->wait()) { ga_error_thread = thrdata->m_part_id; return NdbToolsProgramExitCode::FAILED; } restoreLogger.log_debug("Rebuilding indexes"); Logger::format_timestamp(time(NULL), timestamp, sizeof(timestamp)); restoreLogger.log_info("%s [rebuild_indexes] Rebuilding indexes", timestamp); for(i = 0; irebuild_indexes(* table)) { return NdbToolsProgramExitCode::FAILED; } } } for (Uint32 j = 0; j < g_consumers.size(); j++) { if (!g_consumers[j]->endOfTablesFK()) { return NdbToolsProgramExitCode::FAILED; } } } if (ga_error_thread > 0) { restoreLogger.log_error("Thread %u exits on error", thrdata->m_part_id); // thread 1 failed to restore metadata, exiting return NdbToolsProgramExitCode::FAILED; } /* report to clusterlog if applicable */ for (i = 0; i < g_consumers.size(); i++) g_consumers[i]->report_completed(ga_backupId, ga_nodeId); for(i = 0; i < metaData.getNoOfTables(); i++) { if (table_output[i] && table_output[i] != ndbout.m_out) { my_fclose(((FileOutputStream *)table_output[i])->getFile(), MYF(MY_WME)); delete table_output[i]; table_output[i] = NULL; } } return NdbToolsProgramExitCode::OK; } // do_restore /* Detects the backup type (single part or multiple parts) by locating * the ctl file. It sets the backup format as BF_SINGLE/BF_MULTI_PART * for future file-handling. Also counts the parts to be restored. */ int detect_backup_format() { // construct name of ctl file char name[PATH_MAX]; const Uint32 sz = sizeof(name); BaseString::snprintf(name, sz, "%s%sBACKUP-%u.%d.ctl", ga_backupPath, DIR_SEPARATOR, ga_backupId, ga_nodeId); MY_STAT buf; if(my_stat(name, &buf, 0)) { // for single part, backup path leads directly to ctl file // File-handlers search for the files in // BACKUP_PATH/BACKUP-/ // E.g. /usr/local/mysql/datadir/BACKUP/BACKUP-1 ga_backup_format = BF_SINGLE; ga_part_count = 1; } else { // for multiple parts, backup patch has subdirectories which // contain ctl files // file-handlers search for files in // BACKUP_PATH/BACKUP-/BACKUP-./ // E.g. /usr/local/mysql/datadir/BACKUP/BACKUP-1/BACKUP-1.2/ ga_backup_format = BF_MULTI_PART; // count number of backup parts in multi-set backup for(ga_part_count = 1; ga_part_count <= g_max_parts; ga_part_count++) { // backup parts are named as BACKUP--PART--OF- // E.g. Part 2 of backup 3 which has 4 parts will be in the path // BACKUP-3/BACKUP-3-PART-2-OF-4/ // Try out different values of for PART-1 until correct total found // E.g. for total = 4, // BACKUP-1-PART-1-OF-1 : not found, continue // BACKUP-1-PART-1-OF-2 : not found, continue // BACKUP-1-PART-1-OF-3 : not found, continue // BACKUP-1-PART-1-OF-4 : FOUND, set ga_part_count and break BaseString::snprintf(name, sz, "%s%sBACKUP-%d-PART-1-OF-%u%sBACKUP-%u.%d.ctl", ga_backupPath, DIR_SEPARATOR, ga_backupId, ga_part_count, DIR_SEPARATOR, ga_backupId, ga_nodeId); if (my_stat(name, &buf, 0)) { info << "Found backup " << ga_backupId << " with " << ga_part_count << " backup parts" << endl; break; // part found, end of parts } if (ga_part_count == g_max_parts) { err << "Failed to find backup " << ga_backupId << " in path " << ga_backupPath << endl; return NdbToolsProgramExitCode::FAILED; // too many parts } } } return NdbToolsProgramExitCode::OK; } // detect_backup_format static void* start_restore_worker(void *data) { RestoreThreadData *rdata = (RestoreThreadData*)data; rdata->m_result = do_restore(rdata); if (rdata->m_result == NdbToolsProgramExitCode::FAILED) { info << "Thread " << rdata->m_part_id << " failed, exiting" << endl; ga_error_thread = rdata->m_part_id; } return 0; } int main(int argc, char** argv) { NDB_INIT(argv[0]); const char *load_default_groups[]= { "mysql_cluster","ndb_restore",0 }; Ndb_opts opts(argc, argv, my_long_options, load_default_groups); if (!readArguments(opts, &argv)) { exitHandler(NdbToolsProgramExitCode::FAILED); } g_options.appfmt(" -b %u", ga_backupId); g_options.appfmt(" -n %d", ga_nodeId); if (_restore_meta) g_options.appfmt(" -m"); if (ga_no_upgrade) g_options.appfmt(" -u"); if (ga_promote_attributes) g_options.appfmt(" -A"); if (ga_demote_attributes) g_options.appfmt(" -L"); if (_preserve_trailing_spaces) g_options.appfmt(" -P"); if (ga_skip_table_check) g_options.appfmt(" -s"); if (_restore_data) g_options.appfmt(" -r"); if (ga_restore_epoch) g_options.appfmt(" -e"); if (_no_restore_disk) g_options.appfmt(" -d"); if (ga_exclude_missing_columns) g_options.append(" --exclude-missing-columns"); if (ga_exclude_missing_tables) g_options.append(" --exclude-missing-tables"); if (ga_disable_indexes) g_options.append(" --disable-indexes"); if (ga_rebuild_indexes) g_options.append(" --rebuild-indexes"); g_options.appfmt(" -p %d", ga_nParallelism); if (ga_skip_unknown_objects) g_options.append(" --skip-unknown-objects"); if (ga_skip_broken_objects) g_options.append(" --skip-broken-objects"); // determine backup format: simple or multi-part, and count parts int result = detect_backup_format(); if (result != NdbToolsProgramExitCode::OK) { exitHandler(result); } init_restore(); g_restoring_in_parallel = true; // check if single-threaded restore is necessary if (_print || _print_meta || _print_data || _print_log || _print_sql_log || ga_backup_format == BF_SINGLE) { g_restoring_in_parallel = false; for (int i=1; i<=ga_part_count; i++) { /* * do_restore uses its parameter 'partId' to select the backup part. * Each restore thread is started with a unique part ID. * E.g. while restoring BACKUP-2, * restore-thread 1 restores BACKUP-2/BACKUP-2-PART-1-OF-4, * restore-thread 3 restores BACKUP-2/BACKUP-2-PART-3-OF-4 * and so on. * do_restore uses the backup format and partId to locate backup files. * The tid and backup type are passed to the file-handlers: * - RestoreMetadata: finds ctl file * - RestoreDataIterator: finds data file * - RestoreLogIterator: finds log file * * For BF_SINGLE, the file-handlers search for the files in * * BACKUP_PATH/BACKUP-/ * E.g. /usr/local/mysql/datadir/BACKUP/BACKUP-1 * * For BF_MULTI_PART, the file-handlers search in * * BACKUP_PATH/BACKUP-/BACKUP--PART- * -OF-/ * E.g. /usr/local/mysql/datadir/BACKUP/BACKUP-1/BACKUP-1-PART-2-OF-4/ */ CyclicBarrier barrier(1); RestoreThreadData thrdata(i, &barrier); if (!create_consumers(&thrdata)) { info << "Failed to init restore thread for BACKUP-" << ga_backupId << "-PART-" << i << "-OF-" << ga_part_count << endl; ga_error_thread = i; break; } if (do_restore(&thrdata) == NdbToolsProgramExitCode::FAILED) { if (ga_backup_format == BF_SINGLE) { info << "Failed to restore BACKUP-" << ga_backupId << endl; } else { info << "Failed to restore BACKUP-" << ga_backupId << "-PART-" << i << "-OF-" << ga_part_count << endl; } ga_error_thread = i; clear_consumers(&thrdata); break; } clear_consumers(&thrdata); } } else { // create one restore thread per backup part Vector thrdata; CyclicBarrier barrier(ga_part_count); for (int part_id=1; part_id<=ga_part_count; part_id++) { NDB_THREAD_PRIO prio = NDB_THREAD_PRIO_MEAN; uint stack_size = 64*1024; char name[20]; snprintf (name, sizeof(name), "restore%d", part_id); RestoreThreadData *data = new RestoreThreadData(part_id, &barrier); if (!create_consumers(data)) { info << "Failed to init restore thread for BACKUP-" << ga_backupId << "-PART-" << part_id << "-OF-" << ga_part_count << endl; ga_error_thread = part_id; break; } NdbThread *thread = NdbThread_Create (start_restore_worker, (void**)(data), stack_size, name, prio); if(!thread) { info << "Failed to start restore thread for BACKUP-" << ga_backupId << "-PART-" << part_id << "-OF-" << ga_part_count << endl; ga_error_thread = part_id; break; } data->m_thread = thread; thrdata.push_back(data); } // join all threads for (Uint32 i=0; i 0) barrier.cancel(); NdbThread_WaitFor(thrdata[i]->m_thread, &status); NdbThread_Destroy(&thrdata[i]->m_thread); } for (int i=0; i 0) { exitHandler(NdbToolsProgramExitCode::FAILED); } return NdbToolsProgramExitCode::OK; } // main template class Vector; template class Vector; template class Vector;