Skip to content

Commit faf0518

Browse files
authored
Merge branch 'main' into keep-drop-attributes-when-resolving-field-names
2 parents 6d80d58 + bc574c9 commit faf0518

File tree

13 files changed

+329
-58
lines changed

13 files changed

+329
-58
lines changed

docs/changelog/127921.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 127921
2+
summary: "[9.x] Revert \"Enable madvise by default for all builds\""
3+
area: Vector Search
4+
type: bug
5+
issues: []

muted-tests.yml

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -66,9 +66,6 @@ tests:
6666
- class: org.elasticsearch.xpack.test.rest.XPackRestIT
6767
method: test {p0=transform/transforms_start_stop/Test start already started transform}
6868
issue: https://github.com/elastic/elasticsearch/issues/98802
69-
- class: org.elasticsearch.xpack.deprecation.DeprecationHttpIT
70-
method: testDeprecatedSettingsReturnWarnings
71-
issue: https://github.com/elastic/elasticsearch/issues/108628
7269
- class: org.elasticsearch.xpack.shutdown.NodeShutdownIT
7370
method: testAllocationPreventedForRemoval
7471
issue: https://github.com/elastic/elasticsearch/issues/116363
@@ -408,9 +405,6 @@ tests:
408405
- class: org.elasticsearch.xpack.esql.heap_attack.HeapAttackIT
409406
method: testLookupExplosionNoFetch
410407
issue: https://github.com/elastic/elasticsearch/issues/127365
411-
- class: org.elasticsearch.xpack.esql.qa.single_node.GenerativeIT
412-
method: test
413-
issue: https://github.com/elastic/elasticsearch/issues/127536
414408
- class: org.elasticsearch.xpack.test.rest.XPackRestIT
415409
method: test {p0=ml/data_frame_analytics_cat_apis/Test cat data frame analytics all jobs with header}
416410
issue: https://github.com/elastic/elasticsearch/issues/127625
@@ -468,12 +462,9 @@ tests:
468462
- class: org.elasticsearch.indices.stats.IndexStatsIT
469463
method: testThrottleStats
470464
issue: https://github.com/elastic/elasticsearch/issues/126359
471-
- class: org.elasticsearch.xpack.esql.expression.function.aggregate.SampleTests
472-
method: testGroupingAggregate {TestCase=<long unicode KEYWORDs>}
473-
issue: https://github.com/elastic/elasticsearch/issues/127950
474-
- class: org.elasticsearch.xpack.esql.expression.function.aggregate.SampleTests
475-
method: testGroupingAggregate {TestCase=<long unicode TEXTs>}
476-
issue: https://github.com/elastic/elasticsearch/issues/127951
465+
- class: org.elasticsearch.search.vectors.IVFKnnFloatVectorQueryTests
466+
method: testRandomWithFilter
467+
issue: https://github.com/elastic/elasticsearch/issues/127963
477468

478469
# Examples:
479470
#

server/src/main/java/org/elasticsearch/cluster/service/MasterService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -913,7 +913,7 @@ public void onFailure(Exception failure) {
913913

914914
@Override
915915
public Releasable captureResponseHeaders() {
916-
final var storedContext = threadContext.newStoredContext();
916+
final var storedContext = threadContextSupplier.get();
917917
return Releasables.wrap(() -> {
918918
final var newResponseHeaders = threadContext.getResponseHeaders();
919919
if (newResponseHeaders.isEmpty()) {

server/src/main/java/org/elasticsearch/index/store/FsDirectoryFactory.java

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@
2020
import org.apache.lucene.store.MMapDirectory;
2121
import org.apache.lucene.store.NIOFSDirectory;
2222
import org.apache.lucene.store.NativeFSLockFactory;
23+
import org.apache.lucene.store.ReadAdvice;
2324
import org.apache.lucene.store.SimpleFSLockFactory;
2425
import org.elasticsearch.common.settings.Setting;
2526
import org.elasticsearch.common.settings.Setting.Property;
27+
import org.elasticsearch.common.util.FeatureFlag;
2628
import org.elasticsearch.core.IOUtils;
2729
import org.elasticsearch.index.IndexModule;
2830
import org.elasticsearch.index.IndexSettings;
@@ -43,6 +45,7 @@
4345
public class FsDirectoryFactory implements IndexStorePlugin.DirectoryFactory {
4446

4547
private static final Logger Log = LogManager.getLogger(FsDirectoryFactory.class);
48+
private static final FeatureFlag MADV_RANDOM_FEATURE_FLAG = new FeatureFlag("madv_random");
4649

4750
public static final Setting<LockFactory> INDEX_LOCK_FACTOR_SETTING = new Setting<>("index.store.fs.fs_lock", "native", (s) -> {
4851
return switch (s) {
@@ -75,12 +78,20 @@ protected Directory newFSDirectory(Path location, LockFactory lockFactory, Index
7578
// Use Lucene defaults
7679
final FSDirectory primaryDirectory = FSDirectory.open(location, lockFactory);
7780
if (primaryDirectory instanceof MMapDirectory mMapDirectory) {
78-
return new HybridDirectory(lockFactory, setPreload(mMapDirectory, preLoadExtensions));
81+
Directory dir = new HybridDirectory(lockFactory, setPreload(mMapDirectory, preLoadExtensions));
82+
if (MADV_RANDOM_FEATURE_FLAG.isEnabled() == false) {
83+
dir = disableRandomAdvice(dir);
84+
}
85+
return dir;
7986
} else {
8087
return primaryDirectory;
8188
}
8289
case MMAPFS:
83-
return setPreload(new MMapDirectory(location, lockFactory), preLoadExtensions);
90+
Directory dir = setPreload(new MMapDirectory(location, lockFactory), preLoadExtensions);
91+
if (MADV_RANDOM_FEATURE_FLAG.isEnabled() == false) {
92+
dir = disableRandomAdvice(dir);
93+
}
94+
return dir;
8495
case SIMPLEFS:
8596
case NIOFS:
8697
return new NIOFSDirectory(location, lockFactory);
@@ -108,6 +119,23 @@ static BiPredicate<String, IOContext> getPreloadFunc(Set<String> preLoadExtensio
108119
return MMapDirectory.NO_FILES;
109120
}
110121

122+
/**
123+
* Return a {@link FilterDirectory} around the provided {@link Directory} that forcefully disables {@link IOContext#readAdvice random
124+
* access}.
125+
*/
126+
static Directory disableRandomAdvice(Directory dir) {
127+
return new FilterDirectory(dir) {
128+
@Override
129+
public IndexInput openInput(String name, IOContext context) throws IOException {
130+
if (context.readAdvice() == ReadAdvice.RANDOM) {
131+
context = context.withReadAdvice(ReadAdvice.NORMAL);
132+
}
133+
assert context.readAdvice() != ReadAdvice.RANDOM;
134+
return super.openInput(name, context);
135+
}
136+
};
137+
}
138+
111139
/**
112140
* Returns true iff the directory is a hybrid fs directory
113141
*/

server/src/test/java/org/elasticsearch/cluster/service/MasterServiceTests.java

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,6 @@ public void testThreadContext() {
299299
}
300300

301301
try (ThreadContext.StoredContext ignored = threadPool.getThreadContext().stashContext()) {
302-
303302
final var expectedHeaders = new HashMap<String, String>();
304303
expectedHeaders.put(randomIdentifier(), randomIdentifier());
305304
for (final var copiedHeader : Task.HEADERS_TO_COPY) {
@@ -319,8 +318,10 @@ public void testThreadContext() {
319318
new AckedClusterStateUpdateTask(ackedRequest(ackTimeout, masterTimeout), null) {
320319
@Override
321320
public ClusterState execute(ClusterState currentState) {
322-
assertTrue(threadPool.getThreadContext().isSystemContext());
323-
assertEquals(Collections.emptyMap(), threadPool.getThreadContext().getHeaders());
321+
// the task is executed in the context in which the task was originally created.
322+
// note: this is typically not a system context.
323+
assertExpectedThreadContext(Map.of());
324+
324325
expectedResponseHeaders.forEach(
325326
(name, values) -> values.forEach(v -> threadPool.getThreadContext().addResponseHeader(name, v))
326327
);

server/src/test/java/org/elasticsearch/index/store/FsDirectoryFactoryTests.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,16 @@
99
package org.elasticsearch.index.store;
1010

1111
import org.apache.lucene.store.AlreadyClosedException;
12+
import org.apache.lucene.store.ByteBuffersDirectory;
1213
import org.apache.lucene.store.Directory;
1314
import org.apache.lucene.store.FilterDirectory;
1415
import org.apache.lucene.store.IOContext;
16+
import org.apache.lucene.store.IndexInput;
17+
import org.apache.lucene.store.IndexOutput;
1518
import org.apache.lucene.store.MMapDirectory;
1619
import org.apache.lucene.store.NIOFSDirectory;
1720
import org.apache.lucene.store.NoLockFactory;
21+
import org.apache.lucene.store.ReadAdvice;
1822
import org.apache.lucene.store.SleepingLockWrapper;
1923
import org.apache.lucene.util.Constants;
2024
import org.elasticsearch.cluster.metadata.IndexMetadata;
@@ -34,6 +38,7 @@
3438
import java.nio.file.Path;
3539
import java.util.Arrays;
3640
import java.util.HashMap;
41+
import java.util.List;
3742
import java.util.Locale;
3843
import java.util.Map;
3944
import java.util.Set;
@@ -74,6 +79,29 @@ public void testPreload() throws IOException {
7479
}
7580
}
7681

82+
public void testDisableRandomAdvice() throws IOException {
83+
Directory dir = new FilterDirectory(new ByteBuffersDirectory()) {
84+
@Override
85+
public IndexInput openInput(String name, IOContext context) throws IOException {
86+
assertFalse(context.readAdvice() == ReadAdvice.RANDOM);
87+
return super.openInput(name, context);
88+
}
89+
};
90+
Directory noRandomAccessDir = FsDirectoryFactory.disableRandomAdvice(dir);
91+
try (IndexOutput out = noRandomAccessDir.createOutput("foo", IOContext.DEFAULT)) {
92+
out.writeInt(42);
93+
}
94+
// Test the tester
95+
expectThrows(AssertionError.class, () -> dir.openInput("foo", IOContext.DEFAULT.withReadAdvice(ReadAdvice.RANDOM)));
96+
97+
// The wrapped directory shouldn't fail regardless of the IOContext
98+
for (IOContext context : List.of(IOContext.DEFAULT, IOContext.READONCE, IOContext.DEFAULT.withReadAdvice(ReadAdvice.RANDOM))) {
99+
try (IndexInput in = noRandomAccessDir.openInput("foo", context)) {
100+
assertEquals(42, in.readInt());
101+
}
102+
}
103+
}
104+
77105
private Directory newDirectory(Settings settings) throws IOException {
78106
IndexSettings idxSettings = IndexSettingsModule.newIndexSettings("foo", settings);
79107
Path tempDir = createTempDir().resolve(idxSettings.getUUID()).resolve("0");
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.action.admin.indices.stats;
11+
12+
import org.elasticsearch.client.Request;
13+
import org.elasticsearch.client.ResponseException;
14+
import org.elasticsearch.common.Strings;
15+
import org.elasticsearch.common.settings.SecureString;
16+
import org.elasticsearch.common.settings.Settings;
17+
import org.elasticsearch.common.util.concurrent.ThreadContext;
18+
import org.elasticsearch.multiproject.MultiProjectRestTestCase;
19+
import org.elasticsearch.test.cluster.ElasticsearchCluster;
20+
import org.elasticsearch.test.cluster.local.distribution.DistributionType;
21+
import org.elasticsearch.test.rest.ObjectPath;
22+
import org.junit.ClassRule;
23+
24+
import java.io.IOException;
25+
import java.util.Map;
26+
27+
import static org.hamcrest.Matchers.containsInAnyOrder;
28+
import static org.hamcrest.Matchers.equalTo;
29+
30+
public class IndicesStatsMultiProjectIT extends MultiProjectRestTestCase {
31+
32+
private static final String PASSWORD = "hunter2";
33+
34+
@ClassRule
35+
public static ElasticsearchCluster cluster = ElasticsearchCluster.local()
36+
.nodes(1)
37+
.distribution(DistributionType.INTEG_TEST)
38+
.module("test-multi-project")
39+
.setting("test.multi_project.enabled", "true")
40+
.setting("xpack.security.enabled", "true")
41+
.user("admin", PASSWORD)
42+
.build();
43+
44+
@Override
45+
protected String getTestRestCluster() {
46+
return cluster.getHttpAddresses();
47+
}
48+
49+
@Override
50+
protected Settings restClientSettings() {
51+
final String token = basicAuthHeaderValue("admin", new SecureString(PASSWORD.toCharArray()));
52+
return Settings.builder().put(ThreadContext.PREFIX + ".Authorization", token).build();
53+
}
54+
55+
public void testIndicesStats() throws IOException {
56+
// Create two projects. We will use the default project as the third project in this test.
57+
createProject("project-1");
58+
createProject("project-2");
59+
60+
// Create and write data into a number of indices.
61+
// Some of the index names are used only in one project, some in two projects, some in all three.
62+
int numDocs1Only = createPopulatedIndex("project-1", "my-index-project-1-only");
63+
int numDocs2Only = createPopulatedIndex("project-2", "my-index-project-2-only");
64+
int numDocsDefaultOnly = createPopulatedIndex("default", "my-index-default-project-only");
65+
int numDocs1Of1And2 = createPopulatedIndex("project-1", "my-index-projects-1-and-2");
66+
int numDocs2Of1And2 = createPopulatedIndex("project-2", "my-index-projects-1-and-2");
67+
int numDocs2Of2AndDefault = createPopulatedIndex("project-2", "my-index-projects-2-and-default");
68+
int numDocsDefaultOf2AndDefault = createPopulatedIndex("default", "my-index-projects-2-and-default");
69+
int numDocs1All = createPopulatedIndex("project-1", "my-index-all-projects");
70+
int numDocs2All = createPopulatedIndex("project-2", "my-index-all-projects");
71+
int numDocsDefaultAll = createPopulatedIndex("default", "my-index-all-projects");
72+
73+
// Check indices stats for project 1.
74+
ObjectPath statsForProject1 = getAsObjectPathInProject("/_stats", "project-1");
75+
assertThat(statsForProject1.evaluate("_all.total.docs.count"), equalTo(numDocs1Only + numDocs1Of1And2 + numDocs1All));
76+
assertThat(
77+
statsForProject1.<Map<String, Object>>evaluate("indices").keySet(),
78+
containsInAnyOrder("my-index-project-1-only", "my-index-projects-1-and-2", "my-index-all-projects")
79+
);
80+
assertThat(statsForProject1.evaluate("indices.my-index-project-1-only.total.docs.count"), equalTo(numDocs1Only));
81+
assertThat(statsForProject1.evaluate("indices.my-index-projects-1-and-2.total.docs.count"), equalTo(numDocs1Of1And2));
82+
assertThat(statsForProject1.evaluate("indices.my-index-all-projects.total.docs.count"), equalTo(numDocs1All));
83+
84+
// Check indices stats for project 2.
85+
ObjectPath statsForProject2 = getAsObjectPathInProject("/_stats", "project-2");
86+
assertThat(
87+
statsForProject2.evaluate("_all.total.docs.count"),
88+
equalTo(numDocs2Only + numDocs2Of1And2 + numDocs2Of2AndDefault + numDocs2All)
89+
);
90+
assertThat(
91+
statsForProject2.<Map<String, Object>>evaluate("indices").keySet(),
92+
containsInAnyOrder(
93+
"my-index-project-2-only",
94+
"my-index-projects-1-and-2",
95+
"my-index-projects-2-and-default",
96+
"my-index-all-projects"
97+
)
98+
);
99+
assertThat(statsForProject2.evaluate("indices.my-index-all-projects.total.docs.count"), equalTo(numDocs2All));
100+
101+
// Check indices stats for default project.
102+
ObjectPath statsForDefaultProject = getAsObjectPathInDefaultProject("/_stats");
103+
assertThat(
104+
statsForDefaultProject.evaluate("_all.total.docs.count"),
105+
equalTo(numDocsDefaultOnly + numDocsDefaultOf2AndDefault + numDocsDefaultAll)
106+
);
107+
assertThat(
108+
statsForDefaultProject.<Map<String, Object>>evaluate("indices").keySet(),
109+
containsInAnyOrder("my-index-default-project-only", "my-index-projects-2-and-default", "my-index-all-projects")
110+
);
111+
assertThat(statsForDefaultProject.evaluate("indices.my-index-all-projects.total.docs.count"), equalTo(numDocsDefaultAll));
112+
113+
// Check single-index stats for each project.
114+
assertThat(
115+
getAsObjectPathInProject("/my-index-all-projects/_stats", "project-1").evaluate("_all.total.docs.count"),
116+
equalTo(numDocs1All)
117+
);
118+
assertThat(
119+
getAsObjectPathInProject("/my-index-all-projects/_stats", "project-2").evaluate("_all.total.docs.count"),
120+
equalTo(numDocs2All)
121+
);
122+
assertThat(
123+
getAsObjectPathInDefaultProject("/my-index-all-projects/_stats").evaluate("_all.total.docs.count"),
124+
equalTo(numDocsDefaultAll)
125+
);
126+
127+
// Check that getting single-index stats for an index that does not exist in a project returns a 404.
128+
ResponseException exception = assertThrows(
129+
ResponseException.class,
130+
() -> client().performRequest(setRequestProjectId(new Request("GET", "/my-index-project-1-only/_stats"), "project-2"))
131+
);
132+
assertThat(exception.getResponse().getStatusLine().getStatusCode(), equalTo(404));
133+
134+
// Check using a wildcard gets the matching indices for that project.
135+
ObjectPath statsWithWildcardForProject1 = getAsObjectPathInProject("/my-index-project*/_stats", "project-1");
136+
assertThat(statsWithWildcardForProject1.evaluate("_all.total.docs.count"), equalTo(numDocs1Only + numDocs1Of1And2));
137+
assertThat(
138+
statsWithWildcardForProject1.<Map<String, Object>>evaluate("indices").keySet(),
139+
containsInAnyOrder("my-index-project-1-only", "my-index-projects-1-and-2")
140+
);
141+
}
142+
143+
private int createPopulatedIndex(String projectId, String indexName) throws IOException {
144+
createIndex(req -> {
145+
setRequestProjectId(req, projectId);
146+
return client().performRequest(req);
147+
}, indexName, null, null, null);
148+
int numDocs = randomIntBetween(5, 10);
149+
for (int i = 0; i < numDocs; i++) {
150+
Request request = new Request("POST", "/" + indexName + "/_doc");
151+
request.setJsonEntity(Strings.format("{ \"num\": %d, \"str\": \"%s\" }", randomInt(), randomAlphaOfLengthBetween(5, 10)));
152+
setRequestProjectId(request, projectId);
153+
client().performRequest(request);
154+
}
155+
client().performRequest(setRequestProjectId(new Request("POST", "/" + indexName + "/_refresh"), projectId));
156+
return numDocs;
157+
}
158+
159+
private static ObjectPath getAsObjectPathInProject(String endpoint, String projectId) throws IOException {
160+
return new ObjectPath(responseAsOrderedMap(client().performRequest(setRequestProjectId(new Request("GET", endpoint), projectId))));
161+
}
162+
163+
private static ObjectPath getAsObjectPathInDefaultProject(String endpoint) throws IOException {
164+
return new ObjectPath(getAsMap(endpoint));
165+
}
166+
}

x-pack/plugin/build.gradle

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,11 @@ tasks.named("yamlRestCompatTestTransform").configure({ task ->
105105
task.skipTest("esql/130_spatial/values unsupported for geo_point", "Spatial types are now supported in VALUES aggregation")
106106
task.skipTest("esql/130_spatial/values unsupported for geo_point status code", "Spatial types are now supported in VALUES aggregation")
107107
// Expected deprecation warning to compat yaml tests:
108-
task.addAllowedWarningRegex(".*rollup functionality will be removed in Elasticsearch.*")
108+
task.addAllowedWarningRegex(
109+
".*rollup functionality will be removed in Elasticsearch.*",
110+
// https://github.com/elastic/elasticsearch/issues/127911
111+
"Index \\[\\.profiling-.*\\] name begins with a dot.* and will not be allowed in a future Elasticsearch version."
112+
)
109113
task.skipTest("esql/40_tsdb/from doc with aggregate_metric_double", "TODO: support for subset of metric fields")
110114
task.skipTest("esql/40_tsdb/stats on aggregate_metric_double", "TODO: support for subset of metric fields")
111115
task.skipTest("esql/40_tsdb/from index pattern unsupported counter", "TODO: support for subset of metric fields")

0 commit comments

Comments
 (0)