Commit a4fc7dca authored by Andre Freyssinet's avatar Andre Freyssinet

Multiple enhancements.

parent dddb5cde
......@@ -21,7 +21,6 @@
*/
package org.ow2.joram.tools.jmscheck;
import javax.naming.InitialContext;
import org.objectweb.util.monolog.api.BasicLevel;
......@@ -32,6 +31,32 @@ import org.osgi.framework.BundleContext;
import fr.dyade.aaa.agent.AgentServer;
import fr.dyade.aaa.common.Debug;
/**
* This component regularly tests the state of a JMS connector and publishes the results to JMX.
* <br>
* It is configured through a set of OSGi properties:<ul>
* <li>org.ow2.joram.check.jndi.file: 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.<br>
* These values can be overloaded by specific properties below.</li>
* <li>org.ow2.joram.check.jndi.factory: Classname of the JNDI factory (cf java.naming.factory.initial
* property).</li>
* <li>org.ow2.joram.check.jndi.host: Hostname ou IP address of JNDI server.</li>
* <li>org.ow2.joram.check.jndi.port: Listening port of JNDI server.</li>
* <li>org.ow2.joram.check.period: Period between 2 checks, by default 60s.</li>
* <li>org.ow2.joram.check.timeout: Maximum amount of time to wait connecting and receiving
* messages, by default 10s.</li>
* </ul>
* For each JMS connector to monitor there is 4 properties to define:<ul>
* <li>org.ow2.joram.check.cf: JNDI name of the ConnectionFactory to use.</li>
* <li>org.ow2.joram.check.queue: Internal name of JMS destination.</li>
* <li>org.ow2.joram.check.user: User name for authentication, if no defined uses the
* ConnectionFactory default.</li>
* <li>org.ow2.joram.check.pass: Password for authentication, if no defined uses the
* ConnectionFactory default.</li>
* </ul>
* If there are multiple connectors to monitor, suffix each property with 1, 2, etc.
*/
public class Activator implements BundleActivator {
public static final Logger logger = Debug.getLogger(Activator.class.getName());
......@@ -144,7 +169,6 @@ public class Activator implements BundleActivator {
jmsStatus.start();
}
// public synchronized Object lookup(String name) throws NamingException {
// if (logger.isLoggable(BasicLevel.DEBUG))
// logger.log(BasicLevel.DEBUG, "Helper.lookup " + name);
......
/*
* 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.Properties;
import javax.naming.InitialContext;
......@@ -8,31 +32,120 @@ import org.objectweb.util.monolog.api.Logger;
import fr.dyade.aaa.common.Debug;
/**
* This client regularly tests the state of a JMS connector and publishes the results to JMX.
* <br><br>
* usage: java -DJNDI_FILE=./jndi.properties -DPERIOD=1 -DTIMEOUT=5000<br>
* <dd>-DCF=cf -DQUEUE=queue</dd>
* <dd>-jar jmscheck.jar [help]</dd>
* <br>
* Options, set by Java environment variable ("-Dproperty=value" in command line):<ul>
* <li>JNDI_FILE: 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.<br>
* These values can be overloaded by specific properties below.</li>
* <li>JNDI_FACTORY: Classname of the JNDI factory (cf java.naming.factory.initial
* property).</li>
* <li>JNDI_HOST: Hostname ou IP address of JNDI server.</li>
* <li>JNDI_PORT: Listening port of JNDI server.</li>
*
* <li>PERIOD: Period between 2 checks, by default 60s.</li>
* <li>TIMEOUT: Maximum amount of time to wait connecting and receiving messages,
* by default 10s.</li>
* <br>
* For each JMS connector to monitor there is 4 properties to define:<ul>
* <li>CF: JNDI name of the ConnectionFactory to use.</li>
* <li>QUEUE: Internal name of JMS destination.</li>
* <li>USER: User name for authentication, if no defined uses the ConnectionFactory
* default.</li>
* <li>PASS: Password for authentication, if no defined uses the ConnectionFactory
* default.</li>
* </ul>
* If there are multiple connectors to monitor, suffix each property with 1, 2,
* 3, etc.
* <br>
* All these properties can be defined in a file whose name is given by the
* CONF_FILE property. In this case the other properties defined in the command
* line are ignored.
*/
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 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 JNDI_FILE = "JNDI_FILE";
public static final String CF_NAME = "cf";
public static final String JNDI_FACTORY = "JNDI_FACTORY";
public static final String JNDI_HOST = "JNDI_HOST";
public static final 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 USER_NAME = "USER";
public static final String PASSWORD = "PASS";
public static final String QUEUE_NAME = "QUEUE";
public static final String CONF_FILE = "CONF_FILE";
public static final String QUEUE_NAME = "queue";
static int period, timeout;
static JMSStatus jmsStatus;
public static void main(String[] args) throws Exception {
if (args.length != 0) {
System.out.println("usage: java -DJNDI_FILE=./jndi.properties -DPERIOD=1 -DTIMEOUT=5000\n" +
"\t-DCF=cf -DQUEUE=queue\n" +
"\t-jar jmscheck.jar [help]\n");
System.err.println("Options, set by Java environment variable (\"-Dproperty=value\" in command line)");
System.err.println(" - JNDI_FILE: Path of JNDI properties file. If not defined, Joram's default are\n" +
" used. \"fr.dyade.aaa.jndi2.client.NamingContextFactory\" for JNDI Factory,\n" +
" \"localhost\", and 16400 for host and port.\n" +
" These values can be overloaded by specific properties below.");
System.err.println(" - JNDI_FACTORY: Classname of the JNDI factory (cf java.naming.factory.initial\n" +
" property).");
System.err.println(" - JNDI_HOST: Hostname ou IP address of JNDI server.");
System.err.println(" - JNDI_PORT: Listening port of JNDI server.\n");
System.err.println(" - PERIOD: Period between 2 checks, by default 60s.");
System.err.println(" - TIMEOUT: Maximum amount of time to wait connecting and receiving messages,\n" +
" by default 10s.\n");
System.err.println("For each JMS connector to monitor there is 4 properties to define:");
System.err.println(" - CF: JNDI name of the ConnectionFactory to use.");
System.err.println(" - QUEUE: Internal name of JMS destination.");
System.err.println(" - USER: User name for authentication, if no defined uses the ConnectionFactory\n" +
" default.");
System.err.println(" - PASS: Password for authentication, if no defined uses the ConnectionFactory\n" +
" default.");
System.err.println("If there are multiple connectors to monitor, suffix each property with 1, 2, \n" +
" 3, etc.\n");
System.err.println("All these properties can be defined in a file whose name is given by the \n" +
"CONF_FILE property. In this case the other properties defined in the command\n" +
"line are ignored.");
System.exit(0);
}
String conf = System.getProperty(CONF_FILE);
Properties props = null;
if (conf != null) {
try {
props = new Properties();
props.load(new FileInputStream(conf));
} catch (IOException exc) {
System.err.println("Cannot read properties from \"" + conf + "\": " + exc.getMessage());
System.exit(-1);
}
} else {
props = System.getProperties();
}
// Gets the global period.
period = JMSStatus.DFLT_PERIOD;
String value = System.getProperty(GLOBAL_PERIOD);
String value = props.getProperty(GLOBAL_PERIOD);
if (value != null) {
try {
period = Integer.parseInt(value);
......@@ -46,7 +159,7 @@ public class JMSCheck {
// Gets the global timeout.
timeout = JMSStatus.DFLT_TIMEOUT;
value = System.getProperty(GLOBAL_TIMEOUT);
value = props.getProperty(GLOBAL_TIMEOUT);
if (value != null) {
try {
timeout = Integer.parseInt(value);
......@@ -59,16 +172,16 @@ public class JMSCheck {
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);
String jndiFile = props.getProperty(JNDI_FILE);
String jndiFactory = props.getProperty(JNDI_FACTORY);
String jndiHost = props.getProperty(JNDI_HOST);
String jndiPort = props.getProperty(JNDI_PORT);
InitialContext ictx = jmsStatus.getInitialContext(jndiFile, jndiFactory, jndiHost, jndiPort);
createConnector(jmsStatus, "", ictx);
createConnector(props, jmsStatus, "", ictx);
for (int i=1;; i++) {
if (!createConnector(jmsStatus, "." + i, ictx)) break;
if (!createConnector(props, jmsStatus, "." + i, ictx)) break;
}
jmsStatus.start();
......@@ -82,12 +195,12 @@ public class JMSCheck {
* @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);
private static boolean createConnector(Properties props, JMSStatus jmsStatus, String suffix, InitialContext ictx) {
String cfname = props.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);
String user = props.getProperty(USER_NAME + suffix);
String pass = props.getProperty(PASSWORD + suffix);
String qname = props.getProperty(QUEUE_NAME + suffix);
// TODO (AF): Allow to override period and timeout.
jmsStatus.addConnectorStatus(cfname, ictx, user, pass, qname, period, timeout);
......
......@@ -44,12 +44,12 @@ 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());
class JMSConnectorStatus implements JMSConnectorStatusMBean {
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" };
static String[] info = { "RUNNING", "UNREACHABLE" };
/** retryStatusCount == 0 => RUNNING, retryStatusCount > 0 => UNREACHABLE */
private int retryStatusCount = UNREACHABLE;
......@@ -187,6 +187,7 @@ public class JMSConnectorStatus implements JMSConnectorStatusMBean {
ScheduledFuture<?> callableHandle;
/**
* Creates a JMS connectors healthcheck component.
*
* @param jmsStatus Root component.
* @param cfname JNDI name of the ConnectionFactory to use.
......@@ -198,7 +199,7 @@ public class JMSConnectorStatus implements JMSConnectorStatusMBean {
* @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,
JMSConnectorStatus(JMSStatus jmsStatus,
String cfname,
InitialContext ictx,
String user, String pass,
......@@ -242,33 +243,35 @@ public class JMSConnectorStatus implements JMSConnectorStatusMBean {
// --------------------------------------------------------------------------------
private void schedule() {
logger.log(BasicLevel.WARN, "JMSConnectorStatus(" + cfname + ").schedule #1");
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, "JMSConnectorStatus(" + cfname + ").schedule: " + period);
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();
check();
} 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;
/**
* Checks the related JMS connector, creates a connection, then sends and receives a message.
*/
private void check() {
if (logger.isLoggable(BasicLevel.DEBUG))
logger.log(BasicLevel.DEBUG, "JMSConnectorStatus(" + cfname + ").check");
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);
......@@ -286,12 +289,10 @@ public class JMSConnectorStatus implements JMSConnectorStatusMBean {
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());
......@@ -309,31 +310,24 @@ public class JMSConnectorStatus implements JMSConnectorStatusMBean {
}
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);
......
......@@ -34,13 +34,15 @@ 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;
/**
* Root component managing all JMS connector healthcheck components.
*/
public class JMSStatus implements JMSStatusMBean {
public static final Logger logger = Debug.getLogger(JMSStatus.class.getName());
static final Logger logger = Debug.getLogger(JMSStatus.class.getName());
@Override
public int getStatus() {
......@@ -113,10 +115,17 @@ public class JMSStatus implements JMSStatusMBean {
private HashMap<String, JMSConnectorStatus> connectors;
// public void add(String name, JMSConnectorStatus connector) {
// connectors.put(name, connector);
// }
/**
* Adds an healthcheck for the specified JMS connector.
*
* @param cfname JNDI name of the ConnectionFactory to use.
* @param ictx JNDI context allowing to retrieve ConnectionFactory.
* @param user User name for authentication, if no defined uses the ConnectionFactory default.
* @param pass Password for authentication, if no defined uses the ConnectionFactory default.
* @param qname Internal name of JMS destination.
* @param period Period between 2 checks, by default 60s.
* @param timeout Maximum amount of time to wait connecting and receiving messages, by default 10s.
*/
public void addConnectorStatus(String cfname, InitialContext ictx,
String user, String pass, String qname,
int period, int timeout) {
......@@ -130,7 +139,7 @@ public class JMSStatus implements JMSStatusMBean {
static long lastDump = 0L;
public static void checkDump(String name, int retry) {
static void checkDump(String name, int retry) {
if ((dumpFilePath == null) || (threshold <= 0) || (delay <= 0))
return;
......@@ -151,11 +160,12 @@ public class JMSStatus implements JMSStatusMBean {
private String name;
public String getName() {
String getName() {
return name;
}
/**
* Creates a JMS connectors healthcheck root component.
*
* @param name Name of component, used to define the domain of the JMX MBeans.
* @param period Default period of activation for checks (seconds).
......@@ -177,6 +187,7 @@ public class JMSStatus implements JMSStatusMBean {
}
/**
* Creates the JNDI context to use.
*
* @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.
......@@ -224,6 +235,9 @@ public class JMSStatus implements JMSStatusMBean {
// Life cycle
// --------------------------------------------------------------------------------
/**
* Starts the component and all connector's healthcheck defined.
*/
@Override
public void start() throws Exception {
if (scheduler == null || scheduler.isTerminated())
......@@ -236,6 +250,9 @@ public class JMSStatus implements JMSStatusMBean {
registerMBean();
}
/**
* Stops the component and all connector's healthcheck defined.
*/
@Override
public void stop() throws Exception {
for (JMSConnectorStatus status : connectors.values()) {
......@@ -253,7 +270,7 @@ public class JMSStatus implements JMSStatusMBean {
static String mbeanName = "type=healthcheck";
public void registerMBean() {
void registerMBean() {
try {
MXWrapper.registerMBean(this, name, mbeanName);
} catch (Exception e) {
......@@ -261,7 +278,7 @@ public class JMSStatus implements JMSStatusMBean {
}
}
public void unregisterMBean() {
void unregisterMBean() {
try {
MXWrapper.unregisterMBean(name, mbeanName);
} catch (Exception e) {
......@@ -274,7 +291,7 @@ public class JMSStatus implements JMSStatusMBean {
dumpServerState(path, null);
}
public static void dumpServerState(String path, String cause) {
static void dumpServerState(String path, String cause) {
SCServer.dumpServerState(path, cause);
}
}
......@@ -22,7 +22,6 @@
package org.ow2.joram.tools.jmscheck;
public interface JMSStatusMBean {
/**
* Starts the JMS healthcheck component.
*
......@@ -37,25 +36,80 @@ public interface JMSStatusMBean {
*/
void stop() throws Exception;
/**
* Returns the global healthcheck indication, 0 if all connectors are running, 1 otherwise.
*
* @return the global healthcheck indication.
*/
int getStatus();
/**
* Returns a user friendly indication of the global healthcheck: RUNNING or UNREACHABLE.
*
* @return a user friendly indication of the global healthcheck
*/
String getStatusInfo();
/**
* Returns the activation period in seconds.
*
* @return the activation period in seconds.
*/
int getPeriod();
/**
* Sets the activation period in seconds.
*
* @param period the activation period in seconds.
*/
void setPeriod(int period);
/**
* Returns the timeout used during check.
*
* @return the timeout used during check.
*/
int getTimeout();
/**
* Sets the timeout used during check.
*
* @param timeout the timeout used during check.
*/
void setTimeout(int timeout);
/**
* Returns the minimum number of failures before generating a dump.
*
* @return the minimum number of failures before generating a dump.
*/
int getThreshold();
/**
* Sets the minimum number of failures before generating a dump.
*
* @param threshold the minimum number of failures before generating a dump.
*/
void setThreshold(int threshold);
/**
* Returns the minimal delay between 2 dumps.
*
* @return the minimal delay between 2 dumps.
*/
int getDelay();
/**
* Sets the minimal delay between 2 dumps.
*
* @param delay the minimal delay between 2 dumps.
*/
void setDelay(int delay);
/**
* Generates a server dump.
*