From bfd66d601b15a99207ae3d751202e8ca65aafaea Mon Sep 17 00:00:00 2001 From: Nikolaj Volgushev Date: Tue, 3 Dec 2024 14:01:29 +0100 Subject: [PATCH 01/37] Use feature flags in OperatorPrivilegesIT (#117491) (#117631) # Backport This will backport the following commits from `main` to `8.16`: - [Use feature flags in OperatorPrivilegesIT (#117491)](https://github.com/elastic/elasticsearch/pull/117491) Closes: https://github.com/elastic/elasticsearch/issues/117683 --- .../elasticsearch/xpack/security/operator/Constants.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java b/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java index 4405ef575b24f..86e3e387b2577 100644 --- a/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java +++ b/x-pack/plugin/security/qa/operator-privileges-tests/src/javaRestTest/java/org/elasticsearch/xpack/security/operator/Constants.java @@ -7,6 +7,8 @@ package org.elasticsearch.xpack.security.operator; +import org.elasticsearch.cluster.metadata.DataStream; + import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; @@ -503,9 +505,9 @@ public class Constants { "indices:admin/data_stream/lifecycle/get", "indices:admin/data_stream/lifecycle/put", "indices:admin/data_stream/lifecycle/explain", - "indices:admin/data_stream/options/delete", - "indices:admin/data_stream/options/get", - "indices:admin/data_stream/options/put", + DataStream.isFailureStoreFeatureFlagEnabled() ? "indices:admin/data_stream/options/delete" : null, + DataStream.isFailureStoreFeatureFlagEnabled() ? "indices:admin/data_stream/options/get" : null, + DataStream.isFailureStoreFeatureFlagEnabled() ? "indices:admin/data_stream/options/put" : null, "indices:admin/delete", "indices:admin/flush", "indices:admin/flush[s]", From 4bd6449dffd32ec15c48da35874de05698f2ca0e Mon Sep 17 00:00:00 2001 From: Nhat Nguyen Date: Tue, 3 Dec 2024 20:25:57 -0800 Subject: [PATCH 02/37] [8.16] Fix BWC for ES|QL cluster request (#117864) We identified a BWC bug in the cluster computer request. Specifically, the indices options were not properly selected for requests from an older querying cluster. This caused the search_shards API on the remote cluster to use restricted indices options, leading to failures when resolving wildcard index patterns. Our tests didn't catch this issue because the current BWC tests for cross-cluster queries only cover one direction: the querying cluster on the current version and the remote cluster on a compatible version. This PR fixes the issue and expands BWC tests to support both directions: the querying cluster on the current version with the remote cluster on a compatible version, and vice versa. --- docs/changelog/117865.yaml | 5 + .../qa/server/multi-clusters/build.gradle | 17 +- .../xpack/esql/ccq/Clusters.java | 19 +- .../xpack/esql/ccq/EsqlRestValidationIT.java | 7 + .../xpack/esql/ccq/MultiClusterSpecIT.java | 7 +- .../xpack/esql/ccq/MultiClustersIT.java | 104 ++++++--- .../xpack/esql/qa/single_node/RestEsqlIT.java | 1 - .../xpack/esql/plugin/RemoteClusterPlan.java | 21 +- .../esql/plugin/ClusterRequestTests.java | 206 ++++++++++++++++++ 9 files changed, 345 insertions(+), 42 deletions(-) create mode 100644 docs/changelog/117865.yaml create mode 100644 x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plugin/ClusterRequestTests.java diff --git a/docs/changelog/117865.yaml b/docs/changelog/117865.yaml new file mode 100644 index 0000000000000..33dc497725f92 --- /dev/null +++ b/docs/changelog/117865.yaml @@ -0,0 +1,5 @@ +pr: 117865 +summary: Fix BWC for ES|QL cluster request +area: ES|QL +type: bug +issues: [] diff --git a/x-pack/plugin/esql/qa/server/multi-clusters/build.gradle b/x-pack/plugin/esql/qa/server/multi-clusters/build.gradle index 84b3f6e2dc4cc..4a138825f93be 100644 --- a/x-pack/plugin/esql/qa/server/multi-clusters/build.gradle +++ b/x-pack/plugin/esql/qa/server/multi-clusters/build.gradle @@ -23,9 +23,22 @@ def supportedVersion = bwcVersion -> { } buildParams.bwcVersions.withWireCompatible(supportedVersion) { bwcVersion, baseName -> - tasks.register(bwcTaskName(bwcVersion), StandaloneRestIntegTestTask) { + tasks.register("${baseName}#newToOld", StandaloneRestIntegTestTask) { + usesBwcDistribution(bwcVersion) + systemProperty("tests.version.remote_cluster", bwcVersion) + maxParallelForks = 1 + } + + tasks.register("${baseName}#oldToNew", StandaloneRestIntegTestTask) { usesBwcDistribution(bwcVersion) - systemProperty("tests.old_cluster_version", bwcVersion) + systemProperty("tests.version.local_cluster", bwcVersion) + maxParallelForks = 1 + } + + // TODO: avoid running tests twice with the current version + tasks.register(bwcTaskName(bwcVersion), StandaloneRestIntegTestTask) { + dependsOn tasks.named("${baseName}#oldToNew") + dependsOn tasks.named("${baseName}#newToOld") maxParallelForks = 1 } } diff --git a/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/Clusters.java b/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/Clusters.java index fa8cb49c59aed..5f3f135810322 100644 --- a/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/Clusters.java +++ b/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/Clusters.java @@ -20,7 +20,7 @@ public static ElasticsearchCluster remoteCluster() { return ElasticsearchCluster.local() .name(REMOTE_CLUSTER_NAME) .distribution(DistributionType.DEFAULT) - .version(Version.fromString(System.getProperty("tests.old_cluster_version"))) + .version(distributionVersion("tests.version.remote_cluster")) .nodes(2) .setting("node.roles", "[data,ingest,master]") .setting("xpack.security.enabled", "false") @@ -34,7 +34,7 @@ public static ElasticsearchCluster localCluster(ElasticsearchCluster remoteClust return ElasticsearchCluster.local() .name(LOCAL_CLUSTER_NAME) .distribution(DistributionType.DEFAULT) - .version(Version.CURRENT) + .version(distributionVersion("tests.version.local_cluster")) .nodes(2) .setting("xpack.security.enabled", "false") .setting("xpack.license.self_generated.type", "trial") @@ -46,7 +46,18 @@ public static ElasticsearchCluster localCluster(ElasticsearchCluster remoteClust .build(); } - public static org.elasticsearch.Version oldVersion() { - return org.elasticsearch.Version.fromString(System.getProperty("tests.old_cluster_version")); + public static org.elasticsearch.Version localClusterVersion() { + String prop = System.getProperty("tests.version.local_cluster"); + return prop != null ? org.elasticsearch.Version.fromString(prop) : org.elasticsearch.Version.CURRENT; + } + + public static org.elasticsearch.Version remoteClusterVersion() { + String prop = System.getProperty("tests.version.remote_cluster"); + return prop != null ? org.elasticsearch.Version.fromString(prop) : org.elasticsearch.Version.CURRENT; + } + + private static Version distributionVersion(String key) { + final String val = System.getProperty(key); + return val != null ? Version.fromString(val) : Version.CURRENT; } } diff --git a/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/EsqlRestValidationIT.java b/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/EsqlRestValidationIT.java index 21307c5362417..55500aa1c9537 100644 --- a/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/EsqlRestValidationIT.java +++ b/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/EsqlRestValidationIT.java @@ -10,12 +10,14 @@ import com.carrotsearch.randomizedtesting.annotations.ThreadLeakFilters; import org.apache.http.HttpHost; +import org.elasticsearch.Version; import org.elasticsearch.client.RestClient; import org.elasticsearch.core.IOUtils; import org.elasticsearch.test.TestClustersThreadFilter; import org.elasticsearch.test.cluster.ElasticsearchCluster; import org.elasticsearch.xpack.esql.qa.rest.EsqlRestValidationTestCase; import org.junit.AfterClass; +import org.junit.Before; import org.junit.ClassRule; import org.junit.rules.RuleChain; import org.junit.rules.TestRule; @@ -78,4 +80,9 @@ private RestClient remoteClusterClient() throws IOException { } return remoteClient; } + + @Before + public void skipTestOnOldVersions() { + assumeTrue("skip on old versions", Clusters.localClusterVersion().equals(Version.V_8_16_0)); + } } diff --git a/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClusterSpecIT.java b/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClusterSpecIT.java index 3cd70026c4133..f97f10edad8dc 100644 --- a/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClusterSpecIT.java +++ b/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClusterSpecIT.java @@ -12,6 +12,7 @@ import org.apache.http.HttpEntity; import org.apache.http.HttpHost; +import org.elasticsearch.Version; import org.elasticsearch.client.Request; import org.elasticsearch.client.Response; import org.elasticsearch.client.RestClient; @@ -105,10 +106,8 @@ protected void shouldSkipTest(String testName) throws IOException { super.shouldSkipTest(testName); checkCapabilities(remoteClusterClient(), remoteFeaturesService(), testName, testCase); assumeFalse("can't test with _index metadata", hasIndexMetadata(testCase.query)); - assumeTrue( - "Test " + testName + " is skipped on " + Clusters.oldVersion(), - isEnabled(testName, instructions, Clusters.oldVersion()) - ); + Version oldVersion = Version.min(Clusters.localClusterVersion(), Clusters.remoteClusterVersion()); + assumeTrue("Test " + testName + " is skipped on " + oldVersion, isEnabled(testName, instructions, oldVersion)); assumeFalse("INLINESTATS not yet supported in CCS", testCase.requiredCapabilities.contains("inlinestats")); assumeFalse("INLINESTATS not yet supported in CCS", testCase.requiredCapabilities.contains("inlinestats_v2")); } diff --git a/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClustersIT.java b/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClustersIT.java index dbeaed1596eff..452f40baa34a8 100644 --- a/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClustersIT.java +++ b/x-pack/plugin/esql/qa/server/multi-clusters/src/javaRestTest/java/org/elasticsearch/xpack/esql/ccq/MultiClustersIT.java @@ -10,6 +10,7 @@ import com.carrotsearch.randomizedtesting.annotations.ThreadLeakFilters; import org.apache.http.HttpHost; +import org.elasticsearch.Version; import org.elasticsearch.client.Request; import org.elasticsearch.client.RestClient; import org.elasticsearch.common.Strings; @@ -29,7 +30,6 @@ import java.io.IOException; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -127,10 +127,12 @@ void indexDocs(RestClient client, String index, List docs) throws IOExcepti } private Map run(String query, boolean includeCCSMetadata) throws IOException { - Map resp = runEsql( - new RestEsqlTestCase.RequestObjectBuilder().query(query).includeCCSMetadata(includeCCSMetadata).build() - ); - logger.info("--> query {} response {}", query, resp); + var queryBuilder = new RestEsqlTestCase.RequestObjectBuilder().query(query); + if (includeCCSMetadata) { + queryBuilder.includeCCSMetadata(true); + } + Map resp = runEsql(queryBuilder.build()); + logger.info("--> query {} response {}", queryBuilder, resp); return resp; } @@ -156,7 +158,7 @@ private Map runEsql(RestEsqlTestCase.RequestObjectBuilder reques public void testCount() throws Exception { { - boolean includeCCSMetadata = randomBoolean(); + boolean includeCCSMetadata = includeCCSMetadata(); Map result = run("FROM test-local-index,*:test-remote-index | STATS c = COUNT(*)", includeCCSMetadata); var columns = List.of(Map.of("name", "c", "type", "long")); var values = List.of(List.of(localDocs.size() + remoteDocs.size())); @@ -165,13 +167,16 @@ public void testCount() throws Exception { if (includeCCSMetadata) { mapMatcher = mapMatcher.entry("_clusters", any(Map.class)); } - assertMap(result, mapMatcher.entry("columns", columns).entry("values", values).entry("took", greaterThanOrEqualTo(0))); + if (ccsMetadataAvailable()) { + mapMatcher = mapMatcher.entry("took", greaterThanOrEqualTo(0)); + } + assertMap(result, mapMatcher.entry("columns", columns).entry("values", values)); if (includeCCSMetadata) { assertClusterDetailsMap(result, false); } } { - boolean includeCCSMetadata = randomBoolean(); + boolean includeCCSMetadata = includeCCSMetadata(); Map result = run("FROM *:test-remote-index | STATS c = COUNT(*)", includeCCSMetadata); var columns = List.of(Map.of("name", "c", "type", "long")); var values = List.of(List.of(remoteDocs.size())); @@ -180,7 +185,10 @@ public void testCount() throws Exception { if (includeCCSMetadata) { mapMatcher = mapMatcher.entry("_clusters", any(Map.class)); } - assertMap(result, mapMatcher.entry("columns", columns).entry("values", values).entry("took", greaterThanOrEqualTo(0))); + if (ccsMetadataAvailable()) { + mapMatcher = mapMatcher.entry("took", greaterThanOrEqualTo(0)); + } + assertMap(result, mapMatcher.entry("columns", columns).entry("values", values)); if (includeCCSMetadata) { assertClusterDetailsMap(result, true); } @@ -189,7 +197,7 @@ public void testCount() throws Exception { public void testUngroupedAggs() throws Exception { { - boolean includeCCSMetadata = randomBoolean(); + boolean includeCCSMetadata = includeCCSMetadata(); Map result = run("FROM test-local-index,*:test-remote-index | STATS total = SUM(data)", includeCCSMetadata); var columns = List.of(Map.of("name", "total", "type", "long")); long sum = Stream.concat(localDocs.stream(), remoteDocs.stream()).mapToLong(d -> d.data).sum(); @@ -200,13 +208,16 @@ public void testUngroupedAggs() throws Exception { if (includeCCSMetadata) { mapMatcher = mapMatcher.entry("_clusters", any(Map.class)); } - assertMap(result, mapMatcher.entry("columns", columns).entry("values", values).entry("took", greaterThanOrEqualTo(0))); + if (ccsMetadataAvailable()) { + mapMatcher = mapMatcher.entry("took", greaterThanOrEqualTo(0)); + } + assertMap(result, mapMatcher.entry("columns", columns).entry("values", values)); if (includeCCSMetadata) { assertClusterDetailsMap(result, false); } } { - boolean includeCCSMetadata = randomBoolean(); + boolean includeCCSMetadata = includeCCSMetadata(); Map result = run("FROM *:test-remote-index | STATS total = SUM(data)", includeCCSMetadata); var columns = List.of(Map.of("name", "total", "type", "long")); long sum = remoteDocs.stream().mapToLong(d -> d.data).sum(); @@ -216,12 +227,16 @@ public void testUngroupedAggs() throws Exception { if (includeCCSMetadata) { mapMatcher = mapMatcher.entry("_clusters", any(Map.class)); } - assertMap(result, mapMatcher.entry("columns", columns).entry("values", values).entry("took", greaterThanOrEqualTo(0))); + if (ccsMetadataAvailable()) { + mapMatcher = mapMatcher.entry("took", greaterThanOrEqualTo(0)); + } + assertMap(result, mapMatcher.entry("columns", columns).entry("values", values)); if (includeCCSMetadata) { assertClusterDetailsMap(result, true); } } { + assumeTrue("requires ccs metadata", ccsMetadataAvailable()); Map result = runWithColumnarAndIncludeCCSMetadata("FROM *:test-remote-index | STATS total = SUM(data)"); var columns = List.of(Map.of("name", "total", "type", "long")); long sum = remoteDocs.stream().mapToLong(d -> d.data).sum(); @@ -293,7 +308,7 @@ private void assertClusterDetailsMap(Map result, boolean remoteO public void testGroupedAggs() throws Exception { { - boolean includeCCSMetadata = randomBoolean(); + boolean includeCCSMetadata = includeCCSMetadata(); Map result = run( "FROM test-local-index,*:test-remote-index | STATS total = SUM(data) BY color | SORT color", includeCCSMetadata @@ -311,13 +326,16 @@ public void testGroupedAggs() throws Exception { if (includeCCSMetadata) { mapMatcher = mapMatcher.entry("_clusters", any(Map.class)); } - assertMap(result, mapMatcher.entry("columns", columns).entry("values", values).entry("took", greaterThanOrEqualTo(0))); + if (ccsMetadataAvailable()) { + mapMatcher = mapMatcher.entry("took", greaterThanOrEqualTo(0)); + } + assertMap(result, mapMatcher.entry("columns", columns).entry("values", values)); if (includeCCSMetadata) { assertClusterDetailsMap(result, false); } } { - boolean includeCCSMetadata = randomBoolean(); + boolean includeCCSMetadata = includeCCSMetadata(); Map result = run( "FROM *:test-remote-index | STATS total = SUM(data) by color | SORT color", includeCCSMetadata @@ -336,29 +354,57 @@ public void testGroupedAggs() throws Exception { if (includeCCSMetadata) { mapMatcher = mapMatcher.entry("_clusters", any(Map.class)); } - assertMap(result, mapMatcher.entry("columns", columns).entry("values", values).entry("took", greaterThanOrEqualTo(0))); + if (ccsMetadataAvailable()) { + mapMatcher = mapMatcher.entry("took", greaterThanOrEqualTo(0)); + } + assertMap(result, mapMatcher.entry("columns", columns).entry("values", values)); if (includeCCSMetadata) { assertClusterDetailsMap(result, true); } } } + public void testIndexPattern() throws Exception { + { + String indexPattern = randomFrom( + "test-local-index,*:test-remote-index", + "test-local-index,*:test-remote-*", + "test-local-index,*:test-*", + "test-*,*:test-remote-index" + ); + Map result = run("FROM " + indexPattern + " | STATS c = COUNT(*)", false); + var columns = List.of(Map.of("name", "c", "type", "long")); + var values = List.of(List.of(localDocs.size() + remoteDocs.size())); + MapMatcher mapMatcher = matchesMap(); + if (ccsMetadataAvailable()) { + mapMatcher = mapMatcher.entry("took", greaterThanOrEqualTo(0)); + } + assertMap(result, mapMatcher.entry("columns", columns).entry("values", values)); + } + { + String indexPattern = randomFrom("*:test-remote-index", "*:test-remote-*", "*:test-*"); + Map result = run("FROM " + indexPattern + " | STATS c = COUNT(*)", false); + var columns = List.of(Map.of("name", "c", "type", "long")); + var values = List.of(List.of(remoteDocs.size())); + + MapMatcher mapMatcher = matchesMap(); + if (ccsMetadataAvailable()) { + mapMatcher = mapMatcher.entry("took", greaterThanOrEqualTo(0)); + } + assertMap(result, mapMatcher.entry("columns", columns).entry("values", values)); + } + } + private RestClient remoteClusterClient() throws IOException { var clusterHosts = parseClusterHosts(remoteCluster.getHttpAddresses()); return buildClient(restClientSettings(), clusterHosts.toArray(new HttpHost[0])); } - private TestFeatureService remoteFeaturesService() throws IOException { - if (remoteFeaturesService == null) { - try (RestClient remoteClient = remoteClusterClient()) { - var remoteNodeVersions = readVersionsFromNodesInfo(remoteClient); - var semanticNodeVersions = remoteNodeVersions.stream() - .map(ESRestTestCase::parseLegacyVersion) - .flatMap(Optional::stream) - .collect(Collectors.toSet()); - remoteFeaturesService = createTestFeatureService(getClusterStateFeatures(remoteClient), semanticNodeVersions); - } - } - return remoteFeaturesService; + private static boolean ccsMetadataAvailable() { + return Clusters.localClusterVersion().onOrAfter(Version.V_8_16_0); + } + + private static boolean includeCCSMetadata() { + return ccsMetadataAvailable() && randomBoolean(); } } diff --git a/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/RestEsqlIT.java b/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/RestEsqlIT.java index 7de4ee4ccae28..3388f6f517bdf 100644 --- a/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/RestEsqlIT.java +++ b/x-pack/plugin/esql/qa/server/single-node/src/javaRestTest/java/org/elasticsearch/xpack/esql/qa/single_node/RestEsqlIT.java @@ -76,7 +76,6 @@ public void testBasicEsql() throws IOException { indexTimestampData(1); RequestObjectBuilder builder = requestObjectBuilder().query(fromIndex() + " | stats avg(value)"); - requestObjectBuilder().includeCCSMetadata(randomBoolean()); if (Build.current().isSnapshot()) { builder.pragmas(Settings.builder().put("data_partitioning", "shard").build()); } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/RemoteClusterPlan.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/RemoteClusterPlan.java index 8564e4b3afde1..031bfd7139a84 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/RemoteClusterPlan.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/RemoteClusterPlan.java @@ -9,12 +9,14 @@ import org.elasticsearch.TransportVersions; import org.elasticsearch.action.OriginalIndices; -import org.elasticsearch.action.support.IndicesOptions; +import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.xpack.esql.io.stream.PlanStreamInput; import org.elasticsearch.xpack.esql.io.stream.PlanStreamOutput; import org.elasticsearch.xpack.esql.plan.physical.PhysicalPlan; import java.io.IOException; +import java.util.Arrays; +import java.util.Objects; record RemoteClusterPlan(PhysicalPlan plan, String[] targetIndices, OriginalIndices originalIndices) { static RemoteClusterPlan from(PlanStreamInput planIn) throws IOException { @@ -24,7 +26,8 @@ static RemoteClusterPlan from(PlanStreamInput planIn) throws IOException { if (planIn.getTransportVersion().onOrAfter(TransportVersions.ESQL_ORIGINAL_INDICES)) { originalIndices = OriginalIndices.readOriginalIndices(planIn); } else { - originalIndices = new OriginalIndices(planIn.readStringArray(), IndicesOptions.strictSingleIndexNoExpandForbidClosed()); + // fallback to the previous behavior + originalIndices = new OriginalIndices(planIn.readStringArray(), SearchRequest.DEFAULT_INDICES_OPTIONS); } return new RemoteClusterPlan(plan, targetIndices, originalIndices); } @@ -38,4 +41,18 @@ public void writeTo(PlanStreamOutput out) throws IOException { out.writeStringArray(originalIndices.indices()); } } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) return false; + RemoteClusterPlan that = (RemoteClusterPlan) o; + return Objects.equals(plan, that.plan) + && Objects.deepEquals(targetIndices, that.targetIndices) + && Objects.equals(originalIndices, that.originalIndices); + } + + @Override + public int hashCode() { + return Objects.hash(plan, Arrays.hashCode(targetIndices), originalIndices); + } } diff --git a/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plugin/ClusterRequestTests.java b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plugin/ClusterRequestTests.java new file mode 100644 index 0000000000000..f89e94f0789ec --- /dev/null +++ b/x-pack/plugin/esql/src/test/java/org/elasticsearch/xpack/esql/plugin/ClusterRequestTests.java @@ -0,0 +1,206 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.esql.plugin; + +import org.elasticsearch.TransportVersions; +import org.elasticsearch.action.OriginalIndices; +import org.elasticsearch.action.search.SearchRequest; +import org.elasticsearch.action.support.IndicesOptions; +import org.elasticsearch.common.io.stream.NamedWriteableRegistry; +import org.elasticsearch.common.io.stream.Writeable; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.index.IndexMode; +import org.elasticsearch.search.SearchModule; +import org.elasticsearch.test.AbstractWireSerializingTestCase; +import org.elasticsearch.test.TransportVersionUtils; +import org.elasticsearch.xpack.esql.ConfigurationTestUtils; +import org.elasticsearch.xpack.esql.EsqlTestUtils; +import org.elasticsearch.xpack.esql.analysis.Analyzer; +import org.elasticsearch.xpack.esql.analysis.AnalyzerContext; +import org.elasticsearch.xpack.esql.core.type.EsField; +import org.elasticsearch.xpack.esql.expression.function.EsqlFunctionRegistry; +import org.elasticsearch.xpack.esql.index.EsIndex; +import org.elasticsearch.xpack.esql.index.IndexResolution; +import org.elasticsearch.xpack.esql.optimizer.LogicalOptimizerContext; +import org.elasticsearch.xpack.esql.optimizer.LogicalPlanOptimizer; +import org.elasticsearch.xpack.esql.parser.EsqlParser; +import org.elasticsearch.xpack.esql.plan.logical.LogicalPlan; +import org.elasticsearch.xpack.esql.plan.physical.PhysicalPlan; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static org.elasticsearch.xpack.esql.ConfigurationTestUtils.randomConfiguration; +import static org.elasticsearch.xpack.esql.ConfigurationTestUtils.randomTables; +import static org.elasticsearch.xpack.esql.EsqlTestUtils.TEST_CFG; +import static org.elasticsearch.xpack.esql.EsqlTestUtils.TEST_VERIFIER; +import static org.elasticsearch.xpack.esql.EsqlTestUtils.emptyPolicyResolution; +import static org.elasticsearch.xpack.esql.EsqlTestUtils.loadMapping; +import static org.elasticsearch.xpack.esql.EsqlTestUtils.withDefaultLimitWarning; +import static org.hamcrest.Matchers.equalTo; + +public class ClusterRequestTests extends AbstractWireSerializingTestCase { + + @Override + protected Writeable.Reader instanceReader() { + return ClusterComputeRequest::new; + } + + @Override + protected NamedWriteableRegistry getNamedWriteableRegistry() { + List writeables = new ArrayList<>(); + writeables.addAll(new SearchModule(Settings.EMPTY, List.of()).getNamedWriteables()); + writeables.addAll(new EsqlPlugin().getNamedWriteables()); + return new NamedWriteableRegistry(writeables); + } + + @Override + protected ClusterComputeRequest createTestInstance() { + var sessionId = randomAlphaOfLength(10); + String query = randomQuery(); + PhysicalPlan physicalPlan = DataNodeRequestTests.mapAndMaybeOptimize(parse(query)); + OriginalIndices originalIndices = new OriginalIndices( + generateRandomStringArray(10, 10, false, false), + IndicesOptions.fromOptions(randomBoolean(), randomBoolean(), randomBoolean(), randomBoolean()) + ); + String[] targetIndices = generateRandomStringArray(10, 10, false, false); + ClusterComputeRequest request = new ClusterComputeRequest( + randomAlphaOfLength(10), + sessionId, + randomConfiguration(query, randomTables()), + new RemoteClusterPlan(physicalPlan, targetIndices, originalIndices) + ); + request.setParentTask(randomAlphaOfLength(10), randomNonNegativeLong()); + return request; + } + + @Override + protected ClusterComputeRequest mutateInstance(ClusterComputeRequest in) throws IOException { + return switch (between(0, 4)) { + case 0 -> { + var request = new ClusterComputeRequest( + randomValueOtherThan(in.clusterAlias(), () -> randomAlphaOfLength(10)), + in.sessionId(), + in.configuration(), + in.remoteClusterPlan() + ); + request.setParentTask(in.getParentTask()); + yield request; + } + case 1 -> { + var request = new ClusterComputeRequest( + in.clusterAlias(), + randomValueOtherThan(in.sessionId(), () -> randomAlphaOfLength(10)), + in.configuration(), + in.remoteClusterPlan() + ); + request.setParentTask(in.getParentTask()); + yield request; + } + case 2 -> { + var request = new ClusterComputeRequest( + in.clusterAlias(), + in.sessionId(), + randomValueOtherThan(in.configuration(), ConfigurationTestUtils::randomConfiguration), + in.remoteClusterPlan() + ); + request.setParentTask(in.getParentTask()); + yield request; + } + case 3 -> { + RemoteClusterPlan plan = in.remoteClusterPlan(); + var request = new ClusterComputeRequest( + in.clusterAlias(), + in.sessionId(), + in.configuration(), + new RemoteClusterPlan( + plan.plan(), + randomValueOtherThan(plan.targetIndices(), () -> generateRandomStringArray(10, 10, false, false)), + plan.originalIndices() + ) + ); + request.setParentTask(in.getParentTask()); + yield request; + } + case 4 -> { + RemoteClusterPlan plan = in.remoteClusterPlan(); + var request = new ClusterComputeRequest( + in.clusterAlias(), + in.sessionId(), + in.configuration(), + new RemoteClusterPlan( + plan.plan(), + plan.targetIndices(), + new OriginalIndices( + plan.originalIndices().indices(), + randomValueOtherThan( + plan.originalIndices().indicesOptions(), + () -> IndicesOptions.fromOptions(randomBoolean(), randomBoolean(), randomBoolean(), randomBoolean()) + ) + ) + ) + ); + request.setParentTask(in.getParentTask()); + yield request; + } + default -> throw new AssertionError("invalid value"); + }; + } + + public void testFallbackIndicesOptions() throws Exception { + ClusterComputeRequest request = createTestInstance(); + var version = TransportVersionUtils.randomVersionBetween( + random(), + TransportVersions.V_8_14_0, + TransportVersionUtils.getPreviousVersion(TransportVersions.ESQL_ORIGINAL_INDICES) + ); + ClusterComputeRequest cloned = copyInstance(request, version); + assertThat(cloned.clusterAlias(), equalTo(request.clusterAlias())); + assertThat(cloned.sessionId(), equalTo(request.sessionId())); + assertThat(cloned.configuration(), equalTo(request.configuration())); + RemoteClusterPlan plan = cloned.remoteClusterPlan(); + assertThat(plan.plan(), equalTo(request.remoteClusterPlan().plan())); + assertThat(plan.targetIndices(), equalTo(request.remoteClusterPlan().targetIndices())); + OriginalIndices originalIndices = plan.originalIndices(); + assertThat(originalIndices.indices(), equalTo(request.remoteClusterPlan().originalIndices().indices())); + assertThat(originalIndices.indicesOptions(), equalTo(SearchRequest.DEFAULT_INDICES_OPTIONS)); + } + + private static String randomQuery() { + return randomFrom(""" + from test + | where round(emp_no) > 10 + | limit 10 + """, """ + from test + | sort last_name + | limit 10 + | where round(emp_no) > 10 + | eval c = first_name + """); + } + + static LogicalPlan parse(String query) { + Map mapping = loadMapping("mapping-basic.json"); + EsIndex test = new EsIndex("test", mapping, Map.of("test", IndexMode.STANDARD)); + IndexResolution getIndexResult = IndexResolution.valid(test); + var logicalOptimizer = new LogicalPlanOptimizer(new LogicalOptimizerContext(TEST_CFG)); + var analyzer = new Analyzer( + new AnalyzerContext(EsqlTestUtils.TEST_CFG, new EsqlFunctionRegistry(), getIndexResult, emptyPolicyResolution()), + TEST_VERIFIER + ); + return logicalOptimizer.optimize(analyzer.analyze(new EsqlParser().createStatement(query))); + } + + @Override + protected List filteredWarnings() { + return withDefaultLimitWarning(super.filteredWarnings()); + } +} From 299e4c70c01d157fa6c352548c9886b57d123cfc Mon Sep 17 00:00:00 2001 From: Jakob Reiter Date: Wed, 4 Dec 2024 11:56:23 +0100 Subject: [PATCH 03/37] Update troubleshooting-unstable-cluster.asciidoc (#117887) Added missing word --- .../troubleshooting/troubleshooting-unstable-cluster.asciidoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/reference/troubleshooting/troubleshooting-unstable-cluster.asciidoc b/docs/reference/troubleshooting/troubleshooting-unstable-cluster.asciidoc index cbb35f7731034..e47b85aa99547 100644 --- a/docs/reference/troubleshooting/troubleshooting-unstable-cluster.asciidoc +++ b/docs/reference/troubleshooting/troubleshooting-unstable-cluster.asciidoc @@ -126,7 +126,7 @@ repeatedly-dropped connections will severely affect its operation. The connections from the elected master node to every other node in the cluster are particularly important. The elected master never spontaneously closes its outbound connections to other nodes. Similarly, once an inbound connection is -fully established, a node never spontaneously it unless the node is shutting +fully established, a node never spontaneously closes it unless the node is shutting down. If you see a node unexpectedly leave the cluster with the `disconnected` From 97dc64bc6208de570e87cc65c2079357e47153d0 Mon Sep 17 00:00:00 2001 From: kosabogi <105062005+kosabogi@users.noreply.github.com> Date: Wed, 4 Dec 2024 14:49:53 +0100 Subject: [PATCH 04/37] Updates minimum_number_of_allocations description (#117746) (#117982) --- docs/reference/ml/ml-shared.asciidoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/reference/ml/ml-shared.asciidoc b/docs/reference/ml/ml-shared.asciidoc index d01047eac9815..4948db48664ed 100644 --- a/docs/reference/ml/ml-shared.asciidoc +++ b/docs/reference/ml/ml-shared.asciidoc @@ -18,7 +18,8 @@ end::adaptive-allocation-max-number[] tag::adaptive-allocation-min-number[] Specifies the minimum number of allocations to scale to. -If set, it must be greater than or equal to `1`. +If set, it must be greater than or equal to `0`. +If not defined, the deployment scales to `0`. end::adaptive-allocation-min-number[] tag::aggregations[] From 9019716d2639c3029b9b5ce2f1afef77c19fcf37 Mon Sep 17 00:00:00 2001 From: kosabogi <105062005+kosabogi@users.noreply.github.com> Date: Wed, 4 Dec 2024 14:50:21 +0100 Subject: [PATCH 05/37] [DOCS] Adds adaptive allocations information to Inference APIs (#117546) (#117986) * Adds adaptive allocations information to Inference APIs * Update docs/reference/inference/inference-apis.asciidoc * Update docs/reference/inference/put-inference.asciidoc * Update docs/reference/inference/inference-apis.asciidoc --------- Co-authored-by: Liam Thompson <32779855+leemthompo@users.noreply.github.com> --- docs/reference/inference/inference-apis.asciidoc | 13 +++++++++++++ docs/reference/inference/put-inference.asciidoc | 15 ++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/docs/reference/inference/inference-apis.asciidoc b/docs/reference/inference/inference-apis.asciidoc index 037d7abeb2a36..c7b779a994a05 100644 --- a/docs/reference/inference/inference-apis.asciidoc +++ b/docs/reference/inference/inference-apis.asciidoc @@ -35,6 +35,19 @@ Elastic –, then create an {infer} endpoint by the <>. Now use <> to perform <> on your data. +[discrete] +[[adaptive-allocations]] +=== Adaptive allocations + +Adaptive allocations allow inference services to dynamically adjust the number of model allocations based on the current load. + +When adaptive allocations are enabled: + +* The number of allocations scales up automatically when the load increases. +- Allocations scale down to a minimum of 0 when the load decreases, saving resources. + +For more information about adaptive allocations and resources, refer to the {ml-docs}/ml-nlp-auto-scale.html[trained model autoscaling] documentation. + //[discrete] //[[default-enpoints]] //=== Default {infer} endpoints diff --git a/docs/reference/inference/put-inference.asciidoc b/docs/reference/inference/put-inference.asciidoc index e7e25ec98b49d..ed93c290b6ad4 100644 --- a/docs/reference/inference/put-inference.asciidoc +++ b/docs/reference/inference/put-inference.asciidoc @@ -67,4 +67,17 @@ Click the links to review the configuration details of the services: * <> (`text_embedding`) The {es} and ELSER services run on a {ml} node in your {es} cluster. The rest of -the services connect to external providers. \ No newline at end of file +the services connect to external providers. + +[discrete] +[[adaptive-allocations-put-inference]] +==== Adaptive allocations + +Adaptive allocations allow inference services to dynamically adjust the number of model allocations based on the current load. + +When adaptive allocations are enabled: + +- The number of allocations scales up automatically when the load increases. +- Allocations scale down to a minimum of 0 when the load decreases, saving resources. + +For more information about adaptive allocations and resources, refer to the {ml-docs}/ml-nlp-auto-scale.html[trained model autoscaling] documentation. \ No newline at end of file From b560f290a6ece80eb9a931d6973abce6a15bbaf2 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine Date: Wed, 4 Dec 2024 15:57:46 +0000 Subject: [PATCH 06/37] Bump versions after 7.17.26 release --- .buildkite/pipelines/intake.yml | 2 +- .buildkite/pipelines/periodic-packaging.yml | 6 +++--- .buildkite/pipelines/periodic.yml | 10 +++++----- .ci/bwcVersions | 2 +- .ci/snapshotBwcVersions | 2 +- server/src/main/java/org/elasticsearch/Version.java | 1 + .../resources/org/elasticsearch/TransportVersions.csv | 1 + .../org/elasticsearch/index/IndexVersions.csv | 1 + 8 files changed, 14 insertions(+), 11 deletions(-) diff --git a/.buildkite/pipelines/intake.yml b/.buildkite/pipelines/intake.yml index 8e0866be89e12..4bec17d3d8088 100644 --- a/.buildkite/pipelines/intake.yml +++ b/.buildkite/pipelines/intake.yml @@ -56,7 +56,7 @@ steps: timeout_in_minutes: 300 matrix: setup: - BWC_VERSION: ["7.17.26", "8.16.2"] + BWC_VERSION: ["7.17.27", "8.16.2"] agents: provider: gcp image: family/elasticsearch-ubuntu-2004 diff --git a/.buildkite/pipelines/periodic-packaging.yml b/.buildkite/pipelines/periodic-packaging.yml index 4c01659f22138..9772e465d4a5f 100644 --- a/.buildkite/pipelines/periodic-packaging.yml +++ b/.buildkite/pipelines/periodic-packaging.yml @@ -303,8 +303,8 @@ steps: env: BWC_VERSION: 7.16.3 - - label: "{{matrix.image}} / 7.17.26 / packaging-tests-upgrade" - command: ./.ci/scripts/packaging-test.sh -Dbwc.checkout.align=true destructiveDistroUpgradeTest.v7.17.26 + - label: "{{matrix.image}} / 7.17.27 / packaging-tests-upgrade" + command: ./.ci/scripts/packaging-test.sh -Dbwc.checkout.align=true destructiveDistroUpgradeTest.v7.17.27 timeout_in_minutes: 300 matrix: setup: @@ -317,7 +317,7 @@ steps: machineType: custom-16-32768 buildDirectory: /dev/shm/bk env: - BWC_VERSION: 7.17.26 + BWC_VERSION: 7.17.27 - label: "{{matrix.image}} / 8.0.1 / packaging-tests-upgrade" command: ./.ci/scripts/packaging-test.sh -Dbwc.checkout.align=true destructiveDistroUpgradeTest.v8.0.1 diff --git a/.buildkite/pipelines/periodic.yml b/.buildkite/pipelines/periodic.yml index 4be6fc0e741b7..9163e6d58791d 100644 --- a/.buildkite/pipelines/periodic.yml +++ b/.buildkite/pipelines/periodic.yml @@ -325,8 +325,8 @@ steps: - signal_reason: agent_stop limit: 3 - - label: 7.17.26 / bwc - command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true v7.17.26#bwcTest + - label: 7.17.27 / bwc + command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true v7.17.27#bwcTest timeout_in_minutes: 300 agents: provider: gcp @@ -335,7 +335,7 @@ steps: buildDirectory: /dev/shm/bk preemptible: true env: - BWC_VERSION: 7.17.26 + BWC_VERSION: 7.17.27 retry: automatic: - exit_status: "-1" @@ -733,7 +733,7 @@ steps: setup: ES_RUNTIME_JAVA: - openjdk17 - BWC_VERSION: ["7.17.26", "8.16.2"] + BWC_VERSION: ["7.17.27", "8.16.2"] agents: provider: gcp image: family/elasticsearch-ubuntu-2004 @@ -781,7 +781,7 @@ steps: - openjdk21 - openjdk22 - openjdk23 - BWC_VERSION: ["7.17.26", "8.16.2"] + BWC_VERSION: ["7.17.27", "8.16.2"] agents: provider: gcp image: family/elasticsearch-ubuntu-2004 diff --git a/.ci/bwcVersions b/.ci/bwcVersions index d4e57d380644f..8d6d2779fdd46 100644 --- a/.ci/bwcVersions +++ b/.ci/bwcVersions @@ -16,7 +16,7 @@ BWC_VERSION: - "7.14.2" - "7.15.2" - "7.16.3" - - "7.17.26" + - "7.17.27" - "8.0.1" - "8.1.3" - "8.2.3" diff --git a/.ci/snapshotBwcVersions b/.ci/snapshotBwcVersions index ce1ff63a7be7d..322e2b855f02a 100644 --- a/.ci/snapshotBwcVersions +++ b/.ci/snapshotBwcVersions @@ -1,3 +1,3 @@ BWC_VERSION: - - "7.17.26" + - "7.17.27" - "8.16.2" diff --git a/server/src/main/java/org/elasticsearch/Version.java b/server/src/main/java/org/elasticsearch/Version.java index 8c4a551a5d5de..bea211d7c36db 100644 --- a/server/src/main/java/org/elasticsearch/Version.java +++ b/server/src/main/java/org/elasticsearch/Version.java @@ -127,6 +127,7 @@ public class Version implements VersionId, ToXContentFragment { public static final Version V_7_17_24 = new Version(7_17_24_99); public static final Version V_7_17_25 = new Version(7_17_25_99); public static final Version V_7_17_26 = new Version(7_17_26_99); + public static final Version V_7_17_27 = new Version(7_17_27_99); public static final Version V_8_0_0 = new Version(8_00_00_99); public static final Version V_8_0_1 = new Version(8_00_01_99); diff --git a/server/src/main/resources/org/elasticsearch/TransportVersions.csv b/server/src/main/resources/org/elasticsearch/TransportVersions.csv index 678cf50866595..2cdb0a4879f87 100644 --- a/server/src/main/resources/org/elasticsearch/TransportVersions.csv +++ b/server/src/main/resources/org/elasticsearch/TransportVersions.csv @@ -73,6 +73,7 @@ 7.17.23,7172399 7.17.24,7172499 7.17.25,7172599 +7.17.26,7172699 8.0.0,8000099 8.0.1,8000199 8.1.0,8010099 diff --git a/server/src/main/resources/org/elasticsearch/index/IndexVersions.csv b/server/src/main/resources/org/elasticsearch/index/IndexVersions.csv index 332dff2bd83b6..58d0db5f6c3ef 100644 --- a/server/src/main/resources/org/elasticsearch/index/IndexVersions.csv +++ b/server/src/main/resources/org/elasticsearch/index/IndexVersions.csv @@ -73,6 +73,7 @@ 7.17.23,7172399 7.17.24,7172499 7.17.25,7172599 +7.17.26,7172699 8.0.0,8000099 8.0.1,8000199 8.1.0,8010099 From cd6d9625b24a7ce793e47f0375ba52c79199262b Mon Sep 17 00:00:00 2001 From: Nhat Nguyen Date: Wed, 4 Dec 2024 08:52:02 -0800 Subject: [PATCH 07/37] Remove ccs banner (#117844) (#118010) Backport of #117844 to 8.16 --- docs/reference/esql/esql-across-clusters.asciidoc | 5 ----- 1 file changed, 5 deletions(-) diff --git a/docs/reference/esql/esql-across-clusters.asciidoc b/docs/reference/esql/esql-across-clusters.asciidoc index db266fafde9d6..6decc351bc1c8 100644 --- a/docs/reference/esql/esql-across-clusters.asciidoc +++ b/docs/reference/esql/esql-across-clusters.asciidoc @@ -8,11 +8,6 @@ preview::["{ccs-cap} for {esql} is in technical preview and may be changed or removed in a future release. Elastic will work to fix any issues, but features in technical preview are not subject to the support SLA of official GA features."] -[NOTE] -==== -For {ccs-cap} with {esql} on version 8.16 or later, remote clusters must also be on version 8.16 or later. -==== - With {esql}, you can execute a single query across multiple clusters. [discrete] From ff4531d2e90c2f9639ad9c2fb582bbb9a2d74c7e Mon Sep 17 00:00:00 2001 From: Nhat Nguyen Date: Wed, 4 Dec 2024 08:56:45 -0800 Subject: [PATCH 08/37] Acquire stats searcher for data stream stats (#117953) (#118004) Here, we only need to extract the minimum and maximum values of the timestamp field; therefore, using a stats searcher should suffice. This is important for frozen indices. --- docs/changelog/117953.yaml | 5 +++++ .../datastreams/action/DataStreamsStatsTransportAction.java | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 docs/changelog/117953.yaml diff --git a/docs/changelog/117953.yaml b/docs/changelog/117953.yaml new file mode 100644 index 0000000000000..62f0218b1cdc7 --- /dev/null +++ b/docs/changelog/117953.yaml @@ -0,0 +1,5 @@ +pr: 117953 +summary: Acquire stats searcher for data stream stats +area: Data streams +type: bug +issues: [] diff --git a/modules/data-streams/src/main/java/org/elasticsearch/datastreams/action/DataStreamsStatsTransportAction.java b/modules/data-streams/src/main/java/org/elasticsearch/datastreams/action/DataStreamsStatsTransportAction.java index 1b0b0aa6abebe..1d3b1b676282a 100644 --- a/modules/data-streams/src/main/java/org/elasticsearch/datastreams/action/DataStreamsStatsTransportAction.java +++ b/modules/data-streams/src/main/java/org/elasticsearch/datastreams/action/DataStreamsStatsTransportAction.java @@ -31,6 +31,7 @@ import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexService; import org.elasticsearch.index.engine.Engine; +import org.elasticsearch.index.engine.ReadOnlyEngine; import org.elasticsearch.index.shard.IndexShard; import org.elasticsearch.index.store.StoreStats; import org.elasticsearch.indices.IndicesService; @@ -130,7 +131,7 @@ protected void shardOperation( DataStream dataStream = indexAbstraction.getParentDataStream(); assert dataStream != null; long maxTimestamp = 0L; - try (Engine.Searcher searcher = indexShard.acquireSearcher("data_stream_stats")) { + try (Engine.Searcher searcher = indexShard.acquireSearcher(ReadOnlyEngine.FIELD_RANGE_SEARCH_SOURCE)) { IndexReader indexReader = searcher.getIndexReader(); byte[] maxPackedValue = PointValues.getMaxPackedValue(indexReader, DataStream.TIMESTAMP_FIELD_NAME); if (maxPackedValue != null) { From 01e9388868016baae2afedf52c522247fca5ad21 Mon Sep 17 00:00:00 2001 From: Benjamin Trent Date: Wed, 4 Dec 2024 14:52:34 -0500 Subject: [PATCH 09/37] [8.16] Indicate that rescore isn't allowed with retrievers, yet (#118019) (#118023) Backports the following commits to 8.16: - Indicate that rescore isn't allowed with retrievers, yet (#118019) --- docs/reference/search/retriever.asciidoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/reference/search/retriever.asciidoc b/docs/reference/search/retriever.asciidoc index 504c3d3488a27..cdd682723d67b 100644 --- a/docs/reference/search/retriever.asciidoc +++ b/docs/reference/search/retriever.asciidoc @@ -534,11 +534,11 @@ clauses in a <>. ==== Restrictions on search parameters when specifying a retriever -When a retriever is specified as part of a search the following elements are not allowed -at the top-level and instead are only allowed as elements of specific retrievers: +When a retriever is specified as part of a search, the following elements are not allowed at the top-level: * <> * <> * <> * <> -* <> \ No newline at end of file +* <> +* <> From a07433731d57841a8e956124d4f24acdd7afbfe8 Mon Sep 17 00:00:00 2001 From: Stanislav Malyshev Date: Wed, 4 Dec 2024 13:56:05 -0700 Subject: [PATCH 10/37] Fix reconstituting version string from components (#117213) (#117952) * Fix reconstituting version string from components Co-authored-by: Joe Gallo (cherry picked from commit 28eda97ddd48fc23f6d21d3f5b8a68f977f9627f) --- docs/changelog/117213.yaml | 6 +++ .../ingest/useragent/UserAgentProcessor.java | 47 +++++++++---------- .../useragent/UserAgentProcessorTests.java | 17 +++++++ 3 files changed, 45 insertions(+), 25 deletions(-) create mode 100644 docs/changelog/117213.yaml diff --git a/docs/changelog/117213.yaml b/docs/changelog/117213.yaml new file mode 100644 index 0000000000000..3b4cd0cee966c --- /dev/null +++ b/docs/changelog/117213.yaml @@ -0,0 +1,6 @@ +pr: 117213 +summary: Fix reconstituting version string from components +area: Ingest Node +type: bug +issues: + - 116950 diff --git a/modules/ingest-user-agent/src/main/java/org/elasticsearch/ingest/useragent/UserAgentProcessor.java b/modules/ingest-user-agent/src/main/java/org/elasticsearch/ingest/useragent/UserAgentProcessor.java index 6224bb4d502d7..742b4c8c7e8e1 100644 --- a/modules/ingest-user-agent/src/main/java/org/elasticsearch/ingest/useragent/UserAgentProcessor.java +++ b/modules/ingest-user-agent/src/main/java/org/elasticsearch/ingest/useragent/UserAgentProcessor.java @@ -9,6 +9,7 @@ package org.elasticsearch.ingest.useragent; +import org.elasticsearch.common.Strings; import org.elasticsearch.common.logging.DeprecationCategory; import org.elasticsearch.common.logging.DeprecationLogger; import org.elasticsearch.common.util.Maps; @@ -98,19 +99,8 @@ public IngestDocument execute(IngestDocument ingestDocument) { } break; case VERSION: - StringBuilder version = new StringBuilder(); if (uaClient.userAgent() != null && uaClient.userAgent().major() != null) { - version.append(uaClient.userAgent().major()); - if (uaClient.userAgent().minor() != null) { - version.append(".").append(uaClient.userAgent().minor()); - if (uaClient.userAgent().patch() != null) { - version.append(".").append(uaClient.userAgent().patch()); - if (uaClient.userAgent().build() != null) { - version.append(".").append(uaClient.userAgent().build()); - } - } - } - uaDetails.put("version", version.toString()); + uaDetails.put("version", versionToString(uaClient.userAgent())); } break; case OS: @@ -118,20 +108,10 @@ public IngestDocument execute(IngestDocument ingestDocument) { Map osDetails = Maps.newMapWithExpectedSize(3); if (uaClient.operatingSystem().name() != null) { osDetails.put("name", uaClient.operatingSystem().name()); - StringBuilder sb = new StringBuilder(); if (uaClient.operatingSystem().major() != null) { - sb.append(uaClient.operatingSystem().major()); - if (uaClient.operatingSystem().minor() != null) { - sb.append(".").append(uaClient.operatingSystem().minor()); - if (uaClient.operatingSystem().patch() != null) { - sb.append(".").append(uaClient.operatingSystem().patch()); - if (uaClient.operatingSystem().build() != null) { - sb.append(".").append(uaClient.operatingSystem().build()); - } - } - } - osDetails.put("version", sb.toString()); - osDetails.put("full", uaClient.operatingSystem().name() + " " + sb.toString()); + String version = versionToString(uaClient.operatingSystem()); + osDetails.put("version", version); + osDetails.put("full", uaClient.operatingSystem().name() + " " + version); } uaDetails.put("os", osDetails); } @@ -163,6 +143,23 @@ public IngestDocument execute(IngestDocument ingestDocument) { return ingestDocument; } + private static String versionToString(final UserAgentParser.VersionedName version) { + final StringBuilder versionString = new StringBuilder(); + if (Strings.hasLength(version.major())) { + versionString.append(version.major()); + if (Strings.hasLength(version.minor())) { + versionString.append(".").append(version.minor()); + if (Strings.hasLength(version.patch())) { + versionString.append(".").append(version.patch()); + if (Strings.hasLength(version.build())) { + versionString.append(".").append(version.build()); + } + } + } + } + return versionString.toString(); + } + @Override public String getType() { return TYPE; diff --git a/modules/ingest-user-agent/src/test/java/org/elasticsearch/ingest/useragent/UserAgentProcessorTests.java b/modules/ingest-user-agent/src/test/java/org/elasticsearch/ingest/useragent/UserAgentProcessorTests.java index d9459404987df..df023f4d13428 100644 --- a/modules/ingest-user-agent/src/test/java/org/elasticsearch/ingest/useragent/UserAgentProcessorTests.java +++ b/modules/ingest-user-agent/src/test/java/org/elasticsearch/ingest/useragent/UserAgentProcessorTests.java @@ -331,4 +331,21 @@ public void testExtractDeviceTypeDisabled() { device.put("name", "Other"); assertThat(target.get("device"), is(device)); } + + // From https://github.com/elastic/elasticsearch/issues/116950 + @SuppressWarnings("unchecked") + public void testFirefoxVersion() { + Map document = new HashMap<>(); + document.put("source_field", "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:128.0) Gecko/20100101 Firefox/128.0"); + IngestDocument ingestDocument = RandomDocumentPicks.randomIngestDocument(random(), document); + + processor.execute(ingestDocument); + Map data = ingestDocument.getSourceAndMetadata(); + + assertThat(data, hasKey("target_field")); + Map target = (Map) data.get("target_field"); + + assertThat(target.get("name"), is("Firefox")); + assertThat(target.get("version"), is("128.0")); + } } From 78f5defab5fb156366220919f7d523cd72f51960 Mon Sep 17 00:00:00 2001 From: kosabogi <105062005+kosabogi@users.noreply.github.com> Date: Thu, 5 Dec 2024 16:59:40 +0100 Subject: [PATCH 11/37] Adds warning to Create inference API page (#118073) (#118089) --- docs/reference/inference/put-inference.asciidoc | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/reference/inference/put-inference.asciidoc b/docs/reference/inference/put-inference.asciidoc index ed93c290b6ad4..4f82889f562d8 100644 --- a/docs/reference/inference/put-inference.asciidoc +++ b/docs/reference/inference/put-inference.asciidoc @@ -10,7 +10,6 @@ Creates an {infer} endpoint to perform an {infer} task. * For built-in models and models uploaded through Eland, the {infer} APIs offer an alternative way to use and manage trained models. However, if you do not plan to use the {infer} APIs to use these models or if you want to use non-NLP models, use the <>. ==== - [discrete] [[put-inference-api-request]] ==== {api-request-title} @@ -47,6 +46,14 @@ Refer to the service list in the <> API. In the response, look for `"state": "fully_allocated"` and ensure the `"allocation_count"` matches the `"target_allocation_count"`. +* Avoid creating multiple endpoints for the same model unless required, as each endpoint consumes significant resources. +==== + + The following services are available through the {infer} API. You can find the available task types next to the service name. Click the links to review the configuration details of the services: From ddcc11f8ec732fa7bbe4d6e84f188427790c8a93 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine <58790826+elasticsearchmachine@users.noreply.github.com> Date: Thu, 21 Nov 2024 09:30:15 +1100 Subject: [PATCH 12/37] Add 8.17 to branches.json --- branches.json | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/branches.json b/branches.json index 1d860501cbc87..0e23a795664dd 100644 --- a/branches.json +++ b/branches.json @@ -4,6 +4,15 @@ { "branch": "main" }, + { + "branch": "8.16" + }, + { + "branch": "8.17" + }, + { + "branch": "8.x" + }, { "branch": "8.15" }, From ad2c631c09770ef452ed57fcadfb528ada50152c Mon Sep 17 00:00:00 2001 From: Max Hniebergall <137079448+maxhniebergall@users.noreply.github.com> Date: Thu, 5 Dec 2024 15:25:51 -0500 Subject: [PATCH 13/37] [ML] Fix deberta tokenizer bug caused by bug in normalizer (#117189) (#117256) * Fix deberta tokenizer bug caused by bug in normalizer which caused offesets to be negative * Update docs/changelog/117189.yaml --- docs/changelog/117189.yaml | 5 +++++ .../tokenizers/PrecompiledCharMapNormalizer.java | 2 +- .../nlp/tokenizers/DebertaV2TokenizerTests.java | 14 ++++++++++++++ 3 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 docs/changelog/117189.yaml diff --git a/docs/changelog/117189.yaml b/docs/changelog/117189.yaml new file mode 100644 index 0000000000000..e89c2d81506d9 --- /dev/null +++ b/docs/changelog/117189.yaml @@ -0,0 +1,5 @@ +pr: 117189 +summary: Fix deberta tokenizer bug caused by bug in normalizer +area: Machine Learning +type: bug +issues: [] diff --git a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/nlp/tokenizers/PrecompiledCharMapNormalizer.java b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/nlp/tokenizers/PrecompiledCharMapNormalizer.java index bbe5bea691c35..5dd7dbbffaa61 100644 --- a/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/nlp/tokenizers/PrecompiledCharMapNormalizer.java +++ b/x-pack/plugin/ml/src/main/java/org/elasticsearch/xpack/ml/inference/nlp/tokenizers/PrecompiledCharMapNormalizer.java @@ -194,7 +194,7 @@ Reader normalize(CharSequence str) { if (charDelta < 0) { // normalised form is shorter int lastDiff = getLastCumulativeDiff(); - addOffCorrectMap(normalizedCharPos, lastDiff + charDelta); + addOffCorrectMap(normalizedCharPos, lastDiff - charDelta); } else if (charDelta > 0) { // inserted chars, add the offset in the output stream int lastDiff = getLastCumulativeDiff(); diff --git a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/nlp/tokenizers/DebertaV2TokenizerTests.java b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/nlp/tokenizers/DebertaV2TokenizerTests.java index 99164c9bd3d63..fc070ec25dc68 100644 --- a/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/nlp/tokenizers/DebertaV2TokenizerTests.java +++ b/x-pack/plugin/ml/src/test/java/org/elasticsearch/xpack/ml/inference/nlp/tokenizers/DebertaV2TokenizerTests.java @@ -94,6 +94,20 @@ public void testTokenize() throws IOException { } } + public void testTokenizeWithHiddenControlCharacters() throws IOException { + try ( + DebertaV2Tokenizer tokenizer = DebertaV2Tokenizer.builder( + TEST_CASE_VOCAB, + TEST_CASE_SCORES, + new DebertaV2Tokenization(false, false, null, Tokenization.Truncate.NONE, -1) + ).build() + ) { + TokenizationResult.Tokens tokenization = tokenizer.tokenize("\u009F\u008Fz", Tokenization.Truncate.NONE, -1, 0, null).get(0); + assertThat(tokenStrings(tokenization.tokens().get(0)), contains("▁", "z")); + + } + } + public void testSurrogatePair() throws IOException { try ( DebertaV2Tokenizer tokenizer = DebertaV2Tokenizer.builder( From 82ebd0aeb5b255d903c4bc4542f557b475bceb5a Mon Sep 17 00:00:00 2001 From: Mark Vieira Date: Thu, 5 Dec 2024 16:15:12 -0800 Subject: [PATCH 14/37] Update BWC version logic to support multiple bugfix versions (#117943) (#118116) (#118119) --- .buildkite/pipelines/intake.yml | 2 +- .buildkite/pipelines/periodic.yml | 4 +- .ci/snapshotBwcVersions | 1 + ...lDistributionBwcSetupPluginFuncTest.groovy | 30 +- ...lDistributionDownloadPluginFuncTest.groovy | 4 +- ...acyYamlRestCompatTestPluginFuncTest.groovy | 14 +- .../distribution/bwc/bugfix2/build.gradle | 0 .../distribution/bwc/maintenance/build.gradle | 0 .../internal/fake_git/remote/settings.gradle | 2 + .../gradle/internal/BwcVersions.java | 139 +++++--- .../internal/info/GlobalBuildInfoPlugin.java | 25 +- .../gradle/internal/BwcVersionsSpec.groovy | 313 +++++++++++------- ...stractDistributionDownloadPluginTests.java | 14 +- .../fixtures/AbstractGradleFuncTest.groovy | 18 +- distribution/bwc/bugfix2/build.gradle | 0 .../main/java/org/elasticsearch/Version.java | 1 + .../java/org/elasticsearch/VersionTests.java | 41 +-- settings.gradle | 1 + test/framework/build.gradle | 1 - .../org/elasticsearch/test/VersionUtils.java | 139 +------- .../elasticsearch/test/VersionUtilsTests.java | 273 +-------------- 21 files changed, 386 insertions(+), 636 deletions(-) create mode 100644 build-tools-internal/src/integTest/resources/org/elasticsearch/gradle/internal/fake_git/remote/distribution/bwc/bugfix2/build.gradle create mode 100644 build-tools-internal/src/integTest/resources/org/elasticsearch/gradle/internal/fake_git/remote/distribution/bwc/maintenance/build.gradle create mode 100644 distribution/bwc/bugfix2/build.gradle diff --git a/.buildkite/pipelines/intake.yml b/.buildkite/pipelines/intake.yml index 4bec17d3d8088..887df6c939d9a 100644 --- a/.buildkite/pipelines/intake.yml +++ b/.buildkite/pipelines/intake.yml @@ -56,7 +56,7 @@ steps: timeout_in_minutes: 300 matrix: setup: - BWC_VERSION: ["7.17.27", "8.16.2"] + BWC_VERSION: ["7.17.27", "8.15.6", "8.16.2"] agents: provider: gcp image: family/elasticsearch-ubuntu-2004 diff --git a/.buildkite/pipelines/periodic.yml b/.buildkite/pipelines/periodic.yml index 9163e6d58791d..002404e0b8198 100644 --- a/.buildkite/pipelines/periodic.yml +++ b/.buildkite/pipelines/periodic.yml @@ -733,7 +733,7 @@ steps: setup: ES_RUNTIME_JAVA: - openjdk17 - BWC_VERSION: ["7.17.27", "8.16.2"] + BWC_VERSION: ["7.17.27", "8.15.6", "8.16.2"] agents: provider: gcp image: family/elasticsearch-ubuntu-2004 @@ -781,7 +781,7 @@ steps: - openjdk21 - openjdk22 - openjdk23 - BWC_VERSION: ["7.17.27", "8.16.2"] + BWC_VERSION: ["7.17.27", "8.15.6", "8.16.2"] agents: provider: gcp image: family/elasticsearch-ubuntu-2004 diff --git a/.ci/snapshotBwcVersions b/.ci/snapshotBwcVersions index 322e2b855f02a..8d4c53e62657d 100644 --- a/.ci/snapshotBwcVersions +++ b/.ci/snapshotBwcVersions @@ -1,3 +1,4 @@ BWC_VERSION: - "7.17.27" + - "8.15.6" - "8.16.2" diff --git a/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/InternalDistributionBwcSetupPluginFuncTest.groovy b/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/InternalDistributionBwcSetupPluginFuncTest.groovy index 101993d2c341e..bb100b6b23882 100644 --- a/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/InternalDistributionBwcSetupPluginFuncTest.groovy +++ b/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/InternalDistributionBwcSetupPluginFuncTest.groovy @@ -9,9 +9,10 @@ package org.elasticsearch.gradle.internal +import spock.lang.Unroll + import org.elasticsearch.gradle.fixtures.AbstractGitAwareGradleFuncTest import org.gradle.testkit.runner.TaskOutcome -import spock.lang.Unroll class InternalDistributionBwcSetupPluginFuncTest extends AbstractGitAwareGradleFuncTest { @@ -22,9 +23,11 @@ class InternalDistributionBwcSetupPluginFuncTest extends AbstractGitAwareGradleF buildFile << """ apply plugin: 'elasticsearch.internal-distribution-bwc-setup' """ - execute("git branch origin/8.0", file("cloned")) + execute("git branch origin/8.x", file("cloned")) + execute("git branch origin/8.3", file("cloned")) + execute("git branch origin/8.2", file("cloned")) + execute("git branch origin/8.1", file("cloned")) execute("git branch origin/7.16", file("cloned")) - execute("git branch origin/7.15", file("cloned")) } def "builds distribution from branches via archives extractedAssemble"() { @@ -48,10 +51,11 @@ class InternalDistributionBwcSetupPluginFuncTest extends AbstractGitAwareGradleF assertOutputContains(result.output, "[$bwcDistVersion] > Task :distribution:archives:darwin-tar:${expectedAssembleTaskName}") where: - bwcDistVersion | bwcProject | expectedAssembleTaskName - "8.0.0" | "minor" | "extractedAssemble" - "7.16.0" | "staged" | "extractedAssemble" - "7.15.2" | "bugfix" | "extractedAssemble" + bwcDistVersion | bwcProject | expectedAssembleTaskName + "8.4.0" | "minor" | "extractedAssemble" + "8.3.0" | "staged" | "extractedAssemble" + "8.2.1" | "bugfix" | "extractedAssemble" + "8.1.3" | "bugfix2" | "extractedAssemble" } @Unroll @@ -70,8 +74,8 @@ class InternalDistributionBwcSetupPluginFuncTest extends AbstractGitAwareGradleF where: bwcDistVersion | platform - "8.0.0" | "darwin" - "8.0.0" | "linux" + "8.4.0" | "darwin" + "8.4.0" | "linux" } def "bwc expanded distribution folder can be resolved as bwc project artifact"() { @@ -107,11 +111,11 @@ class InternalDistributionBwcSetupPluginFuncTest extends AbstractGitAwareGradleF result.task(":resolveExpandedDistribution").outcome == TaskOutcome.SUCCESS result.task(":distribution:bwc:minor:buildBwcDarwinTar").outcome == TaskOutcome.SUCCESS and: "assemble task triggered" - result.output.contains("[8.0.0] > Task :distribution:archives:darwin-tar:extractedAssemble") - result.output.contains("expandedRootPath /distribution/bwc/minor/build/bwc/checkout-8.0/" + + result.output.contains("[8.4.0] > Task :distribution:archives:darwin-tar:extractedAssemble") + result.output.contains("expandedRootPath /distribution/bwc/minor/build/bwc/checkout-8.x/" + "distribution/archives/darwin-tar/build/install") - result.output.contains("nested folder /distribution/bwc/minor/build/bwc/checkout-8.0/" + - "distribution/archives/darwin-tar/build/install/elasticsearch-8.0.0-SNAPSHOT") + result.output.contains("nested folder /distribution/bwc/minor/build/bwc/checkout-8.x/" + + "distribution/archives/darwin-tar/build/install/elasticsearch-8.4.0-SNAPSHOT") } } diff --git a/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/InternalDistributionDownloadPluginFuncTest.groovy b/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/InternalDistributionDownloadPluginFuncTest.groovy index eb6185e5aed57..fc5d432a9ef9a 100644 --- a/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/InternalDistributionDownloadPluginFuncTest.groovy +++ b/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/InternalDistributionDownloadPluginFuncTest.groovy @@ -57,7 +57,7 @@ class InternalDistributionDownloadPluginFuncTest extends AbstractGradleFuncTest elasticsearch_distributions { test_distro { - version = "8.0.0" + version = "8.4.0" type = "archive" platform = "linux" architecture = Architecture.current(); @@ -87,7 +87,7 @@ class InternalDistributionDownloadPluginFuncTest extends AbstractGradleFuncTest elasticsearch_distributions { test_distro { - version = "8.0.0" + version = "8.4.0" type = "archive" platform = "linux" architecture = Architecture.current(); diff --git a/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/test/rest/LegacyYamlRestCompatTestPluginFuncTest.groovy b/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/test/rest/LegacyYamlRestCompatTestPluginFuncTest.groovy index af740cbdabd99..8a02b34023a27 100644 --- a/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/test/rest/LegacyYamlRestCompatTestPluginFuncTest.groovy +++ b/build-tools-internal/src/integTest/groovy/org/elasticsearch/gradle/internal/test/rest/LegacyYamlRestCompatTestPluginFuncTest.groovy @@ -39,6 +39,8 @@ class LegacyYamlRestCompatTestPluginFuncTest extends AbstractRestResourcesFuncTe def "yamlRestTestVxCompatTest does nothing when there are no tests"() { given: + internalBuild() + subProject(":distribution:bwc:maintenance") << """ configurations { checkout } artifacts { @@ -47,9 +49,7 @@ class LegacyYamlRestCompatTestPluginFuncTest extends AbstractRestResourcesFuncTe """ buildFile << """ - plugins { - id 'elasticsearch.legacy-yaml-rest-compat-test' - } + apply plugin: 'elasticsearch.legacy-yaml-rest-compat-test' """ when: @@ -62,7 +62,7 @@ class LegacyYamlRestCompatTestPluginFuncTest extends AbstractRestResourcesFuncTe result.task(transformTask).outcome == TaskOutcome.NO_SOURCE } - def "yamlRestTestVxCompatTest executes and copies api and transforms tests from :bwc:maintenance"() { + def "yamlRestCompatTest executes and copies api and transforms tests from :bwc:maintenance"() { given: internalBuild() @@ -144,6 +144,7 @@ class LegacyYamlRestCompatTestPluginFuncTest extends AbstractRestResourcesFuncTe def "yamlRestTestVxCompatTest is wired into check and checkRestCompat"() { given: + internalBuild() withVersionCatalogue() subProject(":distribution:bwc:maintenance") << """ configurations { checkout } @@ -153,10 +154,7 @@ class LegacyYamlRestCompatTestPluginFuncTest extends AbstractRestResourcesFuncTe """ buildFile << """ - plugins { - id 'elasticsearch.legacy-yaml-rest-compat-test' - } - + apply plugin: 'elasticsearch.legacy-yaml-rest-compat-test' """ when: diff --git a/build-tools-internal/src/integTest/resources/org/elasticsearch/gradle/internal/fake_git/remote/distribution/bwc/bugfix2/build.gradle b/build-tools-internal/src/integTest/resources/org/elasticsearch/gradle/internal/fake_git/remote/distribution/bwc/bugfix2/build.gradle new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/build-tools-internal/src/integTest/resources/org/elasticsearch/gradle/internal/fake_git/remote/distribution/bwc/maintenance/build.gradle b/build-tools-internal/src/integTest/resources/org/elasticsearch/gradle/internal/fake_git/remote/distribution/bwc/maintenance/build.gradle new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/build-tools-internal/src/integTest/resources/org/elasticsearch/gradle/internal/fake_git/remote/settings.gradle b/build-tools-internal/src/integTest/resources/org/elasticsearch/gradle/internal/fake_git/remote/settings.gradle index 8c321294b585f..e931537fcd6e9 100644 --- a/build-tools-internal/src/integTest/resources/org/elasticsearch/gradle/internal/fake_git/remote/settings.gradle +++ b/build-tools-internal/src/integTest/resources/org/elasticsearch/gradle/internal/fake_git/remote/settings.gradle @@ -10,9 +10,11 @@ rootProject.name = "root" include ":distribution:bwc:bugfix" +include ":distribution:bwc:bugfix2" include ":distribution:bwc:minor" include ":distribution:bwc:major" include ":distribution:bwc:staged" +include ":distribution:bwc:maintenance" include ":distribution:archives:darwin-tar" include ":distribution:archives:oss-darwin-tar" include ":distribution:archives:darwin-aarch64-tar" diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/BwcVersions.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/BwcVersions.java index 2bde9725d71f5..cb93d2498327e 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/BwcVersions.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/BwcVersions.java @@ -23,7 +23,6 @@ import java.util.Optional; import java.util.Set; import java.util.TreeMap; -import java.util.TreeSet; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Predicate; @@ -31,7 +30,9 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; +import static java.util.Collections.reverseOrder; import static java.util.Collections.unmodifiableList; +import static java.util.Comparator.comparing; /** * A container for elasticsearch supported version information used in BWC testing. @@ -70,18 +71,17 @@ public class BwcVersions implements Serializable { private static final Pattern LINE_PATTERN = Pattern.compile( "\\W+public static final Version V_(\\d+)_(\\d+)_(\\d+)(_alpha\\d+|_beta\\d+|_rc\\d+)?.*\\);" ); - private static final Version MINIMUM_WIRE_COMPATIBLE_VERSION = Version.fromString("7.17.0"); private static final String GLIBC_VERSION_ENV_VAR = "GLIBC_VERSION"; private final Version currentVersion; private final transient List versions; private final Map unreleased; - public BwcVersions(List versionLines) { - this(versionLines, Version.fromString(VersionProperties.getElasticsearch())); + public BwcVersions(List versionLines, List developmentBranches) { + this(versionLines, Version.fromString(VersionProperties.getElasticsearch()), developmentBranches); } - public BwcVersions(Version currentVersionProperty, List allVersions) { + public BwcVersions(Version currentVersionProperty, List allVersions, List developmentBranches) { if (allVersions.isEmpty()) { throw new IllegalArgumentException("Could not parse any versions"); } @@ -90,12 +90,12 @@ public BwcVersions(Version currentVersionProperty, List allVersions) { this.currentVersion = allVersions.get(allVersions.size() - 1); assertCurrentVersionMatchesParsed(currentVersionProperty); - this.unreleased = computeUnreleased(); + this.unreleased = computeUnreleased(developmentBranches); } // Visible for testing - BwcVersions(List versionLines, Version currentVersionProperty) { - this(currentVersionProperty, parseVersionLines(versionLines)); + BwcVersions(List versionLines, Version currentVersionProperty, List developmentBranches) { + this(currentVersionProperty, parseVersionLines(versionLines), developmentBranches); } private static List parseVersionLines(List versionLines) { @@ -132,51 +132,77 @@ public void forPreviousUnreleased(Consumer consumer) { ).stream().map(unreleased::get).forEach(consumer); } - private String getBranchFor(Version version) { - if (version.equals(currentVersion)) { - // Just assume the current branch is 'main'. It's actually not important, we never check out the current branch. - return "main"; - } else { + private String getBranchFor(Version version, List developmentBranches) { + // If the current version matches a specific feature freeze branch, use that + if (developmentBranches.contains(version.getMajor() + "." + version.getMinor())) { return version.getMajor() + "." + version.getMinor(); + } else if (developmentBranches.contains(version.getMajor() + ".x")) { // Otherwise if an n.x branch exists and we are that major + return version.getMajor() + ".x"; + } else { // otherwise we're the main branch + return "main"; } } - private Map computeUnreleased() { - Set unreleased = new TreeSet<>(); - // The current version is being worked, is always unreleased - unreleased.add(currentVersion); - // Recurse for all unreleased versions starting from the current version - addUnreleased(unreleased, currentVersion, 0); + private Map computeUnreleased(List developmentBranches) { + Map result = new TreeMap<>(); - // Grab the latest version from the previous major if necessary as well, this is going to be a maintenance release - Version maintenance = versions.stream() - .filter(v -> v.getMajor() == currentVersion.getMajor() - 1) - .max(Comparator.naturalOrder()) - .orElseThrow(); - // This is considered the maintenance release only if we haven't yet encountered it - boolean hasMaintenanceRelease = unreleased.add(maintenance); + // The current version is always in development + String currentBranch = getBranchFor(currentVersion, developmentBranches); + result.put(currentVersion, new UnreleasedVersionInfo(currentVersion, currentBranch, ":distribution")); + + // Check for an n.x branch as well + if (currentBranch.equals("main") && developmentBranches.stream().anyMatch(s -> s.endsWith(".x"))) { + // This should correspond to the latest new minor + Version version = versions.stream() + .sorted(Comparator.reverseOrder()) + .filter(v -> v.getMajor() == (currentVersion.getMajor() - 1) && v.getRevision() == 0) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Unable to determine development version for branch")); + String branch = getBranchFor(version, developmentBranches); + assert branch.equals(currentVersion.getMajor() - 1 + ".x") : "Expected branch does not match development branch"; + + result.put(version, new UnreleasedVersionInfo(version, branch, ":distribution:bwc:minor")); + } - List unreleasedList = unreleased.stream().sorted(Comparator.reverseOrder()).toList(); - Map result = new TreeMap<>(); - for (int i = 0; i < unreleasedList.size(); i++) { - Version esVersion = unreleasedList.get(i); - // This is either a new minor or staged release - if (currentVersion.equals(esVersion)) { - result.put(esVersion, new UnreleasedVersionInfo(esVersion, getBranchFor(esVersion), ":distribution")); - } else if (esVersion.getRevision() == 0) { - // If there are two upcoming unreleased minors then this one is the new minor - if (unreleasedList.get(i + 1).getRevision() == 0) { - result.put(esVersion, new UnreleasedVersionInfo(esVersion, getBranchFor(esVersion), ":distribution:bwc:minor")); - } else { - result.put(esVersion, new UnreleasedVersionInfo(esVersion, getBranchFor(esVersion), ":distribution:bwc:staged")); - } - } else { - // If this is the oldest unreleased version and we have a maintenance release - if (i == unreleasedList.size() - 1 && hasMaintenanceRelease) { - result.put(esVersion, new UnreleasedVersionInfo(esVersion, getBranchFor(esVersion), ":distribution:bwc:maintenance")); - } else { - result.put(esVersion, new UnreleasedVersionInfo(esVersion, getBranchFor(esVersion), ":distribution:bwc:bugfix")); - } + // Now handle all the feature freeze branches + List featureFreezeBranches = developmentBranches.stream() + .filter(b -> Pattern.matches("[0-9]+\\.[0-9]+", b)) + .sorted(reverseOrder(comparing(s -> Version.fromString(s, Version.Mode.RELAXED)))) + .toList(); + + boolean existingBugfix = false; + for (int i = 0; i < featureFreezeBranches.size(); i++) { + String branch = featureFreezeBranches.get(i); + Version version = versions.stream() + .sorted(Comparator.reverseOrder()) + .filter(v -> v.toString().startsWith(branch)) + .findFirst() + .orElse(null); + + // If we don't know about this version we can ignore it + if (version == null) { + continue; + } + + // If this is the current version we can ignore as we've already handled it + if (version.equals(currentVersion)) { + continue; + } + + // We only maintain compatibility back one major so ignore anything older + if (currentVersion.getMajor() - version.getMajor() > 1) { + continue; + } + + // This is the maintenance version + if (i == featureFreezeBranches.size() - 1) { + result.put(version, new UnreleasedVersionInfo(version, branch, ":distribution:bwc:maintenance")); + } else if (version.getRevision() == 0) { // This is the next staged minor + result.put(version, new UnreleasedVersionInfo(version, branch, ":distribution:bwc:staged")); + } else { // This is a bugfix + String project = existingBugfix ? "bugfix2" : "bugfix"; + result.put(version, new UnreleasedVersionInfo(version, branch, ":distribution:bwc:" + project)); + existingBugfix = true; } } @@ -225,7 +251,10 @@ public void compareToAuthoritative(List authoritativeReleasedVersions) } private List getReleased() { - return versions.stream().filter(v -> unreleased.containsKey(v) == false).toList(); + return versions.stream() + .filter(v -> v.getMajor() >= currentVersion.getMajor() - 1) + .filter(v -> unreleased.containsKey(v) == false) + .toList(); } /** @@ -251,7 +280,7 @@ public void withIndexCompatible(Predicate filter, BiConsumer getWireCompatible() { - return filterSupportedVersions(versions.stream().filter(v -> v.compareTo(MINIMUM_WIRE_COMPATIBLE_VERSION) >= 0).toList()); + return filterSupportedVersions(versions.stream().filter(v -> v.compareTo(getMinimumWireCompatibleVersion()) >= 0).toList()); } public void withWireCompatible(BiConsumer versionAction) { @@ -289,7 +318,17 @@ public List getUnreleasedWireCompatible() { } public Version getMinimumWireCompatibleVersion() { - return MINIMUM_WIRE_COMPATIBLE_VERSION; + // Determine minimum wire compatible version from list of known versions. + // Current BWC policy states the minimum wire compatible version is the last minor release or the previous major version. + return versions.stream() + .filter(v -> v.getRevision() == 0) + .filter(v -> v.getMajor() == currentVersion.getMajor() - 1) + .max(Comparator.naturalOrder()) + .orElseThrow(() -> new IllegalStateException("Unable to determine minimum wire compatible version.")); + } + + public Version getCurrentVersion() { + return currentVersion; } public record UnreleasedVersionInfo(Version version, String branch, String gradleProjectPath) {} diff --git a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/info/GlobalBuildInfoPlugin.java b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/info/GlobalBuildInfoPlugin.java index 761b0601a1c24..4b44d6578128c 100644 --- a/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/info/GlobalBuildInfoPlugin.java +++ b/build-tools-internal/src/main/java/org/elasticsearch/gradle/internal/info/GlobalBuildInfoPlugin.java @@ -8,6 +8,9 @@ */ package org.elasticsearch.gradle.internal.info; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + import org.apache.commons.io.IOUtils; import org.elasticsearch.gradle.VersionProperties; import org.elasticsearch.gradle.internal.BwcVersions; @@ -44,11 +47,13 @@ import java.io.File; import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.io.InputStreamReader; import java.io.UncheckedIOException; import java.nio.file.Files; import java.time.ZoneOffset; import java.time.ZonedDateTime; +import java.util.ArrayList; import java.util.List; import java.util.Locale; import java.util.Random; @@ -68,6 +73,7 @@ public class GlobalBuildInfoPlugin implements Plugin { private final JavaInstallationRegistry javaInstallationRegistry; private final JvmMetadataDetector metadataDetector; private final ProviderFactory providers; + private final ObjectMapper objectMapper; private JavaToolchainService toolChainService; private Project project; @@ -82,7 +88,7 @@ public GlobalBuildInfoPlugin( this.javaInstallationRegistry = javaInstallationRegistry; this.metadataDetector = new ErrorTraceMetadataDetector(metadataDetector); this.providers = providers; - + this.objectMapper = new ObjectMapper(); } @Override @@ -197,12 +203,27 @@ private BwcVersions resolveBwcVersions() { ); try (var is = new FileInputStream(versionsFilePath)) { List versionLines = IOUtils.readLines(is, "UTF-8"); - return new BwcVersions(versionLines); + return new BwcVersions(versionLines, getDevelopmentBranches()); } catch (IOException e) { throw new IllegalStateException("Unable to resolve to resolve bwc versions from versionsFile.", e); } } + private List getDevelopmentBranches() { + List branches = new ArrayList<>(); + File branchesFile = new File(Util.locateElasticsearchWorkspace(project.getGradle()), "branches.json"); + try (InputStream is = new FileInputStream(branchesFile)) { + JsonNode json = objectMapper.readTree(is); + for (JsonNode node : json.get("branches")) { + branches.add(node.get("branch").asText()); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + return branches; + } + private void logGlobalBuildInfo(BuildParameterExtension buildParams) { final String osName = System.getProperty("os.name"); final String osVersion = System.getProperty("os.version"); diff --git a/build-tools-internal/src/test/groovy/org/elasticsearch/gradle/internal/BwcVersionsSpec.groovy b/build-tools-internal/src/test/groovy/org/elasticsearch/gradle/internal/BwcVersionsSpec.groovy index 4257ff132186e..4d033564a42b4 100644 --- a/build-tools-internal/src/test/groovy/org/elasticsearch/gradle/internal/BwcVersionsSpec.groovy +++ b/build-tools-internal/src/test/groovy/org/elasticsearch/gradle/internal/BwcVersionsSpec.groovy @@ -11,186 +11,263 @@ package org.elasticsearch.gradle.internal import spock.lang.Specification -import org.elasticsearch.gradle.Architecture -import org.elasticsearch.gradle.ElasticsearchDistribution import org.elasticsearch.gradle.Version import org.elasticsearch.gradle.internal.BwcVersions.UnreleasedVersionInfo - class BwcVersionsSpec extends Specification { List versionLines = [] - def "current version is next major with last minor staged"() { + def "current version is next major"() { + given: + addVersion('7.17.10', '8.9.0') + addVersion('8.14.0', '9.9.0') + addVersion('8.14.1', '9.9.0') + addVersion('8.14.2', '9.9.0') + addVersion('8.15.0', '9.9.0') + addVersion('8.15.1', '9.9.0') + addVersion('8.15.2', '9.9.0') + addVersion('8.16.0', '9.10.0') + addVersion('8.16.1', '9.10.0') + addVersion('8.17.0', '9.10.0') + addVersion('9.0.0', '10.0.0') + + when: + def bwc = new BwcVersions(versionLines, v('9.0.0'), ['main', '8.x', '8.16', '8.15', '7.17']) + def unreleased = bwc.unreleased.collectEntries { [it, bwc.unreleasedInfo(it)] } + + then: + unreleased == [ + (v('8.15.2')): new UnreleasedVersionInfo(v('8.15.2'), '8.15', ':distribution:bwc:bugfix2'), + (v('8.16.1')): new UnreleasedVersionInfo(v('8.16.1'), '8.16', ':distribution:bwc:bugfix'), + (v('8.17.0')): new UnreleasedVersionInfo(v('8.17.0'), '8.x', ':distribution:bwc:minor'), + (v('9.0.0')): new UnreleasedVersionInfo(v('9.0.0'), 'main', ':distribution'), + ] + bwc.wireCompatible == [v('8.17.0'), v('9.0.0')] + bwc.indexCompatible == [v('8.14.0'), v('8.14.1'), v('8.14.2'), v('8.15.0'), v('8.15.1'), v('8.15.2'), v('8.16.0'), v('8.16.1'), v('8.17.0'), v('9.0.0')] + } + + def "current version is next major with staged minor"() { + given: + addVersion('7.17.10', '8.9.0') + addVersion('8.14.0', '9.9.0') + addVersion('8.14.1', '9.9.0') + addVersion('8.14.2', '9.9.0') + addVersion('8.15.0', '9.9.0') + addVersion('8.15.1', '9.9.0') + addVersion('8.15.2', '9.9.0') + addVersion('8.16.0', '9.10.0') + addVersion('8.16.1', '9.10.0') + addVersion('8.17.0', '9.10.0') + addVersion('8.18.0', '9.10.0') + addVersion('9.0.0', '10.0.0') + + when: + def bwc = new BwcVersions(versionLines, v('9.0.0'), ['main', '8.x', '8.17', '8.16', '8.15', '7.17']) + def unreleased = bwc.unreleased.collectEntries { [it, bwc.unreleasedInfo(it)] } + + then: + unreleased == [ + (v('8.15.2')): new UnreleasedVersionInfo(v('8.15.2'), '8.15', ':distribution:bwc:bugfix2'), + (v('8.16.1')): new UnreleasedVersionInfo(v('8.16.1'), '8.16', ':distribution:bwc:bugfix'), + (v('8.17.0')): new UnreleasedVersionInfo(v('8.17.0'), '8.17', ':distribution:bwc:staged'), + (v('8.18.0')): new UnreleasedVersionInfo(v('8.18.0'), '8.x', ':distribution:bwc:minor'), + (v('9.0.0')): new UnreleasedVersionInfo(v('9.0.0'), 'main', ':distribution'), + ] + bwc.wireCompatible == [v('8.18.0'), v('9.0.0')] + bwc.indexCompatible == [v('8.14.0'), v('8.14.1'), v('8.14.2'), v('8.15.0'), v('8.15.1'), v('8.15.2'), v('8.16.0'), v('8.16.1'), v('8.17.0'), v('8.18.0'), v('9.0.0')] + } + + def "current version is first new minor in major series"() { + given: + addVersion('7.17.10', '8.9.0') + addVersion('8.16.0', '9.10.0') + addVersion('8.16.1', '9.10.0') + addVersion('8.17.0', '9.10.0') + addVersion('8.18.0', '9.10.0') + addVersion('9.0.0', '10.0.0') + addVersion('9.1.0', '10.0.0') + + when: + def bwc = new BwcVersions(versionLines, v('9.1.0'), ['main', '9.0', '8.18']) + def unreleased = bwc.unreleased.collectEntries { [it, bwc.unreleasedInfo(it)] } + + then: + unreleased == [ + (v('8.18.0')): new UnreleasedVersionInfo(v('8.18.0'), '8.18', ':distribution:bwc:maintenance'), + (v('9.0.0')): new UnreleasedVersionInfo(v('9.0.0'), '9.0', ':distribution:bwc:staged'), + (v('9.1.0')): new UnreleasedVersionInfo(v('9.1.0'), 'main', ':distribution'), + ] + bwc.wireCompatible == [v('8.18.0'), v('9.0.0'), v('9.1.0')] + bwc.indexCompatible == [v('8.16.0'), v('8.16.1'), v('8.17.0'), v('8.18.0'), v('9.0.0'), v('9.1.0')] + } + + def "current version is new minor with single bugfix"() { given: - addVersion('7.14.0', '8.9.0') - addVersion('7.14.1', '8.9.0') - addVersion('7.14.2', '8.9.0') - addVersion('7.15.0', '8.9.0') - addVersion('7.15.1', '8.9.0') - addVersion('7.15.2', '8.9.0') - addVersion('7.16.0', '8.10.0') - addVersion('7.16.1', '8.10.0') - addVersion('7.16.2', '8.10.0') - addVersion('7.17.0', '8.10.0') - addVersion('8.0.0', '9.0.0') - addVersion('8.1.0', '9.0.0') + addVersion('7.17.10', '8.9.0') + addVersion('8.16.0', '9.10.0') + addVersion('8.16.1', '9.10.0') + addVersion('8.17.0', '9.10.0') + addVersion('8.18.0', '9.10.0') + addVersion('9.0.0', '10.0.0') + addVersion('9.0.1', '10.0.0') + addVersion('9.1.0', '10.0.0') when: - def bwc = new BwcVersions(versionLines, v('8.1.0')) + def bwc = new BwcVersions(versionLines, v('9.1.0'), ['main', '9.0', '8.18']) def unreleased = bwc.unreleased.collectEntries { [it, bwc.unreleasedInfo(it)] } then: unreleased == [ - (v('7.16.2')): new UnreleasedVersionInfo(v('7.16.2'), '7.16', ':distribution:bwc:bugfix'), - (v('7.17.0')): new UnreleasedVersionInfo(v('7.17.0'), '7.17', ':distribution:bwc:staged'), - (v('8.0.0')): new UnreleasedVersionInfo(v('8.0.0'), '8.0', ':distribution:bwc:minor'), - (v('8.1.0')): new UnreleasedVersionInfo(v('8.1.0'), 'main', ':distribution') + (v('8.18.0')): new UnreleasedVersionInfo(v('8.18.0'), '8.18', ':distribution:bwc:maintenance'), + (v('9.0.1')): new UnreleasedVersionInfo(v('9.0.1'), '9.0', ':distribution:bwc:bugfix'), + (v('9.1.0')): new UnreleasedVersionInfo(v('9.1.0'), 'main', ':distribution'), ] - bwc.wireCompatible == [v('7.17.0'), v('8.0.0'), v('8.1.0')] - bwc.indexCompatible == osFiltered([v('7.14.0'), v('7.14.1'), v('7.14.2'), v('7.15.0'), v('7.15.1'), v('7.15.2'), v('7.16.0'), v('7.16.1'), v('7.16.2'), v('7.17.0'), v('8.0.0'), v('8.1.0')]) + bwc.wireCompatible == [v('8.18.0'), v('9.0.0'), v('9.0.1'), v('9.1.0')] + bwc.indexCompatible == [v('8.16.0'), v('8.16.1'), v('8.17.0'), v('8.18.0'), v('9.0.0'), v('9.0.1'), v('9.1.0')] } - def "current version is next minor with next major and last minor both staged"() { + def "current version is new minor with single bugfix and staged minor"() { given: - addVersion('7.14.0', '8.9.0') - addVersion('7.14.1', '8.9.0') - addVersion('7.14.2', '8.9.0') - addVersion('7.15.0', '8.9.0') - addVersion('7.15.1', '8.9.0') - addVersion('7.15.2', '8.9.0') - addVersion('7.16.0', '8.10.0') - addVersion('7.16.1', '8.10.0') - addVersion('7.17.0', '8.10.0') - addVersion('8.0.0', '9.0.0') - addVersion('8.1.0', '9.1.0') + addVersion('7.17.10', '8.9.0') + addVersion('8.16.0', '9.10.0') + addVersion('8.16.1', '9.10.0') + addVersion('8.17.0', '9.10.0') + addVersion('8.18.0', '9.10.0') + addVersion('9.0.0', '10.0.0') + addVersion('9.0.1', '10.0.0') + addVersion('9.1.0', '10.0.0') + addVersion('9.2.0', '10.0.0') when: - def bwc = new BwcVersions(versionLines, v('8.1.0')) + def bwc = new BwcVersions(versionLines, v('9.2.0'), ['main', '9.1', '9.0', '8.18']) def unreleased = bwc.unreleased.collectEntries { [it, bwc.unreleasedInfo(it)] } then: unreleased == [ - (v('7.16.1')): new UnreleasedVersionInfo(v('7.16.1'), '7.16', ':distribution:bwc:bugfix'), - (v('7.17.0')): new UnreleasedVersionInfo(v('7.17.0'), '7.17', ':distribution:bwc:staged'), - (v('8.0.0')): new UnreleasedVersionInfo(v('8.0.0'), '8.0', ':distribution:bwc:minor'), - (v('8.1.0')): new UnreleasedVersionInfo(v('8.1.0'), 'main', ':distribution') + (v('8.18.0')): new UnreleasedVersionInfo(v('8.18.0'), '8.18', ':distribution:bwc:maintenance'), + (v('9.0.1')): new UnreleasedVersionInfo(v('9.0.1'), '9.0', ':distribution:bwc:bugfix'), + (v('9.1.0')): new UnreleasedVersionInfo(v('9.1.0'), '9.1', ':distribution:bwc:staged'), + (v('9.2.0')): new UnreleasedVersionInfo(v('9.2.0'), 'main', ':distribution'), ] - bwc.wireCompatible == [v('7.17.0'), v('8.0.0'), v('8.1.0')] - bwc.indexCompatible == osFiltered([v('7.14.0'), v('7.14.1'), v('7.14.2'), v('7.15.0'), v('7.15.1'), v('7.15.2'), v('7.16.0'), v('7.16.1'), v('7.17.0'), v('8.0.0'), v('8.1.0')]) + bwc.wireCompatible == [v('8.18.0'), v('9.0.0'), v('9.0.1'), v('9.1.0'), v('9.2.0')] + bwc.indexCompatible == [v('8.16.0'), v('8.16.1'), v('8.17.0'), v('8.18.0'), v('9.0.0'), v('9.0.1'), v('9.1.0'), v('9.2.0')] } - def "current is next minor with upcoming minor staged"() { + def "current version is next minor"() { given: - addVersion('7.14.0', '8.9.0') - addVersion('7.14.1', '8.9.0') - addVersion('7.14.2', '8.9.0') - addVersion('7.15.0', '8.9.0') - addVersion('7.15.1', '8.9.0') - addVersion('7.15.2', '8.9.0') - addVersion('7.16.0', '8.10.0') - addVersion('7.16.1', '8.10.0') - addVersion('7.17.0', '8.10.0') - addVersion('7.17.1', '8.10.0') - addVersion('8.0.0', '9.0.0') - addVersion('8.1.0', '9.1.0') + addVersion('7.16.3', '8.9.0') + addVersion('7.17.0', '8.9.0') + addVersion('7.17.1', '8.9.0') + addVersion('8.14.0', '9.9.0') + addVersion('8.14.1', '9.9.0') + addVersion('8.14.2', '9.9.0') + addVersion('8.15.0', '9.9.0') + addVersion('8.15.1', '9.9.0') + addVersion('8.15.2', '9.9.0') + addVersion('8.16.0', '9.10.0') + addVersion('8.16.1', '9.10.0') + addVersion('8.17.0', '9.10.0') + addVersion('8.17.1', '9.10.0') + addVersion('8.18.0', '9.10.0') when: - def bwc = new BwcVersions(versionLines, v('8.1.0')) + def bwc = new BwcVersions(versionLines, v('8.18.0'), ['main', '8.x', '8.17', '8.16', '7.17']) def unreleased = bwc.unreleased.collectEntries { [it, bwc.unreleasedInfo(it)] } then: unreleased == [ - (v('7.17.1')): new UnreleasedVersionInfo(v('7.17.1'), '7.17', ':distribution:bwc:bugfix'), - (v('8.0.0')): new UnreleasedVersionInfo(v('8.0.0'), '8.0', ':distribution:bwc:staged'), - (v('8.1.0')): new UnreleasedVersionInfo(v('8.1.0'), 'main', ':distribution') + (v('7.17.1')): new UnreleasedVersionInfo(v('7.17.1'), '7.17', ':distribution:bwc:maintenance'), + (v('8.16.1')): new UnreleasedVersionInfo(v('8.16.1'), '8.16', ':distribution:bwc:bugfix2'), + (v('8.17.1')): new UnreleasedVersionInfo(v('8.17.1'), '8.17', ':distribution:bwc:bugfix'), + (v('8.18.0')): new UnreleasedVersionInfo(v('8.18.0'), '8.x', ':distribution'), ] - bwc.wireCompatible == [v('7.17.0'), v('7.17.1'), v('8.0.0'), v('8.1.0')] - bwc.indexCompatible == osFiltered([v('7.14.0'), v('7.14.1'), v('7.14.2'), v('7.15.0'), v('7.15.1'), v('7.15.2'), v('7.16.0'), v('7.16.1'), v('7.17.0'), v('7.17.1'), v('8.0.0'), v('8.1.0')]) + bwc.wireCompatible == [v('7.17.0'), v('7.17.1'), v('8.14.0'), v('8.14.1'), v('8.14.2'), v('8.15.0'), v('8.15.1'), v('8.15.2'), v('8.16.0'), v('8.16.1'), v('8.17.0'), v('8.17.1'), v('8.18.0')] + bwc.indexCompatible == [v('7.16.3'), v('7.17.0'), v('7.17.1'), v('8.14.0'), v('8.14.1'), v('8.14.2'), v('8.15.0'), v('8.15.1'), v('8.15.2'), v('8.16.0'), v('8.16.1'), v('8.17.0'), v('8.17.1'), v('8.18.0')] } - def "current version is staged major"() { + def "current version is new minor with staged minor"() { given: - addVersion('7.14.0', '8.9.0') - addVersion('7.14.1', '8.9.0') - addVersion('7.14.2', '8.9.0') - addVersion('7.15.0', '8.9.0') - addVersion('7.15.1', '8.9.0') - addVersion('7.15.2', '8.9.0') - addVersion('7.16.0', '8.10.0') - addVersion('7.16.1', '8.10.0') - addVersion('7.17.0', '8.10.0') - addVersion('7.17.1', '8.10.0') - addVersion('8.0.0', '9.0.0') + addVersion('7.16.3', '8.9.0') + addVersion('7.17.0', '8.9.0') + addVersion('7.17.1', '8.9.0') + addVersion('8.14.0', '9.9.0') + addVersion('8.14.1', '9.9.0') + addVersion('8.14.2', '9.9.0') + addVersion('8.15.0', '9.9.0') + addVersion('8.15.1', '9.9.0') + addVersion('8.15.2', '9.9.0') + addVersion('8.16.0', '9.10.0') + addVersion('8.16.1', '9.10.0') + addVersion('8.17.0', '9.10.0') + addVersion('8.18.0', '9.10.0') when: - def bwc = new BwcVersions(versionLines, v('8.0.0')) + def bwc = new BwcVersions(versionLines, v('8.18.0'), ['main', '8.x', '8.17', '8.16', '8.15', '7.17']) def unreleased = bwc.unreleased.collectEntries { [it, bwc.unreleasedInfo(it)] } then: unreleased == [ - (v('7.17.1')): new UnreleasedVersionInfo(v('7.17.1'), '7.17', ':distribution:bwc:bugfix'), - (v('8.0.0')): new UnreleasedVersionInfo(v('8.0.0'), 'main', ':distribution'), + (v('7.17.1')): new UnreleasedVersionInfo(v('7.17.1'), '7.17', ':distribution:bwc:maintenance'), + (v('8.15.2')): new UnreleasedVersionInfo(v('8.15.2'), '8.15', ':distribution:bwc:bugfix2'), + (v('8.16.1')): new UnreleasedVersionInfo(v('8.16.1'), '8.16', ':distribution:bwc:bugfix'), + (v('8.17.0')): new UnreleasedVersionInfo(v('8.17.0'), '8.17', ':distribution:bwc:staged'), + (v('8.18.0')): new UnreleasedVersionInfo(v('8.18.0'), '8.x', ':distribution'), ] - bwc.wireCompatible == [v('7.17.0'), v('7.17.1'), v('8.0.0')] - bwc.indexCompatible == osFiltered([v('7.14.0'), v('7.14.1'), v('7.14.2'), v('7.15.0'), v('7.15.1'), v('7.15.2'), v('7.16.0'), v('7.16.1'), v('7.17.0'), v('7.17.1'), v('8.0.0')]) + bwc.wireCompatible == [v('7.17.0'), v('7.17.1'), v('8.14.0'), v('8.14.1'), v('8.14.2'), v('8.15.0'), v('8.15.1'), v('8.15.2'), v('8.16.0'), v('8.16.1'), v('8.17.0'), v('8.18.0')] + bwc.indexCompatible == [v('7.16.3'), v('7.17.0'), v('7.17.1'), v('8.14.0'), v('8.14.1'), v('8.14.2'), v('8.15.0'), v('8.15.1'), v('8.15.2'), v('8.16.0'), v('8.16.1'), v('8.17.0'), v('8.18.0')] } - def "current version is next bugfix"() { + def "current version is first bugfix"() { given: - addVersion('7.14.0', '8.9.0') - addVersion('7.14.1', '8.9.0') - addVersion('7.14.2', '8.9.0') - addVersion('7.15.0', '8.9.0') - addVersion('7.15.1', '8.9.0') - addVersion('7.15.2', '8.9.0') - addVersion('7.16.0', '8.10.0') - addVersion('7.16.1', '8.10.0') - addVersion('7.17.0', '8.10.0') - addVersion('7.17.1', '8.10.0') - addVersion('8.0.0', '9.0.0') - addVersion('8.0.1', '9.0.0') + addVersion('7.16.3', '8.9.0') + addVersion('7.17.0', '8.9.0') + addVersion('7.17.1', '8.9.0') + addVersion('8.14.0', '9.9.0') + addVersion('8.14.1', '9.9.0') + addVersion('8.14.2', '9.9.0') + addVersion('8.15.0', '9.9.0') + addVersion('8.15.1', '9.9.0') + addVersion('8.15.2', '9.9.0') + addVersion('8.16.0', '9.10.0') + addVersion('8.16.1', '9.10.0') when: - def bwc = new BwcVersions(versionLines, v('8.0.1')) + def bwc = new BwcVersions(versionLines, v('8.16.1'), ['main', '8.x', '8.17', '8.16', '8.15', '7.17']) def unreleased = bwc.unreleased.collectEntries { [it, bwc.unreleasedInfo(it)] } then: unreleased == [ (v('7.17.1')): new UnreleasedVersionInfo(v('7.17.1'), '7.17', ':distribution:bwc:maintenance'), - (v('8.0.1')): new UnreleasedVersionInfo(v('8.0.1'), 'main', ':distribution'), + (v('8.15.2')): new UnreleasedVersionInfo(v('8.15.2'), '8.15', ':distribution:bwc:bugfix'), + (v('8.16.1')): new UnreleasedVersionInfo(v('8.16.1'), '8.16', ':distribution'), ] - bwc.wireCompatible == [v('7.17.0'), v('7.17.1'), v('8.0.0'), v('8.0.1')] - bwc.indexCompatible == osFiltered([v('7.14.0'), v('7.14.1'), v('7.14.2'), v('7.15.0'), v('7.15.1'), v('7.15.2'), v('7.16.0'), v('7.16.1'), v('7.17.0'), v('7.17.1'), v('8.0.0'), v('8.0.1')]) + bwc.wireCompatible == [v('7.17.0'), v('7.17.1'), v('8.14.0'), v('8.14.1'), v('8.14.2'), v('8.15.0'), v('8.15.1'), v('8.15.2'), v('8.16.0'), v('8.16.1')] + bwc.indexCompatible == [v('7.16.3'), v('7.17.0'), v('7.17.1'), v('8.14.0'), v('8.14.1'), v('8.14.2'), v('8.15.0'), v('8.15.1'), v('8.15.2'), v('8.16.0'), v('8.16.1')] } - def "current version is next minor with no staged releases"() { + def "current version is second bugfix"() { given: - addVersion('7.14.0', '8.9.0') - addVersion('7.14.1', '8.9.0') - addVersion('7.14.2', '8.9.0') - addVersion('7.15.0', '8.9.0') - addVersion('7.15.1', '8.9.0') - addVersion('7.15.2', '8.9.0') - addVersion('7.16.0', '8.10.0') - addVersion('7.16.1', '8.10.0') - addVersion('7.17.0', '8.10.0') - addVersion('7.17.1', '8.10.0') - addVersion('8.0.0', '9.0.0') - addVersion('8.0.1', '9.0.0') - addVersion('8.1.0', '9.1.0') + addVersion('7.16.3', '8.9.0') + addVersion('7.17.0', '8.9.0') + addVersion('7.17.1', '8.9.0') + addVersion('8.14.0', '9.9.0') + addVersion('8.14.1', '9.9.0') + addVersion('8.14.2', '9.9.0') + addVersion('8.15.0', '9.9.0') + addVersion('8.15.1', '9.9.0') + addVersion('8.15.2', '9.9.0') when: - def bwc = new BwcVersions(versionLines, v('8.1.0')) + def bwc = new BwcVersions(versionLines, v('8.15.2'), ['main', '8.x', '8.17', '8.16', '8.15', '7.17']) def unreleased = bwc.unreleased.collectEntries { [it, bwc.unreleasedInfo(it)] } then: unreleased == [ (v('7.17.1')): new UnreleasedVersionInfo(v('7.17.1'), '7.17', ':distribution:bwc:maintenance'), - (v('8.0.1')): new UnreleasedVersionInfo(v('8.0.1'), '8.0', ':distribution:bwc:bugfix'), - (v('8.1.0')): new UnreleasedVersionInfo(v('8.1.0'), 'main', ':distribution') + (v('8.15.2')): new UnreleasedVersionInfo(v('8.15.2'), '8.15', ':distribution'), ] - bwc.wireCompatible == [v('7.17.0'), v('7.17.1'), v('8.0.0'), v('8.0.1'), v('8.1.0')] - bwc.indexCompatible == osFiltered([v('7.14.0'), v('7.14.1'), v('7.14.2'), v('7.15.0'), v('7.15.1'), v('7.15.2'), v('7.16.0'), v('7.16.1'), v('7.17.0'), v('7.17.1'), v('8.0.0'), v('8.0.1'), v('8.1.0')]) + bwc.wireCompatible == [v('7.17.0'), v('7.17.1'), v('8.14.0'), v('8.14.1'), v('8.14.2'), v('8.15.0'), v('8.15.1'), v('8.15.2')] + bwc.indexCompatible == [v('7.16.3'), v('7.17.0'), v('7.17.1'), v('8.14.0'), v('8.14.1'), v('8.14.2'), v('8.15.0'), v('8.15.1'), v('8.15.2')] } private void addVersion(String elasticsearch, String lucene) { @@ -203,12 +280,4 @@ class BwcVersionsSpec extends Specification { return Version.fromString(version) } - private boolean osxAarch64() { - Architecture.current() == Architecture.AARCH64 && - ElasticsearchDistribution.CURRENT_PLATFORM.equals(ElasticsearchDistribution.Platform.DARWIN) - } - - private List osFiltered(ArrayList versions) { - return osxAarch64() ? versions.findAll {it.onOrAfter("7.16.0")} : versions - } } diff --git a/build-tools-internal/src/test/java/org/elasticsearch/gradle/AbstractDistributionDownloadPluginTests.java b/build-tools-internal/src/test/java/org/elasticsearch/gradle/AbstractDistributionDownloadPluginTests.java index 639dec280ae9a..7512fa20814c6 100644 --- a/build-tools-internal/src/test/java/org/elasticsearch/gradle/AbstractDistributionDownloadPluginTests.java +++ b/build-tools-internal/src/test/java/org/elasticsearch/gradle/AbstractDistributionDownloadPluginTests.java @@ -16,6 +16,7 @@ import java.io.File; import java.util.Arrays; +import java.util.List; public class AbstractDistributionDownloadPluginTests { protected static Project rootProject; @@ -28,22 +29,27 @@ public class AbstractDistributionDownloadPluginTests { protected static final Version BWC_STAGED_VERSION = Version.fromString("1.0.0"); protected static final Version BWC_BUGFIX_VERSION = Version.fromString("1.0.1"); protected static final Version BWC_MAINTENANCE_VERSION = Version.fromString("0.90.1"); + protected static final List DEVELOPMENT_BRANCHES = Arrays.asList("main", "1.1", "1.0", "0.90"); protected static final BwcVersions BWC_MINOR = new BwcVersions( BWC_MAJOR_VERSION, - Arrays.asList(BWC_BUGFIX_VERSION, BWC_MINOR_VERSION, BWC_MAJOR_VERSION) + Arrays.asList(BWC_BUGFIX_VERSION, BWC_MINOR_VERSION, BWC_MAJOR_VERSION), + DEVELOPMENT_BRANCHES ); protected static final BwcVersions BWC_STAGED = new BwcVersions( BWC_MAJOR_VERSION, - Arrays.asList(BWC_MAINTENANCE_VERSION, BWC_STAGED_VERSION, BWC_MINOR_VERSION, BWC_MAJOR_VERSION) + Arrays.asList(BWC_MAINTENANCE_VERSION, BWC_STAGED_VERSION, BWC_MINOR_VERSION, BWC_MAJOR_VERSION), + DEVELOPMENT_BRANCHES ); protected static final BwcVersions BWC_BUGFIX = new BwcVersions( BWC_MAJOR_VERSION, - Arrays.asList(BWC_BUGFIX_VERSION, BWC_MINOR_VERSION, BWC_MAJOR_VERSION) + Arrays.asList(BWC_BUGFIX_VERSION, BWC_MINOR_VERSION, BWC_MAJOR_VERSION), + DEVELOPMENT_BRANCHES ); protected static final BwcVersions BWC_MAINTENANCE = new BwcVersions( BWC_MINOR_VERSION, - Arrays.asList(BWC_MAINTENANCE_VERSION, BWC_BUGFIX_VERSION, BWC_MINOR_VERSION) + Arrays.asList(BWC_MAINTENANCE_VERSION, BWC_BUGFIX_VERSION, BWC_MINOR_VERSION), + DEVELOPMENT_BRANCHES ); protected static String projectName(String base, boolean bundledJdk) { diff --git a/build-tools/src/testFixtures/groovy/org/elasticsearch/gradle/fixtures/AbstractGradleFuncTest.groovy b/build-tools/src/testFixtures/groovy/org/elasticsearch/gradle/fixtures/AbstractGradleFuncTest.groovy index 181e3599cdf2f..3e1a7fe672e7a 100644 --- a/build-tools/src/testFixtures/groovy/org/elasticsearch/gradle/fixtures/AbstractGradleFuncTest.groovy +++ b/build-tools/src/testFixtures/groovy/org/elasticsearch/gradle/fixtures/AbstractGradleFuncTest.groovy @@ -156,12 +156,12 @@ abstract class AbstractGradleFuncTest extends Specification { File internalBuild( List extraPlugins = [], - String bugfix = "7.15.2", - String bugfixLucene = "8.9.0", - String staged = "7.16.0", - String stagedLucene = "8.10.0", - String minor = "8.0.0", - String minorLucene = "9.0.0" + String maintenance = "7.16.10", + String bugfix2 = "8.1.3", + String bugfix = "8.2.1", + String staged = "8.3.0", + String minor = "8.4.0", + String current = "9.0.0" ) { buildFile << """plugins { id 'elasticsearch.global-build-info' @@ -172,15 +172,17 @@ abstract class AbstractGradleFuncTest extends Specification { import org.elasticsearch.gradle.internal.BwcVersions import org.elasticsearch.gradle.Version - Version currentVersion = Version.fromString("8.1.0") + Version currentVersion = Version.fromString("${current}") def versionList = [ + Version.fromString("$maintenance"), + Version.fromString("$bugfix2"), Version.fromString("$bugfix"), Version.fromString("$staged"), Version.fromString("$minor"), currentVersion ] - BwcVersions versions = new BwcVersions(currentVersion, versionList) + BwcVersions versions = new BwcVersions(currentVersion, versionList, ['main', '8.x', '8.3', '8.2', '8.1', '7.16']) buildParams.getBwcVersionsProperty().set(versions) """ } diff --git a/distribution/bwc/bugfix2/build.gradle b/distribution/bwc/bugfix2/build.gradle new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/server/src/main/java/org/elasticsearch/Version.java b/server/src/main/java/org/elasticsearch/Version.java index bea211d7c36db..f8abeb8e30488 100644 --- a/server/src/main/java/org/elasticsearch/Version.java +++ b/server/src/main/java/org/elasticsearch/Version.java @@ -189,6 +189,7 @@ public class Version implements VersionId, ToXContentFragment { public static final Version V_8_15_2 = new Version(8_15_02_99); public static final Version V_8_15_3 = new Version(8_15_03_99); public static final Version V_8_15_4 = new Version(8_15_04_99); + public static final Version V_8_15_5 = new Version(8_15_05_99); public static final Version V_8_15_6 = new Version(8_15_06_99); public static final Version V_8_16_0 = new Version(8_16_00_99); public static final Version V_8_16_1 = new Version(8_16_01_99); diff --git a/server/src/test/java/org/elasticsearch/VersionTests.java b/server/src/test/java/org/elasticsearch/VersionTests.java index 0b35a3cc23c16..5e10a7d37aea1 100644 --- a/server/src/test/java/org/elasticsearch/VersionTests.java +++ b/server/src/test/java/org/elasticsearch/VersionTests.java @@ -179,8 +179,7 @@ public void testParseVersion() { } public void testAllVersionsMatchId() throws Exception { - final Set releasedVersions = new HashSet<>(VersionUtils.allReleasedVersions()); - final Set unreleasedVersions = new HashSet<>(VersionUtils.allUnreleasedVersions()); + final Set versions = new HashSet<>(VersionUtils.allVersions()); Map maxBranchVersions = new HashMap<>(); for (java.lang.reflect.Field field : Version.class.getFields()) { if (field.getName().matches("_ID")) { @@ -195,43 +194,15 @@ public void testAllVersionsMatchId() throws Exception { Version v = (Version) versionConstant.get(null); logger.debug("Checking {}", v); - if (field.getName().endsWith("_UNRELEASED")) { - assertTrue(unreleasedVersions.contains(v)); - } else { - assertTrue(releasedVersions.contains(v)); - } + assertTrue(versions.contains(v)); assertEquals("Version id " + field.getName() + " does not point to " + constantName, v, Version.fromId(versionId)); assertEquals("Version " + constantName + " does not have correct id", versionId, v.id); String number = v.toString(); assertEquals("V_" + number.replace('.', '_'), constantName); - - // only the latest version for a branch should be a snapshot (ie unreleased) - String branchName = "" + v.major + "." + v.minor; - Version maxBranchVersion = maxBranchVersions.get(branchName); - if (maxBranchVersion == null) { - maxBranchVersions.put(branchName, v); - } else if (v.after(maxBranchVersion)) { - if (v == Version.CURRENT) { - // Current is weird - it counts as released even though it shouldn't. - continue; - } - assertFalse( - "Version " + maxBranchVersion + " cannot be a snapshot because version " + v + " exists", - VersionUtils.allUnreleasedVersions().contains(maxBranchVersion) - ); - maxBranchVersions.put(branchName, v); - } } } } - public static void assertUnknownVersion(Version version) { - assertFalse( - "Version " + version + " has been releaed don't use a new instance of this version", - VersionUtils.allReleasedVersions().contains(version) - ); - } - public void testIsCompatible() { assertTrue(isCompatible(Version.CURRENT, Version.CURRENT.minimumCompatibilityVersion())); assertFalse(isCompatible(Version.V_7_0_0, Version.V_8_0_0)); @@ -279,14 +250,6 @@ public boolean isCompatible(Version left, Version right) { return result; } - // This exists because 5.1.0 was never released due to a mistake in the release process. - // This verifies that we never declare the version as "released" accidentally. - // It would never pass qa tests later on, but those come very far in the build and this is quick to check now. - public void testUnreleasedVersion() { - Version VERSION_5_1_0_UNRELEASED = Version.fromString("5.1.0"); - VersionTests.assertUnknownVersion(VERSION_5_1_0_UNRELEASED); - } - public void testIllegalMinorAndPatchNumbers() { IllegalArgumentException e = expectThrows(IllegalArgumentException.class, () -> Version.fromString("8.2.999")); assertThat( diff --git a/settings.gradle b/settings.gradle index e8d0c8ac95ed5..56101606228d8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -77,6 +77,7 @@ List projects = [ 'distribution:packages:aarch64-rpm', 'distribution:packages:rpm', 'distribution:bwc:bugfix', + 'distribution:bwc:bugfix2', 'distribution:bwc:maintenance', 'distribution:bwc:minor', 'distribution:bwc:staged', diff --git a/test/framework/build.gradle b/test/framework/build.gradle index 1fc512824f1c2..ffd02a1b9e602 100644 --- a/test/framework/build.gradle +++ b/test/framework/build.gradle @@ -87,7 +87,6 @@ tasks.named("thirdPartyAudit").configure { tasks.named("test").configure { systemProperty 'tests.gradle_index_compat_versions', buildParams.bwcVersions.indexCompatible.join(',') systemProperty 'tests.gradle_wire_compat_versions', buildParams.bwcVersions.wireCompatible.join(',') - systemProperty 'tests.gradle_unreleased_versions', buildParams.bwcVersions.unreleased.join(',') } tasks.register("integTest", Test) { diff --git a/test/framework/src/main/java/org/elasticsearch/test/VersionUtils.java b/test/framework/src/main/java/org/elasticsearch/test/VersionUtils.java index d561c5512b614..8b7ab620774b9 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/VersionUtils.java +++ b/test/framework/src/main/java/org/elasticsearch/test/VersionUtils.java @@ -12,132 +12,15 @@ import org.elasticsearch.Build; import org.elasticsearch.Version; import org.elasticsearch.core.Nullable; -import org.elasticsearch.core.Tuple; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.Random; -import java.util.stream.Collectors; -import java.util.stream.Stream; /** Utilities for selecting versions in tests */ public class VersionUtils { - /** - * Sort versions that have backwards compatibility guarantees from - * those that don't. Doesn't actually check whether or not the versions - * are released, instead it relies on gradle to have already checked - * this which it does in {@code :core:verifyVersions}. So long as the - * rules here match up with the rules in gradle then this should - * produce sensible results. - * @return a tuple containing versions with backwards compatibility - * guarantees in v1 and versions without the guranteees in v2 - */ - static Tuple, List> resolveReleasedVersions(Version current, Class versionClass) { - // group versions into major version - Map> majorVersions = Version.getDeclaredVersions(versionClass) - .stream() - .collect(Collectors.groupingBy(v -> (int) v.major)); - // this breaks b/c 5.x is still in version list but master doesn't care about it! - // assert majorVersions.size() == 2; - // TODO: remove oldVersions, we should only ever have 2 majors in Version - List> oldVersions = splitByMinor(majorVersions.getOrDefault((int) current.major - 2, Collections.emptyList())); - List> previousMajor = splitByMinor(majorVersions.get((int) current.major - 1)); - List> currentMajor = splitByMinor(majorVersions.get((int) current.major)); - - List unreleasedVersions = new ArrayList<>(); - final List> stableVersions; - if (currentMajor.size() == 1) { - // on master branch - stableVersions = previousMajor; - // remove current - moveLastToUnreleased(currentMajor, unreleasedVersions); - } else { - // on a stable or release branch, ie N.x - stableVersions = currentMajor; - // remove the next maintenance bugfix - moveLastToUnreleased(previousMajor, unreleasedVersions); - } - - // remove next minor - Version lastMinor = moveLastToUnreleased(stableVersions, unreleasedVersions); - if (lastMinor.revision == 0) { - if (stableVersions.get(stableVersions.size() - 1).size() == 1) { - // a minor is being staged, which is also unreleased - moveLastToUnreleased(stableVersions, unreleasedVersions); - } - // remove the next bugfix - if (stableVersions.isEmpty() == false) { - moveLastToUnreleased(stableVersions, unreleasedVersions); - } - } - - // If none of the previous major was released, then the last minor and bugfix of the old version was not released either. - if (previousMajor.isEmpty()) { - assert currentMajor.isEmpty() : currentMajor; - // minor of the old version is being staged - moveLastToUnreleased(oldVersions, unreleasedVersions); - // bugix of the old version is also being staged - moveLastToUnreleased(oldVersions, unreleasedVersions); - } - List releasedVersions = Stream.of(oldVersions, previousMajor, currentMajor) - .flatMap(List::stream) - .flatMap(List::stream) - .collect(Collectors.toList()); - Collections.sort(unreleasedVersions); // we add unreleased out of order, so need to sort here - return new Tuple<>(Collections.unmodifiableList(releasedVersions), Collections.unmodifiableList(unreleasedVersions)); - } - - // split the given versions into sub lists grouped by minor version - private static List> splitByMinor(List versions) { - Map> byMinor = versions.stream().collect(Collectors.groupingBy(v -> (int) v.minor)); - return byMinor.entrySet().stream().sorted(Map.Entry.comparingByKey()).map(Map.Entry::getValue).collect(Collectors.toList()); - } - - // move the last version of the last minor in versions to the unreleased versions - private static Version moveLastToUnreleased(List> versions, List unreleasedVersions) { - List lastMinor = new ArrayList<>(versions.get(versions.size() - 1)); - Version lastVersion = lastMinor.remove(lastMinor.size() - 1); - if (lastMinor.isEmpty()) { - versions.remove(versions.size() - 1); - } else { - versions.set(versions.size() - 1, lastMinor); - } - unreleasedVersions.add(lastVersion); - return lastVersion; - } - - private static final List RELEASED_VERSIONS; - private static final List UNRELEASED_VERSIONS; - private static final List ALL_VERSIONS; - - static { - Tuple, List> versions = resolveReleasedVersions(Version.CURRENT, Version.class); - RELEASED_VERSIONS = versions.v1(); - UNRELEASED_VERSIONS = versions.v2(); - List allVersions = new ArrayList<>(RELEASED_VERSIONS.size() + UNRELEASED_VERSIONS.size()); - allVersions.addAll(RELEASED_VERSIONS); - allVersions.addAll(UNRELEASED_VERSIONS); - Collections.sort(allVersions); - ALL_VERSIONS = Collections.unmodifiableList(allVersions); - } - - /** - * Returns an immutable, sorted list containing all released versions. - */ - public static List allReleasedVersions() { - return RELEASED_VERSIONS; - } - - /** - * Returns an immutable, sorted list containing all unreleased versions. - */ - public static List allUnreleasedVersions() { - return UNRELEASED_VERSIONS; - } + private static final List ALL_VERSIONS = Version.getDeclaredVersions(Version.class); /** * Returns an immutable, sorted list containing all versions, both released and unreleased. @@ -147,16 +30,16 @@ public static List allVersions() { } /** - * Get the released version before {@code version}. + * Get the version before {@code version}. */ public static Version getPreviousVersion(Version version) { - for (int i = RELEASED_VERSIONS.size() - 1; i >= 0; i--) { - Version v = RELEASED_VERSIONS.get(i); + for (int i = ALL_VERSIONS.size() - 1; i >= 0; i--) { + Version v = ALL_VERSIONS.get(i); if (v.before(version)) { return v; } } - throw new IllegalArgumentException("couldn't find any released versions before [" + version + "]"); + throw new IllegalArgumentException("couldn't find any versions before [" + version + "]"); } /** @@ -169,22 +52,22 @@ public static Version getPreviousVersion() { } /** - * Returns the released {@link Version} before the {@link Version#CURRENT} + * Returns the {@link Version} before the {@link Version#CURRENT} * where the minor version is less than the currents minor version. */ public static Version getPreviousMinorVersion() { - for (int i = RELEASED_VERSIONS.size() - 1; i >= 0; i--) { - Version v = RELEASED_VERSIONS.get(i); + for (int i = ALL_VERSIONS.size() - 1; i >= 0; i--) { + Version v = ALL_VERSIONS.get(i); if (v.minor < Version.CURRENT.minor || v.major < Version.CURRENT.major) { return v; } } - throw new IllegalArgumentException("couldn't find any released versions of the minor before [" + Build.current().version() + "]"); + throw new IllegalArgumentException("couldn't find any versions of the minor before [" + Build.current().version() + "]"); } - /** Returns the oldest released {@link Version} */ + /** Returns the oldest {@link Version} */ public static Version getFirstVersion() { - return RELEASED_VERSIONS.get(0); + return ALL_VERSIONS.get(0); } /** Returns a random {@link Version} from all available versions. */ diff --git a/test/framework/src/test/java/org/elasticsearch/test/VersionUtilsTests.java b/test/framework/src/test/java/org/elasticsearch/test/VersionUtilsTests.java index e0013e06f3248..5ae7e5640fc91 100644 --- a/test/framework/src/test/java/org/elasticsearch/test/VersionUtilsTests.java +++ b/test/framework/src/test/java/org/elasticsearch/test/VersionUtilsTests.java @@ -9,19 +9,11 @@ package org.elasticsearch.test; import org.elasticsearch.Version; -import org.elasticsearch.core.Booleans; -import org.elasticsearch.core.Tuple; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashSet; import java.util.List; -import java.util.Set; import static org.elasticsearch.Version.fromId; -import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.greaterThanOrEqualTo; -import static org.hamcrest.Matchers.lessThanOrEqualTo; /** * Tests VersionUtils. Note: this test should remain unchanged across major versions @@ -30,7 +22,7 @@ public class VersionUtilsTests extends ESTestCase { public void testAllVersionsSorted() { - List allVersions = VersionUtils.allReleasedVersions(); + List allVersions = VersionUtils.allVersions(); for (int i = 0, j = 1; j < allVersions.size(); ++i, ++j) { assertTrue(allVersions.get(i).before(allVersions.get(j))); } @@ -58,9 +50,9 @@ public void testRandomVersionBetween() { got = VersionUtils.randomVersionBetween(random(), null, fromId(7000099)); assertTrue(got.onOrAfter(VersionUtils.getFirstVersion())); assertTrue(got.onOrBefore(fromId(7000099))); - got = VersionUtils.randomVersionBetween(random(), null, VersionUtils.allReleasedVersions().get(0)); + got = VersionUtils.randomVersionBetween(random(), null, VersionUtils.allVersions().get(0)); assertTrue(got.onOrAfter(VersionUtils.getFirstVersion())); - assertTrue(got.onOrBefore(VersionUtils.allReleasedVersions().get(0))); + assertTrue(got.onOrBefore(VersionUtils.allVersions().get(0))); // unbounded upper got = VersionUtils.randomVersionBetween(random(), VersionUtils.getFirstVersion(), null); @@ -83,265 +75,34 @@ public void testRandomVersionBetween() { assertEquals(got, VersionUtils.getFirstVersion()); got = VersionUtils.randomVersionBetween(random(), Version.CURRENT, null); assertEquals(got, Version.CURRENT); - - if (Booleans.parseBoolean(System.getProperty("build.snapshot", "true"))) { - // max or min can be an unreleased version - final Version unreleased = randomFrom(VersionUtils.allUnreleasedVersions()); - assertThat(VersionUtils.randomVersionBetween(random(), null, unreleased), lessThanOrEqualTo(unreleased)); - assertThat(VersionUtils.randomVersionBetween(random(), unreleased, null), greaterThanOrEqualTo(unreleased)); - assertEquals(unreleased, VersionUtils.randomVersionBetween(random(), unreleased, unreleased)); - } - } - - public static class TestReleaseBranch { - public static final Version V_4_0_0 = Version.fromString("4.0.0"); - public static final Version V_4_0_1 = Version.fromString("4.0.1"); - public static final Version V_5_3_0 = Version.fromString("5.3.0"); - public static final Version V_5_3_1 = Version.fromString("5.3.1"); - public static final Version V_5_3_2 = Version.fromString("5.3.2"); - public static final Version V_5_4_0 = Version.fromString("5.4.0"); - public static final Version V_5_4_1 = Version.fromString("5.4.1"); - public static final Version CURRENT = V_5_4_1; - } - - public void testResolveReleasedVersionsForReleaseBranch() { - Tuple, List> t = VersionUtils.resolveReleasedVersions(TestReleaseBranch.CURRENT, TestReleaseBranch.class); - List released = t.v1(); - List unreleased = t.v2(); - - assertThat( - released, - equalTo( - Arrays.asList( - TestReleaseBranch.V_4_0_0, - TestReleaseBranch.V_5_3_0, - TestReleaseBranch.V_5_3_1, - TestReleaseBranch.V_5_3_2, - TestReleaseBranch.V_5_4_0 - ) - ) - ); - assertThat(unreleased, equalTo(Arrays.asList(TestReleaseBranch.V_4_0_1, TestReleaseBranch.V_5_4_1))); - } - - public static class TestStableBranch { - public static final Version V_4_0_0 = Version.fromString("4.0.0"); - public static final Version V_4_0_1 = Version.fromString("4.0.1"); - public static final Version V_5_0_0 = Version.fromString("5.0.0"); - public static final Version V_5_0_1 = Version.fromString("5.0.1"); - public static final Version V_5_0_2 = Version.fromString("5.0.2"); - public static final Version V_5_1_0 = Version.fromString("5.1.0"); - public static final Version CURRENT = V_5_1_0; - } - - public void testResolveReleasedVersionsForUnreleasedStableBranch() { - Tuple, List> t = VersionUtils.resolveReleasedVersions(TestStableBranch.CURRENT, TestStableBranch.class); - List released = t.v1(); - List unreleased = t.v2(); - - assertThat(released, equalTo(Arrays.asList(TestStableBranch.V_4_0_0, TestStableBranch.V_5_0_0, TestStableBranch.V_5_0_1))); - assertThat(unreleased, equalTo(Arrays.asList(TestStableBranch.V_4_0_1, TestStableBranch.V_5_0_2, TestStableBranch.V_5_1_0))); - } - - public static class TestStableBranchBehindStableBranch { - public static final Version V_4_0_0 = Version.fromString("4.0.0"); - public static final Version V_4_0_1 = Version.fromString("4.0.1"); - public static final Version V_5_3_0 = Version.fromString("5.3.0"); - public static final Version V_5_3_1 = Version.fromString("5.3.1"); - public static final Version V_5_3_2 = Version.fromString("5.3.2"); - public static final Version V_5_4_0 = Version.fromString("5.4.0"); - public static final Version V_5_5_0 = Version.fromString("5.5.0"); - public static final Version CURRENT = V_5_5_0; - } - - public void testResolveReleasedVersionsForStableBranchBehindStableBranch() { - Tuple, List> t = VersionUtils.resolveReleasedVersions( - TestStableBranchBehindStableBranch.CURRENT, - TestStableBranchBehindStableBranch.class - ); - List released = t.v1(); - List unreleased = t.v2(); - - assertThat( - released, - equalTo( - Arrays.asList( - TestStableBranchBehindStableBranch.V_4_0_0, - TestStableBranchBehindStableBranch.V_5_3_0, - TestStableBranchBehindStableBranch.V_5_3_1 - ) - ) - ); - assertThat( - unreleased, - equalTo( - Arrays.asList( - TestStableBranchBehindStableBranch.V_4_0_1, - TestStableBranchBehindStableBranch.V_5_3_2, - TestStableBranchBehindStableBranch.V_5_4_0, - TestStableBranchBehindStableBranch.V_5_5_0 - ) - ) - ); - } - - public static class TestUnstableBranch { - public static final Version V_5_3_0 = Version.fromString("5.3.0"); - public static final Version V_5_3_1 = Version.fromString("5.3.1"); - public static final Version V_5_3_2 = Version.fromString("5.3.2"); - public static final Version V_5_4_0 = Version.fromString("5.4.0"); - public static final Version V_6_0_0 = Version.fromString("6.0.0"); - public static final Version CURRENT = V_6_0_0; - } - - public void testResolveReleasedVersionsForUnstableBranch() { - Tuple, List> t = VersionUtils.resolveReleasedVersions(TestUnstableBranch.CURRENT, TestUnstableBranch.class); - List released = t.v1(); - List unreleased = t.v2(); - - assertThat(released, equalTo(Arrays.asList(TestUnstableBranch.V_5_3_0, TestUnstableBranch.V_5_3_1))); - assertThat(unreleased, equalTo(Arrays.asList(TestUnstableBranch.V_5_3_2, TestUnstableBranch.V_5_4_0, TestUnstableBranch.V_6_0_0))); - } - - public static class TestNewMajorRelease { - public static final Version V_5_6_0 = Version.fromString("5.6.0"); - public static final Version V_5_6_1 = Version.fromString("5.6.1"); - public static final Version V_5_6_2 = Version.fromString("5.6.2"); - public static final Version V_6_0_0 = Version.fromString("6.0.0"); - public static final Version V_6_0_1 = Version.fromString("6.0.1"); - public static final Version CURRENT = V_6_0_1; - } - - public void testResolveReleasedVersionsAtNewMajorRelease() { - Tuple, List> t = VersionUtils.resolveReleasedVersions( - TestNewMajorRelease.CURRENT, - TestNewMajorRelease.class - ); - List released = t.v1(); - List unreleased = t.v2(); - - assertThat(released, equalTo(Arrays.asList(TestNewMajorRelease.V_5_6_0, TestNewMajorRelease.V_5_6_1, TestNewMajorRelease.V_6_0_0))); - assertThat(unreleased, equalTo(Arrays.asList(TestNewMajorRelease.V_5_6_2, TestNewMajorRelease.V_6_0_1))); - } - - public static class TestVersionBumpIn6x { - public static final Version V_5_6_0 = Version.fromString("5.6.0"); - public static final Version V_5_6_1 = Version.fromString("5.6.1"); - public static final Version V_5_6_2 = Version.fromString("5.6.2"); - public static final Version V_6_0_0 = Version.fromString("6.0.0"); - public static final Version V_6_0_1 = Version.fromString("6.0.1"); - public static final Version V_6_1_0 = Version.fromString("6.1.0"); - public static final Version CURRENT = V_6_1_0; - } - - public void testResolveReleasedVersionsAtVersionBumpIn6x() { - Tuple, List> t = VersionUtils.resolveReleasedVersions( - TestVersionBumpIn6x.CURRENT, - TestVersionBumpIn6x.class - ); - List released = t.v1(); - List unreleased = t.v2(); - - assertThat(released, equalTo(Arrays.asList(TestVersionBumpIn6x.V_5_6_0, TestVersionBumpIn6x.V_5_6_1, TestVersionBumpIn6x.V_6_0_0))); - assertThat( - unreleased, - equalTo(Arrays.asList(TestVersionBumpIn6x.V_5_6_2, TestVersionBumpIn6x.V_6_0_1, TestVersionBumpIn6x.V_6_1_0)) - ); - } - - public static class TestNewMinorBranchIn6x { - public static final Version V_5_6_0 = Version.fromString("5.6.0"); - public static final Version V_5_6_1 = Version.fromString("5.6.1"); - public static final Version V_5_6_2 = Version.fromString("5.6.2"); - public static final Version V_6_0_0 = Version.fromString("6.0.0"); - public static final Version V_6_0_1 = Version.fromString("6.0.1"); - public static final Version V_6_1_0 = Version.fromString("6.1.0"); - public static final Version V_6_1_1 = Version.fromString("6.1.1"); - public static final Version V_6_1_2 = Version.fromString("6.1.2"); - public static final Version V_6_2_0 = Version.fromString("6.2.0"); - public static final Version CURRENT = V_6_2_0; - } - - public void testResolveReleasedVersionsAtNewMinorBranchIn6x() { - Tuple, List> t = VersionUtils.resolveReleasedVersions( - TestNewMinorBranchIn6x.CURRENT, - TestNewMinorBranchIn6x.class - ); - List released = t.v1(); - List unreleased = t.v2(); - - assertThat( - released, - equalTo( - Arrays.asList( - TestNewMinorBranchIn6x.V_5_6_0, - TestNewMinorBranchIn6x.V_5_6_1, - TestNewMinorBranchIn6x.V_6_0_0, - TestNewMinorBranchIn6x.V_6_0_1, - TestNewMinorBranchIn6x.V_6_1_0, - TestNewMinorBranchIn6x.V_6_1_1 - ) - ) - ); - assertThat( - unreleased, - equalTo(Arrays.asList(TestNewMinorBranchIn6x.V_5_6_2, TestNewMinorBranchIn6x.V_6_1_2, TestNewMinorBranchIn6x.V_6_2_0)) - ); } /** - * Tests that {@link Version#minimumCompatibilityVersion()} and {@link VersionUtils#allReleasedVersions()} + * Tests that {@link Version#minimumCompatibilityVersion()} and {@link VersionUtils#allVersions()} * agree with the list of wire compatible versions we build in gradle. */ public void testGradleVersionsMatchVersionUtils() { // First check the index compatible versions - List released = VersionUtils.allReleasedVersions() + List versions = VersionUtils.allVersions() .stream() /* Java lists all versions from the 5.x series onwards, but we only want to consider * ones that we're supposed to be compatible with. */ .filter(v -> v.onOrAfter(Version.CURRENT.minimumCompatibilityVersion())) + .map(Version::toString) .toList(); - VersionsFromProperty wireCompatible = new VersionsFromProperty("tests.gradle_wire_compat_versions"); - - Version minimumCompatibleVersion = Version.CURRENT.minimumCompatibilityVersion(); - List releasedWireCompatible = released.stream() - .filter(v -> Version.CURRENT.equals(v) == false) - .filter(v -> v.onOrAfter(minimumCompatibleVersion)) - .map(Object::toString) - .toList(); - assertEquals(releasedWireCompatible, wireCompatible.released); - - List unreleasedWireCompatible = VersionUtils.allUnreleasedVersions() - .stream() - .filter(v -> v.onOrAfter(minimumCompatibleVersion)) - .map(Object::toString) - .toList(); - assertEquals(unreleasedWireCompatible, wireCompatible.unreleased); + List gradleVersions = versionFromProperty("tests.gradle_wire_compat_versions"); + assertEquals(versions, gradleVersions); } - /** - * Read a versions system property as set by gradle into a tuple of {@code (releasedVersion, unreleasedVersion)}. - */ - private class VersionsFromProperty { - private final List released = new ArrayList<>(); - private final List unreleased = new ArrayList<>(); - - private VersionsFromProperty(String property) { - Set allUnreleased = new HashSet<>(Arrays.asList(System.getProperty("tests.gradle_unreleased_versions", "").split(","))); - if (allUnreleased.isEmpty()) { - fail("[tests.gradle_unreleased_versions] not set or empty. Gradle should set this before running."); - } - String versions = System.getProperty(property); - assertNotNull("Couldn't find [" + property + "]. Gradle should set this before running the tests.", versions); - logger.info("Looked up versions [{}={}]", property, versions); - - for (String version : versions.split(",")) { - if (allUnreleased.contains(version)) { - unreleased.add(version); - } else { - released.add(version); - } - } + private List versionFromProperty(String property) { + List versions = new ArrayList<>(); + String versionsString = System.getProperty(property); + assertNotNull("Couldn't find [" + property + "]. Gradle should set this before running the tests.", versionsString); + logger.info("Looked up versions [{}={}]", property, versionsString); + for (String version : versionsString.split(",")) { + versions.add(version); } + + return versions; } } From 0e0d6244d47a430f1b7f521cd00ae24d71f875c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20Zolt=C3=A1n=20Szab=C3=B3?= Date: Fri, 6 Dec 2024 09:41:02 +0100 Subject: [PATCH 15/37] [DOCS] Adds examples to inference processor docs (#116018) (#118130) --- .../ingest/processors/inference.asciidoc | 67 +++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/docs/reference/ingest/processors/inference.asciidoc b/docs/reference/ingest/processors/inference.asciidoc index 9c6f0592a1d91..e079b9d665290 100644 --- a/docs/reference/ingest/processors/inference.asciidoc +++ b/docs/reference/ingest/processors/inference.asciidoc @@ -735,3 +735,70 @@ You can also specify the target field as follows: In this case, {feat-imp} is exposed in the `my_field.foo.feature_importance` field. + + +[discrete] +[[inference-processor-examples]] +==== {infer-cap} processor examples + +The following example uses an <> in an {infer} processor named `query_helper_pipeline` to perform a chat completion task. +The processor generates an {es} query from natural language input using a prompt designed for a completion task type. +Refer to <> for the {infer} service you use and check the corresponding examples of setting up an endpoint with the chat completion task type. + + +[source,console] +-------------------------------------------------- +PUT _ingest/pipeline/query_helper_pipeline +{ + "processors": [ + { + "script": { + "source": "ctx.prompt = 'Please generate an elasticsearch search query on index `articles_index` for the following natural language query. Dates are in the field `@timestamp`, document types are in the field `type` (options are `news`, `publication`), categories in the field `category` and can be multiple (options are `medicine`, `pharmaceuticals`, `technology`), and document names are in the field `title` which should use a fuzzy match. Ignore fields which cannot be determined from the natural language query context: ' + ctx.content" <1> + } + }, + { + "inference": { + "model_id": "openai_chat_completions", <2> + "input_output": { + "input_field": "prompt", + "output_field": "query" + } + } + }, + { + "remove": { + "field": "prompt" + } + } + ] +} +-------------------------------------------------- +// TEST[skip: An inference endpoint is required.] +<1> The `prompt` field contains the prompt used for the completion task, created with <>. +`+ ctx.content` appends the natural language input to the prompt. +<2> The ID of the pre-configured {infer} endpoint, which utilizes the <> with the `completion` task type. + +The following API request will simulate running a document through the ingest pipeline created previously: + +[source,console] +-------------------------------------------------- +POST _ingest/pipeline/query_helper_pipeline/_simulate +{ + "docs": [ + { + "_source": { + "content": "artificial intelligence in medicine articles published in the last 12 months" <1> + } + } + ] +} +-------------------------------------------------- +// TEST[skip: An inference processor with an inference endpoint is required.] +<1> The natural language query used to generate an {es} query within the prompt created by the {infer} processor. + + +[discrete] +[[infer-proc-readings]] +==== Further readings + +* https://www.elastic.co/search-labs/blog/openwebcrawler-llms-semantic-text-resume-job-search[Which job is the best for you? Using LLMs and semantic_text to match resumes to jobs] \ No newline at end of file From e52a6f20104e462b558388b2fad2fa3a4987d5e7 Mon Sep 17 00:00:00 2001 From: Panagiotis Bailis Date: Fri, 6 Dec 2024 12:50:41 +0200 Subject: [PATCH 16/37] [8.16] Fix for propagating filters from compound to inner retrievers (#117914) (#118047) * Fix for propagating filters from compound to inner retrievers * fix for lucene 9 * Update CompoundRetrieverBuilder.java * Update CompoundRetrieverBuilder.java * Update CompoundRetrieverBuilder.java --------- Co-authored-by: Elastic Machine --- docs/changelog/117914.yaml | 5 ++ .../retriever/CompoundRetrieverBuilder.java | 32 +++++--- .../search/retriever/KnnRetrieverBuilder.java | 3 +- .../retriever/RankDocsRetrieverBuilder.java | 16 +--- .../RankDocsRetrieverBuilderTests.java | 7 +- .../vectors/TestQueryVectorBuilderPlugin.java | 8 +- .../TestCompoundRetrieverBuilder.java | 10 ++- .../TextSimilarityRankRetrieverBuilder.java | 7 +- .../xpack/rank/rrf/RRFRetrieverBuilderIT.java | 38 +++++++++- .../xpack/rank/rrf/RRFFeatures.java | 6 ++ .../xpack/rank/rrf/RRFRetrieverBuilder.java | 7 +- ...rrf_retriever_search_api_compatibility.yml | 74 +++++++++++++++++++ 12 files changed, 168 insertions(+), 45 deletions(-) create mode 100644 docs/changelog/117914.yaml diff --git a/docs/changelog/117914.yaml b/docs/changelog/117914.yaml new file mode 100644 index 0000000000000..da58ed7bb04b7 --- /dev/null +++ b/docs/changelog/117914.yaml @@ -0,0 +1,5 @@ +pr: 117914 +summary: Fix for propagating filters from compound to inner retrievers +area: Ranking +type: bug +issues: [] diff --git a/server/src/main/java/org/elasticsearch/search/retriever/CompoundRetrieverBuilder.java b/server/src/main/java/org/elasticsearch/search/retriever/CompoundRetrieverBuilder.java index c461c510cfe85..4233df8f9a4f2 100644 --- a/server/src/main/java/org/elasticsearch/search/retriever/CompoundRetrieverBuilder.java +++ b/server/src/main/java/org/elasticsearch/search/retriever/CompoundRetrieverBuilder.java @@ -20,6 +20,7 @@ import org.elasticsearch.action.search.SearchRequest; import org.elasticsearch.action.search.SearchResponse; import org.elasticsearch.action.search.TransportMultiSearchAction; +import org.elasticsearch.features.NodeFeature; import org.elasticsearch.index.query.BoolQueryBuilder; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryRewriteContext; @@ -47,6 +48,8 @@ */ public abstract class CompoundRetrieverBuilder> extends RetrieverBuilder { + public static final NodeFeature INNER_RETRIEVERS_FILTER_SUPPORT = new NodeFeature("inner_retrievers_filter_support"); + public record RetrieverSource(RetrieverBuilder retriever, SearchSourceBuilder source) {} protected final int rankWindowSize; @@ -65,9 +68,9 @@ public T addChild(RetrieverBuilder retrieverBuilder) { /** * Returns a clone of the original retriever, replacing the sub-retrievers with - * the provided {@code newChildRetrievers}. + * the provided {@code newChildRetrievers} and the filters with the {@code newPreFilterQueryBuilders}. */ - protected abstract T clone(List newChildRetrievers); + protected abstract T clone(List newChildRetrievers, List newPreFilterQueryBuilders); /** * Combines the provided {@code rankResults} to return the final top documents. @@ -86,13 +89,25 @@ public final RetrieverBuilder rewrite(QueryRewriteContext ctx) throws IOExceptio } // Rewrite prefilters - boolean hasChanged = false; + // We eagerly rewrite prefilters, because some of the innerRetrievers + // could be compound too, so we want to propagate all the necessary filter information to them + // and have it available as part of their own rewrite step var newPreFilters = rewritePreFilters(ctx); - hasChanged |= newPreFilters != preFilterQueryBuilders; + if (newPreFilters != preFilterQueryBuilders) { + return clone(innerRetrievers, newPreFilters); + } + boolean hasChanged = false; // Rewrite retriever sources List newRetrievers = new ArrayList<>(); for (var entry : innerRetrievers) { + // we propagate the filters only for compound retrievers as they won't be attached through + // the createSearchSourceBuilder. + // We could remove this check, but we would end up adding the same filters + // multiple times in case an inner retriever rewrites itself, when we re-enter to rewrite + if (entry.retriever.isCompound() && false == preFilterQueryBuilders.isEmpty()) { + entry.retriever.getPreFilterQueryBuilders().addAll(preFilterQueryBuilders); + } RetrieverBuilder newRetriever = entry.retriever.rewrite(ctx); if (newRetriever != entry.retriever) { newRetrievers.add(new RetrieverSource(newRetriever, null)); @@ -107,7 +122,7 @@ public final RetrieverBuilder rewrite(QueryRewriteContext ctx) throws IOExceptio } } if (hasChanged) { - return clone(newRetrievers); + return clone(newRetrievers, newPreFilters); } // execute searches @@ -167,12 +182,7 @@ public void onFailure(Exception e) { }); }); - return new RankDocsRetrieverBuilder( - rankWindowSize, - newRetrievers.stream().map(s -> s.retriever).toList(), - results::get, - newPreFilters - ); + return new RankDocsRetrieverBuilder(rankWindowSize, newRetrievers.stream().map(s -> s.retriever).toList(), results::get); } @Override diff --git a/server/src/main/java/org/elasticsearch/search/retriever/KnnRetrieverBuilder.java b/server/src/main/java/org/elasticsearch/search/retriever/KnnRetrieverBuilder.java index 8be9a78dae154..f1464c41ca3be 100644 --- a/server/src/main/java/org/elasticsearch/search/retriever/KnnRetrieverBuilder.java +++ b/server/src/main/java/org/elasticsearch/search/retriever/KnnRetrieverBuilder.java @@ -184,8 +184,7 @@ public RetrieverBuilder rewrite(QueryRewriteContext ctx) throws IOException { ll.onResponse(null); })); }); - var rewritten = new KnnRetrieverBuilder(this, () -> toSet.get(), null); - return rewritten; + return new KnnRetrieverBuilder(this, () -> toSet.get(), null); } return super.rewrite(ctx); } diff --git a/server/src/main/java/org/elasticsearch/search/retriever/RankDocsRetrieverBuilder.java b/server/src/main/java/org/elasticsearch/search/retriever/RankDocsRetrieverBuilder.java index 02f890f51d011..4d3f3fefd4462 100644 --- a/server/src/main/java/org/elasticsearch/search/retriever/RankDocsRetrieverBuilder.java +++ b/server/src/main/java/org/elasticsearch/search/retriever/RankDocsRetrieverBuilder.java @@ -33,19 +33,13 @@ public class RankDocsRetrieverBuilder extends RetrieverBuilder { final List sources; final Supplier rankDocs; - public RankDocsRetrieverBuilder( - int rankWindowSize, - List sources, - Supplier rankDocs, - List preFilterQueryBuilders - ) { + public RankDocsRetrieverBuilder(int rankWindowSize, List sources, Supplier rankDocs) { this.rankWindowSize = rankWindowSize; this.rankDocs = rankDocs; if (sources == null || sources.isEmpty()) { throw new IllegalArgumentException("sources must not be null or empty"); } this.sources = sources; - this.preFilterQueryBuilders = preFilterQueryBuilders; } @Override @@ -73,10 +67,6 @@ private boolean sourceShouldRewrite(QueryRewriteContext ctx) throws IOException @Override public RetrieverBuilder rewrite(QueryRewriteContext ctx) throws IOException { assert false == sourceShouldRewrite(ctx) : "retriever sources should be rewritten first"; - var rewrittenFilters = rewritePreFilters(ctx); - if (rewrittenFilters != preFilterQueryBuilders) { - return new RankDocsRetrieverBuilder(rankWindowSize, sources, rankDocs, rewrittenFilters); - } return this; } @@ -94,7 +84,7 @@ public QueryBuilder topDocsQuery() { boolQuery.should(query); } } - // ignore prefilters of this level, they are already propagated to children + // ignore prefilters of this level, they were already propagated to children return boolQuery; } @@ -133,7 +123,7 @@ public void extractToSearchSourceBuilder(SearchSourceBuilder searchSourceBuilder } else { rankQuery = new RankDocsQueryBuilder(rankDocResults, null, false); } - // ignore prefilters of this level, they are already propagated to children + // ignore prefilters of this level, they were already propagated to children searchSourceBuilder.query(rankQuery); if (sourceHasMinScore()) { searchSourceBuilder.minScore(this.minScore() == null ? Float.MIN_VALUE : this.minScore()); diff --git a/server/src/test/java/org/elasticsearch/search/retriever/RankDocsRetrieverBuilderTests.java b/server/src/test/java/org/elasticsearch/search/retriever/RankDocsRetrieverBuilderTests.java index af6782c45dce8..ccf33c0b71b6b 100644 --- a/server/src/test/java/org/elasticsearch/search/retriever/RankDocsRetrieverBuilderTests.java +++ b/server/src/test/java/org/elasticsearch/search/retriever/RankDocsRetrieverBuilderTests.java @@ -95,12 +95,7 @@ private List preFilters(QueryRewriteContext queryRewriteContext) t } private RankDocsRetrieverBuilder createRandomRankDocsRetrieverBuilder(QueryRewriteContext queryRewriteContext) throws IOException { - return new RankDocsRetrieverBuilder( - randomIntBetween(1, 100), - innerRetrievers(queryRewriteContext), - rankDocsSupplier(), - preFilters(queryRewriteContext) - ); + return new RankDocsRetrieverBuilder(randomIntBetween(1, 100), innerRetrievers(queryRewriteContext), rankDocsSupplier()); } public void testExtractToSearchSourceBuilder() throws IOException { diff --git a/server/src/test/java/org/elasticsearch/search/vectors/TestQueryVectorBuilderPlugin.java b/server/src/test/java/org/elasticsearch/search/vectors/TestQueryVectorBuilderPlugin.java index c47c8c16f6a2f..5733a51bb7e9c 100644 --- a/server/src/test/java/org/elasticsearch/search/vectors/TestQueryVectorBuilderPlugin.java +++ b/server/src/test/java/org/elasticsearch/search/vectors/TestQueryVectorBuilderPlugin.java @@ -27,9 +27,9 @@ /** * A SearchPlugin to exercise query vector builder */ -class TestQueryVectorBuilderPlugin implements SearchPlugin { +public class TestQueryVectorBuilderPlugin implements SearchPlugin { - static class TestQueryVectorBuilder implements QueryVectorBuilder { + public static class TestQueryVectorBuilder implements QueryVectorBuilder { private static final String NAME = "test_query_vector_builder"; private static final ParseField QUERY_VECTOR = new ParseField("query_vector"); @@ -47,11 +47,11 @@ static class TestQueryVectorBuilder implements QueryVectorBuilder { private List vectorToBuild; - TestQueryVectorBuilder(List vectorToBuild) { + public TestQueryVectorBuilder(List vectorToBuild) { this.vectorToBuild = vectorToBuild; } - TestQueryVectorBuilder(float[] expected) { + public TestQueryVectorBuilder(float[] expected) { this.vectorToBuild = new ArrayList<>(expected.length); for (float f : expected) { vectorToBuild.add(f); diff --git a/test/framework/src/main/java/org/elasticsearch/search/retriever/TestCompoundRetrieverBuilder.java b/test/framework/src/main/java/org/elasticsearch/search/retriever/TestCompoundRetrieverBuilder.java index 9f199aa7f3ef8..4a5f280c10a99 100644 --- a/test/framework/src/main/java/org/elasticsearch/search/retriever/TestCompoundRetrieverBuilder.java +++ b/test/framework/src/main/java/org/elasticsearch/search/retriever/TestCompoundRetrieverBuilder.java @@ -10,6 +10,7 @@ package org.elasticsearch.search.retriever; import org.apache.lucene.search.ScoreDoc; +import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.search.rank.RankDoc; import org.elasticsearch.xcontent.XContentBuilder; @@ -23,16 +24,17 @@ public class TestCompoundRetrieverBuilder extends CompoundRetrieverBuilder(), rankWindowSize); + this(new ArrayList<>(), rankWindowSize, new ArrayList<>()); } - TestCompoundRetrieverBuilder(List childRetrievers, int rankWindowSize) { + TestCompoundRetrieverBuilder(List childRetrievers, int rankWindowSize, List preFilterQueryBuilders) { super(childRetrievers, rankWindowSize); + this.preFilterQueryBuilders = preFilterQueryBuilders; } @Override - protected TestCompoundRetrieverBuilder clone(List newChildRetrievers) { - return new TestCompoundRetrieverBuilder(newChildRetrievers, rankWindowSize); + protected TestCompoundRetrieverBuilder clone(List newChildRetrievers, List newPreFilterQueryBuilders) { + return new TestCompoundRetrieverBuilder(newChildRetrievers, rankWindowSize, newPreFilterQueryBuilders); } @Override diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/rank/textsimilarity/TextSimilarityRankRetrieverBuilder.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/rank/textsimilarity/TextSimilarityRankRetrieverBuilder.java index feb7dd669f25a..b42afa48532fd 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/rank/textsimilarity/TextSimilarityRankRetrieverBuilder.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/rank/textsimilarity/TextSimilarityRankRetrieverBuilder.java @@ -130,7 +130,10 @@ public TextSimilarityRankRetrieverBuilder( } @Override - protected TextSimilarityRankRetrieverBuilder clone(List newChildRetrievers) { + protected TextSimilarityRankRetrieverBuilder clone( + List newChildRetrievers, + List newPreFilterQueryBuilders + ) { return new TextSimilarityRankRetrieverBuilder( newChildRetrievers, inferenceId, @@ -139,7 +142,7 @@ protected TextSimilarityRankRetrieverBuilder clone(List newChil rankWindowSize, minScore, retrieverName, - preFilterQueryBuilders + newPreFilterQueryBuilders ); } diff --git a/x-pack/plugin/rank-rrf/src/internalClusterTest/java/org/elasticsearch/xpack/rank/rrf/RRFRetrieverBuilderIT.java b/x-pack/plugin/rank-rrf/src/internalClusterTest/java/org/elasticsearch/xpack/rank/rrf/RRFRetrieverBuilderIT.java index ab4b2b34e5b9c..e4e06b5031005 100644 --- a/x-pack/plugin/rank-rrf/src/internalClusterTest/java/org/elasticsearch/xpack/rank/rrf/RRFRetrieverBuilderIT.java +++ b/x-pack/plugin/rank-rrf/src/internalClusterTest/java/org/elasticsearch/xpack/rank/rrf/RRFRetrieverBuilderIT.java @@ -33,6 +33,7 @@ import org.elasticsearch.search.sort.SortOrder; import org.elasticsearch.search.vectors.KnnVectorQueryBuilder; import org.elasticsearch.search.vectors.QueryVectorBuilder; +import org.elasticsearch.search.vectors.TestQueryVectorBuilderPlugin; import org.elasticsearch.test.ESIntegTestCase; import org.elasticsearch.test.hamcrest.ElasticsearchAssertions; import org.elasticsearch.xcontent.XContentBuilder; @@ -57,7 +58,6 @@ public class RRFRetrieverBuilderIT extends ESIntegTestCase { protected static String INDEX = "test_index"; - protected static final String ID_FIELD = "_id"; protected static final String DOC_FIELD = "doc"; protected static final String TEXT_FIELD = "text"; protected static final String VECTOR_FIELD = "vector"; @@ -743,6 +743,42 @@ public void extractToSearchSourceBuilder(SearchSourceBuilder searchSourceBuilder expectThrows(UnsupportedOperationException.class, () -> client().prepareSearch(INDEX).setSource(source).get()); } + public void testRRFFiltersPropagatedToKnnQueryVectorBuilder() { + final int rankWindowSize = 100; + final int rankConstant = 10; + SearchSourceBuilder source = new SearchSourceBuilder(); + // this will retriever all but 7 only due to top-level filter + StandardRetrieverBuilder standardRetriever = new StandardRetrieverBuilder(QueryBuilders.matchAllQuery()); + // this will too retrieve just doc 7 + KnnRetrieverBuilder knnRetriever = new KnnRetrieverBuilder( + "vector", + null, + new TestQueryVectorBuilderPlugin.TestQueryVectorBuilder(new float[] { 3 }), + 10, + 10, + null + ); + source.retriever( + new RRFRetrieverBuilder( + Arrays.asList( + new CompoundRetrieverBuilder.RetrieverSource(standardRetriever, null), + new CompoundRetrieverBuilder.RetrieverSource(knnRetriever, null) + ), + rankWindowSize, + rankConstant + ) + ); + source.retriever().getPreFilterQueryBuilders().add(QueryBuilders.boolQuery().must(QueryBuilders.termQuery(DOC_FIELD, "doc_7"))); + source.size(10); + SearchRequestBuilder req = client().prepareSearch(INDEX).setSource(source); + ElasticsearchAssertions.assertResponse(req, resp -> { + assertNull(resp.pointInTimeId()); + assertNotNull(resp.getHits().getTotalHits()); + assertThat(resp.getHits().getTotalHits().value, equalTo(1L)); + assertThat(resp.getHits().getHits()[0].getId(), equalTo("doc_7")); + }); + } + public void testRewriteOnce() { final float[] vector = new float[] { 1 }; AtomicInteger numAsyncCalls = new AtomicInteger(); diff --git a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/rrf/RRFFeatures.java b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/rrf/RRFFeatures.java index bbc0f622724a3..bb61fa951948d 100644 --- a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/rrf/RRFFeatures.java +++ b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/rrf/RRFFeatures.java @@ -12,6 +12,7 @@ import java.util.Set; +import static org.elasticsearch.search.retriever.CompoundRetrieverBuilder.INNER_RETRIEVERS_FILTER_SUPPORT; import static org.elasticsearch.xpack.rank.rrf.RRFRetrieverBuilder.RRF_RETRIEVER_COMPOSITION_SUPPORTED; /** @@ -23,4 +24,9 @@ public class RRFFeatures implements FeatureSpecification { public Set getFeatures() { return Set.of(RRFRetrieverBuilder.RRF_RETRIEVER_SUPPORTED, RRF_RETRIEVER_COMPOSITION_SUPPORTED); } + + @Override + public Set getTestFeatures() { + return Set.of(INNER_RETRIEVERS_FILTER_SUPPORT); + } } diff --git a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/rrf/RRFRetrieverBuilder.java b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/rrf/RRFRetrieverBuilder.java index 792ff4eac3893..f1171b74f7468 100644 --- a/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/rrf/RRFRetrieverBuilder.java +++ b/x-pack/plugin/rank-rrf/src/main/java/org/elasticsearch/xpack/rank/rrf/RRFRetrieverBuilder.java @@ -11,6 +11,7 @@ import org.elasticsearch.common.ParsingException; import org.elasticsearch.common.util.Maps; import org.elasticsearch.features.NodeFeature; +import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.license.LicenseUtils; import org.elasticsearch.search.rank.RankBuilder; import org.elasticsearch.search.rank.RankDoc; @@ -108,8 +109,10 @@ public String getName() { } @Override - protected RRFRetrieverBuilder clone(List newRetrievers) { - return new RRFRetrieverBuilder(newRetrievers, this.rankWindowSize, this.rankConstant); + protected RRFRetrieverBuilder clone(List newRetrievers, List newPreFilterQueryBuilders) { + RRFRetrieverBuilder clone = new RRFRetrieverBuilder(newRetrievers, this.rankWindowSize, this.rankConstant); + clone.preFilterQueryBuilders = newPreFilterQueryBuilders; + return clone; } @Override diff --git a/x-pack/plugin/rank-rrf/src/yamlRestTest/resources/rest-api-spec/test/rrf/700_rrf_retriever_search_api_compatibility.yml b/x-pack/plugin/rank-rrf/src/yamlRestTest/resources/rest-api-spec/test/rrf/700_rrf_retriever_search_api_compatibility.yml index 42c01f0b9636c..cb30542d80003 100644 --- a/x-pack/plugin/rank-rrf/src/yamlRestTest/resources/rest-api-spec/test/rrf/700_rrf_retriever_search_api_compatibility.yml +++ b/x-pack/plugin/rank-rrf/src/yamlRestTest/resources/rest-api-spec/test/rrf/700_rrf_retriever_search_api_compatibility.yml @@ -1071,3 +1071,77 @@ setup: - match: { hits.hits.2.inner_hits.nested_data_field.hits.total.value: 0 } - match: { hits.hits.2.inner_hits.nested_vector_field.hits.total.value: 0 } + + +--- +"rrf retriever with filters to be passed to nested rrf retrievers": + - requires: + cluster_features: 'inner_retrievers_filter_support' + reason: 'requires fix for properly propagating filters to nested sub-retrievers' + + - do: + search: + _source: false + index: test + body: + retriever: + { + rrf: + { + filter: { + term: { + keyword: "technology" + } + }, + retrievers: [ + { + rrf: { + retrievers: [ + { + # this should only return docs 3 and 5 due to top level filter + standard: { + query: { + knn: { + field: vector, + query_vector: [ 4.0 ], + k: 3 + } + } + } }, + { + # this should return no docs as no docs match both biology and technology + standard: { + query: { + term: { + keyword: "biology" + } + } + } + } + ], + rank_window_size: 10, + rank_constant: 10 + } + }, + # this should only return doc 5 + { + standard: { + query: { + term: { + text: "term5" + } + } + } + } + ], + rank_window_size: 10, + rank_constant: 10 + } + } + size: 10 + + - match: { hits.total.value: 2 } + - match: { hits.hits.0._id: "5" } + - match: { hits.hits.1._id: "3" } + + From 093673a10e30a90db480b494159337b3c1963b0a Mon Sep 17 00:00:00 2001 From: Matteo Piergiovanni <134913285+piergm@users.noreply.github.com> Date: Fri, 6 Dec 2024 12:12:11 +0100 Subject: [PATCH 17/37] fixes and unmutes testSearchableSnapshotShardsAreSkipped... (#118133) (#118140) (cherry picked from commit 0a2c9fbc2925adac6057b86c4ea9d6174121c6ef) # Conflicts: # muted-tests.yml --- muted-tests.yml | 3 --- .../SearchableSnapshotsCanMatchOnCoordinatorIntegTests.java | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/muted-tests.yml b/muted-tests.yml index d558bd553ef0c..2cd836fae438d 100644 --- a/muted-tests.yml +++ b/muted-tests.yml @@ -274,9 +274,6 @@ tests: - class: org.elasticsearch.xpack.esql.qa.multi_node.EsqlSpecIT method: test {categorize.Categorize SYNC} issue: https://github.com/elastic/elasticsearch/issues/113054 -- class: org.elasticsearch.xpack.searchablesnapshots.SearchableSnapshotsCanMatchOnCoordinatorIntegTests - method: testSearchableSnapshotShardsAreSkippedBySearchRequestWithoutQueryingAnyNodeWhenTheyAreOutsideOfTheQueryRange - issue: https://github.com/elastic/elasticsearch/issues/116523 - class: org.elasticsearch.ingest.geoip.EnterpriseGeoIpDownloaderIT method: testEnterpriseDownloaderTask issue: https://github.com/elastic/elasticsearch/issues/115163 diff --git a/x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsCanMatchOnCoordinatorIntegTests.java b/x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsCanMatchOnCoordinatorIntegTests.java index 194a9379b4b63..8877f31ce9e39 100644 --- a/x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsCanMatchOnCoordinatorIntegTests.java +++ b/x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsCanMatchOnCoordinatorIntegTests.java @@ -378,7 +378,7 @@ public void testSearchableSnapshotShardsAreSkippedBySearchRequestWithoutQuerying } if (searchShardsResponse != null) { for (SearchShardsGroup group : searchShardsResponse.getGroups()) { - assertFalse("no shard should be marked as skipped", group.skipped()); + assertTrue("the shard is skipped because index value is outside the query time range", group.skipped()); } } } From afd11667540bd7b13fc7c8f0a8bd9d375c4fa0a6 Mon Sep 17 00:00:00 2001 From: Rene Groeschke Date: Fri, 6 Dec 2024 16:45:53 +0100 Subject: [PATCH 18/37] [Build] Declare mirror for eclipse p2 repository (#117732) (#117733) The spotlight plugin directly resolves dependencies from p2 which causes `java.io.IOException: Failed to load eclipse jdt formatter` issues if that repo is not accessible. This is a workaround for the eclipse p2 default repository being down resulting in all our ci jobs to fail. The artifacts in question we wanna cache live in `~/.m2/repository` --- .../conventions/precommit/FormattingPrecommitPlugin.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build-conventions/src/main/java/org/elasticsearch/gradle/internal/conventions/precommit/FormattingPrecommitPlugin.java b/build-conventions/src/main/java/org/elasticsearch/gradle/internal/conventions/precommit/FormattingPrecommitPlugin.java index ea9009172c7e2..41c0b4d67e1df 100644 --- a/build-conventions/src/main/java/org/elasticsearch/gradle/internal/conventions/precommit/FormattingPrecommitPlugin.java +++ b/build-conventions/src/main/java/org/elasticsearch/gradle/internal/conventions/precommit/FormattingPrecommitPlugin.java @@ -17,6 +17,8 @@ import org.gradle.api.Project; import java.io.File; +import java.util.Arrays; +import java.util.Map; /** * This plugin configures formatting for Java source using Spotless @@ -64,7 +66,8 @@ public void apply(Project project) { java.importOrderFile(new File(elasticsearchWorkspace, importOrderPath)); // Most formatting is done through the Eclipse formatter - java.eclipse().configFile(new File(elasticsearchWorkspace, formatterConfigPath)); + java.eclipse().withP2Mirrors(Map.of("https://download.eclipse.org/", "https://mirror.umd.edu/eclipse/")) + .configFile(new File(elasticsearchWorkspace, formatterConfigPath)); // Ensure blank lines are actually empty. Since formatters are applied in // order, apply this one last, otherwise non-empty blank lines can creep From 83a8b881ff1d321ea40d0edb1cd0048300f163f8 Mon Sep 17 00:00:00 2001 From: Rene Groeschke Date: Fri, 6 Dec 2024 17:52:15 +0100 Subject: [PATCH 19/37] [Build] Replace usage of deprecated develocity system prop (#117793) (#117794) see https://buildkite.com/elastic/elasticsearch-intake/builds/13680#019374ed-096e-4965-8651-1b3fd26dd9c2/79-392 --- .buildkite/pipelines/intake.template.yml | 16 ++++++++-------- .buildkite/pipelines/intake.yml | 16 ++++++++-------- .../pipelines/lucene-snapshot/run-tests.yml | 16 ++++++++-------- 3 files changed, 24 insertions(+), 24 deletions(-) diff --git a/.buildkite/pipelines/intake.template.yml b/.buildkite/pipelines/intake.template.yml index f530f237113a9..78752ab597ed6 100644 --- a/.buildkite/pipelines/intake.template.yml +++ b/.buildkite/pipelines/intake.template.yml @@ -1,6 +1,6 @@ steps: - label: sanity-check - command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-task-input-files precommit + command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-file-fingerprints precommit timeout_in_minutes: 300 agents: provider: gcp @@ -9,7 +9,7 @@ steps: buildDirectory: /dev/shm/bk - wait - label: part1 - command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-task-input-files checkPart1 + command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-file-fingerprints checkPart1 timeout_in_minutes: 300 agents: provider: gcp @@ -17,7 +17,7 @@ steps: machineType: n1-standard-32 buildDirectory: /dev/shm/bk - label: part2 - command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-task-input-files checkPart2 + command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-file-fingerprints checkPart2 timeout_in_minutes: 300 agents: provider: gcp @@ -25,7 +25,7 @@ steps: machineType: n1-standard-32 buildDirectory: /dev/shm/bk - label: part3 - command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-task-input-files checkPart3 + command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-file-fingerprints checkPart3 timeout_in_minutes: 300 agents: provider: gcp @@ -33,7 +33,7 @@ steps: machineType: n1-standard-32 buildDirectory: /dev/shm/bk - label: part4 - command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-task-input-files checkPart4 + command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-file-fingerprints checkPart4 timeout_in_minutes: 300 agents: provider: gcp @@ -41,7 +41,7 @@ steps: machineType: n1-standard-32 buildDirectory: /dev/shm/bk - label: part5 - command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-task-input-files checkPart5 + command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-file-fingerprints checkPart5 timeout_in_minutes: 300 agents: provider: gcp @@ -51,7 +51,7 @@ steps: - group: bwc-snapshots steps: - label: "{{matrix.BWC_VERSION}} / bwc-snapshots" - command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-task-input-files v$$BWC_VERSION#bwcTest + command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-file-fingerprints v$$BWC_VERSION#bwcTest timeout_in_minutes: 300 matrix: setup: @@ -64,7 +64,7 @@ steps: env: BWC_VERSION: "{{matrix.BWC_VERSION}}" - label: rest-compat - command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-task-input-files checkRestCompat + command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-file-fingerprints checkRestCompat timeout_in_minutes: 300 agents: provider: gcp diff --git a/.buildkite/pipelines/intake.yml b/.buildkite/pipelines/intake.yml index 887df6c939d9a..12ea14eed1cd3 100644 --- a/.buildkite/pipelines/intake.yml +++ b/.buildkite/pipelines/intake.yml @@ -1,7 +1,7 @@ # This file is auto-generated. See .buildkite/pipelines/intake.template.yml steps: - label: sanity-check - command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-task-input-files precommit + command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-file-fingerprints precommit timeout_in_minutes: 300 agents: provider: gcp @@ -10,7 +10,7 @@ steps: buildDirectory: /dev/shm/bk - wait - label: part1 - command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-task-input-files checkPart1 + command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-file-fingerprints checkPart1 timeout_in_minutes: 300 agents: provider: gcp @@ -18,7 +18,7 @@ steps: machineType: n1-standard-32 buildDirectory: /dev/shm/bk - label: part2 - command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-task-input-files checkPart2 + command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-file-fingerprints checkPart2 timeout_in_minutes: 300 agents: provider: gcp @@ -26,7 +26,7 @@ steps: machineType: n1-standard-32 buildDirectory: /dev/shm/bk - label: part3 - command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-task-input-files checkPart3 + command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-file-fingerprints checkPart3 timeout_in_minutes: 300 agents: provider: gcp @@ -34,7 +34,7 @@ steps: machineType: n1-standard-32 buildDirectory: /dev/shm/bk - label: part4 - command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-task-input-files checkPart4 + command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-file-fingerprints checkPart4 timeout_in_minutes: 300 agents: provider: gcp @@ -42,7 +42,7 @@ steps: machineType: n1-standard-32 buildDirectory: /dev/shm/bk - label: part5 - command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-task-input-files checkPart5 + command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-file-fingerprints checkPart5 timeout_in_minutes: 300 agents: provider: gcp @@ -52,7 +52,7 @@ steps: - group: bwc-snapshots steps: - label: "{{matrix.BWC_VERSION}} / bwc-snapshots" - command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-task-input-files v$$BWC_VERSION#bwcTest + command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-file-fingerprints v$$BWC_VERSION#bwcTest timeout_in_minutes: 300 matrix: setup: @@ -65,7 +65,7 @@ steps: env: BWC_VERSION: "{{matrix.BWC_VERSION}}" - label: rest-compat - command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-task-input-files checkRestCompat + command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-file-fingerprints checkRestCompat timeout_in_minutes: 300 agents: provider: gcp diff --git a/.buildkite/pipelines/lucene-snapshot/run-tests.yml b/.buildkite/pipelines/lucene-snapshot/run-tests.yml index c76c54a56494e..d25fa7b2067c1 100644 --- a/.buildkite/pipelines/lucene-snapshot/run-tests.yml +++ b/.buildkite/pipelines/lucene-snapshot/run-tests.yml @@ -1,6 +1,6 @@ steps: - label: sanity-check - command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-task-input-files precommit + command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-file-fingerprints precommit timeout_in_minutes: 300 agents: provider: gcp @@ -9,7 +9,7 @@ steps: buildDirectory: /dev/shm/bk - wait: null - label: part1 - command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-task-input-files checkPart1 + command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-file-fingerprints checkPart1 timeout_in_minutes: 300 agents: provider: gcp @@ -17,7 +17,7 @@ steps: machineType: custom-32-98304 buildDirectory: /dev/shm/bk - label: part2 - command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-task-input-files checkPart2 + command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-file-fingerprints checkPart2 timeout_in_minutes: 300 agents: provider: gcp @@ -25,7 +25,7 @@ steps: machineType: custom-32-98304 buildDirectory: /dev/shm/bk - label: part3 - command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-task-input-files checkPart3 + command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-file-fingerprints checkPart3 timeout_in_minutes: 300 agents: provider: gcp @@ -33,7 +33,7 @@ steps: machineType: custom-32-98304 buildDirectory: /dev/shm/bk - label: part4 - command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-task-input-files checkPart4 + command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-file-fingerprints checkPart4 timeout_in_minutes: 300 agents: provider: gcp @@ -41,7 +41,7 @@ steps: machineType: custom-32-98304 buildDirectory: /dev/shm/bk - label: part5 - command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-task-input-files checkPart5 + command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-file-fingerprints checkPart5 timeout_in_minutes: 300 agents: provider: gcp @@ -51,7 +51,7 @@ steps: - group: bwc-snapshots steps: - label: "{{matrix.BWC_VERSION}} / bwc-snapshots" - command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-task-input-files v$$BWC_VERSION#bwcTest + command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-file-fingerprints v$$BWC_VERSION#bwcTest timeout_in_minutes: 300 matrix: setup: @@ -67,7 +67,7 @@ steps: env: BWC_VERSION: "{{matrix.BWC_VERSION}}" - label: rest-compat - command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-task-input-files checkRestCompat + command: .ci/scripts/run-gradle.sh -Dbwc.checkout.align=true -Dorg.elasticsearch.build.cache.push=true -Dignore.tests.seed -Dscan.capture-file-fingerprints checkRestCompat timeout_in_minutes: 300 agents: provider: gcp From c60c464fbb7264bfbdf9096240d650f3b042bc81 Mon Sep 17 00:00:00 2001 From: elasticsearchmachine <58790826+elasticsearchmachine@users.noreply.github.com> Date: Sat, 7 Dec 2024 09:32:39 +1100 Subject: [PATCH 20/37] Mute org.elasticsearch.xpack.esql.plugin.ClusterRequestTests testFallbackIndicesOptions #117937 --- muted-tests.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/muted-tests.yml b/muted-tests.yml index 2cd836fae438d..b0710005f8f99 100644 --- a/muted-tests.yml +++ b/muted-tests.yml @@ -286,6 +286,9 @@ tests: - class: org.elasticsearch.threadpool.SimpleThreadPoolIT method: testThreadPoolMetrics issue: https://github.com/elastic/elasticsearch/issues/108320 +- class: org.elasticsearch.xpack.esql.plugin.ClusterRequestTests + method: testFallbackIndicesOptions + issue: https://github.com/elastic/elasticsearch/issues/117937 # Examples: # From ee5fa65d1090ff023bc47d5035dc9c4bc847465e Mon Sep 17 00:00:00 2001 From: kosabogi <105062005+kosabogi@users.noreply.github.com> Date: Mon, 9 Dec 2024 12:04:48 +0100 Subject: [PATCH 21/37] Updates h7 and h8 formatting (#118132) (#118248) --- .../connector/docs/connectors-box.asciidoc | 12 +++---- .../connectors-content-extraction.asciidoc | 4 +-- .../docs/connectors-dropbox.asciidoc | 16 +++++----- .../connector/docs/connectors-github.asciidoc | 20 ++++++------ .../connector/docs/connectors-ms-sql.asciidoc | 12 +++---- .../docs/connectors-network-drive.asciidoc | 16 +++++----- .../connector/docs/connectors-notion.asciidoc | 32 +++++++++---------- .../docs/connectors-onedrive.asciidoc | 24 +++++++------- .../docs/connectors-postgresql.asciidoc | 24 +++++++------- .../connector/docs/connectors-s3.asciidoc | 4 +-- .../docs/connectors-salesforce.asciidoc | 12 +++---- .../docs/connectors-servicenow.asciidoc | 12 +++---- .../connectors-sharepoint-online.asciidoc | 16 +++++----- 13 files changed, 102 insertions(+), 102 deletions(-) diff --git a/docs/reference/connector/docs/connectors-box.asciidoc b/docs/reference/connector/docs/connectors-box.asciidoc index 07e4308d67c20..3e95f15d16ccd 100644 --- a/docs/reference/connector/docs/connectors-box.asciidoc +++ b/docs/reference/connector/docs/connectors-box.asciidoc @@ -54,7 +54,7 @@ For additional operations, see <>. ====== Box Free Account [discrete#es-connectors-box-create-oauth-custom-app] -======= Create Box User Authentication (OAuth 2.0) Custom App +*Create Box User Authentication (OAuth 2.0) Custom App* You'll need to create an OAuth app in the Box developer console by following these steps: @@ -64,7 +64,7 @@ You'll need to create an OAuth app in the Box developer console by following the 4. Once the app is created, *Client ID* and *Client secret* values are available in the configuration tab. Keep these handy. [discrete#es-connectors-box-connector-generate-a-refresh-token] -======= Generate a refresh Token +*Generate a refresh Token* To generate a refresh token, follow these steps: @@ -97,7 +97,7 @@ Save the refresh token from the response. You'll need this for the connector con ====== Box Enterprise Account [discrete#es-connectors-box-connector-create-box-server-authentication-client-credentials-grant-custom-app] -======= Create Box Server Authentication (Client Credentials Grant) Custom App +*Create Box Server Authentication (Client Credentials Grant) Custom App* 1. Register a new app in the https://app.box.com/developers/console[Box dev console] with custom App and select Server Authentication (Client Credentials Grant). 2. Check following permissions: @@ -224,7 +224,7 @@ For additional operations, see <>. ====== Box Free Account [discrete#es-connectors-box-client-create-oauth-custom-app] -======= Create Box User Authentication (OAuth 2.0) Custom App +*Create Box User Authentication (OAuth 2.0) Custom App* You'll need to create an OAuth app in the Box developer console by following these steps: @@ -234,7 +234,7 @@ You'll need to create an OAuth app in the Box developer console by following the 4. Once the app is created, *Client ID* and *Client secret* values are available in the configuration tab. Keep these handy. [discrete#es-connectors-box-client-connector-generate-a-refresh-token] -======= Generate a refresh Token +*Generate a refresh Token* To generate a refresh token, follow these steps: @@ -267,7 +267,7 @@ Save the refresh token from the response. You'll need this for the connector con ====== Box Enterprise Account [discrete#es-connectors-box-client-connector-create-box-server-authentication-client-credentials-grant-custom-app] -======= Create Box Server Authentication (Client Credentials Grant) Custom App +*Create Box Server Authentication (Client Credentials Grant) Custom App* 1. Register a new app in the https://app.box.com/developers/console[Box dev console] with custom App and select Server Authentication (Client Credentials Grant). 2. Check following permissions: diff --git a/docs/reference/connector/docs/connectors-content-extraction.asciidoc b/docs/reference/connector/docs/connectors-content-extraction.asciidoc index 5d2a9550a7c3c..a87d38c9bf531 100644 --- a/docs/reference/connector/docs/connectors-content-extraction.asciidoc +++ b/docs/reference/connector/docs/connectors-content-extraction.asciidoc @@ -183,7 +183,7 @@ Be aware that the self-managed connector will download files with randomized fil For that reason, we recommend using a dedicated directory for self-hosted extraction. [discrete#es-connectors-content-extraction-data-extraction-service-file-pointers-configuration-example] -======= Example +*Example* 1. For this example, we will be using `/app/files` as both our local directory and our container directory. When you run the extraction service docker container, you can mount the directory as a volume using the command-line option `-v /app/files:/app/files`. @@ -228,7 +228,7 @@ When using self-hosted extraction from a dockerized self-managed connector, ther * The self-managed connector and the extraction service will also need to share a volume. You can decide what directory inside these docker containers the volume will be mounted onto, but the directory must be the same for both docker containers. [discrete#es-connectors-content-extraction-data-extraction-service-file-pointers-configuration-dockerized-example] -======= Example +*Example* 1. First, set up a volume for the two docker containers to share. This will be where files are downloaded into and then extracted from. diff --git a/docs/reference/connector/docs/connectors-dropbox.asciidoc b/docs/reference/connector/docs/connectors-dropbox.asciidoc index 1f80a0ab4e952..295b7e2936625 100644 --- a/docs/reference/connector/docs/connectors-dropbox.asciidoc +++ b/docs/reference/connector/docs/connectors-dropbox.asciidoc @@ -190,7 +190,7 @@ When both are provided, priority is given to `file_categories`. We have some examples below for illustration. [discrete#es-connectors-dropbox-sync-rules-advanced-example-1] -======= Example: Query only +*Example: Query only* [source,js] ---- @@ -206,7 +206,7 @@ We have some examples below for illustration. // NOTCONSOLE [discrete#es-connectors-dropbox-sync-rules-advanced-example-2] -======= Example: Query with file extension filter +*Example: Query with file extension filter* [source,js] ---- @@ -225,7 +225,7 @@ We have some examples below for illustration. // NOTCONSOLE [discrete#es-connectors-dropbox-sync-rules-advanced-example-3] -======= Example: Query with file category filter +*Example: Query with file category filter* [source,js] ---- @@ -248,7 +248,7 @@ We have some examples below for illustration. // NOTCONSOLE [discrete#es-connectors-dropbox-sync-rules-advanced-limitations] -======= Limitations +*Limitations* * Content extraction is not supported for Dropbox *Paper* files when advanced sync rules are enabled. @@ -474,7 +474,7 @@ When both are provided, priority is given to `file_categories`. We have some examples below for illustration. [discrete#es-connectors-dropbox-client-sync-rules-advanced-example-1] -======= Example: Query only +*Example: Query only* [source,js] ---- @@ -490,7 +490,7 @@ We have some examples below for illustration. // NOTCONSOLE [discrete#es-connectors-dropbox-client-sync-rules-advanced-example-2] -======= Example: Query with file extension filter +*Example: Query with file extension filter* [source,js] ---- @@ -509,7 +509,7 @@ We have some examples below for illustration. // NOTCONSOLE [discrete#es-connectors-dropbox-client-sync-rules-advanced-example-3] -======= Example: Query with file category filter +*Example: Query with file category filter* [source,js] ---- @@ -532,7 +532,7 @@ We have some examples below for illustration. // NOTCONSOLE [discrete#es-connectors-dropbox-client-sync-rules-advanced-limitations] -======= Limitations +*Limitations* * Content extraction is not supported for Dropbox *Paper* files when advanced sync rules are enabled. diff --git a/docs/reference/connector/docs/connectors-github.asciidoc b/docs/reference/connector/docs/connectors-github.asciidoc index aa683e4bb0829..df577d83e8121 100644 --- a/docs/reference/connector/docs/connectors-github.asciidoc +++ b/docs/reference/connector/docs/connectors-github.asciidoc @@ -210,7 +210,7 @@ Advanced sync rules are defined through a source-specific DSL JSON snippet. The following sections provide examples of advanced sync rules for this connector. [discrete#es-connectors-github-sync-rules-advanced-branch] -======= Indexing document and files based on branch name configured via branch key +*Indexing document and files based on branch name configured via branch key* [source,js] ---- @@ -226,7 +226,7 @@ The following sections provide examples of advanced sync rules for this connecto // NOTCONSOLE [discrete#es-connectors-github-sync-rules-advanced-issue-key] -======= Indexing document based on issue query related to bugs via issue key +*Indexing document based on issue query related to bugs via issue key* [source,js] ---- @@ -242,7 +242,7 @@ The following sections provide examples of advanced sync rules for this connecto // NOTCONSOLE [discrete#es-connectors-github-sync-rules-advanced-pr-key] -======= Indexing document based on PR query related to open PR's via PR key +*Indexing document based on PR query related to open PR's via PR key* [source,js] ---- @@ -258,7 +258,7 @@ The following sections provide examples of advanced sync rules for this connecto // NOTCONSOLE [discrete#es-connectors-github-sync-rules-advanced-issue-query-branch-name] -======= Indexing document and files based on queries and branch name +*Indexing document and files based on queries and branch name* [source,js] ---- @@ -283,7 +283,7 @@ Check the Elasticsearch index for the actual document count. ==== [discrete#es-connectors-github-sync-rules-advanced-overlapping] -======= Advanced rules for overlapping +*Advanced rules for overlapping* [source,js] ---- @@ -550,7 +550,7 @@ Advanced sync rules are defined through a source-specific DSL JSON snippet. The following sections provide examples of advanced sync rules for this connector. [discrete#es-connectors-github-client-sync-rules-advanced-branch] -======= Indexing document and files based on branch name configured via branch key +*Indexing document and files based on branch name configured via branch key* [source,js] ---- @@ -566,7 +566,7 @@ The following sections provide examples of advanced sync rules for this connecto // NOTCONSOLE [discrete#es-connectors-github-client-sync-rules-advanced-issue-key] -======= Indexing document based on issue query related to bugs via issue key +*Indexing document based on issue query related to bugs via issue key* [source,js] ---- @@ -582,7 +582,7 @@ The following sections provide examples of advanced sync rules for this connecto // NOTCONSOLE [discrete#es-connectors-github-client-sync-rules-advanced-pr-key] -======= Indexing document based on PR query related to open PR's via PR key +*Indexing document based on PR query related to open PR's via PR key* [source,js] ---- @@ -598,7 +598,7 @@ The following sections provide examples of advanced sync rules for this connecto // NOTCONSOLE [discrete#es-connectors-github-client-sync-rules-advanced-issue-query-branch-name] -======= Indexing document and files based on queries and branch name +*Indexing document and files based on queries and branch name* [source,js] ---- @@ -623,7 +623,7 @@ Check the Elasticsearch index for the actual document count. ==== [discrete#es-connectors-github-client-sync-rules-advanced-overlapping] -======= Advanced rules for overlapping +*Advanced rules for overlapping* [source,js] ---- diff --git a/docs/reference/connector/docs/connectors-ms-sql.asciidoc b/docs/reference/connector/docs/connectors-ms-sql.asciidoc index 47fb282b16877..d706af8ca8043 100644 --- a/docs/reference/connector/docs/connectors-ms-sql.asciidoc +++ b/docs/reference/connector/docs/connectors-ms-sql.asciidoc @@ -196,7 +196,7 @@ Here are a few examples of advanced sync rules for this connector. ==== [discrete#es-connectors-ms-sql-sync-rules-advanced-queries] -======= Example: Two queries +*Example: Two queries* These rules fetch all records from both the `employee` and `customer` tables. The data from these tables will be synced separately to Elasticsearch. @@ -220,7 +220,7 @@ These rules fetch all records from both the `employee` and `customer` tables. Th // NOTCONSOLE [discrete#es-connectors-ms-sql-sync-rules-example-one-where] -======= Example: One WHERE query +*Example: One WHERE query* This rule fetches only the records from the `employee` table where the `emp_id` is greater than 5. Only these filtered records will be synced to Elasticsearch. @@ -236,7 +236,7 @@ This rule fetches only the records from the `employee` table where the `emp_id` // NOTCONSOLE [discrete#es-connectors-ms-sql-sync-rules-example-one-join] -======= Example: One JOIN query +*Example: One JOIN query* This rule fetches records by performing an INNER JOIN between the `employee` and `customer` tables on the condition that the `emp_id` in `employee` matches the `c_id` in `customer`. The result of this combined data will be synced to Elasticsearch. @@ -484,7 +484,7 @@ Here are a few examples of advanced sync rules for this connector. ==== [discrete#es-connectors-ms-sql-client-sync-rules-advanced-queries] -======= Example: Two queries +*Example: Two queries* These rules fetch all records from both the `employee` and `customer` tables. The data from these tables will be synced separately to Elasticsearch. @@ -508,7 +508,7 @@ These rules fetch all records from both the `employee` and `customer` tables. Th // NOTCONSOLE [discrete#es-connectors-ms-sql-client-sync-rules-example-one-where] -======= Example: One WHERE query +*Example: One WHERE query* This rule fetches only the records from the `employee` table where the `emp_id` is greater than 5. Only these filtered records will be synced to Elasticsearch. @@ -524,7 +524,7 @@ This rule fetches only the records from the `employee` table where the `emp_id` // NOTCONSOLE [discrete#es-connectors-ms-sql-client-sync-rules-example-one-join] -======= Example: One JOIN query +*Example: One JOIN query* This rule fetches records by performing an INNER JOIN between the `employee` and `customer` tables on the condition that the `emp_id` in `employee` matches the `c_id` in `customer`. The result of this combined data will be synced to Elasticsearch. diff --git a/docs/reference/connector/docs/connectors-network-drive.asciidoc b/docs/reference/connector/docs/connectors-network-drive.asciidoc index 91c9d3b28c385..909e3440c9f02 100644 --- a/docs/reference/connector/docs/connectors-network-drive.asciidoc +++ b/docs/reference/connector/docs/connectors-network-drive.asciidoc @@ -174,7 +174,7 @@ Advanced sync rules for this connector use *glob patterns*. The following sections provide examples of advanced sync rules for this connector. [discrete#es-connectors-network-drive-indexing-files-and-folders-recursively-within-folders] -======= Indexing files and folders recursively within folders +*Indexing files and folders recursively within folders* [source,js] ---- @@ -190,7 +190,7 @@ The following sections provide examples of advanced sync rules for this connecto // NOTCONSOLE [discrete#es-connectors-network-drive-indexing-files-and-folders-directly-inside-folder] -======= Indexing files and folders directly inside folder +*Indexing files and folders directly inside folder* [source,js] ---- @@ -203,7 +203,7 @@ The following sections provide examples of advanced sync rules for this connecto // NOTCONSOLE [discrete#es-connectors-network-drive-indexing-files-and-folders-directly-inside-a-set-of-folders] -======= Indexing files and folders directly inside a set of folders +*Indexing files and folders directly inside a set of folders* [source,js] ---- @@ -216,7 +216,7 @@ The following sections provide examples of advanced sync rules for this connecto // NOTCONSOLE [discrete#es-connectors-network-drive-excluding-files-and-folders-that-match-a-pattern] -======= Excluding files and folders that match a pattern +*Excluding files and folders that match a pattern* [source,js] ---- @@ -432,7 +432,7 @@ Advanced sync rules for this connector use *glob patterns*. The following sections provide examples of advanced sync rules for this connector. [discrete#es-connectors-network-drive-client-indexing-files-and-folders-recursively-within-folders] -======= Indexing files and folders recursively within folders +*Indexing files and folders recursively within folders* [source,js] ---- @@ -448,7 +448,7 @@ The following sections provide examples of advanced sync rules for this connecto // NOTCONSOLE [discrete#es-connectors-network-drive-client-indexing-files-and-folders-directly-inside-folder] -======= Indexing files and folders directly inside folder +*Indexing files and folders directly inside folder* [source,js] ---- @@ -461,7 +461,7 @@ The following sections provide examples of advanced sync rules for this connecto // NOTCONSOLE [discrete#es-connectors-network-drive-client-indexing-files-and-folders-directly-inside-a-set-of-folders] -======= Indexing files and folders directly inside a set of folders +*Indexing files and folders directly inside a set of folders* [source,js] ---- @@ -474,7 +474,7 @@ The following sections provide examples of advanced sync rules for this connecto // NOTCONSOLE [discrete#es-connectors-network-drive-client-excluding-files-and-folders-that-match-a-pattern] -======= Excluding files and folders that match a pattern +*Excluding files and folders that match a pattern* [source,js] ---- diff --git a/docs/reference/connector/docs/connectors-notion.asciidoc b/docs/reference/connector/docs/connectors-notion.asciidoc index 2d7a71bff20de..7c08c5d81e032 100644 --- a/docs/reference/connector/docs/connectors-notion.asciidoc +++ b/docs/reference/connector/docs/connectors-notion.asciidoc @@ -140,7 +140,7 @@ Advanced sync rules for Notion take the following parameters: ====== Examples [discrete] -======= Example 1 +*Example 1* Indexing every page where the title contains `Demo Page`: @@ -160,7 +160,7 @@ Indexing every page where the title contains `Demo Page`: // NOTCONSOLE [discrete] -======= Example 2 +*Example 2* Indexing every database where the title contains `Demo Database`: @@ -180,7 +180,7 @@ Indexing every database where the title contains `Demo Database`: // NOTCONSOLE [discrete] -======= Example 3 +*Example 3* Indexing every database where the title contains `Demo Database` and every page where the title contains `Demo Page`: @@ -206,7 +206,7 @@ Indexing every database where the title contains `Demo Database` and every page // NOTCONSOLE [discrete] -======= Example 4 +*Example 4* Indexing all pages in the workspace: @@ -226,7 +226,7 @@ Indexing all pages in the workspace: // NOTCONSOLE [discrete] -======= Example 5 +*Example 5* Indexing all the pages and databases connected to the workspace: @@ -243,7 +243,7 @@ Indexing all the pages and databases connected to the workspace: // NOTCONSOLE [discrete] -======= Example 6 +*Example 6* Indexing all the rows of a database where the record is `true` for the column `Task completed` and its property(datatype) is a checkbox: @@ -266,7 +266,7 @@ Indexing all the rows of a database where the record is `true` for the column `T // NOTCONSOLE [discrete] -======= Example 7 +*Example 7* Indexing all rows of a specific database: @@ -283,7 +283,7 @@ Indexing all rows of a specific database: // NOTCONSOLE [discrete] -======= Example 8 +*Example 8* Indexing all blocks defined in `searches` and `database_query_filters`: @@ -498,7 +498,7 @@ Advanced sync rules for Notion take the following parameters: ====== Examples [discrete] -======= Example 1 +*Example 1* Indexing every page where the title contains `Demo Page`: @@ -518,7 +518,7 @@ Indexing every page where the title contains `Demo Page`: // NOTCONSOLE [discrete] -======= Example 2 +*Example 2* Indexing every database where the title contains `Demo Database`: @@ -538,7 +538,7 @@ Indexing every database where the title contains `Demo Database`: // NOTCONSOLE [discrete] -======= Example 3 +*Example 3* Indexing every database where the title contains `Demo Database` and every page where the title contains `Demo Page`: @@ -564,7 +564,7 @@ Indexing every database where the title contains `Demo Database` and every page // NOTCONSOLE [discrete] -======= Example 4 +*Example 4* Indexing all pages in the workspace: @@ -584,7 +584,7 @@ Indexing all pages in the workspace: // NOTCONSOLE [discrete] -======= Example 5 +*Example 5* Indexing all the pages and databases connected to the workspace: @@ -601,7 +601,7 @@ Indexing all the pages and databases connected to the workspace: // NOTCONSOLE [discrete] -======= Example 6 +*Example 6* Indexing all the rows of a database where the record is `true` for the column `Task completed` and its property(datatype) is a checkbox: @@ -624,7 +624,7 @@ Indexing all the rows of a database where the record is `true` for the column `T // NOTCONSOLE [discrete] -======= Example 7 +*Example 7* Indexing all rows of a specific database: @@ -641,7 +641,7 @@ Indexing all rows of a specific database: // NOTCONSOLE [discrete] -======= Example 8 +*Example 8* Indexing all blocks defined in `searches` and `database_query_filters`: diff --git a/docs/reference/connector/docs/connectors-onedrive.asciidoc b/docs/reference/connector/docs/connectors-onedrive.asciidoc index 7d1a21aeb78db..44ac96e2ad99d 100644 --- a/docs/reference/connector/docs/connectors-onedrive.asciidoc +++ b/docs/reference/connector/docs/connectors-onedrive.asciidoc @@ -160,7 +160,7 @@ A <> is required for advanced sync rul Here are a few examples of advanced sync rules for this connector. [discrete#es-connectors-onedrive-sync-rules-advanced-examples-1] -======= Example 1 +*Example 1* This rule skips indexing for files with `.xlsx` and `.docx` extensions. All other files and folders will be indexed. @@ -176,7 +176,7 @@ All other files and folders will be indexed. // NOTCONSOLE [discrete#es-connectors-onedrive-sync-rules-advanced-examples-2] -======= Example 2 +*Example 2* This rule focuses on indexing files and folders owned by `user1-domain@onmicrosoft.com` and `user2-domain@onmicrosoft.com` but excludes files with `.py` extension. @@ -192,7 +192,7 @@ This rule focuses on indexing files and folders owned by `user1-domain@onmicroso // NOTCONSOLE [discrete#es-connectors-onedrive-sync-rules-advanced-examples-3] -======= Example 3 +*Example 3* This rule indexes only the files and folders directly inside the root folder, excluding any `.md` files. @@ -208,7 +208,7 @@ This rule indexes only the files and folders directly inside the root folder, ex // NOTCONSOLE [discrete#es-connectors-onedrive-sync-rules-advanced-examples-4] -======= Example 4 +*Example 4* This rule indexes files and folders owned by `user1-domain@onmicrosoft.com` and `user3-domain@onmicrosoft.com` that are directly inside the `abc` folder, which is a subfolder of any folder under the `hello` directory in the root. Files with extensions `.pdf` and `.py` are excluded. @@ -225,7 +225,7 @@ This rule indexes files and folders owned by `user1-domain@onmicrosoft.com` and // NOTCONSOLE [discrete#es-connectors-onedrive-sync-rules-advanced-examples-5] -======= Example 5 +*Example 5* This example contains two rules. The first rule indexes all files and folders owned by `user1-domain@onmicrosoft.com` and `user2-domain@onmicrosoft.com`. @@ -245,7 +245,7 @@ The second rule indexes files for all other users, but skips files with a `.py` // NOTCONSOLE [discrete#es-connectors-onedrive-sync-rules-advanced-examples-6] -======= Example 6 +*Example 6* This example contains two rules. The first rule indexes all files owned by `user1-domain@onmicrosoft.com` and `user2-domain@onmicrosoft.com`, excluding `.md` files. @@ -449,7 +449,7 @@ A <> is required for advanced sync rul Here are a few examples of advanced sync rules for this connector. [discrete#es-connectors-onedrive-client-sync-rules-advanced-examples-1] -======= Example 1 +*Example 1* This rule skips indexing for files with `.xlsx` and `.docx` extensions. All other files and folders will be indexed. @@ -465,7 +465,7 @@ All other files and folders will be indexed. // NOTCONSOLE [discrete#es-connectors-onedrive-client-sync-rules-advanced-examples-2] -======= Example 2 +*Example 2* This rule focuses on indexing files and folders owned by `user1-domain@onmicrosoft.com` and `user2-domain@onmicrosoft.com` but excludes files with `.py` extension. @@ -481,7 +481,7 @@ This rule focuses on indexing files and folders owned by `user1-domain@onmicroso // NOTCONSOLE [discrete#es-connectors-onedrive-client-sync-rules-advanced-examples-3] -======= Example 3 +*Example 3* This rule indexes only the files and folders directly inside the root folder, excluding any `.md` files. @@ -497,7 +497,7 @@ This rule indexes only the files and folders directly inside the root folder, ex // NOTCONSOLE [discrete#es-connectors-onedrive-client-sync-rules-advanced-examples-4] -======= Example 4 +*Example 4* This rule indexes files and folders owned by `user1-domain@onmicrosoft.com` and `user3-domain@onmicrosoft.com` that are directly inside the `abc` folder, which is a subfolder of any folder under the `hello` directory in the root. Files with extensions `.pdf` and `.py` are excluded. @@ -514,7 +514,7 @@ This rule indexes files and folders owned by `user1-domain@onmicrosoft.com` and // NOTCONSOLE [discrete#es-connectors-onedrive-client-sync-rules-advanced-examples-5] -======= Example 5 +*Example 5* This example contains two rules. The first rule indexes all files and folders owned by `user1-domain@onmicrosoft.com` and `user2-domain@onmicrosoft.com`. @@ -534,7 +534,7 @@ The second rule indexes files for all other users, but skips files with a `.py` // NOTCONSOLE [discrete#es-connectors-onedrive-client-sync-rules-advanced-examples-6] -======= Example 6 +*Example 6* This example contains two rules. The first rule indexes all files owned by `user1-domain@onmicrosoft.com` and `user2-domain@onmicrosoft.com`, excluding `.md` files. diff --git a/docs/reference/connector/docs/connectors-postgresql.asciidoc b/docs/reference/connector/docs/connectors-postgresql.asciidoc index 1fe28f867337c..aa6cb7f29e633 100644 --- a/docs/reference/connector/docs/connectors-postgresql.asciidoc +++ b/docs/reference/connector/docs/connectors-postgresql.asciidoc @@ -188,7 +188,7 @@ Advanced sync rules are defined through a source-specific DSL JSON snippet. Here is some example data that will be used in the following examples. [discrete#connectors-postgresql-sync-rules-advanced-example-data-1] -======= `employee` table +*`employee` table* [cols="3*", options="header"] |=== @@ -199,7 +199,7 @@ Here is some example data that will be used in the following examples. |=== [discrete#connectors-postgresql-sync-rules-advanced-example-2] -======= `customer` table +*`customer` table* [cols="3*", options="header"] |=== @@ -213,7 +213,7 @@ Here is some example data that will be used in the following examples. ====== Advanced sync rules examples [discrete#connectors-postgresql-sync-rules-advanced-examples-1] -======= Multiple table queries +*Multiple table queries* [source,js] ---- @@ -235,7 +235,7 @@ Here is some example data that will be used in the following examples. // NOTCONSOLE [discrete#connectors-postgresql-sync-rules-advanced-examples-1-id-columns] -======= Multiple table queries with `id_columns` +*Multiple table queries with `id_columns`* In 8.15.0, we added a new optional `id_columns` field in our advanced sync rules for the PostgreSQL connector. Use the `id_columns` field to ingest tables which do not have a primary key. Include the names of unique fields so that the connector can use them to generate unique IDs for documents. @@ -264,7 +264,7 @@ Use the `id_columns` field to ingest tables which do not have a primary key. Inc This example uses the `id_columns` field to specify the unique fields `emp_id` and `c_id` for the `employee` and `customer` tables, respectively. [discrete#connectors-postgresql-sync-rules-advanced-examples-2] -======= Filtering data with `WHERE` clause +*Filtering data with `WHERE` clause* [source,js] ---- @@ -278,7 +278,7 @@ This example uses the `id_columns` field to specify the unique fields `emp_id` a // NOTCONSOLE [discrete#connectors-postgresql-sync-rules-advanced-examples-3] -======= `JOIN` operations +*`JOIN` operations* [source,js] ---- @@ -494,7 +494,7 @@ Advanced sync rules are defined through a source-specific DSL JSON snippet. Here is some example data that will be used in the following examples. [discrete#es-connectors-postgresql-client-sync-rules-advanced-example-data-1] -======= `employee` table +*`employee` table* [cols="3*", options="header"] |=== @@ -505,7 +505,7 @@ Here is some example data that will be used in the following examples. |=== [discrete#es-connectors-postgresql-client-sync-rules-advanced-example-2] -======= `customer` table +*`customer` table* [cols="3*", options="header"] |=== @@ -519,7 +519,7 @@ Here is some example data that will be used in the following examples. ====== Advanced sync rules examples [discrete#es-connectors-postgresql-client-sync-rules-advanced-examples-1] -======== Multiple table queries +*Multiple table queries* [source,js] ---- @@ -541,7 +541,7 @@ Here is some example data that will be used in the following examples. // NOTCONSOLE [discrete#es-connectors-postgresql-client-sync-rules-advanced-examples-1-id-columns] -======== Multiple table queries with `id_columns` +*Multiple table queries with `id_columns`* In 8.15.0, we added a new optional `id_columns` field in our advanced sync rules for the PostgreSQL connector. Use the `id_columns` field to ingest tables which do not have a primary key. Include the names of unique fields so that the connector can use them to generate unique IDs for documents. @@ -570,7 +570,7 @@ Use the `id_columns` field to ingest tables which do not have a primary key. Inc This example uses the `id_columns` field to specify the unique fields `emp_id` and `c_id` for the `employee` and `customer` tables, respectively. [discrete#es-connectors-postgresql-client-sync-rules-advanced-examples-2] -======== Filtering data with `WHERE` clause +*Filtering data with `WHERE` clause* [source,js] ---- @@ -584,7 +584,7 @@ This example uses the `id_columns` field to specify the unique fields `emp_id` a // NOTCONSOLE [discrete#es-connectors-postgresql-client-sync-rules-advanced-examples-3] -======== `JOIN` operations +*`JOIN` operations* [source,js] ---- diff --git a/docs/reference/connector/docs/connectors-s3.asciidoc b/docs/reference/connector/docs/connectors-s3.asciidoc index b4d08d3884631..90c070f7b8044 100644 --- a/docs/reference/connector/docs/connectors-s3.asciidoc +++ b/docs/reference/connector/docs/connectors-s3.asciidoc @@ -118,7 +118,7 @@ The connector will fetch file and folder data that matches the string. Defaults to `""` (syncs all bucket objects). [discrete#es-connectors-s3-sync-rules-advanced-examples] -======= Advanced sync rules examples +*Advanced sync rules examples* *Fetching files and folders recursively by prefix* @@ -336,7 +336,7 @@ The connector will fetch file and folder data that matches the string. Defaults to `""` (syncs all bucket objects). [discrete#es-connectors-s3-client-sync-rules-advanced-examples] -======= Advanced sync rules examples +*Advanced sync rules examples* *Fetching files and folders recursively by prefix* diff --git a/docs/reference/connector/docs/connectors-salesforce.asciidoc b/docs/reference/connector/docs/connectors-salesforce.asciidoc index 3676f7663089c..c640751de92c0 100644 --- a/docs/reference/connector/docs/connectors-salesforce.asciidoc +++ b/docs/reference/connector/docs/connectors-salesforce.asciidoc @@ -227,7 +227,7 @@ They take the following parameters: Allowed values are *SOQL* and *SOSL*. [discrete#es-connectors-salesforce-sync-rules-advanced-fetch-query-language] -======= Fetch documents based on the query and language specified +*Fetch documents based on the query and language specified* **Example**: Fetch documents using SOQL query @@ -256,7 +256,7 @@ Allowed values are *SOQL* and *SOSL*. // NOTCONSOLE [discrete#es-connectors-salesforce-sync-rules-advanced-fetch-objects] -======= Fetch standard and custom objects using SOQL and SOSL queries +*Fetch standard and custom objects using SOQL and SOSL queries* **Example**: Fetch documents for standard objects via SOQL and SOSL query. @@ -293,7 +293,7 @@ Allowed values are *SOQL* and *SOSL*. // NOTCONSOLE [discrete#es-connectors-salesforce-sync-rules-advanced-fetch-standard-custom-fields] -======= Fetch documents with standard and custom fields +*Fetch documents with standard and custom fields* **Example**: Fetch documents with all standard and custom fields for Account object. @@ -626,7 +626,7 @@ They take the following parameters: Allowed values are *SOQL* and *SOSL*. [discrete#es-connectors-salesforce-client-sync-rules-advanced-fetch-query-language] -======= Fetch documents based on the query and language specified +*Fetch documents based on the query and language specified* **Example**: Fetch documents using SOQL query @@ -655,7 +655,7 @@ Allowed values are *SOQL* and *SOSL*. // NOTCONSOLE [discrete#es-connectors-salesforce-client-sync-rules-advanced-fetch-objects] -======= Fetch standard and custom objects using SOQL and SOSL queries +*Fetch standard and custom objects using SOQL and SOSL queries* **Example**: Fetch documents for standard objects via SOQL and SOSL query. @@ -692,7 +692,7 @@ Allowed values are *SOQL* and *SOSL*. // NOTCONSOLE [discrete#es-connectors-salesforce-client-sync-rules-advanced-fetch-standard-custom-fields] -======= Fetch documents with standard and custom fields +*Fetch documents with standard and custom fields* **Example**: Fetch documents with all standard and custom fields for Account object. diff --git a/docs/reference/connector/docs/connectors-servicenow.asciidoc b/docs/reference/connector/docs/connectors-servicenow.asciidoc index a02c418f11d74..3dc98ed9a44c9 100644 --- a/docs/reference/connector/docs/connectors-servicenow.asciidoc +++ b/docs/reference/connector/docs/connectors-servicenow.asciidoc @@ -167,7 +167,7 @@ Advanced sync rules are defined through a source-specific DSL JSON snippet. The following sections provide examples of advanced sync rules for this connector. [discrete#es-connectors-servicenow-sync-rules-number-incident-service] -======= Indexing document based on incident number for Incident service +*Indexing document based on incident number for Incident service* [source,js] ---- @@ -181,7 +181,7 @@ The following sections provide examples of advanced sync rules for this connecto // NOTCONSOLE [discrete#es-connectors-servicenow-sync-rules-active-false-user-service] -======= Indexing document based on user activity state for User service +*Indexing document based on user activity state for User service* [source,js] ---- @@ -195,7 +195,7 @@ The following sections provide examples of advanced sync rules for this connecto // NOTCONSOLE [discrete#es-connectors-servicenow-sync-rules-author-administrator-knowledge-service] -======= Indexing document based on author name for Knowledge service +*Indexing document based on author name for Knowledge service* [source,js] ---- @@ -407,7 +407,7 @@ Advanced sync rules are defined through a source-specific DSL JSON snippet. The following sections provide examples of advanced sync rules for this connector. [discrete#es-connectors-servicenow-client-sync-rules-number-incident-service] -======= Indexing document based on incident number for Incident service +*Indexing document based on incident number for Incident service* [source,js] ---- @@ -421,7 +421,7 @@ The following sections provide examples of advanced sync rules for this connecto // NOTCONSOLE [discrete#es-connectors-servicenow-client-sync-rules-active-false-user-service] -======= Indexing document based on user activity state for User service +*Indexing document based on user activity state for User service* [source,js] ---- @@ -435,7 +435,7 @@ The following sections provide examples of advanced sync rules for this connecto // NOTCONSOLE [discrete#es-connectors-servicenow-client-sync-rules-author-administrator-knowledge-service] -======= Indexing document based on author name for Knowledge service +*Indexing document based on author name for Knowledge service* [source,js] ---- diff --git a/docs/reference/connector/docs/connectors-sharepoint-online.asciidoc b/docs/reference/connector/docs/connectors-sharepoint-online.asciidoc index 21d0890e436c5..02f598c16f63c 100644 --- a/docs/reference/connector/docs/connectors-sharepoint-online.asciidoc +++ b/docs/reference/connector/docs/connectors-sharepoint-online.asciidoc @@ -277,7 +277,7 @@ Example: This rule will not extract content of any drive items (files in document libraries) that haven't been modified for 60 days or more. [discrete#es-connectors-sharepoint-online-sync-rules-limitations] -======= Limitations of sync rules with incremental syncs +*Limitations of sync rules with incremental syncs* Changing sync rules after Sharepoint Online content has already been indexed can bring unexpected results, when using <>. @@ -288,7 +288,7 @@ Incremental syncs ensure _updates_ from 3rd-party system, but do not modify exis Let's take a look at several examples where incremental syncs might lead to inconsistent data on your index. [discrete#es-connectors-sharepoint-online-sync-rules-limitations-restrictive-added] -======== Example: Restrictive basic sync rule added after a full sync +*Example: Restrictive basic sync rule added after a full sync* Imagine your Sharepoint Online drive contains the following drive items: @@ -322,7 +322,7 @@ If no files were changed, incremental sync will not receive information about ch After a *full sync*, the index will be updated and files that are excluded by sync rules will be removed. [discrete#es-connectors-sharepoint-online-sync-rules-limitations-restrictive-removed] -======== Example: Restrictive basic sync rules removed after a full sync +*Example: Restrictive basic sync rules removed after a full sync* Imagine that Sharepoint Online drive has the following drive items: @@ -354,7 +354,7 @@ Afterwards, we can remove the filtering rule and run an incremental sync. If no Only a *full sync* will include the items previously ignored by the sync rule. [discrete#es-connectors-sharepoint-online-sync-rules-limitations-restrictive-changed] -======== Example: Advanced sync rules edge case +*Example: Advanced sync rules edge case* Advanced sync rules can be applied to limit which documents will have content extracted. For example, it's possible to set a rule so that documents older than 180 days won't have content extracted. @@ -763,7 +763,7 @@ Example: This rule will not extract content of any drive items (files in document libraries) that haven't been modified for 60 days or more. [discrete#es-connectors-sharepoint-online-client-sync-rules-limitations] -======= Limitations of sync rules with incremental syncs +*Limitations of sync rules with incremental syncs* Changing sync rules after Sharepoint Online content has already been indexed can bring unexpected results, when using <>. @@ -774,7 +774,7 @@ Incremental syncs ensure _updates_ from 3rd-party system, but do not modify exis Let's take a look at several examples where incremental syncs might lead to inconsistent data on your index. [discrete#es-connectors-sharepoint-online-client-sync-rules-limitations-restrictive-added] -======== Example: Restrictive basic sync rule added after a full sync +*Example: Restrictive basic sync rule added after a full sync* Imagine your Sharepoint Online drive contains the following drive items: @@ -808,7 +808,7 @@ If no files were changed, incremental sync will not receive information about ch After a *full sync*, the index will be updated and files that are excluded by sync rules will be removed. [discrete#es-connectors-sharepoint-online-client-sync-rules-limitations-restrictive-removed] -======== Example: Restrictive basic sync rules removed after a full sync +*Example: Restrictive basic sync rules removed after a full sync* Imagine that Sharepoint Online drive has the following drive items: @@ -840,7 +840,7 @@ Afterwards, we can remove the filtering rule and run an incremental sync. If no Only a *full sync* will include the items previously ignored by the sync rule. [discrete#es-connectors-sharepoint-online-client-sync-rules-limitations-restrictive-changed] -======== Example: Advanced sync rules edge case +*Example: Advanced sync rules edge case* Advanced sync rules can be applied to limit which documents will have content extracted. For example, it's possible to set a rule so that documents older than 180 days won't have content extracted. From f7b97316d41e318f850260a5c4c92b70b9181f09 Mon Sep 17 00:00:00 2001 From: Liam Thompson <32779855+leemthompo@users.noreply.github.com> Date: Mon, 9 Dec 2024 15:53:03 +0100 Subject: [PATCH 22/37] Update connectors overview diagram (#118261) (#118269) --- .../docs/images/connectors-overview.png | Bin 315897 -> 0 bytes .../docs/images/connectors-overview.svg | 70 ++++++++++++++++++ docs/reference/connector/docs/index.asciidoc | 2 +- 3 files changed, 71 insertions(+), 1 deletion(-) delete mode 100644 docs/reference/connector/docs/images/connectors-overview.png create mode 100644 docs/reference/connector/docs/images/connectors-overview.svg diff --git a/docs/reference/connector/docs/images/connectors-overview.png b/docs/reference/connector/docs/images/connectors-overview.png deleted file mode 100644 index 4d0edfeb6adaeb9e3dbaa4d18432a3dd952f531f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 315897 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 z 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≧#mm(*7-2Pr&yx?ZPT(UU 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

a?#d&=x4_fIsI=Q=mFO%b@0K=)uhNeX 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�~erdnl##I@6 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%0A14TPkHmth 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�mRwCV;@rxg&0 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

g^MhyNf$c7tK$u&va`g|0^l< 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 diff --git a/docs/reference/connector/docs/images/connectors-overview.svg b/docs/reference/connector/docs/images/connectors-overview.svg new file mode 100644 index 0000000000000..0a7fb30c61d6d --- /dev/null +++ b/docs/reference/connector/docs/images/connectors-overview.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/reference/connector/docs/index.asciidoc b/docs/reference/connector/docs/index.asciidoc index 481e124a1a117..dfca45f86ebce 100644 --- a/docs/reference/connector/docs/index.asciidoc +++ b/docs/reference/connector/docs/index.asciidoc @@ -72,7 +72,7 @@ Refer to <> for details. The following diagram provides a high-level overview of the Elastic connectors offering and some key facts. -image::connectors-overview.png[align="center",width="100%"] +image::connectors-overview.svg[align="center",width="100%"] [discrete#es-connectors-overview-available-connectors] == Available connectors and feature support From 73b14189b375733266c602c49045a9b936a6db87 Mon Sep 17 00:00:00 2001 From: eshaffer321 Date: Mon, 9 Dec 2024 08:59:18 -0700 Subject: [PATCH 23/37] Update Semantic Search Tutorial to Use Elasticsearch Service and Remove References to Deprecated ELSER Service (#117826) * Update tutorial to use elasticsearch service --------- Co-authored-by: Max Hniebergall <137079448+maxhniebergall@users.noreply.github.com> --- docs/changelog/117826.yaml | 5 +++++ .../semantic-search-semantic-text.asciidoc | 14 +++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) create mode 100644 docs/changelog/117826.yaml diff --git a/docs/changelog/117826.yaml b/docs/changelog/117826.yaml new file mode 100644 index 0000000000000..98ca84bc0bd96 --- /dev/null +++ b/docs/changelog/117826.yaml @@ -0,0 +1,5 @@ +pr: 117826 +summary: "Switch ELSER service to elasticsearch service in semantic search tutorial" +area: Docs +type: doc +issues: ["117829"] diff --git a/docs/reference/search/search-your-data/semantic-search-semantic-text.asciidoc b/docs/reference/search/search-your-data/semantic-search-semantic-text.asciidoc index 6c3f3b2128740..3d799cd68f1b9 100644 --- a/docs/reference/search/search-your-data/semantic-search-semantic-text.asciidoc +++ b/docs/reference/search/search-your-data/semantic-search-semantic-text.asciidoc @@ -14,7 +14,7 @@ You don't need to define model related settings and parameters, or create {infer The recommended way to use <> in the {stack} is following the `semantic_text` workflow. When you need more control over indexing and query settings, you can still use the complete {infer} workflow (refer to <> to review the process). -This tutorial uses the <> for demonstration, but you can use any service and their supported models offered by the {infer-cap} API. +This tutorial uses the <> for demonstration, but you can use any service and their supported models offered by the {infer-cap} API. [discrete] @@ -34,14 +34,15 @@ Create an inference endpoint by using the <>: ------------------------------------------------------------ PUT _inference/sparse_embedding/my-elser-endpoint <1> { - "service": "elser", <2> + "service": "elasticsearch", <2> "service_settings": { "adaptive_allocations": { <3> "enabled": true, "min_number_of_allocations": 3, "max_number_of_allocations": 10 }, - "num_threads": 1 + "num_threads": 1, + "model_id": ".elser_model_2" <4> } } ------------------------------------------------------------ @@ -49,9 +50,12 @@ PUT _inference/sparse_embedding/my-elser-endpoint <1> <1> The task type is `sparse_embedding` in the path as the `elser` service will be used and ELSER creates sparse vectors. The `inference_id` is `my-elser-endpoint`. -<2> The `elser` service is used in this example. +<2> The `elasticsearch` service is used in this example. <3> This setting enables and configures {ml-docs}/ml-nlp-auto-scale.html#nlp-model-adaptive-allocations[adaptive allocations]. Adaptive allocations make it possible for ELSER to automatically scale up or down resources based on the current load on the process. +<4> The `model_id` must be the ID of one of the built-in ELSER models. +Valid values are `.elser_model_2` and `.elser_model_2_linux-x86_64`. +For further details, refer to the {ml-docs}/ml-nlp-elser.html[ELSER model documentation]. [NOTE] ==== @@ -282,4 +286,4 @@ query from the `semantic-embedding` index: * If you want to use `semantic_text` in hybrid search, refer to https://colab.research.google.com/github/elastic/elasticsearch-labs/blob/main/notebooks/search/09-semantic-text.ipynb[this notebook] for a step-by-step guide. * For more information on how to optimize your ELSER endpoints, refer to {ml-docs}/ml-nlp-elser.html#elser-recommendations[the ELSER recommendations] section in the model documentation. -* To learn more about model autoscaling, refer to the {ml-docs}/ml-nlp-auto-scale.html[trained model autoscaling] page. \ No newline at end of file +* To learn more about model autoscaling, refer to the {ml-docs}/ml-nlp-auto-scale.html[trained model autoscaling] page. From ec59206fb47bba653c45ed0e4e8d8199916856ba Mon Sep 17 00:00:00 2001 From: Max Hniebergall Date: Mon, 9 Dec 2024 11:09:34 -0500 Subject: [PATCH 24/37] Remove uneeded changelog --- docs/changelog/117826.yaml | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 docs/changelog/117826.yaml diff --git a/docs/changelog/117826.yaml b/docs/changelog/117826.yaml deleted file mode 100644 index 98ca84bc0bd96..0000000000000 --- a/docs/changelog/117826.yaml +++ /dev/null @@ -1,5 +0,0 @@ -pr: 117826 -summary: "Switch ELSER service to elasticsearch service in semantic search tutorial" -area: Docs -type: doc -issues: ["117829"] From 631519894f264b31e2902cb9258c3154656926c1 Mon Sep 17 00:00:00 2001 From: Niels Bauman <33722607+nielsbauman@users.noreply.github.com> Date: Mon, 9 Dec 2024 19:15:49 +0100 Subject: [PATCH 25/37] [8.16] Fix enrich cache size setting name (#117575) (#118286) * Fix enrich cache size setting name (#117575) The enrich cache size setting accidentally got renamed from `enrich.cache_size` to `enrich.cache.size` in #111412. This commit updates the enrich plugin to accept both names and deprecates the wrong name. * Remove `UpdateForV10` annotation --- docs/changelog/117575.yaml | 5 ++ .../xpack/enrich/EnrichPlugin.java | 57 ++++++++++++++++- .../xpack/enrich/EnrichPluginTests.java | 62 +++++++++++++++++++ 3 files changed, 121 insertions(+), 3 deletions(-) create mode 100644 docs/changelog/117575.yaml create mode 100644 x-pack/plugin/enrich/src/test/java/org/elasticsearch/xpack/enrich/EnrichPluginTests.java diff --git a/docs/changelog/117575.yaml b/docs/changelog/117575.yaml new file mode 100644 index 0000000000000..781444ae97be5 --- /dev/null +++ b/docs/changelog/117575.yaml @@ -0,0 +1,5 @@ +pr: 117575 +summary: Fix enrich cache size setting name +area: Ingest Node +type: bug +issues: [] diff --git a/x-pack/plugin/enrich/src/main/java/org/elasticsearch/xpack/enrich/EnrichPlugin.java b/x-pack/plugin/enrich/src/main/java/org/elasticsearch/xpack/enrich/EnrichPlugin.java index 1a68ada60b6f1..2a2d5211daadf 100644 --- a/x-pack/plugin/enrich/src/main/java/org/elasticsearch/xpack/enrich/EnrichPlugin.java +++ b/x-pack/plugin/enrich/src/main/java/org/elasticsearch/xpack/enrich/EnrichPlugin.java @@ -14,6 +14,8 @@ import org.elasticsearch.cluster.node.DiscoveryNodes; import org.elasticsearch.common.Strings; import org.elasticsearch.common.io.stream.NamedWriteableRegistry; +import org.elasticsearch.common.logging.DeprecationCategory; +import org.elasticsearch.common.logging.DeprecationLogger; import org.elasticsearch.common.settings.ClusterSettings; import org.elasticsearch.common.settings.IndexScopedSettings; import org.elasticsearch.common.settings.Setting; @@ -74,6 +76,8 @@ public class EnrichPlugin extends Plugin implements SystemIndexPlugin, IngestPlugin { + private static final DeprecationLogger deprecationLogger = DeprecationLogger.getLogger(EnrichPlugin.class); + static final Setting ENRICH_FETCH_SIZE_SETTING = Setting.intSetting( "enrich.fetch_size", 10000, @@ -126,9 +130,9 @@ public class EnrichPlugin extends Plugin implements SystemIndexPlugin, IngestPlu return String.valueOf(maxConcurrentRequests * maxLookupsPerRequest); }, val -> Setting.parseInt(val, 1, Integer.MAX_VALUE, QUEUE_CAPACITY_SETTING_NAME), Setting.Property.NodeScope); - public static final String CACHE_SIZE_SETTING_NAME = "enrich.cache.size"; + public static final String CACHE_SIZE_SETTING_NAME = "enrich.cache_size"; public static final Setting CACHE_SIZE = new Setting<>( - "enrich.cache.size", + CACHE_SIZE_SETTING_NAME, (String) null, (String s) -> FlatNumberOrByteSizeValue.parse( s, @@ -138,16 +142,58 @@ public class EnrichPlugin extends Plugin implements SystemIndexPlugin, IngestPlu Setting.Property.NodeScope ); + /** + * This setting solely exists because the original setting was accidentally renamed in + * https://github.com/elastic/elasticsearch/pull/111412. + */ + public static final String CACHE_SIZE_SETTING_BWC_NAME = "enrich.cache.size"; + public static final Setting CACHE_SIZE_BWC = new Setting<>( + CACHE_SIZE_SETTING_BWC_NAME, + (String) null, + (String s) -> FlatNumberOrByteSizeValue.parse( + s, + CACHE_SIZE_SETTING_BWC_NAME, + new FlatNumberOrByteSizeValue(ByteSizeValue.ofBytes((long) (0.01 * JvmInfo.jvmInfo().getConfiguredMaxHeapSize()))) + ), + Setting.Property.NodeScope, + Setting.Property.Deprecated + ); + private final Settings settings; private final EnrichCache enrichCache; + private final long maxCacheSize; public EnrichPlugin(final Settings settings) { this.settings = settings; - FlatNumberOrByteSizeValue maxSize = CACHE_SIZE.get(settings); + FlatNumberOrByteSizeValue maxSize; + if (settings.hasValue(CACHE_SIZE_SETTING_BWC_NAME)) { + if (settings.hasValue(CACHE_SIZE_SETTING_NAME)) { + throw new IllegalArgumentException( + Strings.format( + "Both [{}] and [{}] are set, please use [{}]", + CACHE_SIZE_SETTING_NAME, + CACHE_SIZE_SETTING_BWC_NAME, + CACHE_SIZE_SETTING_NAME + ) + ); + } + deprecationLogger.warn( + DeprecationCategory.SETTINGS, + "enrich_cache_size_name", + "The [{}] setting is deprecated and will be removed in a future version. Please use [{}] instead.", + CACHE_SIZE_SETTING_BWC_NAME, + CACHE_SIZE_SETTING_NAME + ); + maxSize = CACHE_SIZE_BWC.get(settings); + } else { + maxSize = CACHE_SIZE.get(settings); + } if (maxSize.byteSizeValue() != null) { this.enrichCache = new EnrichCache(maxSize.byteSizeValue()); + this.maxCacheSize = maxSize.byteSizeValue().getBytes(); } else { this.enrichCache = new EnrichCache(maxSize.flatNumber()); + this.maxCacheSize = maxSize.flatNumber(); } } @@ -286,6 +332,11 @@ public String getFeatureDescription() { return "Manages data related to Enrich policies"; } + // Visible for testing + long getMaxCacheSize() { + return maxCacheSize; + } + /** * A class that specifies either a flat (unit-less) number or a byte size value. */ diff --git a/x-pack/plugin/enrich/src/test/java/org/elasticsearch/xpack/enrich/EnrichPluginTests.java b/x-pack/plugin/enrich/src/test/java/org/elasticsearch/xpack/enrich/EnrichPluginTests.java new file mode 100644 index 0000000000000..07de0e0967448 --- /dev/null +++ b/x-pack/plugin/enrich/src/test/java/org/elasticsearch/xpack/enrich/EnrichPluginTests.java @@ -0,0 +1,62 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.enrich; + +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.test.ESTestCase; + +import java.util.List; + +public class EnrichPluginTests extends ESTestCase { + + public void testConstructWithByteSize() { + final var size = randomNonNegativeInt(); + Settings settings = Settings.builder().put(EnrichPlugin.CACHE_SIZE_SETTING_NAME, size + "b").build(); + EnrichPlugin plugin = new EnrichPlugin(settings); + assertEquals(size, plugin.getMaxCacheSize()); + } + + public void testConstructWithFlatNumber() { + final var size = randomNonNegativeInt(); + Settings settings = Settings.builder().put(EnrichPlugin.CACHE_SIZE_SETTING_NAME, size).build(); + EnrichPlugin plugin = new EnrichPlugin(settings); + assertEquals(size, plugin.getMaxCacheSize()); + } + + public void testConstructWithByteSizeBwc() { + final var size = randomNonNegativeInt(); + Settings settings = Settings.builder().put(EnrichPlugin.CACHE_SIZE_SETTING_BWC_NAME, size + "b").build(); + EnrichPlugin plugin = new EnrichPlugin(settings); + assertEquals(size, plugin.getMaxCacheSize()); + } + + public void testConstructWithFlatNumberBwc() { + final var size = randomNonNegativeInt(); + Settings settings = Settings.builder().put(EnrichPlugin.CACHE_SIZE_SETTING_BWC_NAME, size).build(); + EnrichPlugin plugin = new EnrichPlugin(settings); + assertEquals(size, plugin.getMaxCacheSize()); + } + + public void testConstructWithBothSettings() { + Settings settings = Settings.builder() + .put(EnrichPlugin.CACHE_SIZE_SETTING_NAME, randomNonNegativeInt()) + .put(EnrichPlugin.CACHE_SIZE_SETTING_BWC_NAME, randomNonNegativeInt()) + .build(); + assertThrows(IllegalArgumentException.class, () -> new EnrichPlugin(settings)); + } + + @Override + protected List filteredWarnings() { + final var warnings = super.filteredWarnings(); + warnings.add("[enrich.cache.size] setting was deprecated in Elasticsearch and will be removed in a future release."); + warnings.add( + "The [enrich.cache.size] setting is deprecated and will be removed in a future version. Please use [enrich.cache_size] instead." + ); + return warnings; + } +} From 226a8ceb8ef3956727b2f57e7af078bd36712051 Mon Sep 17 00:00:00 2001 From: Luca Cavanna Date: Mon, 9 Dec 2024 21:07:07 +0100 Subject: [PATCH 26/37] [8.16] Don't skip shards in coord rewrite if timestamp is an alias (#117271) (#117854) * Don't skip shards in coord rewrite if timestamp is an alias (#117271) The coordinator rewrite has logic to skip indices if the provided date range filter is not within the min and max range of all of its shards. This mechanism is enabled for event.ingested and @timestamp fields, against searchable snapshots. We have basic checks that such fields need to be of date field type, yet if they are defined as alias of a date field, their range will be empty, which indicates that the shards are empty, and the coord rewrite logic resolves the alias and ends up skipping shards that may have matching docs. This commit adds an explicit check that declares the range UNKNOWN instead of EMPTY in these circumstances. The same check is also performed in the coord rewrite logic, so that shards are no longer skipped by mistake. * fix compile --- docs/changelog/117271.yaml | 5 + .../index/query/RangeQueryBuilder.java | 1 + .../elasticsearch/index/shard/IndexShard.java | 4 +- .../indices/TimestampFieldMapperService.java | 6 +- ...pshotsCanMatchOnCoordinatorIntegTests.java | 124 +++++++++++++++++- 5 files changed, 130 insertions(+), 10 deletions(-) create mode 100644 docs/changelog/117271.yaml diff --git a/docs/changelog/117271.yaml b/docs/changelog/117271.yaml new file mode 100644 index 0000000000000..1a328279b9635 --- /dev/null +++ b/docs/changelog/117271.yaml @@ -0,0 +1,5 @@ +pr: 117271 +summary: Don't skip shards in coord rewrite if timestamp is an alias +area: Search +type: bug +issues: [] diff --git a/server/src/main/java/org/elasticsearch/index/query/RangeQueryBuilder.java b/server/src/main/java/org/elasticsearch/index/query/RangeQueryBuilder.java index f1081d06d649d..86de022da495e 100644 --- a/server/src/main/java/org/elasticsearch/index/query/RangeQueryBuilder.java +++ b/server/src/main/java/org/elasticsearch/index/query/RangeQueryBuilder.java @@ -438,6 +438,7 @@ public String getWriteableName() { protected MappedFieldType.Relation getRelation(final CoordinatorRewriteContext coordinatorRewriteContext) { final MappedFieldType fieldType = coordinatorRewriteContext.getFieldType(fieldName); if (fieldType instanceof final DateFieldMapper.DateFieldType dateFieldType) { + assert fieldName.equals(fieldType.name()); IndexLongFieldRange fieldRange = coordinatorRewriteContext.getFieldRange(fieldName); if (fieldRange.isComplete() == false || fieldRange == IndexLongFieldRange.EMPTY) { // if not all shards for this (frozen) index have reported ranges to cluster state, OR if they diff --git a/server/src/main/java/org/elasticsearch/index/shard/IndexShard.java b/server/src/main/java/org/elasticsearch/index/shard/IndexShard.java index c25fdb174bfcf..9959386e2ddfd 100644 --- a/server/src/main/java/org/elasticsearch/index/shard/IndexShard.java +++ b/server/src/main/java/org/elasticsearch/index/shard/IndexShard.java @@ -2258,8 +2258,8 @@ private ShardLongFieldRange determineShardLongFieldRange(String fieldName) { return ShardLongFieldRange.UNKNOWN; // no mapper service, no idea if the field even exists } final MappedFieldType mappedFieldType = mapperService().fieldType(fieldName); - if (mappedFieldType instanceof DateFieldMapper.DateFieldType == false) { - return ShardLongFieldRange.UNKNOWN; // field missing or not a date + if (mappedFieldType instanceof DateFieldMapper.DateFieldType == false || mappedFieldType.name().equals(fieldName) == false) { + return ShardLongFieldRange.UNKNOWN; // field is missing, an alias (as the field type has a different name) or not a date field } if (mappedFieldType.isIndexed() == false) { return ShardLongFieldRange.UNKNOWN; // range information missing diff --git a/server/src/main/java/org/elasticsearch/indices/TimestampFieldMapperService.java b/server/src/main/java/org/elasticsearch/indices/TimestampFieldMapperService.java index 026766671e5aa..158cc1f44b608 100644 --- a/server/src/main/java/org/elasticsearch/indices/TimestampFieldMapperService.java +++ b/server/src/main/java/org/elasticsearch/indices/TimestampFieldMapperService.java @@ -166,11 +166,13 @@ private static DateFieldRangeInfo fromMapperService(MapperService mapperService) DateFieldMapper.DateFieldType eventIngestedFieldType = null; MappedFieldType mappedFieldType = mapperService.fieldType(DataStream.TIMESTAMP_FIELD_NAME); - if (mappedFieldType instanceof DateFieldMapper.DateFieldType dateFieldType) { + if (mappedFieldType instanceof DateFieldMapper.DateFieldType dateFieldType + && dateFieldType.name().equals(DataStream.TIMESTAMP_FIELD_NAME)) { timestampFieldType = dateFieldType; } mappedFieldType = mapperService.fieldType(IndexMetadata.EVENT_INGESTED_FIELD_NAME); - if (mappedFieldType instanceof DateFieldMapper.DateFieldType dateFieldType) { + if (mappedFieldType instanceof DateFieldMapper.DateFieldType dateFieldType + && dateFieldType.name().equals(IndexMetadata.EVENT_INGESTED_FIELD_NAME)) { eventIngestedFieldType = dateFieldType; } if (timestampFieldType == null && eventIngestedFieldType == null) { diff --git a/x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsCanMatchOnCoordinatorIntegTests.java b/x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsCanMatchOnCoordinatorIntegTests.java index 8877f31ce9e39..69c5141fcef41 100644 --- a/x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsCanMatchOnCoordinatorIntegTests.java +++ b/x-pack/plugin/searchable-snapshots/src/internalClusterTest/java/org/elasticsearch/xpack/searchablesnapshots/SearchableSnapshotsCanMatchOnCoordinatorIntegTests.java @@ -15,6 +15,7 @@ import org.elasticsearch.action.search.SearchShardsGroup; import org.elasticsearch.action.search.SearchShardsRequest; import org.elasticsearch.action.search.SearchShardsResponse; +import org.elasticsearch.action.search.SearchType; import org.elasticsearch.action.search.TransportSearchShardsAction; import org.elasticsearch.blobcache.shared.SharedBlobCacheService; import org.elasticsearch.cluster.metadata.DataStream; @@ -1100,6 +1101,119 @@ public void testCanMatchSkipsPartiallyMountedIndicesWhenFrozenNodesUnavailable() } } + public void testTimestampAsAlias() throws Exception { + doTestCoordRewriteWithAliasField("@timestamp"); + } + + public void testEventIngestedAsAlias() throws Exception { + doTestCoordRewriteWithAliasField("event.ingested"); + } + + private void doTestCoordRewriteWithAliasField(String aliasFieldName) throws Exception { + internalCluster().startMasterOnlyNode(); + internalCluster().startCoordinatingOnlyNode(Settings.EMPTY); + final String dataNodeHoldingRegularIndex = internalCluster().startDataOnlyNode(); + final String dataNodeHoldingSearchableSnapshot = internalCluster().startDataOnlyNode(); + + String timestampFieldName = randomAlphaOfLengthBetween(3, 10); + String[] indices = new String[] { "index-0001", "index-0002" }; + for (String index : indices) { + Settings extraSettings = Settings.builder() + .put(INDEX_ROUTING_REQUIRE_GROUP_SETTING.getConcreteSettingForNamespace("_name").getKey(), dataNodeHoldingRegularIndex) + .build(); + + assertAcked( + indicesAdmin().prepareCreate(index) + .setMapping( + XContentFactory.jsonBuilder() + .startObject() + .startObject("properties") + + .startObject(timestampFieldName) + .field("type", "date") + .endObject() + + .startObject(aliasFieldName) + .field("type", "alias") + .field("path", timestampFieldName) + .endObject() + + .endObject() + .endObject() + ) + .setSettings(indexSettingsNoReplicas(1).put(INDEX_SOFT_DELETES_SETTING.getKey(), true).put(extraSettings)) + ); + } + ensureGreen(indices); + + for (String index : indices) { + final List indexRequestBuilders = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + indexRequestBuilders.add(prepareIndex(index).setSource(timestampFieldName, "2024-11-19T08:08:08Z")); + } + indexRandom(true, false, indexRequestBuilders); + + assertThat( + indicesAdmin().prepareForceMerge(index).setOnlyExpungeDeletes(true).setFlush(true).get().getFailedShards(), + equalTo(0) + ); + refresh(index); + forceMerge(); + } + + final String repositoryName = randomAlphaOfLength(10).toLowerCase(Locale.ROOT); + createRepository(repositoryName, "mock"); + + final SnapshotId snapshotId = createSnapshot(repositoryName, "snapshot-1", List.of(indices[0])).snapshotId(); + assertAcked(indicesAdmin().prepareDelete(indices[0])); + + // Block the repository for the node holding the searchable snapshot shards + // to delay its restore + blockDataNode(repositoryName, dataNodeHoldingSearchableSnapshot); + + // Force the searchable snapshot to be allocated in a particular node + Settings restoredIndexSettings = Settings.builder() + .put(INDEX_ROUTING_REQUIRE_GROUP_SETTING.getConcreteSettingForNamespace("_name").getKey(), dataNodeHoldingSearchableSnapshot) + .build(); + + String mountedIndex = indices[0] + "-mounted"; + final MountSearchableSnapshotRequest mountRequest = new MountSearchableSnapshotRequest( + TEST_REQUEST_TIMEOUT, + mountedIndex, + repositoryName, + snapshotId.getName(), + indices[0], + restoredIndexSettings, + Strings.EMPTY_ARRAY, + false, + randomFrom(MountSearchableSnapshotRequest.Storage.values()) + ); + client().execute(MountSearchableSnapshotAction.INSTANCE, mountRequest).actionGet(); + + // Allow the searchable snapshots to be finally mounted + unblockNode(repositoryName, dataNodeHoldingSearchableSnapshot); + waitUntilRecoveryIsDone(mountedIndex); + ensureGreen(mountedIndex); + + String[] fieldsToQuery = new String[] { timestampFieldName, aliasFieldName }; + for (String fieldName : fieldsToQuery) { + RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery(fieldName).from("2024-11-01T00:00:00.000000000Z", true); + SearchRequest request = new SearchRequest().searchType(SearchType.QUERY_THEN_FETCH) + .source(new SearchSourceBuilder().query(rangeQuery)); + if (randomBoolean()) { + // pre_filter_shard_size default to 1 because there are read-only indices in the mix. It does not hurt to force it though. + request.setPreFilterShardSize(1); + } + assertResponse(client().search(request), searchResponse -> { + assertThat(searchResponse.getSuccessfulShards(), equalTo(2)); + assertThat(searchResponse.getFailedShards(), equalTo(0)); + assertThat(searchResponse.getSkippedShards(), equalTo(0)); + assertThat(searchResponse.getTotalShards(), equalTo(2)); + assertThat(searchResponse.getHits().getTotalHits().value, equalTo(20L)); + }); + } + } + private void createIndexWithTimestampAndEventIngested(String indexName, int numShards, Settings extraSettings) throws IOException { assertAcked( indicesAdmin().prepareCreate(indexName) @@ -1148,8 +1262,7 @@ private void createIndexWithOnlyOneTimestampField(String timestampField, String ensureGreen(index); } - private void indexDocumentsWithOnlyOneTimestampField(String timestampField, String index, int docCount, String timestampTemplate) - throws Exception { + private void indexDocumentsWithOnlyOneTimestampField(String timestampField, String index, int docCount, String timestampTemplate) { final List indexRequestBuilders = new ArrayList<>(); for (int i = 0; i < docCount; i++) { indexRequestBuilders.add( @@ -1173,8 +1286,7 @@ private void indexDocumentsWithOnlyOneTimestampField(String timestampField, Stri forceMerge(); } - private void indexDocumentsWithTimestampAndEventIngestedDates(String indexName, int docCount, String timestampTemplate) - throws Exception { + private void indexDocumentsWithTimestampAndEventIngestedDates(String indexName, int docCount, String timestampTemplate) { final List indexRequestBuilders = new ArrayList<>(); for (int i = 0; i < docCount; i++) { @@ -1211,7 +1323,7 @@ private void indexDocumentsWithTimestampAndEventIngestedDates(String indexName, forceMerge(); } - private IndexMetadata getIndexMetadata(String indexName) { + private static IndexMetadata getIndexMetadata(String indexName) { return clusterAdmin().prepareState(TEST_REQUEST_TIMEOUT) .clear() .setMetadata(true) @@ -1222,7 +1334,7 @@ private IndexMetadata getIndexMetadata(String indexName) { .index(indexName); } - private void waitUntilRecoveryIsDone(String index) throws Exception { + private static void waitUntilRecoveryIsDone(String index) throws Exception { assertBusy(() -> { RecoveryResponse recoveryResponse = indicesAdmin().prepareRecoveries(index).get(); assertThat(recoveryResponse.hasRecoveries(), equalTo(true)); From 97eef8c36d4b443528568a571a4f9ddfd8d1c1a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20Zolt=C3=A1n=20Szab=C3=B3?= Date: Tue, 10 Dec 2024 14:41:21 +0100 Subject: [PATCH 27/37] [DOCS] Documents `dimensions` param for `openai` service of Inference API (#118317) (#118333) Co-authored-by: David Kyle --- docs/reference/inference/service-openai.asciidoc | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/docs/reference/inference/service-openai.asciidoc b/docs/reference/inference/service-openai.asciidoc index 21643133553e1..9211e2d08e88b 100644 --- a/docs/reference/inference/service-openai.asciidoc +++ b/docs/reference/inference/service-openai.asciidoc @@ -76,6 +76,12 @@ https://platform.openai.com/api-keys[API keys section]. include::inference-shared.asciidoc[tag=api-key-admonition] -- +`dimensions`::: +(Optional, integer) +The number of dimensions the resulting output embeddings should have. +Only supported in `text-embedding-3` and later models. +If not set the OpenAI defined default for the model is used. + `model_id`::: (Required, string) The name of the model to use for the {infer} task. @@ -134,8 +140,8 @@ Specifies the user issuing the request, which can be used for abuse detection. [[inference-example-openai]] ==== OpenAI service example -The following example shows how to create an {infer} endpoint called -`openai-embeddings` to perform a `text_embedding` task type. +The following example shows how to create an {infer} endpoint called `openai-embeddings` to perform a `text_embedding` task type. +The embeddings created by requests to this endpoint will have 128 dimensions. [source,console] ------------------------------------------------------------ @@ -144,14 +150,14 @@ PUT _inference/text_embedding/openai-embeddings "service": "openai", "service_settings": { "api_key": "", - "model_id": "text-embedding-ada-002" + "model_id": "text-embedding-3-small", + "dimensions": 128 } } ------------------------------------------------------------ // TEST[skip:TBD] -The next example shows how to create an {infer} endpoint called -`openai-completion` to perform a `completion` task type. +The next example shows how to create an {infer} endpoint called `openai-completion` to perform a `completion` task type. [source,console] ------------------------------------------------------------ From 210f9d22899fb3377a462f47239cc01d2a2cc49d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Istv=C3=A1n=20Zolt=C3=A1n=20Szab=C3=B3?= Date: Tue, 10 Dec 2024 15:45:59 +0100 Subject: [PATCH 28/37] [DOCS] Reviews docker examples. (#118339) (#118343) --- docs/reference/setup/install/docker.asciidoc | 31 ++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/docs/reference/setup/install/docker.asciidoc b/docs/reference/setup/install/docker.asciidoc index 58feb55f32e2f..8694d7f5b46c6 100644 --- a/docs/reference/setup/install/docker.asciidoc +++ b/docs/reference/setup/install/docker.asciidoc @@ -39,7 +39,7 @@ adjust memory usage in Docker Desktop by going to **Settings > Resources**. ---- docker network create elastic ---- - +// REVIEWED[DEC.10.24] . Pull the {es} Docker image. + -- @@ -52,10 +52,11 @@ endif::[] ---- docker pull {docker-image} ---- +// REVIEWED[DEC.10.24] -- . Optional: Install -https://docs.sigstore.dev/system_config/installation/[Cosign] for your +https://docs.sigstore.dev/cosign/system_config/installation/[Cosign] for your environment. Then use Cosign to verify the {es} image's signature. + [[docker-verify-signature]] @@ -64,6 +65,7 @@ environment. Then use Cosign to verify the {es} image's signature. wget https://artifacts.elastic.co/cosign.pub cosign verify --key cosign.pub {docker-image} ---- +// REVIEWED[DEC.10.24] + The `cosign` command prints the check results and the signature payload in JSON format: + @@ -75,6 +77,7 @@ The following checks were performed on each of these signatures: - Existence of the claims in the transparency log was verified offline - The signatures were verified against the specified public key ---- +// REVIEWED[DEC.10.24] . Start an {es} container. + @@ -82,6 +85,7 @@ The following checks were performed on each of these signatures: ---- docker run --name es01 --net elastic -p 9200:9200 -it -m 1GB {docker-image} ---- +// REVIEWED[DEC.10.24] + TIP: Use the `-m` flag to set a memory limit for the container. This removes the need to <>. @@ -95,6 +99,7 @@ If you intend to use the {ml} capabilities, then start the container with this c ---- docker run --name es01 --net elastic -p 9200:9200 -it -m 6GB -e "xpack.ml.use_auto_machine_memory_percent=true" {docker-image} ---- +// REVIEWED[DEC.10.24] The command prints the `elastic` user password and an enrollment token for {kib}. . Copy the generated `elastic` password and enrollment token. These credentials @@ -106,6 +111,7 @@ credentials using the following commands. docker exec -it es01 /usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic docker exec -it es01 /usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s kibana ---- +// REVIEWED[DEC.10.24] + We recommend storing the `elastic` password as an environment variable in your shell. Example: + @@ -113,6 +119,7 @@ We recommend storing the `elastic` password as an environment variable in your s ---- export ELASTIC_PASSWORD="your_password" ---- +// REVIEWED[DEC.10.24] . Copy the `http_ca.crt` SSL certificate from the container to your local machine. + @@ -120,6 +127,7 @@ export ELASTIC_PASSWORD="your_password" ---- docker cp es01:/usr/share/elasticsearch/config/certs/http_ca.crt . ---- +// REVIEWED[DEC.10.24] . Make a REST API call to {es} to ensure the {es} container is running. + @@ -128,6 +136,7 @@ docker cp es01:/usr/share/elasticsearch/config/certs/http_ca.crt . curl --cacert http_ca.crt -u elastic:$ELASTIC_PASSWORD https://localhost:9200 ---- // NOTCONSOLE +// REVIEWED[DEC.10.24] ===== Add more nodes @@ -137,6 +146,7 @@ curl --cacert http_ca.crt -u elastic:$ELASTIC_PASSWORD https://localhost:9200 ---- docker exec -it es01 /usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s node ---- +// REVIEWED[DEC.10.24] + The enrollment token is valid for 30 minutes. @@ -146,6 +156,7 @@ The enrollment token is valid for 30 minutes. ---- docker run -e ENROLLMENT_TOKEN="" --name es02 --net elastic -it -m 1GB {docker-image} ---- +// REVIEWED[DEC.10.24] . Call the <> to verify the node was added to the cluster. + @@ -154,6 +165,7 @@ docker run -e ENROLLMENT_TOKEN="" --name es02 --net elastic -it -m 1GB {d curl --cacert http_ca.crt -u elastic:$ELASTIC_PASSWORD https://localhost:9200/_cat/nodes ---- // NOTCONSOLE +// REVIEWED[DEC.10.24] [[run-kibana-docker]] ===== Run {kib} @@ -170,6 +182,7 @@ endif::[] ---- docker pull {kib-docker-image} ---- +// REVIEWED[DEC.10.24] -- . Optional: Verify the {kib} image's signature. @@ -179,6 +192,7 @@ docker pull {kib-docker-image} wget https://artifacts.elastic.co/cosign.pub cosign verify --key cosign.pub {kib-docker-image} ---- +// REVIEWED[DEC.10.24] . Start a {kib} container. + @@ -186,6 +200,7 @@ cosign verify --key cosign.pub {kib-docker-image} ---- docker run --name kib01 --net elastic -p 5601:5601 {kib-docker-image} ---- +// REVIEWED[DEC.10.24] . When {kib} starts, it outputs a unique generated link to the terminal. To access {kib}, open this link in a web browser. @@ -198,6 +213,7 @@ To regenerate the token, run: ---- docker exec -it es01 /usr/share/elasticsearch/bin/elasticsearch-create-enrollment-token -s kibana ---- +// REVIEWED[DEC.10.24] . Log in to {kib} as the `elastic` user with the password that was generated when you started {es}. @@ -208,6 +224,7 @@ To regenerate the password, run: ---- docker exec -it es01 /usr/share/elasticsearch/bin/elasticsearch-reset-password -u elastic ---- +// REVIEWED[DEC.10.24] [[remove-containers-docker]] ===== Remove containers @@ -226,6 +243,7 @@ docker rm es02 # Remove the {kib} container docker rm kib01 ---- +// REVIEWED[DEC.10.24] ===== Next steps @@ -306,6 +324,7 @@ ES_PORT=127.0.0.1:9200 ---- docker-compose up -d ---- +// REVIEWED[DEC.10.24] . After the cluster has started, open http://localhost:5601 in a web browser to access {kib}. @@ -321,6 +340,7 @@ is preserved and loaded when you restart the cluster with `docker-compose up`. ---- docker-compose down ---- +// REVIEWED[DEC.10.24] To delete the network, containers, and volumes when you stop the cluster, specify the `-v` option: @@ -329,6 +349,7 @@ specify the `-v` option: ---- docker-compose down -v ---- +// REVIEWED[DEC.10.24] ===== Next steps @@ -377,6 +398,7 @@ The `vm.max_map_count` setting must be set within the xhyve virtual machine: -------------------------------------------- screen ~/Library/Containers/com.docker.docker/Data/vms/0/tty -------------------------------------------- +// REVIEWED[DEC.10.24] . Press enter and use `sysctl` to configure `vm.max_map_count`: + @@ -494,6 +516,7 @@ To check the Docker daemon defaults for ulimits, run: -------------------------------------------- docker run --rm {docker-image} /bin/bash -c 'ulimit -Hn && ulimit -Sn && ulimit -Hu && ulimit -Su' -------------------------------------------- +// REVIEWED[DEC.10.24] If needed, adjust them in the Daemon or override them per container. For example, when using `docker run`, set: @@ -502,6 +525,7 @@ For example, when using `docker run`, set: -------------------------------------------- --ulimit nofile=65535:65535 -------------------------------------------- +// REVIEWED[DEC.10.24] ===== Disable swapping @@ -518,6 +542,7 @@ When using `docker run`, you can specify: ---- -e "bootstrap.memory_lock=true" --ulimit memlock=-1:-1 ---- +// REVIEWED[DEC.10.24] ===== Randomize published ports @@ -545,6 +570,7 @@ environment variable. For example, to use 1GB, use the following command. ---- docker run -e ES_JAVA_OPTS="-Xms1g -Xmx1g" -e ENROLLMENT_TOKEN="" --name es01 -p 9200:9200 --net elastic -it {docker-image} ---- +// REVIEWED[DEC.10.24] The `ES_JAVA_OPTS` variable overrides all other JVM options. We do not recommend using `ES_JAVA_OPTS` in production. @@ -616,6 +642,7 @@ If you mount the password file to `/run/secrets/bootstrapPassword.txt`, specify: -------------------------------------------- -e ELASTIC_PASSWORD_FILE=/run/secrets/bootstrapPassword.txt -------------------------------------------- +// REVIEWED[DEC.10.24] You can override the default command for the image to pass {es} configuration parameters as command line options. For example: From c7ec10d4248a9a5c58dad44a2fb18c508d6f0768 Mon Sep 17 00:00:00 2001 From: Adam Demjen Date: Tue, 10 Dec 2024 13:39:24 -0500 Subject: [PATCH 29/37] [8.16] Update sparse text embeddings API route for Inference Service (#118367) * Update sparse text embeddings API route for Inference Service * Update docs/changelog/118367.yaml --- docs/changelog/118367.yaml | 5 +++++ .../ElasticInferenceServiceSparseEmbeddingsModel.java | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 docs/changelog/118367.yaml diff --git a/docs/changelog/118367.yaml b/docs/changelog/118367.yaml new file mode 100644 index 0000000000000..d37a4433fa929 --- /dev/null +++ b/docs/changelog/118367.yaml @@ -0,0 +1,5 @@ +pr: 118367 +summary: "[8.16] Update sparse text embeddings API route for Inference Service" +area: Inference +type: enhancement +issues: [] diff --git a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/ElasticInferenceServiceSparseEmbeddingsModel.java b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/ElasticInferenceServiceSparseEmbeddingsModel.java index 731153b3d5dbc..9b5baac09ef8e 100644 --- a/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/ElasticInferenceServiceSparseEmbeddingsModel.java +++ b/x-pack/plugin/inference/src/main/java/org/elasticsearch/xpack/inference/services/elastic/ElasticInferenceServiceSparseEmbeddingsModel.java @@ -108,6 +108,6 @@ private URI createUri() throws URISyntaxException { default -> throw new IllegalArgumentException("Unsupported model for EIS [" + modelId + "]"); } - return new URI(elasticInferenceServiceComponents().eisGatewayUrl() + "/api/v1/sparse-text-embedding/" + modelIdUriPath); + return new URI(elasticInferenceServiceComponents().eisGatewayUrl() + "/api/v1/sparse-text-embeddings/" + modelIdUriPath); } } From 78fb1ec9706ec6e481cb5fde75d70f45267e1de0 Mon Sep 17 00:00:00 2001 From: Joe Gallo Date: Tue, 10 Dec 2024 16:59:16 -0500 Subject: [PATCH 30/37] Fix log message format bugs (#118354) (#118387) --- docs/changelog/118354.yaml | 5 +++++ .../org/elasticsearch/repositories/s3/S3BlobContainer.java | 2 +- .../upgrades/FullClusterRestartDownsampleIT.java | 2 +- .../java/org/elasticsearch/upgrades/DownsampleIT.java | 2 +- .../java/org/elasticsearch/index/seqno/RetentionLeaseIT.java | 2 +- .../xpack/downsample/DownsampleShardIndexer.java | 2 +- .../java/org/elasticsearch/xpack/enrich/EnrichPlugin.java | 2 +- 7 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 docs/changelog/118354.yaml diff --git a/docs/changelog/118354.yaml b/docs/changelog/118354.yaml new file mode 100644 index 0000000000000..e2d72db121276 --- /dev/null +++ b/docs/changelog/118354.yaml @@ -0,0 +1,5 @@ +pr: 118354 +summary: Fix log message format bugs +area: Ingest Node +type: bug +issues: [] diff --git a/modules/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3BlobContainer.java b/modules/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3BlobContainer.java index c1d5dd0c13887..2454a3733d70c 100644 --- a/modules/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3BlobContainer.java +++ b/modules/repository-s3/src/main/java/org/elasticsearch/repositories/s3/S3BlobContainer.java @@ -987,7 +987,7 @@ public void onResponse(Void unused) { // should be no other processes interacting with the repository. logger.warn( Strings.format( - "failed to clean up multipart upload [{}] of blob [{}][{}][{}]", + "failed to clean up multipart upload [%s] of blob [%s][%s][%s]", abortMultipartUploadRequest.getUploadId(), blobStore.getRepositoryMetadata().name(), abortMultipartUploadRequest.getBucketName(), diff --git a/qa/full-cluster-restart/src/javaRestTest/java/org/elasticsearch/upgrades/FullClusterRestartDownsampleIT.java b/qa/full-cluster-restart/src/javaRestTest/java/org/elasticsearch/upgrades/FullClusterRestartDownsampleIT.java index 3a983dbd058df..7145f4b1d2d84 100644 --- a/qa/full-cluster-restart/src/javaRestTest/java/org/elasticsearch/upgrades/FullClusterRestartDownsampleIT.java +++ b/qa/full-cluster-restart/src/javaRestTest/java/org/elasticsearch/upgrades/FullClusterRestartDownsampleIT.java @@ -264,7 +264,7 @@ private String getRollupIndexName() throws IOException { if (asMap.size() == 1) { return (String) asMap.keySet().toArray()[0]; } - logger.warn("--> No matching rollup name for path [%s]", endpoint); + logger.warn("--> No matching rollup name for path [{}]", endpoint); return null; } diff --git a/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/DownsampleIT.java b/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/DownsampleIT.java index 70658da70eb80..f963bedf5d188 100644 --- a/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/DownsampleIT.java +++ b/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/DownsampleIT.java @@ -239,7 +239,7 @@ private String getRollupIndexName() throws IOException { if (asMap.size() == 1) { return (String) asMap.keySet().toArray()[0]; } - logger.warn("--> No matching rollup name for path [%s]", endpoint); + logger.warn("--> No matching rollup name for path [{}]", endpoint); return null; } diff --git a/server/src/internalClusterTest/java/org/elasticsearch/index/seqno/RetentionLeaseIT.java b/server/src/internalClusterTest/java/org/elasticsearch/index/seqno/RetentionLeaseIT.java index 1c5d67d1fa40a..70689dc689673 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/index/seqno/RetentionLeaseIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/index/seqno/RetentionLeaseIT.java @@ -336,7 +336,7 @@ public void testRetentionLeasesSyncOnRecovery() throws Exception { .getShardOrNull(new ShardId(resolveIndex("index"), 0)); final int length = randomIntBetween(1, 8); final Map currentRetentionLeases = new LinkedHashMap<>(); - logger.info("adding retention [{}}] leases", length); + logger.info("adding retention [{}] leases", length); for (int i = 0; i < length; i++) { final String id = randomValueOtherThanMany(currentRetentionLeases.keySet()::contains, () -> randomAlphaOfLength(8)); final long retainingSequenceNumber = randomLongBetween(0, Long.MAX_VALUE); diff --git a/x-pack/plugin/downsample/src/main/java/org/elasticsearch/xpack/downsample/DownsampleShardIndexer.java b/x-pack/plugin/downsample/src/main/java/org/elasticsearch/xpack/downsample/DownsampleShardIndexer.java index 81aea221ebb4f..50149ec2cbe58 100644 --- a/x-pack/plugin/downsample/src/main/java/org/elasticsearch/xpack/downsample/DownsampleShardIndexer.java +++ b/x-pack/plugin/downsample/src/main/java/org/elasticsearch/xpack/downsample/DownsampleShardIndexer.java @@ -386,7 +386,7 @@ public void collect(int docId, long owningBucketOrd) throws IOException { if (logger.isTraceEnabled()) { logger.trace( - "Doc: [{}] - _tsid: [{}], @timestamp: [{}}] -> downsample bucket ts: [{}]", + "Doc: [{}] - _tsid: [{}], @timestamp: [{}] -> downsample bucket ts: [{}]", docId, DocValueFormat.TIME_SERIES_ID.format(tsidHash), timestampFormat.format(timestamp), diff --git a/x-pack/plugin/enrich/src/main/java/org/elasticsearch/xpack/enrich/EnrichPlugin.java b/x-pack/plugin/enrich/src/main/java/org/elasticsearch/xpack/enrich/EnrichPlugin.java index 2a2d5211daadf..3dc0b83dd5b5c 100644 --- a/x-pack/plugin/enrich/src/main/java/org/elasticsearch/xpack/enrich/EnrichPlugin.java +++ b/x-pack/plugin/enrich/src/main/java/org/elasticsearch/xpack/enrich/EnrichPlugin.java @@ -170,7 +170,7 @@ public EnrichPlugin(final Settings settings) { if (settings.hasValue(CACHE_SIZE_SETTING_NAME)) { throw new IllegalArgumentException( Strings.format( - "Both [{}] and [{}] are set, please use [{}]", + "Both [%s] and [%s] are set, please use [%s]", CACHE_SIZE_SETTING_NAME, CACHE_SIZE_SETTING_BWC_NAME, CACHE_SIZE_SETTING_NAME From e205f3e4afd65626f0cd8840159f0d95d643cb4f Mon Sep 17 00:00:00 2001 From: Nik Everett Date: Tue, 10 Dec 2024 17:33:47 -0500 Subject: [PATCH 31/37] ESQL: Opt into extra data stream resolution (#118378) (#118389) * ESQL: Opt into extra data stream resolution This opts ESQL's data node request into extra data stream resolution. * Update docs/changelog/118378.yaml --- docs/changelog/118378.yaml | 5 + .../xpack/esql/EsqlSecurityIT.java | 129 ++++++++++++++++++ .../src/javaRestTest/resources/roles.yml | 54 ++++++++ .../xpack/esql/plugin/DataNodeRequest.java | 5 + 4 files changed, 193 insertions(+) create mode 100644 docs/changelog/118378.yaml diff --git a/docs/changelog/118378.yaml b/docs/changelog/118378.yaml new file mode 100644 index 0000000000000..d6c388b671968 --- /dev/null +++ b/docs/changelog/118378.yaml @@ -0,0 +1,5 @@ +pr: 118378 +summary: Opt into extra data stream resolution +area: ES|QL +type: bug +issues: [] diff --git a/x-pack/plugin/esql/qa/security/src/javaRestTest/java/org/elasticsearch/xpack/esql/EsqlSecurityIT.java b/x-pack/plugin/esql/qa/security/src/javaRestTest/java/org/elasticsearch/xpack/esql/EsqlSecurityIT.java index f8f1fe872711d..9566aeb8f28dc 100644 --- a/x-pack/plugin/esql/qa/security/src/javaRestTest/java/org/elasticsearch/xpack/esql/EsqlSecurityIT.java +++ b/x-pack/plugin/esql/qa/security/src/javaRestTest/java/org/elasticsearch/xpack/esql/EsqlSecurityIT.java @@ -34,6 +34,7 @@ import java.util.Locale; import java.util.Map; +import static org.elasticsearch.test.ListMatcher.matchesList; import static org.elasticsearch.test.MapMatcher.assertMap; import static org.elasticsearch.test.MapMatcher.matchesMap; import static org.hamcrest.Matchers.containsString; @@ -56,6 +57,11 @@ public class EsqlSecurityIT extends ESRestTestCase { .user("metadata1_read2", "x-pack-test-password", "metadata1_read2", false) .user("alias_user1", "x-pack-test-password", "alias_user1", false) .user("alias_user2", "x-pack-test-password", "alias_user2", false) + .user("logs_foo_all", "x-pack-test-password", "logs_foo_all", false) + .user("logs_foo_16_only", "x-pack-test-password", "logs_foo_16_only", false) + .user("logs_foo_after_2021", "x-pack-test-password", "logs_foo_after_2021", false) + .user("logs_foo_after_2021_pattern", "x-pack-test-password", "logs_foo_after_2021_pattern", false) + .user("logs_foo_after_2021_alias", "x-pack-test-password", "logs_foo_after_2021_alias", false) .build(); @Override @@ -342,6 +348,14 @@ public void testDocumentLevelSecurity() throws Exception { assertThat(respMap.get("values"), equalTo(List.of(List.of(10.0)))); } + public void testDocumentLevelSecurityFromStar() throws Exception { + Response resp = runESQLCommand("user3", "from in*x | stats sum=sum(value)"); + assertOK(resp); + Map respMap = entityAsMap(resp); + assertThat(respMap.get("columns"), equalTo(List.of(Map.of("name", "sum", "type", "double")))); + assertThat(respMap.get("values"), equalTo(List.of(List.of(10.0)))); + } + public void testFieldLevelSecurityAllow() throws Exception { Response resp = runESQLCommand("fls_user", "FROM index* | SORT value | LIMIT 1"); assertOK(resp); @@ -545,6 +559,22 @@ private void removeEnrichPolicy() throws Exception { client().performRequest(new Request("DELETE", "_enrich/policy/songs")); } + public void testDataStream() throws IOException { + createDataStream(); + MapMatcher twoResults = matchesMap().extraOk().entry("values", matchesList().item(matchesList().item(2))); + MapMatcher oneResult = matchesMap().extraOk().entry("values", matchesList().item(matchesList().item(1))); + assertMap(entityAsMap(runESQLCommand("logs_foo_all", "FROM logs-foo | STATS COUNT(*)")), twoResults); + assertMap(entityAsMap(runESQLCommand("logs_foo_16_only", "FROM logs-foo | STATS COUNT(*)")), oneResult); + assertMap(entityAsMap(runESQLCommand("logs_foo_after_2021", "FROM logs-foo | STATS COUNT(*)")), oneResult); + assertMap(entityAsMap(runESQLCommand("logs_foo_after_2021_pattern", "FROM logs-foo | STATS COUNT(*)")), oneResult); + assertMap(entityAsMap(runESQLCommand("logs_foo_after_2021_alias", "FROM alias-foo | STATS COUNT(*)")), oneResult); + assertMap(entityAsMap(runESQLCommand("logs_foo_all", "FROM logs-* | STATS COUNT(*)")), twoResults); + assertMap(entityAsMap(runESQLCommand("logs_foo_16_only", "FROM logs-* | STATS COUNT(*)")), oneResult); + assertMap(entityAsMap(runESQLCommand("logs_foo_after_2021", "FROM logs-* | STATS COUNT(*)")), oneResult); + assertMap(entityAsMap(runESQLCommand("logs_foo_after_2021_pattern", "FROM logs-* | STATS COUNT(*)")), oneResult); + assertMap(entityAsMap(runESQLCommand("logs_foo_after_2021_alias", "FROM alias-* | STATS COUNT(*)")), oneResult); + } + protected Response runESQLCommand(String user, String command) throws IOException { if (command.toLowerCase(Locale.ROOT).contains("limit") == false) { // add a (high) limit to avoid warnings on default limit @@ -592,4 +622,103 @@ static Settings randomPragmas() { } return settings.build(); } + + private void createDataStream() throws IOException { + createDataStreamPolicy(); + createDataStreamComponentTemplate(); + createDataStreamIndexTemplate(); + createDataStreamDocuments(); + createDataStreamAlias(); + } + + private void createDataStreamPolicy() throws IOException { + Request request = new Request("PUT", "_ilm/policy/my-lifecycle-policy"); + request.setJsonEntity(""" + { + "policy": { + "phases": { + "hot": { + "actions": { + "rollover": { + "max_primary_shard_size": "50gb" + } + } + }, + "delete": { + "min_age": "735d", + "actions": { + "delete": {} + } + } + } + } + }"""); + client().performRequest(request); + } + + private void createDataStreamComponentTemplate() throws IOException { + Request request = new Request("PUT", "_component_template/my-template"); + request.setJsonEntity(""" + { + "template": { + "settings": { + "index.lifecycle.name": "my-lifecycle-policy" + }, + "mappings": { + "properties": { + "@timestamp": { + "type": "date", + "format": "date_optional_time||epoch_millis" + }, + "data_stream": { + "properties": { + "namespace": {"type": "keyword"} + } + } + } + } + } + }"""); + client().performRequest(request); + } + + private void createDataStreamIndexTemplate() throws IOException { + Request request = new Request("PUT", "_index_template/my-index-template"); + request.setJsonEntity(""" + { + "index_patterns": ["logs-*"], + "data_stream": {}, + "composed_of": ["my-template"], + "priority": 500 + }"""); + client().performRequest(request); + } + + private void createDataStreamDocuments() throws IOException { + Request request = new Request("POST", "logs-foo/_bulk"); + request.addParameter("refresh", ""); + request.setJsonEntity(""" + { "create" : {} } + { "@timestamp": "2099-05-06T16:21:15.000Z", "data_stream": {"namespace": "16"} } + { "create" : {} } + { "@timestamp": "2001-05-06T16:21:15.000Z", "data_stream": {"namespace": "17"} } + """); + assertMap(entityAsMap(client().performRequest(request)), matchesMap().extraOk().entry("errors", false)); + } + + private void createDataStreamAlias() throws IOException { + Request request = new Request("PUT", "_alias"); + request.setJsonEntity(""" + { + "actions": [ + { + "add": { + "index": "logs-foo", + "alias": "alias-foo" + } + } + ] + }"""); + assertMap(entityAsMap(client().performRequest(request)), matchesMap().extraOk().entry("errors", false)); + } } diff --git a/x-pack/plugin/esql/qa/security/src/javaRestTest/resources/roles.yml b/x-pack/plugin/esql/qa/security/src/javaRestTest/resources/roles.yml index 5c0164782d181..365a072edb74e 100644 --- a/x-pack/plugin/esql/qa/security/src/javaRestTest/resources/roles.yml +++ b/x-pack/plugin/esql/qa/security/src/javaRestTest/resources/roles.yml @@ -92,3 +92,57 @@ fls_user: privileges: [ 'read' ] field_security: grant: [ value ] + +logs_foo_all: + cluster: [] + indices: + - names: [ 'logs-foo' ] + privileges: [ 'read' ] + +logs_foo_16_only: + cluster: [] + indices: + - names: [ 'logs-foo' ] + privileges: [ 'read' ] + query: | + { + "term": { + "data_stream.namespace": "16" + } + } + +logs_foo_after_2021: + cluster: [] + indices: + - names: [ 'logs-foo' ] + privileges: [ 'read' ] + query: | + { + "range": { + "@timestamp": {"gte": "2021-01-01T00:00:00"} + } + } + +logs_foo_after_2021_pattern: + cluster: [] + indices: + - names: [ 'logs-*' ] + privileges: [ 'read' ] + query: | + { + "range": { + "@timestamp": {"gte": "2021-01-01T00:00:00"} + } + } + +logs_foo_after_2021_alias: + cluster: [] + indices: + - names: [ 'alias-foo' ] + privileges: [ 'read' ] + query: | + { + "range": { + "@timestamp": {"gte": "2021-01-01T00:00:00"} + } + } diff --git a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/DataNodeRequest.java b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/DataNodeRequest.java index 8f890e63bf54e..f9399d3e2addd 100644 --- a/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/DataNodeRequest.java +++ b/x-pack/plugin/esql/src/main/java/org/elasticsearch/xpack/esql/plugin/DataNodeRequest.java @@ -118,6 +118,11 @@ public IndicesRequest indices(String... indices) { return this; } + @Override + public boolean includeDataStreams() { + return true; + } + @Override public IndicesOptions indicesOptions() { return indicesOptions; From 129645628de0968014b3266b5af32b0461414708 Mon Sep 17 00:00:00 2001 From: Chris Hegarty <62058229+ChrisHegarty@users.noreply.github.com> Date: Tue, 10 Dec 2024 16:59:44 +0000 Subject: [PATCH 32/37] Improve logging of native vector scorer - vec_caps (#118325) (#118356) This commit adds logging of the system's vector capability check, to help with diagnosing whether AVX2 or AVX 512 will be used. --- .../org/elasticsearch/nativeaccess/jdk/JdkVectorLibrary.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libs/native/src/main21/java/org/elasticsearch/nativeaccess/jdk/JdkVectorLibrary.java b/libs/native/src/main21/java/org/elasticsearch/nativeaccess/jdk/JdkVectorLibrary.java index 2fccb27fa6e6c..3277cb8f8e6c7 100644 --- a/libs/native/src/main21/java/org/elasticsearch/nativeaccess/jdk/JdkVectorLibrary.java +++ b/libs/native/src/main21/java/org/elasticsearch/nativeaccess/jdk/JdkVectorLibrary.java @@ -9,6 +9,8 @@ package org.elasticsearch.nativeaccess.jdk; +import org.elasticsearch.logging.LogManager; +import org.elasticsearch.logging.Logger; import org.elasticsearch.nativeaccess.VectorSimilarityFunctions; import org.elasticsearch.nativeaccess.lib.LoaderHelper; import org.elasticsearch.nativeaccess.lib.VectorLibrary; @@ -25,6 +27,8 @@ public final class JdkVectorLibrary implements VectorLibrary { + static final Logger logger = LogManager.getLogger(JdkVectorLibrary.class); + static final MethodHandle dot7u$mh; static final MethodHandle sqr7u$mh; @@ -36,6 +40,7 @@ public final class JdkVectorLibrary implements VectorLibrary { try { int caps = (int) vecCaps$mh.invokeExact(); + logger.info("vec_caps=" + caps); if (caps != 0) { if (caps == 2) { dot7u$mh = downcallHandle( From e355d989b643f8691fbcc6493ed1ffe00ff7e30b Mon Sep 17 00:00:00 2001 From: David Kyle Date: Wed, 11 Dec 2024 11:18:10 +0000 Subject: [PATCH 33/37] [DOCS][ML] Use elasticsearch service instead of deprecated elser service in tutorials (#118007) (#118440) --- .../tab-widgets/inference-api/infer-api-task.asciidoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/reference/tab-widgets/inference-api/infer-api-task.asciidoc b/docs/reference/tab-widgets/inference-api/infer-api-task.asciidoc index 02995591d9c8a..227eb774a4a9f 100644 --- a/docs/reference/tab-widgets/inference-api/infer-api-task.asciidoc +++ b/docs/reference/tab-widgets/inference-api/infer-api-task.asciidoc @@ -36,7 +36,7 @@ the `cosine` measures are equivalent. ------------------------------------------------------------ PUT _inference/sparse_embedding/elser_embeddings <1> { - "service": "elser", + "service": "elasticsearch", "service_settings": { "num_allocations": 1, "num_threads": 1 @@ -206,7 +206,7 @@ PUT _inference/text_embedding/google_vertex_ai_embeddings <1> <2> A valid service account in JSON format for the Google Vertex AI API. <3> For the list of the available models, refer to the https://cloud.google.com/vertex-ai/generative-ai/docs/model-reference/text-embeddings-api[Text embeddings API] page. <4> The name of the location to use for the {infer} task. Refer to https://cloud.google.com/vertex-ai/generative-ai/docs/learn/locations[Generative AI on Vertex AI locations] for available locations. -<5> The name of the project to use for the {infer} task. +<5> The name of the project to use for the {infer} task. // end::google-vertex-ai[] From dda00b7176517d4ca7e1ff3ae52376a69aa2e664 Mon Sep 17 00:00:00 2001 From: Mike Pellegrini Date: Wed, 11 Dec 2024 09:23:44 -0500 Subject: [PATCH 34/37] Restore original "is within leaf" value in SparseVectorFieldMapper (#118380) (#118456) --- docs/changelog/118380.yaml | 5 +++++ .../index/mapper/vectors/SparseVectorFieldMapper.java | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) create mode 100644 docs/changelog/118380.yaml diff --git a/docs/changelog/118380.yaml b/docs/changelog/118380.yaml new file mode 100644 index 0000000000000..8b26c871fb172 --- /dev/null +++ b/docs/changelog/118380.yaml @@ -0,0 +1,5 @@ +pr: 118380 +summary: Restore original "is within leaf" value in `SparseVectorFieldMapper` +area: Mapping +type: bug +issues: [] diff --git a/server/src/main/java/org/elasticsearch/index/mapper/vectors/SparseVectorFieldMapper.java b/server/src/main/java/org/elasticsearch/index/mapper/vectors/SparseVectorFieldMapper.java index d0a8dfae4f242..19ae649c12a83 100644 --- a/server/src/main/java/org/elasticsearch/index/mapper/vectors/SparseVectorFieldMapper.java +++ b/server/src/main/java/org/elasticsearch/index/mapper/vectors/SparseVectorFieldMapper.java @@ -171,6 +171,7 @@ public void parse(DocumentParserContext context) throws IOException { ); } + final boolean isWithinLeaf = context.path().isWithinLeafObject(); String feature = null; try { // make sure that we don't expand dots in field names while parsing @@ -205,7 +206,7 @@ public void parse(DocumentParserContext context) throws IOException { context.addToFieldNames(fieldType().name()); } } finally { - context.path().setWithinLeafObject(false); + context.path().setWithinLeafObject(isWithinLeaf); } } From 75c4721cc3bb007b618a3b8b7b70c8cdf365fe10 Mon Sep 17 00:00:00 2001 From: Liam Thompson <32779855+leemthompo@users.noreply.github.com> Date: Wed, 11 Dec 2024 16:50:55 +0100 Subject: [PATCH 35/37] [DOCS][101] Aggregations quickstart tutorial (#116251) (#118470) (cherry picked from commit 56e1ca52ea38671a320c9e9421fe3cb8fe5f15e3) # Conflicts: # docs/reference/quickstart/index.asciidoc --- .../quickstart/aggs-tutorial.asciidoc | 2184 +++++++++++++++++ docs/reference/quickstart/index.asciidoc | 2 + 2 files changed, 2186 insertions(+) create mode 100644 docs/reference/quickstart/aggs-tutorial.asciidoc diff --git a/docs/reference/quickstart/aggs-tutorial.asciidoc b/docs/reference/quickstart/aggs-tutorial.asciidoc new file mode 100644 index 0000000000000..0a8494c3eb75d --- /dev/null +++ b/docs/reference/quickstart/aggs-tutorial.asciidoc @@ -0,0 +1,2184 @@ +[[aggregations-tutorial]] +== Analyze eCommerce data with aggregations using Query DSL +++++ +Basics: Analyze eCommerce data with aggregations +++++ + +This hands-on tutorial shows you how to analyze eCommerce data using {es} <> with the `_search` API and Query DSL. + +You'll learn how to: + +* Calculate key business metrics such as average order value +* Analyze sales patterns over time +* Compare performance across product categories +* Track moving averages and cumulative totals + +[discrete] +[[aggregations-tutorial-requirements]] +=== Requirements + +You'll need: + +. A running instance of <>, either on {serverless-full} or together with {kib} on Elastic Cloud Hosted/Self Managed deployments. +** If you don't have a deployment, you can run the following command in your terminal to set up a <>: ++ +[source,sh] +---- +curl -fsSL https://elastic.co/start-local | sh +---- +// NOTCONSOLE +. The {kibana-ref}/get-started.html#gs-get-data-into-kibana[sample eCommerce data] loaded into {es}. To load sample data follow these steps in your UI: +* Open the *Integrations* pages by searching in the global search field. +* Search for `sample data` in the **Integrations** search field. +* Open the *Sample data* page. +* Select the *Other sample data sets* collapsible. +* Add the *Sample eCommerce orders* data set. +This will create and populate an index called `kibana_sample_data_ecommerce`. + +[discrete] +[[aggregations-tutorial-inspect-data]] +=== Inspect index structure + +Before we start analyzing the data, let's examine the structure of the documents in our sample eCommerce index. Run this command to see the field <>: + +[source,console] +---- +GET kibana_sample_data_ecommerce/_mapping +---- +// TEST[skip:Using Kibana sample data] + +The response shows the field mappings for the `kibana_sample_data_ecommerce` index. + +.Example response +[%collapsible] +============== +[source,console-response] +---- +{ + "kibana_sample_data_ecommerce": { + "mappings": { + "properties": { + "category": { + "type": "text", + "fields": { <1> + "keyword": { + "type": "keyword" + } + } + }, + "currency": { + "type": "keyword" + }, + "customer_birth_date": { + "type": "date" + }, + "customer_first_name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "customer_full_name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "customer_gender": { + "type": "keyword" + }, + "customer_id": { + "type": "keyword" + }, + "customer_last_name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "customer_phone": { + "type": "keyword" + }, + "day_of_week": { + "type": "keyword" + }, + "day_of_week_i": { + "type": "integer" + }, + "email": { + "type": "keyword" + }, + "event": { + "properties": { + "dataset": { + "type": "keyword" + } + } + }, + "geoip": { + "properties": { <2> + "city_name": { + "type": "keyword" + }, + "continent_name": { + "type": "keyword" + }, + "country_iso_code": { + "type": "keyword" + }, + "location": { + "type": "geo_point" <3> + }, + "region_name": { + "type": "keyword" + } + } + }, + "manufacturer": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + }, + "order_date": { + "type": "date" + }, + "order_id": { + "type": "keyword" + }, + "products": { + "properties": { <4> + "_id": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword", + "ignore_above": 256 + } + } + }, + "base_price": { + "type": "half_float" + }, + "base_unit_price": { + "type": "half_float" + }, + "category": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + }, + "created_on": { + "type": "date" + }, + "discount_amount": { + "type": "half_float" + }, + "discount_percentage": { + "type": "half_float" + }, + "manufacturer": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + } + }, + "min_price": { + "type": "half_float" + }, + "price": { + "type": "half_float" + }, + "product_id": { + "type": "long" + }, + "product_name": { + "type": "text", + "fields": { + "keyword": { + "type": "keyword" + } + }, + "analyzer": "english" + }, + "quantity": { + "type": "integer" + }, + "sku": { + "type": "keyword" + }, + "tax_amount": { + "type": "half_float" + }, + "taxful_price": { + "type": "half_float" + }, + "taxless_price": { + "type": "half_float" + }, + "unit_discount_amount": { + "type": "half_float" + } + } + }, + "sku": { + "type": "keyword" + }, + "taxful_total_price": { + "type": "half_float" + }, + "taxless_total_price": { + "type": "half_float" + }, + "total_quantity": { + "type": "integer" + }, + "total_unique_products": { + "type": "integer" + }, + "type": { + "type": "keyword" + }, + "user": { + "type": "keyword" + } + } + } + } +} +---- +<1> `fields`: Multi-field mapping that allows both full text and exact matching +<2> `geoip.properties`: Object type field containing location-related properties +<3> `geoip.location`: Geographic coordinates stored as geo_point for location-based queries +<4> `products.properties`: Nested structure containing details about items in each order +============== + +The sample data includes the following <>: + +* <> and <> for text fields +** Most `text` fields have a `.keyword` subfield for exact matching using <> +* <> for date fields +* 3 <> types: +** `integer` for whole numbers +** `long` for large whole numbers +** `half_float` for floating-point numbers +* <> for geographic coordinates +* <> for nested structures such as `products`, `geoip`, `event` + +Now that we understand the structure of our sample data, let's start analyzing it. + +[discrete] +[[aggregations-tutorial-basic-metrics]] +=== Get key business metrics + +Let's start by calculating important metrics about orders and customers. + +[discrete] +[[aggregations-tutorial-order-value]] +==== Get average order size + +Calculate the average order value across all orders in the dataset using the <> aggregation. + +[source,console] +---- +GET kibana_sample_data_ecommerce/_search +{ + "size": 0, <1> + "aggs": { + "avg_order_value": { <2> + "avg": { <3> + "field": "taxful_total_price" + } + } + } +} +---- +// TEST[skip:Using Kibana sample data] +<1> Set `size` to 0 to avoid returning matched documents in the response and return only the aggregation results +<2> A meaningful name that describes what this metric represents +<3> Configures an `avg` aggregation, which calculates a simple arithmetic mean + +.Example response +[%collapsible] +============== +[source,console-result] +---- +{ + "took": 0, + "timed_out": false, + "_shards": { + "total": 1, + "successful": 1, + "skipped": 0, + "failed": 0 + }, + "hits": { + "total": { + "value": 4675, <1> + "relation": "eq" + }, + "max_score": null, + "hits": [] <2> + }, + "aggregations": { + "avg_order_value": { <3> + "value": 75.05542864304813 <4> + } + } +} +---- +// TEST[skip:Using Kibana sample data] +<1> Total number of orders in the dataset +<2> `hits` is empty because we set `size` to 0 +<3> Results appear under the name we specified in the request +<4> The average order value is calculated dynamically from all the orders in the dataset +============== + +[discrete] +[[aggregations-tutorial-order-stats]] +==== Get multiple order statistics at once + +Calculate multiple statistics about orders in one request using the <> aggregation. + +[source,console] +---- +GET kibana_sample_data_ecommerce/_search +{ + "size": 0, + "aggs": { + "order_stats": { <1> + "stats": { <2> + "field": "taxful_total_price" + } + } + } +} +---- +// TEST[skip:Using Kibana sample data] +<1> A descriptive name for this set of statistics +<2> `stats` returns count, min, max, avg, and sum at once + +.Example response +[%collapsible] +============== +[source,console-result] +---- +{ + "aggregations": { + "order_stats": { + "count": 4675, <1> + "min": 6.98828125, <2> + "max": 2250, <3> + "avg": 75.05542864304813, <4> + "sum": 350884.12890625 <5> + } + } +} +---- +// TEST[skip:Using Kibana sample data] +<1> `"count"`: Total number of orders in the dataset +<2> `"min"`: Lowest individual order value in the dataset +<3> `"max"`: Highest individual order value in the dataset +<4> `"avg"`: Average value per order across all orders +<5> `"sum"`: Total revenue from all orders combined +============== + +[TIP] +==== +The <> is more efficient than running individual min, max, avg, and sum aggregations. +==== + +[discrete] +[[aggregations-tutorial-sales-patterns]] +=== Analyze sales patterns + +Let's group orders in different ways to understand sales patterns. + +[discrete] +[[aggregations-tutorial-category-breakdown]] +==== Break down sales by category + +Group orders by category to see which product categories are most popular, using the <> aggregation. + +[source,console] +---- +GET kibana_sample_data_ecommerce/_search +{ + "size": 0, + "aggs": { + "sales_by_category": { <1> + "terms": { <2> + "field": "category.keyword", <3> + "size": 5, <4> + "order": { "_count": "desc" } <5> + } + } + } +} +---- +// TEST[skip:Using Kibana sample data] +<1> Name reflecting the business purpose of this breakdown +<2> `terms` aggregation groups documents by field values +<3> Use <> field for exact matching on text fields +<4> Limit to top 5 categories +<5> Order by number of orders (descending) + +.Example response +[%collapsible] +============== +[source,console-result] +---- +{ + "took": 4, + "timed_out": false, + "_shards": { + "total": 5, + "successful": 5, + "skipped": 0, + "failed": 0 + }, + "hits": { + "total": { + "value": 4675, + "relation": "eq" + }, + "max_score": null, + "hits": [] + }, + "aggregations": { + "sales_by_category": { + "doc_count_error_upper_bound": 0, <1> + "sum_other_doc_count": 572, <2> + "buckets": [ <3> + { + "key": "Men's Clothing", <4> + "doc_count": 2024 <5> + }, + { + "key": "Women's Clothing", + "doc_count": 1903 + }, + { + "key": "Women's Shoes", + "doc_count": 1136 + }, + { + "key": "Men's Shoes", + "doc_count": 944 + }, + { + "key": "Women's Accessories", + "doc_count": 830 + } + ] + } + } +} +---- +// TEST[skip:Using Kibana sample data] +<1> Due to Elasticsearch's distributed architecture, when <> run across multiple shards, the doc counts may have a small margin of error. This value indicates the maximum possible error in the counts. +<2> Count of documents in categories beyond the requested size. +<3> Array of category buckets, ordered by count. +<4> Category name. +<5> Number of orders in this category. +============== + +[discrete] +[[aggregations-tutorial-daily-sales]] +==== Track daily sales patterns + +Group orders by day to track daily sales patterns using the <> aggregation. + +[source,console] +---- +GET kibana_sample_data_ecommerce/_search +{ + "size": 0, + "aggs": { + "daily_orders": { <1> + "date_histogram": { <2> + "field": "order_date", + "calendar_interval": "day", <3> + "format": "yyyy-MM-dd", <4> + "min_doc_count": 0 <5> + } + } + } +} +---- +// TEST[skip:Using Kibana sample data] +<1> Descriptive name for the time-series aggregation results. +<2> The `date_histogram` aggregration groups documents into time-based buckets, similar to terms aggregation but for dates. +<3> Uses <> to handle months with different lengths. `"day"` ensures consistent daily grouping regardless of timezone. +<4> Formats dates in response using <> (e.g. "yyyy-MM-dd"). Refer to <> for additional options. +<5> When `min_doc_count` is 0, returns buckets for days with no orders, useful for continuous time series visualization. + +.Example response +[%collapsible] +============== +[source,console-result] +---- +{ + "took": 2, + "timed_out": false, + "_shards": { + "total": 5, + "successful": 5, + "skipped": 0, + "failed": 0 + }, + "hits": { + "total": { + "value": 4675, + "relation": "eq" + }, + "max_score": null, + "hits": [] + }, + "aggregations": { + "daily_orders": { <1> + "buckets": [ <2> + { + "key_as_string": "2024-11-28", <3> + "key": 1732752000000, <4> + "doc_count": 146 <5> + }, + { + "key_as_string": "2024-11-29", + "key": 1732838400000, + "doc_count": 153 + }, + { + "key_as_string": "2024-11-30", + "key": 1732924800000, + "doc_count": 143 + }, + { + "key_as_string": "2024-12-01", + "key": 1733011200000, + "doc_count": 140 + }, + { + "key_as_string": "2024-12-02", + "key": 1733097600000, + "doc_count": 139 + }, + { + "key_as_string": "2024-12-03", + "key": 1733184000000, + "doc_count": 157 + }, + { + "key_as_string": "2024-12-04", + "key": 1733270400000, + "doc_count": 145 + }, + { + "key_as_string": "2024-12-05", + "key": 1733356800000, + "doc_count": 152 + }, + { + "key_as_string": "2024-12-06", + "key": 1733443200000, + "doc_count": 163 + }, + { + "key_as_string": "2024-12-07", + "key": 1733529600000, + "doc_count": 141 + }, + { + "key_as_string": "2024-12-08", + "key": 1733616000000, + "doc_count": 151 + }, + { + "key_as_string": "2024-12-09", + "key": 1733702400000, + "doc_count": 143 + }, + { + "key_as_string": "2024-12-10", + "key": 1733788800000, + "doc_count": 143 + }, + { + "key_as_string": "2024-12-11", + "key": 1733875200000, + "doc_count": 142 + }, + { + "key_as_string": "2024-12-12", + "key": 1733961600000, + "doc_count": 161 + }, + { + "key_as_string": "2024-12-13", + "key": 1734048000000, + "doc_count": 144 + }, + { + "key_as_string": "2024-12-14", + "key": 1734134400000, + "doc_count": 157 + }, + { + "key_as_string": "2024-12-15", + "key": 1734220800000, + "doc_count": 158 + }, + { + "key_as_string": "2024-12-16", + "key": 1734307200000, + "doc_count": 144 + }, + { + "key_as_string": "2024-12-17", + "key": 1734393600000, + "doc_count": 151 + }, + { + "key_as_string": "2024-12-18", + "key": 1734480000000, + "doc_count": 145 + }, + { + "key_as_string": "2024-12-19", + "key": 1734566400000, + "doc_count": 157 + }, + { + "key_as_string": "2024-12-20", + "key": 1734652800000, + "doc_count": 158 + }, + { + "key_as_string": "2024-12-21", + "key": 1734739200000, + "doc_count": 153 + }, + { + "key_as_string": "2024-12-22", + "key": 1734825600000, + "doc_count": 165 + }, + { + "key_as_string": "2024-12-23", + "key": 1734912000000, + "doc_count": 153 + }, + { + "key_as_string": "2024-12-24", + "key": 1734998400000, + "doc_count": 158 + }, + { + "key_as_string": "2024-12-25", + "key": 1735084800000, + "doc_count": 160 + }, + { + "key_as_string": "2024-12-26", + "key": 1735171200000, + "doc_count": 159 + }, + { + "key_as_string": "2024-12-27", + "key": 1735257600000, + "doc_count": 152 + }, + { + "key_as_string": "2024-12-28", + "key": 1735344000000, + "doc_count": 142 + } + ] + } + } +} +---- +// TEST[skip:Using Kibana sample data] +<1> Results of our named aggregation "daily_orders" +<2> Time-based buckets from date_histogram aggregation +<3> `key_as_string` is the human-readable date for this bucket +<4> `key` is the same date represented as the Unix timestamp for this bucket +<5> `doc_count` counts the number of documents that fall into this time bucket +============== + +[discrete] +[[aggregations-tutorial-combined-analysis]] +=== Combine metrics with groupings + +Now let's calculate <> within each group to get deeper insights. + +[discrete] +[[aggregations-tutorial-category-metrics]] +==== Compare category performance + +Calculate metrics within each category to compare performance across categories. + +[source,console] +---- +GET kibana_sample_data_ecommerce/_search +{ + "size": 0, + "aggs": { + "categories": { + "terms": { + "field": "category.keyword", + "size": 5, + "order": { "total_revenue": "desc" } <1> + }, + "aggs": { <2> + "total_revenue": { <3> + "sum": { + "field": "taxful_total_price" + } + }, + "avg_order_value": { <4> + "avg": { + "field": "taxful_total_price" + } + }, + "total_items": { <5> + "sum": { + "field": "total_quantity" + } + } + } + } + } +} +---- +// TEST[skip:Using Kibana sample data] +<1> Order categories by their total revenue instead of count +<2> Define metrics to calculate within each category +<3> Total revenue for the category +<4> Average order value in the category +<5> Total number of items sold + +.Example response +[%collapsible] +============== +[source,console-result] +---- +{ + "aggregations": { + "categories": { + "buckets": [ + { + "key": "Men's Clothing", <1> + "doc_count": 2179, <2> + "total_revenue": { <3> + "value": 156729.453125 + }, + "avg_order_value": { <4> + "value": 71.92726898715927 + }, + "total_items": { <5> + "value": 8716 + } + }, + { + "key": "Women's Clothing", + "doc_count": 2262, + ... + } + ] + } + } +} +---- +// TEST[skip:Using Kibana sample data] +<1> Category name +<2> Number of orders +<3> Total revenue for this category +<4> Average order value for this category +<5> Total quantity of items sold +============== + +[discrete] +[[aggregations-tutorial-daily-metrics]] +==== Analyze daily sales performance + +Let's combine metrics to track daily trends: daily revenue, unique customers, and average basket size. + +[source,console] +---- +GET kibana_sample_data_ecommerce/_search +{ + "size": 0, + "aggs": { + "daily_sales": { + "date_histogram": { + "field": "order_date", + "calendar_interval": "day", + "format": "yyyy-MM-dd" + }, + "aggs": { + "revenue": { <1> + "sum": { + "field": "taxful_total_price" + } + }, + "unique_customers": { <2> + "cardinality": { + "field": "customer_id" + } + }, + "avg_basket_size": { <3> + "avg": { + "field": "total_quantity" + } + } + } + } + } +} +---- +// TEST[skip:Using Kibana sample data] +<1> Daily revenue +<2> Uses the <> aggregation to count unique customers per day +<3> Average number of items per order + +.Example response +[%collapsible] +============== +[source,console-result] +---- +{ + "took": 119, + "timed_out": false, + "_shards": { + "total": 5, + "successful": 5, + "skipped": 0, + "failed": 0 + }, + "hits": { + "total": { + "value": 4675, + "relation": "eq" + }, + "max_score": null, + "hits": [] + }, + "aggregations": { + "daily_sales": { + "buckets": [ + { + "key_as_string": "2024-11-14", + "key": 1731542400000, + "doc_count": 146, + "unique_customers": { <1> + "value": 42 + }, + "revenue": { <2> + "value": 10578.53125 + }, + "avg_basket_size": { <3> + "value": 2.1780821917808217 + } + }, + { + "key_as_string": "2024-11-15", + "key": 1731628800000, + "doc_count": 153, + "unique_customers": { + "value": 44 + }, + "revenue": { + "value": 10448 + }, + "avg_basket_size": { + "value": 2.183006535947712 + } + }, + { + "key_as_string": "2024-11-16", + "key": 1731715200000, + "doc_count": 143, + "unique_customers": { + "value": 45 + }, + "revenue": { + "value": 10283.484375 + }, + "avg_basket_size": { + "value": 2.111888111888112 + } + }, + { + "key_as_string": "2024-11-17", + "key": 1731801600000, + "doc_count": 140, + "unique_customers": { + "value": 42 + }, + "revenue": { + "value": 10145.5234375 + }, + "avg_basket_size": { + "value": 2.142857142857143 + } + }, + { + "key_as_string": "2024-11-18", + "key": 1731888000000, + "doc_count": 139, + "unique_customers": { + "value": 42 + }, + "revenue": { + "value": 12012.609375 + }, + "avg_basket_size": { + "value": 2.158273381294964 + } + }, + { + "key_as_string": "2024-11-19", + "key": 1731974400000, + "doc_count": 157, + "unique_customers": { + "value": 43 + }, + "revenue": { + "value": 11009.45703125 + }, + "avg_basket_size": { + "value": 2.0955414012738856 + } + }, + { + "key_as_string": "2024-11-20", + "key": 1732060800000, + "doc_count": 145, + "unique_customers": { + "value": 44 + }, + "revenue": { + "value": 10720.59375 + }, + "avg_basket_size": { + "value": 2.179310344827586 + } + }, + { + "key_as_string": "2024-11-21", + "key": 1732147200000, + "doc_count": 152, + "unique_customers": { + "value": 43 + }, + "revenue": { + "value": 11185.3671875 + }, + "avg_basket_size": { + "value": 2.1710526315789473 + } + }, + { + "key_as_string": "2024-11-22", + "key": 1732233600000, + "doc_count": 163, + "unique_customers": { + "value": 44 + }, + "revenue": { + "value": 13560.140625 + }, + "avg_basket_size": { + "value": 2.2576687116564416 + } + }, + { + "key_as_string": "2024-11-23", + "key": 1732320000000, + "doc_count": 141, + "unique_customers": { + "value": 45 + }, + "revenue": { + "value": 9884.78125 + }, + "avg_basket_size": { + "value": 2.099290780141844 + } + }, + { + "key_as_string": "2024-11-24", + "key": 1732406400000, + "doc_count": 151, + "unique_customers": { + "value": 44 + }, + "revenue": { + "value": 11075.65625 + }, + "avg_basket_size": { + "value": 2.0927152317880795 + } + }, + { + "key_as_string": "2024-11-25", + "key": 1732492800000, + "doc_count": 143, + "unique_customers": { + "value": 41 + }, + "revenue": { + "value": 10323.8515625 + }, + "avg_basket_size": { + "value": 2.167832167832168 + } + }, + { + "key_as_string": "2024-11-26", + "key": 1732579200000, + "doc_count": 143, + "unique_customers": { + "value": 44 + }, + "revenue": { + "value": 10369.546875 + }, + "avg_basket_size": { + "value": 2.167832167832168 + } + }, + { + "key_as_string": "2024-11-27", + "key": 1732665600000, + "doc_count": 142, + "unique_customers": { + "value": 46 + }, + "revenue": { + "value": 11711.890625 + }, + "avg_basket_size": { + "value": 2.1971830985915495 + } + }, + { + "key_as_string": "2024-11-28", + "key": 1732752000000, + "doc_count": 161, + "unique_customers": { + "value": 43 + }, + "revenue": { + "value": 12612.6640625 + }, + "avg_basket_size": { + "value": 2.1180124223602483 + } + }, + { + "key_as_string": "2024-11-29", + "key": 1732838400000, + "doc_count": 144, + "unique_customers": { + "value": 42 + }, + "revenue": { + "value": 10176.87890625 + }, + "avg_basket_size": { + "value": 2.0347222222222223 + } + }, + { + "key_as_string": "2024-11-30", + "key": 1732924800000, + "doc_count": 157, + "unique_customers": { + "value": 43 + }, + "revenue": { + "value": 11480.33203125 + }, + "avg_basket_size": { + "value": 2.159235668789809 + } + }, + { + "key_as_string": "2024-12-01", + "key": 1733011200000, + "doc_count": 158, + "unique_customers": { + "value": 42 + }, + "revenue": { + "value": 11533.265625 + }, + "avg_basket_size": { + "value": 2.0822784810126582 + } + }, + { + "key_as_string": "2024-12-02", + "key": 1733097600000, + "doc_count": 144, + "unique_customers": { + "value": 43 + }, + "revenue": { + "value": 10499.8125 + }, + "avg_basket_size": { + "value": 2.201388888888889 + } + }, + { + "key_as_string": "2024-12-03", + "key": 1733184000000, + "doc_count": 151, + "unique_customers": { + "value": 40 + }, + "revenue": { + "value": 12111.6875 + }, + "avg_basket_size": { + "value": 2.172185430463576 + } + }, + { + "key_as_string": "2024-12-04", + "key": 1733270400000, + "doc_count": 145, + "unique_customers": { + "value": 40 + }, + "revenue": { + "value": 10530.765625 + }, + "avg_basket_size": { + "value": 2.0965517241379312 + } + }, + { + "key_as_string": "2024-12-05", + "key": 1733356800000, + "doc_count": 157, + "unique_customers": { + "value": 43 + }, + "revenue": { + "value": 11872.5625 + }, + "avg_basket_size": { + "value": 2.1464968152866244 + } + }, + { + "key_as_string": "2024-12-06", + "key": 1733443200000, + "doc_count": 158, + "unique_customers": { + "value": 42 + }, + "revenue": { + "value": 12109.453125 + }, + "avg_basket_size": { + "value": 2.151898734177215 + } + }, + { + "key_as_string": "2024-12-07", + "key": 1733529600000, + "doc_count": 153, + "unique_customers": { + "value": 42 + }, + "revenue": { + "value": 11057.40625 + }, + "avg_basket_size": { + "value": 2.111111111111111 + } + }, + { + "key_as_string": "2024-12-08", + "key": 1733616000000, + "doc_count": 165, + "unique_customers": { + "value": 42 + }, + "revenue": { + "value": 13095.609375 + }, + "avg_basket_size": { + "value": 2.1818181818181817 + } + }, + { + "key_as_string": "2024-12-09", + "key": 1733702400000, + "doc_count": 153, + "unique_customers": { + "value": 41 + }, + "revenue": { + "value": 12574.015625 + }, + "avg_basket_size": { + "value": 2.2287581699346406 + } + }, + { + "key_as_string": "2024-12-10", + "key": 1733788800000, + "doc_count": 158, + "unique_customers": { + "value": 42 + }, + "revenue": { + "value": 11188.1875 + }, + "avg_basket_size": { + "value": 2.151898734177215 + } + }, + { + "key_as_string": "2024-12-11", + "key": 1733875200000, + "doc_count": 160, + "unique_customers": { + "value": 42 + }, + "revenue": { + "value": 12117.65625 + }, + "avg_basket_size": { + "value": 2.20625 + } + }, + { + "key_as_string": "2024-12-12", + "key": 1733961600000, + "doc_count": 159, + "unique_customers": { + "value": 45 + }, + "revenue": { + "value": 11558.25 + }, + "avg_basket_size": { + "value": 2.1823899371069184 + } + }, + { + "key_as_string": "2024-12-13", + "key": 1734048000000, + "doc_count": 152, + "unique_customers": { + "value": 45 + }, + "revenue": { + "value": 11921.1171875 + }, + "avg_basket_size": { + "value": 2.289473684210526 + } + }, + { + "key_as_string": "2024-12-14", + "key": 1734134400000, + "doc_count": 142, + "unique_customers": { + "value": 45 + }, + "revenue": { + "value": 11135.03125 + }, + "avg_basket_size": { + "value": 2.183098591549296 + } + } + ] + } + } +} +---- +// TEST[skip:Using Kibana sample data] +============== + +[discrete] +[[aggregations-tutorial-trends]] +=== Track trends and patterns + +You can use <> on the results of other aggregations. +Let's analyze how metrics change over time. + +[discrete] +[[aggregations-tutorial-moving-average]] +==== Smooth out daily fluctuations + +Moving averages help identify trends by reducing day-to-day noise in the data. +Let's observe sales trends more clearly by smoothing daily revenue variations, using the <> aggregation. + +[source,console] +---- +GET kibana_sample_data_ecommerce/_search +{ + "size": 0, + "aggs": { + "daily_sales": { + "date_histogram": { + "field": "order_date", + "calendar_interval": "day" + }, + "aggs": { + "daily_revenue": { <1> + "sum": { + "field": "taxful_total_price" + } + }, + "smoothed_revenue": { <2> + "moving_fn": { <3> + "buckets_path": "daily_revenue", <4> + "window": 3, <5> + "script": "MovingFunctions.unweightedAvg(values)" <6> + } + } + } + } + } +} +---- +// TEST[skip:Using Kibana sample data] +<1> Calculate daily revenue first. +<2> Create a smoothed version of the daily revenue. +<3> Use `moving_fn` for moving window calculations. +<4> Reference the revenue from our date histogram. +<5> Use a 3-day window — use different window sizes to see trends at different time scales. +<6> Use the built-in unweighted average function in the `moving_fn` aggregation. + +.Example response +[%collapsible] +============== +[source,console-result] +---- +{ + "took": 13, + "timed_out": false, + "_shards": { + "total": 5, + "successful": 5, + "skipped": 0, + "failed": 0 + }, + "hits": { + "total": { + "value": 4675, + "relation": "eq" + }, + "max_score": null, + "hits": [] + }, + "aggregations": { + "daily_sales": { + "buckets": [ + { + "key_as_string": "2024-11-14T00:00:00.000Z", <1> + "key": 1731542400000, + "doc_count": 146, <2> + "daily_revenue": { <3> + "value": 10578.53125 + }, + "smoothed_revenue": { <4> + "value": null + } + }, + { + "key_as_string": "2024-11-15T00:00:00.000Z", + "key": 1731628800000, + "doc_count": 153, + "daily_revenue": { + "value": 10448 + }, + "smoothed_revenue": { <5> + "value": 10578.53125 + } + }, + { + "key_as_string": "2024-11-16T00:00:00.000Z", + "key": 1731715200000, + "doc_count": 143, + "daily_revenue": { + "value": 10283.484375 + }, + "smoothed_revenue": { + "value": 10513.265625 + } + }, + { + "key_as_string": "2024-11-17T00:00:00.000Z", + "key": 1731801600000, + "doc_count": 140, + "daily_revenue": { + "value": 10145.5234375 + }, + "smoothed_revenue": { + "value": 10436.671875 + } + }, + { + "key_as_string": "2024-11-18T00:00:00.000Z", + "key": 1731888000000, + "doc_count": 139, + "daily_revenue": { + "value": 12012.609375 + }, + "smoothed_revenue": { + "value": 10292.3359375 + } + }, + { + "key_as_string": "2024-11-19T00:00:00.000Z", + "key": 1731974400000, + "doc_count": 157, + "daily_revenue": { + "value": 11009.45703125 + }, + "smoothed_revenue": { + "value": 10813.872395833334 + } + }, + { + "key_as_string": "2024-11-20T00:00:00.000Z", + "key": 1732060800000, + "doc_count": 145, + "daily_revenue": { + "value": 10720.59375 + }, + "smoothed_revenue": { + "value": 11055.86328125 + } + }, + { + "key_as_string": "2024-11-21T00:00:00.000Z", + "key": 1732147200000, + "doc_count": 152, + "daily_revenue": { + "value": 11185.3671875 + }, + "smoothed_revenue": { + "value": 11247.553385416666 + } + }, + { + "key_as_string": "2024-11-22T00:00:00.000Z", + "key": 1732233600000, + "doc_count": 163, + "daily_revenue": { + "value": 13560.140625 + }, + "smoothed_revenue": { + "value": 10971.805989583334 + } + }, + { + "key_as_string": "2024-11-23T00:00:00.000Z", + "key": 1732320000000, + "doc_count": 141, + "daily_revenue": { + "value": 9884.78125 + }, + "smoothed_revenue": { + "value": 11822.033854166666 + } + }, + { + "key_as_string": "2024-11-24T00:00:00.000Z", + "key": 1732406400000, + "doc_count": 151, + "daily_revenue": { + "value": 11075.65625 + }, + "smoothed_revenue": { + "value": 11543.4296875 + } + }, + { + "key_as_string": "2024-11-25T00:00:00.000Z", + "key": 1732492800000, + "doc_count": 143, + "daily_revenue": { + "value": 10323.8515625 + }, + "smoothed_revenue": { + "value": 11506.859375 + } + }, + { + "key_as_string": "2024-11-26T00:00:00.000Z", + "key": 1732579200000, + "doc_count": 143, + "daily_revenue": { + "value": 10369.546875 + }, + "smoothed_revenue": { + "value": 10428.096354166666 + } + }, + { + "key_as_string": "2024-11-27T00:00:00.000Z", + "key": 1732665600000, + "doc_count": 142, + "daily_revenue": { + "value": 11711.890625 + }, + "smoothed_revenue": { + "value": 10589.684895833334 + } + }, + { + "key_as_string": "2024-11-28T00:00:00.000Z", + "key": 1732752000000, + "doc_count": 161, + "daily_revenue": { + "value": 12612.6640625 + }, + "smoothed_revenue": { + "value": 10801.763020833334 + } + }, + { + "key_as_string": "2024-11-29T00:00:00.000Z", + "key": 1732838400000, + "doc_count": 144, + "daily_revenue": { + "value": 10176.87890625 + }, + "smoothed_revenue": { + "value": 11564.700520833334 + } + }, + { + "key_as_string": "2024-11-30T00:00:00.000Z", + "key": 1732924800000, + "doc_count": 157, + "daily_revenue": { + "value": 11480.33203125 + }, + "smoothed_revenue": { + "value": 11500.477864583334 + } + }, + { + "key_as_string": "2024-12-01T00:00:00.000Z", + "key": 1733011200000, + "doc_count": 158, + "daily_revenue": { + "value": 11533.265625 + }, + "smoothed_revenue": { + "value": 11423.291666666666 + } + }, + { + "key_as_string": "2024-12-02T00:00:00.000Z", + "key": 1733097600000, + "doc_count": 144, + "daily_revenue": { + "value": 10499.8125 + }, + "smoothed_revenue": { + "value": 11063.4921875 + } + }, + { + "key_as_string": "2024-12-03T00:00:00.000Z", + "key": 1733184000000, + "doc_count": 151, + "daily_revenue": { + "value": 12111.6875 + }, + "smoothed_revenue": { + "value": 11171.13671875 + } + }, + { + "key_as_string": "2024-12-04T00:00:00.000Z", + "key": 1733270400000, + "doc_count": 145, + "daily_revenue": { + "value": 10530.765625 + }, + "smoothed_revenue": { + "value": 11381.588541666666 + } + }, + { + "key_as_string": "2024-12-05T00:00:00.000Z", + "key": 1733356800000, + "doc_count": 157, + "daily_revenue": { + "value": 11872.5625 + }, + "smoothed_revenue": { + "value": 11047.421875 + } + }, + { + "key_as_string": "2024-12-06T00:00:00.000Z", + "key": 1733443200000, + "doc_count": 158, + "daily_revenue": { + "value": 12109.453125 + }, + "smoothed_revenue": { + "value": 11505.005208333334 + } + }, + { + "key_as_string": "2024-12-07T00:00:00.000Z", + "key": 1733529600000, + "doc_count": 153, + "daily_revenue": { + "value": 11057.40625 + }, + "smoothed_revenue": { + "value": 11504.260416666666 + } + }, + { + "key_as_string": "2024-12-08T00:00:00.000Z", + "key": 1733616000000, + "doc_count": 165, + "daily_revenue": { + "value": 13095.609375 + }, + "smoothed_revenue": { + "value": 11679.807291666666 + } + }, + { + "key_as_string": "2024-12-09T00:00:00.000Z", + "key": 1733702400000, + "doc_count": 153, + "daily_revenue": { + "value": 12574.015625 + }, + "smoothed_revenue": { + "value": 12087.489583333334 + } + }, + { + "key_as_string": "2024-12-10T00:00:00.000Z", + "key": 1733788800000, + "doc_count": 158, + "daily_revenue": { + "value": 11188.1875 + }, + "smoothed_revenue": { + "value": 12242.34375 + } + }, + { + "key_as_string": "2024-12-11T00:00:00.000Z", + "key": 1733875200000, + "doc_count": 160, + "daily_revenue": { + "value": 12117.65625 + }, + "smoothed_revenue": { + "value": 12285.9375 + } + }, + { + "key_as_string": "2024-12-12T00:00:00.000Z", + "key": 1733961600000, + "doc_count": 159, + "daily_revenue": { + "value": 11558.25 + }, + "smoothed_revenue": { + "value": 11959.953125 + } + }, + { + "key_as_string": "2024-12-13T00:00:00.000Z", + "key": 1734048000000, + "doc_count": 152, + "daily_revenue": { + "value": 11921.1171875 + }, + "smoothed_revenue": { + "value": 11621.364583333334 + } + }, + { + "key_as_string": "2024-12-14T00:00:00.000Z", + "key": 1734134400000, + "doc_count": 142, + "daily_revenue": { + "value": 11135.03125 + }, + "smoothed_revenue": { + "value": 11865.674479166666 + } + } + ] + } + } +} +---- +// TEST[skip:Using Kibana sample data] +<1> Date of the bucket is in default ISO format because we didn't specify a format +<2> Number of orders for this day +<3> Raw daily revenue before smoothing +<4> First day has no smoothed value as it needs previous days for the calculation +<5> Moving average starts from second day, using a 3-day window +============== + +[TIP] +==== +Notice how the smoothed values lag behind the actual values - this is because they need previous days' data to calculate. The first day will always be null when using moving averages. +==== + +[discrete] +[[aggregations-tutorial-cumulative]] +==== Track running totals + +Track running totals over time using the <> aggregation. + +[source,console] +---- +GET kibana_sample_data_ecommerce/_search +{ + "size": 0, + "aggs": { + "daily_sales": { + "date_histogram": { + "field": "order_date", + "calendar_interval": "day" + }, + "aggs": { + "revenue": { + "sum": { + "field": "taxful_total_price" + } + }, + "cumulative_revenue": { <1> + "cumulative_sum": { <2> + "buckets_path": "revenue" <3> + } + } + } + } + } +} +---- +// TEST[skip:Using Kibana sample data] +<1> Name for our running total +<2> `cumulative_sum` adds up values across buckets +<3> Reference the revenue we want to accumulate + +.Example response +[%collapsible] +============== +[source,console-result] +---- +{ + "took": 4, + "timed_out": false, + "_shards": { + "total": 5, + "successful": 5, + "skipped": 0, + "failed": 0 + }, + "hits": { + "total": { + "value": 4675, + "relation": "eq" + }, + "max_score": null, + "hits": [] + }, + "aggregations": { + "daily_sales": { <1> + "buckets": [ <2> + { + "key_as_string": "2024-11-14T00:00:00.000Z", <3> + "key": 1731542400000, + "doc_count": 146, + "revenue": { <4> + "value": 10578.53125 + }, + "cumulative_revenue": { <5> + "value": 10578.53125 + } + }, + { + "key_as_string": "2024-11-15T00:00:00.000Z", + "key": 1731628800000, + "doc_count": 153, + "revenue": { + "value": 10448 + }, + "cumulative_revenue": { + "value": 21026.53125 + } + }, + { + "key_as_string": "2024-11-16T00:00:00.000Z", + "key": 1731715200000, + "doc_count": 143, + "revenue": { + "value": 10283.484375 + }, + "cumulative_revenue": { + "value": 31310.015625 + } + }, + { + "key_as_string": "2024-11-17T00:00:00.000Z", + "key": 1731801600000, + "doc_count": 140, + "revenue": { + "value": 10145.5234375 + }, + "cumulative_revenue": { + "value": 41455.5390625 + } + }, + { + "key_as_string": "2024-11-18T00:00:00.000Z", + "key": 1731888000000, + "doc_count": 139, + "revenue": { + "value": 12012.609375 + }, + "cumulative_revenue": { + "value": 53468.1484375 + } + }, + { + "key_as_string": "2024-11-19T00:00:00.000Z", + "key": 1731974400000, + "doc_count": 157, + "revenue": { + "value": 11009.45703125 + }, + "cumulative_revenue": { + "value": 64477.60546875 + } + }, + { + "key_as_string": "2024-11-20T00:00:00.000Z", + "key": 1732060800000, + "doc_count": 145, + "revenue": { + "value": 10720.59375 + }, + "cumulative_revenue": { + "value": 75198.19921875 + } + }, + { + "key_as_string": "2024-11-21T00:00:00.000Z", + "key": 1732147200000, + "doc_count": 152, + "revenue": { + "value": 11185.3671875 + }, + "cumulative_revenue": { + "value": 86383.56640625 + } + }, + { + "key_as_string": "2024-11-22T00:00:00.000Z", + "key": 1732233600000, + "doc_count": 163, + "revenue": { + "value": 13560.140625 + }, + "cumulative_revenue": { + "value": 99943.70703125 + } + }, + { + "key_as_string": "2024-11-23T00:00:00.000Z", + "key": 1732320000000, + "doc_count": 141, + "revenue": { + "value": 9884.78125 + }, + "cumulative_revenue": { + "value": 109828.48828125 + } + }, + { + "key_as_string": "2024-11-24T00:00:00.000Z", + "key": 1732406400000, + "doc_count": 151, + "revenue": { + "value": 11075.65625 + }, + "cumulative_revenue": { + "value": 120904.14453125 + } + }, + { + "key_as_string": "2024-11-25T00:00:00.000Z", + "key": 1732492800000, + "doc_count": 143, + "revenue": { + "value": 10323.8515625 + }, + "cumulative_revenue": { + "value": 131227.99609375 + } + }, + { + "key_as_string": "2024-11-26T00:00:00.000Z", + "key": 1732579200000, + "doc_count": 143, + "revenue": { + "value": 10369.546875 + }, + "cumulative_revenue": { + "value": 141597.54296875 + } + }, + { + "key_as_string": "2024-11-27T00:00:00.000Z", + "key": 1732665600000, + "doc_count": 142, + "revenue": { + "value": 11711.890625 + }, + "cumulative_revenue": { + "value": 153309.43359375 + } + }, + { + "key_as_string": "2024-11-28T00:00:00.000Z", + "key": 1732752000000, + "doc_count": 161, + "revenue": { + "value": 12612.6640625 + }, + "cumulative_revenue": { + "value": 165922.09765625 + } + }, + { + "key_as_string": "2024-11-29T00:00:00.000Z", + "key": 1732838400000, + "doc_count": 144, + "revenue": { + "value": 10176.87890625 + }, + "cumulative_revenue": { + "value": 176098.9765625 + } + }, + { + "key_as_string": "2024-11-30T00:00:00.000Z", + "key": 1732924800000, + "doc_count": 157, + "revenue": { + "value": 11480.33203125 + }, + "cumulative_revenue": { + "value": 187579.30859375 + } + }, + { + "key_as_string": "2024-12-01T00:00:00.000Z", + "key": 1733011200000, + "doc_count": 158, + "revenue": { + "value": 11533.265625 + }, + "cumulative_revenue": { + "value": 199112.57421875 + } + }, + { + "key_as_string": "2024-12-02T00:00:00.000Z", + "key": 1733097600000, + "doc_count": 144, + "revenue": { + "value": 10499.8125 + }, + "cumulative_revenue": { + "value": 209612.38671875 + } + }, + { + "key_as_string": "2024-12-03T00:00:00.000Z", + "key": 1733184000000, + "doc_count": 151, + "revenue": { + "value": 12111.6875 + }, + "cumulative_revenue": { + "value": 221724.07421875 + } + }, + { + "key_as_string": "2024-12-04T00:00:00.000Z", + "key": 1733270400000, + "doc_count": 145, + "revenue": { + "value": 10530.765625 + }, + "cumulative_revenue": { + "value": 232254.83984375 + } + }, + { + "key_as_string": "2024-12-05T00:00:00.000Z", + "key": 1733356800000, + "doc_count": 157, + "revenue": { + "value": 11872.5625 + }, + "cumulative_revenue": { + "value": 244127.40234375 + } + }, + { + "key_as_string": "2024-12-06T00:00:00.000Z", + "key": 1733443200000, + "doc_count": 158, + "revenue": { + "value": 12109.453125 + }, + "cumulative_revenue": { + "value": 256236.85546875 + } + }, + { + "key_as_string": "2024-12-07T00:00:00.000Z", + "key": 1733529600000, + "doc_count": 153, + "revenue": { + "value": 11057.40625 + }, + "cumulative_revenue": { + "value": 267294.26171875 + } + }, + { + "key_as_string": "2024-12-08T00:00:00.000Z", + "key": 1733616000000, + "doc_count": 165, + "revenue": { + "value": 13095.609375 + }, + "cumulative_revenue": { + "value": 280389.87109375 + } + }, + { + "key_as_string": "2024-12-09T00:00:00.000Z", + "key": 1733702400000, + "doc_count": 153, + "revenue": { + "value": 12574.015625 + }, + "cumulative_revenue": { + "value": 292963.88671875 + } + }, + { + "key_as_string": "2024-12-10T00:00:00.000Z", + "key": 1733788800000, + "doc_count": 158, + "revenue": { + "value": 11188.1875 + }, + "cumulative_revenue": { + "value": 304152.07421875 + } + }, + { + "key_as_string": "2024-12-11T00:00:00.000Z", + "key": 1733875200000, + "doc_count": 160, + "revenue": { + "value": 12117.65625 + }, + "cumulative_revenue": { + "value": 316269.73046875 + } + }, + { + "key_as_string": "2024-12-12T00:00:00.000Z", + "key": 1733961600000, + "doc_count": 159, + "revenue": { + "value": 11558.25 + }, + "cumulative_revenue": { + "value": 327827.98046875 + } + }, + { + "key_as_string": "2024-12-13T00:00:00.000Z", + "key": 1734048000000, + "doc_count": 152, + "revenue": { + "value": 11921.1171875 + }, + "cumulative_revenue": { + "value": 339749.09765625 + } + }, + { + "key_as_string": "2024-12-14T00:00:00.000Z", + "key": 1734134400000, + "doc_count": 142, + "revenue": { + "value": 11135.03125 + }, + "cumulative_revenue": { + "value": 350884.12890625 + } + } + ] + } + } +} +---- +// TEST[skip:Using Kibana sample data] +<1> `daily_sales`: Results from our daily sales date histogram +<2> `buckets`: Array of time-based buckets +<3> `key_as_string`: Date for this bucket (in ISO format since no format specified) +<4> `revenue`: Daily revenue for this date +<5> `cumulative_revenue`: Running total of revenue up to this date +============== + +[discrete] +[[aggregations-tutorial-next-steps]] +=== Next steps + +Refer to the <> for more details on all available aggregation types. \ No newline at end of file diff --git a/docs/reference/quickstart/index.asciidoc b/docs/reference/quickstart/index.asciidoc index 31ba67e1b7a60..330582956c457 100644 --- a/docs/reference/quickstart/index.asciidoc +++ b/docs/reference/quickstart/index.asciidoc @@ -26,6 +26,7 @@ Alternatively, refer to our <>. Learn about indices, documents, and mappings, and perform a basic search using the Query DSL. * <>. Learn about different options for querying data, including full-text search and filtering, using the Query DSL. * <>: Learn how to query and aggregate your data using {esql}. +* <>. Learn how to analyze data using different types of aggregations, including metrics, buckets, and pipelines. * <>: Learn how to create embeddings for your data with `semantic_text` and query using the `semantic` query. ** <>: Learn how to combine semantic search with full-text search. * <>: Learn how to ingest dense vector embeddings into {es}. @@ -41,3 +42,4 @@ If you're interested in using {es} with Python, check out Elastic Search Labs: include::getting-started.asciidoc[] include::full-text-filtering-tutorial.asciidoc[] +include::aggs-tutorial.asciidoc[] From fc1fb13693e87881046baa93e2cf1f4caf2fd58b Mon Sep 17 00:00:00 2001 From: Sean Story Date: Wed, 11 Dec 2024 14:02:52 -0600 Subject: [PATCH 36/37] Add known issue for salesforce DLS (#118489) (#118500) --- .../docs/connectors-salesforce.asciidoc | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/docs/reference/connector/docs/connectors-salesforce.asciidoc b/docs/reference/connector/docs/connectors-salesforce.asciidoc index c640751de92c0..f5c5512ad5cc4 100644 --- a/docs/reference/connector/docs/connectors-salesforce.asciidoc +++ b/docs/reference/connector/docs/connectors-salesforce.asciidoc @@ -200,7 +200,7 @@ Once the permissions are set, assign the Profiles, Permission Set or Permission Follow these steps in Salesforce: 1. Navigate to `Administration` under the `Users` section. -2. Select `Users` and choose the user to set the permissions to. +2. Select `Users` and choose the user to set the permissions to. 3. Set the `Profile`, `Permission Set` or `Permission Set Groups` created in the earlier steps. [discrete#es-connectors-salesforce-sync-rules] @@ -249,7 +249,7 @@ Allowed values are *SOQL* and *SOSL*. [ { "query": "FIND {Salesforce} IN ALL FIELDS", - "language": "SOSL" + "language": "SOSL" } ] ---- @@ -381,7 +381,13 @@ See <> for more specifics o [discrete#es-connectors-salesforce-known-issues] ===== Known issues -There are currently no known issues for this connector. +* *DLS feature is "type-level" not "document-level"* ++ +Salesforce DLS, added in 8.13.0, does not accomodate specific access controls to specific Salesforce Objects. +Instead, if a given user/group can have access to _any_ Objects of a given type (`Case`, `Lead`, `Opportunity`, etc), that user/group will appear in the `\_allow_access_control` list for _all_ of the Objects of that type. +See https://github.com/elastic/connectors/issues/3028 for more details. ++ + Refer to <> for a list of known issues for all connectors. [discrete#es-connectors-salesforce-security] @@ -396,7 +402,7 @@ This connector is built with the {connectors-python}[Elastic connector framework View the {connectors-python}/connectors/sources/salesforce.py[source code for this connector^] (branch _{connectors-branch}_, compatible with Elastic _{minor-version}_). -// Closing the collapsible section +// Closing the collapsible section =============== @@ -598,7 +604,7 @@ Once the permissions are set, assign the Profiles, Permission Set or Permission Follow these steps in Salesforce: 1. Navigate to `Administration` under the `Users` section. -2. Select `Users` and choose the user to set the permissions to. +2. Select `Users` and choose the user to set the permissions to. 3. Set the `Profile`, `Permission Set` or `Permission Set Groups` created in the earlier steps. [discrete#es-connectors-salesforce-client-sync-rules] @@ -648,7 +654,7 @@ Allowed values are *SOQL* and *SOSL*. [ { "query": "FIND {Salesforce} IN ALL FIELDS", - "language": "SOSL" + "language": "SOSL" } ] ---- @@ -781,7 +787,13 @@ See <> for more specifics o [discrete#es-connectors-salesforce-client-known-issues] ===== Known issues -There are currently no known issues for this connector. +* *DLS feature is "type-level" not "document-level"* ++ +Salesforce DLS, added in 8.13.0, does not accomodate specific access controls to specific Salesforce Objects. +Instead, if a given user/group can have access to _any_ Objects of a given type (`Case`, `Lead`, `Opportunity`, etc), that user/group will appear in the `\_allow_access_control` list for _all_ of the Objects of that type. +See https://github.com/elastic/connectors/issues/3028 for more details. ++ + Refer to <> for a list of known issues for all connectors. [discrete#es-connectors-salesforce-client-security] @@ -797,5 +809,5 @@ This connector is built with the {connectors-python}[Elastic connector framework View the {connectors-python}/connectors/sources/salesforce.py[source code for this connector^] (branch _{connectors-branch}_, compatible with Elastic _{minor-version}_). -// Closing the collapsible section +// Closing the collapsible section =============== From 0c9090b455e773cd41905d7094070229c96786e2 Mon Sep 17 00:00:00 2001 From: Nikolaj Volgushev Date: Thu, 12 Dec 2024 10:02:28 +0100 Subject: [PATCH 37/37] ESQL: Add CCS tests for FLS and DLS against data streams (#118423) (#118525) CCS test coverage for https://github.com/elastic/elasticsearch/pull/118378 --- .../security/qa/multi-cluster/build.gradle | 1 + ...teClusterSecurityDataStreamEsqlRcs1IT.java | 402 ++++++++++++++++++ ...teClusterSecurityDataStreamEsqlRcs2IT.java | 126 ++++++ .../src/javaRestTest/resources/roles.yml | 99 +++++ 4 files changed, 628 insertions(+) create mode 100644 x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecurityDataStreamEsqlRcs1IT.java create mode 100644 x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecurityDataStreamEsqlRcs2IT.java diff --git a/x-pack/plugin/security/qa/multi-cluster/build.gradle b/x-pack/plugin/security/qa/multi-cluster/build.gradle index 4f239e323c38f..5e05498809bb3 100644 --- a/x-pack/plugin/security/qa/multi-cluster/build.gradle +++ b/x-pack/plugin/security/qa/multi-cluster/build.gradle @@ -24,6 +24,7 @@ dependencies { clusterModules project(':x-pack:plugin:enrich') clusterModules project(':x-pack:plugin:autoscaling') clusterModules project(':x-pack:plugin:ml') + clusterModules project(xpackModule('ilm')) clusterModules(project(":modules:ingest-common")) } diff --git a/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecurityDataStreamEsqlRcs1IT.java b/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecurityDataStreamEsqlRcs1IT.java new file mode 100644 index 0000000000000..57eb583912c49 --- /dev/null +++ b/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecurityDataStreamEsqlRcs1IT.java @@ -0,0 +1,402 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.remotecluster; + +import org.elasticsearch.Build; +import org.elasticsearch.client.Request; +import org.elasticsearch.client.RequestOptions; +import org.elasticsearch.client.Response; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.core.CheckedFunction; +import org.elasticsearch.core.Strings; +import org.elasticsearch.test.MapMatcher; +import org.elasticsearch.test.cluster.ElasticsearchCluster; +import org.elasticsearch.test.cluster.util.resource.Resource; +import org.elasticsearch.xcontent.XContentBuilder; +import org.elasticsearch.xcontent.json.JsonXContent; +import org.junit.ClassRule; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; + +import java.io.IOException; +import java.util.List; +import java.util.Locale; + +import static org.elasticsearch.test.ListMatcher.matchesList; +import static org.elasticsearch.test.MapMatcher.assertMap; +import static org.elasticsearch.test.MapMatcher.matchesMap; + +// TODO consolidate me with RemoteClusterSecurityDataStreamEsqlRcs2IT +public class RemoteClusterSecurityDataStreamEsqlRcs1IT extends AbstractRemoteClusterSecurityTestCase { + static { + fulfillingCluster = ElasticsearchCluster.local() + .name("fulfilling-cluster") + .module("x-pack-autoscaling") + .module("x-pack-esql") + .module("x-pack-enrich") + .module("x-pack-ml") + .module("x-pack-ilm") + .module("ingest-common") + .apply(commonClusterConfig) + .setting("xpack.ml.enabled", "false") + .setting("xpack.security.authc.token.enabled", "true") + .rolesFile(Resource.fromClasspath("roles.yml")) + .build(); + + queryCluster = ElasticsearchCluster.local() + .name("query-cluster") + .module("x-pack-autoscaling") + .module("x-pack-esql") + .module("x-pack-enrich") + .module("x-pack-ml") + .module("x-pack-ilm") + .module("ingest-common") + .apply(commonClusterConfig) + .setting("xpack.ml.enabled", "false") + .setting("xpack.security.authc.token.enabled", "true") + .rolesFile(Resource.fromClasspath("roles.yml")) + .user("logs_foo_all", "x-pack-test-password", "logs_foo_all", false) + .user("logs_foo_16_only", "x-pack-test-password", "logs_foo_16_only", false) + .user("logs_foo_after_2021", "x-pack-test-password", "logs_foo_after_2021", false) + .user("logs_foo_after_2021_pattern", "x-pack-test-password", "logs_foo_after_2021_pattern", false) + .user("logs_foo_after_2021_alias", "x-pack-test-password", "logs_foo_after_2021_alias", false) + .build(); + } + + @ClassRule + public static TestRule clusterRule = RuleChain.outerRule(fulfillingCluster).around(queryCluster); + + public void testDataStreamsWithDlsAndFls() throws Exception { + configureRemoteCluster(REMOTE_CLUSTER_ALIAS, fulfillingCluster, true, randomBoolean(), randomBoolean()); + createDataStreamOnFulfillingCluster(); + setupAdditionalUsersAndRoles(); + + doTestDataStreamsWithFlsAndDls(); + } + + private void setupAdditionalUsersAndRoles() throws IOException { + createUserAndRoleOnQueryCluster("fls_user_logs_pattern", "fls_user_logs_pattern", """ + { + "indices": [ + { + "names": ["logs-*"], + "privileges": ["read"], + "field_security": { + "grant": ["@timestamp", "data_stream.namespace"] + } + } + ] + }"""); + createUserAndRoleOnFulfillingCluster("fls_user_logs_pattern", "fls_user_logs_pattern", """ + { + "indices": [ + { + "names": ["logs-*"], + "privileges": ["read"], + "field_security": { + "grant": ["@timestamp", "data_stream.namespace"] + } + } + ] + }"""); + } + + static void createUserAndRoleOnQueryCluster(String username, String roleName, String roleJson) throws IOException { + final var putRoleRequest = new Request("PUT", "/_security/role/" + roleName); + putRoleRequest.setJsonEntity(roleJson); + assertOK(adminClient().performRequest(putRoleRequest)); + + final var putUserRequest = new Request("PUT", "/_security/user/" + username); + putUserRequest.setJsonEntity(Strings.format(""" + { + "password": "%s", + "roles" : ["%s"] + }""", PASS, roleName)); + assertOK(adminClient().performRequest(putUserRequest)); + } + + static void createUserAndRoleOnFulfillingCluster(String username, String roleName, String roleJson) throws IOException { + final var putRoleRequest = new Request("PUT", "/_security/role/" + roleName); + putRoleRequest.setJsonEntity(roleJson); + assertOK(performRequestAgainstFulfillingCluster(putRoleRequest)); + + final var putUserRequest = new Request("PUT", "/_security/user/" + username); + putUserRequest.setJsonEntity(Strings.format(""" + { + "password": "%s", + "roles" : ["%s"] + }""", PASS, roleName)); + assertOK(performRequestAgainstFulfillingCluster(putUserRequest)); + } + + static Response runESQLCommandAgainstQueryCluster(String user, String command) throws IOException { + if (command.toLowerCase(Locale.ROOT).contains("limit") == false) { + // add a (high) limit to avoid warnings on default limit + command += " | limit 10000000"; + } + XContentBuilder json = JsonXContent.contentBuilder(); + json.startObject(); + json.field("query", command); + addRandomPragmas(json); + json.endObject(); + Request request = new Request("POST", "_query"); + request.setJsonEntity(org.elasticsearch.common.Strings.toString(json)); + request.setOptions(RequestOptions.DEFAULT.toBuilder().addHeader("es-security-runas-user", user)); + request.addParameter("error_trace", "true"); + Response response = adminClient().performRequest(request); + assertOK(response); + return response; + } + + static void addRandomPragmas(XContentBuilder builder) throws IOException { + if (Build.current().isSnapshot()) { + Settings pragmas = randomPragmas(); + if (pragmas != Settings.EMPTY) { + builder.startObject("pragma"); + builder.value(pragmas); + builder.endObject(); + } + } + } + + static Settings randomPragmas() { + Settings.Builder settings = Settings.builder(); + if (randomBoolean()) { + settings.put("page_size", between(1, 5)); + } + if (randomBoolean()) { + settings.put("exchange_buffer_size", between(1, 2)); + } + if (randomBoolean()) { + settings.put("data_partitioning", randomFrom("shard", "segment", "doc")); + } + if (randomBoolean()) { + settings.put("enrich_max_workers", between(1, 5)); + } + if (randomBoolean()) { + settings.put("node_level_reduction", randomBoolean()); + } + return settings.build(); + } + + static void createDataStreamOnFulfillingCluster() throws Exception { + createDataStreamPolicy(AbstractRemoteClusterSecurityTestCase::performRequestAgainstFulfillingCluster); + createDataStreamComponentTemplate(AbstractRemoteClusterSecurityTestCase::performRequestAgainstFulfillingCluster); + createDataStreamIndexTemplate(AbstractRemoteClusterSecurityTestCase::performRequestAgainstFulfillingCluster); + createDataStreamDocuments(AbstractRemoteClusterSecurityTestCase::performRequestAgainstFulfillingCluster); + createDataStreamAlias(AbstractRemoteClusterSecurityTestCase::performRequestAgainstFulfillingCluster); + } + + private static void createDataStreamPolicy(CheckedFunction requestConsumer) throws Exception { + Request request = new Request("PUT", "_ilm/policy/my-lifecycle-policy"); + request.setJsonEntity(""" + { + "policy": { + "phases": { + "hot": { + "actions": { + "rollover": { + "max_primary_shard_size": "50gb" + } + } + }, + "delete": { + "min_age": "735d", + "actions": { + "delete": {} + } + } + } + } + }"""); + + requestConsumer.apply(request); + } + + private static void createDataStreamComponentTemplate(CheckedFunction requestConsumer) throws Exception { + Request request = new Request("PUT", "_component_template/my-template"); + request.setJsonEntity(""" + { + "template": { + "settings": { + "index.lifecycle.name": "my-lifecycle-policy" + }, + "mappings": { + "properties": { + "@timestamp": { + "type": "date", + "format": "date_optional_time||epoch_millis" + }, + "data_stream": { + "properties": { + "namespace": {"type": "keyword"}, + "environment": {"type": "keyword"} + } + } + } + } + } + }"""); + requestConsumer.apply(request); + } + + private static void createDataStreamIndexTemplate(CheckedFunction requestConsumer) throws Exception { + Request request = new Request("PUT", "_index_template/my-index-template"); + request.setJsonEntity(""" + { + "index_patterns": ["logs-*"], + "data_stream": {}, + "composed_of": ["my-template"], + "priority": 500 + }"""); + requestConsumer.apply(request); + } + + private static void createDataStreamDocuments(CheckedFunction requestConsumer) throws Exception { + Request request = new Request("POST", "logs-foo/_bulk"); + request.addParameter("refresh", ""); + request.setJsonEntity(""" + { "create" : {} } + { "@timestamp": "2099-05-06T16:21:15.000Z", "data_stream": {"namespace": "16", "environment": "dev"} } + { "create" : {} } + { "@timestamp": "2001-05-06T16:21:15.000Z", "data_stream": {"namespace": "17", "environment": "prod"} } + """); + assertMap(entityAsMap(requestConsumer.apply(request)), matchesMap().extraOk().entry("errors", false)); + } + + private static void createDataStreamAlias(CheckedFunction requestConsumer) throws Exception { + Request request = new Request("PUT", "_alias"); + request.setJsonEntity(""" + { + "actions": [ + { + "add": { + "index": "logs-foo", + "alias": "alias-foo" + } + } + ] + }"""); + assertMap(entityAsMap(requestConsumer.apply(request)), matchesMap().extraOk().entry("errors", false)); + } + + static void doTestDataStreamsWithFlsAndDls() throws IOException { + // DLS + MapMatcher twoResults = matchesMap().extraOk().entry("values", matchesList().item(matchesList().item(2))); + MapMatcher oneResult = matchesMap().extraOk().entry("values", matchesList().item(matchesList().item(1))); + assertMap( + entityAsMap(runESQLCommandAgainstQueryCluster("logs_foo_all", "FROM my_remote_cluster:logs-foo | STATS COUNT(*)")), + twoResults + ); + assertMap( + entityAsMap(runESQLCommandAgainstQueryCluster("logs_foo_16_only", "FROM my_remote_cluster:logs-foo | STATS COUNT(*)")), + oneResult + ); + assertMap( + entityAsMap(runESQLCommandAgainstQueryCluster("logs_foo_after_2021", "FROM my_remote_cluster:logs-foo | STATS COUNT(*)")), + oneResult + ); + assertMap( + entityAsMap( + runESQLCommandAgainstQueryCluster("logs_foo_after_2021_pattern", "FROM my_remote_cluster:logs-foo | STATS COUNT(*)") + ), + oneResult + ); + assertMap( + entityAsMap(runESQLCommandAgainstQueryCluster("logs_foo_all", "FROM my_remote_cluster:logs-* | STATS COUNT(*)")), + twoResults + ); + assertMap( + entityAsMap(runESQLCommandAgainstQueryCluster("logs_foo_16_only", "FROM my_remote_cluster:logs-* | STATS COUNT(*)")), + oneResult + ); + assertMap( + entityAsMap(runESQLCommandAgainstQueryCluster("logs_foo_after_2021", "FROM my_remote_cluster:logs-* | STATS COUNT(*)")), + oneResult + ); + assertMap( + entityAsMap(runESQLCommandAgainstQueryCluster("logs_foo_after_2021_pattern", "FROM my_remote_cluster:logs-* | STATS COUNT(*)")), + oneResult + ); + + assertMap( + entityAsMap( + runESQLCommandAgainstQueryCluster("logs_foo_after_2021_alias", "FROM my_remote_cluster:alias-foo | STATS COUNT(*)") + ), + oneResult + ); + assertMap( + entityAsMap(runESQLCommandAgainstQueryCluster("logs_foo_after_2021_alias", "FROM my_remote_cluster:alias-* | STATS COUNT(*)")), + oneResult + ); + + // FLS + // logs_foo_all does not have FLS restrictions so should be able to access all fields + assertMap( + entityAsMap( + runESQLCommandAgainstQueryCluster("logs_foo_all", "FROM my_remote_cluster:logs-foo | SORT data_stream.namespace | LIMIT 1") + ), + matchesMap().extraOk() + .entry( + "columns", + List.of( + matchesMap().entry("name", "@timestamp").entry("type", "date"), + matchesMap().entry("name", "data_stream.environment").entry("type", "keyword"), + matchesMap().entry("name", "data_stream.namespace").entry("type", "keyword") + ) + ) + ); + assertMap( + entityAsMap( + runESQLCommandAgainstQueryCluster("logs_foo_all", "FROM my_remote_cluster:logs-* | SORT data_stream.namespace | LIMIT 1") + ), + matchesMap().extraOk() + .entry( + "columns", + List.of( + matchesMap().entry("name", "@timestamp").entry("type", "date"), + matchesMap().entry("name", "data_stream.environment").entry("type", "keyword"), + matchesMap().entry("name", "data_stream.namespace").entry("type", "keyword") + ) + ) + ); + + assertMap( + entityAsMap( + runESQLCommandAgainstQueryCluster( + "fls_user_logs_pattern", + "FROM my_remote_cluster:logs-foo | SORT data_stream.namespace | LIMIT 1" + ) + ), + matchesMap().extraOk() + .entry( + "columns", + List.of( + matchesMap().entry("name", "@timestamp").entry("type", "date"), + matchesMap().entry("name", "data_stream.namespace").entry("type", "keyword") + ) + ) + ); + assertMap( + entityAsMap( + runESQLCommandAgainstQueryCluster( + "fls_user_logs_pattern", + "FROM my_remote_cluster:logs-* | SORT data_stream.namespace | LIMIT 1" + ) + ), + matchesMap().extraOk() + .entry( + "columns", + List.of( + matchesMap().entry("name", "@timestamp").entry("type", "date"), + matchesMap().entry("name", "data_stream.namespace").entry("type", "keyword") + ) + ) + ); + } +} diff --git a/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecurityDataStreamEsqlRcs2IT.java b/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecurityDataStreamEsqlRcs2IT.java new file mode 100644 index 0000000000000..c5cf704177020 --- /dev/null +++ b/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/java/org/elasticsearch/xpack/remotecluster/RemoteClusterSecurityDataStreamEsqlRcs2IT.java @@ -0,0 +1,126 @@ +/* + * 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; you may not use this file except in compliance with the Elastic License + * 2.0. + */ + +package org.elasticsearch.xpack.remotecluster; + +import org.elasticsearch.test.cluster.ElasticsearchCluster; +import org.elasticsearch.test.cluster.util.resource.Resource; +import org.elasticsearch.test.junit.RunnableTestRuleAdapter; +import org.junit.ClassRule; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; + +import java.io.IOException; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import static org.elasticsearch.xpack.remotecluster.RemoteClusterSecurityDataStreamEsqlRcs1IT.createDataStreamOnFulfillingCluster; +import static org.elasticsearch.xpack.remotecluster.RemoteClusterSecurityDataStreamEsqlRcs1IT.createUserAndRoleOnQueryCluster; +import static org.elasticsearch.xpack.remotecluster.RemoteClusterSecurityDataStreamEsqlRcs1IT.doTestDataStreamsWithFlsAndDls; + +// TODO consolidate me with RemoteClusterSecurityDataStreamEsqlRcs1IT +public class RemoteClusterSecurityDataStreamEsqlRcs2IT extends AbstractRemoteClusterSecurityTestCase { + private static final AtomicReference> API_KEY_MAP_REF = new AtomicReference<>(); + private static final AtomicBoolean SSL_ENABLED_REF = new AtomicBoolean(); + private static final AtomicBoolean NODE1_RCS_SERVER_ENABLED = new AtomicBoolean(); + private static final AtomicBoolean NODE2_RCS_SERVER_ENABLED = new AtomicBoolean(); + + static { + fulfillingCluster = ElasticsearchCluster.local() + .name("fulfilling-cluster") + .nodes(3) + .module("x-pack-autoscaling") + .module("x-pack-esql") + .module("x-pack-enrich") + .module("x-pack-ml") + .module("x-pack-ilm") + .module("ingest-common") + .apply(commonClusterConfig) + .setting("remote_cluster.port", "0") + .setting("xpack.ml.enabled", "false") + .setting("xpack.security.remote_cluster_server.ssl.enabled", () -> String.valueOf(SSL_ENABLED_REF.get())) + .setting("xpack.security.remote_cluster_server.ssl.key", "remote-cluster.key") + .setting("xpack.security.remote_cluster_server.ssl.certificate", "remote-cluster.crt") + .setting("xpack.security.authc.token.enabled", "true") + .keystore("xpack.security.remote_cluster_server.ssl.secure_key_passphrase", "remote-cluster-password") + .node(0, spec -> spec.setting("remote_cluster_server.enabled", "true")) + .node(1, spec -> spec.setting("remote_cluster_server.enabled", () -> String.valueOf(NODE1_RCS_SERVER_ENABLED.get()))) + .node(2, spec -> spec.setting("remote_cluster_server.enabled", () -> String.valueOf(NODE2_RCS_SERVER_ENABLED.get()))) + .build(); + + queryCluster = ElasticsearchCluster.local() + .name("query-cluster") + .module("x-pack-autoscaling") + .module("x-pack-esql") + .module("x-pack-enrich") + .module("x-pack-ml") + .module("x-pack-ilm") + .module("ingest-common") + .apply(commonClusterConfig) + .setting("xpack.ml.enabled", "false") + .setting("xpack.security.remote_cluster_client.ssl.enabled", () -> String.valueOf(SSL_ENABLED_REF.get())) + .setting("xpack.security.remote_cluster_client.ssl.certificate_authorities", "remote-cluster-ca.crt") + .setting("xpack.security.authc.token.enabled", "true") + .keystore("cluster.remote.my_remote_cluster.credentials", () -> { + if (API_KEY_MAP_REF.get() == null) { + final Map apiKeyMap = createCrossClusterAccessApiKey(""" + { + "search": [ + { + "names": ["logs-*", "alias-*"] + } + ] + }"""); + API_KEY_MAP_REF.set(apiKeyMap); + } + return (String) API_KEY_MAP_REF.get().get("encoded"); + }) + .rolesFile(Resource.fromClasspath("roles.yml")) + .user("logs_foo_all", "x-pack-test-password", "logs_foo_all", false) + .user("logs_foo_16_only", "x-pack-test-password", "logs_foo_16_only", false) + .user("logs_foo_after_2021", "x-pack-test-password", "logs_foo_after_2021", false) + .user("logs_foo_after_2021_pattern", "x-pack-test-password", "logs_foo_after_2021_pattern", false) + .user("logs_foo_after_2021_alias", "x-pack-test-password", "logs_foo_after_2021_alias", false) + .build(); + } + + @ClassRule + // Use a RuleChain to ensure that fulfilling cluster is started before query cluster + // `SSL_ENABLED_REF` is used to control the SSL-enabled setting on the test clusters + // We set it here, since randomization methods are not available in the static initialize context above + public static TestRule clusterRule = RuleChain.outerRule(new RunnableTestRuleAdapter(() -> { + SSL_ENABLED_REF.set(usually()); + NODE1_RCS_SERVER_ENABLED.set(randomBoolean()); + NODE2_RCS_SERVER_ENABLED.set(randomBoolean()); + })).around(fulfillingCluster).around(queryCluster); + + public void testDataStreamsWithDlsAndFls() throws Exception { + configureRemoteCluster(); + createDataStreamOnFulfillingCluster(); + setupAdditionalUsersAndRoles(); + + doTestDataStreamsWithFlsAndDls(); + } + + private void setupAdditionalUsersAndRoles() throws IOException { + createUserAndRoleOnQueryCluster("fls_user_logs_pattern", "fls_user_logs_pattern", """ + { + "indices": [{"names": [""], "privileges": ["read"]}], + "remote_indices": [ + { + "names": ["logs-*"], + "privileges": ["read"], + "field_security": { + "grant": ["@timestamp", "data_stream.namespace"] + }, + "clusters": ["*"] + } + ] + }"""); + } +} diff --git a/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/resources/roles.yml b/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/resources/roles.yml index b61daa068ed1a..c09f9dc620a4c 100644 --- a/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/resources/roles.yml +++ b/x-pack/plugin/security/qa/multi-cluster/src/javaRestTest/resources/roles.yml @@ -41,3 +41,102 @@ ccr_user_role: manage_role: cluster: [ 'manage' ] + +logs_foo_all: + cluster: [] + indices: + - names: [ 'logs-foo' ] + privileges: [ 'read' ] + remote_indices: + - names: [ 'logs-foo' ] + clusters: [ '*' ] + privileges: [ 'read' ] + +logs_foo_16_only: + cluster: [] + indices: + - names: [ 'logs-foo' ] + privileges: [ 'read' ] + query: | + { + "term": { + "data_stream.namespace": "16" + } + } + remote_indices: + - names: [ 'logs-foo' ] + clusters: [ '*' ] + privileges: [ 'read' ] + query: | + { + "term": { + "data_stream.namespace": "16" + } + } + +logs_foo_after_2021: + cluster: [] + indices: + - names: [ 'logs-foo' ] + privileges: [ 'read' ] + query: | + { + "range": { + "@timestamp": {"gte": "2021-01-01T00:00:00"} + } + } + remote_indices: + - names: [ 'logs-foo' ] + clusters: [ '*' ] + privileges: [ 'read' ] + query: | + { + "range": { + "@timestamp": {"gte": "2021-01-01T00:00:00"} + } + } + +logs_foo_after_2021_pattern: + cluster: [] + indices: + - names: [ 'logs-*' ] + privileges: [ 'read' ] + query: | + { + "range": { + "@timestamp": {"gte": "2021-01-01T00:00:00"} + } + } + remote_indices: + - names: [ 'logs-*' ] + clusters: [ '*' ] + privileges: [ 'read' ] + query: | + { + "range": { + "@timestamp": {"gte": "2021-01-01T00:00:00"} + } + } + +logs_foo_after_2021_alias: + cluster: [] + indices: + - names: [ 'alias-foo' ] + privileges: [ 'read' ] + query: | + { + "range": { + "@timestamp": {"gte": "2021-01-01T00:00:00"} + } + } + remote_indices: + - names: [ 'alias-foo' ] + clusters: [ '*' ] + privileges: [ 'read' ] + query: | + { + "range": { + "@timestamp": {"gte": "2021-01-01T00:00:00"} + } + } +