/** * 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.rest.resources.system.indexer; import org.apache.shiro.subject.Subject; import org.graylog2.indexer.IndexSet; import org.graylog2.indexer.IndexSetRegistry; import org.graylog2.indexer.IndexSetStatsCreator; import org.graylog2.indexer.IndexSetValidator; import org.graylog2.indexer.indexset.DefaultIndexSetConfig; import org.graylog2.indexer.indexset.IndexSetConfig; import org.graylog2.indexer.indexset.IndexSetService; import org.graylog2.indexer.indices.jobs.IndexSetCleanupJob; import org.graylog2.indexer.retention.strategies.NoopRetentionStrategy; import org.graylog2.indexer.retention.strategies.NoopRetentionStrategyConfig; import org.graylog2.indexer.rotation.strategies.MessageCountRotationStrategy; import org.graylog2.indexer.rotation.strategies.MessageCountRotationStrategyConfig; import org.graylog2.plugin.cluster.ClusterConfigService; import org.graylog2.rest.resources.system.indexer.requests.IndexSetUpdateRequest; import org.graylog2.rest.resources.system.indexer.responses.IndexSetResponse; import org.graylog2.rest.resources.system.indexer.responses.IndexSetSummary; import org.graylog2.shared.bindings.GuiceInjectorHolder; import org.graylog2.system.jobs.SystemJobManager; import org.junit.Before; import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import javax.inject.Provider; import javax.ws.rs.BadRequestException; import javax.ws.rs.ClientErrorException; import javax.ws.rs.ForbiddenException; import javax.ws.rs.NotFoundException; import java.time.ZoneOffset; import java.time.ZonedDateTime; import java.util.Collections; import java.util.Optional; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; 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.verifyNoMoreInteractions; import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; public class IndexSetsResourceTest { @Rule public final MockitoRule mockitoRule = MockitoJUnit.rule(); @Rule public final ExpectedException expectedException = ExpectedException.none(); @Mock private IndexSetService indexSetService; @Mock private IndexSetRegistry indexSetRegistry; @Mock private IndexSetValidator indexSetValidator; @Mock private IndexSetCleanupJob.Factory indexSetCleanupJobFactory; @Mock private IndexSetStatsCreator indexSetStatsCreator; @Mock private SystemJobManager systemJobManager; @Mock private ClusterConfigService clusterConfigService; public IndexSetsResourceTest() { GuiceInjectorHolder.createInjector(Collections.emptyList()); } private IndexSetsResource indexSetsResource; private Boolean permitted; @Before public void setUp() throws Exception { this.permitted = true; this.indexSetsResource = new TestResource(indexSetService, indexSetRegistry, indexSetValidator, indexSetCleanupJobFactory, indexSetStatsCreator, clusterConfigService, systemJobManager, () -> permitted); } private void notPermitted() { this.permitted = false; } @Test public void list() { final IndexSetConfig indexSetConfig = IndexSetConfig.create( "id", "title", "description", true, "prefix", 1, 0, MessageCountRotationStrategy.class.getCanonicalName(), MessageCountRotationStrategyConfig.create(1000), NoopRetentionStrategy.class.getCanonicalName(), NoopRetentionStrategyConfig.create(1), ZonedDateTime.of(2016, 10, 10, 12, 0, 0, 0, ZoneOffset.UTC), "standard", "index-template", 1, false ); when(indexSetService.findAll()).thenReturn(Collections.singletonList(indexSetConfig)); final IndexSetResponse list = indexSetsResource.list(0, 0, false); verify(indexSetService, times(1)).findAll(); verify(indexSetService, times(1)).getDefault(); verifyNoMoreInteractions(indexSetService); assertThat(list.total()).isEqualTo(1); assertThat(list.indexSets()).containsExactly(IndexSetSummary.fromIndexSetConfig(indexSetConfig, false)); } @Test public void listDenied() { notPermitted(); final IndexSetConfig indexSetConfig = IndexSetConfig.create( "id", "title", "description", true, "prefix", 1, 0, MessageCountRotationStrategy.class.getCanonicalName(), MessageCountRotationStrategyConfig.create(1000), NoopRetentionStrategy.class.getCanonicalName(), NoopRetentionStrategyConfig.create(1), ZonedDateTime.of(2016, 10, 10, 12, 0, 0, 0, ZoneOffset.UTC), "standard", "index-template", 1, false ); when(indexSetService.findAll()).thenReturn(Collections.singletonList(indexSetConfig)); final IndexSetResponse list = indexSetsResource.list(0, 0, false); verify(indexSetService, times(1)).findAll(); verify(indexSetService, times(1)).getDefault(); verifyNoMoreInteractions(indexSetService); assertThat(list.total()).isEqualTo(0); assertThat(list.indexSets()).isEmpty(); } @Test public void list0() { when(indexSetService.findAll()).thenReturn(Collections.emptyList()); final IndexSetResponse list = indexSetsResource.list(0, 0, false); verify(indexSetService, times(1)).findAll(); verify(indexSetService, times(1)).getDefault(); verifyNoMoreInteractions(indexSetService); assertThat(list.total()).isEqualTo(0); assertThat(list.indexSets()).isEmpty(); } @Test public void get() { final IndexSetConfig indexSetConfig = IndexSetConfig.create( "id", "title", "description", true, "prefix", 1, 0, MessageCountRotationStrategy.class.getCanonicalName(), MessageCountRotationStrategyConfig.create(1000), NoopRetentionStrategy.class.getCanonicalName(), NoopRetentionStrategyConfig.create(1), ZonedDateTime.of(2016, 10, 10, 12, 0, 0, 0, ZoneOffset.UTC), "standard", "index-template", 1, false ); when(indexSetService.get("id")).thenReturn(Optional.of(indexSetConfig)); final IndexSetSummary summary = indexSetsResource.get("id"); verify(indexSetService, times(1)).get("id"); verify(indexSetService, times(1)).getDefault(); verifyNoMoreInteractions(indexSetService); assertThat(summary).isEqualTo(IndexSetSummary.fromIndexSetConfig(indexSetConfig, false)); } @Test public void get0() { when(indexSetService.get("id")).thenReturn(Optional.empty()); expectedException.expect(NotFoundException.class); expectedException.expectMessage("Couldn't load index set with ID <id>"); try { indexSetsResource.get("id"); } finally { verify(indexSetService, times(1)).get("id"); verify(indexSetService, times(1)).getDefault(); verifyNoMoreInteractions(indexSetService); } } @Test public void getDenied() { notPermitted(); expectedException.expect(ForbiddenException.class); expectedException.expectMessage("Not authorized to access resource id <id>"); try { indexSetsResource.get("id"); } finally { verifyZeroInteractions(indexSetService); } } @Test public void save() { final IndexSetConfig indexSetConfig = IndexSetConfig.create( "title", "description", true, "prefix", 1, 0, MessageCountRotationStrategy.class.getCanonicalName(), MessageCountRotationStrategyConfig.create(1000), NoopRetentionStrategy.class.getCanonicalName(), NoopRetentionStrategyConfig.create(1), ZonedDateTime.of(2016, 10, 10, 12, 0, 0, 0, ZoneOffset.UTC), "standard", "prefix-template", 1, false ); final IndexSetConfig savedIndexSetConfig = indexSetConfig.toBuilder() .id("id") .build(); when(indexSetService.save(indexSetConfig)).thenReturn(savedIndexSetConfig); final IndexSetSummary summary = indexSetsResource.save(IndexSetSummary.fromIndexSetConfig(indexSetConfig, false)); verify(indexSetService, times(1)).save(indexSetConfig); verify(indexSetService, times(1)).getDefault(); verifyNoMoreInteractions(indexSetService); assertThat(summary.toIndexSetConfig()).isEqualTo(savedIndexSetConfig); } @Test @Ignore("Currently doesn't work with @RequiresPermissions") public void saveDenied() { notPermitted(); final IndexSetConfig indexSetConfig = IndexSetConfig.create( "title", "description", true, "prefix", 1, 0, MessageCountRotationStrategy.class.getCanonicalName(), MessageCountRotationStrategyConfig.create(1000), NoopRetentionStrategy.class.getCanonicalName(), NoopRetentionStrategyConfig.create(1), ZonedDateTime.of(2016, 10, 10, 12, 0, 0, 0, ZoneOffset.UTC), "standard", "index-template", 1, false ); expectedException.expect(ForbiddenException.class); expectedException.expectMessage("Not authorized to access resource id <id>"); try { indexSetsResource.save(IndexSetSummary.fromIndexSetConfig(indexSetConfig, false)); } finally { verifyZeroInteractions(indexSetService); } } @Test public void update() { final IndexSetConfig indexSetConfig = IndexSetConfig.create( "id", "new title", "description", true, "prefix", 1, 0, MessageCountRotationStrategy.class.getCanonicalName(), MessageCountRotationStrategyConfig.create(1000), NoopRetentionStrategy.class.getCanonicalName(), NoopRetentionStrategyConfig.create(1), ZonedDateTime.of(2016, 10, 10, 12, 0, 0, 0, ZoneOffset.UTC), "standard", "index-template", 1, false ); final IndexSetConfig updatedIndexSetConfig = indexSetConfig.toBuilder() .title("new title") .build(); when(indexSetService.get("id")).thenReturn(Optional.of(indexSetConfig)); when(indexSetService.save(indexSetConfig)).thenReturn(updatedIndexSetConfig); final IndexSetSummary summary = indexSetsResource.update("id", IndexSetUpdateRequest.fromIndexSetConfig(indexSetConfig)); verify(indexSetService, times(1)).get("id"); verify(indexSetService, times(1)).save(indexSetConfig); verify(indexSetService, times(1)).getDefault(); verifyNoMoreInteractions(indexSetService); // The real update wouldn't replace the index template nameā€¦ final IndexSetConfig actual = summary.toIndexSetConfig().toBuilder() .indexTemplateName("index-template") .build(); assertThat(actual).isEqualTo(updatedIndexSetConfig); } @Test public void updateDenied() { notPermitted(); final IndexSetConfig indexSetConfig = IndexSetConfig.create( "id", "title", "description", true, "prefix", 1, 0, MessageCountRotationStrategy.class.getCanonicalName(), MessageCountRotationStrategyConfig.create(1000), NoopRetentionStrategy.class.getCanonicalName(), NoopRetentionStrategyConfig.create(1), ZonedDateTime.of(2016, 10, 10, 12, 0, 0, 0, ZoneOffset.UTC), "standard", "index-template", 1, false ); expectedException.expect(ForbiddenException.class); expectedException.expectMessage("Not authorized to access resource id <wrong-id>"); try { indexSetsResource.update("wrong-id", IndexSetUpdateRequest.fromIndexSetConfig(indexSetConfig)); } finally { verifyZeroInteractions(indexSetService); } } @Test public void updateFailsWhenDefaultSetIsSetReadOnly() throws Exception { final String defaultIndexSetId = "defaultIndexSet"; final IndexSetConfig defaultIndexSetConfig = IndexSetConfig.create( defaultIndexSetId, "title", "description", true, "prefix", 1, 0, MessageCountRotationStrategy.class.getCanonicalName(), MessageCountRotationStrategyConfig.create(1000), NoopRetentionStrategy.class.getCanonicalName(), NoopRetentionStrategyConfig.create(1), ZonedDateTime.of(2016, 10, 10, 12, 0, 0, 0, ZoneOffset.UTC), "standard", "index-template", 1, false ); when(indexSetService.getDefault()).thenReturn(defaultIndexSetConfig); when(indexSetService.get(defaultIndexSetId)).thenReturn(Optional.of(defaultIndexSetConfig)); final IndexSetConfig defaultIndexSetConfigSetReadOnly = defaultIndexSetConfig.toBuilder().isWritable(false).build(); expectedException.expect(ClientErrorException.class); expectedException.expectMessage("Default index set must be writable."); try { indexSetsResource.update("defaultIndexSet", IndexSetUpdateRequest.fromIndexSetConfig(defaultIndexSetConfigSetReadOnly)); } finally { verify(indexSetService, never()).save(any()); } } @Test public void delete() throws Exception { final IndexSet indexSet = mock(IndexSet.class); final IndexSetConfig indexSetConfig = mock(IndexSetConfig.class); when(indexSet.getConfig()).thenReturn(indexSetConfig); when(indexSetRegistry.get("id")).thenReturn(Optional.of(indexSet)); when(indexSetCleanupJobFactory.create(indexSet)).thenReturn(mock(IndexSetCleanupJob.class)); when(indexSetRegistry.getDefault()).thenReturn(null); when(indexSetService.delete("id")).thenReturn(1); indexSetsResource.delete("id", false); indexSetsResource.delete("id", true); verify(indexSetRegistry, times(2)).getDefault(); verify(indexSetService, times(2)).delete("id"); verify(systemJobManager, times(1)).submit(any(IndexSetCleanupJob.class)); verifyNoMoreInteractions(indexSetService); } @Test public void delete0() throws Exception { final IndexSet indexSet = mock(IndexSet.class); final IndexSetConfig indexSetConfig = mock(IndexSetConfig.class); when(indexSet.getConfig()).thenReturn(indexSetConfig); when(indexSetRegistry.getDefault()).thenReturn(null); when(indexSetRegistry.get("id")).thenReturn(Optional.of(indexSet)); when(indexSetService.delete("id")).thenReturn(0); expectedException.expect(NotFoundException.class); expectedException.expectMessage("Couldn't delete index set with ID <id>"); try { indexSetsResource.delete("id", false); } finally { verify(indexSetRegistry, times(1)).getDefault(); verify(indexSetService, times(1)).delete("id"); verifyNoMoreInteractions(indexSetService); } } @Test public void deleteDefaultIndexSet() throws Exception { final IndexSet indexSet = mock(IndexSet.class); final IndexSetConfig indexSetConfig = mock(IndexSetConfig.class); when(indexSet.getConfig()).thenReturn(indexSetConfig); when(indexSetRegistry.getDefault()).thenReturn(indexSet); when(indexSetRegistry.get("id")).thenReturn(Optional.of(indexSet)); when(indexSetCleanupJobFactory.create(indexSet)).thenReturn(mock(IndexSetCleanupJob.class)); when(indexSetService.delete("id")).thenReturn(1); expectedException.expect(BadRequestException.class); indexSetsResource.delete("id", false); indexSetsResource.delete("id", true); verify(indexSetService, never()).delete("id"); verify(systemJobManager, never()).submit(any(IndexSetCleanupJob.class)); verifyNoMoreInteractions(indexSetService); } @Test public void deleteDenied() { notPermitted(); expectedException.expect(ForbiddenException.class); expectedException.expectMessage("Not authorized to access resource id <id>"); try { indexSetsResource.delete("id", false); } finally { verifyZeroInteractions(indexSetService); } } @Test public void setDefaultMakesIndexDefaultIfWritable() throws Exception { final String indexSetId = "newDefaultIndexSetId"; final IndexSet indexSet = mock(IndexSet.class); final IndexSetConfig indexSetConfig = IndexSetConfig.create( indexSetId, "title", "description", true, "prefix", 1, 0, MessageCountRotationStrategy.class.getCanonicalName(), MessageCountRotationStrategyConfig.create(1000), NoopRetentionStrategy.class.getCanonicalName(), NoopRetentionStrategyConfig.create(1), ZonedDateTime.of(2016, 10, 10, 12, 0, 0, 0, ZoneOffset.UTC), "standard", "index-template", 1, false ); when(indexSet.getConfig()).thenReturn(indexSetConfig); when(indexSetService.get(indexSetId)).thenReturn(Optional.of(indexSetConfig)); indexSetsResource.setDefault(indexSetId); final ArgumentCaptor<DefaultIndexSetConfig> defaultIndexSetIdCaptor = ArgumentCaptor.forClass(DefaultIndexSetConfig.class); verify(clusterConfigService, times(1)).write(defaultIndexSetIdCaptor.capture()); final DefaultIndexSetConfig defaultIndexSetConfig = defaultIndexSetIdCaptor.getValue(); assertThat(defaultIndexSetConfig).isNotNull(); assertThat(defaultIndexSetConfig.defaultIndexSetId()).isEqualTo(indexSetId); } @Test public void setDefaultDoesNotDoAnyThingIfNotPermitted() throws Exception { notPermitted(); expectedException.expect(ForbiddenException.class); expectedException.expectMessage("Not authorized to access resource id <someIndexSetId>"); try { indexSetsResource.setDefault("someIndexSetId"); } finally { verifyZeroInteractions(indexSetService); verifyZeroInteractions(clusterConfigService); } } @Test public void setDefaultDoesNotDoAnythingForInvalidId() throws Exception { final String nonExistingIndexSetId = "nonExistingId"; when(indexSetService.get(nonExistingIndexSetId)).thenReturn(Optional.empty()); expectedException.expect(NotFoundException.class); expectedException.expectMessage("Index set <" + nonExistingIndexSetId + "> does not exist"); try { indexSetsResource.setDefault(nonExistingIndexSetId); } finally { verifyZeroInteractions(clusterConfigService); } } @Test public void setDefaultDoesNotDoAnythingIfIndexSetIsNotWritable() throws Exception { final String readOnlyIndexSetId = "newDefaultIndexSetId"; final IndexSet readOnlyIndexSet = mock(IndexSet.class); final IndexSetConfig readOnlyIndexSetConfig = IndexSetConfig.create( readOnlyIndexSetId, "title", "description", false, "prefix", 1, 0, MessageCountRotationStrategy.class.getCanonicalName(), MessageCountRotationStrategyConfig.create(1000), NoopRetentionStrategy.class.getCanonicalName(), NoopRetentionStrategyConfig.create(1), ZonedDateTime.of(2016, 10, 10, 12, 0, 0, 0, ZoneOffset.UTC), "standard", "index-template", 1, false ); when(readOnlyIndexSet.getConfig()).thenReturn(readOnlyIndexSetConfig); when(indexSetService.get(readOnlyIndexSetId)).thenReturn(Optional.of(readOnlyIndexSetConfig)); expectedException.expect(ClientErrorException.class); expectedException.expectMessage("Default index set must be writable."); try { indexSetsResource.setDefault(readOnlyIndexSetId); } finally { verifyZeroInteractions(clusterConfigService); } } private static class TestResource extends IndexSetsResource { private final Provider<Boolean> permitted; TestResource(IndexSetService indexSetService, IndexSetRegistry indexSetRegistry, IndexSetValidator indexSetValidator, IndexSetCleanupJob.Factory indexSetCleanupJobFactory, IndexSetStatsCreator indexSetStatsCreator, ClusterConfigService clusterConfigService, SystemJobManager systemJobManager, Provider<Boolean> permitted) { super(indexSetService, indexSetRegistry, indexSetValidator, indexSetCleanupJobFactory, indexSetStatsCreator, clusterConfigService, systemJobManager); this.permitted = permitted; } @Override protected Subject getSubject() { final Subject mockSubject = mock(Subject.class); when(mockSubject.isPermitted(anyString())).thenReturn(permitted.get()); return mockSubject; } } }