Skip to content

Commit

Permalink
WIP address some pr comments, new live and archive roots for block pe…
Browse files Browse the repository at this point in the history
…rsistence

Signed-off-by: Atanas Atanasov <[email protected]>
  • Loading branch information
ata-nas committed Nov 27, 2024
1 parent 5daabfb commit d869521
Show file tree
Hide file tree
Showing 16 changed files with 335 additions and 99 deletions.
3 changes: 2 additions & 1 deletion server/docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ defaults and can be left unchanged. It is recommended to browse the properties b

| Environment Variable | Description | Default Value |
|:---|:---|---:|
| PERSISTENCE_STORAGE_ROOT_PATH | The root path for the storage, if not provided will attempt to create a `data` on the working dir of the app. | |
| PERSISTENCE_STORAGE_LIVE_ROOT_PATH | The root path for the live storage. The provided path must be absolute! | |
| PERSISTENCE_STORAGE_ARCHIVE_ROOT_PATH | The root path for the archive storage. The provided path must be absolute! | |
| PERSISTENCE_STORAGE_TYPE | Type of the persistence storage | BLOCK_AS_LOCAL_FILE |
| CONSUMER_TIMEOUT_THRESHOLD_MILLIS | Time to wait for subscribers before disconnecting in milliseconds | 1500 |
| SERVICE_DELAY_MILLIS | Service shutdown delay in milliseconds | 500 |
Expand Down
7 changes: 5 additions & 2 deletions server/src/main/java/com/hedera/block/server/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,11 @@

/** Constants used in the BlockNode service. */
public final class Constants {
/** Constant mapped to the semantic name of the Block Node root directory */
public static final String BLOCK_NODE_ROOT_DIRECTORY_SEMANTIC_NAME = "Block Node Root Directory";
/** Constant mapped to the semantic name of the Block Node live root directory */
public static final String BLOCK_NODE_LIVE_ROOT_DIRECTORY_SEMANTIC_NAME = "Block Node Live Root Directory";

/** Constant mapped to the semantic name of the Block Node archive root directory */
public static final String BLOCK_NODE_ARCHIVE_ROOT_DIRECTORY_SEMANTIC_NAME = "Block Node Archive Root Directory";

/** Constant mapped to PbjProtocolProvider.CONFIG_NAME in the PBJ Helidon Plugin */
public static final String PBJ_PROTOCOL_PROVIDER_CONFIG_NAME = "pbj";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
public final class ServerMappedConfigSourceInitializer {
private static final List<ConfigMapping> MAPPINGS = List.of(
new ConfigMapping("consumer.timeoutThresholdMillis", "CONSUMER_TIMEOUT_THRESHOLD_MILLIS"),
new ConfigMapping("persistence.storage.rootPath", "PERSISTENCE_STORAGE_ROOT_PATH"),
new ConfigMapping("persistence.storage.liveRootPath", "PERSISTENCE_STORAGE_LIVE_ROOT_PATH"),
new ConfigMapping("persistence.storage.archiveRootPath", "PERSISTENCE_STORAGE_ARCHIVE_ROOT_PATH"),
new ConfigMapping("persistence.storage.type", "PERSISTENCE_STORAGE_TYPE"),
new ConfigMapping("service.delayMillis", "SERVICE_DELAY_MILLIS"),
new ConfigMapping("mediator.ringBufferSize", "MEDIATOR_RING_BUFFER_SIZE"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,8 @@ static BlockReader<BlockUnparsed> providesBlockReader(@NonNull final Persistence
final StorageType persistenceType = config.type();
return switch (persistenceType) {
case BLOCK_AS_LOCAL_FILE -> BlockAsFileReaderBuilder.newBuilder().build();
case BLOCK_AS_LOCAL_DIRECTORY -> BlockAsDirReaderBuilder.newBuilder(config).build();
case BLOCK_AS_LOCAL_DIRECTORY -> BlockAsDirReaderBuilder.newBuilder(config)
.build();
case NO_OP -> new NoOpBlockReader();
};
}
Expand Down Expand Up @@ -140,7 +141,7 @@ static BlockRemover providesBlockRemover(
@Singleton
static BlockPathResolver providesPathResolver(@NonNull final PersistenceStorageConfig config) {
final StorageType persistenceType = config.type();
final Path blockStorageRoot = Path.of(config.rootPath());
final Path blockStorageRoot = Path.of(config.liveRootPath());
return switch (persistenceType) {
case BLOCK_AS_LOCAL_FILE -> new BlockAsFilePathResolver(blockStorageRoot);
case BLOCK_AS_LOCAL_DIRECTORY -> new BlockAsDirPathResolver(blockStorageRoot);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,65 +16,125 @@

package com.hedera.block.server.persistence.storage;

import static com.hedera.block.server.Constants.BLOCK_NODE_ROOT_DIRECTORY_SEMANTIC_NAME;
import static java.lang.System.Logger.Level.ERROR;
import static com.hedera.block.server.Constants.BLOCK_NODE_ARCHIVE_ROOT_DIRECTORY_SEMANTIC_NAME;
import static com.hedera.block.server.Constants.BLOCK_NODE_LIVE_ROOT_DIRECTORY_SEMANTIC_NAME;
import static java.lang.System.Logger.Level.INFO;

import com.hedera.block.common.utils.FileUtilities;
import com.hedera.block.common.utils.StringUtilities;
import com.swirlds.config.api.ConfigData;
import com.swirlds.config.api.ConfigProperty;
import edu.umd.cs.findbugs.annotations.NonNull;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Objects;

/**
* Use this configuration across the persistent storage package
* Use this configuration across the persistence storage package.
*
* @param rootPath provides the root path for saving block data, if you want to override it need to
* set it as persistence.storage.rootPath
* @param liveRootPath provides the root path for saving blocks live
* @param archiveRootPath provides the root path for archived blocks
* @param type use a predefined type string to replace the persistence component implementation.
* Non-PRODUCTION values should only be used for troubleshooting and development purposes.
* Non-PRODUCTION values should only be used for troubleshooting and development purposes.
*/
@ConfigData("persistence.storage")
public record PersistenceStorageConfig(
@ConfigProperty(defaultValue = "") String rootPath,
@ConfigProperty(defaultValue = "") String liveRootPath,
@ConfigProperty(defaultValue = "") String archiveRootPath,
@ConfigProperty(defaultValue = "BLOCK_AS_LOCAL_FILE") StorageType type) {
private static final System.Logger LOGGER = System.getLogger(PersistenceStorageConfig.class.getName());
private static final String LIVE_ROOT_PATH =
Path.of("hashgraph/blocknode/data/live/").toAbsolutePath().toString();
private static final String ARCHIVE_ROOT_PATH =
Path.of("hashgraph/blocknode/data/archive/").toAbsolutePath().toString();

/**
* Constructor to set the default root path if not provided, it will be set to the data
* directory in the current working directory
*/
public PersistenceStorageConfig {
Objects.requireNonNull(type);
Objects.requireNonNull(rootPath);
liveRootPath = resolvePath(liveRootPath, LIVE_ROOT_PATH, BLOCK_NODE_LIVE_ROOT_DIRECTORY_SEMANTIC_NAME);
archiveRootPath =
resolvePath(archiveRootPath, ARCHIVE_ROOT_PATH, BLOCK_NODE_ARCHIVE_ROOT_DIRECTORY_SEMANTIC_NAME);
LOGGER.log(INFO, "Persistence Storage Configuration: persistence.storage.type=" + type);
LOGGER.log(INFO, "Persistence Storage Configuration: persistence.storage.liveRootPath=" + liveRootPath);
LOGGER.log(INFO, "Persistence Storage Configuration: persistence.storage.archiveRootPath=" + archiveRootPath);
}

// verify rootPath prop
Path path = Path.of(rootPath);
if (rootPath.isBlank()) {
path = Paths.get("").toAbsolutePath().resolve("data");
/**
* This method attempts to resolve a given configured path. If the input
* path is blank, a default path is used. The resolved path must be
* absolute! If the path resolution is successful, at attempt is made to
* create the directory path. If the directory path cannot be created, an
* {@link UncheckedIOException} is thrown.
*
* @param pathToResolve the path to resolve
* @param defaultIfBlank the default path if the path to resolve is blank
* @param semanticPathName the semantic name of the path used for logging
* @return the resolved path
* @throws IllegalArgumentException if the resolved path is not absolute
* @throws UncheckedIOException if the resolved path cannot be created
*/
@NonNull
private String resolvePath(
final String pathToResolve, @NonNull final String defaultIfBlank, @NonNull final String semanticPathName) {
final Path normalized = getNormalizedPath(pathToResolve, defaultIfBlank, semanticPathName);
createDirectoryPath(normalized, semanticPathName);
return normalized.toString();
}

/**
* This method normalizes a given path. If the path to normalize is blank,
* a default path is used. The normalized path must be absolute! If the
* normalized path is not absolute, an {@link IllegalArgumentException} is
* thrown.
*
* @param pathToNormalize the path to normalize
* @param defaultIfBlank the default path if the path to normalize is blank
* @param semanticPathName the semantic name of the path used for logging
* @return the normalized path
* @throws IllegalArgumentException if the path to normalize is not absolute
*/
@NonNull
private Path getNormalizedPath(
final String pathToNormalize,
@NonNull final String defaultIfBlank,
@NonNull final String semanticPathName) {
final Path result;
if (StringUtilities.isBlank(pathToNormalize)) {
result = Path.of(defaultIfBlank).toAbsolutePath();
} else {
result = Path.of(pathToNormalize);
}

// Check if absolute
if (!path.isAbsolute()) {
throw new IllegalArgumentException(rootPath + " Root path must be absolute");
if (!result.isAbsolute()) {
throw new IllegalArgumentException("Path provided for [%s] must be absolute!".formatted(semanticPathName));
} else {
return result;
}
}

// Create Directory if it does not exist
/**
* This method creates a directory path at the given target path. If the
* directory path cannot be created, an {@link UncheckedIOException} is
* thrown.
*
* @param targetPath the target path to create the directory path
* @param semanticPathName the semantic name of the path used for logging
* @throws UncheckedIOException if the directory path cannot be created
*/
private void createDirectoryPath(@NonNull final Path targetPath, @NonNull final String semanticPathName) {
try {
FileUtilities.createFolderPathIfNotExists(path, ERROR, BLOCK_NODE_ROOT_DIRECTORY_SEMANTIC_NAME);
Files.createDirectories(targetPath);
} catch (final IOException e) {
final String classname = this.getClass().getName();
final String message =
"Unable to instantiate [%s]! Unable to create the root directory for the block storage [%s]"
.formatted(this.getClass().getName(), path);
"Unable to instantiate [%s]! Unable to create the [%s] path for the block storage at [%s]"
.formatted(classname, semanticPathName, targetPath);
throw new UncheckedIOException(message, e);
}

rootPath = path.toString();
LOGGER.log(INFO, "Persistence Storage Configuration: persistence.storage.rootPath=" + path);
LOGGER.log(INFO, "Persistence Storage Configuration: persistence.storage.type=" + type);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ class BlockAsDirReader implements BlockReader<BlockUnparsed> {
final FileAttribute<Set<PosixFilePermission>> folderPermissions) {
LOGGER.log(INFO, "Initializing FileSystemBlockReader");

final Path blockNodeRootPath = Path.of(config.rootPath());
final Path blockNodeRootPath = Path.of(config.liveRootPath());

LOGGER.log(INFO, config.toString());
LOGGER.log(INFO, "Block Node Root Path: " + blockNodeRootPath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package com.hedera.block.server.persistence.storage.write;

import static com.hedera.block.server.Constants.BLOCK_FILE_EXTENSION;
import static com.hedera.block.server.Constants.BLOCK_NODE_ROOT_DIRECTORY_SEMANTIC_NAME;
import static com.hedera.block.server.Constants.BLOCK_NODE_LIVE_ROOT_DIRECTORY_SEMANTIC_NAME;
import static com.hedera.block.server.metrics.BlockNodeMetricTypes.Counter.BlocksPersisted;
import static java.lang.System.Logger;
import static java.lang.System.Logger.Level.DEBUG;
Expand Down Expand Up @@ -92,7 +92,7 @@ class BlockAsDirWriter implements LocalBlockWriter<List<BlockItemUnparsed>> {

final PersistenceStorageConfig config =
blockNodeContext.configuration().getConfigData(PersistenceStorageConfig.class);
this.blockNodeRootPath = Path.of(config.rootPath());
this.blockNodeRootPath = Path.of(config.liveRootPath());
LOGGER.log(INFO, "Block Node Root Path: " + blockNodeRootPath);

if (Objects.nonNull(folderPermissions)) {
Expand All @@ -111,7 +111,7 @@ class BlockAsDirWriter implements LocalBlockWriter<List<BlockItemUnparsed>> {

// Initialize the block node root directory if it does not exist
FileUtilities.createFolderPathIfNotExists(
blockNodeRootPath, INFO, this.folderPermissions, BLOCK_NODE_ROOT_DIRECTORY_SEMANTIC_NAME);
blockNodeRootPath, INFO, this.folderPermissions, BLOCK_NODE_LIVE_ROOT_DIRECTORY_SEMANTIC_NAME);
}

/**
Expand Down Expand Up @@ -197,7 +197,7 @@ private void resetState(@NonNull final BlockHeader blockHeader) throws IOExcepti
blockPathResolver.resolvePathToBlock(currentBlockNumber),
DEBUG,
folderPermissions,
BLOCK_NODE_ROOT_DIRECTORY_SEMANTIC_NAME);
BLOCK_NODE_LIVE_ROOT_DIRECTORY_SEMANTIC_NAME);

// Reset
blockNodeFileNameIndex = 0;
Expand Down
3 changes: 2 additions & 1 deletion server/src/main/resources/app.properties
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ prometheus.endpointPortNumber=9999
#consumer.timeoutThresholdMillis=1500

#Persistence Storage Config
#persistence.storage.rootPath=
#persistence.storage.liveRootPath=
#persistence.storage.archiveRootPath=
#persistence.storage.type=BLOCK_AS_LOCAL_DIRECTORY
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,8 @@
class ServerMappedConfigSourceInitializerTest {
private static final ConfigMapping[] SUPPORTED_MAPPINGS = {
new ConfigMapping("consumer.timeoutThresholdMillis", "CONSUMER_TIMEOUT_THRESHOLD_MILLIS"),
new ConfigMapping("persistence.storage.rootPath", "PERSISTENCE_STORAGE_ROOT_PATH"),
new ConfigMapping("persistence.storage.liveRootPath", "PERSISTENCE_STORAGE_LIVE_ROOT_PATH"),
new ConfigMapping("persistence.storage.archiveRootPath", "PERSISTENCE_STORAGE_ARCHIVE_ROOT_PATH"),
new ConfigMapping("persistence.storage.type", "PERSISTENCE_STORAGE_TYPE"),
new ConfigMapping("service.delayMillis", "SERVICE_DELAY_MILLIS"),
new ConfigMapping("mediator.ringBufferSize", "MEDIATOR_RING_BUFFER_SIZE"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ class BlockAccessServiceTest {
public void setUp() throws IOException {

blockNodeContext =
TestConfigUtil.getTestBlockNodeContext(Map.of("persistence.storage.rootPath", testPath.toString()));
TestConfigUtil.getTestBlockNodeContext(Map.of("persistence.storage.liveRootPath", testPath.toString()));
config = blockNodeContext.configuration().getConfigData(PersistenceStorageConfig.class);

blockAccessService = new PbjBlockAccessServiceProxy(serviceStatus, blockReader, blockNodeContext);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ public class PbjBlockStreamServiceIntegrationTest {
@BeforeEach
public void setUp() throws IOException {
Map<String, String> properties = new HashMap<>();
properties.put("persistence.storage.rootPath", testPath.toString());
properties.put("persistence.storage.liveRootPath", testPath.toString());
blockNodeContext = TestConfigUtil.getTestBlockNodeContext(properties);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ void testProvidesBlockWriter_IOException() {
final BlockNodeContext blockNodeContext = mock(BlockNodeContext.class);

final PersistenceStorageConfig persistenceStorageConfig = mock(PersistenceStorageConfig.class);
when(persistenceStorageConfig.rootPath()).thenReturn("/invalid_path/:invalid_directory");
when(persistenceStorageConfig.liveRootPath()).thenReturn("/invalid_path/:invalid_directory");
when(persistenceStorageConfig.type()).thenReturn(StorageType.BLOCK_AS_LOCAL_DIRECTORY);

final Configuration configuration = mock(Configuration.class);
Expand Down
Loading

0 comments on commit d869521

Please sign in to comment.