Skip to content

Commit

Permalink
Extended PostgreSQL cluster builder (#238)
Browse files Browse the repository at this point in the history
* Added ability to setup custom database name and to force PG version

* Fixed Gradle OOM on Windows 11

* Updated Gradle

* Updated dependabot rules

---------

Co-authored-by: Ivan Vakhrushev <mfvanek@gmail.com>
  • Loading branch information
mfvanek and mfvanek authored May 14, 2023
1 parent 19f3f79 commit 830236e
Show file tree
Hide file tree
Showing 15 changed files with 178 additions and 90 deletions.
4 changes: 2 additions & 2 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ updates:
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 3
open-pull-requests-limit: 5
- package-ecosystem: "gradle"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 3
open-pull-requests-limit: 5
1 change: 0 additions & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,3 @@ sonatypePassword=
systemProp.org.gradle.internal.publish.checksums.insecure=true
org.gradle.parallel=true
org.gradle.daemon=true
org.gradle.jvmargs=-Xmx2048M
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
networkTimeout=10000
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
7 changes: 4 additions & 3 deletions gradlew
Original file line number Diff line number Diff line change
Expand Up @@ -85,9 +85,6 @@ done
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum

Expand Down Expand Up @@ -197,6 +194,10 @@ if "$cygwin" || "$msys" ; then
done
fi


# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'

# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright (c) 2019-2023. Ivan Vakhrushev and others.
* https://github.com/mfvanek/pg-index-health
*
* This file is a part of "pg-index-health" - a Java library for
* analyzing and maintaining indexes health in PostgreSQL databases.
*
* Licensed under the Apache License 2.0
*/

package io.github.mfvanek.pg.support;

import io.github.mfvanek.pg.connection.PgSqlException;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import javax.annotation.Nonnull;
import javax.sql.DataSource;

public final class PostgresVersionReader {

private PostgresVersionReader() {
throw new UnsupportedOperationException();
}

@Nonnull
public static String readVersion(@Nonnull final DataSource dataSource) {
try (Connection connection = dataSource.getConnection();
Statement statement = connection.createStatement()) {
try (ResultSet resultSet = statement.executeQuery("show server_version")) {
resultSet.next();
return resultSet.getString(1);
}
} catch (SQLException e) {
throw new PgSqlException(e);
}
}
}
1 change: 1 addition & 0 deletions pg-index-health-testing/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ dependencies {
testImplementation(testFixtures(project(":pg-index-health-jdbc-connection")))
testImplementation "org.mockito:mockito-core:${mockitoVersion}"
testImplementation "ch.qos.logback:logback-classic:${logbackVersion}"
testRuntimeOnly "org.postgresql:postgresql:${postgresqlVersion}"
}

test {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.Immutable;
Expand Down Expand Up @@ -49,21 +48,19 @@ String getStandbyAlias() {

@Nonnull
Map<String, String> createPrimaryEnvVarsMap(
@Nonnull final String username,
@Nonnull final String password
@Nonnull final PostgreSqlClusterWrapper.PostgreSqlClusterBuilder builder
) {
final Map<String, String> envVarsMap = createCommonEnvVarsMap(username, password);
final Map<String, String> envVarsMap = createCommonEnvVarsMap(builder);
envVarsMap.put("REPMGR_NODE_NAME", primaryAlias);
envVarsMap.put("REPMGR_NODE_NETWORK_NAME", primaryAlias);
return envVarsMap;
}

@Nonnull
Map<String, String> createStandbyEnvVarsMap(
@Nonnull final String username,
@Nonnull final String password
@Nonnull final PostgreSqlClusterWrapper.PostgreSqlClusterBuilder builder
) {
final Map<String, String> envVarsMap = createCommonEnvVarsMap(username, password);
final Map<String, String> envVarsMap = createCommonEnvVarsMap(builder);
envVarsMap.put("REPMGR_NODE_NAME", standbyAlias);
envVarsMap.put("REPMGR_NODE_NETWORK_NAME", standbyAlias);
return envVarsMap;
Expand All @@ -85,17 +82,13 @@ WaitStrategy getWaitStrategyForStandBy() {

@Nonnull
private Map<String, String> createCommonEnvVarsMap(
@Nonnull final String customUsername,
@Nonnull final String customPassword
@Nonnull final PostgreSqlClusterWrapper.PostgreSqlClusterBuilder builder
) {
final String username = Objects.requireNonNull(customUsername, "username cannot be null");
final String password = Objects.requireNonNull(customPassword, "password cannot be null");

final Map<String, String> envVarsMap = new HashMap<>();
envVarsMap.put("POSTGRESQL_POSTGRES_PASSWORD", "adminpassword");
envVarsMap.put("POSTGRESQL_USERNAME", username);
envVarsMap.put("POSTGRESQL_PASSWORD", password);
envVarsMap.put("POSTGRESQL_DATABASE", "customdatabase");
envVarsMap.put("POSTGRESQL_USERNAME", builder.getUsername());
envVarsMap.put("POSTGRESQL_PASSWORD", builder.getPassword());
envVarsMap.put("POSTGRESQL_DATABASE", builder.getDatabaseName());
envVarsMap.put("REPMGR_PASSWORD", "repmgrpassword");
envVarsMap.put("REPMGR_PRIMARY_HOST", primaryAlias);
envVarsMap.put("REPMGR_PRIMARY_PORT", "5432");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.util.Map;
import java.util.Objects;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.sql.DataSource;

/**
Expand All @@ -44,27 +45,24 @@ public final class PostgreSqlClusterWrapper implements AutoCloseable {

private final PostgresVersionHolder pgVersion;
private final Network network;
private final JdbcDatabaseContainer<?> containerForPrimary;
private final JdbcDatabaseContainer<?> containerForStandBy;
private final JdbcDatabaseContainer<PostgresBitnamiRepmgrContainer> containerForPrimary;
private final JdbcDatabaseContainer<PostgresBitnamiRepmgrContainer> containerForStandBy;
private final BasicDataSource dataSourceForPrimary;
private final BasicDataSource dataSourceForStandBy;

private PostgreSqlClusterWrapper(
@Nonnull final String username,
@Nonnull final String password
) {
this.pgVersion = PostgresVersionHolder.forCluster();
private PostgreSqlClusterWrapper(@Nonnull final PostgreSqlClusterBuilder builder) {
this.pgVersion = PostgresVersionHolder.forCluster(builder.getPostgresVersion());
this.network = Network.newNetwork();

final PostgreSqlClusterAliasHolder aliases = new PostgreSqlClusterAliasHolder();
// Primary node
this.containerForPrimary = createContainerAndInitWith(
aliases.createPrimaryEnvVarsMap(username, password),
aliases.createPrimaryEnvVarsMap(builder),
aliases.getPrimaryAlias(),
aliases.getWaitStrategyForPrimary());
// Standby node
this.containerForStandBy = createContainerAndInitWith(
aliases.createStandbyEnvVarsMap(username, password),
aliases.createStandbyEnvVarsMap(builder),
aliases.getStandbyAlias(),
aliases.getWaitStrategyForStandBy()
);
Expand Down Expand Up @@ -184,8 +182,8 @@ private void throwErrorIfNotInitialized() {
}

@Nonnull
public static Builder builder() {
return new Builder();
public static PostgreSqlClusterBuilder builder() {
return new PostgreSqlClusterBuilder();
}

/**
Expand All @@ -194,35 +192,68 @@ public static Builder builder() {
*
* @author Alexey Antipin
*/
public static class Builder {
public static class PostgreSqlClusterBuilder {

private String username = "customuser";
private String password = "custompassword";
private String databaseName = "customdatabase";
private String postgresVersion;

private PostgreSqlClusterBuilder() {
}

private Builder() {
@Nonnull
public String getUsername() {
return username;
}

@Nonnull
public Builder withUsername(@Nonnull final String username) {
public String getPassword() {
return password;
}

@Nonnull
public String getDatabaseName() {
return databaseName;
}

@Nullable
public String getPostgresVersion() {
return postgresVersion;
}

@Nonnull
public PostgreSqlClusterBuilder withUsername(@Nonnull final String username) {
this.username = Objects.requireNonNull(username, "username cannot be null");
return this;
}

@Nonnull
public Builder withPassword(@Nonnull final String password) {
public PostgreSqlClusterBuilder withPassword(@Nonnull final String password) {
this.password = Objects.requireNonNull(password, "password cannot be null");
return this;
}

@Nonnull
public PostgreSqlClusterBuilder withDatabaseName(@Nonnull final String databaseName) {
this.databaseName = Objects.requireNonNull(databaseName, "databaseName cannot be null");
return this;
}

@Nonnull
public PostgreSqlClusterBuilder withPostgresVersion(@Nonnull final String postgresVersion) {
this.postgresVersion = Objects.requireNonNull(postgresVersion, "postgresVersion cannot be null");
return this;
}

/**
* Used to create a PostgresSqlClusterWrapper with a given username/password.
* Creates a PostgresSqlClusterWrapper with a given parameters.
*
* @return PostgreSqlClusterWrapper
*/
@Nonnull
public PostgreSqlClusterWrapper build() {

return new PostgreSqlClusterWrapper(username, password);
return new PostgreSqlClusterWrapper(this);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

import java.util.Objects;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

final class PostgresVersionHolder implements PostgresVersionAware {

Expand Down Expand Up @@ -67,9 +68,11 @@ private static String preparePostgresVersion() {
return "15.2";
}

public static PostgresVersionHolder forCluster() {
public static PostgresVersionHolder forCluster(@Nullable final String forcedPostgresVersion) {
final String pgVersion = forcedPostgresVersion != null ?
forcedPostgresVersion : preparePostgresVersion();
// Bitnami images use semantic versioning with three digits
return new PostgresVersionHolder(preparePostgresVersion() + ".0");
return new PostgresVersionHolder(pgVersion + ".0");
}

public static PostgresVersionHolder forSingleNode() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

@Tag("fast")
class PostgreSqlClusterAliasHolderTest {
Expand All @@ -37,26 +36,14 @@ void primaryAndStandbyNamesShouldDiffer() {
@Test
void shouldCreateEnvMaps() {
final PostgreSqlClusterAliasHolder aliases = new PostgreSqlClusterAliasHolder();
final var builder = PostgreSqlClusterWrapper.builder()
.withUsername("username")
.withPassword("any#pwd")
.withDatabaseName("test_db");
assertThat(aliases)
.isNotNull()
.satisfies(a -> assertThat(a.createPrimaryEnvVarsMap("username", "password"))
.satisfies(a -> assertThat(a.createPrimaryEnvVarsMap(builder))
.hasSize(14)
.hasSameSizeAs(a.createStandbyEnvVarsMap("username", "password")));
}

@SuppressWarnings("DataFlowIssue")
@Test
void shouldNotCreateEnvMapsWithInvalidArgs() {
final PostgreSqlClusterAliasHolder aliases = new PostgreSqlClusterAliasHolder();
assertThat(aliases)
.isNotNull()
.satisfies(a -> {
assertThatThrownBy(() -> a.createPrimaryEnvVarsMap(null, null))
.isInstanceOf(NullPointerException.class)
.hasMessage("username cannot be null");
assertThatThrownBy(() -> a.createPrimaryEnvVarsMap("username", null))
.isInstanceOf(NullPointerException.class)
.hasMessage("password cannot be null");
});
.hasSameSizeAs(a.createStandbyEnvVarsMap(builder)));
}
}
Loading

0 comments on commit 830236e

Please sign in to comment.