Skip to content

Commit 57618c7

Browse files
committed
Test extension with primary/standby servers
1 parent 25982b6 commit 57618c7

File tree

4 files changed

+181
-0
lines changed

4 files changed

+181
-0
lines changed
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package io.r2dbc.postgresql.client;
2+
3+
import io.r2dbc.postgresql.util.PostgresqlHighAvailabilityClusterExtension;
4+
import org.junit.jupiter.api.Assertions;
5+
import org.junit.jupiter.api.Test;
6+
import org.junit.jupiter.api.extension.RegisterExtension;
7+
8+
public class HighAvailabilityClusterTest {
9+
10+
@RegisterExtension
11+
static final PostgresqlHighAvailabilityClusterExtension SERVERS = new PostgresqlHighAvailabilityClusterExtension();
12+
13+
@Test
14+
void testPrimaryAndStandbyStartup() {
15+
Assertions.assertFalse(SERVERS.getPrimaryJdbc().queryForObject("show transaction_read_only", Boolean.class));
16+
Assertions.assertTrue(SERVERS.getStandbyJdbc().queryForObject("show transaction_read_only", Boolean.class));
17+
}
18+
}
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
package io.r2dbc.postgresql.util;
2+
3+
import com.zaxxer.hikari.HikariConfig;
4+
import com.zaxxer.hikari.HikariDataSource;
5+
import org.junit.jupiter.api.extension.AfterAllCallback;
6+
import org.junit.jupiter.api.extension.BeforeAllCallback;
7+
import org.junit.jupiter.api.extension.ExtensionContext;
8+
import org.springframework.jdbc.core.JdbcTemplate;
9+
import org.testcontainers.containers.Network;
10+
import org.testcontainers.containers.PostgreSQLContainer;
11+
import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy;
12+
import org.testcontainers.utility.MountableFile;
13+
14+
import java.net.URISyntaxException;
15+
import java.net.URL;
16+
import java.nio.file.Path;
17+
import java.nio.file.Paths;
18+
import java.time.Duration;
19+
import java.time.temporal.ChronoUnit;
20+
21+
import static org.testcontainers.utility.MountableFile.forHostPath;
22+
23+
public class PostgresqlHighAvailabilityClusterExtension implements BeforeAllCallback, AfterAllCallback {
24+
25+
private PostgreSQLContainer<?> primary;
26+
27+
private HikariDataSource primaryDataSource;
28+
29+
private PostgreSQLContainer<?> standby;
30+
31+
private HikariDataSource standbyDataSource;
32+
33+
@Override
34+
public void afterAll(ExtensionContext extensionContext) {
35+
if (this.standbyDataSource != null) {
36+
this.standbyDataSource.close();
37+
}
38+
if (this.standby != null) {
39+
this.standby.stop();
40+
}
41+
if (this.primaryDataSource != null) {
42+
this.primaryDataSource.close();
43+
}
44+
if (this.primary != null) {
45+
this.primary.stop();
46+
}
47+
}
48+
49+
@Override
50+
public void beforeAll(ExtensionContext extensionContext) {
51+
Network network = Network.newNetwork();
52+
this.startPrimary(network);
53+
this.startStandby(network);
54+
}
55+
56+
public PostgreSQLContainer<?> getPrimary() {
57+
return this.primary;
58+
}
59+
60+
public JdbcTemplate getPrimaryJdbc() {
61+
return new JdbcTemplate(this.primaryDataSource);
62+
}
63+
64+
public PostgreSQLContainer<?> getStandby() {
65+
return standby;
66+
}
67+
68+
public JdbcTemplate getStandbyJdbc() {
69+
return new JdbcTemplate(this.standbyDataSource);
70+
}
71+
72+
private static MountableFile getHostPath(String name, int mode) {
73+
return forHostPath(getResourcePath(name), mode);
74+
}
75+
76+
private static Path getResourcePath(String name) {
77+
URL resource = PostgresqlHighAvailabilityClusterExtension.class.getClassLoader().getResource(name);
78+
if (resource == null) {
79+
throw new IllegalStateException("Resource not found: " + name);
80+
}
81+
82+
try {
83+
return Paths.get(resource.toURI());
84+
} catch (URISyntaxException e) {
85+
throw new IllegalStateException("Cannot convert to path for: " + name, e);
86+
}
87+
}
88+
89+
private void startPrimary(Network network) {
90+
this.primary = new PostgreSQLContainer<>("postgres:latest")
91+
.withNetwork(network)
92+
.withNetworkAliases("postgres-primary")
93+
.withCopyFileToContainer(getHostPath("setup-primary.sh", 0755), "/docker-entrypoint-initdb.d/setup-primary.sh")
94+
.withEnv("PG_REP_USER", "replication")
95+
.withEnv("PG_REP_PASSWORD", "replication_password");
96+
this.primary.start();
97+
HikariConfig primaryConfig = new HikariConfig();
98+
primaryConfig.setJdbcUrl(this.primary.getJdbcUrl());
99+
primaryConfig.setUsername(this.primary.getUsername());
100+
primaryConfig.setPassword(this.primary.getPassword());
101+
this.primaryDataSource = new HikariDataSource(primaryConfig);
102+
}
103+
104+
private void startStandby(Network network) {
105+
this.standby = new PostgreSQLContainer<>("postgres:latest")
106+
.withNetwork(network)
107+
.withCopyFileToContainer(getHostPath("setup-standby.sh", 0755), "/setup-standby.sh")
108+
.withCommand("/setup-standby.sh")
109+
.withEnv("PG_REP_USER", "replication")
110+
.withEnv("PG_REP_PASSWORD", "replication_password")
111+
.withEnv("PG_MASTER_HOST", "postgres-primary")
112+
.withEnv("PG_MASTER_PORT", "5432");
113+
this.standby.setWaitStrategy(new LogMessageWaitStrategy()
114+
.withRegEx(".*database system is ready to accept read only connections.*\\s")
115+
.withTimes(1)
116+
.withStartupTimeout(Duration.of(60L, ChronoUnit.SECONDS)));
117+
this.standby.start();
118+
HikariConfig standbyConfig = new HikariConfig();
119+
standbyConfig.setJdbcUrl(this.standby.getJdbcUrl());
120+
standbyConfig.setUsername(this.standby.getUsername());
121+
standbyConfig.setPassword(this.standby.getPassword());
122+
this.standbyDataSource = new HikariDataSource(standbyConfig);
123+
}
124+
}

src/test/resources/setup-primary.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
#!/bin/sh
2+
3+
echo "host replication all 0.0.0.0/0 md5" >> "$PGDATA/pg_hba.conf"
4+
5+
set -e
6+
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
7+
CREATE USER $PG_REP_USER REPLICATION LOGIN CONNECTION LIMIT 100 ENCRYPTED PASSWORD '$PG_REP_PASSWORD';
8+
EOSQL
9+
10+
cat >> ${PGDATA}/postgresql.conf <<EOF
11+
wal_level = hot_standby
12+
archive_mode = on
13+
archive_command = 'cd .'
14+
max_wal_senders = 8
15+
wal_keep_segments = 8
16+
hot_standby = on
17+
EOF

src/test/resources/setup-standby.sh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#!/bin/sh
2+
3+
if [ ! -s "$PGDATA/PG_VERSION" ]; then
4+
echo "*:*:*:$PG_REP_USER:$PG_REP_PASSWORD" > ~/.pgpass
5+
chmod 0600 ~/.pgpass
6+
until pg_basebackup -h "${PG_MASTER_HOST}" -p "${PG_MASTER_PORT}" -D "${PGDATA}" -U "${PG_REP_USER}" -vP -W
7+
do
8+
echo "Waiting for primary server to connect..."
9+
sleep 1s
10+
done
11+
echo "host replication all 0.0.0.0/0 md5" >> "$PGDATA/pg_hba.conf"
12+
set -e
13+
cat > "${PGDATA}"/standby.signal <<EOF
14+
primary_conninfo = 'host=$PG_MASTER_HOST port=$PG_MASTER_PORT user=$PG_REP_USER password=$PG_REP_PASSWORD'
15+
promote_trigger_file = '/tmp/promote_trigger_file'
16+
EOF
17+
chown postgres. "${PGDATA}" -R
18+
chmod 700 "${PGDATA}" -R
19+
fi
20+
sed -i 's/wal_level = hot_standby/wal_level = replica/g' "${PGDATA}"/postgresql.conf
21+
echo "ready to run"
22+
exec gosu postgres postgres

0 commit comments

Comments
 (0)