Merge branch 'pgpro_1053_697'

This commit is contained in:
Grigory Smolkin
2018-04-11 19:48:08 +03:00
17 changed files with 1033 additions and 304 deletions
+1
View File
@@ -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);
+5
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
+1
View File
@@ -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';
}
+114
View File
@@ -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)
{
+1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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"
]
)
+16 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff