Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/ timeline id in clone section for Spilo #760

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 37 additions & 15 deletions postgres-appliance/bootstrap/clone_with_wale.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,25 +20,45 @@

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,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
parser.add_argument('--scope', required=True,
parser.add_argument('--scope', required=True,

help='target cluster name')
parser.add_argument('--datadir', required=True,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
parser.add_argument('--datadir', required=True,
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',
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
parser.add_argument('--dry-run', action='store_true',
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. Leave empty for latest.',
dest='recovery_target_timeline',
type=lambda timeline_id: int(timeline_id,16))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
type=lambda timeline_id: int(timeline_id,16))
type=lambda timeline_id: int(timeline_id, 16))

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:
raise Exception("recovery target time must contain a timezone")
else:
recovery_target_time = None

return options(args.scope, args.datadir, recovery_target_time, args.dry_run)
if args.recovery_target_timeline == None:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if args.recovery_target_timeline == None:
if args.recovery_target_timeline is 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():
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def get_latest_timeline():
def get_latest_timeline():

env = os.environ.copy()
backup_list = list_backups(env)
latest_timeline_id = int("00000000",16)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
latest_timeline_id = int("00000000",16)
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):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def build_wale_command(command, datadir=None, backup=None):
def build_wale_command(command, datadir=None, backup=None):

cmd = ['wal-g' if os.getenv('USE_WALG_RESTORE') == 'true' else 'wal-e'] + [command]
Expand All @@ -65,16 +85,18 @@ 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):
hughcapet marked this conversation as resolved.
Show resolved Hide resolved
""" pick up the latest backup file starting before time recovery_target_time"""

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']

Expand Down Expand Up @@ -140,7 +162,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)
Expand All @@ -150,11 +172,11 @@ 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!
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')
Expand All @@ -163,7 +185,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))
Expand Down
2 changes: 1 addition & 1 deletion postgres-appliance/scripts/configure_spilo.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down