use of io.pravega.segmentstore.server.DataCorruptionException in project pravega by pravega.
the class SegmentAggregatorTests method testSegmentMissingData.
/**
* Tests the case when a Segment's data is missing from the ReadIndex (but the Segment itself is not deleted).
*/
@Test
public void testSegmentMissingData() throws Exception {
final WriterConfig config = DEFAULT_CONFIG;
@Cleanup TestContext context = new TestContext(config);
context.segmentAggregator.initialize(TIMEOUT).join();
// Add one operation big enough to trigger a Flush.
byte[] appendData = new byte[config.getFlushThresholdBytes() + 1];
StorageOperation appendOp = generateAppendAndUpdateMetadata(SEGMENT_ID, appendData, context);
context.segmentAggregator.add(appendOp);
Assert.assertTrue("Unexpected value returned by mustFlush() (size threshold).", context.segmentAggregator.mustFlush());
// Clear the append data.
context.dataSource.clearAppendData();
// Call flush() and verify it throws DataCorruptionException.
AssertExtensions.assertSuppliedFutureThrows("flush() did not throw when unable to read data from ReadIndex.", () -> context.segmentAggregator.flush(TIMEOUT), ex -> ex instanceof DataCorruptionException);
}
use of io.pravega.segmentstore.server.DataCorruptionException in project pravega by pravega.
the class DurableLogTests method testRecoveryWithMetadataCleanup.
/**
* Tests the following recovery scenario:
* 1. A Segment is created and recorded in the metadata with some optional operations executing on it.
* 2. The segment is evicted from the metadata.
* 3. The segment is reactivated (with a new metadata mapping) - possibly due to an append. No truncation since #2.
* 4. Recovery.
*/
@Test
public void testRecoveryWithMetadataCleanup() throws Exception {
final long truncatedSeqNo = Integer.MAX_VALUE;
// Setup a DurableLog and start it.
@Cleanup TestDurableDataLogFactory dataLogFactory = new TestDurableDataLogFactory(new InMemoryDurableDataLogFactory(MAX_DATA_LOG_APPEND_SIZE, executorService()));
@Cleanup Storage storage = InMemoryStorageFactory.newStorage(executorService());
storage.initialize(1);
long segmentId;
// First DurableLog. We use this for generating data.
val metadata1 = (StreamSegmentContainerMetadata) new MetadataBuilder(CONTAINER_ID).build();
@Cleanup CacheStorage cacheStorage = new DirectMemoryCache(Integer.MAX_VALUE);
@Cleanup CacheManager cacheManager = new CacheManager(CachePolicy.INFINITE, cacheStorage, executorService());
SegmentProperties originalSegmentInfo;
try (ReadIndex readIndex = new ContainerReadIndex(DEFAULT_READ_INDEX_CONFIG, metadata1, storage, cacheManager, executorService());
DurableLog durableLog = new DurableLog(ContainerSetup.defaultDurableLogConfig(), metadata1, dataLogFactory, readIndex, executorService())) {
durableLog.startAsync().awaitRunning();
// Create the segment.
val segmentIds = createStreamSegmentsWithOperations(1, durableLog);
segmentId = segmentIds.stream().findFirst().orElse(-1L);
// Evict the segment.
val sm1 = metadata1.getStreamSegmentMetadata(segmentId);
originalSegmentInfo = sm1.getSnapshot();
// Simulate a truncation. This is needed in order to trigger a cleanup.
metadata1.removeTruncationMarkers(truncatedSeqNo);
val cleanedUpSegments = metadata1.cleanup(Collections.singleton(sm1), truncatedSeqNo);
Assert.assertEquals("Unexpected number of segments evicted.", 1, cleanedUpSegments.size());
// Map the segment again.
val reMapOp = new StreamSegmentMapOperation(originalSegmentInfo);
reMapOp.setStreamSegmentId(segmentId);
durableLog.add(reMapOp, OperationPriority.Normal, TIMEOUT).join();
// Stop.
durableLog.stopAsync().awaitTerminated();
}
// Recovery #1. This should work well.
val metadata2 = (StreamSegmentContainerMetadata) new MetadataBuilder(CONTAINER_ID).build();
try (ReadIndex readIndex = new ContainerReadIndex(DEFAULT_READ_INDEX_CONFIG, metadata2, storage, cacheManager, executorService());
DurableLog durableLog = new DurableLog(ContainerSetup.defaultDurableLogConfig(), metadata2, dataLogFactory, readIndex, executorService())) {
durableLog.startAsync().awaitRunning();
// Get segment info
val recoveredSegmentInfo = metadata1.getStreamSegmentMetadata(segmentId).getSnapshot();
Assert.assertEquals("Unexpected length from recovered segment.", originalSegmentInfo.getLength(), recoveredSegmentInfo.getLength());
// Now evict the segment again ...
val sm = metadata2.getStreamSegmentMetadata(segmentId);
// Simulate a truncation. This is needed in order to trigger a cleanup.
metadata2.removeTruncationMarkers(truncatedSeqNo);
val cleanedUpSegments = metadata2.cleanup(Collections.singleton(sm), truncatedSeqNo);
Assert.assertEquals("Unexpected number of segments evicted.", 1, cleanedUpSegments.size());
// ... and re-map it with a new Id. This is a perfectly valid operation, and we can't prevent it.
durableLog.add(new StreamSegmentMapOperation(originalSegmentInfo), OperationPriority.Normal, TIMEOUT).join();
// Stop.
durableLog.stopAsync().awaitTerminated();
}
// Recovery #2. This should fail due to the same segment mapped multiple times with different ids.
val metadata3 = (StreamSegmentContainerMetadata) new MetadataBuilder(CONTAINER_ID).build();
try (ReadIndex readIndex = new ContainerReadIndex(DEFAULT_READ_INDEX_CONFIG, metadata3, storage, cacheManager, executorService());
DurableLog durableLog = new DurableLog(ContainerSetup.defaultDurableLogConfig(), metadata3, dataLogFactory, readIndex, executorService())) {
AssertExtensions.assertThrows("Recovery did not fail with the expected exception in case of multi-mapping", () -> durableLog.startAsync().awaitRunning(), ex -> ex instanceof IllegalStateException && ex.getCause() instanceof DataCorruptionException && ex.getCause().getCause() instanceof MetadataUpdateException);
}
}
use of io.pravega.segmentstore.server.DataCorruptionException in project pravega by pravega.
the class MemoryStateUpdaterTests method testProcessWithErrors.
/**
* Tests the functionality of the {@link MemoryStateUpdater#process} method with critical errors.
*/
@Test
public void testProcessWithErrors() {
final int corruptAtIndex = 10;
final int segmentCount = 10;
final int operationCountPerType = 5;
// Add to MTL + Add to ReadIndex (append; beginMerge).
val opLog = new OperationLogTestBase.CorruptedMemoryOperationLog(corruptAtIndex);
val readIndex = mock(ReadIndex.class);
MemoryStateUpdater updater = new MemoryStateUpdater(opLog, readIndex);
AssertExtensions.assertThrows("Expected a DataCorruptionException", () -> populate(updater, segmentCount, operationCountPerType), ex -> ex instanceof DataCorruptionException);
// Verify they were properly processed.
verify(readIndex, never()).triggerFutureReads(anyCollection());
Queue<Operation> logIterator = opLog.poll(corruptAtIndex * 2);
int addCount = 0;
while (!logIterator.isEmpty()) {
addCount++;
logIterator.poll();
}
Assert.assertEquals("Unexpected number of operations added to the log.", corruptAtIndex - 1, addCount);
// The rest of the checks is done in the populate() method: it verifies that the callback is invoked for every
// operation, including the failed ones.
}
use of io.pravega.segmentstore.server.DataCorruptionException in project pravega by pravega.
the class SegmentAggregator method getFlushData.
/**
* Returns a {@link BufferView} which contains the data needing to be flushed to Storage.
*
* @return A {@link BufferView} to flush or null if the segment was deleted.
* @throws DataCorruptionException If a unable to retrieve required data from the Data Source.
*/
@Nullable
private BufferView getFlushData() throws DataCorruptionException {
StorageOperation first = this.operations.getFirst();
if (!(first instanceof AggregatedAppendOperation)) {
// Nothing to flush - first operation is not an AggregatedAppend.
return null;
}
AggregatedAppendOperation appendOp = (AggregatedAppendOperation) first;
int length = (int) appendOp.getLength();
BufferView data = null;
if (length > 0) {
data = this.dataSource.getAppendData(appendOp.getStreamSegmentId(), appendOp.getStreamSegmentOffset(), length);
if (data == null) {
if (this.metadata.isDeleted()) {
// Segment was deleted - nothing more to do.
return null;
}
throw new DataCorruptionException(String.format("Unable to retrieve CacheContents for '%s'.", appendOp));
}
}
appendOp.seal();
return data;
}
use of io.pravega.segmentstore.server.DataCorruptionException in project pravega by pravega.
the class SegmentAggregator method initialize.
// endregion
// region Operations
/**
* Initializes the SegmentAggregator by pulling information from the given Storage.
*
* @param timeout Timeout for the operation.
* @return A CompletableFuture that, when completed, will indicate that the operation finished successfully. If any
* errors occurred during the operation, the Future will be completed with the appropriate exception.
*/
CompletableFuture<Void> initialize(Duration timeout) {
Exceptions.checkNotClosed(isClosed(), this);
Preconditions.checkState(this.state.get() == AggregatorState.NotInitialized, "SegmentAggregator has already been initialized.");
assert this.handle.get() == null : "non-null handle but state == " + this.state.get();
long traceId = LoggerHelpers.traceEnterWithContext(log, this.traceObjectId, "initialize");
if (this.metadata.isDeleted()) {
// Segment is dead on arrival. Delete it from Storage (if it exists) and do not bother to do anything else with it).
// This is a rather uncommon case, but it can happen in one of two cases: 1) the segment has been deleted
// immediately after creation or 2) after a container recovery.
log.info("{}: Segment '{}' is marked as Deleted in Metadata. Attempting Storage delete.", this.traceObjectId, this.metadata.getName());
return Futures.exceptionallyExpecting(this.storage.openWrite(this.metadata.getName()).thenComposeAsync(handle -> this.storage.delete(handle, timeout), this.executor), ex -> ex instanceof StreamSegmentNotExistsException, // It's OK if already deleted.
null).thenRun(() -> {
updateMetadataPostDeletion(this.metadata);
log.info("{}: Segment '{}' is marked as Deleted in Metadata and has been deleted from Storage. Ignoring all further operations on it.", this.traceObjectId, this.metadata.getName());
setState(AggregatorState.Writing);
LoggerHelpers.traceLeave(log, this.traceObjectId, "initialize", traceId);
});
}
// Segment not deleted.
return openWrite(this.metadata.getName(), this.handle, timeout).thenAcceptAsync(segmentInfo -> {
// Check & Update StorageLength in metadata.
if (this.metadata.getStorageLength() != segmentInfo.getLength()) {
if (this.metadata.getStorageLength() >= 0) {
// Only log warning if the StorageLength has actually been initialized, but is different.
log.info("{}: SegmentMetadata has a StorageLength ({}) that is different than the actual one ({}) - updating metadata.", this.traceObjectId, this.metadata.getStorageLength(), segmentInfo.getLength());
}
// It is very important to keep this value up-to-date and correct.
this.metadata.setStorageLength(segmentInfo.getLength());
}
// Check if the Storage segment is sealed, but it's not in metadata (this is 100% indicative of some data corruption happening).
if (segmentInfo.isSealed()) {
if (!this.metadata.isSealed()) {
throw new CompletionException(new DataCorruptionException(String.format("Segment '%s' is sealed in Storage but not in the metadata.", this.metadata.getName())));
}
if (!this.metadata.isSealedInStorage()) {
this.metadata.markSealedInStorage();
log.info("{}: Segment is sealed in Storage but metadata does not reflect that - updating metadata.", this.traceObjectId);
}
}
log.info("{}: Initialized. StorageLength = {}, Sealed = {}.", this.traceObjectId, segmentInfo.getLength(), segmentInfo.isSealed());
LoggerHelpers.traceLeave(log, this.traceObjectId, "initialize", traceId);
setState(AggregatorState.Writing);
}, this.executor).exceptionally(ex -> {
ex = Exceptions.unwrap(ex);
if (ex instanceof StreamSegmentNotExistsException) {
// Segment does not exist in Storage. There are two possibilities here:
if (this.metadata.getStorageLength() == 0 && !this.metadata.isDeletedInStorage()) {
// Segment has never been created because there was nothing to write to it. As long as we know
// its expected length is zero, this is a valid case.
this.handle.set(null);
log.info("{}: Initialized. Segment does not exist in Storage but Metadata indicates it should be empty.", this.traceObjectId);
if (this.metadata.isSealed() && this.metadata.getLength() == 0) {
// Truly an empty segment that is sealed; mark it as such in Storage.
this.metadata.markSealedInStorage();
log.info("{}: Segment does not exist in Storage, but Metadata indicates it is empty and sealed - marking as sealed in storage.", this.traceObjectId);
}
} else {
// Segment does not exist anymore. This is a real possibility during recovery, in the following cases:
// * We already processed a Segment Deletion but did not have a chance to checkpoint metadata
// * We processed a MergeSegmentOperation but did not have a chance to ack/truncate the DataSource
// Update metadata, just in case it is not already updated.
updateMetadataPostDeletion(this.metadata);
log.info("{}: Segment '{}' does not exist in Storage. Ignoring all further operations on it.", this.traceObjectId, this.metadata.getName());
}
setState(AggregatorState.Writing);
LoggerHelpers.traceLeave(log, this.traceObjectId, "initialize", traceId);
} else {
// Other kind of error - re-throw.
throw new CompletionException(ex);
}
return null;
});
}
Aggregations