All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.github.mfvanek.pg.testing.PostgreSqlClusterWrapper Maven / Gradle / Ivy

Go to download

pg-index-health-testing is an auxiliary library that allows you to run a PostgreSQL cluster in tests.

There is a newer version: 0.14.4
Show newest version
/*
 * 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.testing;

import io.github.mfvanek.pg.connection.PgUrlParser;
import io.github.mfvanek.pg.model.MemoryUnit;
import io.github.mfvanek.pg.testing.annotations.ExcludeFromJacocoGeneratedReport;
import org.apache.commons.dbcp2.BasicDataSource;
import org.awaitility.Awaitility;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.JdbcDatabaseContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.WaitStrategy;
import org.testcontainers.utility.DockerImageName;

import java.sql.SQLException;
import java.time.Duration;
import java.util.Map;
import java.util.Objects;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.sql.DataSource;

/**
 * This wrapper provides postgres containers as part of HA cluster with repmgr configured.
 * 

* If master goes down, repmgr will ensure any of the standby nodes takes the primary role. * * @author Alexey Antipin * @since 0.6.2 */ public final class PostgreSqlClusterWrapper implements AutoCloseable { public static final Duration WAIT_INTERVAL_SECONDS = Duration.ofSeconds(100L); private static final String IMAGE_NAME = "docker.io/bitnami/postgresql-repmgr"; private static final Logger LOGGER = LoggerFactory.getLogger(PostgreSqlClusterWrapper.class); private final PostgresVersionHolder pgVersion; private final Network network; private final JdbcDatabaseContainer containerForPrimary; private final JdbcDatabaseContainer containerForStandBy; private final BasicDataSource dataSourceForPrimary; private final BasicDataSource dataSourceForStandBy; private PostgreSqlClusterWrapper(@Nonnull final PostgreSqlClusterBuilder builder) { this.pgVersion = builder.getPostgresVersion() != null ? PostgresVersionHolder.forCluster(builder.getPostgresVersion()) : PostgresVersionHolder.forCluster(); this.network = Network.newNetwork(); final PostgreSqlClusterAliasHolder aliases = new PostgreSqlClusterAliasHolder(); // Primary node this.containerForPrimary = createContainerAndInitWith( aliases.createPrimaryEnvVarsMap(builder), aliases.getPrimaryAlias(), aliases.getWaitStrategyForPrimary()); // Standby node this.containerForStandBy = createContainerAndInitWith( aliases.createStandbyEnvVarsMap(builder), aliases.getStandbyAlias(), aliases.getWaitStrategyForStandBy() ); this.containerForPrimary.start(); Awaitility.await("Ensure primary is ready") .atMost(PostgreSqlClusterAliasHolder.STARTUP_TIMEOUT) .pollInterval(Duration.ofSeconds(1L)) .until(() -> containerForPrimary.getLogs().contains("database system is ready to accept connections")); this.containerForStandBy.start(); Awaitility.await("Ensure cluster is ready") .atMost(PostgreSqlClusterAliasHolder.STARTUP_TIMEOUT) .pollInterval(Duration.ofSeconds(1L)) .until(() -> containerForStandBy.getLogs().contains("started streaming WAL from primary")); this.dataSourceForPrimary = PostgreSqlDataSourceHelper.buildDataSource(containerForPrimary); this.dataSourceForStandBy = PostgreSqlDataSourceHelper.buildDataSource(containerForStandBy); } /** * {@inheritDoc} */ @ExcludeFromJacocoGeneratedReport @Override public void close() { try { dataSourceForStandBy.close(); } catch (SQLException ex) { LOGGER.warn(ex.getMessage(), ex); } try { dataSourceForPrimary.close(); } catch (SQLException ex) { LOGGER.warn(ex.getMessage(), ex); } containerForStandBy.close(); containerForPrimary.close(); } @Nonnull public DataSource getDataSourceForPrimary() { throwErrorIfNotInitialized(); return dataSourceForPrimary; } @Nonnull public DataSource getDataSourceForStandBy() { throwErrorIfNotInitialized(); return dataSourceForStandBy; } @Nonnull public String getFirstContainerJdbcUrl() { throwErrorIfNotInitialized(); return containerForPrimary.getJdbcUrl(); } @Nonnull public String getSecondContainerJdbcUrl() { throwErrorIfNotInitialized(); return containerForStandBy.getJdbcUrl(); } @Nonnull public String getCommonUrlToPrimary() { throwErrorIfNotInitialized(); return PgUrlParser.buildCommonUrlToPrimary(containerForPrimary.getJdbcUrl(), containerForStandBy.getJdbcUrl()); } @Nonnull public String getUsername() { return containerForPrimary.getUsername(); } @Nonnull public String getPassword() { return containerForPrimary.getPassword(); } /** * Stops first container in the cluster and waits for auto failover. * * @return always true */ public boolean stopFirstContainer() { containerForPrimary.stop(); LOGGER.info("Waiting for standby will be promoted to primary"); Awaitility.await("Promoting standby to primary") .atMost(WAIT_INTERVAL_SECONDS) .pollInterval(Duration.ofSeconds(1L)) .until(() -> containerForStandBy.getLogs().contains("promoting standby to primary")); Awaitility.await("Standby promoted to primary") .atMost(WAIT_INTERVAL_SECONDS) .pollInterval(Duration.ofSeconds(1L)) .until(() -> containerForStandBy.getLogs().contains("standby promoted to primary after")); return true; } @Nonnull private PostgresBitnamiRepmgrContainer createContainerAndInitWith( final Map envVars, final String alias, final WaitStrategy waitStrategy ) { final DockerImageName dockerImageName = DockerImageName.parse(IMAGE_NAME) .withTag(pgVersion.getVersion()); //noinspection resource return new PostgresBitnamiRepmgrContainer(dockerImageName, envVars) //NOSONAR .withCreateContainerCmdModifier(cmd -> cmd.withName(alias)) .withSharedMemorySize(MemoryUnit.MB.convertToBytes(768)) .withTmpFs(Map.of("/var/lib/postgresql/data", "rw")) .withNetwork(network) .withNetworkAliases(alias) .withExposedPorts(5432) .waitingFor(waitStrategy); } @ExcludeFromJacocoGeneratedReport private void throwErrorIfNotInitialized() { if (containerForPrimary == null || dataSourceForPrimary == null || containerForStandBy == null || dataSourceForStandBy == null) { throw new AssertionError("not initialized"); } } @Nonnull public static PostgreSqlClusterBuilder builder() { return new PostgreSqlClusterBuilder(); } /** * Provide convenient way to create cluster with single username/password. * If no username/password is specified, "customuser" and "custompassword" will be used as default values for username and password, respectively. * * @author Alexey Antipin */ public static class PostgreSqlClusterBuilder { private String username = "customuser"; private String password = "custompassword"; private String databaseName = "customdatabase"; private String postgresVersion; private PostgreSqlClusterBuilder() { } @Nonnull public String getUsername() { return username; } @Nonnull 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 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; } /** * Creates a PostgresSqlClusterWrapper with a given parameters. * * @return PostgreSqlClusterWrapper */ @Nonnull public PostgreSqlClusterWrapper build() { return new PostgreSqlClusterWrapper(this); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy