diff --git a/joram/joram/tools/jmscheck/pom.xml b/joram/joram/tools/jmscheck/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..2920753574307f6db3e2d78eb2725132285b9433 --- /dev/null +++ b/joram/joram/tools/jmscheck/pom.xml @@ -0,0 +1,100 @@ + + + + 4.0.0 + joram-tools-jmscheck + bundle + JORAM :: joram :: tools :: jmscheck + JMS Health-Check bundle. + + + org.ow2.joram + joram-tools + 5.17.6-SNAPSHOT + + + + + + org.apache.felix + maven-bundle-plugin + ${maven.bundle.plugin.version} + true + + + ${project.artifactId} + org.ow2.joram.tools.jmscheck.Activator + + fr.dyade.aaa.common, + fr.dyade.aaa.util.management, + fr.dyade.aaa.agent, + javax.jms, + javax.naming, + fr.dyade.aaa.jndi2.client, + javax.transaction.xa, + org.osgi.framework, + org.objectweb.util.monolog, + org.objectweb.util.monolog.api, + org.objectweb.joram.client.jms, + org.objectweb.joram.client.jms.tcp, + org.objectweb.joram.client.jms.local + + + * + + + + + maven-assembly-plugin + + + + attached + + package + + + jar-with-dependencies + + + + org.ow2.joram.tools.jmscheck.JMSCheck + + + + + + + + + + + + org.ow2.spec.ee + ow2-jms-2.0-spec + + + org.ow2.joram + a3-common + ${project.version} + + + org.ow2.joram + a3-rt + ${project.version} + + + org.ow2.joram + joram-client-jms + ${project.version} + + + org.ow2.joram + jndi-client + ${project.version} + + + + + + diff --git a/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/Activator.java b/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/Activator.java new file mode 100644 index 0000000000000000000000000000000000000000..2faf4cf4211a3fda6a4e4628790095fc725756a0 --- /dev/null +++ b/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/Activator.java @@ -0,0 +1,191 @@ +/* + * Copyright (C) 2020 ScalAgent Distributed Technologies + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA. + * + * Initial developer(s): ScalAgent Distributed Technologies + * Contributor(s): + */ +package org.ow2.joram.tools.jmscheck; + + +import javax.naming.InitialContext; + +import org.objectweb.util.monolog.api.BasicLevel; +import org.objectweb.util.monolog.api.Logger; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; + +import fr.dyade.aaa.agent.AgentServer; +import fr.dyade.aaa.common.Debug; + +public class Activator implements BundleActivator { + public static final Logger logger = Debug.getLogger(Activator.class.getName()); + + public final static String CHECK_PREFIX = "org.ow2.joram.check."; + + public final static String GLOBAL_PERIOD = CHECK_PREFIX + "period"; + public final static String GLOBAL_TIMEOUT = CHECK_PREFIX + "timeout"; + + public final static String DUMP_FILE = CHECK_PREFIX + "dump.file"; + public final static String DUMP_THRESHOLD = CHECK_PREFIX + "dump.threshold"; + public final static String DUMP_DELAY = CHECK_PREFIX + "dump.delay"; + + public final static String JNDI_FILE = CHECK_PREFIX + "jndi.file"; + public final static String JNDI_FACTORY = CHECK_PREFIX + "jndi.factory"; + public final static String JNDI_HOST = CHECK_PREFIX + "jndi.host"; + public final static String JNDI_PORT = CHECK_PREFIX + "jndi.port"; + + public static final String CF_NAME = CHECK_PREFIX + "cf"; + + public static final String USER_NAME = CHECK_PREFIX + "user"; + public static final String PASSWORD = CHECK_PREFIX + "password"; + + public static final String QUEUE_NAME = CHECK_PREFIX + "queue"; + + private static BundleContext context; + + public Activator() { + } + + int period, timeout; + JMSStatus jmsStatus; + + @Override + public void start(BundleContext context) throws Exception { + Activator.context = context; + + // Gets the global period. + period = JMSStatus.DFLT_PERIOD; + String value = context.getProperty(GLOBAL_PERIOD); + if (value != null) { + try { + period = Integer.parseInt(value); + } catch (NumberFormatException exc) { + logger.log(BasicLevel.WARN, + "MqttCheckActivator.start: bad value for property " + GLOBAL_PERIOD + ", set to default."); + period = JMSStatus.DFLT_PERIOD; + } + } + if (period <= 0) return; + + // Gets the global timeout. + timeout = JMSStatus.DFLT_TIMEOUT; + value = context.getProperty(GLOBAL_TIMEOUT); + if (value != null) { + try { + timeout = Integer.parseInt(value); + } catch (NumberFormatException exc) { + logger.log(BasicLevel.WARN, + "MqttCheckActivator.start: bad value for property " + GLOBAL_TIMEOUT + ", set to default."); + timeout = JMSStatus.DFLT_TIMEOUT; + } + } + + // Gets the pathname of the dump file. + String dumpFilePath = context.getProperty(DUMP_FILE); + if (dumpFilePath == null) { + dumpFilePath = JMSStatus.DFLT_DUMP_FILE; + } + + // Gets the threshold to generate a dump file. + int threshold = JMSStatus.DFLT_THRESHOLD; + value = context.getProperty(DUMP_THRESHOLD); + if (value != null) { + try { + threshold = Integer.parseInt(value); + } catch (NumberFormatException exc) { + logger.log(BasicLevel.WARN, + "MqttCheckActivator.start: bad value for property " + DUMP_THRESHOLD + ", set to default."); + threshold = JMSStatus.DFLT_THRESHOLD; + } + } + + // Gets the minimal delay before to generate anew a dump file. + int delay = JMSStatus.DFLT_DELAY; + value = context.getProperty(DUMP_DELAY); + if (value != null) { + try { + delay = Integer.parseInt(value); + } catch (NumberFormatException exc) { + logger.log(BasicLevel.WARN, + "MqttCheckActivator.start: bad value for property " + DUMP_DELAY + ", set to default."); + delay = JMSStatus.DFLT_DELAY; + } + } + + jmsStatus = new JMSStatus("Joram#" + AgentServer.getServerId(), period, timeout, dumpFilePath, threshold, delay); + + String jndiFile = context.getProperty(JNDI_FILE); + String jndiFactory = context.getProperty(JNDI_FACTORY); + String jndiHost = context.getProperty(JNDI_HOST); + String jndiPort = context.getProperty(JNDI_PORT); + + InitialContext ictx = jmsStatus.getInitialContext(jndiFile, jndiFactory, jndiHost, jndiPort); + + createConnector(jmsStatus, "", ictx); + for (int i=1;; i++) { + if (!createConnector(jmsStatus, "." + i, ictx)) break; + } + + jmsStatus.start(); + } + + +// public synchronized Object lookup(String name) throws NamingException { +// if (logger.isLoggable(BasicLevel.DEBUG)) +// logger.log(BasicLevel.DEBUG, "Helper.lookup " + name); +// +// ClassLoader originalContextClassLoader = Thread.currentThread().getContextClassLoader(); +// try { +// Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); +// +// if (ictx == null) +// ictx = new InitialContext(jndiProps); +// return ictx.lookup(name); +// } finally { +// Thread.currentThread().setContextClassLoader(originalContextClassLoader); +// } +// } + + /** + * Adds a new check component using the related properties. + * + * @param check The root component. + * @param suffix The property suffix. + * @param ictx The InitialContext to use. + * @return True if the component is successively created. + */ + private boolean createConnector(JMSStatus check, String suffix, InitialContext ictx) { + String cfname = context.getProperty(CF_NAME + suffix); + if (cfname != null) { + String user = context.getProperty(USER_NAME + suffix); + String pass = context.getProperty(PASSWORD + suffix); + String qname = context.getProperty(QUEUE_NAME + suffix); + + // TODO (AF): Allow to override period and timeout. + check.addConnectorStatus(cfname, ictx, user, pass, qname, period, timeout); + return true; + } + return false; + } + + @Override + public void stop(BundleContext context) throws Exception { + context = null; + jmsStatus.stop(); + } +} diff --git a/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSCheck.java b/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSCheck.java new file mode 100644 index 0000000000000000000000000000000000000000..85d1519d4434b53ebfda1895fa8895e4430fd196 --- /dev/null +++ b/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSCheck.java @@ -0,0 +1,98 @@ +package org.ow2.joram.tools.jmscheck; + + +import javax.naming.InitialContext; + +import org.objectweb.util.monolog.api.BasicLevel; +import org.objectweb.util.monolog.api.Logger; + +import fr.dyade.aaa.common.Debug; + +public class JMSCheck { + public static final Logger logger = Debug.getLogger(JMSCheck.class.getName()); + + public final static String GLOBAL_PERIOD = "period"; + public final static String GLOBAL_TIMEOUT = "timeout"; + + public final static String JNDI_FILE = "jndi.file"; + public final static String JNDI_FACTORY = "jndi.factory"; + public final static String JNDI_HOST = "jndi.host"; + public final static String JNDI_PORT = "jndi.port"; + + public static final String CF_NAME = "cf"; + + public static final String USER_NAME = "user"; + public static final String PASSWORD = "password"; + + public static final String QUEUE_NAME = "queue"; + + static int period, timeout; + static JMSStatus jmsStatus; + + public static void main(String[] args) throws Exception { + // Gets the global period. + period = JMSStatus.DFLT_PERIOD; + String value = System.getProperty(GLOBAL_PERIOD); + if (value != null) { + try { + period = Integer.parseInt(value); + } catch (NumberFormatException exc) { + logger.log(BasicLevel.WARN, + "MqttCheckActivator.start: bad value for property " + GLOBAL_PERIOD + ", set to default."); + period = JMSStatus.DFLT_PERIOD; + } + } + if (period <= 0) return; + + // Gets the global timeout. + timeout = JMSStatus.DFLT_TIMEOUT; + value = System.getProperty(GLOBAL_TIMEOUT); + if (value != null) { + try { + timeout = Integer.parseInt(value); + } catch (NumberFormatException exc) { + logger.log(BasicLevel.WARN, + "MqttCheckActivator.start: bad value for property " + GLOBAL_TIMEOUT + ", set to default."); + timeout = JMSStatus.DFLT_TIMEOUT; + } + } + + jmsStatus = new JMSStatus("Joram", period, timeout, null, 0, 0); + + String jndiFile = System.getProperty(JNDI_FILE); + String jndiFactory = System.getProperty(JNDI_FACTORY); + String jndiHost = System.getProperty(JNDI_HOST); + String jndiPort = System.getProperty(JNDI_PORT); + + InitialContext ictx = jmsStatus.getInitialContext(jndiFile, jndiFactory, jndiHost, jndiPort); + + createConnector(jmsStatus, "", ictx); + for (int i=1;; i++) { + if (!createConnector(jmsStatus, "." + i, ictx)) break; + } + + jmsStatus.start(); + } + + /** + * Adds a new check component using the related properties. + * + * @param check The root component. + * @param suffix The property suffix. + * @param ictx The InitialContext to use. + * @return True if the component is successively created. + */ + private static boolean createConnector(JMSStatus jmsStatus, String suffix, InitialContext ictx) { + String cfname = System.getProperty(CF_NAME + suffix); + if (cfname != null) { + String user = System.getProperty(USER_NAME + suffix); + String pass = System.getProperty(PASSWORD + suffix); + String qname = System.getProperty(QUEUE_NAME + suffix); + + // TODO (AF): Allow to override period and timeout. + jmsStatus.addConnectorStatus(cfname, ictx, user, pass, qname, period, timeout); + return true; + } + return false; + } +} diff --git a/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSConnectorStatus.java b/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSConnectorStatus.java new file mode 100644 index 0000000000000000000000000000000000000000..114d1a5b532637ae1451746172ec3f5546b162d2 --- /dev/null +++ b/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSConnectorStatus.java @@ -0,0 +1,399 @@ +/* + * Copyright (C) 2020 ScalAgent Distributed Technologies + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA. + * + * Initial developer(s): ScalAgent Distributed Technologies + * Contributor(s): + */ +package org.ow2.joram.tools.jmscheck; + +import java.text.DateFormat; +import java.util.Date; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import javax.jms.Connection; +import javax.jms.JMSException; +import javax.jms.Message; +import javax.jms.MessageConsumer; +import javax.jms.MessageProducer; +import javax.jms.Session; +import javax.jms.TextMessage; +import javax.naming.InitialContext; +import javax.naming.NamingException; + +import org.objectweb.joram.client.jms.ConnectionFactory; +import org.objectweb.joram.client.jms.Queue; +import org.objectweb.util.monolog.api.BasicLevel; +import org.objectweb.util.monolog.api.Logger; + +import fr.dyade.aaa.common.Debug; +import fr.dyade.aaa.util.management.MXWrapper; + +public class JMSConnectorStatus implements JMSConnectorStatusMBean { + public static Logger logger = Debug.getLogger(JMSConnectorStatus.class.getName()); + + public static final int RUNNING = 0; + public static final int UNREACHABLE = 1; + public static String[] info = { "RUNNING", "UNREACHABLE" }; + + /** retryStatusCount == 0 => RUNNING, retryStatusCount > 0 => UNREACHABLE */ + private int retryStatusCount = UNREACHABLE; + + @Override + public int getStatus() { + return retryStatusCount; + } + + @Override + public String getStatusInfo() { + if (retryStatusCount > 0) + return info[1] + '(' + retryStatusCount + ')'; + return info[0]; + } + + void setStatus(int retry) { + JMSStatus.checkDump(cfname, retry); + this.retryStatusCount = retry; + } + + private int nbtry = 0; + + @Override + public int getNbTry() { + return nbtry; + } + + private transient int failures = 0; + + @Override + public int getNbFailures() { + return failures; + } + + private String errorMsg; + + @Override + public String getErrorMsg() { + return errorMsg; + } + + private long latencyConnect = -1; + + @Override + public long getLatencyConnect() { + return latencyConnect; + } + + private void setLatencyConnect(long latencyConnect) { + this.latencyConnect = latencyConnect; + } + + private String lastConnectTime = "-"; + + @Override + public String getLastConnectTime() { + return lastConnectTime; + } + + private long latencyPubSub = -1; + + @Override + public long getLatencyPubSub() { + return latencyPubSub; + } + + private void setLatencyPubSub(long latencyPubSub) { + this.latencyPubSub = latencyPubSub; + } + + /** The period of time between 2 checks. */ + private int period = 60; // in seconds + + @Override + public int getPeriod() { + return period; + } + + @Override + public void setPeriod(int period) { + try { + if (period < 5) + period = 5; + if (period < (2* timeout)) + period = 2* timeout; + this.period = period; + schedule(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** The maximum time waiting for connection. */ + private int timeout = 10; // in seconds + + @Override + public int getTimeOut() { + return timeout; + } + + @Override + public void setTimeOut(int timeOut) { + if (timeOut < 5) + timeOut = 5; + if (period < (2* timeOut)) + period = 2* timeOut; + this.timeout = timeOut; + schedule(); + } + + /** Root component */ + JMSStatus jmsStatus; + + /** JNDI Name of the ConnectionFactory to use. */ + private String cfname; + + @Override + public String getName() { + return cfname; + } + + /* User name and password for authentication */ + private String user; + private String pass; + + /* Internal name of the queue, be careful this name is not a JNDI name. */ + private String qname; + + private InitialContext ictx; + + private ConnectionFactory cf; + private Queue queue; + + ScheduledFuture callableHandle; + + /** + * + * @param jmsStatus Root component. + * @param cfname JNDI name of the ConnectionFactory to use. + * @param ictx JNDI context allowing to get the ConnectionFactory. + * @param user User name for authentication, if null uses the ConnectioNfactory default. + * @param pass Password for authentication, if null uses the ConnectioNfactory default. + * @param qname Name of the queue used to check the JMS behavior. It is an internal not a JNDI name, this destination is created by the + * module. + * @param period Period (in seconds) between 2 attempts of check. + * @param timeOut Maximum amount of time to wait either the connection or the message. + */ + public JMSConnectorStatus(JMSStatus jmsStatus, + String cfname, + InitialContext ictx, + String user, String pass, + String qname, + int period, int timeOut) { + this.jmsStatus = jmsStatus; + + this.cfname = cfname; + this.ictx = ictx; + this.user = user; + this.pass = pass; + + this.qname = qname; + + this.period = period; + this.timeout = timeOut; + + mbeanName = JMSStatus.mbeanName + ",cf=" + cfname; + } + + private void getConnectionFactory() throws NamingException { + if (cf != null) return; + + ClassLoader originalContextClassLoader = Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + cf = (ConnectionFactory) ictx.lookup(cfname); + } catch (NamingException exc) { + if (logger.isLoggable(BasicLevel.DEBUG)) + logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + "): Cannot get ConnectionFactory.", exc); + else + logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + "): Cannot get ConnectionFactory."); + throw exc; + } finally { + Thread.currentThread().setContextClassLoader(originalContextClassLoader); + } + } + + // -------------------------------------------------------------------------------- + // JMS Health-check + // -------------------------------------------------------------------------------- + + private void schedule() { + logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").schedule #1"); + if (callableHandle != null && !callableHandle.isCancelled()) { + callableHandle.cancel(true); + } + if (period > 0) { + logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").schedule #2"); + callableHandle = JMSStatus.scheduler.scheduleAtFixedRate(new Runnable() { + public void run() { + try { + call(); + } catch (Throwable t) { + logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").schedule", t); + } + } + }, period, period, TimeUnit.SECONDS); + } + logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").schedule #3"); + } + + public void call() { + logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").call #1"); + nbtry += 1; + + try { + logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").call #2 -> " + cf); + getConnectionFactory(); + logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").call #3 -> " + cf); + } catch (NamingException exc) { + failures += 1; + setStatus(getStatus()+1); + setLatencyConnect(-1L); + setLatencyPubSub(-1L); + errorMsg = "Cannot get ConnectionFactory"; + if (logger.isLoggable(BasicLevel.DEBUG)) + logger.log(BasicLevel.WARN, "JMSConnectorStatus.check(" + cfname + ")<: Cannot get ConnectionFactory.", exc); + else + logger.log(BasicLevel.WARN, "JMSConnectorStatus.check(" + cfname + "): Cannot get ConnectionFactory."); + return; + } + + Connection cnx = null; + try { + long start = System.nanoTime(); + cf.getParameters().connectingTimer = timeout; + logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").call #4"); + if ((user == null) || (pass == null)) + cnx = cf.createConnection(); + else + cnx = cf.createConnection(user, pass); + logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").call #5"); + long dt = System.nanoTime() - start; + setLatencyConnect(dt / 1000000L); + lastConnectTime = DateFormat.getDateTimeInstance().format(new Date()); + } catch (JMSException exc) { + failures += 1; + setStatus(getStatus()+1); + setLatencyConnect(-1L); + setLatencyPubSub(-1L); + errorMsg = "Cannot connect"; + if (logger.isLoggable(BasicLevel.DEBUG)) + logger.log(BasicLevel.WARN, "JMSConnectorStatus.check(" + cfname + "): Cannot connect.", exc); + else + logger.log(BasicLevel.WARN, "JMSConnectorStatus.check(" + cfname + "): Cannot connect."); + return; + } + + try { + logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").call #6"); + Session sess = cnx.createSession(false, Session.AUTO_ACKNOWLEDGE); + logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").call #7"); + if (queue == null) + queue = (Queue) sess.createQueue(qname); + logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").call #8"); + MessageProducer producer = sess.createProducer(queue); + MessageConsumer cons = sess.createConsumer(queue); + cnx.start(); + + logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").call #9"); + TextMessage sent = sess.createTextMessage("Test number " + nbtry); + long start = System.nanoTime(); + producer.send(sent, Message.DEFAULT_DELIVERY_MODE, Message.DEFAULT_PRIORITY, timeout *1000); + + logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").call #10"); + Message received = cons.receive(timeout *1000); + logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").call #11 -> " + received); + long dt = System.nanoTime() - start; + if (received == null) { + failures += 1; + setStatus(getStatus()+1); + setLatencyPubSub(-1L); + errorMsg = "Message not received"; + logger.log(BasicLevel.WARN, "JMSConnectorStatus.check(" + cfname + ") message not received." + dt); + } else { + retryStatusCount = 0; + setLatencyPubSub(dt / 1000000L); + errorMsg = null; + } + } catch (JMSException exc) { + failures += 1; + setStatus(getStatus()+1); + setLatencyPubSub(-1L); + errorMsg = "Error during message send/receive."; + if (logger.isLoggable(BasicLevel.DEBUG)) + logger.log(BasicLevel.WARN, "JMSConnectorStatus.check(" + cfname + "): Error during message send/receive.", exc); + else + logger.log(BasicLevel.WARN, "JMSConnectorStatus.check(" + cfname + "): Error during message send/receive."); + } finally { + try { + cnx.close(); + } catch (JMSException exc) { + if (logger.isLoggable(BasicLevel.DEBUG)) + logger.log(BasicLevel.WARN, "JMSConnectorStatus.check(" + cfname + "): Error closing connection.", exc); + else + logger.log(BasicLevel.WARN, "JMSConnectorStatus.check(" + cfname + "): Error closing connection."); + } + } + } + + // -------------------------------------------------------------------------------- + // Life cycle + // -------------------------------------------------------------------------------- + + void start() { + schedule(); + registerMBean(); + } + + void stop() { + unregisterMBean(); + if (callableHandle != null) + callableHandle.cancel(true); + } + + // -------------------------------------------------------------------------------- + // JMX handling + // -------------------------------------------------------------------------------- + + private String mbeanName; + + private void registerMBean() { + try { + MXWrapper.registerMBean(this, jmsStatus.getName(), mbeanName); + } catch (Exception e) { + logger.log(BasicLevel.WARN, "registerMBean: " + mbeanName, e); + } + } + + private void unregisterMBean() { + try { + MXWrapper.unregisterMBean(jmsStatus.getName(), mbeanName); + } catch (Exception e) { + logger.log(BasicLevel.WARN, "unregisterMBean: " + mbeanName, e); + } + } +} diff --git a/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSConnectorStatusMBean.java b/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSConnectorStatusMBean.java new file mode 100644 index 0000000000000000000000000000000000000000..4de5e4cda7524b4ceb262855cf92da77dad5ba26 --- /dev/null +++ b/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSConnectorStatusMBean.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2020 ScalAgent Distributed Technologies + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA. + * + * Initial developer(s): ScalAgent Distributed Technologies + * Contributor(s): + */ +package org.ow2.joram.tools.jmscheck; + +public interface JMSConnectorStatusMBean { + String getName(); + + /** + * Returns the period of time between 2 checks. + * @return the period of time between 2 checks. + */ + int getPeriod(); + + /** + * Sets the period of time between 2 checks. + * @param period the period of time between 2 checks. + */ + void setPeriod(int period); + + /** + * Returns the maximum time waiting for connection. + * @return the maximum time waiting for connection. + */ + int getTimeOut(); + + /** + * Sets the maximum time waiting for connection. + * @param timeOut the maximum time waiting for connection. + */ + void setTimeOut(int timeOut); + + /** + * Returns the status of the connector: 0 => RUNNING, >0 => UNREACHABLE. + * + * @return the number of unreachable connection + */ + int getStatus(); + + String getStatusInfo(); + + String getLastConnectTime(); + + int getNbTry(); + + int getNbFailures(); + + long getLatencyConnect(); + + long getLatencyPubSub(); + + String getErrorMsg(); +} diff --git a/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSStatus.java b/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSStatus.java new file mode 100644 index 0000000000000000000000000000000000000000..1e96f03158f434225497a5f08e10f62e21b3fcaf --- /dev/null +++ b/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSStatus.java @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2020 ScalAgent Distributed Technologies + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA. + * + * Initial developer(s): ScalAgent Distributed Technologies + * Contributor(s): + */ +package org.ow2.joram.tools.jmscheck; + +import java.io.FileInputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Properties; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; + +import javax.naming.InitialContext; +import javax.naming.NamingException; + +import org.objectweb.util.monolog.api.BasicLevel; +import org.objectweb.util.monolog.api.Logger; + +import fr.dyade.aaa.agent.AgentServer; +import fr.dyade.aaa.agent.SCServer; +import fr.dyade.aaa.common.Debug; +import fr.dyade.aaa.util.management.MXWrapper; + +public class JMSStatus implements JMSStatusMBean { + public static final Logger logger = Debug.getLogger(JMSStatus.class.getName()); + + @Override + public int getStatus() { + for (JMSConnectorStatus connector : connectors.values()) { + if (connector.getStatus() != 0) + return JMSConnectorStatus.UNREACHABLE; + } + return JMSConnectorStatus.RUNNING; + } + + @Override + public String getStatusInfo() { + return JMSConnectorStatus.info[getStatus()]; + } + + static final int DFLT_PERIOD = 60; // in seconds + private int globalPeriod = DFLT_PERIOD; // in seconds + + @Override + public int getPeriod() { + return globalPeriod; + } + + @Override + public void setPeriod(int period) { + this.globalPeriod = period; + for (JMSConnectorStatus connector : connectors.values()) { + connector.setPeriod(period); + } + } + + static final int DFLT_TIMEOUT = 10; // in seconds + private int globalTimeout = DFLT_TIMEOUT; // in seconds + + @Override + public int getTimeout() { + return globalTimeout; + } + + @Override + public void setTimeout(int timeout) { + this.globalTimeout = timeout; + } + + static final int DFLT_THRESHOLD = 5; + static int threshold = DFLT_THRESHOLD; + + @Override + public int getThreshold() { + return threshold; + } + + @Override + public void setThreshold(int threshold) { + this.threshold = threshold; + } + + static final int DFLT_DELAY = 600; + static int delay = DFLT_DELAY; + + @Override + public int getDelay() { + return delay; + } + + @Override + public void setDelay(int delay) { + this.delay = delay; + } + + private HashMap connectors; + +// public void add(String name, JMSConnectorStatus connector) { +// connectors.put(name, connector); +// } + + public void addConnectorStatus(String cfname, InitialContext ictx, + String user, String pass, String qname, + int period, int timeout) { + JMSConnectorStatus connector = new JMSConnectorStatus(this, cfname, ictx, user, pass, qname, period, timeout); + connectors.put(cfname, connector); + } + + static final String DFLT_DUMP_FILE = "joram.dump"; + + static String dumpFilePath = DFLT_DUMP_FILE; + + static long lastDump = 0L; + + public static void checkDump(String name, int retry) { + if ((dumpFilePath == null) || (threshold <= 0) || (delay <= 0)) + return; + + long current = System.currentTimeMillis(); + if ((retry >= threshold) && ((current - lastDump) > delay)) { + JMSStatus.dumpServerState(dumpFilePath + '.' + current, + "Fail to connect to " + name + " after " + retry + " attempts."); + JMSStatus.lastDump = current; + } + } + + public static final String JNDI_FACTORY_DFLT = "fr.dyade.aaa.jndi2.client.NamingContextFactory"; + public static final String JNDI_HOST_DFLT = "localhost"; + public static final String JNDI_PORT_DFLT = "16400"; + + static ScheduledExecutorService scheduler; + private static final int DFLT_THREAD_POOL_SIZE = 1; + + private String name; + + public String getName() { + return name; + } + + /** + * + * @param name Name of component, used to define the domain of the JMX MBeans. + * @param period Default period of activation for checks (seconds). + * @param timeout Default timeout for checks (seconds). + * @param dumpFilePath Pathname for server dump, the current time is aggregated at the end of the file name. + * @param threshold Number of consecutive failures needed to trigger a dump. + * @param delay Minimal delay between 2 dumps (seconds). + */ + public JMSStatus(String name, int period, int timeout, String dumpFilePath, int threshold, int delay) { + this.name = name; + this.globalPeriod = period; + this.globalTimeout = timeout; + + this.dumpFilePath = dumpFilePath; + this.threshold = threshold; + this.delay = delay; + + connectors = new HashMap(); + } + + /** + * + * @param jndiFile Path of JNDI properties file. If not defined, Joram's default are used. "fr.dyade.aaa.jndi2.client.NamingContextFactory" for + * JNDI Factory, "localhost", and 16400 for host and port. These values can be overloaded by specific properties below. + * @param jndiFactory Classname of the JNDI factory (see "java.naming.factory.initial" property). + * @param jndiHost Hostname ou IP address of JNDI server. + * @param jndiPort Listening port of JNDI server. + * @return + * @throws IOException + * @throws NamingException + */ + InitialContext getInitialContext(String jndiFile, + String jndiFactory, + String jndiHost, + String jndiPort) throws IOException, NamingException { + Properties jndiProps = new Properties(); + if (jndiFile != null) { + jndiProps.load(new FileInputStream(jndiFile)); + } else { + jndiProps.setProperty("java.naming.factory.initial", JNDI_FACTORY_DFLT); + jndiProps.setProperty("java.naming.factory.host", JNDI_HOST_DFLT); + jndiProps.setProperty("java.naming.factory.port", JNDI_PORT_DFLT); + } + + if (jndiFactory != null) + jndiProps.setProperty("java.naming.factory.initial", jndiFactory); + + if (jndiHost != null) + jndiProps.setProperty("java.naming.factory.host", jndiHost); + + if (jndiPort != null) + jndiProps.setProperty("java.naming.factory.port", jndiPort); + + ClassLoader originalContextClassLoader = Thread.currentThread().getContextClassLoader(); + try { + Thread.currentThread().setContextClassLoader(getClass().getClassLoader()); + + InitialContext ictx = new InitialContext(jndiProps); + return ictx; + } finally { + Thread.currentThread().setContextClassLoader(originalContextClassLoader); + } + } + + // -------------------------------------------------------------------------------- + // Life cycle + // -------------------------------------------------------------------------------- + + @Override + public void start() throws Exception { + if (scheduler == null || scheduler.isTerminated()) + scheduler = Executors.newScheduledThreadPool(DFLT_THREAD_POOL_SIZE); + + // start task + for (JMSConnectorStatus status : connectors.values()) { + status.start(); + } + registerMBean(); + } + + @Override + public void stop() throws Exception { + for (JMSConnectorStatus status : connectors.values()) { + try { + status.stop(); + } catch (Exception e) { } + } + scheduler.shutdown(); + unregisterMBean(); + } + + // -------------------------------------------------------------------------------- + // JMX handling + // -------------------------------------------------------------------------------- + + static String mbeanName = "type=healthcheck"; + + public void registerMBean() { + try { + MXWrapper.registerMBean(this, name, mbeanName); + } catch (Exception e) { + logger.log(BasicLevel.WARN, "registerMBean: " + mbeanName, e); + } + } + + public void unregisterMBean() { + try { + MXWrapper.unregisterMBean(name, mbeanName); + } catch (Exception e) { + logger.log(BasicLevel.WARN, "unregisterMBean: " + mbeanName, e); + } + } + + @Override + public void dumpServerState(String path) { + dumpServerState(path, null); + } + + public static void dumpServerState(String path, String cause) { + SCServer.dumpServerState(path, cause); + } +} diff --git a/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSStatusMBean.java b/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSStatusMBean.java new file mode 100644 index 0000000000000000000000000000000000000000..bfcb456aa2466141344c17719ec90f4043315ae4 --- /dev/null +++ b/joram/joram/tools/jmscheck/src/main/java/org/ow2/joram/tools/jmscheck/JMSStatusMBean.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2020 ScalAgent Distributed Technologies + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or any later version. + * + * This library 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA. + * + * Initial developer(s): ScalAgent Distributed Technologies + * Contributor(s): + */ +package org.ow2.joram.tools.jmscheck; + +public interface JMSStatusMBean { + + /** + * Starts the JMS healthcheck component. + * + * @throws Exception + */ + void start() throws Exception; + + /** + * Stops the JMS healthcheck component. + * + * @throws Exception + */ + void stop() throws Exception; + + int getStatus(); + + String getStatusInfo(); + + int getPeriod(); + + void setPeriod(int period); + + int getTimeout(); + + void setTimeout(int timeout); + + int getThreshold(); + + void setThreshold(int threshold); + + int getDelay(); + + void setDelay(int delay); + + void dumpServerState(String path); +} diff --git a/joram/joram/tools/pom.xml b/joram/joram/tools/pom.xml index caadb4a4a8f0f49539a11679468641e1a5756898..59cd588d24bb6d77a0b05aabefdcdd52eac2ba67 100644 --- a/joram/joram/tools/pom.xml +++ b/joram/joram/tools/pom.xml @@ -17,5 +17,6 @@ jasp rest monitoring + jmscheck \ No newline at end of file