Файловый менеджер - Редактировать - /opt/cpmigrate/environments/mysql.py
Ðазад
"""MySQL Environment module""" import os import subprocess from environments.base import Environment, FatalMigrationFailure class MySQL(Environment): """ Checks the origin server's MySQL version and compares it to this server. Checks to ensure MySQL is up and running on the origin and locally. Migrates all databases and MySQL users for all users on the migration. Replicates grants and synchronizes the MySQL password for all users. """ def __init__(self): Environment.__init__(self) self.resync_relevant = True self.require_prefix = False self.origin_version = None self.local_version = None self.local_databases = {} self.ignore_databases = [ 'cphulkd', 'information_schema', 'leechprotect', 'modsec', 'mysql', 'performance_schema', 'roundcube', 'logaholicDB_ce6clone', 'perl', 'sys', ] self.origin_databases = [] self.dumped_databases = [] self.db_dump_location = '/root/db_dump_cpmigrate/' self.owned_databases = {} self.mysql_users = {} def check(self, _): if self.xfer.check_should_resync(): if not self.xfer.args.get('resync_databases'): return self.fix_permissions() self.check_origin_mysql_status() self.check_local_mysql_status() self.get_origin_prefix_setting() self.get_origin_databases_info() self.get_origin_database_list() self.check_local_mysql_version() self.check_origin_mysql_version() self.get_users() self.check_actions() def check_actions(self): """Based on all the gathered information, determines what actions need to be performed. """ if self.origin_version and self.local_version: if self.origin_version != self.local_version: self.actions.append( "!! Different MySQL versions detected. Origin version is " f"{self.origin_version}. Target server version is " f"{self.local_version}. Please be aware of the " "differences between the MySQL versions." ) if len(self.origin_databases) > 0: db_list = ', '.join(self.origin_databases) self.actions.append( f"+ Migrate the following databases from origin: {db_list}" ) if len(self.mysql_users) > 0: user_list = ', '.join(self.mysql_users) self.actions.append( f"+ Migrate the following MySQL users: {user_list}" ) def get_origin_databases_info(self): """ Retrieves origin databases and stores it. """ self.info("Obtaining full list of origin databases.") data = self.xfer.origin_whmapi_call('list_databases') databases = data.get('payload', {}) if not databases: self.info("No databases detected.") for database in databases: db_engine = database.get('engine') db_name = database.get('name') db_owner = database.get('cpuser') if self.xfer.get_user(db_owner): self.owned_databases[db_name] = { 'owner': db_owner, 'engine': db_engine, } else: self.ignore_databases.append(db_name) self.info( f"Ignoring database {db_name}, owned by {db_owner} which " "is not being migrated." ) def get_origin_database_list(self): """ Obtains the list of all databases on the origin server. """ ret_code, databases = self.xfer.origin_command( '/usr/bin/mysql -BNe "show databases"', sleep=1, command_out=subprocess.PIPE, quiet=True, ) if ret_code == 0: for database in databases: database = database.replace('\n', '') self.check_database_owned(database) else: self.error("Failed to obtain database list from origin.", note=True) def check_database_exists(self, database_name): """Checks if the provided database exists exists on the target server. Args: database_name (str): name of database """ ret_code, databases = self.xfer.local_command( ['/usr/bin/mysql', '-B', '-N', '-e', "show databases"], sleep=1, command_out=subprocess.PIPE, quiet=True, ) if ret_code == 0: for database in databases: database = database.replace('\n', '') if database == database_name: return True return False def check_migrated_databases(self): """ Confirms all origin databases were migrated and restored. """ self.info("Confirming databases are migrated.") ret_code, databases = self.xfer.local_command( ['/usr/bin/mysql', '-B', '-N', '-e', "show databases"], sleep=1, command_out=subprocess.PIPE, quiet=True, ) local_databases = [] if ret_code == 0: for database in databases: database = database.replace('\n', '') local_databases.append(database) for database in self.origin_databases: if database not in self.ignore_databases: if database not in local_databases: self.error( f"Database {database} is missing and/or failed " "to migrate!", note=True, ) self.info("Database confirmation has been completed.") else: self.error("Failed to obtain database list from target.", note=True) def check_database_owned(self, database): """ Checks to see if the database is owned or not. """ if database not in self.ignore_databases: if database not in self.owned_databases: self.warning( f"Could not find owner for database: {database}", note=True ) self.origin_databases.append(database) def check_origin_mysql_status(self): """Checks if MySQL is running on the origin.""" ret_code, _ = self.xfer.origin_command( '/usr/bin/mysqladmin status', sleep=1, quiet=True ) if ret_code != 0: raise FatalMigrationFailure( "MySQL is not running or is crashed on the origin. " "Databases cannot be migrated unless MySQL is running. " "If you want to not migrate MySQL, please skip the mysql env." ) def check_local_mysql_status(self): """Checks if MySQL is running on the target server.""" ret_code, _ = self.xfer.local_command( ['/usr/bin/mysqladmin', 'status'], sleep=1, quiet=True ) if ret_code != 0: raise FatalMigrationFailure( "MySQL is not running or is crashed locally. " "Databases cannot be migrated unless MySQL is running. " "If you want to not migrate MySQL, please skip the mysql env." ) def extract_version(self, data): """Extracts the MySQL version from the output of mysql -V""" vsplit = data.split() vstring = vsplit[4].split('.') version = None if len(vstring) == 3: if 'MariaDB' in vstring[2]: version = f"MariaDB {vstring[0]}.{vstring[1]}" elif 'MySQL' in vstring[2]: version = f"MySQL {vstring[0]}.{vstring[1]}" else: try: version = f"MySQL {vstring[0]}.{vstring[1]}" except IndexError: self.error(f"Abnormal MySQL version detected: {data}") return version def check_local_mysql_version(self): """Checks the local MySQL version.""" ret_code, out = self.xfer.local_command( ['/usr/bin/mysql', '-V'], sleep=1, command_out=subprocess.PIPE, quiet=True, ) if ret_code == 0: version = self.extract_version(out[0]) if version: self.local_version = version self.info(f"Target server is using {self.local_version}.") else: self.error("Failed to retrieve local MySQL version.") def check_origin_mysql_version(self): """Checks the origin MySQL version.""" ret_code, out = self.xfer.origin_command( '/usr/bin/mysql -V', sleep=1, command_out=subprocess.PIPE, quiet=True, ) if ret_code == 0: version = self.extract_version(out[0]) if version: self.origin_version = version self.info(f"Origin server is using {self.origin_version}.") else: self.error("Failed to retrieve origin MySQL version.") def fix_permissions(self): """Fixes any invalid permissions of /var/cpanel/databases files. This is in response to several internal containers with invalid group ownership. """ self.info("Fixing permissions of cPanel database files on origin.") _, out = self.xfer.origin_command( r"for user in $(awk -F: '{print $1}' /etc/trueuserowners); do " r"find /var/cpanel/databases -type f " r"\( -name $user.json -o -name $user.yaml \) -exec " r"chown -v root.$user {} +; done", sleep=1, command_out=subprocess.PIPE, quiet=True, ) for line in out: self.info(line) def run_resync(self): """Called when MySQL re-sync has been called.""" if self.dump_databases(): self.migrate_databases() self.restore_databases() def post_transfer(self): if self.dump_databases(): self.update_prefix_setting(False) self.create_databases() self.migrate_databases() self.restore_databases() self.fix_dbmap() self.check_migrated_databases() self.update_prefix_setting(self.require_prefix) def create_origin_dump_location(self): """Creates the dump location on the origin server. Returns: bool: whether or not we successfully created the dump location """ self.info(f"Making dump location on origin: {self.db_dump_location}") ret_code, _ = self.xfer.origin_command( f"/bin/mkdir -p {self.db_dump_location}", sleep=1, quiet=True ) if ret_code == 0: return True self.error("Failed to make dump location on origin.") return False def create_target_dump_location(self): """ Creates the dump location on the target. """ self.info(f"Creating dump location on target: {self.db_dump_location}") try: os.makedirs(self.db_dump_location) except FileExistsError: self.info("Dump location already exists on target.") def dump_databases(self): """Iterates through all databases to get dumped. Returns: bool: whether or not we failed to dump databases """ if not self.create_origin_dump_location(): self.error("Databases could not be dumped.", note=True) return False self.create_target_dump_location() for database in self.origin_databases: self.dump_database(database) return True def dump_database(self, db_name): """ Dumps the MySQL database on the origin server. """ self.info(f"Dumping database: {db_name}") db_path = os.path.join(self.db_dump_location, f"{db_name}.sql") ret_code, _ = self.xfer.origin_command( "/usr/bin/mysqldump --add-drop-table " "--max-allowed-packet=268435456 " f"--force {db_name} " f"> {db_path}", sleep=2, ) if ret_code == 0: self.info("Database dump completed successfully.") else: self.error( ( f"Error occurred while dumping database: {db_name}" "\n(The database will either be missing tables or empty)" ), note=True, ) self.dumped_databases.append(db_name) def create_databases(self): """ Creates all databases that existed on the origin server. """ self.info("Creating missing databases from migration.") for database in self.origin_databases: if self.check_database_exists(database): self.info(f"Skipping creating {database}, already exists.") continue if database in self.owned_databases: owner = self.owned_databases[database]['owner'] user = self.xfer.get_user(owner) success, data = user.uapi_call( 'Mysql::create_database', {'name': database} ) name = user.name if success: self.info( f"Successfully created database: {database} for {name}" ) else: self.error( f"Failed to create {database} for {name}. ({data})" ) else: ret_code, _ = self.xfer.local_command( [ '/usr/bin/mysql', '-B', '-N', '-e', f'create database {database}', ] ) if ret_code == 0: self.info( f"Successfully created unowned database: {database}" ) self.info("Finished creating databases from migration.") def migrate_databases(self): """ Migrates all databases that were dumped to the target server. """ self.info("Migrating databases to target server.") success = self.xfer.do_rsync( origin=f"{self.xfer.origin_server}:{self.db_dump_location}", destination=self.db_dump_location, name="mv_databases", ) if success: self.info("Database migration successful.") else: self.error( "Failed to migrate databases to target server.", note=True ) def restore_databases(self): """ Iterates through list of databases to restore on the target server. """ self.info("Restoring databases on target server.") for database in self.dumped_databases: self.restore_database(database) self.info("Finished restoring databases on target server.") def restore_database(self, name): """ Restores the specific database on the target server. """ self.info(f"Restoring database: {name}") db_path = os.path.join(self.db_dump_location, f"{name}.sql") if os.path.exists(db_path): with open(db_path, encoding="utf-8") as db_in: ret_code, _ = self.xfer.local_command( ['/usr/bin/mysql', name], command_in=db_in ) if ret_code == 0: self.info(f"Imported {name} successfully.") else: self.error( f"Failed to import {name}. SQL Path: {db_path}", note=True, ) else: self.error(f"Could not find {name} to restore?", note=True) def get_users(self): """ Obtains list of all MySQL users for all users on the origin server. """ for user in self.xfer.users: data = self.xfer.origin_uapi_call(user.name, 'Mysql', 'list_users') for mysql_user in data: name = mysql_user.get('user') databases = mysql_user.get('databases') if name not in self.mysql_users: self.mysql_users[name] = { 'owner': user.name, 'databases': {}, 'password': '', } for database in databases: self.mysql_users[name]['databases'][database] = [] def get_origin_prefix_setting(self): """Obtains the origin prefix setting for databases.""" response = self.xfer.origin_whmapi_call( 'get_tweaksetting', {'key': 'database_prefix'} ) self.require_prefix = bool(response.get('tweaksetting').get('value')) def update_prefix_setting(self, require_prefix): """Updates the prefix setting on the target server to what was provided. Args: require_prefix (bool): whether or not to require prefix """ self.info(f"Updating database_prefix to {require_prefix}.") self.xfer.whmapi_call( 'set_tweaksetting', {'key': 'database_prefix', 'value': str(int(require_prefix))}, ) def fix_dbmap(self): """Iterates through databases and users in order to fix their dbmaps. This is to ensure that everything is properly owned by the users, in the event that cPanel decided to be dumb. """ users = {} for user in self.xfer.users: users[user.name] = {'databases': [], 'users': []} for database in self.owned_databases: owner = self.owned_databases[database]['owner'] users[owner]['databases'].append(database) for mysql_user, dbinfo in self.mysql_users: owner = dbinfo['owner'] users[owner]['users'].append(mysql_user) for user, dbinfo in users.items(): self.info(f"Fixing DBMap for {user}.") user_list = ', '.join(dbinfo['users']) db_list = ', '.join(dbinfo['databases']) command = [ '/usr/local/cpanel/bin/dbmaptool', user, '--type', 'mysql', ] if user_list != '': command.append('--dbusers') command.append(user_list) if db_list != '': command.append('--dbs') command.append(db_list) self.debug(f"DBMap Command: {command}") ret_code, _ = self.xfer.local_command(command, sleep=1, quiet=True) if ret_code != 0: self.error(f"Failed to run DBMap tool for {user}.", note=True) def capture_state(self): state = { 'origin_databases': self.origin_databases, 'dumped_databases': self.dumped_databases, 'owned_databases': self.owned_databases, } return super().capture_state(state) def load_state(self, loadstate): self.origin_databases = loadstate.get('origin_databases', []) self.dumped_databases = loadstate.get('dumped_databases', []) self.owned_databases = loadstate.get('owned_databases', {}) self.require_prefix = loadstate.get('require_prefix', False) super().load_state(loadstate)
| ver. 1.1 | |
.
| PHP 8.3.30 | Ð“ÐµÐ½ÐµÑ€Ð°Ñ†Ð¸Ñ Ñтраницы: 0 |
proxy
|
phpinfo
|
ÐаÑтройка