/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.engine.calcnode; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.io.StringReader; import java.net.URI; import java.net.URISyntaxException; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.xml.XmlBeanDefinitionReader; import org.springframework.context.support.GenericApplicationContext; import org.xml.sax.InputSource; /** * Starts a calculation node and joins a job dispatcher. */ public final class CalculationNodeProcess { private static final Logger s_logger = LoggerFactory.getLogger(CalculationNodeProcess.class); private static final int CONFIGURATION_RETRY = 3; private static final int CONFIGURATION_POLL_PERIOD = 5; private static HttpClient s_httpClient; /** * A job item execution watchdog that can terminate the host process if one (or all) calculation threads hang. */ public static class JobItemExecutionWatchdog extends MaximumJobItemExecutionWatchdog { public JobItemExecutionWatchdog() { setTimeoutAction(new Action() { @Override public void jobItemExecutionLimitExceeded(final CalculationJobItem jobItem, final Thread thread) { s_logger.error("Starting graceful shutdown after thread {} hung on {}", thread, jobItem); startGracefulShutdown(); if (!areThreadsAlive()) { s_logger.error("Halting remote calc process", thread, jobItem); System.exit(0); } } }); } } private CalculationNodeProcess() { } private static void sleep(int period) { try { Thread.sleep(1000 * period); } catch (InterruptedException e) { } } private static String getConfigurationXml(final String url) { if (s_httpClient == null) { s_httpClient = new DefaultHttpClient(); } s_logger.debug("Fetching {}", url); final HttpResponse resp; try { resp = s_httpClient.execute(new HttpGet(url)); } catch (Exception e) { s_logger.warn("Error fetching {} - {}", url, e.getMessage()); return null; } s_logger.debug("HTTP result {}", resp.getStatusLine()); if (resp.getStatusLine().getStatusCode() != 200) { s_logger.warn("No configuration available (HTTP {})", resp.getStatusLine().getStatusCode()); return null; } try { final Reader in = new InputStreamReader(resp.getEntity().getContent()); final StringBuilder sb = new StringBuilder(); final char[] buf = new char[1024]; int i = in.read(buf); while (i > 0) { sb.append(buf, 0, i); i = in.read(buf); } s_logger.debug("Configuration document received - {} characters", sb.length()); return sb.toString(); } catch (IOException e) { s_logger.warn("Error retrieving response from {} - {}", url, e.getMessage()); return null; } } private static boolean startContext(final String configuration) { try { final GenericApplicationContext context = new GenericApplicationContext(); final XmlBeanDefinitionReader beanReader = new XmlBeanDefinitionReader(context); beanReader.setValidationMode(XmlBeanDefinitionReader.VALIDATION_NONE); s_logger.debug("Loading configuration"); beanReader.loadBeanDefinitions(new InputSource(new StringReader(configuration))); s_logger.debug("Instantiating beans"); context.refresh(); s_logger.debug("Starting node"); context.start(); return true; } catch (RuntimeException e) { s_logger.warn("Spring initialisation error", e); return false; } } private static String getBaseUrl(final String url) { final int slash = url.lastIndexOf('/'); return url.substring(0, slash + 1); } private static void setConnectionDefaults(final String url) { try { final URI uri = new URI(url); if (uri.getHost() != null) { System.setProperty("opengamma.engine.calcnode.host", uri.getHost()); } if (uri.getPort() != -1) { System.setProperty("opengamma.engine.calcnode.port", Integer.toString(uri.getPort())); } } catch (URISyntaxException e) { s_logger.warn("Couldn't set connection defaults", e); } } private static void startGracefulShutdown() { s_logger.error("TODO: [PLAT-2351] start graceful shutdown"); // TODO: [PLAT-2351] stop accepting jobs and allow current ones to run to completion } /** * Starts a calculation node, retrieving configuration from the given URL * * @param url The URL to use */ public static void main(final String url) { s_logger.info("Using configuration URL {}", url); String configuration = getConfigurationXml(url); if (configuration == null) { for (int i = 0; i < CONFIGURATION_RETRY; i++) { s_logger.warn("Failed to retrieve configuration - retrying"); sleep(1); configuration = getConfigurationXml(url); if (configuration != null) { break; } } if (configuration == null) { s_logger.error("No response from {}", url); System.exit(1); } } // Create and start the spring config System.setProperty("opengamma.engine.calcnode.baseurl", getBaseUrl(url)); setConnectionDefaults(url); if (startContext(configuration)) { s_logger.info("Calculation node started"); } else { s_logger.error("Couldn't start calculation node"); System.exit(1); } // Terminate if the configuration changes - the O/S will restart us int retry = 0; do { sleep(CONFIGURATION_POLL_PERIOD); final String newConfiguration = getConfigurationXml(url); if (newConfiguration != null) { if (!configuration.equals(newConfiguration)) { s_logger.info("Configuration at {} has changed", url); System.exit(0); } retry = 0; } else { switch (++retry) { case 1: s_logger.debug("No response from configuration at {}", url); break; case 2: s_logger.info("No response from configuration at {}", url); break; case 3: s_logger.warn("No response from configuration at {}", url); break; case 4: s_logger.error("No response from configuration at {}", url); startGracefulShutdown(); // TODO: wait for the graceful shutdown to complete (i.e. node goes idle) System.exit(0); break; } } s_logger.info("Free memory = {}Mb, total memory = {}Mb", (double) Runtime.getRuntime().freeMemory() / (1024d * 1024d), (double) Runtime.getRuntime().totalMemory() / (1024d * 1024d)); } while (true); } /** * Starts a calculation node * * @param args the arguments, should contain one parameter - the configuration URL to use */ public static void main(String[] args) { // CSIGNORE if (args.length != 1) { s_logger.error("Configuration URL not specified"); System.exit(1); } main(args[0]); } }