/** * 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.fasterxml.jackson.databind.ObjectMapper; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoDatabase; import org.assertj.core.api.AbstractAssert; import org.assertj.core.api.Assertions; import org.bson.Document; import org.graylog2.bindings.providers.MongoJackObjectMapperProvider; import org.graylog2.cluster.ClusterConfigServiceImpl; import org.graylog2.database.MongoConnection; import org.graylog2.events.ClusterEventBus; import org.graylog2.fongo.SeedingFongoRule; import org.graylog2.migrations.V20170110150100_FixAlertConditionsMigration.MigrationCompleted; import org.graylog2.plugin.system.NodeId; import org.graylog2.shared.bindings.providers.ObjectMapperProvider; import org.graylog2.shared.plugins.ChainingClassLoader; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import java.time.ZonedDateTime; import java.util.Collections; import java.util.List; import static com.mongodb.client.model.Filters.eq; 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.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; public class V20170110150100_FixAlertConditionsMigrationTest { @Rule public SeedingFongoRule fongoRule = SeedingFongoRule.create("graylog_test") .addSeed("org/graylog2/migrations/V20170110150100_FixAlertConditionsMigration.json"); @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock public NodeId nodeId; private final ObjectMapper objectMapper = new ObjectMapperProvider().get(); private final MongoJackObjectMapperProvider objectMapperProvider = new MongoJackObjectMapperProvider(objectMapper); private ClusterConfigServiceImpl clusterConfigService; private Migration migration; private MongoCollection<Document> collection; @Before public void setUp() throws Exception { this.clusterConfigService = spy(new ClusterConfigServiceImpl(objectMapperProvider, fongoRule.getConnection(), nodeId, new ChainingClassLoader(getClass().getClassLoader()), new ClusterEventBus())); final MongoConnection mongoConnection = spy(fongoRule.getConnection()); final MongoDatabase mongoDatabase = spy(fongoRule.getDatabase()); when(mongoConnection.getMongoDatabase()).thenReturn(mongoDatabase); this.collection = spy(mongoDatabase.getCollection("streams")); when(mongoDatabase.getCollection("streams")).thenReturn(collection); this.migration = new V20170110150100_FixAlertConditionsMigration(mongoConnection, clusterConfigService); } @Test public void createdAt() throws Exception { assertThat(migration.createdAt()).isEqualTo(ZonedDateTime.parse("2017-01-10T15:01:00Z")); } @Test public void upgrade() throws Exception { // First check all types of the existing documents AlertConditionAssertions.assertThat(getAlertCondition("2fa6a415-ce0c-4a36-accc-dd9519eb06d9")) .hasParameter("backlog", 2) .hasParameter("grace", 1) .hasParameter("threshold_type", "MORE") .hasParameter("threshold", "5") .hasParameter("time", "1"); AlertConditionAssertions.assertThat(getAlertCondition("393fd8b2-9b17-42d3-86b0-6e55d0f5343a")) .hasParameter("backlog", 0) .hasParameter("field", "bar") .hasParameter("grace", "0") .hasParameter("value", "baz"); AlertConditionAssertions.assertThat(getAlertCondition("0e75404f-c0ee-40b0-8872-b1aec441ba1c")) .hasParameter("backlog", "0") .hasParameter("field", "foo") .hasParameter("grace", "0") .hasParameter("threshold_type", "HIGHER") .hasParameter("threshold", "0") .hasParameter("time", "5") .hasParameter("type", "MAX"); // Run the migration that should convert all affected fields to integers migration.upgrade(); // Check all types again AlertConditionAssertions.assertThat(getAlertCondition("2fa6a415-ce0c-4a36-accc-dd9519eb06d9")) .hasParameter("backlog", 2) .hasParameter("grace", 1) .hasParameter("threshold_type", "MORE") .hasParameter("threshold", 5) .hasParameter("time", 1); AlertConditionAssertions.assertThat(getAlertCondition("393fd8b2-9b17-42d3-86b0-6e55d0f5343a")) .hasParameter("backlog", 0) .hasParameter("field", "bar") .hasParameter("grace", 0) .hasParameter("value", "baz"); AlertConditionAssertions.assertThat(getAlertCondition("0e75404f-c0ee-40b0-8872-b1aec441ba1c")) .hasParameter("backlog", 0) .hasParameter("field", "foo") .hasParameter("grace", 0) .hasParameter("threshold_type", "HIGHER") .hasParameter("threshold", 0) .hasParameter("time", 5) .hasParameter("type", "MAX"); final MigrationCompleted migrationCompleted = clusterConfigService.get(MigrationCompleted.class); assertThat(migrationCompleted).isNotNull(); assertThat(migrationCompleted.streamIds()).containsOnly("58458e442f857c314491344e", "58458e442f857c314491345e"); assertThat(migrationCompleted.alertConditionIds()).containsOnly("2fa6a415-ce0c-4a36-accc-dd9519eb06d9", "393fd8b2-9b17-42d3-86b0-6e55d0f5343a", "0e75404f-c0ee-40b0-8872-b1aec441ba1c"); } @Test public void upgradeWhenMigrationCompleted() throws Exception { clusterConfigService.write(MigrationCompleted.create(Collections.emptySet(), Collections.emptySet())); // Reset the spy to be able to verify that there wasn't a write reset(clusterConfigService); migration.upgrade(); verify(collection, never()).updateOne(any(), any()); verify(clusterConfigService, never()).write(any(MigrationCompleted.class)); } @SuppressWarnings("unchecked") private Document getAlertCondition(String id) { final Document stream = collection.find(eq("alert_conditions.id", id)).first(); final List<Document> alertConditions = (List<Document>) stream.get("alert_conditions"); return alertConditions.stream() .filter(alertCondition -> alertCondition.get("id", String.class).equals(id)) .findFirst().orElse(null); } private static class AlertConditionAssertions extends AbstractAssert<AlertConditionAssertions, Document> { public static AlertConditionAssertions assertThat(Document actual) { return new AlertConditionAssertions(actual); } private final String id; private final Document parameters; AlertConditionAssertions(Document actual) { super(actual, AlertConditionAssertions.class); this.id = actual.get("id", String.class); this.parameters = actual.get("parameters", Document.class); } AlertConditionAssertions hasParameter(String field, Object expected) { isNotNull(); if (!parameters.containsKey(field)) { failWithMessage("Parameters do not contain field <%s>", field); } final Object actual = parameters.get(field); Assertions.assertThat(actual) .withFailMessage("Value of field <%s> in alert condition <%s>\nExpected: <%s> (%s)\nActual: <%s> (%s)", field, id, expected, expected.getClass().getCanonicalName(), actual, actual.getClass().getCanonicalName()) .isEqualTo(expected); return this; } } }