|
@@ -70,12 +70,22 @@ def load_settings():
|
|
|
return yaml.load(f, Loader=yaml.FullLoader)
|
|
return yaml.load(f, Loader=yaml.FullLoader)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
+def _print(msg, end=False):
|
|
|
|
|
+ msg = msg.ljust(80)
|
|
|
|
|
+ print(f'\r{msg}', end='' if not end else '\n', flush=True)
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
class MysqldumpHandler(PipeHandler):
|
|
class MysqldumpHandler(PipeHandler):
|
|
|
""" Handle and process the stdout / stderr output from a mysqldump process
|
|
""" Handle and process the stdout / stderr output from a mysqldump process
|
|
|
"""
|
|
"""
|
|
|
_rx_prog = re.compile(r'Retrieving table structure for table (\w+)')
|
|
_rx_prog = re.compile(r'Retrieving table structure for table (\w+)')
|
|
|
_log_all = LOG_PIPES_OUTPUT
|
|
_log_all = LOG_PIPES_OUTPUT
|
|
|
- _action_name = "dumping"
|
|
|
|
|
|
|
+
|
|
|
|
|
+ def __init__(self, logger_name, level, total_prog):
|
|
|
|
|
+ super().__init__(logger_name, level)
|
|
|
|
|
+ self.total_prog = total_prog
|
|
|
|
|
+ self.prog = 0
|
|
|
|
|
+ self._last_logged = ""
|
|
|
|
|
|
|
|
def process(self, line):
|
|
def process(self, line):
|
|
|
""" Process the last line that was read
|
|
""" Process the last line that was read
|
|
@@ -84,25 +94,32 @@ class MysqldumpHandler(PipeHandler):
|
|
|
if SHOW_PROGRESSION:
|
|
if SHOW_PROGRESSION:
|
|
|
match = self._rx_prog.search(line)
|
|
match = self._rx_prog.search(line)
|
|
|
if match:
|
|
if match:
|
|
|
- logger.debug('... %s %s', self._action_name, match.group(1))
|
|
|
|
|
- print('.', end="", flush=True)
|
|
|
|
|
|
|
+ self.log_new_table(match.group(1), "dumping")
|
|
|
if self._log_all:
|
|
if self._log_all:
|
|
|
logger.debug(line)
|
|
logger.debug(line)
|
|
|
|
|
|
|
|
|
|
+ def log_new_table(self, tname, action_name=""):
|
|
|
|
|
+ if tname == self._last_logged:
|
|
|
|
|
+ return
|
|
|
|
|
+ self.prog += 1
|
|
|
|
|
+ logger.debug('... %s %s', action_name, tname)
|
|
|
|
|
+ _print(f'{action_name} `{tname}` [{self.prog} / {self.total_prog}]')
|
|
|
|
|
+ self._last_logged = tname
|
|
|
|
|
+
|
|
|
|
|
+ def log_end(self):
|
|
|
|
|
+ _print(f'\r-- done --', end=True)
|
|
|
|
|
+
|
|
|
def close(self):
|
|
def close(self):
|
|
|
""" Close the write end of the pipe.
|
|
""" Close the write end of the pipe.
|
|
|
"""
|
|
"""
|
|
|
- print('', flush=True)
|
|
|
|
|
super().close()
|
|
super().close()
|
|
|
|
|
|
|
|
-
|
|
|
|
|
class MysqlHandler(MysqldumpHandler):
|
|
class MysqlHandler(MysqldumpHandler):
|
|
|
""" Handle and process the stdout / stderr output from a mysql process
|
|
""" Handle and process the stdout / stderr output from a mysql process
|
|
|
"""
|
|
"""
|
|
|
_rx_prog = re.compile(r'^((?:CREATE TABLE )|(?:INSERT INTO ))`(\w+)`')
|
|
_rx_prog = re.compile(r'^((?:CREATE TABLE )|(?:INSERT INTO ))`(\w+)`')
|
|
|
_log_all = LOG_PIPES_OUTPUT
|
|
_log_all = LOG_PIPES_OUTPUT
|
|
|
_action_name = "restoring"
|
|
_action_name = "restoring"
|
|
|
- _last_logged = ""
|
|
|
|
|
|
|
|
|
|
def process(self, line):
|
|
def process(self, line):
|
|
|
""" Process the last line that was read
|
|
""" Process the last line that was read
|
|
@@ -111,13 +128,11 @@ class MysqlHandler(MysqldumpHandler):
|
|
|
if SHOW_PROGRESSION:
|
|
if SHOW_PROGRESSION:
|
|
|
match = self._rx_prog.search(line)
|
|
match = self._rx_prog.search(line)
|
|
|
if match:
|
|
if match:
|
|
|
- tname = match.group(2)
|
|
|
|
|
- if tname != self._last_logged:
|
|
|
|
|
- logger.debug('... %s %s %s', self._action_name,
|
|
|
|
|
- 'structure of' if 'CREATE' in match.group(1) else 'data of',
|
|
|
|
|
- tname)
|
|
|
|
|
- print('.', end="", flush=True)
|
|
|
|
|
- self._last_logged = tname
|
|
|
|
|
|
|
+ action_name = "restoring {}".format('structure of'
|
|
|
|
|
+ if 'CREATE' in match.group(1)
|
|
|
|
|
+ else 'data of')
|
|
|
|
|
+ self.log_new_table(match.group(2), action_name)
|
|
|
|
|
+
|
|
|
if self._log_all:
|
|
if self._log_all:
|
|
|
logger.debug(line)
|
|
logger.debug(line)
|
|
|
|
|
|
|
@@ -329,15 +344,15 @@ class CloningOperation:
|
|
|
return cmd
|
|
return cmd
|
|
|
|
|
|
|
|
@staticmethod
|
|
@staticmethod
|
|
|
- def _run_piped_processes(dump_cmd, restore_cmd):
|
|
|
|
|
|
|
+ def _run_piped_processes(dump_cmd, restore_cmd, tbl_count):
|
|
|
""" Run the dump and the restore commands by piping them
|
|
""" Run the dump and the restore commands by piping them
|
|
|
The output of the mysqldump process is piped into the input of the mysql one
|
|
The output of the mysqldump process is piped into the input of the mysql one
|
|
|
"""
|
|
"""
|
|
|
logger.debug(">>> Dump command: %s", " ".join(map(str, dump_cmd)))
|
|
logger.debug(">>> Dump command: %s", " ".join(map(str, dump_cmd)))
|
|
|
logger.debug(">>> Piped into: %s", " ".join(map(str, restore_cmd)))
|
|
logger.debug(">>> Piped into: %s", " ".join(map(str, restore_cmd)))
|
|
|
|
|
|
|
|
- mysqldump_handler = MysqldumpHandler(logger.name)
|
|
|
|
|
- mysql_handler = MysqlHandler(logger.name)
|
|
|
|
|
|
|
+ mysqldump_handler = MysqldumpHandler(logger.name, logging.INFO, tbl_count)
|
|
|
|
|
+ mysql_handler = MysqlHandler(logger.name, logging.INFO, tbl_count)
|
|
|
try:
|
|
try:
|
|
|
# noinspection PyTypeChecker
|
|
# noinspection PyTypeChecker
|
|
|
with Popen(restore_cmd, stdin=PIPE, stdout=mysql_handler, stderr=mysql_handler) as mysql:
|
|
with Popen(restore_cmd, stdin=PIPE, stdout=mysql_handler, stderr=mysql_handler) as mysql:
|
|
@@ -350,6 +365,8 @@ class CloningOperation:
|
|
|
if mysql.returncode:
|
|
if mysql.returncode:
|
|
|
raise RuntimeError('mysql returned a non zero code')
|
|
raise RuntimeError('mysql returned a non zero code')
|
|
|
|
|
|
|
|
|
|
+ mysql_handler.log_end()
|
|
|
|
|
+
|
|
|
except (OSError, RuntimeError, CalledProcessError) as e:
|
|
except (OSError, RuntimeError, CalledProcessError) as e:
|
|
|
logger.error("Execution failed: %s", e)
|
|
logger.error("Execution failed: %s", e)
|
|
|
raise RuntimeError(f"An error happened at runtime: {e}")
|
|
raise RuntimeError(f"An error happened at runtime: {e}")
|
|
@@ -409,11 +426,11 @@ class CloningOperation:
|
|
|
try:
|
|
try:
|
|
|
if dump_structure_for:
|
|
if dump_structure_for:
|
|
|
logger.info(f"Cloning structure for {len(dump_structure_for)} tables (on {len(tables)})...")
|
|
logger.info(f"Cloning structure for {len(dump_structure_for)} tables (on {len(tables)})...")
|
|
|
- self._run_piped_processes(dump_structure_cmd, restore_cmd)
|
|
|
|
|
|
|
+ self._run_piped_processes(dump_structure_cmd, restore_cmd, len(dump_structure_for))
|
|
|
|
|
|
|
|
if dump_data_for:
|
|
if dump_data_for:
|
|
|
logger.info(f"Cloning data for {len(dump_data_for)} tables (on {len(tables)})...")
|
|
logger.info(f"Cloning data for {len(dump_data_for)} tables (on {len(tables)})...")
|
|
|
- self._run_piped_processes(dump_data_cmd, restore_cmd)
|
|
|
|
|
|
|
+ self._run_piped_processes(dump_data_cmd, restore_cmd, len(dump_data_for))
|
|
|
|
|
|
|
|
logger.info(f"Cloning views...")
|
|
logger.info(f"Cloning views...")
|
|
|
self.from_server.set_active_db(self.dbname)
|
|
self.from_server.set_active_db(self.dbname)
|