EQL: Refactor testing infrastructure (#62928)
Extract reusable methods inside QL TestUtils Rename abstract base classes for clarity Clean-up EQL DataLoader (cherry picked from commit 48db3f285aa8976ead5a9f5d071a9c1046d7bd31)
This commit is contained in:
parent
25106ba58f
commit
ef7a6ce4b2
|
@ -6,12 +6,10 @@
|
||||||
|
|
||||||
package org.elasticsearch.test.eql;
|
package org.elasticsearch.test.eql;
|
||||||
|
|
||||||
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
|
|
||||||
|
|
||||||
import org.elasticsearch.Build;
|
|
||||||
import org.elasticsearch.client.EqlClient;
|
import org.elasticsearch.client.EqlClient;
|
||||||
import org.elasticsearch.client.Request;
|
import org.elasticsearch.client.Request;
|
||||||
import org.elasticsearch.client.RequestOptions;
|
import org.elasticsearch.client.RequestOptions;
|
||||||
|
import org.elasticsearch.client.ResponseException;
|
||||||
import org.elasticsearch.client.RestHighLevelClient;
|
import org.elasticsearch.client.RestHighLevelClient;
|
||||||
import org.elasticsearch.client.eql.EqlSearchRequest;
|
import org.elasticsearch.client.eql.EqlSearchRequest;
|
||||||
import org.elasticsearch.client.eql.EqlSearchResponse;
|
import org.elasticsearch.client.eql.EqlSearchResponse;
|
||||||
|
@ -21,76 +19,49 @@ import org.elasticsearch.client.eql.EqlSearchResponse.Sequence;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.logging.LoggerMessageFormat;
|
import org.elasticsearch.common.logging.LoggerMessageFormat;
|
||||||
import org.elasticsearch.test.rest.ESRestTestCase;
|
import org.elasticsearch.test.rest.ESRestTestCase;
|
||||||
import org.junit.After;
|
import org.junit.AfterClass;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.BeforeClass;
|
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
import static java.util.stream.Collectors.toList;
|
import static java.util.stream.Collectors.toList;
|
||||||
import static org.elasticsearch.test.eql.DataLoader.testIndexName;
|
|
||||||
|
|
||||||
public abstract class CommonEqlActionTestCase extends ESRestTestCase {
|
public abstract class BaseEqlSpecTestCase extends ESRestTestCase {
|
||||||
|
|
||||||
protected static final String PARAM_FORMATTING = "%1$s.test -> %2$s";
|
protected static final String PARAM_FORMATTING = "%1$s.test -> %2$s";
|
||||||
private static int counter = 0;
|
|
||||||
private RestHighLevelClient highLevelClient;
|
private RestHighLevelClient highLevelClient;
|
||||||
|
|
||||||
@BeforeClass
|
private final String index;
|
||||||
public static void checkForSnapshot() {
|
private final String query;
|
||||||
assumeTrue("Only works on snapshot builds for now", Build.CURRENT.isSnapshot());
|
private final String name;
|
||||||
}
|
private final long[] eventIds;
|
||||||
|
private final boolean caseSensitive;
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void setup() throws Exception {
|
private void setup() throws Exception {
|
||||||
if (client().performRequest(new Request("HEAD", "/" + testIndexName)).getStatusLine().getStatusCode() == 404) {
|
if (client().performRequest(new Request("HEAD", "/" + index)).getStatusLine().getStatusCode() == 404) {
|
||||||
DataLoader.loadDatasetIntoEs(highLevelClient(), (t, u) -> createParser(t, u));
|
DataLoader.loadDatasetIntoEs(highLevelClient(), this::createParser);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@AfterClass
|
||||||
public void cleanup() throws Exception {
|
public static void wipeTestData() throws IOException {
|
||||||
if (--counter == 0) {
|
try {
|
||||||
deleteIndex(testIndexName);
|
adminClient().performRequest(new Request("DELETE", "/*"));
|
||||||
}
|
} catch (ResponseException e) {
|
||||||
}
|
// 404 here just means we had no indexes
|
||||||
|
if (e.getResponse().getStatusLine().getStatusCode() != 404) {
|
||||||
@ParametersFactory(shuffle = false, argumentFormatting = PARAM_FORMATTING)
|
throw e;
|
||||||
public static List<Object[]> readTestSpecs() throws Exception {
|
|
||||||
|
|
||||||
// Load EQL validation specs
|
|
||||||
Set<String> uniqueTestNames = new HashSet<>();
|
|
||||||
List<EqlSpec> specs = EqlSpecLoader.load("/test_queries.toml", true, uniqueTestNames);
|
|
||||||
specs.addAll(EqlSpecLoader.load("/additional_test_queries.toml", true, uniqueTestNames));
|
|
||||||
List<EqlSpec> unsupportedSpecs = EqlSpecLoader.load("/test_queries_unsupported.toml", false, uniqueTestNames);
|
|
||||||
|
|
||||||
// Validate only currently supported specs
|
|
||||||
List<EqlSpec> filteredSpecs = new ArrayList<>();
|
|
||||||
|
|
||||||
for (EqlSpec spec : specs) {
|
|
||||||
boolean supported = true;
|
|
||||||
// Check if spec is supported, simple iteration, cause the list is short.
|
|
||||||
for (EqlSpec unSpec : unsupportedSpecs) {
|
|
||||||
if (spec.equals(unSpec)) {
|
|
||||||
supported = false;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (supported) {
|
|
||||||
filteredSpecs.add(spec);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
counter = specs.size();
|
|
||||||
return asArray(filteredSpecs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<Object[]> asArray(List<EqlSpec> specs) {
|
protected static List<Object[]> asArray(List<EqlSpec> specs) {
|
||||||
int counter = 0;
|
int counter = 0;
|
||||||
List<Object[]> results = new ArrayList<>();
|
List<Object[]> results = new ArrayList<>();
|
||||||
|
|
||||||
|
@ -104,7 +75,7 @@ public abstract class CommonEqlActionTestCase extends ESRestTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean[] values = spec.caseSensitive() == null ? new boolean[] { true, false } : new boolean[] { spec.caseSensitive() };
|
boolean[] values = spec.caseSensitive() == null ? new boolean[] { true, false } : new boolean[] { spec.caseSensitive() };
|
||||||
|
|
||||||
for (boolean sensitive : values) {
|
for (boolean sensitive : values) {
|
||||||
String prefixed = name + (sensitive ? "-sensitive" : "-insensitive");
|
String prefixed = name + (sensitive ? "-sensitive" : "-insensitive");
|
||||||
results.add(new Object[] { spec.query(), prefixed, spec.expectedEventIds(), sensitive });
|
results.add(new Object[] { spec.query(), prefixed, spec.expectedEventIds(), sensitive });
|
||||||
|
@ -114,12 +85,9 @@ public abstract class CommonEqlActionTestCase extends ESRestTestCase {
|
||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
private final String query;
|
BaseEqlSpecTestCase(String index, String query, String name, long[] eventIds, boolean caseSensitive) {
|
||||||
private final String name;
|
this.index = index;
|
||||||
private final long[] eventIds;
|
|
||||||
private final boolean caseSensitive;
|
|
||||||
|
|
||||||
public CommonEqlActionTestCase(String query, String name, long[] eventIds, boolean caseSensitive) {
|
|
||||||
this.query = query;
|
this.query = query;
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.eventIds = eventIds;
|
this.eventIds = eventIds;
|
||||||
|
@ -127,7 +95,7 @@ public abstract class CommonEqlActionTestCase extends ESRestTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void test() throws Exception {
|
public void test() throws Exception {
|
||||||
assertResponse(runQuery(testIndexName, query, caseSensitive));
|
assertResponse(runQuery(index, query, caseSensitive));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertResponse(EqlSearchResponse response) {
|
protected void assertResponse(EqlSearchResponse response) {
|
||||||
|
@ -150,15 +118,20 @@ public abstract class CommonEqlActionTestCase extends ESRestTestCase {
|
||||||
// some queries return more than 10 results
|
// some queries return more than 10 results
|
||||||
request.size(50);
|
request.size(50);
|
||||||
request.fetchSize(randomIntBetween(2, 50));
|
request.fetchSize(randomIntBetween(2, 50));
|
||||||
return eqlClient().search(request, RequestOptions.DEFAULT);
|
return runRequest(eqlClient(), request);
|
||||||
}
|
}
|
||||||
|
|
||||||
private EqlClient eqlClient() {
|
protected EqlSearchResponse runRequest(EqlClient eqlClient, EqlSearchRequest request) throws IOException {
|
||||||
|
return eqlClient.search(request, RequestOptions.DEFAULT);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected EqlClient eqlClient() {
|
||||||
return highLevelClient().eql();
|
return highLevelClient().eql();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void assertEvents(List<Event> events) {
|
protected void assertEvents(List<Event> events) {
|
||||||
assertNotNull(events);
|
assertNotNull(events);
|
||||||
|
logger.info("Events {}", events);
|
||||||
long[] expected = eventIds;
|
long[] expected = eventIds;
|
||||||
long[] actual = extractIds(events);
|
long[] actual = extractIds(events);
|
||||||
assertArrayEquals(LoggerMessageFormat.format(null, "unexpected result for spec[{}] [{}] -> {} vs {}", name, query, Arrays.toString(
|
assertArrayEquals(LoggerMessageFormat.format(null, "unexpected result for spec[{}] [{}] -> {} vs {}", name, query, Arrays.toString(
|
||||||
|
@ -166,11 +139,13 @@ public abstract class CommonEqlActionTestCase extends ESRestTestCase {
|
||||||
expected, actual);
|
expected, actual);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static long[] extractIds(List<Event> events) {
|
@SuppressWarnings("unchecked")
|
||||||
|
private long[] extractIds(List<Event> events) {
|
||||||
final int len = events.size();
|
final int len = events.size();
|
||||||
final long ids[] = new long[len];
|
final long[] ids = new long[len];
|
||||||
for (int i = 0; i < len; i++) {
|
for (int i = 0; i < len; i++) {
|
||||||
ids[i] = ((Number) events.get(i).sourceAsMap().get("serial_event_id")).longValue();
|
Object field = events.get(i).sourceAsMap().get(sequenceField());
|
||||||
|
ids[i] = ((Number) field).longValue();
|
||||||
}
|
}
|
||||||
return ids;
|
return ids;
|
||||||
}
|
}
|
||||||
|
@ -199,4 +174,8 @@ public abstract class CommonEqlActionTestCase extends ESRestTestCase {
|
||||||
// Need to preserve data between parameterized tests runs
|
// Need to preserve data between parameterized tests runs
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected String sequenceField() {
|
||||||
|
return "sequence";
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -24,12 +24,12 @@ import org.elasticsearch.common.xcontent.XContentParser;
|
||||||
import org.elasticsearch.common.xcontent.XContentType;
|
import org.elasticsearch.common.xcontent.XContentType;
|
||||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||||
import org.elasticsearch.test.rest.ESRestTestCase;
|
import org.elasticsearch.test.rest.ESRestTestCase;
|
||||||
|
import org.elasticsearch.xpack.ql.TestUtils;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.net.URL;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
@ -39,13 +39,20 @@ import java.util.Map.Entry;
|
||||||
import static org.hamcrest.Matchers.instanceOf;
|
import static org.hamcrest.Matchers.instanceOf;
|
||||||
import static org.junit.Assert.assertThat;
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads EQL dataset into ES.
|
||||||
|
*
|
||||||
|
* Checks for predefined indices:
|
||||||
|
* - endgame-140 - for existing data
|
||||||
|
* - extra - additional data
|
||||||
|
*
|
||||||
|
* While the loader could be made generic, the queries are bound to each index and generalizing that would make things way too complicated.
|
||||||
|
*/
|
||||||
public class DataLoader {
|
public class DataLoader {
|
||||||
|
public static final String TEST_INDEX = "endgame-140";
|
||||||
|
public static final String TEST_EXTRA_INDEX = "extra";
|
||||||
|
|
||||||
private static final String TEST_DATA = "/test_data.json";
|
|
||||||
private static final String MAPPING = "/mapping-default.json";
|
|
||||||
private static final Map<String, String[]> replacementPatterns = Collections.unmodifiableMap(getReplacementPatterns());
|
private static final Map<String, String[]> replacementPatterns = Collections.unmodifiableMap(getReplacementPatterns());
|
||||||
static final String indexPrefix = "endgame";
|
|
||||||
public static final String testIndexName = indexPrefix + "-1.4.0";
|
|
||||||
|
|
||||||
private static final long FILETIME_EPOCH_DIFF = 11644473600000L;
|
private static final long FILETIME_EPOCH_DIFF = 11644473600000L;
|
||||||
private static final long FILETIME_ONE_MILLISECOND = 10 * 1000;
|
private static final long FILETIME_ONE_MILLISECOND = 10 * 1000;
|
||||||
|
@ -63,26 +70,52 @@ public class DataLoader {
|
||||||
ignore -> {
|
ignore -> {
|
||||||
},
|
},
|
||||||
Collections.emptyList()) {
|
Collections.emptyList()) {
|
||||||
}, (t, u) -> createParser(t, u));
|
}, DataLoader::createParser);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void loadDatasetIntoEs(RestHighLevelClient client,
|
public static void loadDatasetIntoEs(RestHighLevelClient client,
|
||||||
CheckedBiFunction<XContent, InputStream, XContentParser, IOException> p) throws IOException {
|
CheckedBiFunction<XContent, InputStream, XContentParser, IOException> p) throws IOException {
|
||||||
|
|
||||||
createTestIndex(client);
|
//
|
||||||
loadData(client, p);
|
// Main Index
|
||||||
|
//
|
||||||
|
load(client, TEST_INDEX, true, p);
|
||||||
|
//
|
||||||
|
// Aux Index
|
||||||
|
//
|
||||||
|
load(client, TEST_EXTRA_INDEX, false, p);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void createTestIndex(RestHighLevelClient client) throws IOException {
|
private static void load(RestHighLevelClient client, String indexName, boolean winFileTime,
|
||||||
CreateIndexRequest request = new CreateIndexRequest(testIndexName).mapping(getMapping(MAPPING), XContentType.JSON);
|
CheckedBiFunction<XContent, InputStream, XContentParser, IOException> p) throws IOException {
|
||||||
|
String name = "/data/" + indexName + ".mapping";
|
||||||
|
URL mapping = DataLoader.class.getResource(name);
|
||||||
|
if (mapping == null) {
|
||||||
|
throw new IllegalArgumentException("Cannot find resource " + name);
|
||||||
|
}
|
||||||
|
name = "/data/" + indexName + ".data";
|
||||||
|
URL data = DataLoader.class.getResource(name);
|
||||||
|
if (data == null) {
|
||||||
|
throw new IllegalArgumentException("Cannot find resource " + name);
|
||||||
|
}
|
||||||
|
createTestIndex(client, indexName, readMapping(mapping));
|
||||||
|
loadData(client, indexName, winFileTime, data, p);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void createTestIndex(RestHighLevelClient client, String indexName, String mapping) throws IOException {
|
||||||
|
CreateIndexRequest request = new CreateIndexRequest(indexName);
|
||||||
|
if (mapping != null) {
|
||||||
|
request.mapping(mapping, XContentType.JSON);
|
||||||
|
}
|
||||||
client.indices().create(request, RequestOptions.DEFAULT);
|
client.indices().create(request, RequestOptions.DEFAULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String getMapping(String mappingPath) throws IOException {
|
/**
|
||||||
try (BufferedReader reader = new BufferedReader(
|
* Reads the mapping file, ignoring comments and replacing placeholders for random types.
|
||||||
new InputStreamReader(DataLoader.class.getResourceAsStream(mappingPath), StandardCharsets.UTF_8)))
|
*/
|
||||||
{
|
private static String readMapping(URL resource) throws IOException {
|
||||||
|
try (BufferedReader reader = TestUtils.reader(resource)) {
|
||||||
StringBuilder b = new StringBuilder();
|
StringBuilder b = new StringBuilder();
|
||||||
String line;
|
String line;
|
||||||
while ((line = reader.readLine()) != null) {
|
while ((line = reader.readLine()) != null) {
|
||||||
|
@ -98,27 +131,30 @@ public class DataLoader {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
private static void loadData(RestHighLevelClient client, CheckedBiFunction<XContent, InputStream, XContentParser, IOException> p)
|
private static void loadData(RestHighLevelClient client, String indexName, boolean winfileTime, URL resource,
|
||||||
|
CheckedBiFunction<XContent, InputStream, XContentParser, IOException> p)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
BulkRequest bulk = new BulkRequest();
|
BulkRequest bulk = new BulkRequest();
|
||||||
bulk.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
|
bulk.setRefreshPolicy(WriteRequest.RefreshPolicy.IMMEDIATE);
|
||||||
|
|
||||||
try (XContentParser parser = p.apply(JsonXContent.jsonXContent, DataLoader.class.getResourceAsStream(TEST_DATA))) {
|
try (XContentParser parser = p.apply(JsonXContent.jsonXContent, TestUtils.inputStream(resource))) {
|
||||||
List<Object> list = parser.list();
|
List<Object> list = parser.list();
|
||||||
for (Object item : list) {
|
for (Object item : list) {
|
||||||
assertThat(item, instanceOf(Map.class));
|
assertThat(item, instanceOf(Map.class));
|
||||||
Map<String, Object> entry = (Map<String, Object>) item;
|
Map<String, Object> entry = (Map<String, Object>) item;
|
||||||
|
if (winfileTime) {
|
||||||
transformDataset(entry);
|
transformDataset(entry);
|
||||||
bulk.add(new IndexRequest(testIndexName).source(entry, XContentType.JSON));
|
|
||||||
}
|
}
|
||||||
|
bulk.add(new IndexRequest(indexName).source(entry, XContentType.JSON));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bulk.numberOfActions() > 0) {
|
if (bulk.numberOfActions() > 0) {
|
||||||
BulkResponse bulkResponse = client.bulk(bulk, RequestOptions.DEFAULT);
|
BulkResponse bulkResponse = client.bulk(bulk, RequestOptions.DEFAULT);
|
||||||
if (bulkResponse.hasFailures()) {
|
if (bulkResponse.hasFailures()) {
|
||||||
LogManager.getLogger(DataLoader.class).info("Data FAILED loading");
|
LogManager.getLogger(DataLoader.class).info("Data loading FAILED");
|
||||||
} else {
|
} else {
|
||||||
LogManager.getLogger(DataLoader.class).info("Data loaded");
|
LogManager.getLogger(DataLoader.class).info("Data loading OK");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.elasticsearch.test.eql;
|
||||||
|
|
||||||
|
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
|
||||||
|
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.elasticsearch.test.eql.DataLoader.TEST_EXTRA_INDEX;
|
||||||
|
|
||||||
|
public abstract class EqlExtraSpecTestCase extends BaseEqlSpecTestCase {
|
||||||
|
|
||||||
|
@ParametersFactory(shuffle = false, argumentFormatting = PARAM_FORMATTING)
|
||||||
|
public static List<Object[]> readTestSpecs() throws Exception {
|
||||||
|
return asArray(EqlSpecLoader.load("/test_extra.toml", true, new HashSet<>()));
|
||||||
|
}
|
||||||
|
|
||||||
|
public EqlExtraSpecTestCase(String query, String name, long[] eventIds, boolean caseSensitive) {
|
||||||
|
super(TEST_EXTRA_INDEX, query, name, eventIds, caseSensitive);
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,7 +26,7 @@ import static org.hamcrest.Matchers.equalTo;
|
||||||
import static org.hamcrest.Matchers.is;
|
import static org.hamcrest.Matchers.is;
|
||||||
import static org.hamcrest.Matchers.not;
|
import static org.hamcrest.Matchers.not;
|
||||||
|
|
||||||
public abstract class CommonEqlRestTestCase extends ESRestTestCase {
|
public abstract class EqlRestTestCase extends ESRestTestCase {
|
||||||
|
|
||||||
private static final String defaultValidationIndexName = "eql_search_validation_test";
|
private static final String defaultValidationIndexName = "eql_search_validation_test";
|
||||||
private static final String validQuery = "process where user = \\\"SYSTEM\\\"";
|
private static final String validQuery = "process where user = \\\"SYSTEM\\\"";
|
|
@ -20,6 +20,9 @@ import java.util.Set;
|
||||||
public class EqlSpecLoader {
|
public class EqlSpecLoader {
|
||||||
public static List<EqlSpec> load(String path, boolean supported, Set<String> uniqueTestNames) throws Exception {
|
public static List<EqlSpec> load(String path, boolean supported, Set<String> uniqueTestNames) throws Exception {
|
||||||
try (InputStream is = EqlSpecLoader.class.getResourceAsStream(path)) {
|
try (InputStream is = EqlSpecLoader.class.getResourceAsStream(path)) {
|
||||||
|
if (is == null) {
|
||||||
|
throw new IllegalAccessException("Cannot find classpath resource " + path);
|
||||||
|
}
|
||||||
return readFromStream(is, supported, uniqueTestNames);
|
return readFromStream(is, supported, uniqueTestNames);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,52 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.elasticsearch.test.eql;
|
||||||
|
|
||||||
|
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import static org.elasticsearch.test.eql.DataLoader.TEST_INDEX;
|
||||||
|
|
||||||
|
public abstract class EqlSpecTestCase extends BaseEqlSpecTestCase {
|
||||||
|
|
||||||
|
@ParametersFactory(shuffle = false, argumentFormatting = PARAM_FORMATTING)
|
||||||
|
public static List<Object[]> readTestSpecs() throws Exception {
|
||||||
|
|
||||||
|
// Load EQL validation specs
|
||||||
|
Set<String> uniqueTestNames = new HashSet<>();
|
||||||
|
List<EqlSpec> specs = EqlSpecLoader.load("/test_queries.toml", true, uniqueTestNames);
|
||||||
|
specs.addAll(EqlSpecLoader.load("/additional_test_queries.toml", true, uniqueTestNames));
|
||||||
|
List<EqlSpec> unsupportedSpecs = EqlSpecLoader.load("/test_queries_unsupported.toml", false, uniqueTestNames);
|
||||||
|
|
||||||
|
// Validate only currently supported specs
|
||||||
|
List<EqlSpec> filteredSpecs = new ArrayList<>();
|
||||||
|
|
||||||
|
for (EqlSpec spec : specs) {
|
||||||
|
boolean supported = true;
|
||||||
|
// Check if spec is supported, simple iteration, cause the list is short.
|
||||||
|
for (EqlSpec unSpec : unsupportedSpecs) {
|
||||||
|
if (spec.equals(unSpec)) {
|
||||||
|
supported = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (supported) {
|
||||||
|
filteredSpecs.add(spec);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return asArray(filteredSpecs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public EqlSpecTestCase(String query, String name, long[] eventIds, boolean caseSensitive) {
|
||||||
|
super(TEST_INDEX, query, name, eventIds, caseSensitive);
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
package org.elasticsearch.test.eql.stats;
|
package org.elasticsearch.test.eql.stats;
|
||||||
|
|
||||||
import org.elasticsearch.Build;
|
|
||||||
import org.elasticsearch.client.Request;
|
import org.elasticsearch.client.Request;
|
||||||
import org.elasticsearch.client.RestHighLevelClient;
|
import org.elasticsearch.client.RestHighLevelClient;
|
||||||
import org.elasticsearch.common.xcontent.XContentHelper;
|
import org.elasticsearch.common.xcontent.XContentHelper;
|
||||||
|
@ -14,7 +13,6 @@ import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||||
import org.elasticsearch.test.eql.DataLoader;
|
import org.elasticsearch.test.eql.DataLoader;
|
||||||
import org.elasticsearch.test.rest.ESRestTestCase;
|
import org.elasticsearch.test.rest.ESRestTestCase;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.BeforeClass;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -27,23 +25,18 @@ import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static java.util.Collections.unmodifiableSet;
|
import static java.util.Collections.unmodifiableSet;
|
||||||
import static org.elasticsearch.test.eql.DataLoader.testIndexName;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests a random number of queries that increase various (most of the times, one query will "touch" multiple metrics values) metrics.
|
* Tests a random number of queries that increase various (most of the times, one query will "touch" multiple metrics values) metrics.
|
||||||
*/
|
*/
|
||||||
public abstract class RestEqlUsageTestCase extends ESRestTestCase {
|
public abstract class EqlUsageRestTestCase extends ESRestTestCase {
|
||||||
|
|
||||||
private RestHighLevelClient highLevelClient;
|
private RestHighLevelClient highLevelClient;
|
||||||
private Map<String, Integer> baseMetrics = new HashMap<String, Integer>();
|
private Map<String, Integer> baseMetrics = new HashMap<String, Integer>();
|
||||||
private Integer baseAllTotalQueries = 0;
|
private Integer baseAllTotalQueries = 0;
|
||||||
private Integer baseAllFailedQueries = 0;
|
private Integer baseAllFailedQueries = 0;
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
public static void checkForSnapshot() {
|
|
||||||
assumeTrue("Only works on snapshot builds for now", Build.CURRENT.isSnapshot());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This method gets the metrics' values before the test runs, in case these values
|
* This method gets the metrics' values before the test runs, in case these values
|
||||||
* were changed by other tests running in the same REST test cluster. The test itself
|
* were changed by other tests running in the same REST test cluster. The test itself
|
||||||
|
@ -57,7 +50,7 @@ public abstract class RestEqlUsageTestCase extends ESRestTestCase {
|
||||||
List<Map<String, Map<String, Map>>> nodesListStats = (List) baseStats.get("stats");
|
List<Map<String, Map<String, Map>>> nodesListStats = (List) baseStats.get("stats");
|
||||||
|
|
||||||
for (Map perNodeStats : nodesListStats) {
|
for (Map perNodeStats : nodesListStats) {
|
||||||
Map queriesMetrics = (Map) ((Map) perNodeStats.get("stats")).get("queries");
|
Map queriesMetrics = (Map) ((Map) perNodeStats.get("stats")).get("queries");
|
||||||
Map featuresMetrics = getFeaturesMetrics(perNodeStats);
|
Map featuresMetrics = getFeaturesMetrics(perNodeStats);
|
||||||
|
|
||||||
for (FeatureMetric metric : FeatureMetric.values()) {
|
for (FeatureMetric metric : FeatureMetric.values()) {
|
||||||
|
@ -121,10 +114,10 @@ public abstract class RestEqlUsageTestCase extends ESRestTestCase {
|
||||||
|
|
||||||
public void testEqlRestUsage() throws IOException {
|
public void testEqlRestUsage() throws IOException {
|
||||||
// create the index and load the data, if the index doesn't exist
|
// create the index and load the data, if the index doesn't exist
|
||||||
// it doesn't matter if the index is already there (probably created by another test); _if_ its mapping is the expected one
|
// it doesn't matter if the index is already there (probably created by another test); _if_ its mapping is the expected one
|
||||||
// it should be enough
|
// it should be enough
|
||||||
if (client().performRequest(new Request("HEAD", "/" + testIndexName)).getStatusLine().getStatusCode() == 404) {
|
if (client().performRequest(new Request("HEAD", "/" + DataLoader.TEST_INDEX)).getStatusLine().getStatusCode() == 404) {
|
||||||
DataLoader.loadDatasetIntoEs(highLevelClient(), (t, u) -> createParser(t, u));
|
DataLoader.loadDatasetIntoEs(highLevelClient(), this::createParser);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
@ -177,13 +170,13 @@ public abstract class RestEqlUsageTestCase extends ESRestTestCase {
|
||||||
int randomMaxspanExecutions = randomIntBetween(1, 15);
|
int randomMaxspanExecutions = randomIntBetween(1, 15);
|
||||||
allTotalQueries += randomMaxspanExecutions;
|
allTotalQueries += randomMaxspanExecutions;
|
||||||
for (int i = 0; i < randomMaxspanExecutions; i++) {
|
for (int i = 0; i < randomMaxspanExecutions; i++) {
|
||||||
runEql("sequence with maxspan=1d" +
|
runEql("sequence with maxspan=1d" +
|
||||||
" [process where serial_event_id < 4] by exit_code" +
|
" [process where serial_event_id < 4] by exit_code" +
|
||||||
" [process where opcode == 1] by user" +
|
" [process where opcode == 1] by user" +
|
||||||
" [process where opcode == 2] by user" +
|
" [process where opcode == 2] by user" +
|
||||||
" [file where parent_process_name == \\\"file_delete_event\\\"] by exit_code" +
|
" [file where parent_process_name == \\\"file_delete_event\\\"] by exit_code" +
|
||||||
" until [process where opcode==1] by ppid" +
|
" until [process where opcode==1] by ppid" +
|
||||||
" | head 4" +
|
" | head 4" +
|
||||||
" | tail 2");
|
" | tail 2");
|
||||||
}
|
}
|
||||||
responseAsMap = getStats();
|
responseAsMap = getStats();
|
||||||
|
@ -199,9 +192,9 @@ public abstract class RestEqlUsageTestCase extends ESRestTestCase {
|
||||||
int randomThreeQueriesSequences = randomIntBetween(1, 15);
|
int randomThreeQueriesSequences = randomIntBetween(1, 15);
|
||||||
allTotalQueries += randomThreeQueriesSequences;
|
allTotalQueries += randomThreeQueriesSequences;
|
||||||
for (int i = 0; i < randomThreeQueriesSequences; i++) {
|
for (int i = 0; i < randomThreeQueriesSequences; i++) {
|
||||||
runEql("sequence with maxspan=1d" +
|
runEql("sequence with maxspan=1d" +
|
||||||
" [process where serial_event_id < 4] by exit_code" +
|
" [process where serial_event_id < 4] by exit_code" +
|
||||||
" [process where opcode == 1] by user" +
|
" [process where opcode == 1] by user" +
|
||||||
" [process where opcode == 2] by user");
|
" [process where opcode == 2] by user");
|
||||||
}
|
}
|
||||||
responseAsMap = getStats();
|
responseAsMap = getStats();
|
||||||
|
@ -217,12 +210,12 @@ public abstract class RestEqlUsageTestCase extends ESRestTestCase {
|
||||||
int randomFiveQueriesSequences = randomIntBetween(1, 15);
|
int randomFiveQueriesSequences = randomIntBetween(1, 15);
|
||||||
allTotalQueries += randomFiveQueriesSequences;
|
allTotalQueries += randomFiveQueriesSequences;
|
||||||
for (int i = 0; i < randomFiveQueriesSequences; i++) {
|
for (int i = 0; i < randomFiveQueriesSequences; i++) {
|
||||||
runEql("sequence by user, ppid, exit_code with maxspan=1m" +
|
runEql("sequence by user, ppid, exit_code with maxspan=1m" +
|
||||||
" [process where serial_event_id < 4]" +
|
" [process where serial_event_id < 4]" +
|
||||||
" [process where opcode == 1]" +
|
" [process where opcode == 1]" +
|
||||||
" [file where parent_process_name == \\\"file_delete_event\\\"]" +
|
" [file where parent_process_name == \\\"file_delete_event\\\"]" +
|
||||||
" [process where serial_event_id < 4]" +
|
" [process where serial_event_id < 4]" +
|
||||||
" [process where opcode == 1]" +
|
" [process where opcode == 1]" +
|
||||||
"| tail 4");
|
"| tail 4");
|
||||||
}
|
}
|
||||||
responseAsMap = getStats();
|
responseAsMap = getStats();
|
||||||
|
@ -238,8 +231,8 @@ public abstract class RestEqlUsageTestCase extends ESRestTestCase {
|
||||||
int randomFourJoinKeysExecutions = randomIntBetween(1, 15);
|
int randomFourJoinKeysExecutions = randomIntBetween(1, 15);
|
||||||
allTotalQueries += randomFourJoinKeysExecutions;
|
allTotalQueries += randomFourJoinKeysExecutions;
|
||||||
for (int i = 0; i < randomFourJoinKeysExecutions; i++) {
|
for (int i = 0; i < randomFourJoinKeysExecutions; i++) {
|
||||||
runEql("sequence by exit_code, user, serial_event_id, pid" +
|
runEql("sequence by exit_code, user, serial_event_id, pid" +
|
||||||
" [process where serial_event_id < 4]" +
|
" [process where serial_event_id < 4]" +
|
||||||
" [process where opcode == 1]");
|
" [process where opcode == 1]");
|
||||||
}
|
}
|
||||||
responseAsMap = getStats();
|
responseAsMap = getStats();
|
||||||
|
@ -254,8 +247,8 @@ public abstract class RestEqlUsageTestCase extends ESRestTestCase {
|
||||||
int randomFiveJoinKeysExecutions = randomIntBetween(1, 15);
|
int randomFiveJoinKeysExecutions = randomIntBetween(1, 15);
|
||||||
allTotalQueries += randomFiveJoinKeysExecutions;
|
allTotalQueries += randomFiveJoinKeysExecutions;
|
||||||
for (int i = 0; i < randomFiveJoinKeysExecutions; i++) {
|
for (int i = 0; i < randomFiveJoinKeysExecutions; i++) {
|
||||||
runEql("sequence by exit_code, user, serial_event_id, pid, ppid" +
|
runEql("sequence by exit_code, user, serial_event_id, pid, ppid" +
|
||||||
" [process where serial_event_id < 4]" +
|
" [process where serial_event_id < 4]" +
|
||||||
" [process where opcode == 1]");
|
" [process where opcode == 1]");
|
||||||
}
|
}
|
||||||
responseAsMap = getStats();
|
responseAsMap = getStats();
|
||||||
|
@ -309,8 +302,12 @@ public abstract class RestEqlUsageTestCase extends ESRestTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void runEql(String eql) throws IOException {
|
private void runEql(String eql) throws IOException {
|
||||||
Request request = new Request("POST", DataLoader.testIndexName + "/_eql/search");
|
Request request = new Request("POST", DataLoader.TEST_INDEX + "/_eql/search");
|
||||||
request.setJsonEntity("{\"query\":\"" + eql +"\"}");
|
request.setJsonEntity("{\"query\":\"" + eql +"\"}");
|
||||||
|
runRequest(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void runRequest(Request request) throws IOException {
|
||||||
client().performRequest(request);
|
client().performRequest(request);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"@timestamp": "1",
|
||||||
|
"event_type": "REQUEST",
|
||||||
|
"transID": 1234,
|
||||||
|
"sequence": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@timestamp": "2",
|
||||||
|
"event_type": "ERROR",
|
||||||
|
"transID": 1234,
|
||||||
|
"sequence": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@timestamp": "3",
|
||||||
|
"event_type": "STAT",
|
||||||
|
"transID": 1234,
|
||||||
|
"sequence": 3
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@timestamp": "10",
|
||||||
|
"event_type": "REQUEST",
|
||||||
|
"transID": 1235,
|
||||||
|
"sequence": 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@timestamp": "11",
|
||||||
|
"event_type": "ERROR",
|
||||||
|
"transID": 1235,
|
||||||
|
"sequence": 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"@timestamp": "11",
|
||||||
|
"event_type": "STAT",
|
||||||
|
"transID": 1235,
|
||||||
|
"sequence": 3
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,25 @@
|
||||||
|
{
|
||||||
|
"properties": {
|
||||||
|
"@timestamp": {
|
||||||
|
"type": "date"
|
||||||
|
},
|
||||||
|
"event_type": {
|
||||||
|
"type": "[runtime_random_keyword_type]"
|
||||||
|
},
|
||||||
|
"sequence": {
|
||||||
|
"type": "long"
|
||||||
|
},
|
||||||
|
"event": {
|
||||||
|
"properties": {
|
||||||
|
"category": {
|
||||||
|
"type": "alias",
|
||||||
|
"path": "event_type"
|
||||||
|
},
|
||||||
|
"sequence": {
|
||||||
|
"type": "alias",
|
||||||
|
"path": "sequence"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
[[queries]]
|
||||||
|
name = "basic"
|
||||||
|
query = '''
|
||||||
|
sequence by transID
|
||||||
|
[ REQUEST where true ]
|
||||||
|
[ ERROR where true ]
|
||||||
|
[ STAT where true ]
|
||||||
|
'''
|
||||||
|
expected_event_ids = [1,2,3]
|
||||||
|
|
||||||
|
[[queries]]
|
||||||
|
name = "basicWithFilter"
|
||||||
|
query = '''
|
||||||
|
sequence by transID
|
||||||
|
[ REQUEST where transID == 1234 ]
|
||||||
|
[ ERROR where true ]
|
||||||
|
[ STAT where true ]
|
||||||
|
'''
|
||||||
|
expected_event_ids = [1,2,3]
|
||||||
|
|
||||||
|
[[queries]]
|
||||||
|
name = "basicWithFilters"
|
||||||
|
query = '''
|
||||||
|
sequence by transID
|
||||||
|
[ REQUEST where transID == 1234 ]
|
||||||
|
[ ERROR where transID == 1234 ]
|
||||||
|
[ STAT where transID == 1234 ]
|
||||||
|
'''
|
||||||
|
expected_event_ids = [1,2,3]
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.elasticsearch.xpack.eql;
|
||||||
|
|
||||||
|
import org.elasticsearch.test.eql.EqlExtraSpecTestCase;
|
||||||
|
import org.elasticsearch.test.junit.annotations.TestLogging;
|
||||||
|
|
||||||
|
@TestLogging(value = "org.elasticsearch.xpack.eql:TRACE", reason = "results logging")
|
||||||
|
public class EqlExtraIT extends EqlExtraSpecTestCase {
|
||||||
|
|
||||||
|
public EqlExtraIT(String query, String name, long[] eventIds, boolean caseSensitive) {
|
||||||
|
super(query, name, eventIds, caseSensitive);
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,7 @@
|
||||||
|
|
||||||
package org.elasticsearch.xpack.eql;
|
package org.elasticsearch.xpack.eql;
|
||||||
|
|
||||||
import org.elasticsearch.test.eql.CommonEqlRestTestCase;
|
import org.elasticsearch.test.eql.EqlRestTestCase;
|
||||||
|
|
||||||
public class EqlIT extends CommonEqlRestTestCase {
|
public class EqlRestIT extends EqlRestTestCase {
|
||||||
}
|
}
|
|
@ -6,12 +6,16 @@
|
||||||
|
|
||||||
package org.elasticsearch.xpack.eql;
|
package org.elasticsearch.xpack.eql;
|
||||||
|
|
||||||
import org.elasticsearch.test.eql.CommonEqlActionTestCase;
|
import org.elasticsearch.test.eql.EqlSpecTestCase;
|
||||||
|
|
||||||
public class EqlActionIT extends CommonEqlActionTestCase {
|
public class EqlSpecIT extends EqlSpecTestCase {
|
||||||
|
|
||||||
public EqlActionIT(String query, String name, long[] eventIds, boolean caseSensitive) {
|
public EqlSpecIT(String query, String name, long[] eventIds, boolean caseSensitive) {
|
||||||
super(query, name, eventIds, caseSensitive);
|
super(query, name, eventIds, caseSensitive);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected String sequenceField() {
|
||||||
|
return "serial_event_id";
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -6,8 +6,8 @@
|
||||||
|
|
||||||
package org.elasticsearch.xpack.eql;
|
package org.elasticsearch.xpack.eql;
|
||||||
|
|
||||||
import org.elasticsearch.test.eql.stats.RestEqlUsageTestCase;
|
import org.elasticsearch.test.eql.stats.EqlUsageRestTestCase;
|
||||||
|
|
||||||
public class EqlStatsIT extends RestEqlUsageTestCase {
|
public class EqlStatsIT extends EqlUsageRestTestCase {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,16 +7,12 @@
|
||||||
package org.elasticsearch.xpack.eql;
|
package org.elasticsearch.xpack.eql;
|
||||||
|
|
||||||
import org.apache.http.util.EntityUtils;
|
import org.apache.http.util.EntityUtils;
|
||||||
import org.elasticsearch.Build;
|
|
||||||
import org.elasticsearch.client.Request;
|
import org.elasticsearch.client.Request;
|
||||||
import org.elasticsearch.client.RequestOptions;
|
|
||||||
import org.elasticsearch.client.Response;
|
import org.elasticsearch.client.Response;
|
||||||
import org.elasticsearch.client.ResponseException;
|
import org.elasticsearch.client.ResponseException;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.settings.SecureString;
|
|
||||||
import org.elasticsearch.common.settings.Settings;
|
import org.elasticsearch.common.settings.Settings;
|
||||||
import org.elasticsearch.common.unit.TimeValue;
|
import org.elasticsearch.common.unit.TimeValue;
|
||||||
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
|
||||||
import org.elasticsearch.common.xcontent.XContentBuilder;
|
import org.elasticsearch.common.xcontent.XContentBuilder;
|
||||||
import org.elasticsearch.common.xcontent.XContentHelper;
|
import org.elasticsearch.common.xcontent.XContentHelper;
|
||||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||||
|
@ -24,33 +20,24 @@ import org.elasticsearch.test.rest.ESRestTestCase;
|
||||||
import org.elasticsearch.xpack.core.XPackPlugin;
|
import org.elasticsearch.xpack.core.XPackPlugin;
|
||||||
import org.elasticsearch.xpack.core.async.AsyncExecutionId;
|
import org.elasticsearch.xpack.core.async.AsyncExecutionId;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.BeforeClass;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
|
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
|
||||||
import static org.elasticsearch.xpack.core.security.authc.AuthenticationServiceField.RUN_AS_USER_HEADER;
|
import static org.elasticsearch.xpack.eql.SecurityUtils.secureClientSettings;
|
||||||
import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
|
import static org.elasticsearch.xpack.eql.SecurityUtils.setRunAsHeader;
|
||||||
import static org.hamcrest.Matchers.containsString;
|
import static org.hamcrest.Matchers.containsString;
|
||||||
import static org.hamcrest.Matchers.equalTo;
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
|
||||||
public class AsyncEqlSecurityIT extends ESRestTestCase {
|
public class AsyncEqlSecurityIT extends ESRestTestCase {
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
public static void checkForSnapshot() {
|
|
||||||
assumeTrue("Only works on snapshot builds for now", Build.CURRENT.isSnapshot());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* All tests run as a superuser but use <code>es-security-runas-user</code> to become a less privileged user.
|
* All tests run as a superuser but use <code>es-security-runas-user</code> to become a less privileged user.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
protected Settings restClientSettings() {
|
protected Settings restClientSettings() {
|
||||||
String token = basicAuthHeaderValue("test-admin", new SecureString("x-pack-test-password".toCharArray()));
|
return secureClientSettings();
|
||||||
return Settings.builder()
|
|
||||||
.put(ThreadContext.PREFIX + ".Authorization", token)
|
|
||||||
.build();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
|
@ -166,11 +153,4 @@ public class AsyncEqlSecurityIT extends ESRestTestCase {
|
||||||
static Map<String, Object> toMap(String response) {
|
static Map<String, Object> toMap(String response) {
|
||||||
return XContentHelper.convertToMap(JsonXContent.jsonXContent, response, false);
|
return XContentHelper.convertToMap(JsonXContent.jsonXContent, response, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void setRunAsHeader(Request request, String user) {
|
|
||||||
final RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
|
|
||||||
builder.addHeader(RUN_AS_USER_HEADER, user);
|
|
||||||
request.setOptions(builder);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.elasticsearch.xpack.eql;
|
||||||
|
|
||||||
|
import org.elasticsearch.client.Request;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.test.eql.stats.EqlUsageRestTestCase;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.elasticsearch.xpack.eql.SecurityUtils.secureClientSettings;
|
||||||
|
|
||||||
|
public class EqlStatsIT extends EqlUsageRestTestCase {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All tests run as a superuser but use <code>es-security-runas-user</code> to become a less privileged user.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
protected Settings restClientSettings() {
|
||||||
|
return secureClientSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void runRequest(Request request) throws IOException {
|
||||||
|
SecurityUtils.setRunAsHeader(request,"test-admin");
|
||||||
|
super.runRequest(request);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
||||||
|
* or more contributor license agreements. Licensed under the Elastic License;
|
||||||
|
* you may not use this file except in compliance with the Elastic License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.elasticsearch.xpack.eql;
|
||||||
|
|
||||||
|
import org.elasticsearch.client.Request;
|
||||||
|
import org.elasticsearch.client.RequestOptions;
|
||||||
|
import org.elasticsearch.common.settings.SecureString;
|
||||||
|
import org.elasticsearch.common.settings.Settings;
|
||||||
|
import org.elasticsearch.common.util.concurrent.ThreadContext;
|
||||||
|
|
||||||
|
import static org.elasticsearch.xpack.core.security.authc.AuthenticationServiceField.RUN_AS_USER_HEADER;
|
||||||
|
import static org.elasticsearch.xpack.core.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
|
||||||
|
|
||||||
|
public class SecurityUtils {
|
||||||
|
|
||||||
|
static Settings secureClientSettings() {
|
||||||
|
String token = basicAuthHeaderValue("test-admin", new SecureString("x-pack-test-password".toCharArray()));
|
||||||
|
return Settings.builder()
|
||||||
|
.put(ThreadContext.PREFIX + ".Authorization", token)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setRunAsHeader(Request request, String user) {
|
||||||
|
final RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
|
||||||
|
builder.addHeader(RUN_AS_USER_HEADER, user);
|
||||||
|
request.setOptions(builder);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void setUserRole(Request request) {
|
||||||
|
setRunAsHeader(request, "user1");
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,6 +9,10 @@ package org.elasticsearch.xpack.ql;
|
||||||
import org.elasticsearch.client.Request;
|
import org.elasticsearch.client.Request;
|
||||||
import org.elasticsearch.client.Response;
|
import org.elasticsearch.client.Response;
|
||||||
import org.elasticsearch.client.RestClient;
|
import org.elasticsearch.client.RestClient;
|
||||||
|
import org.elasticsearch.common.SuppressForbidden;
|
||||||
|
import org.elasticsearch.common.collect.Tuple;
|
||||||
|
import org.elasticsearch.common.io.PathUtils;
|
||||||
|
import org.elasticsearch.common.regex.Regex;
|
||||||
import org.elasticsearch.common.xcontent.XContentHelper;
|
import org.elasticsearch.common.xcontent.XContentHelper;
|
||||||
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
import org.elasticsearch.common.xcontent.json.JsonXContent;
|
||||||
import org.elasticsearch.xpack.ql.expression.Expression;
|
import org.elasticsearch.xpack.ql.expression.Expression;
|
||||||
|
@ -24,11 +28,28 @@ import org.elasticsearch.xpack.ql.expression.predicate.operator.comparison.NullE
|
||||||
import org.elasticsearch.xpack.ql.session.Configuration;
|
import org.elasticsearch.xpack.ql.session.Configuration;
|
||||||
import org.elasticsearch.xpack.ql.tree.Source;
|
import org.elasticsearch.xpack.ql.tree.Source;
|
||||||
import org.elasticsearch.xpack.ql.type.DataTypes;
|
import org.elasticsearch.xpack.ql.type.DataTypes;
|
||||||
|
import org.elasticsearch.xpack.ql.util.StringUtils;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLConnection;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.nio.file.FileVisitOption;
|
||||||
|
import java.nio.file.FileVisitResult;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.SimpleFileVisitor;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
import java.time.ZoneId;
|
import java.time.ZoneId;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.jar.JarInputStream;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
|
||||||
import static org.elasticsearch.test.ESTestCase.randomAlphaOfLength;
|
import static org.elasticsearch.test.ESTestCase.randomAlphaOfLength;
|
||||||
import static org.elasticsearch.test.ESTestCase.randomZone;
|
import static org.elasticsearch.test.ESTestCase.randomZone;
|
||||||
|
@ -131,4 +152,96 @@ public final class TestUtils {
|
||||||
stats = (Map<String, Object>) stats.get("search");
|
stats = (Map<String, Object>) stats.get("search");
|
||||||
return (Integer) stats.get("open_contexts");
|
return (Integer) stats.get("open_contexts");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Classpath
|
||||||
|
//
|
||||||
|
/**
|
||||||
|
* Returns the classpath resources matching a simple pattern ("*.csv").
|
||||||
|
* It supports folders separated by "/" (e.g. "/some/folder/*.txt").
|
||||||
|
*
|
||||||
|
* Currently able to resolve resources inside the classpath either from:
|
||||||
|
* folders in the file-system (typically IDEs) or
|
||||||
|
* inside jars (gradle).
|
||||||
|
*/
|
||||||
|
@SuppressForbidden(reason = "classpath discovery")
|
||||||
|
public static List<URL> classpathResources(String pattern) throws IOException {
|
||||||
|
while (pattern.startsWith("/")) {
|
||||||
|
pattern = pattern.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Tuple<String, String> split = pathAndName(pattern);
|
||||||
|
|
||||||
|
// the root folder searched inside the classpath - default is the root classpath
|
||||||
|
// default file match
|
||||||
|
final String root = split.v1();
|
||||||
|
final String filePattern = split.v2();
|
||||||
|
|
||||||
|
String[] resources = System.getProperty("java.class.path").split(System.getProperty("path.separator"));
|
||||||
|
|
||||||
|
List<URL> matches = new ArrayList<>();
|
||||||
|
|
||||||
|
for (String resource : resources) {
|
||||||
|
Path path = PathUtils.get(resource);
|
||||||
|
|
||||||
|
// check whether we're dealing with a jar
|
||||||
|
// Java 7 java.nio.fileFileSystem can be used on top of ZIPs/JARs but consumes more memory
|
||||||
|
// hence the use of the JAR API
|
||||||
|
if (path.toString().endsWith(".jar")) {
|
||||||
|
try (JarInputStream jar = jarInputStream(path.toUri().toURL())) {
|
||||||
|
ZipEntry entry = null;
|
||||||
|
while ((entry = jar.getNextEntry()) != null) {
|
||||||
|
String name = entry.getName();
|
||||||
|
Tuple<String, String> entrySplit = pathAndName(name);
|
||||||
|
if (root.equals(entrySplit.v1()) && Regex.simpleMatch(filePattern, entrySplit.v2())) {
|
||||||
|
matches.add(new URL("jar:" + path.toUri() + "!/" + name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// normal file access
|
||||||
|
else if (Files.isDirectory(path)) {
|
||||||
|
Files.walkFileTree(path, EnumSet.allOf(FileVisitOption.class), 1, new SimpleFileVisitor<Path>() {
|
||||||
|
@Override
|
||||||
|
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||||
|
if (Regex.simpleMatch(filePattern, file.toString())) {
|
||||||
|
matches.add(file.toUri().toURL());
|
||||||
|
}
|
||||||
|
return FileVisitResult.CONTINUE;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return matches;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressForbidden(reason = "need to open stream")
|
||||||
|
public static InputStream inputStream(URL resource) throws IOException {
|
||||||
|
URLConnection con = resource.openConnection();
|
||||||
|
// do not to cache files (to avoid keeping file handles around)
|
||||||
|
con.setUseCaches(false);
|
||||||
|
return con.getInputStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressForbidden(reason = "need to open jar")
|
||||||
|
public static JarInputStream jarInputStream(URL resource) throws IOException {
|
||||||
|
return new JarInputStream(inputStream(resource));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BufferedReader reader(URL resource) throws IOException {
|
||||||
|
return new BufferedReader(new InputStreamReader(inputStream(resource), StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Tuple<String, String> pathAndName(String string) {
|
||||||
|
String folder = StringUtils.EMPTY;
|
||||||
|
String file = string;
|
||||||
|
int lastIndexOf = string.lastIndexOf("/");
|
||||||
|
if (lastIndexOf > 0) {
|
||||||
|
folder = string.substring(0, lastIndexOf - 1);
|
||||||
|
if (lastIndexOf + 1 < string.length()) {
|
||||||
|
file = string.substring(lastIndexOf + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Tuple<>(folder, file);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ package org.elasticsearch.xpack.sql.qa.jdbc;
|
||||||
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
|
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
|
||||||
|
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
|
import org.elasticsearch.xpack.ql.TestUtils;
|
||||||
import org.elasticsearch.xpack.sql.qa.jdbc.CsvTestUtils.CsvTestCase;
|
import org.elasticsearch.xpack.sql.qa.jdbc.CsvTestUtils.CsvTestCase;
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
@ -29,7 +30,7 @@ public abstract class CsvSpecTestCase extends SpecBaseIntegrationTestCase {
|
||||||
|
|
||||||
@ParametersFactory(argumentFormatting = PARAM_FORMATTING)
|
@ParametersFactory(argumentFormatting = PARAM_FORMATTING)
|
||||||
public static List<Object[]> readScriptSpec() throws Exception {
|
public static List<Object[]> readScriptSpec() throws Exception {
|
||||||
List<URL> urls = JdbcTestUtils.classpathResources("/*.csv-spec");
|
List<URL> urls = TestUtils.classpathResources("/*.csv-spec");
|
||||||
assertTrue("Not enough specs found (" + urls.size() + ") " + urls.toString(), urls.size() >= 23);
|
assertTrue("Not enough specs found (" + urls.size() + ") " + urls.toString(), urls.size() >= 23);
|
||||||
return readScriptSpec(urls, specParser());
|
return readScriptSpec(urls, specParser());
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,34 +6,14 @@
|
||||||
package org.elasticsearch.xpack.sql.qa.jdbc;
|
package org.elasticsearch.xpack.sql.qa.jdbc;
|
||||||
|
|
||||||
import org.apache.logging.log4j.Logger;
|
import org.apache.logging.log4j.Logger;
|
||||||
import org.elasticsearch.common.SuppressForbidden;
|
|
||||||
import org.elasticsearch.common.collect.Tuple;
|
|
||||||
import org.elasticsearch.common.io.PathUtils;
|
|
||||||
import org.elasticsearch.common.regex.Regex;
|
|
||||||
import org.elasticsearch.xpack.sql.action.BasicFormatter;
|
import org.elasticsearch.xpack.sql.action.BasicFormatter;
|
||||||
import org.elasticsearch.xpack.sql.proto.ColumnInfo;
|
import org.elasticsearch.xpack.sql.proto.ColumnInfo;
|
||||||
import org.elasticsearch.xpack.sql.proto.StringUtils;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLConnection;
|
|
||||||
import java.nio.file.FileVisitOption;
|
|
||||||
import java.nio.file.FileVisitResult;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.SimpleFileVisitor;
|
|
||||||
import java.nio.file.attribute.BasicFileAttributes;
|
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.ResultSetMetaData;
|
import java.sql.ResultSetMetaData;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.time.Instant;
|
|
||||||
import java.time.ZoneId;
|
|
||||||
import java.time.ZonedDateTime;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.EnumSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.jar.JarInputStream;
|
|
||||||
import java.util.zip.ZipEntry;
|
|
||||||
|
|
||||||
import static org.elasticsearch.xpack.sql.action.BasicFormatter.FormatOption.CLI;
|
import static org.elasticsearch.xpack.sql.action.BasicFormatter.FormatOption.CLI;
|
||||||
|
|
||||||
|
@ -146,87 +126,4 @@ final class JdbcTestUtils {
|
||||||
BasicFormatter formatter = new BasicFormatter(cols, data, CLI);
|
BasicFormatter formatter = new BasicFormatter(cols, data, CLI);
|
||||||
logger.info("\n" + formatter.formatWithHeader(cols, data));
|
logger.info("\n" + formatter.formatWithHeader(cols, data));
|
||||||
}
|
}
|
||||||
|
|
||||||
static String of(long millis, String zoneId) {
|
|
||||||
return StringUtils.toString(ZonedDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneId.of(zoneId)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the classpath resources matching a simple pattern ("*.csv").
|
|
||||||
* It supports folders separated by "/" (e.g. "/some/folder/*.txt").
|
|
||||||
*
|
|
||||||
* Currently able to resolve resources inside the classpath either from:
|
|
||||||
* folders in the file-system (typically IDEs) or
|
|
||||||
* inside jars (gradle).
|
|
||||||
*/
|
|
||||||
static List<URL> classpathResources(String pattern) throws Exception {
|
|
||||||
while (pattern.startsWith("/")) {
|
|
||||||
pattern = pattern.substring(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
Tuple<String, String> split = pathAndName(pattern);
|
|
||||||
|
|
||||||
// the root folder searched inside the classpath - default is the root classpath
|
|
||||||
// default file match
|
|
||||||
final String root = split.v1();
|
|
||||||
final String filePattern = split.v2();
|
|
||||||
|
|
||||||
String[] resources = System.getProperty("java.class.path").split(System.getProperty("path.separator"));
|
|
||||||
|
|
||||||
List<URL> matches = new ArrayList<>();
|
|
||||||
|
|
||||||
for (String resource : resources) {
|
|
||||||
Path path = PathUtils.get(resource);
|
|
||||||
|
|
||||||
// check whether we're dealing with a jar
|
|
||||||
// Java 7 java.nio.fileFileSystem can be used on top of ZIPs/JARs but consumes more memory
|
|
||||||
// hence the use of the JAR API
|
|
||||||
if (path.toString().endsWith(".jar")) {
|
|
||||||
try (JarInputStream jar = getJarStream(path.toUri().toURL())) {
|
|
||||||
ZipEntry entry = null;
|
|
||||||
while ((entry = jar.getNextEntry()) != null) {
|
|
||||||
String name = entry.getName();
|
|
||||||
Tuple<String, String> entrySplit = pathAndName(name);
|
|
||||||
if (root.equals(entrySplit.v1()) && Regex.simpleMatch(filePattern, entrySplit.v2())) {
|
|
||||||
matches.add(new URL("jar:" + path.toUri() + "!/" + name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// normal file access
|
|
||||||
else if (Files.isDirectory(path)) {
|
|
||||||
Files.walkFileTree(path, EnumSet.allOf(FileVisitOption.class), 1, new SimpleFileVisitor<Path>() {
|
|
||||||
@Override
|
|
||||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
|
||||||
if (Regex.simpleMatch(filePattern, file.toString())) {
|
|
||||||
matches.add(file.toUri().toURL());
|
|
||||||
}
|
|
||||||
return FileVisitResult.CONTINUE;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return matches;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressForbidden(reason = "need to open jar")
|
|
||||||
private static JarInputStream getJarStream(URL resource) throws IOException {
|
|
||||||
URLConnection con = resource.openConnection();
|
|
||||||
// do not to cache files (to avoid keeping file handles around)
|
|
||||||
con.setUseCaches(false);
|
|
||||||
return new JarInputStream(con.getInputStream());
|
|
||||||
}
|
|
||||||
|
|
||||||
static Tuple<String, String> pathAndName(String string) {
|
|
||||||
String folder = StringUtils.EMPTY;
|
|
||||||
String file = string;
|
|
||||||
int lastIndexOf = string.lastIndexOf("/");
|
|
||||||
if (lastIndexOf > 0) {
|
|
||||||
folder = string.substring(0, lastIndexOf - 1);
|
|
||||||
if (lastIndexOf + 1 < string.length()) {
|
|
||||||
file = string.substring(lastIndexOf + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new Tuple<>(folder, file);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,17 +10,13 @@ import org.elasticsearch.client.Request;
|
||||||
import org.elasticsearch.client.ResponseException;
|
import org.elasticsearch.client.ResponseException;
|
||||||
import org.elasticsearch.client.RestClient;
|
import org.elasticsearch.client.RestClient;
|
||||||
import org.elasticsearch.common.Strings;
|
import org.elasticsearch.common.Strings;
|
||||||
import org.elasticsearch.common.SuppressForbidden;
|
import org.elasticsearch.xpack.ql.TestUtils;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLConnection;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.sql.Connection;
|
import java.sql.Connection;
|
||||||
import java.sql.ResultSet;
|
import java.sql.ResultSet;
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
|
@ -33,6 +29,7 @@ import java.util.Objects;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
|
import static org.elasticsearch.xpack.ql.TestUtils.pathAndName;
|
||||||
import static org.elasticsearch.xpack.sql.qa.jdbc.JdbcTestUtils.JDBC_TIMEZONE;
|
import static org.elasticsearch.xpack.sql.qa.jdbc.JdbcTestUtils.JDBC_TIMEZONE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -168,14 +165,14 @@ public abstract class SpecBaseIntegrationTestCase extends JdbcIntegrationTestCas
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<Object[]> readURLSpec(URL source, Parser parser) throws Exception {
|
private static List<Object[]> readURLSpec(URL source, Parser parser) throws Exception {
|
||||||
String fileName = JdbcTestUtils.pathAndName(source.getFile()).v2();
|
String fileName = pathAndName(source.getFile()).v2();
|
||||||
String groupName = fileName.substring(0, fileName.lastIndexOf("."));
|
String groupName = fileName.substring(0, fileName.lastIndexOf("."));
|
||||||
|
|
||||||
Map<String, Integer> testNames = new LinkedHashMap<>();
|
Map<String, Integer> testNames = new LinkedHashMap<>();
|
||||||
List<Object[]> testCases = new ArrayList<>();
|
List<Object[]> testCases = new ArrayList<>();
|
||||||
|
|
||||||
String testName = null;
|
String testName = null;
|
||||||
try (BufferedReader reader = new BufferedReader(new InputStreamReader(readFromJarUrl(source), StandardCharsets.UTF_8))) {
|
try (BufferedReader reader = TestUtils.reader(source)) {
|
||||||
String line;
|
String line;
|
||||||
int lineNumber = 1;
|
int lineNumber = 1;
|
||||||
while ((line = reader.readLine()) != null) {
|
while ((line = reader.readLine()) != null) {
|
||||||
|
@ -221,12 +218,4 @@ public abstract class SpecBaseIntegrationTestCase extends JdbcIntegrationTestCas
|
||||||
public interface Parser {
|
public interface Parser {
|
||||||
Object parse(String line);
|
Object parse(String line);
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressForbidden(reason = "test reads from jar")
|
|
||||||
public static InputStream readFromJarUrl(URL source) throws IOException {
|
|
||||||
URLConnection con = source.openConnection();
|
|
||||||
// do not to cache files (to avoid keeping file handles around)
|
|
||||||
con.setUseCaches(false);
|
|
||||||
return con.getInputStream();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
package org.elasticsearch.xpack.sql.qa.jdbc;
|
package org.elasticsearch.xpack.sql.qa.jdbc;
|
||||||
|
|
||||||
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
|
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
|
||||||
|
|
||||||
import org.junit.Assume;
|
import org.junit.Assume;
|
||||||
import org.junit.ClassRule;
|
import org.junit.ClassRule;
|
||||||
|
|
||||||
|
@ -20,6 +19,8 @@ import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
import static org.elasticsearch.xpack.ql.TestUtils.classpathResources;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests comparing sql queries executed against our jdbc client
|
* Tests comparing sql queries executed against our jdbc client
|
||||||
* with those executed against H2's jdbc client.
|
* with those executed against H2's jdbc client.
|
||||||
|
@ -33,7 +34,7 @@ public abstract class SqlSpecTestCase extends SpecBaseIntegrationTestCase {
|
||||||
|
|
||||||
@ParametersFactory(argumentFormatting = PARAM_FORMATTING)
|
@ParametersFactory(argumentFormatting = PARAM_FORMATTING)
|
||||||
public static List<Object[]> readScriptSpec() throws Exception {
|
public static List<Object[]> readScriptSpec() throws Exception {
|
||||||
List<URL> urls = JdbcTestUtils.classpathResources("/*.sql-spec");
|
List<URL> urls = classpathResources("/*.sql-spec");
|
||||||
assertTrue("Not enough specs found " + urls.toString(), urls.size() > 10);
|
assertTrue("Not enough specs found " + urls.toString(), urls.size() > 10);
|
||||||
return readScriptSpec(urls, specParser());
|
return readScriptSpec(urls, specParser());
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue