Merge branch 'master' into release_2_5

This commit is contained in:
Grigory Smolkin
2021-02-14 18:40:16 +03:00
16 changed files with 647 additions and 282 deletions
+330 -167
View File
@@ -26,12 +26,27 @@ static pgBackup *readBackupControlFile(const char *path);
static time_t create_backup_dir(pgBackup *backup, const char *backup_instance_path);
static bool backup_lock_exit_hook_registered = false;
static parray *lock_files = NULL;
static parray *locks = 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 int grab_excl_lock_file(const char *backup_dir, const char *backup_id, bool strict);
static int grab_shared_lock_file(pgBackup *backup);
static int wait_shared_owners(pgBackup *backup);
static void unlock_backup(const char *backup_dir, const char *backup_id, bool exclusive);
static void release_excl_lock_file(const char *backup_dir);
static void release_shared_lock_file(const char *backup_dir);
#define LOCK_OK 0
#define LOCK_FAIL_TIMEOUT 1
#define LOCK_FAIL_ENOSPC 2
#define LOCK_FAIL_EROFS 3
typedef struct LockInfo
{
char backup_id[10];
char backup_dir[MAXPGPATH];
bool exclusive;
} LockInfo;
static timelineInfo *
timelineInfoNew(TimeLineID tli)
@@ -66,28 +81,24 @@ timelineInfoFree(void *tliInfo)
pfree(tliInfo);
}
/* Iterate over locked backups and delete locks files */
/* Iterate over locked backups and unlock them */
static void
unlink_lock_atexit(void)
{
int i;
int i;
if (lock_files == NULL)
if (locks == NULL)
return;
for (i = 0; i < parray_num(lock_files); i++)
for (i = 0; i < parray_num(locks); i++)
{
char *lock_file = (char *) parray_get(lock_files, i);
int res;
res = fio_unlink(lock_file, FIO_BACKUP_HOST);
if (res != 0 && errno != ENOENT)
elog(WARNING, "%s: %s", lock_file, strerror(errno));
LockInfo *lock = (LockInfo *) parray_get(locks, i);
unlock_backup(lock->backup_dir, lock->backup_dir, lock->exclusive);
}
parray_walk(lock_files, pfree);
parray_free(lock_files);
lock_files = NULL;
parray_walk(locks, pg_free);
parray_free(locks);
locks = NULL;
}
/*
@@ -147,95 +158,111 @@ write_backup_status(pgBackup *backup, BackupStatus status,
}
/*
* Lock backup in either exclusive or non-exclusive (read-only) mode.
* Lock backup in either exclusive or shared 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.
* Only read only tasks (validate, restore) are allowed to take shared 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
* Multiple proccess are allowed to take shared locks simultaneously.
* Shared 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.
* When taking shared lock, a brief exclusive lock is taken.
*
* -> exclusive -> grab exclusive lock file and wait until all shared lockers are gone, return
* -> shared -> grab exclusive lock file, grab shared lock file, release exclusive lock file, return
*
* TODO: lock-timeout as parameter
* TODO: we must think about more fine grain unlock mechanism - separate unlock_backup() function.
* TODO: more accurate naming
* -> exclusive lock -> acquire HW_LATCH and wait until all LW_LATCH`es are clear
* -> shared lock -> acquire HW_LATCH, acquire LW_LATCH, release HW_LATCH
*/
bool
lock_backup(pgBackup *backup, bool strict, bool exclusive)
{
int rc;
char lock_file[MAXPGPATH];
bool enospc_detected = false;
int rc;
char lock_file[MAXPGPATH];
bool enospc_detected = false;
LockInfo *lock = NULL;
join_path_components(lock_file, backup->root_dir, BACKUP_LOCK_FILE);
rc = lock_backup_exclusive(backup, strict);
rc = grab_excl_lock_file(backup->root_dir, base36enc(backup->start_time), strict);
if (rc == 1)
if (rc == LOCK_FAIL_TIMEOUT)
return false;
else if (rc == 2)
else if (rc == LOCK_FAIL_ENOSPC)
{
/*
* If we failed to take exclusive lock due to ENOSPC,
* then in lax mode treat such condition as if lock was taken.
*/
enospc_detected = true;
if (strict)
return false;
}
else if (rc == LOCK_FAIL_EROFS)
{
/*
* If we failed to take exclusive lock due to EROFS,
* then in shared mode treat such condition as if lock was taken.
*/
return !exclusive;
}
/*
* We have exclusive lock, now there are following scenarios:
*
* 1. If we are for exlusive lock, then we must open the RO lock file
* 1. If we are for exlusive lock, then we must open the shared 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.
* into shared lock file 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 exclusive 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;
}
}
if (exclusive)
rc = wait_shared_owners(backup);
else
rc = grab_shared_lock_file(backup);
if (rc != 0)
{
/*
* Failed to grab shared lock or (in case of exclusive mode) shared lock owners
* are not going away in time, release the exclusive lock file and return in shame.
*/
release_excl_lock_file(backup->root_dir);
return false;
}
if (!exclusive)
{
/* Shared lock file is grabbed, now we can release exclusive lock file */
release_excl_lock_file(backup->root_dir);
}
if (exclusive && !strict && enospc_detected)
{
/* We are in lax exclusive mode and EONSPC was encountered:
* once again try to grab exclusive lock file,
* because there is a chance that release of shared lock file in wait_shared_owners may have
* freed some space on filesystem, thanks to unlinking of BACKUP_RO_LOCK_FILE.
* If somebody concurrently acquired exclusive lock file first, then we should give up.
*/
if (grab_excl_lock_file(backup->root_dir, base36enc(backup->start_time), strict) == LOCK_FAIL_TIMEOUT)
return false;
return true;
}
/*
* Arrange to unlink the lock file(s) at proc_exit.
* Arrange the unlocking at proc_exit.
*/
if (!backup_lock_exit_hook_registered)
{
@@ -243,32 +270,40 @@ lock_backup(pgBackup *backup, bool strict, bool exclusive)
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));
/* save lock metadata for later unlocking */
lock = pgut_malloc(sizeof(LockInfo));
snprintf(lock->backup_id, 10, "%s", base36enc(backup->backup_id));
snprintf(lock->backup_dir, MAXPGPATH, "%s", backup->root_dir);
lock->exclusive = exclusive;
/* Use parray for lock release */
if (locks == NULL)
locks = parray_new();
parray_append(locks, lock);
return true;
}
/* Lock backup in exclusive mode
/*
* 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
* LOCK_OK Success
* LOCK_FAIL_TIMEOUT Failed to acquire lock in lock_timeout time
* LOCK_FAIL_ENOSPC Failed to acquire lock due to ENOSPC
* LOCK_FAIL_EROFS Failed to acquire lock due to EROFS
*/
int
lock_backup_exclusive(pgBackup *backup, bool strict)
grab_excl_lock_file(const char *root_dir, const char *backup_id, bool strict)
{
char lock_file[MAXPGPATH];
int fd = 0;
char buffer[MAXPGPATH * 2 + 256];
char buffer[256];
int ntries = LOCK_TIMEOUT;
int empty_tries = LOCK_STALE_TIMEOUT;
int len;
int encoded_pid;
join_path_components(lock_file, backup->root_dir, BACKUP_LOCK_FILE);
join_path_components(lock_file, root_dir, BACKUP_LOCK_FILE);
/*
* We need a loop here because of race conditions. But don't loop forever
@@ -280,8 +315,7 @@ lock_backup_exclusive(pgBackup *backup, bool strict)
FILE *fp_out = NULL;
if (interrupted)
elog(ERROR, "Interrupted while locking backup %s",
base36enc(backup->start_time));
elog(ERROR, "Interrupted while locking backup %s", backup_id);
/*
* Try to create the lock file --- O_EXCL makes this atomic.
@@ -293,6 +327,14 @@ lock_backup_exclusive(pgBackup *backup, bool strict)
if (fd >= 0)
break; /* Success; exit the retry loop */
/* read-only fs is a special case */
if (errno == EROFS)
{
elog(WARNING, "Could not create lock file \"%s\": %s",
lock_file, strerror(errno));
return LOCK_FAIL_EROFS;
}
/*
* Couldn't create the pid file. Probably it already exists.
* If file already exists or we have some permission problem (???),
@@ -344,7 +386,7 @@ lock_backup_exclusive(pgBackup *backup, bool strict)
if ((empty_tries % LOG_FREQ) == 0)
elog(WARNING, "Waiting %u seconds on empty exclusive lock for backup %s",
empty_tries, base36enc(backup->start_time));
empty_tries, backup_id);
sleep(1);
/*
@@ -371,35 +413,33 @@ lock_backup_exclusive(pgBackup *backup, bool strict)
* exist.
*/
if (encoded_pid == my_pid)
return 0;
return LOCK_OK;
if (kill(encoded_pid, 0) == 0)
{
/* complain every fifth interval */
if ((ntries % LOG_FREQ) == 0)
{
elog(WARNING, "Process %d is using backup %s, and is still running",
encoded_pid, backup_id);
elog(WARNING, "Waiting %u seconds on exclusive lock for backup %s",
ntries, backup_id);
}
sleep(1);
/* try again */
continue;
}
else
{
if (kill(encoded_pid, 0) == 0)
{
/* 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 exclusive lock for backup %s",
ntries, base36enc(backup->start_time));
}
sleep(1);
/* try again */
continue;
}
if (errno == ESRCH)
elog(WARNING, "Process %d which used backup %s no longer exists",
encoded_pid, backup_id);
else
{
if (errno == ESRCH)
elog(WARNING, "Process %d which used backup %s no longer exists",
encoded_pid, base36enc(backup->start_time));
else
elog(ERROR, "Failed to send signal 0 to a process %d: %s",
encoded_pid, strerror(errno));
}
elog(ERROR, "Failed to send signal 0 to a process %d: %s",
encoded_pid, strerror(errno));
}
grab_lock:
@@ -420,7 +460,7 @@ grab_lock:
/* Failed to acquire exclusive lock in time */
if (fd <= 0)
return 1;
return LOCK_FAIL_TIMEOUT;
/*
* Successfully created the file, now fill it.
@@ -440,7 +480,7 @@ grab_lock:
* Only delete command should be run in lax mode.
*/
if (!strict && save_errno == ENOSPC)
return 2;
return LOCK_FAIL_ENOSPC;
else
elog(ERROR, "Could not write lock file \"%s\": %s",
lock_file, strerror(save_errno));
@@ -458,7 +498,7 @@ grab_lock:
* Only delete command should be run in lax mode.
*/
if (!strict && save_errno == ENOSPC)
return 2;
return LOCK_FAIL_ENOSPC;
else
elog(ERROR, "Could not flush lock file \"%s\": %s",
lock_file, strerror(save_errno));
@@ -471,7 +511,7 @@ grab_lock:
fio_unlink(lock_file, FIO_BACKUP_HOST);
if (!strict && errno == ENOSPC)
return 2;
return LOCK_FAIL_ENOSPC;
else
elog(ERROR, "Could not close lock file \"%s\": %s",
lock_file, strerror(save_errno));
@@ -481,16 +521,19 @@ grab_lock:
// base36enc(backup->start_time),
// LOCK_TIMEOUT - ntries + LOCK_STALE_TIMEOUT - empty_tries);
return 0;
return LOCK_OK;
}
/* Wait until all read-only lock owners are gone */
bool
wait_read_only_owners(pgBackup *backup)
/* Wait until all shared lock owners are gone
* 0 - successs
* 1 - fail
*/
int
wait_shared_owners(pgBackup *backup)
{
FILE *fp = NULL;
FILE *fp = NULL;
char buffer[256];
pid_t encoded_pid;
pid_t encoded_pid = 0;
int ntries = LOCK_TIMEOUT;
char lock_file[MAXPGPATH];
@@ -500,7 +543,7 @@ wait_read_only_owners(pgBackup *backup)
if (fp == NULL && errno != ENOENT)
elog(ERROR, "Cannot open lock file \"%s\": %s", lock_file, strerror(errno));
/* iterate over pids in lock file */
/* iterate over pids in lock file */
while (fp && fgets(buffer, sizeof(buffer), fp))
{
encoded_pid = atoi(buffer);
@@ -510,47 +553,42 @@ wait_read_only_owners(pgBackup *backup)
continue;
}
/* wait until RO lock owners go away */
/* wait until shared lock owners go away */
do
{
if (interrupted)
elog(ERROR, "Interrupted while locking backup %s",
base36enc(backup->start_time));
if (encoded_pid != my_pid)
if (encoded_pid == my_pid)
break;
/* check if lock owner is still alive */
if (kill(encoded_pid, 0) == 0)
{
if (kill(encoded_pid, 0) == 0)
/* complain from time to time */
if ((ntries % LOG_FREQ) == 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, "Process %d is using backup %s in shared 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;
elog(WARNING, "Waiting %u seconds on lock for backup %s", ntries,
base36enc(backup->start_time));
}
else if (errno != ESRCH)
elog(ERROR, "Failed to send signal 0 to a process %d: %s",
encoded_pid, strerror(errno));
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))
@@ -559,22 +597,26 @@ wait_read_only_owners(pgBackup *backup)
if (fp)
fclose(fp);
/* unlink RO lock list */
/* some shared owners are still alive */
if (ntries <= 0)
{
elog(WARNING, "Cannot to lock backup %s in exclusive mode, because process %u owns shared lock",
base36enc(backup->start_time), encoded_pid);
return 1;
}
/* unlink shared lock file */
fio_unlink(lock_file, FIO_BACKUP_HOST);
return true;
return 0;
}
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)
/*
* Lock backup in shared mode
* 0 - successs
* 1 - fail
*/
int
grab_shared_lock_file(pgBackup *backup)
{
FILE *fp_in = NULL;
FILE *fp_out = NULL;
@@ -604,20 +646,20 @@ lock_backup_read_only(pgBackup *backup)
continue;
}
if (encoded_pid != my_pid)
if (encoded_pid == my_pid)
continue;
if (kill(encoded_pid, 0) == 0)
{
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));
/*
* Somebody is still using this backup in shared 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)
@@ -629,7 +671,12 @@ lock_backup_read_only(pgBackup *backup)
fp_out = fopen(lock_file_tmp, "w");
if (fp_out == NULL)
{
if (errno == EROFS)
return 0;
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);
@@ -647,7 +694,123 @@ lock_backup_read_only(pgBackup *backup)
elog(ERROR, "Cannot rename file \"%s\" to \"%s\": %s",
lock_file_tmp, lock_file, strerror(errno));
return true;
return 0;
}
void
unlock_backup(const char *backup_dir, const char *backup_id, bool exclusive)
{
if (exclusive)
{
release_excl_lock_file(backup_dir);
return;
}
/* To remove shared lock, we must briefly obtain exclusive lock, ... */
if (grab_excl_lock_file(backup_dir, backup_id, false) != LOCK_OK)
/* ... if it's not possible then leave shared lock */
return;
release_shared_lock_file(backup_dir);
release_excl_lock_file(backup_dir);
}
void
release_excl_lock_file(const char *backup_dir)
{
char lock_file[MAXPGPATH];
join_path_components(lock_file, backup_dir, BACKUP_LOCK_FILE);
/* TODO Sanity check: maybe we should check, that pid in lock file is my_pid */
/* unlink pid file */
fio_unlink(lock_file, FIO_BACKUP_HOST);
}
void
release_shared_lock_file(const char *backup_dir)
{
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_dir, BACKUP_RO_LOCK_FILE);
snprintf(lock_file_tmp, MAXPGPATH, "%s%s", lock_file, "tmp");
/* open lock file */
fp_in = fopen(lock_file, "r");
if (fp_in == NULL)
{
if (errno == ENOENT)
return;
else
elog(ERROR, "Cannot open lock file \"%s\": %s", lock_file, strerror(errno));
}
/* read PIDs of owners */
while (fgets(buf_in, sizeof(buf_in), fp_in))
{
encoded_pid = atoi(buf_in);
if (encoded_pid <= 0)
{
elog(WARNING, "Bogus data in lock file \"%s\": \"%s\"", lock_file, buf_in);
continue;
}
/* remove my pid */
if (encoded_pid == my_pid)
continue;
if (kill(encoded_pid, 0) == 0)
{
/*
* Somebody is still using this backup in shared 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 (ferror(fp_in))
elog(ERROR, "Cannot read from lock file: \"%s\"", lock_file);
fclose(fp_in);
/* if there is no active pid left, then there is nothing to do */
if (buffer_len == 0)
{
fio_unlink(lock_file, FIO_BACKUP_HOST);
return;
}
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));
/* 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;
}
/*
+3 -2
View File
@@ -711,8 +711,9 @@ backup_non_data_file(pgFile *file, pgFile *prev_file,
/*
* If nonedata file exists in previous backup
* and its mtime is less than parent backup start time ... */
if (prev_file && file->exists_in_prev &&
file->mtime <= parent_backup_time)
if ((pg_strcasecmp(file->name, RELMAPPER_FILENAME) != 0) &&
(prev_file && file->exists_in_prev &&
file->mtime <= parent_backup_time))
{
file->crc = fio_get_crc32(from_fullpath, FIO_DB_HOST, false);
+5 -1
View File
@@ -78,7 +78,8 @@ extern const char *PROGRAM_EMAIL;
#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"
#define PG_TABLESPACE_MAP_FILE "tablespace_map"
#define RELMAPPER_FILENAME "pg_filenode.map"
#define EXTERNAL_DIR "external_directories/externaldir"
#define DATABASE_MAP "database_map"
#define HEADER_MAP "page_header_map"
@@ -331,7 +332,10 @@ typedef enum ShowFormat
#define FILE_NOT_FOUND (-2) /* file disappeared during backup */
#define BLOCKNUM_INVALID (-1)
#define PROGRAM_VERSION "2.5.0"
/* update when remote agent API or behaviour changes */
#define AGENT_PROTOCOL_VERSION 20500
#define AGENT_PROTOCOL_VERSION_STR "2.5.0"
/* update only when changing storage format */
#define STORAGE_FORMAT_VERSION "2.5.0"
+6
View File
@@ -905,6 +905,11 @@ restore_chain(pgBackup *dest_backup, parray *parent_chain,
if (parray_bsearch(dest_backup->files, file, pgFileCompareRelPathWithExternal))
redundant = false;
/* pg_filenode.map are always restored, because it's crc cannot be trusted */
if (file->external_dir_num == 0 &&
pg_strcasecmp(file->name, RELMAPPER_FILENAME) == 0)
redundant = true;
/* do not delete the useful internal directories */
if (S_ISDIR(file->mode) && !redundant)
continue;
@@ -1541,6 +1546,7 @@ update_recovery_options(InstanceState *instanceState, pgBackup *backup,
if (errno != ENOENT)
elog(ERROR, "cannot stat file \"%s\": %s", postgres_auto_path,
strerror(errno));
st.st_size = 0;
}
/* Kludge for 0-sized postgresql.auto.conf file. TODO: make something more intelligent */
+3 -2
View File
@@ -247,8 +247,9 @@ bool launch_agent(void)
(agent_version / 100) % 100,
agent_version % 100);
elog(ERROR, "Remote agent version %s does not match local program version %s",
agent_version_str, PROGRAM_VERSION);
elog(ERROR, "Remote agent protocol version %s does not match local program protocol version %s, "
"consider to upgrade pg_probackup binary",
agent_version_str, AGENT_PROTOCOL_VERSION_STR);
}
return true;
+6 -2
View File
@@ -1,7 +1,11 @@
[см wiki](https://confluence.postgrespro.ru/display/DEV/pg_probackup)
[see wiki](https://confluence.postgrespro.ru/display/DEV/pg_probackup)
```
Note: For now these are works on Linux and "kinda" works on Windows
Note: For now these tests work on Linux and "kinda" work on Windows
```
```
Note: tests require python3 to work properly.
```
```
+3 -3
View File
@@ -1913,7 +1913,7 @@ class ArchiveTest(ProbackupTest, unittest.TestCase):
output = self.show_archive(
backup_dir, 'node', as_json=False, as_text=True,
options=['--log-level-console=VERBOSE'])
options=['--log-level-console=INFO'])
self.assertNotIn('WARNING', output)
@@ -2186,7 +2186,7 @@ class ArchiveTest(ProbackupTest, unittest.TestCase):
restore_command += ' -j 2 --batch-size=10'
print(restore_command)
# print(restore_command)
if node.major_version >= 12:
self.set_auto_conf(replica, {'restore_command': restore_command})
@@ -2303,7 +2303,7 @@ class ArchiveTest(ProbackupTest, unittest.TestCase):
dst_file = os.path.join(replica.data_dir, wal_dir, 'pbk_prefetch', filename)
shutil.copyfile(src_file, dst_file)
print(dst_file)
# print(dst_file)
# corrupt file
if files[-2].endswith('.gz'):
+15 -43
View File
@@ -34,10 +34,8 @@ class SimpleAuthTest(ProbackupTest, unittest.TestCase):
node = self.make_simple_node(
base_dir=os.path.join(module_name, fname, 'node'),
set_replication=True,
initdb_params=['--data-checksums'],
pg_options={
'max_wal_senders': '2'}
)
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)
@@ -64,7 +62,15 @@ class SimpleAuthTest(ProbackupTest, unittest.TestCase):
"GRANT EXECUTE ON FUNCTION"
" pg_start_backup(text, boolean, boolean) TO backup;")
time.sleep(1)
if self.get_version(node) < 100000:
node.safe_psql(
'postgres',
"GRANT EXECUTE ON FUNCTION pg_catalog.pg_switch_xlog() TO backup")
else:
node.safe_psql(
'postgres',
"GRANT EXECUTE ON FUNCTION pg_catalog.pg_switch_wal() TO backup")
try:
self.backup_node(
backup_dir, 'node', node, options=['-U', 'backup'])
@@ -84,8 +90,6 @@ class SimpleAuthTest(ProbackupTest, unittest.TestCase):
"GRANT EXECUTE ON FUNCTION"
" pg_create_restore_point(text) TO backup;")
time.sleep(1)
try:
self.backup_node(
backup_dir, 'node', node, options=['-U', 'backup'])
@@ -129,50 +133,18 @@ class SimpleAuthTest(ProbackupTest, unittest.TestCase):
node.stop()
node.slow_start()
try:
self.backup_node(
backup_dir, 'node', node, options=['-U', 'backup'])
self.assertEqual(
1, 0,
"Expecting Error due to missing grant on clearing ptrack_files.")
except ProbackupException as e:
self.assertIn(
"ERROR: must be superuser or replication role to clear ptrack files\n"
"query was: SELECT pg_catalog.pg_ptrack_clear()", e.message,
'\n Unexpected Error Message: {0}\n CMD: {1}'.format(
repr(e.message), self.cmd))
time.sleep(1)
try:
self.backup_node(
backup_dir, 'node', node,
backup_type='ptrack', options=['-U', 'backup'])
self.assertEqual(
1, 0,
"Expecting Error due to missing grant on clearing ptrack_files.")
except ProbackupException as e:
self.assertIn(
"ERROR: must be superuser or replication role read ptrack files\n"
"query was: select pg_catalog.pg_ptrack_control_lsn()", e.message,
'\n Unexpected Error Message: {0}\n CMD: {1}'.format(
repr(e.message), self.cmd))
node.safe_psql(
"postgres",
"ALTER ROLE backup REPLICATION")
time.sleep(1)
# FULL
self.backup_node(
backup_dir, 'node', node,
options=['-U', 'backup'])
backup_dir, 'node', node, options=['-U', 'backup'])
# PTRACK
self.backup_node(
backup_dir, 'node', node,
backup_type='ptrack', options=['-U', 'backup'])
# self.backup_node(
# backup_dir, 'node', node,
# backup_type='ptrack', options=['-U', 'backup'])
# Clean after yourself
self.del_test_dir(module_name, fname)
+52
View File
@@ -5,6 +5,7 @@ from .helpers.ptrack_helpers import ProbackupTest, ProbackupException
import shutil
from distutils.dir_util import copy_tree
from testgres import ProcessType
import subprocess
module_name = 'backup'
@@ -3027,3 +3028,54 @@ class BackupTest(ProbackupTest, unittest.TestCase):
# Clean after yourself
self.del_test_dir(module_name, fname)
def test_incr_backup_filenode_map(self):
"""
https://github.com/postgrespro/pg_probackup/issues/320
"""
fname = self.id().split('.')[3]
backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
node = self.make_simple_node(
base_dir=os.path.join(module_name, fname, 'node'),
initdb_params=['--data-checksums'])
self.init_pb(backup_dir)
self.add_instance(backup_dir, 'node', node)
self.set_archiving(backup_dir, 'node', node)
node.slow_start()
node1 = self.make_simple_node(
base_dir=os.path.join(module_name, fname, 'node1'),
initdb_params=['--data-checksums'])
node1.cleanup()
node.pgbench_init(scale=5)
# FULL backup
backup_id = self.backup_node(backup_dir, 'node', node)
pgbench = node.pgbench(
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
options=['-T', '10', '-c', '1'])
backup_id = self.backup_node(backup_dir, 'node', node, backup_type='delta')
node.safe_psql(
'postgres',
'reindex index pg_type_oid_index')
backup_id = self.backup_node(
backup_dir, 'node', node, backup_type='delta')
# incremental restore into node1
node.cleanup()
self.restore_node(backup_dir, 'node', node)
node.slow_start()
node.safe_psql(
'postgres',
'select 1')
# Clean after yourself
self.del_test_dir(module_name, fname)
+47 -44
View File
@@ -20,9 +20,9 @@ class CfsBackupNoEncTest(ProbackupTest, unittest.TestCase):
self.node = self.make_simple_node(
base_dir="{0}/{1}/node".format(module_name, self.fname),
set_replication=True,
ptrack_enable=True,
initdb_params=['--data-checksums'],
pg_options={
'ptrack_enable': 'on',
'cfs_encryption': 'off',
'max_wal_senders': '2',
'shared_buffers': '200MB'
@@ -35,18 +35,27 @@ class CfsBackupNoEncTest(ProbackupTest, unittest.TestCase):
self.node.slow_start()
if self.node.major_version >= 12:
self.node.safe_psql(
"postgres",
"CREATE EXTENSION ptrack")
self.create_tblspace_in_node(self.node, tblspace_name, cfs=True)
tblspace = self.node.safe_psql(
"postgres",
"SELECT * FROM pg_tablespace WHERE spcname='{0}'".format(
tblspace_name)
)
self.assertTrue(
tblspace_name in tblspace and "compression=true" in tblspace,
tblspace_name))
self.assertIn(
tblspace_name, str(tblspace),
"ERROR: The tablespace not created "
"or it create without compressions"
)
"or it create without compressions")
self.assertIn(
"compression=true", str(tblspace),
"ERROR: The tablespace not created "
"or it create without compressions")
self.assertTrue(
find_by_name(
@@ -473,7 +482,7 @@ class CfsBackupNoEncTest(ProbackupTest, unittest.TestCase):
)
# --- Section: Incremental from fill tablespace --- #
@unittest.expectedFailure
# @unittest.expectedFailure
# @unittest.skip("skip")
@unittest.skipUnless(ProbackupTest.enterprise, 'skip')
def test_fullbackup_after_create_table_ptrack_after_create_table(self):
@@ -537,7 +546,7 @@ class CfsBackupNoEncTest(ProbackupTest, unittest.TestCase):
)
)
@unittest.expectedFailure
# @unittest.expectedFailure
# @unittest.skip("skip")
@unittest.skipUnless(ProbackupTest.enterprise, 'skip')
def test_fullbackup_after_create_table_ptrack_after_create_table_stream(self):
@@ -603,7 +612,7 @@ class CfsBackupNoEncTest(ProbackupTest, unittest.TestCase):
)
)
@unittest.expectedFailure
# @unittest.expectedFailure
# @unittest.skip("skip")
@unittest.skipUnless(ProbackupTest.enterprise, 'skip')
def test_fullbackup_after_create_table_page_after_create_table(self):
@@ -738,12 +747,14 @@ class CfsBackupNoEncTest(ProbackupTest, unittest.TestCase):
# CHECK FULL BACKUP
self.node.stop()
self.node.cleanup()
shutil.rmtree(
self.get_tblspace_path(self.node, tblspace_name),
ignore_errors=True)
shutil.rmtree(self.get_tblspace_path(self.node, tblspace_name))
self.restore_node(
self.backup_dir, 'node', self.node,
backup_id=backup_id_full, options=["-j", "4"])
self.backup_dir, 'node', self.node, backup_id=backup_id_full,
options=[
"-j", "4",
"--recovery-target=immediate",
"--recovery-target-action=promote"])
self.node.slow_start()
self.assertEqual(
full_result,
@@ -757,8 +768,12 @@ class CfsBackupNoEncTest(ProbackupTest, unittest.TestCase):
self.get_tblspace_path(self.node, tblspace_name),
ignore_errors=True)
self.restore_node(
self.backup_dir, 'node', self.node,
backup_id=backup_id_page, options=["-j", "4"])
self.backup_dir, 'node', self.node, backup_id=backup_id_page,
options=[
"-j", "4",
"--recovery-target=immediate",
"--recovery-target-action=promote"])
self.node.slow_start()
self.assertEqual(
page_result,
@@ -786,8 +801,7 @@ class CfsBackupNoEncTest(ProbackupTest, unittest.TestCase):
"AS SELECT i AS id, MD5(i::text) AS text, "
"MD5(repeat(i::text,10))::tsvector AS tsvector "
"FROM generate_series(0,1005000) i".format(
't_heap_1', tblspace_name_1)
)
't_heap_1', tblspace_name_1))
self.node.safe_psql(
"postgres",
@@ -795,8 +809,7 @@ class CfsBackupNoEncTest(ProbackupTest, unittest.TestCase):
"AS SELECT i AS id, MD5(i::text) AS text, "
"MD5(repeat(i::text,10))::tsvector AS tsvector "
"FROM generate_series(0,1005000) i".format(
't_heap_2', tblspace_name_2)
)
't_heap_2', tblspace_name_2))
full_result_1 = self.node.safe_psql(
"postgres", "SELECT * FROM t_heap_1")
@@ -864,21 +877,16 @@ class CfsBackupNoEncTest(ProbackupTest, unittest.TestCase):
# CHECK FULL BACKUP
self.node.stop()
self.node.cleanup()
shutil.rmtree(
self.get_tblspace_path(self.node, tblspace_name),
ignore_errors=True)
shutil.rmtree(
self.get_tblspace_path(self.node, tblspace_name_1),
ignore_errors=True)
shutil.rmtree(
self.get_tblspace_path(self.node, tblspace_name_2),
ignore_errors=True)
self.restore_node(
self.backup_dir, 'node', self.node,
backup_id=backup_id_full, options=["-j", "4"])
backup_id=backup_id_full,
options=[
"-j", "4", "--incremental-mode=checksum",
"--recovery-target=immediate",
"--recovery-target-action=promote"])
self.node.slow_start()
self.assertEqual(
full_result_1,
self.node.safe_psql("postgres", "SELECT * FROM t_heap_1"),
@@ -890,21 +898,16 @@ class CfsBackupNoEncTest(ProbackupTest, unittest.TestCase):
# CHECK PAGE BACKUP
self.node.stop()
self.node.cleanup()
shutil.rmtree(
self.get_tblspace_path(self.node, tblspace_name),
ignore_errors=True)
shutil.rmtree(
self.get_tblspace_path(self.node, tblspace_name_1),
ignore_errors=True)
shutil.rmtree(
self.get_tblspace_path(self.node, tblspace_name_2),
ignore_errors=True)
self.restore_node(
self.backup_dir, 'node', self.node,
backup_id=backup_id_page, options=["-j", "4"])
backup_id=backup_id_page,
options=[
"-j", "4", "--incremental-mode=checksum",
"--recovery-target=immediate",
"--recovery-target-action=promote"])
self.node.slow_start()
self.assertEqual(
page_result_1,
self.node.safe_psql("postgres", "SELECT * FROM t_heap_1"),
@@ -914,7 +917,7 @@ class CfsBackupNoEncTest(ProbackupTest, unittest.TestCase):
self.node.safe_psql("postgres", "SELECT * FROM t_heap_2"),
'Lost data after restore')
@unittest.expectedFailure
# @unittest.expectedFailure
# @unittest.skip("skip")
@unittest.skipUnless(ProbackupTest.enterprise, 'skip')
def test_fullbackup_after_create_table_page_after_create_table_stream(self):
+3 -1
View File
@@ -88,4 +88,6 @@ def corrupt_file(filename):
def random_string(n):
a = string.ascii_letters + string.digits
return ''.join([random.choice(a) for i in range(int(n)+1)])
random_str = ''.join([random.choice(a) for i in range(int(n)+1)])
return str.encode(random_str)
# return ''.join([random.choice(a) for i in range(int(n)+1)])
+63 -1
View File
@@ -1711,6 +1711,9 @@ class IncrRestoreTest(ProbackupTest, unittest.TestCase):
# @unittest.skip("skip")
# @unittest.expectedFailure
# This test will pass with Enterprise
# because it has checksums enabled by default
@unittest.skipIf(ProbackupTest.enterprise, 'skip')
def test_incr_lsn_long_xact_1(self):
"""
"""
@@ -2390,5 +2393,64 @@ class IncrRestoreTest(ProbackupTest, unittest.TestCase):
# Clean after yourself
self.del_test_dir(module_name, fname, [node2])
def test_incremental_pg_filenode_map(self):
"""
https://github.com/postgrespro/pg_probackup/issues/320
"""
fname = self.id().split('.')[3]
backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
node = self.make_simple_node(
base_dir=os.path.join(module_name, fname, 'node'),
initdb_params=['--data-checksums'])
self.init_pb(backup_dir)
self.add_instance(backup_dir, 'node', node)
self.set_archiving(backup_dir, 'node', node)
node.slow_start()
node1 = self.make_simple_node(
base_dir=os.path.join(module_name, fname, 'node1'),
initdb_params=['--data-checksums'])
node1.cleanup()
node.pgbench_init(scale=5)
# FULL backup
backup_id = self.backup_node(backup_dir, 'node', node)
# in node1 restore full backup
self.restore_node(backup_dir, 'node', node1)
self.set_auto_conf(node1, {'port': node1.port})
node1.slow_start()
pgbench = node.pgbench(
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
options=['-T', '10', '-c', '1'])
pgbench = node1.pgbench(
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
options=['-T', '10', '-c', '1'])
node.safe_psql(
'postgres',
'reindex index pg_type_oid_index')
# FULL backup
backup_id = self.backup_node(backup_dir, 'node', node)
node1.stop()
# incremental restore into node1
self.restore_node(backup_dir, 'node', node1, options=["-I", "checksum"])
self.set_auto_conf(node1, {'port': node1.port})
node1.slow_start()
node1.safe_psql(
'postgres',
'select 1')
# Clean after yourself
self.del_test_dir(module_name, fname)
# check that MinRecPoint and BackupStartLsn are correctly used in case of --incrementa-lsn
# incremental restore + partial restore.
+56 -3
View File
@@ -581,6 +581,59 @@ 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
def test_shared_lock(self):
"""
Make sure that shared lock leaves no files with pids
"""
fname = self.id().split('.')[3]
node = self.make_simple_node(
base_dir=os.path.join(module_name, fname, 'node'),
initdb_params=['--data-checksums'])
backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
self.init_pb(backup_dir)
self.add_instance(backup_dir, 'node', node)
self.set_archiving(backup_dir, 'node', node)
node.slow_start()
# Fill with data
node.pgbench_init(scale=1)
# FULL
backup_id = self.backup_node(backup_dir, 'node', node)
lockfile_excl = os.path.join(backup_dir, 'backups', 'node', backup_id, 'backup.pid')
lockfile_shr = os.path.join(backup_dir, 'backups', 'node', backup_id, 'backup_ro.pid')
self.validate_pb(backup_dir, 'node', backup_id)
self.assertFalse(
os.path.exists(lockfile_excl),
"File should not exist: {0}".format(lockfile_excl))
self.assertFalse(
os.path.exists(lockfile_shr),
"File should not exist: {0}".format(lockfile_shr))
gdb = self.validate_pb(backup_dir, 'node', backup_id, gdb=True)
gdb.set_breakpoint('validate_one_page')
gdb.run_until_break()
gdb.kill()
self.assertTrue(
os.path.exists(lockfile_shr),
"File should exist: {0}".format(lockfile_shr))
self.validate_pb(backup_dir, 'node', backup_id)
self.assertFalse(
os.path.exists(lockfile_excl),
"File should not exist: {0}".format(lockfile_excl))
self.assertFalse(
os.path.exists(lockfile_shr),
"File should not exist: {0}".format(lockfile_shr))
# Clean after yourself
self.del_test_dir(module_name, fname)
+53
View File
@@ -7,6 +7,7 @@ from testgres import QueryException
import shutil
from datetime import datetime, timedelta
import time
import subprocess
module_name = "merge"
@@ -2829,5 +2830,57 @@ class MergeTest(ProbackupTest, unittest.TestCase):
self.del_test_dir(module_name, fname)
def test_merge_pg_filenode_map(self):
"""
https://github.com/postgrespro/pg_probackup/issues/320
"""
fname = self.id().split('.')[3]
backup_dir = os.path.join(self.tmp_path, module_name, fname, 'backup')
node = self.make_simple_node(
base_dir=os.path.join(module_name, fname, 'node'),
initdb_params=['--data-checksums'])
self.init_pb(backup_dir)
self.add_instance(backup_dir, 'node', node)
self.set_archiving(backup_dir, 'node', node)
node.slow_start()
node1 = self.make_simple_node(
base_dir=os.path.join(module_name, fname, 'node1'),
initdb_params=['--data-checksums'])
node1.cleanup()
node.pgbench_init(scale=5)
# FULL backup
self.backup_node(backup_dir, 'node', node)
pgbench = node.pgbench(
stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
options=['-T', '10', '-c', '1'])
self.backup_node(backup_dir, 'node', node, backup_type='delta')
node.safe_psql(
'postgres',
'reindex index pg_type_oid_index')
backup_id = self.backup_node(
backup_dir, 'node', node, backup_type='delta')
self.merge_backup(backup_dir, 'node', backup_id)
node.cleanup()
self.restore_node(backup_dir, 'node', node)
node.slow_start()
node.safe_psql(
'postgres',
'select 1')
# Clean after yourself
self.del_test_dir(module_name, fname)
# 1. Need new test with corrupted FULL backup
# 2. different compression levels
-12
View File
@@ -757,8 +757,6 @@ class PageTest(ProbackupTest, unittest.TestCase):
self.output, self.cmd))
except ProbackupException as e:
self.assertTrue(
'INFO: Wait for WAL segment' in e.message and
'to be archived' in e.message and
'Could not read WAL record at' in e.message and
'is absent' in e.message,
'\n Unexpected Error Message: {0}\n CMD: {1}'.format(
@@ -782,8 +780,6 @@ class PageTest(ProbackupTest, unittest.TestCase):
self.output, self.cmd))
except ProbackupException as e:
self.assertTrue(
'INFO: Wait for WAL segment' in e.message and
'to be archived' in e.message and
'Could not read WAL record at' in e.message and
'is absent' in e.message,
'\n Unexpected Error Message: {0}\n CMD: {1}'.format(
@@ -872,8 +868,6 @@ class PageTest(ProbackupTest, unittest.TestCase):
self.output, self.cmd))
except ProbackupException as e:
self.assertTrue(
'INFO: Wait for WAL segment' in e.message and
'to be archived' in e.message and
'Could not read WAL record at' in e.message and
'Possible WAL corruption. Error has occured during reading WAL segment' in e.message,
'\n Unexpected Error Message: {0}\n CMD: {1}'.format(
@@ -896,8 +890,6 @@ class PageTest(ProbackupTest, unittest.TestCase):
self.output, self.cmd))
except ProbackupException as e:
self.assertTrue(
'INFO: Wait for WAL segment' in e.message and
'to be archived' in e.message and
'Could not read WAL record at' in e.message and
'Possible WAL corruption. Error has occured during reading WAL segment "{0}"'.format(
file) in e.message,
@@ -997,8 +989,6 @@ class PageTest(ProbackupTest, unittest.TestCase):
self.output, self.cmd))
except ProbackupException as e:
self.assertTrue(
'INFO: Wait for WAL segment' in e.message and
'to be archived' in e.message and
'Could not read WAL record at' in e.message and
'Possible WAL corruption. Error has occured during reading WAL segment' in e.message,
'\n Unexpected Error Message: {0}\n CMD: {1}'.format(
@@ -1020,8 +1010,6 @@ class PageTest(ProbackupTest, unittest.TestCase):
"Output: {0} \n CMD: {1}".format(
self.output, self.cmd))
except ProbackupException as e:
self.assertIn('INFO: Wait for WAL segment', e.message)
self.assertIn('to be archived', e.message)
self.assertIn('Could not read WAL record at', e.message)
self.assertIn('WAL file is from different database system: '
'WAL file database system identifier is', e.message)
+2 -1
View File
@@ -3451,7 +3451,8 @@ class ValidateTest(ProbackupTest, unittest.TestCase):
# Clean after yourself
self.del_test_dir(module_name, fname)
# @unittest.expectedFailure
#TODO fix the test
@unittest.expectedFailure
# @unittest.skip("skip")
def test_validate_target_lsn(self):
"""