def test_recover_waiting_for_wals(self, backup_info_mock, rsync_copy_controller_mock, output_mock, rsync_pgdata_mock, unix_remote_command_mock, tmpdir): # This backup is waiting for WALs and it remains in that status # even after having copied the data files backup_info_mock.WAITING_FOR_WALS = "WAITING_FOR_WALS" backup_info_mock.return_value.status = BackupInfo.WAITING_FOR_WALS backup_info = testing_helpers.build_test_backup_info() backup_manager = testing_helpers.build_backup_manager() executor = RecoveryExecutor(backup_manager) backup_info.status = BackupInfo.WAITING_FOR_WALS destination = tmpdir.mkdir('destination').strpath with closing(executor): executor.recover(backup_info, destination, standby_mode=None) # The backup info has been read again backup_info_mock.assert_called() # The following two warning messages have been emitted output_mock.warning.assert_has_calls([ mock.call( "IMPORTANT: You have requested a recovery operation for " "a backup that does not have yet all the WAL files that " "are required for consistency."), mock.call( "IMPORTANT: The backup we have recovered IS NOT " "VALID. Required WAL files for consistency are " "missing. Please verify that WAL archiving is " "working correctly or evaluate using the 'get-wal' " "option for recovery")]) # In the following test case, the backup will be validated during # the copy of the data files, so there is no need for the warning # message at the end of the recovery process to be emitted again output_mock.warning.reset_mock() backup_info_mock.return_value.status = BackupInfo.DONE with closing(executor): executor.recover(backup_info, destination, standby_mode=None) # The backup info has been read again backup_info_mock.assert_called() # The following two warning messages have been emitted output_mock.warning.assert_has_calls([ mock.call( "IMPORTANT: You have requested a recovery operation for " "a backup that does not have yet all the WAL files that " "are required for consistency.")])
def recover(self, backup_info, dest, tablespaces=None, target_tli=None, target_time=None, target_xid=None, target_name=None, exclusive=False, remote_command=None): """ Performs a recovery of a backup :param barman.infofile.BackupInfo backup_info: the backup to recover :param str dest: the destination directory :param dict[str,str]|None tablespaces: a tablespace name -> location map (for relocation) :param str|None target_tli: the target timeline :param str|None target_time: the target time :param str|None target_xid: the target xid :param str|None target_name: the target name created previously with pg_create_restore_point() function call :param bool exclusive: whether the recovery is exclusive or not :param str|None remote_command: default None. The remote command to recover the base backup, in case of remote backup. """ # Archive every WAL files in the incoming directory of the server self.server.archive_wal(verbose=False) # Delegate the recovery operation to a RecoveryExecutor object executor = RecoveryExecutor(self) recovery_info = executor.recover(backup_info, dest, tablespaces, target_tli, target_time, target_xid, target_name, exclusive, remote_command) # Output recovery results output.result('recovery', recovery_info['results'])
def test_recover_standby_mode(self, tmpdir): backup_info = testing_helpers.build_test_backup_info() backup_manager = testing_helpers.build_backup_manager() executor = RecoveryExecutor(backup_manager) backup_info.version = 90300 destination = tmpdir.mkdir('destination').strpath # If standby mode is not enabled, recovery.conf is not generated executor._prepare_tablespaces = MagicMock() executor._backup_copy = MagicMock() executor._xlog_copy = MagicMock() executor._generate_recovery_conf = MagicMock() with closing(executor): executor.recover(backup_info, destination, standby_mode=None) executor._generate_recovery_conf.assert_not_called() # If standby mode is enabled, recovery.conf is generated executor._prepare_tablespaces.reset_mock() executor._backup_copy.reset_mock() executor._xlog_copy.reset_mock() executor._generate_recovery_conf.reset_mock() with closing(executor): executor.recover(backup_info, destination, standby_mode=True) executor._generate_recovery_conf.assert_called() # If standby mode is passed but PostgreSQL is older than 9.0, # we must raise an exception backup_info.version = 80000 with pytest.raises(RecoveryStandbyModeException): with closing(executor): executor.recover(backup_info, destination, standby_mode=True)
def test_recovery(self, remote_cmd_mock, rsync_pg_mock, tmpdir): """ Test the execution of a recovery """ # Prepare basic directory/files structure dest = tmpdir.mkdir('destination') base = tmpdir.mkdir('base') wals = tmpdir.mkdir('wals') backup_info = testing_helpers.build_test_backup_info(tablespaces=[]) backup_info.config.basebackups_directory = base.strpath backup_info.config.wals_directory = wals.strpath backup_info.version = 90400 datadir = base.mkdir(backup_info.backup_id).mkdir('data') backup_info.pgdata = datadir.strpath postgresql_conf_local = datadir.join('postgresql.conf') postgresql_auto_local = datadir.join('postgresql.auto.conf') postgresql_conf_local.write('archive_command = something\n' 'data_directory = something') postgresql_auto_local.write('archive_command = something\n' 'data_directory = something') shutil.copy2(postgresql_conf_local.strpath, dest.strpath) shutil.copy2(postgresql_auto_local.strpath, dest.strpath) # Build an executor server = testing_helpers.build_real_server( global_conf={ "barman_lock_directory": tmpdir.mkdir('lock').strpath }, main_conf={ "wals_directory": wals.strpath }) executor = RecoveryExecutor(server.backup_manager) # test local recovery rec_info = executor.recover(backup_info, dest.strpath, None, None, None, None, None, True, None) # remove not usefull keys from the result del rec_info['cmd'] sys_tempdir = rec_info['tempdir'] assert rec_info == { 'rsync': None, 'tempdir': sys_tempdir, 'wal_dest': dest.join('pg_xlog').strpath, 'recovery_dest': 'local', 'destination_path': dest.strpath, 'temporary_configuration_files': [ dest.join('postgresql.conf').strpath, dest.join('postgresql.auto.conf').strpath], 'results': { 'delete_barman_xlog': False, 'get_wal': False, 'changes': [ Assertion._make([ 'postgresql.conf', 0, 'archive_command', 'false']), Assertion._make([ 'postgresql.auto.conf', 0, 'archive_command', 'false'])], 'warnings': [ Assertion._make([ 'postgresql.conf', 2, 'data_directory', 'something']), Assertion._make([ 'postgresql.auto.conf', 2, 'data_directory', 'something'])]}, 'target_epoch': None, 'configuration_files': [ 'postgresql.conf', 'postgresql.auto.conf'], 'target_datetime': None, 'safe_horizon': None, 'is_pitr': False, 'get_wal': False, } # test remote recovery rec_info = executor.recover(backup_info, dest.strpath, {}, None, None, None, None, True, "remote@command") # remove not usefull keys from the result del rec_info['cmd'] del rec_info['rsync'] sys_tempdir = rec_info['tempdir'] assert rec_info == { 'tempdir': sys_tempdir, 'wal_dest': dest.join('pg_xlog').strpath, 'recovery_dest': 'remote', 'destination_path': dest.strpath, 'temporary_configuration_files': [ os.path.join(sys_tempdir, 'postgresql.conf'), os.path.join(sys_tempdir, 'postgresql.auto.conf')], 'results': { 'delete_barman_xlog': False, 'get_wal': False, 'changes': [ Assertion._make([ 'postgresql.conf', 0, 'archive_command', 'false']), Assertion._make([ 'postgresql.auto.conf', 0, 'archive_command', 'false'])], 'warnings': [ Assertion._make([ 'postgresql.conf', 2, 'data_directory', 'something']), Assertion._make([ 'postgresql.auto.conf', 2, 'data_directory', 'something'])]}, 'target_epoch': None, 'configuration_files': [ 'postgresql.conf', 'postgresql.auto.conf'], 'target_datetime': None, 'safe_horizon': None, 'is_pitr': False, 'get_wal': False, } # test failed rsync rsync_pg_mock.side_effect = CommandFailedException() with pytest.raises(CommandFailedException): executor.recover(backup_info, dest.strpath, {}, None, None, None, None, True, "remote@command")
def test_recovery(self, remote_cmd_mock, rsync_pg_mock, copy_controller_mock, tmpdir): """ Test the execution of a recovery """ # Prepare basic directory/files structure dest = tmpdir.mkdir('destination') base = tmpdir.mkdir('base') wals = tmpdir.mkdir('wals') backup_info = testing_helpers.build_test_backup_info(tablespaces=[]) backup_info.config.basebackups_directory = base.strpath backup_info.config.wals_directory = wals.strpath backup_info.version = 90400 datadir = base.mkdir(backup_info.backup_id).mkdir('data') backup_info.pgdata = datadir.strpath postgresql_conf_local = datadir.join('postgresql.conf') postgresql_auto_local = datadir.join('postgresql.auto.conf') postgresql_conf_local.write('archive_command = something\n' 'data_directory = something') postgresql_auto_local.write('archive_command = something\n' 'data_directory = something') shutil.copy2(postgresql_conf_local.strpath, dest.strpath) shutil.copy2(postgresql_auto_local.strpath, dest.strpath) # Avoid triggering warning for missing config files datadir.ensure('pg_hba.conf') datadir.ensure('pg_ident.conf') # Build an executor server = testing_helpers.build_real_server( global_conf={ "barman_lock_directory": tmpdir.mkdir('lock').strpath }, main_conf={"wals_directory": wals.strpath}) executor = RecoveryExecutor(server.backup_manager) # test local recovery rec_info = executor.recover(backup_info, dest.strpath, None, None, None, None, None, True, None) # remove not usefull keys from the result del rec_info['cmd'] sys_tempdir = rec_info['tempdir'] assert rec_info == { 'rsync': None, 'tempdir': sys_tempdir, 'wal_dest': dest.join('pg_xlog').strpath, 'recovery_dest': 'local', 'destination_path': dest.strpath, 'temporary_configuration_files': [ dest.join('postgresql.conf').strpath, dest.join('postgresql.auto.conf').strpath ], 'results': { 'delete_barman_xlog': False, 'get_wal': False, 'changes': [ Assertion._make( ['postgresql.conf', 0, 'archive_command', 'false']), Assertion._make([ 'postgresql.auto.conf', 0, 'archive_command', 'false' ]) ], 'missing_files': [], 'warnings': [ Assertion._make( ['postgresql.conf', 2, 'data_directory', 'something']), Assertion._make([ 'postgresql.auto.conf', 2, 'data_directory', 'something' ]) ] }, 'target_epoch': None, 'configuration_files': ['postgresql.conf', 'postgresql.auto.conf'], 'target_datetime': None, 'safe_horizon': None, 'is_pitr': False, 'get_wal': False, } # test remote recovery rec_info = executor.recover(backup_info, dest.strpath, {}, None, None, None, None, True, "remote@command") # remove not useful keys from the result del rec_info['cmd'] del rec_info['rsync'] sys_tempdir = rec_info['tempdir'] assert rec_info == { 'tempdir': sys_tempdir, 'wal_dest': dest.join('pg_xlog').strpath, 'recovery_dest': 'remote', 'destination_path': dest.strpath, 'temporary_configuration_files': [ os.path.join(sys_tempdir, 'postgresql.conf'), os.path.join(sys_tempdir, 'postgresql.auto.conf') ], 'results': { 'delete_barman_xlog': False, 'get_wal': False, 'changes': [ Assertion._make( ['postgresql.conf', 0, 'archive_command', 'false']), Assertion._make([ 'postgresql.auto.conf', 0, 'archive_command', 'false' ]) ], 'missing_files': [], 'warnings': [ Assertion._make( ['postgresql.conf', 2, 'data_directory', 'something']), Assertion._make([ 'postgresql.auto.conf', 2, 'data_directory', 'something' ]) ] }, 'target_epoch': None, 'configuration_files': ['postgresql.conf', 'postgresql.auto.conf'], 'target_datetime': None, 'safe_horizon': None, 'is_pitr': False, 'get_wal': False, } # test failed rsync rsync_pg_mock.side_effect = CommandFailedException() with pytest.raises(CommandFailedException): executor.recover(backup_info, dest.strpath, {}, None, None, None, None, True, "remote@command")
def recover(self, backup_info, dest, tablespaces=None, remote_command=None, **kwargs): """ Performs a recovery of a backup :param barman.infofile.LocalBackupInfo backup_info: the backup to recover :param str dest: the destination directory :param dict[str,str]|None tablespaces: a tablespace name -> location map (for relocation) :param str|None remote_command: default None. The remote command to recover the base backup, in case of remote backup. :kwparam str|None target_tli: the target timeline :kwparam str|None target_time: the target time :kwparam str|None target_xid: the target xid :kwparam str|None target_lsn: the target LSN :kwparam str|None target_name: the target name created previously with pg_create_restore_point() function call :kwparam bool|None target_immediate: end recovery as soon as consistency is reached :kwparam bool exclusive: whether the recovery is exclusive or not :kwparam str|None target_action: default None. The recovery target action :kwparam bool|None standby_mode: the standby mode if needed """ # Archive every WAL files in the incoming directory of the server self.server.archive_wal(verbose=False) # Delegate the recovery operation to a RecoveryExecutor object executor = RecoveryExecutor(self) # Run the pre_recovery_script if present. script = HookScriptRunner(self, 'recovery_script', 'pre') script.env_from_recover(backup_info, dest, tablespaces, remote_command, **kwargs) script.run() # Run the pre_recovery_retry_script if present. retry_script = RetryHookScriptRunner(self, 'recovery_retry_script', 'pre') retry_script.env_from_recover(backup_info, dest, tablespaces, remote_command, **kwargs) retry_script.run() # Execute the recovery. # We use a closing context to automatically remove # any resource eventually allocated during recovery. with closing(executor): recovery_info = executor.recover(backup_info, dest, tablespaces=tablespaces, remote_command=remote_command, **kwargs) # Run the post_recovery_retry_script if present. try: retry_script = RetryHookScriptRunner(self, 'recovery_retry_script', 'post') retry_script.env_from_recover(backup_info, dest, tablespaces, remote_command, **kwargs) retry_script.run() except AbortedRetryHookScript as e: # Ignore the ABORT_STOP as it is a post-hook operation _logger.warning( "Ignoring stop request after receiving " "abort (exit code %d) from post-recovery " "retry hook script: %s", e.hook.exit_status, e.hook.script) # Run the post-recovery-script if present. script = HookScriptRunner(self, 'recovery_script', 'post') script.env_from_recover(backup_info, dest, tablespaces, remote_command, **kwargs) script.run() # Output recovery results output.result('recovery', recovery_info['results'])
def test_recovery(self, remote_cmd_mock, rsync_pg_mock, copy_controller_mock, tmpdir): """ Test the execution of a recovery """ # Prepare basic directory/files structure dest = tmpdir.mkdir("destination") base = tmpdir.mkdir("base") wals = tmpdir.mkdir("wals") backup_info = testing_helpers.build_test_backup_info(tablespaces=[]) backup_info.config.basebackups_directory = base.strpath backup_info.config.wals_directory = wals.strpath backup_info.version = 90400 datadir = base.mkdir(backup_info.backup_id).mkdir("data") backup_info.pgdata = datadir.strpath postgresql_conf_local = datadir.join("postgresql.conf") postgresql_auto_local = datadir.join("postgresql.auto.conf") postgresql_conf_local.write("archive_command = something\n" "data_directory = something") postgresql_auto_local.write("archive_command = something\n" "data_directory = something") shutil.copy2(postgresql_conf_local.strpath, dest.strpath) shutil.copy2(postgresql_auto_local.strpath, dest.strpath) # Avoid triggering warning for missing config files datadir.ensure("pg_hba.conf") datadir.ensure("pg_ident.conf") # Build an executor server = testing_helpers.build_real_server( global_conf={"barman_lock_directory": tmpdir.mkdir("lock").strpath}, main_conf={"wals_directory": wals.strpath}, ) executor = RecoveryExecutor(server.backup_manager) # test local recovery rec_info = executor.recover(backup_info, dest.strpath, None, None, None, None, None, True, None) # remove not usefull keys from the result del rec_info["cmd"] sys_tempdir = rec_info["tempdir"] assert rec_info == { "rsync": None, "tempdir": sys_tempdir, "wal_dest": dest.join("pg_xlog").strpath, "recovery_dest": "local", "destination_path": dest.strpath, "temporary_configuration_files": [ dest.join("postgresql.conf").strpath, dest.join("postgresql.auto.conf").strpath, ], "results": { "delete_barman_xlog": False, "get_wal": False, "changes": [ Assertion._make(["postgresql.conf", 0, "archive_command", "false"]), Assertion._make(["postgresql.auto.conf", 0, "archive_command", "false"]), ], "missing_files": [], "warnings": [ Assertion._make(["postgresql.conf", 2, "data_directory", "something"]), Assertion._make(["postgresql.auto.conf", 2, "data_directory", "something"]), ], }, "target_epoch": None, "configuration_files": ["postgresql.conf", "postgresql.auto.conf"], "target_datetime": None, "safe_horizon": None, "is_pitr": False, "get_wal": False, } # test remote recovery rec_info = executor.recover(backup_info, dest.strpath, {}, None, None, None, None, True, "remote@command") # remove not useful keys from the result del rec_info["cmd"] del rec_info["rsync"] sys_tempdir = rec_info["tempdir"] assert rec_info == { "tempdir": sys_tempdir, "wal_dest": dest.join("pg_xlog").strpath, "recovery_dest": "remote", "destination_path": dest.strpath, "temporary_configuration_files": [ os.path.join(sys_tempdir, "postgresql.conf"), os.path.join(sys_tempdir, "postgresql.auto.conf"), ], "results": { "delete_barman_xlog": False, "get_wal": False, "changes": [ Assertion._make(["postgresql.conf", 0, "archive_command", "false"]), Assertion._make(["postgresql.auto.conf", 0, "archive_command", "false"]), ], "missing_files": [], "warnings": [ Assertion._make(["postgresql.conf", 2, "data_directory", "something"]), Assertion._make(["postgresql.auto.conf", 2, "data_directory", "something"]), ], }, "target_epoch": None, "configuration_files": ["postgresql.conf", "postgresql.auto.conf"], "target_datetime": None, "safe_horizon": None, "is_pitr": False, "get_wal": False, } # test failed rsync rsync_pg_mock.side_effect = CommandFailedException() with pytest.raises(CommandFailedException): executor.recover(backup_info, dest.strpath, {}, None, None, None, None, True, "remote@command")
def test_recovery( self, remote_cmd_mock, rsync_pg_mock, copy_controller_mock, tmpdir ): """ Test the execution of a recovery """ # Prepare basic directory/files structure dest = tmpdir.mkdir("destination") base = tmpdir.mkdir("base") wals = tmpdir.mkdir("wals") backup_info = testing_helpers.build_test_backup_info(tablespaces=[]) backup_info.config.basebackups_directory = base.strpath backup_info.config.wals_directory = wals.strpath backup_info.version = 90400 datadir = base.mkdir(backup_info.backup_id).mkdir("data") backup_info.pgdata = datadir.strpath postgresql_conf_local = datadir.join("postgresql.conf") postgresql_auto_local = datadir.join("postgresql.auto.conf") postgresql_conf_local.write( "archive_command = something\n" "data_directory = something" ) postgresql_auto_local.write( "archive_command = something\n" "data_directory = something" ) shutil.copy2(postgresql_conf_local.strpath, dest.strpath) shutil.copy2(postgresql_auto_local.strpath, dest.strpath) # Avoid triggering warning for missing config files datadir.ensure("pg_hba.conf") datadir.ensure("pg_ident.conf") # Build an executor server = testing_helpers.build_real_server( global_conf={"barman_lock_directory": tmpdir.mkdir("lock").strpath}, main_conf={"wals_directory": wals.strpath}, ) executor = RecoveryExecutor(server.backup_manager) # test local recovery with closing(executor): rec_info = executor.recover(backup_info, dest.strpath, exclusive=True) # remove not useful keys from the result del rec_info["cmd"] sys_tempdir = rec_info["tempdir"] assert rec_info == { "rsync": None, "tempdir": sys_tempdir, "wal_dest": dest.join("pg_xlog").strpath, "recovery_dest": "local", "destination_path": dest.strpath, "temporary_configuration_files": [ dest.join("postgresql.conf").strpath, dest.join("postgresql.auto.conf").strpath, ], "results": { "delete_barman_wal": False, "recovery_start_time": rec_info["results"]["recovery_start_time"], "get_wal": False, "changes": [ Assertion._make(["postgresql.conf", 0, "archive_command", "false"]), Assertion._make( ["postgresql.auto.conf", 0, "archive_command", "false"] ), ], "missing_files": [], "recovery_configuration_file": "recovery.conf", "warnings": [ Assertion._make( ["postgresql.conf", 2, "data_directory", "something"] ), Assertion._make( ["postgresql.auto.conf", 2, "data_directory", "something"] ), ], }, "target_epoch": None, "configuration_files": ["postgresql.conf", "postgresql.auto.conf"], "target_datetime": None, "safe_horizon": None, "is_pitr": False, "get_wal": False, } # test remote recovery with closing(executor): rec_info = executor.recover( backup_info, dest.strpath, remote_command="remote@command", exclusive=True, ) # remove not useful keys from the result del rec_info["cmd"] del rec_info["rsync"] sys_tempdir = rec_info["tempdir"] assert rec_info == { "tempdir": sys_tempdir, "wal_dest": dest.join("pg_xlog").strpath, "recovery_dest": "remote", "destination_path": dest.strpath, "temporary_configuration_files": [ os.path.join(sys_tempdir, "postgresql.conf"), os.path.join(sys_tempdir, "postgresql.auto.conf"), ], "results": { "delete_barman_wal": False, "get_wal": False, "recovery_start_time": rec_info["results"]["recovery_start_time"], "changes": [ Assertion._make(["postgresql.conf", 0, "archive_command", "false"]), Assertion._make( ["postgresql.auto.conf", 0, "archive_command", "false"] ), ], "missing_files": [], "recovery_configuration_file": "recovery.conf", "warnings": [ Assertion._make( ["postgresql.conf", 2, "data_directory", "something"] ), Assertion._make( ["postgresql.auto.conf", 2, "data_directory", "something"] ), ], }, "target_epoch": None, "configuration_files": ["postgresql.conf", "postgresql.auto.conf"], "target_datetime": None, "safe_horizon": None, "is_pitr": False, "get_wal": False, } # test failed rsync rsync_pg_mock.side_effect = CommandFailedException() with pytest.raises(CommandFailedException): with closing(executor): executor.recover( backup_info, dest.strpath, exclusive=True, remote_command="remote@command", )