Skip to content

Commit 9ace401

Browse files
author
Artur Zakirov
committed
Refactoring do_restore() and do_validate(). Check tablespace mapping before actual restore
1 parent 320d0b6 commit 9ace401

File tree

5 files changed

+187
-141
lines changed

5 files changed

+187
-141
lines changed

doc/pg_probackup.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -480,7 +480,7 @@ Specifies whether to stop just after the specified recovery target (true), or ju
480480

481481
Specifies recovering into a particular timeline.
482482

483-
-T
483+
-T OLDDIR=NEWDIR
484484
--tablespace-mapping=OLDDIR=NEWDIR
485485

486486
Relocate the tablespace in directory `OLDDIR` to `NEWDIR` during restore. Both

restore.c

Lines changed: 120 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ typedef struct TablespaceListCell
3030
struct TablespaceListCell *next;
3131
char old_dir[MAXPGPATH];
3232
char new_dir[MAXPGPATH];
33+
bool checked; /* If this mapping was checked during
34+
restore */
3335
} TablespaceListCell;
3436

3537
typedef struct TablespaceList
@@ -54,6 +56,7 @@ typedef struct TablespaceCreatedList
5456
static void restore_database(pgBackup *backup);
5557
static void restore_directories(const char *pg_data_dir,
5658
const char *backup_dir);
59+
static void check_tablespace_mapping(pgBackup *backup);
5760
static void create_recovery_conf(time_t backup_id,
5861
const char *target_time,
5962
const char *target_xid,
@@ -82,20 +85,25 @@ do_restore(time_t backup_id,
8285
{
8386
int i;
8487
int base_index; /* index of base (full) backup */
88+
int last_diff_index = -1; /* index of last differential backup */
8589
int ret;
8690
parray *backups;
8791

8892
parray *timelines;
8993
pgBackup *base_backup = NULL;
9094
pgBackup *dest_backup = NULL;
9195
pgRecoveryTarget *rt = NULL;
92-
bool backup_id_found = false;
96+
bool need_recovery_conf = false;
9397

9498
/* PGDATA and ARCLOG_PATH are always required */
9599
if (pgdata == NULL)
96100
elog(ERROR,
97101
"required parameter not specified: PGDATA (-D, --pgdata)");
98102

103+
/* Check if restore destination empty */
104+
if (!dir_is_empty(pgdata))
105+
elog(ERROR, "restore destination is not empty: \"%s\"", pgdata);
106+
99107
elog(LOG, "========================================");
100108
elog(LOG, "restore start");
101109

@@ -131,110 +139,91 @@ do_restore(time_t backup_id,
131139
elog(LOG, "searching recent full backup");
132140
for (i = 0; i < parray_num(backups); i++)
133141
{
142+
bool satisfied = false;
143+
134144
base_backup = (pgBackup *) parray_get(backups, i);
135145

136146
if (backup_id && base_backup->start_time > backup_id)
137147
continue;
138148

139-
if (backup_id == base_backup->start_time &&
140-
base_backup->status == BACKUP_STATUS_OK)
149+
if (backup_id == base_backup->start_time)
141150
{
142-
backup_id_found = true;
151+
/* Checks for target backup */
152+
if (base_backup->status != BACKUP_STATUS_OK)
153+
elog(ERROR, "given backup %s is in %s status",
154+
base36enc(backup_id), status2str(base_backup->status));
155+
143156
dest_backup = base_backup;
144157
}
145158

146-
if (backup_id == base_backup->start_time &&
147-
base_backup->status != BACKUP_STATUS_OK)
148-
elog(ERROR, "given backup %s is %s", base36enc(backup_id),
149-
status2str(base_backup->status));
150-
151159
if (dest_backup != NULL &&
152160
base_backup->backup_mode == BACKUP_MODE_FULL &&
153161
base_backup->status != BACKUP_STATUS_OK)
154-
elog(ERROR, "base backup %s for given backup %s is %s",
162+
elog(ERROR, "base backup %s for given backup %s is in %s status",
155163
base36enc(base_backup->start_time),
156164
base36enc(dest_backup->start_time),
157165
status2str(base_backup->status));
158166

159-
if (base_backup->backup_mode < BACKUP_MODE_FULL ||
160-
base_backup->status != BACKUP_STATUS_OK)
167+
/* Dont check error backups */
168+
if (base_backup->status != BACKUP_STATUS_OK ||
169+
/* Dont check differential backups if we found latest */
170+
(last_diff_index >= 0 && base_backup->backup_mode != BACKUP_MODE_FULL))
161171
continue;
162172

163173
if (target_tli)
164174
{
165175
if (satisfy_timeline(timelines, base_backup) &&
166176
satisfy_recovery_target(base_backup, rt) &&
167-
(backup_id_found || backup_id == 0))
168-
goto base_backup_found;
177+
(dest_backup || backup_id == 0))
178+
satisfied = true;
169179
}
170180
else
171181
if (satisfy_recovery_target(base_backup, rt) &&
172-
(backup_id_found || backup_id == 0))
173-
goto base_backup_found;
182+
(dest_backup || backup_id == 0))
183+
satisfied = true;
174184

175-
backup_id_found = false;
185+
/* Target backup should satisfy restore options */
186+
if (backup_id == base_backup->start_time && !satisfied)
187+
elog(ERROR, "backup %s does not satisfy restore options",
188+
base36enc(base_backup->start_time));
189+
190+
if (satisfied)
191+
{
192+
if (base_backup->backup_mode != BACKUP_MODE_FULL)
193+
last_diff_index = i;
194+
else
195+
goto base_backup_found;
196+
}
176197
}
177198
/* no full backup found, cannot restore */
178-
elog(ERROR, "no full backup found, cannot restore.");
199+
elog(ERROR, "no full backup found, cannot restore");
179200

180201
base_backup_found:
181202
base_index = i;
203+
if (last_diff_index == -1)
204+
last_diff_index = base_index;
182205

183-
/* Check if restore destination empty */
184-
if (!dir_is_empty(pgdata))
185-
elog(ERROR, "restore destination is not empty");
186-
187-
print_backup_lsn(base_backup);
188-
189-
if (backup_id != 0)
190-
stream_wal = base_backup->stream;
191-
192-
/* restore base backup */
193-
restore_database(base_backup);
194-
195-
/* restore following differential backup */
196-
elog(LOG, "searching differential backup...");
206+
Assert(last_diff_index <= base_index);
207+
/* Tablespace directories checking */
208+
check_tablespace_mapping((pgBackup *) parray_get(backups, last_diff_index));
197209

198-
for (i = base_index - 1; i >= 0; i--)
210+
/* Restore backups from base_index to last_diff_index */
211+
need_recovery_conf = target_time != NULL || target_xid != NULL;
212+
for (i = base_index; i >= last_diff_index; i--)
199213
{
200-
pgBackup *backup = (pgBackup *) parray_get(backups, i);
201-
202-
/* don't use incomplete nor different timeline backup */
203-
if (backup->status != BACKUP_STATUS_OK ||
204-
backup->tli != base_backup->tli)
205-
continue;
206-
207-
if (backup->backup_mode == BACKUP_MODE_FULL)
208-
break;
214+
pgBackup *backup = (pgBackup *) parray_get(backups, i);
209215

210-
if (backup_id && backup->start_time > backup_id)
211-
break;
212-
213-
/* use database backup only */
214-
if (backup->backup_mode != BACKUP_MODE_DIFF_PAGE &&
215-
backup->backup_mode != BACKUP_MODE_DIFF_PTRACK)
216-
continue;
217-
218-
/* is the backup is necessary for restore to target timeline ? */
219-
if (target_tli)
216+
if (backup->status == BACKUP_STATUS_OK)
220217
{
221-
if (!satisfy_timeline(timelines, backup) ||
222-
!satisfy_recovery_target(backup, rt))
223-
continue;
224-
}
225-
else
226-
if (!satisfy_recovery_target(backup, rt))
227-
continue;
228-
229-
if (backup_id != 0)
230-
stream_wal = backup->stream;
218+
need_recovery_conf = need_recovery_conf || !backup->stream;
231219

232-
print_backup_lsn(backup);
233-
restore_database(backup);
220+
print_backup_lsn(backup);
221+
restore_database(backup);
222+
}
234223
}
235224

236225
/* create recovery.conf */
237-
if (!stream_wal || target_time != NULL || target_xid != NULL)
226+
if (need_recovery_conf)
238227
create_recovery_conf(backup_id, target_time, target_xid,
239228
target_inclusive, base_backup->tli);
240229

@@ -470,9 +459,12 @@ restore_directories(const char *pg_data_dir, const char *backup_dir)
470459
linked_path, dir_created, link_name);
471460
}
472461

473-
/* Check if restore destination empty */
462+
/*
463+
* This check was done in check_tablespace_mapping(). But do
464+
* it again.
465+
*/
474466
if (!dir_is_empty(linked_path))
475-
elog(ERROR, "restore destination is not empty \"%s\"",
467+
elog(ERROR, "restore destination is not empty: \"%s\"",
476468
linked_path);
477469

478470
if (link_sep)
@@ -523,6 +515,65 @@ restore_directories(const char *pg_data_dir, const char *backup_dir)
523515
parray_free(dirs);
524516
}
525517

518+
/*
519+
* Check that all tablespace mapping entries have correct linked directory
520+
* paths. Linked directories should be empty or do not exist.
521+
*
522+
* If tablespace-mapping option is supplied all OLDDIR entries should have
523+
* entries in tablespace_map file.
524+
*/
525+
static void
526+
check_tablespace_mapping(pgBackup *backup)
527+
{
528+
char backup_path[MAXPGPATH];
529+
parray *links;
530+
size_t i;
531+
TablespaceListCell *cell;
532+
533+
links = parray_new();
534+
535+
pgBackupGetPath(backup, backup_path, lengthof(backup_path), NULL);
536+
read_tablespace_map(links, backup_path);
537+
538+
elog(LOG, "check tablespace directories...");
539+
540+
/* 1 - all linked directories should be empty */
541+
for (i = 0; i < parray_num(links); i++)
542+
{
543+
pgFile *link = (pgFile *) parray_get(links, i);
544+
const char *linked_path = link->linked;
545+
TablespaceListCell *cell;
546+
547+
for (cell = tablespace_dirs.head; cell; cell = cell->next)
548+
if (strcmp(link->linked, cell->old_dir) == 0)
549+
{
550+
linked_path = cell->new_dir;
551+
cell->checked = true;
552+
break;
553+
}
554+
555+
if (!is_absolute_path(linked_path))
556+
elog(ERROR, "tablespace directory is not an absolute path: %s\n",
557+
linked_path);
558+
559+
if (!dir_is_empty(linked_path))
560+
elog(ERROR, "restore destination is not empty: \"%s\"",
561+
linked_path);
562+
}
563+
564+
/* 2 - OLDDIR should has an entry in links */
565+
for (cell = tablespace_dirs.head; cell; cell = cell->next)
566+
{
567+
if (!cell->checked)
568+
elog(ERROR, "--tablespace-mapping option's old directory "
569+
"has not an entry in tablespace_map file: \"%s\"",
570+
cell->old_dir);
571+
}
572+
573+
parray_walk(links, pgBackupFree);
574+
parray_free(links);
575+
}
576+
526577
/*
527578
* Restore files into $PGDATA.
528579
*/
@@ -998,6 +1049,8 @@ opt_tablespace_map(pgut_option *opt, const char *arg)
9981049
elog(ERROR, "new directory is not an absolute path in tablespace mapping: %s\n",
9991050
cell->new_dir);
10001051

1052+
cell->checked = false;
1053+
10011054
if (tablespace_dirs.tail)
10021055
tablespace_dirs.tail->next = cell;
10031056
else

tests/restore_test.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -515,7 +515,7 @@ def test_restore_with_tablespace_mapping_12(self):
515515

516516
# 1 - Try to restore to existing directory
517517
node.stop()
518-
self.assertEqual(six.b("ERROR: restore destination is not empty\n"),
518+
self.assertIn(six.b("ERROR: restore destination is not empty"),
519519
self.restore_pb(node))
520520

521521
# 2 - Try to restore to existing tablespace directory
@@ -524,7 +524,6 @@ def test_restore_with_tablespace_mapping_12(self):
524524
self.restore_pb(node))
525525

526526
# 3 - Restore using tablespace-mapping
527-
node.cleanup()
528527
tblspc_path_new = path.join(node.base_dir, "tblspc_new")
529528
self.assertIn(six.b("INFO: restore complete."),
530529
self.restore_pb(node,

tests/validate_test.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def test_validate_wal_1(self):
5656
id_backup = self.show_pb(node)[0].id
5757

5858
# Validate to real time
59-
self.assertIn(six.b("INFO: Backup validation stopped on"),
59+
self.assertIn(six.b("INFO: backup validation completed successfully on"),
6060
self.validate_pb(node, options=["--time='{:%Y-%m-%d %H:%M:%S}'".format(
6161
target_time)]))
6262

@@ -66,20 +66,20 @@ def test_validate_wal_1(self):
6666
target_time - timedelta(days=2))]))
6767

6868
# Validate to unreal time #2
69-
self.assertIn(six.b("ERROR: there are no WAL records to time"),
69+
self.assertIn(six.b("ERROR: not enough WAL records to time"),
7070
self.validate_pb(node, options=["--time='{:%Y-%m-%d %H:%M:%S}'".format(
7171
target_time + timedelta(days=2))]))
7272

7373
# Validate to real xid
74-
self.assertIn(six.b("INFO: Backup validation stopped on"),
74+
self.assertIn(six.b("INFO: backup validation completed successfully on"),
7575
self.validate_pb(node, options=["--xid=%s" % target_xid]))
7676

7777
# Validate to unreal xid
78-
self.assertIn(six.b("ERROR: there are no WAL records to xid"),
78+
self.assertIn(six.b("ERROR: not enough WAL records to xid"),
7979
self.validate_pb(node, options=["--xid=%d" % (int(target_xid) + 1000)]))
8080

8181
# Validate with backup ID
82-
self.assertIn(six.b("INFO: Backup validation stopped on"),
82+
self.assertIn(six.b("INFO: backup validation completed successfully on"),
8383
self.validate_pb(node, id_backup))
8484

8585
# Validate broken WAL
@@ -91,4 +91,4 @@ def test_validate_wal_1(self):
9191
f.write(six.b("blablabla"))
9292

9393
res = self.validate_pb(node, id_backup, options=['--xid=%s' % target_xid])
94-
self.assertIn(six.b("there are no WAL records to xid"), res)
94+
self.assertIn(six.b("not enough WAL records to xid"), res)

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy