/**
* Copyright 2012 Comcast Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.comcast.cqs.test.unit;
import static org.junit.Assert.*;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.services.sqs.AmazonSQS;
import com.amazonaws.services.sqs.model.DeleteMessageRequest;
import com.amazonaws.services.sqs.model.ReceiveMessageRequest;
import com.amazonaws.services.sqs.model.ReceiveMessageResult;
import com.amazonaws.services.sqs.model.SendMessageRequest;
import com.amazonaws.services.sqs.model.SetQueueAttributesRequest;
import com.comcast.cmb.test.tools.CMBAWSBaseTest;
public class CQSLongPollTest extends CMBAWSBaseTest {
private static HashMap<String, String> attributeParams = new HashMap<String, String>();
private static Map<String, String> messageMap;
private static String queueUrl;
static {
attributeParams.put("MessageRetentionPeriod", "600");
attributeParams.put("VisibilityTimeout", "30");
}
private class MessageSender extends Thread {
public void run() {
try {
logger.info("sender sleeping for 5 sec");
sleep(5000);
cqs1.sendMessage(new SendMessageRequest(queueUrl, "test message"));
logger.info("test message sent");
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
/**
* Simple functional test: Call receive() with 20 sec TO, then 5 sec later call send()
* and check if message is received exactly once after around 5 sec.
*/
private void testLongPoll(AmazonSQS receiverSqs) {
messageMap = new HashMap<String, String>();
try {
queueUrl = getQueueUrl(1, USR.USER1);
cqs1.setQueueAttributes(new SetQueueAttributesRequest(queueUrl, attributeParams));
Thread.sleep(1000);
ReceiveMessageRequest receiveMessageRequest = new ReceiveMessageRequest();
receiveMessageRequest.setQueueUrl(queueUrl);
receiveMessageRequest.setMaxNumberOfMessages(1);
receiveMessageRequest.setWaitTimeSeconds(20);
long start = System.currentTimeMillis();
(new MessageSender()).start();
logger.info("calling receive message");
ReceiveMessageResult receiveMessageResult = receiverSqs.receiveMessage(receiveMessageRequest);
logger.info("receive message returns");
long end = System.currentTimeMillis();
assertTrue("No message received", receiveMessageResult.getMessages().size() == 1);
assertTrue("Wrong message content", receiveMessageResult.getMessages().get(0).getBody().equals("test message"));
assertTrue("Message came back too fast: " + (end-start) + " ms", end-start >= 4750);
assertTrue("Message came back too slow: " + (end-start) + " ms", end-start <= 5250);
} catch (Exception ex) {
ex.printStackTrace();
fail("failed long poll test: " + ex.getMessage());
}
}
/**
* Simple functional test: Call receive() on a queue with a default 15 sec wait time, then 5 sec later call
* send() and check if message is received exactly once after around 5 sec.
*/
@Test
public void testLongPollQueue() {
messageMap = new HashMap<String, String>();
try {
queueUrl = getQueueUrl(1, USR.USER1);
cqs1.setQueueAttributes(new SetQueueAttributesRequest(queueUrl, attributeParams));
Thread.sleep(1000);
SetQueueAttributesRequest setQueueAttributesRequest = new SetQueueAttributesRequest();
setQueueAttributesRequest.setQueueUrl(queueUrl);
attributeParams.put("ReceiveMessageWaitTimeSeconds", "15");
setQueueAttributesRequest.setAttributes(attributeParams);
cqs1.setQueueAttributes(setQueueAttributesRequest);
ReceiveMessageRequest receiveMessageRequest = new ReceiveMessageRequest();
receiveMessageRequest.setQueueUrl(queueUrl);
receiveMessageRequest.setMaxNumberOfMessages(1);
long start = System.currentTimeMillis();
(new MessageSender()).start();
logger.info("calling receive message");
// note: we are calling receivemessage without waittime set and yet we should see long poll
// behavior because the queue has a default wait time set
ReceiveMessageResult receiveMessageResult = cqs1.receiveMessage(receiveMessageRequest);
logger.info("receive message returns");
long end = System.currentTimeMillis();
assertTrue("No message received", receiveMessageResult.getMessages().size() == 1);
assertTrue("Wrong message content", receiveMessageResult.getMessages().get(0).getBody().equals("test message"));
assertTrue("Message came back too fast: " + (end-start) + " ms", end-start >= 4750);
assertTrue("Message came back too slow: " + (end-start) + " ms", end-start <= 5250);
} catch (Exception ex) {
logger.error("test failed", ex);
fail(ex.getMessage());
}
}
@Test
public void testLongPoll() {
testLongPoll(cqs1);
}
@Test
public void testLongPollWithLoadBalancer() {
if (this.cqsAlt == null) {
logger.info("skipping load balanced long poll test due to missing alternate sqs service url");
return;
}
testLongPoll(cqsAlt);
}
/**
* Simple functional test: Like testLongPoll above but in this test send() happens before
* receive(). Here we ensure that the message is received immediately despite a long poll
* timeout of 20 sec.
*/
@Test
public void testLongPollNoDelay() {
messageMap = new HashMap<String, String>();
try {
queueUrl = getQueueUrl(1, USR.USER1);
cqs1.setQueueAttributes(new SetQueueAttributesRequest(queueUrl, attributeParams));
Thread.sleep(1000);
ReceiveMessageRequest receiveMessageRequest = new ReceiveMessageRequest();
receiveMessageRequest.setQueueUrl(queueUrl);
receiveMessageRequest.setMaxNumberOfMessages(1);
receiveMessageRequest.setWaitTimeSeconds(20);
long start = System.currentTimeMillis();
cqs1.sendMessage(new SendMessageRequest(queueUrl, "test message"));
logger.info("calling receive message");
ReceiveMessageResult receiveMessageResult = cqs1.receiveMessage(receiveMessageRequest);
logger.info("receive message returns");
long end = System.currentTimeMillis();
assertTrue("No message received", receiveMessageResult.getMessages().size() == 1);
assertTrue("Wrong message content", receiveMessageResult.getMessages().get(0).getBody().equals("test message"));
assertTrue("Message came back too slow: " + (end-start) + " ms", end-start <= 250);
} catch (Exception ex) {
logger.error("test failed", ex);
fail(ex.getMessage());
}
}
/**
* Test if long poll calls timeout after desired periods of wait time (e.g. after 1, 5, 20 sec) and if
* the normal empty response comes back indicating no messages available.
*/
private void testLongPollTimeout(int timeoutSecs) {
messageMap = new HashMap<String, String>();
try {
queueUrl = getQueueUrl(1, USR.USER1);
cqs1.setQueueAttributes(new SetQueueAttributesRequest(queueUrl, attributeParams));
Thread.sleep(1000);
ReceiveMessageRequest receiveMessageRequest = new ReceiveMessageRequest();
receiveMessageRequest.setQueueUrl(queueUrl);
receiveMessageRequest.setMaxNumberOfMessages(1);
receiveMessageRequest.setWaitTimeSeconds(timeoutSecs);
long start = System.currentTimeMillis();
logger.info("calling receive message");
ReceiveMessageResult receiveMessageResult = cqs1.receiveMessage(receiveMessageRequest);
logger.info("receive message returns");
long end = System.currentTimeMillis();
assertTrue("Unexpected message received", receiveMessageResult.getMessages().size() == 0);
assertTrue("Receive came back too fast: " + (end-start) + " ms", end-start >= timeoutSecs*1000-100);
assertTrue("Receive came back too slow: " + (end-start) + " ms", end-start <= timeoutSecs*1000+100);
} catch (Exception ex) {
logger.error("test failed", ex);
fail(ex.getMessage());
}
}
@Test
public void testLongPollTimeout20() {
testLongPollTimeout(20);
}
@Test
public void testLongPollTimeout5() {
testLongPollTimeout(5);
}
@Test
public void testLongPollTimeout1() {
testLongPollTimeout(1);
}
private class MultiMessageSender extends Thread {
int count;
int delay;
public MultiMessageSender(int count, int delay) {
this.count = count;
this.delay = delay;
}
public void run() {
try {
Thread.sleep(delay);
for (int i=0; i<count; i++) {
cqs1.sendMessage(new SendMessageRequest(queueUrl, "test message " + i));
}
} catch (Exception ex) {
logger.error("error", ex);
}
}
}
/**
* Single-threaded load test, with one thread sending 5000 messages and another thread receiving
* messages. Test verifies that 5000 unique messages are received with no duplicates (all 5000
* messages have different content to spot duplicates). Test can be benchmarked against sending
* and receiving 5000 messages without using the long poll feature (WaitTime parameter not set).
*/
private void testLongPollLoad(int timeoutSecs) {
messageMap = new HashMap<String, String>();
// set timeoutSecs to 0 to test traditional polling receives (mainly for benchmarking)
try {
queueUrl = getQueueUrl(1, USR.USER1);
cqs1.setQueueAttributes(new SetQueueAttributesRequest(queueUrl, attributeParams));
(new MultiMessageSender(5000,0)).start();
int messageCounter = 0;
long begin = System.currentTimeMillis();
while (messageCounter < 5000) {
ReceiveMessageRequest receiveMessageRequest = new ReceiveMessageRequest();
receiveMessageRequest.setQueueUrl(queueUrl);
receiveMessageRequest.setMaxNumberOfMessages(1);
if (timeoutSecs > 0) {
receiveMessageRequest.setWaitTimeSeconds(timeoutSecs);
}
ReceiveMessageResult receiveMessageResult = cqs1.receiveMessage(receiveMessageRequest);
messageCounter += receiveMessageResult.getMessages().size();
if (receiveMessageResult.getMessages().size() == 1) {
messageMap.put(receiveMessageResult.getMessages().get(0).getBody(), "");
DeleteMessageRequest deleteMessageRequest = new DeleteMessageRequest();
deleteMessageRequest.setQueueUrl(queueUrl);
deleteMessageRequest.setReceiptHandle(receiveMessageResult.getMessages().get(0).getReceiptHandle());
cqs1.deleteMessage(deleteMessageRequest);
}
}
long end = System.currentTimeMillis();
logger.info("duration=" + (end-begin));
assertTrue("wrong number of messages: " + messageMap.size(), messageMap.size() == 5000);
} catch (Exception ex) {
logger.error("test failed", ex);
fail(ex.getMessage());
}
}
@Test
public void testLongPollLoadTO20() {
testLongPollLoad(20);
}
@Test
public void testLongPollLoadTO0() {
testLongPollLoad(0);
}
/**
* Functional test to check if correct errors are produced for invalid parameters. So far we only
* test WaitSeconds > 20. Other tests could include WaitSeconds < 1 or WaitSeconds not an integer.
*/
@Test
public void testInvalidParameters() {
messageMap = new HashMap<String, String>();
try {
queueUrl = getQueueUrl(1, USR.USER1);
cqs1.setQueueAttributes(new SetQueueAttributesRequest(queueUrl, attributeParams));
Thread.sleep(1000);
ReceiveMessageRequest receiveMessageRequest = new ReceiveMessageRequest();
receiveMessageRequest.setQueueUrl(queueUrl);
receiveMessageRequest.setMaxNumberOfMessages(1);
// timeout > 20 sec, should fail
receiveMessageRequest.setWaitTimeSeconds(21);
boolean failed = false;
try {
cqs1.receiveMessage(receiveMessageRequest);
} catch (AmazonServiceException ex) {
assertTrue("Wrong error message: " + ex.getErrorCode(), ex.getErrorCode().equals("InvalidParameterValue"));
failed = true;
}
assertTrue("Didn't fail", failed);
receiveMessageRequest.setWaitTimeSeconds(-1);
failed = false;
try {
cqs1.receiveMessage(receiveMessageRequest);
} catch (AmazonServiceException ex) {
assertTrue("Wrong error message: " + ex.getErrorCode(), ex.getErrorCode().equals("InvalidParameterValue"));
failed = true;
}
assertTrue("Didn't fail", failed);
} catch (Exception ex) {
logger.error("test failed", ex);
fail(ex.getMessage());
}
}
private class MessageReceiver extends Thread {
public void run() {
try {
ReceiveMessageRequest receiveMessageRequest = new ReceiveMessageRequest();
receiveMessageRequest.setQueueUrl(queueUrl);
receiveMessageRequest.setMaxNumberOfMessages(1);
receiveMessageRequest.setWaitTimeSeconds(20);
ReceiveMessageResult receiveMessageResult = cqs1.receiveMessage(receiveMessageRequest);
if (receiveMessageResult.getMessages().size() == 1) {
messageMap.put(receiveMessageResult.getMessages().get(0).getBody(), "");
DeleteMessageRequest deleteMessageRequest = new DeleteMessageRequest();
deleteMessageRequest.setQueueUrl(queueUrl);
deleteMessageRequest.setReceiptHandle(receiveMessageResult.getMessages().get(0).getReceiptHandle());
cqs1.deleteMessage(deleteMessageRequest);
}
} catch (Exception ex) {
logger.error("test failed", ex);
fail(ex.getMessage());
}
}
}
/**
* Multi-threaded load test. This test launches 25 concurrent message receivers each of them consecutively
* calling receive() with a TO of 20 sec. After a dealy of 2 sec a single threaded messages sender starts
* sending 25 seconds. Test verifies that 25 unique messages are received.
*/
private void testConcurrentLPRequests(AmazonSQS senderSqs) {
messageMap = new HashMap<String, String>();
try {
queueUrl = getQueueUrl(1, USR.USER1);
// apparently there is a limit of 50 concurrent operations in aws sdk
int numMessages = 25;
for (int i=0; i<numMessages; i++) {
new MessageReceiver().start();
}
Thread.sleep(2000);
for (int i=0; i<numMessages; i++) {
senderSqs.sendMessage(new SendMessageRequest(queueUrl, "test message " + i));
}
Thread.sleep(1000);
assertTrue("Wrong number of messages: " + messageMap.size(), messageMap.size() == numMessages);
} catch (Exception ex) {
logger.error("test failed", ex);
fail(ex.getMessage());
}
}
@Test
public void testConcurrentLPRequests() {
testConcurrentLPRequests(cqs1);
}
@Test
public void testConcurrentLPRequestsLoadBalancer() {
if (cqsAlt == null) {
logger.info("skipping concurrent load balanced long poll test due to missing alternate sqs service url");
return;
}
testConcurrentLPRequests(cqsAlt);
}
}