D7net
Home
Console
Upload
information
Create File
Create Folder
About
Tools
:
/
proc
/
2
/
root
/
proc
/
2
/
cwd
/
opt
/
imh-python
/
lib
/
python2.7
/
site-packages
/
cmu_ded
/
Libs
/
Filename :
MySQLTransfer.py
back
Copy
""" File containing the class for moving mysql configurations between servers. """ import os import shutil import common from datetime import datetime class MySQLTransfer(common.Migration): """ Transfering MySQL configurations betweens servers. For the most part most people don't modify their mysql configurations. """ conversion_table = { 'innodb_large_prefix': None, 'innodb_log_arch_dir': None, 'nice': None, 'sort_buffer': None, # Deprecated: MariaDB 10.2.6 'innodb_flush_log_at_trx_commit': None, 'default-character-set': None, # Removed: MariaDB 10.2.2 'innodb_additional_mem_pool_size': None, # Deprecated: MariaDB 5.5 'thread_concurrency': None, # Reverts to pre-MySQL 4.1 hash. 'old-passwords': 'old_passwords', # Changed names as of MariaDB 10.0/MySQL 5.6.1 'log-slow-queries': 'slow_query_log', 'read_buffer': 'read_buffer_size', # Previously called table_cache prior to MySQL 5.1.3 'table_cache': 'table_open_cache', 'key_buffer': 'key_buffer_size' } def __init__(self, logger=None): super(MySQLTransfer, self).__init__(logger=logger) # List of errors in the transfer. self.errors = [] # This will hold the configs of MySQL self.configs = {} # TODO Handle multiple of one config. def load(self, config): """ Loads the mysql configuration file as a string. For instance open().read(), or "\n".join(list of strings) :config: Config file as a string :return: Updates the self.config variable. """ self.debug("Entering Load.") header = None parsed_configs = {} return_dict = {} lines = config.split("\n") for line in lines: # Removing whitespace line = line.strip().replace(" ", "") # Checking for [mysqld] and that it is a line if line and (line[0] == '[' and line[-1] == ']'): if header is None: header = line[1:-1] return_dict.update(parsed_configs) parsed_configs = {} else: return_dict[header] = parsed_configs header = line[1:-1] parsed_configs = {} # Checking for values elif "=" in line: # Checking if the value it commented out. if line[0] != ';' and line[0] != '#': split_line = line.split('=') # This syntax is deprecated as of MySQL 4.0 if 'set-variable' in split_line[0]: split_line.pop(0) parsed_configs[split_line[0]] = split_line[1] # Wrapping up anything that hasn't been dumped: if header is None: return_dict.update(parsed_configs) else: return_dict[header] = parsed_configs self.configs = return_dict return return_dict def load_file(self, config_file): """ Loads the configruration for mysql from a file. :config_file: Path to the file you want to load. :Return: Updates self.config variable. """ self.debug("Entering load_file.") if os.path.exists(config_file): with open(config_file, 'r') as mysqlconf: self.load(mysqlconf.read()) mysqlconf.close() else: self.errors.append("%s file does not exist" % config_file) self.error("%s file does not exist" % config_file) return True def dump(self): """ Dumps the current information in self.config as a string. Looping through the configs twice so we can get anything above the header first. :return: Returns a string of the configuration. """ self.debug("Entering dump.") return_string = "" for key in self.configs.keys(): # Adding values without a header. if not isinstance(self.configs[key], dict): return_string += key + "=" + self.configs[key] + "\n" for key in self.configs.keys(): # Checking for headers. [mysqld] for instance. if isinstance(self.configs[key], dict): # Printing header. return_string += "[" + key + "]\n" # Printing values under the header. for config_key in self.configs[key].keys(): return_string += config_key + "=" + self.configs[key][config_key] + "\n" return return_string def dump_file(self, config_file): """ Dumps the current information in self.config into a file. :config_file: The file you want to dump the information into. :Return: The file object. """ self.debug("Entering dump_file.") if os.path.exists(config_file): shutil.move(config_file, config_file + "-premove-" + datetime.now().strftime('%y%m%d')) with open(config_file, 'a') as cnf: cnf.write(self.dump()) cnf.close() return True def pre_flight_check(self): """ Checks the MySQL you are transfering from to see if everything looks good. """ self.debug("Entering pre_flight_check.") return self.check_innodb_recovery() and self.check_mysql_replication() def check_innodb_recovery(self): """ Checks to see if MySQL is in any level of a recovery mode. From the checks I ran overall, out of 1500 vps's only 6 had innodb recovery enabled. """ self.debug("Entering check_innodb_recovery") for key in self.configs.keys(): if isinstance(self.configs[key], dict): for config_key in self.configs[key].keys(): if 'innodb_force_recovery' in config_key: self.errors.append("Innodb recovery detected. Aborting.") self.error("Innodb recovery is enabled. Aborting.") return False else: if 'innodb_force_recovery' in key: self.errors.append("Innodb recovery detected. Aborting.") self.error("Innodb recovery is enabled. Aborting.") return False return True def check_mysql_replication(self): """ Checking for MySQL replication. This is something we do not support and as such we don't want to transfer. """ self.debug("Entering check_mysql_replication.") for key in self.configs.keys(): if isinstance(self.configs[key], dict): for config_key in self.configs[key].keys(): if 'replicate' in config_key or 'sync' in config_key: self.errors.append("MySQL replication detected. Aborting.") self.error("MySQL replication detected. Aborting.") return False else: if 'replicate' in key or 'sync' in key: self.errors.append("MySQL replication detected. Aborting.") self.error("MySQL replication detected. Aborting.") return False return True def convert(self, conversion_table=None, conversion_additions=None): """ Converts the current self.config using the convert_table. :conversion_table: This will replaces the current conversion_table. :conversion_additions: This will add it to the current table. :return: Updates self.config """ self.debug("Entering Convert.") if conversion_table or conversion_additions: self.update_conversion_table(conversion_table=conversion_table, conversion_additions=conversion_additions) if not self.check_innodb_recovery() or not self.check_mysql_replication(): self.error("MySQL Config either has innodb recovery enabled or mysql replication. " "Not transfering") return False for key in self.configs.keys(): if isinstance(self.configs[key], dict): for config_key in self.configs[key].keys(): for conv_key in self.conversion_table.keys(): if config_key == conv_key and self.conversion_table[conv_key] is not None: self.configs[key][self.conversion_table[conv_key]] = self.configs[key][config_key] # Removing the old config. self.configs[key].pop(config_key) elif config_key == conv_key and self.conversion_table[conv_key] is None: # If the conversion table has nothing in it, then we add nothing and remove the old config self.configs[key].pop(config_key) else: for conv_key in self.conversion_table.keys(): if key == conv_key and self.conversion_table[conv_key] is not None: self.configs[self.conversion_table[conv_key]] = self.configs[config_key] self.configs.pop(key) elif config_key == conv_key and self.conversion_table[conv_key] is None: self.configs.pop(key) if not self.check_configurations(): self.error("Issues with the MySQL configuration, please double check the values.") return False return True def update_conversion_table(self, conversion_table=None, conversion_additions=None): """ This will update the conversion table in the class. :conversion_table: This will replaces the current conversion_table. :conversion_additions: This will add it to the current table. :return: Updates self.conversion_table and returns it. """ self.debug("Entering update_conversion_table") if conversion_table: self.conversion_table = conversion_table if conversion_additions: self.conversion_table.update(conversion_additions) def check_configurations(self): """ Validates the configurations is kosher. Uses mysqld --help and checkes for [Error] to see if everything is alright. After that, creates log files as needed. :return: True for success, False for failure. """ self.debug("Entering check_configurations.") # Checking for the existence of log file location. for x_key in self.configs.keys(): if isinstance(self.configs[x_key], dict): for y_key in self.configs[x_key].keys(): if 'log' in y_key and "/" in self.configs[x_key][y_key]: result = self.local_cmd_exec("stat %s" % self.configs[x_key][y_key]) if "No such file or directory" in result: self.warning("Creating %s for mysql" % self.configs[x_key][y_key]) self.local_cmd_exec("touch %s" % self.configs[x_key][y_key]) self.local_cmd_exec("chmod mysql:mysql %s" % self.configs[x_key][y_key]) else: if 'log' in x_key and "/" in self.configs[x_key]: result = self.local_cmd_exec("stat %s" % self.configs[x_key]) if "No such file or directory" in result: self.warning("Creating %s for mysql" % self.configs[x_key]) self.local_cmd_exec("touch %s" % self.configs[x_key]) self.local_cmd_exec("chmod mysql:mysql %s" % self.configs[x_key]) # Testing config. result = self.local_cmd_exec('/sbin/mysqld --help --verbose') for line in result.split('\n'): if '[ERROR]' in line: self.errors.append(line) self.error(line) if 'Aborting' in line: self.errors.append("MySQL Check failed. Run /sbin/mysqld --held --verbose for more information") return False return True def __str__(self): return '\n'.join(self.errors) def __repr__(self): return "<MySQLTransfer: errors=%r configs=%r >" % (self.errors, self.configs) if __name__ == '__main__': print "Stuff and things!"