Skip to content

Commit

Permalink
Store db metadata file in the root data directory. (#46)
Browse files Browse the repository at this point in the history
* Update version to 1.2.5-SNAPSHOT (#42)

Signed-off-by: Edward Evans <edward.joshua.evans@gmail.com>
Signed-off-by: Abdelhamid Bakhta <abdelhamid.bakhta@consensys.net>

* Store db metadata file in the root data directory
The database metadata file should be stored in the root data directory rather than the database subdirectory.
The database subdirectory is owned by the database itself and should not be directly manipulated by the node.
- first look in the data directory for the metadata file
- if the metadata file is found there, process it as normal
- if no metadata file is found in the root directory, look in the database subdirectory
- if the file is found here, copy it to the root directory, and run based on the root directory version

Signed-off-by: Abdelhamid Bakhta <abdelhamid.bakhta@consensys.net>

* add logs

Signed-off-by: Abdelhamid Bakhta <abdelhamid.bakhta@consensys.net>

* create database directory if database not detected

Signed-off-by: Abdelhamid Bakhta <abdelhamid.bakhta@consensys.net>

* change plugin API know hash

Signed-off-by: Abdelhamid Bakhta <abdelhamid.bakhta@consensys.net>
  • Loading branch information
AbdelStark authored Sep 24, 2019
1 parent a8ddd21 commit aa264b3
Show file tree
Hide file tree
Showing 11 changed files with 221 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,8 @@ public void startNode(final BesuNode node) {
}

final StorageServiceImpl storageService = new StorageServiceImpl();
final BesuConfiguration commonPluginConfiguration =
new BesuConfigurationImpl(Files.createTempDir().toPath());
final Path path = Files.createTempDir().toPath();
final BesuConfiguration commonPluginConfiguration = new BesuConfigurationImpl(path, path);
final BesuPluginContextImpl besuPluginContext =
besuPluginContextMap.computeIfAbsent(
node, n -> buildPluginContext(node, storageService, commonPluginConfiguration));
Expand Down
4 changes: 3 additions & 1 deletion besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -771,7 +771,9 @@ public void run() {

private void addConfigurationService() {
if (pluginCommonConfiguration == null) {
pluginCommonConfiguration = new BesuConfigurationImpl(dataDir().resolve(DATABASE_PATH));
final Path dataDir = dataDir();
pluginCommonConfiguration =
new BesuConfigurationImpl(dataDir.resolve(DATABASE_PATH), dataDir);
besuPluginContext.addService(BesuConfiguration.class, pluginCommonConfiguration);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,24 @@
public class BesuConfigurationImpl implements BesuConfiguration {

private final Path storagePath;
private final Path dataPath;

public BesuConfigurationImpl(final Path storagePath) {
this(storagePath, storagePath);
}

public BesuConfigurationImpl(final Path storagePath, final Path dataPath) {
this.storagePath = storagePath;
this.dataPath = dataPath;
}

@Override
public Path getStoragePath() {
return storagePath;
}

@Override
public Path getDataPath() {
return dataPath;
}
}
4 changes: 1 addition & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -164,9 +164,7 @@ allprojects {
}

// Below this line are currently only license header tasks
format 'groovy', {
target '**/src/*/grovy/**/*.groovy'
}
format 'groovy', { target '**/src/*/grovy/**/*.groovy' }

// Currently disabled due to referencetest issues
// format 'bash', {
Expand Down
2 changes: 1 addition & 1 deletion plugin-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ Calculated : ${currentHash}
tasks.register('checkAPIChanges', FileStateChecker) {
description = "Checks that the API for the Plugin-API project does not change without deliberate thought"
files = sourceSets.main.allJava.files
knownHash = '7GpOlkPAtanchkveLlCKOTmafqC8cAa19/s/eLLGFyE='
knownHash = 'f0SPUj4/aLZKyjAGcDYai021dO8pg5xLaNvALEWxoIg='
}
check.dependsOn('checkAPIChanges')

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,11 @@ public interface BesuConfiguration {
* @return location of the storage in the file system of the client.
*/
Path getStoragePath();

/**
* Location of the data directory in the file system running the client.
*
* @return location of the data directory in the file system of the client.
*/
Path getDataPath();
}
Original file line number Diff line number Diff line change
Expand Up @@ -147,17 +147,18 @@ private boolean requiresInit() {

private int readDatabaseVersion(final BesuConfiguration commonConfiguration) throws IOException {
final Path databaseDir = commonConfiguration.getStoragePath();
final Path dataDir = commonConfiguration.getDataPath();
final boolean databaseExists = databaseDir.resolve("IDENTITY").toFile().exists();
final int databaseVersion;
if (databaseExists) {
databaseVersion = DatabaseMetadata.fromDirectory(databaseDir).getVersion();
LOG.debug("Existing database detected at {}. Version {}", databaseDir, databaseVersion);
databaseVersion = DatabaseMetadata.lookUpFrom(databaseDir, dataDir).getVersion();
LOG.info("Existing database detected at {}. Version {}", dataDir, databaseVersion);
} else {
databaseVersion = DEFAULT_VERSION;
LOG.info(
"No existing database detected at {}. Using version {}", databaseDir, databaseVersion);
LOG.info("No existing database detected at {}. Using version {}", dataDir, databaseVersion);
Files.createDirectories(databaseDir);
new DatabaseMetadata(databaseVersion).writeToDirectory(databaseDir);
Files.createDirectories(dataDir);
new DatabaseMetadata(databaseVersion).writeToDirectory(dataDir);
}

if (!SUPPORTED_VERSIONS.contains(databaseVersion)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,12 @@
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

public class DatabaseMetadata {
private static final Logger LOG = LogManager.getLogger();

private static final String METADATA_FILENAME = "DATABASE_METADATA.json";
private static ObjectMapper MAPPER = new ObjectMapper();
private final int version;
Expand All @@ -38,20 +42,41 @@ public int getVersion() {
return version;
}

public static DatabaseMetadata fromDirectory(final Path databaseDir) throws IOException {
final File metadataFile = getDefaultMetadataFile(databaseDir);
public static DatabaseMetadata lookUpFrom(final Path databaseDir, final Path dataDir)
throws IOException {
LOG.info("Lookup database metadata file in data directory: {}", dataDir.toString());
File metadataFile = getDefaultMetadataFile(dataDir);
final boolean shouldLookupInDatabaseDir = !metadataFile.exists();
if (shouldLookupInDatabaseDir) {
LOG.info(
"Database metadata file not found in data directory. Lookup in database directory: {}",
databaseDir.toString());
metadataFile = getDefaultMetadataFile(databaseDir);
}
DatabaseMetadata databaseMetadata;
try {
return MAPPER.readValue(metadataFile, DatabaseMetadata.class);
databaseMetadata = MAPPER.readValue(metadataFile, DatabaseMetadata.class);
} catch (FileNotFoundException fnfe) {
return new DatabaseMetadata(0);
databaseMetadata = new DatabaseMetadata(0);
} catch (JsonProcessingException jpe) {
throw new IllegalStateException(
String.format("Invalid metadata file %s", metadataFile.getAbsolutePath()), jpe);
}
if (shouldLookupInDatabaseDir) {
LOG.warn(
"Database metadata file has been copied from old location (database directory). Be aware that the old file might be removed in future release.");
writeToDirectory(databaseMetadata, dataDir);
}
return databaseMetadata;
}

public void writeToDirectory(final Path databaseDir) throws IOException {
MAPPER.writeValue(getDefaultMetadataFile(databaseDir), this);
writeToDirectory(this, databaseDir);
}

private static void writeToDirectory(
final DatabaseMetadata databaseMetadata, final Path databaseDir) throws IOException {
MAPPER.writeValue(getDefaultMetadataFile(databaseDir), databaseMetadata);
}

private static File getDefaultMetadataFile(final Path databaseDir) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/*
* Copyright ConsenSys AG.
*
* 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.
*
* SPDX-License-Identifier: Apache-2.0
*/

package org.hyperledger.besu.plugin.services.helper;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;

import org.assertj.core.api.Condition;

public class Conditions {
public static final Condition<Path> FILE_EXISTS =
new Condition<>(path -> path != null && path.toFile().exists(), "File must exist.");
public static final Condition<Path> FILE_DOES_NOT_EXIST =
new Condition<>(path -> path == null || !path.toFile().exists(), "File must not exist.");

public static Condition<Path> shouldContain(final String content) {
return new Condition<>(
path -> {
try {
return content.equals(Files.readString(path));
} catch (IOException e) {
return false;
}
},
"File should contain specified content.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,56 +54,70 @@ public class RocksDBKeyValueStorageFactoryTest {

@Test
public void shouldCreateCorrectMetadataFileForLatestVersion() throws Exception {
final Path tempDataDir = temporaryFolder.newFolder().toPath().resolve("data");
final Path tempDatabaseDir = temporaryFolder.newFolder().toPath().resolve("db");
when(commonConfiguration.getStoragePath()).thenReturn(tempDatabaseDir);
when(commonConfiguration.getDataPath()).thenReturn(tempDataDir);

final RocksDBKeyValueStorageFactory storageFactory =
new RocksDBKeyValueStorageFactory(() -> rocksDbConfiguration, segments);

// Side effect is creation of the Metadata version file
storageFactory.create(segment, commonConfiguration, metricsSystem);

assertThat(DatabaseMetadata.fromDirectory(commonConfiguration.getStoragePath()).getVersion())
assertThat(
DatabaseMetadata.lookUpFrom(
commonConfiguration.getStoragePath(), commonConfiguration.getDataPath())
.getVersion())
.isEqualTo(DEFAULT_VERSION);
}

@Test
public void shouldDetectVersion0DatabaseIfNoMetadataFileFound() throws Exception {
final Path tempDataDir = temporaryFolder.newFolder().toPath().resolve("data");
final Path tempDatabaseDir = temporaryFolder.newFolder().toPath().resolve("db");
Files.createDirectories(tempDatabaseDir);
Files.createDirectories(tempDataDir);
tempDatabaseDir.resolve("IDENTITY").toFile().createNewFile();
when(commonConfiguration.getStoragePath()).thenReturn(tempDatabaseDir);
when(commonConfiguration.getDataPath()).thenReturn(tempDataDir);

final RocksDBKeyValueStorageFactory storageFactory =
new RocksDBKeyValueStorageFactory(() -> rocksDbConfiguration, segments);

storageFactory.create(segment, commonConfiguration, metricsSystem);

assertThat(DatabaseMetadata.fromDirectory(tempDatabaseDir).getVersion()).isZero();
assertThat(DatabaseMetadata.lookUpFrom(tempDatabaseDir, tempDataDir).getVersion()).isZero();
}

@Test
public void shouldDetectCorrectVersionIfMetadataFileExists() throws Exception {
final Path tempDataDir = temporaryFolder.newFolder().toPath().resolve("data");
final Path tempDatabaseDir = temporaryFolder.newFolder().toPath().resolve("db");
Files.createDirectories(tempDatabaseDir);
tempDatabaseDir.resolve("IDENTITY").toFile().createNewFile();
new DatabaseMetadata(DEFAULT_VERSION).writeToDirectory(tempDatabaseDir);
Files.createDirectories(tempDataDir);
when(commonConfiguration.getStoragePath()).thenReturn(tempDatabaseDir);
when(commonConfiguration.getDataPath()).thenReturn(tempDataDir);

final RocksDBKeyValueStorageFactory storageFactory =
new RocksDBKeyValueStorageFactory(() -> rocksDbConfiguration, segments);

storageFactory.create(segment, commonConfiguration, metricsSystem);

assertThat(DatabaseMetadata.fromDirectory(tempDatabaseDir).getVersion())
assertThat(DatabaseMetadata.lookUpFrom(tempDatabaseDir, tempDataDir).getVersion())
.isEqualTo(DEFAULT_VERSION);
assertThat(storageFactory.isSegmentIsolationSupported()).isTrue();
}

@Test
public void shouldDetectCorrectVersionInCaseOfRollback() throws Exception {
final Path tempDataDir = temporaryFolder.newFolder().toPath().resolve("data");
final Path tempDatabaseDir = temporaryFolder.newFolder().toPath().resolve("db");
Files.createDirectories(tempDatabaseDir);
Files.createDirectories(tempDataDir);
when(commonConfiguration.getStoragePath()).thenReturn(tempDatabaseDir);
when(commonConfiguration.getDataPath()).thenReturn(tempDataDir);

final RocksDBKeyValueStorageFactory storageFactory =
new RocksDBKeyValueStorageFactory(() -> rocksDbConfiguration, segments, 1);

Expand All @@ -117,12 +131,14 @@ public void shouldDetectCorrectVersionInCaseOfRollback() throws Exception {

@Test
public void shouldThrowExceptionWhenVersionNumberIsInvalid() throws Exception {
final Path tempDataDir = temporaryFolder.newFolder().toPath().resolve("data");
final Path tempDatabaseDir = temporaryFolder.newFolder().toPath().resolve("db");
Files.createDirectories(tempDatabaseDir);
Files.createDirectories(tempDataDir);
tempDatabaseDir.resolve("IDENTITY").toFile().createNewFile();
new DatabaseMetadata(-1).writeToDirectory(tempDatabaseDir);
when(commonConfiguration.getStoragePath()).thenReturn(tempDatabaseDir);

when(commonConfiguration.getDataPath()).thenReturn(tempDataDir);
new DatabaseMetadata(-1).writeToDirectory(tempDatabaseDir);
assertThatThrownBy(
() ->
new RocksDBKeyValueStorageFactory(() -> rocksDbConfiguration, segments)
Expand All @@ -132,9 +148,13 @@ public void shouldThrowExceptionWhenVersionNumberIsInvalid() throws Exception {

@Test
public void shouldSetSegmentationFieldDuringCreation() throws Exception {
final Path tempDataDir = temporaryFolder.newFolder().toPath().resolve("data");
final Path tempDatabaseDir = temporaryFolder.newFolder().toPath().resolve("db");
Files.createDirectories(tempDatabaseDir);
Files.createDirectories(tempDataDir);
when(commonConfiguration.getStoragePath()).thenReturn(tempDatabaseDir);
when(commonConfiguration.getDataPath()).thenReturn(tempDataDir);

final RocksDBKeyValueStorageFactory storageFactory =
new RocksDBKeyValueStorageFactory(() -> rocksDbConfiguration, segments);
storageFactory.create(segment, commonConfiguration, metricsSystem);
Expand All @@ -143,10 +163,14 @@ public void shouldSetSegmentationFieldDuringCreation() throws Exception {

@Test
public void shouldThrowExceptionWhenMetaDataFileIsCorrupted() throws Exception {
final Path tempDataDir = temporaryFolder.newFolder().toPath().resolve("data");
final Path tempDatabaseDir = temporaryFolder.newFolder().toPath().resolve("db");
Files.createDirectories(tempDatabaseDir);
when(commonConfiguration.getStoragePath()).thenReturn(tempDatabaseDir);
Files.createDirectories(tempDataDir);
tempDatabaseDir.resolve("IDENTITY").toFile().createNewFile();
when(commonConfiguration.getStoragePath()).thenReturn(tempDatabaseDir);
when(commonConfiguration.getDataPath()).thenReturn(tempDataDir);

final String badVersion = "{\"🦄\":1}";
Files.write(
tempDatabaseDir.resolve(METADATA_FILENAME), badVersion.getBytes(Charset.defaultCharset()));
Expand Down
Loading

0 comments on commit aa264b3

Please sign in to comment.