├── mysql_innodb_stat_tables.sql ├── README.md ├── LICENSE └── innodb_export_import.py /mysql_innodb_stat_tables.sql: -------------------------------------------------------------------------------- 1 | /* MySQL InnoDB stat tables 2 | These are used in MySQL 5.6, and though are not required, will show errors in the error log 3 | if InnoDB tables are accessed and these tables are not present! 4 | */ 5 | 6 | CREATE TABLE IF NOT EXISTS `innodb_index_stats` ( 7 | `database_name` varchar(64) COLLATE utf8_bin NOT NULL, 8 | `table_name` varchar(64) COLLATE utf8_bin NOT NULL, 9 | `index_name` varchar(64) COLLATE utf8_bin NOT NULL, 10 | `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 11 | `stat_name` varchar(64) COLLATE utf8_bin NOT NULL, 12 | `stat_value` bigint(20) unsigned NOT NULL, 13 | `sample_size` bigint(20) unsigned DEFAULT NULL, 14 | `stat_description` varchar(1024) COLLATE utf8_bin NOT NULL, 15 | PRIMARY KEY (`database_name`,`table_name`,`index_name`,`stat_name`) 16 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin STATS_PERSISTENT=0; 17 | 18 | 19 | CREATE TABLE IF NOT EXISTS `innodb_table_stats` ( 20 | `database_name` varchar(64) COLLATE utf8_bin NOT NULL, 21 | `table_name` varchar(64) COLLATE utf8_bin NOT NULL, 22 | `last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 23 | `n_rows` bigint(20) unsigned NOT NULL, 24 | `clustered_index_size` bigint(20) unsigned NOT NULL, 25 | `sum_of_other_index_sizes` bigint(20) unsigned NOT NULL, 26 | PRIMARY KEY (`database_name`,`table_name`) 27 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin STATS_PERSISTENT=0; 28 | 29 | 30 | CREATE TABLE IF NOT EXISTS `slave_master_info` ( 31 | `Number_of_lines` int(10) unsigned NOT NULL COMMENT 'Number of lines in the file.', 32 | `Master_log_name` text CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'The name of the master binary log currently being read from the master.', 33 | `Master_log_pos` bigint(20) unsigned NOT NULL COMMENT 'The master log position of the last read event.', 34 | `Host` char(64) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL DEFAULT '' COMMENT 'The host name of the master.', 35 | `User_name` text CHARACTER SET utf8 COLLATE utf8_bin COMMENT 'The user name used to connect to the master.', 36 | `User_password` text CHARACTER SET utf8 COLLATE utf8_bin COMMENT 'The password used to connect to the master.', 37 | `Port` int(10) unsigned NOT NULL COMMENT 'The network port used to connect to the master.', 38 | `Connect_retry` int(10) unsigned NOT NULL COMMENT 'The period (in seconds) that the slave will wait before trying to reconnect to the master.', 39 | `Enabled_ssl` tinyint(1) NOT NULL COMMENT 'Indicates whether the server supports SSL connections.', 40 | `Ssl_ca` text CHARACTER SET utf8 COLLATE utf8_bin COMMENT 'The file used for the Certificate Authority (CA) certificate.', 41 | `Ssl_capath` text CHARACTER SET utf8 COLLATE utf8_bin COMMENT 'The path to the Certificate Authority (CA) certificates.', 42 | `Ssl_cert` text CHARACTER SET utf8 COLLATE utf8_bin COMMENT 'The name of the SSL certificate file.', 43 | `Ssl_cipher` text CHARACTER SET utf8 COLLATE utf8_bin COMMENT 'The name of the cipher in use for the SSL connection.', 44 | `Ssl_key` text CHARACTER SET utf8 COLLATE utf8_bin COMMENT 'The name of the SSL key file.', 45 | `Ssl_verify_server_cert` tinyint(1) NOT NULL COMMENT 'Whether to verify the server certificate.', 46 | `Heartbeat` float NOT NULL, 47 | `Bind` text CHARACTER SET utf8 COLLATE utf8_bin COMMENT 'Displays which interface is employed when connecting to the MySQL server', 48 | `Ignored_server_ids` text CHARACTER SET utf8 COLLATE utf8_bin COMMENT 'The number of server IDs to be ignored, followed by the 49 | actual server IDs', 50 | `Uuid` text CHARACTER SET utf8 COLLATE utf8_bin COMMENT 'The master server uuid.', 51 | `Retry_count` bigint(20) unsigned NOT NULL COMMENT 'Number of reconnect attempts, to the master, before giving up.', 52 | `Ssl_crl` text CHARACTER SET utf8 COLLATE utf8_bin COMMENT 'The file used for the Certificate Revocation List (CRL)', 53 | `Ssl_crlpath` text CHARACTER SET utf8 COLLATE utf8_bin COMMENT 'The path used for Certificate Revocation List (CRL) files', 54 | `Enabled_auto_position` tinyint(1) NOT NULL COMMENT 'Indicates whether GTIDs will be used to retrieve events from the master.', 55 | PRIMARY KEY (`Host`,`Port`) 56 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 STATS_PERSISTENT=0 COMMENT='Master Information'; 57 | 58 | 59 | CREATE TABLE IF NOT EXISTS `slave_relay_log_info` ( 60 | `Number_of_lines` int(10) unsigned NOT NULL COMMENT 'Number of lines in the file or rows in the table. Used to version table definitions.', 61 | `Relay_log_name` text CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'The name of the current relay log file.', 62 | `Relay_log_pos` bigint(20) unsigned NOT NULL COMMENT 'The relay log position of the last executed event.', 63 | `Master_log_name` text CHARACTER SET utf8 COLLATE utf8_bin NOT NULL COMMENT 'The name of the master binary log file from which the events in the relay log file were read.', 64 | `Master_log_pos` bigint(20) unsigned NOT NULL COMMENT 'The master log position of the last executed event.', 65 | `Sql_delay` int(11) NOT NULL COMMENT 'The number of seconds that the slave must lag behind the master.', 66 | `Number_of_workers` int(10) unsigned NOT NULL, 67 | `Id` int(10) unsigned NOT NULL COMMENT 'Internal Id that uniquely identifies this record.', 68 | PRIMARY KEY (`Id`) 69 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 STATS_PERSISTENT=0 COMMENT='Relay Log Information'; 70 | 71 | 72 | CREATE TABLE IF NOT EXISTS `slave_worker_info` ( 73 | `Id` int(10) unsigned NOT NULL, 74 | `Relay_log_name` text CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, 75 | `Relay_log_pos` bigint(20) unsigned NOT NULL, 76 | `Master_log_name` text CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, 77 | `Master_log_pos` bigint(20) unsigned NOT NULL, 78 | `Checkpoint_relay_log_name` text CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, 79 | `Checkpoint_relay_log_pos` bigint(20) unsigned NOT NULL, 80 | `Checkpoint_master_log_name` text CHARACTER SET utf8 COLLATE utf8_bin NOT NULL, 81 | `Checkpoint_master_log_pos` bigint(20) unsigned NOT NULL, 82 | `Checkpoint_seqno` int(10) unsigned NOT NULL, 83 | `Checkpoint_group_size` int(10) unsigned NOT NULL, 84 | `Checkpoint_group_bitmap` blob NOT NULL, 85 | PRIMARY KEY (`Id`) 86 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8 STATS_PERSISTENT=0 COMMENT='Worker Information'; 87 | 88 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # InnoDB Exporter/Importer 2 | 3 | Python script for exporting and importing InnoDB tables. 4 | 5 | Vendor Websites: 6 | 7 | * The cPanel Admin/TCA Server Solutions, LLC 8 | * InMotion Hosting, Inc 9 | 10 | Author: Vanessa Vasile 11 | 12 | This script does a mass export and import of InnoDB tables for MySQL. Doing 13 | this can resolve a number of InnoDB-related MySQL issues, including: 14 | 15 | * InnoDB log sequence mismatches 16 | * Converting to innodb_file_per_table, and shrinking the shared ibdata1 file 17 | * Restoring from InnoDB recovery mode where MySQL/InnoDB will not start without 18 | recovery being enabled 19 | 20 | Usage Scenario Example: 21 | 22 | * http://www.thecpaneladmin.com/how-to-convert-innodb-to-innodb_file_per_table-and-shrink-ibdata1/ 23 | 24 | DISCLAIMER: 25 | 26 | As usual, before you make any drastic changes to MySQL data, you should make a 27 | backup. We are not responsible for any damage done to your system resulting 28 | from failure to back up data or follow instructions. While this script has been 29 | repeatedly tested and used in production environments, we must reiterate that 30 | you use this script at your own risk. 31 | 32 | 33 | ## PREREQUISITES 34 | 35 | ### A. Running MySQL instance 36 | 37 | MySQL must be running for this script to be able to export tables properly. 38 | If You are having trouble starting MySQL, check the MySQL error log for 39 | anything obvious. If the issue is related to corrupted InnoDB data: 40 | 41 | 1) Open /etc/my.cnf in a text editor 42 | 2) Add the following line: 43 | 44 | innodb_force_recovery = 1 45 | 46 | 3) Attempt to start MySQL 47 | 48 | If MySQL still does not start, keep increasing the recovery level up to 6 49 | until it does. If MySQL still refuses to start, your issues are likely 50 | outside the scope of what this script would be able to do for you, and you 51 | should consult a professional. 52 | 53 | More information: 54 | http://dev.mysql.com/doc/refman/5.6/en/forcing-innodb-recovery.html 55 | 56 | If you recently upgraded from 5.5 to 5.6, also try downgrading back to 5.5, 57 | using this script to re-import all tables, then upgrading again. 58 | 59 | Note: A lot of InnoDB problems can be resolved simply by starting InnoDB in 60 | recovery mode (1-4), restarting MySQL, then restarting again without 61 | recovery mode. 62 | 63 | 64 | ### B. Backups 65 | 66 | Make sure you have a backup of your MySQL data. Since you may be dealing with 67 | corrupted data, we'd recommend stopping MySQL and making a copy of your MySQL 68 | data directory (i.e. /var/lib/mysql). Again, MySQL should be stopped when you 69 | do this. This allows you to easily restore MySQL to the a previous state if 70 | needed. 71 | 72 | If you prefer to use dumps for backups, you can use mysqldump: 73 | 74 | http://webcheatsheet.com/sql/mysql_backup_restore.php 75 | 76 | 77 | ### C. Miscellaneous 78 | 79 | The following is also needed: 80 | 81 | * MySQL 5.0 or higher, or MariaDB 10.0 or higher, with client packages installed 82 | * Python 2.6 or 2.7 with standard modules includes, plus MySQLDB 83 | * Enough free disk space on the partition this script will dump to 84 | (typically the size of your MySQL data folder) 85 | * Root MySQL access 86 | * Write permissions to the folder this script is dumping data to 87 | 88 | Most standard systems already meet the above requirements. This script also 89 | works with MySQL running on cPanel servers. 90 | 91 | 92 | ## USAGE 93 | 94 | ### Exporting tables 95 | 96 | The script will only export InnoDB tables, and does so by iterating through 97 | all databases and dumping only the ones listed as having InnoDB as their 98 | engine. 99 | 100 | Export syntax: 101 | 102 | ./innodb_import_export.py --export [--dir=DIR] [--config=CONFIG] 103 | 104 | Where: 105 | 106 | --dir = The folder where you would like the dumps to be stored. By default 107 | this is /home/innodb_data. A child folder will becreated within this 108 | directory for each session, denoted in these examples as $SESSION 109 | --config = The client configuration file for MySQL. By default, this is 110 | /root/.my.cnf, but you can create your own 111 | 112 | More information: 113 | http://dev.mysql.com/doc/refman/5.6/en/option-files.html 114 | 115 | Export format: 116 | 117 | /$DIR/$SESSION/$DATABASE_NAME/$TABLE_NAME.sql 118 | 119 | Log file: 120 | 121 | /$DIR/$SESSION/innodb_export.log 122 | 123 | 124 | ### Importing tables 125 | 126 | Before you import, you may need to do the following: 127 | 128 | * If innodb_recovery_mode > 0, disable it and restart MySQL 129 | * If you had corruption or are switching to innodb_file_per_table, move the 130 | ibdata1 and ib_logfile* files out of the MySQL data folder and restart MySQL. 131 | MySQL will automatically recreate these files at default sizes 132 | 133 | When you exported, the script provided the name of the export location: 134 | 135 | /$DIR/$SESSION 136 | 137 | ie: /home/innodb_data/201407281740/ 138 | 139 | This is the location we are restoring from. 140 | 141 | Import syntax: 142 | 143 | ./innodb_export_import.py --import --dir=DIR [--skip-working] [--config=CONFIG] 144 | 145 | Where: 146 | 147 | --dir = The directory containing the exports taken, as discussed above 148 | --skip-working = Use this option if you do not want to import tables that 149 | are working. This does not check data integrity, but rather whether 150 | MySQL recognizes that the table exists. You would want to use this 151 | option when resuming from a previous restoration 152 | --config = The client configuration file for MySQL. By default, this is 153 | /root/.my.cnf, but you can create your own 154 | 155 | More information: 156 | 157 | http://dev.mysql.com/doc/refman/5.6/en/option-files.html 158 | 159 | 160 | #### Notes: 161 | 162 | * When restoring, the .ibd files for each table are moved into the respective 163 | table's export folder. This is to clear up tablespace for corrupted tables 164 | that would otherwise prevent an import from occurring. 165 | 166 | * If you want to STOP an import, you should avoid using CTRL+C. Instead, touch 167 | a stop file in the import folder and the script will stop importing after the 168 | current table is done. 169 | 170 | Example: 171 | 172 | touch /$DIR/$SESSION/stop 173 | 174 | Make sure to remove this file before resuming, and consider using 175 | --skip-working as to prevent re-importing tables that were already 176 | imported. 177 | 178 | * If you deleted the ibdata1 and ib_logfile* files, it may be necessary to also 179 | remove the .frm files for each table, since ibdata1 will no longer reference 180 | them. You'll know this is necessary if you are not able to import due to 181 | MySQL claiming the table could not be dropped or created because it already 182 | exists. A quick way to do this is to dump the below lines into a file and run: 183 | 184 | Note: You should stop MySQL before doing this. 185 | 186 | ``` 187 | #!/bin/bash 188 | DIR='/home/innodb_data/$SESSION/' # PUT THE DIR NAME HERE, ie /$DIR/$SESSION/ 189 | for file in $(find /var/lib/mysql -name *.frm) 190 | do 191 | DB=$(dirname $file | xargs basename) 192 | TABLE=$(basename $file .frm) 193 | /bin/mv -f /var/lib/mysql/$DB/$TABLE.frm $DIR/$DB/ 194 | done 195 | ``` 196 | 197 | This is not coded into the script as doing this may be irreversible and 198 | dangerous. Again, make sure you have a backup. 199 | 200 | 201 | ### Recreating InnoDB Stat Tables 202 | 203 | When you nuke the InnoDB tablespace information and are running MySQL 5.6 or 204 | higher, you will need to recreate the InnoDB stat tables. These tables are: 205 | 206 | * innodb_index_stats 207 | * innodb_table_stats 208 | * slave_master_info 209 | * slave_relay_log_info 210 | * slave_worker_info 211 | 212 | WHILE MYSQL IS STOPPED, and you have deleted ibdata1 and ib_logfile*, go into 213 | the 'mysql' database folder and remove the .frm and .ibd files for these 214 | tables. 215 | 216 | When you start MySQL, go into this script's source folder and re-import them: 217 | 218 | mysql mysql < mysql_innodb_stat_tables.sql 219 | 220 | 221 | ### Checking tables 222 | 223 | This script can automate post-import sanity checks against InnoDB tables 224 | 225 | Check syntax: 226 | 227 | ./innodb_export_import.py --verify 228 | 229 | A summary and location of the log file will be given when the script run is 230 | completed. The log file will log failures at an ERROR level for review. 231 | 232 | One thing to keep in mind here is that this script (and rather any script of 233 | of this nature, is incapable of verifying the integrity of data within the 234 | table itself. This script specifically checks that MySQL can access the 235 | table, even if it means the table contains partial or no data. If MySQL can at 236 | least access the table, it means that 1) data can be imported, and 2) The 237 | InnoDB engine will not be halted on startup due to the table being corrupted. 238 | 239 | If you were running innodb_file_per_table prior to running this script and 240 | notice that the .ibd files are smaller than they were before, this does not 241 | mean you lost data, it may mean that unused space within the tablespace file 242 | was removed, resulting in a smaller file. 243 | 244 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 2, June 1991 3 | 4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc., 5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | Preamble 10 | 11 | The licenses for most software are designed to take away your 12 | freedom to share and change it. By contrast, the GNU General Public 13 | License is intended to guarantee your freedom to share and change free 14 | software--to make sure the software is free for all its users. This 15 | General Public License applies to most of the Free Software 16 | Foundation's software and to any other program whose authors commit to 17 | using it. (Some other Free Software Foundation software is covered by 18 | the GNU Lesser General Public License instead.) You can apply it to 19 | your programs, too. 20 | 21 | When we speak of free software, we are referring to freedom, not 22 | price. Our General Public Licenses are designed to make sure that you 23 | have the freedom to distribute copies of free software (and charge for 24 | this service if you wish), that you receive source code or can get it 25 | if you want it, that you can change the software or use pieces of it 26 | in new free programs; and that you know you can do these things. 27 | 28 | To protect your rights, we need to make restrictions that forbid 29 | anyone to deny you these rights or to ask you to surrender the rights. 30 | These restrictions translate to certain responsibilities for you if you 31 | distribute copies of the software, or if you modify it. 32 | 33 | For example, if you distribute copies of such a program, whether 34 | gratis or for a fee, you must give the recipients all the rights that 35 | you have. You must make sure that they, too, receive or can get the 36 | source code. And you must show them these terms so they know their 37 | rights. 38 | 39 | We protect your rights with two steps: (1) copyright the software, and 40 | (2) offer you this license which gives you legal permission to copy, 41 | distribute and/or modify the software. 42 | 43 | Also, for each author's protection and ours, we want to make certain 44 | that everyone understands that there is no warranty for this free 45 | software. If the software is modified by someone else and passed on, we 46 | want its recipients to know that what they have is not the original, so 47 | that any problems introduced by others will not reflect on the original 48 | authors' reputations. 49 | 50 | Finally, any free program is threatened constantly by software 51 | patents. We wish to avoid the danger that redistributors of a free 52 | program will individually obtain patent licenses, in effect making the 53 | program proprietary. To prevent this, we have made it clear that any 54 | patent must be licensed for everyone's free use or not licensed at all. 55 | 56 | The precise terms and conditions for copying, distribution and 57 | modification follow. 58 | 59 | GNU GENERAL PUBLIC LICENSE 60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 61 | 62 | 0. This License applies to any program or other work which contains 63 | a notice placed by the copyright holder saying it may be distributed 64 | under the terms of this General Public License. The "Program", below, 65 | refers to any such program or work, and a "work based on the Program" 66 | means either the Program or any derivative work under copyright law: 67 | that is to say, a work containing the Program or a portion of it, 68 | either verbatim or with modifications and/or translated into another 69 | language. (Hereinafter, translation is included without limitation in 70 | the term "modification".) Each licensee is addressed as "you". 71 | 72 | Activities other than copying, distribution and modification are not 73 | covered by this License; they are outside its scope. The act of 74 | running the Program is not restricted, and the output from the Program 75 | is covered only if its contents constitute a work based on the 76 | Program (independent of having been made by running the Program). 77 | Whether that is true depends on what the Program does. 78 | 79 | 1. You may copy and distribute verbatim copies of the Program's 80 | source code as you receive it, in any medium, provided that you 81 | conspicuously and appropriately publish on each copy an appropriate 82 | copyright notice and disclaimer of warranty; keep intact all the 83 | notices that refer to this License and to the absence of any warranty; 84 | and give any other recipients of the Program a copy of this License 85 | along with the Program. 86 | 87 | You may charge a fee for the physical act of transferring a copy, and 88 | you may at your option offer warranty protection in exchange for a fee. 89 | 90 | 2. You may modify your copy or copies of the Program or any portion 91 | of it, thus forming a work based on the Program, and copy and 92 | distribute such modifications or work under the terms of Section 1 93 | above, provided that you also meet all of these conditions: 94 | 95 | a) You must cause the modified files to carry prominent notices 96 | stating that you changed the files and the date of any change. 97 | 98 | b) You must cause any work that you distribute or publish, that in 99 | whole or in part contains or is derived from the Program or any 100 | part thereof, to be licensed as a whole at no charge to all third 101 | parties under the terms of this License. 102 | 103 | c) If the modified program normally reads commands interactively 104 | when run, you must cause it, when started running for such 105 | interactive use in the most ordinary way, to print or display an 106 | announcement including an appropriate copyright notice and a 107 | notice that there is no warranty (or else, saying that you provide 108 | a warranty) and that users may redistribute the program under 109 | these conditions, and telling the user how to view a copy of this 110 | License. (Exception: if the Program itself is interactive but 111 | does not normally print such an announcement, your work based on 112 | the Program is not required to print an announcement.) 113 | 114 | These requirements apply to the modified work as a whole. If 115 | identifiable sections of that work are not derived from the Program, 116 | and can be reasonably considered independent and separate works in 117 | themselves, then this License, and its terms, do not apply to those 118 | sections when you distribute them as separate works. But when you 119 | distribute the same sections as part of a whole which is a work based 120 | on the Program, the distribution of the whole must be on the terms of 121 | this License, whose permissions for other licensees extend to the 122 | entire whole, and thus to each and every part regardless of who wrote it. 123 | 124 | Thus, it is not the intent of this section to claim rights or contest 125 | your rights to work written entirely by you; rather, the intent is to 126 | exercise the right to control the distribution of derivative or 127 | collective works based on the Program. 128 | 129 | In addition, mere aggregation of another work not based on the Program 130 | with the Program (or with a work based on the Program) on a volume of 131 | a storage or distribution medium does not bring the other work under 132 | the scope of this License. 133 | 134 | 3. You may copy and distribute the Program (or a work based on it, 135 | under Section 2) in object code or executable form under the terms of 136 | Sections 1 and 2 above provided that you also do one of the following: 137 | 138 | a) Accompany it with the complete corresponding machine-readable 139 | source code, which must be distributed under the terms of Sections 140 | 1 and 2 above on a medium customarily used for software interchange; or, 141 | 142 | b) Accompany it with a written offer, valid for at least three 143 | years, to give any third party, for a charge no more than your 144 | cost of physically performing source distribution, a complete 145 | machine-readable copy of the corresponding source code, to be 146 | distributed under the terms of Sections 1 and 2 above on a medium 147 | customarily used for software interchange; or, 148 | 149 | c) Accompany it with the information you received as to the offer 150 | to distribute corresponding source code. (This alternative is 151 | allowed only for noncommercial distribution and only if you 152 | received the program in object code or executable form with such 153 | an offer, in accord with Subsection b above.) 154 | 155 | The source code for a work means the preferred form of the work for 156 | making modifications to it. For an executable work, complete source 157 | code means all the source code for all modules it contains, plus any 158 | associated interface definition files, plus the scripts used to 159 | control compilation and installation of the executable. However, as a 160 | special exception, the source code distributed need not include 161 | anything that is normally distributed (in either source or binary 162 | form) with the major components (compiler, kernel, and so on) of the 163 | operating system on which the executable runs, unless that component 164 | itself accompanies the executable. 165 | 166 | If distribution of executable or object code is made by offering 167 | access to copy from a designated place, then offering equivalent 168 | access to copy the source code from the same place counts as 169 | distribution of the source code, even though third parties are not 170 | compelled to copy the source along with the object code. 171 | 172 | 4. You may not copy, modify, sublicense, or distribute the Program 173 | except as expressly provided under this License. Any attempt 174 | otherwise to copy, modify, sublicense or distribute the Program is 175 | void, and will automatically terminate your rights under this License. 176 | However, parties who have received copies, or rights, from you under 177 | this License will not have their licenses terminated so long as such 178 | parties remain in full compliance. 179 | 180 | 5. You are not required to accept this License, since you have not 181 | signed it. However, nothing else grants you permission to modify or 182 | distribute the Program or its derivative works. These actions are 183 | prohibited by law if you do not accept this License. Therefore, by 184 | modifying or distributing the Program (or any work based on the 185 | Program), you indicate your acceptance of this License to do so, and 186 | all its terms and conditions for copying, distributing or modifying 187 | the Program or works based on it. 188 | 189 | 6. Each time you redistribute the Program (or any work based on the 190 | Program), the recipient automatically receives a license from the 191 | original licensor to copy, distribute or modify the Program subject to 192 | these terms and conditions. You may not impose any further 193 | restrictions on the recipients' exercise of the rights granted herein. 194 | You are not responsible for enforcing compliance by third parties to 195 | this License. 196 | 197 | 7. If, as a consequence of a court judgment or allegation of patent 198 | infringement or for any other reason (not limited to patent issues), 199 | conditions are imposed on you (whether by court order, agreement or 200 | otherwise) that contradict the conditions of this License, they do not 201 | excuse you from the conditions of this License. If you cannot 202 | distribute so as to satisfy simultaneously your obligations under this 203 | License and any other pertinent obligations, then as a consequence you 204 | may not distribute the Program at all. For example, if a patent 205 | license would not permit royalty-free redistribution of the Program by 206 | all those who receive copies directly or indirectly through you, then 207 | the only way you could satisfy both it and this License would be to 208 | refrain entirely from distribution of the Program. 209 | 210 | If any portion of this section is held invalid or unenforceable under 211 | any particular circumstance, the balance of the section is intended to 212 | apply and the section as a whole is intended to apply in other 213 | circumstances. 214 | 215 | It is not the purpose of this section to induce you to infringe any 216 | patents or other property right claims or to contest validity of any 217 | such claims; this section has the sole purpose of protecting the 218 | integrity of the free software distribution system, which is 219 | implemented by public license practices. Many people have made 220 | generous contributions to the wide range of software distributed 221 | through that system in reliance on consistent application of that 222 | system; it is up to the author/donor to decide if he or she is willing 223 | to distribute software through any other system and a licensee cannot 224 | impose that choice. 225 | 226 | This section is intended to make thoroughly clear what is believed to 227 | be a consequence of the rest of this License. 228 | 229 | 8. If the distribution and/or use of the Program is restricted in 230 | certain countries either by patents or by copyrighted interfaces, the 231 | original copyright holder who places the Program under this License 232 | may add an explicit geographical distribution limitation excluding 233 | those countries, so that distribution is permitted only in or among 234 | countries not thus excluded. In such case, this License incorporates 235 | the limitation as if written in the body of this License. 236 | 237 | 9. The Free Software Foundation may publish revised and/or new versions 238 | of the General Public License from time to time. Such new versions will 239 | be similar in spirit to the present version, but may differ in detail to 240 | address new problems or concerns. 241 | 242 | Each version is given a distinguishing version number. If the Program 243 | specifies a version number of this License which applies to it and "any 244 | later version", you have the option of following the terms and conditions 245 | either of that version or of any later version published by the Free 246 | Software Foundation. If the Program does not specify a version number of 247 | this License, you may choose any version ever published by the Free Software 248 | Foundation. 249 | 250 | 10. If you wish to incorporate parts of the Program into other free 251 | programs whose distribution conditions are different, write to the author 252 | to ask for permission. For software which is copyrighted by the Free 253 | Software Foundation, write to the Free Software Foundation; we sometimes 254 | make exceptions for this. Our decision will be guided by the two goals 255 | of preserving the free status of all derivatives of our free software and 256 | of promoting the sharing and reuse of software generally. 257 | 258 | NO WARRANTY 259 | 260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY 261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN 262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES 263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED 264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS 266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE 267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, 268 | REPAIR OR CORRECTION. 269 | 270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR 272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, 273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING 274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED 275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY 276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER 277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE 278 | POSSIBILITY OF SUCH DAMAGES. 279 | 280 | END OF TERMS AND CONDITIONS 281 | 282 | How to Apply These Terms to Your New Programs 283 | 284 | If you develop a new program, and you want it to be of the greatest 285 | possible use to the public, the best way to achieve this is to make it 286 | free software which everyone can redistribute and change under these terms. 287 | 288 | To do so, attach the following notices to the program. It is safest 289 | to attach them to the start of each source file to most effectively 290 | convey the exclusion of warranty; and each file should have at least 291 | the "copyright" line and a pointer to where the full notice is found. 292 | 293 | {description} 294 | Copyright (C) {year} {fullname} 295 | 296 | This program is free software; you can redistribute it and/or modify 297 | it under the terms of the GNU General Public License as published by 298 | the Free Software Foundation; either version 2 of the License, or 299 | (at your option) any later version. 300 | 301 | This program is distributed in the hope that it will be useful, 302 | but WITHOUT ANY WARRANTY; without even the implied warranty of 303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 304 | GNU General Public License for more details. 305 | 306 | You should have received a copy of the GNU General Public License along 307 | with this program; if not, write to the Free Software Foundation, Inc., 308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 309 | 310 | Also add information on how to contact you by electronic and paper mail. 311 | 312 | If the program is interactive, make it output a short notice like this 313 | when it starts in an interactive mode: 314 | 315 | Gnomovision version 69, Copyright (C) year name of author 316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 317 | This is free software, and you are welcome to redistribute it 318 | under certain conditions; type `show c' for details. 319 | 320 | The hypothetical commands `show w' and `show c' should show the appropriate 321 | parts of the General Public License. Of course, the commands you use may 322 | be called something other than `show w' and `show c'; they could even be 323 | mouse-clicks or menu items--whatever suits your program. 324 | 325 | You should also get your employer (if you work as a programmer) or your 326 | school, if any, to sign a "copyright disclaimer" for the program, if 327 | necessary. Here is a sample; alter the names: 328 | 329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program 330 | `Gnomovision' (which makes passes at compilers) written by James Hacker. 331 | 332 | {signature of Ty Coon}, 1 April 1989 333 | Ty Coon, President of Vice 334 | 335 | This General Public License does not permit incorporating your program into 336 | proprietary programs. If your program is a subroutine library, you may 337 | consider it more useful to permit linking proprietary applications with the 338 | library. If this is what you want to do, use the GNU Lesser General 339 | Public License instead of this License. 340 | -------------------------------------------------------------------------------- /innodb_export_import.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """InnoDB Import/Export Script""" 3 | 4 | from glob import glob 5 | import logging 6 | import os 7 | import subprocess 8 | import sys 9 | from datetime import datetime 10 | from optparse import OptionParser 11 | 12 | try: 13 | import MySQLdb 14 | except ImportError: 15 | print 'MySQLdb module not found. To install, run: easy_install MySQL-python' 16 | sys.exit(1) 17 | 18 | 19 | def init_logging(log_file): 20 | """Initializes logger""" 21 | 22 | inc = datetime.now().strftime('%M%S') 23 | 24 | if os.path.exists(log_file): 25 | rename_to = os.path.join(log_file, inc) 26 | print 'Moving existing log file to %s' % rename_to 27 | 28 | try: 29 | os.rename(log_file, rename_to) 30 | except OSError: 31 | pass 32 | 33 | open(log_file, 'w').close() 34 | logging.basicConfig(format='%(levelname)s: %(message)s', 35 | filename=log_file, level=logging.INFO) 36 | 37 | return True 38 | 39 | 40 | def stop(data_dir): 41 | """Checks for a stop file and exits gracefully""" 42 | 43 | # Check for a stop file 44 | if os.path.exists(os.path.join(data_dir, 'stop')): 45 | msg = 'Stop file detected...stopping' 46 | print color_me(msg, 'red') 47 | logging.info(msg) 48 | return True 49 | 50 | return False 51 | 52 | 53 | def color_me(string, color): 54 | """Makes pretty colors if you want 'em""" 55 | 56 | options = opts()[0] 57 | 58 | if not options.do_color: 59 | return string 60 | 61 | pretty_colors = { 62 | 'red': '\033[91m', 63 | 'orange': '\033[93m', 64 | 'green': '\033[92m', 65 | 'blue': '\033[36m', 66 | 'normal': '\033[0m' 67 | } 68 | 69 | return '%s %s %s' % (pretty_colors[color], 70 | string, pretty_colors['normal']) 71 | 72 | 73 | def display_stats(stats): 74 | """Displays summary of what this script actually did""" 75 | 76 | summary = {} 77 | 78 | print '\nSUMMARY:\n' 79 | for key, value in stats.items(): 80 | name = key.replace('_', ' ').title() 81 | summary[key] = value 82 | print '%s: %s' % (name, value) 83 | 84 | return summary 85 | 86 | 87 | def mysql_connect(config, dbname=None): 88 | """Establishes a MySQL connection""" 89 | 90 | try: 91 | conn = MySQLdb.connect( 92 | db=dbname, 93 | read_default_file=config 94 | ) 95 | 96 | dbconn = conn.cursor() 97 | 98 | except MySQLdb.Error, err: 99 | print 'MySQL Error %d: %s' % (err.args[0], err.args[1]) 100 | return None 101 | 102 | return dbconn 103 | 104 | 105 | def get_recovery_level(config): 106 | """Checks InnoDB recovery level""" 107 | 108 | dbname = '' 109 | dbconn = mysql_connect(config, dbname) 110 | 111 | if not dbconn: 112 | return False 113 | 114 | try: 115 | dbconn.execute("SHOW VARIABLES LIKE 'innodb_force_recovery'") 116 | except (MySQLdb.Error, TypeError), err: 117 | print 'MySQL Error %d: %s' % (err.args[0], err.args[1]) 118 | sys.exit(1) 119 | 120 | result = dbconn.fetchone()[1] 121 | 122 | return result 123 | 124 | 125 | def get_mysql_version(config): 126 | """Gets MySQL Server Version""" 127 | 128 | dbname = '' 129 | dbconn = mysql_connect(config, dbname) 130 | 131 | if not dbconn: 132 | return False 133 | 134 | try: 135 | dbconn.execute("SHOW VARIABLES LIKE 'version'") 136 | except (MySQLdb.Error, TypeError), err: 137 | print 'MySQL Error %d: %s' % (err.args[0], err.args[1]) 138 | sys.exit(1) 139 | 140 | result = ('').join(dbconn.fetchone()[1].split('-')[0].split('.')[:2]) 141 | 142 | return int(result) 143 | 144 | 145 | def database_exists(config, dbname): 146 | """Checks if a database exists""" 147 | 148 | dbconn = mysql_connect(config, dbname) 149 | 150 | if not dbconn: 151 | return False 152 | 153 | print 'Checking %s...' % dbname 154 | try: 155 | dbconn.execute("SHOW TABLES") 156 | except (MySQLdb.Error, TypeError): 157 | return False 158 | 159 | return True 160 | 161 | 162 | def check_table(config, dbname, table): 163 | """Checks an InnoDB table""" 164 | # Really what we're doing is making sure the tablespace is intact. 165 | # This is usually as simply as trying to access the table 166 | 167 | dbconn = mysql_connect(config, dbname) 168 | 169 | if not dbconn: 170 | return False 171 | 172 | print 'Checking %s.%s...' % (dbname, table) 173 | try: 174 | dbconn.execute("EXPLAIN %s" % (table,)) 175 | except (MySQLdb.Error, TypeError), err: 176 | print err 177 | return False 178 | 179 | return True 180 | 181 | 182 | def get_dbs_with_innodb(config): 183 | """Gets a list of databases that contain InnoDB tables""" 184 | 185 | dbname = 'INFORMATION_SCHEMA' 186 | dbconn = mysql_connect(config, dbname) 187 | 188 | if not dbconn: 189 | return False 190 | 191 | data = {} 192 | 193 | # This is a little 'eh'. You can poll for all InnoDB tables via 194 | # INFORMATION_SCHEMA, but on very large servers this query can time 195 | # out or hang the server. Therefore, we're going to pull a list of 196 | # databases and query INFORMATION_SCHEMA individually for all InnoDB 197 | # tables within each database. 198 | 199 | # Format: dict = { 'db': ['table_1', 'table_2' ... ] 200 | 201 | print 'Getting a list of databases...' 202 | try: 203 | dbconn.execute("SHOW DATABASES") 204 | except (MySQLdb.Error, TypeError), err: 205 | print 'MySQL Error %d: %s' % (err.args[0], err.args[1]) 206 | return False 207 | 208 | databases = [ d[0] for d in dbconn.fetchall() ] 209 | for exclude_db in [ 'mysql', 'information_schema' ]: 210 | databases.remove(exclude_db) 211 | 212 | print 'Checking for InnoDB tables...' 213 | 214 | for database in databases: 215 | 216 | print('Database %s...' % database), 217 | try: 218 | dbconn.execute( 219 | "SELECT table_name FROM INFORMATION_SCHEMA.TABLES" 220 | " WHERE table_schema='%s' and engine='innodb';" % database 221 | ) 222 | except (MySQLdb.Error, TypeError), err: 223 | print 'MySQL Error %d: %s' % (err.args[0], err.args[1]) 224 | continue 225 | 226 | tables_raw = dbconn.fetchall() 227 | if tables_raw: 228 | tables = [item[0] for item in tables_raw] 229 | 230 | print 'Detected %s InnoDB tables' % len(tables_raw) 231 | data[database] = tables 232 | else: 233 | print 'No InnoDB tables' 234 | 235 | return data 236 | 237 | 238 | def dump_table(dbname, table, mysql_version, data_dir, config): 239 | """Dumps a table""" 240 | 241 | dump_opts = '' 242 | if mysql_version >= 56 and mysql_version < 10: # 10 = MariaDB 243 | dump_opts = '--add-drop-trigger' # Only supported in mysqldump for 5.6 244 | 245 | dump_path = os.path.join(data_dir, dbname) 246 | if not os.path.exists(dump_path): 247 | os.makedirs(dump_path) 248 | 249 | dump_file = os.path.join(dump_path, '%s.sql' % table) 250 | fhandle = open(dump_file, 'w+') 251 | 252 | try: 253 | process = subprocess.Popen( 254 | "mysqldump --defaults-extra-file=%s --add-drop-table %s %s %s" % ( 255 | config, dump_opts, dbname, table 256 | ), 257 | stdout=fhandle, stderr=subprocess.PIPE, shell=True) 258 | fhandle.close() 259 | output = process.stderr.read() 260 | 261 | if output == '': 262 | return True 263 | else: 264 | return False 265 | 266 | except subprocess.CalledProcessError: 267 | return False 268 | 269 | 270 | def import_table(dbname, table, data_dir, config): 271 | """Imports a table""" 272 | 273 | dump_file = os.path.join(data_dir, dbname, '%s.sql' % table) 274 | 275 | try: 276 | process = subprocess.Popen( 277 | "mysql --defaults-file=%s %s < %s" % ( 278 | config, dbname, dump_file 279 | 280 | ), 281 | stderr=subprocess.PIPE, shell=True) 282 | output = process.stderr.read() 283 | 284 | if output == '': 285 | return True 286 | else: 287 | return False 288 | 289 | except subprocess.CalledProcessError: 290 | return False 291 | 292 | 293 | def main(): 294 | """Default function""" 295 | 296 | options = opts()[0] 297 | timestamp = datetime.now().strftime('%Y%m%d%H%M') 298 | config = options.config 299 | 300 | if options.do_export and options.do_import: 301 | print 'Please specify either --import or --export' 302 | sys.exit(1) 303 | elif (not options.do_export 304 | and not options.do_import 305 | and not options.do_verify): 306 | print 'Please specify either --import, --export, or --verify' 307 | sys.exit(1) 308 | 309 | mysql_version = get_mysql_version(config) 310 | if not mysql_version: 311 | print 'Unable to determine MySQL version' 312 | sys.exit(1) 313 | elif mysql_version < 50: 314 | print 'This script requires MySQL 5.0 or higher' 315 | sys.exit(1) 316 | 317 | if options.do_export: 318 | do_export(options, config, timestamp, mysql_version) 319 | 320 | if options.do_import: 321 | do_import(options, config) 322 | 323 | if options.do_verify: 324 | do_verify(options, config, timestamp) 325 | 326 | 327 | def do_export(options, config, timestamp, mysql_version): 328 | """Handles table exports""" 329 | 330 | # We only want a default dir for exports 331 | if not options.data_dir: 332 | options.data_dir = '/home/innodb_data' 333 | 334 | data_dir = os.path.join(options.data_dir, timestamp) # Change dir 335 | if not os.path.exists(data_dir): 336 | os.makedirs(data_dir) 337 | 338 | log_file = os.path.join(data_dir, 'innodb_export.log') 339 | 340 | init_logging(log_file) 341 | 342 | databases = get_dbs_with_innodb(config) 343 | 344 | stats = { 345 | 'databases_total': len(databases), 346 | 'tables_total': 0, 347 | 'tables_exported': 0, 348 | 'tables_failed': 0, 349 | } 350 | 351 | if databases: 352 | print 'Dumping tables...' 353 | else: 354 | print 'No databases detected, or none contain InnoDB data.' 355 | 356 | # Keep a ghetto counter with the number of tables, so we can check progress 357 | num_tables = 0 358 | for dbname, tables in databases.items(): 359 | num_tables += len(tables) 360 | 361 | for dbname, tables in databases.items(): 362 | for table in tables: 363 | stats['tables_total'] += 1 364 | 365 | if stop(data_dir): 366 | sys.exit(0) 367 | 368 | if dump_table(dbname, table, mysql_version, data_dir, config): 369 | msg = 'Dumped table %s.%s (%s / %s)' % (dbname, table, stats['tables_total'], num_tables) 370 | print color_me(msg, 'green') 371 | logging.info(msg) 372 | stats['tables_exported'] += 1 373 | else: 374 | msg = 'Error dumping table %s.%s' % (dbname, table) 375 | print color_me(msg, 'red') 376 | logging.error(msg) 377 | stats['tables_failed'] += 1 378 | 379 | summary = display_stats(stats) 380 | logging.info(summary) 381 | 382 | print "\nLog file: %s " % log_file 383 | print "Tables were dumped to: %s" % data_dir 384 | 385 | 386 | def do_import(options, config): 387 | """Handles table imports""" 388 | 389 | # We can't do this if we're in recovery mode 390 | if int(get_recovery_level(config)) > 0: 391 | print('MySQL is running with innodb_recovery_mode > 0. ' 392 | 'Please disable before continuing.') 393 | sys.exit(1) 394 | if options.data_dir: 395 | data_dir = options.data_dir.rstrip("/") 396 | else: 397 | print('An import requires a directory containing InnoDB dumps. ' 398 | ' Please pass --dir with a valid directory name') 399 | sys.exit(1) 400 | 401 | if not os.path.exists(data_dir): 402 | print 'Data directory "%s" does not exist' % data_dir 403 | sys.exit(1) 404 | 405 | log_file = os.path.join(data_dir, 'innodb_import.log') 406 | init_logging(log_file) 407 | 408 | if not os.path.exists(data_dir): 409 | print 'Import directory %s does not exist' % data_dir 410 | sys.exit(1) 411 | 412 | # Loop through the import folder 413 | try: 414 | databases = os.walk(data_dir).next()[1] 415 | except StopIteration: 416 | print 'No databases to restore' 417 | sys.exit(0) 418 | 419 | stats = { 420 | 'databases_total': len(databases), 421 | 'tables_total': 0, 422 | 'tables_imported': 0, 423 | 'tables_failed': 0, 424 | 'tables_skipped': 0 425 | } 426 | 427 | for dbname in databases: 428 | # Make sure this is a valid database 429 | if database_exists(config, dbname): 430 | if dbname == 'mysql': 431 | continue 432 | 433 | # Get tables 434 | table_files = glob(os.path.join(data_dir, dbname, '*.sql')) 435 | for dump in table_files: 436 | table = os.path.splitext(os.path.basename(dump))[0] 437 | 438 | if stop(data_dir): 439 | summary = display_stats(stats) 440 | logging.info(summary) 441 | sys.exit(0) 442 | 443 | stats['tables_total'] += 1 444 | 445 | if options.skip_working: 446 | # Skip tables that are already working, if we said to 447 | if check_table(config, dbname, table): 448 | msg = 'Skipping table %s.%s' % (dbname, table) 449 | print color_me(msg, 'blue') 450 | logging.info(msg) 451 | stats['tables_skipped'] += 1 452 | 453 | # Import the table 454 | else: 455 | original = os.path.join( 456 | '/var/lib/mysql/', dbname, '%s.ibd' % table 457 | ) 458 | new = os.path.join(data_dir, dbname, '%s.ibd' % table) 459 | print original 460 | try: 461 | os.rename(original, new) 462 | print color_me('Backed up %s' % original, 'blue') 463 | 464 | except OSError: 465 | pass 466 | 467 | # Import the table 468 | if import_table(dbname, table, data_dir, config): 469 | msg = 'Imported table %s.%s' % (dbname, table) 470 | print color_me(msg, 'green') 471 | logging.info(msg) 472 | stats['tables_imported'] += 1 473 | else: 474 | msg = 'Error importing table %s.%s' % (dbname, table) 475 | print color_me(msg, 'red') 476 | logging.error(msg) 477 | stats['tables_failed'] += 1 478 | 479 | 480 | summary = display_stats(stats) 481 | logging.info(summary) 482 | 483 | print "\nLog file: %s " % log_file 484 | 485 | 486 | def do_verify(options, config, timestamp): 487 | """Handles table sanity checks""" 488 | 489 | # We only want a default dir for exports 490 | if not options.data_dir: 491 | options.data_dir = '/home/innodb_data' 492 | 493 | data_dir = os.path.join(options.data_dir, timestamp) 494 | if not os.path.exists(data_dir): 495 | os.makedirs(data_dir) 496 | 497 | log_file = os.path.join(data_dir, 'innodb_check.log') 498 | 499 | init_logging(log_file) 500 | 501 | if not os.path.exists(data_dir): 502 | print 'Import directory %s does not exist' % data_dir 503 | sys.exit(1) 504 | 505 | databases = get_dbs_with_innodb(config) 506 | 507 | stats = { 508 | 'databases_total': len(databases), 509 | 'tables_total': 0, 510 | 'tables_checked': 0, 511 | 'tables_ok': 0, 512 | 'tables_bad': 0, 513 | } 514 | 515 | if databases: 516 | print 'Checking tables...' 517 | else: 518 | print 'No databases detected!' 519 | 520 | for dbname, tables in databases.items(): 521 | for table in tables: 522 | if stop(data_dir): 523 | summary = display_stats(stats) 524 | logging.info(summary) 525 | sys.exit(0) 526 | 527 | stats['tables_total'] += 1 528 | stats['tables_checked'] += 1 529 | 530 | if check_table(config, dbname, table): 531 | msg = 'Table %s.%s is OK' % (dbname, table) 532 | print color_me(msg, 'green') 533 | logging.info(msg) 534 | stats['tables_ok'] += 1 535 | else: 536 | msg = 'Table %s.%s has errors' % (dbname, table) 537 | print color_me(msg, 'red') 538 | logging.error(msg) 539 | stats['tables_bad'] += 1 540 | 541 | summary = display_stats(stats) 542 | logging.info(summary) 543 | 544 | print "\nLog file: %s " % log_file 545 | 546 | 547 | def opts(): 548 | """Defines valid command line options""" 549 | 550 | parser = OptionParser( 551 | usage='usage: %prog [options]', 552 | version='%prog 1.0' 553 | ) 554 | parser.add_option( 555 | '-e', '--export', 556 | help='Export InnoDB tables', 557 | action='store_true', dest='do_export', default=False 558 | ) 559 | parser.add_option( 560 | '-i', '--import', 561 | help="Import InnoDB data", 562 | action='store_true', dest='do_import', default=False 563 | ) 564 | parser.add_option( 565 | '-v', '--verify', 566 | help="Verify InnoDB tables", 567 | action='store_true', dest='do_verify', default=False 568 | ) 569 | parser.add_option( 570 | '-d', '--dir', 571 | help="Location of InnoDB data (default: /home/innodb_data)", 572 | action='store', type='string', dest='data_dir' 573 | ) 574 | parser.add_option( 575 | '-s', '--skip-working', 576 | help="Skip import of tables that are a already working", 577 | action='store_true', dest='skip_working', default=False 578 | ) 579 | parser.add_option( 580 | '-c', '--config', 581 | help="Local user config file (default: /root/.my.cnf)", 582 | action='store', type='string', dest='config', default='/root/.my.cnf' 583 | ) 584 | parser.add_option( 585 | '-p', '--colors', 586 | help="Color output; Makes failures more visible.", 587 | action='store_true', dest='do_color', default=False 588 | ) 589 | 590 | (options, args) = parser.parse_args() 591 | 592 | return options, args 593 | 594 | 595 | if __name__ == '__main__': 596 | main() 597 | 598 | --------------------------------------------------------------------------------