├── .github └── md │ └── README.md ├── .gitignore ├── LICENSE.txt ├── README.md ├── client ├── pom.xml └── src │ └── main │ └── java │ └── io │ └── metaloom │ └── test │ └── container │ └── provider │ └── client │ ├── ClientAllocation.java │ ├── JSON.java │ ├── ProviderClient.java │ ├── TestDatabaseProvider.java │ └── WebsocketLinkListener.java ├── common ├── pom.xml └── src │ ├── main │ ├── java │ │ └── io │ │ │ └── metaloom │ │ │ └── test │ │ │ └── container │ │ │ └── provider │ │ │ ├── common │ │ │ ├── ClientEnv.java │ │ │ ├── ServerEnv.java │ │ │ ├── config │ │ │ │ ├── AbstractDatabaseConfig.java │ │ │ │ ├── DatabaseConfig.java │ │ │ │ ├── PostgresqlConfig.java │ │ │ │ ├── ProviderConfig.java │ │ │ │ └── ProviderConfigHelper.java │ │ │ └── version │ │ │ │ ├── BuildInfo.java │ │ │ │ └── Version.java │ │ │ └── model │ │ │ ├── AbstractDatabasePoolModel.java │ │ │ ├── DatabaseAllocationResponse.java │ │ │ ├── DatabasePoolConnection.java │ │ │ ├── DatabasePoolListResponse.java │ │ │ ├── DatabasePoolRequest.java │ │ │ ├── DatabasePoolResponse.java │ │ │ ├── DatabasePoolSettings.java │ │ │ └── RestModel.java │ └── resources │ │ └── provider.build.properties │ └── test │ └── java │ └── io │ └── metaloom │ └── test │ └── container │ └── provider │ └── common │ ├── config │ └── ProviderConfigHelperTest.java │ └── version │ └── VersionTest.java ├── examples ├── complex │ ├── README.md │ ├── moduleA │ │ ├── pom.xml │ │ └── src │ │ │ └── main │ │ │ └── flyway │ │ │ └── V1__initial_setup.sql │ ├── moduleB │ │ ├── pom.xml │ │ └── src │ │ │ └── test │ │ │ └── java │ │ │ └── io │ │ │ └── metaloom │ │ │ └── example │ │ │ ├── ExampleJunit4Test.java │ │ │ └── ExampleJunit5Test.java │ └── pom.xml ├── dedicated-no-maven-plugin │ ├── docker-compose.yml │ ├── pom.xml │ └── src │ │ └── test │ │ └── java │ │ └── io │ │ └── metaloom │ │ └── example │ │ ├── ExampleJunit4Test.java │ │ ├── ExampleJunit5Test.java │ │ └── PoolSetupAction.java ├── dedicated │ ├── README.md │ ├── docker-compose.yml │ ├── pom.xml │ └── src │ │ └── test │ │ └── java │ │ └── io │ │ └── metaloom │ │ └── example │ │ ├── ExampleJunit4Test.java │ │ └── ExampleJunit5Test.java ├── minimal │ ├── README.md │ ├── pom.xml │ └── src │ │ ├── main │ │ └── resources │ │ │ └── db │ │ │ └── migration │ │ │ └── V1__initial_setup.sql │ │ └── test │ │ ├── java │ │ └── io │ │ │ └── metaloom │ │ │ └── example │ │ │ ├── ExampleJunit4Test.java │ │ │ ├── ExampleJunit5Test.java │ │ │ └── PoolSetupAction.java │ │ └── resources │ │ └── logback.xml └── pom.xml ├── junit4 ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── metaloom │ │ └── test │ │ └── provider │ │ └── junit4 │ │ └── DatabaseProviderRule.java │ └── test │ └── java │ └── io │ └── metaloom │ └── test │ └── provider │ └── junit4 │ ├── DatabaseProviderRuleTest.java │ └── PoolSetupAction.java ├── junit5 ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── metaloom │ │ └── test │ │ └── provider │ │ └── junit5 │ │ └── ProviderExtension.java │ └── test │ └── java │ └── io │ └── metaloom │ └── test │ └── provider │ └── junit5 │ └── DatabaseProviderExtensionTest.java ├── maven ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── metaloom │ │ ├── maven │ │ └── provider │ │ │ ├── AbstractProviderMojo.java │ │ │ ├── PoolLimits.java │ │ │ ├── PoolMavenConfiguration.java │ │ │ ├── PostgresqlMavenConfiguration.java │ │ │ ├── ProviderCleanMojo.java │ │ │ ├── ProviderMavenConfiguration.java │ │ │ ├── ProviderPoolMojo.java │ │ │ ├── ProviderStartMojo.java │ │ │ └── ProviderStopMojo.java │ │ └── test │ │ └── container │ │ └── provider │ │ └── container │ │ └── DatabaseProviderContainer.java │ └── test │ └── java │ └── io │ └── metaloom │ └── maven │ └── provider │ └── ProviderStartMojoTest.java ├── pom.xml ├── postgresql-db ├── pom.xml └── src │ ├── main │ └── java │ │ └── io │ │ └── metaloom │ │ └── maven │ │ └── provider │ │ └── container │ │ └── PostgreSQLPoolContainer.java │ └── test │ └── java │ └── io │ └── metaloom │ └── maven │ └── provider │ └── container │ └── PostgreSQLPoolContainerTest.java └── server ├── Dockerfile ├── pom.xml ├── src ├── main │ └── java │ │ └── io │ │ └── metaloom │ │ └── test │ │ └── container │ │ └── provider │ │ ├── BootstrapInitializer.java │ │ ├── Database.java │ │ ├── DatabaseAllocation.java │ │ ├── DatabaseJsonCommentModel.java │ │ ├── DatabasePool.java │ │ ├── DatabasePoolFactory.java │ │ ├── DatabasePoolManager.java │ │ ├── DatabaseSettings.java │ │ ├── SQLUtils.java │ │ └── server │ │ ├── DatabaseProviderServer.java │ │ ├── DatabaseProviderServerRunner.java │ │ ├── JSON.java │ │ ├── ModelHelper.java │ │ ├── ProviderRequest.java │ │ ├── ServerApi.java │ │ ├── ServerConfiguration.java │ │ ├── ServerConfigurationLoader.java │ │ ├── ServerError.java │ │ └── dagger │ │ ├── ProviderModule.java │ │ ├── ServerComponent.java │ │ └── VertxModule.java └── test │ └── java │ └── io │ └── metaloom │ └── test │ └── container │ └── server │ ├── AbstractProviderServerTest.java │ ├── AllocationTestHelper.java │ ├── DatabasePoolManagerTest.java │ ├── DatabasePoolTest.java │ ├── DatabaseProviderTest.java │ ├── DatabaseProviderTestServer.java │ ├── JSONTest.java │ ├── ModelHelperTest.java │ ├── ProviderClientServerTest.java │ └── TestSQLHelper.java ├── stop.sh └── test.sh /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .classpath 3 | .factorypath 4 | .settings 5 | target 6 | /out 7 | dependency-reduced-pom.xml 8 | 9 | -------------------------------------------------------------------------------- /client/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | testdatabase-provider-client 6 | 7 | 8 | io.metaloom.test 9 | testdatabase-provider 10 | 0.1.4 11 | 12 | 13 | Testdatabase Provider :: Client 14 | 15 | 16 | 17 | 18 | io.metaloom.test 19 | testdatabase-provider-common 20 | ${project.version} 21 | 22 | 23 | org.slf4j 24 | slf4j-api 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /client/src/main/java/io/metaloom/test/container/provider/client/ClientAllocation.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.client; 2 | 3 | import java.net.http.WebSocket; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import io.metaloom.test.container.provider.model.DatabaseAllocationResponse; 9 | 10 | /** 11 | * Allocation that was returned by the {@link ProviderClient}. 12 | */ 13 | public class ClientAllocation { 14 | 15 | public static final Logger log = LoggerFactory.getLogger(ClientAllocation.class); 16 | 17 | private WebSocket socket; 18 | private DatabaseAllocationResponse response; 19 | 20 | public ClientAllocation(WebSocket socket, DatabaseAllocationResponse response) { 21 | this.socket = socket; 22 | this.response = response; 23 | } 24 | 25 | /** 26 | * Release the allocation. This will terminate the websocket and thus let the provider server know that the database is no longer in use can be be removed. 27 | */ 28 | public void release() { 29 | if (log.isDebugEnabled()) { 30 | String id = response == null ? "unknown" : response.getId(); 31 | log.debug("Releasing allocation {}", id); 32 | } 33 | socket.abort(); 34 | } 35 | 36 | /** 37 | * Returns the allocation response which contains the database settings. 38 | * 39 | * @return 40 | */ 41 | public DatabaseAllocationResponse response() { 42 | return response; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /client/src/main/java/io/metaloom/test/container/provider/client/JSON.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.client; 2 | 3 | import com.fasterxml.jackson.databind.JsonNode; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | 6 | import io.metaloom.test.container.provider.model.RestModel; 7 | 8 | public final class JSON { 9 | 10 | private static ObjectMapper mapper = new ObjectMapper(); 11 | 12 | private JSON() { 13 | 14 | } 15 | 16 | public static String toString(RestModel request) { 17 | try { 18 | return mapper.writeValueAsString(request); 19 | } catch (Exception e) { 20 | throw new RuntimeException(e); 21 | } 22 | } 23 | 24 | public static T fromString(String json, Class clazzOfT) { 25 | try { 26 | return mapper.readValue(json, clazzOfT); 27 | } catch (Exception e) { 28 | throw new RuntimeException(e); 29 | } 30 | } 31 | 32 | public static JsonNode toJsonNode(String json) { 33 | try { 34 | return mapper.readTree(json); 35 | } catch (Exception e) { 36 | throw new RuntimeException(e); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /client/src/main/java/io/metaloom/test/container/provider/client/ProviderClient.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.client; 2 | 3 | import java.io.IOException; 4 | import java.net.URI; 5 | import java.net.URISyntaxException; 6 | import java.net.http.HttpClient; 7 | import java.net.http.HttpRequest; 8 | import java.net.http.HttpResponse; 9 | import java.net.http.HttpResponse.BodyHandlers; 10 | import java.util.concurrent.CompletableFuture; 11 | 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | import io.metaloom.test.container.provider.model.DatabasePoolListResponse; 16 | import io.metaloom.test.container.provider.model.DatabasePoolRequest; 17 | import io.metaloom.test.container.provider.model.DatabasePoolResponse; 18 | 19 | public class ProviderClient { 20 | 21 | public static final Logger log = LoggerFactory.getLogger(ProviderClient.class); 22 | private String host; 23 | private int port; 24 | private HttpClient client; 25 | 26 | public ProviderClient(String host, int port) { 27 | this.host = host; 28 | this.port = port; 29 | this.client = HttpClient.newBuilder().build(); 30 | } 31 | 32 | /** 33 | * Connect the test to the database provider. The provider will assign a test database which can be used by the caller. 34 | * 35 | * @param poolName 36 | * @param testRef 37 | * @return 38 | */ 39 | public CompletableFuture link(String poolName, String testRef) { 40 | WebsocketLinkListener listener = new WebsocketLinkListener(poolName, testRef); 41 | HttpClient 42 | .newHttpClient() 43 | .newWebSocketBuilder() 44 | .buildAsync(URI.create("ws://" + host + ":" + port + "/connect/websocket"), listener) 45 | .join(); 46 | return listener.allocation(); 47 | } 48 | 49 | /** 50 | * List all pools that have been created. 51 | * 52 | * @return 53 | * @throws URISyntaxException 54 | */ 55 | public CompletableFuture listPools() throws URISyntaxException { 56 | HttpRequest request = HttpRequest.newBuilder() 57 | .uri(uri("/pools")) 58 | .version(HttpClient.Version.HTTP_2) 59 | .GET() 60 | .build(); 61 | 62 | CompletableFuture> response = client.sendAsync(request, BodyHandlers.ofString()); 63 | 64 | return response.thenApply(resp -> resp.body()) 65 | .thenApply(body -> { 66 | return JSON.fromString(body, DatabasePoolListResponse.class); 67 | }); 68 | } 69 | 70 | /** 71 | * Load the pool with the given id. 72 | * 73 | * @param id 74 | * @return 75 | * @throws IOException 76 | * @throws InterruptedException 77 | * @throws URISyntaxException 78 | */ 79 | public CompletableFuture loadPool(String id) throws IOException, InterruptedException, URISyntaxException { 80 | HttpRequest request = HttpRequest.newBuilder() 81 | .uri(uri("/pools/" + id)) 82 | .version(HttpClient.Version.HTTP_2) 83 | .GET() 84 | .build(); 85 | 86 | // HttpResponse response = client.send(request, BodyHandlers.ofString()); 87 | 88 | CompletableFuture> asyncResp = client.sendAsync(request, BodyHandlers.ofString()); 89 | return asyncResp 90 | .thenApply(resp -> resp.body()) 91 | .thenApply(body -> { 92 | return JSON.fromString(body, DatabasePoolResponse.class); 93 | }); 94 | } 95 | 96 | public CompletableFuture deletePool(String id) throws URISyntaxException { 97 | HttpRequest request = HttpRequest.newBuilder() 98 | .uri(uri("/pools/" + id)) 99 | .version(HttpClient.Version.HTTP_2) 100 | .DELETE() 101 | .build(); 102 | 103 | CompletableFuture> response = client.sendAsync(request, BodyHandlers.discarding()); 104 | return response.thenApply(body -> { 105 | return body.body(); 106 | }); 107 | 108 | } 109 | 110 | public CompletableFuture createPool(String id, DatabasePoolRequest request) throws URISyntaxException { 111 | 112 | String json = JSON.toString(request); 113 | HttpRequest httpRequest = HttpRequest.newBuilder() 114 | .uri(uri("/pools/" + id)) 115 | .version(HttpClient.Version.HTTP_2) 116 | .POST(HttpRequest.BodyPublishers.ofString(json)) 117 | .build(); 118 | 119 | CompletableFuture> response = client.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString()); 120 | 121 | return response.thenApply(resp -> resp.body()) 122 | .thenApply(body -> { 123 | return JSON.fromString(body, DatabasePoolResponse.class); 124 | }); 125 | } 126 | 127 | private URI uri(String path) throws URISyntaxException { 128 | return new URI("http", null, host, port, path, null, null); 129 | } 130 | 131 | } 132 | -------------------------------------------------------------------------------- /client/src/main/java/io/metaloom/test/container/provider/client/TestDatabaseProvider.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.client; 2 | 3 | import java.io.IOException; 4 | import java.sql.Connection; 5 | import java.sql.DriverManager; 6 | import java.sql.SQLException; 7 | import java.sql.Statement; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import io.metaloom.test.container.provider.common.ClientEnv; 13 | import io.metaloom.test.container.provider.common.config.PostgresqlConfig; 14 | import io.metaloom.test.container.provider.common.config.ProviderConfig; 15 | import io.metaloom.test.container.provider.common.config.ProviderConfigHelper; 16 | import io.metaloom.test.container.provider.model.DatabasePoolConnection; 17 | import io.metaloom.test.container.provider.model.DatabasePoolRequest; 18 | import io.metaloom.test.container.provider.model.DatabasePoolResponse; 19 | 20 | public class TestDatabaseProvider { 21 | 22 | private static final Logger log = LoggerFactory.getLogger(TestDatabaseProvider.class); 23 | 24 | private static ProviderConfig localConfig = null; 25 | 26 | /** 27 | * Return the REST client for the given host and port. 28 | * 29 | * @param host 30 | * @param port 31 | * @return 32 | */ 33 | public static ProviderClient client(String host, int port) { 34 | return new ProviderClient(host, port); 35 | } 36 | 37 | /** 38 | * Return the REST client which can be used to communicate with the provider server that manages the database pooling. 39 | * 40 | * @return 41 | * @throws IOException 42 | */ 43 | public static ProviderClient client() throws IOException { 44 | String host = ClientEnv.getProviderHost(); 45 | Integer port = ClientEnv.getProviderPort(); 46 | if (host == null || port == null) { 47 | log.debug("Client host,port environment variables for provider not found"); 48 | } else { 49 | return new ProviderClient(host, port); 50 | } 51 | ProviderConfig config = config(); 52 | requireConfig(config); 53 | host = config.getProviderHost(); 54 | port = config.getProviderPort(); 55 | return client(host, port); 56 | } 57 | 58 | /** 59 | * Locate the config which was written by the testdatabase-provider-plugin 60 | * 61 | * @return 62 | */ 63 | public static ProviderConfig config() { 64 | if (localConfig != null) { 65 | return localConfig; 66 | } else { 67 | return ProviderConfigHelper.readConfig(); 68 | } 69 | } 70 | 71 | /** 72 | * Create a new testdatabase pool 73 | * 74 | * @param poolName 75 | * @param templateDatabaseName 76 | * @return 77 | * @throws Exception 78 | */ 79 | public static DatabasePoolResponse createPool(String poolName, String templateDatabaseName) throws Exception { 80 | ProviderConfig config = config(); 81 | requireConfig(config); 82 | DatabasePoolRequest request = new DatabasePoolRequest(); 83 | request.setTemplateDatabaseName(templateDatabaseName); 84 | DatabasePoolConnection connection = new DatabasePoolConnection(config.getPostgresql()); 85 | request.setConnection(connection); 86 | ProviderClient client = client(); 87 | DatabasePoolResponse response = client.createPool(poolName, request).get(); 88 | return response; 89 | } 90 | 91 | /** 92 | * Create a new empty database which can be used to setup a new pool. 93 | * 94 | * @param name 95 | * @throws SQLException 96 | */ 97 | public static void createPostgreSQLDatabase(String name) throws SQLException { 98 | ProviderConfig config = config(); 99 | requireConfig(config); 100 | PostgresqlConfig postgresConfig = config.getPostgresql(); 101 | String sql = "CREATE DATABASE " + name; 102 | try (Connection connection = DriverManager.getConnection(config.getPostgresql().adminJdbcUrl(), postgresConfig.getUsername(), 103 | postgresConfig.getPassword())) { 104 | Statement statement1 = connection.createStatement(); 105 | statement1.execute(sql); 106 | } 107 | } 108 | 109 | /** 110 | * Drop and create create the database with the given name. 111 | * 112 | * @param name 113 | * @throws SQLException 114 | */ 115 | public static void dropCreatePostgreSQLDatabase(String name) throws SQLException { 116 | ProviderConfig config = config(); 117 | requireConfig(config); 118 | PostgresqlConfig postgresConfig = config.getPostgresql(); 119 | String dropSQL = "DROP DATABASE IF EXISTS " + name; 120 | String createSQL = "CREATE DATABASE " + name; 121 | try (Connection connection = DriverManager.getConnection(config.getPostgresql().adminJdbcUrl(), postgresConfig.getUsername(), 122 | postgresConfig.getPassword())) { 123 | connection.createStatement().execute(dropSQL); 124 | connection.createStatement().execute(createSQL); 125 | } 126 | 127 | } 128 | 129 | private static void requireConfig(ProviderConfig config) { 130 | if (config == null) { 131 | throw new RuntimeException( 132 | "Unable to locate provider configuration file in filesystem. Started search here: " + ProviderConfigHelper.currentConfigPath()); 133 | } 134 | 135 | } 136 | 137 | /** 138 | * Set a local provider config which will supersede any other config (e.g. ENV, config file). 139 | * 140 | * @param config 141 | */ 142 | public static void localConfig(ProviderConfig config) { 143 | TestDatabaseProvider.localConfig = config; 144 | } 145 | 146 | } 147 | -------------------------------------------------------------------------------- /client/src/main/java/io/metaloom/test/container/provider/client/WebsocketLinkListener.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.client; 2 | 3 | import java.net.http.WebSocket; 4 | import java.nio.ByteBuffer; 5 | import java.nio.charset.StandardCharsets; 6 | import java.util.concurrent.CompletableFuture; 7 | import java.util.concurrent.CompletionStage; 8 | import java.util.concurrent.Executors; 9 | import java.util.concurrent.ScheduledExecutorService; 10 | import java.util.concurrent.ScheduledFuture; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | import com.fasterxml.jackson.databind.JsonNode; 17 | 18 | import io.metaloom.test.container.provider.model.DatabaseAllocationResponse; 19 | 20 | public class WebsocketLinkListener implements WebSocket.Listener { 21 | 22 | public static final Logger log = LoggerFactory.getLogger(WebsocketLinkListener.class); 23 | 24 | private CompletableFuture allocationFuture = new CompletableFuture<>(); 25 | 26 | private ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1); 27 | 28 | private ScheduledFuture pingExec; 29 | 30 | private final String testRef; 31 | 32 | private final String poolName; 33 | 34 | public WebsocketLinkListener(String poolName, String testRef) { 35 | this.poolName = poolName; 36 | this.testRef = testRef; 37 | } 38 | 39 | @Override 40 | public void onOpen(WebSocket webSocket) { 41 | log.debug("Opened websocket - requesting allocation"); 42 | 43 | // Sending name of the currently executed test to the server. 44 | // It will allocate a database and send us the result. 45 | try { 46 | String id = poolName + "/" + testRef; 47 | webSocket.sendText(id, true).get(2000, TimeUnit.MILLISECONDS); 48 | } catch (Exception e) { 49 | log.error("Error while sending allocation request", e); 50 | } 51 | 52 | // Start sending pings 53 | this.pingExec = executorService.scheduleAtFixedRate(() -> { 54 | String data = "Ping"; 55 | ByteBuffer payload = ByteBuffer.wrap(data.getBytes()); 56 | webSocket.sendPing(payload); 57 | }, 1000, 500, TimeUnit.MILLISECONDS); 58 | 59 | WebSocket.Listener.super.onOpen(webSocket); 60 | } 61 | 62 | @Override 63 | public CompletionStage onClose(WebSocket webSocket, int statusCode, String reason) { 64 | if (pingExec != null && !pingExec.isCancelled()) { 65 | pingExec.cancel(true); 66 | } 67 | return WebSocket.Listener.super.onClose(webSocket, statusCode, reason); 68 | } 69 | 70 | @Override 71 | public CompletionStage onText(WebSocket webSocket, CharSequence data, boolean last) { 72 | System.out.println("onText received " + data); 73 | return WebSocket.Listener.super.onText(webSocket, data, last); 74 | } 75 | 76 | @Override 77 | public CompletionStage onPong(WebSocket webSocket, ByteBuffer message) { 78 | log.debug("Got pong"); 79 | return WebSocket.Listener.super.onPong(webSocket, message); 80 | } 81 | 82 | @Override 83 | public CompletionStage onPing(WebSocket webSocket, ByteBuffer message) { 84 | log.debug("Got ping"); 85 | return WebSocket.Listener.super.onPing(webSocket, message); 86 | } 87 | 88 | @Override 89 | public CompletionStage onBinary(WebSocket webSocket, ByteBuffer data, boolean last) { 90 | String json = StandardCharsets.UTF_8.decode(data).toString(); 91 | JsonNode node = JSON.toJsonNode(json); 92 | if (node.has("error")) { 93 | String error = node.get("error").asText(); 94 | allocationFuture.completeExceptionally(new Exception("Got error from server {" + error + "}")); 95 | } else { 96 | // String json = new String(data.array(), Charset.defaultCharset()); 97 | log.info("Got provider allocation info:\n{}", json); 98 | DatabaseAllocationResponse response = JSON.fromString(json, DatabaseAllocationResponse.class); 99 | ClientAllocation allocation = new ClientAllocation(webSocket, response); 100 | allocationFuture.complete(allocation); 101 | } 102 | // result.complete(allocation); 103 | return WebSocket.Listener.super.onBinary(webSocket, data, last); 104 | } 105 | 106 | @Override 107 | public void onError(WebSocket webSocket, Throwable error) { 108 | log.error("Error occured while handling the connection to the provider", error); 109 | if (pingExec != null && !pingExec.isCancelled()) { 110 | pingExec.cancel(true); 111 | } 112 | } 113 | 114 | public CompletableFuture allocation() { 115 | return allocationFuture; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /common/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | testdatabase-provider-common 6 | 7 | 8 | io.metaloom.test 9 | testdatabase-provider 10 | 0.1.4 11 | 12 | 13 | Testdatabase Provider :: Common 14 | 15 | 16 | 17 | 18 | com.fasterxml.jackson.core 19 | jackson-databind 20 | 2.14.2 21 | 22 | 23 | com.fasterxml.jackson.datatype 24 | jackson-datatype-jsr310 25 | 2.14.2 26 | 27 | 28 | org.slf4j 29 | slf4j-api 30 | 31 | 32 | 33 | 34 | org.junit.jupiter 35 | junit-jupiter-api 36 | test 37 | 38 | 39 | ch.qos.logback 40 | logback-classic 41 | test 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | src/main/resources 50 | true 51 | 52 | **/provider.build.properties 53 | 54 | 55 | 56 | src/main/resources 57 | false 58 | 59 | **/* 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /common/src/main/java/io/metaloom/test/container/provider/common/ClientEnv.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.common; 2 | 3 | public final class ClientEnv { 4 | 5 | public static final String TESTDATABASE_PROVIDER_HOST_KEY = "TESTDATABASE_PROVIDER_HOST"; 6 | 7 | public static final String TESTDATABASE_PROVIDER_PORT_KEY = "TESTDATABASE_PROVIDER_PORT"; 8 | 9 | public static Integer getProviderPort() { 10 | String portStr = System.getenv(ClientEnv.TESTDATABASE_PROVIDER_PORT_KEY); 11 | if (portStr == null) { 12 | return null; 13 | } 14 | return Integer.parseInt(portStr); 15 | } 16 | 17 | public static String getProviderHost() { 18 | return System.getenv(ClientEnv.TESTDATABASE_PROVIDER_HOST_KEY); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /common/src/main/java/io/metaloom/test/container/provider/common/ServerEnv.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.common; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | public final class ServerEnv { 7 | 8 | private static final Logger log = LoggerFactory.getLogger(ServerEnv.class); 9 | 10 | public static final int DEFAULT_POOL_MINIMUM = 10; 11 | public static final int DEFAULT_POOL_MAXIMUM = 20; 12 | public static final int DEFAULT_POOL_INCREMENT = 5; 13 | private static final int DEFAULT_HTTP_PORT = 8080; 14 | 15 | public static final String TESTDATABASE_PROVIDER_DATABASE_HOST_KEY = "TESTDATABASE_PROVIDER_DATABASE_HOST"; 16 | 17 | public static final String TESTDATABASE_PROVIDER_DATABASE_PORT_KEY = "TESTDATABASE_PROVIDER_DATABASE_PORT"; 18 | 19 | public static final String TESTDATABASE_PROVIDER_DATABASE_INTERNAL_HOST_KEY = "TESTDATABASE_PROVIDER_DATABASE_INTERNAL_HOST"; 20 | 21 | public static final String TESTDATABASE_PROVIDER_DATABASE_INTERNAL_PORT_KEY = "TESTDATABASE_PROVIDER_DATABASE_INTERNAL_PORT"; 22 | 23 | public static final String TESTDATABASE_PROVIDER_DATABASE_USERNAME_KEY = "TESTDATABASE_PROVIDER_DATABASE_USERNAME"; 24 | 25 | public static final String TESTDATABASE_PROVIDER_DATABASE_PASSWORD_KEY = "TESTDATABASE_PROVIDER_DATABASE_PASSWORD"; 26 | 27 | public static final String TESTDATABASE_PROVIDER_DATABASE_DBNAME_KEY = "TESTDATABASE_PROVIDER_DATABASE_DBNAME_KEY"; 28 | 29 | public static final String TESTDATABASE_PROVIDER_POOL_MINIMUM_KEY = "TESTDATABASE_PROVIDER_POOL_MINIMUM"; 30 | 31 | public static final String TESTDATABASE_PROVIDER_POOL_MAXIMUM_KEY = "TESTDATABASE_PROVIDER_POOL_MAXIMUM"; 32 | 33 | public static final String TESTDATABASE_PROVIDER_POOL_INCREMENT_KEY = "TESTDATABASE_PROVIDER_POOL_INCREMENT"; 34 | 35 | public static final String TESTDATABASE_PROVIDER_DATABASE_TEMPLATE_DBNAME_KEY = "TESTDATABASE_PROVIDER_POOL_TEMPLATE_NAME"; 36 | 37 | private static final String TESTDATABASE_PROVIDER_HTTP_PORT_KEY = "TESTDATABASE_PROVIDER_HTTP_PORT"; 38 | 39 | 40 | public static int getPoolMinimum() { 41 | String minimumStr = getEnv(ServerEnv.TESTDATABASE_PROVIDER_POOL_MINIMUM_KEY); 42 | if (minimumStr == null) { 43 | log.info("Using default pool minimum value {}", DEFAULT_POOL_MINIMUM); 44 | return DEFAULT_POOL_MINIMUM; 45 | } else { 46 | return Integer.parseInt(minimumStr); 47 | } 48 | } 49 | 50 | public static int getPoolMaximum() { 51 | String maximumStr = getEnv(ServerEnv.TESTDATABASE_PROVIDER_POOL_MAXIMUM_KEY); 52 | if (maximumStr == null) { 53 | log.info("Using default pool maximum value {}", DEFAULT_POOL_MAXIMUM); 54 | return DEFAULT_POOL_MAXIMUM; 55 | } else { 56 | return Integer.parseInt(maximumStr); 57 | } 58 | } 59 | 60 | public static int getPoolIncrement() { 61 | String incrementStr = getEnv(ServerEnv.TESTDATABASE_PROVIDER_POOL_INCREMENT_KEY); 62 | if (incrementStr == null) { 63 | log.info("Using default increment value {}", DEFAULT_POOL_INCREMENT); 64 | return DEFAULT_POOL_INCREMENT; 65 | } else { 66 | return Integer.parseInt(incrementStr); 67 | } 68 | } 69 | 70 | public static Integer getDatabasePort() { 71 | String portStr = getEnv(ServerEnv.TESTDATABASE_PROVIDER_DATABASE_PORT_KEY); 72 | if (portStr == null) { 73 | return null; 74 | } 75 | return Integer.parseInt(portStr); 76 | } 77 | 78 | public static String getDatabaseHost() { 79 | return getEnv(ServerEnv.TESTDATABASE_PROVIDER_DATABASE_HOST_KEY); 80 | } 81 | 82 | public static String getInternalDatabaseHost() { 83 | return getEnv(ServerEnv.TESTDATABASE_PROVIDER_DATABASE_INTERNAL_HOST_KEY); 84 | } 85 | 86 | public static Integer getInternalDatabasePort() { 87 | String portStr = getEnv(ServerEnv.TESTDATABASE_PROVIDER_DATABASE_INTERNAL_PORT_KEY); 88 | if (portStr == null) { 89 | return null; 90 | } 91 | return Integer.parseInt(portStr); 92 | } 93 | 94 | public static String getDatabasePassword() { 95 | return getEnv(ServerEnv.TESTDATABASE_PROVIDER_DATABASE_PASSWORD_KEY); 96 | } 97 | 98 | public static String getDatabaseUsername() { 99 | return getEnv(ServerEnv.TESTDATABASE_PROVIDER_DATABASE_USERNAME_KEY); 100 | } 101 | 102 | public static String getDatabaseName() { 103 | return getEnv(ServerEnv.TESTDATABASE_PROVIDER_DATABASE_DBNAME_KEY); 104 | } 105 | 106 | public static String getDatabaseTemplateName() { 107 | return getEnv(ServerEnv.TESTDATABASE_PROVIDER_DATABASE_TEMPLATE_DBNAME_KEY); 108 | } 109 | 110 | private static String getEnv(String key) { 111 | return System.getenv(key); 112 | } 113 | 114 | public static int getHttpPort() { 115 | String portStr = getEnv(ServerEnv.TESTDATABASE_PROVIDER_HTTP_PORT_KEY); 116 | if (portStr == null) { 117 | return DEFAULT_HTTP_PORT; 118 | } 119 | return Integer.parseInt(portStr); 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /common/src/main/java/io/metaloom/test/container/provider/common/config/AbstractDatabaseConfig.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.common.config; 2 | 3 | public abstract class AbstractDatabaseConfig implements DatabaseConfig { 4 | 5 | private String host; 6 | private Integer port; 7 | 8 | /** 9 | * When using docker the provider connects to the database via the internal hostname/port 10 | */ 11 | private String internalHost; 12 | private Integer internalPort; 13 | private String username; 14 | private String password; 15 | private String databaseName; 16 | private String containerId; 17 | 18 | @Override 19 | public String getContainerId() { 20 | return containerId; 21 | } 22 | 23 | @Override 24 | public void setContainerId(String containerId) { 25 | this.containerId = containerId; 26 | } 27 | 28 | @Override 29 | public String getHost() { 30 | return host; 31 | } 32 | 33 | @Override 34 | public void setHost(String host) { 35 | this.host = host; 36 | } 37 | 38 | @Override 39 | public String getPassword() { 40 | return password; 41 | } 42 | 43 | @Override 44 | public void setPassword(String password) { 45 | this.password = password; 46 | } 47 | 48 | @Override 49 | public Integer getPort() { 50 | return port; 51 | } 52 | 53 | @Override 54 | public void setPort(Integer port) { 55 | this.port = port; 56 | } 57 | 58 | @Override 59 | public String getUsername() { 60 | return username; 61 | } 62 | 63 | @Override 64 | public void setUsername(String username) { 65 | this.username = username; 66 | } 67 | 68 | @Override 69 | public String getDatabaseName() { 70 | return databaseName; 71 | } 72 | 73 | @Override 74 | public void setDatabaseName(String databaseName) { 75 | this.databaseName = databaseName; 76 | } 77 | 78 | @Override 79 | public String getInternalHost() { 80 | return internalHost; 81 | } 82 | 83 | @Override 84 | public void setInternalHost(String internalHost) { 85 | this.internalHost = internalHost; 86 | } 87 | 88 | @Override 89 | public Integer getInternalPort() { 90 | return internalPort; 91 | } 92 | 93 | @Override 94 | public void setInternalPort(Integer internalPort) { 95 | this.internalPort = internalPort; 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /common/src/main/java/io/metaloom/test/container/provider/common/config/DatabaseConfig.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.common.config; 2 | 3 | public interface DatabaseConfig { 4 | 5 | String getContainerId(); 6 | 7 | void setContainerId(String containerId); 8 | 9 | String getHost(); 10 | 11 | void setHost(String host); 12 | 13 | Integer getPort(); 14 | 15 | void setPort(Integer port); 16 | 17 | String getInternalHost(); 18 | 19 | void setInternalHost(String internalHost); 20 | 21 | Integer getInternalPort(); 22 | 23 | void setInternalPort(Integer internalPort); 24 | 25 | String getUsername(); 26 | 27 | void setUsername(String username); 28 | 29 | String getPassword(); 30 | 31 | void setPassword(String password); 32 | 33 | String getDatabaseName(); 34 | 35 | void setDatabaseName(String databaseName); 36 | 37 | String adminJdbcUrl(); 38 | 39 | String jdbcUrl(String databaseName); 40 | 41 | } 42 | -------------------------------------------------------------------------------- /common/src/main/java/io/metaloom/test/container/provider/common/config/PostgresqlConfig.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.common.config; 2 | 3 | public class PostgresqlConfig extends AbstractDatabaseConfig { 4 | 5 | @Override 6 | public String adminJdbcUrl() { 7 | return jdbcUrl(getDatabaseName()); 8 | } 9 | 10 | @Override 11 | public String jdbcUrl(String databaseName) { 12 | return ("jdbc:postgresql://" + 13 | getHost() + 14 | ":" + 15 | getPort() + 16 | "/" + 17 | databaseName); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /common/src/main/java/io/metaloom/test/container/provider/common/config/ProviderConfig.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.common.config; 2 | 3 | public class ProviderConfig { 4 | 5 | private String providerHost; 6 | private int providerPort; 7 | private String providerContainerId; 8 | 9 | private PostgresqlConfig postgresql = new PostgresqlConfig(); 10 | 11 | public String getProviderContainerId() { 12 | return providerContainerId; 13 | } 14 | 15 | public ProviderConfig setProviderContainerId(String containerId) { 16 | this.providerContainerId = containerId; 17 | return this; 18 | } 19 | 20 | public int getProviderPort() { 21 | return providerPort; 22 | } 23 | 24 | public ProviderConfig setProviderPort(int port) { 25 | this.providerPort = port; 26 | return this; 27 | } 28 | 29 | public String getProviderHost() { 30 | return providerHost; 31 | } 32 | 33 | public ProviderConfig setProviderHost(String host) { 34 | this.providerHost = host; 35 | return this; 36 | } 37 | 38 | public PostgresqlConfig getPostgresql() { 39 | return postgresql; 40 | } 41 | 42 | public ProviderConfig setPostgresql(PostgresqlConfig postgresql) { 43 | this.postgresql = postgresql; 44 | return this; 45 | } 46 | 47 | @Override 48 | public String toString() { 49 | return "provider: " + getProviderContainerId() + " " + getProviderHost() + ":" + getProviderPort(); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /common/src/main/java/io/metaloom/test/container/provider/common/config/ProviderConfigHelper.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.common.config; 2 | 3 | import java.io.File; 4 | import java.io.IOException; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | import java.nio.file.Paths; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import com.fasterxml.jackson.databind.ObjectMapper; 13 | 14 | public final class ProviderConfigHelper { 15 | 16 | public static final Logger log = LoggerFactory.getLogger(ProviderConfigHelper.class); 17 | 18 | public static String PROVIDER_CONFIG_FILENAME = "testdatabase-provider.json"; 19 | 20 | public static final String CONFIG_FOLDERNAME = "target"; 21 | 22 | private ProviderConfigHelper() { 23 | } 24 | 25 | private static ObjectMapper mapper = new ObjectMapper(); 26 | 27 | /** 28 | * Searches for the config in all parent directory and overwrites the config if found. Otherwise the config will be written in the current working directory 29 | * location for the config. 30 | * 31 | * @param config 32 | */ 33 | public static void writeConfig(ProviderConfig config) { 34 | try { 35 | Path configPath = locateConfigPath(); 36 | if (configPath == null) { 37 | configPath = currentConfigPath(); 38 | ensureConfigFolder(); 39 | } 40 | mapper.writerWithDefaultPrettyPrinter().writeValue(configPath.toFile(), config); 41 | } catch (Exception e) { 42 | throw new RuntimeException(e); 43 | } 44 | } 45 | 46 | /** 47 | * Config path which points to the location based on the current working dir. 48 | * 49 | * @return 50 | */ 51 | public static Path currentConfigPath() { 52 | return Paths.get(CONFIG_FOLDERNAME, PROVIDER_CONFIG_FILENAME); 53 | } 54 | 55 | /** 56 | * Locate the config and delete it. 57 | * 58 | * @throws IOException 59 | */ 60 | public static void deleteConfig() throws IOException { 61 | Path config = locateConfigPath(); 62 | if (Files.isRegularFile(config)) { 63 | Files.delete(config); 64 | } else { 65 | throw new RuntimeException("The config " + config + " is not a rgular file"); 66 | } 67 | } 68 | 69 | /** 70 | * Attempt to locate the config file by looking into parent directories if needed. 71 | * 72 | * @return 73 | */ 74 | public static ProviderConfig readConfig() { 75 | try { 76 | Path configPath = locateConfigPath(); 77 | if (configPath == null) { 78 | return null; 79 | } else { 80 | return readConfig(configPath); 81 | } 82 | } catch (Exception e) { 83 | throw new RuntimeException(e); 84 | } 85 | } 86 | 87 | private static Path locateConfigPath() { 88 | Path folder = Paths.get("").toAbsolutePath(); 89 | while (folder != null) { 90 | log.debug("Looking for config file in {}", folder); 91 | Path path = locateIn(folder); 92 | if (path != null) { 93 | return path; 94 | } 95 | folder = folder.getParent(); 96 | } 97 | return null; 98 | } 99 | 100 | private static Path locateIn(Path folder) { 101 | Path path = folder.resolve(Paths.get("target", PROVIDER_CONFIG_FILENAME)); 102 | if (Files.exists(path)) { 103 | log.debug("Found config in {}", path); 104 | return path; 105 | } 106 | Path path2 = folder.resolve(Paths.get(PROVIDER_CONFIG_FILENAME)); 107 | if (Files.exists(path2)) { 108 | log.debug("Found config in {}", path2); 109 | return path2; 110 | } 111 | return null; 112 | } 113 | 114 | private static void ensureConfigFolder() { 115 | File folder = new File(CONFIG_FOLDERNAME); 116 | if (!folder.exists()) { 117 | folder.mkdirs(); 118 | } 119 | } 120 | 121 | private static ProviderConfig readConfig(Path path) throws IOException { 122 | if (Files.exists(path)) { 123 | return mapper.readValue(path.toFile(), ProviderConfig.class); 124 | } else { 125 | return null; 126 | } 127 | } 128 | 129 | } -------------------------------------------------------------------------------- /common/src/main/java/io/metaloom/test/container/provider/common/version/BuildInfo.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.common.version; 2 | 3 | import java.util.Properties; 4 | 5 | public class BuildInfo { 6 | private String version; 7 | private String buildtimestamp; 8 | 9 | public BuildInfo(String version, String buildtimestamp) { 10 | this.version = version; 11 | this.buildtimestamp = buildtimestamp; 12 | } 13 | 14 | /** 15 | * Create a new build info using the provided properties. 16 | * 17 | * @param buildProperties 18 | */ 19 | public BuildInfo(Properties buildProperties) { 20 | this(buildProperties.getProperty("provider.version"), buildProperties.getProperty("provider.build.timestamp")); 21 | } 22 | 23 | /** 24 | * Return the build timestamp. 25 | * 26 | * @return 27 | */ 28 | public String getBuildtimestamp() { 29 | return buildtimestamp; 30 | } 31 | 32 | /** 33 | * Set the build timestamp 34 | * 35 | * @param buildtimestamp 36 | * @return Fluent API 37 | */ 38 | public BuildInfo setBuildtimestamp(String buildtimestamp) { 39 | this.buildtimestamp = buildtimestamp; 40 | return this; 41 | } 42 | 43 | /** 44 | * Return the version string. 45 | * 46 | * @return 47 | */ 48 | public String getVersion() { 49 | return version; 50 | } 51 | 52 | /** 53 | * Set the version string. 54 | * 55 | * @param version 56 | * @return 57 | */ 58 | public BuildInfo setVersion(String version) { 59 | this.version = version; 60 | return this; 61 | } 62 | 63 | @Override 64 | public String toString() { 65 | return getVersion() + " " + getBuildtimestamp(); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /common/src/main/java/io/metaloom/test/container/provider/common/version/Version.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.common.version; 2 | 3 | import java.util.Properties; 4 | 5 | public final class Version { 6 | 7 | private static BuildInfo buildInfo = null; 8 | 9 | private Version() { 10 | } 11 | 12 | /** 13 | * Return the mesh build information. 14 | * 15 | * @return Provider version and build timestamp. 16 | */ 17 | public static BuildInfo getBuildInfo() { 18 | try { 19 | if (buildInfo == null) { 20 | Properties buildProperties = new Properties(); 21 | buildProperties.load(Version.class.getResourceAsStream("/provider.build.properties")); 22 | // Cache the build information 23 | buildInfo = new BuildInfo(buildProperties); 24 | } 25 | return buildInfo; 26 | } catch (Exception e) { 27 | return new BuildInfo("unknown", "unknown"); 28 | } 29 | } 30 | 31 | /** 32 | * Return the mesh version (without build timestamp) 33 | * 34 | * @return Provider version 35 | */ 36 | public static String getPlainVersion() { 37 | return getBuildInfo().getVersion(); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /common/src/main/java/io/metaloom/test/container/provider/model/AbstractDatabasePoolModel.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.model; 2 | 3 | public abstract class AbstractDatabasePoolModel implements RestModel { 4 | 5 | private DatabasePoolConnection connection; 6 | 7 | private DatabasePoolSettings settings; 8 | 9 | private String templateDatabaseName; 10 | 11 | public DatabasePoolConnection getConnection() { 12 | return connection; 13 | } 14 | 15 | public AbstractDatabasePoolModel setConnection(DatabasePoolConnection connection) { 16 | this.connection = connection; 17 | return this; 18 | } 19 | 20 | public String getTemplateDatabaseName() { 21 | return templateDatabaseName; 22 | } 23 | 24 | public AbstractDatabasePoolModel setTemplateDatabaseName(String templateDatabaseName) { 25 | this.templateDatabaseName = templateDatabaseName; 26 | return this; 27 | } 28 | 29 | public DatabasePoolSettings getSettings() { 30 | return settings; 31 | } 32 | 33 | public AbstractDatabasePoolModel setSettings(DatabasePoolSettings settings) { 34 | this.settings = settings; 35 | return this; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /common/src/main/java/io/metaloom/test/container/provider/model/DatabaseAllocationResponse.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.model; 2 | 3 | public class DatabaseAllocationResponse implements RestModel { 4 | 5 | private String id; 6 | private String poolId; 7 | 8 | private String host; 9 | private int port; 10 | private String jdbcUrl; 11 | private String username; 12 | private String password; 13 | private String databaseName; 14 | 15 | public String getJdbcUrl() { 16 | return jdbcUrl; 17 | } 18 | 19 | public DatabaseAllocationResponse setJdbcUrl(String jdbcUrl) { 20 | this.jdbcUrl = jdbcUrl; 21 | return this; 22 | } 23 | 24 | public String getUsername() { 25 | return username; 26 | } 27 | 28 | public DatabaseAllocationResponse setUsername(String username) { 29 | this.username = username; 30 | return this; 31 | } 32 | 33 | public String getPassword() { 34 | return password; 35 | } 36 | 37 | public DatabaseAllocationResponse setPassword(String password) { 38 | this.password = password; 39 | return this; 40 | } 41 | 42 | public String getHost() { 43 | return host; 44 | } 45 | 46 | public DatabaseAllocationResponse setHost(String host) { 47 | this.host = host; 48 | return this; 49 | } 50 | 51 | public int getPort() { 52 | return port; 53 | } 54 | 55 | public DatabaseAllocationResponse setPort(int port) { 56 | this.port = port; 57 | return this; 58 | } 59 | 60 | public String getId() { 61 | return id; 62 | } 63 | 64 | public DatabaseAllocationResponse setId(String id) { 65 | this.id = id; 66 | return this; 67 | } 68 | 69 | public String getPoolId() { 70 | return poolId; 71 | } 72 | 73 | public DatabaseAllocationResponse setPoolId(String poolId) { 74 | this.poolId = poolId; 75 | return this; 76 | } 77 | 78 | public String getDatabaseName() { 79 | return databaseName; 80 | } 81 | 82 | public DatabaseAllocationResponse setDatabaseName(String databaseName) { 83 | this.databaseName = databaseName; 84 | return this; 85 | } 86 | 87 | @Override 88 | public String toString() { 89 | return "allocation: " + getId() + " of " + getPoolId() + " => " + getJdbcUrl(); 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /common/src/main/java/io/metaloom/test/container/provider/model/DatabasePoolConnection.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.model; 2 | 3 | import io.metaloom.test.container.provider.common.config.PostgresqlConfig; 4 | 5 | public class DatabasePoolConnection implements RestModel { 6 | 7 | private Integer port; 8 | private String host; 9 | 10 | private String internalHost; 11 | private Integer internalPort; 12 | 13 | private String username; 14 | private String password; 15 | private String database; 16 | 17 | public DatabasePoolConnection() { 18 | } 19 | 20 | /** 21 | * Construct a new pool connection using the provided config 22 | * 23 | * @param config 24 | */ 25 | public DatabasePoolConnection(PostgresqlConfig config) { 26 | this.host = config.getHost(); 27 | this.port = config.getPort(); 28 | this.internalHost = config.getInternalHost(); 29 | this.internalPort = config.getInternalPort(); 30 | this.username = config.getUsername(); 31 | this.password = config.getPassword(); 32 | this.database = config.getDatabaseName(); 33 | } 34 | 35 | public String getHost() { 36 | return host; 37 | } 38 | 39 | public DatabasePoolConnection setHost(String host) { 40 | this.host = host; 41 | return this; 42 | } 43 | 44 | public Integer getPort() { 45 | return port; 46 | } 47 | 48 | public DatabasePoolConnection setPort(Integer port) { 49 | this.port = port; 50 | return this; 51 | } 52 | 53 | public String getInternalHost() { 54 | return internalHost; 55 | } 56 | 57 | public DatabasePoolConnection setInternalHost(String internalHost) { 58 | this.internalHost = internalHost; 59 | return this; 60 | } 61 | 62 | public Integer getInternalPort() { 63 | return internalPort; 64 | } 65 | 66 | public DatabasePoolConnection setInternalPort(Integer internalPort) { 67 | this.internalPort = internalPort; 68 | return this; 69 | } 70 | 71 | public String getUsername() { 72 | return username; 73 | } 74 | 75 | public DatabasePoolConnection setUsername(String username) { 76 | this.username = username; 77 | return this; 78 | } 79 | 80 | public String getPassword() { 81 | return password; 82 | } 83 | 84 | public DatabasePoolConnection setPassword(String password) { 85 | this.password = password; 86 | return this; 87 | } 88 | 89 | public String getDatabase() { 90 | return database; 91 | } 92 | 93 | public DatabasePoolConnection setDatabase(String database) { 94 | this.database = database; 95 | return this; 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /common/src/main/java/io/metaloom/test/container/provider/model/DatabasePoolListResponse.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.model; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public class DatabasePoolListResponse implements RestModel { 7 | 8 | private List list = new ArrayList<>(); 9 | 10 | public List getList() { 11 | return list; 12 | } 13 | 14 | public DatabasePoolListResponse setList(List list) { 15 | this.list = list; 16 | return this; 17 | } 18 | 19 | public void add(DatabasePoolResponse response) { 20 | list.add(response); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /common/src/main/java/io/metaloom/test/container/provider/model/DatabasePoolRequest.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.model; 2 | 3 | public class DatabasePoolRequest extends AbstractDatabasePoolModel { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /common/src/main/java/io/metaloom/test/container/provider/model/DatabasePoolResponse.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.model; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | import com.fasterxml.jackson.annotation.JsonFormat; 6 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 7 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 8 | import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; 9 | import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; 10 | 11 | public class DatabasePoolResponse extends AbstractDatabasePoolModel { 12 | 13 | private String id; 14 | private int level; 15 | 16 | private int allocationLevel; 17 | private boolean started; 18 | 19 | @JsonDeserialize(using = LocalDateTimeDeserializer.class) 20 | @JsonSerialize(using = LocalDateTimeSerializer.class) 21 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss") 22 | private LocalDateTime created; 23 | 24 | public int getLevel() { 25 | return level; 26 | } 27 | 28 | public DatabasePoolResponse setLevel(int level) { 29 | this.level = level; 30 | return this; 31 | } 32 | 33 | public int getAllocationLevel() { 34 | return allocationLevel; 35 | } 36 | 37 | public DatabasePoolResponse setAllocationLevel(int allocationLevel) { 38 | this.allocationLevel = allocationLevel; 39 | return this; 40 | } 41 | 42 | public boolean isStarted() { 43 | return started; 44 | } 45 | 46 | public DatabasePoolResponse setStarted(boolean started) { 47 | this.started = started; 48 | return this; 49 | } 50 | 51 | public String getId() { 52 | return id; 53 | } 54 | 55 | public DatabasePoolResponse setId(String id) { 56 | this.id = id; 57 | return this; 58 | } 59 | 60 | public LocalDateTime getCreated() { 61 | return created; 62 | } 63 | 64 | public void setCreated(LocalDateTime creationDate) { 65 | this.created = creationDate; 66 | } 67 | 68 | @Override 69 | public String toString() { 70 | return "pool: " + getId() + " " + getCreated() + ", started: " + isStarted() + ", level: " + getLevel() + ", already allocated: " 71 | + getAllocationLevel(); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /common/src/main/java/io/metaloom/test/container/provider/model/DatabasePoolSettings.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.model; 2 | 3 | public class DatabasePoolSettings implements RestModel { 4 | 5 | private Integer increment; 6 | private Integer minimum; 7 | private Integer maximum; 8 | 9 | public Integer getMinimum() { 10 | return minimum; 11 | } 12 | 13 | public DatabasePoolSettings setMinimum(Integer minimum) { 14 | this.minimum = minimum; 15 | return this; 16 | } 17 | 18 | public Integer getMaximum() { 19 | return maximum; 20 | } 21 | 22 | public DatabasePoolSettings setMaximum(Integer maximum) { 23 | this.maximum = maximum; 24 | return this; 25 | } 26 | 27 | public Integer getIncrement() { 28 | return increment; 29 | } 30 | 31 | public DatabasePoolSettings setIncrement(Integer increment) { 32 | this.increment = increment; 33 | return this; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /common/src/main/java/io/metaloom/test/container/provider/model/RestModel.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.model; 2 | 3 | /** 4 | * Marker interface for REST model classes. 5 | */ 6 | public interface RestModel { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /common/src/main/resources/provider.build.properties: -------------------------------------------------------------------------------- 1 | provider.version=${version} 2 | provider.build.timestamp=${provider.build.timestamp} -------------------------------------------------------------------------------- /common/src/test/java/io/metaloom/test/container/provider/common/config/ProviderConfigHelperTest.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.common.config; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertNotNull; 5 | import static org.junit.jupiter.api.Assertions.assertTrue; 6 | 7 | import java.io.File; 8 | import java.io.IOException; 9 | import java.nio.file.Files; 10 | 11 | import org.junit.jupiter.api.Test; 12 | 13 | public class ProviderConfigHelperTest { 14 | 15 | @Test 16 | public void testReadFromParent() throws IOException { 17 | File testFile = writeConfigIntoParentFolder(); 18 | ProviderConfig state = ProviderConfigHelper.readConfig(); 19 | assertNotNull(state); 20 | assertEquals("test1234", state.getPostgresql().getHost()); 21 | testFile.delete(); 22 | } 23 | 24 | @Test 25 | public void testWriteCurrent() { 26 | File config = new File(ProviderConfigHelper.CONFIG_FOLDERNAME, ProviderConfigHelper.PROVIDER_CONFIG_FILENAME); 27 | if (config.exists()) { 28 | config.delete(); 29 | } 30 | ProviderConfigHelper.writeConfig(new ProviderConfig()); 31 | assertTrue(config.exists()); 32 | config.delete(); 33 | } 34 | 35 | @Test 36 | public void testWriteParent() throws IOException { 37 | writeConfigIntoParentFolder(); 38 | ProviderConfigHelper.writeConfig(new ProviderConfig().setProviderContainerId("enemenemuh")); 39 | assertEquals("enemenemuh", ProviderConfigHelper.readConfig().getProviderContainerId()); 40 | } 41 | 42 | private File writeConfigIntoParentFolder() throws IOException { 43 | ProviderConfigHelper.PROVIDER_CONFIG_FILENAME = "test-testdb-provider.json"; 44 | File testFile = new File("../target/", ProviderConfigHelper.PROVIDER_CONFIG_FILENAME); 45 | if (testFile.exists()) { 46 | testFile.delete(); 47 | } 48 | String json = """ 49 | { 50 | "postgresql": { 51 | "host": "test1234" 52 | } 53 | 54 | } 55 | """; 56 | Files.writeString(testFile.toPath(), json); 57 | return testFile; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /common/src/test/java/io/metaloom/test/container/provider/common/version/VersionTest.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.common.version; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertNotNull; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | public class VersionTest { 8 | 9 | @Test 10 | public void testVersion() { 11 | String version = Version.getPlainVersion(); 12 | System.out.println(version); 13 | assertNotNull(version); 14 | String buildTS = Version.getBuildInfo().getBuildtimestamp(); 15 | assertNotNull(buildTS); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /examples/complex/README.md: -------------------------------------------------------------------------------- 1 | # Complex Example 2 | 3 | This example shows how to use the `testdb-maven-plugin` in a multi-module maven build. 4 | 5 | It assumes two sub modules (`moduleA`, `moduleB`) 6 | 7 | ## moduleA 8 | 9 | This module contains the flyway migration resources. It prepares the provided database and initializes the testdb pool. Once the pool has been setup the provider daemon can hand out databases for tests. 10 | 11 | ## moduleB 12 | 13 | This module contains the tests for the project which make use of the created pool. 14 | 15 | 16 | ## Parent 17 | 18 | The parent project `testdatabase-provider-complex-example` contains the invocation of the `start` goal in order to setup the database container so that the flyway container can setup the db. The startup of the containers should be located in the parent module for all modules that want to access the database. Otherwise the configuration can't be located by tests. To prevent `start` from being invoked in submodules it is important to add the `false` tag to the execution. -------------------------------------------------------------------------------- /examples/complex/moduleA/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | testdatabase-provider-complex-example-modulea 6 | 7 | 8 | io.metaloom.test.example 9 | testdatabase-provider-complex-example 10 | 0.0.1-SNAPSHOT 11 | 12 | 13 | Testdatabase Provider :: Example :: Complex - Module A 14 | This module contains the database setup and pool creation. 15 | 16 | 17 | 18 | 19 | org.flywaydb 20 | flyway-maven-plugin 21 | 22 | 23 | 24 | generate-sources 25 | 26 | migrate 27 | 28 | 29 | 30 | 31 | ${maven.testdb.postgresql.jdbcurl} 32 | ${maven.testdb.postgresql.username} 33 | ${maven.testdb.postgresql.password} 34 | 35 | filesystem:src/main/flyway 36 | 37 | 38 | 39 | 40 | org.postgresql 41 | postgresql 42 | ${postgres.driver.version} 43 | 44 | 45 | 46 | 47 | 48 | io.metaloom.maven 49 | testdb-maven-plugin 50 | 51 | 53 | 54 | 55 | pool 56 | process-test-classes 57 | 58 | pool 59 | 60 | 61 | 62 | 63 | dummy 64 | test 65 | 66 | 10 67 | 30 68 | 5 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /examples/complex/moduleA/src/main/flyway/V1__initial_setup.sql: -------------------------------------------------------------------------------- 1 | DROP SCHEMA IF EXISTS helloworld CASCADE; 2 | 3 | CREATE SCHEMA helloworld; 4 | 5 | CREATE TABLE helloworld.test ( 6 | id INT NOT NULL GENERATED ALWAYS AS IDENTITY, 7 | cd INT, 8 | 9 | CONSTRAINT pk_test PRIMARY KEY (id) 10 | ); -------------------------------------------------------------------------------- /examples/complex/moduleB/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | testdatabase-provider-complex-example-moduleb 6 | 7 | 8 | io.metaloom.test.example 9 | testdatabase-provider-complex-example 10 | 0.0.1-SNAPSHOT 11 | 12 | 13 | Testdatabase Provider :: Example :: Complex - Module B 14 | This module contains the actual tests which utiilize the 15 | previously setup database 16 | 17 | 18 | 19 | 20 | io.metaloom.test 21 | testdatabase-provider-junit5 22 | test 23 | 24 | 25 | 26 | 27 | io.metaloom.test 28 | testdatabase-provider-junit4 29 | test 30 | 31 | 32 | -------------------------------------------------------------------------------- /examples/complex/moduleB/src/test/java/io/metaloom/example/ExampleJunit4Test.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.example; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | 6 | import io.metaloom.test.provider.junit4.DatabaseProviderRule; 7 | 8 | public class ExampleJunit4Test { 9 | // SNIPPET START test_snippet 10 | @Rule 11 | public DatabaseProviderRule provider = DatabaseProviderRule.create("dummy"); 12 | 13 | @Test 14 | public void testDB() throws Exception { 15 | System.out.println(provider.db()); 16 | } 17 | // SNIPPET END test_snippet 18 | 19 | @Test 20 | public void testDB2() { 21 | System.out.println(provider.db()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/complex/moduleB/src/test/java/io/metaloom/example/ExampleJunit5Test.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.example; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.RegisterExtension; 5 | 6 | import io.metaloom.test.provider.junit5.ProviderExtension; 7 | 8 | public class ExampleJunit5Test { 9 | 10 | // SNIPPET START test_snippet 11 | @RegisterExtension 12 | public static ProviderExtension ext = ProviderExtension.create("dummy"); 13 | 14 | @Test 15 | public void testDB() throws Exception { 16 | System.out.println(ext.db()); 17 | } 18 | // SNIPPET END test_snippet 19 | 20 | @Test 21 | public void testDB2() { 22 | System.out.println(ext.db()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/complex/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | testdatabase-provider-complex-example 6 | 7 | 8 | io.metaloom.test.example 9 | testdatabase-provider-examples 10 | 0.0.1-SNAPSHOT 11 | 12 | 13 | Testdatabase Provider :: Complex Example 14 | 15 | pom 16 | 17 | moduleA 18 | moduleB 19 | 20 | 21 | 22 | 23 | org.postgresql 24 | postgresql 25 | 26 | 27 | 28 | 29 | io.metaloom.test 30 | testdatabase-provider-junit5 31 | test 32 | 33 | 34 | ch.qos.logback 35 | logback-classic 36 | test 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | io.metaloom.maven 46 | testdb-maven-plugin 47 | 48 | 49 | 50 | false 51 | clean 52 | 53 | clean 54 | 55 | 56 | 57 | 58 | false 59 | 60 | start 61 | initialize 62 | 63 | start 64 | 65 | 66 | false 67 | 68 | 69 | 10 70 | 20 71 | 5 72 | 73 | false 74 | true 75 | 76 | 77 | postgres:13.2 78 | true 79 | 256 80 | sa 81 | sa 82 | test 83 | 89 | 90 | true 91 | 92 | 93 | 94 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /examples/dedicated-no-maven-plugin/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | db: 4 | container_name: 'testprovider-postgresql' 5 | hostname: 'postgresql' 6 | image: 'postgres:13.2' 7 | ports: 8 | - '15432:5432/tcp' 9 | environment: 10 | - POSTGRES_USER=sa 11 | - POSTGRES_PASSWORD=sa 12 | - POSTGRES_DB=test 13 | command: [ "postgres", "-c", "fsync=off" ] 14 | restart: unless-stopped 15 | provider: 16 | container_name: 'testprovider-provider' 17 | hostname: 'provider' 18 | image: 'metaloom/testdatabase-provider:0.1.4' 19 | ports: 20 | - '7543:8080/tcp' 21 | environment: 22 | # Admin DB 23 | - TESTDATABASE_PROVIDER_DATABASE_DBNAME_KEY=test 24 | # Public DB connection details 25 | - TESTDATABASE_PROVIDER_DATABASE_HOST=localhost 26 | - TESTDATABASE_PROVIDER_DATABASE_PORT=15432 27 | # Internal DB connection details (inter container communication) 28 | - TESTDATABASE_PROVIDER_DATABASE_INTERNAL_HOST=postgresql 29 | - TESTDATABASE_PROVIDER_DATABASE_INTERNAL_PORT=5432 30 | - TESTDATABASE_PROVIDER_DATABASE_USERNAME=sa 31 | - TESTDATABASE_PROVIDER_DATABASE_PASSWORD=sa 32 | # Default pool settings 33 | - TESTDATABASE_PROVIDER_POOL_MAXIMUM=40 34 | - TESTDATABASE_PROVIDER_POOL_MINIMUM=20 35 | - TESTDATABASE_PROVIDER_POOL_INCREMENT=10 36 | restart: unless-stopped 37 | -------------------------------------------------------------------------------- /examples/dedicated-no-maven-plugin/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | testdatabase-provider-dedicated-no-mvn-example 6 | 7 | 8 | io.metaloom.test.example 9 | testdatabase-provider-examples 10 | 0.0.1-SNAPSHOT 11 | 12 | 13 | Testdatabase Provider :: Dedicated (No Maven Plugin) Example 14 | 15 | 16 | 17 | io.metaloom.test 18 | testdatabase-provider-junit5 19 | test 20 | 21 | 22 | io.metaloom.test 23 | testdatabase-provider-junit4 24 | test 25 | 26 | 27 | org.postgresql 28 | postgresql 29 | test 30 | 31 | 32 | ch.qos.logback 33 | logback-classic 34 | test 35 | 36 | 37 | org.flywaydb 38 | flyway-core 39 | test 40 | 41 | 42 | -------------------------------------------------------------------------------- /examples/dedicated-no-maven-plugin/src/test/java/io/metaloom/example/ExampleJunit4Test.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.example; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | 6 | import io.metaloom.test.provider.junit4.DatabaseProviderRule; 7 | 8 | public class ExampleJunit4Test { 9 | 10 | // SNIPPET START provider 11 | @Rule 12 | public DatabaseProviderRule provider = DatabaseProviderRule.create("localhost", 7543, "dummy"); 13 | // SNIPPET END provider 14 | 15 | @Test 16 | public void testDB() throws Exception { 17 | System.out.println(provider.db()); 18 | } 19 | 20 | @Test 21 | public void testDB2() { 22 | System.out.println(provider.db()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/dedicated-no-maven-plugin/src/test/java/io/metaloom/example/ExampleJunit5Test.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.example; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.RegisterExtension; 5 | 6 | import io.metaloom.test.provider.junit5.ProviderExtension; 7 | 8 | public class ExampleJunit5Test { 9 | 10 | // SNIPPET START provider 11 | @RegisterExtension 12 | public static ProviderExtension ext = ProviderExtension.create("localhost", 7543, "dummy"); 13 | // SNIPPET END provider 14 | 15 | @Test 16 | public void testDB() throws Exception { 17 | System.out.println(ext.db()); 18 | } 19 | 20 | @Test 21 | public void testDB2() { 22 | System.out.println(ext.db()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /examples/dedicated-no-maven-plugin/src/test/java/io/metaloom/example/PoolSetupAction.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.example; 2 | 3 | import org.flywaydb.core.Flyway; 4 | import org.flywaydb.core.api.output.MigrateResult; 5 | 6 | import io.metaloom.test.container.provider.client.TestDatabaseProvider; 7 | import io.metaloom.test.container.provider.common.config.ProviderConfig; 8 | import io.metaloom.test.container.provider.model.DatabasePoolResponse; 9 | 10 | /** 11 | * Example implementation for a custom pool setup operation. 12 | */ 13 | public class PoolSetupAction { 14 | 15 | public static void main(String[] args) throws Exception { 16 | 17 | 18 | // 1. Set a local configuration that points to 19 | // the provider and database 20 | // SNIPPET START localconfig 21 | ProviderConfig config = new ProviderConfig(); 22 | config.setProviderHost("localhost"); 23 | config.setProviderPort(7543); 24 | config.getPostgresql().setPassword("sa"); 25 | config.getPostgresql().setUsername("sa"); 26 | config.getPostgresql().setDatabaseName("test"); 27 | config.getPostgresql().setHost("saturn"); 28 | config.getPostgresql().setPort(15432); 29 | TestDatabaseProvider.localConfig(config); 30 | // SNIPPET END localconfig 31 | 32 | // 2. Replace the old database with an empty one. 33 | // The settings will be taken from the database settings 34 | // which were defined in the testdb-maven-plugin section of your pom.xml 35 | String templateDBName = "template-database"; 36 | TestDatabaseProvider.dropCreatePostgreSQLDatabase(templateDBName); 37 | 38 | // 3. Now setup your tables using the flyway migration. 39 | String url = config.getPostgresql().jdbcUrl(templateDBName); 40 | String user = config.getPostgresql().getUsername(); 41 | String password = config.getPostgresql().getPassword(); 42 | Flyway flyway = Flyway.configure().dataSource(url, user, password).load(); 43 | MigrateResult result = flyway.migrate(); 44 | 45 | System.out.println(result.success ? "Flyway migration OK" : "Flyway migration Failed"); 46 | 47 | // 4. Now recreate the dummy pool. The pool will provide the new databases for our tests. 48 | DatabasePoolResponse response = TestDatabaseProvider.createPool("dummy", templateDBName); 49 | System.out.println("\nPool Created: " + response.toString()); 50 | 51 | // 5. Now run your unit tests and happy testing 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /examples/dedicated/README.md: -------------------------------------------------------------------------------- 1 | # Dedicated Example 2 | 3 | This example shows how the needed database and provider services can be setup in a dedicated way which is not managed by the `testdb-maven-plugin`. This way one DB + daemon can be setup to be utilized by multiple projects or in a different hosting environment. 4 | 5 | The `testdb-maven-plugin` will in this case only be used to provide the needed configuration for the unit tests. It will write the configuration file in the `target` folder. 6 | 7 | Additionally it can be used to setup the test database pools. 8 | -------------------------------------------------------------------------------- /examples/dedicated/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | db: 4 | container_name: 'testprovider-postgresql' 5 | hostname: 'postgresql' 6 | image: 'postgres:13.2' 7 | ports: 8 | - '15432:5432/tcp' 9 | environment: 10 | - POSTGRES_USER=sa 11 | - POSTGRES_PASSWORD=sa 12 | - POSTGRES_DB=test 13 | command: [ "postgres", "-c", "fsync=off" ] 14 | restart: unless-stopped 15 | provider: 16 | container_name: 'testprovider-provider' 17 | hostname: 'provider' 18 | image: 'metaloom/testdatabase-provider:0.1.4' 19 | ports: 20 | - '7543:8080/tcp' 21 | environment: 22 | # Admin DB 23 | - TESTDATABASE_PROVIDER_DATABASE_DBNAME_KEY=test 24 | # Public DB connection details 25 | - TESTDATABASE_PROVIDER_DATABASE_HOST=localhost 26 | - TESTDATABASE_PROVIDER_DATABASE_PORT=15432 27 | # Internal DB connection details (inter container communication) 28 | - TESTDATABASE_PROVIDER_DATABASE_INTERNAL_HOST=postgresql 29 | - TESTDATABASE_PROVIDER_DATABASE_INTERNAL_PORT=5432 30 | - TESTDATABASE_PROVIDER_DATABASE_USERNAME=sa 31 | - TESTDATABASE_PROVIDER_DATABASE_PASSWORD=sa 32 | # Default pool settings 33 | - TESTDATABASE_PROVIDER_POOL_MAXIMUM=40 34 | - TESTDATABASE_PROVIDER_POOL_MINIMUM=20 35 | - TESTDATABASE_PROVIDER_POOL_INCREMENT=10 36 | restart: unless-stopped 37 | -------------------------------------------------------------------------------- /examples/dedicated/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | testdatabase-provider-dedicated-example 6 | 7 | 8 | io.metaloom.test.example 9 | testdatabase-provider-examples 10 | 0.0.1-SNAPSHOT 11 | 12 | 13 | Testdatabase Provider :: Dedicated Example 14 | 15 | 16 | 17 | io.metaloom.test 18 | testdatabase-provider-junit5 19 | test 20 | 21 | 22 | io.metaloom.test 23 | testdatabase-provider-junit4 24 | test 25 | 26 | 27 | 28 | 29 | 30 | 31 | io.metaloom.maven 32 | testdb-maven-plugin 33 | 34 | 35 | 36 | clean 37 | 38 | 39 | 41 | 42 | 43 | pool 44 | process-test-classes 45 | 46 | start 47 | pool 48 | 49 | 50 | 51 | 52 | dummy 53 | test 54 | 55 | 10 56 | 30 57 | 5 58 | 59 | 60 | 61 | 62 | 63 | 64 | stop 65 | 66 | post-integration-test 67 | 68 | stop 69 | 70 | 71 | 72 | 73 | 74 | 75 | false 76 | localhost 77 | 7543 78 | 79 | 80 | false 81 | sa 82 | sa 83 | test 84 | saturn 85 | 15432 86 | postgresql 87 | 5432 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | -------------------------------------------------------------------------------- /examples/dedicated/src/test/java/io/metaloom/example/ExampleJunit4Test.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.example; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | 6 | import io.metaloom.test.provider.junit4.DatabaseProviderRule; 7 | 8 | public class ExampleJunit4Test { 9 | 10 | @Rule 11 | public DatabaseProviderRule provider = DatabaseProviderRule.create("dummy"); 12 | 13 | @Test 14 | public void testDB() throws Exception { 15 | System.out.println(provider.db()); 16 | } 17 | 18 | @Test 19 | public void testDB2() { 20 | System.out.println(provider.db()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/dedicated/src/test/java/io/metaloom/example/ExampleJunit5Test.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.example; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.RegisterExtension; 5 | 6 | import io.metaloom.test.provider.junit5.ProviderExtension; 7 | 8 | public class ExampleJunit5Test { 9 | 10 | @RegisterExtension 11 | public static ProviderExtension ext = ProviderExtension.create("dummy"); 12 | 13 | @Test 14 | public void testDB() throws Exception { 15 | System.out.println(ext.db()); 16 | } 17 | 18 | @Test 19 | public void testDB2() { 20 | System.out.println(ext.db()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /examples/minimal/README.md: -------------------------------------------------------------------------------- 1 | # Minimal Example 2 | 3 | The minimal example only contains the bare minimum maven configuration. 4 | 5 | The maven configuration does: 6 | 7 | 1. Clean the environment from previously running instances 8 | 2. Startup the needed db + provider containers 9 | 3. Invoke flyway to run the db migration 10 | 4. Setup the testdb pool for the prepared database 11 | 5. Run the tests 12 | 6. Stop the containers 13 | 14 | This example also shows how a pooled database can be updated via the dedicated `PoolSetupAction` class. It recreates the db and runs flyway to setup the tables. Afterwards it re-created the testdatabase pool. 15 | -------------------------------------------------------------------------------- /examples/minimal/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | testdatabase-provider-minimal-example 6 | 7 | 8 | io.metaloom.test.example 9 | testdatabase-provider-examples 10 | 0.0.1-SNAPSHOT 11 | 12 | 13 | Testdatabase Provider :: Minimal Example 14 | 15 | 16 | 17 | 18 | io.metaloom.test 19 | testdatabase-provider-junit5 20 | test 21 | 22 | 23 | 24 | io.metaloom.test 25 | testdatabase-provider-junit4 26 | test 27 | 28 | 29 | org.postgresql 30 | postgresql 31 | test 32 | 33 | 34 | ch.qos.logback 35 | logback-classic 36 | test 37 | 38 | 39 | org.flywaydb 40 | flyway-core 41 | test 42 | 43 | 44 | 45 | 46 | 47 | 48 | org.flywaydb 49 | flyway-maven-plugin 50 | 51 | 52 | 53 | 54 | generate-sources 55 | 56 | migrate 57 | 58 | 59 | 60 | 61 | ${maven.testdb.postgresql.jdbcurl} 62 | ${maven.testdb.postgresql.username} 63 | ${maven.testdb.postgresql.password} 64 | 65 | filesystem:src/main/resources/db/migration 66 | 67 | 68 | 69 | 70 | org.postgresql 71 | postgresql 72 | ${postgres.driver.version} 73 | 74 | 75 | 76 | 77 | 78 | io.metaloom.maven 79 | testdb-maven-plugin 80 | 81 | 83 | 84 | cleanup 85 | 86 | clean 87 | 88 | 89 | 90 | 91 | 92 | setup 93 | 94 | start 95 | 96 | 97 | 99 | 100 | 101 | pool 102 | process-test-classes 103 | 104 | pool 105 | 106 | 107 | 108 | 109 | dummy 110 | postgres 111 | 112 | 10 113 | 30 114 | 5 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | stop 123 | 124 | stop 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | -------------------------------------------------------------------------------- /examples/minimal/src/main/resources/db/migration/V1__initial_setup.sql: -------------------------------------------------------------------------------- 1 | 2 | CREATE TABLE users (id INT PRIMARY KEY, name TEXT); 3 | -------------------------------------------------------------------------------- /examples/minimal/src/test/java/io/metaloom/example/ExampleJunit4Test.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.example; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | 6 | import io.metaloom.test.provider.junit4.DatabaseProviderRule; 7 | 8 | public class ExampleJunit4Test { 9 | // SNIPPET START test_snippet 10 | @Rule 11 | public DatabaseProviderRule provider = DatabaseProviderRule.create("dummy"); 12 | 13 | @Test 14 | public void testDB() throws Exception { 15 | System.out.println(provider.db()); 16 | } 17 | // SNIPPET END test_snippet 18 | 19 | @Test 20 | public void testDB2() { 21 | System.out.println(provider.db()); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /examples/minimal/src/test/java/io/metaloom/example/ExampleJunit5Test.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.example; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.sql.Connection; 6 | import java.sql.DriverManager; 7 | import java.sql.PreparedStatement; 8 | import java.sql.SQLException; 9 | 10 | import org.junit.jupiter.api.Test; 11 | import org.junit.jupiter.api.extension.RegisterExtension; 12 | 13 | import io.metaloom.test.provider.junit5.ProviderExtension; 14 | 15 | public class ExampleJunit5Test { 16 | 17 | // SNIPPET START test_snippet 18 | @RegisterExtension 19 | public static ProviderExtension ext = ProviderExtension.create("dummy"); 20 | 21 | @Test 22 | public void testDB() throws Exception { 23 | System.out.println(ext.db()); 24 | } 25 | // SNIPPET END test_snippet 26 | 27 | @Test 28 | public void testDB2() throws SQLException { 29 | String INSERT_USER = "INSERT INTO users (id, name) VALUES (?, ?)"; 30 | 31 | try (Connection connection = DriverManager.getConnection(ext.db().getJdbcUrl(), ext.db().getUsername(), ext.db().getPassword())) { 32 | PreparedStatement statement = connection.prepareStatement(INSERT_USER); 33 | statement.setInt(1, 42); 34 | statement.setString(2, "johannes"); 35 | assertEquals(1, statement.executeUpdate(), "One row should have been updated"); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /examples/minimal/src/test/java/io/metaloom/example/PoolSetupAction.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.example; 2 | 3 | import org.flywaydb.core.Flyway; 4 | import org.flywaydb.core.api.output.MigrateResult; 5 | 6 | import io.metaloom.test.container.provider.client.TestDatabaseProvider; 7 | import io.metaloom.test.container.provider.common.config.ProviderConfig; 8 | import io.metaloom.test.container.provider.model.DatabasePoolResponse; 9 | 10 | /** 11 | * Example implementation for a custom pool setup operation. 12 | */ 13 | public class PoolSetupAction { 14 | 15 | public static void main(String[] args) throws Exception { 16 | 17 | // SNIPPET START pool_setup 18 | String templateDBName = "test3"; 19 | 20 | // 1. Replace the old database with an empty one. 21 | // The settings will be taken from the database settings 22 | // which were defined in the testdb-maven-plugin section of your pom.xml 23 | TestDatabaseProvider.dropCreatePostgreSQLDatabase(templateDBName); 24 | 25 | // 2. Now setup your tables using the flyway migration. 26 | ProviderConfig config = TestDatabaseProvider.config(); 27 | String url = config.getPostgresql().jdbcUrl(templateDBName); 28 | String user = config.getPostgresql().getUsername(); 29 | String password = config.getPostgresql().getPassword(); 30 | Flyway flyway = Flyway.configure().dataSource(url, user, password).load(); 31 | MigrateResult result = flyway.migrate(); 32 | 33 | System.out.println(result.success ? "Flyway migration OK" : "Flyway migration Failed"); 34 | 35 | // 3. Now recreate the dummy pool. The pool will provide the new databases for our tests. 36 | DatabasePoolResponse response = TestDatabaseProvider.createPool("dummy", templateDBName); 37 | System.out.println("\nPool Created: " + response.toString()); 38 | 39 | // 5. Now run your unit tests and happy testing 40 | // SNIPPET END pool_setup 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /examples/minimal/src/test/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | %d{HH:mm:ss.SSS} [%thread] %-5level %logger - %msg%n 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /examples/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | 6 | io.metaloom.test.example 7 | testdatabase-provider-examples 8 | 0.0.1-SNAPSHOT 9 | 10 | 11 | 42.2.2 12 | 1.17.6 13 | 0.1.4 14 | UTF-8 15 | 16 | 17 | Testdatabase Provider :: Examples 18 | pom 19 | 20 | 21 | minimal 22 | complex 23 | dedicated 24 | dedicated-no-maven-plugin 25 | 26 | 27 | 28 | 29 | 30 | org.postgresql 31 | postgresql 32 | ${postgres.driver.version} 33 | 34 | 35 | io.metaloom.test 36 | testdatabase-provider-junit5 37 | ${testdatabase-provider.version} 38 | 39 | 40 | io.metaloom.test 41 | testdatabase-provider-junit4 42 | ${testdatabase-provider.version} 43 | 44 | 45 | ch.qos.logback 46 | logback-classic 47 | 1.2.10 48 | 49 | 50 | org.flywaydb 51 | flyway-core 52 | 9.16.1 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | io.metaloom.maven 62 | testdb-maven-plugin 63 | 0.1.4 64 | 65 | 66 | org.flywaydb 67 | flyway-maven-plugin 68 | 9.12.0 69 | 70 | 71 | org.apache.maven.plugins 72 | maven-compiler-plugin 73 | 3.11.0 74 | 75 | 17 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | io.metaloom.maven 84 | testdb-maven-plugin 85 | 86 | 88 | 89 | 91 | false 92 | cleanup 93 | 94 | clean 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /junit4/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | testdatabase-provider-junit4 6 | 7 | 8 | io.metaloom.test 9 | testdatabase-provider 10 | 0.1.4 11 | 12 | 13 | Testdatabase Provider :: JUnit 4 14 | 15 | 16 | 17 | io.metaloom.test 18 | testdatabase-provider-client 19 | ${project.version} 20 | 21 | 22 | junit 23 | junit 24 | 25 | 26 | 27 | 28 | io.metaloom.test 29 | testdatabase-provider-postgresql-db 30 | ${project.version} 31 | test 32 | 33 | 34 | io.metaloom.test 35 | testdatabase-provider-server 36 | ${project.version} 37 | test 38 | 39 | 40 | io.metaloom.test 41 | testdatabase-provider-server 42 | ${project.version} 43 | test-jar 44 | test 45 | 46 | 47 | -------------------------------------------------------------------------------- /junit4/src/main/java/io/metaloom/test/provider/junit4/DatabaseProviderRule.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.provider.junit4; 2 | 3 | import java.io.IOException; 4 | 5 | import org.junit.rules.TestRule; 6 | import org.junit.runner.Description; 7 | import org.junit.runners.model.Statement; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import io.metaloom.test.container.provider.client.ClientAllocation; 12 | import io.metaloom.test.container.provider.client.ProviderClient; 13 | import io.metaloom.test.container.provider.client.TestDatabaseProvider; 14 | import io.metaloom.test.container.provider.model.DatabaseAllocationResponse; 15 | 16 | public class DatabaseProviderRule implements TestRule { 17 | 18 | public static final Logger log = LoggerFactory.getLogger(DatabaseProviderRule.class); 19 | 20 | private ProviderClient client; 21 | private ClientAllocation allocation; 22 | private String poolId; 23 | 24 | public DatabaseProviderRule(ProviderClient client, String poolId) { 25 | this.client = client; 26 | this.poolId = poolId; 27 | } 28 | 29 | public DatabaseProviderRule(String host, int port, String poolId) { 30 | this(new ProviderClient(host, port), poolId); 31 | } 32 | 33 | @Override 34 | public Statement apply(Statement base, Description description) { 35 | return new Statement() { 36 | @Override 37 | public void evaluate() throws Throwable { 38 | starting(description); 39 | try { 40 | base.evaluate(); 41 | } finally { 42 | finished(description); 43 | } 44 | } 45 | }; 46 | } 47 | 48 | protected void finished(Description description) { 49 | if (allocation != null) { 50 | allocation.release(); 51 | allocation = null; 52 | } 53 | } 54 | 55 | protected void starting(Description description) { 56 | String testName = description.getMethodName(); 57 | String testClass = description.getClassName(); 58 | String testRef = testClass + "_" + testName; 59 | try { 60 | log.debug("Linking test {}. Requesting DB from {}", testRef, poolId); 61 | allocation = client.link(poolId, testRef).get(); 62 | } catch (Exception e) { 63 | log.error("Error while linking test {}", testRef, e); 64 | throw new RuntimeException(e); 65 | } 66 | } 67 | 68 | public DatabaseAllocationResponse db() { 69 | return allocation == null ? null : allocation.response(); 70 | } 71 | 72 | public static DatabaseProviderRule create(String host, int port, String poolId) { 73 | return new DatabaseProviderRule(host, port, poolId); 74 | } 75 | 76 | /** 77 | * Create a new extension which connects to the provider server. 78 | * 79 | * @param poolId 80 | * @return 81 | */ 82 | public static DatabaseProviderRule create(String poolId) { 83 | try { 84 | ProviderClient client = TestDatabaseProvider.client(); 85 | return new DatabaseProviderRule(client, poolId); 86 | } catch (IOException e) { 87 | throw new RuntimeException("Error while preparing client to connect to provider", e); 88 | } 89 | } 90 | 91 | } 92 | -------------------------------------------------------------------------------- /junit4/src/test/java/io/metaloom/test/provider/junit4/DatabaseProviderRuleTest.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.provider.junit4; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import org.junit.Rule; 6 | import org.junit.Test; 7 | 8 | import io.metaloom.test.container.server.AbstractProviderServerTest; 9 | 10 | public class DatabaseProviderRuleTest extends AbstractProviderServerTest { 11 | 12 | @Rule 13 | public DatabaseProviderRule provider = DatabaseProviderRule.create("default"); 14 | 15 | @Test 16 | public void testA() { 17 | assertEquals("There should only be one allocation.", 1, server.getPool().allocationLevel()); 18 | assertAllocation(server, provider.db(), "testA"); 19 | } 20 | 21 | @Test 22 | public void testB() { 23 | assertEquals("There should only be one allocation.", 1, server.getPool().allocationLevel()); 24 | assertAllocation(server, provider.db(), "testB"); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /junit4/src/test/java/io/metaloom/test/provider/junit4/PoolSetupAction.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.provider.junit4; 2 | 3 | import io.metaloom.test.container.provider.client.TestDatabaseProvider; 4 | import io.metaloom.test.container.provider.common.config.ProviderConfig; 5 | import io.metaloom.test.container.server.TestSQLHelper; 6 | 7 | /** 8 | * Example implementation for a custom pool setup operation. 9 | */ 10 | public class PoolSetupAction { 11 | 12 | public static void main(String[] args) throws Exception { 13 | // 1. Setup a new database - the settings will be taken from the database settings which were defined in the testprovider-plugin section of your pom.xml 14 | TestDatabaseProvider.createPostgreSQLDatabase("test2"); 15 | 16 | // 3. Setup your tables (e.g. run flyway here) 17 | ProviderConfig config = TestDatabaseProvider.config(); 18 | TestSQLHelper.setupTable(config.getPostgresql().adminJdbcUrl(), config.getPostgresql().getUsername(), config.getPostgresql().getPassword()); 19 | 20 | // 4. Create pool to be used in tests 21 | TestDatabaseProvider.createPool("default", "test2"); 22 | 23 | // 5. Now run your unit tests and happy testing 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /junit5/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | testdatabase-provider-junit5 6 | 7 | 8 | io.metaloom.test 9 | testdatabase-provider 10 | 0.1.4 11 | 12 | 13 | Testdatabase Provider :: JUnit 5 14 | 15 | 16 | 17 | io.metaloom.test 18 | testdatabase-provider-client 19 | ${project.version} 20 | 21 | 22 | org.junit.jupiter 23 | junit-jupiter-api 24 | 25 | 26 | 27 | 28 | io.metaloom.test 29 | testdatabase-provider-postgresql-db 30 | ${project.version} 31 | test 32 | 33 | 34 | io.metaloom.test 35 | testdatabase-provider-server 36 | ${project.version} 37 | test 38 | 39 | 40 | io.metaloom.test 41 | testdatabase-provider-server 42 | ${project.version} 43 | test-jar 44 | test 45 | 46 | 47 | -------------------------------------------------------------------------------- /junit5/src/main/java/io/metaloom/test/provider/junit5/ProviderExtension.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.provider.junit5; 2 | 3 | import java.io.IOException; 4 | 5 | import org.junit.jupiter.api.extension.AfterEachCallback; 6 | import org.junit.jupiter.api.extension.BeforeEachCallback; 7 | import org.junit.jupiter.api.extension.ExtensionContext; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import io.metaloom.test.container.provider.client.ClientAllocation; 12 | import io.metaloom.test.container.provider.client.ProviderClient; 13 | import io.metaloom.test.container.provider.client.TestDatabaseProvider; 14 | import io.metaloom.test.container.provider.model.DatabaseAllocationResponse; 15 | 16 | public class ProviderExtension implements BeforeEachCallback, AfterEachCallback { 17 | 18 | public static final Logger log = LoggerFactory.getLogger(ProviderExtension.class); 19 | 20 | private ProviderClient client; 21 | private ClientAllocation allocation; 22 | private String poolId; 23 | 24 | public ProviderExtension(ProviderClient client, String poolId) { 25 | this.client = client; 26 | this.poolId = poolId; 27 | } 28 | 29 | public ProviderExtension(String host, int port, String poolId) { 30 | this(new ProviderClient(host, port), poolId); 31 | } 32 | 33 | @Override 34 | public void beforeEach(ExtensionContext context) throws Exception { 35 | String testName = context.getRequiredTestMethod().getName(); 36 | String testClass = context.getRequiredTestClass().getSimpleName(); 37 | String testRef = testClass + "_" + testName; 38 | log.debug("Linking test {}. Requesting DB from {}", testRef, poolId); 39 | allocation = client.link(poolId, testRef).get(); 40 | } 41 | 42 | @Override 43 | public void afterEach(ExtensionContext context) throws Exception { 44 | if (allocation != null) { 45 | allocation.release(); 46 | } 47 | } 48 | 49 | public DatabaseAllocationResponse db() { 50 | return allocation == null ? null : allocation.response(); 51 | } 52 | 53 | public static ProviderExtension create(String host, int port, String poolId) { 54 | return new ProviderExtension(host, port, poolId); 55 | } 56 | 57 | /** 58 | * Create a new extension which connects to the provider server. 59 | * 60 | * @param poolId 61 | * @return 62 | */ 63 | public static ProviderExtension create(String poolId) { 64 | try { 65 | ProviderClient client = TestDatabaseProvider.client(); 66 | return new ProviderExtension(client, poolId); 67 | } catch (IOException e) { 68 | throw new RuntimeException("Error while preparing client to connect to provider", e); 69 | } 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /junit5/src/test/java/io/metaloom/test/provider/junit5/DatabaseProviderExtensionTest.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.provider.junit5; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | 6 | import org.junit.jupiter.api.Test; 7 | import org.junit.jupiter.api.extension.RegisterExtension; 8 | 9 | import io.metaloom.test.container.server.AbstractProviderServerTest; 10 | 11 | public class DatabaseProviderExtensionTest extends AbstractProviderServerTest { 12 | 13 | @RegisterExtension 14 | public static ProviderExtension ext = ProviderExtension.create("default"); 15 | 16 | @Test 17 | public void testDB() throws Exception { 18 | Thread.sleep(2_000); 19 | assertEquals("There should only be one allocation.", 1, server.getPool().allocationLevel()); 20 | assertTrue(server.getPool().isStarted()); 21 | assertTrue(server.getPool().level() != 0); 22 | assertAllocation(server, ext.db(), getClass().getSimpleName() + "_testDB"); 23 | } 24 | 25 | @Test 26 | public void testDB2() { 27 | assertEquals("There should only be one allocation.", 1, server.getPool().allocationLevel()); 28 | assertAllocation(server, ext.db(), getClass().getSimpleName() + "_testDB2"); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /maven/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | io.metaloom.maven 6 | testdb-maven-plugin 7 | 8 | 9 | io.metaloom.test 10 | testdatabase-provider 11 | 0.1.4 12 | 13 | maven-plugin 14 | 15 | A Maven Plugin to provide test databases via a dedicated provider 16 | server 17 | Testdatabase Provider :: Maven Plugin 18 | 19 | 20 | UTF-8 21 | 22 | 23 | 24 | 25 | io.metaloom.test 26 | testdatabase-provider-client 27 | ${project.version} 28 | 29 | 30 | io.metaloom.test 31 | testdatabase-provider-postgresql-db 32 | ${project.version} 33 | 34 | 35 | 36 | 37 | org.apache.maven 38 | maven-core 39 | 3.9.0 40 | provided 41 | 42 | 43 | org.apache.maven.plugin-tools 44 | maven-plugin-annotations 45 | 3.8.1 46 | provided 47 | 48 | 49 | 50 | 51 | org.junit.jupiter 52 | junit-jupiter-api 53 | test 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | org.apache.maven.plugins 62 | maven-plugin-plugin 63 | 64 | 65 | 66 | generated-helpmojo 67 | 68 | helpmojo 69 | 70 | 71 | 72 | 73 | io.metaloom.maven.provider 74 | 75 | 76 | 77 | org.codehaus.mojo 78 | build-helper-maven-plugin 79 | 80 | 81 | add-source 82 | generate-sources 83 | 84 | add-source 85 | 86 | 87 | 88 | ${project.build.directory}/generated-sources/plugin 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | org.apache.maven.plugins 101 | maven-plugin-report-plugin 102 | 103 | 104 | org.apache.maven.plugins 105 | maven-plugin-plugin 106 | 107 | 108 | 109 | report 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /maven/src/main/java/io/metaloom/maven/provider/PoolLimits.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.maven.provider; 2 | 3 | import org.apache.maven.plugins.annotations.Parameter; 4 | 5 | import io.metaloom.test.container.provider.common.ServerEnv; 6 | 7 | public class PoolLimits { 8 | 9 | /** 10 | * Minimum level of test databases which the provider should allocate for the pool. 11 | */ 12 | @Parameter 13 | private int minimum = ServerEnv.DEFAULT_POOL_MINIMUM; 14 | 15 | /** 16 | * Maximum level of test databases which the provider should allocate for the pool. 17 | */ 18 | @Parameter 19 | private int maximum = ServerEnv.DEFAULT_POOL_MAXIMUM; 20 | 21 | /** 22 | * Incremental of new databases which the provider should create in one operation to increase the pool level. 23 | */ 24 | @Parameter 25 | private int increment = ServerEnv.DEFAULT_POOL_INCREMENT; 26 | 27 | public int getMinimum() { 28 | return minimum; 29 | } 30 | 31 | public int getMaximum() { 32 | return maximum; 33 | } 34 | 35 | public int getIncrement() { 36 | return increment; 37 | } 38 | 39 | @Override 40 | public String toString() { 41 | return "limit: " + minimum + " to " + maximum + " inc: " + increment; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /maven/src/main/java/io/metaloom/maven/provider/PoolMavenConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.maven.provider; 2 | 3 | import org.apache.maven.plugins.annotations.Parameter; 4 | 5 | public class PoolMavenConfiguration { 6 | 7 | /** 8 | * Id of the pool. Tests can reference this pool by id to get the desired databases. 9 | */ 10 | @Parameter 11 | private String id; 12 | 13 | /** 14 | * Name of the database which should be copied in the pool. 15 | */ 16 | @Parameter 17 | private String templateName; 18 | 19 | /** 20 | * Additional limits to tune the pool size. 21 | */ 22 | @Parameter 23 | private PoolLimits limits = new PoolLimits(); 24 | 25 | /** 26 | * Database host setting to be used by the pool which will be exposed to tests. 27 | */ 28 | @Parameter 29 | private String host; 30 | 31 | /** 32 | * Database port setting to be used by the pool which will be exposed to tests. 33 | */ 34 | @Parameter 35 | private Integer port; 36 | 37 | /** 38 | * Internal database host setting to be used by the pool for internal connections of the provider. 39 | */ 40 | @Parameter 41 | private String internalHost; 42 | 43 | /** 44 | * Internal database port setting to be used by the pool for internal connections of the provider. 45 | */ 46 | @Parameter 47 | private Integer internalPort; 48 | 49 | /** 50 | * Username for the database connection. 51 | */ 52 | @Parameter 53 | private String username; 54 | 55 | /** 56 | * Password for the database connection. 57 | */ 58 | @Parameter 59 | private String password; 60 | 61 | /** 62 | * Admin database to be used when exeuction drop database on no longer needed test databases. 63 | */ 64 | @Parameter 65 | private String database; 66 | 67 | public String getId() { 68 | return id; 69 | } 70 | 71 | public String getTemplateName() { 72 | return templateName; 73 | } 74 | 75 | public PoolLimits getLimits() { 76 | return limits; 77 | } 78 | 79 | public String getHost() { 80 | return host; 81 | } 82 | 83 | public Integer getPort() { 84 | return port; 85 | } 86 | 87 | public String getDatabase() { 88 | return database; 89 | } 90 | 91 | public String getUsername() { 92 | return username; 93 | } 94 | 95 | public String getPassword() { 96 | return password; 97 | } 98 | 99 | public String getInternalHost() { 100 | return internalHost; 101 | } 102 | 103 | public Integer getInternalPort() { 104 | return internalPort; 105 | } 106 | 107 | @Override 108 | public String toString() { 109 | return "pool: " + getId() + " @ " + getHost() + ":" + getPort() + "/" + getDatabase() + " => " + getTemplateName() + "(Internal: " 110 | + getInternalHost() + ":" + getInternalPort() + "/" + getDatabase() + ")"; 111 | } 112 | 113 | } 114 | -------------------------------------------------------------------------------- /maven/src/main/java/io/metaloom/maven/provider/PostgresqlMavenConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.maven.provider; 2 | 3 | import org.apache.maven.plugins.annotations.Parameter; 4 | 5 | import io.metaloom.maven.provider.container.PostgreSQLPoolContainer; 6 | 7 | public class PostgresqlMavenConfiguration { 8 | 9 | public static final String POSTGRESQL_CONFIG_PROP_KEY = "maven.testdb.postgresql"; 10 | public static final String POSTGRESQL_CONTAINER_ID_PROP_KEY = "maven.testdb.postgresql.container_id"; 11 | public static final String POSTGRESQL_DB_PROP_KEY = "maven.testdb.postgresql.database"; 12 | public static final String POSTGRESQL_USERNAME_PROP_KEY = "maven.testdb.postgresql.username"; 13 | public static final String POSTGRESQL_PASSWORD_PROP_KEY = "maven.testdb.postgresql.password"; 14 | public static final String POSTGRESQL_JDBCURL_PROP_KEY = "maven.testdb.postgresql.jdbcurl"; 15 | 16 | public static final String POSTGRESQL_HOST_PROP_KEY = "maven.testdb.postgresql.host"; 17 | public static final String POSTGRESQL_PORT_PROP_KEY = "maven.testdb.postgresql.port"; 18 | 19 | public static final String POSTGRESQL_INTERNAL_HOST_PROP_KEY = "maven.testdb.postgresql.internal_host"; 20 | public static final String POSTGRESQL_INTERNAL_PORT_PROP_KEY = "maven.testdb.postgresql.internal_port"; 21 | 22 | public static final String POSTGRESQL_START_CONTAINER_PROP_KEY = "maven.testdb.postgresql.start_container"; 23 | private static final String POSTGRESQL_CONTAINER_IMAGE_PROP_KEY = "maven.testdb.postgresql.container_image"; 24 | private static final String POSTGRESQL_TMPFS_SIZE_PROP_KEY = "maven.testdb.postgresql.tmpfs_size_mb"; 25 | 26 | /** 27 | * Whether a postgreSQL server should be started automatically. The properties maven.provider.db.url, maven.provider.db.username and 28 | * maven.provider.db.password will automatically be set and can be uses by other plugins after the execution of the plugin goal. 29 | */ 30 | @Parameter(property = POSTGRESQL_START_CONTAINER_PROP_KEY, required = false, defaultValue = "true") 31 | private boolean startContainer = true; 32 | 33 | /** 34 | * Container image to be used to startup the postgreSQL. 35 | */ 36 | @Parameter(property = POSTGRESQL_CONTAINER_IMAGE_PROP_KEY, required = false, defaultValue = PostgreSQLPoolContainer.DEFAULT_IMAGE) 37 | private String containerImage; 38 | 39 | /** 40 | * Host to be used for the provider to send to tests. This setting will not affect the started db container. 41 | */ 42 | @Parameter(property = POSTGRESQL_HOST_PROP_KEY, required = false) 43 | private String host; 44 | 45 | /** 46 | * Port to be used for the provider to send to tests. This setting will not affect the started db container. 47 | */ 48 | @Parameter(property = POSTGRESQL_PORT_PROP_KEY, required = false) 49 | private Integer port; 50 | 51 | /** 52 | * Internal host to be used for the provider to connect to the database. This setting will not affect the started db container. 53 | */ 54 | @Parameter(property = POSTGRESQL_INTERNAL_HOST_PROP_KEY, required = false) 55 | private String internalHost; 56 | 57 | /** 58 | * Internal port to be used for the provider to connect to the database. This setting will not affect the started db container. 59 | */ 60 | @Parameter(property = POSTGRESQL_INTERNAL_PORT_PROP_KEY, required = false) 61 | private Integer internalPort; 62 | 63 | /** 64 | * Username to be used for the provider to connect to the database. Additionally this username will be used when {@link #startContainer} is enabled. 65 | */ 66 | @Parameter(property = POSTGRESQL_USERNAME_PROP_KEY, required = false, defaultValue = PostgreSQLPoolContainer.DEFAULT_USERNAME) 67 | private String username; 68 | 69 | /** 70 | * Password to be used for the provider to connect to the database. Additionally this password will be used when {@link #startContainer} is enabled. 71 | */ 72 | @Parameter(property = POSTGRESQL_PASSWORD_PROP_KEY, required = false, defaultValue = PostgreSQLPoolContainer.DEFAULT_PASSWORD) 73 | private String password; 74 | 75 | /** 76 | * Name of the initial admin database being created when the container starts. This database will not be used to provide test databases. 77 | */ 78 | @Parameter(property = POSTGRESQL_DB_PROP_KEY, required = false, defaultValue = PostgreSQLPoolContainer.DEFAULT_DATABASE_NAME) 79 | private String database; 80 | 81 | /** 82 | * Size in MB of the tmpfs filesystem to be used for the database container. Use 0 to disable tmpfs. 83 | */ 84 | @Parameter(property = POSTGRESQL_TMPFS_SIZE_PROP_KEY, required = false, defaultValue = "0") 85 | private int tmpfsSizeMB = 0; 86 | 87 | public boolean isStartContainer() { 88 | return startContainer; 89 | } 90 | 91 | public String getContainerImage() { 92 | return containerImage; 93 | } 94 | 95 | public String getHost() { 96 | return host; 97 | } 98 | 99 | public Integer getPort() { 100 | return port; 101 | } 102 | 103 | public String getInternalHost() { 104 | return internalHost; 105 | } 106 | 107 | public Integer getInternalPort() { 108 | return internalPort; 109 | } 110 | 111 | public String getUsername() { 112 | return username; 113 | } 114 | 115 | public String getPassword() { 116 | return password; 117 | } 118 | 119 | public int getTmpfsSizeMB() { 120 | return tmpfsSizeMB; 121 | } 122 | 123 | public String getDatabase() { 124 | return database; 125 | } 126 | 127 | public boolean hasConnectionSettings() { 128 | return host != null && port != null && username != null && password != null && database != null; 129 | } 130 | 131 | public String getJdbcUrl() { 132 | return ("jdbc:postgresql://" + getHost() + ":" + getPort() + "/" + getDatabase()); 133 | } 134 | 135 | } 136 | -------------------------------------------------------------------------------- /maven/src/main/java/io/metaloom/maven/provider/ProviderCleanMojo.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.maven.provider; 2 | 3 | import org.apache.maven.plugins.annotations.LifecyclePhase; 4 | import org.apache.maven.plugins.annotations.Mojo; 5 | 6 | /** 7 | * The stop all containers that were previously started and delete the test provider config file. 8 | */ 9 | @Mojo(name = "clean", defaultPhase = LifecyclePhase.PRE_CLEAN) 10 | public class ProviderCleanMojo extends ProviderStopMojo { 11 | } 12 | -------------------------------------------------------------------------------- /maven/src/main/java/io/metaloom/maven/provider/ProviderMavenConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.maven.provider; 2 | 3 | import org.apache.maven.plugins.annotations.Parameter; 4 | 5 | import io.metaloom.test.container.provider.container.DatabaseProviderContainer; 6 | 7 | public class ProviderMavenConfiguration { 8 | 9 | public static final String PROVIDER_CONFIG_PROP_KEY = "maven.testdb.provider"; 10 | public static final String PROVIDER_HOST_PROP_KEY = "maven.testdb.provider.host"; 11 | public static final String PROVIDER_PORT_PROP_KEY = "maven.testdb.provider.port"; 12 | public static final String PROVIDER_LIMITS_PROP_KEY = "maven.testdb.provider.limits"; 13 | public static final String PROVIDER_CREATE_POOL_PROP_KEY = "maven.testdb.createPool"; 14 | public static final String PROVIDER_CONTAINER_IMAGE_PROP_KEY = "maven.testdb.provider.container_image"; 15 | public static final String PROVIDER_START_CONTAINER_PROP_KEY = "maven.testdb.provider.start_container"; 16 | 17 | /** 18 | * Default limits to be used for new pools. 19 | */ 20 | @Parameter(property = PROVIDER_LIMITS_PROP_KEY) 21 | private PoolLimits limits = new PoolLimits(); 22 | 23 | /** 24 | * Whether the test database provider should be started. 25 | */ 26 | @Parameter(property = PROVIDER_START_CONTAINER_PROP_KEY, defaultValue = "true") 27 | private boolean startContainer = true; 28 | 29 | /** 30 | * Configure the used container image to be used when starting the provider container. 31 | */ 32 | @Parameter(property = PROVIDER_CONTAINER_IMAGE_PROP_KEY) 33 | private String containerImage = DatabaseProviderContainer.DEFAULT_IMAGE; 34 | 35 | /** 36 | * Connection detail used to connect to the provider to manage it. 37 | */ 38 | @Parameter(property = PROVIDER_HOST_PROP_KEY, defaultValue = "localhost") 39 | private String host; 40 | 41 | /** 42 | * Connection detail used to connect to the provider to manage it. 43 | */ 44 | @Parameter(property = PROVIDER_PORT_PROP_KEY) 45 | private Integer port; 46 | 47 | /** 48 | * Whether to directly create a test database pool using the provided settings. Please note that the pool should be first created when the template database 49 | * is ready. You can create defer the pool creation using the "pool" goal. 50 | */ 51 | @Parameter(property = PROVIDER_CREATE_POOL_PROP_KEY, defaultValue = "false") 52 | private boolean createPool = false; 53 | 54 | public String getHost() { 55 | return host; 56 | } 57 | 58 | public void setHost(String host) { 59 | this.host = host; 60 | } 61 | 62 | public Integer getPort() { 63 | return port; 64 | } 65 | 66 | public void setPort(Integer port) { 67 | this.port = port; 68 | } 69 | 70 | public String getContainerImage() { 71 | return containerImage; 72 | } 73 | 74 | public boolean isStartContainer() { 75 | return startContainer; 76 | } 77 | 78 | public PoolLimits getLimits() { 79 | return limits; 80 | } 81 | 82 | public boolean isCreatePool() { 83 | return createPool; 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /maven/src/main/java/io/metaloom/maven/provider/ProviderPoolMojo.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.maven.provider; 2 | 3 | import static io.metaloom.test.container.provider.common.config.ProviderConfigHelper.readConfig; 4 | 5 | import java.util.List; 6 | 7 | import org.apache.maven.plugin.MojoExecutionException; 8 | import org.apache.maven.plugin.MojoFailureException; 9 | import org.apache.maven.plugins.annotations.LifecyclePhase; 10 | import org.apache.maven.plugins.annotations.Mojo; 11 | import org.apache.maven.plugins.annotations.Parameter; 12 | 13 | import io.metaloom.test.container.provider.client.JSON; 14 | import io.metaloom.test.container.provider.client.ProviderClient; 15 | import io.metaloom.test.container.provider.common.config.ProviderConfig; 16 | import io.metaloom.test.container.provider.common.config.ProviderConfigHelper; 17 | import io.metaloom.test.container.provider.model.DatabasePoolConnection; 18 | import io.metaloom.test.container.provider.model.DatabasePoolRequest; 19 | import io.metaloom.test.container.provider.model.DatabasePoolResponse; 20 | import io.metaloom.test.container.provider.model.DatabasePoolSettings; 21 | 22 | /** 23 | * The pool operation will setup a new test database pool. After this step the provider daemon will automatically populate the database with copies from the 24 | * template database and allow tests to allocate databases. 25 | */ 26 | @Mojo(name = "pool", defaultPhase = LifecyclePhase.PROCESS_TEST_CLASSES) 27 | public class ProviderPoolMojo extends AbstractProviderMojo { 28 | 29 | /** 30 | * List of pool definitions which should be setup by the goal. 31 | */ 32 | @Parameter(property = PROVIDER_POOLS_PROPS_KEY) 33 | private List pools; 34 | 35 | @Override 36 | public void execute() throws MojoExecutionException, MojoFailureException { 37 | if (skip) { 38 | getLog().info("Pool is skipped."); 39 | return; 40 | } 41 | 42 | try { 43 | ProviderConfig config = readConfig(); 44 | if (config == null) { 45 | getLog().warn("Provider config file not found " + ProviderConfigHelper.currentConfigPath()); 46 | updateProviderConfig(null, null); 47 | } 48 | 49 | getLog().info("Applying pool configuration"); 50 | String host = merge("provider.host", providerMavenConfig.getHost(), config.getProviderHost()); 51 | int port = config.getProviderPort(); 52 | 53 | ProviderClient client = new ProviderClient(host, port); 54 | for (PoolMavenConfiguration pool : pools) { 55 | if (pool.getId() == null) { 56 | throw new MojoExecutionException("Pool id is missing for " + pool); 57 | } 58 | 59 | DatabasePoolSettings settings = new DatabasePoolSettings(); 60 | PoolLimits limits = pool.getLimits(); 61 | if (limits != null) { 62 | getLog().info("Using provided " + limits); 63 | settings.setMaximum(limits.getMaximum()); 64 | settings.setMinimum(limits.getMinimum()); 65 | settings.setIncrement(limits.getIncrement()); 66 | } 67 | DatabasePoolConnection connection = new DatabasePoolConnection(); 68 | connection.setHost(merge("postgresql.host", pool.getHost(), config.getPostgresql().getHost())); 69 | connection.setPort(merge("postgresql.port", pool.getPort(), config.getPostgresql().getPort())); 70 | connection.setUsername(merge("postgresql.username", pool.getUsername(), config.getPostgresql().getUsername())); 71 | connection.setPassword(merge("postgresql.password", pool.getPassword(), config.getPostgresql().getPassword())); 72 | connection.setDatabase(merge("postgresql.database", pool.getDatabase(), config.getPostgresql().getDatabaseName())); 73 | connection.setInternalHost(merge("postgresql.internalHost", pool.getInternalHost(), config.getPostgresql().getInternalHost())); 74 | connection.setInternalPort(merge("postgresql.internalPort", pool.getInternalPort(), config.getPostgresql().getInternalPort())); 75 | DatabasePoolRequest request = new DatabasePoolRequest(); 76 | if (pool.getTemplateName() == null) { 77 | throw new MojoExecutionException("Database template name is missing in pool configuration for " + pool); 78 | } 79 | request.setTemplateDatabaseName(pool.getTemplateName()); 80 | 81 | request.setSettings(settings); 82 | request.setConnection(connection); 83 | DatabasePoolResponse response = client.createPool(pool.getId(), request).get(); 84 | getLog().debug("Response:\n" + JSON.toString(response)); 85 | } 86 | 87 | } catch (Exception e) { 88 | throw new MojoExecutionException("Unexpected error while invoking pool setup.", e); 89 | } 90 | } 91 | 92 | /** 93 | * Merge the setting by first looking at the pool configuration. If no value can be found the state variable will be used. 94 | * 95 | * @param key 96 | * @param poolValue 97 | * @param stateValue 98 | * @return 99 | * @throws MojoExecutionException 100 | */ 101 | private T merge(String key, T poolValue, T stateValue) throws MojoExecutionException { 102 | if (poolValue != null) { 103 | return poolValue; 104 | } 105 | if (stateValue != null) { 106 | getLog().debug("Using config value for " + key); 107 | return stateValue; 108 | } 109 | 110 | throw new MojoExecutionException( 111 | "Pool setting {" + key + "} is not present in pool configuration or global state file which was written by the start goal."); 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /maven/src/main/java/io/metaloom/maven/provider/ProviderStartMojo.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.maven.provider; 2 | 3 | import static io.metaloom.test.container.provider.common.config.ProviderConfigHelper.readConfig; 4 | 5 | import org.apache.maven.plugin.MojoExecutionException; 6 | import org.apache.maven.plugin.MojoFailureException; 7 | import org.apache.maven.plugins.annotations.LifecyclePhase; 8 | import org.apache.maven.plugins.annotations.Mojo; 9 | import org.apache.maven.plugins.annotations.Parameter; 10 | 11 | import io.metaloom.maven.provider.container.PostgreSQLPoolContainer; 12 | import io.metaloom.test.container.provider.common.config.ProviderConfig; 13 | import io.metaloom.test.container.provider.container.DatabaseProviderContainer; 14 | 15 | /** 16 | * The start operation will provide the needed testdatabase provider daemon and optionally also a database which will automatically be configured to work in 17 | * conjunction with the started daemon. 18 | */ 19 | @Mojo(name = "start", defaultPhase = LifecyclePhase.INITIALIZE) 20 | public class ProviderStartMojo extends AbstractProviderMojo { 21 | 22 | /** 23 | * Whether to re-use the started docker containers. If not enabled the container will be shut down when the maven command terminates. The settings 24 | * `testcontainers.reuse.enable=true` must be added to the .testcontainers.properties file in order to enable re-use of containers. A provider state file in 25 | * the build directory will keep track of containerIds and reuse them when goals are run again (if possible). 26 | */ 27 | @Parameter(property = PROVIDER_REUSE_CONTAINERS_PROP_KEY, defaultValue = "true") 28 | private boolean reuseContainers = true; 29 | 30 | @Override 31 | public void execute() throws MojoExecutionException, MojoFailureException { 32 | if (skip) { 33 | getLog().info("Start is skipped."); 34 | return; 35 | } 36 | 37 | ProviderConfig providerConfig = readConfig(); 38 | if (providerConfig != null) { 39 | getLog().warn( 40 | "Found config file. This means the provider is probably still running. You can stop containers via mvn testdatabase-provider:stop. Aborting start."); 41 | return; 42 | } 43 | 44 | PostgreSQLPoolContainer dbContainer = null; 45 | if (postgresqlMavenConfig != null) { 46 | if (postgresqlMavenConfig.isStartContainer()) { 47 | if (postgresqlMavenConfig.getPort() != null) { 48 | getLog().warn("Ignoring port setting. When starting a container the mapped port will be randomized."); 49 | } 50 | if (postgresqlMavenConfig.getHost() != null) { 51 | getLog().warn("Ignoring hostname setting. When starting a container the used host can't be selected."); 52 | } 53 | dbContainer = startPostgres(reuseContainers); 54 | } else { 55 | getLog().info("Not starting postgreSQL container"); 56 | } 57 | } else { 58 | getLog().info("No postgreSQL settings found. Not starting database container."); 59 | } 60 | 61 | DatabaseProviderContainer providerContainer = null; 62 | if (providerMavenConfig != null && providerMavenConfig.isStartContainer()) { 63 | providerContainer = startProvider(reuseContainers, dbContainer); 64 | } else if (providerMavenConfig != null) { 65 | getLog().info( 66 | "Not starting testdatabase provider. Using " + providerMavenConfig.getHost() + ":" + providerMavenConfig.getPort() + " instead."); 67 | updateConfig(config -> { 68 | config.setProviderHost(providerMavenConfig.getHost()); 69 | config.setProviderPort(providerMavenConfig.getPort()); 70 | }); 71 | } else { 72 | throw new MojoExecutionException("No provider config specified. Unable to execute goal without config."); 73 | } 74 | updateProviderConfig(providerContainer, dbContainer); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /maven/src/main/java/io/metaloom/maven/provider/ProviderStopMojo.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.maven.provider; 2 | 3 | import static io.metaloom.test.container.provider.common.config.ProviderConfigHelper.deleteConfig; 4 | import static io.metaloom.test.container.provider.common.config.ProviderConfigHelper.readConfig; 5 | 6 | import org.apache.maven.plugin.MojoExecutionException; 7 | import org.apache.maven.plugin.MojoFailureException; 8 | import org.apache.maven.plugins.annotations.LifecyclePhase; 9 | import org.apache.maven.plugins.annotations.Mojo; 10 | import org.testcontainers.DockerClientFactory; 11 | 12 | import com.github.dockerjava.api.DockerClient; 13 | import com.github.dockerjava.api.command.StopContainerCmd; 14 | 15 | import io.metaloom.test.container.provider.common.config.ProviderConfig; 16 | import io.metaloom.test.container.provider.common.config.ProviderConfigHelper; 17 | 18 | /** 19 | * The stop operation will terminate previously started databases and the testdatabase provider daemon container. 20 | */ 21 | @Mojo(name = "stop", defaultPhase = LifecyclePhase.PREPARE_PACKAGE) 22 | public class ProviderStopMojo extends AbstractProviderMojo { 23 | 24 | @Override 25 | public void execute() throws MojoExecutionException, MojoFailureException { 26 | 27 | if (skip) { 28 | getLog().info("Stop is skipped."); 29 | return; 30 | } 31 | 32 | try { 33 | ProviderConfig config = readConfig(); 34 | if (config == null) { 35 | getLog().warn("Unable to stop containers. Provider config file not found " + ProviderConfigHelper.currentConfigPath()); 36 | return; 37 | } 38 | DockerClient client = DockerClientFactory.lazyClient(); 39 | if (config.getProviderContainerId() != null) { 40 | stopProvider(client, config); 41 | } 42 | if (config.getPostgresql().getContainerId() != null) { 43 | stopDatabase(client, config); 44 | } 45 | deleteConfig(); 46 | } catch (Exception e) { 47 | throw new MojoExecutionException("Error while stopping containers", e); 48 | } 49 | } 50 | 51 | private void stopDatabase(DockerClient client, ProviderConfig state) { 52 | try { 53 | getLog().info("Stopping postgreSQL container"); 54 | try (StopContainerCmd cmd = client.stopContainerCmd(state.getPostgresql().getContainerId())) { 55 | cmd.exec(); 56 | } 57 | } catch (Exception e) { 58 | getLog().error("Error while stopping database ", e); 59 | } 60 | } 61 | 62 | private void stopProvider(DockerClient client, ProviderConfig state) { 63 | try { 64 | getLog().info("Stopping database provider container"); 65 | try (StopContainerCmd cmd = client.stopContainerCmd(state.getProviderContainerId())) { 66 | cmd.exec(); 67 | } 68 | } catch (Exception e) { 69 | getLog().error("Error while stopping database provider", e); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /maven/src/main/java/io/metaloom/test/container/provider/container/DatabaseProviderContainer.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.container; 2 | 3 | import org.testcontainers.containers.GenericContainer; 4 | import org.testcontainers.utility.DockerImageName; 5 | 6 | import io.metaloom.test.container.provider.common.ServerEnv; 7 | import io.metaloom.test.container.provider.common.version.Version; 8 | 9 | /** 10 | * Provider testcontainer which can be used for tests. 11 | */ 12 | public class DatabaseProviderContainer extends GenericContainer { 13 | 14 | public static final String DEFAULT_IMAGE = "metaloom/testdatabase-provider:" + Version.getPlainVersion(); 15 | 16 | public DatabaseProviderContainer() { 17 | this(DEFAULT_IMAGE); 18 | } 19 | 20 | public DatabaseProviderContainer(String imageName) { 21 | super(DockerImageName.parse(imageName)); 22 | withExposedPorts(8080); 23 | } 24 | 25 | public int getPort() { 26 | return getFirstMappedPort(); 27 | } 28 | 29 | /** 30 | * Set the parameters that are needed to directly setup a testdatabase pool for the provided database. 31 | * 32 | * @param host 33 | * Host which will be exposed to tests 34 | * @param port 35 | * Port which will be exposed to tests 36 | * @param internalHost 37 | * Host which will only be used by the provider to handle allocation and cleanup 38 | * @param internalPort 39 | * Port which will only be used by the provider to handle allocation and cleanup 40 | * @param username 41 | * @param password 42 | * @param database 43 | * @return 44 | */ 45 | public DatabaseProviderContainer withDefaultPoolDatabase(String host, int port, String internalHost, int internalPort, String username, 46 | String password, String database) { 47 | withEnv(ServerEnv.TESTDATABASE_PROVIDER_DATABASE_HOST_KEY, host); 48 | withEnv(ServerEnv.TESTDATABASE_PROVIDER_DATABASE_PORT_KEY, String.valueOf(port)); 49 | withEnv(ServerEnv.TESTDATABASE_PROVIDER_DATABASE_INTERNAL_HOST_KEY, internalHost); 50 | withEnv(ServerEnv.TESTDATABASE_PROVIDER_DATABASE_INTERNAL_PORT_KEY, String.valueOf(internalPort)); 51 | withEnv(ServerEnv.TESTDATABASE_PROVIDER_DATABASE_USERNAME_KEY, username); 52 | withEnv(ServerEnv.TESTDATABASE_PROVIDER_DATABASE_PASSWORD_KEY, password); 53 | withEnv(ServerEnv.TESTDATABASE_PROVIDER_DATABASE_DBNAME_KEY, database); 54 | return this; 55 | } 56 | 57 | public DatabaseProviderContainer withDefaultMinimum(int minimumDatabases) { 58 | withEnv(ServerEnv.TESTDATABASE_PROVIDER_POOL_MINIMUM_KEY, String.valueOf(minimumDatabases)); 59 | return this; 60 | } 61 | 62 | public DatabaseProviderContainer withDefaultMaximum(int maximumDatabases) { 63 | withEnv(ServerEnv.TESTDATABASE_PROVIDER_POOL_MAXIMUM_KEY, String.valueOf(maximumDatabases)); 64 | return this; 65 | } 66 | 67 | public DatabaseProviderContainer withDefaultIncrement(int increment) { 68 | withEnv(ServerEnv.TESTDATABASE_PROVIDER_POOL_INCREMENT_KEY, String.valueOf(increment)); 69 | return this; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /maven/src/test/java/io/metaloom/maven/provider/ProviderStartMojoTest.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.maven.provider; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertNotNull; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | import io.metaloom.test.container.provider.client.ProviderClient; 8 | import io.metaloom.test.container.provider.common.config.ProviderConfig; 9 | import io.metaloom.test.container.provider.common.config.ProviderConfigHelper; 10 | import io.metaloom.test.container.provider.model.DatabasePoolListResponse; 11 | 12 | public class ProviderStartMojoTest { 13 | 14 | @Test 15 | public void testStart() throws Exception { 16 | new ProviderStartMojo().execute(); 17 | ProviderConfig config = ProviderConfigHelper.readConfig(); 18 | assertNotNull(config); 19 | Thread.sleep(2000); 20 | System.out.println(config.toString()); 21 | ProviderClient client = new ProviderClient(config.getProviderHost(), config.getProviderPort()); 22 | DatabasePoolListResponse response = client.listPools().get(); 23 | assertNotNull(response); 24 | assertNotNull(response.getList()); 25 | new ProviderStopMojo().execute(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | io.metaloom.test 6 | testdatabase-provider 7 | 0.1.4 8 | 9 | pom 10 | 11 | io.metaloom 12 | maven-parent 13 | 2.0.2 14 | 15 | 16 | Testdatabase Provider 17 | 2023 18 | 19 | 20 | 21 | Apache-2.0 22 | 23 | 24 | 25 | 26 | Metaloom 27 | https://metaloom.io/ 28 | 29 | 30 | 31 | 32 | jotschi 33 | Johannes Schüth 34 | 35 | Developer 36 | 37 | 38 | 39 | 40 | 41 | 4.4.0 42 | 42.2.2 43 | 1.17.6 44 | ${maven.build.timestamp} 45 | 2.45 46 | UTF-8 47 | 48 | 49 | 50 | common 51 | postgresql-db 52 | client 53 | junit4 54 | junit5 55 | server 56 | maven 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | io.vertx 65 | vertx-core 66 | ${vertx.version} 67 | 68 | 69 | io.vertx 70 | vertx-web 71 | ${vertx.version} 72 | 73 | 74 | org.postgresql 75 | postgresql 76 | ${postgres.driver.version} 77 | 78 | 79 | 80 | 81 | org.testcontainers 82 | junit-jupiter 83 | ${testcontainer.version} 84 | 85 | 86 | org.testcontainers 87 | postgresql 88 | ${testcontainer.version} 89 | 90 | 91 | org.testcontainers 92 | testcontainers 93 | ${testcontainer.version} 94 | 95 | 96 | 97 | 98 | org.junit.jupiter 99 | junit-jupiter-api 100 | 5.9.2 101 | 102 | 103 | 104 | 105 | com.google.dagger 106 | dagger 107 | ${dagger.version} 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | org.apache.maven.plugins 117 | maven-plugin-report-plugin 118 | 3.8.1 119 | 120 | 121 | org.apache.maven.plugins 122 | maven-plugin-plugin 123 | 3.8.1 124 | 125 | 126 | org.apache.maven.plugins 127 | maven-site-plugin 128 | 3.8.2 129 | 130 | 131 | io.fabric8 132 | docker-maven-plugin 133 | 0.42.0 134 | 135 | 136 | 137 | 138 | 139 | org.apache.maven.plugins 140 | maven-resources-plugin 141 | 3.3.1 142 | false 143 | 144 | 145 | readme-md 146 | clean 147 | 148 | copy-resources 149 | 150 | 151 | ${project.basedir} 152 | 153 | 154 | ${project.basedir}/.github/md 155 | 156 | README.md 157 | 158 | true 159 | 160 | 161 | UTF-8 162 | 163 | snippetFilter 164 | 165 | 166 | 167 | 168 | 169 | 170 | io.metaloom.maven 171 | snippet-resource-filter 172 | 0.1.1 173 | 174 | 175 | 176 | 177 | org.apache.maven.plugins 178 | maven-compiler-plugin 179 | 180 | 17 181 | 182 | 183 | 184 | 185 | -------------------------------------------------------------------------------- /postgresql-db/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | testdatabase-provider-postgresql-db 6 | 7 | 8 | io.metaloom.test 9 | testdatabase-provider 10 | 0.1.4 11 | 12 | 13 | Testdatabase Provider :: PostgreSQL DB 14 | 15 | 16 | 17 | org.testcontainers 18 | postgresql 19 | 20 | 21 | 22 | 23 | org.testcontainers 24 | junit-jupiter 25 | test 26 | 27 | 28 | org.junit.jupiter 29 | junit-jupiter-api 30 | test 31 | 32 | 33 | -------------------------------------------------------------------------------- /postgresql-db/src/main/java/io/metaloom/maven/provider/container/PostgreSQLPoolContainer.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.maven.provider.container; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.testcontainers.containers.PostgreSQLContainer; 7 | import org.testcontainers.utility.DockerImageName; 8 | 9 | public class PostgreSQLPoolContainer extends PostgreSQLContainer { 10 | 11 | public static final String DEFAULT_IMAGE = "postgres:13.2"; 12 | 13 | public static final String DEFAULT_USERNAME = "sa"; 14 | 15 | public static final String DEFAULT_PASSWORD = "sa"; 16 | 17 | public static final String DEFAULT_DATABASE_NAME = "postgres"; 18 | 19 | public PostgreSQLPoolContainer() { 20 | this(DEFAULT_IMAGE); 21 | } 22 | 23 | public PostgreSQLPoolContainer(String imageName) { 24 | super(DockerImageName.parse(imageName).asCompatibleSubstituteFor(DEFAULT_IMAGE)); 25 | withDatabaseName(DEFAULT_DATABASE_NAME); 26 | withUsername(DEFAULT_USERNAME); 27 | withPassword(DEFAULT_PASSWORD); 28 | } 29 | 30 | /** 31 | * Enable the usage of a tmpFs to store the actual database data. 32 | * 33 | * @param liveTmpFsSizeInMB 34 | * @return 35 | */ 36 | public PostgreSQLPoolContainer withTmpFs(int liveTmpFsSizeInMB) { 37 | if (liveTmpFsSizeInMB != 0) { 38 | withEnv("PGDATA", "/live/pgdata"); 39 | withTmpFs(tmpFs(liveTmpFsSizeInMB)); 40 | } 41 | return this; 42 | } 43 | 44 | private Map tmpFs(int liveSizeMB) { 45 | Map mapping = new HashMap<>(); 46 | mapping.put("/live", "rw,size=" + liveSizeMB + "m"); 47 | return mapping; 48 | } 49 | 50 | public String getShortJdbcUrl() { 51 | return ("jdbc:postgresql://" + 52 | getHost() + 53 | ":" + 54 | getMappedPort(POSTGRESQL_PORT) + 55 | "/"); 56 | } 57 | 58 | public int getPort() { 59 | return getFirstMappedPort(); 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /postgresql-db/src/test/java/io/metaloom/maven/provider/container/PostgreSQLPoolContainerTest.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.maven.provider.container; 2 | 3 | import static org.junit.Assert.assertNotNull; 4 | 5 | import org.junit.jupiter.api.Test; 6 | import org.testcontainers.junit.jupiter.Container; 7 | import org.testcontainers.junit.jupiter.Testcontainers; 8 | 9 | @Testcontainers 10 | public class PostgreSQLPoolContainerTest { 11 | 12 | @Container 13 | private static PostgreSQLPoolContainer container = new PostgreSQLPoolContainer().withTmpFs(128); 14 | 15 | @Test 16 | public void testSetup() { 17 | assertNotNull(container.getContainerIpAddress()); 18 | assertNotNull(container.getHost()); 19 | assertNotNull(container.getJdbcUrl()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /server/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | FROM eclipse-temurin:19-jre 3 | 4 | COPY maven/ /opt 5 | RUN mv /opt/*.jar /opt/server.jar 6 | 7 | EXPOSE 8080/tcp 8 | 9 | CMD ["java", "-jar", "/opt/server.jar"] 10 | -------------------------------------------------------------------------------- /server/pom.xml: -------------------------------------------------------------------------------- 1 | 4 | 4.0.0 5 | testdatabase-provider-server 6 | 7 | 8 | io.metaloom.test 9 | testdatabase-provider 10 | 0.1.4 11 | 12 | 13 | Testdatabase Provider :: Server 14 | 15 | 16 | 17 | io.metaloom.test 18 | testdatabase-provider-common 19 | ${project.version} 20 | 21 | 22 | io.vertx 23 | vertx-web 24 | 25 | 26 | org.postgresql 27 | postgresql 28 | 29 | 30 | ch.qos.logback 31 | logback-classic 32 | 33 | 34 | org.slf4j 35 | slf4j-api 36 | 37 | 38 | com.google.dagger 39 | dagger 40 | 41 | 42 | 43 | 44 | io.metaloom.test 45 | testdatabase-provider-postgresql-db 46 | ${project.version} 47 | test 48 | 49 | 50 | io.metaloom.test 51 | testdatabase-provider-client 52 | ${project.version} 53 | test 54 | 55 | 56 | org.junit.jupiter 57 | junit-jupiter-api 58 | test 59 | 60 | 61 | org.testcontainers 62 | junit-jupiter 63 | test 64 | 65 | 66 | 67 | 68 | 69 | 70 | org.apache.maven.plugins 71 | maven-jar-plugin 72 | 73 | 74 | 75 | test-jar 76 | 77 | 78 | 79 | 80 | 81 | org.apache.maven.plugins 82 | maven-compiler-plugin 83 | 84 | 85 | 86 | com.google.dagger 87 | dagger-compiler 88 | ${dagger.version} 89 | 90 | 91 | 92 | 93 | 94 | org.apache.maven.plugins 95 | maven-shade-plugin 96 | 97 | 98 | package 99 | 100 | shade 101 | 102 | 103 | 104 | 106 | 107 | io.metaloom.test.container.provider.server.DatabaseProviderServerRunner 108 | 109 | 111 | 112 | 113 | 114 | *:* 115 | 116 | META-INF/*.SF 117 | META-INF/*.DSA 118 | META-INF/*.RSA 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | io.fabric8 130 | docker-maven-plugin 131 | 132 | 133 | build-image 134 | package 135 | 136 | build 137 | 138 | 139 | 140 | ${project.version} 141 | 142 | true 143 | 144 | 145 | 146 | push-image 147 | deploy 148 | 149 | push 150 | 151 | 152 | 153 | 154 | 155 | 156 | metaloom/testdatabase-provider 157 | 158 | ${project.basedir} 159 | 160 | artifact 161 | 162 | 163 | ${project.version} 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | -------------------------------------------------------------------------------- /server/src/main/java/io/metaloom/test/container/provider/BootstrapInitializer.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider; 2 | 3 | import java.sql.SQLException; 4 | 5 | import javax.inject.Inject; 6 | import javax.inject.Singleton; 7 | 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import io.metaloom.test.container.provider.common.ServerEnv; 12 | import io.metaloom.test.container.provider.server.DatabaseProviderServer; 13 | import io.metaloom.test.container.provider.server.ServerConfiguration; 14 | 15 | @Singleton 16 | public class BootstrapInitializer { 17 | 18 | private static final Logger log = LoggerFactory.getLogger(BootstrapInitializer.class); 19 | 20 | private final DatabasePoolManager manager; 21 | 22 | private final ServerConfiguration config; 23 | 24 | private final DatabaseProviderServer server; 25 | 26 | @Inject 27 | public BootstrapInitializer(DatabasePoolManager manager, ServerConfiguration config, DatabaseProviderServer server) { 28 | this.manager = manager; 29 | this.config = config; 30 | this.server = server; 31 | } 32 | 33 | public void start() { 34 | if (config.hasConnectionDetails()) { 35 | log.info("Searching for previously created pools and databases"); 36 | try { 37 | manager.loadFromDB(); 38 | } catch (SQLException e) { 39 | log.error("Error while loading databases", e); 40 | } 41 | } else { 42 | log.info("Skipping loading of existing databases because not all connection details have been provided."); 43 | } 44 | 45 | log.info("Starting server using port {}", config.httpPort()); 46 | server.start(); 47 | 48 | String templateDatabaseName = config.templateDatabaseName(); 49 | if (templateDatabaseName != null) { 50 | log.info("Creating default pool for database " + config.host() + ":" + config.port() + "/" + templateDatabaseName + " using admin db " 51 | + config.adminDB()); 52 | DatabasePool pool = manager.createPool("default", config.host(), config.port(), config.host(), config.port(), config.username(), 53 | config.password(), config.adminDB(), templateDatabaseName); 54 | pool.setTemplateDatabaseName(templateDatabaseName); 55 | pool.start(); 56 | } else { 57 | log.debug("Skipping creation of default pool since no template db was specified via " 58 | + ServerEnv.TESTDATABASE_PROVIDER_DATABASE_TEMPLATE_DBNAME_KEY); 59 | } 60 | 61 | } 62 | 63 | public DatabaseProviderServer server() { 64 | return server; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /server/src/main/java/io/metaloom/test/container/provider/Database.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider; 2 | 3 | public record Database(DatabaseSettings settings, String name, DatabaseJsonCommentModel comment) { 4 | 5 | public String jdbcUrl() { 6 | return settings.jdbcUrl() + name; 7 | } 8 | 9 | } -------------------------------------------------------------------------------- /server/src/main/java/io/metaloom/test/container/provider/DatabaseAllocation.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider; 2 | 3 | import java.sql.SQLException; 4 | 5 | public class DatabaseAllocation { 6 | 7 | private final DatabasePool pool; 8 | private final String id; 9 | private final Database db; 10 | 11 | public DatabaseAllocation(DatabasePool pool, String id, Database db) { 12 | this.pool = pool; 13 | this.id = id; 14 | this.db = db; 15 | } 16 | 17 | public String id() { 18 | return id; 19 | } 20 | 21 | public DatabasePool getPool() { 22 | return pool; 23 | } 24 | 25 | public Database db() { 26 | return db; 27 | } 28 | 29 | public void release() throws SQLException { 30 | getPool().release(this); 31 | } 32 | 33 | @Override 34 | public String toString() { 35 | return "allocation: " + getPool().id() + " / " + id(); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /server/src/main/java/io/metaloom/test/container/provider/DatabaseJsonCommentModel.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | import com.fasterxml.jackson.annotation.JsonFormat; 6 | import com.fasterxml.jackson.databind.annotation.JsonDeserialize; 7 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 8 | import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer; 9 | import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer; 10 | 11 | public class DatabaseJsonCommentModel { 12 | 13 | private String origin; 14 | 15 | private String poolId; 16 | 17 | @JsonDeserialize(using = LocalDateTimeDeserializer.class) 18 | @JsonSerialize(using = LocalDateTimeSerializer.class) 19 | @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss") 20 | private LocalDateTime creationDate; 21 | 22 | public String getOrigin() { 23 | return origin; 24 | } 25 | 26 | public DatabaseJsonCommentModel setOrigin(String templateName) { 27 | this.origin = templateName; 28 | return this; 29 | } 30 | 31 | public String getPoolId() { 32 | return poolId; 33 | } 34 | 35 | public DatabaseJsonCommentModel setPoolId(String id) { 36 | this.poolId = id; 37 | return this; 38 | } 39 | 40 | public LocalDateTime getCreationDate() { 41 | return creationDate; 42 | } 43 | 44 | public DatabaseJsonCommentModel setCreationDate(LocalDateTime creationDate) { 45 | this.creationDate = creationDate; 46 | return this; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /server/src/main/java/io/metaloom/test/container/provider/DatabasePool.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider; 2 | 3 | import java.sql.SQLException; 4 | import java.time.LocalDateTime; 5 | import java.util.HashMap; 6 | import java.util.Map; 7 | import java.util.Stack; 8 | import java.util.UUID; 9 | 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | import io.metaloom.test.container.provider.common.ServerEnv; 14 | import io.metaloom.test.container.provider.server.ServerConfiguration; 15 | import io.metaloom.test.container.provider.server.ServerError; 16 | import io.vertx.core.Vertx; 17 | 18 | /** 19 | * A database pool manages the database copies for a specific template database. The pool fulfills allocation requests and creates new databases to be 20 | * allocated. 21 | */ 22 | public class DatabasePool { 23 | 24 | public static final Logger log = LoggerFactory.getLogger(DatabasePool.class); 25 | 26 | private static final String DB_PREFIX = "test_db_"; 27 | 28 | private Stack databases = new Stack<>(); 29 | 30 | private Map allocations = new HashMap<>(); 31 | 32 | private final String id; 33 | 34 | private LocalDateTime creationDate; 35 | 36 | private int minimum = ServerEnv.DEFAULT_POOL_MINIMUM; 37 | 38 | private int maximum = ServerEnv.DEFAULT_POOL_MAXIMUM; 39 | 40 | private int increment = ServerEnv.DEFAULT_POOL_INCREMENT; 41 | 42 | private final Vertx vertx; 43 | 44 | private final ServerConfiguration config; 45 | 46 | private Long maintainPoolTimerId; 47 | 48 | private String templateName; 49 | 50 | private DatabaseSettings settings; 51 | 52 | /** 53 | * Create a new database pool with the specified levels. 54 | * 55 | * @param vertx 56 | * @param config 57 | * @param id 58 | * @param host 59 | * @param port 60 | * @param internalHost 61 | * @param internalPort 62 | * @param username 63 | * @param password 64 | * @param adminDB 65 | */ 66 | protected DatabasePool(Vertx vertx, ServerConfiguration config, String id, String host, int port, String internalHost, int internalPort, 67 | String username, String password, 68 | String adminDB) { 69 | this.vertx = vertx; 70 | this.config = config; 71 | this.id = id; 72 | this.settings = new DatabaseSettings(host, port, internalHost, internalPort, username, password, adminDB); 73 | this.creationDate = LocalDateTime.now(); 74 | } 75 | 76 | public void start() { 77 | if (isStarted()) { 78 | log.error("Pool already started. Ignoring start request."); 79 | return; 80 | } 81 | // TODO configure interval? 82 | this.maintainPoolTimerId = vertx.setPeriodic(2_000, th -> { 83 | try { 84 | preAllocate(); 85 | } catch (SQLException e) { 86 | log.error("Error while preallocating database.", e); 87 | } 88 | }); 89 | } 90 | 91 | public void stop() { 92 | if (maintainPoolTimerId != null) { 93 | log.info("Stopping pre-allocation process"); 94 | vertx.cancelTimer(maintainPoolTimerId); 95 | maintainPoolTimerId = null; 96 | } 97 | } 98 | 99 | public DatabaseAllocation allocate(String testName) throws SQLException { 100 | if (databases.isEmpty()) { 101 | preAllocate(); 102 | } 103 | if (!databases.isEmpty()) { 104 | Database database = databases.pop(); 105 | String id = UUID.randomUUID() 106 | .toString() 107 | .substring(0, 4) + "#" + testName; 108 | DatabaseAllocation allocation = new DatabaseAllocation(this, id, database); 109 | allocations.put(id, allocation); 110 | return allocation; 111 | } 112 | return null; 113 | } 114 | 115 | public void preAllocate() throws SQLException { 116 | int size = level(); 117 | if (templateName == null) { 118 | log.warn("Unable to preallocate database. No template name set."); 119 | return; 120 | } 121 | if (size < minimum && !(size > maximum) && templateName != null) { 122 | log.info("Need more databases. Got {} but need {} / {}", size, minimum, maximum); 123 | for (int i = 0; i < increment; i++) { 124 | String newName = DB_PREFIX + UUID.randomUUID() 125 | .toString() 126 | .substring(0, 4); 127 | log.debug("Creating " + newName + " from " + templateName); 128 | DatabaseJsonCommentModel comment = new DatabaseJsonCommentModel(); 129 | comment.setOrigin(templateName); 130 | comment.setPoolId(id()); 131 | comment.setCreationDate(LocalDateTime.now()); 132 | Database database = SQLUtils.copyDatabase(settings, templateName, newName, comment); 133 | databases.push(database); 134 | } 135 | } 136 | } 137 | 138 | public boolean release(DatabaseAllocation allocation) throws SQLException { 139 | String id = allocation.id(); 140 | log.info("Releasing allocation {}", id); 141 | if (allocations.remove(id) == null) { 142 | log.error("Allocation {} not found in pool {}", allocation, id()); 143 | return false; 144 | } else { 145 | SQLUtils.dropDatabase(allocation.db()); 146 | return true; 147 | } 148 | } 149 | 150 | public String id() { 151 | return id; 152 | } 153 | 154 | public LocalDateTime getCreationDate() { 155 | return creationDate; 156 | } 157 | 158 | /** 159 | * Return the count of database that the pool is ready to provide. 160 | * 161 | * @return 162 | */ 163 | public int level() { 164 | return databases.size(); 165 | } 166 | 167 | public int allocationLevel() { 168 | return allocations.size(); 169 | } 170 | 171 | public String getTemplateName() { 172 | return templateName; 173 | } 174 | 175 | public DatabasePool setTemplateDatabaseName(String databaseName) { 176 | this.templateName = databaseName; 177 | return this; 178 | } 179 | 180 | public DatabaseSettings settings() { 181 | return settings; 182 | } 183 | 184 | public boolean isStarted() { 185 | return maintainPoolTimerId != null; 186 | } 187 | 188 | public int getIncrement() { 189 | return increment; 190 | } 191 | 192 | public int getMaximum() { 193 | return maximum; 194 | } 195 | 196 | public int getMinimum() { 197 | return minimum; 198 | } 199 | 200 | /** 201 | * Removes all databases that are managed by the pool (free and allocated). Does not touch the template database. 202 | */ 203 | public void drain() { 204 | if (isStarted()) { 205 | throw new ServerError("Can't drain a running pool. Please ensure that the pool is stopped first."); 206 | } 207 | log.info("Draining allocations from pool {}", id()); 208 | for (DatabaseAllocation allocation : allocations.values()) { 209 | try { 210 | log.info("Releasing allocation db {} from pool {}", allocation.db(), id()); 211 | allocation.release(); 212 | } catch (Exception e) { 213 | log.error("Error while dropping allocated db.", e); 214 | } 215 | } 216 | log.info("Draining free databases from pool {}", id()); 217 | for (Database db : databases) { 218 | try { 219 | SQLUtils.dropDatabase(db); 220 | } catch (Exception e) { 221 | log.error("Error while dropping free db.", e); 222 | } 223 | } 224 | } 225 | 226 | public void setMinimum(int minimum) { 227 | this.minimum = minimum; 228 | } 229 | 230 | public void setMaximum(int maximum) { 231 | this.maximum = maximum; 232 | } 233 | 234 | public void setIncrement(int increment) { 235 | this.increment = increment; 236 | } 237 | 238 | public boolean hasDatabase(String databaseName) { 239 | for (Database database : databases) { 240 | if (database.name().equals(databaseName)) { 241 | log.debug("Found database {} in pool {}" + databaseName, id()); 242 | return true; 243 | } 244 | } 245 | return false; 246 | } 247 | 248 | public void addDatabase(Database db) { 249 | databases.add(db); 250 | } 251 | 252 | } 253 | -------------------------------------------------------------------------------- /server/src/main/java/io/metaloom/test/container/provider/DatabasePoolFactory.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider; 2 | 3 | import javax.inject.Inject; 4 | import javax.inject.Singleton; 5 | 6 | import io.metaloom.test.container.provider.server.ServerConfiguration; 7 | import io.vertx.core.Vertx; 8 | 9 | @Singleton 10 | public class DatabasePoolFactory { 11 | 12 | private Vertx vertx; 13 | 14 | private ServerConfiguration config; 15 | 16 | @Inject 17 | public DatabasePoolFactory(Vertx vertx, ServerConfiguration config) { 18 | this.vertx = vertx; 19 | this.config = config; 20 | } 21 | 22 | public DatabasePool createPool(String id, String host, int port, String internalHost, int internalPort, 23 | String username, String password, 24 | String adminDB) { 25 | return new DatabasePool(vertx, config, id, host, port, internalHost, internalPort, username, password, adminDB); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /server/src/main/java/io/metaloom/test/container/provider/DatabasePoolManager.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider; 2 | 3 | import java.sql.SQLException; 4 | import java.util.Collection; 5 | import java.util.Collections; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.Map; 9 | import java.util.Stack; 10 | 11 | import javax.inject.Inject; 12 | import javax.inject.Singleton; 13 | 14 | import io.metaloom.test.container.provider.server.ServerConfiguration; 15 | import io.vertx.core.Vertx; 16 | 17 | @Singleton 18 | public class DatabasePoolManager { 19 | 20 | private Vertx vertx; 21 | 22 | private Map pools = new HashMap<>(); 23 | 24 | private ServerConfiguration config; 25 | 26 | @Inject 27 | public DatabasePoolManager(Vertx vertx, ServerConfiguration config) { 28 | this.vertx = vertx; 29 | this.config = config; 30 | } 31 | 32 | public Collection getPools() { 33 | return Collections.unmodifiableCollection(pools.values()); 34 | } 35 | 36 | public boolean contains(String id) { 37 | return pools.containsKey(id); 38 | } 39 | 40 | /** 41 | * Removes the pool from the list of pools, stops and drains it. 42 | * 43 | * @param id 44 | * @return 45 | */ 46 | public boolean deletePool(String id) { 47 | DatabasePool pool = pools.remove(id); 48 | if (pool == null) { 49 | return false; 50 | } else { 51 | pool.stop(); 52 | pool.drain(); 53 | return true; 54 | } 55 | } 56 | 57 | public DatabasePool getPool(String id) { 58 | return pools.get(id); 59 | } 60 | 61 | public void release(DatabaseAllocation allocation) throws SQLException { 62 | allocation.release(); 63 | } 64 | 65 | public DatabasePool createPool(String id, String host, int port, String internalHost, int internalPort, String username, String password, 66 | String adminDB) { 67 | DatabasePool pool = new DatabasePool(vertx, config, id, host, port, internalHost, internalPort, username, password, adminDB); 68 | pools.put(id, pool); 69 | return pool; 70 | } 71 | 72 | public DatabasePool createPool(String id, String host, int port, String internalHost, int internalPort, String username, String password, 73 | String adminDB, String templateName) { 74 | DatabasePool pool = new DatabasePool(vertx, config, id, host, port, internalHost, internalPort, username, password, adminDB); 75 | pool.setTemplateDatabaseName(templateName); 76 | pools.put(id, pool); 77 | return pool; 78 | } 79 | 80 | public int loadFromDB() throws SQLException { 81 | int importedDBs = 0; 82 | for (Database db : SQLUtils.listDatabasesWithComment(config.databaseSettings())) { 83 | String databaseName = db.name(); 84 | DatabaseJsonCommentModel comment = db.comment(); 85 | if (comment != null) { 86 | String poolId = comment.getPoolId(); 87 | String templateName = comment.getOrigin(); 88 | DatabasePool pool = getPool(poolId); 89 | if (pool == null) { 90 | pool = createPool(poolId, config.host(), config.port(), config.host(), config.port(), config.username(), config.password(), 91 | config.adminDB(), templateName); 92 | } 93 | if (!pool.hasDatabase(databaseName)) { 94 | pool.addDatabase(db); 95 | importedDBs++; 96 | } 97 | } 98 | System.out.println(db.name() + " " + (db.comment() != null ? db.comment().getPoolId() : "")); 99 | } 100 | return importedDBs; 101 | } 102 | 103 | } 104 | -------------------------------------------------------------------------------- /server/src/main/java/io/metaloom/test/container/provider/DatabaseSettings.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider; 2 | 3 | public record DatabaseSettings(String host, int port, String internalHost, int internalPort, String username, String password, String adminDB) { 4 | 5 | public String jdbcUrl() { 6 | return ("jdbc:postgresql://" + 7 | host() + 8 | ":" + 9 | port() + 10 | "/"); 11 | } 12 | 13 | public String internalJdbcUrl() { 14 | return ("jdbc:postgresql://" + 15 | internalHost() + 16 | ":" + 17 | internalPort() + 18 | "/"); 19 | } 20 | 21 | public String toString() { 22 | return "Host: " + host() + ":" + port() + " IntHost:" + internalHost() + ":" + internalPort() + " , username: " + username() + ", password: " 23 | + password() + ", adminDB: " + adminDB(); 24 | } 25 | 26 | public String adminJdbcUrl() { 27 | return jdbcUrl() + adminDB; 28 | } 29 | 30 | public String internalAdminJdbcUrl() { 31 | return internalJdbcUrl() + adminDB; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /server/src/main/java/io/metaloom/test/container/provider/SQLUtils.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider; 2 | 3 | import java.sql.Connection; 4 | import java.sql.DriverManager; 5 | import java.sql.PreparedStatement; 6 | import java.sql.ResultSet; 7 | import java.sql.SQLException; 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import io.vertx.core.json.Json; 15 | import io.vertx.core.json.JsonObject; 16 | 17 | public final class SQLUtils { 18 | 19 | public static final String SELECT_DATABASES = "SELECT datname FROM pg_database"; 20 | 21 | private static final Logger log = LoggerFactory.getLogger(SQLUtils.class); 22 | 23 | private SQLUtils() { 24 | } 25 | 26 | public static void dropDatabase(Database db) throws SQLException { 27 | DatabaseSettings settings = db.settings(); 28 | try (Connection connection = DriverManager.getConnection(db.settings().internalAdminJdbcUrl(), settings.username(), settings.password())) { 29 | PreparedStatement statement = connection.prepareStatement("DROP DATABASE " + db.name()); 30 | statement.executeUpdate(); 31 | } 32 | } 33 | 34 | public static Database copyDatabase(DatabaseSettings settings, String sourceName, String targetName, DatabaseJsonCommentModel comment) 35 | throws SQLException { 36 | try (Connection connection = DriverManager.getConnection(settings.internalAdminJdbcUrl(), settings.username(), settings.password())) { 37 | String COPY_SQL = "CREATE DATABASE " + targetName + " WITH TEMPLATE " + sourceName + " OWNER " + settings.username(); 38 | PreparedStatement statement = connection 39 | .prepareStatement(COPY_SQL); 40 | statement.executeUpdate(); 41 | 42 | String json = JsonObject.mapFrom(comment).encode(); 43 | String COMMENT_SQL = "COMMENT ON database " + targetName + " is '" + json + "'"; 44 | PreparedStatement statement2 = connection 45 | .prepareStatement(COMMENT_SQL); 46 | statement2.executeUpdate(); 47 | return new Database(settings, targetName, comment); 48 | } 49 | } 50 | 51 | public static List listDatabases(DatabaseSettings settings) throws SQLException { 52 | List databaseNames = new ArrayList<>(); 53 | try (Connection connection = DriverManager.getConnection(settings.internalAdminJdbcUrl(), settings.username(), settings.password())) { 54 | PreparedStatement statement = connection.prepareStatement(SELECT_DATABASES); 55 | ResultSet rs = statement.executeQuery(); 56 | while (rs.next()) { 57 | String name = rs.getString(1); 58 | databaseNames.add(name); 59 | } 60 | } 61 | return databaseNames; 62 | } 63 | 64 | public static void setDatabaseComment(DatabaseSettings settings, String databaseName, DatabaseJsonCommentModel comment) throws SQLException { 65 | String json = JsonObject.mapFrom(comment).encode(); 66 | String SQL = "COMMENT ON database " + databaseName + " is '" + json + "'"; 67 | try (Connection connection = DriverManager.getConnection(settings.internalAdminJdbcUrl(), settings.username(), settings.password())) { 68 | PreparedStatement statement = connection 69 | .prepareStatement(SQL); 70 | statement.executeUpdate(); 71 | } 72 | } 73 | 74 | public static List listDatabasesWithComment(DatabaseSettings settings) throws SQLException { 75 | String SQL = "SELECT datname, pg_catalog.shobj_description(d.oid, 'pg_database') AS \"comment\" FROM pg_catalog.pg_database d;"; 76 | try (Connection connection = DriverManager.getConnection(settings.internalAdminJdbcUrl(), settings.username(), settings.password())) { 77 | PreparedStatement statement = connection 78 | .prepareStatement(SQL); 79 | ResultSet rs = statement.executeQuery(); 80 | List list = new ArrayList<>(); 81 | while (rs.next()) { 82 | String name = rs.getString(1); 83 | String commentStr = rs.getString(2); 84 | DatabaseJsonCommentModel comment = null; 85 | try { 86 | comment = Json.decodeValue(commentStr, DatabaseJsonCommentModel.class); 87 | } catch (Exception e) { 88 | log.warn("Error while decoding comment {} of database {} to model", commentStr, name); 89 | log.debug("Error while decoding comment", e); 90 | } 91 | list.add(new Database(settings, name, comment)); 92 | } 93 | return list; 94 | } 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /server/src/main/java/io/metaloom/test/container/provider/server/DatabaseProviderServer.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.server; 2 | 3 | import javax.inject.Inject; 4 | 5 | import org.slf4j.Logger; 6 | import org.slf4j.LoggerFactory; 7 | 8 | import io.metaloom.test.container.provider.DatabasePoolManager; 9 | import io.vertx.core.Future; 10 | import io.vertx.core.Vertx; 11 | import io.vertx.core.http.HttpMethod; 12 | import io.vertx.core.http.HttpServer; 13 | import io.vertx.core.http.HttpServerOptions; 14 | import io.vertx.ext.web.Router; 15 | import io.vertx.ext.web.handler.BodyHandler; 16 | import io.vertx.ext.web.handler.sockjs.SockJSHandler; 17 | import io.vertx.ext.web.handler.sockjs.SockJSHandlerOptions; 18 | 19 | public class DatabaseProviderServer { 20 | 21 | public static final Logger log = LoggerFactory.getLogger(DatabaseProviderServer.class); 22 | 23 | private final Vertx vertx; 24 | 25 | private final ServerConfiguration config; 26 | 27 | private final DatabasePoolManager manager; 28 | 29 | private final ServerApi api; 30 | 31 | private HttpServer server; 32 | 33 | @Inject 34 | public DatabaseProviderServer(Vertx vertx, ServerConfiguration config, DatabasePoolManager manager, ServerApi api) { 35 | this.vertx = vertx; 36 | this.config = config; 37 | this.manager = manager; 38 | this.api = api; 39 | } 40 | 41 | public Future start() { 42 | HttpServerOptions options = new HttpServerOptions(); 43 | options.setPort(config.httpPort()); 44 | options.setHost("0.0.0.0"); 45 | this.server = vertx.createHttpServer(options); 46 | 47 | Router router = Router.router(vertx); 48 | 49 | SockJSHandlerOptions sockOptions = new SockJSHandlerOptions().setHeartbeatInterval(500); 50 | 51 | SockJSHandler sockJSHandler = SockJSHandler.create(vertx, sockOptions); 52 | Router sockRouter = sockJSHandler.socketHandler(api::websocketHandler); 53 | router.route("/connect/*") 54 | .subRouter(sockRouter); 55 | router.route() 56 | .handler(BodyHandler.create()); 57 | router.route("/pools") 58 | .method(HttpMethod.GET) 59 | .handler(api::listPoolsHandler); 60 | router.route("/pools/:id") 61 | .method(HttpMethod.GET) 62 | .handler(api::loadPoolHandler); 63 | router.route("/pools/:id") 64 | .method(HttpMethod.DELETE) 65 | .handler(api::poolDeleteHandler); 66 | router.route("/pools/:id") 67 | .method(HttpMethod.POST) 68 | .handler(api::upsertPoolHandler); 69 | router.route() 70 | .failureHandler(api::failureHandler); 71 | server.requestHandler(router); 72 | return server.listen() 73 | .onSuccess(s -> { 74 | log.info("Server started. Listening on port {}", s.actualPort()); 75 | }); 76 | } 77 | 78 | public Future stop() { 79 | if (server != null) { 80 | return server.close(); 81 | } 82 | return Future.succeededFuture(); 83 | } 84 | 85 | public DatabasePoolManager getManager() { 86 | return manager; 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /server/src/main/java/io/metaloom/test/container/provider/server/DatabaseProviderServerRunner.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.server; 2 | 3 | import org.slf4j.Logger; 4 | import org.slf4j.LoggerFactory; 5 | 6 | import io.metaloom.test.container.provider.common.version.Version; 7 | import io.metaloom.test.container.provider.server.dagger.DaggerServerComponent; 8 | import io.metaloom.test.container.provider.server.dagger.ServerComponent; 9 | 10 | public class DatabaseProviderServerRunner { 11 | 12 | private static final Logger log = LoggerFactory.getLogger(DatabaseProviderServerRunner.class); 13 | 14 | public static void main(String[] args) { 15 | log.info("Starting Test Database Provider Server [" + Version.getPlainVersion() + "] - [" + Version.getBuildInfo().getBuildtimestamp() + "]"); 16 | ServerConfiguration config = ServerConfigurationLoader.load(); 17 | ServerComponent component = DaggerServerComponent.builder().configuration(config).build(); 18 | component.boot().start(); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /server/src/main/java/io/metaloom/test/container/provider/server/JSON.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.server; 2 | 3 | import io.vertx.core.buffer.Buffer; 4 | import io.vertx.core.json.JsonObject; 5 | 6 | public final class JSON { 7 | 8 | private JSON() { 9 | 10 | } 11 | 12 | public static Buffer toBuffer(Object obj) { 13 | return JsonObject.mapFrom(obj).toBuffer(); 14 | } 15 | 16 | public static T fromBuffer(Buffer buffer, Class classOfT) { 17 | JsonObject jsonObject = buffer.toJsonObject(); 18 | T obj = jsonObject.mapTo(classOfT); 19 | return obj; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /server/src/main/java/io/metaloom/test/container/provider/server/ModelHelper.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.server; 2 | 3 | import io.metaloom.test.container.provider.DatabaseAllocation; 4 | import io.metaloom.test.container.provider.DatabasePool; 5 | import io.metaloom.test.container.provider.model.DatabaseAllocationResponse; 6 | import io.metaloom.test.container.provider.model.DatabasePoolConnection; 7 | import io.metaloom.test.container.provider.model.DatabasePoolResponse; 8 | import io.metaloom.test.container.provider.model.DatabasePoolSettings; 9 | 10 | public final class ModelHelper { 11 | 12 | private ModelHelper() { 13 | } 14 | 15 | public static DatabasePoolResponse toModel(DatabasePool pool) { 16 | DatabasePoolConnection connection = new DatabasePoolConnection(); 17 | connection.setDatabase(pool.settings().adminDB()); 18 | connection.setHost(pool.settings().host()); 19 | connection.setPort(pool.settings().port()); 20 | connection.setInternalHost(pool.settings().internalHost()); 21 | connection.setInternalPort(pool.settings().internalPort()); 22 | 23 | connection.setUsername(pool.settings().username()); 24 | connection.setPassword(pool.settings().password()); 25 | 26 | DatabasePoolSettings settings = new DatabasePoolSettings(); 27 | settings.setIncrement(pool.getIncrement()); 28 | settings.setMaximum(pool.getMaximum()); 29 | settings.setMinimum(pool.getMinimum()); 30 | 31 | DatabasePoolResponse response = new DatabasePoolResponse(); 32 | response.setCreated(pool.getCreationDate()); 33 | response.setId(pool.id()); 34 | response.setConnection(connection); 35 | response.setSettings(settings); 36 | response.setAllocationLevel(pool.allocationLevel()); 37 | response.setLevel(pool.level()); 38 | response.setTemplateDatabaseName(pool.getTemplateName()); 39 | response.setStarted(pool.isStarted()); 40 | return response; 41 | } 42 | 43 | public static DatabaseAllocationResponse toModel(DatabaseAllocation allocation) { 44 | DatabaseAllocationResponse response = new DatabaseAllocationResponse(); 45 | response.setPoolId(allocation.getPool().id()); 46 | response.setId(allocation.id()); 47 | response.setHost(allocation.db().settings().host()); 48 | response.setPort(allocation.db().settings().port()); 49 | response.setJdbcUrl(allocation.db().jdbcUrl()); 50 | response.setUsername(allocation.db().settings().username()); 51 | response.setPassword(allocation.db().settings().password()); 52 | response.setDatabaseName(allocation.db().name()); 53 | return response; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /server/src/main/java/io/metaloom/test/container/provider/server/ProviderRequest.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.server; 2 | 3 | public record ProviderRequest(String poolId, String testName) { 4 | 5 | public static ProviderRequest from(String id) { 6 | if (!id.contains("/")) { 7 | throw new ServerError("Id must contain pool name. Got: " + id); 8 | } 9 | String[] parts = id.split("/"); 10 | String poolId = parts[0]; 11 | String testName = parts[1]; 12 | return new ProviderRequest(poolId, testName); 13 | } 14 | 15 | public String toString() { 16 | return poolId + " / " + testName; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /server/src/main/java/io/metaloom/test/container/provider/server/ServerApi.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.server; 2 | 3 | import java.sql.SQLException; 4 | import java.util.concurrent.atomic.AtomicReference; 5 | 6 | import javax.inject.Inject; 7 | import javax.inject.Singleton; 8 | 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | 12 | import io.metaloom.test.container.provider.DatabaseAllocation; 13 | import io.metaloom.test.container.provider.DatabasePool; 14 | import io.metaloom.test.container.provider.DatabasePoolManager; 15 | import io.metaloom.test.container.provider.DatabaseSettings; 16 | import io.metaloom.test.container.provider.model.DatabaseAllocationResponse; 17 | import io.metaloom.test.container.provider.model.DatabasePoolConnection; 18 | import io.metaloom.test.container.provider.model.DatabasePoolListResponse; 19 | import io.metaloom.test.container.provider.model.DatabasePoolRequest; 20 | import io.metaloom.test.container.provider.model.DatabasePoolResponse; 21 | import io.metaloom.test.container.provider.model.DatabasePoolSettings; 22 | import io.vertx.core.buffer.Buffer; 23 | import io.vertx.core.http.HttpHeaders; 24 | import io.vertx.core.json.JsonObject; 25 | import io.vertx.ext.web.RoutingContext; 26 | import io.vertx.ext.web.handler.sockjs.SockJSSocket; 27 | 28 | @Singleton 29 | public class ServerApi { 30 | 31 | private static final Logger log = LoggerFactory.getLogger(ServerApi.class); 32 | 33 | private final DatabasePoolManager manager; 34 | 35 | @Inject 36 | public ServerApi(DatabasePoolManager manager) { 37 | this.manager = manager; 38 | } 39 | 40 | public void poolDeleteHandler(RoutingContext rc) { 41 | String id = rc.pathParam("id"); 42 | log.info("Deleting pool {}", id); 43 | boolean hasPool = manager.contains(id); 44 | if (!hasPool) { 45 | rc.response().setStatusCode(404).end(); 46 | return; 47 | } 48 | boolean result = manager.deletePool(id); 49 | if (result) { 50 | log.info("Pool {} deleted", id); 51 | rc.response().setStatusCode(204).end(); 52 | return; 53 | } else { 54 | log.error("Error while deleting pool {}", id); 55 | rc.response().setStatusCode(400).end(); 56 | } 57 | } 58 | 59 | public void upsertPoolHandler(RoutingContext rc) { 60 | String id = rc.pathParam("id"); 61 | DatabasePoolRequest model = rc.body().asPojo(DatabasePoolRequest.class); 62 | String templateDatabaseName = require(model.getTemplateDatabaseName(), "templateDatabaseName"); 63 | 64 | DatabasePool pool = manager.getPool(id); 65 | if (pool == null) { 66 | DatabasePoolConnection connection = model.getConnection(); 67 | DatabasePoolSettings settings = model.getSettings(); 68 | log.info("Adding pool {}", id); 69 | 70 | require(connection, "connection"); 71 | String host = require(connection.getHost(), "host"); 72 | Integer port = require(connection.getPort(), "port"); 73 | String internalHost = connection.getInternalHost() == null ? host : connection.getInternalHost(); 74 | Integer internalPort = connection.getInternalPort() == null ? port : connection.getInternalPort(); 75 | String username = require(connection.getUsername(), "username"); 76 | String password = require(connection.getPassword(), "password"); 77 | String adminDB = require(connection.getDatabase(), "database"); 78 | pool = manager.createPool(id, host, port, internalHost, internalPort, username, password, adminDB); 79 | 80 | // Apply the custom setting if those were provided. Otherwise the manager will use the default values. 81 | if (settings != null) { 82 | Integer minimum = settings.getMinimum(); 83 | if (minimum != null) { 84 | pool.setMinimum(minimum); 85 | } 86 | Integer maximum = settings.getMaximum(); 87 | if (maximum != null) { 88 | pool.setMaximum(maximum); 89 | } 90 | Integer increment = settings.getIncrement(); 91 | if (increment != null) { 92 | pool.setIncrement(increment); 93 | } 94 | } 95 | pool.setTemplateDatabaseName(templateDatabaseName); 96 | pool.start(); 97 | } else { 98 | log.info("Recreating pool {}", id); 99 | DatabaseSettings settings = pool.settings(); 100 | manager.deletePool(id); 101 | DatabasePool newPool = manager.createPool(id, settings.host(), settings.port(), settings.internalHost(), settings.internalPort(), 102 | settings.username(), settings.password(), settings.adminDB(), templateDatabaseName); 103 | if (!newPool.isStarted()) { 104 | newPool.start(); 105 | } 106 | pool = newPool; 107 | } 108 | 109 | DatabasePoolResponse response = ModelHelper.toModel(pool); 110 | rc.response().putHeader(HttpHeaders.CONTENT_TYPE, "application/json").end(JSON.toBuffer(response)); 111 | 112 | } 113 | 114 | private T require(T value, String key) { 115 | if (value == null) { 116 | throw new ServerError(String.format("Field %s is missing", key)); 117 | } 118 | return value; 119 | } 120 | 121 | public void failureHandler(RoutingContext rc) { 122 | if (rc.failed() && rc.failure() instanceof ServerError se) { 123 | rc.response() 124 | .setStatusCode(400) 125 | .putHeader(HttpHeaders.CONTENT_TYPE, "application/json") 126 | .end(se.toJson().toBuffer()); 127 | return; 128 | } 129 | log.error("Error while handling request for " + rc.normalizedPath(), rc.failure()); 130 | rc.next(); 131 | } 132 | 133 | public void loadPoolHandler(RoutingContext rc) { 134 | String id = rc.pathParam("id"); 135 | DatabasePool pool = manager.getPool(id); 136 | if (pool == null) { 137 | log.info("Pool {} not found", id); 138 | rc.response().setStatusCode(404).end(); 139 | } else { 140 | DatabasePoolResponse response = ModelHelper.toModel(pool); 141 | rc.response().putHeader(HttpHeaders.CONTENT_TYPE, "application/json").end(JSON.toBuffer(response)); 142 | } 143 | } 144 | 145 | public void listPoolsHandler(RoutingContext rc) { 146 | log.info("Getting stat request"); 147 | 148 | DatabasePoolListResponse response = new DatabasePoolListResponse(); 149 | for (DatabasePool pool : manager.getPools()) { 150 | response.add(ModelHelper.toModel(pool)); 151 | } 152 | 153 | Buffer buffer = JSON.toBuffer(response); 154 | rc.response().putHeader(HttpHeaders.CONTENT_TYPE, "application/json").end(buffer); 155 | } 156 | 157 | public void websocketHandler(SockJSSocket sock) { 158 | 159 | AtomicReference allocationRef = new AtomicReference<>(); 160 | 161 | // Check the message - currently msgs will only be send from the client to request a new database 162 | sock.handler(msg -> { 163 | ProviderRequest providerRequest = ProviderRequest.from(msg.toString()); 164 | 165 | log.info("Allocating db for {}", providerRequest); 166 | try { 167 | DatabasePool pool = manager.getPool(providerRequest.poolId()); 168 | if (pool == null) { 169 | log.error("Unable to fullfil request. Provided pool for id {} not found", providerRequest.poolId()); 170 | error(sock, "Pool not found {" + providerRequest.poolId() + "}"); 171 | return; 172 | } 173 | DatabaseAllocation allocation = pool.allocate(providerRequest.testName()); 174 | if (allocation == null) { 175 | error(sock, "Unable to allocate database from pool {" + providerRequest.poolId() + "}"); 176 | return; 177 | } 178 | allocationRef.set(allocation); 179 | DatabaseAllocationResponse response = ModelHelper.toModel(allocation); 180 | sock.write(JsonObject.mapFrom(response).toBuffer()); 181 | } catch (SQLException e) { 182 | log.error("Error while allocating database for test {}", providerRequest, e); 183 | error(sock, "Unknown error"); 184 | } 185 | }); 186 | sock.closeHandler(close -> { 187 | DatabaseAllocation allocation = allocationRef.get(); 188 | if (allocation != null) { 189 | log.info("Releasing allocation {}", allocation); 190 | try { 191 | allocation.release(); 192 | } catch (Exception e) { 193 | log.error("Error while releasing database for test {}", allocation.db().name(), e); 194 | } 195 | } else { 196 | log.debug("No allocation found. Just closing connection."); 197 | } 198 | }); 199 | } 200 | 201 | private void error(SockJSSocket sock, String msg) { 202 | sock.write(new JsonObject().put("error", msg).toBuffer()); 203 | } 204 | 205 | } 206 | -------------------------------------------------------------------------------- /server/src/main/java/io/metaloom/test/container/provider/server/ServerConfiguration.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.server; 2 | 3 | import io.metaloom.test.container.provider.DatabaseSettings; 4 | 5 | public record ServerConfiguration(int httpPort, String host, Integer port, String username, String password, String adminDB, 6 | String templateDatabaseName, Integer defaultPoolMinimum, Integer defaultPoolMaximum, 7 | Integer defaultPoolIncrement) { 8 | 9 | public static ServerConfiguration create(int httpPort, String host, Integer port, String username, String password, String adminDB, 10 | String templateDatabaseName, Integer defaultPoolMinimum, Integer defaultPoolMaximum, 11 | Integer defaultPoolIncrement) { 12 | return new ServerConfiguration(httpPort, host, port, username, password, adminDB, templateDatabaseName, defaultPoolMinimum, 13 | defaultPoolMaximum, defaultPoolIncrement); 14 | } 15 | 16 | public static ServerConfiguration create(int httpPort) { 17 | return new ServerConfiguration(httpPort, null, null, null, null, null, null, null, null, null); 18 | } 19 | 20 | public DatabaseSettings databaseSettings() { 21 | return new DatabaseSettings(host, port, host, port, username, password, adminDB); 22 | } 23 | 24 | public boolean hasConnectionDetails() { 25 | return host != null && port != null && username != null && password != null && adminDB != null; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /server/src/main/java/io/metaloom/test/container/provider/server/ServerConfigurationLoader.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.server; 2 | 3 | import io.metaloom.test.container.provider.common.ServerEnv; 4 | 5 | public final class ServerConfigurationLoader { 6 | 7 | private ServerConfigurationLoader() { 8 | } 9 | 10 | /** 11 | * Load the effective server configuration by examining env variables. 12 | * 13 | * @return 14 | */ 15 | public static ServerConfiguration load() { 16 | String host = ServerEnv.getDatabaseHost(); 17 | Integer port = ServerEnv.getDatabasePort(); 18 | String username = ServerEnv.getDatabaseUsername(); 19 | String password = ServerEnv.getDatabasePassword(); 20 | String adminDB = ServerEnv.getDatabaseName(); 21 | String templateDatabaseName = ServerEnv.getDatabaseTemplateName(); 22 | int httpPort = ServerEnv.getHttpPort(); 23 | 24 | if (templateDatabaseName != null) { 25 | requireEnv(host, ServerEnv.TESTDATABASE_PROVIDER_DATABASE_HOST_KEY, templateDatabaseName); 26 | requireEnv(port, ServerEnv.TESTDATABASE_PROVIDER_DATABASE_PORT_KEY, templateDatabaseName); 27 | requireEnv(username, ServerEnv.TESTDATABASE_PROVIDER_DATABASE_USERNAME_KEY, templateDatabaseName); 28 | requireEnv(password, ServerEnv.TESTDATABASE_PROVIDER_DATABASE_PASSWORD_KEY, templateDatabaseName); 29 | requireEnv(adminDB, ServerEnv.TESTDATABASE_PROVIDER_DATABASE_DBNAME_KEY, templateDatabaseName); 30 | } 31 | 32 | int defaultMinimum = ServerEnv.getPoolMinimum(); 33 | int defaultMaximum = ServerEnv.getPoolMaximum(); 34 | int defaultIncrement = ServerEnv.getPoolIncrement(); 35 | 36 | return new ServerConfiguration(httpPort, host, port, username, password, adminDB, templateDatabaseName, defaultMinimum, defaultMaximum, 37 | defaultIncrement); 38 | } 39 | 40 | private static void requireEnv(Object value, String env, String templateDatabaseName) { 41 | if (value == null) { 42 | throw new ServerError("The env " + env + " must be set when a pool should be created for " + templateDatabaseName); 43 | } 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /server/src/main/java/io/metaloom/test/container/provider/server/ServerError.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.server; 2 | 3 | import io.vertx.core.json.JsonObject; 4 | 5 | public class ServerError extends RuntimeException { 6 | 7 | private static final long serialVersionUID = -6011015473604155831L; 8 | 9 | public ServerError(String msg) { 10 | super(msg); 11 | } 12 | 13 | public JsonObject toJson() { 14 | JsonObject json = new JsonObject(); 15 | return json; 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /server/src/main/java/io/metaloom/test/container/provider/server/dagger/ProviderModule.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.server.dagger; 2 | 3 | import dagger.Module; 4 | 5 | @Module 6 | public class ProviderModule { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /server/src/main/java/io/metaloom/test/container/provider/server/dagger/ServerComponent.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.server.dagger; 2 | 3 | import javax.inject.Singleton; 4 | 5 | import dagger.BindsInstance; 6 | import dagger.Component; 7 | import io.metaloom.test.container.provider.BootstrapInitializer; 8 | import io.metaloom.test.container.provider.server.ServerConfiguration; 9 | 10 | /** 11 | * Central dagger component. 12 | */ 13 | @Singleton 14 | @Component(modules = { VertxModule.class, ProviderModule.class }) 15 | public interface ServerComponent { 16 | 17 | BootstrapInitializer boot(); 18 | 19 | @Component.Builder 20 | interface Builder { 21 | 22 | /** 23 | * Inject configuration options. 24 | * 25 | * @param options 26 | * @return 27 | */ 28 | @BindsInstance 29 | Builder configuration(ServerConfiguration options); 30 | 31 | /** 32 | * Build the component. 33 | * 34 | * @return 35 | */ 36 | ServerComponent build(); 37 | 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /server/src/main/java/io/metaloom/test/container/provider/server/dagger/VertxModule.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.provider.server.dagger; 2 | 3 | import javax.inject.Singleton; 4 | 5 | import dagger.Module; 6 | import dagger.Provides; 7 | import io.vertx.core.Vertx; 8 | import io.vertx.core.file.FileSystem; 9 | 10 | @Module 11 | public class VertxModule { 12 | 13 | @Provides 14 | @Singleton 15 | public Vertx vertx() { 16 | return Vertx.vertx(); 17 | } 18 | 19 | @Provides 20 | @Singleton 21 | public FileSystem filesystem(Vertx vertx) { 22 | return vertx.fileSystem(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /server/src/test/java/io/metaloom/test/container/server/AbstractProviderServerTest.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.server; 2 | 3 | public abstract class AbstractProviderServerTest implements AllocationTestHelper { 4 | 5 | public static DatabaseProviderTestServer server = new DatabaseProviderTestServer(); 6 | } 7 | -------------------------------------------------------------------------------- /server/src/test/java/io/metaloom/test/container/server/AllocationTestHelper.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.server; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertTrue; 5 | import static org.junit.Assert.assertNotNull; 6 | 7 | import io.metaloom.test.container.provider.model.DatabaseAllocationResponse; 8 | 9 | public interface AllocationTestHelper { 10 | 11 | default void assertAllocation(DatabaseProviderTestServer server, DatabaseAllocationResponse db, String testName) { 12 | assertNotNull(db.getJdbcUrl()); 13 | assertEquals("The database hostname did not match.", "localhost", db.getHost()); 14 | assertEquals("The port did not match up with the container", server.db().getPort(), db.getPort()); 15 | assertNotNull("The database name should not be null", db.getDatabaseName()); 16 | assertEquals("The username did not match.", server.db().getUsername(), db.getUsername()); 17 | assertEquals("The password did not match.", server.db().getPassword(), db.getPassword()); 18 | assertNotNull("The test id must not be null", db.getId()); 19 | assertTrue("The allocation id {" + db.getId() + "} did not contain the testname", db.getId().endsWith(testName)); 20 | assertEquals("default", db.getPoolId()); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /server/src/test/java/io/metaloom/test/container/server/DatabasePoolManagerTest.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.server; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import java.io.IOException; 6 | import java.sql.SQLException; 7 | 8 | import org.junit.jupiter.api.BeforeAll; 9 | import org.junit.jupiter.api.Test; 10 | import org.testcontainers.junit.jupiter.Container; 11 | import org.testcontainers.junit.jupiter.Testcontainers; 12 | 13 | import io.metaloom.maven.provider.container.PostgreSQLPoolContainer; 14 | import io.metaloom.test.container.provider.DatabasePool; 15 | import io.metaloom.test.container.provider.DatabasePoolManager; 16 | import io.metaloom.test.container.provider.server.ServerConfiguration; 17 | import io.vertx.core.Vertx; 18 | 19 | @Testcontainers 20 | public class DatabasePoolManagerTest { 21 | 22 | public static Vertx vertx = Vertx.vertx(); 23 | 24 | public static ServerConfiguration config; 25 | 26 | @Container 27 | public static PostgreSQLPoolContainer container = new PostgreSQLPoolContainer().withTmpFs(128); 28 | 29 | @BeforeAll 30 | public static void setup() { 31 | config = ServerConfiguration.create(0, container.getHost(), container.getPort(), container.getUsername(), container.getPassword(), 32 | container.getDatabaseName(), null, 10, 25, 5); 33 | } 34 | 35 | @Test 36 | public void testPool() { 37 | DatabasePoolManager manager = new DatabasePoolManager(vertx, config); 38 | assertEquals(0, manager.getPools() 39 | .size()); 40 | DatabasePool pool = createPool(manager, "dummy"); 41 | pool.start(); 42 | assertEquals(1, manager.getPools().size()); 43 | manager.deletePool(pool.id()); 44 | assertEquals(0, manager.getPools().size()); 45 | } 46 | 47 | @Test 48 | public void testPoolRestartBehaviour() throws SQLException, IOException, InterruptedException { 49 | DatabasePool pool = createPool(new DatabasePoolManager(vertx, config), "dummy1234").setTemplateDatabaseName(container.getDatabaseName()); 50 | pool.preAllocate(); 51 | assertEquals(5, pool.level()); 52 | DatabasePoolManager manager = new DatabasePoolManager(vertx, config); 53 | int importedDbs = manager.loadFromDB(); 54 | assertEquals(5, importedDbs); 55 | assertEquals(1, manager.getPools().size()); 56 | } 57 | 58 | private DatabasePool createPool(DatabasePoolManager manager, String name) { 59 | return manager.createPool("test123", container.getHost(), container.getPort(), container.getHost(), container.getPort(), 60 | container.getUsername(), 61 | container.getPassword(), container.getDatabaseName()); 62 | 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /server/src/test/java/io/metaloom/test/container/server/DatabasePoolTest.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.server; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertFalse; 5 | import static org.junit.Assert.assertNotNull; 6 | import static org.junit.Assert.assertTrue; 7 | 8 | import java.sql.SQLException; 9 | import java.util.List; 10 | 11 | import org.junit.jupiter.api.AfterEach; 12 | import org.junit.jupiter.api.BeforeEach; 13 | import org.junit.jupiter.api.Test; 14 | import org.testcontainers.junit.jupiter.Container; 15 | import org.testcontainers.junit.jupiter.Testcontainers; 16 | 17 | import io.metaloom.maven.provider.container.PostgreSQLPoolContainer; 18 | import io.metaloom.test.container.provider.DatabaseAllocation; 19 | import io.metaloom.test.container.provider.DatabasePool; 20 | import io.metaloom.test.container.provider.DatabasePoolFactory; 21 | import io.metaloom.test.container.provider.SQLUtils; 22 | import io.vertx.core.Vertx; 23 | 24 | @Testcontainers 25 | public class DatabasePoolTest { 26 | 27 | public static final Vertx vertx = Vertx.vertx(); 28 | 29 | @Container 30 | public static PostgreSQLPoolContainer container = new PostgreSQLPoolContainer().withTmpFs(128); 31 | 32 | DatabasePool pool; 33 | 34 | @BeforeEach 35 | public void setup() throws SQLException { 36 | DatabasePoolFactory factory = new DatabasePoolFactory(vertx, null); 37 | this.pool = factory.createPool("dummy", container.getHost(), container.getPort(), container.getHost(), container.getPort(), 38 | container.getUsername(), container.getPassword(), 39 | container.getDatabaseName()); 40 | String databaseName = TestSQLHelper.setupTable(pool.settings().jdbcUrl(), pool.settings().username(), pool.settings().password()); 41 | pool.setTemplateDatabaseName(databaseName); 42 | } 43 | 44 | @AfterEach 45 | public void stop() { 46 | if (pool != null) { 47 | pool.stop(); 48 | } 49 | } 50 | 51 | @Test 52 | public void testPool() throws SQLException, InterruptedException { 53 | Thread.sleep(2000); 54 | assertEquals("There should be no databases allocated yet.", 0, pool.allocationLevel()); 55 | assertEquals("There should be no databases in the pool", 0, pool.level()); 56 | assertNotNull("The template should already been set", pool.getTemplateName()); 57 | assertFalse("The pool should still be not started.", pool.isStarted()); 58 | 59 | pool.start(); 60 | Thread.sleep(4000); 61 | assertTrue("The pool should have been started.", pool.isStarted()); 62 | assertTrue("The pool should already started preparing databases.", pool.level() != 0); 63 | assertEquals("There should be no databases allocated yet.", 0, pool.allocationLevel()); 64 | 65 | DatabaseAllocation allocation = pool.allocate("test123"); 66 | assertTrue("The id was wrong. Got: " + allocation.id(), allocation.id() 67 | .endsWith("#test123")); 68 | assertEquals("One allocation should be listed", 1, pool.allocationLevel()); 69 | assertTrue("The allocation could not be released", pool.release(allocation)); 70 | List dbs = SQLUtils.listDatabases(pool.settings()); 71 | for (String db : dbs) { 72 | System.out.println(db); 73 | } 74 | assertEquals("The allocation should now be gone", 0, pool.allocationLevel()); 75 | assertTrue(dbs.size() > 1); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /server/src/test/java/io/metaloom/test/container/server/DatabaseProviderTest.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.server; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | import java.util.concurrent.ExecutionException; 5 | 6 | import org.junit.jupiter.api.Test; 7 | 8 | import io.metaloom.test.container.provider.DatabasePoolManager; 9 | import io.metaloom.test.container.provider.client.ClientAllocation; 10 | import io.metaloom.test.container.provider.client.ProviderClient; 11 | import io.metaloom.test.container.provider.server.DatabaseProviderServer; 12 | import io.metaloom.test.container.provider.server.ServerApi; 13 | import io.metaloom.test.container.provider.server.ServerConfiguration; 14 | import io.vertx.core.Future; 15 | import io.vertx.core.Vertx; 16 | 17 | public class DatabaseProviderTest { 18 | 19 | @Test 20 | public void testAcquire() throws InterruptedException, ExecutionException { 21 | Vertx vertx = Vertx.vertx(); 22 | ServerConfiguration config = ServerConfiguration.create(0); 23 | DatabasePoolManager manager = new DatabasePoolManager(vertx, config); 24 | ServerApi api = new ServerApi(manager); 25 | Future future = new DatabaseProviderServer(vertx, config, manager, api).start().compose(server -> { 26 | ProviderClient client = new ProviderClient("localhost", server.actualPort()); 27 | CompletableFuture fut = client.link("dummy", "test"); 28 | return Future.fromCompletionStage(fut); 29 | }); 30 | 31 | future.toCompletionStage().toCompletableFuture().get(); 32 | 33 | Thread.sleep(5_000); 34 | 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /server/src/test/java/io/metaloom/test/container/server/DatabaseProviderTestServer.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.server; 2 | 3 | import io.metaloom.maven.provider.container.PostgreSQLPoolContainer; 4 | import io.metaloom.test.container.provider.BootstrapInitializer; 5 | import io.metaloom.test.container.provider.DatabasePool; 6 | import io.metaloom.test.container.provider.server.DatabaseProviderServer; 7 | import io.metaloom.test.container.provider.server.ServerConfiguration; 8 | import io.metaloom.test.container.provider.server.dagger.DaggerServerComponent; 9 | import io.vertx.core.http.HttpServer; 10 | 11 | public class DatabaseProviderTestServer { 12 | 13 | private HttpServer httpServer; 14 | private DatabasePool pool; 15 | private PostgreSQLPoolContainer db; 16 | 17 | public DatabaseProviderTestServer() { 18 | try { 19 | db = new PostgreSQLPoolContainer(); 20 | db.start(); 21 | ServerConfiguration config = new ServerConfiguration(0, db.getHost(), db.getPort(), db.getUsername(), db.getPassword(), db.getDatabaseName(), 22 | null, null, null, null); 23 | BootstrapInitializer boot = DaggerServerComponent.builder().configuration(config).build().boot(); 24 | DatabaseProviderServer server = boot.server(); 25 | this.httpServer = server.start().toCompletionStage().toCompletableFuture().get(); 26 | this.pool = server.getManager().createPool("default", "localhost", db.getPort(), "localhost", db.getPort(), db.getUsername(), 27 | db.getPassword(), db.getDatabaseName(), db.getDatabaseName()); 28 | pool.start(); 29 | } catch (Exception e) { 30 | e.printStackTrace(); 31 | } 32 | } 33 | 34 | public DatabasePool getPool() { 35 | return pool; 36 | } 37 | 38 | public int getPort() { 39 | return httpServer.actualPort(); 40 | } 41 | 42 | public PostgreSQLPoolContainer db() { 43 | return db; 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /server/src/test/java/io/metaloom/test/container/server/JSONTest.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.server; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import org.junit.jupiter.api.Test; 6 | 7 | import io.metaloom.test.container.provider.model.DatabasePoolConnection; 8 | import io.metaloom.test.container.provider.model.DatabasePoolRequest; 9 | import io.metaloom.test.container.provider.server.JSON; 10 | import io.vertx.core.buffer.Buffer; 11 | 12 | public class JSONTest { 13 | 14 | @Test 15 | public void testJson() { 16 | Buffer buffer = JSON.toBuffer(new DatabasePoolRequest().setConnection(new DatabasePoolConnection().setHost("ABC"))); 17 | System.out.println(buffer.toJsonObject().encodePrettily()); 18 | DatabasePoolRequest obj = JSON.fromBuffer(buffer, DatabasePoolRequest.class); 19 | assertEquals("ABC", obj.getConnection().getHost()); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /server/src/test/java/io/metaloom/test/container/server/ModelHelperTest.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.server; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import io.metaloom.test.container.provider.DatabasePool; 6 | import io.metaloom.test.container.provider.DatabasePoolFactory; 7 | import io.metaloom.test.container.provider.model.DatabasePoolResponse; 8 | import io.metaloom.test.container.provider.server.ModelHelper; 9 | import io.vertx.core.json.JsonObject; 10 | 11 | public class ModelHelperTest { 12 | 13 | // static { 14 | // io.vertx.core.json.jackson.DatabindCodec codec = (io.vertx.core.json.jackson.DatabindCodec) io.vertx.core.json.Json.CODEC; 15 | // // returns the ObjectMapper used by Vert.x 16 | // ObjectMapper mapper = codec.mapper(); 17 | // mapper.registerModule(new JavaTimeModule()); 18 | // mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); 19 | // } 20 | 21 | @Test 22 | public void testPoolModel() { 23 | DatabasePoolFactory factory = new DatabasePoolFactory(null, null); 24 | DatabasePool pool = factory.createPool("dummy", "localhost", 42, "localhost-int", 42, "user", "pw", "admin-db"); 25 | DatabasePoolResponse model = ModelHelper.toModel(pool); 26 | JsonObject json = JsonObject.mapFrom(model); 27 | System.out.println(json.encodePrettily()); 28 | System.out.println(json.encode()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /server/src/test/java/io/metaloom/test/container/server/ProviderClientServerTest.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.server; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.jupiter.api.Assertions.assertNotNull; 5 | 6 | import java.util.concurrent.CompletableFuture; 7 | 8 | import org.junit.jupiter.api.AfterAll; 9 | import org.junit.jupiter.api.BeforeAll; 10 | import org.junit.jupiter.api.Test; 11 | import org.testcontainers.junit.jupiter.Container; 12 | import org.testcontainers.junit.jupiter.Testcontainers; 13 | 14 | import io.metaloom.maven.provider.container.PostgreSQLPoolContainer; 15 | import io.metaloom.test.container.provider.DatabasePoolManager; 16 | import io.metaloom.test.container.provider.client.ClientAllocation; 17 | import io.metaloom.test.container.provider.client.ProviderClient; 18 | import io.metaloom.test.container.provider.model.DatabasePoolConnection; 19 | import io.metaloom.test.container.provider.model.DatabasePoolListResponse; 20 | import io.metaloom.test.container.provider.model.DatabasePoolRequest; 21 | import io.metaloom.test.container.provider.model.DatabasePoolResponse; 22 | import io.metaloom.test.container.provider.model.DatabasePoolSettings; 23 | import io.metaloom.test.container.provider.server.DatabaseProviderServer; 24 | import io.metaloom.test.container.provider.server.ServerApi; 25 | import io.metaloom.test.container.provider.server.ServerConfiguration; 26 | import io.vertx.core.Vertx; 27 | import io.vertx.core.http.HttpServer; 28 | import io.vertx.core.json.Json; 29 | 30 | @Testcontainers 31 | public class ProviderClientServerTest { 32 | 33 | @Container 34 | public static PostgreSQLPoolContainer db = new PostgreSQLPoolContainer().withTmpFs(128); 35 | 36 | private static ProviderClient client; 37 | private static DatabaseProviderServer server; 38 | 39 | @BeforeAll 40 | public static void setup() throws Exception { 41 | Vertx vertx = Vertx.vertx(); 42 | DatabasePoolManager manager = new DatabasePoolManager(vertx, null); 43 | ServerApi api = new ServerApi(manager); 44 | server = new DatabaseProviderServer(vertx, ServerConfiguration.create(0), manager, api); 45 | HttpServer httpServer = server.start().toCompletionStage().toCompletableFuture().get(); 46 | client = new ProviderClient("localhost", httpServer.actualPort()); 47 | } 48 | 49 | @AfterAll 50 | public static void tearDown() throws Exception { 51 | server.stop().toCompletionStage().toCompletableFuture().get(); 52 | } 53 | 54 | @Test 55 | public void testSetupPool() throws Exception { 56 | 57 | CompletableFuture result = client.createPool("dummy", poolCreateRequest()); 58 | DatabasePoolResponse response = result.get(); 59 | System.out.println(Json.encodePrettily(response)); 60 | 61 | Thread.sleep(2000); 62 | DatabasePoolResponse result2 = client.loadPool("dummy").get(); 63 | System.out.println(Json.encodePrettily(result2)); 64 | 65 | DatabasePoolListResponse list = client.listPools().get(); 66 | assertEquals(1, list.getList().size()); 67 | 68 | client.deletePool(response.getId()).get(); 69 | assertEquals(0, client.listPools().get().getList().size()); 70 | } 71 | 72 | @Test 73 | public void testAcquire() throws Exception { 74 | assertNotNull(client.createPool("dummy", poolCreateRequest()).get()); 75 | Thread.sleep(2_000); 76 | 77 | ClientAllocation allocation = client.link("dummy", "testAcquire").get(); 78 | assertNotNull(allocation.response()); 79 | allocation.release(); 80 | } 81 | 82 | @Test 83 | public void testAcquire2() throws Exception { 84 | client.link("dummy", "testAcquire2").get(); 85 | Thread.sleep(2_000); 86 | } 87 | 88 | private DatabasePoolRequest poolCreateRequest() { 89 | DatabasePoolRequest request = new DatabasePoolRequest(); 90 | DatabasePoolConnection connection = new DatabasePoolConnection().setHost("localhost") 91 | .setHost("localhost") 92 | .setPort(db.getPort()) 93 | .setPassword("sa") 94 | .setUsername("sa") 95 | .setDatabase("postgres"); 96 | 97 | DatabasePoolSettings settings = new DatabasePoolSettings() 98 | .setMinimum(10) 99 | .setMaximum(20) 100 | .setIncrement(5); 101 | 102 | request.setConnection(connection) 103 | .setSettings(settings) 104 | .setTemplateDatabaseName("postgres"); 105 | return request; 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /server/src/test/java/io/metaloom/test/container/server/TestSQLHelper.java: -------------------------------------------------------------------------------- 1 | package io.metaloom.test.container.server; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | 5 | import java.sql.Connection; 6 | import java.sql.DriverManager; 7 | import java.sql.PreparedStatement; 8 | import java.sql.SQLException; 9 | import java.sql.Statement; 10 | 11 | import org.testcontainers.containers.PostgreSQLContainer; 12 | 13 | public final class TestSQLHelper { 14 | 15 | private TestSQLHelper() { 16 | } 17 | 18 | private static final String CREATE_TABLE = """ 19 | CREATE TABLE users 20 | (id INT PRIMARY KEY, name TEXT) 21 | """; 22 | 23 | private static final String INSERT_USER = "INSERT INTO users (id, name) VALUES (?, ?)"; 24 | 25 | private static final String SELECT_USERS = "SELECT id, name from users"; 26 | 27 | private static final String DELETE_USERS = "DELETE FROM users"; 28 | 29 | public static final int USER_COUNT = 50_000; 30 | 31 | private static final String CREATE_DB = "CREATE DATABASE test_template"; 32 | 33 | public static String setupTable(String jdbcUrl, String username, String password) throws SQLException { 34 | String dbName = "test_template"; 35 | try (Connection connection = DriverManager.getConnection(jdbcUrl + "postgres", username, password)) { 36 | Statement statement1 = connection.createStatement(); 37 | statement1.execute(CREATE_DB); 38 | } 39 | try (Connection connection = DriverManager.getConnection(jdbcUrl + dbName, username, password)) { 40 | Statement statement2 = connection.createStatement(); 41 | statement2.execute(CREATE_TABLE); 42 | } 43 | return dbName; 44 | } 45 | 46 | public static void insertUsers(PostgreSQLContainer db, int id, String name) throws SQLException { 47 | try (Connection connection = DriverManager.getConnection(db.getJdbcUrl(), db.getUsername(), db.getPassword())) { 48 | for (int i = 1; i <= USER_COUNT; i++) { 49 | PreparedStatement statement = connection.prepareStatement(INSERT_USER); 50 | statement.setInt(1, id + i); 51 | statement.setString(2, name + "_" + i); 52 | assertEquals(1, statement.executeUpdate()); 53 | } 54 | } 55 | } 56 | 57 | public static void deleteUsers(PostgreSQLContainer db) throws SQLException { 58 | try (Connection connection = DriverManager.getConnection(db.getJdbcUrl(), db.getUsername(), db.getPassword())) { 59 | PreparedStatement statement = connection.prepareStatement(DELETE_USERS); 60 | statement.executeUpdate(); 61 | } 62 | 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /server/stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | for i in $(docker ps | awk '{print $1}') ; do docker rm -f $i ; done 4 | -------------------------------------------------------------------------------- /server/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | IMAGE=metaloom/testdatabase-provider 4 | TAG=0.1.0-SNAPSHOT 5 | 6 | docker run --rm \ 7 | --env "TESTDATABASE_PROVIDER_DATABASE_HOST=localhost" \ 8 | --env "TESTDATABASE_PROVIDER_DATABASE_PORT=1234" \ 9 | --env "TESTDATABASE_PROVIDER_DATABASE_USERNAME=sa" \ 10 | --env "TESTDATABASE_PROVIDER_DATABASE_PASSWORD=sa" \ 11 | $IMAGE:$TAG 12 | --------------------------------------------------------------------------------