/** Copyright (C) SYSTAP, LLC DBA Blazegraph 2013. All rights reserved. Contact: SYSTAP, LLC DBA Blazegraph 2501 Calvert ST NW #106 Washington, DC 20008 licenses@blazegraph.com This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; version 2 of the License. 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 General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.bigdata.rdf.sail.webapp.health; import java.net.MalformedURLException; import java.net.URL; import java.util.LinkedHashMap; import java.util.Map; import java.util.Properties; import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import junit.framework.AssertionFailedError; import junit.framework.Test; import junit.framework.TestCase2; import junit.framework.TestListener; import junit.framework.TestResult; import junit.framework.TestSuite; import junit.textui.ResultPrinter; import org.eclipse.jetty.client.HttpClient; import org.eclipse.jetty.server.Server; import com.bigdata.BigdataStatics; import com.bigdata.journal.BufferMode; import com.bigdata.journal.IIndexManager; import com.bigdata.journal.Journal; import com.bigdata.journal.Journal.Options; import com.bigdata.rdf.sail.CreateKBTask; import com.bigdata.rdf.sail.DestroyKBTask; import com.bigdata.rdf.sail.webapp.ConfigParams; import com.bigdata.rdf.sail.webapp.DatasetNotFoundException; import com.bigdata.rdf.sail.webapp.NanoSparqlServer; import com.bigdata.rdf.sail.webapp.client.ConnectOptions; import com.bigdata.rdf.sail.webapp.client.HttpClientConfigurator; import com.bigdata.rdf.sail.webapp.client.HttpException; import com.bigdata.rdf.sail.webapp.client.JettyResponseListener; import com.bigdata.rdf.sail.webapp.client.RemoteRepository; import com.bigdata.rdf.sail.webapp.client.RemoteRepositoryManager; import com.bigdata.rdf.task.AbstractApiTask; import com.bigdata.util.DaemonThreadFactory; import com.bigdata.util.InnerCause; import com.bigdata.util.config.NicUtil; /** * Utility test suite provides a health check for a deployed instance. * * @author <a href="mailto:thompsonbry@users.sourceforge.net">Bryan Thompson</a> */ public class TestNSSHealthCheck extends TestCase2 { /** * A marker placed into index.html so we can recognize when that page is * served. */ private static final String JUNIT_TEST_MARKER_INDEX_HTML = "junit test marker: index.html"; /** * The executor used by the http client. */ private ExecutorService executorService; /** * The http client. */ protected HttpClient m_client; /** * The client-API wrapper to the NSS. */ protected RemoteRepositoryManager m_repo; /** * The effective {@link NanoSparqlServer} http end point (including the * ContextPath). * * <pre> * http://localhost:9999/bigdata -- webapp URL (includes "/bigdata" context path. * </pre> */ protected String m_serviceURL; /** * The URL of the root of the web application server. This does NOT include * the ContextPath for the webapp. * * <pre> * http://localhost:9999 -- root URL * </pre> */ protected String m_rootURL; public TestNSSHealthCheck(final String name) {// , final String requestURI) // { super(name); // m_requestURI = requestURI; } protected Server m_fixture; protected String m_namespace; private Journal m_indexManager; /** * Setup a random namespace for each test. */ @Override protected void setUp() throws Exception { super.setUp(); // m_rootURL = requestURI; m_namespace = getName() + UUID.randomUUID(); m_fixture = newFixture(); final int port = NanoSparqlServer.getLocalPort(m_fixture); // log.info("Getting host address"); final String hostAddr = NicUtil.getIpAddress("default.nic", "default", true/* loopbackOk */); if (hostAddr == null) { fail("Could not identify network address for this host."); } m_rootURL = new URL("http", hostAddr, port, ""/* contextPath */ ).toExternalForm(); m_serviceURL = new URL("http", hostAddr, port, BigdataStatics.getContextPath()).toExternalForm(); // m_cm = DefaultClientConnectionManagerFactory.getInstance() // .newInstance(); /* * Ensure that the client follows redirects using a standard policy. * * Note: This is necessary for tests of the webapp structure since the * container may respond with a redirect (302) to the location of the * webapp when the client requests the root URL. */ executorService = Executors.newCachedThreadPool(DaemonThreadFactory .defaultThreadFactory()); m_client = HttpClientConfigurator.getInstance().newInstance(); m_repo = new RemoteRepositoryManager(m_serviceURL, m_client, executorService); } @Override protected void tearDown() throws Exception { m_rootURL = null; m_serviceURL = null; if (m_fixture != null) { m_fixture.stop(); m_fixture = null; } if (m_indexManager != null && m_namespace != null) { dropTripleStore(m_indexManager, m_namespace); } if (m_repo != null) { m_repo.close(); m_repo = null; } if (m_client != null) { m_client.stop(); m_client = null; } if (executorService != null) { executorService.shutdownNow(); executorService = null; } super.tearDown(); } private void dropTripleStore(final IIndexManager indexManager, final String namespace) { if (log.isInfoEnabled()) log.info("KB namespace=" + namespace); try { AbstractApiTask.submitApiTask(indexManager, new DestroyKBTask(namespace)).get(); } catch (InterruptedException | ExecutionException e) { if (InnerCause.isInnerCause(e, DatasetNotFoundException.class)) { // Namespace does not exist. return; } // Wrap and throw. throw new RuntimeException(e); } } /* * Define local NSS */ protected Server newFixture() throws Exception { final Properties properties = getProperties(); m_indexManager = new Journal(properties); // Create the triple store instance. AbstractApiTask.submitApiTask(m_indexManager, new CreateKBTask(m_namespace, properties)).get(); // createTripleStore(m_indexManager, m_namespace, properties); final Map<String, String> initParams = new LinkedHashMap<String, String>(); { initParams.put(ConfigParams.NAMESPACE, m_namespace); initParams.put(ConfigParams.CREATE, "false"); } // Start server for that kb instance. final Server fixture = NanoSparqlServer.newInstance(0/* port */, m_indexManager, initParams); fixture.start(); return fixture; } // protected AbstractTripleStore createTripleStore( // final IIndexManager indexManager, final String namespace, // final Properties properties) { // // if (log.isInfoEnabled()) // log.info("KB namespace=" + namespace); // // // Locate the resource declaration (aka "open"). This tells us if it // // exists already. // AbstractTripleStore tripleStore = (AbstractTripleStore) indexManager // .getResourceLocator().locate(namespace, ITx.UNISOLATED); // // if (tripleStore != null) { // // fail("exists: " + namespace); // // } // // /* // * Create the KB instance. // */ // // if (log.isInfoEnabled()) { // log.info("Creating KB instance: namespace=" + namespace); // log.info("Properties=" + properties.toString()); // } // // if (indexManager instanceof Journal) { // // // Create the kb instance. // tripleStore = new LocalTripleStore(indexManager, namespace, // ITx.UNISOLATED, properties); // // } else { // // tripleStore = new ScaleOutTripleStore(indexManager, namespace, // ITx.UNISOLATED, properties); // } // // // create the triple store. // tripleStore.create(); // // if (log.isInfoEnabled()) // log.info("Created tripleStore: " + namespace); // // // New KB instance was created. // return tripleStore; // // } @Override public Properties getProperties() { final Properties props = super.getProperties(); props.setProperty(Options.BUFFER_MODE, BufferMode.DiskRW.toString()); props.setProperty(Options.CREATE_TEMP_FILE, "true"); return props; } static class HealthCheckTestSuite extends TestSuite { /** * The URL of the bigdata web application. */ @SuppressWarnings("unused") private final String requestURI; /** * * @param name * @param requestURI * The URL of the bigdata web application. */ private HealthCheckTestSuite(final String name, final String requestURI) { super(name); this.requestURI = requestURI; } } static HealthCheckTestSuite createTestSuite(final String name, final String requestURI) { final HealthCheckTestSuite suite = new HealthCheckTestSuite(name, requestURI); suite.addTestSuite(TestNSSHealthCheck.class); return suite; } /** * bare URL of the server * * <pre> * http://localhost:9999 * </pre> * * The response is should be <code>index.html</code> since we want the * bigdata webapp to respond for the top-level context. * * <p> * Note: You must ensure that the client follows redirects using a standard * policy. This is necessary for tests of the webapp structure since the * container may respond with a redirect (302) to the location of the webapp * when the client requests the root URL. */ public void test_webapp_structure_rootURL() throws Exception { final String content = doGET(m_rootURL); assertTrue(content.contains(JUNIT_TEST_MARKER_INDEX_HTML)); } /** * URL with correct context path * * <pre> * http://localhost:9999/bigdata * </pre> * * The response is should be <code>index.html</code>, which is specified * through the welcome files list. */ public void test_webapp_structure_contextPath() throws Exception { final String content = doGET(m_serviceURL); assertTrue(content.contains(JUNIT_TEST_MARKER_INDEX_HTML)); } /** * URL with context path and index.html reference * * <pre> * http://localhost:9999/bigdata/index.html * </pre> * * This URL does NOT get mapped to anything (404). */ public void test_webapp_structure_contextPath_indexHtml() throws Exception { try { doGET(m_serviceURL + "/index.html"); } catch (HttpException ex) { assertEquals(404, ex.getStatusCode()); } } /** * The <code>favicon.ico</code> file. * * @see <a href="http://www.w3.org/2005/10/howto-favicon"> How to add a * favicon </a> */ public void test_webapp_structure_favicon() throws Exception { doGET(m_serviceURL + "/html/favicon.ico"); } /** * The <code>/status</code> servlet responds. */ public void test_webapp_structure_status() throws Exception { doGET(m_serviceURL + "/status"); } /** * The <code>/counters</code> servlet responds. */ public void test_webapp_structure_counters() throws Exception { doGET(m_serviceURL + "/counters"); } // /** // * The <code>/namespace/</code> servlet responds (multi-tenancy API). // */ // public void test_webapp_structure_namespace() throws Exception { // // doGET(m_serviceURL + "/namespace/"); // // } /** * The fully qualified URL for <code>index.html</code> * * <pre> * http://localhost:9999/bigdata/html/index.html * </pre> * * The response is should be <code>index.html</code>, which is specified * through the welcome files list. */ public void test_webapp_structure_contextPath_html_indexHtml() throws Exception { doGET(m_serviceURL + "/html/index.html"); } private String doGET(final String url) throws Exception { JettyResponseListener response = null; try { final ConnectOptions opts = new ConnectOptions(url); opts.method = "GET"; response = m_repo.doConnect(opts); RemoteRepository.checkResponseCode(response); return response.getResponseBody(); } finally { if (response != null) response.abort(); } } /** * Connect to the NSS end point and run a test suite designed to verify the * health of that instance. * * @param args * URL * * @throws MalformedURLException * * TODO Support HA health checks as well. */ public static void main(final String[] args) throws MalformedURLException { if (args.length < 1) { System.err.println("usage: Request-URI"); System.exit(1); } final String requestURI = args[0]; // Setup test result. final TestResult result = new TestResult(); // Setup listener, which will write the result on System.out result.addListener(new ResultPrinter(System.out)); result.addListener(new TestListener() { @Override public void startTest(Test arg0) { log.info(arg0); } @Override public void endTest(Test arg0) { log.info(arg0); } @Override public void addFailure(Test arg0, AssertionFailedError arg1) { log.error(arg0, arg1); } @Override public void addError(Test arg0, Throwable arg1) { log.error(arg0, arg1); } }); try { // Setup test suite final Test test = createTestSuite(null/* name */, requestURI); System.out.println("Running health check: Request-URI=" + requestURI); // Run the test suite. test.run(result); } finally { } final String msg = "nerrors=" + result.errorCount() + ", nfailures=" + result.failureCount() + ", nrun=" + result.runCount() + " : Request-URI=" + requestURI; System.out.println(msg); if (result.errorCount() > 0 || result.failureCount() > 0) { // At least one test failed. System.exit(1); } // All green. System.exit(0); } }