def test_prepare_tablespaces(self, tmpdir): """ Test tablespaces preparation for recovery """ # Prepare basic directory/files structure dest = tmpdir.mkdir('destination') wals = tmpdir.mkdir('wals') backup_info = testing_helpers.build_test_backup_info( tablespaces=[('tbs1', 16387, '/fake/location')]) # build an executor server = testing_helpers.build_real_server( main_conf={'wals_directory': wals.strpath}) executor = RecoveryExecutor(server.backup_manager) # use a mock as cmd obj cmd_mock = mock.Mock() executor._prepare_tablespaces(backup_info, cmd_mock, dest.strpath, {}) cmd_mock.create_dir_if_not_exists.assert_any_call( dest.join('pg_tblspc').strpath) cmd_mock.create_dir_if_not_exists.assert_any_call( '/fake/location') cmd_mock.delete_if_exists.assert_called_once_with( dest.join('pg_tblspc').join('16387').strpath) cmd_mock.create_symbolic_link.assert_called_once_with( '/fake/location', dest.join('pg_tblspc').join('16387').strpath)
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_basebackup_copy(self, rsync_pg_mock, tmpdir): """ Test the copy of a content of a backup during a recovery :param rsync_pg_mock: Mock rsync object for the purpose if this test """ # Build basic folder/files structure dest = tmpdir.mkdir('destination') server = testing_helpers.build_real_server() backup_info = testing_helpers.build_test_backup_info( server=server, tablespaces=[('tbs1', 16387, '/fake/location')]) # Build a executor executor = RecoveryExecutor(server.backup_manager) executor.config.tablespace_bandwidth_limit = {'tbs1': ''} executor.config.bandwidth_limit = 10 executor.basebackup_copy( backup_info, dest.strpath, tablespaces=None) rsync_pg_mock.assert_called_with( network_compression=False, bwlimit=10, ssh=None, path=None, exclude_and_protect=['/pg_tblspc/16387']) rsync_pg_mock.assert_any_call( network_compression=False, bwlimit='', ssh=None, path=None, check=True) rsync_pg_mock.return_value.smart_copy.assert_any_call( '/some/barman/home/main/base/1234567890/16387/', '/fake/location', None) rsync_pg_mock.return_value.smart_copy.assert_called_with( '/some/barman/home/main/base/1234567890/data/', dest.strpath, None)
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_setup(self, rsync_mock): """ Test the method that set up a recovery """ backup_info = testing_helpers.build_test_backup_info() backup_manager = testing_helpers.build_backup_manager() executor = RecoveryExecutor(backup_manager) backup_info.version = 90300 # setup should create a temporary directory # and teardown should delete it ret = executor._setup(backup_info, None, "/tmp") assert os.path.exists(ret['tempdir']) executor._teardown(ret) assert not os.path.exists(ret['tempdir']) # no postgresql.auto.conf on version 9.3 ret = executor._setup(backup_info, None, "/tmp") executor._teardown(ret) assert "postgresql.auto.conf" not in ret['configuration_files'] # Check the present for postgresql.auto.conf on version 9.4 backup_info.version = 90400 ret = executor._setup(backup_info, None, "/tmp") executor._teardown(ret) assert "postgresql.auto.conf" in ret['configuration_files'] # Receive a error if the remote command is invalid with pytest.raises(SystemExit): executor.server.path = None executor._setup(backup_info, "invalid", "/tmp")
def test_rsync_backup_executor_init(self): """ Test the construction of a RecoveryExecutor """ # Test backup_manager = testing_helpers.build_backup_manager() assert RecoveryExecutor(backup_manager)
def test_rsync_backup_executor_init(self): """ Test the construction of a RecoveryExecutor """ # Test server = testing_helpers.build_mocked_server() backup_manager = Mock(server=server, config=server.config) assert RecoveryExecutor(backup_manager)
def test_recover_backup_copy(self, copy_controller_mock, tmpdir): """ Test the copy of a content of a backup during a recovery """ # Build basic folder/files structure dest = tmpdir.mkdir("destination") server = testing_helpers.build_real_server() backup_info = testing_helpers.build_test_backup_info( server=server, tablespaces=[("tbs1", 16387, "/fake/location")] ) # Build a executor executor = RecoveryExecutor(server.backup_manager) executor.config.tablespace_bandwidth_limit = {"tbs1": ""} executor.config.bandwidth_limit = 10 executor._backup_copy(backup_info, dest.strpath, tablespaces=None) # Check the calls assert copy_controller_mock.mock_calls == [ mock.call( network_compression=False, path=None, safe_horizon=None, ssh_command=None, retry_sleep=30, retry_times=0, workers=1, ), mock.call().add_directory( bwlimit="", dst="/fake/location", item_class=copy_controller_mock.return_value.TABLESPACE_CLASS, label="tbs1", src=backup_info.get_data_directory(16387) + "/", ), mock.call().add_directory( bwlimit=10, dst=dest.strpath, exclude=[ "/pg_log/*", "/log/*", "/pg_xlog/*", "/pg_wal/*", "/postmaster.pid", "/recovery.conf", "/tablespace_map", ], exclude_and_protect=["/pg_tblspc/16387"], item_class=copy_controller_mock.return_value.PGDATA_CLASS, label="pgdata", src=backup_info.get_data_directory() + "/", ), mock.call().copy(), ]
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 test_map_temporary_config_files(self, tmpdir): """ Test the method that prepares configuration files for the final steps of a recovery """ # Build directory/files structure for testing tempdir = tmpdir.mkdir("tempdir") recovery_info = { "configuration_files": ["postgresql.conf", "postgresql.auto.conf"], "tempdir": tempdir.strpath, "temporary_configuration_files": [], "results": {"changes": [], "warnings": [], "missing_files": []}, } backup_info = testing_helpers.build_test_backup_info() backup_info.config.basebackups_directory = tmpdir.strpath datadir = tmpdir.mkdir(backup_info.backup_id).mkdir("data") 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" ) # Build a RecoveryExecutor object (using a mock as server and backup # manager. backup_manager = testing_helpers.build_backup_manager() executor = RecoveryExecutor(backup_manager) executor._map_temporary_config_files( recovery_info, backup_info, "ssh@something" ) # check that configuration files have been moved by the method assert tempdir.join("postgresql.conf").check() assert ( tempdir.join("postgresql.conf").computehash() == postgresql_conf_local.computehash() ) assert tempdir.join("postgresql.auto.conf").check() assert ( tempdir.join("postgresql.auto.conf").computehash() == postgresql_auto_local.computehash() ) assert recovery_info["results"]["missing_files"] == [ "pg_hba.conf", "pg_ident.conf", ]
def test_set_pitr_targets(self, tmpdir): """ Evaluate targets for point in time recovery """ # Build basic folder/files structure tempdir = tmpdir.mkdir('temp_dir') dest = tmpdir.mkdir('dest') wal_dest = tmpdir.mkdir('wal_dest') recovery_info = { 'configuration_files': ['postgresql.conf', 'postgresql.auto.conf'], 'tempdir': tempdir.strpath, 'results': { 'changes': [], 'warnings': [] }, 'is_pitr': False, 'wal_dest': wal_dest.strpath, 'get_wal': False, } backup_info = testing_helpers.build_test_backup_info() backup_manager = testing_helpers.build_backup_manager() # Build a recovery executor executor = RecoveryExecutor(backup_manager) executor._set_pitr_targets(recovery_info, backup_info, dest.strpath, '', '', '', '') # Test with empty values (no PITR) assert recovery_info['target_epoch'] is None assert recovery_info['target_datetime'] is None assert recovery_info['wal_dest'] == wal_dest.strpath # Test for PITR targets executor._set_pitr_targets( recovery_info, backup_info, dest.strpath, 'target_name', '2015-06-03 16:11:03.71038+02', '2', None, ) target_datetime = dateutil.parser.parse( '2015-06-03 16:11:03.710380+02:00') target_epoch = (time.mktime(target_datetime.timetuple()) + (target_datetime.microsecond / 1000000.)) assert recovery_info['target_datetime'] == target_datetime assert recovery_info['target_epoch'] == target_epoch assert recovery_info['wal_dest'] == dest.join('barman_xlog').strpath
def test_map_temporary_config_files(self, tmpdir): """ Test the method that prepares configuration files for the final steps of a recovery """ # Build directory/files structure for testing tempdir = tmpdir.mkdir('tempdir') recovery_info = { 'configuration_files': ['postgresql.conf', 'postgresql.auto.conf'], 'tempdir': tempdir.strpath, 'temporary_configuration_files': [], 'results': { 'changes': [], 'warnings': [], 'missing_files': [] }, } backup_info = testing_helpers.build_test_backup_info() backup_info.config.basebackups_directory = tmpdir.strpath datadir = tmpdir.mkdir(backup_info.backup_id).mkdir('data') 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') # Build a RecoveryExecutor object (using a mock as server and backup # manager. backup_manager = testing_helpers.build_backup_manager() executor = RecoveryExecutor(backup_manager) executor._map_temporary_config_files(recovery_info, backup_info, 'ssh@something') # check that configuration files have been moved by the method assert tempdir.join('postgresql.conf').check() assert tempdir.join('postgresql.conf').computehash() == \ postgresql_conf_local.computehash() assert tempdir.join('postgresql.auto.conf').check() assert tempdir.join('postgresql.auto.conf').computehash() == \ postgresql_auto_local.computehash() assert recovery_info['results']['missing_files'] == [ 'pg_hba.conf', 'pg_ident.conf' ]
def test_generate_recovery_conf(self, rsync_pg_mock, tmpdir): """ Test the generation of recovery.conf file """ # Build basic folder/files structure recovery_info = { 'configuration_files': ['postgresql.conf', 'postgresql.auto.conf'], 'tempdir': tmpdir.strpath, 'results': { 'changes': [], 'warnings': [] }, 'get_wal': False, } backup_info = testing_helpers.build_test_backup_info() dest = tmpdir.mkdir('destination') # Build a recovery executor using a real server server = testing_helpers.build_real_server() executor = RecoveryExecutor(server.backup_manager) executor._generate_recovery_conf(recovery_info, backup_info, dest.strpath, True, 'remote@command', 'target_name', '2015-06-03 16:11:03.71038+02', '2', '') # Check that the recovery.conf file exists recovery_conf_file = tmpdir.join("recovery.conf") assert recovery_conf_file.check() # Parse the generated recovery.conf recovery_conf = {} for line in recovery_conf_file.readlines(): key, value = (s.strip() for s in line.strip().split('=', 1)) recovery_conf[key] = value # check for contents assert 'recovery_end_command' in recovery_conf assert 'recovery_target_time' in recovery_conf assert 'recovery_target_timeline' in recovery_conf assert 'recovery_target_xid' not in recovery_conf assert 'recovery_target_name' in recovery_conf assert recovery_conf['recovery_end_command'] == "'rm -fr barman_xlog'" assert recovery_conf['recovery_target_time'] == \ "'2015-06-03 16:11:03.71038+02'" assert recovery_conf['recovery_target_timeline'] == '2' assert recovery_conf['recovery_target_name'] == "'target_name'"
def test_analyse_temporary_config_files(self, tmpdir): """ Test the method that identifies dangerous options into the configuration files """ # Build directory/files structure for testing tempdir = tmpdir.mkdir('tempdir') recovery_info = { 'configuration_files': ['postgresql.conf', 'postgresql.auto.conf'], 'tempdir': tempdir.strpath, 'temporary_configuration_files': [], 'results': { 'changes': [], 'warnings': [] } } postgresql_conf = tempdir.join('postgresql.conf') postgresql_auto = tempdir.join('postgresql.auto.conf') postgresql_conf.write('archive_command = something\n' 'data_directory = something\n' 'include = something\n' 'include "without braces"') postgresql_auto.write('archive_command = something\n' 'data_directory = something') recovery_info['temporary_configuration_files'].append( postgresql_conf.strpath) recovery_info['temporary_configuration_files'].append( postgresql_auto.strpath) # Build a RecoveryExecutor object (using a mock as server and backup # manager. backup_manager = testing_helpers.build_backup_manager() executor = RecoveryExecutor(backup_manager) # Identify dangerous options into config files for remote recovery executor._analyse_temporary_config_files(recovery_info) assert len(recovery_info['results']['changes']) == 2 assert len(recovery_info['results']['warnings']) == 4 # Clean for a local recovery test recovery_info['results']['changes'] = [] recovery_info['results']['warnings'] = [] # Identify dangerous options for local recovery executor._analyse_temporary_config_files(recovery_info) assert len(recovery_info['results']['changes']) == 2 assert len(recovery_info['results']['warnings']) == 4
def test_analyse_temporary_config_files(self, tmpdir): """ Test the method that identifies dangerous options into the configuration files """ # Build directory/files structure for testing tempdir = tmpdir.mkdir("tempdir") recovery_info = { "configuration_files": ["postgresql.conf", "postgresql.auto.conf"], "tempdir": tempdir.strpath, "temporary_configuration_files": [], "results": {"changes": [], "warnings": []}, } postgresql_conf = tempdir.join("postgresql.conf") postgresql_auto = tempdir.join("postgresql.auto.conf") postgresql_conf.write( "archive_command = something\n" "data_directory = something\n" "include = something\n" 'include "without braces"' ) postgresql_auto.write( "archive_command = something\n" "data_directory = something" ) recovery_info["temporary_configuration_files"].append(postgresql_conf.strpath) recovery_info["temporary_configuration_files"].append(postgresql_auto.strpath) # Build a RecoveryExecutor object (using a mock as server and backup # manager. backup_manager = testing_helpers.build_backup_manager() executor = RecoveryExecutor(backup_manager) # Identify dangerous options into config files for remote recovery executor._analyse_temporary_config_files(recovery_info) assert len(recovery_info["results"]["changes"]) == 2 assert len(recovery_info["results"]["warnings"]) == 4 # Clean for a local recovery test recovery_info["results"]["changes"] = [] recovery_info["results"]["warnings"] = [] # Identify dangerous options for local recovery executor._analyse_temporary_config_files(recovery_info) assert len(recovery_info["results"]["changes"]) == 2 assert len(recovery_info["results"]["warnings"]) == 4
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_generate_recovery_conf(self, rsync_pg_mock, tmpdir): """ Test the generation of recovery configuration :type tmpdir: py.path.local """ # Build basic folder/files structure recovery_info = { "configuration_files": ["postgresql.conf", "postgresql.auto.conf"], "tempdir": tmpdir.strpath, "results": {"changes": [], "warnings": []}, "get_wal": False, } backup_info = testing_helpers.build_test_backup_info( version=120000, ) dest = tmpdir.mkdir("destination") # Build a recovery executor using a real server server = testing_helpers.build_real_server() executor = RecoveryExecutor(server.backup_manager) executor._generate_recovery_conf( recovery_info, backup_info, dest.strpath, True, True, "remote@command", "target_name", "2015-06-03 16:11:03.71038+02", "2", "", "", None, ) # Check that the recovery.conf file doesn't exist recovery_conf_file = tmpdir.join("recovery.conf") assert not recovery_conf_file.check() # Check that the recovery.signal file exists signal_file = tmpdir.join("recovery.signal") assert signal_file.check() # Parse the generated recovery configuration pg_auto_conf = self.parse_auto_conf_lines(recovery_info) # check for contents assert "recovery_end_command" in pg_auto_conf assert "recovery_target_time" in pg_auto_conf assert "recovery_target_timeline" in pg_auto_conf assert "recovery_target_xid" not in pg_auto_conf assert "recovery_target_lsn" not in pg_auto_conf assert "recovery_target_name" in pg_auto_conf assert "recovery_target" in pg_auto_conf assert pg_auto_conf["recovery_end_command"] == "'rm -fr barman_wal'" assert pg_auto_conf["recovery_target_time"] == "'2015-06-03 16:11:03.71038+02'" assert pg_auto_conf["recovery_target_timeline"] == "2" assert pg_auto_conf["recovery_target_name"] == "'target_name'" # Test 'pause_at_recovery_target' recovery_info entry signal_file.remove() recovery_info["pause_at_recovery_target"] = "on" executor._generate_recovery_conf( recovery_info, backup_info, dest.strpath, True, True, "remote@command", "target_name", "2015-06-03 16:11:03.71038+02", "2", "", "", None, ) # Check that the recovery.conf file doesn't exist recovery_conf_file = tmpdir.join("recovery.conf") assert not recovery_conf_file.check() # Check that the recovery.signal file exists signal_file = tmpdir.join("recovery.signal") assert signal_file.check() # Parse the generated recovery configuration pg_auto_conf = self.parse_auto_conf_lines(recovery_info) # Finally check pause_at_recovery_target value assert pg_auto_conf["pause_at_recovery_target"] == "'on'" # Test 'recovery_target_action' signal_file.remove() del recovery_info["pause_at_recovery_target"] recovery_info["recovery_target_action"] = "pause" executor._generate_recovery_conf( recovery_info, backup_info, dest.strpath, True, True, "remote@command", "target_name", "2015-06-03 16:11:03.71038+02", "2", "", "", None, ) # Check that the recovery.conf file doesn't exist recovery_conf_file = tmpdir.join("recovery.conf") assert not recovery_conf_file.check() # Check that the recovery.signal file exists signal_file = tmpdir.join("recovery.signal") assert signal_file.check() # Parse the generated recovery configuration pg_auto_conf = self.parse_auto_conf_lines(recovery_info) # Finally check recovery_target_action value assert pg_auto_conf["recovery_target_action"] == "'pause'" # Test 'standby_mode' signal_file.remove() executor._generate_recovery_conf( recovery_info, backup_info, dest.strpath, True, True, "remote@command", "target_name", "2015-06-03 16:11:03.71038+02", "2", "", "", True, ) # Check that the recovery.conf file doesn't exist recovery_conf_file = tmpdir.join("recovery.conf") assert not recovery_conf_file.check() # Check that the recovery.signal file doesn't exist wrong_signal_file = tmpdir.join("recovery.signal") assert not wrong_signal_file.check() # Check that the standby.signal file exists signal_file = tmpdir.join("standby.signal") assert signal_file.check() # Parse the generated recovery configuration pg_auto_conf = self.parse_auto_conf_lines(recovery_info) # standby_mode is not a valid configuration in PostgreSQL 12 assert "standby_mode" not in pg_auto_conf signal_file.remove() executor._generate_recovery_conf( recovery_info, backup_info, dest.strpath, True, True, "remote@command", "target_name", "2015-06-03 16:11:03.71038+02", "2", "", "", False, ) # Check that the recovery.conf file doesn't exist recovery_conf_file = tmpdir.join("recovery.conf") assert not recovery_conf_file.check() # Check that the standby.signal file doesn't exist wrong_signal_file = tmpdir.join("standby.signal") assert not wrong_signal_file.check() # Check that the recovery.signal file exists signal_file = tmpdir.join("recovery.signal") assert signal_file.check() # Parse the generated recovery configuration pg_auto_conf = self.parse_auto_conf_lines(recovery_info) # standby_mode is not a valid configuration in PostgreSQL 12 assert "standby_mode" not in pg_auto_conf signal_file.remove() executor._generate_recovery_conf( recovery_info, backup_info, dest.strpath, True, True, "remote@command", "target_name", "2015-06-03 16:11:03.71038+02", "2", "", "", None, ) # Check that the recovery.conf file doesn't exist recovery_conf_file = tmpdir.join("recovery.conf") assert not recovery_conf_file.check() # Check that the standby.signal file doesn't exist wrong_signal_file = tmpdir.join("standby.signal") assert not wrong_signal_file.check() # Check that the recovery.signal file exists signal_file = tmpdir.join("recovery.signal") assert signal_file.check() # Parse the generated recovery configuration pg_auto_conf = self.parse_auto_conf_lines(recovery_info) # standby_mode is not a valid configuration in PostgreSQL 12 assert "standby_mode" not in pg_auto_conf
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", )
def test_generate_recovery_conf_pre12(self, rsync_pg_mock, tmpdir): """ Test the generation of recovery.conf file """ # Build basic folder/files structure recovery_info = { "configuration_files": ["postgresql.conf", "postgresql.auto.conf"], "tempdir": tmpdir.strpath, "results": {"changes": [], "warnings": []}, "get_wal": False, } backup_info = testing_helpers.build_test_backup_info() dest = tmpdir.mkdir("destination") # Build a recovery executor using a real server server = testing_helpers.build_real_server() executor = RecoveryExecutor(server.backup_manager) executor._generate_recovery_conf( recovery_info, backup_info, dest.strpath, True, True, "remote@command", "target_name", "2015-06-03 16:11:03.71038+02", "2", "", "", None, ) # Check that the recovery.conf file exists recovery_conf_file = tmpdir.join("recovery.conf") assert recovery_conf_file.check() # Parse the generated recovery.conf recovery_conf = testing_helpers.parse_recovery_conf(recovery_conf_file) # check for contents assert "recovery_end_command" in recovery_conf assert "recovery_target_time" in recovery_conf assert "recovery_target_timeline" in recovery_conf assert "recovery_target_xid" not in recovery_conf assert "recovery_target_lsn" not in recovery_conf assert "recovery_target_name" in recovery_conf assert "recovery_target" not in recovery_conf assert recovery_conf["recovery_end_command"] == "'rm -fr barman_wal'" assert recovery_conf["recovery_target_time"] == "'2015-06-03 16:11:03.71038+02'" assert recovery_conf["recovery_target_timeline"] == "2" assert recovery_conf["recovery_target_name"] == "'target_name'" # Test 'pause_at_recovery_target' recovery_info entry recovery_info["pause_at_recovery_target"] = "on" executor._generate_recovery_conf( recovery_info, backup_info, dest.strpath, True, True, "remote@command", "target_name", "2015-06-03 16:11:03.71038+02", "2", "", "", None, ) recovery_conf_file = tmpdir.join("recovery.conf") assert recovery_conf_file.check() recovery_conf = testing_helpers.parse_recovery_conf(recovery_conf_file) assert recovery_conf["pause_at_recovery_target"] == "'on'" # Test 'recovery_target_action' del recovery_info["pause_at_recovery_target"] recovery_info["recovery_target_action"] = "pause" executor._generate_recovery_conf( recovery_info, backup_info, dest.strpath, True, True, "remote@command", "target_name", "2015-06-03 16:11:03.71038+02", "2", "", "", None, ) recovery_conf_file = tmpdir.join("recovery.conf") assert recovery_conf_file.check() recovery_conf = testing_helpers.parse_recovery_conf(recovery_conf_file) assert recovery_conf["recovery_target_action"] == "'pause'" # Test 'standby_mode' executor._generate_recovery_conf( recovery_info, backup_info, dest.strpath, True, True, "remote@command", "target_name", "2015-06-03 16:11:03.71038+02", "2", "", "", True, ) recovery_conf_file = tmpdir.join("recovery.conf") assert recovery_conf_file.check() recovery_conf = testing_helpers.parse_recovery_conf(recovery_conf_file) assert recovery_conf["standby_mode"] == "'on'" executor._generate_recovery_conf( recovery_info, backup_info, dest.strpath, True, True, "remote@command", "target_name", "2015-06-03 16:11:03.71038+02", "2", "", "", False, ) recovery_conf_file = tmpdir.join("recovery.conf") assert recovery_conf_file.check() recovery_conf = testing_helpers.parse_recovery_conf(recovery_conf_file) assert "standby_mode" not in recovery_conf executor._generate_recovery_conf( recovery_info, backup_info, dest.strpath, True, True, "remote@command", "target_name", "2015-06-03 16:11:03.71038+02", "2", "", "", None, ) recovery_conf_file = tmpdir.join("recovery.conf") assert recovery_conf_file.check() recovery_conf = testing_helpers.parse_recovery_conf(recovery_conf_file) assert "standby_mode" not in recovery_conf
def test_recover_xlog(self, rsync_pg_mock, cm_mock, tmpdir): """ Test the recovery of the xlogs of a backup :param rsync_pg_mock: Mock rsync object for the purpose if this test """ # Build basic folders/files structure dest = tmpdir.mkdir('destination') wals = tmpdir.mkdir('wals') # Create 3 WAL files with different compressions xlog_dir = wals.mkdir(xlog.hash_dir('000000000000000000000002')) xlog_plain = xlog_dir.join('000000000000000000000001') xlog_gz = xlog_dir.join('000000000000000000000002') xlog_bz2 = xlog_dir.join('000000000000000000000003') xlog_plain.write('dummy content') xlog_gz.write('dummy content gz') xlog_bz2.write('dummy content bz2') server = testing_helpers.build_real_server( main_conf={'wals_directory': wals.strpath}) # Prepare compressors mock c = { 'gzip': mock.Mock(name='gzip'), 'bzip2': mock.Mock(name='bzip2'), } cm_mock.return_value.get_compressor = \ lambda compression=None, path=None: c[compression] # touch destination files to avoid errors on cleanup c['gzip'].decompress.side_effect = lambda src, dst: open(dst, 'w') c['bzip2'].decompress.side_effect = lambda src, dst: open(dst, 'w') # Build executor executor = RecoveryExecutor(server.backup_manager) # Test: local copy required_wals = ( WalFileInfo.from_xlogdb_line( '000000000000000000000001\t42\t43\tNone\n'), WalFileInfo.from_xlogdb_line( '000000000000000000000002\t42\t43\tgzip\n'), WalFileInfo.from_xlogdb_line( '000000000000000000000003\t42\t43\tbzip2\n'), ) executor._xlog_copy(required_wals, dest.strpath, None) # Check for a correct invocation of rsync using local paths rsync_pg_mock.assert_called_once_with(network_compression=False, bwlimit=None, path=None, ssh=None) assert not rsync_pg_mock.return_value.from_file_list.called c['gzip'].decompress.assert_called_once_with(xlog_gz.strpath, mock.ANY) c['bzip2'].decompress.assert_called_once_with(xlog_bz2.strpath, mock.ANY) # Reset mock calls rsync_pg_mock.reset_mock() c['gzip'].reset_mock() c['bzip2'].reset_mock() # Test: remote copy executor._xlog_copy(required_wals, dest.strpath, 'remote_command') # Check for the invocation of rsync on a remote call rsync_pg_mock.assert_called_once_with(network_compression=False, bwlimit=None, path=mock.ANY, ssh='remote_command') rsync_pg_mock.return_value.from_file_list.assert_called_once_with([ '000000000000000000000001', '000000000000000000000002', '000000000000000000000003' ], mock.ANY, mock.ANY) c['gzip'].decompress.assert_called_once_with(xlog_gz.strpath, mock.ANY) c['bzip2'].decompress.assert_called_once_with(xlog_bz2.strpath, mock.ANY)
def test_set_pitr_targets(self, tmpdir): """ Evaluate targets for point in time recovery """ # Build basic folder/files structure tempdir = tmpdir.mkdir('temp_dir') dest = tmpdir.mkdir('dest') wal_dest = tmpdir.mkdir('wal_dest') recovery_info = { 'configuration_files': ['postgresql.conf', 'postgresql.auto.conf'], 'tempdir': tempdir.strpath, 'results': {'changes': [], 'warnings': []}, 'is_pitr': False, 'wal_dest': wal_dest.strpath, 'get_wal': False, } backup_info = testing_helpers.build_test_backup_info( end_time=dateutil.parser.parse('2015-06-03 16:11:01.71038+02')) backup_manager = testing_helpers.build_backup_manager() # Build a recovery executor executor = RecoveryExecutor(backup_manager) executor._set_pitr_targets(recovery_info, backup_info, dest.strpath, '', '', '', '', '', False, None) # Test with empty values (no PITR) assert recovery_info['target_epoch'] is None assert recovery_info['target_datetime'] is None assert recovery_info['wal_dest'] == wal_dest.strpath # Test for PITR targets executor._set_pitr_targets(recovery_info, backup_info, dest.strpath, 'target_name', '2015-06-03 16:11:03.71038+02', '2', None, '', False, None) target_datetime = dateutil.parser.parse( '2015-06-03 16:11:03.710380+02:00') target_epoch = ( time.mktime(target_datetime.timetuple()) + ( target_datetime.microsecond / 1000000.)) assert recovery_info['target_datetime'] == target_datetime assert recovery_info['target_epoch'] == target_epoch assert recovery_info['wal_dest'] == dest.join('barman_wal').strpath # Test for too early PITR target with pytest.raises(RecoveryInvalidTargetException) as exc_info: executor._set_pitr_targets(recovery_info, backup_info, dest.strpath, None, '2015-06-03 16:11:00.71038+02', None, None, None, False, None) assert str(exc_info.value) == \ "The requested target time " \ "2015-06-03 16:11:00.710380+02:00 " \ "is before the backup end time " \ "2015-06-03 16:11:01.710380+02:00" # Tests for PostgreSQL < 9.1 backup_info.version = 90000 with pytest.raises(RecoveryTargetActionException) as exc_info: executor._set_pitr_targets(recovery_info, backup_info, dest.strpath, 'target_name', '2015-06-03 16:11:03.71038+02', '2', None, None, False, 'pause') assert str(exc_info.value) == "Illegal target action 'pause' " \ "for this version of PostgreSQL" # Tests for PostgreSQL between 9.1 and 9.4 included backup_info.version = 90100 executor._set_pitr_targets(recovery_info, backup_info, dest.strpath, 'target_name', '2015-06-03 16:11:03.71038+02', '2', None, None, False, None) assert 'pause_at_recovery_target' not in recovery_info executor._set_pitr_targets(recovery_info, backup_info, dest.strpath, 'target_name', '2015-06-03 16:11:03.71038+02', '2', None, None, False, 'pause') assert recovery_info['pause_at_recovery_target'] == "on" del recovery_info['pause_at_recovery_target'] with pytest.raises(RecoveryTargetActionException) as exc_info: executor._set_pitr_targets(recovery_info, backup_info, dest.strpath, 'target_name', '2015-06-03 16:11:03.71038+02', '2', None, None, False, 'promote') assert str(exc_info.value) == "Illegal target action 'promote' " \ "for this version of PostgreSQL" # Tests for PostgreSQL >= 9.5 backup_info.version = 90500 executor._set_pitr_targets(recovery_info, backup_info, dest.strpath, 'target_name', '2015-06-03 16:11:03.71038+02', '2', None, None, False, 'pause') assert recovery_info['recovery_target_action'] == "pause" executor._set_pitr_targets(recovery_info, backup_info, dest.strpath, 'target_name', '2015-06-03 16:11:03.71038+02', '2', None, None, False, 'promote') assert recovery_info['recovery_target_action'] == "promote" with pytest.raises(RecoveryTargetActionException) as exc_info: executor._set_pitr_targets(recovery_info, backup_info, dest.strpath, 'target_name', '2015-06-03 16:11:03.71038+02', '2', None, None, False, 'unavailable') assert str(exc_info.value) == "Illegal target action 'unavailable' " \ "for this version of PostgreSQL" # Recovery target action should not be available is PITR is not # enabled backup_info.version = 90500 with pytest.raises(RecoveryTargetActionException) as exc_info: executor._set_pitr_targets(recovery_info, backup_info, dest.strpath, None, None, None, None, None, False, 'pause') assert str(exc_info.value) == "Can't enable recovery target action " \ "when PITR is not required" # Test that we are not using target_lsn with a version < 10 backup_info.version = 90500 with pytest.raises(RecoveryInvalidTargetException) as exc_info: executor._set_pitr_targets(recovery_info, backup_info, dest.strpath, None, None, None, None, 10000, False, 'pause') assert str(exc_info.value) == "Illegal use of recovery_target_lsn " \ "'10000' for this version " \ "of PostgreSQL " \ "(version 10 minimum required)" # Test that we are not using target_immediate with a version < 9.4 backup_info.version = 90300 with pytest.raises(RecoveryInvalidTargetException) as exc_info: executor._set_pitr_targets(recovery_info, backup_info, dest.strpath, None, None, None, None, None, True, 'pause') assert str(exc_info.value) == "Illegal use of " \ "recovery_target_immediate " \ "for this version " \ "of PostgreSQL " \ "(version 9.4 minimum required)"
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_generate_recovery_conf_pre12(self, rsync_pg_mock, tmpdir): """ Test the generation of recovery.conf file """ # Build basic folder/files structure recovery_info = { 'configuration_files': ['postgresql.conf', 'postgresql.auto.conf'], 'tempdir': tmpdir.strpath, 'results': {'changes': [], 'warnings': []}, 'get_wal': False, } backup_info = testing_helpers.build_test_backup_info() dest = tmpdir.mkdir('destination') # Build a recovery executor using a real server server = testing_helpers.build_real_server() executor = RecoveryExecutor(server.backup_manager) executor._generate_recovery_conf(recovery_info, backup_info, dest.strpath, True, True, 'remote@command', 'target_name', '2015-06-03 16:11:03.71038+02', '2', '', '', None) # Check that the recovery.conf file exists recovery_conf_file = tmpdir.join("recovery.conf") assert recovery_conf_file.check() # Parse the generated recovery.conf recovery_conf = testing_helpers.parse_recovery_conf(recovery_conf_file) # check for contents assert 'recovery_end_command' in recovery_conf assert 'recovery_target_time' in recovery_conf assert 'recovery_target_timeline' in recovery_conf assert 'recovery_target_xid' not in recovery_conf assert 'recovery_target_lsn' not in recovery_conf assert 'recovery_target_name' in recovery_conf assert 'recovery_target' not in recovery_conf assert recovery_conf['recovery_end_command'] == "'rm -fr barman_wal'" assert recovery_conf['recovery_target_time'] == \ "'2015-06-03 16:11:03.71038+02'" assert recovery_conf['recovery_target_timeline'] == '2' assert recovery_conf['recovery_target_name'] == "'target_name'" # Test 'pause_at_recovery_target' recovery_info entry recovery_info['pause_at_recovery_target'] = 'on' executor._generate_recovery_conf(recovery_info, backup_info, dest.strpath, True, True, 'remote@command', 'target_name', '2015-06-03 16:11:03.71038+02', '2', '', '', None) recovery_conf_file = tmpdir.join("recovery.conf") assert recovery_conf_file.check() recovery_conf = testing_helpers.parse_recovery_conf(recovery_conf_file) assert recovery_conf['pause_at_recovery_target'] == "'on'" # Test 'recovery_target_action' del recovery_info['pause_at_recovery_target'] recovery_info['recovery_target_action'] = 'pause' executor._generate_recovery_conf(recovery_info, backup_info, dest.strpath, True, True, 'remote@command', 'target_name', '2015-06-03 16:11:03.71038+02', '2', '', '', None) recovery_conf_file = tmpdir.join("recovery.conf") assert recovery_conf_file.check() recovery_conf = testing_helpers.parse_recovery_conf(recovery_conf_file) assert recovery_conf['recovery_target_action'] == "'pause'" # Test 'standby_mode' executor._generate_recovery_conf(recovery_info, backup_info, dest.strpath, True, True, 'remote@command', 'target_name', '2015-06-03 16:11:03.71038+02', '2', '', '', True) recovery_conf_file = tmpdir.join("recovery.conf") assert recovery_conf_file.check() recovery_conf = testing_helpers.parse_recovery_conf(recovery_conf_file) assert recovery_conf['standby_mode'] == "'on'" executor._generate_recovery_conf(recovery_info, backup_info, dest.strpath, True, True, 'remote@command', 'target_name', '2015-06-03 16:11:03.71038+02', '2', '', '', False) recovery_conf_file = tmpdir.join("recovery.conf") assert recovery_conf_file.check() recovery_conf = testing_helpers.parse_recovery_conf(recovery_conf_file) assert 'standby_mode' not in recovery_conf executor._generate_recovery_conf(recovery_info, backup_info, dest.strpath, True, True, 'remote@command', 'target_name', '2015-06-03 16:11:03.71038+02', '2', '', '', None) recovery_conf_file = tmpdir.join("recovery.conf") assert recovery_conf_file.check() recovery_conf = testing_helpers.parse_recovery_conf(recovery_conf_file) assert 'standby_mode' not in recovery_conf
def test_generate_recovery_conf(self, rsync_pg_mock, tmpdir): """ Test the generation of recovery configuration :type tmpdir: py.path.local """ # Build basic folder/files structure recovery_info = { 'configuration_files': ['postgresql.conf', 'postgresql.auto.conf'], 'tempdir': tmpdir.strpath, 'results': {'changes': [], 'warnings': []}, 'get_wal': False, } backup_info = testing_helpers.build_test_backup_info( version=120000, ) dest = tmpdir.mkdir('destination') # Build a recovery executor using a real server server = testing_helpers.build_real_server() executor = RecoveryExecutor(server.backup_manager) executor._generate_recovery_conf(recovery_info, backup_info, dest.strpath, True, True, 'remote@command', 'target_name', '2015-06-03 16:11:03.71038+02', '2', '', '', None) # Check that the recovery.conf file doesn't exist recovery_conf_file = tmpdir.join("recovery.conf") assert not recovery_conf_file.check() # Check that the recovery.signal file exists signal_file = tmpdir.join("recovery.signal") assert signal_file.check() # Parse the generated recovery configuration pg_auto_conf = self.parse_auto_conf_lines(recovery_info) # check for contents assert 'recovery_end_command' in pg_auto_conf assert 'recovery_target_time' in pg_auto_conf assert 'recovery_target_timeline' in pg_auto_conf assert 'recovery_target_xid' not in pg_auto_conf assert 'recovery_target_lsn' not in pg_auto_conf assert 'recovery_target_name' in pg_auto_conf assert 'recovery_target' in pg_auto_conf assert pg_auto_conf['recovery_end_command'] == "'rm -fr barman_wal'" assert pg_auto_conf['recovery_target_time'] == \ "'2015-06-03 16:11:03.71038+02'" assert pg_auto_conf['recovery_target_timeline'] == '2' assert pg_auto_conf['recovery_target_name'] == "'target_name'" # Test 'pause_at_recovery_target' recovery_info entry signal_file.remove() recovery_info['pause_at_recovery_target'] = 'on' executor._generate_recovery_conf(recovery_info, backup_info, dest.strpath, True, True, 'remote@command', 'target_name', '2015-06-03 16:11:03.71038+02', '2', '', '', None) # Check that the recovery.conf file doesn't exist recovery_conf_file = tmpdir.join("recovery.conf") assert not recovery_conf_file.check() # Check that the recovery.signal file exists signal_file = tmpdir.join("recovery.signal") assert signal_file.check() # Parse the generated recovery configuration pg_auto_conf = self.parse_auto_conf_lines(recovery_info) # Finally check pause_at_recovery_target value assert pg_auto_conf['pause_at_recovery_target'] == "'on'" # Test 'recovery_target_action' signal_file.remove() del recovery_info['pause_at_recovery_target'] recovery_info['recovery_target_action'] = 'pause' executor._generate_recovery_conf(recovery_info, backup_info, dest.strpath, True, True, 'remote@command', 'target_name', '2015-06-03 16:11:03.71038+02', '2', '', '', None) # Check that the recovery.conf file doesn't exist recovery_conf_file = tmpdir.join("recovery.conf") assert not recovery_conf_file.check() # Check that the recovery.signal file exists signal_file = tmpdir.join("recovery.signal") assert signal_file.check() # Parse the generated recovery configuration pg_auto_conf = self.parse_auto_conf_lines(recovery_info) # Finally check recovery_target_action value assert pg_auto_conf['recovery_target_action'] == "'pause'" # Test 'standby_mode' signal_file.remove() executor._generate_recovery_conf(recovery_info, backup_info, dest.strpath, True, True, 'remote@command', 'target_name', '2015-06-03 16:11:03.71038+02', '2', '', '', True) # Check that the recovery.conf file doesn't exist recovery_conf_file = tmpdir.join("recovery.conf") assert not recovery_conf_file.check() # Check that the recovery.signal file doesn't exist wrong_signal_file = tmpdir.join("recovery.signal") assert not wrong_signal_file.check() # Check that the standby.signal file exists signal_file = tmpdir.join("standby.signal") assert signal_file.check() # Parse the generated recovery configuration pg_auto_conf = self.parse_auto_conf_lines(recovery_info) # standby_mode is not a valid configuration in PostgreSQL 12 assert 'standby_mode' not in pg_auto_conf signal_file.remove() executor._generate_recovery_conf(recovery_info, backup_info, dest.strpath, True, True, 'remote@command', 'target_name', '2015-06-03 16:11:03.71038+02', '2', '', '', False) # Check that the recovery.conf file doesn't exist recovery_conf_file = tmpdir.join("recovery.conf") assert not recovery_conf_file.check() # Check that the standby.signal file doesn't exist wrong_signal_file = tmpdir.join("standby.signal") assert not wrong_signal_file.check() # Check that the recovery.signal file exists signal_file = tmpdir.join("recovery.signal") assert signal_file.check() # Parse the generated recovery configuration pg_auto_conf = self.parse_auto_conf_lines(recovery_info) # standby_mode is not a valid configuration in PostgreSQL 12 assert 'standby_mode' not in pg_auto_conf signal_file.remove() executor._generate_recovery_conf(recovery_info, backup_info, dest.strpath, True, True, 'remote@command', 'target_name', '2015-06-03 16:11:03.71038+02', '2', '', '', None) # Check that the recovery.conf file doesn't exist recovery_conf_file = tmpdir.join("recovery.conf") assert not recovery_conf_file.check() # Check that the standby.signal file doesn't exist wrong_signal_file = tmpdir.join("standby.signal") assert not wrong_signal_file.check() # Check that the recovery.signal file exists signal_file = tmpdir.join("recovery.signal") assert signal_file.check() # Parse the generated recovery configuration pg_auto_conf = self.parse_auto_conf_lines(recovery_info) # standby_mode is not a valid configuration in PostgreSQL 12 assert 'standby_mode' not in pg_auto_conf
def test_set_pitr_targets(self, tmpdir): """ Evaluate targets for point in time recovery """ # Build basic folder/files structure tempdir = tmpdir.mkdir("temp_dir") dest = tmpdir.mkdir("dest") wal_dest = tmpdir.mkdir("wal_dest") recovery_info = { "configuration_files": ["postgresql.conf", "postgresql.auto.conf"], "tempdir": tempdir.strpath, "results": {"changes": [], "warnings": []}, "is_pitr": False, "wal_dest": wal_dest.strpath, "get_wal": False, } backup_info = testing_helpers.build_test_backup_info( end_time=dateutil.parser.parse("2015-06-03 16:11:01.71038+02") ) backup_manager = testing_helpers.build_backup_manager() # Build a recovery executor executor = RecoveryExecutor(backup_manager) executor._set_pitr_targets( recovery_info, backup_info, dest.strpath, "", "", "", "", "", False, None ) # Test with empty values (no PITR) assert recovery_info["target_epoch"] is None assert recovery_info["target_datetime"] is None assert recovery_info["wal_dest"] == wal_dest.strpath # Test for PITR targets executor._set_pitr_targets( recovery_info, backup_info, dest.strpath, "target_name", "2015-06-03 16:11:03.71038+02", "2", None, "", False, None, ) target_datetime = dateutil.parser.parse("2015-06-03 16:11:03.710380+02:00") target_epoch = time.mktime(target_datetime.timetuple()) + ( target_datetime.microsecond / 1000000.0 ) assert recovery_info["target_datetime"] == target_datetime assert recovery_info["target_epoch"] == target_epoch assert recovery_info["wal_dest"] == dest.join("barman_wal").strpath # Test for too early PITR target with pytest.raises(RecoveryInvalidTargetException) as exc_info: executor._set_pitr_targets( recovery_info, backup_info, dest.strpath, None, "2015-06-03 16:11:00.71038+02", None, None, None, False, None, ) assert ( str(exc_info.value) == "The requested target time " "2015-06-03 16:11:00.710380+02:00 " "is before the backup end time " "2015-06-03 16:11:01.710380+02:00" ) # Tests for PostgreSQL < 9.1 backup_info.version = 90000 with pytest.raises(RecoveryTargetActionException) as exc_info: executor._set_pitr_targets( recovery_info, backup_info, dest.strpath, "target_name", "2015-06-03 16:11:03.71038+02", "2", None, None, False, "pause", ) assert ( str(exc_info.value) == "Illegal target action 'pause' " "for this version of PostgreSQL" ) # Tests for PostgreSQL between 9.1 and 9.4 included backup_info.version = 90100 executor._set_pitr_targets( recovery_info, backup_info, dest.strpath, "target_name", "2015-06-03 16:11:03.71038+02", "2", None, None, False, None, ) assert "pause_at_recovery_target" not in recovery_info executor._set_pitr_targets( recovery_info, backup_info, dest.strpath, "target_name", "2015-06-03 16:11:03.71038+02", "2", None, None, False, "pause", ) assert recovery_info["pause_at_recovery_target"] == "on" del recovery_info["pause_at_recovery_target"] with pytest.raises(RecoveryTargetActionException) as exc_info: executor._set_pitr_targets( recovery_info, backup_info, dest.strpath, "target_name", "2015-06-03 16:11:03.71038+02", "2", None, None, False, "promote", ) assert ( str(exc_info.value) == "Illegal target action 'promote' " "for this version of PostgreSQL" ) # Tests for PostgreSQL >= 9.5 backup_info.version = 90500 executor._set_pitr_targets( recovery_info, backup_info, dest.strpath, "target_name", "2015-06-03 16:11:03.71038+02", "2", None, None, False, "pause", ) assert recovery_info["recovery_target_action"] == "pause" executor._set_pitr_targets( recovery_info, backup_info, dest.strpath, "target_name", "2015-06-03 16:11:03.71038+02", "2", None, None, False, "promote", ) assert recovery_info["recovery_target_action"] == "promote" with pytest.raises(RecoveryTargetActionException) as exc_info: executor._set_pitr_targets( recovery_info, backup_info, dest.strpath, "target_name", "2015-06-03 16:11:03.71038+02", "2", None, None, False, "unavailable", ) assert ( str(exc_info.value) == "Illegal target action 'unavailable' " "for this version of PostgreSQL" ) # Recovery target action should not be available is PITR is not # enabled backup_info.version = 90500 with pytest.raises(RecoveryTargetActionException) as exc_info: executor._set_pitr_targets( recovery_info, backup_info, dest.strpath, None, None, None, None, None, False, "pause", ) assert ( str(exc_info.value) == "Can't enable recovery target action " "when PITR is not required" ) # Test that we are not using target_lsn with a version < 10 backup_info.version = 90500 with pytest.raises(RecoveryInvalidTargetException) as exc_info: executor._set_pitr_targets( recovery_info, backup_info, dest.strpath, None, None, None, None, 10000, False, "pause", ) assert ( str(exc_info.value) == "Illegal use of recovery_target_lsn " "'10000' for this version " "of PostgreSQL " "(version 10 minimum required)" ) # Test that we are not using target_immediate with a version < 9.4 backup_info.version = 90300 with pytest.raises(RecoveryInvalidTargetException) as exc_info: executor._set_pitr_targets( recovery_info, backup_info, dest.strpath, None, None, None, None, None, True, "pause", ) assert ( str(exc_info.value) == "Illegal use of " "recovery_target_immediate " "for this version " "of PostgreSQL " "(version 9.4 minimum required)" )