Skip to content

Commit 006ba0e

Browse files
authored
Fix error in RemoteSegmentStoreDirectory when debug logging is enabled (opensearch-project#12328)
Signed-off-by: Peter Nied <petern@amazon.com>
1 parent 8fe15e6 commit 006ba0e

File tree

5 files changed

+124
-17
lines changed

5 files changed

+124
-17
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
9090
- Update supported version for must_exist parameter in update aliases API ([#11872](https://github.com/opensearch-project/OpenSearch/pull/11872))
9191
- [Bug] Check phase name before SearchRequestOperationsListener onPhaseStart ([#12035](https://github.com/opensearch-project/OpenSearch/pull/12035))
9292
- Fix Span operation names generated from RestActions ([#12005](https://github.com/opensearch-project/OpenSearch/pull/12005))
93+
- Fix error in RemoteSegmentStoreDirectory when debug logging is enabled ([#12328](https://github.com/opensearch-project/OpenSearch/pull/12328))
9394

9495
### Security
9596

server/src/main/java/org/opensearch/index/store/RemoteSegmentStoreDirectory.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import java.io.IOException;
4646
import java.io.InputStream;
4747
import java.nio.file.NoSuchFileException;
48+
import java.util.ArrayList;
4849
import java.util.Collection;
4950
import java.util.Collections;
5051
import java.util.HashMap;
@@ -745,9 +746,8 @@ public void deleteStaleSegments(int lastNMetadataFilesToKeep) throws IOException
745746
return;
746747
}
747748

748-
List<String> metadataFilesEligibleToDelete = sortedMetadataFileList.subList(
749-
lastNMetadataFilesToKeep,
750-
sortedMetadataFileList.size()
749+
List<String> metadataFilesEligibleToDelete = new ArrayList<>(
750+
sortedMetadataFileList.subList(lastNMetadataFilesToKeep, sortedMetadataFileList.size())
751751
);
752752
Set<String> allLockFiles;
753753
try {
@@ -764,7 +764,7 @@ public void deleteStaleSegments(int lastNMetadataFilesToKeep) throws IOException
764764
logger.debug(
765765
"metadataFilesEligibleToDelete={} metadataFilesToBeDeleted={}",
766766
metadataFilesEligibleToDelete,
767-
metadataFilesEligibleToDelete
767+
metadataFilesToBeDeleted
768768
);
769769

770770
Map<String, UploadedSegmentMetadata> activeSegmentFilesMetadataMap = new HashMap<>();

server/src/test/java/org/opensearch/index/store/RemoteSegmentStoreDirectoryTests.java

+34-13
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
package org.opensearch.index.store;
1010

11+
import org.apache.logging.log4j.Level;
1112
import org.apache.logging.log4j.LogManager;
1213
import org.apache.logging.log4j.Logger;
1314
import org.apache.lucene.codecs.CodecUtil;
@@ -41,6 +42,8 @@
4142
import org.opensearch.index.store.remote.metadata.RemoteSegmentMetadata;
4243
import org.opensearch.index.store.remote.metadata.RemoteSegmentMetadataHandler;
4344
import org.opensearch.indices.replication.common.ReplicationType;
45+
import org.opensearch.test.MockLogAppender;
46+
import org.opensearch.test.junit.annotations.TestLogging;
4447
import org.opensearch.threadpool.ThreadPool;
4548
import org.junit.After;
4649
import org.junit.Before;
@@ -58,6 +61,7 @@
5861
import java.util.concurrent.CountDownLatch;
5962
import java.util.concurrent.ExecutorService;
6063
import java.util.concurrent.TimeUnit;
64+
import java.util.stream.Collectors;
6165

6266
import org.mockito.Mockito;
6367

@@ -971,21 +975,38 @@ public void testDeleteStaleCommitsWithinThreshold() throws Exception {
971975
verify(remoteMetadataDirectory, times(0)).openInput(any(String.class), eq(IOContext.DEFAULT));
972976
}
973977

978+
@TestLogging(value = "_root:debug", reason = "Validate logging output")
974979
public void testDeleteStaleCommitsActualDelete() throws Exception {
975-
Map<String, Map<String, String>> metadataFilenameContentMapping = populateMetadata();
976-
remoteSegmentStoreDirectory.init();
977-
978-
// popluateMetadata() adds stub to return 3 metadata files
979-
// We are passing lastNMetadataFilesToKeep=2 here so that oldest 1 metadata file will be deleted
980-
remoteSegmentStoreDirectory.deleteStaleSegmentsAsync(2);
981-
982-
for (String metadata : metadataFilenameContentMapping.get(metadataFilename3).values()) {
983-
String uploadedFilename = metadata.split(RemoteSegmentStoreDirectory.UploadedSegmentMetadata.SEPARATOR)[1];
984-
verify(remoteDataDirectory).deleteFile(uploadedFilename);
980+
try (final MockLogAppender appender = MockLogAppender.createForLoggers(LogManager.getRootLogger())) {
981+
appender.addExpectation(
982+
new MockLogAppender.PatternSeenWithLoggerPrefixExpectation(
983+
"Metadata files to delete message",
984+
"org.opensearch.index.store.RemoteSegmentStoreDirectory",
985+
Level.DEBUG,
986+
"metadataFilesEligibleToDelete=\\[" + metadataFilename3 + "\\] metadataFilesToBeDeleted=\\[" + metadataFilename3 + "\\]"
987+
)
988+
);
989+
990+
final Map<String, Map<String, String>> metadataFilenameContentMapping = populateMetadata();
991+
final List<String> filesToBeDeleted = metadataFilenameContentMapping.get(metadataFilename3)
992+
.values()
993+
.stream()
994+
.map(metadata -> metadata.split(RemoteSegmentStoreDirectory.UploadedSegmentMetadata.SEPARATOR)[1])
995+
.collect(Collectors.toList());
996+
997+
remoteSegmentStoreDirectory.init();
998+
999+
// popluateMetadata() adds stub to return 3 metadata files
1000+
// We are passing lastNMetadataFilesToKeep=2 here so that oldest 1 metadata file will be deleted
1001+
remoteSegmentStoreDirectory.deleteStaleSegmentsAsync(2);
1002+
1003+
for (final String file : filesToBeDeleted) {
1004+
verify(remoteDataDirectory).deleteFile(file);
1005+
}
1006+
assertBusy(() -> assertThat(remoteSegmentStoreDirectory.canDeleteStaleCommits.get(), is(true)));
1007+
verify(remoteMetadataDirectory).deleteFile(metadataFilename3);
1008+
appender.assertAllExpectationsMatched();
9851009
}
986-
;
987-
assertBusy(() -> assertThat(remoteSegmentStoreDirectory.canDeleteStaleCommits.get(), is(true)));
988-
verify(remoteMetadataDirectory).deleteFile(metadataFilename3);
9891010
}
9901011

9911012
public void testDeleteStaleCommitsActualDeleteWithLocks() throws Exception {

test/framework/src/main/java/org/opensearch/test/MockLogAppender.java

+57
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,19 @@
3939
import org.apache.logging.log4j.core.filter.RegexFilter;
4040
import org.opensearch.common.logging.Loggers;
4141
import org.opensearch.common.regex.Regex;
42+
import org.opensearch.test.junit.annotations.TestLogging;
4243

44+
import java.util.ArrayList;
4345
import java.util.Arrays;
4446
import java.util.Collections;
4547
import java.util.List;
4648
import java.util.concurrent.CopyOnWriteArrayList;
49+
import java.util.concurrent.atomic.AtomicBoolean;
4750
import java.util.regex.Pattern;
4851

4952
import static org.hamcrest.CoreMatchers.equalTo;
5053
import static org.hamcrest.MatcherAssert.assertThat;
54+
import static org.junit.Assert.fail;
5155

5256
/**
5357
* Test appender that can be used to verify that certain events were logged correctly
@@ -259,6 +263,59 @@ public void assertMatched() {
259263

260264
}
261265

266+
/**
267+
* Used for cases when the logger is dynamically named such as to include an index name or shard id
268+
*
269+
* Best used in conjunction with the root logger:
270+
* {@code @TestLogging(value = "_root:debug", reason = "Validate logging output");}
271+
* @see TestLogging
272+
* */
273+
public static class PatternSeenWithLoggerPrefixExpectation implements LoggingExpectation {
274+
private final String expectationName;
275+
private final String loggerPrefix;
276+
private final Level level;
277+
private final String messageMatchingRegex;
278+
279+
private final List<String> loggerMatches = new ArrayList<>();
280+
private final AtomicBoolean eventSeen = new AtomicBoolean(false);
281+
282+
public PatternSeenWithLoggerPrefixExpectation(
283+
final String expectationName,
284+
final String loggerPrefix,
285+
final Level level,
286+
final String messageMatchingRegex
287+
) {
288+
this.expectationName = expectationName;
289+
this.loggerPrefix = loggerPrefix;
290+
this.level = level;
291+
this.messageMatchingRegex = messageMatchingRegex;
292+
}
293+
294+
@Override
295+
public void match(final LogEvent event) {
296+
if (event.getLevel() == level && event.getLoggerName().startsWith(loggerPrefix)) {
297+
final String formattedMessage = event.getMessage().getFormattedMessage();
298+
loggerMatches.add(formattedMessage);
299+
if (formattedMessage.matches(messageMatchingRegex)) {
300+
eventSeen.set(true);
301+
}
302+
}
303+
}
304+
305+
@Override
306+
public void assertMatched() {
307+
if (!eventSeen.get()) {
308+
final StringBuilder failureMessage = new StringBuilder();
309+
failureMessage.append(expectationName + " was not seen, found " + loggerMatches.size() + " messages matching the logger.");
310+
failureMessage.append("\r\nMessage matching regex: " + messageMatchingRegex);
311+
if (!loggerMatches.isEmpty()) {
312+
failureMessage.append("\r\nMessage details:\r\n" + String.join("\r\n", loggerMatches));
313+
}
314+
fail(failureMessage.toString());
315+
}
316+
}
317+
}
318+
262319
private static String getLoggerName(String name) {
263320
if (name.startsWith("org.opensearch.")) {
264321
name = name.substring("org.opensearch.".length());

test/framework/src/main/java/org/opensearch/test/OpenSearchTestCase.java

+28
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,8 @@
145145

146146
import java.io.IOException;
147147
import java.io.InputStream;
148+
import java.io.PrintWriter;
149+
import java.io.StringWriter;
148150
import java.math.BigInteger;
149151
import java.net.InetAddress;
150152
import java.net.UnknownHostException;
@@ -170,6 +172,7 @@
170172
import java.util.concurrent.atomic.AtomicInteger;
171173
import java.util.function.BooleanSupplier;
172174
import java.util.function.Consumer;
175+
import java.util.function.Function;
173176
import java.util.function.IntFunction;
174177
import java.util.function.Predicate;
175178
import java.util.function.Supplier;
@@ -639,7 +642,32 @@ protected static void checkStaticState(boolean afterClass) throws Exception {
639642
try {
640643
// ensure that there are no status logger messages which would indicate a problem with our Log4j usage; we map the
641644
// StatusData instances to Strings as otherwise their toString output is useless
645+
646+
final Function<StatusData, String> statusToString = (statusData) -> {
647+
try (final StringWriter sw = new StringWriter(); final PrintWriter pw = new PrintWriter(sw)) {
648+
649+
pw.print(statusData.getLevel());
650+
pw.print(":");
651+
pw.print(statusData.getMessage().getFormattedMessage());
652+
653+
if (statusData.getStackTraceElement() != null) {
654+
final var messageSource = statusData.getStackTraceElement();
655+
pw.println("Source:");
656+
pw.println(messageSource.getFileName() + "@" + messageSource.getLineNumber());
657+
}
658+
659+
if (statusData.getThrowable() != null) {
660+
pw.println("Throwable:");
661+
statusData.getThrowable().printStackTrace(pw);
662+
}
663+
return sw.toString();
664+
} catch (IOException ioe) {
665+
throw new RuntimeException(ioe);
666+
}
667+
};
668+
642669
assertThat(
670+
statusData.stream().map(statusToString::apply).collect(Collectors.joining("\r\n")),
643671
statusData.stream().map(status -> status.getMessage().getFormattedMessage()).collect(Collectors.toList()),
644672
empty()
645673
);

0 commit comments

Comments
 (0)