Skip to content

PG-1411 Make pg_resetwal work with TDE #476

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: TDE_REL_17_STABLE
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
150 changes: 150 additions & 0 deletions contrib/pg_tde/t/pg_resetwal_basic.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@

# Copyright (c) 2021-2024, PostgreSQL Global Development Group

use strict;
use warnings FATAL => 'all';

use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;

unlink('/tmp/pg_resetwal_basic.per');

my $node = PostgreSQL::Test::Cluster->new('main');
$node->init;
$node->append_conf(
'postgresql.conf', q{
track_commit_timestamp = on

# WAL Encryption
shared_preload_libraries = 'pg_tde'
});

$node->start;
$node->safe_psql('postgres', "CREATE EXTENSION pg_tde;");
$node->safe_psql('postgres',
"SELECT pg_tde_add_global_key_provider_file('file-keyring-wal', '/tmp/pg_resetwal_basic.per');"
);
$node->safe_psql('postgres',
"SELECT pg_tde_create_key_using_global_key_provider('server-key', 'file-keyring-wal');"
);
$node->safe_psql('postgres',
"SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-wal');"
);

$node->append_conf(
'postgresql.conf', q{
pg_tde.wal_encrypt = on
});
$node->stop;


command_like([ 'pg_resetwal', '-n', $node->data_dir ],
qr/checkpoint/, 'pg_resetwal -n produces output');


# Permissions on PGDATA should be default
SKIP:
{
skip "unix-style permissions not supported on Windows", 1
if ($windows_os);

ok(check_mode_recursive($node->data_dir, 0700, 0600),
'check PGDATA permissions');
}

command_ok([ 'pg_resetwal', '-D', $node->data_dir ], 'pg_resetwal runs');
$node->start;
is($node->safe_psql("postgres", "SELECT 1;"),
1, 'server running and working after reset');

command_fails_like(
[ 'pg_resetwal', $node->data_dir ],
qr/lock file .* exists/,
'fails if server running');

$node->stop('immediate');
command_fails_like(
[ 'pg_resetwal', $node->data_dir ],
qr/database server was not shut down cleanly/,
'does not run after immediate shutdown');
command_ok(
[ 'pg_resetwal', '-f', $node->data_dir ],
'runs after immediate shutdown with force');
$node->start;
is($node->safe_psql("postgres", "SELECT 1;"),
1, 'server running and working after forced reset');

$node->stop;

# check various command-line handling

# Note: This test intends to check that a nonexistent data directory
# gives a reasonable error message. Because of the way the code is
# currently structured, you get an error about readings permissions,
# which is perhaps suboptimal, so feel free to update this test if
# this gets improved.


# run with control override options

my $out = (run_command([ 'pg_resetwal', '-n', $node->data_dir ]))[0];
$out =~ /^Database block size: *(\d+)$/m or die;
my $blcksz = $1;

my @cmd = ('pg_resetwal', '-D', $node->data_dir);

# some not-so-critical hardcoded values
push @cmd, '-e', 1;
push @cmd, '-l', '00000001000000320000004B';
push @cmd, '-o', 100_000;
push @cmd, '--wal-segsize', 1;

# these use the guidance from the documentation

sub get_slru_files
{
opendir(my $dh, $node->data_dir . '/' . $_[0]) or die $!;
my @files = sort grep { /[0-9A-F]+/ } readdir $dh;
closedir $dh;
return @files;
}

my (@files, $mult);

@files = get_slru_files('pg_commit_ts');
# XXX: Should there be a multiplier, similar to the other options?
# -c argument is "old,new"
push @cmd,
'-c',
sprintf("%d,%d", hex($files[0]) == 0 ? 3 : hex($files[0]), hex($files[-1]));

@files = get_slru_files('pg_multixact/offsets');
$mult = 32 * $blcksz / 4;
# -m argument is "new,old"
push @cmd, '-m',
sprintf("%d,%d",
(hex($files[-1]) + 1) * $mult,
hex($files[0]) == 0 ? 1 : hex($files[0] * $mult));

@files = get_slru_files('pg_multixact/members');
$mult = 32 * int($blcksz / 20) * 4;
push @cmd, '-O', (hex($files[-1]) + 1) * $mult;

@files = get_slru_files('pg_xact');
$mult = 32 * $blcksz * 4;
push @cmd,
'-u', (hex($files[0]) == 0 ? 3 : hex($files[0]) * $mult),
'-x', ((hex($files[-1]) + 1) * $mult);

command_ok([ @cmd, '-n' ], 'runs with control override options, dry run');
command_ok(\@cmd, 'runs with control override options');
command_like(
[ 'pg_resetwal', '-n', $node->data_dir ],
qr/^Latest checkpoint's NextOID: *100000$/m,
'spot check that control changes were applied');

$node->start;
ok(1, 'server started after reset');

done_testing();
92 changes: 92 additions & 0 deletions contrib/pg_tde/t/pg_resetwal_corrupted.pl
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@

# Copyright (c) 2021-2024, PostgreSQL Global Development Group

# Tests for handling a corrupted pg_control

use strict;
use warnings FATAL => 'all';

use PostgreSQL::Test::Cluster;
use PostgreSQL::Test::Utils;
use Test::More;

unlink('/tmp/pg_resetwal_corrupted.per');

my $node = PostgreSQL::Test::Cluster->new('main');
$node->init;
$node->append_conf(
'postgresql.conf', q{

# WAL Encryption
shared_preload_libraries = 'pg_tde'
});

$node->start;
$node->safe_psql('postgres', "CREATE EXTENSION pg_tde;");
$node->safe_psql('postgres',
"SELECT pg_tde_add_global_key_provider_file('file-keyring-wal', '/tmp/pg_waldump_corrupted.per');"
);
$node->safe_psql('postgres',
"SELECT pg_tde_create_key_using_global_key_provider('server-key', 'file-keyring-wal');"
);
$node->safe_psql('postgres',
"SELECT pg_tde_set_server_key_using_global_key_provider('server-key', 'file-keyring-wal');"
);

$node->append_conf(
'postgresql.conf', q{
pg_tde.wal_encrypt = on
});
$node->stop;

my $pg_control = $node->data_dir . '/global/pg_control';
my $size = -s $pg_control;

# Read out the head of the file to get PG_CONTROL_VERSION in
# particular.
my $data;
open my $fh, '<', $pg_control or BAIL_OUT($!);
binmode $fh;
read $fh, $data, 16 or die $!;
close $fh;

# Fill pg_control with zeros
open $fh, '>', $pg_control or BAIL_OUT($!);
binmode $fh;
print $fh pack("x[$size]");
close $fh;

command_checks_all(
[ 'pg_resetwal', '-n', $node->data_dir ],
0,
[qr/pg_control version number/],
[
qr/pg_resetwal: warning: pg_control exists but is broken or wrong version; ignoring it/
],
'processes corrupted pg_control all zeroes');

# Put in the previously saved header data. This uses a different code
# path internally, allowing us to process a zero WAL segment size.
open $fh, '>', $pg_control or BAIL_OUT($!);
binmode $fh;
print $fh $data, pack("x[" . ($size - 16) . "]");
close $fh;

command_checks_all(
[ 'pg_resetwal', '-n', $node->data_dir ],
0,
[qr/pg_control version number/],
[
qr/\Qpg_resetwal: warning: pg_control specifies invalid WAL segment size (0 bytes); proceed with caution\E/
],
'processes zero WAL segment size');

# now try to run it
command_fails_like(
[ 'pg_resetwal', $node->data_dir ],
qr/not proceeding because control file values were guessed/,
'does not run when control file values were guessed');
command_ok([ 'pg_resetwal', '-f', $node->data_dir ],
'runs with force when control file values were guessed');

done_testing();
3 changes: 3 additions & 0 deletions src/bin/pg_resetwal/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
/pg_resetwal
/tmp_check/

# Source files copied from src/backend/access/transam/
/xlogreader.c
17 changes: 16 additions & 1 deletion src/bin/pg_resetwal/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,29 @@ subdir = src/bin/pg_resetwal
top_builddir = ../../..
include $(top_builddir)/src/Makefile.global

override CPPFLAGS := -DFRONTEND $(CPPFLAGS)
LDFLAGS_INTERNAL += -L$(top_builddir)/src/fe_utils -lpgfeutils

OBJS = \
$(WIN32RES) \
xlogreader.o \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be also part of the if percona ext part later?

pg_resetwal.o

ifeq ($(enable_percona_ext),yes)

OBJS += \
$(top_srcdir)/src/fe_utils/simple_list.o \
$(top_builddir)/src/libtde/libtdexlog.a \
$(top_builddir)/src/libtde/libtde.a

override CPPFLAGS := -I$(top_srcdir)/contrib/pg_tde/src/include -I$(top_srcdir)/contrib/pg_tde/src/libkmip/libkmip/include $(CPPFLAGS)
endif

all: pg_resetwal

xlogreader.c: % : $(top_srcdir)/src/backend/access/transam/%
rm -f $@ && $(LN_S) $< .

pg_resetwal: $(OBJS) | submake-libpgport
$(CC) $(CFLAGS) $^ $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@$(X)

Expand All @@ -36,7 +51,7 @@ uninstall:
rm -f '$(DESTDIR)$(bindir)/pg_resetwal$(X)'

clean distclean:
rm -f pg_resetwal$(X) $(OBJS)
rm -f pg_resetwal$(X) $(OBJS) xlogreader.c
rm -rf tmp_check

check:
Expand Down
12 changes: 12 additions & 0 deletions src/bin/pg_resetwal/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,22 @@ if host_system == 'windows'
'--FILEDESC', 'pg_resetwal - reset PostgreSQL WAL log'])
endif

link_w = []
include_dirs = [postgres_inc]

if percona_ext == true
link_w = [pg_tde_frontend]
include_dirs = [postgres_inc, pg_tde_inc]
pg_resetwal_sources += xlogreader_sources
endif

pg_resetwal = executable('pg_resetwal',
pg_resetwal_sources,
dependencies: [frontend_code],
c_args: ['-DFRONTEND'], # needed for xlogreader et al
kwargs: default_bin_args,
include_directories: include_dirs,
link_with: link_w
)
bin_targets += pg_resetwal

Expand Down
36 changes: 36 additions & 0 deletions src/bin/pg_resetwal/pg_resetwal.c
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,13 @@
#include "pg_getopt.h"
#include "storage/large_object.h"

#ifdef PERCONA_EXT
#include "pg_tde.h"
#include "access/pg_tde_fe_init.h"
#include "access/pg_tde_xlog_smgr.h"
#include "access/xlog_smgr.h"
#endif

static ControlFileData ControlFile; /* pg_control values */
static XLogSegNo newXlogSegNo; /* new XLOG segment # */
static bool guessed = false; /* T if we had to guess at any values */
Expand Down Expand Up @@ -344,6 +351,15 @@ main(int argc, char *argv[])
}
#endif

#ifdef PERCONA_EXT
{
char tde_path[MAXPGPATH];
snprintf(tde_path, sizeof(tde_path), "%s/%s", DataDir, PG_TDE_DATA_DIR);
pg_tde_fe_init(tde_path);
TDEXLogSmgrInit();
}
#endif

get_restricted_token();

/* Set mask based on PGDATA permissions */
Expand Down Expand Up @@ -488,6 +504,15 @@ main(int argc, char *argv[])
exit(1);
}

#ifdef PERCONA_EXT
/*
* As we always write empty record here we can do it in unencrypted mode.
* We initialize writes here because new WAL key may be created in some cases,
* and we want it only when we are ready to perform acutal writes.
*/
TDEXLogSmgrInitWrite(false);
#endif

/*
* Else, do the dirty deed.
*/
Expand Down Expand Up @@ -1134,14 +1159,25 @@ WriteEmptyXLOG(void)
pg_fatal("could not open file \"%s\": %m", path);

errno = 0;
#ifdef PERCONA_EXT
if (xlog_smgr->seg_write(fd, buffer.data, XLOG_BLCKSZ, 0,
ControlFile.checkPointCopy.ThisTimeLineID,
newXlogSegNo, WalSegSz) != XLOG_BLCKSZ)
#else
if (write(fd, buffer.data, XLOG_BLCKSZ) != XLOG_BLCKSZ)
#endif
{
/* if write didn't set errno, assume problem is no disk space */
if (errno == 0)
errno = ENOSPC;
pg_fatal("could not write file \"%s\": %m", path);
}

#ifdef PERCONA_EXT
/* If we used xlog smgr, we need to update the file offset */
lseek(fd, XLOG_BLCKSZ, SEEK_CUR);
#endif

/* Fill the rest of the file with zeroes */
memset(buffer.data, 0, XLOG_BLCKSZ);
for (nbytes = XLOG_BLCKSZ; nbytes < WalSegSz; nbytes += XLOG_BLCKSZ)
Expand Down
Loading