/* * ALMA - Atacama Large Millimiter Array (c) European Southern Observatory, 2006 * * 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 (at your option) * 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 * */ package alma.lasersource.test; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Random; import java.util.logging.Level; import cern.laser.source.alarmsysteminterface.FaultState; import cern.laser.source.alarmsysteminterface.impl.ASIMessageHelper; import cern.laser.source.alarmsysteminterface.impl.XMLMessageHelper; import cern.laser.source.alarmsysteminterface.impl.message.ASIMessage; import com.cosylab.acs.jms.ACSJMSMessageEntity; import alma.acs.component.client.ComponentClientTestCase; import alma.acs.nc.AcsEventSubscriber; import alma.acscommon.ACS_NC_DOMAIN_ALARMSYSTEM; import alma.acsnc.EventDescription; import alma.alarmsystem.source.ACSAlarmSystemInterface; import alma.alarmsystem.source.ACSAlarmSystemInterfaceFactory; import alma.alarmsystem.source.ACSFaultState; /** * A stress test: it sends a great number of alarm sources and checks * - if all of them have been published * - they arrived in the right order * - their contents match with the sent fault states * * The FaultStates are sent in the same order their are published if the sending * is done with one source per each FS. * If the source is only one for all the alarms, then it applies a kind of * caching and the FSs do not arrive in the same order the are published. * In the latter case, the FSs published and the FSs received are both sorted * by FM and compared. * * @author acaproni * */ public class MultiSourceStressTest extends ComponentClientTestCase implements AcsEventSubscriber.Callback<ACSJMSMessageEntity> { /** * The relevant fields of a fault state to compare against * the fault states received from the NC * * @author acaproni * */ private class MiniFaultState implements Comparable<MiniFaultState> { // The fields of the fault state public final String FF, FM; public final int FC; public final long msec; public final String description; // ACTIVE/Terminate public final Timestamp timestamp; public MiniFaultState() { FF=MultiSourceStressTest.FF+(countFF++); FM=MultiSourceStressTest.FM+(countFM++); FC=Math.abs(rnd.nextInt()); msec=System.currentTimeMillis(); timestamp=new Timestamp(msec); if (rnd.nextInt()%2==0) { description=FaultState.ACTIVE; } else { description=FaultState.TERMINATE; } assertNotNull(FF); assertNotNull(FM); assertNotNull(description); assertNotNull(timestamp); } /** * @see <code>java.lang.Comparable</code> */ @Override public int compareTo(MiniFaultState o) { return FM.compareTo(o.FM); } } /** * A class to store the FaultStates received from the NC * This calls is needed to reimplement Comparable * * @author acaproni * */ private class FaultStateReceived implements Comparable { public final FaultState faultState; public FaultStateReceived(FaultState fs) { if (fs==null) { throw new IllegalArgumentException("The FaultState can't be null"); } faultState=fs; } @Override public int compareTo(Object o) { if (! (o instanceof FaultState)) { } return faultState.getMember().compareTo(((FaultStateReceived)o).faultState.getMember()); } } // The consumer private volatile AcsEventSubscriber<ACSJMSMessageEntity> m_consumer; // The number of fault states to send with the same source private static final int NUM_OF_FS_TO_SEND_ONE_SOURCE = 10000; // The number of fault states to send with the more sources // Changing this number remember that CERN code SynchroBuffer // create a thread per each source private static final int NUM_OF_FS_TO_SEND_MORE_SOURCES = 1000; // The NC name to listen for published fault states private static final String m_channelName = "CMW.ALARM_SYSTEM.ALARMS.SOURCES.ALARM_SYSTEM_SOURCES"; // The fault states have a random FF and a random FM each of which // is generated by appending a random number to the following strings private static String FF = "Family"; private static String FM = "Member"; // The random number generator to create FF and FM private static Random rnd = new Random(System.currentTimeMillis()); // The source private ACSAlarmSystemInterface alarmSource; // The fault states received from the NC private List<FaultStateReceived> receivedFS; // The fault state ready to be published private List<MiniFaultState> statesToPublish; /** * This is to generate the name of the fault member sequentially * by postponing the number to a string */ private static int countFM=0; /** * This is to generate the name of the fault family sequentially * by postponing the number to a string */ private static int countFF=100000; /** * Constructor * @throws Exception */ public MultiSourceStressTest() throws Exception { super("Source stress test"); } /** * * @see alma.acs.component.client.ComponentClientTestCase#tearDown() */ protected void setUp() throws Exception { // TODO Auto-generated method stub super.setUp(); assertNotNull(getContainerServices()); m_consumer = getContainerServices().createNotificationChannelSubscriber(m_channelName, ACS_NC_DOMAIN_ALARMSYSTEM.value, ACSJMSMessageEntity.class); assertNotNull("Error instantiating the consumer", m_consumer); m_consumer.addSubscription(this); m_consumer.startReceivingEvents(); alarmSource = ACSAlarmSystemInterfaceFactory.createSource(); assertNotNull("Error instantiating the source",alarmSource); receivedFS= Collections.synchronizedList(new ArrayList<FaultStateReceived>()); assertNotNull(receivedFS); } /** * * @see alma.acs.component.client.ComponentClientTestCase#tearDown() */ protected void tearDown() throws Exception { m_consumer.disconnect(); super.tearDown(); } @Override public synchronized void receive(ACSJMSMessageEntity msg, EventDescription eventDescrip) { ASIMessage asiMsg = null; try { asiMsg = XMLMessageHelper.unmarshal(msg.text); } catch (Exception ex) { // TODO: How to handle this XML parse/validation exception? In the old implementation using NC Consumer // the Consumer would have simply logged this exception. Should we take any action instead? m_logger.log(Level.INFO, "Failed to parse ASIMessage XML", ex); } Collection<FaultState> faultStates = ASIMessageHelper.unmarshal(asiMsg); assertNotNull(faultStates); for (FaultState fs: faultStates) { assertNotNull(fs); receivedFS.add(new FaultStateReceived(fs)); } } @Override public Class<ACSJMSMessageEntity> getEventType() { return ACSJMSMessageEntity.class; } /** * Send a fault state to the NC. * It uses the global source or build a new one depending on the * parameter * * @param mfs The fault state to publish * @param sameSource If true the same source is used to send the alarm * if true a new source is built and the fault state is * sent using this new source */ private void send(MiniFaultState mfs, boolean sameSource) throws Exception { assertNotNull(mfs); ACSFaultState fs = ACSAlarmSystemInterfaceFactory.createFaultState(mfs.FF, mfs.FM, mfs.FC); assertNotNull("Error instantiating the FS",fs); fs.setDescriptor(mfs.description); fs.setUserTimestamp(mfs.timestamp); if (sameSource) { alarmSource.push(fs); } else { ACSAlarmSystemInterface newSource= ACSAlarmSystemInterfaceFactory.createSource(); assertNotNull(newSource); newSource.push(fs); } } private void checkFaultStates(MiniFaultState sent, FaultState recv) throws Exception { assertEquals(sent.FF, recv.getFamily()); assertEquals(sent.FM, recv.getMember()); assertEquals(sent.FC, recv.getCode()); assertEquals(sent.description, recv.getDescriptor()); assertEquals(sent.timestamp, recv.getUserTimestamp()); } /** * Build the data to publish and check for correctness * * @param len The number of items to biuld and put in the array */ private void buildData(int len) throws Exception { statesToPublish= Collections.synchronizedList(new ArrayList<MiniFaultState>(len)); assertNotNull(statesToPublish); // Build the array of state to publish for (int t=0; t<len; t++) { MiniFaultState fs = new MiniFaultState(); assertNotNull(fs); statesToPublish.add(fs); } } /** * Wait until all the fault states are received or a timeout happened. * * @throws Exception in case of timeout */ private void waitForFSs() throws Exception { int timeout = 60; // timeout in secs int count=0; int old=0; // The number of items read in the previous iteration // Wait for all the alarms to be in the vector while (receivedFS.size()<statesToPublish.size() && count<2*timeout) { if (old!=receivedFS.size()) { count=0; old=receivedFS.size(); } try { Thread.sleep(500); count++; } catch (Exception e) {} } assertEquals(statesToPublish.size(), receivedFS.size()); } /** * Test by sending all the fault states using the same source. * When all the alarms have arrived it checks for their * correctness * * @throws Exception */ public void testStressDifferentSources() throws Exception { buildData(NUM_OF_FS_TO_SEND_MORE_SOURCES); // Send the alarms for (int t=0; t<statesToPublish.size(); t++) { send(statesToPublish.get(t),false); } waitForFSs(); Collections.sort(receivedFS); Collections.sort(statesToPublish); for (int t=0; t< statesToPublish.size(); t++) { checkFaultStates(statesToPublish.get(t), receivedFS.get(t).faultState); } } }