
org.elasticsearch.cluster.action.index.MappingUpdatedAction Maven / Gradle / Ivy
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0 and the Server Side Public License, v 1; you may not use this file except
* in compliance with, at your election, the Elastic License 2.0 or the Server
* Side Public License, v 1.
*/
package org.elasticsearch.cluster.action.index;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.Version;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.admin.indices.mapping.put.AutoPutMappingAction;
import org.elasticsearch.action.admin.indices.mapping.put.PutMappingRequest;
import org.elasticsearch.action.support.master.MasterNodeRequest;
import org.elasticsearch.client.Client;
import org.elasticsearch.client.IndicesAdminClient;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.inject.Inject;
import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Setting.Property;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.AdjustableSemaphore;
import org.elasticsearch.common.util.concurrent.RunOnce;
import org.elasticsearch.common.util.concurrent.UncategorizedExecutionException;
import org.elasticsearch.core.TimeValue;
import org.elasticsearch.index.Index;
import org.elasticsearch.index.mapper.MapperService;
import org.elasticsearch.index.mapper.Mapping;
import org.elasticsearch.xcontent.XContentType;
/**
* Called by shards in the cluster when their mapping was dynamically updated and it needs to be updated
* in the cluster state meta data (and broadcast to all members).
*/
public class MappingUpdatedAction {
public static final Setting INDICES_MAPPING_DYNAMIC_TIMEOUT_SETTING = Setting.positiveTimeSetting(
"indices.mapping.dynamic_timeout",
TimeValue.timeValueSeconds(30),
Property.Dynamic,
Property.NodeScope
);
public static final Setting INDICES_MAX_IN_FLIGHT_UPDATES_SETTING = Setting.intSetting(
"indices.mapping.max_in_flight_updates",
10,
1,
1000,
Property.Dynamic,
Property.NodeScope
);
private IndicesAdminClient client;
private volatile TimeValue dynamicMappingUpdateTimeout;
private final AdjustableSemaphore semaphore;
private final ClusterService clusterService;
@Inject
public MappingUpdatedAction(Settings settings, ClusterSettings clusterSettings, ClusterService clusterService) {
this.dynamicMappingUpdateTimeout = INDICES_MAPPING_DYNAMIC_TIMEOUT_SETTING.get(settings);
this.semaphore = new AdjustableSemaphore(INDICES_MAX_IN_FLIGHT_UPDATES_SETTING.get(settings), true);
this.clusterService = clusterService;
clusterSettings.addSettingsUpdateConsumer(INDICES_MAPPING_DYNAMIC_TIMEOUT_SETTING, this::setDynamicMappingUpdateTimeout);
clusterSettings.addSettingsUpdateConsumer(INDICES_MAX_IN_FLIGHT_UPDATES_SETTING, this::setMaxInFlightUpdates);
}
private void setDynamicMappingUpdateTimeout(TimeValue dynamicMappingUpdateTimeout) {
this.dynamicMappingUpdateTimeout = dynamicMappingUpdateTimeout;
}
private void setMaxInFlightUpdates(int maxInFlightUpdates) {
semaphore.setMaxPermits(maxInFlightUpdates);
}
public void setClient(Client client) {
this.client = client.admin().indices();
}
/**
* Update mappings on the master node, waiting for the change to be committed,
* but not for the mapping update to be applied on all nodes. The timeout specified by
* {@code timeout} is the master node timeout ({@link MasterNodeRequest#masterNodeTimeout()}),
* potentially waiting for a master node to be available.
*/
public void updateMappingOnMaster(Index index, String type, Mapping mappingUpdate, ActionListener listener) {
if (type.equals(MapperService.DEFAULT_MAPPING)) {
throw new IllegalArgumentException("_default_ mapping should not be updated");
}
final RunOnce release = new RunOnce(() -> semaphore.release());
try {
semaphore.acquire();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
listener.onFailure(e);
return;
}
boolean successFullySent = false;
try {
sendUpdateMapping(index, type, mappingUpdate, ActionListener.runBefore(listener, release::run));
successFullySent = true;
} finally {
if (successFullySent == false) {
release.run();
}
}
}
// used by tests
int blockedThreads() {
return semaphore.getQueueLength();
}
// can be overridden by tests
protected void sendUpdateMapping(Index index, String type, Mapping mappingUpdate, ActionListener listener) {
PutMappingRequest putMappingRequest = new PutMappingRequest();
putMappingRequest.setConcreteIndex(index);
putMappingRequest.type(type);
putMappingRequest.source(mappingUpdate.toString(), XContentType.JSON);
putMappingRequest.masterNodeTimeout(dynamicMappingUpdateTimeout);
putMappingRequest.timeout(TimeValue.ZERO);
if (clusterService.state().nodes().getMinNodeVersion().onOrAfter(Version.V_7_9_0)) {
client.execute(
AutoPutMappingAction.INSTANCE,
putMappingRequest,
ActionListener.wrap(r -> listener.onResponse(null), listener::onFailure)
);
} else {
client.putMapping(
putMappingRequest,
ActionListener.wrap(r -> listener.onResponse(null), e -> listener.onFailure(unwrapException(e)))
);
}
}
// todo: this explicit unwrap should not be necessary, but is until guessRootCause is fixed to allow wrapped non-es exception.
private static Exception unwrapException(Exception cause) {
return cause instanceof ElasticsearchException ? unwrapEsException((ElasticsearchException) cause) : cause;
}
private static RuntimeException unwrapEsException(ElasticsearchException esEx) {
Throwable root = esEx.unwrapCause();
if (root instanceof RuntimeException) {
return (RuntimeException) root;
}
return new UncategorizedExecutionException("Failed execution", root);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy