From da1c1e914ad36f956284777491984ab864fecd3f Mon Sep 17 00:00:00 2001 From: Sudeepta Patra Date: Tue, 19 Jul 2022 08:45:58 +0000 Subject: [PATCH 1/5] add timeline in clone --- .../bootstrap/clone_with_wale.py | 22 +++++++++++++------ postgres-appliance/scripts/configure_spilo.py | 2 +- 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/postgres-appliance/bootstrap/clone_with_wale.py b/postgres-appliance/bootstrap/clone_with_wale.py index e8d31962d..c41559446 100755 --- a/postgres-appliance/bootstrap/clone_with_wale.py +++ b/postgres-appliance/bootstrap/clone_with_wale.py @@ -27,9 +27,12 @@ def read_configuration(): dest='recovery_target_time_string') parser.add_argument('--dry-run', action='store_true', help='find a matching backup and build the wal-e ' 'command to fetch that backup without running it') + parser.add_argument('--recovery-target-timeline', + help='the timeline up to which recovery will proceed', + dest='recovery_target_timeline_string') args = parser.parse_args() - options = namedtuple('Options', 'name datadir recovery_target_time dry_run') + options = namedtuple('Options', 'name datadir recovery_target_time recovery_target_timeline dry_run') if args.recovery_target_time_string: recovery_target_time = parse(args.recovery_target_time_string) if recovery_target_time.tzinfo is None: @@ -37,7 +40,12 @@ def read_configuration(): else: recovery_target_time = None - return options(args.scope, args.datadir, recovery_target_time, args.dry_run) + if args.recovery_target_timeline_string == None: + recovery_target_timeline = "latest" + else: + recovery_target_timeline = args.recovery_target_timeline_string + + return options(args.scope, args.datadir, recovery_target_time, recovery_target_timeline, args.dry_run) def build_wale_command(command, datadir=None, backup=None): @@ -65,7 +73,7 @@ def fix_output(output): yield '\t'.join(line.split()) -def choose_backup(backup_list, recovery_target_time): +def choose_backup(backup_list, recovery_target_time, recovery_target_timeline): """ pick up the latest backup file starting before time recovery_target_time""" match_timestamp = match = None @@ -140,7 +148,7 @@ def get_wale_environments(env): yield name, orig_value -def find_backup(recovery_target_time, env): +def find_backup(recovery_target_time, recovery_target_timeline, env): old_value = None for name, value in get_wale_environments(env): logger.info('Trying %s for clone', value) @@ -150,7 +158,7 @@ def find_backup(recovery_target_time, env): backup_list = list_backups(env) if backup_list: if recovery_target_time: - backup = choose_backup(backup_list, recovery_target_time) + backup = choose_backup(backup_list, recovery_target_time, recovery_target_timeline) if backup: return backup, (name if value != old_value else None) else: # We assume that the LATEST backup will be for the biggest postgres version! @@ -163,7 +171,7 @@ def find_backup(recovery_target_time, env): def run_clone_from_s3(options): env = os.environ.copy() - backup_name, update_envdir = find_backup(options.recovery_target_time, env) + backup_name, update_envdir = find_backup(options.recovery_target_time, options.recovery_target_timeline, env) backup_fetch_cmd = build_wale_command('backup-fetch', options.datadir, backup_name) logger.info("cloning cluster %s using %s", options.name, ' '.join(backup_fetch_cmd)) @@ -190,4 +198,4 @@ def main(): if __name__ == '__main__': - sys.exit(main()) + sys.exit(main()) \ No newline at end of file diff --git a/postgres-appliance/scripts/configure_spilo.py b/postgres-appliance/scripts/configure_spilo.py index 2102d2eed..abb07a38f 100755 --- a/postgres-appliance/scripts/configure_spilo.py +++ b/postgres-appliance/scripts/configure_spilo.py @@ -226,7 +226,7 @@ def deep_update(a, b): method: clone_with_wale clone_with_wale: command: envdir "{{CLONE_WALE_ENV_DIR}}" python3 /scripts/clone_with_wale.py - --recovery-target-time="{{CLONE_TARGET_TIME}}" + --recovery-target-time="{{CLONE_TARGET_TIME}}" --recovery-target-timeline="{{CLONE_TARGET_TIMELINE}}" recovery_conf: restore_command: envdir "{{CLONE_WALE_ENV_DIR}}" timeout "{{WAL_RESTORE_TIMEOUT}}" /scripts/restore_command.sh "%f" "%p" From c51d4855921c86d1dad946c995e43412d00a43bc Mon Sep 17 00:00:00 2001 From: Sudeepta Patra Date: Tue, 19 Jul 2022 09:32:43 +0000 Subject: [PATCH 2/5] remove default for timeline --- postgres-appliance/bootstrap/clone_with_wale.py | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/postgres-appliance/bootstrap/clone_with_wale.py b/postgres-appliance/bootstrap/clone_with_wale.py index c41559446..cbc5513b4 100755 --- a/postgres-appliance/bootstrap/clone_with_wale.py +++ b/postgres-appliance/bootstrap/clone_with_wale.py @@ -28,8 +28,7 @@ def read_configuration(): parser.add_argument('--dry-run', action='store_true', help='find a matching backup and build the wal-e ' 'command to fetch that backup without running it') parser.add_argument('--recovery-target-timeline', - help='the timeline up to which recovery will proceed', - dest='recovery_target_timeline_string') + help='the timeline from which recovery will proceed') args = parser.parse_args() options = namedtuple('Options', 'name datadir recovery_target_time recovery_target_timeline dry_run') @@ -40,12 +39,7 @@ def read_configuration(): else: recovery_target_time = None - if args.recovery_target_timeline_string == None: - recovery_target_timeline = "latest" - else: - recovery_target_timeline = args.recovery_target_timeline_string - - return options(args.scope, args.datadir, recovery_target_time, recovery_target_timeline, args.dry_run) + return options(args.scope, args.datadir, recovery_target_time, args.recovery_target_timeline, args.dry_run) def build_wale_command(command, datadir=None, backup=None): From 495947508759c82c322482c2d3baccbcdf3eee6c Mon Sep 17 00:00:00 2001 From: Sudeepta Patra Date: Tue, 19 Jul 2022 09:51:54 +0000 Subject: [PATCH 3/5] remove extra space --- postgres-appliance/bootstrap/clone_with_wale.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/postgres-appliance/bootstrap/clone_with_wale.py b/postgres-appliance/bootstrap/clone_with_wale.py index cbc5513b4..088be8b49 100755 --- a/postgres-appliance/bootstrap/clone_with_wale.py +++ b/postgres-appliance/bootstrap/clone_with_wale.py @@ -192,4 +192,4 @@ def main(): if __name__ == '__main__': - sys.exit(main()) \ No newline at end of file + sys.exit(main()) \ No newline at end of file From 95cb05d5c09ef0c64bbc539498be7ea4dfaf5cc4 Mon Sep 17 00:00:00 2001 From: Sudeepta Patra Date: Tue, 19 Jul 2022 09:54:20 +0000 Subject: [PATCH 4/5] add new line eof --- postgres-appliance/bootstrap/clone_with_wale.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/postgres-appliance/bootstrap/clone_with_wale.py b/postgres-appliance/bootstrap/clone_with_wale.py index 088be8b49..2bf5c1629 100755 --- a/postgres-appliance/bootstrap/clone_with_wale.py +++ b/postgres-appliance/bootstrap/clone_with_wale.py @@ -192,4 +192,4 @@ def main(): if __name__ == '__main__': - sys.exit(main()) \ No newline at end of file + sys.exit(main()) From 83170e0ffd52ff50c20d3fd833f5583d5596dbf1 Mon Sep 17 00:00:00 2001 From: sudeepta92 Date: Tue, 2 Aug 2022 11:35:11 +0000 Subject: [PATCH 5/5] fixing bug to choose backup from right timeline --- .../bootstrap/clone_with_wale.py | 42 ++++++++++++++----- 1 file changed, 31 insertions(+), 11 deletions(-) diff --git a/postgres-appliance/bootstrap/clone_with_wale.py b/postgres-appliance/bootstrap/clone_with_wale.py index 2bf5c1629..10e97f043 100755 --- a/postgres-appliance/bootstrap/clone_with_wale.py +++ b/postgres-appliance/bootstrap/clone_with_wale.py @@ -20,15 +20,20 @@ def read_configuration(): parser = argparse.ArgumentParser(description="Script to clone from S3 with support for point-in-time-recovery") - parser.add_argument('--scope', required=True, help='target cluster name') - parser.add_argument('--datadir', required=True, help='target cluster postgres data directory') + parser.add_argument('--scope', required=True, + help='target cluster name') + parser.add_argument('--datadir', required=True, + help='target cluster postgres data directory') parser.add_argument('--recovery-target-time', help='the timestamp up to which recovery will proceed (including time zone)', dest='recovery_target_time_string') - parser.add_argument('--dry-run', action='store_true', help='find a matching backup and build the wal-e ' + parser.add_argument('--dry-run', action='store_true', + help='find a matching backup and build the wal-e ' 'command to fetch that backup without running it') parser.add_argument('--recovery-target-timeline', - help='the timeline from which recovery will proceed') + help='the timeline up to which recovery will proceed. Leave empty for latest.', + dest='recovery_target_timeline', + type=lambda timeline_id: int(timeline_id,16)) args = parser.parse_args() options = namedtuple('Options', 'name datadir recovery_target_time recovery_target_timeline dry_run') @@ -39,8 +44,21 @@ def read_configuration(): else: recovery_target_time = None - return options(args.scope, args.datadir, recovery_target_time, args.recovery_target_timeline, args.dry_run) + if args.recovery_target_timeline == None: + recovery_target_timeline = get_latest_timeline() + else: + recovery_target_timeline = args.recovery_target_timeline + + return options(args.scope, args.datadir, recovery_target_time, recovery_target_timeline, args.dry_run) +def get_latest_timeline(): + env = os.environ.copy() + backup_list = list_backups(env) + latest_timeline_id = int("00000000",16) + for backup in backup_list: + if int(backup["name"][5:13], 16) > latest_timeline_id: + latest_timeline_id = int(backup["name"][5:13], 16) + return latest_timeline_id def build_wale_command(command, datadir=None, backup=None): cmd = ['wal-g' if os.getenv('USE_WALG_RESTORE') == 'true' else 'wal-e'] + [command] @@ -72,11 +90,13 @@ def choose_backup(backup_list, recovery_target_time, recovery_target_timeline): match_timestamp = match = None for backup in backup_list: - last_modified = parse(backup['last_modified']) - if last_modified < recovery_target_time: - if match is None or last_modified > match_timestamp: - match = backup - match_timestamp = last_modified + timeline_id = int(backup["name"][5:13], 16) + if timeline_id == recovery_target_timeline: + last_modified = parse(backup['last_modified']) + if last_modified < recovery_target_time: + if match is None or last_modified > match_timestamp: + match = backup + match_timestamp = last_modified if match is not None: return match['name'] @@ -156,7 +176,7 @@ def find_backup(recovery_target_time, recovery_target_timeline, env): if backup: return backup, (name if value != old_value else None) else: # We assume that the LATEST backup will be for the biggest postgres version! - return 'LATEST', (name if value != old_value else None) + return get_latest_timeline(), (name if value != old_value else None) if recovery_target_time: raise Exception('Could not find any backups prior to the point in time {0}'.format(recovery_target_time)) raise Exception('Could not find any backups')