/** * 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.migrations; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.mongodb.BasicDBObject; import com.mongodb.DB; import com.mongodb.DBCollection; import com.mongodb.WriteResult; import org.bson.types.ObjectId; import org.graylog2.alarmcallbacks.AlarmCallbackConfiguration; import org.graylog2.alarmcallbacks.AlarmCallbackConfigurationImpl; import org.graylog2.alarmcallbacks.AlarmCallbackConfigurationService; import org.graylog2.alarmcallbacks.EmailAlarmCallback; import org.graylog2.alarmcallbacks.HTTPAlarmCallback; import org.graylog2.database.MongoConnection; import org.graylog2.plugin.alarms.AlertCondition; import org.graylog2.plugin.cluster.ClusterConfigService; import org.graylog2.plugin.streams.Stream; import org.graylog2.streams.StreamImpl; import org.graylog2.streams.StreamService; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; 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 V20161125161400_AlertReceiversMigrationTest { @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule(); private V20161125161400_AlertReceiversMigration alertReceiversMigration; @Mock private ClusterConfigService clusterConfigService; @Mock private StreamService streamService; @Mock private AlarmCallbackConfigurationService alarmCallbackConfigurationService; @Mock private DBCollection dbCollection; @Before public void setUp() throws Exception { final MongoConnection mongoConnection = mock(MongoConnection.class); final DB database = mock(DB.class); when(mongoConnection.getDatabase()).thenReturn(database); when(database.getCollection(eq("streams"))).thenReturn(dbCollection); this.alertReceiversMigration = new V20161125161400_AlertReceiversMigration(clusterConfigService, streamService, alarmCallbackConfigurationService, mongoConnection); } @Test public void doNotMigrateAnythingWithoutStreams() throws Exception { when(this.streamService.loadAll()).thenReturn(Collections.emptyList()); this.alertReceiversMigration.upgrade(); verify(this.alarmCallbackConfigurationService, never()).getForStream(any()); verify(this.dbCollection, never()).update(any(), any()); verifyMigrationCompletedWasPosted(); } @Test public void doNotMigrateAnythingWithoutQualifyingStreams() throws Exception { final Stream stream1 = mock(Stream.class); when(stream1.getAlertReceivers()).thenReturn(Collections.emptyMap()); final Stream stream2 = mock(Stream.class); when(stream2.getAlertReceivers()).thenReturn(ImmutableMap.of( "users", Collections.emptyList(), "emails", Collections.emptyList()) ); when(this.streamService.loadAll()).thenReturn(ImmutableList.of(stream1, stream2)); this.alertReceiversMigration.upgrade(); verify(this.streamService, never()).getAlertConditions(any()); verify(this.alarmCallbackConfigurationService, never()).getForStream(any()); verify(this.alarmCallbackConfigurationService, never()).save(any()); verify(this.dbCollection, never()).update(any(), any()); verifyMigrationCompletedWasPosted(); } @Test public void doMigrateSingleQualifyingStream() throws Exception { final String matchingStreamId = new ObjectId().toHexString(); final Stream stream1 = mock(Stream.class); when(stream1.getAlertReceivers()).thenReturn(Collections.emptyMap()); final Stream stream2 = mock(Stream.class); when(stream2.getAlertReceivers()).thenReturn(ImmutableMap.of( "users", ImmutableList.of("foouser"), "emails", ImmutableList.of("foo@bar.com") )); when(stream2.getId()).thenReturn(matchingStreamId); when(this.streamService.loadAll()).thenReturn(ImmutableList.of(stream1, stream2)); final AlertCondition alertCondition = mock(AlertCondition.class); when(this.streamService.getAlertConditions(eq(stream2))).thenReturn(ImmutableList.of(alertCondition)); final String alarmCallbackId = new ObjectId().toHexString(); final AlarmCallbackConfiguration alarmCallback = AlarmCallbackConfigurationImpl.create( alarmCallbackId, matchingStreamId, EmailAlarmCallback.class.getCanonicalName(), "Email Alert Notification", new HashMap<>(), new Date(), "admin" ); when(alarmCallbackConfigurationService.getForStream(eq(stream2))).thenReturn(ImmutableList.of(alarmCallback)); when(alarmCallbackConfigurationService.save(eq(alarmCallback))).thenReturn(alarmCallbackId); when(this.dbCollection.update(any(BasicDBObject.class), any(BasicDBObject.class))).thenReturn(new WriteResult(1, true, matchingStreamId)); this.alertReceiversMigration.upgrade(); final ArgumentCaptor<AlarmCallbackConfiguration> configurationArgumentCaptor = ArgumentCaptor.forClass(AlarmCallbackConfiguration.class); verify(this.alarmCallbackConfigurationService, times(1)).save(configurationArgumentCaptor.capture()); final AlarmCallbackConfiguration updatedConfiguration = configurationArgumentCaptor.getValue(); assertThat(updatedConfiguration).isEqualTo(alarmCallback); assertThat(updatedConfiguration.getType()).isEqualTo(EmailAlarmCallback.class.getCanonicalName()); assertThat(((List) updatedConfiguration.getConfiguration().get(EmailAlarmCallback.CK_EMAIL_RECEIVERS)).size()).isEqualTo(1); assertThat(((List) updatedConfiguration.getConfiguration().get(EmailAlarmCallback.CK_EMAIL_RECEIVERS)).get(0)).isEqualTo("foo@bar.com"); assertThat(((List) updatedConfiguration.getConfiguration().get(EmailAlarmCallback.CK_USER_RECEIVERS)).size()).isEqualTo(1); assertThat(((List) updatedConfiguration.getConfiguration().get(EmailAlarmCallback.CK_USER_RECEIVERS)).get(0)).isEqualTo("foouser"); final ArgumentCaptor<BasicDBObject> queryCaptor = ArgumentCaptor.forClass(BasicDBObject.class); final ArgumentCaptor<BasicDBObject> updateCaptor = ArgumentCaptor.forClass(BasicDBObject.class); verify(this.dbCollection, times(1)).update(queryCaptor.capture(), updateCaptor.capture()); assertThat(queryCaptor.getValue().toJson()).isEqualTo("{ \"_id\" : { \"$oid\" : \"" + matchingStreamId + "\" } }"); assertThat(updateCaptor.getValue().toJson()).isEqualTo("{ \"$unset\" : { \"" + StreamImpl.FIELD_ALERT_RECEIVERS + "\" : \"\" } }"); verifyMigrationCompletedWasPosted(ImmutableMap.of( matchingStreamId, Optional.of(alarmCallbackId) )); } @Test public void doMigrateMultipleQualifyingStreams() throws Exception { final String matchingStreamId1 = new ObjectId().toHexString(); final String matchingStreamId2 = new ObjectId().toHexString(); final Stream stream1 = mock(Stream.class); when(stream1.getAlertReceivers()).thenReturn(Collections.emptyMap()); final Stream stream2 = mock(Stream.class); when(stream2.getAlertReceivers()).thenReturn(ImmutableMap.of( "users", ImmutableList.of("foouser"), "emails", ImmutableList.of("foo@bar.com") )); when(stream2.getId()).thenReturn(matchingStreamId1); final Stream stream3 = mock(Stream.class); when(stream3.getAlertReceivers()).thenReturn(ImmutableMap.of( "users", ImmutableList.of("foouser2") )); when(stream3.getId()).thenReturn(matchingStreamId2); when(this.streamService.loadAll()).thenReturn(ImmutableList.of(stream1, stream2, stream3)); final AlertCondition alertCondition1 = mock(AlertCondition.class); final AlertCondition alertCondition2 = mock(AlertCondition.class); when(this.streamService.getAlertConditions(eq(stream2))).thenReturn(ImmutableList.of(alertCondition1)); when(this.streamService.getAlertConditions(eq(stream3))).thenReturn(ImmutableList.of(alertCondition2)); final String alarmCallbackId1 = new ObjectId().toHexString(); final AlarmCallbackConfiguration alarmCallback1 = AlarmCallbackConfigurationImpl.create( alarmCallbackId1, matchingStreamId1, EmailAlarmCallback.class.getCanonicalName(), "Email Alert Notification", new HashMap<>(), new Date(), "admin" ); final String alarmCallbackId2 = new ObjectId().toHexString(); final AlarmCallbackConfiguration alarmCallback2 = AlarmCallbackConfigurationImpl.create( alarmCallbackId2, matchingStreamId2, EmailAlarmCallback.class.getCanonicalName(), "Email Alert Notification", new HashMap<>(), new Date(), "admin" ); final String alarmCallbackId3 = new ObjectId().toHexString(); final AlarmCallbackConfiguration alarmCallback3 = AlarmCallbackConfigurationImpl.create( alarmCallbackId3, matchingStreamId2, EmailAlarmCallback.class.getCanonicalName(), "Email Alert Notification", new HashMap<>(), new Date(), "admin" ); final String alarmCallbackId4 = new ObjectId().toHexString(); final AlarmCallbackConfiguration alarmCallback4 = AlarmCallbackConfigurationImpl.create( alarmCallbackId4, matchingStreamId2, HTTPAlarmCallback.class.getCanonicalName(), "Email Alert Notification", new HashMap<>(), new Date(), "admin" ); when(alarmCallbackConfigurationService.getForStream(eq(stream2))).thenReturn(ImmutableList.of(alarmCallback1)); when(alarmCallbackConfigurationService.getForStream(eq(stream3))).thenReturn(ImmutableList.of(alarmCallback2, alarmCallback3, alarmCallback4)); when(alarmCallbackConfigurationService.save(eq(alarmCallback1))).thenReturn(alarmCallbackId1); when(alarmCallbackConfigurationService.save(eq(alarmCallback2))).thenReturn(alarmCallbackId2); when(alarmCallbackConfigurationService.save(eq(alarmCallback3))).thenReturn(alarmCallbackId3); when(this.dbCollection.update(any(BasicDBObject.class), any(BasicDBObject.class))).thenReturn(new WriteResult(1, true, matchingStreamId1)); when(this.dbCollection.update(any(BasicDBObject.class), any(BasicDBObject.class))).thenReturn(new WriteResult(1, true, matchingStreamId2)); this.alertReceiversMigration.upgrade(); final ArgumentCaptor<AlarmCallbackConfiguration> configurationArgumentCaptor = ArgumentCaptor.forClass(AlarmCallbackConfiguration.class); verify(this.alarmCallbackConfigurationService, times(3)).save(configurationArgumentCaptor.capture()); final List<AlarmCallbackConfiguration> configurationValues = configurationArgumentCaptor.getAllValues(); assertThat(configurationValues) .isNotNull() .isNotEmpty() .hasSize(3) .contains(alarmCallback1) .contains(alarmCallback2) .contains(alarmCallback3); for (AlarmCallbackConfiguration configurationValue : configurationValues) { if (configurationValue.getStreamId().equals(matchingStreamId1)) { assertThat(((List) configurationValue.getConfiguration().get(EmailAlarmCallback.CK_EMAIL_RECEIVERS)).size()).isEqualTo(1); assertThat(((List) configurationValue.getConfiguration().get(EmailAlarmCallback.CK_EMAIL_RECEIVERS)).get(0)).isEqualTo("foo@bar.com"); assertThat(((List) configurationValue.getConfiguration().get(EmailAlarmCallback.CK_USER_RECEIVERS)).size()).isEqualTo(1); assertThat(((List) configurationValue.getConfiguration().get(EmailAlarmCallback.CK_USER_RECEIVERS)).get(0)).isEqualTo("foouser"); } if (configurationValue.getStreamId().equals(matchingStreamId2)) { assertThat(configurationValue.getConfiguration().get(EmailAlarmCallback.CK_EMAIL_RECEIVERS)).isNull(); assertThat(((List) configurationValue.getConfiguration().get(EmailAlarmCallback.CK_USER_RECEIVERS)).size()).isEqualTo(1); assertThat(((List) configurationValue.getConfiguration().get(EmailAlarmCallback.CK_USER_RECEIVERS)).get(0)).isEqualTo("foouser2"); } } final ArgumentCaptor<BasicDBObject> queryCaptor = ArgumentCaptor.forClass(BasicDBObject.class); final ArgumentCaptor<BasicDBObject> updateCaptor = ArgumentCaptor.forClass(BasicDBObject.class); verify(this.dbCollection, times(2)).update(queryCaptor.capture(), updateCaptor.capture()); final List<BasicDBObject> queries = queryCaptor.getAllValues(); for (BasicDBObject query : queries) { final String streamId = (queries.indexOf(query) == 0 ? matchingStreamId1 : matchingStreamId2); assertThat(query.toJson()).isEqualTo("{ \"_id\" : { \"$oid\" : \"" + streamId + "\" } }"); } updateCaptor.getAllValues() .forEach(update -> assertThat(update.toJson()).isEqualTo("{ \"$unset\" : { \"" + StreamImpl.FIELD_ALERT_RECEIVERS + "\" : \"\" } }")); verifyMigrationCompletedWasPosted(ImmutableMap.of( matchingStreamId1, Optional.of(alarmCallbackId1), matchingStreamId2, Optional.of(alarmCallbackId2 + ", " + alarmCallbackId3) )); } private void verifyMigrationCompletedWasPosted() { verifyMigrationCompletedWasPosted(Collections.emptyMap()); } private void verifyMigrationCompletedWasPosted(Map<String, Optional<String>> migratedStreams) { final ArgumentCaptor<V20161125161400_AlertReceiversMigration.MigrationCompleted> argumentCaptor = ArgumentCaptor.forClass(V20161125161400_AlertReceiversMigration.MigrationCompleted.class); verify(this.clusterConfigService, times(1)).write(argumentCaptor.capture()); final V20161125161400_AlertReceiversMigration.MigrationCompleted alertReceiversMigrated = argumentCaptor.getValue(); assertThat(alertReceiversMigrated) .isNotNull() .isEqualTo(V20161125161400_AlertReceiversMigration.MigrationCompleted.create(migratedStreams)); } }