Skip to content

Commit

Permalink
BlockReader and BlockWriter impls will now attempt to repair director…
Browse files Browse the repository at this point in the history
…y and file permissions

Signed-off-by: Matt Peterson <[email protected]>
  • Loading branch information
mattp-swirldslabs committed Jul 26, 2024
1 parent b1ea596 commit 29dac0f
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 48 deletions.
1 change: 1 addition & 0 deletions server/docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ EXPOSE 8080
# Define version
ARG VERSION

# Create a non-root user and group
ARG UNAME=hedera
ARG UID=2000
ARG GID=2000
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,16 +26,24 @@
import java.io.FileInputStream;
import java.io.FileNotFoundException;
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.Optional;
import java.util.Set;

public class BlockAsDirReader implements BlockReader<Block> {

private final System.Logger LOGGER = System.getLogger(getClass().getName());

final Path blockNodeRootPath;
private final FileAttribute<Set<PosixFilePermission>> filePerms;

public BlockAsDirReader(final String key, final Config config) {
public BlockAsDirReader(
final String key,
final Config config,
final FileAttribute<Set<PosixFilePermission>> filePerms) {

LOGGER.log(System.Logger.Level.INFO, "Initializing FileSystemBlockReader");

Expand All @@ -45,15 +53,21 @@ public BlockAsDirReader(final String key, final Config config) {
LOGGER.log(System.Logger.Level.INFO, "Block Node Root Path: " + blockNodeRootPath);

this.blockNodeRootPath = blockNodeRootPath;
this.filePerms = filePerms;
}

public BlockAsDirReader(final String key, final Config config) {
this(key, config, Util.defaultPerms);
}

@Override
public Optional<Block> read(final long blockNumber) throws IOException {

// Construct the path to the requested Block
final Path blockPath = blockNodeRootPath.resolve(String.valueOf(blockNumber));

// Verify attributes of the Block
if (!isVerified(blockPath)) {
if (!isVerified(blockNodeRootPath, blockPath)) {
return Optional.empty();
}

Expand Down Expand Up @@ -100,17 +114,64 @@ private Optional<BlockItem> readBlockItem(final String blockItemPath) throws IOE
}
}

private boolean isVerified(final Path blockPath) {
if (!blockPath.toFile().isDirectory()) {
LOGGER.log(System.Logger.Level.ERROR, "Block directory not found: " + blockPath);
return false;
private boolean isVerified(final Path blockNodeRootPath, final Path blockPath) {

if (!blockNodeRootPath.toFile().canRead()) {
LOGGER.log(
System.Logger.Level.ERROR,
"Block node root path directory not readable: {0}",
blockNodeRootPath);
LOGGER.log(
System.Logger.Level.ERROR,
"Attempting to repair the permissions: " + blockNodeRootPath);
if (!setPerms(blockNodeRootPath)) {
return false;
}

// retest
if (!blockNodeRootPath.toFile().canRead()) {
LOGGER.log(
System.Logger.Level.ERROR,
"Block node root path directory still not readable: {0}",
blockNodeRootPath);
return false;
}
}

if (!blockPath.toFile().canRead()) {
LOGGER.log(System.Logger.Level.ERROR, "Block directory not readable: " + blockPath);
LOGGER.log(
System.Logger.Level.ERROR,
"Attempting to repair block directory permissions: " + blockPath);
if (!setPerms(blockPath)) {
return false;
}

// retest
if (!blockPath.toFile().canRead()) {
LOGGER.log(
System.Logger.Level.ERROR,
"Block directory still not readable: " + blockPath);
return false;
}
}

if (!blockPath.toFile().isDirectory()) {
LOGGER.log(System.Logger.Level.ERROR, "Block directory not found: " + blockPath);
return false;
}

return true;
}

private boolean setPerms(final Path path) {
try {
Files.setPosixFilePermissions(path, filePerms.value());
return true;
} catch (IOException e) {
LOGGER.log(System.Logger.Level.ERROR, "Error setting permissions on: " + path, e);
}

return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@
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.nio.file.attribute.PosixFilePermissions;
import java.util.Set;

public class BlockAsDirWriter implements BlockWriter<BlockItem> {

Expand All @@ -33,8 +37,13 @@ public class BlockAsDirWriter implements BlockWriter<BlockItem> {
private final Path blockNodeRootPath;
private long blockNodeFileNameIndex = 0;
private Path currentBlockDir;
private final FileAttribute<Set<PosixFilePermission>> filePerms;

public BlockAsDirWriter(final String key, final Config config) throws IOException {
public BlockAsDirWriter(
final String key,
final Config config,
final FileAttribute<Set<PosixFilePermission>> filePerms)
throws IOException {

LOGGER.log(System.Logger.Level.INFO, "Initializing FileSystemBlockStorage");

Expand All @@ -52,8 +61,14 @@ public BlockAsDirWriter(final String key, final Config config) throws IOExceptio
createPath(blockNodeRootPath, System.Logger.Level.INFO);

this.blockNodeRootPath = blockNodeRootPath;
this.filePerms = filePerms;
}

public BlockAsDirWriter(final String key, final Config config) throws IOException {
this(key, config, Util.defaultPerms);
}

@Override
public void write(final BlockItem blockItem) throws IOException {

if (blockItem.hasHeader()) {
Expand All @@ -74,11 +89,54 @@ public void write(final BlockItem blockItem) throws IOException {
}
}

private void removeBlock() throws IOException {
// Remove the block directory
final Path blockPath = blockNodeRootPath.resolve(currentBlockDir);

final Set<PosixFilePermission> writePerm =
PosixFilePermissions.asFileAttribute(Set.of(PosixFilePermission.OWNER_WRITE))
.value();
Files.setPosixFilePermissions(blockPath, writePerm);

Files.delete(blockPath);

LOGGER.log(System.Logger.Level.INFO, "Deleted block node root directory: " + blockPath);
}

private void resetState(final BlockItem blockItem) throws IOException {
// Here a "block" is represented as a directory of BlockItems.
// Create the "block" directory based on the block_number
currentBlockDir = Path.of(String.valueOf(blockItem.getHeader().getBlockNumber()));

final boolean isReadable = Files.isReadable(blockNodeRootPath);
final boolean isWritable = Files.isWritable(blockNodeRootPath);
if (!isWritable || !isReadable) {
if (!isWritable) {
LOGGER.log(
System.Logger.Level.ERROR,
"Block node root directory is not writable. Attempting to change the"
+ " permissions.");
}

if (!isReadable) {
LOGGER.log(
System.Logger.Level.ERROR,
"Block node root directory is not readable. Attempting to change the"
+ " permissions.");
}

try {
// Attempt to restore the permissions on the block node root directory
Files.setPosixFilePermissions(blockNodeRootPath, filePerms.value());
} catch (IOException e) {
LOGGER.log(
System.Logger.Level.ERROR,
"Error setting permissions on the block node root directory",
e);
throw e;
}
}

// Construct the path to the block directory
createPath(blockNodeRootPath.resolve(currentBlockDir), System.Logger.Level.DEBUG);

Expand All @@ -96,7 +154,7 @@ private String calculateBlockItemPath() {
private void createPath(Path blockNodePath, System.Logger.Level logLevel) throws IOException {
// Initialize the Block directory if it does not exist
if (Files.notExists(blockNodePath)) {
Files.createDirectory(blockNodePath);
Files.createDirectory(blockNodePath, filePerms);
LOGGER.log(logLevel, "Created block node root directory: " + blockNodePath);
} else {
LOGGER.log(logLevel, "Using existing block node root directory: " + blockNodePath);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* 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.persistence.storage;

import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.PosixFilePermission;
import java.nio.file.attribute.PosixFilePermissions;
import java.util.Set;

public final class Util {
private Util() {}

// Default permissions: rwxr-xr-x
public static final FileAttribute<Set<PosixFilePermission>> defaultPerms =
PosixFilePermissions.asFileAttribute(
Set.of(
PosixFilePermission.OWNER_READ,
PosixFilePermission.OWNER_WRITE,
PosixFilePermission.OWNER_EXECUTE,
PosixFilePermission.GROUP_READ,
PosixFilePermission.GROUP_EXECUTE,
PosixFilePermission.OTHERS_READ,
PosixFilePermission.OTHERS_EXECUTE));
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@
package com.hedera.block.server;

import static com.hedera.block.protos.BlockStreamService.*;
import static com.hedera.block.protos.BlockStreamService.PublishStreamResponse.EndOfStream;
import static com.hedera.block.protos.BlockStreamService.PublishStreamResponse.ItemAcknowledgement;
import static com.hedera.block.protos.BlockStreamService.PublishStreamResponse.PublishStreamResponseCode;
import static com.hedera.block.protos.BlockStreamService.PublishStreamResponse.*;
import static com.hedera.block.server.util.PersistTestUtils.generateBlockItems;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.mockito.Mockito.*;
Expand All @@ -29,10 +27,7 @@
import com.hedera.block.server.mediator.LiveStreamMediatorImpl;
import com.hedera.block.server.mediator.StreamMediator;
import com.hedera.block.server.persistence.FileSystemPersistenceHandler;
import com.hedera.block.server.persistence.storage.BlockAsDirReader;
import com.hedera.block.server.persistence.storage.BlockAsDirWriter;
import com.hedera.block.server.persistence.storage.BlockReader;
import com.hedera.block.server.persistence.storage.BlockWriter;
import com.hedera.block.server.persistence.storage.*;
import com.hedera.block.server.producer.ItemAckBuilder;
import com.hedera.block.server.util.TestUtils;
import com.lmax.disruptor.BatchEventProcessor;
Expand All @@ -44,8 +39,8 @@
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.nio.file.attribute.PosixFilePermissions;
import java.security.NoSuchAlgorithmException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
Expand Down Expand Up @@ -303,7 +298,7 @@ public void testSubAndUnsubWhileStreaming() throws IOException, InterruptedExcep
BlockItemEventHandler<ObjectEvent<SubscribeStreamResponse>>,
BatchEventProcessor<ObjectEvent<SubscribeStreamResponse>>>
subscribers = new LinkedHashMap<>();
final var streamMediator = buildStreamMediator(subscribers);
final var streamMediator = buildStreamMediator(subscribers, Util.defaultPerms);
final var blockStreamService = buildBlockStreamService(streamMediator);

// Pass a StreamObserver to the producer as Helidon does
Expand Down Expand Up @@ -389,7 +384,10 @@ public void testMediatorExceptionHandlingWhenPersistenceFailure()
BlockItemEventHandler<ObjectEvent<SubscribeStreamResponse>>,
BatchEventProcessor<ObjectEvent<SubscribeStreamResponse>>>
subscribers = new ConcurrentHashMap<>();
final var streamMediator = buildStreamMediator(subscribers);

// Initialize the underlying BlockReader and BlockWriter with ineffective
// permissions to repair the file system.
final var streamMediator = buildStreamMediator(subscribers, TestUtils.getNoPerms());
final var blockStreamService = buildBlockStreamService(streamMediator);

// Register the web server to confirm
Expand Down Expand Up @@ -452,12 +450,9 @@ public void testMediatorExceptionHandlingWhenPersistenceFailure()
verify(webServer, times(1)).stop();
}

private static final String NO_WRITE = "r-xr-xr-x";

private void removeRootPathWritePerms(final Config config) throws IOException {
final Path blockNodeRootPath = Path.of(config.get(JUNIT).asString().get());
final Set<PosixFilePermission> perms = PosixFilePermissions.fromString(NO_WRITE);
Files.setPosixFilePermissions(blockNodeRootPath, perms);
Files.setPosixFilePermissions(blockNodeRootPath, TestUtils.getNoWrite().value());
}

private static void verifySubscribeStreamResponse(
Expand Down Expand Up @@ -499,19 +494,21 @@ private static SubscribeStreamResponse buildSubscribeStreamResponse(BlockItem bl

private StreamMediator<BlockItem, ObjectEvent<SubscribeStreamResponse>> buildStreamMediator()
throws IOException {
return buildStreamMediator(new ConcurrentHashMap<>());
return buildStreamMediator(new ConcurrentHashMap<>(), Util.defaultPerms);
}

private StreamMediator<BlockItem, ObjectEvent<SubscribeStreamResponse>> buildStreamMediator(
final Map<
BlockItemEventHandler<ObjectEvent<SubscribeStreamResponse>>,
BatchEventProcessor<ObjectEvent<SubscribeStreamResponse>>>
subscribers)
subscribers,
FileAttribute<Set<PosixFilePermission>> filePerms)
throws IOException {

// Initialize with concrete a concrete BlockReader, BlockWriter and Mediator
final BlockReader<Block> blockReader = new BlockAsDirReader(JUNIT, testConfig);
final BlockWriter<BlockItem> blockWriter = new BlockAsDirWriter(JUNIT, testConfig);
final BlockReader<Block> blockReader = new BlockAsDirReader(JUNIT, testConfig, filePerms);
final BlockWriter<BlockItem> blockWriter =
new BlockAsDirWriter(JUNIT, testConfig, filePerms);
return new LiveStreamMediatorImpl(
subscribers, new FileSystemPersistenceHandler(blockReader, blockWriter));
}
Expand Down
Loading

0 comments on commit 29dac0f

Please sign in to comment.