From ab2b41260e0664e76bad9a969a7da3958abcdd29 Mon Sep 17 00:00:00 2001 From: Andrew Woods Date: Wed, 26 Sep 2018 16:37:59 -0400 Subject: [PATCH] Add ElasticSmokeTest Resolves: https://jira.duraspace.org/browse/VIVO-1600 --- .gitignore | 1 + .../servlet/setup/ElasticSmokeTest.java | 128 ++++++++++++++++++ .../servlet/setup/SearchEngineSmokeTest.java | 48 +++++++ .../webapp/servlet/setup/SolrSmokeTest.java | 28 ++-- .../WEB-INF/resources/startup_listeners.txt | 4 +- 5 files changed, 193 insertions(+), 16 deletions(-) create mode 100644 api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/ElasticSmokeTest.java create mode 100644 api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/SearchEngineSmokeTest.java diff --git a/.gitignore b/.gitignore index ca5d34dc9..a3e48d48a 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,7 @@ utilities/sdb_to_tdb/.work *~ **/overlays +*~ # Eclipse artifacts **/.settings diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/ElasticSmokeTest.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/ElasticSmokeTest.java new file mode 100644 index 000000000..7dabca699 --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/ElasticSmokeTest.java @@ -0,0 +1,128 @@ +/* $This file is distributed under the terms of the license in LICENSE$ */ + +package edu.cornell.mannlib.vitro.webapp.servlet.setup; + +import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; +import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus; +import edu.cornell.mannlib.vitro.webapp.utils.http.HttpClientFactory; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpResponse; +import org.apache.http.HttpStatus; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.util.EntityUtils; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; + +/** + * If we can't connect to ElasticSearch, add a Warning item to the StartupStatus. + */ +public class ElasticSmokeTest { + + private static final Log log = LogFactory.getLog(ElasticSmokeTest.class); + + private final ServletContextListener listener; + + public ElasticSmokeTest(ServletContextListener listener) { + this.listener = listener; + } + + public void doTest(ServletContextEvent sce) { + final StartupStatus ss = StartupStatus.getBean(sce.getServletContext()); + + String elasticUrlString = ConfigurationProperties.getBean(sce).getProperty("vitro.local.elastic.url", ""); + if (elasticUrlString.isEmpty()) { + ss.fatal(listener, "Can't connect to ElasticSearch engine. " + + "runtime.properties must contain a value for " + + "vitro.local.elastic.url"); + return; + } + + URL elasticUrl = null; + + try { + elasticUrl = new URL(elasticUrlString); + } catch (MalformedURLException e) { + ss.fatal(listener, "Can't connect to ElasticSearch engine. " + + "The value for vitro.local.elastic.url " + + "in runtime.properties is not a valid URL: '" + + elasticUrlString + "'", e); + } + + ss.info(listener, "Starting ElasticSearch test."); + + checkConnection(elasticUrl, ss); + } + + private void checkConnection(URL elasticUrl, StartupStatus ss) { + try { + new ElasticPinger(elasticUrl).ping(); + reportGoodPing(ss); + } catch (ElasticProblemException e) { + reportPingProblem(ss, e); + } + } + + private void reportGoodPing(StartupStatus ss) { + ss.info(listener, "The ElasticSearch server responded to a 'ping'."); + } + + private void reportPingProblem(StartupStatus ss, ElasticProblemException e) { + ss.warning(listener, "The ElasticSearch engine did not respond to a 'ping' request", e); + } + + /** + * Issue a "ping" to ElasticSearch. If we get here, we've already established + * contact, so any error is a fatal one. + */ + private static class ElasticPinger { + private final URL elasticUrl; + private final HttpClient httpClient = HttpClientFactory.getHttpClient(); + + public ElasticPinger(URL elasticUrl) { + this.elasticUrl = elasticUrl; + } + + public void ping() throws ElasticProblemException { + try { + HttpGet method = new HttpGet(elasticUrl.toExternalForm()); + log.debug("Trying to ping ElasticSearch"); + HttpResponse response = httpClient.execute(method); + try { + log.debug("Finished pinging ElasticSearch"); + int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode != HttpStatus.SC_OK) { + throw new ElasticProblemException(statusCode); + } + } finally { + EntityUtils.consume(response.getEntity()); + } + } catch (IOException e) { + throw new ElasticProblemException(e); + } + } + } + + private static class ElasticProblemException extends Exception { + private final int statusCode; + + ElasticProblemException(int statusCode) { + super("HTTP status code = " + statusCode); + this.statusCode = statusCode; + } + + ElasticProblemException(Throwable cause) { + super(cause); + this.statusCode = 0; + } + + int getStatusCode() { + return this.statusCode; + } + } +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/SearchEngineSmokeTest.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/SearchEngineSmokeTest.java new file mode 100644 index 000000000..8c6267a43 --- /dev/null +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/SearchEngineSmokeTest.java @@ -0,0 +1,48 @@ +/* $This file is distributed under the terms of the license in LICENSE$ */ + +package edu.cornell.mannlib.vitro.webapp.servlet.setup; + +import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties; +import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import javax.servlet.ServletContextEvent; +import javax.servlet.ServletContextListener; + +/** + * Start up the appropriate search engine smoke test based on the configured URL property. + */ +public class SearchEngineSmokeTest implements ServletContextListener { + + private static final Log log = LogFactory.getLog(SearchEngineSmokeTest.class); + + @Override + public void contextInitialized(ServletContextEvent sce) { + final StartupStatus ss = StartupStatus.getBean(sce.getServletContext()); + + String solrUrlString = ConfigurationProperties.getBean(sce).getProperty("vitro.local.solr.url", ""); + String elasticUrlString = ConfigurationProperties.getBean(sce).getProperty("vitro.local.elastic.url", ""); + + if (!solrUrlString.isEmpty() && !elasticUrlString.isEmpty()) { + ss.fatal(this, "More than one search engine is configured: " + solrUrlString + ", and " + elasticUrlString); + + } else if (solrUrlString.isEmpty() && elasticUrlString.isEmpty()) { + ss.fatal(this, "No search engine is configured"); + + } else if (!solrUrlString.isEmpty()) { + log.debug("Initializing Solr: " + solrUrlString); + new SolrSmokeTest(this).doTest(sce); + + } else { + log.debug("Initializing ElasticSearch: " + elasticUrlString); + new ElasticSmokeTest(this).doTest(sce); + } + } + + @Override + public void contextDestroyed(ServletContextEvent sce) { + // nothing to tear down. + } + +} diff --git a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/SolrSmokeTest.java b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/SolrSmokeTest.java index dad23248d..c75b0ad2b 100644 --- a/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/SolrSmokeTest.java +++ b/api/src/main/java/edu/cornell/mannlib/vitro/webapp/servlet/setup/SolrSmokeTest.java @@ -35,23 +35,28 @@ import org.apache.http.util.EntityUtils; * * If we can't connect to Solr, add a Warning item to the StartupStatus. */ -public class SolrSmokeTest implements ServletContextListener { +public class SolrSmokeTest { private static final Log log = LogFactory.getLog(SolrSmokeTest.class); + private final ServletContextListener listener; + /* * We don't want to treat socket timeout as a non-recoverable error like the * other exceptions. So pretend there's a status code for it instead. */ private static final int SOCKET_TIMEOUT_STATUS = -500; - @Override - public void contextInitialized(ServletContextEvent sce) { + public SolrSmokeTest(ServletContextListener listener) { + this.listener = listener; + } + + public void doTest(ServletContextEvent sce) { final StartupStatus ss = StartupStatus.getBean(sce.getServletContext()); String solrUrlString = ConfigurationProperties.getBean(sce) .getProperty("vitro.local.solr.url", ""); if (solrUrlString.isEmpty()) { - ss.fatal(this, "Can't connect to Solr search engine. " + ss.fatal(listener, "Can't connect to Solr search engine. " + "runtime.properties must contain a value for " + "vitro.local.solr.url"); return; @@ -62,27 +67,22 @@ public class SolrSmokeTest implements ServletContextListener { try { solrUrl = new URL(solrUrlString); } catch (MalformedURLException e) { - ss.fatal(this, "Can't connect to Solr search engine. " + ss.fatal(listener, "Can't connect to Solr search engine. " + "The value for vitro.local.solr.url " + "in runtime.properties is not a valid URL: '" + solrUrlString + "'", e); } - ss.info(this, "Starting thread for Solr test."); - new SolrSmokeTestThread(this, solrUrl, ss).start(); - } - - @Override - public void contextDestroyed(ServletContextEvent sce) { - // nothing to tear down. + ss.info(listener, "Starting thread for Solr test."); + new SolrSmokeTestThread(listener, solrUrl, ss).start(); } private static class SolrSmokeTestThread extends VitroBackgroundThread { - private final SolrSmokeTest listener; + private final ServletContextListener listener; private final URL solrUrl; private final StartupStatus ss; - public SolrSmokeTestThread(SolrSmokeTest listener, URL solrUrl, + public SolrSmokeTestThread(ServletContextListener listener, URL solrUrl, StartupStatus ss) { super("SolrSmokeTest"); this.listener = listener; diff --git a/webapp/src/main/webapp/WEB-INF/resources/startup_listeners.txt b/webapp/src/main/webapp/WEB-INF/resources/startup_listeners.txt index 90bbfa468..2f04ebfef 100644 --- a/webapp/src/main/webapp/WEB-INF/resources/startup_listeners.txt +++ b/webapp/src/main/webapp/WEB-INF/resources/startup_listeners.txt @@ -67,5 +67,5 @@ org.apache.commons.fileupload.servlet.FileCleanerCleanup # and the PermissionRegistry must already be set up. edu.cornell.mannlib.vitro.webapp.dao.jena.VClassGroupCache$Setup -# This should be near the end, because it will issue a warning if the connection to Solr times out. -edu.cornell.mannlib.vitro.webapp.servlet.setup.SolrSmokeTest +# This should be near the end, because it will issue a warning if the connection to Solr or ElasticSearch times out. +edu.cornell.mannlib.vitro.webapp.servlet.setup.SearchEngineSmokeTest