Skip to content

Commit 4fff78f

Browse files
committed
Restructure pg_upgrade output directories for better idempotence
38bfae3 has moved the contents written to files by pg_upgrade under a new directory called pg_upgrade_output.d/ located in the new cluster's data folder, and it used a simple structure made of two subdirectories leading to a fixed structure: log/ and dump/. This design has made weaker pg_upgrade on repeated calls, as we could get failures when creating one or more of those directories, while potentially losing the logs of a previous run (logs are retained automatically on failure, and cleaned up on success unless --retain is specified). So a user would need to clean up pg_upgrade_output.d/ as an extra step for any repeated calls of pg_upgrade. The most common scenario here is --check followed by the actual upgrade, but one could see a failure when specifying an incorrect input argument value. Removing entirely the logs would have the disadvantage of removing all the past information, even if --retain was specified at some past step. This result is annoying for a lot of users and automated upgrade flows. So, rather than requiring a manual removal of pg_upgrade_output.d/, this redesigns the set of output directories in a more dynamic way, based on a suggestion from Tom Lane and Daniel Gustafsson. pg_upgrade_output.d/ is still the base path, but a second directory level is added, mostly named after an ISO-8601-formatted timestamp (in short human-readable, with milliseconds appended to the name to avoid any conflicts). The logs and dumps are saved within the same subdirectories as previously, as of log/ and dump/, but these are located inside the subdirectory named after the timestamp. The logs of a given run are removed only after a successful run if --retain is not used, and pg_upgrade_output.d/ is kept if there are any logs from a previous run. Note that previously, pg_upgrade would have kept the logs even after a successful --check but that was inconsistent compared to the case without --check when using --retain. The code in charge of the removal of the output directories is now refactored into a single routine. Two TAP tests are added with some --check commands (one failure case and one success case), to look after the issue fixed here. Note that the tests had to be tweaked a bit to fit with the new directory structure so as it can find any logs generated on failure. This is still going to require a change in the buildfarm client for the case where pg_upgrade is tested without the TAP test, though, but I'll tackle that with a separate patch where needed. Reported-by: Tushar Ahuja Author: Michael Paquier Reviewed-by: Daniel Gustafsson, Justin Pryzby Discussion: https://postgr.es/m/77e6ecaa-2785-97aa-f229-4b6e047cbd2b@enterprisedb.com
1 parent fa5185b commit 4fff78f

File tree

6 files changed

+150
-31
lines changed

6 files changed

+150
-31
lines changed

doc/src/sgml/ref/pgupgrade.sgml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -768,7 +768,10 @@ psql --username=postgres --file=script.sql postgres
768768
<para>
769769
<application>pg_upgrade</application> creates various working files, such
770770
as schema dumps, stored within <literal>pg_upgrade_output.d</literal> in
771-
the directory of the new cluster.
771+
the directory of the new cluster. Each run creates a new subdirectory named
772+
with a timestamp formatted as per ISO 8601
773+
(<literal>%Y%m%dT%H%M%S</literal>), where all the generated files are
774+
stored.
772775
</para>
773776

774777
<para>

src/bin/pg_upgrade/check.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,8 @@ report_clusters_compatible(void)
210210
pg_log(PG_REPORT, "\n*Clusters are compatible*\n");
211211
/* stops new cluster */
212212
stop_postmaster(false);
213+
214+
cleanup_output_dirs();
213215
exit(0);
214216
}
215217

src/bin/pg_upgrade/pg_upgrade.c

Lines changed: 46 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ static void copy_xact_xlog_xid(void);
5858
static void set_frozenxids(bool minmxid_only);
5959
static void make_outputdirs(char *pgdata);
6060
static void setup(char *argv0, bool *live_check);
61-
static void cleanup(void);
6261

6362
ClusterInfo old_cluster,
6463
new_cluster;
@@ -204,7 +203,7 @@ main(int argc, char **argv)
204203

205204
pg_free(deletion_script_file_name);
206205

207-
cleanup();
206+
cleanup_output_dirs();
208207

209208
return 0;
210209
}
@@ -221,19 +220,54 @@ make_outputdirs(char *pgdata)
221220
char **filename;
222221
time_t run_time = time(NULL);
223222
char filename_path[MAXPGPATH];
223+
char timebuf[128];
224+
struct timeval time;
225+
time_t tt;
226+
int len;
227+
228+
log_opts.rootdir = (char *) pg_malloc0(MAXPGPATH);
229+
len = snprintf(log_opts.rootdir, MAXPGPATH, "%s/%s", pgdata, BASE_OUTPUTDIR);
230+
if (len >= MAXPGPATH)
231+
pg_fatal("buffer for root directory too small");
232+
233+
/* BASE_OUTPUTDIR/$timestamp/ */
234+
gettimeofday(&time, NULL);
235+
tt = (time_t) time.tv_sec;
236+
strftime(timebuf, sizeof(timebuf), "%Y%m%dT%H%M%S", localtime(&tt));
237+
/* append milliseconds */
238+
snprintf(timebuf + strlen(timebuf), sizeof(timebuf) - strlen(timebuf),
239+
".%03d", (int) (time.tv_usec / 1000));
240+
log_opts.basedir = (char *) pg_malloc0(MAXPGPATH);
241+
len = snprintf(log_opts.basedir, MAXPGPATH, "%s/%s", log_opts.rootdir,
242+
timebuf);
243+
if (len >= MAXPGPATH)
244+
pg_fatal("buffer for base directory too small");
245+
246+
/* BASE_OUTPUTDIR/$timestamp/dump/ */
247+
log_opts.dumpdir = (char *) pg_malloc0(MAXPGPATH);
248+
len = snprintf(log_opts.dumpdir, MAXPGPATH, "%s/%s/%s", log_opts.rootdir,
249+
timebuf, DUMP_OUTPUTDIR);
250+
if (len >= MAXPGPATH)
251+
pg_fatal("buffer for dump directory too small");
252+
253+
/* BASE_OUTPUTDIR/$timestamp/log/ */
254+
log_opts.logdir = (char *) pg_malloc0(MAXPGPATH);
255+
len = snprintf(log_opts.logdir, MAXPGPATH, "%s/%s/%s", log_opts.rootdir,
256+
timebuf, LOG_OUTPUTDIR);
257+
if (len >= MAXPGPATH)
258+
pg_fatal("buffer for log directory too small");
224259

225-
log_opts.basedir = (char *) pg_malloc(MAXPGPATH);
226-
snprintf(log_opts.basedir, MAXPGPATH, "%s/%s", pgdata, BASE_OUTPUTDIR);
227-
log_opts.dumpdir = (char *) pg_malloc(MAXPGPATH);
228-
snprintf(log_opts.dumpdir, MAXPGPATH, "%s/%s", pgdata, DUMP_OUTPUTDIR);
229-
log_opts.logdir = (char *) pg_malloc(MAXPGPATH);
230-
snprintf(log_opts.logdir, MAXPGPATH, "%s/%s", pgdata, LOG_OUTPUTDIR);
231-
232-
if (mkdir(log_opts.basedir, pg_dir_create_mode))
260+
/*
261+
* Ignore the error case where the root path exists, as it is kept the
262+
* same across runs.
263+
*/
264+
if (mkdir(log_opts.rootdir, pg_dir_create_mode) < 0 && errno != EEXIST)
265+
pg_fatal("could not create directory \"%s\": %m\n", log_opts.rootdir);
266+
if (mkdir(log_opts.basedir, pg_dir_create_mode) < 0)
233267
pg_fatal("could not create directory \"%s\": %m\n", log_opts.basedir);
234-
if (mkdir(log_opts.dumpdir, pg_dir_create_mode))
268+
if (mkdir(log_opts.dumpdir, pg_dir_create_mode) < 0)
235269
pg_fatal("could not create directory \"%s\": %m\n", log_opts.dumpdir);
236-
if (mkdir(log_opts.logdir, pg_dir_create_mode))
270+
if (mkdir(log_opts.logdir, pg_dir_create_mode) < 0)
237271
pg_fatal("could not create directory \"%s\": %m\n", log_opts.logdir);
238272

239273
snprintf(filename_path, sizeof(filename_path), "%s/%s", log_opts.logdir,
@@ -745,14 +779,3 @@ set_frozenxids(bool minmxid_only)
745779

746780
check_ok();
747781
}
748-
749-
750-
static void
751-
cleanup(void)
752-
{
753-
fclose(log_opts.internal);
754-
755-
/* Remove dump and log files? */
756-
if (!log_opts.retain)
757-
(void) rmtree(log_opts.basedir, true);
758-
}

src/bin/pg_upgrade/pg_upgrade.h

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,14 @@
3030
#define DB_DUMP_FILE_MASK "pg_upgrade_dump_%u.custom"
3131

3232
/*
33-
* Base directories that include all the files generated internally,
34-
* from the root path of the new cluster.
33+
* Base directories that include all the files generated internally, from the
34+
* root path of the new cluster. The paths are dynamically built as of
35+
* BASE_OUTPUTDIR/$timestamp/{LOG_OUTPUTDIR,DUMP_OUTPUTDIR} to ensure their
36+
* uniqueness in each run.
3537
*/
3638
#define BASE_OUTPUTDIR "pg_upgrade_output.d"
37-
#define LOG_OUTPUTDIR BASE_OUTPUTDIR "/log"
38-
#define DUMP_OUTPUTDIR BASE_OUTPUTDIR "/dump"
39+
#define LOG_OUTPUTDIR "log"
40+
#define DUMP_OUTPUTDIR "dump"
3941

4042
#define DB_DUMP_LOG_FILE_MASK "pg_upgrade_dump_%u.log"
4143
#define SERVER_LOG_FILE "pg_upgrade_server.log"
@@ -276,7 +278,8 @@ typedef struct
276278
bool verbose; /* true -> be verbose in messages */
277279
bool retain; /* retain log files on success */
278280
/* Set of internal directories for output files */
279-
char *basedir; /* Base output directory */
281+
char *rootdir; /* Root directory, aka pg_upgrade_output.d */
282+
char *basedir; /* Base output directory, with timestamp */
280283
char *dumpdir; /* Dumps */
281284
char *logdir; /* Log files */
282285
bool isatty; /* is stdout a tty */
@@ -432,6 +435,7 @@ void report_status(eLogType type, const char *fmt,...) pg_attribute_printf(2, 3
432435
void pg_log(eLogType type, const char *fmt,...) pg_attribute_printf(2, 3);
433436
void pg_fatal(const char *fmt,...) pg_attribute_printf(1, 2) pg_attribute_noreturn();
434437
void end_progress_output(void);
438+
void cleanup_output_dirs(void);
435439
void prep_status(const char *fmt,...) pg_attribute_printf(1, 2);
436440
void prep_status_progress(const char *fmt,...) pg_attribute_printf(1, 2);
437441
unsigned int str2uint(const char *str);

src/bin/pg_upgrade/t/002_pg_upgrade.pl

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
use Cwd qw(abs_path);
66
use File::Basename qw(dirname);
77
use File::Compare;
8+
use File::Find qw(find);
9+
use File::Path qw(rmtree);
810

911
use PostgreSQL::Test::Cluster;
1012
use PostgreSQL::Test::Utils;
@@ -213,6 +215,39 @@ sub generate_db
213215

214216
# Upgrade the instance.
215217
$oldnode->stop;
218+
219+
# Cause a failure at the start of pg_upgrade, this should create the logging
220+
# directory pg_upgrade_output.d but leave it around. Keep --check for an
221+
# early exit.
222+
command_fails(
223+
[
224+
'pg_upgrade', '--no-sync',
225+
'-d', $oldnode->data_dir,
226+
'-D', $newnode->data_dir,
227+
'-b', $oldbindir . '/does/not/exist/',
228+
'-B', $newbindir,
229+
'-p', $oldnode->port,
230+
'-P', $newnode->port,
231+
'--check'
232+
],
233+
'run of pg_upgrade --check for new instance with incorrect binary path');
234+
ok(-d $newnode->data_dir . "/pg_upgrade_output.d",
235+
"pg_upgrade_output.d/ not removed after pg_upgrade failure");
236+
rmtree($newnode->data_dir . "/pg_upgrade_output.d");
237+
238+
# --check command works here, cleans up pg_upgrade_output.d.
239+
command_ok(
240+
[
241+
'pg_upgrade', '--no-sync', '-d', $oldnode->data_dir,
242+
'-D', $newnode->data_dir, '-b', $oldbindir,
243+
'-B', $newbindir, '-p', $oldnode->port,
244+
'-P', $newnode->port, '--check'
245+
],
246+
'run of pg_upgrade --check for new instance');
247+
ok( !-d $newnode->data_dir . "/pg_upgrade_output.d",
248+
"pg_upgrade_output.d/ not removed after pg_upgrade --check success");
249+
250+
# Actual run, pg_upgrade_output.d is removed at the end.
216251
command_ok(
217252
[
218253
'pg_upgrade', '--no-sync', '-d', $oldnode->data_dir,
@@ -221,14 +256,24 @@ sub generate_db
221256
'-P', $newnode->port
222257
],
223258
'run of pg_upgrade for new instance');
259+
ok( !-d $newnode->data_dir . "/pg_upgrade_output.d",
260+
"pg_upgrade_output.d/ removed after pg_upgrade success");
261+
224262
$newnode->start;
225263

226264
# Check if there are any logs coming from pg_upgrade, that would only be
227265
# retained on failure.
228-
my $log_path = $newnode->data_dir . "/pg_upgrade_output.d/log";
266+
my $log_path = $newnode->data_dir . "/pg_upgrade_output.d";
229267
if (-d $log_path)
230268
{
231-
foreach my $log (glob("$log_path/*"))
269+
my @log_files;
270+
find(
271+
sub {
272+
push @log_files, $File::Find::name
273+
if $File::Find::name =~ m/.*\.log/;
274+
},
275+
$newnode->data_dir . "/pg_upgrade_output.d");
276+
foreach my $log (@log_files)
232277
{
233278
note "=== contents of $log ===\n";
234279
print slurp_file($log);

src/bin/pg_upgrade/util.c

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,48 @@ end_progress_output(void)
5555
pg_log(PG_REPORT, "%-*s", MESSAGE_WIDTH, "");
5656
}
5757

58+
/*
59+
* Remove any logs generated internally. To be used once when exiting.
60+
*/
61+
void
62+
cleanup_output_dirs(void)
63+
{
64+
fclose(log_opts.internal);
65+
66+
/* Remove dump and log files? */
67+
if (log_opts.retain)
68+
return;
69+
70+
(void) rmtree(log_opts.basedir, true);
71+
72+
/* Remove pg_upgrade_output.d only if empty */
73+
switch (pg_check_dir(log_opts.rootdir))
74+
{
75+
case 0: /* non-existent */
76+
case 3: /* exists and contains a mount point */
77+
Assert(false);
78+
break;
79+
80+
case 1: /* exists and empty */
81+
case 2: /* exists and contains only dot files */
82+
(void) rmtree(log_opts.rootdir, true);
83+
break;
84+
85+
case 4: /* exists */
86+
87+
/*
88+
* Keep the root directory as this includes some past log
89+
* activity.
90+
*/
91+
break;
92+
93+
default:
94+
/* different failure, just report it */
95+
pg_log(PG_WARNING, "could not access directory \"%s\": %m",
96+
log_opts.rootdir);
97+
break;
98+
}
99+
}
58100

59101
/*
60102
* prep_status

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