/**
* This file is part of Graylog.
*
* Graylog 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, either version 3 of the License, or
* (at your option) any later version.
*
* Graylog 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 Graylog. If not, see <http://www.gnu.org/licenses/>.
*/
package org.graylog2.events;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.ImmutableMap;
import com.google.common.eventbus.DeadEvent;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import com.lordofthejars.nosqlunit.annotation.UsingDataSet;
import com.lordofthejars.nosqlunit.core.LoadStrategyEnum;
import com.lordofthejars.nosqlunit.mongodb.InMemoryMongoDb;
import com.mongodb.BasicDBObjectBuilder;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.WriteConcern;
import org.graylog2.bindings.providers.MongoJackObjectMapperProvider;
import org.graylog2.database.MongoConnection;
import org.graylog2.database.MongoConnectionRule;
import org.graylog2.plugin.system.NodeId;
import org.graylog2.shared.bindings.providers.ObjectMapperProvider;
import org.graylog2.shared.plugins.ChainingClassLoader;
import org.graylog2.system.debug.DebugEvent;
import org.joda.time.DateTime;
import org.joda.time.DateTimeUtils;
import org.joda.time.DateTimeZone;
import org.junit.After;
import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static com.lordofthejars.nosqlunit.mongodb.InMemoryMongoDb.InMemoryMongoRuleBuilder.newInMemoryMongoDbRule;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class ClusterEventPeriodicalTest {
@ClassRule
public static final InMemoryMongoDb IN_MEMORY_MONGO_DB = newInMemoryMongoDbRule().build();
private static final DateTime TIME = new DateTime(2015, 4, 1, 0, 0, DateTimeZone.UTC);
@Rule
public MongoConnectionRule mongoRule = MongoConnectionRule.build("test");
@Rule
public final MockitoRule mockitoRule = MockitoJUnit.rule();
private final ObjectMapper objectMapper = new ObjectMapperProvider().get();
@Mock
private NodeId nodeId;
@Spy
private EventBus serverEventBus;
@Spy
private ClusterEventBus clusterEventBus;
private MongoConnection mongoConnection;
private ClusterEventPeriodical clusterEventPeriodical;
@Before
public void setUpService() throws Exception {
DateTimeUtils.setCurrentMillisFixed(TIME.getMillis());
this.mongoConnection = mongoRule.getMongoConnection();
MongoJackObjectMapperProvider provider = new MongoJackObjectMapperProvider(objectMapper);
when(nodeId.toString()).thenReturn("ID");
this.clusterEventPeriodical = new ClusterEventPeriodical(
provider,
mongoRule.getMongoConnection(),
nodeId,
new ChainingClassLoader(getClass().getClassLoader()),
serverEventBus,
clusterEventBus
);
}
@After
public void tearDown() {
DateTimeUtils.setCurrentMillisSystem();
}
@Test
public void clusterEventServiceRegistersItselfWithClusterEventBus() throws Exception {
verify(clusterEventBus, times(1)).registerClusterEventSubscriber(clusterEventPeriodical);
}
@Test
@UsingDataSet(loadStrategy = LoadStrategyEnum.DELETE_ALL)
public void runHandlesInvalidPayloadsGracefully() throws Exception {
DBObject event = new BasicDBObjectBuilder()
.add("timestamp", TIME.getMillis())
.add("producer", "TEST-PRODUCER")
.add("consumers", Collections.emptyList())
.add("event_class", SimpleEvent.class.getCanonicalName())
.add("payload", ImmutableMap.of("HAHA", "test"))
.get();
final DBCollection collection = mongoConnection.getDatabase().getCollection(ClusterEventPeriodical.COLLECTION_NAME);
collection.save(event);
assertThat(collection.count()).isEqualTo(1L);
clusterEventPeriodical.run();
assertThat(collection.count()).isEqualTo(1L);
@SuppressWarnings("unchecked")
final List<String> consumers = (List<String>) collection.findOne().get("consumers");
assertThat(consumers).containsExactly(nodeId.toString());
verify(serverEventBus, never()).post(any());
verify(clusterEventBus, never()).post(any());
}
@Test
@UsingDataSet(loadStrategy = LoadStrategyEnum.DELETE_ALL)
public void serverEventBusDispatchesTypedEvents() throws Exception {
final SimpleEventHandler handler = new SimpleEventHandler();
serverEventBus.register(handler);
DBObject event = new BasicDBObjectBuilder()
.add("timestamp", TIME.getMillis())
.add("producer", "TEST-PRODUCER")
.add("consumers", Collections.emptyList())
.add("event_class", SimpleEvent.class.getCanonicalName())
.add("payload", ImmutableMap.of("payload", "test"))
.get();
final DBCollection collection = mongoConnection.getDatabase().getCollection(ClusterEventPeriodical.COLLECTION_NAME);
assertThat(collection.save(event).getN()).isEqualTo(1);
assertThat(collection.count()).isEqualTo(1L);
assertThat(handler.invocations).isEqualTo(0);
clusterEventPeriodical.run();
assertThat(handler.invocations).isEqualTo(1);
assertThat(collection.count()).isEqualTo(1L);
@SuppressWarnings("unchecked")
final List<String> consumers = (List<String>) collection.findOne().get("consumers");
assertThat(consumers).containsExactly(nodeId.toString());
verify(serverEventBus, times(1)).post(any(SimpleEvent.class));
verify(clusterEventBus, never()).post(any());
}
@Test
@UsingDataSet(loadStrategy = LoadStrategyEnum.DELETE_ALL)
public void runHandlesAutoValueCorrectly() throws Exception {
final DebugEvent event = DebugEvent.create("Node ID", TIME, "test");
DBObject dbObject = new BasicDBObjectBuilder()
.add("timestamp", TIME.getMillis())
.add("producer", "TEST-PRODUCER")
.add("consumers", Collections.emptyList())
.add("event_class", DebugEvent.class.getCanonicalName())
.add("payload", objectMapper.convertValue(event, Map.class))
.get();
final DBCollection collection = mongoConnection.getDatabase().getCollection(ClusterEventPeriodical.COLLECTION_NAME);
collection.save(dbObject);
assertThat(collection.count()).isEqualTo(1L);
clusterEventPeriodical.run();
assertThat(collection.count()).isEqualTo(1L);
@SuppressWarnings("unchecked")
final List<String> consumers = (List<String>) collection.findOne().get("consumers");
assertThat(consumers).containsExactly(nodeId.toString());
verify(serverEventBus, times(1)).post(event);
verify(clusterEventBus, never()).post(event);
}
@Test
@UsingDataSet(loadStrategy = LoadStrategyEnum.DELETE_ALL)
public void testRun() throws Exception {
DBObject event = new BasicDBObjectBuilder()
.add("timestamp", TIME.getMillis())
.add("producer", "TEST-PRODUCER")
.add("consumers", Collections.emptyList())
.add("event_class", SimpleEvent.class.getCanonicalName())
.add("payload", ImmutableMap.of("payload", "test"))
.get();
final DBCollection collection = mongoConnection.getDatabase().getCollection(ClusterEventPeriodical.COLLECTION_NAME);
collection.save(event);
assertThat(collection.count()).isEqualTo(1L);
clusterEventPeriodical.run();
assertThat(collection.count()).isEqualTo(1L);
@SuppressWarnings("unchecked")
final List<String> consumers = (List<String>) collection.findOne().get("consumers");
assertThat(consumers).containsExactly(nodeId.toString());
verify(serverEventBus, times(1)).post(new SimpleEvent("test"));
verify(clusterEventBus, never()).post(event);
}
@Test
@UsingDataSet(loadStrategy = LoadStrategyEnum.DELETE_ALL)
public void testPublishClusterEvent() throws Exception {
DBCollection collection = mongoConnection.getDatabase().getCollection(ClusterEventPeriodical.COLLECTION_NAME);
SimpleEvent event = new SimpleEvent("test");
assertThat(collection.count()).isEqualTo(0L);
clusterEventPeriodical.publishClusterEvent(event);
verify(clusterEventBus, never()).post(any());
assertThat(collection.count()).isEqualTo(1L);
DBObject dbObject = collection.findOne();
assertThat((String) dbObject.get("producer")).isEqualTo(nodeId.toString());
assertThat((String) dbObject.get("event_class")).isEqualTo(SimpleEvent.class.getCanonicalName());
@SuppressWarnings("unchecked")
Map<String, Object> payload = (Map<String, Object>) dbObject.get("payload");
assertThat(payload).containsEntry("payload", "test");
}
@Test
@UsingDataSet(loadStrategy = LoadStrategyEnum.DELETE_ALL)
public void publishClusterEventHandlesAutoValueCorrectly() throws Exception {
DBCollection collection = mongoConnection.getDatabase().getCollection(ClusterEventPeriodical.COLLECTION_NAME);
DebugEvent event = DebugEvent.create("Node ID", "Test");
assertThat(collection.count()).isEqualTo(0L);
clusterEventPeriodical.publishClusterEvent(event);
verify(clusterEventBus, never()).post(any());
assertThat(collection.count()).isEqualTo(1L);
DBObject dbObject = collection.findOne();
assertThat((String) dbObject.get("producer")).isEqualTo(nodeId.toString());
assertThat((String) dbObject.get("event_class")).isEqualTo(DebugEvent.class.getCanonicalName());
}
@Test
@UsingDataSet(loadStrategy = LoadStrategyEnum.DELETE_ALL)
public void publishClusterEventSkipsDeadEvent() throws Exception {
DBCollection collection = mongoConnection.getDatabase().getCollection(ClusterEventPeriodical.COLLECTION_NAME);
DeadEvent event = new DeadEvent(clusterEventBus, new SimpleEvent("test"));
assertThat(collection.count()).isEqualTo(0L);
clusterEventPeriodical.publishClusterEvent(event);
verify(clusterEventBus, never()).post(any());
assertThat(collection.count()).isEqualTo(0L);
}
@Test
public void prepareCollectionCreatesIndexesOnExistingCollection() throws Exception {
DBCollection original = mongoConnection.getDatabase().getCollection(ClusterEventPeriodical.COLLECTION_NAME);
original.dropIndexes();
assertThat(original.getName()).isEqualTo(ClusterEventPeriodical.COLLECTION_NAME);
assertThat(original.getIndexInfo()).hasSize(1);
DBCollection collection = ClusterEventPeriodical.prepareCollection(mongoConnection);
assertThat(collection.getName()).isEqualTo(ClusterEventPeriodical.COLLECTION_NAME);
assertThat(collection.getIndexInfo()).hasSize(2);
assertThat(collection.getWriteConcern()).isEqualTo(WriteConcern.JOURNALED);
}
@Test
@UsingDataSet(loadStrategy = LoadStrategyEnum.DELETE_ALL)
public void prepareCollectionCreatesCollectionIfItDoesNotExist() throws Exception {
assertThat(mongoConnection.getDatabase().collectionExists(ClusterEventPeriodical.COLLECTION_NAME)).isFalse();
DBCollection collection = ClusterEventPeriodical.prepareCollection(mongoConnection);
assertThat(collection.getName()).isEqualTo(ClusterEventPeriodical.COLLECTION_NAME);
assertThat(collection.getIndexInfo()).hasSize(2);
assertThat(collection.getWriteConcern()).isEqualTo(WriteConcern.JOURNALED);
}
public static class SimpleEventHandler {
public volatile int invocations = 0;
@Subscribe
public void handleSimpleEvent(SimpleEvent event) {
invocations++;
}
}
}