
org.elasticsearch.script.ScriptService 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.script;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.ResourceNotFoundException;
import org.elasticsearch.action.ActionListener;
import org.elasticsearch.action.admin.cluster.storedscripts.DeleteStoredScriptRequest;
import org.elasticsearch.action.admin.cluster.storedscripts.GetStoredScriptRequest;
import org.elasticsearch.action.admin.cluster.storedscripts.PutStoredScriptRequest;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.cluster.AckedClusterStateUpdateTask;
import org.elasticsearch.cluster.ClusterChangedEvent;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.ClusterStateApplier;
import org.elasticsearch.cluster.metadata.Metadata;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.logging.DeprecationCategory;
import org.elasticsearch.common.logging.DeprecationLogger;
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.core.TimeValue;
import org.elasticsearch.core.internal.io.IOUtils;
import java.io.Closeable;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import java.util.stream.Collectors;
public class ScriptService implements Closeable, ClusterStateApplier, ScriptCompiler {
private static final Logger logger = LogManager.getLogger(ScriptService.class);
private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(ScriptService.class);
static final String DISABLE_DYNAMIC_SCRIPTING_SETTING = "script.disable_dynamic";
// Special setting value for SCRIPT_GENERAL_MAX_COMPILATIONS_RATE to indicate the script service should use context
// specific caches
static final ScriptCache.CompilationRate USE_CONTEXT_RATE_VALUE = new ScriptCache.CompilationRate(-1, TimeValue.MINUS_ONE);
static final String USE_CONTEXT_RATE_KEY = "use-context";
public static final Setting SCRIPT_GENERAL_CACHE_SIZE_SETTING = Setting.intSetting(
"script.cache.max_size",
3000,
0,
Property.Dynamic,
Property.NodeScope
);
public static final Setting SCRIPT_GENERAL_CACHE_EXPIRE_SETTING = Setting.positiveTimeSetting(
"script.cache.expire",
TimeValue.timeValueMillis(0),
Property.Dynamic,
Property.NodeScope
);
public static final Setting SCRIPT_MAX_SIZE_IN_BYTES = Setting.intSetting(
"script.max_size_in_bytes",
65535,
0,
Property.Dynamic,
Property.NodeScope
);
public static final Setting SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING = new Setting<>(
"script.max_compilations_rate",
"150/5m",
(String value) -> value.equals(USE_CONTEXT_RATE_KEY) ? USE_CONTEXT_RATE_VALUE : new ScriptCache.CompilationRate(value),
Property.Dynamic,
Property.NodeScope
);
public static final String USE_CONTEXT_RATE_KEY_DEPRECATION_MESSAGE = "Setting [script.max_compilations_rate] to [use-context] is "
+ "deprecated";
// Per-context settings
static final String CONTEXT_PREFIX = "script.context.";
// script.context..{cache_max_size, cache_expire, max_compilations_rate}
public static final Setting.AffixSetting SCRIPT_CACHE_SIZE_SETTING = Setting.affixKeySetting(
CONTEXT_PREFIX,
"cache_max_size",
key -> Setting.intSetting(key, SCRIPT_GENERAL_CACHE_SIZE_SETTING, 0, Property.NodeScope, Property.Dynamic, Property.Deprecated)
);
public static final Setting.AffixSetting SCRIPT_CACHE_EXPIRE_SETTING = Setting.affixKeySetting(
CONTEXT_PREFIX,
"cache_expire",
key -> Setting.positiveTimeSetting(
key,
SCRIPT_GENERAL_CACHE_EXPIRE_SETTING,
TimeValue.timeValueMillis(0),
Property.NodeScope,
Property.Dynamic,
Property.Deprecated
)
);
// Unlimited compilation rate for context-specific script caches
static final String UNLIMITED_COMPILATION_RATE_KEY = "unlimited";
public static final Setting.AffixSetting SCRIPT_MAX_COMPILATIONS_RATE_SETTING = Setting.affixKeySetting(
CONTEXT_PREFIX,
"max_compilations_rate",
key -> new Setting(
key,
"75/5m",
(String value) -> value.equals(UNLIMITED_COMPILATION_RATE_KEY)
? ScriptCache.UNLIMITED_COMPILATION_RATE
: new ScriptCache.CompilationRate(value),
Property.NodeScope,
Property.Dynamic,
Property.Deprecated
)
);
private static final ScriptCache.CompilationRate SCRIPT_COMPILATION_RATE_ZERO = new ScriptCache.CompilationRate(0, TimeValue.ZERO);
public static final Setting SCRIPT_DISABLE_MAX_COMPILATIONS_RATE_SETTING = Setting.boolSetting(
"script.disable_max_compilations_rate",
false,
Property.NodeScope
);
public static final String ALLOW_NONE = "none";
public static final Setting> TYPES_ALLOWED_SETTING = Setting.listSetting(
"script.allowed_types",
Collections.emptyList(),
Function.identity(),
Setting.Property.NodeScope
);
public static final Setting> CONTEXTS_ALLOWED_SETTING = Setting.listSetting(
"script.allowed_contexts",
Collections.emptyList(),
Function.identity(),
Setting.Property.NodeScope
);
private final Set typesAllowed;
private final Set contextsAllowed;
private final Map engines;
private final Map> contexts;
private ClusterState clusterState;
private int maxSizeInBytes;
// package private for tests
final AtomicReference cacheHolder = new AtomicReference<>();
public ScriptService(Settings settings, Map engines, Map> contexts) {
this.engines = Collections.unmodifiableMap(Objects.requireNonNull(engines));
this.contexts = Collections.unmodifiableMap(Objects.requireNonNull(contexts));
if (Strings.hasLength(settings.get(DISABLE_DYNAMIC_SCRIPTING_SETTING))) {
throw new IllegalArgumentException(
DISABLE_DYNAMIC_SCRIPTING_SETTING
+ " is not a supported setting, replace with "
+ "fine-grained script settings. \n Dynamic scripts can be enabled for all languages and all operations not "
+ "using `script.disable_dynamic: false` in elasticsearch.yml"
);
}
this.typesAllowed = TYPES_ALLOWED_SETTING.exists(settings) ? new HashSet<>() : null;
if (this.typesAllowed != null) {
List typesAllowedList = TYPES_ALLOWED_SETTING.get(settings);
if (typesAllowedList.isEmpty()) {
throw new IllegalArgumentException(
"must specify at least one script type or none for setting [" + TYPES_ALLOWED_SETTING.getKey() + "]."
);
}
for (String settingType : typesAllowedList) {
if (ALLOW_NONE.equals(settingType)) {
if (typesAllowedList.size() != 1) {
throw new IllegalArgumentException(
"cannot specify both ["
+ ALLOW_NONE
+ "]"
+ " and other script types for setting ["
+ TYPES_ALLOWED_SETTING.getKey()
+ "]."
);
} else {
break;
}
}
boolean found = false;
for (ScriptType scriptType : ScriptType.values()) {
if (scriptType.getName().equals(settingType)) {
found = true;
this.typesAllowed.add(settingType);
break;
}
}
if (found == false) {
throw new IllegalArgumentException(
"unknown script type [" + settingType + "] found in setting [" + TYPES_ALLOWED_SETTING.getKey() + "]."
);
}
}
}
this.contextsAllowed = CONTEXTS_ALLOWED_SETTING.exists(settings) ? new HashSet<>() : null;
if (this.contextsAllowed != null) {
List contextsAllowedList = CONTEXTS_ALLOWED_SETTING.get(settings);
if (contextsAllowedList.isEmpty()) {
throw new IllegalArgumentException(
"must specify at least one script context or none for setting [" + CONTEXTS_ALLOWED_SETTING.getKey() + "]."
);
}
for (String settingContext : contextsAllowedList) {
if (ALLOW_NONE.equals(settingContext)) {
if (contextsAllowedList.size() != 1) {
throw new IllegalArgumentException(
"cannot specify both ["
+ ALLOW_NONE
+ "]"
+ " and other script contexts for setting ["
+ CONTEXTS_ALLOWED_SETTING.getKey()
+ "]."
);
} else {
break;
}
}
if (contexts.containsKey(settingContext)) {
this.contextsAllowed.add(settingContext);
} else {
throw new IllegalArgumentException(
"unknown script context [" + settingContext + "] found in setting [" + CONTEXTS_ALLOWED_SETTING.getKey() + "]."
);
}
}
}
this.setMaxSizeInBytes(SCRIPT_MAX_SIZE_IN_BYTES.get(settings));
// Validation requires knowing which contexts exist.
this.validateCacheSettings(settings);
this.setCacheHolder(settings);
}
public static boolean isUseContextCacheSet(Settings settings) {
return SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING.get(settings).equals(USE_CONTEXT_RATE_VALUE);
}
public static boolean isImplicitContextCacheSet(Settings settings) {
return new ScriptService.ContextSettings(settings).implicitContextCache();
}
public static String contextDeprecationMessage(Settings settings) {
return new ScriptService.ContextSettings(settings).deprecationMessage();
}
/**
* This is overridden in tests to disable compilation rate limiting.
*/
boolean compilationLimitsEnabled() {
return true;
}
void registerClusterSettingsListeners(ClusterSettings clusterSettings) {
clusterSettings.addSettingsUpdateConsumer(SCRIPT_MAX_SIZE_IN_BYTES, this::setMaxSizeInBytes);
// Handle all updatable per-context settings at once for each context.
for (ScriptContext> context : contexts.values()) {
clusterSettings.addSettingsUpdateConsumer(
(settings) -> cacheHolder.get().set(context.name, contextCache(settings, context)),
Arrays.asList(
SCRIPT_CACHE_SIZE_SETTING.getConcreteSettingForNamespace(context.name),
SCRIPT_CACHE_EXPIRE_SETTING.getConcreteSettingForNamespace(context.name),
SCRIPT_MAX_COMPILATIONS_RATE_SETTING.getConcreteSettingForNamespace(context.name),
SCRIPT_GENERAL_CACHE_EXPIRE_SETTING,
// general settings used for fallbacks
SCRIPT_GENERAL_CACHE_SIZE_SETTING
)
);
}
// Handle all settings for context and general caches, this flips between general and context caches.
clusterSettings.addSettingsUpdateConsumer(
this::setCacheHolder,
Arrays.asList(
SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING,
SCRIPT_GENERAL_CACHE_EXPIRE_SETTING,
SCRIPT_GENERAL_CACHE_SIZE_SETTING,
SCRIPT_MAX_COMPILATIONS_RATE_SETTING,
SCRIPT_DISABLE_MAX_COMPILATIONS_RATE_SETTING,
SCRIPT_CACHE_EXPIRE_SETTING,
SCRIPT_CACHE_SIZE_SETTING
),
this::validateCacheSettings
);
}
/**
* Throw an IllegalArgumentException if any per-context setting does not match a context or if per-context settings are configured
* when using the general cache.
*/
void validateCacheSettings(Settings settings) {
ContextSettings contextSettings = new ContextSettings(settings, contexts.keySet());
if (contextSettings.useContextSet) {
deprecationLogger.warn(DeprecationCategory.SCRIPTING, "scripting-context-cache", USE_CONTEXT_RATE_KEY_DEPRECATION_MESSAGE);
} else if (contextSettings.hasContextSettings()) {
deprecationLogger.warn(DeprecationCategory.SCRIPTING, "scripting-context-cache", contextSettings.deprecationMessage());
}
if (contextSettings.incompatibleSettings()) {
throw new IllegalArgumentException(contextSettings.incompatibleSettingsMessage());
}
if (SCRIPT_DISABLE_MAX_COMPILATIONS_RATE_SETTING.get(settings)) {
if (contextSettings.compilationContexts.size() > 0) {
throw new IllegalArgumentException(
"Cannot set custom context compilation rates ["
+ String.join(", ", contextSettings.contextCompilationKeys())
+ "] if compile rates disabled via ["
+ SCRIPT_DISABLE_MAX_COMPILATIONS_RATE_SETTING.getKey()
+ "]"
);
}
if (contextSettings.useContextSet == false && contextSettings.isGeneralCompilationRateSet) {
throw new IllegalArgumentException(
"Cannot set custom general compilation rates ["
+ SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING.getKey()
+ "] to ["
+ SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING.get(settings)
+ "] if compile rates disabled via ["
+ SCRIPT_DISABLE_MAX_COMPILATIONS_RATE_SETTING.getKey()
+ "]"
);
}
}
}
/**
* Collect settings related to script context and general caches.
*
* The general cache is used by default.
* The context cache is used if {@code script.max_compilations_rate} is {@code "use-context"}, a deprecated value.
* The context cache is used implicitly if {@code script.max_compilations_rate} is unset and any of the context
* cache family of settings is used:
* {@code script.context.*.max_compilations_rate}, {@link ScriptService#SCRIPT_MAX_COMPILATIONS_RATE_SETTING}
* {@code script.context.*.cache_max_size}, {@link ScriptService#SCRIPT_CACHE_SIZE_SETTING}
* {@code script.context.*.cache_expire}, {@link ScriptService#SCRIPT_CACHE_EXPIRE_SETTING}
*/
public static class ContextSettings {
public final Settings settings;
public final boolean useContextSet;
public final boolean isGeneralCompilationRateSet;
public final ScriptCache.CompilationRate generalCompilationRate;
public final List compilationContexts;
public final List sizeContexts;
public final List expireContexts;
public ContextSettings(Settings settings, Set contexts) {
this.settings = settings;
generalCompilationRate = SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING.get(settings);
useContextSet = generalCompilationRate.equals(USE_CONTEXT_RATE_VALUE);
isGeneralCompilationRateSet = SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING.exists(settings);
compilationContexts = getContexts(SCRIPT_MAX_COMPILATIONS_RATE_SETTING, settings, contexts);
sizeContexts = getContexts(SCRIPT_CACHE_SIZE_SETTING, settings, contexts);
expireContexts = getContexts(SCRIPT_CACHE_EXPIRE_SETTING, settings, contexts);
}
public ContextSettings(Settings settings) {
this(settings, Collections.emptySet());
}
protected static List getContexts(Setting.AffixSetting> setting, Settings settings, Set contexts) {
List contextSettings = new ArrayList<>();
for (String context : setting.getAsMap(settings).keySet()) {
if (contexts.isEmpty() == false && contexts.contains(context) == false) {
String settingKey = setting.getConcreteSettingForNamespace(context).getKey();
throw new IllegalArgumentException("Context [" + context + "] doesn't exist for setting [" + settingKey + "]");
}
contextSettings.add(context);
}
contextSettings.sort(Comparator.naturalOrder());
return contextSettings;
}
/** Are there any context specific settings */
public boolean hasContextSettings() {
return compilationContexts.isEmpty() == false || expireContexts.isEmpty() == false || sizeContexts.isEmpty() == false;
}
/** deprecation message for implicitly using the context cache */
public String deprecationMessage() {
// Implicitly using the script context cache is deprecated, remove the following deprecated settings to use the script general
// cache.
if (hasContextSettings() == false) {
return "";
}
List settingsKeys = new ArrayList<>();
settingsKeys.addAll(fullKeys(SCRIPT_MAX_COMPILATIONS_RATE_SETTING, compilationContexts));
settingsKeys.addAll(fullKeys(SCRIPT_CACHE_SIZE_SETTING, sizeContexts));
settingsKeys.addAll(fullKeys(SCRIPT_CACHE_EXPIRE_SETTING, expireContexts));
settingsKeys.sort(Comparator.naturalOrder());
return "Implicitly using the script context cache is deprecated, remove settings "
+ "["
+ String.join(", ", settingsKeys)
+ "] to use the script general cache.";
}
/** the context specific max compilation keys */
public List contextCompilationKeys() {
return fullKeys(SCRIPT_MAX_COMPILATIONS_RATE_SETTING, compilationContexts);
}
/** the full keys for the contexts in the context affix setting */
protected static List fullKeys(Setting.AffixSetting> affix, List contexts) {
return contexts.stream().map(ctx -> affix.getConcreteSettingForNamespace(ctx).getKey()).collect(Collectors.toList());
}
/**
* Should the context cache be used? This is true if "use-context" is set explicitly or implicitly, see above for implicit
* definition.
*/
public boolean useContextCache() {
return useContextSet || implicitContextCache();
}
/**
* Implicitly use the script context cache. False if context cache is explicitly used as well as context cache is unused.
*/
public boolean implicitContextCache() {
return useContextSet == false && hasContextSettings() && isGeneralCompilationRateSet == false;
}
/**
* Is the set of settings incompatible? This is the case if:
* 1) {@code script.max_compilations_rate}, {@link ScriptService#SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING} is set but not
* set to "use-context".
* 2) Any of the context cache family of settings is set.
*/
public boolean incompatibleSettings() {
return useContextSet == false && hasContextSettings() && isGeneralCompilationRateSet;
}
/**
* All context specific settings
*/
public List contextSettings() {
List contextSettings = fullKeys(SCRIPT_MAX_COMPILATIONS_RATE_SETTING, compilationContexts);
contextSettings.addAll(fullKeys(SCRIPT_CACHE_SIZE_SETTING, sizeContexts));
contextSettings.addAll(fullKeys(SCRIPT_CACHE_EXPIRE_SETTING, expireContexts));
return contextSettings;
}
/**
* Error message if there are incompatible settings.
*/
public String incompatibleSettingsMessage() {
if (incompatibleSettings() == false) {
return "";
}
List incompatible = contextSettings();
return "Context cache settings ["
+ String.join(",", incompatible)
+ "] are incompatible with ["
+ SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING.getKey()
+ "] set to non-default value ["
+ generalCompilationRate
+ "]."
+ " Either remove the incompatible settings (recommended) or set ["
+ SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING.getKey()
+ "] to ["
+ USE_CONTEXT_RATE_KEY
+ "] to use per-context settings";
}
}
@Override
public void close() throws IOException {
IOUtils.close(engines.values());
}
/**
* @return an unmodifiable {@link Map} of available script context names to {@link ScriptContext}s
*/
public Map> getScriptContexts() {
return contexts;
}
private ScriptEngine getEngine(String lang) {
ScriptEngine scriptEngine = engines.get(lang);
if (scriptEngine == null) {
throw new IllegalArgumentException("script_lang not supported [" + lang + "]");
}
return scriptEngine;
}
/**
* Changes the maximum number of bytes a script's source is allowed to have.
* @param newMaxSizeInBytes The new maximum number of bytes.
*/
void setMaxSizeInBytes(int newMaxSizeInBytes) {
for (Map.Entry source : getScriptsFromClusterState().entrySet()) {
if (source.getValue().getSource().getBytes(StandardCharsets.UTF_8).length > newMaxSizeInBytes) {
throw new IllegalArgumentException(
"script.max_size_in_bytes cannot be set to ["
+ newMaxSizeInBytes
+ "], "
+ "stored script ["
+ source.getKey()
+ "] exceeds the new value with a size of "
+ "["
+ source.getValue().getSource().getBytes(StandardCharsets.UTF_8).length
+ "]"
);
}
}
maxSizeInBytes = newMaxSizeInBytes;
}
/**
* Compiles a script using the given context.
*
* @return a compiled script which may be used to construct instances of a script for the given context
*/
public FactoryType compile(Script script, ScriptContext context) {
Objects.requireNonNull(script);
Objects.requireNonNull(context);
ScriptType type = script.getType();
String lang = script.getLang();
String idOrCode = script.getIdOrCode();
Map options = script.getOptions();
String id = idOrCode;
if (type == ScriptType.STORED) {
// * lang and options will both be null when looking up a stored script,
// so we must get the source to retrieve them before checking if the
// context is supported
// * a stored script must be pulled from the cluster state every time in case
// the script has been updated since the last compilation
StoredScriptSource source = getScriptFromClusterState(id);
lang = source.getLang();
idOrCode = source.getSource();
options = source.getOptions();
}
ScriptEngine scriptEngine = getEngine(lang);
if (isTypeEnabled(type) == false) {
throw new IllegalArgumentException("cannot execute [" + type + "] scripts");
}
if (contexts.containsKey(context.name) == false) {
throw new IllegalArgumentException("script context [" + context.name + "] not supported");
}
if (isContextEnabled(context) == false) {
throw new IllegalArgumentException("cannot execute scripts using [" + context.name + "] context");
}
if (type == ScriptType.INLINE) {
if (idOrCode.getBytes(StandardCharsets.UTF_8).length > maxSizeInBytes) {
throw new IllegalArgumentException(
"exceeded max allowed inline script size in bytes ["
+ maxSizeInBytes
+ "] "
+ "with size ["
+ idOrCode.getBytes(StandardCharsets.UTF_8).length
+ "] for script ["
+ idOrCode
+ "]"
);
}
}
if (logger.isTraceEnabled()) {
logger.trace("compiling lang: [{}] type: [{}] script: {}", lang, type, idOrCode);
}
ScriptCache scriptCache = cacheHolder.get().get(context.name);
assert scriptCache != null : "script context [" + context.name + "] has no script cache";
return scriptCache.compile(context, scriptEngine, id, idOrCode, type, options);
}
public boolean isLangSupported(String lang) {
Objects.requireNonNull(lang);
return engines.containsKey(lang);
}
public boolean isTypeEnabled(ScriptType scriptType) {
return typesAllowed == null || typesAllowed.contains(scriptType.getName());
}
public boolean isContextEnabled(ScriptContext> scriptContext) {
return contextsAllowed == null || contextsAllowed.contains(scriptContext.name);
}
public boolean isAnyContextEnabled() {
return contextsAllowed == null || contextsAllowed.isEmpty() == false;
}
Map getScriptsFromClusterState() {
if (clusterState == null) {
return Collections.emptyMap();
}
ScriptMetadata scriptMetadata = clusterState.metadata().custom(ScriptMetadata.TYPE);
if (scriptMetadata == null) {
return Collections.emptyMap();
}
return scriptMetadata.getStoredScripts();
}
protected StoredScriptSource getScriptFromClusterState(String id) {
ScriptMetadata scriptMetadata = clusterState.metadata().custom(ScriptMetadata.TYPE);
if (scriptMetadata == null) {
throw new ResourceNotFoundException("unable to find script [" + id + "] in cluster state");
}
StoredScriptSource source = scriptMetadata.getStoredScript(id);
if (source == null) {
throw new ResourceNotFoundException("unable to find script [" + id + "] in cluster state");
}
return source;
}
public void putStoredScript(
ClusterService clusterService,
PutStoredScriptRequest request,
ActionListener listener
) {
if (request.content().length() > maxSizeInBytes) {
throw new IllegalArgumentException(
"exceeded max allowed stored script size in bytes ["
+ maxSizeInBytes
+ "] with size ["
+ request.content().length()
+ "] for script ["
+ request.id()
+ "]"
);
}
StoredScriptSource source = request.source();
if (isLangSupported(source.getLang()) == false) {
throw new IllegalArgumentException("unable to put stored script with unsupported lang [" + source.getLang() + "]");
}
try {
ScriptEngine scriptEngine = getEngine(source.getLang());
if (isTypeEnabled(ScriptType.STORED) == false) {
throw new IllegalArgumentException(
"cannot put [" + ScriptType.STORED + "] script, [" + ScriptType.STORED + "] scripts are not enabled"
);
} else if (isAnyContextEnabled() == false) {
throw new IllegalArgumentException("cannot put [" + ScriptType.STORED + "] script, no script contexts are enabled");
} else if (request.context() != null) {
ScriptContext> context = contexts.get(request.context());
if (context == null) {
throw new IllegalArgumentException("Unknown context [" + request.context() + "]");
}
if (context.allowStoredScript == false) {
throw new IllegalArgumentException("cannot store a script for context [" + request.context() + "]");
}
scriptEngine.compile(request.id(), source.getSource(), context, Collections.emptyMap());
}
} catch (ScriptException good) {
throw good;
} catch (Exception exception) {
throw new IllegalArgumentException("failed to parse/compile stored script [" + request.id() + "]", exception);
}
clusterService.submitStateUpdateTask("put-script-" + request.id(), new AckedClusterStateUpdateTask(request, listener) {
@Override
public ClusterState execute(ClusterState currentState) {
ScriptMetadata smd = currentState.metadata().custom(ScriptMetadata.TYPE);
smd = ScriptMetadata.putStoredScript(smd, request.id(), source);
Metadata.Builder mdb = Metadata.builder(currentState.getMetadata()).putCustom(ScriptMetadata.TYPE, smd);
return ClusterState.builder(currentState).metadata(mdb).build();
}
});
}
public void deleteStoredScript(
ClusterService clusterService,
DeleteStoredScriptRequest request,
ActionListener listener
) {
clusterService.submitStateUpdateTask("delete-script-" + request.id(), new AckedClusterStateUpdateTask(request, listener) {
@Override
public ClusterState execute(ClusterState currentState) {
ScriptMetadata smd = currentState.metadata().custom(ScriptMetadata.TYPE);
smd = ScriptMetadata.deleteStoredScript(smd, request.id());
Metadata.Builder mdb = Metadata.builder(currentState.getMetadata()).putCustom(ScriptMetadata.TYPE, smd);
return ClusterState.builder(currentState).metadata(mdb).build();
}
});
}
public StoredScriptSource getStoredScript(ClusterState state, GetStoredScriptRequest request) {
ScriptMetadata scriptMetadata = state.metadata().custom(ScriptMetadata.TYPE);
if (scriptMetadata != null) {
return scriptMetadata.getStoredScript(request.id());
} else {
return null;
}
}
public Set getContextInfos() {
Set infos = new HashSet(contexts.size());
for (ScriptContext> context : contexts.values()) {
infos.add(new ScriptContextInfo(context.name, context.instanceClazz));
}
return infos;
}
public ScriptLanguagesInfo getScriptLanguages() {
Set types = typesAllowed;
if (types == null) {
types = new HashSet<>();
for (ScriptType type : ScriptType.values()) {
types.add(type.getName());
}
}
final Set contexts = contextsAllowed != null ? contextsAllowed : this.contexts.keySet();
Map> languageContexts = new HashMap<>();
engines.forEach(
(key, value) -> languageContexts.put(
key,
value.getSupportedContexts().stream().map(c -> c.name).filter(contexts::contains).collect(Collectors.toSet())
)
);
return new ScriptLanguagesInfo(types, languageContexts);
}
public ScriptStats stats() {
return cacheHolder.get().stats();
}
public ScriptCacheStats cacheStats() {
return cacheHolder.get().cacheStats();
}
@Override
public void applyClusterState(ClusterChangedEvent event) {
clusterState = event.state();
}
void setCacheHolder(Settings settings) {
CacheHolder current = cacheHolder.get();
ContextSettings contextSettings = new ContextSettings(settings, contexts.keySet());
if (current == null) {
if (contextSettings.useContextCache()) {
cacheHolder.set(contextCacheHolder(settings));
} else {
cacheHolder.set(generalCacheHolder(settings));
}
return;
}
// Update
if (contextSettings.useContextCache()) {
if (current.general != null) {
// Flipping to context specific
cacheHolder.set(contextCacheHolder(settings));
}
} else if (current.general == null) {
// Flipping to general
cacheHolder.set(generalCacheHolder(settings));
} else if (current.general.rate.equals(SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING.get(settings)) == false
|| current.general.cacheExpire.equals(SCRIPT_GENERAL_CACHE_EXPIRE_SETTING.get(settings)) == false
|| current.general.cacheSize != SCRIPT_GENERAL_CACHE_SIZE_SETTING.get(settings)) {
// General compilation rate, cache expiration or cache size changed
cacheHolder.set(generalCacheHolder(settings));
}
}
CacheHolder generalCacheHolder(Settings settings) {
ScriptCache.CompilationRate rate = SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING.get(settings);
if (SCRIPT_DISABLE_MAX_COMPILATIONS_RATE_SETTING.get(settings) || compilationLimitsEnabled() == false) {
rate = SCRIPT_COMPILATION_RATE_ZERO;
}
return new CacheHolder(
SCRIPT_GENERAL_CACHE_SIZE_SETTING.get(settings),
SCRIPT_GENERAL_CACHE_EXPIRE_SETTING.get(settings),
rate,
SCRIPT_GENERAL_MAX_COMPILATIONS_RATE_SETTING.getKey()
);
}
CacheHolder contextCacheHolder(Settings settings) {
Map contextCache = new HashMap<>(contexts.size());
contexts.forEach((k, v) -> contextCache.put(k, contextCache(settings, v)));
return new CacheHolder(contextCache);
}
ScriptCache contextCache(Settings settings, ScriptContext> context) {
Setting cacheSizeSetting = SCRIPT_CACHE_SIZE_SETTING.getConcreteSettingForNamespace(context.name);
int cacheSize = cacheSizeSetting.existsOrFallbackExists(settings) ? cacheSizeSetting.get(settings) : context.cacheSizeDefault;
Setting cacheExpireSetting = SCRIPT_CACHE_EXPIRE_SETTING.getConcreteSettingForNamespace(context.name);
TimeValue cacheExpire = cacheExpireSetting.existsOrFallbackExists(settings)
? cacheExpireSetting.get(settings)
: context.cacheExpireDefault;
Setting rateSetting = SCRIPT_MAX_COMPILATIONS_RATE_SETTING.getConcreteSettingForNamespace(
context.name
);
ScriptCache.CompilationRate rate;
if (SCRIPT_DISABLE_MAX_COMPILATIONS_RATE_SETTING.get(settings)
|| compilationLimitsEnabled() == false
|| context.compilationRateLimited == false) {
rate = SCRIPT_COMPILATION_RATE_ZERO;
} else if (rateSetting.existsOrFallbackExists(settings)) {
rate = rateSetting.get(settings);
} else {
rate = new ScriptCache.CompilationRate(ScriptContext.DEFAULT_COMPILATION_RATE_LIMIT);
}
return new ScriptCache(cacheSize, cacheExpire, rate, rateSetting.getKey());
}
/**
* Container for the ScriptCache(s). This class operates in two modes:
* 1) general mode, if the general script cache is configured. There are no context caches in this case.
* 2) context mode, if the context script cache is configured. There is no general cache in this case.
*/
static class CacheHolder {
final ScriptCache general;
final Map> contextCache;
CacheHolder(int cacheMaxSize, TimeValue cacheExpire, ScriptCache.CompilationRate maxCompilationRate, String contextRateSetting) {
contextCache = null;
general = new ScriptCache(cacheMaxSize, cacheExpire, maxCompilationRate, contextRateSetting);
}
CacheHolder(Map context) {
Map> refs = new HashMap<>(context.size());
context.forEach((k, v) -> refs.put(k, new AtomicReference<>(v)));
contextCache = Collections.unmodifiableMap(refs);
general = null;
}
/**
* get the cache appropriate for the context. If in general mode, return the general cache. Otherwise return the ScriptCache for
* the given context. Returns null in context mode if the requested context does not exist.
*/
ScriptCache get(String context) {
if (general != null) {
return general;
}
AtomicReference ref = contextCache.get(context);
if (ref == null) {
return null;
}
return ref.get();
}
ScriptStats stats() {
if (general != null) {
return general.stats();
}
List contextStats = new ArrayList<>(contextCache.size());
for (Map.Entry> entry : contextCache.entrySet()) {
ScriptCache cache = entry.getValue().get();
contextStats.add(cache.stats(entry.getKey()));
}
return new ScriptStats(contextStats);
}
ScriptCacheStats cacheStats() {
if (general != null) {
return new ScriptCacheStats(general.stats());
}
Map context = new HashMap<>(contextCache.size());
for (String name : contextCache.keySet()) {
context.put(name, contextCache.get(name).get().stats());
}
return new ScriptCacheStats(context);
}
/**
* Update a single context cache if we're in the context cache mode otherwise no-op.
*/
void set(String name, ScriptCache cache) {
if (general != null) {
return;
}
AtomicReference ref = contextCache.get(name);
assert ref != null : "expected script cache to exist for context [" + name + "]";
ScriptCache oldCache = ref.get();
assert oldCache != null : "expected script cache to be non-null for context [" + name + "]";
ref.set(cache);
logger.debug("Replaced context [" + name + "] with new settings");
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy