/*
* Copyright (c) 2008-2014 EMC Corporation
* All Rights Reserved
*/
package com.emc.storageos.db.server.geo;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.emc.storageos.coordinator.client.service.impl.CoordinatorClientInetAddressMap;
import com.emc.storageos.coordinator.client.service.impl.DualInetAddress;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.context.support.FileSystemXmlApplicationContext;
import com.emc.storageos.db.server.DbService;
import com.emc.storageos.services.util.LoggingUtils;
import com.emc.storageos.coordinator.client.model.Constants;
import com.emc.storageos.coordinator.client.model.DbVersionInfo;
import com.emc.storageos.coordinator.client.service.CoordinatorClient;
import com.emc.storageos.coordinator.client.service.impl.CoordinatorClientImpl;
import com.emc.storageos.coordinator.common.Service;
import com.emc.storageos.coordinator.common.impl.ZkConnection;
import com.emc.storageos.coordinator.exceptions.RetryableCoordinatorException;
import com.emc.storageos.coordinator.service.Coordinator;
/**
* A runner to fork new java process for geo or local dbsvc.
*
* We could not run 2 Cassandra instances in single process, so we need the
* runner to start both local & geo dbsvc in a separated java process for
* dbclient testing
*
* @author liuh6
*/
public class DbSvcRunner {
static {
LoggingUtils.configureIfNecessary("geodbtest-log4j.properties");
initDefaultFiles();
}
private static final Logger log = LoggerFactory
.getLogger(DbSvcRunner.class);
protected static CoordinatorClientImpl coordinator;
protected static String DBSVC_CONFIG = "dbtest-conf.xml";
protected static String GEODBSVC_CONFIG = "geodbtest-conf.xml";
protected static String COORDINATORSVC_CONFIG = "coordinatortest-conf.xml";
protected static String DBSVC_MAIN = "com.emc.storageos.db.server.geo.DbSvcRunner";
protected static String SVC_VERSION = "2.2"; // version defined in
// db-var.xml or geodb-var.xml
protected static String GEOSVC_ID = "geodb-standalone"; // svc id defined in
// geodb-var.xml
protected static String DBSVC_ID = "db-standalone";
private static String GEODB_DIR = "geodbtest"; // db dir defined in
// geodb-test.yaml
private static String LOCALDB_DIR = "dbtest"; // db dir defined in
// db-test.yaml
private static String ZK_DIR = "/data/zk"; // zk data dir defined in
private File dataDir;
private String configFile;
private String serviceName;
private Process process;
public DbSvcRunner(String configFile, String serviceName) {
this.configFile = configFile;
this.serviceName = serviceName;
setupDataDir(serviceName);
}
/**
* Start the runner within the current running process
*/
public void startInProcess() {
final String[] args = new String[] { getFullConfigFilePath(configFile) };
log.info("Starting " + DBSVC_MAIN + " with config file : " + args[0]);
File dir = new File(GEODB_DIR);
if (dir.exists()) {
cleanDirectory(dir);
}
Thread inProcRunnerThread = new Thread(new Runnable() {
public void run() {
main(args);
log.info(DBSVC_MAIN + "Thread has stopped");
}
}, DBSVC_MAIN + "Thread");
inProcRunnerThread.start();
}
/**
* Start the runner
*/
public void start() {
startProcessThread(serviceName, DBSVC_MAIN, getFullConfigFilePath(configFile));
}
/**
* Stop the runner
*/
public void stop() {
if (process != null) {
process.destroy();
}
}
/**
* Start coordinatorsvc in new thread of current process
*
* @param args
*/
public void startCoordinator() {
File dir = new File(ZK_DIR);
if (dir.exists()) {
cleanDirectory(dir);
}
Thread coordThread = new Thread(new Runnable() {
public void run() {
try {
final String[] args = new String[] { "classpath:" + COORDINATORSVC_CONFIG };
log.info("Starting Coordinator with config file : {}", args[0]);
FileSystemXmlApplicationContext ctx = new FileSystemXmlApplicationContext(args);
Coordinator coordinator = (Coordinator) ctx.getBean("coordinatorsvc");
coordinator.start();
log.info("CoordinatorThread has stopped");
} catch (Exception ex) {
log.error("Coordinator startup error", ex);
}
}
}, "CoordinatorThread");
coordThread.start();
}
private String getFullConfigFilePath(String fileName) {
String confFile = Thread.currentThread().getContextClassLoader()
.getResource(fileName).getFile();
return confFile;
}
/**
* Start a thread to fork new java process
*
* @param threadName
* @param main
* @param conf
*/
private void startProcessThread(final String threadName, final String main,
final String conf) {
Thread procThread = new Thread(new Runnable() {
public void run() {
try {
startProcess(main, conf);
} catch (Exception e) {
log.error("exception starting process " + threadName, e);
}
}
}, threadName);
procThread.start();
}
/**
* Deletes given directory
*
* @param dir
*/
protected void cleanDirectory(File dir) {
for (File file : dir.listFiles()) {
if (file.isDirectory()) {
cleanDirectory(file);
} else {
file.delete();
}
}
dir.delete();
}
/**
* Setup db data directory
*
* @param name
*/
private void setupDataDir(String serviceName) {
String fileName = LOCALDB_DIR;
if (Constants.GEODBSVC_NAME.equals(serviceName)) {
fileName = GEODB_DIR;
}
dataDir = new File(fileName);
if (dataDir.exists() && dataDir.isDirectory()) {
cleanDirectory(dataDir);
}
dataDir.mkdir();
}
/**
* Fork a new java process to run dbsvc
*
* @param main
* @param conf
* @return
* @throws Exception
*/
private int startProcess(String main, String conf) throws Exception {
String classPath = System.getProperty("java.class.path");
ProcessBuilder processBuilder = new ProcessBuilder("java",
"-Djava.net.preferIPv4Stack=true", main, conf);
processBuilder.environment().put("CLASSPATH", classPath);
processBuilder.redirectErrorStream(true);
processBuilder.directory(dataDir);
log.info("Starting " + main + " with conf " + conf);
process = processBuilder.start();
Thread closeChildThread = new Thread() {
public void run() {
log.info("Stopping child process");
process.destroy();
}
};
Runtime.getRuntime().addShutdownHook(closeChildThread);
doWaitFor(process);
log.info("process " + main + " with conf " + conf + " stopping");
return process.exitValue();
}
/**
* Redirect stdout/stderr of given process to current log until process
* exits.
*
* @param process
*/
private void doWaitFor(Process process) {
startReadStreamThread(process.getInputStream());
startReadStreamThread(process.getErrorStream());
try {
process.waitFor();
} catch (InterruptedException e) {
log.warn("e=", e);
}
}
/**
* Start a new thread to read from process out/err stream and dump to logs
*
* @param is
*/
private void startReadStreamThread(final InputStream is) {
new Thread(Thread.currentThread().getName()) {
public void run() {
BufferedReader br = new BufferedReader(
new InputStreamReader(is));
try {
String line = null;
while ((line = br.readLine()) != null) {
log.info("{}", line);
}
} catch (IOException e) {
// log.error("e=", e);
} finally {
try {
br.close();
} catch (IOException e) {
// log.error("e=", e);
}
}
}
}.start();
}
/**
* Check if service is started
*
* @return
*/
public boolean isStarted() {
try {
CoordinatorClient coordinator = getCoordinator();
List<Service> service = coordinator.locateAllServices(serviceName,
SVC_VERSION, null, null);
if (service.iterator().hasNext()) {
Service svc = service.iterator().next();
URI hostUri = svc.getEndpoint();
log.info("Found " + svc.getName() + "; host = "
+ hostUri.getHost() + "; port = " + hostUri.getPort());
return true;
}
} catch (RetryableCoordinatorException e) {
log.warn(
"no {} instance running. Coordinator exception message: {}",
serviceName, e.getMessage());
} catch (Exception e) {
log.error("service lookup failure", e);
}
return false;
}
/**
* Wait until service started
*
* @param timeout
* max wait time in seconds
*/
public boolean waitUntilStarted(int timeout) {
int cnt = 0;
while (cnt < timeout) {
if (isStarted()) {
log.info("Dbsvc startup OK");
return true;
}
sleep(4);
cnt++;
}
return false;
}
public void sleep(int seconds) {
try {
Thread.sleep(1000 * seconds);
} catch (InterruptedException ex) {
// Ignore this exception.
}
}
/**
* Get CoordinatorClient instance
*
* @return
* @throws URISyntaxException
* @throws IOException
*/
public CoordinatorClient getCoordinator() throws URISyntaxException,
IOException {
if (coordinator == null) {
ZkConnection zkConn = new ZkConnection();
List<URI> uris = new ArrayList<URI>();
uris.add(new URI("coordinator://localhost:2181"));
zkConn.setServer(uris);
zkConn.setTimeoutMs(10000);
zkConn.build();
// Suppress Sonar violation of Lazy initialization of static fields should be synchronized
// Junit test will be called in single thread by default, it's safe to ignore this violation
coordinator = new CoordinatorClientImpl(); // NOSONAR ("squid:S2444")
coordinator.setZkConnection(zkConn);
coordinator.setSysSvcName("syssvc");
coordinator.setSysSvcVersion("1");
coordinator.setNodeCount(1);
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("nodeaddrmap-var.xml");
CoordinatorClientInetAddressMap inetAddressMap = (CoordinatorClientInetAddressMap) ctx.getBean("inetAddessLookupMap");
if (inetAddressMap == null) {
log.error("CoordinatorClientInetAddressMap is not initialized. Node address lookup will fail.");
}
Map<String, DualInetAddress> controlNodes = inetAddressMap.getControllerNodeIPLookupMap();
coordinator.setInetAddessLookupMap(inetAddressMap); // HARCODE FOR NOW
DbVersionInfo dbVersionInfo = new DbVersionInfo();
dbVersionInfo.setSchemaVersion(SVC_VERSION);
coordinator.setDbVersionInfo(dbVersionInfo);
coordinator.start();
}
return coordinator;
}
public static void main(String args[]) {
try {
String ctxFile = args[0];
FileSystemXmlApplicationContext ctx = new FileSystemXmlApplicationContext("file:" + ctxFile);
DbService dbsvc = (DbService) ctx.getBean("dbsvc");
dbsvc.start();
log.info("dbsvc is started");
while (true) {
;
}
} catch (Exception ex) {
log.error("Exception ", ex);
}
}
protected static void initDefaultFiles() {
File file = new File("/etc/config.defaults");
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
log.error("Exception: ", e);
}
}
File file2 = new File("/etc/ovfenv.properties");
if (!file2.exists()) {
try {
file2.createNewFile();
} catch (IOException e) {
log.error("Exception: ", e);
}
}
}
}