1
0
mirror of https://github.com/postgrespro/pg_probackup.git synced 2025-03-17 21:18:00 +02:00

Merge branch 'issue_146'

This commit is contained in:
Grigory Smolkin 2020-04-19 03:15:00 +03:00
commit 765bba4bfe
12 changed files with 323 additions and 359 deletions

View File

@ -2402,6 +2402,11 @@ primary_conninfo = 'user=backup passfile=/var/lib/pgsql/.pgpass port=5432 sslmod
password is not included.
</para>
</listitem>
<listitem>
<para>
<literal>note</literal> — text note attached to backup.
</para>
</listitem>
</itemizedlist>
</para>
<para>
@ -3377,15 +3382,28 @@ pg_probackup set-config -B <replaceable>backup_dir</replaceable> --instance <rep
<title>set-backup</title>
<programlisting>
pg_probackup set-backup -B <replaceable>backup_dir</replaceable> --instance <replaceable>instance_name</replaceable> -i <replaceable>backup_id</replaceable>
{--ttl=<replaceable>ttl</replaceable> | --expire-time=<replaceable>time</replaceable>} [--help]
{--ttl=<replaceable>ttl</replaceable> | --expire-time=<replaceable>time</replaceable>}
[--note=<replaceable>backup_note</replaceable>] [--help]
</programlisting>
<para>
Sets the provided backup-specific settings into the
<filename>backup.control</filename> configuration file, or modifies the previously
defined values.
</para>
<varlistentry>
<term><option>--note=<replaceable>backup_note</replaceable></option></term>
<listitem>
<para>
For all available settings, see the section
Sets the text note for backup copy.
If <replaceable>backup_note</replaceable> contain newline characters,
then only substring before first newline character will be saved.
Max size of text note is 1 KB.
The <replaceable>'none'</replaceable> value removes current note.
</para>
</listitem>
</varlistentry>
<para>
For all available pinning settings, see the section
<link linkend="pbk-pinning-options">Pinning Options</link>.
</para>
</refsect3>
@ -3445,7 +3463,7 @@ pg_probackup backup -B <replaceable>backup_dir</replaceable> -b <replaceable>bac
[--no-validate] [--skip-block-validation]
[-w --no-password] [-W --password]
[--archive-timeout=<replaceable>timeout</replaceable>] [--external-dirs=<replaceable>external_directory_path</replaceable>]
[--no-sync]
[--no-sync] [--note=<replaceable>backup_note</replaceable>]
[<replaceable>connection_options</replaceable>] [<replaceable>compression_options</replaceable>] [<replaceable>remote_options</replaceable>]
[<replaceable>retention_options</replaceable>] [<replaceable>pinning_options</replaceable>] [<replaceable>logging_options</replaceable>]
</programlisting>
@ -3612,6 +3630,19 @@ pg_probackup backup -B <replaceable>backup_dir</replaceable> -b <replaceable>bac
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><option>--note=<replaceable>backup_note</replaceable></option></term>
<listitem>
<para>
Sets the text note for backup copy.
If <replaceable>backup_note</replaceable> contain newline characters,
then only substring before first newline character will be saved.
Max size of text note is 1 KB.
The <replaceable>'none'</replaceable> value removes current note.
</para>
</listitem>
</varlistentry>
</variablelist>
</para>

View File

@ -156,7 +156,6 @@ do_backup_instance(PGconn *backup_conn, PGNodeInfo *nodeInfo, bool no_sync)
/* used for multitimeline incremental backup */
parray *tli_list = NULL;
/* for fancy reporting */
time_t start_time, end_time;
char pretty_time[20];
@ -820,6 +819,7 @@ do_backup(time_t start_time, bool no_validate,
/* Update backup status and other metainfo. */
current.status = BACKUP_STATUS_RUNNING;
current.start_time = start_time;
StrNCpy(current.program_version, PROGRAM_VERSION,
sizeof(current.program_version));
@ -902,6 +902,10 @@ do_backup(time_t start_time, bool no_validate,
if (instance_config.master_conn_opt.pghost == NULL)
elog(ERROR, "Options for connection to master must be provided to perform backup from replica");
/* add note to backup if requested */
if (set_backup_params && set_backup_params->note)
add_note(&current, set_backup_params->note);
/* backup data */
do_backup_instance(backup_conn, &nodeInfo, no_sync);
pgut_atexit_pop(backup_cleanup, NULL);
@ -934,8 +938,7 @@ do_backup(time_t start_time, bool no_validate,
(set_backup_params->ttl > 0 ||
set_backup_params->expire_time > 0))
{
if (!pin_backup(&current, set_backup_params))
elog(ERROR, "Failed to pin the backup %s", base36enc(current.backup_id));
pin_backup(&current, set_backup_params);
}
if (!no_validate)

View File

@ -1534,18 +1534,23 @@ do_set_backup(const char *instance_name, time_t backup_id,
target_backup = (pgBackup *) parray_get(backup_list, 0);
if (!pin_backup(target_backup, set_backup_params))
elog(ERROR, "Failed to pin the backup %s", base36enc(backup_id));
/* Pin or unpin backup if requested */
if (set_backup_params->ttl >= 0 || set_backup_params->expire_time > 0)
pin_backup(target_backup, set_backup_params);
if (set_backup_params->note)
add_note(target_backup, set_backup_params->note);
}
/*
* Set 'expire-time' attribute based on set_backup_params, or unpin backup
* if ttl is equal to zero.
*/
bool
void
pin_backup(pgBackup *target_backup, pgSetBackupParams *set_backup_params)
{
/* sanity, backup must have positive recovery-time */
if (target_backup->recovery_time <= 0)
elog(ERROR, "Failed to set 'expire-time' for backup %s: invalid 'recovery-time'",
base36enc(target_backup->backup_id));
@ -1563,7 +1568,7 @@ pin_backup(pgBackup *target_backup, pgSetBackupParams *set_backup_params)
{
elog(WARNING, "Backup %s is not pinned, nothing to unpin",
base36enc(target_backup->start_time));
return false;
return;
}
target_backup->expire_time = 0;
}
@ -1571,7 +1576,8 @@ pin_backup(pgBackup *target_backup, pgSetBackupParams *set_backup_params)
else if (set_backup_params->expire_time > 0)
target_backup->expire_time = set_backup_params->expire_time;
else
return false;
/* nothing to do */
return;
/* Update backup.control */
write_backup(target_backup);
@ -1587,7 +1593,44 @@ pin_backup(pgBackup *target_backup, pgSetBackupParams *set_backup_params)
else
elog(INFO, "Backup %s is unpinned", base36enc(target_backup->start_time));
return true;
return;
}
/*
* Add note to backup metadata or unset already existing note.
* It is a job of the caller to make sure that note is not NULL.
*/
void
add_note(pgBackup *target_backup, char *note)
{
char *note_string;
/* unset note */
if (pg_strcasecmp(note, "none") == 0)
{
target_backup->note = NULL;
elog(INFO, "Removing note from backup %s",
base36enc(target_backup->start_time));
}
else
{
/* Currently we do not allow string with newlines as note,
* because it will break parsing of backup.control.
* So if user provides string like this "aaa\nbbbbb",
* we save only "aaa"
* Example: tests.set_backup.SetBackupTest.test_add_note_newlines
*/
note_string = pgut_malloc(MAX_NOTE_SIZE);
sscanf(note, "%[^\n]", note_string);
target_backup->note = note_string;
elog(INFO, "Adding note to backup %s: '%s'",
base36enc(target_backup->start_time), target_backup->note);
}
/* Update backup.control */
write_backup(target_backup);
}
/*
@ -1682,6 +1725,10 @@ pgBackupWriteControl(FILE *out, pgBackup *backup)
/* print external directories list */
if (backup->external_dir_str)
fio_fprintf(out, "external-dirs = '%s'\n", backup->external_dir_str);
if (backup->note)
fio_fprintf(out, "note = '%s'\n", backup->note);
}
/*
@ -1919,6 +1966,7 @@ readBackupControlFile(const char *path)
{'b', 0, "from-replica", &backup->from_replica, SOURCE_FILE_STRICT},
{'s', 0, "primary-conninfo", &backup->primary_conninfo, SOURCE_FILE_STRICT},
{'s', 0, "external-dirs", &backup->external_dir_str, SOURCE_FILE_STRICT},
{'s', 0, "note", &backup->note, SOURCE_FILE_STRICT},
{0}
};
@ -2185,6 +2233,8 @@ pgBackupInit(pgBackup *backup)
backup->external_dir_str = NULL;
backup->root_dir = NULL;
backup->files = NULL;
backup->note = NULL;
}
/* free pgBackup object */
@ -2196,6 +2246,7 @@ pgBackupFree(void *backup)
pfree(b->primary_conninfo);
pfree(b->external_dir_str);
pfree(b->root_dir);
pfree(b->note);
pfree(backup);
}

View File

@ -106,7 +106,8 @@ help_pg_probackup(void)
printf(_(" [--help]\n"));
printf(_("\n %s set-backup -B backup-path --instance=instance_name\n"), PROGRAM_NAME);
printf(_(" -i backup-id [--ttl] [--expire-time]\n"));
printf(_(" -i backup-id [--ttl=interval] [--expire-time=timestamp]\n"));
printf(_(" [--note=text]\n"));
printf(_(" [--help]\n"));
printf(_("\n %s show-config -B backup-path --instance=instance_name\n"), PROGRAM_NAME);
@ -140,7 +141,7 @@ help_pg_probackup(void)
printf(_(" [--remote-proto] [--remote-host]\n"));
printf(_(" [--remote-port] [--remote-path] [--remote-user]\n"));
printf(_(" [--ssh-options]\n"));
printf(_(" [--ttl] [--expire-time]\n"));
printf(_(" [--ttl=interval] [--expire-time=timestamp] [--note=text]\n"));
printf(_(" [--help]\n"));
@ -285,7 +286,7 @@ help_backup(void)
printf(_(" [--remote-proto] [--remote-host]\n"));
printf(_(" [--remote-port] [--remote-path] [--remote-user]\n"));
printf(_(" [--ssh-options]\n"));
printf(_(" [--ttl] [--expire-time]\n\n"));
printf(_(" [--ttl=interval] [--expire-time=timestamp] [--note=text]\n\n"));
printf(_(" -B, --backup-path=backup-path location of the backup storage area\n"));
printf(_(" -b, --backup-mode=backup-mode backup mode=FULL|PAGE|DELTA|PTRACK\n"));
@ -304,6 +305,8 @@ help_backup(void)
printf(_(" backup some directories not from pgdata \n"));
printf(_(" (example: --external-dirs=/tmp/dir1:/tmp/dir2)\n"));
printf(_(" --no-sync do not sync backed up files to disk\n"));
printf(_(" --note=text add note to backup\n"));
printf(_(" (example: --note='backup before app update to v13.1')\n"));
printf(_("\n Logging options:\n"));
printf(_(" --log-level-console=log-level-console\n"));
@ -341,8 +344,9 @@ help_backup(void)
printf(_(" --dry-run perform a trial run without any changes\n"));
printf(_("\n Pinning options:\n"));
printf(_(" --ttl=ttl pin backup for specified amount of time; 0 unpin\n"));
printf(_(" --ttl=interval pin backup for specified amount of time; 0 unpin\n"));
printf(_(" available units: 'ms', 's', 'min', 'h', 'd' (default: s)\n"));
printf(_(" (example: --ttl=20d)\n"));
printf(_(" --expire-time=time pin backup until specified time stamp\n"));
printf(_(" (example: --expire-time='2024-01-01 00:00:00+03')\n"));
@ -710,12 +714,15 @@ help_set_backup(void)
{
printf(_("\n%s set-backup -B backup-path --instance=instance_name\n"), PROGRAM_NAME);
printf(_(" -i backup-id\n"));
printf(_(" [--ttl] [--expire-time]\n\n"));
printf(_(" [--ttl=interval] [--expire-time=time] [--note=text]\n\n"));
printf(_(" --ttl=ttl pin backup for specified amount of time; 0 unpin\n"));
printf(_(" --ttl=interval pin backup for specified amount of time; 0 unpin\n"));
printf(_(" available units: 'ms', 's', 'min', 'h', 'd' (default: s)\n"));
printf(_(" (example: --ttl=20d)\n"));
printf(_(" --expire-time=time pin backup until specified time stamp\n"));
printf(_(" (example: --expire-time='2024-01-01 00:00:00+03')\n"));
printf(_(" --note=text add note to backup; 'none' to remove note\n"));
printf(_(" (example: --note='backup before app update to v13.1')\n"));
}
static void

View File

@ -702,6 +702,15 @@ merge_chain(parray *parent_chain, pgBackup *full_backup, pgBackup *dest_backup)
full_backup->compress_alg = dest_backup->compress_alg;
full_backup->compress_level = dest_backup->compress_level;
/* If incremental backup is pinned,
* then result FULL backup must also be pinned.
*/
if (dest_backup->expire_time)
full_backup->expire_time = dest_backup->expire_time;
if (dest_backup->note)
full_backup->note = pgut_strdup(dest_backup->note);
/* FULL backup must inherit wal mode. */
full_backup->stream = dest_backup->stream;

View File

@ -75,10 +75,10 @@ char *replication_slot = NULL;
bool temp_slot = false;
/* backup options */
bool backup_logs = false;
bool smooth_checkpoint;
char *remote_agent;
bool backup_logs = false;
bool smooth_checkpoint;
char *remote_agent;
static char *backup_note = NULL;
/* restore options */
static char *target_time = NULL;
static char *target_xid = NULL;
@ -183,6 +183,7 @@ static ConfigOption cmd_options[] =
{ 'b', 183, "delete-expired", &delete_expired, SOURCE_CMD_STRICT },
{ 'b', 184, "merge-expired", &merge_expired, SOURCE_CMD_STRICT },
{ 'b', 185, "dry-run", &dry_run, SOURCE_CMD_STRICT },
{ 's', 238, "note", &backup_note, SOURCE_CMD_STRICT },
/* restore options */
{ 's', 136, "recovery-target-time", &target_time, SOURCE_CMD_STRICT },
{ 's', 137, "recovery-target-xid", &target_xid, SOURCE_CMD_STRICT },
@ -746,11 +747,15 @@ main(int argc, char *argv[])
expire_time_string);
}
if (expire_time > 0 || ttl >= 0)
if (expire_time > 0 || ttl >= 0 || backup_note)
{
set_backup_params = pgut_new(pgSetBackupParams);
set_backup_params->ttl = ttl;
set_backup_params->expire_time = expire_time;
set_backup_params->note = backup_note;
if (backup_note && strlen(backup_note) > MAX_NOTE_SIZE)
elog(ERROR, "Backup note cannot exceed %u bytes", MAX_NOTE_SIZE);
}
}

View File

@ -90,6 +90,8 @@ extern const char *PROGRAM_EMAIL;
/* retry attempts */
#define PAGE_READ_ATTEMPTS 100
#define MAX_NOTE_SIZE 1024
/* Check if an XLogRecPtr value is pointed to 0 offset */
#define XRecOffIsNull(xlrp) \
((xlrp) % XLOG_BLCKSZ == 0)
@ -390,6 +392,7 @@ struct pgBackup
backup_path/instance_name/backup_id */
parray *files; /* list of files belonging to this backup
* must be populated explicitly */
char *note;
};
/* Recovery target for restore and validate subcommands */
@ -433,13 +436,14 @@ typedef struct pgRestoreParams
/* Options needed for set-backup command */
typedef struct pgSetBackupParams
{
int64 ttl; /* amount of time backup must be pinned
int64 ttl; /* amount of time backup must be pinned
* -1 - do nothing
* 0 - disable pinning
*/
time_t expire_time; /* Point in time before which backup
time_t expire_time; /* Point in time until backup
* must be pinned.
*/
char *note;
} pgSetBackupParams;
typedef struct
@ -778,8 +782,9 @@ extern void timelineInfoFree(void *tliInfo);
extern parray *catalog_get_timelines(InstanceConfig *instance);
extern void do_set_backup(const char *instance_name, time_t backup_id,
pgSetBackupParams *set_backup_params);
extern bool pin_backup(pgBackup *target_backup,
extern void pin_backup(pgBackup *target_backup,
pgSetBackupParams *set_backup_params);
extern void add_note(pgBackup *target_backup, char *note);
extern void pgBackupWriteControl(FILE *out, pgBackup *backup);
extern void write_backup_filelist(pgBackup *backup, parray *files,
const char *root, parray *external_list);

View File

@ -430,6 +430,11 @@ print_backup_json_object(PQExpBuffer buf, pgBackup *backup)
json_add_value(buf, "status", status2str(backup->status), json_level,
true);
if (backup->note){
json_add_value(buf, "note", backup->note,
json_level, true);
}
json_add(buf, JT_END_OBJECT, &json_level);
}

View File

@ -2709,3 +2709,40 @@ class BackupTest(ProbackupTest, unittest.TestCase):
# Clean after yourself
self.del_test_dir(module_name, fname)
def test_note_sanity(self):
"""
test that adding note to backup works as expected
"""
fname = self.id().split('.')[3]
backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
node = self.make_simple_node(
base_dir=os.path.join(module_name, fname, 'node'),
initdb_params=['--data-checksums'])
self.init_pb(backup_dir)
self.add_instance(backup_dir, 'node', node)
self.set_archiving(backup_dir, 'node', node)
node.slow_start()
# FULL backup
backup_id = self.backup_node(
backup_dir, 'node', node,
options=['--stream', '--log-level-file=LOG', '--note=test_note'])
show_backups = self.show_pb(backup_dir, 'node')
print(self.show_pb(backup_dir, as_text=True, as_json=True))
self.assertEqual(show_backups[0]['note'], "test_note")
self.set_backup(backup_dir, 'node', backup_id, options=['--note=none'])
backup_meta = self.show_pb(backup_dir, 'node', backup_id)
self.assertNotIn(
'note',
backup_meta)
# Clean after yourself
self.del_test_dir(module_name, fname)

View File

@ -22,8 +22,7 @@ class MergeTest(ProbackupTest, unittest.TestCase):
# Initialize instance and backup directory
node = self.make_simple_node(
base_dir=os.path.join(module_name, fname, 'node'),
initdb_params=["--data-checksums"]
)
initdb_params=["--data-checksums"])
self.init_pb(backup_dir)
self.add_instance(backup_dir, "node", node)
@ -1981,8 +1980,7 @@ class MergeTest(ProbackupTest, unittest.TestCase):
base_dir=os.path.join(module_name, fname, 'node'),
set_replication=True,
initdb_params=['--data-checksums'],
pg_options={
'autovacuum': 'off'})
pg_options={'autovacuum': 'off'})
backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
self.init_pb(backup_dir)
@ -2384,5 +2382,60 @@ class MergeTest(ProbackupTest, unittest.TestCase):
self.del_test_dir(module_name, fname)
def test_merge_correct_inheritance(self):
"""
Make sure that backup metainformation fields
'note' and 'expire-time' are correctly inherited
during merge
"""
fname = self.id().split('.')[3]
backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
node = self.make_simple_node(
base_dir=os.path.join(module_name, fname, 'node'),
set_replication=True,
initdb_params=['--data-checksums'],
pg_options={'autovacuum': 'off'})
self.init_pb(backup_dir)
self.add_instance(backup_dir, 'node', node)
self.set_archiving(backup_dir, 'node', node)
node.slow_start()
# add database
node.safe_psql(
'postgres',
'CREATE DATABASE testdb')
# take FULL backup
self.backup_node(backup_dir, 'node', node, options=['--stream'])
# create database
node.safe_psql(
'postgres',
'create DATABASE testdb1')
# take PAGE backup
page_id = self.backup_node(
backup_dir, 'node', node, backup_type='page')
self.set_backup(
backup_dir, 'node', page_id, options=['--note=hello', '--ttl=20d'])
page_meta = self.show_pb(backup_dir, 'node', page_id)
self.merge_backup(backup_dir, 'node', page_id)
print(self.show_pb(backup_dir, 'node', page_id))
self.assertEqual(
page_meta['note'],
self.show_pb(backup_dir, 'node', page_id)['note'])
self.assertEqual(
page_meta['expire-time'],
self.show_pb(backup_dir, 'node', page_id)['expire-time'])
self.del_test_dir(module_name, fname)
# 1. Need new test with corrupted FULL backup
# 2. different compression levels

View File

@ -1840,335 +1840,6 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
# Clean after yourself
self.del_test_dir(module_name, fname)
# @unittest.expectedFailure
@unittest.skip("skip")
def test_wal_depth(self):
"""
ARCHIVE replica:
t6 |---------------------->
t5 | |------>
| |
t4 | |----|------>
| |
t3 | |--B1--|/|--B2-|/|-B3-->
| |
t2 |--A1-----|--A2--->
t1 ---------Y1--Y2-|
ARCHIVE master:
t1 -Z1--Z2-->
"""
fname = self.id().split('.')[3]
backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
master = self.make_simple_node(
base_dir=os.path.join(module_name, fname, 'master'),
set_replication=True,
initdb_params=['--data-checksums'],
pg_options={
'archive_timeout': '30s',
'checkpoint_timeout': '30s',
'autovacuum': 'off'})
self.init_pb(backup_dir)
self.add_instance(backup_dir, 'master', master)
self.set_archiving(backup_dir, 'master', master)
master.slow_start()
# FULL
master.safe_psql(
"postgres",
"create table t_heap as select i as id, md5(i::text) as text, "
"md5(repeat(i::text,10))::tsvector as tsvector "
"from generate_series(0,10000) i")
self.backup_node(backup_dir, 'master', master)
# PAGE
master.safe_psql(
"postgres",
"insert into t_heap select i as id, md5(i::text) as text, "
"md5(repeat(i::text,10))::tsvector as tsvector "
"from generate_series(10000,20000) i")
self.backup_node(
backup_dir, 'master', master, backup_type='page')
replica = self.make_simple_node(
base_dir=os.path.join(module_name, fname, 'replica'))
replica.cleanup()
self.restore_node(backup_dir, 'master', replica)
self.set_replica(master, replica)
self.add_instance(backup_dir, 'replica', replica)
self.set_archiving(backup_dir, 'replica', replica, replica=True)
copy_tree(
os.path.join(backup_dir, 'wal', 'master'),
os.path.join(backup_dir, 'wal', 'replica'))
# Check data correctness on replica
replica.slow_start(replica=True)
# FULL backup replica
Y1 = self.backup_node(
backup_dir, 'replica', replica,
options=['--stream', '--archive-timeout=60s'])
master.pgbench_init(scale=5)
# PAGE backup replica
Y2 = self.backup_node(
backup_dir, 'replica', replica,
backup_type='page', options=['--stream', '--archive-timeout=60s'])
# create timeline t2
replica.promote()
# do checkpoint to increment timeline ID in pg_control
replica.safe_psql(
'postgres',
'CHECKPOINT')
# FULL backup replica
A1 = self.backup_node(
backup_dir, 'replica', replica)
replica.pgbench_init(scale=5)
replica.safe_psql(
'postgres',
"CREATE TABLE t1 (a text)")
target_xid = None
with replica.connect("postgres") as con:
res = con.execute(
"INSERT INTO t1 VALUES ('inserted') RETURNING (xmin)")
con.commit()
target_xid = res[0][0]
# DELTA backup replica
A2 = self.backup_node(
backup_dir, 'replica', replica, backup_type='delta')
# create timeline t3
replica.cleanup()
self.restore_node(
backup_dir, 'replica', replica,
options=[
'--recovery-target-xid={0}'.format(target_xid),
'--recovery-target-timeline=2',
'--recovery-target-action=promote'])
replica.slow_start()
B1 = self.backup_node(
backup_dir, 'replica', replica)
replica.pgbench_init(scale=2)
B2 = self.backup_node(
backup_dir, 'replica', replica, backup_type='page')
replica.pgbench_init(scale=2)
target_xid = None
with replica.connect("postgres") as con:
res = con.execute(
"INSERT INTO t1 VALUES ('inserted') RETURNING (xmin)")
con.commit()
target_xid = res[0][0]
B3 = self.backup_node(
backup_dir, 'replica', replica, backup_type='page')
replica.pgbench_init(scale=2)
# create timeline t4
replica.cleanup()
self.restore_node(
backup_dir, 'replica', replica,
options=[
'--recovery-target-xid={0}'.format(target_xid),
'--recovery-target-timeline=3',
'--recovery-target-action=promote'])
replica.slow_start()
replica.safe_psql(
'postgres',
'CREATE TABLE '
't2 as select i, '
'repeat(md5(i::text),5006056) as fat_attr '
'from generate_series(0,6) i')
target_xid = None
with replica.connect("postgres") as con:
res = con.execute(
"INSERT INTO t1 VALUES ('inserted') RETURNING (xmin)")
con.commit()
target_xid = res[0][0]
replica.safe_psql(
'postgres',
'CREATE TABLE '
't3 as select i, '
'repeat(md5(i::text),5006056) as fat_attr '
'from generate_series(0,10) i')
# create timeline t5
replica.cleanup()
self.restore_node(
backup_dir, 'replica', replica,
options=[
'--recovery-target-xid={0}'.format(target_xid),
'--recovery-target-timeline=4',
'--recovery-target-action=promote'])
replica.slow_start()
replica.safe_psql(
'postgres',
'CREATE TABLE '
't4 as select i, '
'repeat(md5(i::text),5006056) as fat_attr '
'from generate_series(0,6) i')
# create timeline t6
replica.cleanup()
self.restore_node(
backup_dir, 'replica', replica, backup_id=A1,
options=[
'--recovery-target=immediate',
'--recovery-target-action=promote'])
replica.slow_start()
replica.pgbench_init(scale=2)
show = self.show_archive(backup_dir, as_text=True)
show = self.show_archive(backup_dir)
for instance in show:
if instance['instance'] == 'replica':
replica_timelines = instance['timelines']
if instance['instance'] == 'master':
master_timelines = instance['timelines']
# check that all timelines are ok
for timeline in replica_timelines:
self.assertTrue(timeline['status'], 'OK')
# check that all timelines are ok
for timeline in master_timelines:
self.assertTrue(timeline['status'], 'OK')
# create holes in t3
wals_dir = os.path.join(backup_dir, 'wal', 'replica')
wals = [
f for f in os.listdir(wals_dir) if os.path.isfile(os.path.join(wals_dir, f))
and not f.endswith('.backup') and not f.endswith('.history') and f.startswith('00000003')
]
wals.sort()
# check that t3 is ok
self.show_archive(backup_dir)
file = os.path.join(backup_dir, 'wal', 'replica', '000000030000000000000017')
if self.archive_compress:
file = file + '.gz'
os.remove(file)
file = os.path.join(backup_dir, 'wal', 'replica', '000000030000000000000012')
if self.archive_compress:
file = file + '.gz'
os.remove(file)
file = os.path.join(backup_dir, 'wal', 'replica', '000000030000000000000013')
if self.archive_compress:
file = file + '.gz'
os.remove(file)
# check that t3 is not OK
show = self.show_archive(backup_dir)
show = self.show_archive(backup_dir)
for instance in show:
if instance['instance'] == 'replica':
replica_timelines = instance['timelines']
# sanity
for timeline in replica_timelines:
if timeline['tli'] == 1:
timeline_1 = timeline
continue
if timeline['tli'] == 2:
timeline_2 = timeline
continue
if timeline['tli'] == 3:
timeline_3 = timeline
continue
if timeline['tli'] == 4:
timeline_4 = timeline
continue
if timeline['tli'] == 5:
timeline_5 = timeline
continue
if timeline['tli'] == 6:
timeline_6 = timeline
continue
self.assertEqual(timeline_6['status'], "OK")
self.assertEqual(timeline_5['status'], "OK")
self.assertEqual(timeline_4['status'], "OK")
self.assertEqual(timeline_3['status'], "DEGRADED")
self.assertEqual(timeline_2['status'], "OK")
self.assertEqual(timeline_1['status'], "OK")
self.assertEqual(len(timeline_3['lost-segments']), 2)
self.assertEqual(timeline_3['lost-segments'][0]['begin-segno'], '0000000000000012')
self.assertEqual(timeline_3['lost-segments'][0]['end-segno'], '0000000000000013')
self.assertEqual(timeline_3['lost-segments'][1]['begin-segno'], '0000000000000017')
self.assertEqual(timeline_3['lost-segments'][1]['end-segno'], '0000000000000017')
self.assertEqual(len(timeline_6['backups']), 0)
self.assertEqual(len(timeline_5['backups']), 0)
self.assertEqual(len(timeline_4['backups']), 0)
self.assertEqual(len(timeline_3['backups']), 3)
self.assertEqual(len(timeline_2['backups']), 2)
self.assertEqual(len(timeline_1['backups']), 2)
# check closest backup correctness
self.assertEqual(timeline_6['closest-backup-id'], A1)
self.assertEqual(timeline_5['closest-backup-id'], B2)
self.assertEqual(timeline_4['closest-backup-id'], B2)
self.assertEqual(timeline_3['closest-backup-id'], A1)
self.assertEqual(timeline_2['closest-backup-id'], Y2)
# check parent tli correctness
self.assertEqual(timeline_6['parent-tli'], 2)
self.assertEqual(timeline_5['parent-tli'], 4)
self.assertEqual(timeline_4['parent-tli'], 3)
self.assertEqual(timeline_3['parent-tli'], 2)
self.assertEqual(timeline_2['parent-tli'], 1)
self.assertEqual(timeline_1['parent-tli'], 0)
output = self.delete_pb(
backup_dir, 'replica',
options=['--delete-wal', '--log-level-console=verbose'])
self.validate_pb(backup_dir, 'node')
self.del_test_dir(module_name, fname)
def test_wal_depth_1(self):
"""
|-------------B5----------> WAL timeline3

View File

@ -383,5 +383,92 @@ class SetBackupTest(ProbackupTest, unittest.TestCase):
self.validate_pb(backup_dir)
# Clean after yourself
self.del_test_dir(module_name, fname)
# @unittest.skip("skip")
def test_add_note_newlines(self):
""""""
fname = self.id().split('.')[3]
node = self.make_simple_node(
base_dir=os.path.join(module_name, fname, 'node'),
initdb_params=['--data-checksums'])
backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
self.init_pb(backup_dir)
self.add_instance(backup_dir, 'node', node)
node.slow_start()
# FULL
backup_id = self.backup_node(
backup_dir, 'node', node,
options=['--stream', '--note={0}'.format('hello\nhello')])
backup_meta = self.show_pb(backup_dir, 'node', backup_id)
self.assertEqual(backup_meta['note'], "hello")
self.set_backup(backup_dir, 'node', backup_id, options=['--note=hello\nhello'])
backup_meta = self.show_pb(backup_dir, 'node', backup_id)
self.assertEqual(backup_meta['note'], "hello")
self.set_backup(backup_dir, 'node', backup_id, options=['--note=none'])
backup_meta = self.show_pb(backup_dir, 'node', backup_id)
self.assertNotIn('note', backup_meta)
# Clean after yourself
self.del_test_dir(module_name, fname)
# @unittest.skip("skip")
def test_add_big_note(self):
""""""
fname = self.id().split('.')[3]
node = self.make_simple_node(
base_dir=os.path.join(module_name, fname, 'node'),
initdb_params=['--data-checksums'])
backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
self.init_pb(backup_dir)
self.add_instance(backup_dir, 'node', node)
node.slow_start()
# note = node.safe_psql(
# "postgres",
# "SELECT repeat('hello', 400)").rstrip() # TODO: investigate
note = node.safe_psql(
"postgres",
"SELECT repeat('hello', 210)").rstrip()
# FULL
try:
self.backup_node(
backup_dir, 'node', node,
options=['--stream', '--note={0}'.format(note)])
# we should die here because exception is what we expect to happen
self.assertEqual(
1, 0,
"Expecting Error because note is too large "
"\n Output: {0} \n CMD: {1}".format(
repr(self.output), self.cmd))
except ProbackupException as e:
self.assertIn(
"ERROR: Backup note cannot exceed 1024 bytes",
e.message,
"\n Unexpected Error Message: {0}\n CMD: {1}".format(
repr(e.message), self.cmd))
note = node.safe_psql(
"postgres",
"SELECT repeat('hello', 200)").rstrip()
backup_id = self.backup_node(
backup_dir, 'node', node,
options=['--stream', '--note={0}'.format(note)])
backup_meta = self.show_pb(backup_dir, 'node', backup_id)
self.assertEqual(backup_meta['note'], note)
# Clean after yourself
self.del_test_dir(module_name, fname)