Skip to content

Commit

Permalink
Index Sets UI (#3132)
Browse files Browse the repository at this point in the history
* Use DocumentTitle component on IndicesPage

* Extenx IndexSetsResource to support pagination

* Start UI for index sets

This allows the creation and configuration of index sets.

* Add config type to retention/rotation config during save and not in store

* Add "standalone" prop to input fields in rotation/retention config

* Let IndexSetRegistry return a list of configs to keep order

* Add comment about MongoDB date issue

* Cleanup after index set deletion and check for assigned streams

- Do not allow index set deletion if any streams use it
- Do not allow deletion of the default index set via REST API
- Create index set cleanup system job to delete the indices and the
  index template

* Implement UI components for index set deletion

* Return to the correct page when pressing Cancel in index set config

* Show index set details/settings in overview and on index set page

* Add missing /cluster/deflector/{indexSet}/cycle endpoint

Fixes manual index rotation for an index set.

* Remove magic cycleCompleted function and list after cycle is done

* Change wording to "Recalculate all index range" until we fix this

* Add missing cancel link
  • Loading branch information
bernd authored and kroepke committed Nov 30, 2016
1 parent fdfb38a commit abbc916
Show file tree
Hide file tree
Showing 53 changed files with 1,514 additions and 355 deletions.
Expand Up @@ -52,6 +52,7 @@
import org.graylog2.indexer.SetIndexReadOnlyJob;
import org.graylog2.indexer.healing.FixDeflectorByDeleteJob;
import org.graylog2.indexer.healing.FixDeflectorByMoveJob;
import org.graylog2.indexer.indices.jobs.IndexSetCleanupJob;
import org.graylog2.indexer.indices.jobs.OptimizeIndexJob;
import org.graylog2.indexer.indices.jobs.SetIndexReadOnlyAndCalculateRangeJob;
import org.graylog2.indexer.ranges.CreateNewSingleIndexRangeJob;
Expand Down Expand Up @@ -130,6 +131,7 @@ private void bindFactoryModules() {
install(new FactoryModuleBuilder().build(RebuildIndexRangesJob.Factory.class));
install(new FactoryModuleBuilder().build(OptimizeIndexJob.Factory.class));
install(new FactoryModuleBuilder().build(SetIndexReadOnlyJob.Factory.class));
install(new FactoryModuleBuilder().build(IndexSetCleanupJob.Factory.class));
install(new FactoryModuleBuilder().build(CreateNewSingleIndexRangeJob.Factory.class));
install(new FactoryModuleBuilder().build(FixDeflectorByDeleteJob.Factory.class));
install(new FactoryModuleBuilder().build(FixDeflectorByMoveJob.Factory.class));
Expand Down
Expand Up @@ -24,6 +24,7 @@
import org.mongojack.DBQuery;

import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;

Expand All @@ -43,7 +44,7 @@ public MongoIndexSetRegistry(IndexSetService indexSetService,
}

private Set<MongoIndexSet> findAllMongoIndexSets() {
final Set<IndexSetConfig> configs = indexSetService.findAll();
final List<IndexSetConfig> configs = indexSetService.findAll();
final ImmutableSet.Builder<MongoIndexSet> mongoIndexSets = ImmutableSet.builder();
for (IndexSetConfig config : configs) {
final MongoIndexSet mongoIndexSet = mongoIndexSetFactory.create(config);
Expand Down
Expand Up @@ -115,6 +115,7 @@ public boolean indexOptimizationDisabled() {
return false;
}

// TODO 2.2: creation_date is a string but needs to be a date - look at AlertImpl for example
@JsonCreator
public static IndexSetConfig create(@Id @ObjectId @JsonProperty("_id") @Nullable String id,
@JsonProperty("title") @NotBlank String title,
Expand Down
Expand Up @@ -19,6 +19,7 @@
import org.bson.types.ObjectId;
import org.mongojack.DBQuery;

import java.util.List;
import java.util.Optional;
import java.util.Set;

Expand Down Expand Up @@ -48,7 +49,17 @@ public interface IndexSetService {
*
* @return All index sets.
*/
Set<IndexSetConfig> findAll();
List<IndexSetConfig> findAll();

/**
* Retrieve a paginated set of index set.
*
* @param indexSetIds List of inde set ids to return
* @param limit Maximum number of index sets
* @param skip Number of index sets to skip
* @return Paginated index sets
*/
List<IndexSetConfig> findPaginated(Set<String> indexSetIds, int limit, int skip);

/**
* Save the given index set.
Expand Down
Expand Up @@ -17,21 +17,26 @@
package org.graylog2.indexer.indexset;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableList;
import org.bson.types.ObjectId;
import org.graylog2.bindings.providers.MongoJackObjectMapperProvider;
import org.graylog2.database.MongoConnection;
import org.graylog2.events.ClusterEventBus;
import org.graylog2.indexer.indexset.events.IndexSetCreatedEvent;
import org.graylog2.indexer.indexset.events.IndexSetDeletedEvent;
import org.graylog2.plugin.streams.Stream;
import org.graylog2.streams.StreamService;
import org.mongojack.DBQuery;
import org.mongojack.DBSort;
import org.mongojack.JacksonDBCollection;
import org.mongojack.WriteResult;

import javax.inject.Inject;
import java.util.Iterator;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;

import static java.util.Objects.requireNonNull;

Expand All @@ -40,23 +45,28 @@ public class MongoIndexSetService implements IndexSetService {

private final JacksonDBCollection<IndexSetConfig, ObjectId> collection;
private final ClusterEventBus clusterEventBus;
private final StreamService streamService;

@Inject
public MongoIndexSetService(MongoConnection mongoConnection,
MongoJackObjectMapperProvider objectMapperProvider,
StreamService streamService,
ClusterEventBus clusterEventBus) {
this(JacksonDBCollection.wrap(
mongoConnection.getDatabase().getCollection(COLLECTION_NAME),
IndexSetConfig.class,
ObjectId.class,
objectMapperProvider.get()),
streamService,
clusterEventBus);
}

@VisibleForTesting
protected MongoIndexSetService(JacksonDBCollection<IndexSetConfig, ObjectId> collection,
StreamService streamService,
ClusterEventBus clusterEventBus) {
this.collection = requireNonNull(collection);
this.streamService = streamService;
this.clusterEventBus = requireNonNull(clusterEventBus);
}

Expand Down Expand Up @@ -91,8 +101,26 @@ public Optional<IndexSetConfig> findOne(DBQuery.Query query) {
* {@inheritDoc}
*/
@Override
public Set<IndexSetConfig> findAll() {
return ImmutableSet.copyOf((Iterator<? extends IndexSetConfig>) collection.find());
public List<IndexSetConfig> findAll() {
return ImmutableList.copyOf((Iterator<? extends IndexSetConfig>) collection.find());
}

/**
* {@inheritDoc}
*/
@Override
public List<IndexSetConfig> findPaginated(Set<String> indexSetIds, int limit, int skip) {
final List<DBQuery.Query> idQuery = indexSetIds.stream()
.map(id -> DBQuery.is("_id", id))
.collect(Collectors.toList());

final DBQuery.Query query = DBQuery.or(idQuery.toArray(new DBQuery.Query[0]));

return ImmutableList.copyOf(collection.find(query)
.sort(DBSort.desc("creation_date"))
.skip(skip)
.limit(limit)
.toArray());
}

/**
Expand Down Expand Up @@ -122,6 +150,10 @@ public int delete(String id) {
*/
@Override
public int delete(ObjectId id) {
if (!isDeletable(id)) {
return 0;
}

final DBQuery.Query query = DBQuery.is("_id", id);
final WriteResult<IndexSetConfig, ObjectId> writeResult = collection.remove(query);

Expand All @@ -133,4 +165,17 @@ public int delete(ObjectId id) {

return removedEntries;
}

private boolean isDeletable(ObjectId id) {
final String stringId = id.toHexString();

// TODO: This can be expensive, create a method in StreamService to find streams for a given index set ID.
for (Stream stream : streamService.loadAll()) {
if (stream.getIndexSetId() != null && stream.getIndexSetId().equals(stringId)) {
return false;
}
}

return true;
}
}
Expand Up @@ -50,6 +50,7 @@
import org.elasticsearch.action.admin.indices.stats.IndicesStatsRequest;
import org.elasticsearch.action.admin.indices.stats.IndicesStatsResponse;
import org.elasticsearch.action.admin.indices.stats.ShardStats;
import org.elasticsearch.action.admin.indices.template.delete.DeleteIndexTemplateRequest;
import org.elasticsearch.action.admin.indices.template.put.PutIndexTemplateRequest;
import org.elasticsearch.action.bulk.BulkRequestBuilder;
import org.elasticsearch.action.bulk.BulkResponse;
Expand Down Expand Up @@ -293,6 +294,28 @@ private void ensureIndexTemplate(IndexSet indexSet) {
}
}

public void deleteIndexTemplate(IndexSet indexSet) {
final String templateName = indexSet.getConfig().indexTemplateName();

final DeleteIndexTemplateRequest deleteRequest = c.admin()
.indices()
.prepareDeleteTemplate(templateName)
.request();

try {
final boolean acknowledged = c.admin()
.indices()
.deleteTemplate(deleteRequest)
.actionGet()
.isAcknowledged();
if (acknowledged) {
LOG.info("Deleted Graylog index template \"{}\" in Elasticsearch.", templateName);
}
} catch (Exception e) {
LOG.error("Unable to delete the Graylog index template: " + templateName, e);
}
}

public boolean create(String indexName, IndexSet indexSet) {
return create(indexName, indexSet, Settings.EMPTY);
}
Expand Down
@@ -0,0 +1,117 @@
/**
* 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.indices.jobs;

import com.google.inject.assistedinject.Assisted;
import org.graylog2.indexer.IndexSet;
import org.graylog2.indexer.indexset.IndexSetConfig;
import org.graylog2.indexer.indices.Indices;
import org.graylog2.system.jobs.SystemJob;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;

public class IndexSetCleanupJob extends SystemJob {
private static final Logger LOG = LoggerFactory.getLogger(IndexSetCleanupJob.class);
private static final int MAX_CONCURRENCY = 1_000;

public interface Factory {
IndexSetCleanupJob create(IndexSet indexSet);
}

private final Indices indices;
private final IndexSet indexSet;

private volatile boolean cancel;
private volatile long total = 0L;
private volatile long deleted = 0L;

@Inject
public IndexSetCleanupJob(final Indices indices, @Assisted final IndexSet indexSet) {
this.indices = indices;
this.indexSet = indexSet;
this.cancel = false;
}

@Override
public void execute() {
final IndexSetConfig config = indexSet.getConfig();
final String[] managedIndices = indexSet.getManagedIndicesNames();

this.total = managedIndices.length;

try {
LOG.info("Deleting index template <{}> from Elasticsearch", config.indexTemplateName());
indices.deleteIndexTemplate(indexSet);
} catch (Exception e) {
LOG.error("Unable to delete index template <{}>", config.indexTemplateName(), e);
}

for (String indexName : managedIndices) {
if (cancel) {
LOG.info("Cancel requested. Deleted <{}> of <{}> indices.", deleted, total);
break;
}
try {
LOG.info("Deleting index <{}> in index set <{}> ({})", indexName, config.id(), config.title());
indices.delete(indexName);
this.deleted++;
} catch (Exception e) {
LOG.error("Unable to delete index <{}>", indexName, e);
}
}
}

@Override
public void requestCancel() {
this.cancel = true;
}

@Override
public int getProgress() {
if (total <= 0) {
return 0;
}
return (int) Math.floor(((float) deleted / (float) total) * 100);
}

@Override
public int maxConcurrency() {
return MAX_CONCURRENCY;
}

@Override
public boolean providesProgress() {
return true;
}

@Override
public boolean isCancelable() {
return true;
}

@Override
public String getDescription() {
return "Deletes all indices in an index set.";
}

@Override
public String getClassName() {
return getClass().getCanonicalName();
}
}
Expand Up @@ -34,6 +34,7 @@

import javax.inject.Inject;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Set;

import static com.google.common.base.Preconditions.checkState;
Expand Down Expand Up @@ -100,7 +101,7 @@ public void upgrade() {
}

private IndexSetConfig findDefaultIndexSet() {
final Set<IndexSetConfig> indexSetConfigs = indexSetService.findAll();
final List<IndexSetConfig> indexSetConfigs = indexSetService.findAll();

// If there is more than one index set, we have a problem. Since there wasn't a way to create index sets
// manually until now, this should not happen.
Expand Down

0 comments on commit abbc916

Please sign in to comment.