Search in sources :

Example 66 with ByteArraySegment

use of io.pravega.common.util.ByteArraySegment in project pravega by pravega.

the class RedirectedReadResultEntryTests method testConstructor.

/**
 * Tests the ability of the ReadResultEntry base class to adjust offsets when instructed so.
 */
@Test
public void testConstructor() {
    final long originalOffset = 123;
    final int originalLength = 321;
    final int positiveDelta = 8976;
    final int negativeDelta = -76;
    val baseEntry = new MockReadResultEntry(originalOffset, originalLength);
    AssertExtensions.assertThrows("Constructor allowed changing to a negative offset.", () -> new RedirectedReadResultEntry(baseEntry, -originalOffset - 1, this::illegalGetNext, 2), ex -> ex instanceof IllegalArgumentException);
    // Adjust up.
    RedirectedReadResultEntry redirectedEntry = new RedirectedReadResultEntry(baseEntry, positiveDelta, this::illegalGetNext, 2);
    Assert.assertEquals("Unexpected value for getStreamSegmentOffset after up-adjustment.", originalOffset + positiveDelta, redirectedEntry.getStreamSegmentOffset());
    Assert.assertEquals("Unexpected value for getRequestedReadLength after up-adjustment (no change expected).", originalLength, redirectedEntry.getRequestedReadLength());
    // Adjust down.
    redirectedEntry = new RedirectedReadResultEntry(baseEntry, negativeDelta, this::illegalGetNext, 2);
    Assert.assertEquals("Unexpected value for getStreamSegmentOffset after down-adjustment.", originalOffset + negativeDelta, redirectedEntry.getStreamSegmentOffset());
    Assert.assertEquals("Unexpected value for getRequestedReadLength after down-adjustment (no change expected).", originalLength, redirectedEntry.getRequestedReadLength());
    // Verify other properties are as they should.
    Assert.assertEquals("Unexpected value for getType.", baseEntry.getType(), redirectedEntry.getType());
    Assert.assertEquals("Unexpected value for getRequestedReadLength.", baseEntry.getRequestedReadLength(), redirectedEntry.getRequestedReadLength());
    // getContent will be thoroughly tested in its own unit test.
    baseEntry.getContent().complete(new ByteArraySegment(new byte[1]));
    Assert.assertEquals("Unexpected result for getContent.", baseEntry.getContent().join(), redirectedEntry.getContent().join());
    redirectedEntry.requestContent(Duration.ZERO);
    Assert.assertTrue("BaseEntry.getContent() was not completed when requestContent was invoked.", Futures.isSuccessful(baseEntry.getContent()));
}
Also used : lombok.val(lombok.val) ByteArraySegment(io.pravega.common.util.ByteArraySegment) Test(org.junit.Test)

Example 67 with ByteArraySegment

use of io.pravega.common.util.ByteArraySegment in project pravega by pravega.

the class StreamSegmentReadResultTests method testNextTerminal.

private void testNextTerminal(BiFunction<Long, Integer, TestReadResultEntry> terminalEntryCreator) {
    AtomicReference<TestReadResultEntry> nextEntry = new AtomicReference<>();
    StreamSegmentReadResult.NextEntrySupplier nes = (offset, length, makeCopy) -> nextEntry.get();
    // We issue a read with length = MAX_RESULT_LENGTH, and return only half the items, 1 byte at a time.
    @Cleanup StreamSegmentReadResult r = new StreamSegmentReadResult(START_OFFSET, MAX_RESULT_LENGTH, nes, "");
    for (int i = 0; i < MAX_RESULT_LENGTH / 2; i++) {
        // Setup an item to be returned.
        final long expectedStartOffset = START_OFFSET + i;
        final int expectedReadLength = MAX_RESULT_LENGTH - i;
        nextEntry.set(TestReadResultEntry.cache(expectedStartOffset, expectedReadLength));
        r.next();
        nextEntry.get().complete(new ByteArraySegment(new byte[READ_ITEM_LENGTH]));
    }
    // Verify we have not reached the end.
    AssertExtensions.assertLessThan("Unexpected state of the StreamSegmentReadResult when consuming half of the result.", r.getMaxResultLength(), r.getConsumedLength());
    Assert.assertTrue("hasNext() did not return true when more items are to be consumed.", r.hasNext());
    // Next time we call next(), return an End-of-StreamSegment entry.
    nextEntry.set(terminalEntryCreator.apply((long) START_OFFSET + MAX_RESULT_LENGTH / 2, MAX_RESULT_LENGTH / 2));
    ReadResultEntry resultEntry = r.next();
    Assert.assertEquals("Unexpected result from nextEntry() when returning the terminal item from the result.", nextEntry.get(), resultEntry);
    Assert.assertFalse("hasNext() did not return false when reaching a terminal state.", r.hasNext());
    resultEntry = r.next();
    Assert.assertNull("next() did return null when it encountered a terminal state.", resultEntry);
}
Also used : ObjectClosedException(io.pravega.common.ObjectClosedException) AssertExtensions(io.pravega.test.common.AssertExtensions) BiFunction(java.util.function.BiFunction) lombok.val(lombok.val) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) Cleanup(lombok.Cleanup) Test(org.junit.Test) ReadResultEntryType(io.pravega.segmentstore.contracts.ReadResultEntryType) AtomicReference(java.util.concurrent.atomic.AtomicReference) Rule(org.junit.Rule) ByteArraySegment(io.pravega.common.util.ByteArraySegment) ReadResultEntry(io.pravega.segmentstore.contracts.ReadResultEntry) Timeout(org.junit.rules.Timeout) Assert(org.junit.Assert) ByteArraySegment(io.pravega.common.util.ByteArraySegment) ReadResultEntry(io.pravega.segmentstore.contracts.ReadResultEntry) AtomicReference(java.util.concurrent.atomic.AtomicReference) Cleanup(lombok.Cleanup)

Example 68 with ByteArraySegment

use of io.pravega.common.util.ByteArraySegment in project pravega by pravega.

the class ContainerReadIndexTests method setupMergeRead.

/**
 * Sets up a partial-merge Future Read for the two segments:
 * 1. Writes some data to transactionId (both Read Index and Storage).
 * 2. Calls beginMerge(parentId, transactionId)
 * 3. Executes a Storage concat (transactionId -> parentId)
 * 4. Clears the cache and returns a ReadResultEntry that requests data at the first offset of the merged transaction.
 * NOTE: this does not call completeMerge().
 */
private ReadResultEntry setupMergeRead(long parentId, long transactionId, byte[] txnData, TestContext context) throws Exception {
    int mergeOffset = 1;
    UpdateableSegmentMetadata parentMetadata = context.metadata.getStreamSegmentMetadata(parentId);
    UpdateableSegmentMetadata transactionMetadata = context.metadata.getStreamSegmentMetadata(transactionId);
    appendSingleWrite(parentId, new ByteArraySegment(new byte[mergeOffset]), context);
    context.storage.openWrite(parentMetadata.getName()).thenCompose(handle -> context.storage.write(handle, 0, new ByteArrayInputStream(new byte[mergeOffset]), mergeOffset, TIMEOUT)).join();
    // Write something to the transaction, and make sure it also makes its way to Storage.
    appendSingleWrite(transactionId, new ByteArraySegment(txnData), context);
    val transactionWriteHandle = context.storage.openWrite(transactionMetadata.getName()).join();
    context.storage.write(transactionWriteHandle, 0, new ByteArrayInputStream(txnData), txnData.length, TIMEOUT).join();
    transactionMetadata.setStorageLength(transactionMetadata.getLength());
    // Seal & Begin-merge the transaction (do not seal in storage).
    transactionMetadata.markSealed();
    parentMetadata.setLength(transactionMetadata.getLength() + mergeOffset);
    context.readIndex.beginMerge(parentId, mergeOffset, transactionId);
    transactionMetadata.markMerged();
    // Clear the cache.
    context.cacheManager.applyCachePolicy();
    // Issue read from the parent and fetch the first entry (there should only be one).
    ReadResult rr = context.readIndex.read(parentId, mergeOffset, txnData.length, TIMEOUT);
    Assert.assertTrue("Parent Segment read indicates no data available.", rr.hasNext());
    ReadResultEntry entry = rr.next();
    Assert.assertEquals("Unexpected offset for read result entry.", mergeOffset, entry.getStreamSegmentOffset());
    Assert.assertEquals("Served read result entry is not from storage.", ReadResultEntryType.Storage, entry.getType());
    // Merge the transaction in storage. Do not complete-merge it.
    transactionMetadata.markSealed();
    transactionMetadata.markSealedInStorage();
    transactionMetadata.markDeleted();
    context.storage.seal(transactionWriteHandle, TIMEOUT).join();
    val parentWriteHandle = context.storage.openWrite(parentMetadata.getName()).join();
    context.storage.concat(parentWriteHandle, 1, transactionWriteHandle.getSegmentName(), TIMEOUT).join();
    parentMetadata.setStorageLength(parentMetadata.getLength());
    // Attempt to extract data from the read.
    entry.requestContent(TIMEOUT);
    Assert.assertFalse("Not expecting the read to be completed.", entry.getContent().isDone());
    return entry;
}
Also used : Arrays(java.util.Arrays) StreamSegmentNotExistsException(io.pravega.segmentstore.contracts.StreamSegmentNotExistsException) SneakyThrows(lombok.SneakyThrows) AssertExtensions(io.pravega.test.common.AssertExtensions) ReadOnlyStorage(io.pravega.segmentstore.storage.ReadOnlyStorage) RequiredArgsConstructor(lombok.RequiredArgsConstructor) TimeoutException(java.util.concurrent.TimeoutException) Cleanup(lombok.Cleanup) Random(java.util.Random) UpdateableSegmentMetadata(io.pravega.segmentstore.server.UpdateableSegmentMetadata) StreamSegmentSealedException(io.pravega.segmentstore.contracts.StreamSegmentSealedException) ByteArrayInputStream(java.io.ByteArrayInputStream) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) BufferView(io.pravega.common.util.BufferView) Duration(java.time.Duration) Map(java.util.Map) CachePolicy(io.pravega.segmentstore.server.CachePolicy) TestCacheManager(io.pravega.segmentstore.server.TestCacheManager) CancellationException(java.util.concurrent.CancellationException) Collection(java.util.Collection) InMemoryStorage(io.pravega.segmentstore.storage.mocks.InMemoryStorage) CompletionException(java.util.concurrent.CompletionException) ReadResultEntryType(io.pravega.segmentstore.contracts.ReadResultEntryType) UUID(java.util.UUID) Collectors(java.util.stream.Collectors) StreamSegmentMetadata(io.pravega.segmentstore.server.containers.StreamSegmentMetadata) List(java.util.List) ByteArraySegment(io.pravega.common.util.ByteArraySegment) ThreadPooledTestSuite(io.pravega.test.common.ThreadPooledTestSuite) DirectMemoryCache(io.pravega.segmentstore.storage.cache.DirectMemoryCache) TestUtils(io.pravega.test.common.TestUtils) Futures(io.pravega.common.concurrent.Futures) ReadResult(io.pravega.segmentstore.contracts.ReadResult) TestStorage(io.pravega.segmentstore.server.TestStorage) ObjectClosedException(io.pravega.common.ObjectClosedException) MetadataBuilder(io.pravega.segmentstore.server.MetadataBuilder) ByteArrayOutputStream(java.io.ByteArrayOutputStream) Getter(lombok.Getter) Exceptions(io.pravega.common.Exceptions) HashMap(java.util.HashMap) CompletableFuture(java.util.concurrent.CompletableFuture) AtomicReference(java.util.concurrent.atomic.AtomicReference) CacheStorage(io.pravega.segmentstore.storage.cache.CacheStorage) ArrayList(java.util.ArrayList) HashSet(java.util.HashSet) EvictableMetadata(io.pravega.segmentstore.server.EvictableMetadata) UpdateableContainerMetadata(io.pravega.segmentstore.server.UpdateableContainerMetadata) SegmentMetadata(io.pravega.segmentstore.server.SegmentMetadata) CacheState(io.pravega.segmentstore.storage.cache.CacheState) ReadResultEntry(io.pravega.segmentstore.contracts.ReadResultEntry) ScheduledExecutorService(java.util.concurrent.ScheduledExecutorService) BiConsumer(java.util.function.BiConsumer) Timeout(org.junit.rules.Timeout) ReusableLatch(io.pravega.common.util.ReusableLatch) StreamSegmentTruncatedException(io.pravega.segmentstore.contracts.StreamSegmentTruncatedException) NameUtils(io.pravega.shared.NameUtils) IntentionalException(io.pravega.test.common.IntentionalException) lombok.val(lombok.val) IOException(java.io.IOException) Test(org.junit.Test) TimeUnit(java.util.concurrent.TimeUnit) Consumer(java.util.function.Consumer) AtomicLong(java.util.concurrent.atomic.AtomicLong) Mockito(org.mockito.Mockito) Rule(org.junit.Rule) Assert(org.junit.Assert) Collections(java.util.Collections) lombok.val(lombok.val) UpdateableSegmentMetadata(io.pravega.segmentstore.server.UpdateableSegmentMetadata) ByteArraySegment(io.pravega.common.util.ByteArraySegment) ByteArrayInputStream(java.io.ByteArrayInputStream) ReadResultEntry(io.pravega.segmentstore.contracts.ReadResultEntry) ReadResult(io.pravega.segmentstore.contracts.ReadResult)

Example 69 with ByteArraySegment

use of io.pravega.common.util.ByteArraySegment in project pravega by pravega.

the class ContainerReadIndexTests method testStorageReadTransactionNoCache.

// region Scenario-based tests
/**
 * Tests the following Scenario, where the ReadIndex would either read from a bad offset or fail with an invalid offset
 * when reading in certain conditions:
 * * A segment has a transaction, which has N bytes written to it.
 * * The transaction is merged into its parent segment at offset M > N.
 * * At least one byte of the transaction is evicted from the cache
 * * A read is issued to the parent segment for that byte that was evicted
 * * The ReadIndex is supposed to issue a Storage Read with an offset inside the transaction range (so translate
 * from the parent's offset to the transaction's offset). However, after the read, it is supposed to look like the
 * data was read from the parent segment, so it should not expose the adjusted offset at all.
 * <p>
 * This very specific unit test is a result of a regression found during testing.
 */
@Test
public void testStorageReadTransactionNoCache() throws Exception {
    CachePolicy cachePolicy = new CachePolicy(1, Duration.ZERO, Duration.ofMillis(1));
    @Cleanup TestContext context = new TestContext(DEFAULT_CONFIG, cachePolicy);
    // Create parent segment and one transaction
    long parentId = createSegment(0, context);
    long transactionId = createTransaction(1, context);
    createSegmentsInStorage(context);
    ByteArrayOutputStream writtenStream = new ByteArrayOutputStream();
    // Write something to the transaction, and make sure it also makes its way to Storage.
    UpdateableSegmentMetadata parentMetadata = context.metadata.getStreamSegmentMetadata(parentId);
    UpdateableSegmentMetadata transactionMetadata = context.metadata.getStreamSegmentMetadata(transactionId);
    ByteArraySegment transactionWriteData = getAppendData(transactionMetadata.getName(), transactionId, 0, 0);
    appendSingleWrite(transactionId, transactionWriteData, context);
    val handle = context.storage.openWrite(transactionMetadata.getName()).join();
    context.storage.write(handle, 0, transactionWriteData.getReader(), transactionWriteData.getLength(), TIMEOUT).join();
    transactionMetadata.setStorageLength(transactionMetadata.getLength());
    // Write some data to the parent, and make sure it is more than what we write to the transaction (hence the 10).
    for (int i = 0; i < 10; i++) {
        ByteArraySegment parentWriteData = getAppendData(parentMetadata.getName(), parentId, i, i);
        appendSingleWrite(parentId, parentWriteData, context);
        parentWriteData.copyTo(writtenStream);
    }
    // Seal & Begin-merge the transaction (do not seal in storage).
    transactionMetadata.markSealed();
    long mergeOffset = parentMetadata.getLength();
    parentMetadata.setLength(mergeOffset + transactionMetadata.getLength());
    context.readIndex.beginMerge(parentId, mergeOffset, transactionId);
    transactionMetadata.markMerged();
    transactionWriteData.copyTo(writtenStream);
    // Clear the cache.
    boolean evicted = context.cacheManager.applyCachePolicy();
    Assert.assertTrue("Expected an eviction.", evicted);
    // Issue read from the parent.
    ReadResult rr = context.readIndex.read(parentId, mergeOffset, transactionWriteData.getLength(), TIMEOUT);
    Assert.assertTrue("Parent Segment read indicates no data available.", rr.hasNext());
    ByteArrayOutputStream readStream = new ByteArrayOutputStream();
    long expectedOffset = mergeOffset;
    while (rr.hasNext()) {
        ReadResultEntry entry = rr.next();
        Assert.assertEquals("Unexpected offset for read result entry.", expectedOffset, entry.getStreamSegmentOffset());
        Assert.assertEquals("Served read result entry is not from storage.", ReadResultEntryType.Storage, entry.getType());
        // Request contents and store for later use.
        entry.requestContent(TIMEOUT);
        BufferView contents = entry.getContent().get(TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
        contents.copyTo(readStream);
        expectedOffset += contents.getLength();
    }
    byte[] readData = readStream.toByteArray();
    Assert.assertArrayEquals("Unexpected data read back.", transactionWriteData.getCopy(), readData);
}
Also used : lombok.val(lombok.val) UpdateableSegmentMetadata(io.pravega.segmentstore.server.UpdateableSegmentMetadata) ByteArraySegment(io.pravega.common.util.ByteArraySegment) ReadResult(io.pravega.segmentstore.contracts.ReadResult) ByteArrayOutputStream(java.io.ByteArrayOutputStream) Cleanup(lombok.Cleanup) CachePolicy(io.pravega.segmentstore.server.CachePolicy) BufferView(io.pravega.common.util.BufferView) ReadResultEntry(io.pravega.segmentstore.contracts.ReadResultEntry) Test(org.junit.Test)

Example 70 with ByteArraySegment

use of io.pravega.common.util.ByteArraySegment in project pravega by pravega.

the class ContainerReadIndexTests method testCacheEviction.

/**
 * Tests the ability to evict entries from the ReadIndex under various conditions:
 * * If an entry is aged out
 * * If an entry is pushed out because of cache space pressure.
 *
 * This also verifies that certain entries, such as RedirectReadIndexEntries and entries after the Storage Offset are
 * not removed.
 *
 * The way this test goes is as follows (it's pretty subtle, because there aren't many ways to hook into the ReadIndex and see what it's doing)
 * 1. It creates a bunch of segments, and populates them in storage (each) up to offset N/2-1 (this is called pre-storage)
 * 2. It populates the ReadIndex for each of those segments from offset N/2 to offset N-1 (this is called post-storage)
 * 3. It loads all the data from Storage into the ReadIndex, in entries of size equal to those already loaded in step #2.
 * 3a. At this point, all the entries added in step #2 have Generations 0..A/4-1, and step #3 have generations A/4..A-1
 * 4. Append more data at the end. This forces the generation to increase to 1.25A.
 * 4a. Nothing should be evicted from the cache now, since the earliest items are all post-storage.
 * 5. We 'touch' (read) the first 1/3 of pre-storage entries (offsets 0..N/4).
 * 5a. At this point, those entries (offsets 0..N/6) will have the newest generations (1.25A..1.5A)
 * 6. We append more data (equivalent to the data we touched)
 * 6a. Nothing should be evicted, since those generations that were just eligible for removal were touched and bumped up.
 * 7. We forcefully increase the current generation by 1 (without touching the ReadIndex)
 * 7a. At this point, we expect all the pre-storage items, except the touched ones, to be evicted. This is generations 0.25A-0.75A.
 * 8. Update the metadata and indicate that all the post-storage entries are now pre-storage and bump the generation by 0.75A.
 * 8a. At this point, we expect all former post-storage items and pre-storage items to be evicted (in this order).
 * <p>
 * The final order of eviction (in terms of offsets, for each segment), is:
 * * 0.25N-0.75N, 0.75N..N, N..1.25N, 0..0.25N, 1.25N..1.5N (remember that we added quite a bunch of items after the initial run).
 */
@Test
@SuppressWarnings("checkstyle:CyclomaticComplexity")
public void testCacheEviction() throws Exception {
    // Create a CachePolicy with a set number of generations and a known max size.
    // Each generation contains exactly one entry, so the number of generations is also the number of entries.
    // We append one byte at each time. This allows us to test edge cases as well by having the finest precision when
    // it comes to selecting which bytes we want evicted and which kept.
    final int appendSize = 1;
    // This also doubles as number of generations (each generation, we add one append for each segment).
    final int entriesPerSegment = 100;
    final int cacheMaxSize = SEGMENT_COUNT * entriesPerSegment * appendSize;
    // 25% of the entries are beyond the StorageOffset
    final int postStorageEntryCount = entriesPerSegment / 4;
    // 75% of the entries are before the StorageOffset.
    final int preStorageEntryCount = entriesPerSegment - postStorageEntryCount;
    CachePolicy cachePolicy = new CachePolicy(cacheMaxSize, 1.0, 1.0, Duration.ofMillis(1000 * 2 * entriesPerSegment), Duration.ofMillis(1000));
    // To properly test this, we want predictable storage reads.
    ReadIndexConfig config = ReadIndexConfig.builder().with(ReadIndexConfig.STORAGE_READ_ALIGNMENT, appendSize).build();
    ArrayList<Integer> removedEntries = new ArrayList<>();
    @Cleanup TestContext context = new TestContext(config, cachePolicy);
    // To ease our testing, we disable appends and instruct the TestCache to report the same value for UsedBytes as it
    // has for StoredBytes. This shields us from having to know internal details about the layout of the cache.
    context.cacheStorage.usedBytesSameAsStoredBytes = true;
    context.cacheStorage.disableAppends = true;
    // Record every cache removal.
    context.cacheStorage.deleteCallback = removedEntries::add;
    // Create the segments (metadata + storage).
    ArrayList<Long> segmentIds = createSegments(context);
    createSegmentsInStorage(context);
    // Populate the Storage with appropriate data.
    byte[] preStorageData = new byte[preStorageEntryCount * appendSize];
    for (long segmentId : segmentIds) {
        UpdateableSegmentMetadata sm = context.metadata.getStreamSegmentMetadata(segmentId);
        val handle = context.storage.openWrite(sm.getName()).join();
        context.storage.write(handle, 0, new ByteArrayInputStream(preStorageData), preStorageData.length, TIMEOUT).join();
        sm.setStorageLength(preStorageData.length);
        sm.setLength(preStorageData.length);
    }
    val cacheMappings = new HashMap<Integer, SegmentOffset>();
    // Callback that appends one entry at the end of the given segment id.
    Consumer<Long> appendOneEntry = segmentId -> {
        UpdateableSegmentMetadata sm = context.metadata.getStreamSegmentMetadata(segmentId);
        byte[] data = new byte[appendSize];
        long offset = sm.getLength();
        sm.setLength(offset + data.length);
        try {
            context.cacheStorage.insertCallback = address -> cacheMappings.put(address, new SegmentOffset(segmentId, offset));
            context.readIndex.append(segmentId, offset, new ByteArraySegment(data));
        } catch (StreamSegmentNotExistsException ex) {
            throw new CompletionException(ex);
        }
    };
    // Populate the ReadIndex with the Append entries (post-StorageOffset)
    for (int i = 0; i < postStorageEntryCount; i++) {
        segmentIds.forEach(appendOneEntry);
        // Each time we make a round of appends (one per segment), we increment the generation in the CacheManager.
        context.cacheManager.applyCachePolicy();
    }
    // Read all the data from Storage, making sure we carefully associate them with the proper generation.
    for (int i = 0; i < preStorageEntryCount; i++) {
        long offset = i * appendSize;
        for (long segmentId : segmentIds) {
            @Cleanup ReadResult result = context.readIndex.read(segmentId, offset, appendSize, TIMEOUT);
            ReadResultEntry resultEntry = result.next();
            Assert.assertEquals("Unexpected type of ReadResultEntry when trying to load up data into the ReadIndex Cache.", ReadResultEntryType.Storage, resultEntry.getType());
            CompletableFuture<Void> insertedInCache = new CompletableFuture<>();
            context.cacheStorage.insertCallback = address -> {
                cacheMappings.put(address, new SegmentOffset(segmentId, offset));
                insertedInCache.complete(null);
            };
            resultEntry.requestContent(TIMEOUT);
            BufferView contents = resultEntry.getContent().get(TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
            Assert.assertFalse("Not expecting more data to be available for reading.", result.hasNext());
            Assert.assertEquals("Unexpected ReadResultEntry length when trying to load up data into the ReadIndex Cache.", appendSize, contents.getLength());
            // Wait for the entry to be inserted into the cache before moving on.
            insertedInCache.get(TIMEOUT.toMillis(), TimeUnit.MILLISECONDS);
        }
        context.cacheManager.applyCachePolicy();
    }
    Assert.assertEquals("Not expecting any removed Cache entries at this point (cache is not full).", 0, removedEntries.size());
    // Append more data (equivalent to all post-storage entries), and verify that NO entries are being evicted (we cannot evict post-storage entries).
    for (int i = 0; i < postStorageEntryCount; i++) {
        segmentIds.forEach(appendOneEntry);
        context.cacheManager.applyCachePolicy();
    }
    Assert.assertEquals("Not expecting any removed Cache entries at this point (only eligible entries were post-storage).", 0, removedEntries.size());
    // 'Touch' the first few entries read from storage. This should move them to the back of the queue (they won't be the first ones to be evicted).
    int touchCount = preStorageEntryCount / 3;
    for (int i = 0; i < touchCount; i++) {
        long offset = i * appendSize;
        for (long segmentId : segmentIds) {
            @Cleanup ReadResult result = context.readIndex.read(segmentId, offset, appendSize, TIMEOUT);
            ReadResultEntry resultEntry = result.next();
            Assert.assertEquals("Unexpected type of ReadResultEntry when trying to load up data into the ReadIndex Cache.", ReadResultEntryType.Cache, resultEntry.getType());
        }
    }
    // Append more data (equivalent to the amount of data we 'touched'), and verify that the entries we just touched are not being removed..
    for (int i = 0; i < touchCount; i++) {
        segmentIds.forEach(appendOneEntry);
        context.cacheManager.applyCachePolicy();
    }
    Assert.assertEquals("Not expecting any removed Cache entries at this point (we touched old entries and they now have the newest generation).", 0, removedEntries.size());
    // Increment the generations so that we are caught up to just before the generation where the "touched" items now live.
    context.cacheManager.applyCachePolicy();
    // We expect all but the 'touchCount' pre-Storage entries to be removed.
    int expectedRemovalCount = (preStorageEntryCount - touchCount) * SEGMENT_COUNT;
    Assert.assertEquals("Unexpected number of removed entries after having forced out all pre-storage entries.", expectedRemovalCount, removedEntries.size());
    // Now update the metadata and indicate that all the post-storage data has been moved to storage.
    segmentIds.forEach(segmentId -> {
        UpdateableSegmentMetadata sm = context.metadata.getStreamSegmentMetadata(segmentId);
        sm.setStorageLength(sm.getLength());
    });
    // We add one artificial entry, which we'll be touching forever and ever; this forces the CacheManager to
    // update its current generation every time. We will be ignoring this entry for our test.
    SegmentMetadata readSegment = context.metadata.getStreamSegmentMetadata(segmentIds.get(0));
    appendOneEntry.accept(readSegment.getId());
    // Now evict everything (whether by size of by aging out).
    for (int i = 0; i < cachePolicy.getMaxGenerations(); i++) {
        @Cleanup ReadResult result = context.readIndex.read(readSegment.getId(), readSegment.getLength() - appendSize, appendSize, TIMEOUT);
        result.next();
        context.cacheManager.applyCachePolicy();
    }
    int expectedRemovalCountPerSegment = entriesPerSegment + touchCount + postStorageEntryCount;
    int expectedTotalRemovalCount = SEGMENT_COUNT * expectedRemovalCountPerSegment;
    Assert.assertEquals("Unexpected number of removed entries after having forced out all the entries.", expectedTotalRemovalCount, removedEntries.size());
    // Finally, verify that the evicted items are in the correct order (for each segment). See this test's description for details.
    for (long segmentId : segmentIds) {
        List<SegmentOffset> segmentRemovedKeys = removedEntries.stream().map(cacheMappings::get).filter(e -> e.segmentId == segmentId).collect(Collectors.toList());
        Assert.assertEquals("Unexpected number of removed entries for segment " + segmentId, expectedRemovalCountPerSegment, segmentRemovedKeys.size());
        // The correct order of eviction (N=entriesPerSegment) is: 0.25N-0.75N, 0.75N..N, N..1.25N, 0..0.25N, 1.25N..1.5N.
        // This is equivalent to the following tests
        // 0.25N-1.25N
        checkOffsets(segmentRemovedKeys, segmentId, 0, entriesPerSegment, entriesPerSegment * appendSize / 4, appendSize);
        // 0..0.25N
        checkOffsets(segmentRemovedKeys, segmentId, entriesPerSegment, entriesPerSegment / 4, 0, appendSize);
        // 1.25N..1.5N
        checkOffsets(segmentRemovedKeys, segmentId, entriesPerSegment + entriesPerSegment / 4, entriesPerSegment / 4, (int) (entriesPerSegment * appendSize * 1.25), appendSize);
    }
}
Also used : Arrays(java.util.Arrays) StreamSegmentNotExistsException(io.pravega.segmentstore.contracts.StreamSegmentNotExistsException) SneakyThrows(lombok.SneakyThrows) AssertExtensions(io.pravega.test.common.AssertExtensions) ReadOnlyStorage(io.pravega.segmentstore.storage.ReadOnlyStorage) RequiredArgsConstructor(lombok.RequiredArgsConstructor) TimeoutException(java.util.concurrent.TimeoutException) Cleanup(lombok.Cleanup) Random(java.util.Random) UpdateableSegmentMetadata(io.pravega.segmentstore.server.UpdateableSegmentMetadata) StreamSegmentSealedException(io.pravega.segmentstore.contracts.StreamSegmentSealedException) ByteArrayInputStream(java.io.ByteArrayInputStream) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) BufferView(io.pravega.common.util.BufferView) Duration(java.time.Duration) Map(java.util.Map) CachePolicy(io.pravega.segmentstore.server.CachePolicy) TestCacheManager(io.pravega.segmentstore.server.TestCacheManager) CancellationException(java.util.concurrent.CancellationException) Collection(java.util.Collection) InMemoryStorage(io.pravega.segmentstore.storage.mocks.InMemoryStorage) CompletionException(java.util.concurrent.CompletionException) ReadResultEntryType(io.pravega.segmentstore.contracts.ReadResultEntryType) UUID(java.util.UUID) Collectors(java.util.stream.Collectors) StreamSegmentMetadata(io.pravega.segmentstore.server.containers.StreamSegmentMetadata) List(java.util.List) ByteArraySegment(io.pravega.common.util.ByteArraySegment) ThreadPooledTestSuite(io.pravega.test.common.ThreadPooledTestSuite) DirectMemoryCache(io.pravega.segmentstore.storage.cache.DirectMemoryCache) TestUtils(io.pravega.test.common.TestUtils) Futures(io.pravega.common.concurrent.Futures) ReadResult(io.pravega.segmentstore.contracts.ReadResult) TestStorage(io.pravega.segmentstore.server.TestStorage) ObjectClosedException(io.pravega.common.ObjectClosedException) MetadataBuilder(io.pravega.segmentstore.server.MetadataBuilder) ByteArrayOutputStream(java.io.ByteArrayOutputStream) Getter(lombok.Getter) Exceptions(io.pravega.common.Exceptions) HashMap(java.util.HashMap) CompletableFuture(java.util.concurrent.CompletableFuture) AtomicReference(java.util.concurrent.atomic.AtomicReference) CacheStorage(io.pravega.segmentstore.storage.cache.CacheStorage) ArrayList(java.util.ArrayList) HashSet(java.util.HashSet) EvictableMetadata(io.pravega.segmentstore.server.EvictableMetadata) UpdateableContainerMetadata(io.pravega.segmentstore.server.UpdateableContainerMetadata) SegmentMetadata(io.pravega.segmentstore.server.SegmentMetadata) CacheState(io.pravega.segmentstore.storage.cache.CacheState) ReadResultEntry(io.pravega.segmentstore.contracts.ReadResultEntry) ScheduledExecutorService(java.util.concurrent.ScheduledExecutorService) BiConsumer(java.util.function.BiConsumer) Timeout(org.junit.rules.Timeout) ReusableLatch(io.pravega.common.util.ReusableLatch) StreamSegmentTruncatedException(io.pravega.segmentstore.contracts.StreamSegmentTruncatedException) NameUtils(io.pravega.shared.NameUtils) IntentionalException(io.pravega.test.common.IntentionalException) lombok.val(lombok.val) IOException(java.io.IOException) Test(org.junit.Test) TimeUnit(java.util.concurrent.TimeUnit) Consumer(java.util.function.Consumer) AtomicLong(java.util.concurrent.atomic.AtomicLong) Mockito(org.mockito.Mockito) Rule(org.junit.Rule) Assert(org.junit.Assert) Collections(java.util.Collections) ByteArraySegment(io.pravega.common.util.ByteArraySegment) HashMap(java.util.HashMap) ArrayList(java.util.ArrayList) ReadResult(io.pravega.segmentstore.contracts.ReadResult) Cleanup(lombok.Cleanup) CompletableFuture(java.util.concurrent.CompletableFuture) BufferView(io.pravega.common.util.BufferView) lombok.val(lombok.val) UpdateableSegmentMetadata(io.pravega.segmentstore.server.UpdateableSegmentMetadata) StreamSegmentNotExistsException(io.pravega.segmentstore.contracts.StreamSegmentNotExistsException) AtomicInteger(java.util.concurrent.atomic.AtomicInteger) UpdateableSegmentMetadata(io.pravega.segmentstore.server.UpdateableSegmentMetadata) StreamSegmentMetadata(io.pravega.segmentstore.server.containers.StreamSegmentMetadata) SegmentMetadata(io.pravega.segmentstore.server.SegmentMetadata) CachePolicy(io.pravega.segmentstore.server.CachePolicy) ByteArrayInputStream(java.io.ByteArrayInputStream) CompletionException(java.util.concurrent.CompletionException) ReadResultEntry(io.pravega.segmentstore.contracts.ReadResultEntry) AtomicLong(java.util.concurrent.atomic.AtomicLong) Test(org.junit.Test)

Aggregations

ByteArraySegment (io.pravega.common.util.ByteArraySegment)222 lombok.val (lombok.val)158 Test (org.junit.Test)145 Cleanup (lombok.Cleanup)114 ArrayList (java.util.ArrayList)88 CompletableFuture (java.util.concurrent.CompletableFuture)58 BufferView (io.pravega.common.util.BufferView)54 HashMap (java.util.HashMap)54 List (java.util.List)52 AssertExtensions (io.pravega.test.common.AssertExtensions)50 Assert (org.junit.Assert)49 Duration (java.time.Duration)48 AtomicReference (java.util.concurrent.atomic.AtomicReference)44 Collectors (java.util.stream.Collectors)42 IOException (java.io.IOException)41 AtomicLong (java.util.concurrent.atomic.AtomicLong)41 IntentionalException (io.pravega.test.common.IntentionalException)40 Random (java.util.Random)40 Map (java.util.Map)39 Rule (org.junit.Rule)39