use of io.pravega.segmentstore.contracts.Attributes in project pravega by pravega.
the class StreamSegmentMapper method createNewTransactionStreamSegment.
/**
* Creates a new Transaction StreamSegment for an existing Parent StreamSegment and persists the given attributes (in Storage).
*
* @param parentStreamSegmentName The case-sensitive StreamSegment Name of the Parent StreamSegment.
* @param transactionId A unique identifier for the transaction to be created.
* @param attributes The initial attributes for the Transaction, if any.
* @param timeout Timeout for the operation.
* @return A CompletableFuture that, when completed normally, will contain the name of the newly created Transaction StreamSegment.
* If the operation failed, this will contain the exception that caused the failure.
* @throws IllegalArgumentException If the given parent StreamSegment cannot have a Transaction (because it is deleted, sealed, inexistent).
*/
public CompletableFuture<String> createNewTransactionStreamSegment(String parentStreamSegmentName, UUID transactionId, Collection<AttributeUpdate> attributes, Duration timeout) {
long traceId = LoggerHelpers.traceEnterWithContext(log, traceObjectId, "createNewTransactionStreamSegment", parentStreamSegmentName);
// We cannot create a Transaction StreamSegment for a what looks like another Transaction.
Exceptions.checkArgument(StreamSegmentNameUtils.getParentStreamSegmentName(parentStreamSegmentName) == null, "parentStreamSegmentName", "Cannot create a Transaction for a Transaction.");
// Validate that Parent StreamSegment exists.
TimeoutTimer timer = new TimeoutTimer(timeout);
CompletableFuture<Void> parentCheck = null;
long mappedParentId = this.containerMetadata.getStreamSegmentId(parentStreamSegmentName, true);
if (isValidStreamSegmentId(mappedParentId)) {
SegmentProperties parentInfo = this.containerMetadata.getStreamSegmentMetadata(mappedParentId);
if (parentInfo != null) {
parentCheck = validateParentSegmentEligibility(parentInfo);
}
}
if (parentCheck == null) {
// The parent is not registered in the metadata. Get required info from Storage and don't map it unnecessarily.
parentCheck = this.storage.getStreamSegmentInfo(parentStreamSegmentName, timer.getRemaining()).thenCompose(this::validateParentSegmentEligibility);
}
String transactionName = StreamSegmentNameUtils.getTransactionNameFromId(parentStreamSegmentName, transactionId);
return parentCheck.thenComposeAsync(parentId -> createSegmentInStorageWithRecovery(transactionName, attributes, timer), this.executor).thenApply(v -> {
LoggerHelpers.traceLeave(log, traceObjectId, "createNewTransactionStreamSegment", traceId, parentStreamSegmentName, transactionName);
return transactionName;
});
}
use of io.pravega.segmentstore.contracts.Attributes in project pravega by pravega.
the class StreamSegmentContainerTests method testMetadataCleanup.
/**
* Tests the ability to clean up SegmentMetadata for those segments which have not been used recently.
* This test does the following:
* 1. Sets up a custom SegmentContainer with a hook into the metadataCleanup task
* 2. Creates a segment and appends something to it, each time updating attributes (and verifies they were updated correctly).
* 3. Waits for the segment to be forgotten (evicted).
* 4. Requests info on the segment, validates it, then makes another append, seals it, at each step verifying it was done
* correctly (checking Metadata, Attributes and Storage).
* 5. Deletes the segment, waits for metadata to be cleared (via forcing another log truncation), re-creates the
* same segment and validates that the old attributes did not "bleed in".
*/
@Test
public void testMetadataCleanup() throws Exception {
final String segmentName = "segment";
final UUID[] attributes = new UUID[] { Attributes.CREATION_TIME, UUID.randomUUID(), UUID.randomUUID(), UUID.randomUUID() };
final byte[] appendData = "hello".getBytes();
final Map<UUID, Long> expectedAttributes = new HashMap<>();
// We need a special DL config so that we can force truncations after every operation - this will speed up metadata
// eviction eligibility.
final DurableLogConfig durableLogConfig = DurableLogConfig.builder().with(DurableLogConfig.CHECKPOINT_MIN_COMMIT_COUNT, 1).with(DurableLogConfig.CHECKPOINT_COMMIT_COUNT, 5).with(DurableLogConfig.CHECKPOINT_TOTAL_COMMIT_LENGTH, 10 * 1024 * 1024L).build();
final TestContainerConfig containerConfig = new TestContainerConfig();
containerConfig.setSegmentMetadataExpiration(Duration.ofMillis(250));
@Cleanup TestContext context = new TestContext(containerConfig);
OperationLogFactory localDurableLogFactory = new DurableLogFactory(durableLogConfig, context.dataLogFactory, executorService());
@Cleanup MetadataCleanupContainer localContainer = new MetadataCleanupContainer(CONTAINER_ID, containerConfig, localDurableLogFactory, context.readIndexFactory, context.writerFactory, context.storageFactory, executorService());
localContainer.startAsync().awaitRunning();
// Create segment with initial attributes and verify they were set correctly.
val initialAttributes = createAttributeUpdates(attributes);
applyAttributes(initialAttributes, expectedAttributes);
localContainer.createStreamSegment(segmentName, initialAttributes, TIMEOUT).get(TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
SegmentProperties sp = localContainer.getStreamSegmentInfo(segmentName, true, TIMEOUT).get(TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
SegmentMetadataComparer.assertSameAttributes("Unexpected attributes after segment creation.", expectedAttributes, sp);
// Add one append with some attribute changes and verify they were set correctly.
val appendAttributes = createAttributeUpdates(attributes);
applyAttributes(appendAttributes, expectedAttributes);
localContainer.append(segmentName, appendData, appendAttributes, TIMEOUT).get(TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
sp = localContainer.getStreamSegmentInfo(segmentName, true, TIMEOUT).get(TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
SegmentMetadataComparer.assertSameAttributes("Unexpected attributes after append.", expectedAttributes, sp);
// Wait until the segment is forgotten.
localContainer.triggerMetadataCleanup(Collections.singleton(segmentName)).get(TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
// Now get attributes again and verify them.
sp = localContainer.getStreamSegmentInfo(segmentName, true, TIMEOUT).get(TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
SegmentMetadataComparer.assertSameAttributes("Unexpected attributes after eviction & resurrection.", expectedAttributes, sp);
// Append again, and make sure we can append at the right offset.
val secondAppendAttributes = createAttributeUpdates(attributes);
applyAttributes(secondAppendAttributes, expectedAttributes);
localContainer.append(segmentName, appendData.length, appendData, secondAppendAttributes, TIMEOUT).get(TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
sp = localContainer.getStreamSegmentInfo(segmentName, true, TIMEOUT).get(TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
Assert.assertEquals("Unexpected length from segment after eviction & resurrection.", 2 * appendData.length, sp.getLength());
SegmentMetadataComparer.assertSameAttributes("Unexpected attributes after eviction & resurrection.", expectedAttributes, sp);
// Seal (this should clear out non-dynamic attributes).
expectedAttributes.keySet().removeIf(Attributes::isDynamic);
localContainer.sealStreamSegment(segmentName, TIMEOUT).get(TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
sp = localContainer.getStreamSegmentInfo(segmentName, true, TIMEOUT).get(TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
SegmentMetadataComparer.assertSameAttributes("Unexpected attributes after seal.", expectedAttributes, sp);
// Verify the segment actually made to Storage in one piece.
waitForSegmentInStorage(sp, context).get(TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
val storageInfo = context.storage.getStreamSegmentInfo(segmentName, TIMEOUT).get(TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
Assert.assertEquals("Unexpected length in storage for segment.", sp.getLength(), storageInfo.getLength());
// Delete segment and wait until it is forgotten again (we need to create another dummy segment so that we can
// force a Metadata Truncation in order to facilitate that; this is the purpose of segment2).
localContainer.deleteStreamSegment(segmentName, TIMEOUT).get(TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
// Wait for the segment to be forgotten again.
localContainer.triggerMetadataCleanup(Collections.singleton(segmentName)).get(TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
// Now Create the Segment again and verify the old attributes were not "remembered".
val newAttributes = createAttributeUpdates(attributes);
applyAttributes(newAttributes, expectedAttributes);
localContainer.createStreamSegment(segmentName, newAttributes, TIMEOUT).get(TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
sp = localContainer.getStreamSegmentInfo(segmentName, true, TIMEOUT).get(TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
SegmentMetadataComparer.assertSameAttributes("Unexpected attributes after deletion and re-creation.", expectedAttributes, sp);
}
use of io.pravega.segmentstore.contracts.Attributes in project pravega by pravega.
the class StreamSegmentMapperTests method testGetOrAssignStreamSegmentId.
/**
* Tests the ability of the StreamSegmentMapper to generate/return the Id of an existing StreamSegment, as well as
* retrieving existing attributes.
*/
@Test
public void testGetOrAssignStreamSegmentId() {
final long minSegmentLength = 1;
final int segmentCount = 10;
final int transactionsPerSegment = 5;
final long noSegmentId = ContainerMetadata.NO_STREAM_SEGMENT_ID;
AtomicLong currentSegmentId = new AtomicLong(Integer.MAX_VALUE);
Supplier<Long> nextSegmentId = () -> currentSegmentId.decrementAndGet() % 2 == 0 ? noSegmentId : currentSegmentId.get();
Function<String, Long> getSegmentLength = segmentName -> minSegmentLength + (long) MathHelpers.abs(segmentName.hashCode());
Function<String, Long> getSegmentStartOffset = segmentName -> getSegmentLength.apply(segmentName) / 2;
@Cleanup TestContext context = new TestContext();
HashSet<String> storageSegments = new HashSet<>();
for (int i = 0; i < segmentCount; i++) {
String segmentName = getName(i);
storageSegments.add(segmentName);
setSavedState(segmentName, nextSegmentId.get(), getSegmentStartOffset.apply(segmentName), storageSegments.size() % ATTRIBUTE_COUNT, context);
for (int j = 0; j < transactionsPerSegment; j++) {
// There is a small chance of a name conflict here, but we don't care. As long as we get at least one
// Transaction per segment, we should be fine.
String transactionName = StreamSegmentNameUtils.getTransactionNameFromId(segmentName, UUID.randomUUID());
storageSegments.add(transactionName);
setSavedState(transactionName, nextSegmentId.get(), getSegmentStartOffset.apply(transactionName), storageSegments.size() % ATTRIBUTE_COUNT, context);
}
}
// We setup all necessary handlers, except the one for create. We do not need to create new Segments here.
setupOperationLog(context);
Predicate<String> isSealed = segmentName -> segmentName.hashCode() % 2 == 0;
setupStorageGetHandler(context, storageSegments, segmentName -> StreamSegmentInformation.builder().name(segmentName).length(getSegmentLength.apply(segmentName)).sealed(isSealed.test(segmentName)).build());
// First, map all the parents (stand-alone segments).
for (String name : storageSegments) {
if (StreamSegmentNameUtils.getParentStreamSegmentName(name) == null) {
long id = context.mapper.getOrAssignStreamSegmentId(name, TIMEOUT).join();
Assert.assertNotEquals("No id was assigned for StreamSegment " + name, ContainerMetadata.NO_STREAM_SEGMENT_ID, id);
SegmentMetadata sm = context.metadata.getStreamSegmentMetadata(id);
Assert.assertNotNull("No metadata was created for StreamSegment " + name, sm);
long expectedLength = getSegmentLength.apply(name);
boolean expectedSeal = isSealed.test(name);
Assert.assertEquals("Metadata does not have the expected length for StreamSegment " + name, expectedLength, sm.getLength());
Assert.assertEquals("Metadata does not have the expected value for isSealed for StreamSegment " + name, expectedSeal, sm.isSealed());
val segmentState = context.stateStore.get(name, TIMEOUT).join();
Map<UUID, Long> expectedAttributes = segmentState == null ? null : segmentState.getAttributes();
SegmentMetadataComparer.assertSameAttributes("Unexpected attributes in metadata for StreamSegment " + name, expectedAttributes, sm);
long expectedStartOffset = segmentState == null ? 0 : segmentState.getStartOffset();
Assert.assertEquals("Unexpected StartOffset in metadata for " + name, expectedStartOffset, sm.getStartOffset());
}
}
// Now, map all the Transactions.
for (String name : storageSegments) {
String parentName = StreamSegmentNameUtils.getParentStreamSegmentName(name);
if (parentName != null) {
long id = context.mapper.getOrAssignStreamSegmentId(name, TIMEOUT).join();
Assert.assertNotEquals("No id was assigned for Transaction " + name, ContainerMetadata.NO_STREAM_SEGMENT_ID, id);
SegmentMetadata sm = context.metadata.getStreamSegmentMetadata(id);
Assert.assertNotNull("No metadata was created for Transaction " + name, sm);
long expectedLength = getSegmentLength.apply(name);
boolean expectedSeal = isSealed.test(name);
Assert.assertEquals("Metadata does not have the expected length for Transaction " + name, expectedLength, sm.getLength());
Assert.assertEquals("Metadata does not have the expected value for isSealed for Transaction " + name, expectedSeal, sm.isSealed());
val segmentState = context.stateStore.get(name, TIMEOUT).join();
Map<UUID, Long> expectedAttributes = segmentState == null ? null : segmentState.getAttributes();
SegmentMetadataComparer.assertSameAttributes("Unexpected attributes in metadata for Transaction " + name, expectedAttributes, sm);
// For transactions we do not expect to see any non-zero start offsets.
Assert.assertEquals("Unexpected StartOffset in metadata for " + name, 0, sm.getStartOffset());
// Check parenthood.
Assert.assertNotEquals("No parent defined in metadata for Transaction " + name, ContainerMetadata.NO_STREAM_SEGMENT_ID, sm.getParentId());
long parentId = context.metadata.getStreamSegmentId(parentName, false);
Assert.assertEquals("Unexpected parent defined in metadata for Transaction " + name, parentId, sm.getParentId());
}
}
}
use of io.pravega.segmentstore.contracts.Attributes in project pravega by pravega.
the class ContainerMetadataUpdateTransactionTests method testAcceptStreamSegmentSeal.
/**
* Tests the accept method with StreamSegmentSeal operations.
*/
@Test
public void testAcceptStreamSegmentSeal() throws Exception {
UpdateableContainerMetadata metadata = createMetadata();
// Set some attributes.
val segmentAttributes = createAttributes();
segmentAttributes.put(Attributes.CREATION_TIME, 1L);
UpdateableSegmentMetadata segmentMetadata = metadata.getStreamSegmentMetadata(SEGMENT_ID);
segmentMetadata.updateAttributes(segmentAttributes);
val txn = createUpdateTransaction(metadata);
StreamSegmentSealOperation sealOp = createSeal();
// When no pre-process has happened.
AssertExtensions.assertThrows("Unexpected behavior from acceptOperation() when no pre-processing was made.", () -> txn.acceptOperation(sealOp), ex -> ex instanceof MetadataUpdateException);
Assert.assertFalse("acceptOperation updated the transaction even if it threw an exception.", txn.getStreamSegmentMetadata(SEGMENT_ID).isSealed());
Assert.assertFalse("acceptOperation updated the metadata.", metadata.getStreamSegmentMetadata(SEGMENT_ID).isSealed());
// When all is good.
txn.preProcessOperation(sealOp);
txn.acceptOperation(sealOp);
Assert.assertTrue("acceptOperation did not update the transaction.", txn.getStreamSegmentMetadata(SEGMENT_ID).isSealed());
Assert.assertFalse("acceptOperation updated the metadata.", segmentMetadata.isSealed());
txn.commit(metadata);
// Check attributes.
// All dynamic attributes should be removed.
segmentAttributes.keySet().removeIf(Attributes::isDynamic);
SegmentMetadataComparer.assertSameAttributes("Unexpected set of attributes after commit.", segmentAttributes, segmentMetadata);
}
Aggregations