mirror of
https://github.com/postgrespro/pg_probackup.git
synced 2026-06-21 01:34:15 +02:00
Merge branch 'pgpro_1053_697'
This commit is contained in:
@@ -811,6 +811,7 @@ do_backup(time_t start_time)
|
||||
backup_conn = pgut_connect(pgut_dbname);
|
||||
pgut_atexit_push(backup_disconnect, NULL);
|
||||
|
||||
current.primary_conninfo = pgut_get_conninfo_string(backup_conn);
|
||||
/* Confirm data block size and xlog block size are compatible */
|
||||
confirm_block_size("block_size", BLCKSZ);
|
||||
confirm_block_size("wal_block_size", XLOG_BLCKSZ);
|
||||
|
||||
@@ -437,6 +437,10 @@ pgBackupWriteControl(FILE *out, pgBackup *backup)
|
||||
/* 'parent_backup' is set if it is incremental backup */
|
||||
if (backup->parent_backup != 0)
|
||||
fprintf(out, "parent-backup-id = '%s'\n", base36enc(backup->parent_backup));
|
||||
|
||||
/* print connection info except password */
|
||||
if (backup->primary_conninfo)
|
||||
fprintf(out, "primary_conninfo = '%s'\n", backup->primary_conninfo);
|
||||
}
|
||||
|
||||
/* create BACKUP_CONTROL_FILE */
|
||||
@@ -498,6 +502,7 @@ readBackupControlFile(const char *path)
|
||||
{'s', 0, "compress-alg", &compress_alg, SOURCE_FILE_STRICT},
|
||||
{'u', 0, "compress-level", &compress_level, SOURCE_FILE_STRICT},
|
||||
{'b', 0, "from-replica", &from_replica, SOURCE_FILE_STRICT},
|
||||
{'s', 0, "primary-conninfo", &backup->primary_conninfo, SOURCE_FILE_STRICT},
|
||||
{0}
|
||||
};
|
||||
|
||||
|
||||
+17
-1
@@ -117,6 +117,9 @@ help_pg_probackup(void)
|
||||
printf(_(" [-D pgdata-dir] [-i backup-id] [--progress]\n"));
|
||||
printf(_(" [--time=time|--xid=xid [--inclusive=boolean]]\n"));
|
||||
printf(_(" [--timeline=timeline] [-T OLDDIR=NEWDIR]\n"));
|
||||
printf(_(" [--immediate] [--recovery-target-name=target-name]\n"));
|
||||
printf(_(" [--recovery-target-action=pause|promote|shutdown]\n"));
|
||||
printf(_(" [--restore-as-replica]\n"));
|
||||
|
||||
printf(_("\n %s validate -B backup-dir [--instance=instance_name]\n"), PROGRAM_NAME);
|
||||
printf(_(" [-i backup-id] [--progress]\n"));
|
||||
@@ -259,7 +262,10 @@ help_restore(void)
|
||||
printf(_("%s restore -B backup-dir --instance=instance_name\n"), PROGRAM_NAME);
|
||||
printf(_(" [-D pgdata-dir] [-i backup-id] [--progress]\n"));
|
||||
printf(_(" [--time=time|--xid=xid [--inclusive=boolean]]\n"));
|
||||
printf(_(" [--timeline=timeline] [-T OLDDIR=NEWDIR]\n\n"));
|
||||
printf(_(" [--timeline=timeline] [-T OLDDIR=NEWDIR]\n"));
|
||||
printf(_(" [--immediate] [--recovery-target-name=target-name]\n"));
|
||||
printf(_(" [--recovery-target-action=pause|promote|shutdown]\n"));
|
||||
printf(_(" [--restore-as-replica]\n\n"));
|
||||
|
||||
printf(_(" -B, --backup-path=backup-path location of the backup storage area\n"));
|
||||
printf(_(" --instance=instance_name name of the instance\n"));
|
||||
@@ -275,6 +281,16 @@ help_restore(void)
|
||||
printf(_(" -T, --tablespace-mapping=OLDDIR=NEWDIR\n"));
|
||||
printf(_(" relocate the tablespace from directory OLDDIR to NEWDIR\n"));
|
||||
|
||||
printf(_(" --immediate end recovery as soon as a consistent state is reached\n"));
|
||||
printf(_(" --recovery-target-name=target-name\n"));
|
||||
printf(_(" the named restore point to which recovery will proceed\n"));
|
||||
printf(_(" --recovery-target-action=pause|promote|shutdown\n"));
|
||||
printf(_(" action the server should take once the recovery target is reached\n"));
|
||||
printf(_(" (default: pause)\n"));
|
||||
|
||||
printf(_(" -R, --restore-as-replica write a minimal recovery.conf in the output directory\n"));
|
||||
printf(_(" to ease setting up a standby server\n"));
|
||||
|
||||
printf(_("\n Logging options:\n"));
|
||||
printf(_(" --log-level-console=log-level-console\n"));
|
||||
printf(_(" level for console logging (default: info)\n"));
|
||||
|
||||
+20
-6
@@ -62,6 +62,13 @@ static char *target_time;
|
||||
static char *target_xid;
|
||||
static char *target_inclusive;
|
||||
static TimeLineID target_tli;
|
||||
static bool target_immediate;
|
||||
static char *target_name = NULL;
|
||||
static char *target_action = NULL;;
|
||||
|
||||
static pgRecoveryTarget *recovery_target_options = NULL;
|
||||
|
||||
bool restore_as_replica = false;
|
||||
|
||||
/* delete options */
|
||||
bool delete_wal = false;
|
||||
@@ -132,6 +139,10 @@ static pgut_option options[] =
|
||||
{ 's', 22, "inclusive", &target_inclusive, SOURCE_CMDLINE },
|
||||
{ 'u', 23, "timeline", &target_tli, SOURCE_CMDLINE },
|
||||
{ 'f', 'T', "tablespace-mapping", opt_tablespace_map, SOURCE_CMDLINE },
|
||||
{ 'b', 24, "immediate", &target_immediate, SOURCE_CMDLINE },
|
||||
{ 's', 25, "recovery-target-name", &target_name, SOURCE_CMDLINE },
|
||||
{ 's', 26, "recovery-target-action", &target_action, SOURCE_CMDLINE },
|
||||
{ 'b', 'R', "restore-as-replica", &restore_as_replica, SOURCE_CMDLINE },
|
||||
/* delete options */
|
||||
{ 'b', 130, "wal", &delete_wal, SOURCE_CMDLINE },
|
||||
{ 'b', 131, "expired", &delete_expired, SOURCE_CMDLINE },
|
||||
@@ -411,8 +422,13 @@ main(int argc, char *argv[])
|
||||
pgdata_exclude_dir[i] = "pg_log";
|
||||
}
|
||||
|
||||
if (target_time != NULL && target_xid != NULL)
|
||||
elog(ERROR, "You can't specify recovery-target-time and recovery-target-xid at the same time");
|
||||
if (backup_subcmd == VALIDATE || backup_subcmd == RESTORE)
|
||||
{
|
||||
/* parse all recovery target options into recovery_target_options structure */
|
||||
recovery_target_options = parseRecoveryTargetOptions(target_time, target_xid,
|
||||
target_inclusive, target_tli, target_immediate,
|
||||
target_name, target_action);
|
||||
}
|
||||
|
||||
if (num_threads < 1)
|
||||
num_threads = 1;
|
||||
@@ -448,16 +464,14 @@ main(int argc, char *argv[])
|
||||
}
|
||||
case RESTORE:
|
||||
return do_restore_or_validate(current.backup_id,
|
||||
target_time, target_xid,
|
||||
target_inclusive, target_tli,
|
||||
recovery_target_options,
|
||||
true);
|
||||
case VALIDATE:
|
||||
if (current.backup_id == 0 && target_time == 0 && target_xid == 0)
|
||||
return do_validate_all();
|
||||
else
|
||||
return do_restore_or_validate(current.backup_id,
|
||||
target_time, target_xid,
|
||||
target_inclusive, target_tli,
|
||||
recovery_target_options,
|
||||
false);
|
||||
case SHOW:
|
||||
return do_show(current.backup_id);
|
||||
|
||||
+18
-7
@@ -226,6 +226,8 @@ typedef struct pgBackup
|
||||
time_t parent_backup; /* Identifier of the previous backup.
|
||||
* Which is basic backup for this
|
||||
* incremental backup. */
|
||||
char *primary_conninfo; /* Connection parameters of the backup
|
||||
* in the format suitable for recovery.conf */
|
||||
} pgBackup;
|
||||
|
||||
/* Recovery target for restore and validate subcommands */
|
||||
@@ -233,9 +235,18 @@ typedef struct pgRecoveryTarget
|
||||
{
|
||||
bool time_specified;
|
||||
time_t recovery_target_time;
|
||||
/* add one more field in order to avoid deparsing recovery_target_time back */
|
||||
const char *target_time_string;
|
||||
bool xid_specified;
|
||||
TransactionId recovery_target_xid;
|
||||
/* add one more field in order to avoid deparsing recovery_target_xid back */
|
||||
const char *target_xid_string;
|
||||
TimeLineID recovery_target_tli;
|
||||
bool recovery_target_inclusive;
|
||||
bool inclusive_specified;
|
||||
bool recovery_target_immediate;
|
||||
const char *recovery_target_name;
|
||||
const char *recovery_target_action;
|
||||
} pgRecoveryTarget;
|
||||
|
||||
/* Union to ease operations on relation pages */
|
||||
@@ -311,6 +322,9 @@ extern bool is_ptrack_support;
|
||||
extern bool is_checksum_enabled;
|
||||
extern bool exclusive_backup;
|
||||
|
||||
/* restore options */
|
||||
extern bool restore_as_replica;
|
||||
|
||||
/* delete options */
|
||||
extern bool delete_wal;
|
||||
extern bool delete_expired;
|
||||
@@ -355,19 +369,16 @@ extern char *pg_ptrack_get_block(backup_files_args *arguments,
|
||||
size_t *result_size);
|
||||
/* in restore.c */
|
||||
extern int do_restore_or_validate(time_t target_backup_id,
|
||||
const char *target_time,
|
||||
const char *target_xid,
|
||||
const char *target_inclusive,
|
||||
TimeLineID target_tli,
|
||||
pgRecoveryTarget *rt,
|
||||
bool is_restore);
|
||||
extern bool satisfy_timeline(const parray *timelines, const pgBackup *backup);
|
||||
extern bool satisfy_recovery_target(const pgBackup *backup,
|
||||
const pgRecoveryTarget *rt);
|
||||
extern parray * readTimeLineHistory_probackup(TimeLineID targetTLI);
|
||||
extern pgRecoveryTarget *parseRecoveryTargetOptions(
|
||||
const char *target_time,
|
||||
const char *target_xid,
|
||||
const char *target_inclusive);
|
||||
const char *target_time, const char *target_xid,
|
||||
const char *target_inclusive, TimeLineID target_tli, bool target_immediate,
|
||||
const char *target_name, const char *target_action);
|
||||
|
||||
extern void opt_tablespace_map(pgut_option *opt, const char *arg);
|
||||
|
||||
|
||||
+132
-54
@@ -63,10 +63,8 @@ static void restore_directories(const char *pg_data_dir,
|
||||
const char *backup_dir);
|
||||
static void check_tablespace_mapping(pgBackup *backup);
|
||||
static void create_recovery_conf(time_t backup_id,
|
||||
const char *target_time,
|
||||
const char *target_xid,
|
||||
const char *target_inclusive,
|
||||
TimeLineID target_tli);
|
||||
pgRecoveryTarget *rt,
|
||||
pgBackup *backup);
|
||||
static void restore_files(void *arg);
|
||||
static void remove_deleted_files(pgBackup *backup);
|
||||
static const char *get_tablespace_mapping(const char *dir);
|
||||
@@ -83,16 +81,12 @@ static TablespaceCreatedList tablespace_created_dirs = {NULL, NULL};
|
||||
*/
|
||||
int
|
||||
do_restore_or_validate(time_t target_backup_id,
|
||||
const char *target_time,
|
||||
const char *target_xid,
|
||||
const char *target_inclusive,
|
||||
TimeLineID target_tli,
|
||||
pgRecoveryTarget *rt,
|
||||
bool is_restore)
|
||||
{
|
||||
int i;
|
||||
parray *backups;
|
||||
parray *timelines;
|
||||
pgRecoveryTarget *rt = NULL;
|
||||
pgBackup *current_backup = NULL;
|
||||
pgBackup *dest_backup = NULL;
|
||||
pgBackup *base_full_backup = NULL;
|
||||
@@ -115,8 +109,6 @@ do_restore_or_validate(time_t target_backup_id,
|
||||
if (instance_name == NULL)
|
||||
elog(ERROR, "required parameter not specified: --instance");
|
||||
|
||||
rt = parseRecoveryTargetOptions(target_time, target_xid, target_inclusive);
|
||||
|
||||
elog(LOG, "%s begin.", action);
|
||||
|
||||
/* Get exclusive lock of backup catalog */
|
||||
@@ -126,13 +118,6 @@ do_restore_or_validate(time_t target_backup_id,
|
||||
if (backups == NULL)
|
||||
elog(ERROR, "Failed to get backup list.");
|
||||
|
||||
if (target_tli)
|
||||
{
|
||||
elog(LOG, "target timeline ID = %u", target_tli);
|
||||
/* Read timeline history files from archives */
|
||||
timelines = readTimeLineHistory_probackup(target_tli);
|
||||
}
|
||||
|
||||
/* Find backup range we should restore or validate. */
|
||||
for (i = 0; i < parray_num(backups); i++)
|
||||
{
|
||||
@@ -182,8 +167,12 @@ do_restore_or_validate(time_t target_backup_id,
|
||||
base36enc(current_backup->start_time), status2str(current_backup->status));
|
||||
}
|
||||
|
||||
if (target_tli)
|
||||
if (rt->recovery_target_tli)
|
||||
{
|
||||
elog(LOG, "target timeline ID = %u", rt->recovery_target_tli);
|
||||
/* Read timeline history files from archives */
|
||||
timelines = readTimeLineHistory_probackup(rt->recovery_target_tli);
|
||||
|
||||
if (!satisfy_timeline(timelines, current_backup))
|
||||
{
|
||||
if (target_backup_id != INVALID_BACKUP_ID)
|
||||
@@ -349,12 +338,7 @@ do_restore_or_validate(time_t target_backup_id,
|
||||
remove_deleted_files(dest_backup);
|
||||
|
||||
/* Create recovery.conf with given recovery target parameters */
|
||||
if (!dest_backup->stream
|
||||
|| (target_time != NULL || target_xid != NULL))
|
||||
{
|
||||
create_recovery_conf(target_backup_id, target_time, target_xid,
|
||||
target_inclusive, target_tli);
|
||||
}
|
||||
create_recovery_conf(target_backup_id, rt, dest_backup);
|
||||
}
|
||||
|
||||
/* cleanup */
|
||||
@@ -797,15 +781,23 @@ restore_files(void *arg)
|
||||
arguments->ret = 0;
|
||||
}
|
||||
|
||||
/* Create recovery.conf with given recovery target parameters */
|
||||
static void
|
||||
create_recovery_conf(time_t backup_id,
|
||||
const char *target_time,
|
||||
const char *target_xid,
|
||||
const char *target_inclusive,
|
||||
TimeLineID target_tli)
|
||||
pgRecoveryTarget *rt,
|
||||
pgBackup *backup)
|
||||
{
|
||||
char path[MAXPGPATH];
|
||||
FILE *fp;
|
||||
bool need_restore_conf = false;
|
||||
|
||||
if (!backup->stream
|
||||
|| (rt->time_specified || rt->xid_specified))
|
||||
need_restore_conf = true;
|
||||
|
||||
/* No need to generate recovery.conf at all. */
|
||||
if (!(need_restore_conf || restore_as_replica))
|
||||
return;
|
||||
|
||||
elog(LOG, "----------------------------------------");
|
||||
elog(LOG, "creating recovery.conf");
|
||||
@@ -818,32 +810,58 @@ create_recovery_conf(time_t backup_id,
|
||||
|
||||
fprintf(fp, "# recovery.conf generated by pg_probackup %s\n",
|
||||
PROGRAM_VERSION);
|
||||
fprintf(fp, "restore_command = '%s archive-get -B %s --instance %s --wal-file-path %%p --wal-file-name %%f'\n",
|
||||
PROGRAM_NAME, backup_path, instance_name);
|
||||
fprintf(fp, "recovery_target_action = 'promote'\n");
|
||||
|
||||
if (target_time)
|
||||
fprintf(fp, "recovery_target_time = '%s'\n", target_time);
|
||||
else if (target_xid)
|
||||
fprintf(fp, "recovery_target_xid = '%s'\n", target_xid);
|
||||
else if (backup_id != 0)
|
||||
if (need_restore_conf)
|
||||
{
|
||||
|
||||
fprintf(fp, "restore_command = '%s archive-get -B %s --instance %s "
|
||||
"--wal-file-path %%p --wal-file-name %%f'\n",
|
||||
PROGRAM_NAME, backup_path, instance_name);
|
||||
|
||||
/*
|
||||
* We need to set this parameters only if 'backup_id' is provided
|
||||
* because the backup will be recovered as soon as possible as stop_lsn
|
||||
* is reached.
|
||||
* If 'backup_id' is not set we want to replay all available WAL records,
|
||||
* if 'recovery_target' is set all available WAL records will not be
|
||||
* replayed.
|
||||
* We've already checked that only one of the four following mutually
|
||||
* exclusive options is specified, so the order of calls is insignificant.
|
||||
*/
|
||||
fprintf(fp, "recovery_target = 'immediate'\n");
|
||||
if (rt->recovery_target_name)
|
||||
fprintf(fp, "recovery_target_name = '%s'\n", rt->recovery_target_name);
|
||||
|
||||
if (rt->time_specified)
|
||||
fprintf(fp, "recovery_target_time = '%s'\n", rt->target_time_string);
|
||||
|
||||
if (rt->xid_specified)
|
||||
fprintf(fp, "recovery_target_xid = '%s'\n", rt->target_xid_string);
|
||||
|
||||
if (rt->recovery_target_immediate)
|
||||
fprintf(fp, "recovery_target = 'immediate'\n");
|
||||
|
||||
/*
|
||||
* If 'backup_id' is provided and no other recovery target option is specified,
|
||||
* end recovery as soon as a consistent state is reached.
|
||||
*/
|
||||
if ((backup_id != 0) &&
|
||||
(!(rt->time_specified || rt->xid_specified || rt->recovery_target_name)))
|
||||
{
|
||||
fprintf(fp, "recovery_target = 'immediate'\n");
|
||||
}
|
||||
|
||||
if (rt->inclusive_specified)
|
||||
fprintf(fp, "recovery_target_inclusive = '%s'\n",
|
||||
rt->recovery_target_inclusive?"true":"false");
|
||||
|
||||
if (rt->recovery_target_tli)
|
||||
fprintf(fp, "recovery_target_timeline = '%u'\n", rt->recovery_target_tli);
|
||||
|
||||
if (rt->recovery_target_action)
|
||||
fprintf(fp, "recovery_target_action = '%s'\n", rt->recovery_target_action);
|
||||
}
|
||||
|
||||
if (target_inclusive)
|
||||
fprintf(fp, "recovery_target_inclusive = '%s'\n", target_inclusive);
|
||||
if (restore_as_replica)
|
||||
{
|
||||
fprintf(fp, "standby_mode = 'on'\n");
|
||||
|
||||
if (target_tli)
|
||||
fprintf(fp, "recovery_target_timeline = '%u'\n", target_tli);
|
||||
if (backup->primary_conninfo)
|
||||
fprintf(fp, "primary_conninfo = '%s'\n", backup->primary_conninfo);
|
||||
}
|
||||
|
||||
if (fflush(fp) != 0 ||
|
||||
fsync(fileno(fp)) != 0 ||
|
||||
@@ -982,40 +1000,62 @@ satisfy_timeline(const parray *timelines, const pgBackup *backup)
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get recovery options in the string format, parse them
|
||||
* and fill up the pgRecoveryTarget structure.
|
||||
*/
|
||||
pgRecoveryTarget *
|
||||
parseRecoveryTargetOptions(const char *target_time,
|
||||
const char *target_xid,
|
||||
const char *target_inclusive)
|
||||
const char *target_xid,
|
||||
const char *target_inclusive,
|
||||
TimeLineID target_tli,
|
||||
bool target_immediate,
|
||||
const char *target_name,
|
||||
const char *target_action)
|
||||
{
|
||||
time_t dummy_time;
|
||||
TransactionId dummy_xid;
|
||||
bool dummy_bool;
|
||||
pgRecoveryTarget *rt;
|
||||
/*
|
||||
* count the number of the mutually exclusive options which may specify
|
||||
* recovery target. If final value > 1, throw an error.
|
||||
*/
|
||||
int recovery_target_specified = 0;
|
||||
pgRecoveryTarget *rt = pgut_new(pgRecoveryTarget);
|
||||
|
||||
/* Initialize pgRecoveryTarget */
|
||||
rt = pgut_new(pgRecoveryTarget);
|
||||
/* fill all options with default values */
|
||||
rt->time_specified = false;
|
||||
rt->xid_specified = false;
|
||||
rt->inclusive_specified = false;
|
||||
rt->recovery_target_time = 0;
|
||||
rt->recovery_target_xid = 0;
|
||||
rt->target_time_string = NULL;
|
||||
rt->target_xid_string = NULL;
|
||||
rt->recovery_target_inclusive = false;
|
||||
rt->recovery_target_tli = 0;
|
||||
rt->recovery_target_immediate = false;
|
||||
rt->recovery_target_name = NULL;
|
||||
rt->recovery_target_action = NULL;
|
||||
|
||||
/* parse given options */
|
||||
if (target_time)
|
||||
{
|
||||
recovery_target_specified++;
|
||||
rt->time_specified = true;
|
||||
rt->target_time_string = target_time;
|
||||
|
||||
if (parse_time(target_time, &dummy_time))
|
||||
rt->recovery_target_time = dummy_time;
|
||||
else
|
||||
elog(ERROR, "Invalid value of --time option %s", target_time);
|
||||
}
|
||||
|
||||
if (target_xid)
|
||||
{
|
||||
recovery_target_specified++;
|
||||
rt->xid_specified = true;
|
||||
rt->target_xid_string = target_xid;
|
||||
|
||||
#ifdef PGPRO_EE
|
||||
if (parse_uint64(target_xid, &dummy_xid, 0))
|
||||
#else
|
||||
@@ -1025,14 +1065,52 @@ parseRecoveryTargetOptions(const char *target_time,
|
||||
else
|
||||
elog(ERROR, "Invalid value of --xid option %s", target_xid);
|
||||
}
|
||||
|
||||
if (target_inclusive)
|
||||
{
|
||||
rt->inclusive_specified = true;
|
||||
if (parse_bool(target_inclusive, &dummy_bool))
|
||||
rt->recovery_target_inclusive = dummy_bool;
|
||||
else
|
||||
elog(ERROR, "Invalid value of --inclusive option %s", target_inclusive);
|
||||
}
|
||||
|
||||
rt->recovery_target_tli = target_tli;
|
||||
if (target_immediate)
|
||||
{
|
||||
recovery_target_specified++;
|
||||
rt->recovery_target_immediate = target_immediate;
|
||||
}
|
||||
|
||||
if (target_name)
|
||||
{
|
||||
recovery_target_specified++;
|
||||
rt->recovery_target_name = target_name;
|
||||
}
|
||||
|
||||
if (target_action)
|
||||
{
|
||||
rt->recovery_target_action = target_action;
|
||||
|
||||
if ((strcmp(target_action, "pause") != 0)
|
||||
&& (strcmp(target_action, "promote") != 0)
|
||||
&& (strcmp(target_action, "shutdown") != 0))
|
||||
elog(ERROR, "Invalid value of --recovery-target-action option %s", target_action);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* Default recovery target action is pause */
|
||||
rt->recovery_target_action = "pause";
|
||||
}
|
||||
|
||||
/* More than one mutually exclusive option was defined. */
|
||||
if (recovery_target_specified > 1)
|
||||
elog(ERROR, "At most one of --immediate, --target-name, --time, or --xid can be used");
|
||||
|
||||
/* If none of the options is defined, '--inclusive' option is meaningless */
|
||||
if (!(rt->xid_specified || rt->time_specified) && rt->recovery_target_inclusive)
|
||||
elog(ERROR, "--inclusive option applies when either --time or --xid is specified");
|
||||
|
||||
return rt;
|
||||
}
|
||||
|
||||
|
||||
@@ -315,5 +315,6 @@ pgBackup_init(pgBackup *backup)
|
||||
backup->wal_block_size = XLOG_BLCKSZ;
|
||||
backup->stream = false;
|
||||
backup->parent_backup = 0;
|
||||
backup->primary_conninfo = NULL;
|
||||
backup->server_version[0] = '\0';
|
||||
}
|
||||
|
||||
@@ -1328,6 +1328,120 @@ prompt_for_password(const char *username)
|
||||
in_password = false;
|
||||
}
|
||||
|
||||
/*
|
||||
* Copied from pg_basebackup.c
|
||||
* Escape a parameter value so that it can be used as part of a libpq
|
||||
* connection string, e.g. in:
|
||||
*
|
||||
* application_name=<value>
|
||||
*
|
||||
* The returned string is malloc'd. Return NULL on out-of-memory.
|
||||
*/
|
||||
static char *
|
||||
escapeConnectionParameter(const char *src)
|
||||
{
|
||||
bool need_quotes = false;
|
||||
bool need_escaping = false;
|
||||
const char *p;
|
||||
char *dstbuf;
|
||||
char *dst;
|
||||
|
||||
/*
|
||||
* First check if quoting is needed. Any quote (') or backslash (\)
|
||||
* characters need to be escaped. Parameters are separated by whitespace,
|
||||
* so any string containing whitespace characters need to be quoted. An
|
||||
* empty string is represented by ''.
|
||||
*/
|
||||
if (strchr(src, '\'') != NULL || strchr(src, '\\') != NULL)
|
||||
need_escaping = true;
|
||||
|
||||
for (p = src; *p; p++)
|
||||
{
|
||||
if (isspace((unsigned char) *p))
|
||||
{
|
||||
need_quotes = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (*src == '\0')
|
||||
return pg_strdup("''");
|
||||
|
||||
if (!need_quotes && !need_escaping)
|
||||
return pg_strdup(src); /* no quoting or escaping needed */
|
||||
|
||||
/*
|
||||
* Allocate a buffer large enough for the worst case that all the source
|
||||
* characters need to be escaped, plus quotes.
|
||||
*/
|
||||
dstbuf = pg_malloc(strlen(src) * 2 + 2 + 1);
|
||||
|
||||
dst = dstbuf;
|
||||
if (need_quotes)
|
||||
*(dst++) = '\'';
|
||||
for (; *src; src++)
|
||||
{
|
||||
if (*src == '\'' || *src == '\\')
|
||||
*(dst++) = '\\';
|
||||
*(dst++) = *src;
|
||||
}
|
||||
if (need_quotes)
|
||||
*(dst++) = '\'';
|
||||
*dst = '\0';
|
||||
|
||||
return dstbuf;
|
||||
}
|
||||
|
||||
/* Construct a connection string for possible future use in recovery.conf */
|
||||
char *
|
||||
pgut_get_conninfo_string(PGconn *conn)
|
||||
{
|
||||
PQconninfoOption *connOptions;
|
||||
PQconninfoOption *option;
|
||||
PQExpBuffer buf = createPQExpBuffer();
|
||||
char *connstr;
|
||||
bool firstkeyword = true;
|
||||
char *escaped;
|
||||
|
||||
connOptions = PQconninfo(conn);
|
||||
if (connOptions == NULL)
|
||||
elog(ERROR, "out of memory");
|
||||
|
||||
/* Construct a new connection string in key='value' format. */
|
||||
for (option = connOptions; option && option->keyword; option++)
|
||||
{
|
||||
/*
|
||||
* Do not emit this setting if: - the setting is "replication",
|
||||
* "dbname" or "fallback_application_name", since these would be
|
||||
* overridden by the libpqwalreceiver module anyway. - not set or
|
||||
* empty.
|
||||
*/
|
||||
if (strcmp(option->keyword, "replication") == 0 ||
|
||||
strcmp(option->keyword, "dbname") == 0 ||
|
||||
strcmp(option->keyword, "fallback_application_name") == 0 ||
|
||||
(option->val == NULL) ||
|
||||
(option->val != NULL && option->val[0] == '\0'))
|
||||
continue;
|
||||
|
||||
/* do not print password into the file */
|
||||
if (strcmp(option->keyword, "password") == 0)
|
||||
continue;
|
||||
|
||||
if (!firstkeyword)
|
||||
appendPQExpBufferChar(buf, ' ');
|
||||
|
||||
firstkeyword = false;
|
||||
|
||||
escaped = escapeConnectionParameter(option->val);
|
||||
appendPQExpBuffer(buf, "%s=%s", option->keyword, escaped);
|
||||
free(escaped);
|
||||
}
|
||||
|
||||
connstr = pg_strdup(buf->data);
|
||||
destroyPQExpBuffer(buf);
|
||||
return connstr;
|
||||
}
|
||||
|
||||
PGconn *
|
||||
pgut_connect(const char *dbname)
|
||||
{
|
||||
|
||||
@@ -122,6 +122,7 @@ extern void pgut_atexit_pop(pgut_atexit_callback callback, void *userdata);
|
||||
/*
|
||||
* Database connections
|
||||
*/
|
||||
extern char *pgut_get_conninfo_string(PGconn *conn);
|
||||
extern PGconn *pgut_connect(const char *dbname);
|
||||
extern PGconn *pgut_connect_extended(const char *pghost, const char *pgport,
|
||||
const char *dbname, const char *login);
|
||||
|
||||
+28
-13
@@ -44,7 +44,9 @@ class ArchiveTest(ProbackupTest, unittest.TestCase):
|
||||
options=["--log-level-file=verbose"])
|
||||
node.cleanup()
|
||||
|
||||
self.restore_node(backup_dir, 'node', node)
|
||||
self.restore_node(
|
||||
backup_dir, 'node', node,
|
||||
options=["--recovery-target-action=promote"])
|
||||
node.start()
|
||||
while node.safe_psql(
|
||||
"postgres",
|
||||
@@ -62,7 +64,9 @@ class ArchiveTest(ProbackupTest, unittest.TestCase):
|
||||
node.cleanup()
|
||||
|
||||
# Restore Database
|
||||
self.restore_node(backup_dir, 'node', node)
|
||||
self.restore_node(
|
||||
backup_dir, 'node', node,
|
||||
options=["--recovery-target-action=promote"])
|
||||
node.start()
|
||||
while node.safe_psql(
|
||||
"postgres",
|
||||
@@ -76,9 +80,12 @@ class ArchiveTest(ProbackupTest, unittest.TestCase):
|
||||
self.del_test_dir(module_name, fname)
|
||||
|
||||
# @unittest.skip("skip")
|
||||
@unittest.expectedFailure
|
||||
# @unittest.expectedFailure
|
||||
def test_pgpro434_2(self):
|
||||
"""Check that timelines are correct. WAITING PGPRO-1053 for --immediate. replace time"""
|
||||
"""
|
||||
Check that timelines are correct.
|
||||
WAITING PGPRO-1053 for --immediate
|
||||
"""
|
||||
fname = self.id().split('.')[3]
|
||||
backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
|
||||
node = self.make_simple_node(
|
||||
@@ -110,7 +117,9 @@ class ArchiveTest(ProbackupTest, unittest.TestCase):
|
||||
|
||||
# SECOND TIMELIN
|
||||
node.cleanup()
|
||||
self.restore_node(backup_dir, 'node', node)
|
||||
self.restore_node(
|
||||
backup_dir, 'node', node,
|
||||
options=['--immediate', '--recovery-target-action=promote'])
|
||||
node.start()
|
||||
while node.safe_psql(
|
||||
"postgres",
|
||||
@@ -134,8 +143,7 @@ class ArchiveTest(ProbackupTest, unittest.TestCase):
|
||||
"from generate_series(100,200) i")
|
||||
|
||||
backup_id = self.backup_node(backup_dir, 'node', node)
|
||||
recovery_time = self.show_pb(
|
||||
backup_dir, 'node', backup_id)["recovery-time"]
|
||||
|
||||
node.safe_psql(
|
||||
"postgres",
|
||||
"insert into t_heap select 100502 as id, md5(i::text) as text, "
|
||||
@@ -144,7 +152,9 @@ class ArchiveTest(ProbackupTest, unittest.TestCase):
|
||||
|
||||
# THIRD TIMELINE
|
||||
node.cleanup()
|
||||
self.restore_node(backup_dir, 'node', node)
|
||||
self.restore_node(
|
||||
backup_dir, 'node', node,
|
||||
options=['--immediate', '--recovery-target-action=promote'])
|
||||
node.start()
|
||||
while node.safe_psql(
|
||||
"postgres",
|
||||
@@ -164,8 +174,7 @@ class ArchiveTest(ProbackupTest, unittest.TestCase):
|
||||
"from generate_series(200,300) i")
|
||||
|
||||
backup_id = self.backup_node(backup_dir, 'node', node)
|
||||
recovery_time = self.show_pb(
|
||||
backup_dir, 'node', backup_id)["recovery-time"]
|
||||
|
||||
result = node.safe_psql("postgres", "SELECT * FROM t_heap")
|
||||
node.safe_psql(
|
||||
"postgres",
|
||||
@@ -175,7 +184,9 @@ class ArchiveTest(ProbackupTest, unittest.TestCase):
|
||||
|
||||
# FOURTH TIMELINE
|
||||
node.cleanup()
|
||||
self.restore_node(backup_dir, 'node', node)
|
||||
self.restore_node(
|
||||
backup_dir, 'node', node,
|
||||
options=['--immediate', '--recovery-target-action=promote'])
|
||||
node.start()
|
||||
while node.safe_psql(
|
||||
"postgres",
|
||||
@@ -189,7 +200,9 @@ class ArchiveTest(ProbackupTest, unittest.TestCase):
|
||||
|
||||
# FIFTH TIMELINE
|
||||
node.cleanup()
|
||||
self.restore_node(backup_dir, 'node', node)
|
||||
self.restore_node(
|
||||
backup_dir, 'node', node,
|
||||
options=['--immediate', '--recovery-target-action=promote'])
|
||||
node.start()
|
||||
while node.safe_psql(
|
||||
"postgres", "select pg_is_in_recovery()") == 't\n':
|
||||
@@ -202,7 +215,9 @@ class ArchiveTest(ProbackupTest, unittest.TestCase):
|
||||
|
||||
# SIXTH TIMELINE
|
||||
node.cleanup()
|
||||
self.restore_node(backup_dir, 'node', node)
|
||||
self.restore_node(
|
||||
backup_dir, 'node', node,
|
||||
options=['--immediate', '--recovery-target-action=promote'])
|
||||
node.start()
|
||||
while node.safe_psql(
|
||||
"postgres", "select pg_is_in_recovery()") == 't\n':
|
||||
|
||||
+19
-7
@@ -297,10 +297,15 @@ class CompressionTest(ProbackupTest, unittest.TestCase):
|
||||
self.maxDiff = None
|
||||
fname = self.id().split('.')[3]
|
||||
backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
|
||||
node = self.make_simple_node(base_dir="{0}/{1}/node".format(module_name, fname),
|
||||
node = self.make_simple_node(
|
||||
base_dir="{0}/{1}/node".format(module_name, fname),
|
||||
set_replication=True,
|
||||
initdb_params=['--data-checksums'],
|
||||
pg_options={'wal_level': 'replica', 'max_wal_senders': '2', 'checkpoint_timeout': '30s', 'ptrack_enable': 'on'}
|
||||
pg_options={
|
||||
'wal_level': 'replica',
|
||||
'max_wal_senders': '2',
|
||||
'checkpoint_timeout': '30s',
|
||||
'ptrack_enable': 'on'}
|
||||
)
|
||||
|
||||
self.init_pb(backup_dir)
|
||||
@@ -309,14 +314,21 @@ class CompressionTest(ProbackupTest, unittest.TestCase):
|
||||
node.start()
|
||||
|
||||
try:
|
||||
self.backup_node(backup_dir, 'node', node, backup_type='full', options=['--compress-algorithm=bla-blah'])
|
||||
self.backup_node(
|
||||
backup_dir, 'node', node,
|
||||
backup_type='full', options=['--compress-algorithm=bla-blah'])
|
||||
# we should die here because exception is what we expect to happen
|
||||
self.assertEqual(1, 0, "Expecting Error because restore destionation is not empty.\n Output: {0} \n CMD: {1}".format(
|
||||
repr(self.output), self.cmd))
|
||||
self.assertEqual(
|
||||
1, 0,
|
||||
"Expecting Error because compress-algorithm is invalid.\n "
|
||||
"Output: {0} \n CMD: {1}".format(
|
||||
repr(self.output), self.cmd))
|
||||
except ProbackupException as e:
|
||||
self.assertEqual(e.message,
|
||||
self.assertEqual(
|
||||
e.message,
|
||||
'ERROR: invalid compress algorithm value "bla-blah"\n',
|
||||
'\n Unexpected Error Message: {0}\n CMD: {1}'.format(repr(e.message), self.cmd))
|
||||
'\n Unexpected Error Message: {0}\n CMD: {1}'.format(
|
||||
repr(e.message), self.cmd))
|
||||
|
||||
# Clean after yourself
|
||||
self.del_test_dir(module_name, fname)
|
||||
|
||||
+4
-2
@@ -487,7 +487,8 @@ class DeltaTest(ProbackupTest, unittest.TestCase):
|
||||
restored_node, 'somedata_restored')
|
||||
|
||||
self.restore_node(backup_dir, 'node', restored_node, options=[
|
||||
"-j", "4", "-T", "{0}={1}".format(tblspc_path, tblspc_path_new)])
|
||||
"-j", "4", "-T", "{0}={1}".format(tblspc_path, tblspc_path_new),
|
||||
"--recovery-target-action=promote"])
|
||||
|
||||
# GET PHYSICAL CONTENT FROM NODE_RESTORED
|
||||
pgdata_restored = self.pgdata_content(restored_node.data_dir)
|
||||
@@ -911,7 +912,8 @@ class DeltaTest(ProbackupTest, unittest.TestCase):
|
||||
"-T", "{0}={1}".format(
|
||||
self.get_tblspace_path(node, 'somedata_new'),
|
||||
self.get_tblspace_path(node_restored, 'somedata_new')
|
||||
)
|
||||
),
|
||||
"--recovery-target-action=promote"
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@@ -29,19 +29,34 @@ pg_probackup - utility to manage backup/recovery of PostgreSQL database.
|
||||
pg_probackup backup -B backup-path -b backup-mode --instance=instance_name
|
||||
[-C] [--stream [-S slot-name]] [--backup-pg-log]
|
||||
[-j num-threads] [--archive-timeout=archive-timeout]
|
||||
[--progress]
|
||||
[--log-level-console=log-level-console]
|
||||
[--log-level-file=log-level-file]
|
||||
[--log-filename=log-filename]
|
||||
[--error-log-filename=error-log-filename]
|
||||
[--log-directory=log-directory]
|
||||
[--log-rotation-size=log-rotation-size]
|
||||
[--log-rotation-age=log-rotation-age]
|
||||
[--delete-expired] [--delete-wal]
|
||||
[--retention-redundancy=retention-redundancy]
|
||||
[--retention-window=retention-window]
|
||||
[--compress]
|
||||
[--compress-algorithm=compress-algorithm]
|
||||
[--compress-level=compress-level]
|
||||
[--progress] [--delete-expired]
|
||||
[-d dbname] [-h host] [-p port] [-U username]
|
||||
[-w --no-password] [-W --password]
|
||||
[--master-db=db_name] [--master-host=host_name]
|
||||
[--master-port=port] [--master-user=user_name]
|
||||
[--replica-timeout=timeout]
|
||||
|
||||
|
||||
pg_probackup restore -B backup-dir --instance=instance_name
|
||||
[-D pgdata-dir] [-i backup-id] [--progress]
|
||||
[--time=time|--xid=xid [--inclusive=boolean]]
|
||||
[--timeline=timeline] [-T OLDDIR=NEWDIR]
|
||||
[--immediate] [--recovery-target-name=target-name]
|
||||
[--recovery-target-action=pause|promote|shutdown]
|
||||
[--restore-as-replica]
|
||||
|
||||
pg_probackup validate -B backup-dir [--instance=instance_name]
|
||||
[-i backup-id] [--progress]
|
||||
|
||||
+10
-4
@@ -84,8 +84,10 @@ class PageBackupTest(ProbackupTest, unittest.TestCase):
|
||||
|
||||
self.restore_node(
|
||||
backup_dir, 'node', node_restored,
|
||||
options=["-j", "4", "-T", "{0}={1}".format(
|
||||
old_tablespace, new_tablespace)]
|
||||
options=[
|
||||
"-j", "4",
|
||||
"-T", "{0}={1}".format(old_tablespace, new_tablespace),
|
||||
"--recovery-target-action=promote"]
|
||||
)
|
||||
|
||||
# Physical comparison
|
||||
@@ -331,8 +333,12 @@ class PageBackupTest(ProbackupTest, unittest.TestCase):
|
||||
tblspc_path_new = self.get_tblspace_path(
|
||||
restored_node, 'somedata_restored')
|
||||
|
||||
self.restore_node(backup_dir, 'node', restored_node, options=[
|
||||
"-j", "4", "-T", "{0}={1}".format(tblspc_path, tblspc_path_new)])
|
||||
self.restore_node(
|
||||
backup_dir, 'node', restored_node,
|
||||
options=[
|
||||
"-j", "4",
|
||||
"--recovery-target-action=promote",
|
||||
"-T", "{0}={1}".format(tblspc_path, tblspc_path_new)])
|
||||
|
||||
# GET PHYSICAL CONTENT FROM NODE_RESTORED
|
||||
pgdata_restored = self.pgdata_content(restored_node.data_dir)
|
||||
|
||||
+17
-8
@@ -759,7 +759,7 @@ class PtrackTest(ProbackupTest, unittest.TestCase):
|
||||
self.restore_node(
|
||||
backup_dir, 'node', node,
|
||||
backup_id=full_backup_id,
|
||||
options=["-j", "4"]
|
||||
options=["-j", "4", "--recovery-target-action=promote"]
|
||||
),
|
||||
'\n Unexpected Error Message: {0}\n CMD: {1}'.format(
|
||||
repr(self.output), self.cmd)
|
||||
@@ -778,7 +778,7 @@ class PtrackTest(ProbackupTest, unittest.TestCase):
|
||||
self.restore_node(
|
||||
backup_dir, 'node', node,
|
||||
backup_id=ptrack_backup_id,
|
||||
options=["-j", "4"]
|
||||
options=["-j", "4", "--recovery-target-action=promote"]
|
||||
),
|
||||
'\n Unexpected Error Message: {0}\n CMD: {1}'.format(
|
||||
repr(self.output), self.cmd)
|
||||
@@ -861,7 +861,9 @@ class PtrackTest(ProbackupTest, unittest.TestCase):
|
||||
self.restore_node(
|
||||
backup_dir, 'node', node,
|
||||
backup_id=full_backup_id,
|
||||
options=["-j", "4", "--time={0}".format(full_target_time)]
|
||||
options=[
|
||||
"-j", "4", "--recovery-target-action=promote",
|
||||
"--time={0}".format(full_target_time)]
|
||||
),
|
||||
'\n Unexpected Error Message: {0}\n CMD: {1}'.format(
|
||||
repr(self.output), self.cmd)
|
||||
@@ -880,7 +882,10 @@ class PtrackTest(ProbackupTest, unittest.TestCase):
|
||||
self.restore_node(
|
||||
backup_dir, 'node', node,
|
||||
backup_id=ptrack_backup_id,
|
||||
options=["-j", "4", "--time={0}".format(ptrack_target_time)]
|
||||
options=[
|
||||
"-j", "4",
|
||||
"--time={0}".format(ptrack_target_time),
|
||||
"--recovery-target-action=promote"]
|
||||
),
|
||||
'\n Unexpected Error Message: {0}\n CMD: {1}'.format(
|
||||
repr(self.output), self.cmd)
|
||||
@@ -1320,7 +1325,8 @@ class PtrackTest(ProbackupTest, unittest.TestCase):
|
||||
"-T", "{0}={1}".format(
|
||||
self.get_tblspace_path(node, 'somedata_new'),
|
||||
self.get_tblspace_path(node_restored, 'somedata_new')
|
||||
)
|
||||
),
|
||||
"--recovery-target-action=promote"
|
||||
]
|
||||
)
|
||||
|
||||
@@ -1549,7 +1555,8 @@ class PtrackTest(ProbackupTest, unittest.TestCase):
|
||||
tblspc_path_new = self.get_tblspace_path(
|
||||
restored_node, 'somedata_restored')
|
||||
self.restore_node(backup_dir, 'node', restored_node, options=[
|
||||
"-j", "4", "-T", "{0}={1}".format(tblspc_path, tblspc_path_new)])
|
||||
"-j", "4", "-T", "{0}={1}".format(tblspc_path, tblspc_path_new),
|
||||
"--recovery-target-action=promote"])
|
||||
|
||||
# GET PHYSICAL CONTENT FROM RESTORED NODE and COMPARE PHYSICAL CONTENT
|
||||
if self.paranoia:
|
||||
@@ -1585,7 +1592,8 @@ class PtrackTest(ProbackupTest, unittest.TestCase):
|
||||
|
||||
# Restore second ptrack backup and check table consistency
|
||||
self.restore_node(backup_dir, 'node', restored_node, options=[
|
||||
"-j", "4", "-T", "{0}={1}".format(tblspc_path, tblspc_path_new)])
|
||||
"-j", "4", "-T", "{0}={1}".format(tblspc_path, tblspc_path_new),
|
||||
"--recovery-target-action=promote"])
|
||||
|
||||
# GET PHYSICAL CONTENT FROM RESTORED NODE and COMPARE PHYSICAL CONTENT
|
||||
if self.paranoia:
|
||||
@@ -1682,7 +1690,8 @@ class PtrackTest(ProbackupTest, unittest.TestCase):
|
||||
)
|
||||
|
||||
self.restore_node(backup_dir, 'node', restored_node, options=[
|
||||
"-j", "4", "-T", "{0}={1}".format(tblspc_path, tblspc_path_new)])
|
||||
"-j", "4", "-T", "{0}={1}".format(tblspc_path, tblspc_path_new),
|
||||
"--recovery-target-action=promote"])
|
||||
|
||||
# GET PHYSICAL CONTENT FROM NODE_RESTORED
|
||||
if self.paranoia:
|
||||
|
||||
+170
-39
@@ -4,22 +4,30 @@ from .helpers.ptrack_helpers import ProbackupTest, ProbackupException, idx_ptrac
|
||||
from datetime import datetime, timedelta
|
||||
import subprocess
|
||||
from sys import exit
|
||||
import time
|
||||
|
||||
|
||||
module_name = 'replica'
|
||||
|
||||
|
||||
class ReplicaTest(ProbackupTest, unittest.TestCase):
|
||||
|
||||
# @unittest.skip("skip")
|
||||
# @unittest.expectedFailure
|
||||
def test_replica_stream_ptrack_backup(self):
|
||||
"""make node, take full backup, restore it and make replica from it, take full stream backup from replica"""
|
||||
"""
|
||||
make node, take full backup, restore it and make replica from it,
|
||||
take full stream backup from replica
|
||||
"""
|
||||
fname = self.id().split('.')[3]
|
||||
backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
|
||||
master = self.make_simple_node(base_dir="{0}/{1}/master".format(module_name, fname),
|
||||
master = self.make_simple_node(
|
||||
base_dir="{0}/{1}/master".format(module_name, fname),
|
||||
set_replication=True,
|
||||
initdb_params=['--data-checksums'],
|
||||
pg_options={'wal_level': 'replica', 'max_wal_senders': '2', 'checkpoint_timeout': '30s', 'ptrack_enable': 'on'}
|
||||
pg_options={
|
||||
'wal_level': 'replica', 'max_wal_senders': '2',
|
||||
'checkpoint_timeout': '30s', 'ptrack_enable': 'on'}
|
||||
)
|
||||
master.start()
|
||||
self.init_pb(backup_dir)
|
||||
@@ -28,12 +36,15 @@ class ReplicaTest(ProbackupTest, unittest.TestCase):
|
||||
# CREATE TABLE
|
||||
master.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,256) i")
|
||||
"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,256) i")
|
||||
before = master.safe_psql("postgres", "SELECT * FROM t_heap")
|
||||
|
||||
# take full backup and restore it
|
||||
self.backup_node(backup_dir, 'master', master, options=['--stream'])
|
||||
replica = self.make_simple_node(base_dir="{0}/{1}/replica".format(module_name, fname))
|
||||
replica = self.make_simple_node(
|
||||
base_dir="{0}/{1}/replica".format(module_name, fname))
|
||||
replica.cleanup()
|
||||
self.restore_node(backup_dir, 'master', replica)
|
||||
self.set_replica(master, replica)
|
||||
@@ -43,41 +54,65 @@ class ReplicaTest(ProbackupTest, unittest.TestCase):
|
||||
after = replica.safe_psql("postgres", "SELECT * FROM t_heap")
|
||||
self.assertEqual(before, after)
|
||||
|
||||
# Change data on master, take FULL backup from replica, restore taken backup and check that restored data equal to original data
|
||||
# Change data on master, take FULL backup from replica,
|
||||
# restore taken backup and check that restored data equal
|
||||
# to original data
|
||||
master.psql(
|
||||
"postgres",
|
||||
"insert into t_heap as select i as id, md5(i::text) as text, md5(repeat(i::text,10))::tsvector as tsvector from generate_series(256,512) i")
|
||||
"insert into t_heap as select i as id, md5(i::text) as text, "
|
||||
"md5(repeat(i::text,10))::tsvector as tsvector "
|
||||
"from generate_series(256,512) i")
|
||||
before = master.safe_psql("postgres", "SELECT * FROM t_heap")
|
||||
self.add_instance(backup_dir, 'replica', replica)
|
||||
backup_id = self.backup_node(backup_dir, 'replica', replica, options=['--stream',
|
||||
'--master-host=localhost', '--master-db=postgres','--master-port={0}'.format(master.port)])
|
||||
backup_id = self.backup_node(
|
||||
backup_dir, 'replica', replica,
|
||||
options=[
|
||||
'--stream',
|
||||
'--master-host=localhost',
|
||||
'--master-db=postgres',
|
||||
'--master-port={0}'.format(master.port)])
|
||||
self.validate_pb(backup_dir, 'replica')
|
||||
self.assertEqual('OK', self.show_pb(backup_dir, 'replica', backup_id)['status'])
|
||||
self.assertEqual(
|
||||
'OK', self.show_pb(backup_dir, 'replica', backup_id)['status'])
|
||||
|
||||
# RESTORE FULL BACKUP TAKEN FROM PREVIOUS STEP
|
||||
node = self.make_simple_node(base_dir="{0}/{1}/node".format(module_name, fname))
|
||||
node = self.make_simple_node(
|
||||
base_dir="{0}/{1}/node".format(module_name, fname))
|
||||
node.cleanup()
|
||||
self.restore_node(backup_dir, 'replica', data_dir=node.data_dir)
|
||||
node.append_conf('postgresql.auto.conf', 'port = {0}'.format(node.port))
|
||||
node.append_conf(
|
||||
'postgresql.auto.conf', 'port = {0}'.format(node.port))
|
||||
node.start()
|
||||
# CHECK DATA CORRECTNESS
|
||||
after = node.safe_psql("postgres", "SELECT * FROM t_heap")
|
||||
self.assertEqual(before, after)
|
||||
|
||||
# Change data on master, take PTRACK backup from replica, restore taken backup and check that restored data equal to original data
|
||||
# Change data on master, take PTRACK backup from replica,
|
||||
# restore taken backup and check that restored data equal
|
||||
# to original data
|
||||
master.psql(
|
||||
"postgres",
|
||||
"insert into t_heap as select i as id, md5(i::text) as text, md5(repeat(i::text,10))::tsvector as tsvector from generate_series(512,768) i")
|
||||
"insert into t_heap as select i as id, md5(i::text) as text, "
|
||||
"md5(repeat(i::text,10))::tsvector as tsvector "
|
||||
"from generate_series(512,768) i")
|
||||
before = master.safe_psql("postgres", "SELECT * FROM t_heap")
|
||||
backup_id = self.backup_node(backup_dir, 'replica', replica, backup_type='ptrack', options=['--stream',
|
||||
'--master-host=localhost', '--master-db=postgres', '--master-port={0}'.format(master.port)])
|
||||
backup_id = self.backup_node(
|
||||
backup_dir, 'replica', replica, backup_type='ptrack',
|
||||
options=[
|
||||
'--stream',
|
||||
'--master-host=localhost',
|
||||
'--master-db=postgres',
|
||||
'--master-port={0}'.format(master.port)])
|
||||
self.validate_pb(backup_dir, 'replica')
|
||||
self.assertEqual('OK', self.show_pb(backup_dir, 'replica', backup_id)['status'])
|
||||
self.assertEqual(
|
||||
'OK', self.show_pb(backup_dir, 'replica', backup_id)['status'])
|
||||
|
||||
# RESTORE PTRACK BACKUP TAKEN FROM replica
|
||||
node.cleanup()
|
||||
self.restore_node(backup_dir, 'replica', data_dir=node.data_dir, backup_id=backup_id)
|
||||
node.append_conf('postgresql.auto.conf', 'port = {0}'.format(node.port))
|
||||
self.restore_node(
|
||||
backup_dir, 'replica', data_dir=node.data_dir, backup_id=backup_id)
|
||||
node.append_conf(
|
||||
'postgresql.auto.conf', 'port = {0}'.format(node.port))
|
||||
node.start()
|
||||
# CHECK DATA CORRECTNESS
|
||||
after = node.safe_psql("postgres", "SELECT * FROM t_heap")
|
||||
@@ -88,13 +123,20 @@ class ReplicaTest(ProbackupTest, unittest.TestCase):
|
||||
|
||||
# @unittest.skip("skip")
|
||||
def test_replica_archive_page_backup(self):
|
||||
"""make archive master, take full and page archive backups from master, set replica, make archive backup from replica"""
|
||||
"""
|
||||
make archive master, take full and page archive backups from master,
|
||||
set replica, make archive backup from replica
|
||||
"""
|
||||
fname = self.id().split('.')[3]
|
||||
backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
|
||||
master = self.make_simple_node(base_dir="{0}/{1}/master".format(module_name, fname),
|
||||
master = self.make_simple_node(
|
||||
base_dir="{0}/{1}/master".format(module_name, fname),
|
||||
set_replication=True,
|
||||
initdb_params=['--data-checksums'],
|
||||
pg_options={'wal_level': 'replica', 'max_wal_senders': '2', 'checkpoint_timeout': '30s'}
|
||||
pg_options={
|
||||
'wal_level': 'replica',
|
||||
'max_wal_senders': '2',
|
||||
'checkpoint_timeout': '30s'}
|
||||
)
|
||||
self.init_pb(backup_dir)
|
||||
self.add_instance(backup_dir, 'master', master)
|
||||
@@ -103,18 +145,22 @@ class ReplicaTest(ProbackupTest, unittest.TestCase):
|
||||
master.append_conf('postgresql.auto.conf', 'archive_timeout = 10')
|
||||
master.start()
|
||||
|
||||
replica = self.make_simple_node(base_dir="{0}/{1}/replica".format(module_name, fname))
|
||||
replica = self.make_simple_node(
|
||||
base_dir="{0}/{1}/replica".format(module_name, fname))
|
||||
replica.cleanup()
|
||||
|
||||
self.backup_node(backup_dir, 'master', master)
|
||||
|
||||
master.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,256) i")
|
||||
"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,256) i")
|
||||
|
||||
before = master.safe_psql("postgres", "SELECT * FROM t_heap")
|
||||
|
||||
backup_id = self.backup_node(backup_dir, 'master', master, backup_type='page')
|
||||
backup_id = self.backup_node(
|
||||
backup_dir, 'master', master, backup_type='page')
|
||||
self.restore_node(backup_dir, 'master', replica)
|
||||
|
||||
# Settings for Replica
|
||||
@@ -126,41 +172,65 @@ class ReplicaTest(ProbackupTest, unittest.TestCase):
|
||||
after = replica.safe_psql("postgres", "SELECT * FROM t_heap")
|
||||
self.assertEqual(before, after)
|
||||
|
||||
# Change data on master, take FULL backup from replica, restore taken backup and check that restored data equal to original data
|
||||
# Change data on master, take FULL backup from replica,
|
||||
# restore taken backup and check that restored data
|
||||
# equal to original data
|
||||
master.psql(
|
||||
"postgres",
|
||||
"insert into t_heap as select i as id, md5(i::text) as text, md5(repeat(i::text,10))::tsvector as tsvector from generate_series(256,512) i")
|
||||
"insert into t_heap as select i as id, md5(i::text) as text, "
|
||||
"md5(repeat(i::text,10))::tsvector as tsvector "
|
||||
"from generate_series(256,512) i")
|
||||
before = master.safe_psql("postgres", "SELECT * FROM t_heap")
|
||||
self.add_instance(backup_dir, 'replica', replica)
|
||||
backup_id = self.backup_node(backup_dir, 'replica', replica, options=['--archive-timeout=300',
|
||||
'--master-host=localhost', '--master-db=postgres','--master-port={0}'.format(master.port)])
|
||||
backup_id = self.backup_node(
|
||||
backup_dir, 'replica', replica,
|
||||
options=[
|
||||
'--archive-timeout=300',
|
||||
'--master-host=localhost',
|
||||
'--master-db=postgres',
|
||||
'--master-port={0}'.format(master.port)])
|
||||
self.validate_pb(backup_dir, 'replica')
|
||||
self.assertEqual('OK', self.show_pb(backup_dir, 'replica', backup_id)['status'])
|
||||
self.assertEqual(
|
||||
'OK', self.show_pb(backup_dir, 'replica', backup_id)['status'])
|
||||
|
||||
# RESTORE FULL BACKUP TAKEN FROM replica
|
||||
node = self.make_simple_node(base_dir="{0}/{1}/node".format(module_name, fname))
|
||||
node = self.make_simple_node(
|
||||
base_dir="{0}/{1}/node".format(module_name, fname))
|
||||
node.cleanup()
|
||||
self.restore_node(backup_dir, 'replica', data_dir=node.data_dir)
|
||||
node.append_conf('postgresql.auto.conf', 'port = {0}'.format(node.port))
|
||||
node.append_conf(
|
||||
'postgresql.auto.conf', 'port = {0}'.format(node.port))
|
||||
node.start()
|
||||
# CHECK DATA CORRECTNESS
|
||||
after = node.safe_psql("postgres", "SELECT * FROM t_heap")
|
||||
self.assertEqual(before, after)
|
||||
|
||||
# Change data on master, make PAGE backup from replica, restore taken backup and check that restored data equal to original data
|
||||
# Change data on master, make PAGE backup from replica,
|
||||
# restore taken backup and check that restored data equal
|
||||
# to original data
|
||||
master.psql(
|
||||
"postgres",
|
||||
"insert into t_heap as select i as id, md5(i::text) as text, md5(repeat(i::text,10))::tsvector as tsvector from generate_series(512,768) i")
|
||||
"insert into t_heap as select i as id, md5(i::text) as text, "
|
||||
"md5(repeat(i::text,10))::tsvector as tsvector "
|
||||
"from generate_series(512,768) i")
|
||||
before = master.safe_psql("postgres", "SELECT * FROM t_heap")
|
||||
backup_id = self.backup_node(backup_dir, 'replica', replica, backup_type='page', options=['--archive-timeout=300',
|
||||
'--master-host=localhost', '--master-db=postgres','--master-port={0}'.format(master.port)])
|
||||
backup_id = self.backup_node(
|
||||
backup_dir, 'replica', replica, backup_type='page',
|
||||
options=[
|
||||
'--archive-timeout=300',
|
||||
'--master-host=localhost',
|
||||
'--master-db=postgres',
|
||||
'--master-port={0}'.format(master.port)])
|
||||
self.validate_pb(backup_dir, 'replica')
|
||||
self.assertEqual('OK', self.show_pb(backup_dir, 'replica', backup_id)['status'])
|
||||
self.assertEqual(
|
||||
'OK', self.show_pb(backup_dir, 'replica', backup_id)['status'])
|
||||
|
||||
# RESTORE PAGE BACKUP TAKEN FROM replica
|
||||
node.cleanup()
|
||||
self.restore_node(backup_dir, 'replica', data_dir=node.data_dir, backup_id=backup_id)
|
||||
node.append_conf('postgresql.auto.conf', 'port = {0}'.format(node.port))
|
||||
self.restore_node(
|
||||
backup_dir, 'replica', data_dir=node.data_dir, backup_id=backup_id)
|
||||
node.append_conf(
|
||||
'postgresql.auto.conf', 'port = {0}'.format(node.port))
|
||||
node.start()
|
||||
# CHECK DATA CORRECTNESS
|
||||
after = node.safe_psql("postgres", "SELECT * FROM t_heap")
|
||||
@@ -168,3 +238,64 @@ class ReplicaTest(ProbackupTest, unittest.TestCase):
|
||||
|
||||
# Clean after yourself
|
||||
self.del_test_dir(module_name, fname)
|
||||
|
||||
# @unittest.skip("skip")
|
||||
def test_make_replica_via_restore(self):
|
||||
"""
|
||||
make archive master, take full and page archive backups from master,
|
||||
set replica, make archive backup from replica
|
||||
"""
|
||||
fname = self.id().split('.')[3]
|
||||
backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
|
||||
master = self.make_simple_node(
|
||||
base_dir="{0}/{1}/master".format(module_name, fname),
|
||||
set_replication=True,
|
||||
initdb_params=['--data-checksums'],
|
||||
pg_options={
|
||||
'wal_level': 'replica', 'max_wal_senders': '2',
|
||||
'checkpoint_timeout': '30s'}
|
||||
)
|
||||
self.init_pb(backup_dir)
|
||||
self.add_instance(backup_dir, 'master', master)
|
||||
self.set_archiving(backup_dir, 'master', master)
|
||||
# force more frequent wal switch
|
||||
master.append_conf('postgresql.auto.conf', 'archive_timeout = 10')
|
||||
master.start()
|
||||
|
||||
replica = self.make_simple_node(
|
||||
base_dir="{0}/{1}/replica".format(module_name, fname))
|
||||
replica.cleanup()
|
||||
|
||||
self.backup_node(backup_dir, 'master', master)
|
||||
|
||||
master.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,256) i")
|
||||
|
||||
before = master.safe_psql("postgres", "SELECT * FROM t_heap")
|
||||
|
||||
backup_id = self.backup_node(
|
||||
backup_dir, 'master', master, backup_type='page')
|
||||
self.restore_node(
|
||||
backup_dir, 'master', replica,
|
||||
options=['-R', '--recovery-target-action=promote'])
|
||||
|
||||
# Settings for Replica
|
||||
# self.set_replica(master, replica)
|
||||
self.set_archiving(backup_dir, 'replica', replica, replica=True)
|
||||
replica.append_conf(
|
||||
'postgresql.auto.conf', 'port = {0}'.format(replica.port))
|
||||
replica.start(["-t", "600"])
|
||||
|
||||
time.sleep(1)
|
||||
self.assertEqual(
|
||||
master.safe_psql(
|
||||
"postgres",
|
||||
"select exists(select * from pg_stat_replication)"
|
||||
).rstrip(),
|
||||
't')
|
||||
|
||||
# Clean after yourself
|
||||
self.del_test_dir(module_name, fname)
|
||||
|
||||
+460
-162
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user