From aa264b3c0c7a05c93ba25612447aac085b78be61 Mon Sep 17 00:00:00 2001 From: Abdelhamid Bakhta <45264458+abdelhamidbakhta@users.noreply.github.com> Date: Tue, 24 Sep 2019 18:38:36 +0200 Subject: [PATCH] Store db metadata file in the root data directory. (#46) * Update version to 1.2.5-SNAPSHOT (#42) Signed-off-by: Edward Evans Signed-off-by: Abdelhamid Bakhta * 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 * add logs Signed-off-by: Abdelhamid Bakhta * create database directory if database not detected Signed-off-by: Abdelhamid Bakhta * change plugin API know hash Signed-off-by: Abdelhamid Bakhta --- .../dsl/node/ThreadBesuNodeRunner.java | 4 +- .../org/hyperledger/besu/cli/BesuCommand.java | 4 +- .../besu/services/BesuConfigurationImpl.java | 11 +++ build.gradle | 4 +- plugin-api/build.gradle | 2 +- .../plugin/services/BesuConfiguration.java | 7 ++ .../RocksDBKeyValueStorageFactory.java | 11 +-- .../configuration/DatabaseMetadata.java | 35 ++++++-- .../plugin/services/helper/Conditions.java | 41 +++++++++ .../RocksDBKeyValueStorageFactoryTest.java | 40 +++++++-- .../configuration/DatabaseMetadataTest.java | 87 +++++++++++++++++++ 11 files changed, 221 insertions(+), 25 deletions(-) create mode 100644 plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/helper/Conditions.java create mode 100644 plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/DatabaseMetadataTest.java diff --git a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java index 3655c1f0b24..18ba82c02dd 100644 --- a/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java +++ b/acceptance-tests/dsl/src/main/java/org/hyperledger/besu/tests/acceptance/dsl/node/ThreadBesuNodeRunner.java @@ -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)); diff --git a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java index f0a88d7c731..cf228fa9253 100644 --- a/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java +++ b/besu/src/main/java/org/hyperledger/besu/cli/BesuCommand.java @@ -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); } } diff --git a/besu/src/main/java/org/hyperledger/besu/services/BesuConfigurationImpl.java b/besu/src/main/java/org/hyperledger/besu/services/BesuConfigurationImpl.java index aa59167034f..d64ff96d951 100644 --- a/besu/src/main/java/org/hyperledger/besu/services/BesuConfigurationImpl.java +++ b/besu/src/main/java/org/hyperledger/besu/services/BesuConfigurationImpl.java @@ -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; + } } diff --git a/build.gradle b/build.gradle index c9893b6ab4a..56478c9d47d 100644 --- a/build.gradle +++ b/build.gradle @@ -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', { diff --git a/plugin-api/build.gradle b/plugin-api/build.gradle index bc2c4602273..ea380a93f9e 100644 --- a/plugin-api/build.gradle +++ b/plugin-api/build.gradle @@ -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') diff --git a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuConfiguration.java b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuConfiguration.java index 29599dd83dd..646841c514c 100644 --- a/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuConfiguration.java +++ b/plugin-api/src/main/java/org/hyperledger/besu/plugin/services/BesuConfiguration.java @@ -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(); } diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDBKeyValueStorageFactory.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDBKeyValueStorageFactory.java index cb465b19f94..4453a0d125a 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDBKeyValueStorageFactory.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDBKeyValueStorageFactory.java @@ -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)) { diff --git a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/DatabaseMetadata.java b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/DatabaseMetadata.java index 43777475757..4eab6e42be3 100644 --- a/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/DatabaseMetadata.java +++ b/plugins/rocksdb/src/main/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/DatabaseMetadata.java @@ -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; @@ -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) { diff --git a/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/helper/Conditions.java b/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/helper/Conditions.java new file mode 100644 index 00000000000..085e9313536 --- /dev/null +++ b/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/helper/Conditions.java @@ -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 FILE_EXISTS = + new Condition<>(path -> path != null && path.toFile().exists(), "File must exist."); + public static final Condition FILE_DOES_NOT_EXIST = + new Condition<>(path -> path == null || !path.toFile().exists(), "File must not exist."); + + public static Condition 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."); + } +} diff --git a/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDBKeyValueStorageFactoryTest.java b/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDBKeyValueStorageFactoryTest.java index 8e0581e019c..20780ce79ae 100644 --- a/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDBKeyValueStorageFactoryTest.java +++ b/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/RocksDBKeyValueStorageFactoryTest.java @@ -54,8 +54,10 @@ 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); @@ -63,47 +65,59 @@ public void shouldCreateCorrectMetadataFileForLatestVersion() throws Exception { // 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); @@ -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) @@ -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); @@ -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())); diff --git a/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/DatabaseMetadataTest.java b/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/DatabaseMetadataTest.java new file mode 100644 index 00000000000..1e90c890312 --- /dev/null +++ b/plugins/rocksdb/src/test/java/org/hyperledger/besu/plugin/services/storage/rocksdb/configuration/DatabaseMetadataTest.java @@ -0,0 +1,87 @@ +/* + * 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.storage.rocksdb.configuration; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.hyperledger.besu.plugin.services.helper.Conditions.FILE_DOES_NOT_EXIST; +import static org.hyperledger.besu.plugin.services.helper.Conditions.FILE_EXISTS; +import static org.hyperledger.besu.plugin.services.helper.Conditions.shouldContain; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +public class DatabaseMetadataTest { + @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); + + @Test + public void getVersion() { + final DatabaseMetadata databaseMetadata = new DatabaseMetadata(42); + assertThat(databaseMetadata).isNotNull(); + assertThat(databaseMetadata.getVersion()).isEqualTo(42); + } + + @Test + public void metaFileShouldBeSoughtIntoDataDirFirst() throws Exception { + final Path tempDataDir = createAndWrite("data", "DATABASE_METADATA.json", "{\"version\":42}"); + final Path tempDatabaseDir = createAndWrite("db", "DATABASE_METADATA.json", "{\"version\":99}"); + final DatabaseMetadata databaseMetadata = + DatabaseMetadata.lookUpFrom(tempDatabaseDir, tempDataDir); + assertThat(databaseMetadata).isNotNull(); + assertThat(databaseMetadata.getVersion()).isEqualTo(42); + } + + @Test + public void metaFileNotFoundInDataDirShouldLookupIntoDbDir() throws Exception { + final Path tempDataDir = temporaryFolder.newFolder().toPath().resolve("data"); + Files.createDirectories(tempDataDir); + final Path tempDatabaseDir = createAndWrite("db", "DATABASE_METADATA.json", "{\"version\":42}"); + final Path metadataPathInDataDir = tempDataDir.resolve("DATABASE_METADATA.json"); + assertThat(metadataPathInDataDir).is(FILE_DOES_NOT_EXIST); + final DatabaseMetadata databaseMetadata = + DatabaseMetadata.lookUpFrom(tempDatabaseDir, tempDataDir); + assertThat(databaseMetadata).isNotNull(); + assertThat(databaseMetadata.getVersion()).isEqualTo(42); + assertThat(metadataPathInDataDir).is(FILE_EXISTS); + assertThat(metadataPathInDataDir).is(shouldContain("{\"version\":42}")); + } + + private Path createAndWrite(final String dir, final String file, final String content) + throws IOException { + return createAndWrite(temporaryFolder, dir, file, content); + } + + private Path createAndWrite( + final TemporaryFolder temporaryFolder, + final String dir, + final String file, + final String content) + throws IOException { + final Path tmpDir = temporaryFolder.newFolder().toPath().resolve(dir); + Files.createDirectories(tmpDir); + createAndWrite(tmpDir.resolve(file), content); + return tmpDir; + } + + private void createAndWrite(final Path path, final String content) throws IOException { + path.toFile().createNewFile(); + Files.writeString(path, content); + } +}