au.net.causal.maven.plugins.boxdb.StartMojo Maven / Gradle / Ivy
Show all versions of boxdb-maven-plugin Show documentation
package au.net.causal.maven.plugins.boxdb;
import au.net.causal.maven.plugins.boxdb.db.BoxContext;
import au.net.causal.maven.plugins.boxdb.db.BoxDatabase;
import au.net.causal.maven.plugins.boxdb.db.BoxDatabaseException;
import au.net.causal.maven.plugins.boxdb.db.DatabaseLog;
import au.net.causal.maven.plugins.boxdb.db.DatabaseStage;
import au.net.causal.maven.plugins.boxdb.db.DatabaseTarget;
import au.net.causal.maven.plugins.boxdb.db.DockerService;
import au.net.causal.maven.plugins.boxdb.db.JdbcConnectionInfo;
import au.net.causal.maven.plugins.boxdb.db.ProjectConfiguration;
import au.net.causal.maven.plugins.boxdb.db.ScriptExecution;
import au.net.causal.maven.plugins.boxdb.db.ScriptSelection;
import io.fabric8.maven.docker.access.DockerAccessException;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugins.annotations.LifecyclePhase;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Connection;
import java.sql.SQLException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
/**
* Starts a database. When this goal has finished executing, the database will have been started and set up.
*/
@Mojo(name="start", defaultPhase = LifecyclePhase.PRE_INTEGRATION_TEST, requiresProject = false)
public class StartMojo extends AbstractDatabaseMojo
{
/**
* SQL files to execute after the database has started up. These files are executed using
* database-native tool (e.g. psql on PostgreSQL).
*/
@Parameter(property = "db.sql")
private List sqlFiles = new ArrayList<>();
/**
* SQL files to execute after the database has started up as the database admin user. These files are executed using
* database-native tool (e.g. psql on PostgreSQL) before boxdb's database creation scripts.
*
* @since 3.0
*/
@Parameter(property = "db.setup.sql")
private List sqlSetupFiles = new ArrayList<>();
/**
* If true, don't trust the plain-socket database-is-up waiting mechanism and poll by attempting to make
* JDBC connections to the database. This should be turned on if basic socket connectivity cannot be trusted for
* containers, which may be the case for certain Docker implementations.
*
*
* As of version 3.0, JDBC waiting is turned on by default. Turning this off may increase performance of polling
* and result in faster database start-up times, but may also result in BoxDB not detecting when a database is
* up properly. Turn this off at your own risk.
*/
@Parameter(property = "db.jdbcWaiting", defaultValue = "true")
private boolean useJdbcWaiting;
/**
* Controls whether/how database logs are dumped to Maven output if the database fails to start.
* Valid options are OFF, MAIN and ALL.
*/
@Parameter(property = "db.dumpLogsOnError", defaultValue = "OFF", required = true)
private DumpLogsOnErrorOption dumpLogsOnError;
private void setupExtraExecutionsForSqlFiles()
{
if (!sqlFiles.isEmpty())
{
List sqlFileNames = sqlFiles.stream().map(File::getAbsolutePath).collect(Collectors.toList());
ScriptExecution execution = new ScriptExecution();
execution.setStage(DatabaseStage.SETUP);
execution.setDirectory(project.getBasedir());
execution.setIgnoreMissing(false);
execution.setFiltering(true);
execution.setSelection(ScriptSelection.ALL);
execution.setScripts(sqlFileNames);
box.getScriptExecutions().add(execution);
}
if (!sqlSetupFiles.isEmpty())
{
List sqlFileNames = sqlSetupFiles.stream().map(File::getAbsolutePath).collect(Collectors.toList());
ScriptExecution execution = new ScriptExecution();
execution.setStage(DatabaseStage.CREATE);
execution.setDirectory(project.getBasedir());
execution.setIgnoreMissing(false);
execution.setFiltering(true);
execution.setSelection(ScriptSelection.ALL);
execution.setScripts(sqlFileNames);
box.getScriptExecutions().add(execution);
}
}
private void waitForDatabaseStart(BoxDatabase database, Duration maxWaitTime, Duration pollTime)
throws TimeoutException, BoxDatabaseException
{
long startTime = System.currentTimeMillis();
long maxEndTime = startTime + maxWaitTime.toMillis();
database.waitUntilStarted(maxWaitTime);
if (useJdbcWaiting)
{
boolean connectionSucceeded = false;
try
{
while (!connectionSucceeded && System.currentTimeMillis() < maxEndTime)
{
try (Connection con = database.createJdbcConnection(DatabaseTarget.ADMIN))
{
//If we get a connection that's all we need, don't bother actually trying to use it
//Might try this later on, but require JDBC 4 driver which we can't guarantee
//con.isValid()
getLog().debug("Successfully connected to database: " + con);
connectionSucceeded = true;
}
catch (SQLException e)
{
if (database.databaseIsReady(e))
{
connectionSucceeded = true;
getLog().debug("Failed to connect, but database deemed it is ready: " + e, e);
}
else
{
//This might happen, ignore and loop again
getLog().debug("Loop waiting for database connectivity failed to get connection", e);
}
}
catch (IOException e)
{
//This might happen, ignore and loop again
getLog().debug("Loop waiting for database connectivity failed to get connection", e);
}
if (!connectionSucceeded)
Thread.sleep(pollTime.toMillis());
}
if (!connectionSucceeded)
throw new TimeoutException("Timeout waiting for successful connection");
} //while loop
catch (InterruptedException e)
{
throw new BoxDatabaseException("Interrupted waiting for database to start up.", e);
}
}
}
@Override
protected void executeInternal(ExceptionalSupplier dockerService)
throws DockerAccessException, MojoExecutionException
{
setupExtraExecutionsForSqlFiles();
Duration maxWaitTime = Duration.ofSeconds(startupTimeout);
Duration pollTime = Duration.ofMillis(pollTimeMillis);
try
{
BoxDatabase boxDatabase = database(dockerService);
if (!boxDatabase.exists())
{
getLog().info("Creating database '" + boxDatabase.getName() + "'");
try
{
boxDatabase.createAndStart();
waitForDatabaseStart(boxDatabase, maxWaitTime, pollTime);
}
catch (BoxDatabaseException | TimeoutException e)
{
try
{
dumpLogs(dumpLogsOnError, boxDatabase);
}
catch (IOException | BoxDatabaseException | RuntimeException ex)
{
getLog().error("Error dumping database logs on error: " + ex, ex);
}
throw e;
}
getLog().info("Database started.");
JdbcConnectionInfo jdbcInfo = boxDatabase.jdbcConnectionInfo(DatabaseTarget.USER);
getLog().info("JDBC URL: " + jdbcInfo.getUri() + " user=" + jdbcInfo.getUser() +
(displayPasswords ? " password=" + jdbcInfo.getPassword() : ""));
runScriptExecutions(box.scriptExecutions(DatabaseStage.CREATE), boxDatabase, dockerService);
if (box.isInitializeDatabase())
{
try
{
boxDatabase.configureNewDatabase();
}
catch (IOException | SQLException e)
{
throw new MojoExecutionException("Error setting up database '" + boxDatabase.getName() + "': " + e, e);
}
}
runScriptExecutions(box.scriptExecutions(DatabaseStage.SETUP), boxDatabase, dockerService);
if (box.getRestorePath() != null)
runDatabaseRestore(box.getRestorePath(), boxDatabase);
runScriptExecutions(box.scriptExecutions(DatabaseStage.POST_RESTORE), boxDatabase, dockerService);
}
else if (!boxDatabase.isRunning())
{
//Container exists but is not started - no need to run setup scripts here
getLog().info("Starting '" + boxDatabase.getName() + "'");
try
{
boxDatabase.start();
waitForDatabaseStart(boxDatabase, maxWaitTime, pollTime);
}
catch (BoxDatabaseException | TimeoutException e)
{
try
{
dumpLogs(dumpLogsOnError, boxDatabase);
}
catch (IOException | BoxDatabaseException ex)
{
getLog().error("Error dumping database logs on error: " + ex, ex);
}
throw e;
}
getLog().info("Database started.");
}
else
getLog().info("Database '" + boxDatabase.getName() + "' is already running");
//Put the JDBC info in properties
JdbcConnectionInfo userJdbcInfo = boxDatabase.jdbcConnectionInfo(DatabaseTarget.USER);
if (userJdbcInfo.getUri() != null)
project.getProperties().setProperty("boxdb.jdbc.url", userJdbcInfo.getUri());
if (userJdbcInfo.getUser() != null)
project.getProperties().setProperty("boxdb.jdbc.user", userJdbcInfo.getUser());
if (userJdbcInfo.getPassword() != null)
project.getProperties().setProperty("boxdb.jdbc.password", userJdbcInfo.getPassword());
JdbcConnectionInfo adminJdbcInfo = boxDatabase.jdbcConnectionInfo(DatabaseTarget.ADMIN);
if (adminJdbcInfo.getUri() != null)
project.getProperties().setProperty("boxdb.admin.jdbc.url", adminJdbcInfo.getUri());
if (adminJdbcInfo.getUser() != null)
project.getProperties().setProperty("boxdb.admin.jdbc.user", adminJdbcInfo.getUser());
if (adminJdbcInfo.getPassword() != null)
project.getProperties().setProperty("boxdb.admin.jdbc.password", adminJdbcInfo.getPassword());
}
catch (BoxDatabaseException e)
{
throw new MojoExecutionException(e.getMessage(), e);
}
catch (TimeoutException e)
{
throw new MojoExecutionException("Timeout waiting for database to start up", e);
}
}
private void dumpLogs(DumpLogsOnErrorOption dumpLogsOnError, BoxDatabase boxDatabase)
throws BoxDatabaseException, IOException
{
if (dumpLogsOnError != DumpLogsOnErrorOption.OFF)
getLog().error("Error occurred starting database. Dumping logs...");
else
getLog().error("Error occurred starting database. Use -Ddb.dumpLogsOnError=ALL or -Ddb.dumpLogsOnError=MAIN to dump database logs and get additional failure details.");
switch (dumpLogsOnError)
{
case OFF:
break;
case MAIN:
DatabaseLog mainLog = boxDatabase.bestLogFile();
if (mainLog != null)
dumpLogToError(mainLog);
break;
case ALL:
for (DatabaseLog curLog : boxDatabase.logFiles())
{
dumpLogToError(curLog);
}
break;
default:
throw new Error("Unknown DumpLogsOnErrorOption: " + dumpLogsOnError);
}
}
private void runDatabaseRestore(Path restoreFile, BoxDatabase boxDatabase)
throws MojoExecutionException
{
if (Files.notExists(restoreFile))
throw new MojoExecutionException("Database restore file " + restoreFile + " does not exist.");
try
{
boxDatabase.restore(restoreFile);
}
catch (IOException | BoxDatabaseException | SQLException e)
{
throw new MojoExecutionException("Failed to restore database from backup file " + restoreFile.toString() + ": " + e, e);
}
}
private void runScriptExecutions(Iterable extends ScriptExecution> scriptExecutions, BoxDatabase boxDatabase, ExceptionalSupplier dockerService)
throws MojoExecutionException
{
ProjectConfiguration projectConfiguration = projectConfiguration();
BoxContext context = boxContext(dockerService);
ScriptRunner scriptRunner = context.createScriptRunner(boxDatabase, box, projectConfiguration);
try
{
for (ScriptExecution scriptExecution : scriptExecutions)
{
try
{
scriptRunner.execute(scriptExecution, Duration.ofSeconds(scriptTimeout));
}
catch (IOException | SQLException | BoxDatabaseException e)
{
throw new MojoExecutionException("Error running SQL scripts: " + e, e);
}
}
}
finally
{
tempFilesCreated.addAll(scriptRunner.getTempFilesCreated());
}
}
}