![JAR search and dependency download from the Maven repository](/logo.png)
io.github.melozzola.crdb.process.Cockroach Maven / Gradle / Ivy
/**
* Copyright 2017 Silvano Riz
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.github.melozzola.crdb.process;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.file.Path;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static io.github.melozzola.crdb.utils.Utils.*;
/**
* CockroachDb process. This class is responsible for
*
* - Allowing pre-configuration of cockroach (startup flags). See {@link Builder} and {@link #builder()}
* - Installing the binaries from web or classpath (if needed).
* - Run the CockroachDb process and wait for the database to be available. ( See {@link #startUp()} )
* - Shut down the process and clean up temporary folders and resources. ( See {@link #shutDown()} )
*
*/
public class Cockroach {
private String version = "v1.1.7";
private boolean cleanUpDataFolder = true;
private String executable;
private int startupWaitTimeMs = 10000;// 10 secs
private int shutDownWaitingTimeMs = 10000;// 10 secs
private Appendable stdErr = System.out;
private Appendable stdOut = System.err;
private boolean redirectStdErr = false;
private boolean redirectStdOut = false;
private Path workFolder;
private ProcessDetails processDetails;
private Process crdb;
private Flags flags = new Flags();
private final AtomicInteger status = new AtomicInteger(0); //0=not started, 1=started, 2=stopped
private Cockroach(){}
public static class Builder {
private Cockroach cockroach = new Cockroach();
/**
* Specifies the cockroach db host. By default is 'localhost'.
*
* @param host The cockroach db host
* @return The builder.
*/
public Builder host(final String host){
cockroach.flags.setHost(host);
return this;
}
/**
*
Specifies the port cockroach db will listen to. By default is randomly generated.
*
* @param port The database port
* @return The builder.
*/
public Builder port(final int port) {
cockroach.flags.setPort(port);
return this;
}
/**
*
Switches on secure connection.
*
* @return The builder.
*/
public Builder secure() {
cockroach.flags.setInsecure(false);
return this;
}
/**
*
Where cockrach db will store the data. By default in a temporary folder and it is deleted after the test.
*
* @param location The data folder location.
* @param cleanUpDataFolder if the folder needs to be deleted at the end of the test.
* @return The builder.
*/
public Builder dataFolder(final String location, final boolean cleanUpDataFolder) {
cockroach.flags.setStore(location);
cockroach.cleanUpDataFolder = cleanUpDataFolder;
return this;
}
/**
*
Sets the http port for the UI. By default is randomly generated.
*
* @param httpPort The http port for the UI.
* @return The builder.
*/
public Builder httpPort(final int httpPort) {
cockroach.flags.setHttpPort(httpPort);
return this;
}
/**
*
Sets the executable location. By default the rule will download the executable into a temporary folder.
*
* @param executable The path to the executable.
* @return The builder.
*/
public Builder executable(final String executable){
cockroach.executable = executable;
return this;
}
/**
*
How long (milliseconds) to wait for cockroach db to start up. By default is 10 seconds.
*
* @param startupWaitTimeMs How many milliseconds to wait.
* @return The builder.
*/
public Builder startupWaitTime(final int startupWaitTimeMs) {
cockroach.startupWaitTimeMs = startupWaitTimeMs;
return this;
}
/**
*
How long (milliseconds) to wait for cockroach db to stop. By default is 10 seconds.
*
* @param shutDownWaitingTimeMs How many milliseconds to wait.
* @return The builder.
*/
public Builder shutDownWaitingTime(final int shutDownWaitingTimeMs) {
cockroach.shutDownWaitingTimeMs = shutDownWaitingTimeMs;
return this;
}
/**
*
What cockroach db version to use. By default is 1.1.7.
*
* @param version The version to use
* @return The builder.
*/
public Builder version(String version){
cockroach.version = version;
return this;
}
/**
*
Where to redirect the cockroach db std error. By default is disabled.
*
* @param stdErr The redirect destination.
* @return The builder.
*/
public Builder stdErr(final Appendable stdErr){
cockroach.redirectStdErr = true;
cockroach.stdErr = stdErr;
return this;
}
/**
*
Where to redirect the cockroach db std output. By default is disabled.
*
* @param stdOut The redirect destination.
* @return The builder.
*/
public Builder stdOut(final Appendable stdOut){
cockroach.redirectStdOut = true;
cockroach.stdOut = stdOut;
return this;
}
/**
*
Enables the std out redirection. Cockroach db std out will be printed to {@code System.out}.
*
* @return The builder.
*/
public Builder redirectStdOut(){
cockroach.redirectStdOut = true;
return this;
}
/**
*
Enables the std err redirection. Cockroach db std out will be printed to {@code System.err}.
*
* @return The builder.
*/
public Builder redirectStdErr(){
cockroach.redirectStdErr = true;
return this;
}
/**
*
Builds a {@link Cockroach} with the specified configuration.
*
* @return the {@link Cockroach} rule.
*/
public Cockroach build(){
setDefaultsIfNeeded();
return cockroach;
}
// Sets defaults
private void setDefaultsIfNeeded(){
if (cockroach.executable == null) {
cockroach.executable = installBinariesIfNeeded(cockroach.version);
}
cockroach.workFolder = createTemporaryDataFolderIn(System.getProperty("java.io.tmpdir"));
cockroach.flags.setPidFile(cockroach.workFolder.resolve("pid.txt"));
cockroach.flags.setListeningUrlFile(cockroach.workFolder.resolve("url.txt"));
}
}
/**
*
Static method that returns a builder that allows to configure the process.
*
* @return The builder.
*/
public static Builder builder() {
return new Builder();
}
/**
*
Starts up the process. This method can be called only once otherwise it will throw an {@link IllegalStateException}.
*
* @return The process details like the pid, host, port and url of the cockroach db.
*/
public ProcessDetails startUp(){
if (status.compareAndSet(0, 1)) {
final String command = executable + " start" + flags.getFlags();
crdb = runOrThrow(command);
if (redirectStdOut) {
new Thread(new StreamReader(crdb.getInputStream(), stdOut)).start();
}
if (redirectStdErr) {
new Thread(new StreamReader(crdb.getErrorStream(), stdErr)).start();
}
processDetails = waitForStartup(flags.getPidFile(), flags.getListeningUrlFile(), startupWaitTimeMs);
return processDetails;
}else {
throw new IllegalStateException("Invalid status: " + status.get());
}
}
/**
*
Shuts down the process. It must be called after the {@link #startUp} otherwise it will throw an {@link IllegalStateException}.
*/
public void shutDown(){
if (status.compareAndSet(1, 2)) {
if (crdb == null){
return;
}
try {
killProcessOrThrow();
}catch (Exception e){
throw new IllegalStateException("failed to shut down cockroach db process. Pid: " + processDetails.pid + ", Url: " + processDetails.url, e);
}finally {
if (cleanUpDataFolder) {
recursiveDelete(workFolder);
}
}
}else {
throw new IllegalStateException("Invalid status. Status: " + status.get());
}
}
private void killProcessOrThrow() throws Exception {
crdb.destroyForcibly();
boolean exited = crdb.waitFor(shutDownWaitingTimeMs, TimeUnit.MILLISECONDS);
if (!exited){
throw new IllegalStateException("Cockroach db process failed to stop within the " + shutDownWaitingTimeMs + " ms timeout. Pid: " + processDetails.pid);
}
}
/**
*
Run the process or throw an {@link IllegalStateException} if it fails.
*
* @param command The cockroach db command.
* @return The started process.
*/
static Process runOrThrow(final String command){
try {
return Runtime.getRuntime().exec(command);
} catch (Exception e) {
throw new IllegalStateException("Failed to start the process " + command, e);
}
}
/**
*
{@link Runnable} that consumes a stream and pipes the data to an {@link Appendable}.
* Used to output the std out and err of the cockroach process to a different place.
*
Each line will be prefixed with 'crdb>'.
*/
private static class StreamReader implements Runnable {
private static final String PREFIX = "crdb> ";
private final BufferedReader in;
private final Appendable out;
private StreamReader(final InputStream in, final Appendable out) {
this.in = new BufferedReader(new InputStreamReader(in));
this.out = out;
}
@Override
public void run() {
while (true) {
try {
final String line = in.readLine();
if (line != null) {
out.append(PREFIX).append(line).append("\n");
}
} catch (Exception e) {
break;
}
}
}
}
}