From a7b878da17e6dc736711000995aed4efa0501273 Mon Sep 17 00:00:00 2001 From: 761417898 <761417898@qq.com> Date: Thu, 12 Nov 2026 12:02:33 +0800 Subject: [PATCH 1/2] Fix snapshot recovery when hard links are unsupported --- .../dataregion/snapshot/SnapshotLoader.java | 11 +-- .../snapshot/SnapshotLinkFallbackTest.java | 72 +++++++++++++++++++ pom.xml | 6 ++ 3 files changed, 79 insertions(+), 10 deletions(-) create mode 100644 iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/snapshot/SnapshotLinkFallbackTest.java diff --git a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/snapshot/SnapshotLoader.java b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/snapshot/SnapshotLoader.java index dcf2246036cc..f0fc34cf1e1d 100644 --- a/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/snapshot/SnapshotLoader.java +++ b/iotdb-core/datanode/src/main/java/org/apache/iotdb/db/storageengine/dataregion/snapshot/SnapshotLoader.java @@ -412,16 +412,7 @@ private File createLinksFromSnapshotToSourceDir( targetFile.getParentFile().getAbsolutePath())); } - try { - Files.createLink(targetFile.toPath(), file.toPath()); - LOGGER.debug(StorageEngineMessages.CREATED_HARD_LINK, file, targetFile); - fileTarget.put(fileKey, finalDir); - return targetFile; - } catch (IOException e) { - LOGGER.info(StorageEngineMessages.CANNOT_CREATE_LINK_FALLBACK_COPY, file, targetFile); - } - - Files.copy(file.toPath(), targetFile.toPath()); + SnapshotFileUtils.createLinkOrCopy(targetFile.toPath(), file.toPath()); fileTarget.put(fileKey, finalDir); return targetFile; } catch (Exception e) { diff --git a/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/snapshot/SnapshotLinkFallbackTest.java b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/snapshot/SnapshotLinkFallbackTest.java new file mode 100644 index 000000000000..52db16882f4b --- /dev/null +++ b/iotdb-core/datanode/src/test/java/org/apache/iotdb/db/storageengine/dataregion/snapshot/SnapshotLinkFallbackTest.java @@ -0,0 +1,72 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.iotdb.db.storageengine.dataregion.snapshot; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.junit.runner.RunWith; +import org.powermock.api.mockito.PowerMockito; +import org.powermock.core.classloader.annotations.PowerMockIgnore; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.junit4.PowerMockRunner; +import org.powermock.reflect.Whitebox; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +@RunWith(PowerMockRunner.class) +@PowerMockIgnore({"com.sun.org.apache.xerces.*", "javax.xml.*", "org.xml.*", "javax.management.*"}) +@PrepareForTest({SnapshotFileUtils.class, SnapshotLoader.class}) +public class SnapshotLinkFallbackTest { + @Rule public TemporaryFolder folder = new TemporaryFolder(); + + @Test + public void testRecoveryWithoutLogCopiesWhenHardLinksAreUnsupported() throws Exception { + Path source = folder.newFile("source.tsfile").toPath(); + Files.write(source, new byte[] {1, 2, 3}); + File dataDir = folder.newFolder("data"); + Map targets = new HashMap<>(); + SnapshotLoader loader = new SnapshotLoader(folder.getRoot().toString(), "root.sg", "0"); + PowerMockito.replace(PowerMockito.method(Files.class, "createLink", Path.class, Path.class)) + .with( + (proxy, method, arguments) -> { + throw new UnsupportedOperationException("Hard links unsupported"); + }); + File restored = + Whitebox.invokeMethod( + loader, + "createLinksFromSnapshotToSourceDir", + "sequence/root.sg/0/0", + source.toFile(), + targets, + "source", + dataDir.getAbsolutePath()); + Files.delete(source); + assertArrayEquals(new byte[] {1, 2, 3}, Files.readAllBytes(restored.toPath())); + assertEquals(dataDir.getAbsolutePath(), targets.get("source")); + } +} diff --git a/pom.xml b/pom.xml index 9dfb37abed86..5eb5db6310aa 100644 --- a/pom.xml +++ b/pom.xml @@ -1437,8 +1437,14 @@ --> Date: Thu, 12 Nov 2026 12:31:17 +0800 Subject: [PATCH 2/2] Declare directly used PowerMock test support dependency --- iotdb-core/datanode/pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/iotdb-core/datanode/pom.xml b/iotdb-core/datanode/pom.xml index 2dad06d8ba70..3a94fd45fbe6 100644 --- a/iotdb-core/datanode/pom.xml +++ b/iotdb-core/datanode/pom.xml @@ -285,6 +285,12 @@ powermock-api-mockito2 test + + org.powermock + powermock-api-support + ${powermock.version} + test + org.powermock powermock-reflect