/* * RHQ Management Platform * Copyright (C) 2005-2008 Red Hat, Inc. * All rights reserved. * * 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., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.enterprise.communications.command.client; import java.io.File; import java.io.IOException; import java.util.LinkedList; import org.testng.annotations.Test; import org.rhq.enterprise.communications.command.Command; import org.rhq.enterprise.communications.command.CommandResponse; import org.rhq.enterprise.communications.command.CommandType; import org.rhq.enterprise.communications.command.impl.generic.GenericCommand; /** * Tests the sender. * * @author John Mazzitelli */ @Test(groups = "comm.client") public class ClientCommandSenderTest { /** * Tests sending alot of volatile and guaranteed messages - since no errors occur, no persisting will occur. * * @throws Exception */ public void testSendAlot() throws Exception { DummyRemoteCommunicator comm = new DummyRemoteCommunicator(); ClientCommandSenderConfiguration config = createConfig(); int num_to_send = 250000; config.queueSize = num_to_send; config.maxConcurrent = 100; config.defaultTimeoutMillis = -1; ClientCommandSender sender = new ClientCommandSender(comm, config); try { sender.startSending(); for (int i = 0; i < num_to_send; i++) { if ((i % 2) == 0) { sender.sendAsynchGuaranteed(createGenericCommand(), null); } else { sender.sendAsynch(createGenericCommand(), null); } } // wait for our commands to be sent; but only wait at most a few seconds (so our test doesn't hang if this fails) for (int i = 0; (i < 30) && (comm.getSentCount() < num_to_send); i++) { Thread.sleep(1000L); } sender.stopSending(false); assert sender.drainQueuedCommands().size() == 0 : "still some commands left to be sent - we didn't wait long enough"; assert comm.getSentCount() == num_to_send : "did not send all commands: " + comm.getSentCount(); assert comm.getSentSuccessfulCount() == num_to_send; } finally { sender.stopSending(false); } return; } /** * Tests sending commands (guaranteed and volatile), and ensuring they get requeue after draining them and * rebuilding the sender. * * @throws Exception */ public void testReconstituteQueue() throws Exception { DummyRemoteCommunicator comm = new DummyRemoteCommunicator(); ClientCommandSenderConfiguration config = createConfig(); config.queueSize = 1000; config.commandSpoolFileMaxSize = 2000000L; config.maxConcurrent = 10; File cmd_spool_file = getPersistentFifoFile(true); ClientCommandSender sender = new ClientCommandSender(comm, config); PersistentFifo fifo = new PersistentFifo(cmd_spool_file, config.commandSpoolFileMaxSize, config.commandSpoolFilePurgePercentage, false); // sent 500 guaranteed and 500 volatile commands, ensure we send all of them try { for (int i = 0; i < 1000; i++) { Command cmd_to_send = createGenericCommand(); cmd_to_send.setParameterValue("num", String.valueOf(i)); if ((i % 2) == 0) { sender.sendAsynchGuaranteed(cmd_to_send, null); } else { sender.sendAsynch(cmd_to_send, null); } } // drain the command from this sender - we'll reconstitute a new sender with these // only volatile commands are returned - guaranteed commands are spooled (why did I do this? I lost the FIFO order of the commands now) // after drained, the only messages this sender will send are the spooled/guaranteed commands LinkedList<Runnable> drained_commands = sender.drainQueuedCommands(); assert fifo.count() == 500 : "did not persist enough: " + fifo.count(); assert drained_commands.size() == 500 : "missing some volatile commands: " + drained_commands.size(); // create a new sender and reconsitute the queue with our old volatile commands, persisted file has the rest sender = new ClientCommandSender(comm, config, drained_commands); sender.startSending(); // wait for our commands to be sent; but only wait at most a few seconds (so our test doesn't hang if this fails) for (int i = 0; (i < 5) && (comm.getSentCount() < 1000); i++) { Thread.sleep(2000L); } sender.stopSending(true); assert sender.drainQueuedCommands().size() == 0 : "still some commands left to be sent - we didn't wait long enough"; assert comm.getSentCount() == 1000 : "should have been able to send all commands with two senders: " + comm.getSentCount(); assert comm.getSentSuccessfulCount() == 1000; assert fifo.isEmpty(); assert fifo.count() == 0; // just checking - making sure isEmpty was right } finally { sender.stopSending(false); getPersistentFifoFile(true); } return; } /** * Tests sending a guaranteed command that takes too long to complete and therefore times out. We will stop the * sender to see that the command will be persisted after the sender is stopped. * * @throws Exception */ public void testSendGuaranteedThatTimesOutThenStopSending() throws Exception { DummyRemoteCommunicator comm = new DummyRemoteCommunicator(); GenericCommand command = createGenericCommand(); ClientCommandSenderConfiguration config = createConfig(); config.retryInterval = 50L; // retry really fast config.defaultTimeoutMillis = 500L; // default will be less than the time the comm.send will return comm.setSleepPeriod(1000L); // simulate the server taking 1sec to process the request File cmd_spool_file = getPersistentFifoFile(true); ClientCommandSender sender = new ClientCommandSender(comm, config); PersistentFifo fifo = new PersistentFifo(cmd_spool_file, config.commandSpoolFileMaxSize, config.commandSpoolFilePurgePercentage, false); try { sender.startSending(); assert comm.getSentCount() == 0 : "should not have sent any command yet"; sender.sendAsynchGuaranteed(command, null); Thread.sleep(2000L); // let it retry a few times sender.stopSending(false); assert comm.getSentSuccessfulCount() == 0 : "should not have been able to send the command successfully"; long sent_count = comm.getSentCount(); assert comm.getSentCount() > 1 : "should have attempted to resend the command multiple times"; Thread.sleep(1000L); // wait a bit and see that we truely did stop sending commands assert sent_count == comm.getSentCount() : "should not have continued sending commands while stopped: " + sent_count + "!=" + comm.getSentCount(); assert comm.getSentSuccessfulCount() == 0 : "should not have been able to send the command successfully: " + comm.getSentSuccessfulCount(); assert fifo.count() == 1 : "not sending so we should have spooled that guaranteed command to disk"; } finally { sender.stopSending(false); getPersistentFifoFile(true); } return; } /** * Tests sending a guaranteed command that takes too long to complete and therefore times out. * * @throws Exception */ public void testSendGuaranteedThatTimesOut() throws Exception { DummyRemoteCommunicator comm = new DummyRemoteCommunicator(); GenericCommand command = createGenericCommand(); ClientCommandSenderConfiguration config = createConfig(); config.retryInterval = 50L; // retry really fast config.defaultTimeoutMillis = 1000L; // default will be less than the time the comm.send will return comm.setSleepPeriod(1500L); getPersistentFifoFile(true); ClientCommandSender sender = new ClientCommandSender(comm, config); try { sender.startSending(); assert comm.getSentCount() == 0 : "should not have sent any command yet"; sender.sendAsynchGuaranteed(command, null); Thread.sleep(2500L); // let it retry a few times comm.setSleepPeriod(0L); // let it finish now - it won't timeout anymore Thread.sleep(1000L); // wait for it to execute one last time (this time successfully) assert comm.getSentCount() > 1 : "should have attempted to resend the command multiple times"; assert comm.getSentSuccessfulCount() == 1 : "should have sent the command successfully by now"; Thread.sleep(1000L); assert comm.getSentSuccessfulCount() == 1 : "should not have sent the command more than once"; } finally { sender.stopSending(false); getPersistentFifoFile(true); } return; } /** * Tests sending a guaranteed command that takes too long to complete and therefore times out. The retry interval * will be long. * * @throws Exception */ public void testSendGuaranteedThatTimesOutWithLongRetryInterval() throws Exception { DummyRemoteCommunicator comm = new DummyRemoteCommunicator(); GenericCommand command = createGenericCommand(); ClientCommandSenderConfiguration config = createConfig(); config.retryInterval = 5000L; config.defaultTimeoutMillis = 750L; // default will be less than the time the comm.send will return comm.setSleepPeriod(1250L); getPersistentFifoFile(true); ClientCommandSender sender = new ClientCommandSender(comm, config); try { sender.startSending(); assert comm.getSentCount() == 0 : "should not have sent any command yet"; sender.sendAsynchGuaranteed(command, null); Thread.sleep(4000L); // not enough time to retry comm.setSleepPeriod(0L); // let it finish - it won't timeout anymore assert comm.getSentCount() == 1 : "should not have attempted to resend the command, only sent this once"; Thread.sleep(3500L); // takes us past the retry interval plus the simulated processing time, it'll retry and be successful assert comm.getSentCount() == 2 : "should have attempted to resend the command one time, only sent this twice"; assert comm.getSentSuccessfulCount() == 1 : "should have sent the command successfully by now"; Thread.sleep(6000L); assert comm.getSentCount() == 2 : "should not have attempted to resend the command any more"; assert comm.getSentSuccessfulCount() == 1 : "should not have sent the command more than once successfully"; } finally { sender.stopSending(false); getPersistentFifoFile(true); } return; } /** * Tests sending a guaranteed command while the sender is not sending. After starting up the sender, the command * (which should have been persisted) will be sent. * * @throws Exception */ public void testSendGuaranteedWhileNotSending() throws Exception { DummyRemoteCommunicator comm = new DummyRemoteCommunicator(); GenericCommand command = createGenericCommand(); ClientCommandSenderConfiguration config = createConfig(); File cmd_spool_file = getPersistentFifoFile(true); ClientCommandSender sender = new ClientCommandSender(comm, config); try { try { sender.sendSynch(command); assert false : "Not sending yet, should not have been allowed to send a sync command"; } catch (Exception ok) { } sender.sendAsynchGuaranteed(command, null); PersistentFifo fifo = new PersistentFifo(cmd_spool_file, config.commandSpoolFileMaxSize, config.commandSpoolFilePurgePercentage, false); assert fifo.count() == 1 : "not sending so we should have spooled that guaranteed command to disk"; assert comm.getSentCount() == 0 : "should not have sent any command yet"; sender.startSending(); Thread.sleep(1000L); // give it time to dequeue and send; there is no throttling enabled so the sending should happen fast assert fifo.count() == 0 : "the command should have been unspooled after the sender was started"; assert comm.getSentCount() == 1 : "should have sent the command by now"; assert comm.getSentSuccessfulCount() == 1 : "should have sent the command by now"; } finally { sender.stopSending(false); getPersistentFifoFile(true); } return; } /** * Tests sending a guaranteed command while the sender is not sending with a non-serializable callback. The command * should still be sent even if the callback cannot be called. * * @throws Exception */ public void testSendGuaranteedWhileNotSendingWithNonSerializableCallback() throws Exception { DummyRemoteCommunicator comm = new DummyRemoteCommunicator(); GenericCommand command = createGenericCommand(); ClientCommandSenderConfiguration config = createConfig(); File cmd_spool_file = getPersistentFifoFile(true); ClientCommandSender sender = new ClientCommandSender(comm, config); try { try { sender.sendSynch(command); assert false : "Not sending yet, should not have been allowed to send a sync command"; } catch (Exception ok) { } sender.sendAsynchGuaranteed(command, new CommandResponseCallback() { private static final long serialVersionUID = 1L; private final Thread notSerializable = Thread.currentThread(); public void commandSent(CommandResponse response) { assert notSerializable != null : "This should never get here"; } }); PersistentFifo fifo = new PersistentFifo(cmd_spool_file, config.commandSpoolFileMaxSize, config.commandSpoolFilePurgePercentage, false); assert fifo.count() == 1 : "not sending so we should have spooled that guaranteed command to disk"; assert comm.getSentCount() == 0 : "should not have sent any command yet"; sender.startSending(); Thread.sleep(1000L); // give it time to dequeue and send; there is no throttling enabled so the sending should happen fast assert fifo.count() == 0 : "the command should have been unspooled after the sender was started"; assert comm.getSentCount() == 1 : "should have sent the command by now"; assert comm.getSentSuccessfulCount() == 1 : "should have sent the command by now"; } finally { sender.stopSending(false); getPersistentFifoFile(true); } return; } /** * Creates a config - caller can change settings as appropriate. * * @return the config */ private ClientCommandSenderConfiguration createConfig() { ClientCommandSenderConfiguration config = new ClientCommandSenderConfiguration(); config.dataDirectory = new File(System.getProperty("java.io.tmpdir")); config.defaultTimeoutMillis = 60000L; config.maxConcurrent = 1; config.commandSpoolFileName = "command-spool.dat"; config.commandSpoolFileMaxSize = 100000L; config.commandSpoolFilePurgePercentage = 90; config.serverPollingIntervalMillis = 0L; config.commandSpoolFileCompressData = false; config.retryInterval = 10000L; config.queueSize = 5; // queue throttling config.enableQueueThrottling = false; config.queueThrottleMaxCommands = 2L; config.queueThrottleBurstPeriodMillis = 1000L; // send throttling config.enableSendThrottling = false; config.sendThrottleMaxCommands = 2L; config.sendThrottleQuietPeriodDurationMillis = 1000L; return config; } /** * Creates a simple test command. * * @return test command */ private GenericCommand createGenericCommand() { GenericCommand cmd = new GenericCommand(); cmd.setCommandType(new CommandType("test", 1)); return cmd; } /** * Returns the spool file. Caller can ask to start with an empty one by passing in <code>true</code>. * * @param delete_it if caller wants to make sure the persistent file is empty, set this to <code>true</code> * * @return the spool file where command/callback pairs are stored */ private File getPersistentFifoFile(boolean delete_it) { // we know the file name that the command spool file is - that's always the same and its in the data directory File ret_file = new File(createConfig().dataDirectory, "command-spool.dat"); if (delete_it) { // in case we can't outright delete it, let's first empty it try { new PersistentFifo(ret_file, 10000L, 0, false).initializeEmptyFile(); } catch (IOException ignore) { } // now try to delete it ret_file.delete(); } return ret_file; } }