/**
* 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.indexer.rotation.strategies;
import org.graylog2.audit.AuditEventSender;
import org.graylog2.indexer.IndexSet;
import org.graylog2.indexer.indexset.IndexSetConfig;
import org.graylog2.indexer.indices.Indices;
import org.graylog2.plugin.InstantMillisProvider;
import org.graylog2.plugin.system.NodeId;
import org.joda.time.DateTime;
import org.joda.time.DateTimeUtils;
import org.joda.time.DateTimeZone;
import org.joda.time.Period;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.util.Optional;
import static org.joda.time.Period.minutes;
import static org.joda.time.Period.seconds;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static org.mockito.internal.verification.VerificationModeFactory.times;
public class TimeBasedRotationStrategyTest {
@Rule
public final MockitoRule mockitoRule = MockitoJUnit.rule();
@Rule
public final ExpectedException expectedException = ExpectedException.none();
@Mock
private IndexSet indexSet;
@Mock
private IndexSetConfig indexSetConfig;
@Mock
private Indices indices;
@Mock
private NodeId nodeId;
@Mock
private AuditEventSender auditEventSender;
private TimeBasedRotationStrategy rotationStrategy;
@Before
public void setUp() {
when(indexSetConfig.id()).thenReturn("index-set-id");
rotationStrategy = new TimeBasedRotationStrategy(indices, nodeId, auditEventSender);
}
@After
public void resetTimeProvider() {
DateTimeUtils.setCurrentMillisSystem();
}
@Test
public void determineAnchor() {
final DateTime initialTime = new DateTime(2014, 3, 15, 14, 48, 35, 0, DateTimeZone.UTC);
final InstantMillisProvider clock = new InstantMillisProvider(initialTime);
DateTimeUtils.setCurrentMillisProvider(clock);
Period period;
// should snap to 14:00:00
period = Period.hours(1);
final DateTime hourAnchor = TimeBasedRotationStrategy.determineRotationPeriodAnchor(null, period);
assertEquals(hourAnchor.getHourOfDay(), 14);
assertEquals(hourAnchor.getMinuteOfHour(), 0);
assertEquals(hourAnchor.getSecondOfMinute(), 0);
// should snap to 14:45:00
period = minutes(5);
final DateTime fiveMins = TimeBasedRotationStrategy.determineRotationPeriodAnchor(null, period);
assertEquals(fiveMins.getHourOfDay(), 14);
assertEquals(fiveMins.getMinuteOfHour(), 45);
assertEquals(fiveMins.getSecondOfMinute(), 0);
// should snap to 2014-3-15 00:00:00
period = Period.days(1).withHours(6);
final DateTime dayAnd6Hours = TimeBasedRotationStrategy.determineRotationPeriodAnchor(null, period);
assertEquals(dayAnd6Hours.getYear(), 2014);
assertEquals(dayAnd6Hours.getMonthOfYear(), 3);
assertEquals(dayAnd6Hours.getDayOfMonth(), 15);
assertEquals(dayAnd6Hours.getHourOfDay(), 0);
assertEquals(dayAnd6Hours.getMinuteOfHour(), 0);
assertEquals(dayAnd6Hours.getSecondOfMinute(), 0);
period = Period.days(30);
final DateTime thirtyDays = TimeBasedRotationStrategy.determineRotationPeriodAnchor(null, period);
assertEquals(thirtyDays.getYear(), 2014);
assertEquals(thirtyDays.getMonthOfYear(), 2);
assertEquals(thirtyDays.getDayOfMonth(), 17);
assertEquals(thirtyDays.getHourOfDay(), 0);
assertEquals(thirtyDays.getMinuteOfHour(), 0);
assertEquals(thirtyDays.getSecondOfMinute(), 0);
period = Period.hours(1);
final DateTime diffAnchor = TimeBasedRotationStrategy.determineRotationPeriodAnchor(initialTime.minusMinutes(61), period);
assertEquals(diffAnchor.getYear(), 2014);
assertEquals(diffAnchor.getMonthOfYear(), 3);
assertEquals(diffAnchor.getDayOfMonth(), 15);
assertEquals(diffAnchor.getHourOfDay(), 13);
assertEquals(diffAnchor.getMinuteOfHour(), 0);
assertEquals(diffAnchor.getSecondOfMinute(), 0);
}
@Test
public void shouldRotateHourly() throws Exception {
final DateTime initialTime = new DateTime(2014, 1, 1, 1, 59, 59, 0, DateTimeZone.UTC);
final Period period = Period.hours(1);
final InstantMillisProvider clock = new InstantMillisProvider(initialTime);
DateTimeUtils.setCurrentMillisProvider(clock);
when(indexSet.getConfig()).thenReturn(indexSetConfig);
when(indexSetConfig.rotationStrategy()).thenReturn(TimeBasedRotationStrategyConfig.create(period));
when(indices.indexCreationDate(anyString())).thenReturn(Optional.of(initialTime.minus(minutes(5))));
// Should not rotate the first index.
when(indexSet.getNewestIndex()).thenReturn("ignored");
rotationStrategy.rotate(indexSet);
verify(indexSet, never()).cycle();
reset(indexSet);
clock.tick(seconds(2));
// Crossed rotation period.
when(indexSet.getNewestIndex()).thenReturn("ignored");
when(indexSet.getConfig()).thenReturn(indexSetConfig);
when(indexSetConfig.rotationStrategy()).thenReturn(TimeBasedRotationStrategyConfig.create(period));
rotationStrategy.rotate(indexSet);
verify(indexSet, times(1)).cycle();
reset(indexSet);
clock.tick(seconds(2));
// Did not cross rotation period.
when(indexSet.getNewestIndex()).thenReturn("ignored");
when(indexSet.getConfig()).thenReturn(indexSetConfig);
when(indexSetConfig.rotationStrategy()).thenReturn(TimeBasedRotationStrategyConfig.create(period));
rotationStrategy.rotate(indexSet);
verify(indexSet, never()).cycle();
reset(indexSet);
}
@Test
public void shouldRotateNonIntegralPeriod() throws Exception {
// start 5 minutes before full hour
final DateTime initialTime = new DateTime(2014, 1, 1, 1, 55, 0, 0, DateTimeZone.UTC);
final Period period = minutes(10);
final InstantMillisProvider clock = new InstantMillisProvider(initialTime);
DateTimeUtils.setCurrentMillisProvider(clock);
when(indexSet.getConfig()).thenReturn(indexSetConfig);
when(indexSetConfig.rotationStrategy()).thenReturn(TimeBasedRotationStrategyConfig.create(period));
when(indices.indexCreationDate(anyString())).thenReturn(Optional.of(initialTime.minus(minutes(11))));
// Should rotate the first index.
// time is 01:55:00, index was created at 01:44:00, so we missed one period, and should rotate
when(indexSet.getNewestIndex()).thenReturn("ignored");
when(indexSet.getConfig()).thenReturn(indexSetConfig);
when(indexSetConfig.rotationStrategy()).thenReturn(TimeBasedRotationStrategyConfig.create(period));
rotationStrategy.rotate(indexSet);
verify(indexSet, times(1)).cycle();
reset(indexSet);
// advance time to 01:55:01
clock.tick(seconds(1));
// Did not cross rotation period.
when(indexSet.getNewestIndex()).thenReturn("ignored");
when(indexSet.getConfig()).thenReturn(indexSetConfig);
when(indexSetConfig.rotationStrategy()).thenReturn(TimeBasedRotationStrategyConfig.create(period));
rotationStrategy.rotate(indexSet);
verify(indexSet, never()).cycle();
reset(indexSet);
// advance time to 02:00:00
clock.tick(minutes(4).withSeconds(59));
// Crossed rotation period.
when(indexSet.getNewestIndex()).thenReturn("ignored");
when(indexSet.getConfig()).thenReturn(indexSetConfig);
when(indexSetConfig.rotationStrategy()).thenReturn(TimeBasedRotationStrategyConfig.create(period));
rotationStrategy.rotate(indexSet);
verify(indexSet, times(1)).cycle();
reset(indexSet);
// advance time multiple rotation periods into the future
// to time 02:51:00
clock.tick(minutes(51));
// Crossed multiple rotation periods.
when(indexSet.getNewestIndex()).thenReturn("ignored");
when(indexSet.getConfig()).thenReturn(indexSetConfig);
when(indexSetConfig.rotationStrategy()).thenReturn(TimeBasedRotationStrategyConfig.create(period));
rotationStrategy.rotate(indexSet);
verify(indexSet, times(1)).cycle();
reset(indexSet);
// move time to 2:52:00
// this should not cycle again, because next valid rotation time is 3:00:00
clock.tick(minutes(1));
when(indexSet.getNewestIndex()).thenReturn("ignored");
when(indexSet.getConfig()).thenReturn(indexSetConfig);
when(indexSetConfig.rotationStrategy()).thenReturn(TimeBasedRotationStrategyConfig.create(period));
rotationStrategy.rotate(indexSet);
verify(indexSet, never()).cycle();
reset(indexSet);
}
@Test
public void shouldRotateThrowsNPEIfIndexSetConfigIsNull() throws Exception {
when(indexSet.getConfig()).thenReturn(null);
when(indexSet.getNewestIndex()).thenReturn("ignored");
expectedException.expect(NullPointerException.class);
expectedException.expectMessage("Index set configuration must not be null");
rotationStrategy.rotate(indexSet);
}
@Test
public void shouldRotateThrowsISEIfIndexIsNull() throws Exception {
when(indexSet.getConfig()).thenReturn(indexSetConfig);
when(indexSet.getNewestIndex()).thenReturn(null);
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Index name must not be null or empty");
rotationStrategy.rotate(indexSet);
}
@Test
public void shouldRotateThrowsISEIfIndexIsEmpty() throws Exception {
when(indexSet.getConfig()).thenReturn(indexSetConfig);
when(indexSet.getNewestIndex()).thenReturn("");
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Index name must not be null or empty");
rotationStrategy.rotate(indexSet);
}
@Test
public void shouldRotateThrowsISEIfIndexSetIdIsNull() throws Exception {
when(indexSet.getConfig()).thenReturn(indexSetConfig);
when(indexSetConfig.id()).thenReturn(null);
when(indexSet.getNewestIndex()).thenReturn("ignored");
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Index set ID must not be null or empty");
rotationStrategy.rotate(indexSet);
}
@Test
public void shouldRotateThrowsISEIfIndexSetIdIsEmpty() throws Exception {
when(indexSet.getConfig()).thenReturn(indexSetConfig);
when(indexSetConfig.id()).thenReturn("");
when(indexSet.getNewestIndex()).thenReturn("ignored");
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Index set ID must not be null or empty");
rotationStrategy.rotate(indexSet);
}
@Test
public void shouldRotateThrowsISEIfRotationStrategyHasIncorrectType() throws Exception {
when(indexSet.getConfig()).thenReturn(indexSetConfig);
when(indexSet.getNewestIndex()).thenReturn("ignored");
when(indexSetConfig.rotationStrategy()).thenReturn(MessageCountRotationStrategyConfig.createDefault());
expectedException.expect(IllegalStateException.class);
expectedException.expectMessage("Invalid rotation strategy config");
rotationStrategy.rotate(indexSet);
}
@Test
public void shouldRotateConcurrently() throws Exception {
final DateTime initialTime = new DateTime(2014, 1, 1, 1, 59, 59, 0, DateTimeZone.UTC);
final Period period = Period.hours(1);
final InstantMillisProvider clock = new InstantMillisProvider(initialTime);
DateTimeUtils.setCurrentMillisProvider(clock);
final IndexSet indexSet1 = mock(IndexSet.class);
final IndexSet indexSet2 = mock(IndexSet.class);
final IndexSetConfig indexSetConfig1 = mock(IndexSetConfig.class);
final IndexSetConfig indexSetConfig2 = mock(IndexSetConfig.class);
when(indexSetConfig1.id()).thenReturn("id1");
when(indexSetConfig2.id()).thenReturn("id2");
when(indexSet1.getConfig()).thenReturn(indexSetConfig1);
when(indexSet2.getConfig()).thenReturn(indexSetConfig2);
when(indexSetConfig1.rotationStrategy()).thenReturn(TimeBasedRotationStrategyConfig.create(period));
when(indexSetConfig2.rotationStrategy()).thenReturn(TimeBasedRotationStrategyConfig.create(period));
when(indices.indexCreationDate(anyString())).thenReturn(Optional.of(initialTime.minus(minutes(5))));
// Should not rotate the initial index.
when(indexSet1.getNewestIndex()).thenReturn("index1");
rotationStrategy.rotate(indexSet1);
verify(indexSet1, never()).cycle();
reset(indexSet1);
when(indexSet2.getNewestIndex()).thenReturn("index2");
rotationStrategy.rotate(indexSet2);
verify(indexSet2, never()).cycle();
reset(indexSet2);
clock.tick(seconds(2));
// Crossed rotation period.
when(indexSet1.getNewestIndex()).thenReturn("index1");
when(indexSet1.getConfig()).thenReturn(indexSetConfig1);
when(indexSetConfig1.rotationStrategy()).thenReturn(TimeBasedRotationStrategyConfig.create(period));
rotationStrategy.rotate(indexSet1);
verify(indexSet1, times(1)).cycle();
reset(indexSet1);
when(indexSet2.getNewestIndex()).thenReturn("index2");
when(indexSet2.getConfig()).thenReturn(indexSetConfig2);
when(indexSetConfig2.rotationStrategy()).thenReturn(TimeBasedRotationStrategyConfig.create(period));
rotationStrategy.rotate(indexSet2);
verify(indexSet2, times(1)).cycle();
reset(indexSet2);
clock.tick(seconds(2));
// Did not cross rotation period.
when(indexSet1.getNewestIndex()).thenReturn("index1");
when(indexSet1.getConfig()).thenReturn(indexSetConfig1);
when(indexSetConfig1.rotationStrategy()).thenReturn(TimeBasedRotationStrategyConfig.create(period));
rotationStrategy.rotate(indexSet1);
verify(indexSet1, never()).cycle();
reset(indexSet1);
when(indexSet2.getNewestIndex()).thenReturn("index2");
when(indexSet2.getConfig()).thenReturn(indexSetConfig2);
when(indexSetConfig2.rotationStrategy()).thenReturn(TimeBasedRotationStrategyConfig.create(period));
rotationStrategy.rotate(indexSet2);
verify(indexSet2, never()).cycle();
reset(indexSet2);
}
}