Skip to content

Commit cb941f6

Browse files
committed
Merge branch 'upstream/main' into add-alibabacloud-inference
* upstream/main: Fail `indexDocs()` on rejection (elastic#111962) Move repo analyzer to its own package (elastic#111963) Add generated evaluators for DateNanos conversion functions (elastic#111961) Clean the last traces from global retention in templates (elastic#111669) Fix known issue docs for elastic#111866 (elastic#111956) x-pack/plugin/otel: introduce x-pack-otel plugin (elastic#111091) Improve reaction to blob store corruptions (elastic#111954) Introduce `StreamingXContentResponse` (elastic#111933) Revert "Add 8.15.0 known issue for memory locking in Windows (elastic#111949)" Test get-snapshots API with missing details (elastic#111903) Add 8.15.0 known issue for memory locking in Windows (elastic#111949) # Conflicts: # server/src/main/java/org/elasticsearch/TransportVersions.java
2 parents 69e44be + ca6d41c commit cb941f6

File tree

90 files changed

+3063
-425
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

90 files changed

+3063
-425
lines changed

.github/CODEOWNERS

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,12 @@ libs/logstash-bridge @elastic/logstash
2727
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/security/authz/store/KibanaOwnedReservedRoleDescriptors.java @elastic/kibana-security
2828

2929
# APM Data index templates, etc.
30-
x-pack/plugin/apm-data/src/main/resources @elastic/apm-server
31-
x-pack/plugin/apm-data/src/yamlRestTest/resources @elastic/apm-server
30+
x-pack/plugin/apm-data/src/main/resources @elastic/obs-ds-intake-services
31+
x-pack/plugin/apm-data/src/yamlRestTest/resources @elastic/obs-ds-intake-services
32+
33+
# OTel
34+
x-pack/plugin/otel-data/src/main/resources @elastic/obs-ds-intake-services
35+
x-pack/plugin/otel-data/src/yamlRestTest/resources @elastic/obs-ds-intake-services
3236

3337
# Delivery
3438
gradle @elastic/es-delivery

docs/changelog/111091.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pr: 111091
2+
summary: "X-pack/plugin/otel: introduce x-pack-otel plugin"
3+
area: Data streams
4+
type: feature
5+
issues: []

docs/reference/release-notes/8.15.0.asciidoc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ after it is killed up to four times in 24 hours. (issue: {es-issue}110530[#11053
1616
* Pipeline aggregations under `time_series` and `categorize_text` aggregations are never
1717
returned (issue: {es-issue}111679[#111679])
1818

19+
* Elasticsearch will not start on Windows machines if
20+
[`bootstrap.memory_lock` is set to `true`](https://www.elastic.co/guide/en/elasticsearch/reference/current/setup-configuration-memory.html#bootstrap-memory_lock).
21+
Either downgrade to an earlier version, upgrade to 8.15.1, or else follow the
22+
recommendation in the manual to entirely disable swap instead of using the
23+
memory lock feature (issue: {es-issue}111847[#111847])
24+
1925
[[breaking-8.15.0]]
2026
[float]
2127
=== Breaking changes
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
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 and the Server Side Public License, v 1; you may not use this file except
5+
* in compliance with, at your election, the Elastic License 2.0 or the Server
6+
* Side Public License, v 1.
7+
*/
8+
9+
package org.elasticsearch.repositories.blobstore;
10+
11+
import org.apache.lucene.tests.mockfile.ExtrasFS;
12+
import org.elasticsearch.ElasticsearchException;
13+
import org.elasticsearch.action.ActionListener;
14+
import org.elasticsearch.action.admin.cluster.snapshots.restore.RestoreSnapshotResponse;
15+
import org.elasticsearch.action.support.ActionTestUtils;
16+
import org.elasticsearch.action.support.SubscribableListener;
17+
import org.elasticsearch.action.support.master.AcknowledgedResponse;
18+
import org.elasticsearch.common.Strings;
19+
import org.elasticsearch.core.CheckedConsumer;
20+
import org.elasticsearch.index.snapshots.blobstore.BlobStoreIndexShardSnapshotsIntegritySuppressor;
21+
import org.elasticsearch.logging.LogManager;
22+
import org.elasticsearch.logging.Logger;
23+
import org.elasticsearch.repositories.fs.FsRepository;
24+
import org.elasticsearch.snapshots.AbstractSnapshotIntegTestCase;
25+
import org.elasticsearch.snapshots.SnapshotState;
26+
import org.elasticsearch.test.ESTestCase;
27+
import org.elasticsearch.test.hamcrest.ElasticsearchAssertions;
28+
import org.junit.Before;
29+
30+
import java.io.IOException;
31+
import java.nio.file.FileVisitResult;
32+
import java.nio.file.Files;
33+
import java.nio.file.Path;
34+
import java.nio.file.SimpleFileVisitor;
35+
import java.nio.file.attribute.BasicFileAttributes;
36+
import java.util.ArrayList;
37+
import java.util.Base64;
38+
import java.util.List;
39+
40+
public class BlobStoreCorruptionIT extends AbstractSnapshotIntegTestCase {
41+
42+
private static final Logger logger = LogManager.getLogger(BlobStoreCorruptionIT.class);
43+
44+
@Before
45+
public void suppressConsistencyCheck() {
46+
disableRepoConsistencyCheck("testing corruption detection involves breaking the repo");
47+
}
48+
49+
public void testCorruptionDetection() throws Exception {
50+
final var repositoryName = randomIdentifier();
51+
final var indexName = randomIdentifier();
52+
final var snapshotName = randomIdentifier();
53+
final var repositoryRootPath = randomRepoPath();
54+
55+
createRepository(repositoryName, FsRepository.TYPE, repositoryRootPath);
56+
createIndexWithRandomDocs(indexName, between(1, 100));
57+
flushAndRefresh(indexName);
58+
createSnapshot(repositoryName, snapshotName, List.of(indexName));
59+
60+
final var corruptedFile = corruptRandomFile(repositoryRootPath);
61+
final var corruptedFileType = RepositoryFileType.getRepositoryFileType(repositoryRootPath, corruptedFile);
62+
final var corruptionDetectors = new ArrayList<CheckedConsumer<ActionListener<Exception>, ?>>();
63+
64+
// detect corruption by listing the snapshots
65+
if (corruptedFileType == RepositoryFileType.SNAPSHOT_INFO) {
66+
corruptionDetectors.add(exceptionListener -> {
67+
logger.info("--> listing snapshots");
68+
client().admin()
69+
.cluster()
70+
.prepareGetSnapshots(TEST_REQUEST_TIMEOUT, repositoryName)
71+
.execute(ActionTestUtils.assertNoSuccessListener(exceptionListener::onResponse));
72+
});
73+
}
74+
75+
// detect corruption by taking another snapshot
76+
if (corruptedFileType == RepositoryFileType.SHARD_GENERATION) {
77+
corruptionDetectors.add(exceptionListener -> {
78+
logger.info("--> taking another snapshot");
79+
client().admin()
80+
.cluster()
81+
.prepareCreateSnapshot(TEST_REQUEST_TIMEOUT, repositoryName, randomIdentifier())
82+
.setWaitForCompletion(true)
83+
.execute(exceptionListener.map(createSnapshotResponse -> {
84+
assertNotEquals(SnapshotState.SUCCESS, createSnapshotResponse.getSnapshotInfo().state());
85+
return new ElasticsearchException("create-snapshot failed as expected");
86+
}));
87+
});
88+
}
89+
90+
// detect corruption by restoring the snapshot
91+
switch (corruptedFileType) {
92+
case SNAPSHOT_INFO, GLOBAL_METADATA, INDEX_METADATA -> corruptionDetectors.add(exceptionListener -> {
93+
logger.info("--> restoring snapshot");
94+
client().admin()
95+
.cluster()
96+
.prepareRestoreSnapshot(TEST_REQUEST_TIMEOUT, repositoryName, snapshotName)
97+
.setRestoreGlobalState(corruptedFileType == RepositoryFileType.GLOBAL_METADATA || randomBoolean())
98+
.setWaitForCompletion(true)
99+
.execute(ActionTestUtils.assertNoSuccessListener(exceptionListener::onResponse));
100+
});
101+
case SHARD_SNAPSHOT_INFO, SHARD_DATA -> corruptionDetectors.add(exceptionListener -> {
102+
logger.info("--> restoring snapshot and checking for failed shards");
103+
SubscribableListener
104+
// if shard-level data is corrupted then the overall restore succeeds but the shard recoveries fail
105+
.<AcknowledgedResponse>newForked(l -> client().admin().indices().prepareDelete(indexName).execute(l))
106+
.andThenAccept(ElasticsearchAssertions::assertAcked)
107+
108+
.<RestoreSnapshotResponse>andThen(
109+
l -> client().admin()
110+
.cluster()
111+
.prepareRestoreSnapshot(TEST_REQUEST_TIMEOUT, repositoryName, snapshotName)
112+
.setRestoreGlobalState(randomBoolean())
113+
.setWaitForCompletion(true)
114+
.execute(l)
115+
)
116+
117+
.addListener(exceptionListener.map(restoreSnapshotResponse -> {
118+
assertNotEquals(0, restoreSnapshotResponse.getRestoreInfo().failedShards());
119+
return new ElasticsearchException("post-restore recoveries failed as expected");
120+
}));
121+
});
122+
}
123+
124+
try (var ignored = new BlobStoreIndexShardSnapshotsIntegritySuppressor()) {
125+
final var exception = safeAwait(randomFrom(corruptionDetectors));
126+
logger.info(Strings.format("--> corrupted [%s] and caught exception", corruptedFile), exception);
127+
}
128+
}
129+
130+
private static Path corruptRandomFile(Path repositoryRootPath) throws IOException {
131+
final var corruptedFileType = getRandomCorruptibleFileType();
132+
final var corruptedFile = getRandomFileToCorrupt(repositoryRootPath, corruptedFileType);
133+
if (randomBoolean()) {
134+
logger.info("--> deleting [{}]", corruptedFile);
135+
Files.delete(corruptedFile);
136+
} else {
137+
corruptFileContents(corruptedFile);
138+
}
139+
return corruptedFile;
140+
}
141+
142+
private static void corruptFileContents(Path fileToCorrupt) throws IOException {
143+
final var oldFileContents = Files.readAllBytes(fileToCorrupt);
144+
logger.info("--> contents of [{}] before corruption: [{}]", fileToCorrupt, Base64.getEncoder().encodeToString(oldFileContents));
145+
final byte[] newFileContents = new byte[randomBoolean() ? oldFileContents.length : between(0, oldFileContents.length)];
146+
System.arraycopy(oldFileContents, 0, newFileContents, 0, newFileContents.length);
147+
if (newFileContents.length == oldFileContents.length) {
148+
final var corruptionPosition = between(0, newFileContents.length - 1);
149+
newFileContents[corruptionPosition] = randomValueOtherThan(oldFileContents[corruptionPosition], ESTestCase::randomByte);
150+
logger.info(
151+
"--> updating byte at position [{}] from [{}] to [{}]",
152+
corruptionPosition,
153+
oldFileContents[corruptionPosition],
154+
newFileContents[corruptionPosition]
155+
);
156+
} else {
157+
logger.info("--> truncating file from length [{}] to length [{}]", oldFileContents.length, newFileContents.length);
158+
}
159+
Files.write(fileToCorrupt, newFileContents);
160+
logger.info("--> contents of [{}] after corruption: [{}]", fileToCorrupt, Base64.getEncoder().encodeToString(newFileContents));
161+
}
162+
163+
private static RepositoryFileType getRandomCorruptibleFileType() {
164+
return randomValueOtherThanMany(
165+
// these blob types do not have reliable corruption detection, so we must skip them
166+
t -> t == RepositoryFileType.ROOT_INDEX_N || t == RepositoryFileType.ROOT_INDEX_LATEST,
167+
() -> randomFrom(RepositoryFileType.values())
168+
);
169+
}
170+
171+
private static Path getRandomFileToCorrupt(Path repositoryRootPath, RepositoryFileType corruptedFileType) throws IOException {
172+
final var corruptibleFiles = new ArrayList<Path>();
173+
Files.walkFileTree(repositoryRootPath, new SimpleFileVisitor<>() {
174+
@Override
175+
public FileVisitResult visitFile(Path filePath, BasicFileAttributes attrs) throws IOException {
176+
if (ExtrasFS.isExtra(filePath.getFileName().toString()) == false
177+
&& RepositoryFileType.getRepositoryFileType(repositoryRootPath, filePath) == corruptedFileType) {
178+
corruptibleFiles.add(filePath);
179+
}
180+
return super.visitFile(filePath, attrs);
181+
}
182+
});
183+
return randomFrom(corruptibleFiles);
184+
}
185+
186+
}

0 commit comments

Comments
 (0)