/*
* Copyright 2010 Outerthought bvba
*
* 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 org.lilyproject.lilyservertestfw;
import javax.management.ObjectName;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import java.io.File;
import java.io.FilenameFilter;
import java.io.IOException;
import java.util.Collections;
import com.ngdata.hbaseindexer.Main;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.filefilter.PrefixFileFilter;
import org.lilyproject.hadooptestfw.HBaseProxy;
import org.lilyproject.lilyservertestfw.launcher.HbaseIndexerLauncherService;
import org.lilyproject.solrtestfw.SolrDefinition;
import org.lilyproject.solrtestfw.SolrProxy;
import org.lilyproject.util.io.Closer;
import org.lilyproject.util.test.TestHomeUtil;
public class LilyProxy {
private static final String TEMP_DIR_PREFIX = "lily-proxy-";
private HBaseProxy hbaseProxy;
private LilyServerProxy lilyServerProxy;
private SolrProxy solrProxy;
private HbaseIndexerLauncherService hbaseIndexerLauncherService;
private Mode mode;
private File testHome;
private boolean started = false;
private boolean hasBeenStarted = false;
private boolean clearData = true;
public enum Mode {EMBED, CONNECT, HADOOP_CONNECT}
public static String MODE_PROP_NAME = "lily.lilyproxy.mode";
public static String TESTHOME_PROP_NAME = "lily.lilyproxy.dir";
public static String CLEARDATA_PROP_NAME = "lily.lilyproxy.clear";
public static String RESTORE_TEMPLATE_DIR_PROP_NAME = "lily.lilyproxy.restoretemplatedir";
public LilyProxy() throws IOException {
this(null);
}
public LilyProxy(Mode mode) throws IOException {
this(mode, null, null);
}
public LilyProxy(Mode mode, File testHome, Boolean clearData) throws IOException{
this(mode, testHome, clearData, Boolean.FALSE);
}
/**
* Creates a new LilyProxy
*
* @param mode either EMBED, CONNECT or HADOOP_CONNECT
* @param testHome the directory in which to store data and logfiles. Can only be used in EMBED mode.
* @param clearData if true, clear the data when stopping the LilyProxy.
* Should be used together with the testHome parameter and can only be used in EMBED mode.
* @param enableSolrCloud if true, solr will be started in cloud mode
* @throws IOException
*/
public LilyProxy(Mode mode, File testHome, Boolean clearData, Boolean enableSolrCloud) throws IOException {
if (mode == null) {
String modeProp = System.getProperty(MODE_PROP_NAME);
if (modeProp == null || modeProp.equals("") || modeProp.equals("embed")) {
this.mode = Mode.EMBED;
} else if (modeProp.equals("connect")) {
this.mode = Mode.CONNECT;
} else if (modeProp.equals("hadoop-connect")) {
this.mode = Mode.HADOOP_CONNECT;
} else {
throw new RuntimeException("Unexpected value for " + MODE_PROP_NAME + ": " + modeProp);
}
} else {
this.mode = mode;
}
if (testHome != null) {
setTestHome(testHome);
} else {
String testHomeProp = System.getProperty(TESTHOME_PROP_NAME);
if (testHomeProp != null) {
setTestHome(new File(testHomeProp));
}
}
if (clearData != null) {
this.clearData = clearData;
} else {
this.clearData = Boolean.parseBoolean(System.getProperty(CLEARDATA_PROP_NAME, "true"));
}
HBaseProxy.Mode hbaseMode;
SolrProxy.Mode solrMode;
LilyServerProxy.Mode lilyServerMode;
// LilyProxy imposes its mode on all of the specific Proxy's. This is because the special resetLilyState
// operation in case of connect requires they are all in the same mode.
// The special HADOOP_CONNECT mode is mainly intended for Lily's own tests: many tests suppose only
// hadoop is running externally because they launch specific parts of the Lily implementation themselves,
// combined with the fact that the mode should be the same for all tests.
switch (this.mode) {
case EMBED:
hbaseMode = HBaseProxy.Mode.EMBED;
solrMode = SolrProxy.Mode.EMBED;
lilyServerMode = LilyServerProxy.Mode.EMBED;
break;
case CONNECT:
hbaseMode = HBaseProxy.Mode.CONNECT;
solrMode = SolrProxy.Mode.CONNECT;
lilyServerMode = LilyServerProxy.Mode.CONNECT;
break;
case HADOOP_CONNECT:
hbaseMode = HBaseProxy.Mode.CONNECT;
solrMode = SolrProxy.Mode.EMBED;
lilyServerMode = LilyServerProxy.Mode.EMBED;
break;
default:
throw new RuntimeException("Unexpected mode: " + this.mode);
}
hbaseProxy = new HBaseProxy(hbaseMode, this.clearData);
if (this.mode == Mode.CONNECT) {
// we'll do the reset through the special JMX call
hbaseProxy.setCleanStateOnConnect(false);
}
hbaseProxy.setEnableMapReduce(true);
solrProxy = new SolrProxy(solrMode, this.clearData, enableSolrCloud);
lilyServerProxy = new LilyServerProxy(lilyServerMode, this.clearData, hbaseProxy);
hbaseIndexerLauncherService = new HbaseIndexerLauncherService();
}
public Mode getMode() {
return mode;
}
public void start() throws Exception {
start(null, null);
}
public void start(byte[] solrSchemaData) throws Exception {
start(solrSchemaData, null);
}
public void start(byte[] solrSchemaData, byte[] solrConfigData) throws Exception {
if (solrSchemaData != null || solrConfigData != null) {
start(new SolrDefinition(solrSchemaData, solrConfigData));
} else {
start((SolrDefinition) null);
}
}
public void start(SolrDefinition solrDef) throws Exception {
if (started) {
throw new IllegalStateException("LilyProxy is already started.");
} else {
started = true;
}
cleanOldTmpDirs();
if (hasBeenStarted && this.mode == Mode.EMBED) {
// In embed mode, we can't support multiple start-stop sequences since
// HBase/Hadoop does not shut down all processes synchronously.
throw new IllegalStateException("LilyProxy can only be started once in a JVM when using embed mode.");
} else {
hasBeenStarted = true;
}
System.out.println("LilyProxy mode: " + mode);
if (mode == Mode.CONNECT) {
// First reset the state
System.out.println("Calling reset state flag on externally launched Lily...");
try {
String hostport = "localhost:10102";
JMXServiceURL url =
new JMXServiceURL("service:jmx:rmi://" + hostport + "/jndi/rmi://" + hostport + "/jmxrmi");
JMXConnector connector = JMXConnectorFactory.connect(url);
connector.connect();
ObjectName lilyLauncher = new ObjectName("LilyLauncher:name=Launcher");
connector.getMBeanServerConnection()
.invoke(lilyLauncher, "resetLilyState", new Object[0], new String[0]);
connector.close();
} catch (Exception e) {
throw new Exception("Resetting Lily state failed.", e);
}
System.out.println("State reset done.");
}
if (mode == Mode.EMBED || mode == Mode.HADOOP_CONNECT) {
if (testHome == null) {
testHome = TestHomeUtil.createTestHome(TEMP_DIR_PREFIX);
}
if (mode == Mode.EMBED) {
hbaseProxy.setTestHome(new File(testHome, TemplateDir.HADOOP_DIR));
}
solrProxy.setTestHome(new File(testHome, TemplateDir.SOLR_DIR));
lilyServerProxy.setTestHome(new File(testHome, TemplateDir.LILYSERVER_DIR));
}
if (mode == Mode.EMBED && Boolean.parseBoolean(System.getProperty(RESTORE_TEMPLATE_DIR_PROP_NAME, "true"))) {
TemplateDir.restoreTemplateDir(testHome);
}
hbaseProxy.start();
solrProxy.start(solrDef);
lilyServerProxy.start();
hbaseIndexerLauncherService.start(Collections.<String>emptyList());
}
public void stop() throws Exception {
Closer.close(hbaseIndexerLauncherService);
Closer.close(lilyServerProxy);
Closer.close(solrProxy);
Closer.close(hbaseProxy);
if (clearData && testHome != null) {
try {
FileUtils.deleteDirectory(testHome);
} catch (IOException e) {
// We're logging this instead of throwing the exception
// since deleting the folder often fails in Windows.
// Throwing the exception would also fail the testcase although
// it is only the cleanup of the temporary folder that failed.
System.out.println(
"Warning! LilyProxy.stop() failed to delete folder: " + testHome.getAbsolutePath() + ", " +
e.getMessage());
}
}
started = false;
}
public void setTestHome(File testHome) throws IOException {
if (mode != Mode.EMBED) {
throw new RuntimeException("testHome should only be set when mode is EMBED");
}
this.testHome = testHome;
System.setProperty("test.build.data", testHome.getAbsolutePath());
}
public File getTestHome() {
return testHome;
}
public void cleanOldTmpDirs() {
if (clearData) {
File tempDirectory = FileUtils.getTempDirectory();
File[] files = tempDirectory.listFiles((FilenameFilter) new PrefixFileFilter(TEMP_DIR_PREFIX));
for (File file : files) {
FileUtils.deleteQuietly(file);
}
}
}
public HBaseProxy getHBaseProxy() {
return hbaseProxy;
}
public LilyServerProxy getLilyServerProxy() {
return lilyServerProxy;
}
public SolrProxy getSolrProxy() {
return solrProxy;
}
/**
* Waits for all SEP events to be processed and if successful commits the solr index.
*
* <p>The canonical usage in tests is along these lines:</p>
*
* <pre>Assert.assertTrue("Processing events took too long", lilyProxy.waitSepEventsProcessed(60000L));</pre>
*
* @param timeout the maximum time to wait
* @return false if the timeout was reached before all events were processed
*/
public boolean waitSepEventsProcessed(long timeout) throws Exception {
return waitSepEventsProcessed(timeout, true);
}
/**
* Waits for all SEP events to be processed and optionally commits the solr index.
*
* @param timeout the maximum time to wait
* @return false if the timeout was reached before all events were processed
*/
public boolean waitSepEventsProcessed(long timeout, boolean commitSolr) throws Exception {
boolean success = hbaseProxy.waitOnReplication(timeout);
if (success && commitSolr) {
solrProxy.commit();
}
return success;
}
public Main getHBaseIndexerService() {
return hbaseIndexerLauncherService.getHbaseIndexerService();
}
}