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 InMemoryCacheFactory cacheFactory = new InMemoryCacheFactory();
@Cleanup CacheManager cacheManager = new CacheManager(DEFAULT_READ_INDEX_CONFIG.getCachePolicy(), executorService());
SegmentProperties originalSegmentInfo;
try (ReadIndex readIndex = new ContainerReadIndex(DEFAULT_READ_INDEX_CONFIG, metadata1, cacheFactory, storage, cacheManager, executorService());
DurableLog durableLog = new DurableLog(ContainerSetup.defaultDurableLogConfig(), metadata1, dataLogFactory, readIndex, executorService())) {
durableLog.startAsync().awaitRunning();
// Create the segment.
val segmentIds = createStreamSegmentsWithOperations(1, metadata1, durableLog, storage);
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, 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, cacheFactory, 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), 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, cacheFactory, 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 SegmentAggregatorTests method testAddWithBadInput.
/**
* Tests the add() method with invalid arguments.
*/
@Test
public void testAddWithBadInput() throws Exception {
final long badTransactionId = 12345;
final long badParentId = 56789;
final String badParentName = "Foo_Parent";
final String badTransactionName = "Foo_Transaction";
@Cleanup TestContext context = new TestContext(DEFAULT_CONFIG);
// We only needs one Transaction for this test.
SegmentAggregator transactionAggregator = context.transactionAggregators[0];
SegmentMetadata transactionMetadata = transactionAggregator.getMetadata();
context.storage.create(context.segmentAggregator.getMetadata().getName(), TIMEOUT).join();
context.storage.create(transactionMetadata.getName(), TIMEOUT).join();
context.segmentAggregator.initialize(TIMEOUT).join();
transactionAggregator.initialize(TIMEOUT).join();
// Create 2 more segments that can be used to verify MergeTransactionOperation.
context.containerMetadata.mapStreamSegmentId(badParentName, badParentId);
UpdateableSegmentMetadata badTransactionMetadata = context.containerMetadata.mapStreamSegmentId(badTransactionName, badTransactionId, badParentId);
badTransactionMetadata.setLength(0);
badTransactionMetadata.setStorageLength(0);
context.storage.create(badTransactionMetadata.getName(), TIMEOUT).join();
// 1. MergeTransactionOperation
// 1a.Verify that MergeTransactionOperation cannot be added to the Transaction segment.
AssertExtensions.assertThrows("add() allowed a MergeTransactionOperation on the Transaction segment.", () -> transactionAggregator.add(generateSimpleMergeTransaction(transactionMetadata.getId(), context)), ex -> ex instanceof IllegalArgumentException);
// 1b. Verify that MergeTransactionOperation has the right parent.
AssertExtensions.assertThrows("add() allowed a MergeTransactionOperation on the parent for a Transaction that did not have it as a parent.", () -> transactionAggregator.add(generateSimpleMergeTransaction(badTransactionId, context)), ex -> ex instanceof IllegalArgumentException);
// 2. StreamSegmentSealOperation.
// 2a. Verify we cannot add a StreamSegmentSealOperation if the segment is not sealed yet.
AssertExtensions.assertThrows("add() allowed a StreamSegmentSealOperation for a non-sealed segment.", () -> {
@Cleanup SegmentAggregator badTransactionAggregator = new SegmentAggregator(badTransactionMetadata, context.dataSource, context.storage, DEFAULT_CONFIG, context.timer, executorService());
badTransactionAggregator.initialize(TIMEOUT).join();
badTransactionAggregator.add(generateSimpleSeal(badTransactionId, context));
}, ex -> ex instanceof DataCorruptionException);
// 2b. Verify that nothing is allowed after Seal (after adding one append to and sealing the Transaction Segment).
StorageOperation transactionAppend1 = generateAppendAndUpdateMetadata(0, transactionMetadata.getId(), context);
transactionAggregator.add(transactionAppend1);
transactionAggregator.add(generateSealAndUpdateMetadata(transactionMetadata.getId(), context));
AssertExtensions.assertThrows("add() allowed operation after seal.", () -> transactionAggregator.add(generateSimpleAppend(transactionMetadata.getId(), context)), ex -> ex instanceof DataCorruptionException);
// 3. CachedStreamSegmentAppendOperation.
final StorageOperation parentAppend1 = generateAppendAndUpdateMetadata(0, SEGMENT_ID, context);
// 3a. Verify we cannot add StreamSegmentAppendOperations.
AssertExtensions.assertThrows("add() allowed a StreamSegmentAppendOperation.", () -> {
// We have the correct offset, but we did not increase the Length.
StreamSegmentAppendOperation badAppend = new StreamSegmentAppendOperation(parentAppend1.getStreamSegmentId(), parentAppend1.getStreamSegmentOffset(), new byte[(int) parentAppend1.getLength()], null);
context.segmentAggregator.add(badAppend);
}, ex -> ex instanceof IllegalArgumentException);
// Add this one append to the parent (nothing unusual here); we'll use this for the next tests.
context.segmentAggregator.add(parentAppend1);
// 3b. Verify we cannot add anything beyond the DurableLogOffset (offset or offset+length).
AssertExtensions.assertThrows("add() allowed an operation beyond the DurableLogOffset (offset).", () -> {
// We have the correct offset, but we did not increase the Length.
StreamSegmentAppendOperation badAppend = new StreamSegmentAppendOperation(context.segmentAggregator.getMetadata().getId(), "foo".getBytes(), null);
badAppend.setStreamSegmentOffset(parentAppend1.getStreamSegmentOffset() + parentAppend1.getLength());
context.segmentAggregator.add(new CachedStreamSegmentAppendOperation(badAppend));
}, ex -> ex instanceof DataCorruptionException);
((UpdateableSegmentMetadata) context.segmentAggregator.getMetadata()).setLength(parentAppend1.getStreamSegmentOffset() + parentAppend1.getLength() + 1);
AssertExtensions.assertThrows("add() allowed an operation beyond the DurableLogOffset (offset+length).", () -> {
// We have the correct offset, but we the append exceeds the Length by 1 byte.
StreamSegmentAppendOperation badAppend = new StreamSegmentAppendOperation(context.segmentAggregator.getMetadata().getId(), "foo".getBytes(), null);
badAppend.setStreamSegmentOffset(parentAppend1.getStreamSegmentOffset() + parentAppend1.getLength());
context.segmentAggregator.add(new CachedStreamSegmentAppendOperation(badAppend));
}, ex -> ex instanceof DataCorruptionException);
// 3c. Verify contiguity (offsets - we cannot have gaps in the data).
AssertExtensions.assertThrows("add() allowed an operation with wrong offset (too small).", () -> {
StreamSegmentAppendOperation badOffsetAppend = new StreamSegmentAppendOperation(context.segmentAggregator.getMetadata().getId(), "foo".getBytes(), null);
badOffsetAppend.setStreamSegmentOffset(0);
context.segmentAggregator.add(new CachedStreamSegmentAppendOperation(badOffsetAppend));
}, ex -> ex instanceof DataCorruptionException);
AssertExtensions.assertThrows("add() allowed an operation with wrong offset (too large).", () -> {
StreamSegmentAppendOperation badOffsetAppend = new StreamSegmentAppendOperation(context.segmentAggregator.getMetadata().getId(), "foo".getBytes(), null);
badOffsetAppend.setStreamSegmentOffset(parentAppend1.getStreamSegmentOffset() + parentAppend1.getLength() + 1);
context.segmentAggregator.add(new CachedStreamSegmentAppendOperation(badOffsetAppend));
}, ex -> ex instanceof DataCorruptionException);
AssertExtensions.assertThrows("add() allowed an operation with wrong offset (too large, but no pending operations).", () -> {
@Cleanup SegmentAggregator badTransactionAggregator = new SegmentAggregator(badTransactionMetadata, context.dataSource, context.storage, DEFAULT_CONFIG, context.timer, executorService());
badTransactionMetadata.setLength(100);
badTransactionAggregator.initialize(TIMEOUT).join();
StreamSegmentAppendOperation badOffsetAppend = new StreamSegmentAppendOperation(context.segmentAggregator.getMetadata().getId(), "foo".getBytes(), null);
badOffsetAppend.setStreamSegmentOffset(1);
context.segmentAggregator.add(new CachedStreamSegmentAppendOperation(badOffsetAppend));
}, ex -> ex instanceof DataCorruptionException);
// 4. Verify Segment Id match.
AssertExtensions.assertThrows("add() allowed an Append operation with wrong Segment Id.", () -> {
StreamSegmentAppendOperation badIdAppend = new StreamSegmentAppendOperation(Integer.MAX_VALUE, "foo".getBytes(), null);
badIdAppend.setStreamSegmentOffset(parentAppend1.getStreamSegmentOffset() + parentAppend1.getLength());
context.segmentAggregator.add(new CachedStreamSegmentAppendOperation(badIdAppend));
}, ex -> ex instanceof IllegalArgumentException);
AssertExtensions.assertThrows("add() allowed a StreamSegmentSealOperation with wrong SegmentId.", () -> {
StreamSegmentSealOperation badIdSeal = new StreamSegmentSealOperation(Integer.MAX_VALUE);
badIdSeal.setStreamSegmentOffset(parentAppend1.getStreamSegmentOffset() + parentAppend1.getLength());
context.segmentAggregator.add(badIdSeal);
}, ex -> ex instanceof IllegalArgumentException);
AssertExtensions.assertThrows("add() allowed a MergeTransactionOperation with wrong SegmentId.", () -> {
MergeTransactionOperation badIdMerge = new MergeTransactionOperation(Integer.MAX_VALUE, transactionMetadata.getId());
badIdMerge.setStreamSegmentOffset(parentAppend1.getStreamSegmentOffset() + parentAppend1.getLength());
badIdMerge.setLength(1);
context.segmentAggregator.add(badIdMerge);
}, ex -> ex instanceof IllegalArgumentException);
// 5. Truncations.
AssertExtensions.assertThrows("add() allowed a StreamSegmentTruncateOperation with a truncation offset beyond the one in the metadata.", () -> {
StreamSegmentTruncateOperation op = new StreamSegmentTruncateOperation(SEGMENT_ID, 10);
op.setSequenceNumber(context.containerMetadata.nextOperationSequenceNumber());
context.segmentAggregator.add(op);
}, ex -> ex instanceof DataCorruptionException);
}
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.storage.create(context.segmentAggregator.getMetadata().getName(), TIMEOUT).join();
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.assertThrows("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 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");
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.warn("{}: 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.warn("{}: 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 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 TransactionMergeOperation but did not have a chance to ack/truncate the DataSource
// Update metadata, just in case it is not already updated.
this.metadata.markDeleted();
log.warn("{}: Segment does not exist in Storage. Ignoring all further operations on it.", this.traceObjectId, ex);
setState(AggregatorState.Writing);
LoggerHelpers.traceLeave(log, this.traceObjectId, "initialize", traceId);
} else {
// Other kind of error - re-throw.
throw new CompletionException(ex);
}
return null;
});
}
use of io.pravega.segmentstore.server.DataCorruptionException in project pravega by pravega.
the class SegmentAggregator method flushPendingAppends.
/**
* Flushes all Append Operations that can be flushed up to the maximum allowed flush size.
*
* @param timeout Timeout for the operation.
* @return A CompletableFuture that, when completed, will contain the result from the flush operation.
*/
private CompletableFuture<FlushResult> flushPendingAppends(Duration timeout) {
// Gather an InputStream made up of all the operations we can flush.
FlushArgs flushArgs;
try {
flushArgs = getFlushArgs();
} catch (DataCorruptionException ex) {
return Futures.failedFuture(ex);
}
long traceId = LoggerHelpers.traceEnterWithContext(log, this.traceObjectId, "flushPendingAppends");
if (flushArgs.getLength() == 0) {
// Nothing to flush.
FlushResult result = new FlushResult();
LoggerHelpers.traceLeave(log, this.traceObjectId, "flushPendingAppends", traceId, result);
return CompletableFuture.completedFuture(result);
}
// Flush them.
InputStream inputStream = flushArgs.getStream();
return this.storage.write(this.handle.get(), this.metadata.getStorageLength(), inputStream, flushArgs.getLength(), timeout).thenApplyAsync(v -> {
FlushResult result = updateStatePostFlush(flushArgs);
LoggerHelpers.traceLeave(log, this.traceObjectId, "flushPendingAppends", traceId, result);
return result;
}, this.executor).exceptionally(ex -> {
if (Exceptions.unwrap(ex) instanceof BadOffsetException) {
// We attempted to write at an offset that already contained other data. This can happen for a number of
// reasons, but we do not have enough information here to determine why. We need to enter reconciliation
// mode, and hope for the best.
setState(AggregatorState.ReconciliationNeeded);
}
// Rethrow all exceptions.
throw new CompletionException(ex);
});
}
Aggregations