diff --git a/server/src/main/java/com/hedera/block/server/ServiceStatus.java b/server/src/main/java/com/hedera/block/server/ServiceStatus.java index 425f74070..c43397142 100644 --- a/server/src/main/java/com/hedera/block/server/ServiceStatus.java +++ b/server/src/main/java/com/hedera/block/server/ServiceStatus.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2024 Hedera Hashgraph, LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.hedera.block.server; import io.helidon.webserver.WebServer; diff --git a/server/src/main/java/com/hedera/block/server/mediator/LiveStreamMediatorImpl.java b/server/src/main/java/com/hedera/block/server/mediator/LiveStreamMediatorImpl.java index 8bbeb0b6b..94a9e5b3b 100644 --- a/server/src/main/java/com/hedera/block/server/mediator/LiveStreamMediatorImpl.java +++ b/server/src/main/java/com/hedera/block/server/mediator/LiveStreamMediatorImpl.java @@ -26,7 +26,6 @@ import com.lmax.disruptor.RingBuffer; import com.lmax.disruptor.dsl.Disruptor; import com.lmax.disruptor.util.DaemonThreadFactory; -import io.helidon.webserver.WebServer; import java.io.IOException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; diff --git a/server/src/main/java/com/hedera/block/server/persistence/storage/BlockAsDirWriter.java b/server/src/main/java/com/hedera/block/server/persistence/storage/BlockAsDirWriter.java index 29594dae4..af52e080c 100644 --- a/server/src/main/java/com/hedera/block/server/persistence/storage/BlockAsDirWriter.java +++ b/server/src/main/java/com/hedera/block/server/persistence/storage/BlockAsDirWriter.java @@ -82,28 +82,34 @@ public void write(final BlockItem blockItem) throws IOException { } final Path blockItemFilePath = calculateBlockItemPath(); - for (int retries = 0;;retries++) { + for (int retries = 0; ; retries++) { try { write(blockItemFilePath, blockItem); break; } catch (IOException e) { - LOGGER.log(System.Logger.Level.ERROR, "Error writing the BlockItem protobuf to a file", e); - if (retries > 1) { + LOGGER.log( + System.Logger.Level.ERROR, + "Error writing the BlockItem protobuf to a file", + e); + if (retries > 0) { // Attempt to remove the block - blockRemover.remove(blockNodeFileNameIndex); + blockRemover.remove(Long.parseLong(currentBlockDir.toString())); throw e; } else { // Attempt to repair the permissions on the block path // and the blockItem path + repairPermissions(blockNodeRootPath); repairPermissions(calculateBlockPath()); - LOGGER.log(System.Logger.Level.INFO, "Retrying to write the BlockItem protobuf to a file"); + LOGGER.log( + System.Logger.Level.INFO, + "Retrying to write the BlockItem protobuf to a file"); } } } } - private void write(final Path blockItemFilePath, final BlockItem blockItem) throws IOException { + void write(final Path blockItemFilePath, final BlockItem blockItem) throws IOException { try (FileOutputStream fos = new FileOutputStream(blockItemFilePath.toString())) { blockItem.writeTo(fos); LOGGER.log( @@ -133,7 +139,7 @@ private void resetState(final BlockItem blockItem) throws IOException { blockNodeFileNameIndex = 0; } - private void repairPermissions(final Path path) throws IOException{ + private void repairPermissions(final Path path) throws IOException { final boolean isReadable = Files.isReadable(path); final boolean isWritable = Files.isWritable(path); if (!isWritable || !isReadable) { @@ -157,7 +163,7 @@ private void repairPermissions(final Path path) throws IOException{ } catch (IOException e) { LOGGER.log( System.Logger.Level.ERROR, - "Error setting permissions on the block node root directory", + "Error setting permissions on the path: " + path, e); throw e; } diff --git a/server/src/main/java/com/hedera/block/server/producer/ProducerBlockItemObserver.java b/server/src/main/java/com/hedera/block/server/producer/ProducerBlockItemObserver.java index 3d96bfdc5..9aa0cb810 100644 --- a/server/src/main/java/com/hedera/block/server/producer/ProducerBlockItemObserver.java +++ b/server/src/main/java/com/hedera/block/server/producer/ProducerBlockItemObserver.java @@ -23,7 +23,6 @@ import com.hedera.block.server.data.ObjectEvent; import com.hedera.block.server.mediator.StreamMediator; import io.grpc.stub.StreamObserver; -import io.helidon.webserver.WebServer; import java.io.IOException; import java.security.NoSuchAlgorithmException; diff --git a/server/src/test/java/com/hedera/block/server/persistence/storage/BlockAsDirectoryTest.java b/server/src/test/java/com/hedera/block/server/persistence/storage/BlockAsDirectoryTest.java index affc8111b..45c148067 100644 --- a/server/src/test/java/com/hedera/block/server/persistence/storage/BlockAsDirectoryTest.java +++ b/server/src/test/java/com/hedera/block/server/persistence/storage/BlockAsDirectoryTest.java @@ -29,12 +29,14 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.PosixFilePermission; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Set; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; public class BlockAsDirectoryTest { @@ -192,18 +194,19 @@ public void testRemoveBlockWritePerms() throws IOException { } @Test - @Disabled public void testRemoveBlockItemWritePerms() throws IOException { final List blockItems = PersistTestUtils.generateBlockItems(1); - final BlockWriter blockWriter = new BlockAsDirWriter(JUNIT, testConfig); - - // Writing the header BlockItem will create the block directory. - blockWriter.write(blockItems.get(0)); + final BlockRemover blockRemover = + new BlockAsDirRemover( + Path.of(testConfig.get(JUNIT).asString().get()), Util.defaultPerms); + final BlockWriter blockWriter = + new TestIOExceptionBlockAsDirWriter( + JUNIT, testConfig, blockRemover, Util.defaultPerms, 0); - // Change the permissions on the block node root directory - removeBlockWritePerms(1, testConfig); - assertThrows(IOException.class, () -> blockWriter.write(blockItems.get(1))); + // TestBlockAsDirWriter overrides the write method to always + // throw an IOException + assertThrows(IOException.class, () -> blockWriter.write(blockItems.get(0))); } @Test @@ -215,21 +218,27 @@ public void testConstructorWithInvalidPath() { } @Test - @Disabled public void testPartialBlockRemoval() throws IOException { final List blockItems = PersistTestUtils.generateBlockItems(1); - final BlockWriter blockWriter = new BlockAsDirWriter(JUNIT, testConfig); + final BlockRemover blockRemover = + new BlockAsDirRemover( + Path.of(testConfig.get(JUNIT).asString().get()), Util.defaultPerms); + final BlockWriter blockWriter = + new TestIOExceptionBlockAsDirWriter( + JUNIT, testConfig, blockRemover, Util.defaultPerms, 3); // Write a few block items blockWriter.write(blockItems.get(0)); blockWriter.write(blockItems.get(1)); blockWriter.write(blockItems.get(2)); - // Remove write permissions from the block item - removeBlockWritePerms(1, testConfig); + // Attempt to write the next block should fail + assertThrows(IOException.class, () -> blockWriter.write(blockItems.get(3))); - // Attempt to read the block - assertThrows(IOException.class, () -> blockWriter.write(blockItems.get(4))); + // Verify the partially written block was removed + final BlockReader blockReader = new BlockAsDirReader(JUNIT, testConfig); + final Optional blockOpt = blockReader.read(1); + assertTrue(blockOpt.isEmpty()); } private void removeBlockReadPerms(int blockNumber, final Config config) throws IOException { @@ -264,4 +273,31 @@ private void removeBlockItemReadPerms(int blockNumber, int blockItem, Config con final Path blockItemPath = blockPath.resolve(blockItem + BLOCK_FILE_EXTENSION); Files.setPosixFilePermissions(blockItemPath, TestUtils.getNoRead().value()); } + + static class TestIOExceptionBlockAsDirWriter extends BlockAsDirWriter { + + final int blockItemsToAdmitBeforeFailure; + private int currentCount; + + public TestIOExceptionBlockAsDirWriter( + final String key, + final Config config, + final BlockRemover blockRemover, + final FileAttribute> filePerms, + final int blockItemsToAdmitBeforeFailure) + throws IOException { + super(key, config, blockRemover, filePerms); + this.blockItemsToAdmitBeforeFailure = blockItemsToAdmitBeforeFailure; + } + + @Override + void write(final Path blockItemFilePath, final BlockItem blockItem) throws IOException { + if (currentCount < blockItemsToAdmitBeforeFailure) { + super.write(blockItemFilePath, blockItem); + currentCount++; + } else { + throw new IOException("Test exception"); + } + } + } } diff --git a/server/src/test/java/com/hedera/block/server/producer/ProducerBlockItemObserverTest.java b/server/src/test/java/com/hedera/block/server/producer/ProducerBlockItemObserverTest.java index f09ab1ffb..e88ff078f 100644 --- a/server/src/test/java/com/hedera/block/server/producer/ProducerBlockItemObserverTest.java +++ b/server/src/test/java/com/hedera/block/server/producer/ProducerBlockItemObserverTest.java @@ -35,7 +35,6 @@ import com.hedera.block.server.persistence.storage.BlockReader; import com.hedera.block.server.persistence.storage.BlockWriter; import io.grpc.stub.StreamObserver; -import io.helidon.webserver.WebServer; import java.io.IOException; import java.security.NoSuchAlgorithmException; import java.util.List; @@ -197,7 +196,10 @@ public void testItemAckBuilderExceptionTest() ProducerBlockItemObserver producerBlockItemObserver = new ProducerBlockItemObserver( - streamMediator, publishStreamResponseObserver, itemAckBuilder, serviceStatus); + streamMediator, + publishStreamResponseObserver, + itemAckBuilder, + serviceStatus); when(streamMediator.isPublishing()).thenReturn(true); when(itemAckBuilder.buildAck(any()))