Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
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

+4-1
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

+2
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

+46-23
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

+9-5
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

+47-2
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

+42
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)