/*
* Licensed to Elasticsearch under one or more contributor
* license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright
* ownership. Elasticsearch licenses this file to you under
* the Apache License, Version 2.0 (the "License"); you may
* not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.elasticsearch.script;
import org.elasticsearch.common.Nullable;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.search.lookup.SearchLookup;
import org.elasticsearch.test.ESTestCase;
import org.junit.After;
import org.junit.Before;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static java.util.Collections.unmodifiableMap;
import static org.elasticsearch.common.util.set.Sets.newHashSet;
import static org.hamcrest.CoreMatchers.equalTo;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.Matchers.containsString;
// TODO: this needs to be a base test class, and all scripting engines extend it
public class ScriptModesTests extends ESTestCase {
ScriptSettings scriptSettings;
ScriptContextRegistry scriptContextRegistry;
private ScriptContext[] scriptContexts;
private Map<String, ScriptEngine> scriptEngines;
private ScriptModes scriptModes;
private Set<String> checkedSettings;
private boolean assertAllSettingsWereChecked;
private boolean assertScriptModesNonNull;
@Before
public void setupScriptEngines() {
//randomly register custom script contexts
int randomInt = randomIntBetween(0, 3);
//prevent duplicates using map
Map<String, ScriptContext.Plugin> contexts = new HashMap<>();
for (int i = 0; i < randomInt; i++) {
String plugin = randomAlphaOfLength(randomIntBetween(1, 10));
String operation = randomAlphaOfLength(randomIntBetween(1, 30));
String context = plugin + "-" + operation;
contexts.put(context, new ScriptContext.Plugin(plugin, operation));
}
scriptContextRegistry = new ScriptContextRegistry(contexts.values());
scriptContexts = scriptContextRegistry.scriptContexts().toArray(new ScriptContext[scriptContextRegistry.scriptContexts().size()]);
scriptEngines = buildScriptEnginesByLangMap(newHashSet(
//add the native engine just to make sure it gets filtered out
new NativeScriptEngine(Settings.EMPTY, Collections.<String, NativeScriptFactory>emptyMap()),
new CustomScriptEngine()));
ScriptEngineRegistry scriptEngineRegistry = new ScriptEngineRegistry(scriptEngines.values());
scriptSettings = new ScriptSettings(scriptEngineRegistry, scriptContextRegistry);
checkedSettings = new HashSet<>();
assertAllSettingsWereChecked = true;
assertScriptModesNonNull = true;
}
@After
public void assertNativeScriptsAreAlwaysAllowed() {
if (assertScriptModesNonNull) {
assertThat(scriptModes.getScriptEnabled(NativeScriptEngine.NAME, randomFrom(ScriptType.values()), randomFrom(scriptContexts)), equalTo(true));
}
}
@After
public void assertAllSettingsWereChecked() {
if (assertScriptModesNonNull) {
assertThat(scriptModes, notNullValue());
int numberOfSettings = ScriptType.values().length * scriptContextRegistry.scriptContexts().size();
numberOfSettings += 3; // for top-level inline/store/file settings
assertThat(scriptModes.scriptEnabled.size(), equalTo(numberOfSettings));
if (assertAllSettingsWereChecked) {
assertThat(checkedSettings.size(), equalTo(numberOfSettings));
}
}
}
public void testDefaultSettings() {
this.scriptModes = new ScriptModes(scriptSettings, Settings.EMPTY);
assertScriptModesAllOps(true, ScriptType.FILE);
assertScriptModesAllOps(false, ScriptType.STORED, ScriptType.INLINE);
}
public void testMissingSetting() {
assertAllSettingsWereChecked = false;
this.scriptModes = new ScriptModes(scriptSettings, Settings.EMPTY);
try {
scriptModes.getScriptEnabled("non_existing", randomFrom(ScriptType.values()), randomFrom(scriptContexts));
fail("Expected IllegalArgumentException");
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(), containsString("not found for lang [non_existing]"));
}
}
public void testScriptTypeGenericSettings() {
int randomInt = randomIntBetween(1, ScriptType.values().length - 1);
Set<ScriptType> randomScriptTypesSet = new HashSet<>();
boolean[] randomScriptModes = new boolean[randomInt];
for (int i = 0; i < randomInt; i++) {
boolean added = false;
while (added == false) {
added = randomScriptTypesSet.add(randomFrom(ScriptType.values()));
}
randomScriptModes[i] = randomBoolean();
}
ScriptType[] randomScriptTypes = randomScriptTypesSet.toArray(new ScriptType[randomScriptTypesSet.size()]);
List<String> deprecated = new ArrayList<>();
Settings.Builder builder = Settings.builder();
for (int i = 0; i < randomInt; i++) {
builder.put("script" + "." + randomScriptTypes[i].getName(), randomScriptModes[i]);
deprecated.add("script" + "." + randomScriptTypes[i].getName());
}
this.scriptModes = new ScriptModes(scriptSettings, builder.build());
for (int i = 0; i < randomInt; i++) {
assertScriptModesAllOps(randomScriptModes[i], randomScriptTypes[i]);
}
if (randomScriptTypesSet.contains(ScriptType.FILE) == false) {
assertScriptModesAllOps(true, ScriptType.FILE);
}
if (randomScriptTypesSet.contains(ScriptType.STORED) == false) {
assertScriptModesAllOps(false, ScriptType.STORED);
}
if (randomScriptTypesSet.contains(ScriptType.INLINE) == false) {
assertScriptModesAllOps(false, ScriptType.INLINE);
}
assertSettingDeprecationsAndWarnings(
ScriptSettingsTests.buildDeprecatedSettingsArray(scriptSettings, deprecated.toArray(new String[] {})));
}
public void testScriptContextGenericSettings() {
int randomInt = randomIntBetween(1, scriptContexts.length - 1);
Set<ScriptContext> randomScriptContextsSet = new HashSet<>();
boolean[] randomScriptModes = new boolean[randomInt];
for (int i = 0; i < randomInt; i++) {
boolean added = false;
while (added == false) {
added = randomScriptContextsSet.add(randomFrom(scriptContexts));
}
randomScriptModes[i] = randomBoolean();
}
ScriptContext[] randomScriptContexts = randomScriptContextsSet.toArray(new ScriptContext[randomScriptContextsSet.size()]);
List<String> deprecated = new ArrayList<>();
Settings.Builder builder = Settings.builder();
for (int i = 0; i < randomInt; i++) {
builder.put("script" + "." + randomScriptContexts[i].getKey(), randomScriptModes[i]);
deprecated.add("script" + "." + randomScriptContexts[i].getKey());
}
this.scriptModes = new ScriptModes(scriptSettings, builder.build());
for (int i = 0; i < randomInt; i++) {
assertScriptModesAllTypes(randomScriptModes[i], randomScriptContexts[i]);
}
ScriptContext[] complementOf = complementOf(randomScriptContexts);
assertScriptModes(true, new ScriptType[]{ScriptType.FILE}, complementOf);
assertScriptModes(false, new ScriptType[]{ScriptType.STORED, ScriptType.INLINE}, complementOf);
assertSettingDeprecationsAndWarnings(
ScriptSettingsTests.buildDeprecatedSettingsArray(scriptSettings, deprecated.toArray(new String[] {})));
}
public void testConflictingScriptTypeAndOpGenericSettings() {
ScriptContext scriptContext = randomFrom(scriptContexts);
Settings.Builder builder = Settings.builder()
.put("script." + scriptContext.getKey(), "false")
.put("script.stored", "true")
.put("script.inline", "true");
//operations generic settings have precedence over script type generic settings
this.scriptModes = new ScriptModes(scriptSettings, builder.build());
assertScriptModesAllTypes(false, scriptContext);
ScriptContext[] complementOf = complementOf(scriptContext);
assertScriptModes(true, new ScriptType[]{ScriptType.FILE, ScriptType.STORED}, complementOf);
assertScriptModes(true, new ScriptType[]{ScriptType.INLINE}, complementOf);
assertSettingDeprecationsAndWarnings(
ScriptSettingsTests.buildDeprecatedSettingsArray(
scriptSettings, "script." + scriptContext.getKey(), "script.stored", "script.inline"));
}
private void assertScriptModesAllOps(boolean expectedScriptEnabled, ScriptType... scriptTypes) {
assertScriptModes(expectedScriptEnabled, scriptTypes, scriptContexts);
}
private void assertScriptModesAllTypes(boolean expectedScriptEnabled, ScriptContext... scriptContexts) {
assertScriptModes(expectedScriptEnabled, ScriptType.values(), scriptContexts);
}
private void assertScriptModes(boolean expectedScriptEnabled, ScriptType[] scriptTypes, ScriptContext... scriptContexts) {
assert scriptTypes.length > 0;
assert scriptContexts.length > 0;
for (ScriptType scriptType : scriptTypes) {
checkedSettings.add("script.engine.custom." + scriptType);
for (ScriptContext scriptContext : scriptContexts) {
assertThat("custom." + scriptType + "." + scriptContext.getKey() + " doesn't have the expected value",
scriptModes.getScriptEnabled("custom", scriptType, scriptContext), equalTo(expectedScriptEnabled));
checkedSettings.add("custom." + scriptType + "." + scriptContext);
}
}
}
private ScriptContext[] complementOf(ScriptContext... scriptContexts) {
Map<String, ScriptContext> copy = new HashMap<>();
for (ScriptContext scriptContext : scriptContextRegistry.scriptContexts()) {
copy.put(scriptContext.getKey(), scriptContext);
}
for (ScriptContext scriptContext : scriptContexts) {
copy.remove(scriptContext.getKey());
}
return copy.values().toArray(new ScriptContext[copy.size()]);
}
static Map<String, ScriptEngine> buildScriptEnginesByLangMap(Set<ScriptEngine> scriptEngines) {
Map<String, ScriptEngine> builder = new HashMap<>();
for (ScriptEngine scriptEngine : scriptEngines) {
String type = scriptEngine.getType();
builder.put(type, scriptEngine);
}
return unmodifiableMap(builder);
}
private static class CustomScriptEngine implements ScriptEngine {
public static final String NAME = "custom";
@Override
public String getType() {
return NAME;
}
@Override
public String getExtension() {
return NAME;
}
@Override
public Object compile(String scriptName, String scriptSource, Map<String, String> params) {
return null;
}
@Override
public ExecutableScript executable(CompiledScript compiledScript, @Nullable Map<String, Object> vars) {
return null;
}
@Override
public SearchScript search(CompiledScript compiledScript, SearchLookup lookup, @Nullable Map<String, Object> vars) {
return null;
}
@Override
public void close() {
}
}
}