//
// Copyright 2010 Cinch Logic Pty Ltd.
//
// http://www.chililog.com
//
// 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 org.chililog.server.engine;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.regex.Pattern;
import org.chililog.server.common.AppProperties;
import org.chililog.server.common.ChiliLogException;
import org.chililog.server.data.MongoConnection;
import org.chililog.server.data.RepositoryFieldConfigBO;
import org.chililog.server.data.RepositoryConfigBO;
import org.chililog.server.data.RepositoryParserConfigBO;
import org.chililog.server.data.UserBO;
import org.chililog.server.data.UserController;
import org.chililog.server.data.RepositoryConfigBO.Status;
import org.chililog.server.data.RepositoryParserConfigBO.AppliesTo;
import org.chililog.server.data.RepositoryParserConfigBO.ParseFieldErrorHandling;
import org.chililog.server.engine.MqService;
import org.chililog.server.engine.Repository;
import org.chililog.server.engine.RepositoryService;
import org.chililog.server.engine.RepositoryStorageWorker;
import org.chililog.server.engine.parsers.DelimitedEntryParser;
import org.hornetq.api.core.HornetQException;
import org.hornetq.api.core.Message;
import org.hornetq.api.core.SimpleString;
import org.hornetq.api.core.client.ClientMessage;
import org.hornetq.api.core.client.ClientProducer;
import org.hornetq.api.core.client.ClientSession;
import org.hornetq.api.core.management.QueueControl;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import static org.junit.Assert.*;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
/**
* Test a repository
*
* @author vibul
*
*/
public class RepositoryTest {
private static DB _db;
private static RepositoryConfigBO _repoConfig;
private static final String REPOSITORY_NAME = "junit_test";
private static final String PUBLISHER_USERNAME = "RepositoryTest.publisher";
private static final String PUBLISHER_PASSWORD = "pw4publisher!";
private static final String SUBSCRIBER_USERNAME = "RepositoryTest.subscriber";
private static final String SUBSCRIBER_PASSWORD = "pw4subscriber!";
private static final String MONGODB_COLLECTION_NAME = "repo_junit_test";
@BeforeClass
public static void classSetup() throws Exception {
_db = MongoConnection.getInstance().getConnection();
// Clean up old test data if any exists
DBCollection coll = _db.getCollection(UserController.MONGODB_COLLECTION_NAME);
Pattern pattern = Pattern.compile("^RepositoryTest\\.[\\w]*$");
DBObject query = new BasicDBObject();
query.put("username", pattern);
coll.remove(query);
UserBO user = new UserBO();
user.setUsername(PUBLISHER_USERNAME);
user.setPassword(PUBLISHER_PASSWORD, true);
user.addRole(UserBO.createRepositoryPublisherRoleName(REPOSITORY_NAME));
UserController.getInstance().save(_db, user);
user = new UserBO();
user.setUsername(SUBSCRIBER_USERNAME);
user.setPassword(SUBSCRIBER_PASSWORD, true);
user.addRole(UserBO.createRepositoryPublisherRoleName(REPOSITORY_NAME));
UserController.getInstance().save(_db, user);
// Create repo
_repoConfig = new RepositoryConfigBO();
_repoConfig.setName(REPOSITORY_NAME);
_repoConfig.setDisplayName("Repository Test 1");
_repoConfig.setStoreEntriesIndicator(true);
_repoConfig.setStorageQueueDurableIndicator(false);
_repoConfig.setStorageQueueWorkerCount(2);
RepositoryParserConfigBO repoParserConfig = new RepositoryParserConfigBO();
repoParserConfig.setName("parser1");
repoParserConfig.setAppliesTo(AppliesTo.All);
repoParserConfig.setClassName(DelimitedEntryParser.class.getName());
repoParserConfig.setParseFieldErrorHandling(ParseFieldErrorHandling.SkipEntry);
repoParserConfig.getProperties().put(DelimitedEntryParser.DELIMITER_PROPERTY_NAME, "|");
_repoConfig.getParsers().add(repoParserConfig);
RepositoryFieldConfigBO repoFieldConfig = new RepositoryFieldConfigBO();
repoFieldConfig.setName("field1");
repoFieldConfig.setDataType(RepositoryFieldConfigBO.DataType.String);
repoFieldConfig.getProperties().put(DelimitedEntryParser.POSITION_FIELD_PROPERTY_NAME, "1");
repoParserConfig.getFields().add(repoFieldConfig);
repoFieldConfig = new RepositoryFieldConfigBO();
repoFieldConfig.setName("field2");
repoFieldConfig.setDataType(RepositoryFieldConfigBO.DataType.Integer);
repoFieldConfig.getProperties().put(DelimitedEntryParser.POSITION_FIELD_PROPERTY_NAME, "2");
repoParserConfig.getFields().add(repoFieldConfig);
repoFieldConfig = new RepositoryFieldConfigBO();
repoFieldConfig.setName("field3");
repoFieldConfig.setDataType(RepositoryFieldConfigBO.DataType.Long);
repoFieldConfig.getProperties().put(DelimitedEntryParser.POSITION_FIELD_PROPERTY_NAME, "3");
repoParserConfig.getFields().add(repoFieldConfig);
repoFieldConfig = new RepositoryFieldConfigBO();
repoFieldConfig.setName("field4");
repoFieldConfig.setDataType(RepositoryFieldConfigBO.DataType.Double);
repoFieldConfig.getProperties().put(DelimitedEntryParser.POSITION_FIELD_PROPERTY_NAME, "4");
repoParserConfig.getFields().add(repoFieldConfig);
repoFieldConfig = new RepositoryFieldConfigBO();
repoFieldConfig.setName("field5");
repoFieldConfig.setDataType(RepositoryFieldConfigBO.DataType.Date);
repoFieldConfig.getProperties().put(DelimitedEntryParser.POSITION_FIELD_PROPERTY_NAME, "5");
repoFieldConfig.getProperties().put(RepositoryFieldConfigBO.DATE_FORMAT_PROPERTY_NAME, "yyyy-MM-dd HH:mm:ss");
repoParserConfig.getFields().add(repoFieldConfig);
repoFieldConfig = new RepositoryFieldConfigBO();
repoFieldConfig.setName("field6");
repoFieldConfig.setDataType(RepositoryFieldConfigBO.DataType.Boolean);
repoFieldConfig.getProperties().put(DelimitedEntryParser.POSITION_FIELD_PROPERTY_NAME, "6");
repoParserConfig.getFields().add(repoFieldConfig);
// Database
_db = MongoConnection.getInstance().getConnection();
assertNotNull(_db);
}
@Before
public void testSetup() throws Exception {
DBCollection coll = _db.getCollection(MONGODB_COLLECTION_NAME);
coll.drop();
}
@AfterClass
public static void classTeardown() throws Exception {
// Clean up old test data if any exists
DBCollection coll = _db.getCollection(MONGODB_COLLECTION_NAME);
coll.drop();
coll = _db.getCollection(UserController.MONGODB_COLLECTION_NAME);
Pattern pattern = Pattern.compile("^RepositoryTest\\.[\\w]*$");
DBObject query = new BasicDBObject();
query.put("username", pattern);
coll.remove(query);
}
@Test
public void testBasicOK() throws Exception {
SimpleDateFormat sf = RepositoryEntryMqMessage.getDateFormatter();
// Start
MqService.getInstance().start();
Repository repo = new Repository(_repoConfig);
repo.bringOnline();
assertEquals(Status.ONLINE, repo.getStatus());
// Write some repository entries
ClientSession producerSession = MqService.getInstance().getTransactionalClientSession(PUBLISHER_USERNAME,
PUBLISHER_PASSWORD);
String queueAddress = _repoConfig.getPubSubAddress();
ClientProducer producer = producerSession.createProducer(queueAddress);
ClientMessage message = producerSession.createMessage(Message.TEXT_TYPE, false);
message.putStringProperty(RepositoryEntryMqMessage.TIMESTAMP, sf.format(new Date()));
message.putStringProperty(RepositoryEntryMqMessage.SOURCE, "RepositoryTest");
message.putStringProperty(RepositoryEntryMqMessage.HOST, "localhost");
message.putStringProperty(RepositoryEntryMqMessage.SEVERITY, "1");
String entry1 = "line1|2|3|4.4|2001-5-5 5:5:5|True";
message.getBodyBuffer().writeNullableSimpleString(SimpleString.toSimpleString(entry1));
producer.send(message);
message = producerSession.createMessage(Message.TEXT_TYPE, false);
message.putStringProperty(RepositoryEntryMqMessage.TIMESTAMP, sf.format(new Date()));
message.putStringProperty(RepositoryEntryMqMessage.SOURCE, "RepositoryTest");
message.putStringProperty(RepositoryEntryMqMessage.HOST, "localhost");
message.putStringProperty(RepositoryEntryMqMessage.SEVERITY, "2");
String entry2 = "line2|2|3|4.4|2001-5-5 5:5:5|True";
message.getBodyBuffer().writeNullableSimpleString(SimpleString.toSimpleString(entry2));
producer.send(message);
message = producerSession.createMessage(Message.TEXT_TYPE, false);
message.putStringProperty(RepositoryEntryMqMessage.TIMESTAMP, sf.format(new Date()));
message.putStringProperty(RepositoryEntryMqMessage.SOURCE, "RepositoryTest");
message.putStringProperty(RepositoryEntryMqMessage.HOST, "localhost");
message.putStringProperty(RepositoryEntryMqMessage.SEVERITY, "3");
String entry3 = "line3|2|3|4.4|2001-5-5 5:5:5|True";
message.getBodyBuffer().writeNullableSimpleString(SimpleString.toSimpleString(entry3));
producer.send(message);
producerSession.commit();
// Wait for threads to process
Thread.sleep(3000);
// Make sure they are in the database
DBCollection coll = _db.getCollection(MONGODB_COLLECTION_NAME);
assertEquals(3, coll.find().count());
// Stop
repo.takeOffline();
assertEquals(Status.OFFLINE, repo.getStatus());
MqService.getInstance().stop();
}
@Test
public void testUpdateRepositoryConfig() throws Exception {
SimpleDateFormat sf = RepositoryEntryMqMessage.getDateFormatter();
// Start
MqService.getInstance().start();
Repository repo = new Repository(_repoConfig);
repo.bringOnline();
assertEquals(Status.ONLINE, repo.getStatus());
// Try to update repo - should error because it is not off line
// Simulate we getting new repoConfig from the DB
try {
repo.setRepoConfig(_repoConfig);
fail();
} catch (Exception ex) {
assertEquals(ChiliLogException.class, ex.getClass());
}
// Always wait for writer threads to properly start
Thread.sleep(1000);
assertEquals(2, repo.getStorageWorkers().size());
// Stop
repo.takeOffline();
assertEquals(Status.OFFLINE, repo.getStatus());
// Update worker count from 2 to 10
_repoConfig.setStorageQueueWorkerCount(10);
// Restart
repo = new Repository(_repoConfig);
repo.bringOnline();
// Write 10,000 repository entries
ClientSession producerSession = MqService.getInstance().getTransactionalClientSession(PUBLISHER_USERNAME,
PUBLISHER_PASSWORD);
String queueAddress = _repoConfig.getPubSubAddress();
ClientProducer producer = producerSession.createProducer(queueAddress);
for (int i = 1; i <= 10000; i++) {
ClientMessage message = producerSession.createMessage(Message.TEXT_TYPE, false);
message.putStringProperty(RepositoryEntryMqMessage.TIMESTAMP, sf.format(new Date()));
message.putStringProperty(RepositoryEntryMqMessage.SOURCE, "RepositoryTest");
message.putStringProperty(RepositoryEntryMqMessage.HOST, "localhost");
message.putStringProperty(RepositoryEntryMqMessage.SEVERITY, "3");
String entry1 = "line" + i + "|2|3|4.4|2001-5-5 5:5:5|True";
message.getBodyBuffer().writeNullableSimpleString(SimpleString.toSimpleString(entry1));
;
producer.send(message);
producerSession.commit();
}
// Wait for threads to process
Thread.sleep(5000);
// Check that threads are still running
for (RepositoryStorageWorker rw : repo.getStorageWorkers()) {
assertTrue(rw.isRunning());
}
assertEquals(10, repo.getStorageWorkers().size());
// Make sure that we've processed all the messages
QueueControl qc = MqService.getInstance().getQueueControl(repo.getRepoConfig().getPubSubAddress(),
repo.getRepoConfig().getStorageQueueName());
assertEquals(0, qc.getMessageCount());
// Make sure they are in the database
DBCollection coll = _db.getCollection(MONGODB_COLLECTION_NAME);
assertEquals(10000, coll.find().count());
// Stop
repo.takeOffline();
assertEquals(Status.OFFLINE, repo.getStatus());
MqService.getInstance().stop();
// Reset count
_repoConfig.setStorageQueueWorkerCount(2);
}
@Test
public void testRepositoryStatusSwitching() throws Exception {
SimpleDateFormat sf = RepositoryEntryMqMessage.getDateFormatter();
// ************************************************************************************************************
// ONLINE
// ************************************************************************************************************
MqService.getInstance().start();
Repository repo = new Repository(_repoConfig);
repo.bringOnline();
assertEquals(Status.ONLINE, repo.getStatus());
// try to bring online again - should error
try {
repo.bringOnline();
fail();
} catch (Exception ex) {
assertEquals(ChiliLogException.class, ex.getClass());
}
// Write some repository entries
ClientSession producerSession = MqService.getInstance().getTransactionalClientSession(PUBLISHER_USERNAME,
PUBLISHER_PASSWORD);
String queueAddress = _repoConfig.getPubSubAddress();
ClientProducer producer = producerSession.createProducer(queueAddress);
for (int i = 1; i <= 10; i++) {
ClientMessage message = producerSession.createMessage(Message.TEXT_TYPE, false);
message.putStringProperty(RepositoryEntryMqMessage.TIMESTAMP, sf.format(new Date()));
message.putStringProperty(RepositoryEntryMqMessage.SOURCE, "RepositoryTest");
message.putStringProperty(RepositoryEntryMqMessage.HOST, "localhost");
message.putStringProperty(RepositoryEntryMqMessage.SEVERITY, "3");
String entry1 = "line" + i + "|2|3|4.4|2001-5-5 5:5:5|True";
message.getBodyBuffer().writeNullableSimpleString(SimpleString.toSimpleString(entry1));
;
producer.send(message);
producerSession.commit();
}
// Make sure that we've processed all the messages
Thread.sleep(3000);
QueueControl qc = MqService.getInstance().getQueueControl(repo.getRepoConfig().getPubSubAddress(),
repo.getRepoConfig().getStorageQueueName());
assertEquals(0, qc.getMessageCount());
// Make sure they are in the database
DBCollection coll = _db.getCollection(MONGODB_COLLECTION_NAME);
assertEquals(10, coll.find().count());
// ************************************************************************************************************
// STOP
// ************************************************************************************************************
repo.takeOffline();
assertEquals(Status.OFFLINE, repo.getStatus());
// Offline again - should be no errors
repo.takeOffline();
assertEquals(Status.OFFLINE, repo.getStatus());
// Sending a message after stopping should result in an error
// Have to wait for at least 1 seconds for credentials cache to timeout
// security-invalidation-interval defaults to 0 milliseconds
Thread.sleep(1000);
try {
ClientMessage message = producerSession.createMessage(Message.TEXT_TYPE, false);
message.putStringProperty(RepositoryEntryMqMessage.TIMESTAMP, sf.format(new Date()));
message.putStringProperty(RepositoryEntryMqMessage.SOURCE, "RepositoryTest");
message.putStringProperty(RepositoryEntryMqMessage.HOST, "localhost");
message.putStringProperty(RepositoryEntryMqMessage.SEVERITY, "3");
String entry1 = "lineXXX|2|3|4.4|2001-5-5 5:5:5|True";
message.getBodyBuffer().writeNullableSimpleString(SimpleString.toSimpleString(entry1));
;
producer.send(message);
producerSession.commit();
} catch (Exception ex) {
// HornetQException[errorCode=105 message=User: junit_test doesn't have permission='SEND' on address
// repo.junit_test]
assertEquals(HornetQException.class, ex.getClass());
assertEquals(HornetQException.SECURITY_EXCEPTION, ((HornetQException) ex).getCode());
}
// Check that there are no threads are still running
for (RepositoryStorageWorker rw : repo.getStorageWorkers()) {
assertTrue(!rw.isRunning());
}
assertEquals(0, repo.getStorageWorkers().size());
// ************************************************************************************************************
// READONLU
// ************************************************************************************************************
repo.makeReadonly();
assertEquals(Status.READONLY, repo.getStatus());
// Offline again - should be no errors
repo.makeReadonly();
assertEquals(Status.READONLY, repo.getStatus());
// Sending a message after stopping should result in an error
// Have to wait for at least 1 seconds for credentials cache to timeout
// security-invalidation-interval defaults to 0 milliseconds
Thread.sleep(1000);
try {
ClientMessage message = producerSession.createMessage(Message.TEXT_TYPE, false);
message.putStringProperty(RepositoryEntryMqMessage.TIMESTAMP, sf.format(new Date()));
message.putStringProperty(RepositoryEntryMqMessage.SOURCE, "RepositoryTest");
message.putStringProperty(RepositoryEntryMqMessage.HOST, "localhost");
message.putStringProperty(RepositoryEntryMqMessage.SEVERITY, "3");
String entry1 = "lineXXX|2|3|4.4|2001-5-5 5:5:5|True";
message.getBodyBuffer().writeNullableSimpleString(SimpleString.toSimpleString(entry1));
;
producer.send(message);
producerSession.commit();
} catch (Exception ex) {
// HornetQException[errorCode=105 message=User: junit_test doesn't have permission='SEND' on address
// repo.junit_test]
assertEquals(HornetQException.class, ex.getClass());
assertEquals(HornetQException.SECURITY_EXCEPTION, ((HornetQException) ex).getCode());
}
// ************************************************************************************************************
// Stop
// ************************************************************************************************************
producer.close();
producerSession.close();
MqService.getInstance().stop();
// Reset count
_repoConfig.setStorageQueueWorkerCount(2);
}
@Test
public void testBadEntries() throws Exception {
String deadLetterAddress = AppProperties.getInstance().getMqDeadLetterAddress();
SimpleDateFormat sf = RepositoryEntryMqMessage.getDateFormatter();
// Start
MqService.getInstance().start();
Repository repo = new Repository(_repoConfig);
repo.bringOnline();
assertEquals(Status.ONLINE, repo.getStatus());
// Create a dead letter queue
MqService.getInstance().deployQueue(deadLetterAddress, "dead_letters.junit_test", false);
// Have to wait for at least 1 seconds for credentials cache to timeout
// security-invalidation-interval defaults to 0 milliseconds
Thread.sleep(1000);
// Write some repository entries
ClientSession producerSession = MqService.getInstance().getTransactionalClientSession(PUBLISHER_USERNAME,
PUBLISHER_PASSWORD);
String queueAddress = _repoConfig.getPubSubAddress();
ClientProducer producer = producerSession.createProducer(queueAddress);
// Write some good entries
for (int i = 1; i <= 100; i++) {
ClientMessage message = producerSession.createMessage(Message.TEXT_TYPE, false);
message.putStringProperty(RepositoryEntryMqMessage.TIMESTAMP, sf.format(new Date()));
message.putStringProperty(RepositoryEntryMqMessage.SOURCE, "RepositoryTest");
message.putStringProperty(RepositoryEntryMqMessage.HOST, "localhost");
message.putStringProperty(RepositoryEntryMqMessage.SEVERITY, "Debug");
String entry1 = "line" + i + "|2|3|4.4|2001-5-5 5:5:5|True";
if (i == 33) {
entry1 = i + " - bad entry no delimiter";
}
message.getBodyBuffer().writeNullableSimpleString(SimpleString.toSimpleString(entry1));
;
producer.send(message);
producerSession.commit();
}
// Wait for threads to process
Thread.sleep(3000);
// Check that threads are still running
for (RepositoryStorageWorker rw : repo.getStorageWorkers()) {
assertTrue(rw.isRunning());
}
assertEquals(2, repo.getStorageWorkers().size());
// Make sure that bad entries have been removed from the write queue
QueueControl qc = MqService.getInstance().getQueueControl(repo.getRepoConfig().getPubSubAddress(),
repo.getRepoConfig().getStorageQueueName());
assertEquals(0, qc.getMessageCount());
// Make sure that the bad entry ends up in the dead letter queue
qc = MqService.getInstance().getQueueControl(deadLetterAddress, "dead_letters.junit_test");
assertEquals((long) 1, qc.getMessageCount());
// Make sure that only good entries have been saved to the DB
DBCollection coll = _db.getCollection(MONGODB_COLLECTION_NAME);
assertEquals(99, coll.find().count());
// Stop
repo.takeOffline();
assertEquals(Status.OFFLINE, repo.getStatus());
MqService.getInstance().stop();
}
@Test
public void testRepositoryService() throws Exception {
// Start queues
MqService.getInstance().start();
// Start
RepositoryService.getInstance().start();
Repository[] repos = RepositoryService.getInstance().getRepositories();
for (Repository r : repos) {
if (r.getRepoConfig().getStartupStatus() == Status.ONLINE) {
assertEquals(Status.ONLINE, r.getStatus());
} else if (r.getRepoConfig().getStartupStatus() == Status.READONLY) {
assertEquals(Status.READONLY, r.getStatus());
} else {
assertEquals(Status.OFFLINE, r.getStatus());
}
}
// Start again - should not error
RepositoryService.getInstance().start();
Repository[] repos2 = RepositoryService.getInstance().getRepositories();
for (Repository r : repos2) {
if (r.getRepoConfig().getStartupStatus() == Status.ONLINE) {
assertEquals(Status.ONLINE, r.getStatus());
} else if (r.getRepoConfig().getStartupStatus() == Status.READONLY) {
assertEquals(Status.READONLY, r.getStatus());
} else {
assertEquals(Status.OFFLINE, r.getStatus());
}
}
assertEquals(repos.length, repos2.length);
// Stop
RepositoryService.getInstance().stop();
repos2 = RepositoryService.getInstance().getRepositories();
for (Repository r : repos2) {
assertEquals(Status.OFFLINE, r.getStatus());
}
assertEquals(repos.length, repos2.length);
// Stop again
RepositoryService.getInstance().stop();
repos2 = RepositoryService.getInstance().getRepositories();
for (Repository r : repos2) {
assertEquals(Status.OFFLINE, r.getStatus());
}
assertEquals(repos.length, repos2.length);
// Stop queues
MqService.getInstance().stop();
}
}