diff --git a/doc/pgprobackup.xml b/doc/pgprobackup.xml
index ccc81fe2..efd73ee9 100644
--- a/doc/pgprobackup.xml
+++ b/doc/pgprobackup.xml
@@ -1880,6 +1880,14 @@ pg_probackup restore -B backup_dir --instance template1 databases are always restored.
+
+
+ Due to how recovery works in PostgreSQL < 12 it is advisable to
+ disable option, when running partial
+ restore of PostgreSQL cluster of version less than .
+ Otherwise recovery may fail.
+
+
@@ -4411,6 +4419,10 @@ pg_probackup archive-get -B backup_dir --instance
Specifies the timestamp up to which recovery will proceed.
+ If timezone offset is not specified, local timezone is used.
+
+
+ Example: --recovery-target-time='2020-01-01 00:00:00+03'
@@ -4597,6 +4609,7 @@ pg_probackup archive-get -B backup_dir --instance
Specifies the timestamp up to which the backup will stay
pinned. Must be an ISO-8601 complaint timestamp.
+ If timezone offset is not specified, local timezone is used.
Example: --expire-time='2020-01-01 00:00:00+03'
diff --git a/gen_probackup_project.pl b/gen_probackup_project.pl
index b78f4699..abc779a4 100644
--- a/gen_probackup_project.pl
+++ b/gen_probackup_project.pl
@@ -167,6 +167,7 @@ sub build_pgprobackup
'pg_probackup.c',
'restore.c',
'show.c',
+ 'stream.c',
'util.c',
'validate.c',
'checkdb.c',
diff --git a/src/backup.c b/src/backup.c
index 17dedcae..7731c8af 100644
--- a/src/backup.c
+++ b/src/backup.c
@@ -133,7 +133,7 @@ do_backup_instance(PGconn *backup_conn, PGNodeInfo *nodeInfo, bool no_sync, bool
pg_ptrack_clear(backup_conn, nodeInfo->ptrack_version_num);
/* notify start of backup to PostgreSQL server */
- time2iso(label, lengthof(label), current.start_time);
+ time2iso(label, lengthof(label), current.start_time, false);
strncat(label, " with pg_probackup", lengthof(label) -
strlen(" with pg_probackup"));
@@ -572,7 +572,6 @@ do_backup_instance(PGconn *backup_conn, PGNodeInfo *nodeInfo, bool no_sync, bool
* NOTHING TO DO HERE
*/
-
/* write database map to file and add it to control file */
if (database_map)
{
@@ -765,7 +764,7 @@ do_backup(time_t start_time, pgSetBackupParams *set_backup_params,
/* Create backup directory and BACKUP_CONTROL_FILE */
if (pgBackupCreateDir(¤t))
elog(ERROR, "Cannot create backup directory");
- if (!lock_backup(¤t, true))
+ if (!lock_backup(¤t, true, true))
elog(ERROR, "Cannot lock backup %s directory",
base36enc(current.start_time));
write_backup(¤t, true);
@@ -1383,7 +1382,7 @@ wait_wal_lsn(XLogRecPtr target_lsn, bool is_start_lsn, TimeLineID tli,
XLogRecPtr res;
res = get_prior_record_lsn(wal_segment_dir, current.start_lsn, target_lsn, tli,
- in_prev_segment, instance_config.xlog_seg_size);
+ in_prev_segment, instance_config.xlog_seg_size);
if (!XLogRecPtrIsInvalid(res))
{
diff --git a/src/catalog.c b/src/catalog.c
index 3525524a..b3b01877 100644
--- a/src/catalog.c
+++ b/src/catalog.c
@@ -24,9 +24,14 @@ static pgBackup* get_oldest_backup(timelineInfo *tlinfo);
static const char *backupModes[] = {"", "PAGE", "PTRACK", "DELTA", "FULL"};
static pgBackup *readBackupControlFile(const char *path);
-static bool exit_hook_registered = false;
+static bool backup_lock_exit_hook_registered = false;
static parray *lock_files = NULL;
+static int lock_backup_exclusive(pgBackup *backup, bool strict);
+static bool lock_backup_internal(pgBackup *backup, bool exclusive);
+static bool lock_backup_read_only(pgBackup *backup);
+static bool wait_read_only_owners(pgBackup *backup);
+
static timelineInfo *
timelineInfoNew(TimeLineID tli)
{
@@ -131,29 +136,140 @@ write_backup_status(pgBackup *backup, BackupStatus status,
tmp->status = backup->status;
tmp->root_dir = pgut_strdup(backup->root_dir);
+ /* lock backup in exclusive mode */
+ if (!lock_backup(tmp, strict, true))
+ elog(ERROR, "Cannot lock backup %s directory", base36enc(backup->start_time));
+
write_backup(tmp, strict);
pgBackupFree(tmp);
}
/*
- * Create exclusive lockfile in the backup's directory.
+ * Lock backup in either exclusive or non-exclusive (read-only) mode.
+ * "strict" flag allows to ignore "out of space" errors and should be
+ * used only by DELETE command to free disk space on filled up
+ * filesystem.
+ *
+ * Only read only tasks (validate, restore) are allowed to take non-exclusive locks.
+ * Changing backup metadata must be done with exclusive lock.
+ *
+ * Only one process can hold exclusive lock at any time.
+ * Exlusive lock - PID of process, holding the lock - is placed in
+ * lock file: BACKUP_LOCK_FILE.
+ *
+ * Multiple proccess are allowed to take non-exclusive locks simultaneously.
+ * Non-exclusive locks - PIDs of proccesses, holding the lock - are placed in
+ * separate lock file: BACKUP_RO_LOCK_FILE.
+ * When taking RO lock, a brief exclusive lock is taken.
+ *
+ * TODO: lock-timeout as parameter
+ * TODO: we must think about more fine grain unlock mechanism - separate unlock_backup() function.
*/
bool
-lock_backup(pgBackup *backup, bool strict)
+lock_backup(pgBackup *backup, bool strict, bool exclusive)
{
- char lock_file[MAXPGPATH];
- int fd;
- char buffer[MAXPGPATH * 2 + 256];
- int ntries;
- int len;
- int encoded_pid;
- pid_t my_pid,
- my_p_pid;
+ int rc;
+ char lock_file[MAXPGPATH];
+ bool enospc_detected = false;
- join_path_components(lock_file, backup->root_dir, BACKUP_CATALOG_PID);
+ join_path_components(lock_file, backup->root_dir, BACKUP_LOCK_FILE);
+
+ rc = lock_backup_exclusive(backup, strict);
+
+ if (rc == 1)
+ return false;
+ else if (rc == 2)
+ {
+ enospc_detected = true;
+ if (strict)
+ return false;
+ }
/*
+ * We have exclusive lock, now there are following scenarios:
+ *
+ * 1. If we are for exlusive lock, then we must open the RO lock file
+ * and check if any of the processes listed there are still alive.
+ * If some processes are alive and are not going away in lock_timeout,
+ * then return false.
+ *
+ * 2. If we are here for non-exlusive lock, then write the pid
+ * into RO lock list and release the exclusive lock.
+ */
+
+ if (lock_backup_internal(backup, exclusive))
+ {
+ if (!exclusive)
+ {
+ /* release exclusive lock */
+ if (fio_unlink(lock_file, FIO_BACKUP_HOST) < 0)
+ elog(ERROR, "Could not remove old lock file \"%s\": %s",
+ lock_file, strerror(errno));
+
+ /* we are done */
+ return true;
+ }
+
+ /* When locking backup in lax exclusive mode,
+ * we should wait until all RO locks owners are gone.
+ */
+ if (!strict && enospc_detected)
+ {
+ /* We are in lax mode and EONSPC was encountered: once again try to grab exclusive lock,
+ * because there is a chance that lock_backup_read_only may have freed some space on filesystem,
+ * thanks to unlinking of BACKUP_RO_LOCK_FILE.
+ * If somebody concurrently acquired exclusive lock first, then we should give up.
+ */
+ if (lock_backup_exclusive(backup, strict) == 1)
+ return false;
+
+ return true;
+ }
+ }
+ else
+ return false;
+
+ /*
+ * Arrange to unlink the lock file(s) at proc_exit.
+ */
+ if (!backup_lock_exit_hook_registered)
+ {
+ atexit(unlink_lock_atexit);
+ backup_lock_exit_hook_registered = true;
+ }
+
+ /* Use parray so that the lock files are unlinked in a loop */
+ if (lock_files == NULL)
+ lock_files = parray_new();
+ parray_append(lock_files, pgut_strdup(lock_file));
+
+ return true;
+}
+
+/* Lock backup in exclusive mode
+ * Result codes:
+ * 0 Success
+ * 1 Failed to acquire lock in lock_timeout time
+ * 2 Failed to acquire lock due to ENOSPC
+ */
+int
+lock_backup_exclusive(pgBackup *backup, bool strict)
+{
+ char lock_file[MAXPGPATH];
+ int fd = 0;
+ char buffer[MAXPGPATH * 2 + 256];
+ int ntries = LOCK_TIMEOUT;
+ int log_freq = ntries / 5;
+ int len;
+ int encoded_pid;
+ pid_t my_p_pid;
+
+ join_path_components(lock_file, backup->root_dir, BACKUP_LOCK_FILE);
+
+ /*
+ * TODO: is this stuff with ppid below is relevant for us ?
+ *
* If the PID in the lockfile is our own PID or our parent's or
* grandparent's PID, then the file must be stale (probably left over from
* a previous system boot cycle). We need to check this because of the
@@ -171,7 +287,6 @@ lock_backup(pgBackup *backup, bool strict)
* would surely never launch a competing postmaster or pg_ctl process
* directly.
*/
- my_pid = getpid();
#ifndef WIN32
my_p_pid = getppid();
#else
@@ -188,8 +303,14 @@ lock_backup(pgBackup *backup, bool strict)
* (for example, a non-writable $backup_instance_path directory might cause a failure
* that won't go away). 100 tries seems like plenty.
*/
- for (ntries = 0;; ntries++)
+ do
{
+ FILE *fp_out = NULL;
+
+ if (interrupted)
+ elog(ERROR, "Interrupted while locking backup %s",
+ base36enc(backup->start_time));
+
/*
* Try to create the lock file --- O_EXCL makes this atomic.
*
@@ -202,8 +323,11 @@ lock_backup(pgBackup *backup, bool strict)
/*
* Couldn't create the pid file. Probably it already exists.
+ * If file already exists or we have some permission problem (???),
+ * then retry;
*/
- if ((errno != EEXIST && errno != EACCES) || ntries > 100)
+// if ((errno != EEXIST && errno != EACCES))
+ if (errno != EEXIST)
elog(ERROR, "Could not create lock file \"%s\": %s",
lock_file, strerror(errno));
@@ -211,28 +335,38 @@ lock_backup(pgBackup *backup, bool strict)
* Read the file to get the old owner's PID. Note race condition
* here: file might have been deleted since we tried to create it.
*/
- fd = fio_open(lock_file, O_RDONLY, FIO_BACKUP_HOST);
- if (fd < 0)
+
+ fp_out = fopen(lock_file, "r");
+ if (fp_out == NULL)
{
if (errno == ENOENT)
- continue; /* race condition; try again */
- elog(ERROR, "Could not open lock file \"%s\": %s",
- lock_file, strerror(errno));
+ continue; /* race condition; try again */
+ elog(ERROR, "Cannot open lock file \"%s\": %s", lock_file, strerror(errno));
}
- if ((len = fio_read(fd, buffer, sizeof(buffer) - 1)) < 0)
- elog(ERROR, "Could not read lock file \"%s\": %s",
- lock_file, strerror(errno));
- fio_close(fd);
+ len = fread(buffer, 1, sizeof(buffer) - 1, fp_out);
+ if (ferror(fp_out))
+ elog(ERROR, "Cannot read from lock file: \"%s\"", lock_file);
+ fclose(fp_out);
+
+ /*
+ * It should be possible only as a result of system crash,
+ * so its hypothetical owner should be dead by now
+ */
if (len == 0)
- elog(ERROR, "Lock file \"%s\" is empty", lock_file);
+ {
+ elog(WARNING, "Lock file \"%s\" is empty", lock_file);
+ goto grab_lock;
+ }
- buffer[len] = '\0';
encoded_pid = atoi(buffer);
if (encoded_pid <= 0)
- elog(ERROR, "Bogus data in lock file \"%s\": \"%s\"",
+ {
+ elog(WARNING, "Bogus data in lock file \"%s\": \"%s\"",
lock_file, buffer);
+ goto grab_lock;
+ }
/*
* Check to see if the other process still exists
@@ -247,9 +381,19 @@ lock_backup(pgBackup *backup, bool strict)
{
if (kill(encoded_pid, 0) == 0)
{
- elog(WARNING, "Process %d is using backup %s and still is running",
- encoded_pid, base36enc(backup->start_time));
- return false;
+ /* complain every fifth interval */
+ if ((ntries % log_freq) == 0)
+ {
+ elog(WARNING, "Process %d is using backup %s, and is still running",
+ encoded_pid, base36enc(backup->start_time));
+
+ elog(WARNING, "Waiting %u seconds on lock for backup %s", ntries, base36enc(backup->start_time));
+ }
+
+ sleep(1);
+
+ /* try again */
+ continue;
}
else
{
@@ -262,15 +406,25 @@ lock_backup(pgBackup *backup, bool strict)
}
}
+grab_lock:
/*
* Looks like nobody's home. Unlink the file and try again to create
* it. Need a loop because of possible race condition against other
* would-be creators.
*/
if (fio_unlink(lock_file, FIO_BACKUP_HOST) < 0)
+ {
+ if (errno == ENOENT)
+ continue; /* race condition, again */
elog(ERROR, "Could not remove old lock file \"%s\": %s",
lock_file, strerror(errno));
- }
+ }
+
+ } while (ntries--);
+
+ /* Failed to acquire exclusive lock in time */
+ if (fd <= 0)
+ return 1;
/*
* Successfully created the file, now fill it.
@@ -284,52 +438,215 @@ lock_backup(pgBackup *backup, bool strict)
fio_close(fd);
fio_unlink(lock_file, FIO_BACKUP_HOST);
- /* if write didn't set errno, assume problem is no disk space */
- errno = save_errno ? save_errno : ENOSPC;
/* In lax mode if we failed to grab lock because of 'out of space error',
* then treat backup as locked.
* Only delete command should be run in lax mode.
*/
- if (!strict && errno == ENOSPC)
- return true;
-
- elog(ERROR, "Could not write lock file \"%s\": %s",
- lock_file, strerror(errno));
+ if (!strict && save_errno == ENOSPC)
+ return 2;
+ else
+ elog(ERROR, "Could not write lock file \"%s\": %s",
+ lock_file, strerror(save_errno));
}
+
if (fio_flush(fd) != 0)
{
- int save_errno = errno;
+ int save_errno = errno;
fio_close(fd);
fio_unlink(lock_file, FIO_BACKUP_HOST);
- errno = save_errno;
- elog(ERROR, "Could not write lock file \"%s\": %s",
- lock_file, strerror(errno));
+
+ /* In lax mode if we failed to grab lock because of 'out of space error',
+ * then treat backup as locked.
+ * Only delete command should be run in lax mode.
+ */
+ if (!strict && save_errno == ENOSPC)
+ return 2;
+ else
+ elog(ERROR, "Could not flush lock file \"%s\": %s",
+ lock_file, strerror(save_errno));
}
+
if (fio_close(fd) != 0)
{
int save_errno = errno;
fio_unlink(lock_file, FIO_BACKUP_HOST);
- errno = save_errno;
- elog(ERROR, "Could not write lock file \"%s\": %s",
- lock_file, strerror(errno));
+
+ if (!strict && errno == ENOSPC)
+ return 2;
+ else
+ elog(ERROR, "Could not close lock file \"%s\": %s",
+ lock_file, strerror(save_errno));
}
- /*
- * Arrange to unlink the lock file(s) at proc_exit.
- */
- if (!exit_hook_registered)
+ return 0;
+}
+
+/* Wait until all read-only lock owners are gone */
+bool
+wait_read_only_owners(pgBackup *backup)
+{
+ FILE *fp = NULL;
+ char buffer[256];
+ pid_t encoded_pid;
+ int ntries = LOCK_TIMEOUT;
+ int log_freq = ntries / 5;
+ char lock_file[MAXPGPATH];
+
+ join_path_components(lock_file, backup->root_dir, BACKUP_RO_LOCK_FILE);
+
+ fp = fopen(lock_file, "r");
+ if (fp == NULL && errno != ENOENT)
+ elog(ERROR, "Cannot open lock file \"%s\": %s", lock_file, strerror(errno));
+
+ /* iterate over pids in lock file */
+ while (fp && fgets(buffer, sizeof(buffer), fp))
+ {
+ encoded_pid = atoi(buffer);
+ if (encoded_pid <= 0)
+ {
+ elog(WARNING, "Bogus data in lock file \"%s\": \"%s\"", lock_file, buffer);
+ continue;
+ }
+
+ /* wait until RO lock owners go away */
+ do
+ {
+ if (interrupted)
+ elog(ERROR, "Interrupted while locking backup %s",
+ base36enc(backup->start_time));
+
+ if (encoded_pid != my_pid)
+ {
+ if (kill(encoded_pid, 0) == 0)
+ {
+ if ((ntries % log_freq) == 0)
+ {
+ elog(WARNING, "Process %d is using backup %s in read only mode, and is still running",
+ encoded_pid, base36enc(backup->start_time));
+
+ elog(WARNING, "Waiting %u seconds on lock for backup %s", ntries,
+ base36enc(backup->start_time));
+ }
+
+ sleep(1);
+
+ /* try again */
+ continue;
+ }
+ else if (errno != ESRCH)
+ elog(ERROR, "Failed to send signal 0 to a process %d: %s",
+ encoded_pid, strerror(errno));
+ }
+
+ /* locker is dead */
+ break;
+
+ } while (ntries--);
+
+ if (ntries <= 0)
+ {
+ elog(WARNING, "Cannot to lock backup %s in exclusive mode, because process %u owns read-only lock",
+ base36enc(backup->start_time), encoded_pid);
+ return false;
+ }
+ }
+
+ if (fp && ferror(fp))
+ elog(ERROR, "Cannot read from lock file: \"%s\"", lock_file);
+
+ if (fp)
+ fclose(fp);
+
+ /* unlink RO lock list */
+ fio_unlink(lock_file, FIO_BACKUP_HOST);
+ return true;
+}
+
+bool
+lock_backup_internal(pgBackup *backup, bool exclusive)
+{
+ if (exclusive)
+ return wait_read_only_owners(backup);
+ else
+ return lock_backup_read_only(backup);
+}
+
+bool
+lock_backup_read_only(pgBackup *backup)
+{
+ FILE *fp_in = NULL;
+ FILE *fp_out = NULL;
+ char buf_in[256];
+ pid_t encoded_pid;
+ char lock_file[MAXPGPATH];
+
+ char buffer[8192]; /*TODO: should be enough, but maybe malloc+realloc is better ? */
+ char lock_file_tmp[MAXPGPATH];
+ int buffer_len = 0;
+
+ join_path_components(lock_file, backup->root_dir, BACKUP_RO_LOCK_FILE);
+ snprintf(lock_file_tmp, MAXPGPATH, "%s%s", lock_file, "tmp");
+
+ /* open already existing lock files */
+ fp_in = fopen(lock_file, "r");
+ if (fp_in == NULL && errno != ENOENT)
+ elog(ERROR, "Cannot open lock file \"%s\": %s", lock_file, strerror(errno));
+
+ /* read PIDs of owners */
+ while (fp_in && fgets(buf_in, sizeof(buf_in), fp_in))
{
- atexit(unlink_lock_atexit);
- exit_hook_registered = true;
+ encoded_pid = atoi(buf_in);
+ if (encoded_pid <= 0)
+ {
+ elog(WARNING, "Bogus data in lock file \"%s\": \"%s\"", lock_file, buf_in);
+ continue;
+ }
+
+ if (encoded_pid != my_pid)
+ {
+ if (kill(encoded_pid, 0) == 0)
+ {
+ /*
+ * Somebody is still using this backup in RO mode,
+ * copy this pid into a new file.
+ */
+ buffer_len += snprintf(buffer+buffer_len, 4096, "%u\n", encoded_pid);
+ }
+ else if (errno != ESRCH)
+ elog(ERROR, "Failed to send signal 0 to a process %d: %s",
+ encoded_pid, strerror(errno));
+ }
+ }
+
+ if (fp_in)
+ {
+ if (ferror(fp_in))
+ elog(ERROR, "Cannot read from lock file: \"%s\"", lock_file);
+ fclose(fp_in);
}
- /* Use parray so that the lock files are unlinked in a loop */
- if (lock_files == NULL)
- lock_files = parray_new();
- parray_append(lock_files, pgut_strdup(lock_file));
+ fp_out = fopen(lock_file_tmp, "w");
+ if (fp_out == NULL)
+ elog(ERROR, "Cannot open temp lock file \"%s\": %s", lock_file_tmp, strerror(errno));
+
+ /* add my own pid */
+ buffer_len += snprintf(buffer+buffer_len, sizeof(buffer), "%u\n", my_pid);
+
+ /* write out the collected PIDs to temp lock file */
+ fwrite(buffer, 1, buffer_len, fp_out);
+
+ if (ferror(fp_out))
+ elog(ERROR, "Cannot write to lock file: \"%s\"", lock_file_tmp);
+
+ if (fclose(fp_out) != 0)
+ elog(ERROR, "Cannot close temp lock file \"%s\": %s", lock_file_tmp, strerror(errno));
+
+ if (rename(lock_file_tmp, lock_file) < 0)
+ elog(ERROR, "Cannot rename file \"%s\" to \"%s\": %s",
+ lock_file_tmp, lock_file, strerror(errno));
return true;
}
@@ -485,7 +802,7 @@ catalog_get_backup_list(const char *instance_name, time_t requested_backup_id)
}
else if (strcmp(base36enc(backup->start_time), data_ent->d_name) != 0)
{
- elog(VERBOSE, "backup ID in control file \"%s\" doesn't match name of the backup folder \"%s\"",
+ elog(WARNING, "backup ID in control file \"%s\" doesn't match name of the backup folder \"%s\"",
base36enc(backup->start_time), backup_conf_path);
}
@@ -583,7 +900,7 @@ get_backup_filelist(pgBackup *backup, bool strict)
* Lock list of backups. Function goes in backward direction.
*/
void
-catalog_lock_backup_list(parray *backup_list, int from_idx, int to_idx, bool strict)
+catalog_lock_backup_list(parray *backup_list, int from_idx, int to_idx, bool strict, bool exclusive)
{
int start_idx,
end_idx;
@@ -598,7 +915,7 @@ catalog_lock_backup_list(parray *backup_list, int from_idx, int to_idx, bool str
for (i = start_idx; i >= end_idx; i--)
{
pgBackup *backup = (pgBackup *) parray_get(backup_list, i);
- if (!lock_backup(backup, strict))
+ if (!lock_backup(backup, strict, exclusive))
elog(ERROR, "Cannot lock backup %s directory",
base36enc(backup->start_time));
}
@@ -1648,7 +1965,7 @@ pin_backup(pgBackup *target_backup, pgSetBackupParams *set_backup_params)
{
char expire_timestamp[100];
- time2iso(expire_timestamp, lengthof(expire_timestamp), target_backup->expire_time);
+ time2iso(expire_timestamp, lengthof(expire_timestamp), target_backup->expire_time, false);
elog(INFO, "Backup %s is pinned until '%s'", base36enc(target_backup->start_time),
expire_timestamp);
}
@@ -1699,7 +2016,7 @@ add_note(pgBackup *target_backup, char *note)
* Write information about backup.in to stream "out".
*/
void
-pgBackupWriteControl(FILE *out, pgBackup *backup)
+pgBackupWriteControl(FILE *out, pgBackup *backup, bool utc)
{
char timestamp[100];
@@ -1731,27 +2048,27 @@ pgBackupWriteControl(FILE *out, pgBackup *backup)
(uint32) (backup->stop_lsn >> 32),
(uint32) backup->stop_lsn);
- time2iso(timestamp, lengthof(timestamp), backup->start_time);
+ time2iso(timestamp, lengthof(timestamp), backup->start_time, utc);
fio_fprintf(out, "start-time = '%s'\n", timestamp);
if (backup->merge_time > 0)
{
- time2iso(timestamp, lengthof(timestamp), backup->merge_time);
+ time2iso(timestamp, lengthof(timestamp), backup->merge_time, utc);
fio_fprintf(out, "merge-time = '%s'\n", timestamp);
}
if (backup->end_time > 0)
{
- time2iso(timestamp, lengthof(timestamp), backup->end_time);
+ time2iso(timestamp, lengthof(timestamp), backup->end_time, utc);
fio_fprintf(out, "end-time = '%s'\n", timestamp);
}
fio_fprintf(out, "recovery-xid = " XID_FMT "\n", backup->recovery_xid);
if (backup->recovery_time > 0)
{
- time2iso(timestamp, lengthof(timestamp), backup->recovery_time);
+ time2iso(timestamp, lengthof(timestamp), backup->recovery_time, utc);
fio_fprintf(out, "recovery-time = '%s'\n", timestamp);
}
if (backup->expire_time > 0)
{
- time2iso(timestamp, lengthof(timestamp), backup->expire_time);
+ time2iso(timestamp, lengthof(timestamp), backup->expire_time, utc);
fio_fprintf(out, "expire-time = '%s'\n", timestamp);
}
@@ -1798,7 +2115,9 @@ pgBackupWriteControl(FILE *out, pgBackup *backup)
/*
* Save the backup content into BACKUP_CONTROL_FILE.
- * TODO: honor the strict flag
+ * Flag strict allows to ignore "out of space" error
+ * when attempting to lock backup. Only delete is allowed
+ * to use this functionality.
*/
void
write_backup(pgBackup *backup, bool strict)
@@ -1806,7 +2125,7 @@ write_backup(pgBackup *backup, bool strict)
FILE *fp = NULL;
char path[MAXPGPATH];
char path_temp[MAXPGPATH];
- char buf[4096];
+ char buf[8192];
join_path_components(path, backup->root_dir, BACKUP_CONTROL_FILE);
snprintf(path_temp, sizeof(path_temp), "%s.tmp", path);
@@ -1822,20 +2141,36 @@ write_backup(pgBackup *backup, bool strict)
setvbuf(fp, buf, _IOFBF, sizeof(buf));
- pgBackupWriteControl(fp, backup);
+ pgBackupWriteControl(fp, backup, true);
+ /* Ignore 'out of space' error in lax mode */
if (fflush(fp) != 0)
- elog(ERROR, "Cannot flush control file \"%s\": %s",
- path_temp, strerror(errno));
+ {
+ int elevel = ERROR;
+ int save_errno = errno;
- if (fsync(fileno(fp)) < 0)
- elog(ERROR, "Cannot sync control file \"%s\": %s",
- path_temp, strerror(errno));
+ if (!strict && (errno == ENOSPC))
+ elevel = WARNING;
+
+ elog(elevel, "Cannot flush control file \"%s\": %s",
+ path_temp, strerror(save_errno));
+
+ if (!strict && (save_errno == ENOSPC))
+ {
+ fclose(fp);
+ fio_unlink(path_temp, FIO_BACKUP_HOST);
+ return;
+ }
+ }
if (fclose(fp) != 0)
elog(ERROR, "Cannot close control file \"%s\": %s",
path_temp, strerror(errno));
+ if (fio_sync(path_temp, FIO_BACKUP_HOST) < 0)
+ elog(ERROR, "Cannot sync control file \"%s\": %s",
+ path_temp, strerror(errno));
+
if (rename(path_temp, path) < 0)
elog(ERROR, "Cannot rename file \"%s\" to \"%s\": %s",
path_temp, path, strerror(errno));
diff --git a/src/delete.c b/src/delete.c
index 798a1653..e06d6808 100644
--- a/src/delete.c
+++ b/src/delete.c
@@ -89,7 +89,7 @@ do_delete(time_t backup_id)
if (!dry_run)
{
/* Lock marked for delete backups */
- catalog_lock_backup_list(delete_list, parray_num(delete_list) - 1, 0, false);
+ catalog_lock_backup_list(delete_list, parray_num(delete_list) - 1, 0, false, true);
/* Delete backups from the end of list */
for (i = (int) parray_num(delete_list) - 1; i >= 0; i--)
@@ -316,7 +316,7 @@ do_retention_internal(parray *backup_list, parray *to_keep_list, parray *to_purg
(backup->expire_time > current_time))
{
char expire_timestamp[100];
- time2iso(expire_timestamp, lengthof(expire_timestamp), backup->expire_time);
+ time2iso(expire_timestamp, lengthof(expire_timestamp), backup->expire_time, false);
elog(LOG, "Backup %s is pinned until '%s', retain",
base36enc(backup->start_time), expire_timestamp);
@@ -513,7 +513,7 @@ do_retention_merge(parray *backup_list, parray *to_keep_list, parray *to_purge_l
parray_rm(to_purge_list, full_backup, pgBackupCompareId);
/* Lock merge chain */
- catalog_lock_backup_list(merge_list, parray_num(merge_list) - 1, 0, true);
+ catalog_lock_backup_list(merge_list, parray_num(merge_list) - 1, 0, true, true);
/* Consider this extreme case */
// PAGEa1 PAGEb1 both valid
@@ -630,7 +630,7 @@ do_retention_purge(parray *to_keep_list, parray *to_purge_list)
continue;
/* Actual purge */
- if (!lock_backup(delete_backup, false))
+ if (!lock_backup(delete_backup, false, true))
{
/* If the backup still is used, do not interrupt and go to the next */
elog(WARNING, "Cannot lock backup %s directory, skip purging",
@@ -740,7 +740,7 @@ delete_backup_files(pgBackup *backup)
return;
}
- time2iso(timestamp, lengthof(timestamp), backup->recovery_time);
+ time2iso(timestamp, lengthof(timestamp), backup->recovery_time, false);
elog(INFO, "Delete: %s %s",
base36enc(backup->start_time), timestamp);
@@ -975,7 +975,7 @@ do_delete_instance(void)
/* Delete all backups. */
backup_list = catalog_get_backup_list(instance_name, INVALID_BACKUP_ID);
- catalog_lock_backup_list(backup_list, 0, parray_num(backup_list) - 1, true);
+ catalog_lock_backup_list(backup_list, 0, parray_num(backup_list) - 1, true, true);
for (i = 0; i < parray_num(backup_list); i++)
{
@@ -1081,7 +1081,7 @@ do_delete_status(InstanceConfig *instance_config, const char *status)
if (backup->stream)
size_to_delete += backup->wal_bytes;
- if (!dry_run && lock_backup(backup, false))
+ if (!dry_run && lock_backup(backup, false, true))
delete_backup_files(backup);
n_deleted++;
diff --git a/src/merge.c b/src/merge.c
index a453a073..05220a5d 100644
--- a/src/merge.c
+++ b/src/merge.c
@@ -58,6 +58,8 @@ merge_non_data_file(parray *parent_chain, pgBackup *full_backup,
pgFile *tmp_file, const char *full_database_dir,
const char *full_external_prefix);
+static bool is_forward_compatible(parray *parent_chain);
+
/*
* Implementation of MERGE command.
*
@@ -400,7 +402,7 @@ do_merge(time_t backup_id)
parray_append(merge_list, full_backup);
/* Lock merge chain */
- catalog_lock_backup_list(merge_list, parray_num(merge_list) - 1, 0, true);
+ catalog_lock_backup_list(merge_list, parray_num(merge_list) - 1, 0, true, true);
/* do actual merge */
merge_chain(merge_list, full_backup, dest_backup);
@@ -532,19 +534,7 @@ merge_chain(parray *parent_chain, pgBackup *full_backup, pgBackup *dest_backup)
* If current program version differs from destination backup version,
* then in-place merge is not possible.
*/
- if ((parse_program_version(full_backup->program_version) ==
- parse_program_version(dest_backup->program_version)) &&
- (parse_program_version(dest_backup->program_version) ==
- parse_program_version(PROGRAM_VERSION)))
- program_version_match = true;
- else
- elog(WARNING, "In-place merge is disabled because of program "
- "versions mismatch. Full backup version: %s, "
- "destination backup version: %s, "
- "current program version: %s",
- full_backup->program_version,
- dest_backup->program_version,
- PROGRAM_VERSION);
+ program_version_match = is_forward_compatible(parent_chain);
/* Forbid merge retry for failed merges between 2.4.0 and any
* older version. Several format changes makes it impossible
@@ -1398,3 +1388,58 @@ merge_non_data_file(parray *parent_chain, pgBackup *full_backup,
to_fullpath_tmp, to_fullpath, strerror(errno));
}
+
+/*
+ * If file format in incremental chain is compatible
+ * with current storage format.
+ * If not, then in-place merge is not possible.
+ *
+ * Consider the following examples:
+ * STORAGE_FORMAT_VERSION = 2.4.4
+ * 2.3.3 \
+ * 2.3.4 \ disable in-place merge, because
+ * 2.4.1 / current STORAGE_FORMAT_VERSION > 2.3.3
+ * 2.4.3 /
+ *
+ * 2.4.4 \ enable in_place merge, because
+ * 2.4.5 / current STORAGE_FORMAT_VERSION == 2.4.4
+ *
+ * 2.4.5 \ enable in_place merge, because
+ * 2.4.6 / current STORAGE_FORMAT_VERSION < 2.4.5
+ *
+ */
+bool
+is_forward_compatible(parray *parent_chain)
+{
+ int i;
+ pgBackup *oldest_ver_backup = NULL;
+ uint32 oldest_ver_in_chain = parse_program_version(PROGRAM_VERSION);
+
+ for (i = 0; i < parray_num(parent_chain); i++)
+ {
+ pgBackup *backup = (pgBackup *) parray_get(parent_chain, i);
+ uint32 current_version = parse_program_version(backup->program_version);
+
+ if (!oldest_ver_backup)
+ oldest_ver_backup = backup;
+
+ if (current_version < oldest_ver_in_chain)
+ {
+ oldest_ver_in_chain = current_version;
+ oldest_ver_backup = backup;
+ }
+ }
+
+ if (oldest_ver_in_chain < parse_program_version(STORAGE_FORMAT_VERSION))
+ {
+ elog(WARNING, "In-place merge is disabled because of storage format incompatibility. "
+ "Backup %s storage format version: %s, "
+ "current storage format version: %s",
+ base36enc(oldest_ver_backup->start_time),
+ oldest_ver_backup->program_version,
+ STORAGE_FORMAT_VERSION);
+ return false;
+ }
+
+ return true;
+}
\ No newline at end of file
diff --git a/src/parsexlog.c b/src/parsexlog.c
index 53597523..41a410d3 100644
--- a/src/parsexlog.c
+++ b/src/parsexlog.c
@@ -480,7 +480,7 @@ validate_wal(pgBackup *backup, const char *archivedir,
last_rec.rec_xid = backup->recovery_xid;
last_rec.rec_lsn = backup->stop_lsn;
- time2iso(last_timestamp, lengthof(last_timestamp), backup->recovery_time);
+ time2iso(last_timestamp, lengthof(last_timestamp), backup->recovery_time, false);
if ((TransactionIdIsValid(target_xid) && target_xid == last_rec.rec_xid)
|| (target_time != 0 && backup->recovery_time >= target_time)
@@ -493,7 +493,7 @@ validate_wal(pgBackup *backup, const char *archivedir,
InvalidXLogRecPtr, true, validateXLogRecord, &last_rec, true);
if (last_rec.rec_time > 0)
time2iso(last_timestamp, lengthof(last_timestamp),
- timestamptz_to_time_t(last_rec.rec_time));
+ timestamptz_to_time_t(last_rec.rec_time), false);
/* There are all needed WAL records */
if (all_wal)
@@ -508,7 +508,7 @@ validate_wal(pgBackup *backup, const char *archivedir,
(uint32) (last_rec.rec_lsn >> 32), (uint32) last_rec.rec_lsn);
if (target_time > 0)
- time2iso(target_timestamp, lengthof(target_timestamp), target_time);
+ time2iso(target_timestamp, lengthof(target_timestamp), target_time, false);
if (TransactionIdIsValid(target_xid) && target_time != 0)
elog(ERROR, "Not enough WAL records to time %s and xid " XID_FMT,
target_timestamp, target_xid);
@@ -846,7 +846,14 @@ get_prior_record_lsn(const char *archivedir, XLogRecPtr start_lsn,
*/
GetXLogSegNo(start_lsn, start_segno, wal_seg_size);
if (start_segno == segno)
+ {
startpoint = start_lsn;
+#if PG_VERSION_NUM >= 130000
+ if (XLogRecPtrIsInvalid(startpoint))
+ startpoint = SizeOfXLogShortPHD;
+ XLogBeginRead(xlogreader, startpoint);
+#endif
+ }
else
{
XLogRecPtr found;
diff --git a/src/pg_probackup.c b/src/pg_probackup.c
index bbc2a5bf..6fdf8baf 100644
--- a/src/pg_probackup.c
+++ b/src/pg_probackup.c
@@ -101,7 +101,7 @@ static pgRecoveryTarget *recovery_target_options = NULL;
static pgRestoreParams *restore_params = NULL;
time_t current_time = 0;
-bool restore_as_replica = false;
+static bool restore_as_replica = false;
bool no_validate = false;
IncrRestoreMode incremental_mode = INCR_NONE;
@@ -714,15 +714,14 @@ main(int argc, char *argv[])
if (force)
no_validate = true;
- if (replication_slot != NULL)
- restore_as_replica = true;
-
/* keep all params in one structure */
restore_params = pgut_new(pgRestoreParams);
restore_params->is_restore = (backup_subcmd == RESTORE_CMD);
restore_params->force = force;
restore_params->no_validate = no_validate;
restore_params->restore_as_replica = restore_as_replica;
+ restore_params->recovery_settings_mode = DEFAULT;
+
restore_params->primary_slot_name = replication_slot;
restore_params->skip_block_validation = skip_block_validation;
restore_params->skip_external_dirs = skip_external_dirs;
diff --git a/src/pg_probackup.h b/src/pg_probackup.h
index 67fcb54c..c53d31e9 100644
--- a/src/pg_probackup.h
+++ b/src/pg_probackup.h
@@ -66,7 +66,8 @@ extern const char *PROGRAM_EMAIL;
#define PG_GLOBAL_DIR "global"
#define BACKUP_CONTROL_FILE "backup.control"
#define BACKUP_CATALOG_CONF_FILE "pg_probackup.conf"
-#define BACKUP_CATALOG_PID "backup.pid"
+#define BACKUP_LOCK_FILE "backup.pid"
+#define BACKUP_RO_LOCK_FILE "backup_ro.pid"
#define DATABASE_FILE_LIST "backup_content.control"
#define PG_BACKUP_LABEL_FILE "backup_label"
#define PG_TABLESPACE_MAP_FILE "tablespace_map"
@@ -78,6 +79,7 @@ extern const char *PROGRAM_EMAIL;
/* Timeout defaults */
#define ARCHIVE_TIMEOUT_DEFAULT 300
#define REPLICA_TIMEOUT_DEFAULT 300
+#define LOCK_TIMEOUT 30
/* Directory/File permission */
#define DIR_PERMISSION (0700)
@@ -160,6 +162,16 @@ typedef enum PartialRestoreType
EXCLUDE,
} PartialRestoreType;
+typedef enum RecoverySettingsMode
+{
+ DEFAULT, /* not set */
+ DONTWRITE, /* explicitly forbid to update recovery settings */
+ //TODO Should we always clean/preserve old recovery settings,
+ // or make it configurable?
+ PITR_REQUESTED, /* can be set based on other parameters
+ * if not explicitly forbidden */
+} RecoverySettingsMode;
+
typedef enum CompressAlg
{
NOT_DEFINED_COMPRESS = 0,
@@ -295,6 +307,8 @@ typedef enum ShowFormat
#define PROGRAM_VERSION "2.5.0"
#define AGENT_PROTOCOL_VERSION 20500
+/* update only when changing storage format */
+#define STORAGE_FORMAT_VERSION "2.5.0"
typedef struct ConnectionOptions
{
@@ -408,10 +422,9 @@ struct pgBackup
TimeLineID tli; /* timeline of start and stop backup lsns */
XLogRecPtr start_lsn; /* backup's starting transaction log location */
XLogRecPtr stop_lsn; /* backup's finishing transaction log location */
- time_t start_time; /* since this moment backup has status
- * BACKUP_STATUS_RUNNING */
- time_t merge_dest_backup; /* start_time of incremental backup,
- * this backup is merging with.
+ time_t start_time; /* UTC time of backup creation */
+ time_t merge_dest_backup; /* start_time of incremental backup with
+ * which this backup is merging with.
* Only available for FULL backups
* with MERGING or MERGED statuses */
time_t merge_time; /* the moment when merge was started or 0 */
@@ -505,6 +518,8 @@ typedef struct pgRestoreParams
bool is_restore;
bool no_validate;
bool restore_as_replica;
+ //TODO maybe somehow add restore_as_replica as one of RecoverySettingsModes
+ RecoverySettingsMode recovery_settings_mode;
bool skip_external_dirs;
bool skip_block_validation; //Start using it
const char *restore_command;
@@ -665,6 +680,9 @@ typedef struct BackupPageHeader2
strcmp((fname) + XLOG_FNAME_LEN, ".gz") == 0)
#if PG_VERSION_NUM >= 110000
+
+#define WalSegmentOffset(xlogptr, wal_segsz_bytes) \
+ XLogSegmentOffset(xlogptr, wal_segsz_bytes)
#define GetXLogSegNo(xlrp, logSegNo, wal_segsz_bytes) \
XLByteToSeg(xlrp, logSegNo, wal_segsz_bytes)
#define GetXLogRecPtr(segno, offset, wal_segsz_bytes, dest) \
@@ -684,6 +702,8 @@ typedef struct BackupPageHeader2
#define GetXLogFromFileName(fname, tli, logSegNo, wal_segsz_bytes) \
XLogFromFileName(fname, tli, logSegNo, wal_segsz_bytes)
#else
+#define WalSegmentOffset(xlogptr, wal_segsz_bytes) \
+ ((xlogptr) & ((XLogSegSize) - 1))
#define GetXLogSegNo(xlrp, logSegNo, wal_segsz_bytes) \
XLByteToSeg(xlrp, logSegNo)
#define GetXLogRecPtr(segno, offset, wal_segsz_bytes, dest) \
@@ -881,7 +901,7 @@ extern void write_backup(pgBackup *backup, bool strict);
extern void write_backup_status(pgBackup *backup, BackupStatus status,
const char *instance_name, bool strict);
extern void write_backup_data_bytes(pgBackup *backup);
-extern bool lock_backup(pgBackup *backup, bool strict);
+extern bool lock_backup(pgBackup *backup, bool strict, bool exclusive);
extern const char *pgBackupGetBackupMode(pgBackup *backup, bool show_color);
extern void pgBackupGetBackupModeColor(pgBackup *backup, char *mode);
@@ -889,7 +909,7 @@ extern void pgBackupGetBackupModeColor(pgBackup *backup, char *mode);
extern parray *catalog_get_instance_list(void);
extern parray *catalog_get_backup_list(const char *instance_name, time_t requested_backup_id);
extern void catalog_lock_backup_list(parray *backup_list, int from_idx,
- int to_idx, bool strict);
+ int to_idx, bool strict, bool exclusive);
extern pgBackup *catalog_get_last_data_backup(parray *backup_list,
TimeLineID tli,
time_t current_start_time);
@@ -903,7 +923,7 @@ extern void do_set_backup(const char *instance_name, time_t backup_id,
extern void pin_backup(pgBackup *target_backup,
pgSetBackupParams *set_backup_params);
extern void add_note(pgBackup *target_backup, char *note);
-extern void pgBackupWriteControl(FILE *out, pgBackup *backup);
+extern void pgBackupWriteControl(FILE *out, pgBackup *backup, bool utc);
extern void write_backup_filelist(pgBackup *backup, parray *files,
const char *root, parray *external_list, bool sync);
@@ -993,8 +1013,8 @@ extern void fio_pgFileDelete(pgFile *file, const char *full_path);
extern void pgFileFree(void *file);
-extern pg_crc32 pgFileGetCRC(const char *file_path, bool missing_ok, bool use_crc32c);
-extern pg_crc32 pgFileGetCRCgz(const char *file_path, bool missing_ok, bool use_crc32c);
+extern pg_crc32 pgFileGetCRC(const char *file_path, bool use_crc32c, bool missing_ok);
+extern pg_crc32 pgFileGetCRCgz(const char *file_path, bool use_crc32c, bool missing_ok);
extern int pgFileMapComparePath(const void *f1, const void *f2);
extern int pgFileCompareName(const void *f1, const void *f2);
@@ -1092,7 +1112,7 @@ extern void set_min_recovery_point(pgFile *file, const char *backup_path,
extern void copy_pgcontrol_file(const char *from_fullpath, fio_location from_location,
const char *to_fullpath, fio_location to_location, pgFile *file);
-extern void time2iso(char *buf, size_t len, time_t time);
+extern void time2iso(char *buf, size_t len, time_t time, bool utc);
extern const char *status2str(BackupStatus status);
const char *status2str_color(BackupStatus status);
extern BackupStatus str2status(const char *status);
@@ -1157,6 +1177,9 @@ extern void fio_list_dir(parray *files, const char *root, bool exclude, bool fol
extern bool pgut_rmtree(const char *path, bool rmtopdir, bool strict);
+extern void pgut_setenv(const char *key, const char *val);
+extern void pgut_unsetenv(const char *key);
+
extern PageState *fio_get_checksum_map(const char *fullpath, uint32 checksum_version, int n_blocks,
XLogRecPtr dest_stop_lsn, BlockNumber segmentno, fio_location location);
diff --git a/src/restore.c b/src/restore.c
index 2ade54fa..bf278f8b 100644
--- a/src/restore.c
+++ b/src/restore.c
@@ -39,13 +39,29 @@ typedef struct
int ret;
} restore_files_arg;
+
+static void
+print_recovery_settings(FILE *fp, pgBackup *backup,
+ pgRestoreParams *params, pgRecoveryTarget *rt);
+static void
+print_standby_settings_common(FILE *fp, pgBackup *backup, pgRestoreParams *params);
+
+#if PG_VERSION_NUM >= 120000
+static void
+update_recovery_options(pgBackup *backup,
+ pgRestoreParams *params, pgRecoveryTarget *rt);
+#else
+static void
+update_recovery_options_before_v12(pgBackup *backup,
+ pgRestoreParams *params, pgRecoveryTarget *rt);
+#endif
+
static void create_recovery_conf(time_t backup_id,
pgRecoveryTarget *rt,
pgBackup *backup,
pgRestoreParams *params);
static void *restore_files(void *arg);
static void set_orphan_status(parray *backups, pgBackup *parent_backup);
-static void pg12_recovery_config(pgBackup *backup, bool add_include);
static void restore_chain(pgBackup *dest_backup, parray *parent_chain,
parray *dbOid_exclude_list, pgRestoreParams *params,
@@ -496,18 +512,11 @@ do_restore_or_validate(time_t target_backup_id, pgRecoveryTarget *rt,
{
tmp_backup = (pgBackup *) parray_get(parent_chain, i);
- /* Do not interrupt, validate the next backup */
- if (!lock_backup(tmp_backup, true))
+ /* lock every backup in chain in read-only mode */
+ if (!lock_backup(tmp_backup, true, false))
{
- if (params->is_restore)
- elog(ERROR, "Cannot lock backup %s directory",
- base36enc(tmp_backup->start_time));
- else
- {
- elog(WARNING, "Cannot lock backup %s directory, skip validation",
- base36enc(tmp_backup->start_time));
- continue;
- }
+ elog(ERROR, "Cannot lock backup %s directory",
+ base36enc(tmp_backup->start_time));
}
/* validate datafiles only */
@@ -604,6 +613,7 @@ do_restore_or_validate(time_t target_backup_id, pgRecoveryTarget *rt,
restore_chain(dest_backup, parent_chain, dbOid_exclude_list,
params, instance_config.pgdata, no_sync);
+ //TODO rename and update comment
/* Create recovery.conf with given recovery target parameters */
create_recovery_conf(target_backup_id, rt, dest_backup, params);
}
@@ -650,7 +660,7 @@ restore_chain(pgBackup *dest_backup, parray *parent_chain,
time_t start_time, end_time;
/* Preparations for actual restoring */
- time2iso(timestamp, lengthof(timestamp), dest_backup->start_time);
+ time2iso(timestamp, lengthof(timestamp), dest_backup->start_time, false);
elog(INFO, "Restoring the database from backup at %s", timestamp);
dest_files = get_backup_filelist(dest_backup, true);
@@ -660,7 +670,7 @@ restore_chain(pgBackup *dest_backup, parray *parent_chain,
{
pgBackup *backup = (pgBackup *) parray_get(parent_chain, i);
- if (!lock_backup(backup, true))
+ if (!lock_backup(backup, true, false))
elog(ERROR, "Cannot lock backup %s", base36enc(backup->start_time));
if (backup->status != BACKUP_STATUS_OK &&
@@ -1202,7 +1212,7 @@ done:
}
/*
- * Create recovery.conf (probackup_recovery.conf in case of PG12)
+ * Create recovery.conf (postgresql.auto.conf in case of PG12)
* with given recovery target parameters
*/
static void
@@ -1211,13 +1221,9 @@ create_recovery_conf(time_t backup_id,
pgBackup *backup,
pgRestoreParams *params)
{
- char path[MAXPGPATH];
- FILE *fp;
- bool pitr_requested;
bool target_latest;
bool target_immediate;
bool restore_command_provided = false;
- char restore_command_guc[16384];
if (instance_config.restore_command &&
(pg_strcasecmp(instance_config.restore_command, "none") != 0))
@@ -1249,36 +1255,142 @@ create_recovery_conf(time_t backup_id,
* We will get a replica that is "in the future" to the master.
* We accept this risk because its probability is low.
*/
- pitr_requested = !backup->stream || rt->time_string ||
+ if (!backup->stream || rt->time_string ||
rt->xid_string || rt->lsn_string || rt->target_name ||
- target_immediate || target_latest || restore_command_provided;
+ target_immediate || target_latest || restore_command_provided)
+ params->recovery_settings_mode = PITR_REQUESTED;
- /* No need to generate recovery.conf at all. */
- if (!(pitr_requested || params->restore_as_replica))
+ elog(LOG, "----------------------------------------");
+
+#if PG_VERSION_NUM >= 120000
+ update_recovery_options(backup, params, rt);
+#else
+ update_recovery_options_before_v12(backup, params, rt);
+#endif
+}
+
+
+/* TODO get rid of using global variables: instance_config, backup_path, instance_name */
+static void
+print_recovery_settings(FILE *fp, pgBackup *backup,
+ pgRestoreParams *params, pgRecoveryTarget *rt)
+{
+ char restore_command_guc[16384];
+ fio_fprintf(fp, "## recovery settings\n");
+
+ /* If restore_command is provided, use it. Otherwise construct it from scratch. */
+ if (instance_config.restore_command &&
+ (pg_strcasecmp(instance_config.restore_command, "none") != 0))
+ sprintf(restore_command_guc, "%s", instance_config.restore_command);
+ else
+ {
+ /* default cmdline, ok for local restore */
+ sprintf(restore_command_guc, "%s archive-get -B %s --instance %s "
+ "--wal-file-path=%%p --wal-file-name=%%f",
+ PROGRAM_FULL_PATH ? PROGRAM_FULL_PATH : PROGRAM_NAME,
+ backup_path, instance_name);
+
+ /* append --remote-* parameters provided via --archive-* settings */
+ if (instance_config.archive.host)
+ {
+ strcat(restore_command_guc, " --remote-host=");
+ strcat(restore_command_guc, instance_config.archive.host);
+ }
+
+ if (instance_config.archive.port)
+ {
+ strcat(restore_command_guc, " --remote-port=");
+ strcat(restore_command_guc, instance_config.archive.port);
+ }
+
+ if (instance_config.archive.user)
+ {
+ strcat(restore_command_guc, " --remote-user=");
+ strcat(restore_command_guc, instance_config.archive.user);
+ }
+ }
+
+ /*
+ * We've already checked that only one of the four following mutually
+ * exclusive options is specified, so the order of calls is insignificant.
+ */
+ if (rt->target_name)
+ fio_fprintf(fp, "recovery_target_name = '%s'\n", rt->target_name);
+
+ if (rt->time_string)
+ fio_fprintf(fp, "recovery_target_time = '%s'\n", rt->time_string);
+
+ if (rt->xid_string)
+ fio_fprintf(fp, "recovery_target_xid = '%s'\n", rt->xid_string);
+
+ if (rt->lsn_string)
+ fio_fprintf(fp, "recovery_target_lsn = '%s'\n", rt->lsn_string);
+
+ if (rt->target_stop && (strcmp(rt->target_stop, "immediate") == 0))
+ fio_fprintf(fp, "recovery_target = '%s'\n", rt->target_stop);
+
+ if (rt->inclusive_specified)
+ fio_fprintf(fp, "recovery_target_inclusive = '%s'\n",
+ rt->target_inclusive ? "true" : "false");
+
+ if (rt->target_tli)
+ fio_fprintf(fp, "recovery_target_timeline = '%u'\n", rt->target_tli);
+ else
+ {
+#if PG_VERSION_NUM >= 120000
+
+ /*
+ * In PG12 default recovery target timeline was changed to 'latest', which
+ * is extremely risky. Explicitly preserve old behavior of recovering to current
+ * timneline for PG12.
+ */
+ fio_fprintf(fp, "recovery_target_timeline = 'current'\n");
+#endif
+ }
+
+ if (rt->target_action)
+ fio_fprintf(fp, "recovery_target_action = '%s'\n", rt->target_action);
+ else
+ /* default recovery_target_action is 'pause' */
+ fio_fprintf(fp, "recovery_target_action = '%s'\n", "pause");
+
+ elog(LOG, "Setting restore_command to '%s'", restore_command_guc);
+ fio_fprintf(fp, "restore_command = '%s'\n", restore_command_guc);
+}
+
+static void
+print_standby_settings_common(FILE *fp, pgBackup *backup, pgRestoreParams *params)
+{
+ fio_fprintf(fp, "\n## standby settings\n");
+ if (params->primary_conninfo)
+ fio_fprintf(fp, "primary_conninfo = '%s'\n", params->primary_conninfo);
+ else if (backup->primary_conninfo)
+ fio_fprintf(fp, "primary_conninfo = '%s'\n", backup->primary_conninfo);
+
+ if (params->primary_slot_name != NULL)
+ fio_fprintf(fp, "primary_slot_name = '%s'\n", params->primary_slot_name);
+}
+
+#if PG_VERSION_NUM < 120000
+static void
+update_recovery_options_before_v12(pgBackup *backup,
+ pgRestoreParams *params, pgRecoveryTarget *rt)
+{
+ FILE *fp;
+ char path[MAXPGPATH];
+
+ /*
+ * If PITR is not requested and instance is not restored as replica,
+ * then recovery.conf should not be created.
+ */
+ if (params->recovery_settings_mode != PITR_REQUESTED &&
+ !params->restore_as_replica)
{
- /*
- * Restoring STREAM backup without PITR and not as replica,
- * recovery.signal and standby.signal for PG12 are not needed
- *
- * We do not add "include" option in this case because
- * here we are creating empty "probackup_recovery.conf"
- * to handle possible already existing "include"
- * directive pointing to "probackup_recovery.conf".
- * If don`t do that, recovery will fail.
- */
- pg12_recovery_config(backup, false);
return;
}
- elog(LOG, "----------------------------------------");
-#if PG_VERSION_NUM >= 120000
- elog(LOG, "creating probackup_recovery.conf");
- pg12_recovery_config(backup, true);
- snprintf(path, lengthof(path), "%s/probackup_recovery.conf", instance_config.pgdata);
-#else
- elog(LOG, "creating recovery.conf");
+ elog(LOG, "update recovery settings in recovery.conf");
snprintf(path, lengthof(path), "%s/recovery.conf", instance_config.pgdata);
-#endif
fp = fio_fopen(path, "w", FIO_DB_HOST);
if (fp == NULL)
@@ -1288,226 +1400,188 @@ create_recovery_conf(time_t backup_id,
if (fio_chmod(path, FILE_PERMISSION, FIO_DB_HOST) == -1)
elog(ERROR, "Cannot change mode of \"%s\": %s", path, strerror(errno));
-#if PG_VERSION_NUM >= 120000
- fio_fprintf(fp, "# probackup_recovery.conf generated by pg_probackup %s\n",
- PROGRAM_VERSION);
-#else
fio_fprintf(fp, "# recovery.conf generated by pg_probackup %s\n",
PROGRAM_VERSION);
-#endif
- /* construct restore_command */
- if (pitr_requested)
- {
- fio_fprintf(fp, "\n## recovery settings\n");
- /* If restore_command is provided, use it. Otherwise construct it from scratch. */
- if (restore_command_provided)
- sprintf(restore_command_guc, "%s", instance_config.restore_command);
- else
- {
- /* default cmdline, ok for local restore */
- sprintf(restore_command_guc, "%s archive-get -B %s --instance %s "
- "--wal-file-path=%%p --wal-file-name=%%f",
- PROGRAM_FULL_PATH ? PROGRAM_FULL_PATH : PROGRAM_NAME,
- backup_path, instance_name);
-
- /* append --remote-* parameters provided via --archive-* settings */
- if (instance_config.archive.host)
- {
- strcat(restore_command_guc, " --remote-host=");
- strcat(restore_command_guc, instance_config.archive.host);
- }
-
- if (instance_config.archive.port)
- {
- strcat(restore_command_guc, " --remote-port=");
- strcat(restore_command_guc, instance_config.archive.port);
- }
-
- if (instance_config.archive.user)
- {
- strcat(restore_command_guc, " --remote-user=");
- strcat(restore_command_guc, instance_config.archive.user);
- }
- }
-
- /*
- * We've already checked that only one of the four following mutually
- * exclusive options is specified, so the order of calls is insignificant.
- */
- if (rt->target_name)
- fio_fprintf(fp, "recovery_target_name = '%s'\n", rt->target_name);
-
- if (rt->time_string)
- fio_fprintf(fp, "recovery_target_time = '%s'\n", rt->time_string);
-
- if (rt->xid_string)
- fio_fprintf(fp, "recovery_target_xid = '%s'\n", rt->xid_string);
-
- if (rt->lsn_string)
- fio_fprintf(fp, "recovery_target_lsn = '%s'\n", rt->lsn_string);
-
- if (rt->target_stop && target_immediate)
- fio_fprintf(fp, "recovery_target = '%s'\n", rt->target_stop);
-
- if (rt->inclusive_specified)
- fio_fprintf(fp, "recovery_target_inclusive = '%s'\n",
- rt->target_inclusive ? "true" : "false");
-
- if (rt->target_tli)
- fio_fprintf(fp, "recovery_target_timeline = '%u'\n", rt->target_tli);
- else
- {
- /*
- * In PG12 default recovery target timeline was changed to 'latest', which
- * is extremely risky. Explicitly preserve old behavior of recovering to current
- * timneline for PG12.
- */
-#if PG_VERSION_NUM >= 120000
- fio_fprintf(fp, "recovery_target_timeline = 'current'\n");
-#endif
- }
-
- if (rt->target_action)
- fio_fprintf(fp, "recovery_target_action = '%s'\n", rt->target_action);
- else
- /* default recovery_target_action is 'pause' */
- fio_fprintf(fp, "recovery_target_action = '%s'\n", "pause");
- }
-
- if (pitr_requested)
- {
- elog(LOG, "Setting restore_command to '%s'", restore_command_guc);
- fio_fprintf(fp, "restore_command = '%s'\n", restore_command_guc);
- }
+ if (params->recovery_settings_mode == PITR_REQUESTED)
+ print_recovery_settings(fp, backup, params, rt);
if (params->restore_as_replica)
{
- fio_fprintf(fp, "\n## standby settings\n");
- /* standby_mode was removed in PG12 */
-#if PG_VERSION_NUM < 120000
+ print_standby_settings_common(fp, backup, params);
fio_fprintf(fp, "standby_mode = 'on'\n");
-#endif
-
- if (params->primary_conninfo)
- fio_fprintf(fp, "primary_conninfo = '%s'\n", params->primary_conninfo);
- else if (backup->primary_conninfo)
- fio_fprintf(fp, "primary_conninfo = '%s'\n", backup->primary_conninfo);
-
- if (params->primary_slot_name != NULL)
- fio_fprintf(fp, "primary_slot_name = '%s'\n", params->primary_slot_name);
}
if (fio_fflush(fp) != 0 ||
fio_fclose(fp))
elog(ERROR, "cannot write file \"%s\": %s", path,
strerror(errno));
-
-#if PG_VERSION_NUM >= 120000
- /*
- * Create "recovery.signal" to mark this recovery as PITR for PostgreSQL.
- * In older versions presense of recovery.conf alone was enough.
- * To keep behaviour consistent with older versions,
- * we are forced to create "recovery.signal"
- * even when only restore_command is provided.
- * Presense of "recovery.signal" by itself determine only
- * one thing: do PostgreSQL must switch to a new timeline
- * after successfull recovery or not?
- */
- if (pitr_requested)
- {
- elog(LOG, "creating recovery.signal file");
- snprintf(path, lengthof(path), "%s/recovery.signal", instance_config.pgdata);
-
- fp = fio_fopen(path, "w", FIO_DB_HOST);
- if (fp == NULL)
- elog(ERROR, "cannot open file \"%s\": %s", path,
- strerror(errno));
-
- if (fio_fflush(fp) != 0 ||
- fio_fclose(fp))
- elog(ERROR, "cannot write file \"%s\": %s", path,
- strerror(errno));
- }
-
- if (params->restore_as_replica)
- {
- elog(LOG, "creating standby.signal file");
- snprintf(path, lengthof(path), "%s/standby.signal", instance_config.pgdata);
-
- fp = fio_fopen(path, "w", FIO_DB_HOST);
- if (fp == NULL)
- elog(ERROR, "cannot open file \"%s\": %s", path,
- strerror(errno));
-
- if (fio_fflush(fp) != 0 ||
- fio_fclose(fp))
- elog(ERROR, "cannot write file \"%s\": %s", path,
- strerror(errno));
- }
-#endif
}
+#endif
/*
- * Create empty probackup_recovery.conf in PGDATA and
- * add "include" directive to postgresql.auto.conf
-
- * When restoring PG12 we always(!) must do this, even
- * when restoring STREAM backup without PITR or replica options
- * because restored instance may have been previously backed up
- * and restored again and user didn`t cleaned up postgresql.auto.conf.
-
- * So for recovery to work regardless of all this factors
- * we must always create empty probackup_recovery.conf file.
+ * Read postgresql.auto.conf, clean old recovery options,
+ * to avoid unexpected intersections.
+ * Write recovery options for this backup.
*/
-static void
-pg12_recovery_config(pgBackup *backup, bool add_include)
-{
#if PG_VERSION_NUM >= 120000
- char probackup_recovery_path[MAXPGPATH];
+static void
+update_recovery_options(pgBackup *backup,
+ pgRestoreParams *params, pgRecoveryTarget *rt)
+
+{
char postgres_auto_path[MAXPGPATH];
+ char postgres_auto_path_tmp[MAXPGPATH];
+ char path[MAXPGPATH];
FILE *fp;
+ FILE *fp_tmp;
+ struct stat st;
+ char current_time_str[100];
+ /* postgresql.auto.conf parsing */
+ char line[16384] = "\0";
+ char *buf = NULL;
+ int buf_len = 0;
+ int buf_len_max = 16384;
- if (add_include)
+ elog(LOG, "update recovery settings in postgresql.auto.conf");
+
+ time2iso(current_time_str, lengthof(current_time_str), current_time, false);
+
+ snprintf(postgres_auto_path, lengthof(postgres_auto_path),
+ "%s/postgresql.auto.conf", instance_config.pgdata);
+
+ if (fio_stat(postgres_auto_path, &st, false, FIO_DB_HOST) < 0)
{
- char current_time_str[100];
+ /* file not found is not an error case */
+ if (errno != ENOENT)
+ elog(ERROR, "cannot stat file \"%s\": %s", postgres_auto_path,
+ strerror(errno));
+ }
- time2iso(current_time_str, lengthof(current_time_str), current_time);
+ fp = fio_open_stream(postgres_auto_path, FIO_DB_HOST);
+ if (fp == NULL && errno != ENOENT)
+ elog(ERROR, "cannot open \"%s\": %s", postgres_auto_path, strerror(errno));
- snprintf(postgres_auto_path, lengthof(postgres_auto_path),
- "%s/postgresql.auto.conf", instance_config.pgdata);
+ sprintf(postgres_auto_path_tmp, "%s.tmp", postgres_auto_path);
+ fp_tmp = fio_fopen(postgres_auto_path_tmp, "w", FIO_DB_HOST);
+ if (fp_tmp == NULL)
+ elog(ERROR, "cannot open \"%s\": %s", postgres_auto_path_tmp, strerror(errno));
+ while (fp && fgets(line, lengthof(line), fp))
+ {
+ /* ignore "include 'probackup_recovery.conf'" directive */
+ if (strstr(line, "include") &&
+ strstr(line, "probackup_recovery.conf"))
+ {
+ continue;
+ }
+
+ /* ignore already existing recovery options */
+ if (strstr(line, "restore_command") ||
+ strstr(line, "recovery_target"))
+ {
+ continue;
+ }
+
+ if (!buf)
+ buf = pgut_malloc(buf_len_max);
+
+ /* avoid buffer overflow */
+ if ((buf_len + strlen(line)) >= buf_len_max)
+ {
+ buf_len_max += (buf_len + strlen(line)) *2;
+ buf = pgut_realloc(buf, buf_len_max);
+ }
+
+ buf_len += snprintf(buf+buf_len, sizeof(line), "%s", line);
+ }
+
+ /* close input postgresql.auto.conf */
+ if (fp)
+ fio_close_stream(fp);
+
+ /* TODO: detect remote error */
+ if (buf_len > 0)
+ fio_fwrite(fp_tmp, buf, buf_len);
+
+ if (fio_fflush(fp_tmp) != 0 ||
+ fio_fclose(fp_tmp))
+ elog(ERROR, "Cannot write file \"%s\": %s", postgres_auto_path_tmp,
+ strerror(errno));
+ pg_free(buf);
+
+ if (fio_rename(postgres_auto_path_tmp, postgres_auto_path, FIO_DB_HOST) < 0)
+ elog(ERROR, "Cannot rename file \"%s\" to \"%s\": %s",
+ postgres_auto_path_tmp, postgres_auto_path, strerror(errno));
+
+ if (fio_chmod(postgres_auto_path, FILE_PERMISSION, FIO_DB_HOST) == -1)
+ elog(ERROR, "Cannot change mode of \"%s\": %s", postgres_auto_path, strerror(errno));
+
+ if (params)
+ {
fp = fio_fopen(postgres_auto_path, "a", FIO_DB_HOST);
if (fp == NULL)
- elog(ERROR, "cannot write to file \"%s\": %s", postgres_auto_path,
+ elog(ERROR, "cannot open file \"%s\": %s", postgres_auto_path,
strerror(errno));
- // TODO: check if include 'probackup_recovery.conf' already exists
- fio_fprintf(fp, "\n# created by pg_probackup restore of backup %s at '%s'\n",
- base36enc(backup->start_time), current_time_str);
- fio_fprintf(fp, "include '%s'\n", "probackup_recovery.conf");
+ fio_fprintf(fp, "\n# recovery settings added by pg_probackup restore of backup %s at '%s'\n",
+ base36enc(backup->start_time), current_time_str);
+
+ if (params->recovery_settings_mode == PITR_REQUESTED)
+ print_recovery_settings(fp, backup, params, rt);
+
+ if (params->restore_as_replica)
+ print_standby_settings_common(fp, backup, params);
if (fio_fflush(fp) != 0 ||
fio_fclose(fp))
- elog(ERROR, "cannot write to file \"%s\": %s", postgres_auto_path,
+ elog(ERROR, "cannot write file \"%s\": %s", postgres_auto_path,
strerror(errno));
+
+ /*
+ * Create "recovery.signal" to mark this recovery as PITR for PostgreSQL.
+ * In older versions presense of recovery.conf alone was enough.
+ * To keep behaviour consistent with older versions,
+ * we are forced to create "recovery.signal"
+ * even when only restore_command is provided.
+ * Presense of "recovery.signal" by itself determine only
+ * one thing: do PostgreSQL must switch to a new timeline
+ * after successfull recovery or not?
+ */
+ if (params->recovery_settings_mode == PITR_REQUESTED)
+ {
+ elog(LOG, "creating recovery.signal file");
+ snprintf(path, lengthof(path), "%s/recovery.signal", instance_config.pgdata);
+
+ fp = fio_fopen(path, PG_BINARY_W, FIO_DB_HOST);
+ if (fp == NULL)
+ elog(ERROR, "cannot open file \"%s\": %s", path,
+ strerror(errno));
+
+ if (fio_fflush(fp) != 0 ||
+ fio_fclose(fp))
+ elog(ERROR, "cannot write file \"%s\": %s", path,
+ strerror(errno));
+ }
+
+ if (params->restore_as_replica)
+ {
+ elog(LOG, "creating standby.signal file");
+ snprintf(path, lengthof(path), "%s/standby.signal", instance_config.pgdata);
+
+ fp = fio_fopen(path, PG_BINARY_W, FIO_DB_HOST);
+ if (fp == NULL)
+ elog(ERROR, "cannot open file \"%s\": %s", path,
+ strerror(errno));
+
+ if (fio_fflush(fp) != 0 ||
+ fio_fclose(fp))
+ elog(ERROR, "cannot write file \"%s\": %s", path,
+ strerror(errno));
+ }
}
-
- /* Create empty probackup_recovery.conf */
- snprintf(probackup_recovery_path, lengthof(probackup_recovery_path),
- "%s/probackup_recovery.conf", instance_config.pgdata);
- fp = fio_fopen(probackup_recovery_path, "w", FIO_DB_HOST);
- if (fp == NULL)
- elog(ERROR, "cannot open file \"%s\": %s", probackup_recovery_path,
- strerror(errno));
-
- if (fio_fflush(fp) != 0 ||
- fio_fclose(fp))
- elog(ERROR, "cannot write to file \"%s\": %s", probackup_recovery_path,
- strerror(errno));
-#endif
- return;
}
+#endif
/*
* Try to read a timeline's history file.
diff --git a/src/show.c b/src/show.c
index a11a115e..61bde9ef 100644
--- a/src/show.c
+++ b/src/show.c
@@ -374,12 +374,12 @@ print_backup_json_object(PQExpBuffer buf, pgBackup *backup)
(uint32) (backup->stop_lsn >> 32), (uint32) backup->stop_lsn);
json_add_value(buf, "stop-lsn", lsn, json_level, true);
- time2iso(timestamp, lengthof(timestamp), backup->start_time);
+ time2iso(timestamp, lengthof(timestamp), backup->start_time, false);
json_add_value(buf, "start-time", timestamp, json_level, true);
if (backup->end_time)
{
- time2iso(timestamp, lengthof(timestamp), backup->end_time);
+ time2iso(timestamp, lengthof(timestamp), backup->end_time, false);
json_add_value(buf, "end-time", timestamp, json_level, true);
}
@@ -388,13 +388,13 @@ print_backup_json_object(PQExpBuffer buf, pgBackup *backup)
if (backup->recovery_time > 0)
{
- time2iso(timestamp, lengthof(timestamp), backup->recovery_time);
+ time2iso(timestamp, lengthof(timestamp), backup->recovery_time, false);
json_add_value(buf, "recovery-time", timestamp, json_level, true);
}
if (backup->expire_time > 0)
{
- time2iso(timestamp, lengthof(timestamp), backup->expire_time);
+ time2iso(timestamp, lengthof(timestamp), backup->expire_time, false);
json_add_value(buf, "expire-time", timestamp, json_level, true);
}
@@ -482,7 +482,7 @@ show_backup(const char *instance_name, time_t requested_backup_id)
}
if (show_format == SHOW_PLAIN)
- pgBackupWriteControl(stdout, backup);
+ pgBackupWriteControl(stdout, backup, false);
else
elog(ERROR, "Invalid show format %d", (int) show_format);
@@ -550,7 +550,7 @@ show_instance_plain(const char *instance_name, parray *backup_list, bool show_na
/* Recovery Time */
if (backup->recovery_time != (time_t) 0)
time2iso(row->recovery_time, lengthof(row->recovery_time),
- backup->recovery_time);
+ backup->recovery_time, false);
else
StrNCpy(row->recovery_time, "----", sizeof(row->recovery_time));
widths[cur] = Max(widths[cur], strlen(row->recovery_time));
diff --git a/src/stream.c b/src/stream.c
index db9ffe13..825aa0e7 100644
--- a/src/stream.c
+++ b/src/stream.c
@@ -64,6 +64,11 @@ static int checkpoint_timeout(PGconn *backup_conn);
static void *StreamLog(void *arg);
static bool stop_streaming(XLogRecPtr xlogpos, uint32 timeline,
bool segment_finished);
+static void add_walsegment_to_filelist(parray *filelist, uint32 timeline,
+ XLogRecPtr xlogpos, char *basedir,
+ uint32 xlog_seg_size);
+static void add_history_file_to_filelist(parray *filelist, uint32 timeline,
+ char *basedir);
/*
* Run IDENTIFY_SYSTEM through a given connection and
@@ -166,6 +171,8 @@ StreamLog(void *arg)
*/
stream_arg->startpos -= stream_arg->startpos % instance_config.xlog_seg_size;
+ xlog_files_list = parray_new();
+
/* Initialize timeout */
stream_stop_begin = 0;
@@ -205,7 +212,7 @@ StreamLog(void *arg)
stream_arg->basedir,
// (instance_config.compress_alg == NONE_COMPRESS) ? 0 : instance_config.compress_level,
0,
- true);
+ false);
ctl.replication_slot = replication_slot;
ctl.stop_socket = PGINVALID_SOCKET;
ctl.do_sync = false; /* We sync all files at the end of backup */
@@ -239,6 +246,25 @@ StreamLog(void *arg)
elog(ERROR, "Problem in receivexlog");
#endif
+ /* be paranoid and sort xlog_files_list,
+ * so if stop_lsn segno is already in the list,
+ * then list must be sorted to detect duplicates.
+ */
+ parray_qsort(xlog_files_list, pgFileCompareRelPathWithExternal);
+
+ /* Add the last segment to the list */
+ add_walsegment_to_filelist(xlog_files_list, stream_arg->starttli,
+ stop_stream_lsn, (char *) stream_arg->basedir,
+ instance_config.xlog_seg_size);
+
+ /* append history file to walsegment filelist */
+ add_history_file_to_filelist(xlog_files_list, stream_arg->starttli, (char *) stream_arg->basedir);
+
+ /*
+ * TODO: remove redundant WAL segments
+ * walk pg_wal and remove files with segno greater that of stop_lsn`s segno +1
+ */
+
elog(LOG, "finished streaming WAL at %X/%X (timeline %u)",
(uint32) (stop_stream_lsn >> 32), (uint32) stop_stream_lsn, stream_arg->starttli);
stream_arg->ret = 0;
@@ -275,41 +301,12 @@ stop_streaming(XLogRecPtr xlogpos, uint32 timeline, bool segment_finished)
/* we assume that we get called once at the end of each segment */
if (segment_finished)
{
- XLogSegNo xlog_segno;
- char wal_segment_name[MAXNAMLEN];
- char wal_segment_relpath[MAXPGPATH];
- char wal_segment_fullpath[MAXPGPATH];
- pgFile *file = NULL;
-
elog(VERBOSE, _("finished segment at %X/%X (timeline %u)"),
(uint32) (xlogpos >> 32), (uint32) xlogpos, timeline);
- /* Add streamed xlog file into the backup's list of files */
- if (!xlog_files_list)
- xlog_files_list = parray_new();
-
- GetXLogSegNo(xlogpos, xlog_segno, instance_config.xlog_seg_size);
-
- /* xlogpos points to the current segment, and we need the finished - previous one */
- xlog_segno--;
- GetXLogFileName(wal_segment_name, timeline, xlog_segno,
- instance_config.xlog_seg_size);
-
- join_path_components(wal_segment_fullpath,
- stream_thread_arg.basedir, wal_segment_name);
-
- join_path_components(wal_segment_relpath,
- PG_XLOG_DIR, wal_segment_name);
-
- /* append file to filelist */
- file = pgFileNew(wal_segment_fullpath, wal_segment_relpath, false, 0, FIO_BACKUP_HOST);
- file->name = file->rel_path;
- file->crc = pgFileGetCRC(wal_segment_fullpath, true, false);
-
- /* Should we recheck it using stat? */
- file->write_size = instance_config.xlog_seg_size;
- file->uncompressed_size = instance_config.xlog_seg_size;
- parray_append(xlog_files_list, file);
+ add_walsegment_to_filelist(xlog_files_list, timeline, xlogpos,
+ (char*) stream_thread_arg.basedir,
+ instance_config.xlog_seg_size);
}
/*
@@ -395,9 +392,94 @@ start_WAL_streaming(PGconn *backup_conn, char *stream_dst_path, ConnectionOption
int
wait_WAL_streaming_end(parray *backup_files_list)
{
- parray_concat(backup_files_list, xlog_files_list);
- parray_free(xlog_files_list);
+ pthread_join(stream_thread, NULL);
- pthread_join(stream_thread, NULL);
- return stream_thread_arg.ret;
+ parray_concat(backup_files_list, xlog_files_list);
+ parray_free(xlog_files_list);
+ return stream_thread_arg.ret;
+}
+
+/* Append streamed WAL segment to filelist */
+void
+add_walsegment_to_filelist(parray *filelist, uint32 timeline, XLogRecPtr xlogpos, char *basedir, uint32 xlog_seg_size)
+{
+ XLogSegNo xlog_segno;
+ char wal_segment_name[MAXFNAMELEN];
+ char wal_segment_relpath[MAXPGPATH];
+ char wal_segment_fullpath[MAXPGPATH];
+ pgFile *file = NULL;
+ pgFile **existing_file = NULL;
+
+ GetXLogSegNo(xlogpos, xlog_segno, xlog_seg_size);
+
+ /*
+ * When xlogpos points to the zero offset (0/3000000),
+ * it means that previous segment was just successfully streamed.
+ * When xlogpos points to the positive offset,
+ * then current segment is successfully streamed.
+ */
+ if (WalSegmentOffset(xlogpos, xlog_seg_size) == 0)
+ xlog_segno--;
+
+ GetXLogFileName(wal_segment_name, timeline, xlog_segno, xlog_seg_size);
+
+ join_path_components(wal_segment_fullpath, basedir, wal_segment_name);
+ join_path_components(wal_segment_relpath, PG_XLOG_DIR, wal_segment_name);
+
+ file = pgFileNew(wal_segment_fullpath, wal_segment_relpath, false, 0, FIO_BACKUP_HOST);
+
+ /*
+ * Check if file is already in the list
+ * stop_lsn segment can be added to this list twice, so
+ * try not to add duplicates
+ */
+
+ existing_file = (pgFile **) parray_bsearch(filelist, file, pgFileCompareRelPathWithExternal);
+
+ if (existing_file)
+ {
+ (*existing_file)->crc = pgFileGetCRC(wal_segment_fullpath, true, false);
+ (*existing_file)->write_size = xlog_seg_size;
+ (*existing_file)->uncompressed_size = xlog_seg_size;
+
+ return;
+ }
+
+ /* calculate crc */
+ file->crc = pgFileGetCRC(wal_segment_fullpath, true, false);
+
+ /* Should we recheck it using stat? */
+ file->write_size = xlog_seg_size;
+ file->uncompressed_size = xlog_seg_size;
+
+ /* append file to filelist */
+ parray_append(filelist, file);
+}
+
+/* Append history file to filelist */
+void
+add_history_file_to_filelist(parray *filelist, uint32 timeline, char *basedir)
+{
+ char filename[MAXFNAMELEN];
+ char fullpath[MAXPGPATH];
+ char relpath[MAXPGPATH];
+ pgFile *file = NULL;
+
+ /* Timeline 1 does not have a history file */
+ if (timeline == 1)
+ return;
+
+ snprintf(filename, lengthof(filename), "%08X.history", timeline);
+ join_path_components(fullpath, basedir, filename);
+ join_path_components(relpath, PG_XLOG_DIR, filename);
+
+ file = pgFileNew(fullpath, relpath, false, 0, FIO_BACKUP_HOST);
+
+ /* calculate crc */
+ file->crc = pgFileGetCRC(fullpath, true, false);
+ file->write_size = file->size;
+ file->uncompressed_size = file->size;
+
+ /* append file to filelist */
+ parray_append(filelist, file);
}
diff --git a/src/utils/configuration.c b/src/utils/configuration.c
index 1ef332ed..d6a7d069 100644
--- a/src/utils/configuration.c
+++ b/src/utils/configuration.c
@@ -490,9 +490,9 @@ config_read_opt(const char *path, ConfigOption options[], int elevel,
bool strict, bool missing_ok)
{
FILE *fp;
- char buf[1024];
+ char buf[4096];
char key[1024];
- char value[1024];
+ char value[2048];
int parsed_options = 0;
if (!options)
@@ -679,7 +679,7 @@ option_get_value(ConfigOption *opt)
if (t > 0)
{
timestamp = palloc(100);
- time2iso(timestamp, 100, t);
+ time2iso(timestamp, 100, t, false);
}
else
timestamp = palloc0(1 /* just null termination */);
@@ -1111,6 +1111,8 @@ parse_time(const char *value, time_t *result, bool utc_default)
struct tm tm;
char junk[2];
+ char *local_tz = getenv("TZ");
+
/* tmp = replace( value, !isalnum, ' ' ) */
tmp = pgut_malloc(strlen(value) + + 1);
len = 0;
@@ -1221,24 +1223,37 @@ parse_time(const char *value, time_t *result, bool utc_default)
/* determine whether Daylight Saving Time is in effect */
tm.tm_isdst = -1;
+ /*
+ * If tz is not set,
+ * treat it as UTC if requested, otherwise as local timezone
+ */
+ if (tz_set || utc_default)
+ {
+ /* set timezone to UTC */
+ pgut_setenv("TZ", "UTC");
+#ifdef WIN32
+ tzset();
+#endif
+ }
+
+ /* convert time to utc unix time */
*result = mktime(&tm);
+ /* return old timezone back if any */
+ if (local_tz)
+ pgut_setenv("TZ", local_tz);
+ else
+ pgut_unsetenv("TZ");
+
+#ifdef WIN32
+ tzset();
+#endif
+
/* adjust time zone */
if (tz_set || utc_default)
{
- time_t ltime = time(NULL);
- struct tm *ptm = gmtime(<ime);
- time_t gmt = mktime(ptm);
- time_t offset;
-
/* UTC time */
*result -= tz;
-
- /* Get local time */
- ptm = localtime(<ime);
- offset = ltime - gmt + (ptm->tm_isdst ? 3600 : 0);
-
- *result += offset;
}
return true;
@@ -1462,14 +1477,40 @@ convert_from_base_unit_u(uint64 base_value, int base_unit,
* Convert time_t value to ISO-8601 format string. Always set timezone offset.
*/
void
-time2iso(char *buf, size_t len, time_t time)
+time2iso(char *buf, size_t len, time_t time, bool utc)
{
- struct tm *ptm = gmtime(&time);
- time_t gmt = mktime(ptm);
+ struct tm *ptm = NULL;
+ time_t gmt;
time_t offset;
char *ptr = buf;
+ char *local_tz = getenv("TZ");
+ /* set timezone to UTC if requested */
+ if (utc)
+ {
+ pgut_setenv("TZ", "UTC");
+#ifdef WIN32
+ tzset();
+#endif
+ }
+
+ ptm = gmtime(&time);
+ gmt = mktime(ptm);
ptm = localtime(&time);
+
+ if (utc)
+ {
+ /* return old timezone back if any */
+ if (local_tz)
+ pgut_setenv("TZ", local_tz);
+ else
+ pgut_unsetenv("TZ");
+#ifdef WIN32
+ tzset();
+#endif
+ }
+
+ /* adjust timezone offset */
offset = time - gmt + (ptm->tm_isdst ? 3600 : 0);
strftime(ptr, len, "%Y-%m-%d %H:%M:%S", ptm);
diff --git a/src/utils/configuration.h b/src/utils/configuration.h
index 46b5d6c1..eea8c774 100644
--- a/src/utils/configuration.h
+++ b/src/utils/configuration.h
@@ -96,7 +96,7 @@ extern bool parse_int(const char *value, int *result, int flags,
const char **hintmsg);
extern bool parse_lsn(const char *value, XLogRecPtr *result);
-extern void time2iso(char *buf, size_t len, time_t time);
+extern void time2iso(char *buf, size_t len, time_t time, bool utc);
extern void convert_from_base_unit(int64 base_value, int base_unit,
int64 *value, const char **unit);
diff --git a/src/utils/pgut.c b/src/utils/pgut.c
index 27cfe6d1..bc35a24f 100644
--- a/src/utils/pgut.c
+++ b/src/utils/pgut.c
@@ -1213,3 +1213,61 @@ pgut_rmtree(const char *path, bool rmtopdir, bool strict)
return result;
}
+
+/* cross-platform setenv */
+void
+pgut_setenv(const char *key, const char *val)
+{
+#ifdef WIN32
+ char *envstr = NULL;
+ envstr = psprintf("%s=%s", key, val);
+ putenv(envstr);
+#else
+ setenv(key, val, 1);
+#endif
+}
+
+/* stolen from unsetenv.c */
+void
+pgut_unsetenv(const char *key)
+{
+#ifdef WIN32
+ char *envstr = NULL;
+
+ if (getenv(key) == NULL)
+ return; /* no work */
+
+ /*
+ * The technique embodied here works if libc follows the Single Unix Spec
+ * and actually uses the storage passed to putenv() to hold the environ
+ * entry. When we clobber the entry in the second step we are ensuring
+ * that we zap the actual environ member. However, there are some libc
+ * implementations (notably recent BSDs) that do not obey SUS but copy the
+ * presented string. This method fails on such platforms. Hopefully all
+ * such platforms have unsetenv() and thus won't be using this hack. See:
+ * http://www.greenend.org.uk/rjk/2008/putenv.html
+ *
+ * Note that repeatedly setting and unsetting a var using this code will
+ * leak memory.
+ */
+
+ envstr = (char *) pgut_malloc(strlen(key) + 2);
+ if (!envstr) /* not much we can do if no memory */
+ return;
+
+ /* Override the existing setting by forcibly defining the var */
+ sprintf(envstr, "%s=", key);
+ putenv(envstr);
+
+ /* Now we can clobber the variable definition this way: */
+ strcpy(envstr, "=");
+
+ /*
+ * This last putenv cleans up if we have multiple zero-length names as a
+ * result of unsetting multiple things.
+ */
+ putenv(envstr);
+#else
+ unsetenv(key);
+#endif
+}
diff --git a/src/validate.c b/src/validate.c
index 8159881a..30bfbf3e 100644
--- a/src/validate.c
+++ b/src/validate.c
@@ -192,7 +192,7 @@ pgBackupValidate(pgBackup *backup, pgRestoreParams *params)
backup->status = BACKUP_STATUS_CORRUPT;
write_backup_status(backup, corrupted ? BACKUP_STATUS_CORRUPT :
- BACKUP_STATUS_OK, instance_name, true);
+ BACKUP_STATUS_OK, instance_name, true);
if (corrupted)
elog(WARNING, "Backup %s data files are corrupted", base36enc(backup->start_time));
@@ -570,7 +570,7 @@ do_validate_instance(void)
base_full_backup = current_backup;
/* Do not interrupt, validate the next backup */
- if (!lock_backup(current_backup, true))
+ if (!lock_backup(current_backup, true, false))
{
elog(WARNING, "Cannot lock backup %s directory, skip validation",
base36enc(current_backup->start_time));
@@ -665,7 +665,7 @@ do_validate_instance(void)
if (backup->status == BACKUP_STATUS_ORPHAN)
{
/* Do not interrupt, validate the next backup */
- if (!lock_backup(backup, true))
+ if (!lock_backup(backup, true, false))
{
elog(WARNING, "Cannot lock backup %s directory, skip validation",
base36enc(backup->start_time));
diff --git a/tests/archive.py b/tests/archive.py
index 5a09bc8c..4551fda9 100644
--- a/tests/archive.py
+++ b/tests/archive.py
@@ -1612,7 +1612,7 @@ class ArchiveTest(ProbackupTest, unittest.TestCase):
])
if self.get_version(node) >= self.version_to_num('12.0'):
- recovery_conf = os.path.join(node.data_dir, 'probackup_recovery.conf')
+ recovery_conf = os.path.join(node.data_dir, 'postgresql.auto.conf')
else:
recovery_conf = os.path.join(node.data_dir, 'recovery.conf')
@@ -1687,7 +1687,7 @@ class ArchiveTest(ProbackupTest, unittest.TestCase):
self.restore_node(backup_dir, 'node', node)
if self.get_version(node) >= self.version_to_num('12.0'):
- recovery_conf = os.path.join(node.data_dir, 'probackup_recovery.conf')
+ recovery_conf = os.path.join(node.data_dir, 'postgresql.auto.conf')
else:
recovery_conf = os.path.join(node.data_dir, 'recovery.conf')
@@ -2101,7 +2101,7 @@ class ArchiveTest(ProbackupTest, unittest.TestCase):
if node.major_version >= 12:
node.append_conf(
- 'probackup_recovery.conf', "restore_command = '{0}'".format(restore_command))
+ 'postgresql.auto.conf', "restore_command = '{0}'".format(restore_command))
else:
node.append_conf(
'recovery.conf', "restore_command = '{0}'".format(restore_command))
diff --git a/tests/backup.py b/tests/backup.py
index 957231cb..bf0cbca8 100644
--- a/tests/backup.py
+++ b/tests/backup.py
@@ -212,9 +212,7 @@ class BackupTest(ProbackupTest, unittest.TestCase):
except ProbackupException as e:
self.assertTrue(
"INFO: Validate backups of the instance 'node'" in e.message and
- "WARNING: Backup file".format(
- file) in e.message and
- "is not found".format(file) in e.message and
+ "WARNING: Backup file" in e.message and "is not found" in e.message and
"WARNING: Backup {0} data files are corrupted".format(
backup_id) in e.message and
"WARNING: Some backups are not valid" in e.message,
diff --git a/tests/checkdb.py b/tests/checkdb.py
index a7527d57..3349ad2e 100644
--- a/tests/checkdb.py
+++ b/tests/checkdb.py
@@ -210,6 +210,7 @@ class CheckdbTest(ProbackupTest, unittest.TestCase):
log_file_content)
# Clean after yourself
+ gdb.kill()
self.del_test_dir(module_name, fname)
# @unittest.skip("skip")
@@ -495,6 +496,7 @@ class CheckdbTest(ProbackupTest, unittest.TestCase):
self.assertNotIn('connection to client lost', output)
# Clean after yourself
+ gdb.kill()
self.del_test_dir(module_name, fname)
# @unittest.skip("skip")
@@ -588,7 +590,7 @@ class CheckdbTest(ProbackupTest, unittest.TestCase):
'GRANT EXECUTE ON FUNCTION pg_catalog.charne("char", "char") TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.pg_is_in_recovery() TO backup; '
'GRANT EXECUTE ON FUNCTION pg_catalog.pg_control_system() TO backup; '
- 'GRANT EXECUTE ON FUNCTION bt_index_check(regclass) TO backup; '
+# 'GRANT EXECUTE ON FUNCTION bt_index_check(regclass) TO backup; '
'GRANT EXECUTE ON FUNCTION bt_index_check(regclass, bool) TO backup;'
)
# >= 10
@@ -618,14 +620,10 @@ class CheckdbTest(ProbackupTest, unittest.TestCase):
'GRANT EXECUTE ON FUNCTION bt_index_check(regclass, bool) TO backup;'
)
-# if ProbackupTest.enterprise:
-# node.safe_psql(
-# "backupdb",
-# "GRANT EXECUTE ON FUNCTION pg_catalog.pgpro_edition() TO backup")
-#
-# node.safe_psql(
-# "backupdb",
-# "GRANT EXECUTE ON FUNCTION pg_catalog.pgpro_version() TO backup")
+ if ProbackupTest.enterprise:
+ node.safe_psql(
+ "backupdb",
+ "GRANT EXECUTE ON FUNCTION pg_catalog.pgpro_edition() TO backup")
# checkdb
try:
@@ -659,3 +657,6 @@ class CheckdbTest(ProbackupTest, unittest.TestCase):
e.message,
"\n Unexpected Error Message: {0}\n CMD: {1}".format(
repr(e.message), self.cmd))
+
+ # Clean after yourself
+ self.del_test_dir(module_name, fname)
diff --git a/tests/compatibility.py b/tests/compatibility.py
index d2db2be2..54793334 100644
--- a/tests/compatibility.py
+++ b/tests/compatibility.py
@@ -675,7 +675,7 @@ class CompatibilityTest(ProbackupTest, unittest.TestCase):
# check that in-place is disabled
self.assertIn(
"WARNING: In-place merge is disabled "
- "because of program versions mismatch", output)
+ "because of storage format incompatibility", output)
# restore merged backup
node_restored = self.make_simple_node(
@@ -1011,6 +1011,87 @@ class CompatibilityTest(ProbackupTest, unittest.TestCase):
# Clean after yourself
self.del_test_dir(module_name, fname)
+ # @unittest.expectedFailure
+ # @unittest.skip("skip")
+ def test_backward_compatibility_merge_5(self):
+ """
+ Create node, take FULL and PAGE backups with old binary,
+ merge them with new binary.
+ old binary version >= STORAGE_FORMAT_VERSION (2.4.4)
+ """
+
+ if self.version_to_num(self.old_probackup_version) < self.version_to_num('2.4.4'):
+ return unittest.skip('OLD pg_probackup binary must be == 2.4.4 for this test')
+
+ self.assertNotEqual(
+ self.version_to_num(self.old_probackup_version),
+ self.version_to_num(self.probackup_version))
+
+ fname = self.id().split('.')[3]
+ backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
+ node = self.make_simple_node(
+ base_dir=os.path.join(module_name, fname, 'node'),
+ set_replication=True,
+ initdb_params=['--data-checksums'],
+ pg_options={'autovacuum': 'off'})
+
+ self.init_pb(backup_dir, old_binary=True)
+ self.add_instance(backup_dir, 'node', node, old_binary=True)
+
+ self.set_archiving(backup_dir, 'node', node, old_binary=True)
+ node.slow_start()
+
+ node.pgbench_init(scale=20)
+
+ # FULL backup with OLD binary
+ self.backup_node(backup_dir, 'node', node, old_binary=True)
+
+ pgbench = node.pgbench(
+ stdout=subprocess.PIPE,
+ stderr=subprocess.STDOUT,
+ options=["-c", "1", "-T", "10", "--no-vacuum"])
+ pgbench.wait()
+ pgbench.stdout.close()
+
+ # PAGE1 backup with OLD binary
+ self.backup_node(
+ backup_dir, 'node', node, backup_type='page', old_binary=True)
+
+ node.safe_psql(
+ 'postgres',
+ 'DELETE from pgbench_accounts')
+
+ node.safe_psql(
+ 'postgres',
+ 'VACUUM pgbench_accounts')
+
+ # PAGE2 backup with OLD binary
+ backup_id = self.backup_node(
+ backup_dir, 'node', node, backup_type='page', old_binary=True)
+
+ pgdata = self.pgdata_content(node.data_dir)
+
+ # merge chain created by old binary with new binary
+ output = self.merge_backup(backup_dir, "node", backup_id)
+
+ # check that in-place is disabled
+ self.assertNotIn(
+ "WARNING: In-place merge is disabled "
+ "because of storage format incompatibility", output)
+
+ # restore merged backup
+ node_restored = self.make_simple_node(
+ base_dir=os.path.join(module_name, fname, 'node_restored'))
+ node_restored.cleanup()
+
+ self.restore_node(backup_dir, 'node', node_restored)
+
+ pgdata_restored = self.pgdata_content(node_restored.data_dir)
+ self.compare_pgdata(pgdata, pgdata_restored)
+
+ # Clean after yourself
+ self.del_test_dir(module_name, fname)
+
# @unittest.skip("skip")
def test_page_vacuum_truncate(self):
"""
diff --git a/tests/delta.py b/tests/delta.py
index 47e6278c..daa423d4 100644
--- a/tests/delta.py
+++ b/tests/delta.py
@@ -433,11 +433,11 @@ class DeltaTest(ProbackupTest, unittest.TestCase):
node.safe_psql("postgres", "checkpoint")
# GET LOGICAL CONTENT FROM NODE
- result = node.safe_psql("postgres", "select * from pgbench_accounts")
+ result = node.safe_psql("postgres", "select count(*) from pgbench_accounts")
# delta BACKUP
self.backup_node(
- backup_dir, 'node', node, backup_type='delta',
- options=['--stream'])
+ backup_dir, 'node', node,
+ backup_type='delta', options=['--stream'])
# GET PHYSICAL CONTENT FROM NODE
pgdata = self.pgdata_content(node.data_dir)
@@ -450,8 +450,10 @@ 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)])
+ backup_dir, 'node', restored_node,
+ options=[
+ "-j", "4", "-T", "{0}={1}".format(
+ tblspc_path, tblspc_path_new)])
# GET PHYSICAL CONTENT FROM NODE_RESTORED
pgdata_restored = self.pgdata_content(restored_node.data_dir)
@@ -461,7 +463,8 @@ class DeltaTest(ProbackupTest, unittest.TestCase):
restored_node.slow_start()
result_new = restored_node.safe_psql(
- "postgres", "select * from pgbench_accounts")
+ "postgres",
+ "select count(*) from pgbench_accounts")
# COMPARE RESTORED FILES
self.assertEqual(result, result_new, 'data is lost')
diff --git a/tests/expected/option_version.out b/tests/expected/option_version.out
index 342842b2..55d7c7f0 100644
--- a/tests/expected/option_version.out
+++ b/tests/expected/option_version.out
@@ -1 +1,5 @@
-pg_probackup 2.5.0
\ No newline at end of file
+<<<<<<< HEAD
+pg_probackup 2.5.0
+=======
+pg_probackup 2.4.8
+>>>>>>> master
diff --git a/tests/helpers/ptrack_helpers.py b/tests/helpers/ptrack_helpers.py
index 5ba43e92..d340213c 100644
--- a/tests/helpers/ptrack_helpers.py
+++ b/tests/helpers/ptrack_helpers.py
@@ -246,18 +246,6 @@ class ProbackupTest(object):
print('pg_probackup binary is not found')
exit(1)
- self.probackup_version = None
-
- try:
- self.probackup_version_output = subprocess.check_output(
- [self.probackup_path, "--version"],
- stderr=subprocess.STDOUT,
- ).decode('utf-8')
- except subprocess.CalledProcessError as e:
- raise ProbackupException(e.output.decode('utf-8'))
-
- self.probackup_version = re.search(r"\d+\.\d+\.\d+", self.probackup_version_output).group(0)
-
if os.name == 'posix':
self.EXTERNAL_DIRECTORY_DELIMITER = ':'
os.environ['PATH'] = os.path.dirname(
@@ -280,6 +268,32 @@ class ProbackupTest(object):
if self.verbose:
print('PGPROBACKUPBIN_OLD is not an executable file')
+ self.probackup_version = None
+ self.old_probackup_version = None
+
+ try:
+ self.probackup_version_output = subprocess.check_output(
+ [self.probackup_path, "--version"],
+ stderr=subprocess.STDOUT,
+ ).decode('utf-8')
+ except subprocess.CalledProcessError as e:
+ raise ProbackupException(e.output.decode('utf-8'))
+
+ if self.probackup_old_path:
+ old_probackup_version_output = subprocess.check_output(
+ [self.probackup_old_path, "--version"],
+ stderr=subprocess.STDOUT,
+ ).decode('utf-8')
+ self.old_probackup_version = re.search(
+ r"\d+\.\d+\.\d+",
+ subprocess.check_output(
+ [self.probackup_old_path, "--version"],
+ stderr=subprocess.STDOUT,
+ ).decode('utf-8')
+ ).group(0)
+
+ self.probackup_version = re.search(r"\d+\.\d+\.\d+", self.probackup_version_output).group(0)
+
self.remote = False
self.remote_host = None
self.remote_port = None
@@ -693,7 +707,7 @@ class ProbackupTest(object):
)
)
- def run_pb(self, command, asynchronous=False, gdb=False, old_binary=False, return_id=True):
+ def run_pb(self, command, asynchronous=False, gdb=False, old_binary=False, return_id=True, env=None):
if not self.probackup_old_path and old_binary:
print('PGPROBACKUPBIN_OLD is not set')
exit(1)
@@ -703,6 +717,9 @@ class ProbackupTest(object):
else:
binary_path = self.probackup_path
+ if not env:
+ env=self.test_env
+
try:
self.cmd = [' '.join(map(str, [binary_path] + command))]
if self.verbose:
@@ -714,13 +731,13 @@ class ProbackupTest(object):
self.cmd,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
- env=self.test_env
+ env=env
)
else:
self.output = subprocess.check_output(
[binary_path] + command,
stderr=subprocess.STDOUT,
- env=self.test_env
+ env=env
).decode('utf-8')
if command[0] == 'backup' and return_id:
# return backup ID
@@ -831,7 +848,8 @@ class ProbackupTest(object):
self, backup_dir, instance, node, data_dir=False,
backup_type='full', datname=False, options=[],
asynchronous=False, gdb=False,
- old_binary=False, return_id=True, no_remote=False
+ old_binary=False, return_id=True, no_remote=False,
+ env=None
):
if not node and not data_dir:
print('You must provide ether node or data_dir for backup')
@@ -864,7 +882,7 @@ class ProbackupTest(object):
if not old_binary:
cmd_list += ['--no-sync']
- return self.run_pb(cmd_list + options, asynchronous, gdb, old_binary, return_id)
+ return self.run_pb(cmd_list + options, asynchronous, gdb, old_binary, return_id, env=env)
def checkdb_node(
self, backup_dir=False, instance=False, data_dir=False,
@@ -928,7 +946,8 @@ class ProbackupTest(object):
def show_pb(
self, backup_dir, instance=None, backup_id=None,
- options=[], as_text=False, as_json=True, old_binary=False
+ options=[], as_text=False, as_json=True, old_binary=False,
+ env=None
):
backup_list = []
@@ -949,7 +968,7 @@ class ProbackupTest(object):
if as_text:
# You should print it when calling as_text=true
- return self.run_pb(cmd_list + options, old_binary=old_binary)
+ return self.run_pb(cmd_list + options, old_binary=old_binary, env=env)
# get show result as list of lines
if as_json:
@@ -974,7 +993,7 @@ class ProbackupTest(object):
return backup_list
else:
show_splitted = self.run_pb(
- cmd_list + options, old_binary=old_binary).splitlines()
+ cmd_list + options, old_binary=old_binary, env=env).splitlines()
if instance is not None and backup_id is None:
# cut header(ID, Mode, etc) from show as single string
header = show_splitted[1:2][0]
@@ -1102,7 +1121,7 @@ class ProbackupTest(object):
def delete_pb(
self, backup_dir, instance,
- backup_id=None, options=[], old_binary=False):
+ backup_id=None, options=[], old_binary=False, gdb=False):
cmd_list = [
'delete',
'-B', backup_dir
@@ -1112,7 +1131,7 @@ class ProbackupTest(object):
if backup_id:
cmd_list += ['-i', backup_id]
- return self.run_pb(cmd_list + options, old_binary=old_binary)
+ return self.run_pb(cmd_list + options, old_binary=old_binary, gdb=gdb)
def delete_expired(
self, backup_dir, instance, options=[], old_binary=False):
@@ -1142,8 +1161,9 @@ class ProbackupTest(object):
out_dict = {}
if self.get_version(node) >= self.version_to_num('12.0'):
- recovery_conf_path = os.path.join(
- node.data_dir, 'probackup_recovery.conf')
+ recovery_conf_path = os.path.join(node.data_dir, 'postgresql.auto.conf')
+ with open(recovery_conf_path, 'r') as f:
+ print(f.read())
else:
recovery_conf_path = os.path.join(node.data_dir, 'recovery.conf')
@@ -1311,10 +1331,6 @@ class ProbackupTest(object):
f.close()
config = 'postgresql.auto.conf'
- probackup_recovery_path = os.path.join(replica.data_dir, 'probackup_recovery.conf')
- if os.path.exists(probackup_recovery_path):
- if os.stat(probackup_recovery_path).st_size > 0:
- config = 'probackup_recovery.conf'
if not log_shipping:
self.set_auto_conf(
@@ -1505,7 +1521,8 @@ class ProbackupTest(object):
'backup_label', 'tablespace_map', 'recovery.conf',
'ptrack_control', 'ptrack_init', 'pg_control',
'probackup_recovery.conf', 'recovery.signal',
- 'standby.signal', 'ptrack.map', 'ptrack.map.mmap'
+ 'standby.signal', 'ptrack.map', 'ptrack.map.mmap',
+ 'ptrack.map.tmp'
]
if exclude_dirs:
@@ -1531,6 +1548,7 @@ class ProbackupTest(object):
directory_dict['files'][file_relpath] = {'is_datafile': False}
with open(file_fullpath, 'rb') as f:
directory_dict['files'][file_relpath]['md5'] = hashlib.md5(f.read()).hexdigest()
+ f.close()
# directory_dict['files'][file_relpath]['md5'] = hashlib.md5(
# f = open(file_fullpath, 'rb').read()).hexdigest()
diff --git a/tests/incr_restore.py b/tests/incr_restore.py
index 6fb0edbc..c71b952c 100644
--- a/tests/incr_restore.py
+++ b/tests/incr_restore.py
@@ -142,6 +142,7 @@ class IncrRestoreTest(ProbackupTest, unittest.TestCase):
fname = self.id().split('.')[3]
node = self.make_simple_node(
base_dir=os.path.join(module_name, fname, 'node'),
+ set_replication=True,
initdb_params=['--data-checksums'])
backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
@@ -184,6 +185,7 @@ class IncrRestoreTest(ProbackupTest, unittest.TestCase):
node = self.make_simple_node(
base_dir=os.path.join(module_name, fname, 'node'),
initdb_params=['--data-checksums'],
+ set_replication=True,
pg_options={'autovacuum': 'off'})
backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
@@ -245,6 +247,7 @@ class IncrRestoreTest(ProbackupTest, unittest.TestCase):
node = self.make_simple_node(
base_dir=os.path.join(module_name, fname, 'node'),
initdb_params=['--data-checksums'],
+ set_replication=True,
pg_options={'autovacuum': 'off'})
backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
@@ -1320,7 +1323,7 @@ class IncrRestoreTest(ProbackupTest, unittest.TestCase):
self.assertEqual(
node.safe_psql(
'postgres',
- 'select count(*) from t1').rstrip(),
+ 'select count(*) from t1').decode('utf-8').rstrip(),
'1')
# Clean after yourself
@@ -1488,7 +1491,7 @@ class IncrRestoreTest(ProbackupTest, unittest.TestCase):
self.assertEqual(
node.safe_psql(
'postgres',
- 'select count(*) from t1').rstrip(),
+ 'select count(*) from t1').decode('utf-8').rstrip(),
'1')
# Clean after yourself
diff --git a/tests/locking.py b/tests/locking.py
index 2da2415e..92c779c8 100644
--- a/tests/locking.py
+++ b/tests/locking.py
@@ -50,7 +50,7 @@ class LockingTest(ProbackupTest, unittest.TestCase):
backup_id = self.show_pb(backup_dir, 'node')[1]['id']
self.assertIn(
- "is using backup {0} and still is running".format(backup_id),
+ "is using backup {0}, and is still running".format(backup_id),
validate_output,
'\n Unexpected Validate Output: {0}\n'.format(repr(validate_output)))
@@ -61,7 +61,8 @@ class LockingTest(ProbackupTest, unittest.TestCase):
'RUNNING', self.show_pb(backup_dir, 'node')[1]['status'])
# Clean after yourself
- # self.del_test_dir(module_name, fname)
+ gdb.kill()
+ self.del_test_dir(module_name, fname)
def test_locking_running_validate_2(self):
"""
@@ -129,6 +130,7 @@ class LockingTest(ProbackupTest, unittest.TestCase):
'ERROR', self.show_pb(backup_dir, 'node')[1]['status'])
# Clean after yourself
+ gdb.kill()
self.del_test_dir(module_name, fname)
def test_locking_running_validate_2_specific_id(self):
@@ -227,6 +229,7 @@ class LockingTest(ProbackupTest, unittest.TestCase):
repr(e.message), self.cmd))
# Clean after yourself
+ gdb.kill()
self.del_test_dir(module_name, fname)
def test_locking_running_3(self):
@@ -296,6 +299,7 @@ class LockingTest(ProbackupTest, unittest.TestCase):
'ERROR', self.show_pb(backup_dir, 'node')[1]['status'])
# Clean after yourself
+ gdb.kill()
self.del_test_dir(module_name, fname)
def test_locking_restore_locked(self):
@@ -303,8 +307,8 @@ class LockingTest(ProbackupTest, unittest.TestCase):
make node, take full backup, take two page backups,
launch validate on PAGE1 and stop it in the middle,
launch restore of PAGE2.
- Expect restore to fail because validation of
- intermediate backup is impossible
+ Expect restore to sucseed because read-only locks
+ do not conflict
"""
fname = self.id().split('.')[3]
node = self.make_simple_node(
@@ -334,24 +338,13 @@ class LockingTest(ProbackupTest, unittest.TestCase):
node.cleanup()
- try:
- self.restore_node(backup_dir, 'node', node)
- self.assertEqual(
- 1, 0,
- "Expecting Error because restore without whole chain validation "
- "is prohibited unless --no-validate provided.\n "
- "Output: {0} \n CMD: {1}".format(
- repr(self.output), self.cmd))
- except ProbackupException as e:
- self.assertTrue(
- "ERROR: Cannot lock backup {0} directory\n".format(full_id) in e.message,
- '\n Unexpected Error Message: {0}\n CMD: {1}'.format(
- repr(e.message), self.cmd))
+ self.restore_node(backup_dir, 'node', node)
# Clean after yourself
+ gdb.kill()
self.del_test_dir(module_name, fname)
- def test_locking_restore_locked_without_validation(self):
+ def test_concurrent_delete_and_restore(self):
"""
make node, take full backup, take page backup,
launch validate on FULL and stop it in the middle,
@@ -376,10 +369,11 @@ class LockingTest(ProbackupTest, unittest.TestCase):
# PAGE1
restore_id = self.backup_node(backup_dir, 'node', node, backup_type='page')
- gdb = self.validate_pb(
+ gdb = self.delete_pb(
backup_dir, 'node', backup_id=backup_id, gdb=True)
- gdb.set_breakpoint('pgBackupValidate')
+ # gdb.set_breakpoint('pgFileDelete')
+ gdb.set_breakpoint('delete_backup_files')
gdb.run_until_break()
node.cleanup()
@@ -397,13 +391,14 @@ class LockingTest(ProbackupTest, unittest.TestCase):
self.assertTrue(
"Backup {0} is used without validation".format(
restore_id) in e.message and
- 'is using backup {0} and still is running'.format(
+ 'is using backup {0}, and is still running'.format(
backup_id) in e.message and
'ERROR: Cannot lock backup' in e.message,
'\n Unexpected Error Message: {0}\n CMD: {1}'.format(
repr(e.message), self.cmd))
# Clean after yourself
+ gdb.kill()
self.del_test_dir(module_name, fname)
def test_locking_concurrent_validate_and_backup(self):
@@ -439,6 +434,7 @@ class LockingTest(ProbackupTest, unittest.TestCase):
self.backup_node(backup_dir, 'node', node, backup_type='page')
# Clean after yourself
+ gdb.kill()
self.del_test_dir(module_name, fname)
def test_locking_concurren_restore_and_delete(self):
@@ -467,7 +463,6 @@ class LockingTest(ProbackupTest, unittest.TestCase):
gdb.set_breakpoint('create_data_directories')
gdb.run_until_break()
- # This PAGE backup is expected to be successfull
try:
self.delete_pb(backup_dir, 'node', full_id)
self.assertEqual(
@@ -483,6 +478,7 @@ class LockingTest(ProbackupTest, unittest.TestCase):
repr(e.message), self.cmd))
# Clean after yourself
+ gdb.kill()
self.del_test_dir(module_name, fname)
def test_backup_directory_name(self):
@@ -538,3 +534,7 @@ class LockingTest(ProbackupTest, unittest.TestCase):
# Clean after yourself
self.del_test_dir(module_name, fname)
+
+# TODO:
+# test that concurrent validation and restore are not locking each other
+# check that quick exclusive lock, when taking RO-lock, is really quick
diff --git a/tests/merge.py b/tests/merge.py
index d7dbae20..44652066 100644
--- a/tests/merge.py
+++ b/tests/merge.py
@@ -1475,7 +1475,7 @@ class MergeTest(ProbackupTest, unittest.TestCase):
self.del_test_dir(module_name, fname)
- @unittest.skip("skip")
+ # @unittest.skip("skip")
def test_crash_after_opening_backup_control_2(self):
"""
check that crashing after opening backup_content.control
@@ -1529,8 +1529,8 @@ class MergeTest(ProbackupTest, unittest.TestCase):
gdb.set_breakpoint('write_backup_filelist')
gdb.run_until_break()
- gdb.set_breakpoint('sprintf')
- gdb.continue_execution_until_break(1)
+# gdb.set_breakpoint('sprintf')
+# gdb.continue_execution_until_break(1)
gdb._execute('signal SIGKILL')
@@ -1567,7 +1567,7 @@ class MergeTest(ProbackupTest, unittest.TestCase):
self.del_test_dir(module_name, fname)
- @unittest.skip("skip")
+ # @unittest.skip("skip")
def test_losing_file_after_failed_merge(self):
"""
check that crashing after opening backup_content.control
@@ -1622,8 +1622,8 @@ class MergeTest(ProbackupTest, unittest.TestCase):
gdb.set_breakpoint('write_backup_filelist')
gdb.run_until_break()
- gdb.set_breakpoint('sprintf')
- gdb.continue_execution_until_break(20)
+# gdb.set_breakpoint('sprintf')
+# gdb.continue_execution_until_break(20)
gdb._execute('signal SIGKILL')
diff --git a/tests/page.py b/tests/page.py
index 201f825e..323c0a6d 100644
--- a/tests/page.py
+++ b/tests/page.py
@@ -393,7 +393,7 @@ class PageTest(ProbackupTest, unittest.TestCase):
pgbench.wait()
# GET LOGICAL CONTENT FROM NODE
- result = node.safe_psql("postgres", "select * from pgbench_accounts")
+ result = node.safe_psql("postgres", "select count(*) from pgbench_accounts")
# PAGE BACKUP
self.backup_node(backup_dir, 'node', node, backup_type='page')
@@ -422,7 +422,7 @@ class PageTest(ProbackupTest, unittest.TestCase):
restored_node.slow_start()
result_new = restored_node.safe_psql(
- "postgres", "select * from pgbench_accounts")
+ "postgres", "select count(*) from pgbench_accounts")
# COMPARE RESTORED FILES
self.assertEqual(result, result_new, 'data is lost')
diff --git a/tests/ptrack.py b/tests/ptrack.py
index 18425237..c45ecd6e 100644
--- a/tests/ptrack.py
+++ b/tests/ptrack.py
@@ -3031,7 +3031,7 @@ class PtrackTest(ProbackupTest, unittest.TestCase):
pg_options={
'max_wal_size': '32MB',
'archive_timeout': '10s',
- 'checkpoint_timeout': '30s',
+ 'checkpoint_timeout': '5min',
'autovacuum': 'off'})
backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
@@ -3108,6 +3108,15 @@ class PtrackTest(ProbackupTest, unittest.TestCase):
# Sync master and replica
self.wait_until_replica_catch_with_master(master, replica)
+ if replica.major_version < 10:
+ replica.safe_psql(
+ "postgres",
+ "select pg_xlog_replay_pause()")
+ else:
+ replica.safe_psql(
+ "postgres",
+ "select pg_wal_replay_pause()")
+
self.backup_node(
backup_dir, 'replica', replica, backup_type='ptrack',
options=[
@@ -3130,8 +3139,16 @@ class PtrackTest(ProbackupTest, unittest.TestCase):
if self.paranoia:
self.compare_pgdata(pgdata, pgdata_restored)
+ self.set_auto_conf(node, {'port': node.port})
+
+ node.slow_start()
+
+ node.safe_psql(
+ 'postgres',
+ 'select 1')
+
# Clean after yourself
- self.del_test_dir(module_name, fname, [master, replica])
+ self.del_test_dir(module_name, fname, [master, replica, node])
# @unittest.skip("skip")
# @unittest.expectedFailure
diff --git a/tests/replica.py b/tests/replica.py
index 9a75a7aa..f664ca88 100644
--- a/tests/replica.py
+++ b/tests/replica.py
@@ -1073,6 +1073,87 @@ class ReplicaTest(ProbackupTest, unittest.TestCase):
# Clean after yourself
self.del_test_dir(module_name, fname)
+ # @unittest.skip("skip")
+ def test_start_stop_lsn_in_the_same_segno(self):
+ """
+ """
+ fname = self.id().split('.')[3]
+ backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
+ master = self.make_simple_node(
+ base_dir=os.path.join(module_name, fname, 'master'),
+ set_replication=True,
+ initdb_params=['--data-checksums'],
+ pg_options={
+ 'autovacuum': 'off',
+ 'checkpoint_timeout': '1h',
+ 'wal_level': 'replica',
+ 'shared_buffers': '128MB'})
+
+ if self.get_version(master) < self.version_to_num('9.6.0'):
+ self.del_test_dir(module_name, fname)
+ return unittest.skip(
+ 'Skipped because backup from replica is not supported in PG 9.5')
+
+ self.init_pb(backup_dir)
+ self.add_instance(backup_dir, 'master', master)
+ master.slow_start()
+
+ # freeze bgwriter to get rid of RUNNING XACTS records
+ bgwriter_pid = master.auxiliary_pids[ProcessType.BackgroundWriter][0]
+ gdb_checkpointer = self.gdb_attach(bgwriter_pid)
+
+ self.backup_node(backup_dir, 'master', master, options=['--stream'])
+
+ # Create replica
+ replica = self.make_simple_node(
+ base_dir=os.path.join(module_name, fname, 'replica'))
+ replica.cleanup()
+ self.restore_node(backup_dir, 'master', replica)
+
+ # Settings for Replica
+ self.add_instance(backup_dir, 'replica', replica)
+ self.set_replica(master, replica, synchronous=True)
+
+ replica.slow_start(replica=True)
+
+ self.switch_wal_segment(master)
+ self.switch_wal_segment(master)
+
+ master.safe_psql(
+ 'postgres',
+ 'CREATE TABLE t1 AS '
+ 'SELECT i, repeat(md5(i::text),5006056) AS fat_attr '
+ 'FROM generate_series(0,10) i')
+
+ master.safe_psql(
+ 'postgres',
+ 'CHECKPOINT')
+
+ self.wait_until_replica_catch_with_master(master, replica)
+
+ sleep(60)
+
+ self.backup_node(
+ backup_dir, 'replica', replica,
+ options=[
+ '--archive-timeout=30',
+ '--log-level-console=LOG',
+ '--no-validate',
+ '--stream'],
+ return_id=False)
+
+ self.backup_node(
+ backup_dir, 'replica', replica,
+ options=[
+ '--archive-timeout=30',
+ '--log-level-console=LOG',
+ '--no-validate',
+ '--stream'],
+ return_id=False)
+
+ # Clean after yourself
+ self.del_test_dir(module_name, fname)
+
@unittest.skip("skip")
def test_replica_promote_1(self):
"""
@@ -1647,6 +1728,80 @@ class ReplicaTest(ProbackupTest, unittest.TestCase):
# Clean after yourself
self.del_test_dir(module_name, fname)
+ # @unittest.skip("skip")
+ def test_replica_via_basebackup(self):
+ """
+ """
+ fname = self.id().split('.')[3]
+ backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
+ node = self.make_simple_node(
+ base_dir=os.path.join(module_name, fname, 'node'),
+ set_replication=True,
+ initdb_params=['--data-checksums'],
+ pg_options={'autovacuum': 'off', 'hot_standby': 'on'})
+
+ self.init_pb(backup_dir)
+ self.add_instance(backup_dir, 'node', node)
+ self.set_archiving(backup_dir, 'node', node)
+
+ node.slow_start()
+
+ node.pgbench_init(scale=10)
+
+ #FULL backup
+ full_id = self.backup_node(backup_dir, 'node', node)
+
+ pgbench = node.pgbench(
+ options=['-T', '10', '-c', '1', '--no-vacuum'])
+ pgbench.wait()
+
+ node.cleanup()
+
+ self.restore_node(
+ backup_dir, 'node', node,
+ options=['--recovery-target=latest', '--recovery-target-action=promote'])
+ node.slow_start()
+
+ # Timeline 2
+ # Take stream page backup from instance in timeline2
+ self.backup_node(
+ backup_dir, 'node', node, backup_type='full',
+ options=['--stream', '--log-level-file=verbose'])
+
+ node.cleanup()
+
+ # restore stream backup
+ self.restore_node(backup_dir, 'node', node)
+
+ xlog_dir = 'pg_wal'
+ if self.get_version(node) < 100000:
+ xlog_dir = 'pg_xlog'
+
+ filepath = os.path.join(node.data_dir, xlog_dir, "00000002.history")
+ self.assertTrue(
+ os.path.exists(filepath),
+ "History file do not exists: {0}".format(filepath))
+
+ node.slow_start()
+
+ node_restored = self.make_simple_node(
+ base_dir=os.path.join(module_name, fname, 'node_restored'))
+ node_restored.cleanup()
+
+ pg_basebackup_path = self.get_bin_path('pg_basebackup')
+
+ self.run_binary(
+ [
+ pg_basebackup_path, '-p', str(node.port), '-h', 'localhost',
+ '-R', '-X', 'stream', '-D', node_restored.data_dir
+ ])
+
+ self.set_auto_conf(node_restored, {'port': node_restored.port})
+ node_restored.slow_start(replica=True)
+
+ # Clean after yourself
+ self.del_test_dir(module_name, fname)
+
# TODO:
# null offset STOP LSN and latest record in previous segment is conrecord (manual only)
# archiving from promoted delayed replica
diff --git a/tests/restore.py b/tests/restore.py
index 2a5fac6a..35fc6dce 100644
--- a/tests/restore.py
+++ b/tests/restore.py
@@ -51,8 +51,11 @@ class RestoreTest(ProbackupTest, unittest.TestCase):
repr(self.output), self.cmd))
# 2 - Test that recovery.conf was created
+ # TODO update test
if self.get_version(node) >= self.version_to_num('12.0'):
- recovery_conf = os.path.join(node.data_dir, 'probackup_recovery.conf')
+ recovery_conf = os.path.join(node.data_dir, 'postgresql.auto.conf')
+ with open(recovery_conf, 'r') as f:
+ print(f.read())
else:
recovery_conf = os.path.join(node.data_dir, 'recovery.conf')
self.assertEqual(os.path.isfile(recovery_conf), True)
@@ -1807,8 +1810,11 @@ class RestoreTest(ProbackupTest, unittest.TestCase):
pgdata = self.pgdata_content(node.data_dir)
+ # TODO update test
if self.get_version(node) >= self.version_to_num('12.0'):
- recovery_conf = os.path.join(node.data_dir, 'probackup_recovery.conf')
+ recovery_conf = os.path.join(node.data_dir, 'postgresql.auto.conf')
+ with open(recovery_conf, 'r') as f:
+ print(f.read())
else:
recovery_conf = os.path.join(node.data_dir, 'recovery.conf')
@@ -1862,8 +1868,11 @@ class RestoreTest(ProbackupTest, unittest.TestCase):
pgdata = self.pgdata_content(node.data_dir)
+ # TODO update test
if self.get_version(node) >= self.version_to_num('12.0'):
- recovery_conf = os.path.join(node.data_dir, 'probackup_recovery.conf')
+ recovery_conf = os.path.join(node.data_dir, 'postgresql.auto.conf')
+ with open(recovery_conf, 'r') as f:
+ print(f.read())
else:
recovery_conf = os.path.join(node.data_dir, 'recovery.conf')
@@ -1912,7 +1921,7 @@ class RestoreTest(ProbackupTest, unittest.TestCase):
self.backup_node(backup_dir, 'node', node)
if self.get_version(node) >= self.version_to_num('12.0'):
- recovery_conf = os.path.join(node.data_dir, 'probackup_recovery.conf')
+ recovery_conf = os.path.join(node.data_dir, 'postgresql.auto.conf')
else:
recovery_conf = os.path.join(node.data_dir, 'recovery.conf')
@@ -1924,23 +1933,35 @@ class RestoreTest(ProbackupTest, unittest.TestCase):
# open(recovery_conf, 'rb').read()).hexdigest()
with open(recovery_conf, 'r') as f:
- content_1 = f.read()
+ content_1 = ''
+ while True:
+ line = f.readline()
+
+ if not line:
+ break
+ if line.startswith("#"):
+ continue
+ content_1 += line
- # restore
node.cleanup()
-
self.restore_node(backup_dir, 'node', node, options=['--recovery-target=latest'])
# hash_2 = hashlib.md5(
# open(recovery_conf, 'rb').read()).hexdigest()
with open(recovery_conf, 'r') as f:
- content_2 = f.read()
+ content_2 = ''
+ while True:
+ line = f.readline()
+
+ if not line:
+ break
+ if line.startswith("#"):
+ continue
+ content_2 += line
self.assertEqual(content_1, content_2)
- # self.assertEqual(hash_1, hash_2)
-
# Clean after yourself
self.del_test_dir(module_name, fname)
@@ -1965,8 +1986,11 @@ class RestoreTest(ProbackupTest, unittest.TestCase):
# Take FULL
self.backup_node(backup_dir, 'node', node)
+ # TODO update test
if self.get_version(node) >= self.version_to_num('12.0'):
- recovery_conf = os.path.join(node.data_dir, 'probackup_recovery.conf')
+ recovery_conf = os.path.join(node.data_dir, 'postgresql.auto.conf')
+ with open(recovery_conf, 'r') as f:
+ print(f.read())
else:
recovery_conf = os.path.join(node.data_dir, 'recovery.conf')
@@ -3093,6 +3117,7 @@ class RestoreTest(ProbackupTest, unittest.TestCase):
"GRANT CONNECT ON DATABASE backupdb to backup; "
"GRANT USAGE ON SCHEMA pg_catalog TO backup; "
"GRANT SELECT ON TABLE pg_catalog.pg_proc TO backup; "
+ "GRANT SELECT ON TABLE pg_catalog.pg_extension TO backup; "
"GRANT SELECT ON TABLE pg_catalog.pg_database TO backup; " # for partial restore, checkdb and ptrack
"GRANT EXECUTE ON FUNCTION pg_catalog.nameeq(name, name) TO backup; "
"GRANT EXECUTE ON FUNCTION pg_catalog.textout(text) TO backup; "
@@ -3263,8 +3288,11 @@ class RestoreTest(ProbackupTest, unittest.TestCase):
self.set_archiving(backup_dir, 'node', node)
node.slow_start()
+ # TODO update test
if self.get_version(node) >= self.version_to_num('12.0'):
- recovery_conf = os.path.join(node.data_dir, 'probackup_recovery.conf')
+ recovery_conf = os.path.join(node.data_dir, 'postgresql.auto.conf')
+ with open(recovery_conf, 'r') as f:
+ print(f.read())
else:
recovery_conf = os.path.join(node.data_dir, 'recovery.conf')
@@ -3351,8 +3379,11 @@ class RestoreTest(ProbackupTest, unittest.TestCase):
os.path.isfile(standby_signal),
"File '{0}' do not exists".format(standby_signal))
+ # TODO update test
if self.get_version(node) >= self.version_to_num('12.0'):
- recovery_conf = os.path.join(replica.data_dir, 'probackup_recovery.conf')
+ recovery_conf = os.path.join(replica.data_dir, 'postgresql.auto.conf')
+ with open(recovery_conf, 'r') as f:
+ print(f.read())
else:
recovery_conf = os.path.join(replica.data_dir, 'recovery.conf')
@@ -3461,7 +3492,7 @@ class RestoreTest(ProbackupTest, unittest.TestCase):
self.set_auto_conf(
node_restored_1,
- {'port': node_restored_1.port, 'hot_standby': 'on'})
+ {'port': node_restored_1.port, 'hot_standby': 'off'})
node_restored_1.slow_start()
@@ -3478,3 +3509,228 @@ class RestoreTest(ProbackupTest, unittest.TestCase):
# Clean after yourself
self.del_test_dir(module_name, fname)
+
+ def test_pg_12_probackup_recovery_conf_compatibility(self):
+ """
+ https://github.com/postgrespro/pg_probackup/issues/249
+
+ pg_probackup version must be 12 or greater
+ """
+
+ if self.old_probackup_version:
+ if self.version_to_num(self.old_probackup_version) >= self.version_to_num('2.4.5'):
+ return unittest.skip('You need pg_probackup < 2.4.5 for this test')
+
+ if self.pg_config_version < self.version_to_num('12.0'):
+ return unittest.skip('You need PostgreSQL >= 12 for this test')
+
+ fname = self.id().split('.')[3]
+ backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
+ node = self.make_simple_node(
+ base_dir=os.path.join(module_name, fname, 'node'),
+ initdb_params=['--data-checksums'],
+ pg_options={'autovacuum': 'off'})
+
+ self.init_pb(backup_dir)
+ self.add_instance(backup_dir, 'node', node)
+ self.set_archiving(backup_dir, 'node', node)
+ node.slow_start()
+
+ # FULL backup
+ self.backup_node(backup_dir, 'node', node, old_binary=True)
+
+ node.pgbench_init(scale=5)
+
+ node.safe_psql(
+ 'postgres',
+ 'CREATE TABLE t1 as SELECT * from pgbench_accounts where aid > 200000 and aid < 450000')
+
+ time = node.safe_psql(
+ 'SELECT current_timestamp(0)::timestamptz;').decode('utf-8').rstrip()
+
+ node.safe_psql(
+ 'postgres',
+ 'DELETE from pgbench_accounts where aid > 200000 and aid < 450000')
+
+ node.cleanup()
+
+ self.restore_node(
+ backup_dir, 'node',node,
+ options=[
+ "--recovery-target-time={0}".format(time),
+ "--recovery-target-action=promote"],
+ old_binary=True)
+
+ node.slow_start()
+
+ self.backup_node(backup_dir, 'node', node, old_binary=True)
+
+ node.pgbench_init(scale=5)
+
+ xid = node.safe_psql(
+ 'SELECT txid_current()').decode('utf-8').rstrip()
+ node.pgbench_init(scale=1)
+
+ node.cleanup()
+
+ self.restore_node(
+ backup_dir, 'node',node,
+ options=[
+ "--recovery-target-xid={0}".format(xid),
+ "--recovery-target-action=promote"])
+
+ node.slow_start()
+
+ # Clean after yourself
+ self.del_test_dir(module_name, fname)
+
+ def test_drop_postgresql_auto_conf(self):
+ """
+ https://github.com/postgrespro/pg_probackup/issues/249
+
+ pg_probackup version must be 12 or greater
+ """
+
+ if self.pg_config_version < self.version_to_num('12.0'):
+ return unittest.skip('You need PostgreSQL >= 12 for this test')
+
+ fname = self.id().split('.')[3]
+ backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
+ node = self.make_simple_node(
+ base_dir=os.path.join(module_name, fname, 'node'),
+ initdb_params=['--data-checksums'],
+ pg_options={'autovacuum': 'off'})
+
+ self.init_pb(backup_dir)
+ self.add_instance(backup_dir, 'node', node)
+ self.set_archiving(backup_dir, 'node', node)
+ node.slow_start()
+
+ # FULL backup
+ self.backup_node(backup_dir, 'node', node)
+
+ # drop postgresql.auto.conf
+ auto_path = os.path.join(node.data_dir, "postgresql.auto.conf")
+ os.remove(auto_path)
+
+ self.backup_node(backup_dir, 'node', node, backup_type='page')
+
+ node.cleanup()
+
+ self.restore_node(
+ backup_dir, 'node',node,
+ options=[
+ "--recovery-target=latest",
+ "--recovery-target-action=promote"])
+
+ node.slow_start()
+
+ self.assertTrue(os.path.exists(auto_path))
+
+ # Clean after yourself
+ self.del_test_dir(module_name, fname)
+
+ def test_truncate_postgresql_auto_conf(self):
+ """
+ https://github.com/postgrespro/pg_probackup/issues/249
+
+ pg_probackup version must be 12 or greater
+ """
+
+ if self.pg_config_version < self.version_to_num('12.0'):
+ return unittest.skip('You need PostgreSQL >= 12 for this test')
+
+ fname = self.id().split('.')[3]
+ backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
+ node = self.make_simple_node(
+ base_dir=os.path.join(module_name, fname, 'node'),
+ initdb_params=['--data-checksums'],
+ pg_options={'autovacuum': 'off'})
+
+ self.init_pb(backup_dir)
+ self.add_instance(backup_dir, 'node', node)
+ self.set_archiving(backup_dir, 'node', node)
+ node.slow_start()
+
+ # FULL backup
+ self.backup_node(backup_dir, 'node', node)
+
+ # truncate postgresql.auto.conf
+ auto_path = os.path.join(node.data_dir, "postgresql.auto.conf")
+ with open(auto_path, "w+") as f:
+ f.truncate()
+
+ self.backup_node(backup_dir, 'node', node, backup_type='page')
+
+ node.cleanup()
+
+ self.restore_node(
+ backup_dir, 'node',node,
+ options=[
+ "--recovery-target=latest",
+ "--recovery-target-action=promote"])
+ node.slow_start()
+
+ self.assertTrue(os.path.exists(auto_path))
+
+ # Clean after yourself
+ self.del_test_dir(module_name, fname)
+
+ # @unittest.skip("skip")
+ def test_concurrent_restore(self):
+ """"""
+ fname = self.id().split('.')[3]
+ backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
+ node = self.make_simple_node(
+ base_dir=os.path.join(module_name, fname, 'node'),
+ set_replication=True,
+ initdb_params=['--data-checksums'])
+
+ self.init_pb(backup_dir)
+ self.add_instance(backup_dir, 'node', node)
+ self.set_archiving(backup_dir, 'node', node)
+ node.slow_start()
+
+ node.pgbench_init(scale=1)
+
+ # FULL backup
+ self.backup_node(
+ backup_dir, 'node', node,
+ options=['--stream', '--compress'])
+
+ pgbench = node.pgbench(options=['-T', '7', '-c', '1', '--no-vacuum'])
+ pgbench.wait()
+
+ # DELTA backup
+ self.backup_node(
+ backup_dir, 'node', node, backup_type='delta',
+ options=['--stream', '--compress', '--no-validate'])
+
+ pgdata1 = self.pgdata_content(node.data_dir)
+
+ node_restored = self.make_simple_node(
+ base_dir=os.path.join(module_name, fname, 'node_restored'))
+
+ node.cleanup()
+ node_restored.cleanup()
+
+ gdb = self.restore_node(
+ backup_dir, 'node', node, options=['--no-validate'], gdb=True)
+
+ gdb.set_breakpoint('restore_data_file')
+ gdb.run_until_break()
+
+ self.restore_node(
+ backup_dir, 'node', node_restored, options=['--no-validate'])
+
+ gdb.remove_all_breakpoints()
+ gdb.continue_execution_until_exit()
+
+ pgdata2 = self.pgdata_content(node.data_dir)
+ pgdata3 = self.pgdata_content(node_restored.data_dir)
+
+ self.compare_pgdata(pgdata1, pgdata2)
+ self.compare_pgdata(pgdata2, pgdata3)
+
+ # Clean after yourself
+ self.del_test_dir(module_name, fname)
diff --git a/tests/retention.py b/tests/retention.py
index 6ab796b4..6dc8536c 100644
--- a/tests/retention.py
+++ b/tests/retention.py
@@ -41,7 +41,7 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
output_before = self.show_archive(backup_dir, 'node', tli=1)
# Purge backups
- log = self.delete_expired(
+ self.delete_expired(
backup_dir, 'node', options=['--expired', '--wal'])
self.assertEqual(len(self.show_pb(backup_dir, 'node')), 2)
@@ -142,13 +142,13 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
node.slow_start()
# take FULL BACKUP
- backup_id_1 = self.backup_node(backup_dir, 'node', node)
+ self.backup_node(backup_dir, 'node', node)
# Take second FULL BACKUP
- backup_id_2 = self.backup_node(backup_dir, 'node', node)
+ self.backup_node(backup_dir, 'node', node)
# Take third FULL BACKUP
- backup_id_3 = self.backup_node(backup_dir, 'node', node)
+ self.backup_node(backup_dir, 'node', node)
backups = os.path.join(backup_dir, 'backups', 'node')
for backup in os.listdir(backups):
@@ -189,7 +189,7 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
node.slow_start()
# take FULL BACKUPs
- backup_id_1 = self.backup_node(backup_dir, 'node', node)
+ self.backup_node(backup_dir, 'node', node)
backup_id_2 = self.backup_node(backup_dir, 'node', node)
@@ -444,8 +444,7 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
# PAGEa1 OK
# FULLb OK
# FULLa ERROR
- page_id_b2 = self.backup_node(
- backup_dir, 'node', node, backup_type='page')
+ self.backup_node(backup_dir, 'node', node, backup_type='page')
# Change PAGEa2 and FULLa status to OK
self.change_backup_status(backup_dir, 'node', page_id_a2, 'OK')
@@ -632,7 +631,7 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
node.pgbench_init(scale=5)
# Take FULL BACKUPs
- backup_id_a = self.backup_node(backup_dir, 'node', node)
+ self.backup_node(backup_dir, 'node', node)
pgbench = node.pgbench(options=['-t', '20', '-c', '1'])
pgbench.wait()
@@ -663,13 +662,13 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
# PAGEa1 ERROR
# FULLb OK
# FULLa OK
- page_id_b1 = self.backup_node(
+ self.backup_node(
backup_dir, 'node', node, backup_type='page')
pgbench = node.pgbench(options=['-t', '20', '-c', '1'])
pgbench.wait()
- page_id_b2 = self.backup_node(
+ self.backup_node(
backup_dir, 'node', node, backup_type='page')
pgbench = node.pgbench(options=['-t', '20', '-c', '1'])
@@ -711,7 +710,7 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
conf.write("recovery_time='{:%Y-%m-%d %H:%M:%S}'\n".format(
datetime.now() - timedelta(days=3)))
- output = self.delete_expired(
+ self.delete_expired(
backup_dir, 'node',
options=['--retention-window=1', '--expired', '--merge-expired'])
@@ -1305,26 +1304,26 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
node.pgbench_init(scale=3)
# Chain A
- backup_id_a = self.backup_node(backup_dir, 'node', node)
- page_id_a1 = self.backup_node(
+ self.backup_node(backup_dir, 'node', node)
+ self.backup_node(
backup_dir, 'node', node, backup_type='page')
- page_id_a2 = self.backup_node(
+ self.backup_node(
backup_dir, 'node', node, backup_type='page')
# Chain B
- backup_id_b = self.backup_node(backup_dir, 'node', node)
+ self.backup_node(backup_dir, 'node', node)
pgbench = node.pgbench(options=['-T', '10', '-c', '2'])
pgbench.wait()
- page_id_b1 = self.backup_node(
+ self.backup_node(
backup_dir, 'node', node, backup_type='delta')
pgbench = node.pgbench(options=['-T', '10', '-c', '2'])
pgbench.wait()
- page_id_b2 = self.backup_node(
+ self.backup_node(
backup_dir, 'node', node, backup_type='page')
pgbench = node.pgbench(options=['-T', '10', '-c', '2'])
@@ -1347,7 +1346,7 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
conf.write("recovery_time='{:%Y-%m-%d %H:%M:%S}'\n".format(
datetime.now() - timedelta(days=3)))
- output = self.delete_expired(
+ self.delete_expired(
backup_dir, 'node',
options=[
'--retention-window=1', '--expired',
@@ -1391,26 +1390,26 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
node.pgbench_init(scale=3)
# Chain A
- backup_id_a = self.backup_node(backup_dir, 'node', node)
- page_id_a1 = self.backup_node(
+ self.backup_node(backup_dir, 'node', node)
+ self.backup_node(
backup_dir, 'node', node, backup_type='page')
- page_id_a2 = self.backup_node(
+ self.backup_node(
backup_dir, 'node', node, backup_type='page')
# Chain B
- backup_id_b = self.backup_node(backup_dir, 'node', node)
+ self.backup_node(backup_dir, 'node', node)
- page_id_b1 = self.backup_node(
+ self.backup_node(
backup_dir, 'node', node, backup_type='delta')
- page_id_b2 = self.backup_node(
+ self.backup_node(
backup_dir, 'node', node, backup_type='page')
page_id_b3 = self.backup_node(
backup_dir, 'node', node, backup_type='delta')
- pgdata = self.pgdata_content(node.data_dir)
+ self.pgdata_content(node.data_dir)
# Purge backups
backups = os.path.join(backup_dir, 'backups', 'node')
@@ -1483,15 +1482,15 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
node.slow_start()
# Take FULL BACKUPs
- backup_id_a1 = self.backup_node(backup_dir, 'node', node)
- gdb = self.backup_node(
- backup_dir, 'node', node, backup_type='page', gdb=True)
+ self.backup_node(backup_dir, 'node', node)
+ self.backup_node(
+ backup_dir, 'node', node, backup_type='page')
- page_id_a3 = self.backup_node(
+ self.backup_node(
backup_dir, 'node', node, backup_type='page')
# Change FULLb backup status to ERROR
- self.change_backup_status(backup_dir, 'node', backup_id_b, 'ERROR')
+ # self.change_backup_status(backup_dir, 'node', backup_id_b, 'ERROR')
# Clean after yourself
self.del_test_dir(module_name, fname)
@@ -1516,7 +1515,7 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
node.slow_start()
# Take FULL BACKUP
- full_id = self.backup_node(backup_dir, 'node', node)
+ self.backup_node(backup_dir, 'node', node)
# Take PAGE BACKUP
gdb = self.backup_node(
@@ -1528,15 +1527,15 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
gdb._execute('signal SIGINT')
gdb.continue_execution_until_error()
- page_id = self.show_pb(backup_dir, 'node')[1]['id']
+ self.show_pb(backup_dir, 'node')[1]['id']
# Take DELTA backup
- delta_id = self.backup_node(
+ self.backup_node(
backup_dir, 'node', node, backup_type='delta',
options=['--retention-window=2', '--delete-expired'])
# Take FULL BACKUP
- full2_id = self.backup_node(backup_dir, 'node', node)
+ self.backup_node(backup_dir, 'node', node)
self.assertEqual(len(self.show_pb(backup_dir, 'node')), 4)
@@ -1563,7 +1562,7 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
node.slow_start()
# Take FULL BACKUP
- full_id = self.backup_node(backup_dir, 'node', node)
+ self.backup_node(backup_dir, 'node', node)
# Take PAGE BACKUP
gdb = self.backup_node(
@@ -1574,7 +1573,7 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
gdb._execute('signal SIGKILL')
gdb.continue_execution_until_error()
- page_id = self.show_pb(backup_dir, 'node')[1]['id']
+ self.show_pb(backup_dir, 'node')[1]['id']
if self.get_version(node) < 90600:
node.safe_psql(
@@ -1582,7 +1581,7 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
'SELECT pg_catalog.pg_stop_backup()')
# Take DELTA backup
- delta_id = self.backup_node(
+ self.backup_node(
backup_dir, 'node', node, backup_type='delta',
options=['--retention-window=2', '--delete-expired'])
@@ -1630,7 +1629,7 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
self.backup_node(backup_dir, 'node', node, backup_type="page")
# Purge backups
- log = self.delete_expired(
+ self.delete_expired(
backup_dir, 'node', options=['--expired', '--wal'])
self.assertEqual(len(self.show_pb(backup_dir, 'node')), 2)
@@ -1639,7 +1638,7 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
# Clean after yourself
self.del_test_dir(module_name, fname)
- def test_retention_redundancy_overlapping_chains(self):
+ def test_retention_redundancy_overlapping_chains_1(self):
""""""
fname = self.id().split('.')[3]
node = self.make_simple_node(
@@ -1678,7 +1677,7 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
self.backup_node(backup_dir, 'node', node, backup_type="page")
# Purge backups
- log = self.delete_expired(
+ self.delete_expired(
backup_dir, 'node', options=['--expired', '--wal'])
self.assertEqual(len(self.show_pb(backup_dir, 'node')), 2)
@@ -1869,7 +1868,7 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
# FULL
node.pgbench_init(scale=1)
- B1 = self.backup_node(backup_dir, 'node', node)
+ self.backup_node(backup_dir, 'node', node)
# PAGE
node.pgbench_init(scale=1)
@@ -1885,12 +1884,12 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
node.pgbench_init(scale=1)
- B3 = self.backup_node(
+ self.backup_node(
backup_dir, 'node', node, backup_type='page')
node.pgbench_init(scale=1)
- B4 = self.backup_node(
+ self.backup_node(
backup_dir, 'node', node, backup_type='page')
# Timeline 2
@@ -1940,11 +1939,11 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
node_restored.slow_start()
node_restored.pgbench_init(scale=1)
- B5 = self.backup_node(
+ self.backup_node(
backup_dir, 'node', node_restored, data_dir=node_restored.data_dir)
node.pgbench_init(scale=1)
- B6 = self.backup_node(backup_dir, 'node', node)
+ self.backup_node(backup_dir, 'node', node)
lsn = self.show_archive(backup_dir, 'node', tli=2)['switchpoint']
@@ -2007,9 +2006,9 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
node.pgbench_init(scale=5)
# B2 FULL on TLI1
- B2 = self.backup_node(backup_dir, 'node', node)
+ self.backup_node(backup_dir, 'node', node)
node.pgbench_init(scale=4)
- B3 = self.backup_node(backup_dir, 'node', node)
+ self.backup_node(backup_dir, 'node', node)
node.pgbench_init(scale=4)
self.delete_pb(backup_dir, 'node', options=['--delete-wal'])
@@ -2023,7 +2022,7 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
backup_dir, 'node', node_tli2,
options=[
'--recovery-target-xid={0}'.format(target_xid),
- '--recovery-target-timeline=1'.format(target_xid),
+ '--recovery-target-timeline=1',
'--recovery-target-action=promote'])
self.assertIn(
@@ -2039,11 +2038,11 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
"select txid_current()").decode('utf-8').rstrip()
node_tli2.pgbench_init(scale=1)
- B4 = self.backup_node(
+ self.backup_node(
backup_dir, 'node', node_tli2, data_dir=node_tli2.data_dir)
node_tli2.pgbench_init(scale=3)
- B5 = self.backup_node(
+ self.backup_node(
backup_dir, 'node', node_tli2, data_dir=node_tli2.data_dir)
node_tli2.pgbench_init(scale=1)
node_tli2.cleanup()
@@ -2086,7 +2085,7 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
node_tli4.pgbench_init(scale=5)
- B6 = self.backup_node(
+ self.backup_node(
backup_dir, 'node', node_tli4, data_dir=node_tli4.data_dir)
node_tli4.pgbench_init(scale=5)
node_tli4.cleanup()
@@ -2232,7 +2231,7 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
# B2 FULL on TLI1
B2 = self.backup_node(backup_dir, 'node', node)
node.pgbench_init(scale=4)
- B3 = self.backup_node(backup_dir, 'node', node)
+ self.backup_node(backup_dir, 'node', node)
node.pgbench_init(scale=4)
# TLI 2
@@ -2244,7 +2243,7 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
backup_dir, 'node', node_tli2,
options=[
'--recovery-target-xid={0}'.format(target_xid),
- '--recovery-target-timeline=1'.format(target_xid),
+ '--recovery-target-timeline=1',
'--recovery-target-action=promote'])
self.assertIn(
@@ -2264,7 +2263,7 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
backup_dir, 'node', node_tli2, data_dir=node_tli2.data_dir)
node_tli2.pgbench_init(scale=3)
- B5 = self.backup_node(
+ self.backup_node(
backup_dir, 'node', node_tli2, data_dir=node_tli2.data_dir)
node_tli2.pgbench_init(scale=1)
node_tli2.cleanup()
@@ -2307,7 +2306,7 @@ class RetentionTest(ProbackupTest, unittest.TestCase):
node_tli4.pgbench_init(scale=5)
- B6 = self.backup_node(
+ self.backup_node(
backup_dir, 'node', node_tli4, data_dir=node_tli4.data_dir)
node_tli4.pgbench_init(scale=5)
node_tli4.cleanup()
diff --git a/tests/set_backup.py b/tests/set_backup.py
index daba9a21..02ce007b 100644
--- a/tests/set_backup.py
+++ b/tests/set_backup.py
@@ -473,4 +473,37 @@ class SetBackupTest(ProbackupTest, unittest.TestCase):
self.assertEqual(backup_meta['note'], note)
# Clean after yourself
- self.del_test_dir(module_name, fname)
\ No newline at end of file
+ self.del_test_dir(module_name, fname)
+
+ # @unittest.skip("skip")
+ def test_add_big_note_1(self):
+ """"""
+ fname = self.id().split('.')[3]
+ node = self.make_simple_node(
+ base_dir=os.path.join(module_name, fname, 'node'),
+ set_replication=True,
+ initdb_params=['--data-checksums'])
+
+ backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
+ self.init_pb(backup_dir)
+ self.add_instance(backup_dir, 'node', node)
+ node.slow_start()
+
+ note = node.safe_psql(
+ "postgres",
+ "SELECT repeat('q', 1024)").decode('utf-8').rstrip()
+
+ # FULL
+ backup_id = self.backup_node(backup_dir, 'node', node, options=['--stream'])
+
+ self.set_backup(
+ backup_dir, 'node', backup_id,
+ options=['--note={0}'.format(note)])
+
+ backup_meta = self.show_pb(backup_dir, 'node', backup_id)
+
+ print(backup_meta)
+ self.assertEqual(backup_meta['note'], note)
+
+ # Clean after yourself
+ self.del_test_dir(module_name, fname)
diff --git a/tests/show.py b/tests/show.py
index 92ef392d..ab418792 100644
--- a/tests/show.py
+++ b/tests/show.py
@@ -6,7 +6,7 @@ from .helpers.ptrack_helpers import ProbackupTest, ProbackupException
module_name = 'show'
-class OptionTest(ProbackupTest, unittest.TestCase):
+class ShowTest(ProbackupTest, unittest.TestCase):
# @unittest.skip("skip")
# @unittest.expectedFailure
diff --git a/tests/time_stamp.py b/tests/time_stamp.py
index 8abd55a2..bf6e91e0 100644
--- a/tests/time_stamp.py
+++ b/tests/time_stamp.py
@@ -1,11 +1,13 @@
import os
import unittest
from .helpers.ptrack_helpers import ProbackupTest, ProbackupException
+import subprocess
+from time import sleep
module_name = 'time_stamp'
-class CheckTimeStamp(ProbackupTest, unittest.TestCase):
+class TimeStamp(ProbackupTest, unittest.TestCase):
def test_start_time_format(self):
"""Test backup ID changing after start-time editing in backup.control.
@@ -47,8 +49,14 @@ class CheckTimeStamp(ProbackupTest, unittest.TestCase):
show_backup = show_backup + self.show_pb(backup_dir, 'node')
i += 1
+ print(show_backup[1]['id'])
+ print(show_backup[2]['id'])
+
self.assertTrue(show_backup[1]['id'] == show_backup[2]['id'], "ERROR: Localtime format using instead of UTC")
+ output = self.show_pb(backup_dir, as_json=False, as_text=True)
+ self.assertNotIn("backup ID in control file", output)
+
node.stop()
# Clean after yourself
self.del_test_dir(module_name, fname)
@@ -72,3 +80,180 @@ class CheckTimeStamp(ProbackupTest, unittest.TestCase):
# Clean after yourself
self.del_test_dir(module_name, fname)
+
+ def test_handling_of_TZ_env_variable(self):
+ """Issue #112"""
+ fname = self.id().split('.')[3]
+ node = self.make_simple_node(
+ base_dir="{0}/{1}/node".format(module_name, fname),
+ set_replication=True,
+ initdb_params=['--data-checksums'])
+
+ backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
+ self.init_pb(backup_dir)
+ self.add_instance(backup_dir, 'node', node)
+ node.start()
+
+ my_env = os.environ.copy()
+ my_env["TZ"] = "America/Detroit"
+
+ self.backup_node(
+ backup_dir, 'node', node, options=['--stream', '-j 2'], env=my_env)
+
+ output = self.show_pb(backup_dir, 'node', as_json=False, as_text=True, env=my_env)
+
+ self.assertNotIn("backup ID in control file", output)
+
+ # Clean after yourself
+ self.del_test_dir(module_name, fname)
+
+ @unittest.skip("skip")
+ # @unittest.expectedFailure
+ def test_dst_timezone_handling(self):
+ """for manual testing"""
+ fname = self.id().split('.')[3]
+ backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
+ node = self.make_simple_node(
+ base_dir=os.path.join(module_name, fname, 'node'),
+ initdb_params=['--data-checksums'],
+ pg_options={'autovacuum': 'off'})
+
+ self.init_pb(backup_dir)
+ self.add_instance(backup_dir, 'node', node)
+ self.set_archiving(backup_dir, 'node', node)
+ node.slow_start()
+
+ print(subprocess.Popen(
+ ['sudo', 'timedatectl', 'set-timezone', 'America/Detroit'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE).communicate())
+
+ subprocess.Popen(
+ ['sudo', 'timedatectl', 'set-ntp', 'false'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE).communicate()
+
+ subprocess.Popen(
+ ['sudo', 'timedatectl', 'set-time', '2020-05-25 12:00:00'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE).communicate()
+
+ # FULL
+ output = self.backup_node(backup_dir, 'node', node, return_id=False)
+ self.assertNotIn("backup ID in control file", output)
+
+ # move to dst
+ subprocess.Popen(
+ ['sudo', 'timedatectl', 'set-time', '2020-10-25 12:00:00'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE).communicate()
+
+ # DELTA
+ output = self.backup_node(
+ backup_dir, 'node', node, backup_type='delta', return_id=False)
+ self.assertNotIn("backup ID in control file", output)
+
+ subprocess.Popen(
+ ['sudo', 'timedatectl', 'set-time', '2020-12-01 12:00:00'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE).communicate()
+
+ # DELTA
+ self.backup_node(backup_dir, 'node', node, backup_type='delta')
+
+ output = self.show_pb(backup_dir, as_json=False, as_text=True)
+ self.assertNotIn("backup ID in control file", output)
+
+ subprocess.Popen(
+ ['sudo', 'timedatectl', 'set-ntp', 'true'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE).communicate()
+
+ sleep(10)
+
+ self.backup_node(backup_dir, 'node', node, backup_type='delta')
+
+ output = self.show_pb(backup_dir, as_json=False, as_text=True)
+ self.assertNotIn("backup ID in control file", output)
+
+ subprocess.Popen(
+ ['sudo', 'timedatectl', 'set-timezone', 'US/Moscow'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE).communicate()
+
+ # Clean after yourself
+ self.del_test_dir(module_name, fname)
+
+ @unittest.skip("skip")
+ def test_dst_timezone_handling_backward_compatibilty(self):
+ """for manual testing"""
+ fname = self.id().split('.')[3]
+ backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
+ node = self.make_simple_node(
+ base_dir=os.path.join(module_name, fname, 'node'),
+ initdb_params=['--data-checksums'],
+ pg_options={'autovacuum': 'off'})
+
+ self.init_pb(backup_dir)
+ self.add_instance(backup_dir, 'node', node)
+ self.set_archiving(backup_dir, 'node', node)
+ node.slow_start()
+
+ subprocess.Popen(
+ ['sudo', 'timedatectl', 'set-timezone', 'America/Detroit'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE).communicate()
+
+ subprocess.Popen(
+ ['sudo', 'timedatectl', 'set-ntp', 'false'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE).communicate()
+
+ subprocess.Popen(
+ ['sudo', 'timedatectl', 'set-time', '2020-05-25 12:00:00'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE).communicate()
+
+ # FULL
+ self.backup_node(backup_dir, 'node', node, old_binary=True, return_id=False)
+
+ # move to dst
+ subprocess.Popen(
+ ['sudo', 'timedatectl', 'set-time', '2020-10-25 12:00:00'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE).communicate()
+
+ # DELTA
+ output = self.backup_node(
+ backup_dir, 'node', node, backup_type='delta', old_binary=True, return_id=False)
+
+ subprocess.Popen(
+ ['sudo', 'timedatectl', 'set-time', '2020-12-01 12:00:00'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE).communicate()
+
+ # DELTA
+ self.backup_node(backup_dir, 'node', node, backup_type='delta')
+
+ output = self.show_pb(backup_dir, as_json=False, as_text=True)
+ self.assertNotIn("backup ID in control file", output)
+
+ subprocess.Popen(
+ ['sudo', 'timedatectl', 'set-ntp', 'true'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE).communicate()
+
+ sleep(10)
+
+ self.backup_node(backup_dir, 'node', node, backup_type='delta')
+
+ output = self.show_pb(backup_dir, as_json=False, as_text=True)
+ self.assertNotIn("backup ID in control file", output)
+
+ subprocess.Popen(
+ ['sudo', 'timedatectl', 'set-timezone', 'US/Moscow'],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE).communicate()
+
+ # Clean after yourself
+ self.del_test_dir(module_name, fname)