/* This file is part of VoltDB.
* Copyright (C) 2008-2017 VoltDB Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with VoltDB. If not, see <http://www.gnu.org/licenses/>.
*/
package org.voltdb;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.voltdb.client.Client;
import org.voltdb.client.ClientConfig;
import org.voltdb.client.ClientFactory;
import org.voltdb.compiler.DeploymentBuilder;
import org.voltdb.utils.SQLCommand;
/**
* Class used to run a single VoltDB server in-process for debug and test
* purposes.
*
*/
public class InProcessVoltDBServer {
ServerThread server = null;
String pathToLicense = null;
Client loaderClient = null;
int sitesPerHost = 8; // default
List<Client> trackedClients = new ArrayList<>();
/**
* Create an instance ready to start.
*/
public InProcessVoltDBServer() {}
/**
* Set the number of partitions the single VoltDB server will use.
* The default is 8. Must be called before {@link #start()}.
* @param partitionCount The number of partitions desired.
* @return InProcessVoltDBServer instance for chaining.
*/
public InProcessVoltDBServer configPartitionCount(int partitionCount) {
sitesPerHost = partitionCount;
return this;
}
/**
* When using enterprise or pro edition, specify a path to the license needed.
* @param path Path to license. Must be called before {@link #start()}.
* @return InProcessVoltDBServer instance for chaining.
*/
public InProcessVoltDBServer configPathToLicense(String path) {
pathToLicense = path;
return this;
}
/**
* Starts the in-process server and blocks until it is ready to accept
* connections.
* @return InProcessVoltDBServer instance for chaining.
*/
public InProcessVoltDBServer start() {
DeploymentBuilder depBuilder = new DeploymentBuilder(sitesPerHost, 1, 0);
depBuilder.setEnableCommandLogging(false);
depBuilder.setUseDDLSchema(true);
depBuilder.setHTTPDPort(8080);
depBuilder.setJSONAPIEnabled(true);
VoltDB.Configuration config = new VoltDB.Configuration();
if (pathToLicense != null) {
config.m_pathToLicense = pathToLicense;
}
else {
config.m_pathToLicense = "./license.xml";
}
File tempDeployment = null;
try {
tempDeployment = File.createTempFile("volt_deployment_", ".xml");
}
catch (IOException e) {
e.printStackTrace();
System.exit(-1);
}
depBuilder.writeXML(tempDeployment.getAbsolutePath());
config.m_pathToDeployment = tempDeployment.getAbsolutePath();
server = new ServerThread(config);
server.start();
server.waitForInitialization();
return this;
}
/**
* Run DDL from a file on disk (integrally uses in-process sqlcommand).
* Must be called after {@link #start()}.
* @param path Path to DDL file.
* @return InProcessVoltDBServer instance for chaining.
*/
public InProcessVoltDBServer runDDLFromPath(String path) {
int ret = SQLCommand.mainWithReturnCode(new String[] { String.format("--ddl-file=%s", path) });
assert(ret == 0);
return this;
}
/**
* Run DDL from a given string (integrally uses in-process sqlcommand).
* Must be called after {@link #start()}.
* @param ddl String containing DDL to run.
* @return InProcessVoltDBServer instance for chaining.
*/
public InProcessVoltDBServer runDDLFromString(String ddl) {
try {
File tempDDLFile = File.createTempFile("volt_ddl_", ".sql");
BufferedWriter writer = new BufferedWriter(new FileWriter(tempDDLFile));
writer.write(ddl + "\n");
writer.close();
runDDLFromPath(tempDDLFile.getAbsolutePath());
}
catch (Exception e) {
e.printStackTrace();
System.exit(-1);
}
return this;
}
/**
* Stop the in-process server and block until it has completely stopped.
* Obviously must be called after {@link #start()}.
*/
public void shutdown() {
for (Client client : trackedClients) {
// best effort closing -- ignores many problems
try {
client.drain();
client.close();
}
catch (Exception e) {}
}
loaderClient = null;
try {
server.shutdown();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
server.join(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* Create and connect a client to the in-process VoltDB server.
* Note, client will be automatically closed when the in-process server
* is shut down.
* Must be called after {@link #start()}.
* @return Connected client.
* @throws Exception on failure to connect properly.
*/
public Client getClient() throws Exception {
ClientConfig config = new ClientConfig();
// turn off the timeout for debugging
config.setProcedureCallTimeout(0);
Client client = ClientFactory.createClient(config);
// track this client so it can be closed at shutdown
trackedClients.add(client);
client.createConnection("localhost");
return client;
}
/**
* Helper method for loading a row into a table.
* Must be called after {@link #start()} and after {@link #runDDLFromPath(String)} or {@link #runDDLFromString(String)}.
* @param tableName The case-insensitive name of the target table.
* @param row An array of schema-compatible values comprising the row to load.
* @throws Exception if the server is unable to complete a transaction of if the input doesn't match table schema.
*/
public void loadRow(String tableName, Object... row) throws Exception {
if (loaderClient == null) {
loaderClient = getClient();
}
String procName = tableName.trim().toUpperCase() + ".insert";
loaderClient.callProcedure(procName, row);
}
}