v1-0003-Add-function-to-make-pg_rewind-to-support-TDE.patch
application/octet-stream
Filename: v1-0003-Add-function-to-make-pg_rewind-to-support-TDE.patch
Type: application/octet-stream
Part: 2
Patch
Same data as JSON:
GET /api/v1/attachments/:id/patch
the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes.
API reference →
Format: unified
Series: patch v1-0003
| File | + | − |
|---|---|---|
| src/bin/pg_rewind/compat.c | 125 | 0 |
| src/bin/pg_rewind/Makefile | 9 | 3 |
| src/bin/pg_rewind/parsexlog.c | 14 | 0 |
| src/bin/pg_rewind/pg_rewind.c | 15 | 1 |
| src/bin/pg_rewind/t/007_basic_tde.pl | 175 | 0 |
| src/bin/pg_rewind/t/RewindTest.pm | 49 | 4 |
diff --git a/src/bin/pg_rewind/Makefile b/src/bin/pg_rewind/Makefile
index 7a97df6..b214501 100644
--- a/src/bin/pg_rewind/Makefile
+++ b/src/bin/pg_rewind/Makefile
@@ -18,9 +18,12 @@ include $(top_builddir)/src/Makefile.global
override CPPFLAGS := -I$(libpq_srcdir) -DFRONTEND $(CPPFLAGS)
LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils $(libpq_pgport)
+ENCRYPTIONSOURCES = $(sort $(notdir $(wildcard $(top_srcdir)/src/backend/storage/encryption/*.c)))
+ENCRYPTIONOBJS = $(patsubst %.c,%.o,$(ENCRYPTIONSOURCES))
+
OBJS = pg_rewind.o parsexlog.o xlogreader.o datapagemap.o timeline.o \
- fetch.o file_ops.o copy_fetch.o libpq_fetch.o filemap.o \
- $(WIN32RES)
+ fetch.o file_ops.o copy_fetch.o libpq_fetch.o filemap.o compat.o \
+ $(ENCRYPTIONOBJS) $(WIN32RES)
EXTRA_CLEAN = xlogreader.c
@@ -32,6 +35,9 @@ pg_rewind: $(OBJS) | submake-libpq submake-libpgport
xlogreader.c: % : $(top_srcdir)/src/backend/access/transam/%
rm -f $@ && $(LN_S) $< .
+$(ENCRYPTIONSOURCES): % : $(top_srcdir)/src/backend/storage/encryption/%
+ rm -f $@ && $(LN_S) $< .
+
install: all installdirs
$(INSTALL_PROGRAM) pg_rewind$(X) '$(DESTDIR)$(bindir)/pg_rewind$(X)'
@@ -42,7 +48,7 @@ uninstall:
rm -f '$(DESTDIR)$(bindir)/pg_rewind$(X)'
clean distclean maintainer-clean:
- rm -f pg_rewind$(X) $(OBJS) xlogreader.c
+ rm -f pg_rewind$(X) $(OBJS) $(ENCRYPTIONSOURCES) xlogreader.c
rm -rf tmp_check
check:
diff --git a/src/bin/pg_rewind/compat.c b/src/bin/pg_rewind/compat.c
new file mode 100644
index 0000000..1555176
--- /dev/null
+++ b/src/bin/pg_rewind/compat.c
@@ -0,0 +1,125 @@
+/*-------------------------------------------------------------------------
+ *
+ * compat.c
+ * Reimplementations of various backend functions.
+ *
+ * Portions Copyright (c) 2013-2019, PostgreSQL Global Development Group
+ *
+ * IDENTIFICATION
+ * src/bin/pg_waldump/compat.c
+ *
+ * This file contains client-side implementations for various backend
+ * functions that the rm_desc functions in *desc.c files rely on.
+ *
+ *-------------------------------------------------------------------------
+ */
+
+/* ugly hack, same as in e.g pg_controldata */
+#define FRONTEND 1
+#include "postgres.h"
+
+#include <time.h>
+
+#include "lib/stringinfo.h"
+#include "utils/datetime.h"
+#include "common/logging.h"
+
+/* copied from timestamp.c */
+pg_time_t
+timestamptz_to_time_t(TimestampTz t)
+{
+ pg_time_t result;
+
+ result = (pg_time_t) (t / USECS_PER_SEC +
+ ((POSTGRES_EPOCH_JDATE - UNIX_EPOCH_JDATE) * SECS_PER_DAY));
+ return result;
+}
+
+/*
+ * Stopgap implementation of timestamptz_to_str that doesn't depend on backend
+ * infrastructure. This will work for timestamps that are within the range
+ * of the platform time_t type. (pg_time_t is compatible except for possibly
+ * being wider.)
+ *
+ * XXX the return value points to a static buffer, so beware of using more
+ * than one result value concurrently.
+ *
+ * XXX: The backend timestamp infrastructure should instead be split out and
+ * moved into src/common. That's a large project though.
+ */
+const char *
+timestamptz_to_str(TimestampTz dt)
+{
+ static char buf[MAXDATELEN + 1];
+ char ts[MAXDATELEN + 1];
+ char zone[MAXDATELEN + 1];
+ time_t result = (time_t) timestamptz_to_time_t(dt);
+ struct tm *ltime = localtime(&result);
+
+ strftime(ts, sizeof(ts), "%Y-%m-%d %H:%M:%S", ltime);
+ strftime(zone, sizeof(zone), "%Z", ltime);
+
+ snprintf(buf, sizeof(buf), "%s.%06d %s",
+ ts, (int) (dt % USECS_PER_SEC), zone);
+
+ return buf;
+}
+
+/*
+ * Provide a hacked up compat layer for StringInfos so xlog desc functions can
+ * be linked/called.
+ */
+void
+initStringInfo(StringInfo str)
+{
+ int size = 1024; /* initial default buffer size */
+
+ str->data = (char *) palloc(size);
+ str->maxlen = size;
+ str->data[0] = '\0';
+ str->len = 0;
+ str->cursor = 0;
+}
+
+void
+appendStringInfo(StringInfo str, const char *fmt,...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ if (str)
+ {
+ int avail;
+ size_t nprinted;
+
+ avail = str->maxlen - str->len;
+ nprinted = pvsnprintf(str->data + str->len, (size_t) avail, fmt, args);
+
+ if (nprinted < (size_t) avail)
+ {
+ /* Success. Note nprinted does not include trailing null. */
+ str->len += (int) nprinted;
+ }
+ else
+ {
+ pg_log_error("the string is too long");
+ exit(1);
+ }
+
+ }
+ else
+ vprintf(fmt, args);
+ va_end(args);
+}
+
+void
+appendStringInfoString(StringInfo str, const char *string)
+{
+ appendStringInfo(str, "%s", string);
+}
+
+void
+appendStringInfoChar(StringInfo str, char ch)
+{
+ appendStringInfo(str, "%c", ch);
+}
diff --git a/src/bin/pg_rewind/parsexlog.c b/src/bin/pg_rewind/parsexlog.c
index 1d03375..e3557de 100644
--- a/src/bin/pg_rewind/parsexlog.c
+++ b/src/bin/pg_rewind/parsexlog.c
@@ -21,6 +21,8 @@
#include "commands/dbcommands_xlog.h"
#include "filemap.h"
#include "pg_rewind.h"
+#include "catalog/pg_control.h"
+#include "storage/encryption.h"
/*
* RmgrNames is an array of resource manager names, to make error messages
@@ -312,6 +314,18 @@ SimpleXLogPageRead(XLogReaderState *xlogreader, XLogRecPtr targetPagePtr,
return -1;
}
+ if (DataEncryptionEnabled())
+ {
+ XLogSegNo readSegNo;
+ uint32 readSegOff;
+
+ XLByteToSeg(targetPagePtr, readSegNo, WalSegSz);
+ readSegOff = targetPagePtr % WalSegSz;
+
+ DecryptXLog(readBuf, XLOG_BLCKSZ, readSegNo, readSegOff);
+ }
+
+
Assert(targetSegNo == xlogreadsegno);
xlogreader->seg.ws_tli = targetHistory[private->tliIndex].tli;
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index e75c7ee..370d00e 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -7,6 +7,7 @@
*
*-------------------------------------------------------------------------
*/
+#define FRONTEND 1
#include "postgres_fe.h"
#include <sys/stat.h>
@@ -74,6 +75,8 @@ usage(const char *progname)
printf(_("%s resynchronizes a PostgreSQL cluster with another copy of the cluster.\n\n"), progname);
printf(_("Usage:\n %s [OPTION]...\n\n"), progname);
printf(_("Options:\n"));
+ printf(_(" -c --cluster-passphrase-command=COMMAND\n"
+ " set command to obtain passphrase for data encryption key\n"));
printf(_(" -D, --target-pgdata=DIRECTORY existing data directory to modify\n"));
printf(_(" --source-pgdata=DIRECTORY source data directory to synchronize with\n"));
printf(_(" --source-server=CONNSTR source server to synchronize with\n"));
@@ -96,6 +99,7 @@ main(int argc, char **argv)
{
static struct option long_options[] = {
{"help", no_argument, NULL, '?'},
+ {"cluster-passphrase-command", required_argument, NULL, 'c'},
{"target-pgdata", required_argument, NULL, 'D'},
{"write-recovery-conf", no_argument, NULL, 'R'},
{"source-pgdata", required_argument, NULL, 1},
@@ -123,6 +127,7 @@ main(int argc, char **argv)
TimeLineID endtli;
ControlFileData ControlFile_new;
bool writerecoveryconf = false;
+ char *cluster_passphrase = NULL;
pg_logging_init(argv[0]);
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_rewind"));
@@ -143,7 +148,7 @@ main(int argc, char **argv)
}
}
- while ((c = getopt_long(argc, argv, "D:nNPR", long_options, &option_index)) != -1)
+ while ((c = getopt_long(argc, argv, "c:D:nNPR", long_options, &option_index)) != -1)
{
switch (c)
{
@@ -151,6 +156,9 @@ main(int argc, char **argv)
fprintf(stderr, _("Try \"%s --help\" for more information.\n"), progname);
exit(1);
+ case 'c':
+ cluster_passphrase = pg_strdup(optarg);
+ break;
case 'P':
showprogress = true;
break;
@@ -242,6 +250,12 @@ main(int argc, char **argv)
}
#endif
+ if (datadir_target)
+ getKmgr(datadir_target, cluster_passphrase, progname);
+ if (datadir_source)
+ getKmgr(datadir_source, cluster_passphrase, progname);
+
+
get_restricted_token();
/* Set mask based on PGDATA permissions */
diff --git a/src/bin/pg_rewind/t/007_basic_tde.pl b/src/bin/pg_rewind/t/007_basic_tde.pl
new file mode 100644
index 0000000..127dbf6
--- /dev/null
+++ b/src/bin/pg_rewind/t/007_basic_tde.pl
@@ -0,0 +1,175 @@
+use strict;
+use warnings;
+use TestLib;
+use Test::More tests => 11;
+
+use FindBin;
+use lib $FindBin::RealBin;
+
+use RewindTest;
+
+sub run_test
+{
+ my $test_mode = shift;
+
+ RewindTest::setup_cluster($test_mode);
+ RewindTest::start_master();
+
+ # Create a test table and insert a row in master.
+ master_psql("CREATE TABLE tbl1 (d text)");
+ master_psql("INSERT INTO tbl1 VALUES ('in master')");
+
+ # This test table will be used to test truncation, i.e. the table
+ # is extended in the old master after promotion
+ master_psql("CREATE TABLE trunc_tbl (d text)");
+ master_psql("INSERT INTO trunc_tbl VALUES ('in master')");
+
+ # This test table will be used to test the "copy-tail" case, i.e. the
+ # table is truncated in the old master after promotion
+ master_psql("CREATE TABLE tail_tbl (id integer, d text)");
+ master_psql("INSERT INTO tail_tbl VALUES (0, 'in master')");
+
+ master_psql("CHECKPOINT");
+
+ RewindTest::create_standby($test_mode);
+
+ # Insert additional data on master that will be replicated to standby
+ master_psql("INSERT INTO tbl1 values ('in master, before promotion')");
+ master_psql(
+ "INSERT INTO trunc_tbl values ('in master, before promotion')");
+ master_psql(
+ "INSERT INTO tail_tbl SELECT g, 'in master, before promotion: ' || g FROM generate_series(1, 10000) g"
+ );
+
+ master_psql('CHECKPOINT');
+
+ RewindTest::promote_standby();
+
+ # Insert a row in the old master. This causes the master and standby
+ # to have "diverged", it's no longer possible to just apply the
+ # standy's logs over master directory - you need to rewind.
+ master_psql("INSERT INTO tbl1 VALUES ('in master, after promotion')");
+
+ # Also insert a new row in the standby, which won't be present in the
+ # old master.
+ standby_psql("INSERT INTO tbl1 VALUES ('in standby, after promotion')");
+
+ # Insert enough rows to trunc_tbl to extend the file. pg_rewind should
+ # truncate it back to the old size.
+ master_psql(
+ "INSERT INTO trunc_tbl SELECT 'in master, after promotion: ' || g FROM generate_series(1, 10000) g"
+ );
+
+ # Truncate tail_tbl. pg_rewind should copy back the truncated part
+ # (We cannot use an actual TRUNCATE command here, as that creates a
+ # whole new relfilenode)
+ master_psql("DELETE FROM tail_tbl WHERE id > 10");
+ master_psql("VACUUM tail_tbl");
+
+ # Before running pg_rewind, do a couple of extra tests with several
+ # option combinations. As the code paths taken by those tests
+ # do not change for the "local" and "remote" modes, just run them
+ # in "local" mode for simplicity's sake.
+ if ($test_mode eq 'local')
+ {
+ my $master_pgdata = $node_master->data_dir;
+ my $standby_pgdata = $node_standby->data_dir;
+
+ # First check that pg_rewind fails if the target cluster is
+ # not stopped as it fails to start up for the forced recovery
+ # step.
+ command_fails(
+ [
+ 'pg_rewind', '--debug',
+ '--source-pgdata', $standby_pgdata,
+ '--target-pgdata', $master_pgdata,
+ '--no-sync'
+ ],
+ 'pg_rewind with running target');
+
+ # Again with --no-ensure-shutdown, which should equally fail.
+ # This time pg_rewind complains without attempting to perform
+ # recovery once.
+ command_fails(
+ [
+ 'pg_rewind', '--debug',
+ '--source-pgdata', $standby_pgdata,
+ '--target-pgdata', $master_pgdata,
+ '--no-sync', '--no-ensure-shutdown'
+ ],
+ 'pg_rewind --no-ensure-shutdown with running target');
+
+ # Stop the target, and attempt to run with a local source
+ # still running. This fails as pg_rewind requires to have
+ # a source cleanly stopped.
+ $node_master->stop;
+ command_fails(
+ [
+ 'pg_rewind', '--debug',
+ '--source-pgdata', $standby_pgdata,
+ '--target-pgdata', $master_pgdata,
+ '--no-sync', '--no-ensure-shutdown'
+ ],
+ 'pg_rewind with unexpected running source');
+
+ # Stop the target cluster cleanly, and run again pg_rewind
+ # with --dry-run mode. If anything gets generated in the data
+ # folder, the follow-up run of pg_rewind will most likely fail,
+ # so keep this test as the last one of this subset.
+ $node_standby->stop;
+ command_ok(
+ [
+ 'pg_rewind', '--debug',
+ '--source-pgdata', $standby_pgdata,
+ '--target-pgdata', $master_pgdata,
+ '--no-sync', '--dry-run'
+ ],
+ 'pg_rewind --dry-run');
+
+ # Both clusters need to be alive moving forward.
+ $node_standby->start;
+ $node_master->start;
+ }
+
+ RewindTest::run_pg_rewind($test_mode);
+
+ check_query(
+ 'SELECT * FROM tbl1',
+ qq(in master
+in master, before promotion
+in standby, after promotion
+),
+ 'table content');
+
+ check_query(
+ 'SELECT * FROM trunc_tbl',
+ qq(in master
+in master, before promotion
+),
+ 'truncation');
+
+ check_query(
+ 'SELECT count(*) FROM tail_tbl',
+ qq(10001
+),
+ 'tail-copy');
+
+ # Permissions on PGDATA should be default
+ SKIP:
+ {
+ skip "unix-style permissions not supported on Windows", 1
+ if ($windows_os);
+
+ ok(check_mode_recursive($node_master->data_dir(), 0700, 0600),
+ 'check PGDATA permissions');
+ }
+
+ RewindTest::clean_rewind_test();
+ return;
+}
+
+# Run the test in both modes
+run_test('local-tde');
+run_test('remote-tde');
+
+exit(0);
diff --git a/src/bin/pg_rewind/t/RewindTest.pm b/src/bin/pg_rewind/t/RewindTest.pm
index 82fa220..43ea17a 100644
--- a/src/bin/pg_rewind/t/RewindTest.pm
+++ b/src/bin/pg_rewind/t/RewindTest.pm
@@ -129,10 +129,21 @@ sub setup_cluster
# Set up pg_hba.conf and pg_ident.conf for the role running
# pg_rewind. This role is used for all the tests, and has
# minimal permissions enough to rewind from an online source.
- $node_master->init(
- allows_streaming => 1,
- extra => $extra,
- auth_extra => [ '--create-role', 'rewind_user' ]);
+ if (($extra_name eq "local-tde") or ($extra_name eq "remote-tde"))
+ {
+ $node_master->init(
+ allows_streaming => 1,
+ extra => $extra,
+ auth_extra => [ '--create-role', 'rewind_user' ],
+ enable_encryption => 1);
+ }
+ else
+ {
+ $node_master->init(
+ allows_streaming => 1,
+ extra => $extra,
+ auth_extra => [ '--create-role', 'rewind_user' ]);
+ }
# Set wal_keep_segments to prevent WAL segment recycling after enforced
# checkpoints in the tests.
@@ -284,6 +295,40 @@ sub run_pg_rewind
$node_standby->safe_psql('postgres',
"ALTER ROLE rewind_user WITH REPLICATION;");
}
+ elsif ($test_mode eq "local-tde")
+ {
+
+ $node_standby->stop;
+ command_ok(
+ [
+ 'pg_rewind',
+ "--debug",
+ "--cluster-passphrase-command", 'echo "password"',
+ "--source-pgdata=$standby_pgdata",
+ "--target-pgdata=$master_pgdata",
+ "--no-sync"
+ ],
+ 'pg_rewind local');
+ }
+ elsif ($test_mode eq "remote-tde")
+ {
+
+ command_ok(
+ [
+ 'pg_rewind', "--debug",
+ "--cluster-passphrase-command", 'echo "password"',
+ "--source-server", $standby_connstr,
+ "--target-pgdata=$master_pgdata", "--no-sync",
+ "--write-recovery-conf"
+ ],
+ 'pg_rewind remote');
+
+ ok( -e "$master_pgdata/standby.signal",
+ 'standby.signal created after pg_rewind');
+
+ $node_standby->safe_psql('postgres',
+ "ALTER ROLE rewind_user WITH REPLICATION;");
+ }
else
{