From eb6273cbe3d140aa686a432e8fbeccc6dad57723 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Mon, 7 Sep 2026 19:35:45 +0300 Subject: [PATCH 01/97] neuroslop --- .../ignite/util/GridCommandHandlerTest.java | 104 ++++++++ .../management/snapshot/SnapshotCommand.java | 2 + .../snapshot/SnapshotDeleteCommand.java | 42 ++++ .../snapshot/SnapshotDeleteCommandArg.java | 62 +++++ .../snapshot/SnapshotDeleteTask.java | 61 +++++ .../snapshot/SnapshotListCommand.java | 51 ++++ .../snapshot/SnapshotListCommandArg.java | 45 ++++ .../management/snapshot/SnapshotListTask.java | 91 +++++++ .../snapshot/IgniteSnapshotManager.java | 65 +++++ .../snapshot/SnapshotDeleteProcess.java | 224 ++++++++++++++++++ .../SnapshotDeleteProcessRequest.java | 53 +++++ .../SnapshotDeleteProcessResponse.java | 56 +++++ .../snapshot/SnapshotRestoreProcess.java | 3 + .../util/distributed/DistributedProcess.java | 7 + 14 files changed, 866 insertions(+) create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommandArg.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteTask.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommandArg.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessRequest.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResponse.java diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java index b5f2659b1fa0b..15d2fefac69ea 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java @@ -30,6 +30,7 @@ import java.util.BitSet; import java.util.Collection; import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; @@ -3243,6 +3244,109 @@ public void testCheckSnapshot() throws Exception { assertContains(log, sb.toString(), "The check procedure has finished, no conflicts have been found"); } + /** @throws Exception If fails. */ + @Test + public void testSnapshotList() throws Exception { + IgniteEx ig = startGrid(0); + ig.cluster().state(ACTIVE); + + createCacheAndPreload(ig, 100); + + String snpName1 = "snapshot_01012024"; + String snpName2 = "snapshot_02022024"; + + snp(ig).createSnapshot(snpName2).get(getTestTimeout()); + snp(ig).createSnapshot(snpName1).get(getTestTimeout()); + + TestCommandHandler h = newCommandHandler(); + + assertEquals(EXIT_CODE_OK, execute(h, "--snapshot", "list")); + + Collection res = h.getLastOperationResult(); + + assertNotNull(res); + + assertEquals(2, res.size()); + + // Result must be sorted. + Iterator it = res.iterator(); + + assertEquals(snpName1, it.next()); + assertEquals(snpName2, it.next()); + } + + /** @throws Exception If fails. */ + @Test + public void testSnapshotDelete() throws Exception { + String snpName = "snapshot_03032024"; + + IgniteEx ig = startGrid(0); + ig.cluster().state(ACTIVE); + + createCacheAndPreload(ig, 100); + + snp(ig).createSnapshot(snpName).get(getTestTimeout()); + + File snpDir = new File(ig.context().pdsFolderResolver().fileTree().snapshotsRoot(), snpName); + + assertTrue("Snapshot directory must exist: " + snpDir, snpDir.exists()); + + injectTestSystemOut(); + + TestCommandHandler h = newCommandHandler(); + + assertEquals(EXIT_CODE_OK, execute(h, "--snapshot", "delete", snpName)); + + assertTrue(waitForCondition(() -> !snpDir.exists(), getTestTimeout())); + } + + /** @throws Exception If fails. */ + @Test + public void testSnapshotDeleteMissing() throws Exception { + IgniteEx ig = startGrid(0); + ig.cluster().state(ACTIVE); + + createCacheAndPreload(ig, 100); + + injectTestSystemOut(); + + TestCommandHandler h = newCommandHandler(); + + // Deleting a non-existent snapshot is a no-op and must complete successfully. + assertEquals(EXIT_CODE_OK, execute(h, "--snapshot", "delete", "snapshot_MISSING")); + } + + /** @throws Exception If fails. */ + @Test + public void testSnapshotDeleteCustomDir() throws Exception { + String snpName = "snapshot_04042024"; + File snpDir = U.resolveWorkDirectory(U.defaultWorkDirectory(), "ex_snapshots_delete", true); + + try { + assertTrue("Target directory is not empty: " + snpDir, F.isEmpty(snpDir.list())); + + IgniteEx ig = startGrid(0); + ig.cluster().state(ACTIVE); + + createCacheAndPreload(ig, 100); + + assertEquals(EXIT_CODE_OK, + execute("--snapshot", "create", snpName, "--sync", "--dest", snpDir.getAbsolutePath())); + + File snpLocDir = new File(snpDir, snpName); + + assertTrue("Snapshot directory must exist: " + snpLocDir, snpLocDir.exists()); + + assertEquals(EXIT_CODE_OK, + execute("--snapshot", "delete", snpName, "--dest", snpDir.getAbsolutePath())); + + assertTrue(waitForCondition(() -> !snpLocDir.exists(), getTestTimeout())); + } + finally { + U.delete(snpDir); + } + } + /** @throws Exception If fails. */ @Test public void testSnapshotRestoreSynchronously() throws Exception { diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCommand.java index deb5416b8e28b..8e1bff06401d0 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCommand.java @@ -27,6 +27,8 @@ public SnapshotCommand() { new SnapshotCreateCommand(), new SnapshotCancelCommand(), new SnapshotCheckCommand(), + new SnapshotDeleteCommand(), + new SnapshotListCommand(), new SnapshotRestoreCommand(), new SnapshotStatusCommand() ); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java new file mode 100644 index 0000000000000..5433c55938c78 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java @@ -0,0 +1,42 @@ +/* + * 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.ignite.internal.management.snapshot; + +/** */ +public class SnapshotDeleteCommand extends AbstractSnapshotCommand { + /** {@inheritDoc} */ + @Override public String description() { + return "Delete snapshot and all its increments from the cluster"; + } + + /** {@inheritDoc} */ + @Override public Class argClass() { + return SnapshotDeleteCommandArg.class; + } + + /** {@inheritDoc} */ + @Override public Class taskClass() { + return SnapshotDeleteTask.class; + } + + /** {@inheritDoc} */ + @Override public String confirmationPrompt(SnapshotDeleteCommandArg arg) { + return "Warning: command will delete snapshot " + arg.snapshotName() + " and all its increments " + + "from all the cluster nodes. This operation is irreversible."; + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommandArg.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommandArg.java new file mode 100644 index 0000000000000..7cb0fbe11ecf3 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommandArg.java @@ -0,0 +1,62 @@ +/* + * 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.ignite.internal.management.snapshot; + +import org.apache.ignite.internal.Order; +import org.apache.ignite.internal.dto.IgniteDataTransferObject; +import org.apache.ignite.internal.management.api.Argument; +import org.apache.ignite.internal.management.api.Positional; + +/** */ +public class SnapshotDeleteCommandArg extends IgniteDataTransferObject { + /** */ + private static final long serialVersionUID = 0; + + /** */ + @Order(0) + @Positional + @Argument(description = "Snapshot name") + String snapshotName; + + /** */ + @Order(1) + @Argument(example = "path", optional = true, + description = "Path to the directory where the snapshot is located. " + + "If not specified, the default configured snapshot directory will be used") + String dest; + + /** */ + public String snapshotName() { + return snapshotName; + } + + /** */ + public void snapshotName(String snapshotName) { + this.snapshotName = snapshotName; + } + + /** */ + public String dest() { + return dest; + } + + /** */ + public void dest(String dest) { + this.dest = dest; + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteTask.java new file mode 100644 index 0000000000000..515983df92687 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteTask.java @@ -0,0 +1,61 @@ +/* + * 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.ignite.internal.management.snapshot; + +import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager; +import org.apache.ignite.internal.processors.task.GridInternal; +import org.apache.ignite.internal.visor.VisorJob; +import org.apache.ignite.internal.visor.VisorOneNodeTask; +import org.jetbrains.annotations.Nullable; + +/** + * @see IgniteSnapshotManager#deleteSnapshot(String, String) + */ +@GridInternal +public class SnapshotDeleteTask extends VisorOneNodeTask { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** {@inheritDoc} */ + @Override protected VisorJob job(SnapshotDeleteCommandArg arg) { + return new SnapshotDeleteJob(arg, debug); + } + + /** */ + private static class SnapshotDeleteJob extends SnapshotJob { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** + * @param arg Snapshot delete task argument. + * @param debug Flag indicating whether debug information should be printed into node log. + */ + protected SnapshotDeleteJob(SnapshotDeleteCommandArg arg, boolean debug) { + super(arg, debug); + } + + /** {@inheritDoc} */ + @Override protected Void run(SnapshotDeleteCommandArg arg) { + IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); + + snpMgr.deleteSnapshot(arg.snapshotName(), arg.dest()).get(); + + return null; + } + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java new file mode 100644 index 0000000000000..3ece964a70d47 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java @@ -0,0 +1,51 @@ +/* + * 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.ignite.internal.management.snapshot; + +import java.util.Collection; +import java.util.function.Consumer; + +/** */ +public class SnapshotListCommand extends AbstractSnapshotCommand> { + /** {@inheritDoc} */ + @Override public String description() { + return "List all snapshots and their increments in the cluster"; + } + + /** {@inheritDoc} */ + @Override public Class argClass() { + return SnapshotListCommandArg.class; + } + + /** {@inheritDoc} */ + @Override public Class taskClass() { + return SnapshotListTask.class; + } + + /** {@inheritDoc} */ + @Override public void printResult(SnapshotListCommandArg arg, Collection snapshots, Consumer printer) { + if (snapshots.isEmpty()) { + printer.accept("There are no snapshots in the cluster."); + + return; + } + + for (String snpName : snapshots) + printer.accept(snpName); + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommandArg.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommandArg.java new file mode 100644 index 0000000000000..22b221b4926b2 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommandArg.java @@ -0,0 +1,45 @@ +/* + * 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.ignite.internal.management.snapshot; + +import org.apache.ignite.internal.Order; +import org.apache.ignite.internal.dto.IgniteDataTransferObject; +import org.apache.ignite.internal.management.api.Argument; + +/** */ +public class SnapshotListCommandArg extends IgniteDataTransferObject { + /** */ + private static final long serialVersionUID = 0; + + /** */ + @Order(0) + @Argument(example = "path", optional = true, + description = "Path to the directory where the snapshots are located. " + + "If not specified, the default configured snapshot directory will be used") + String dest; + + /** */ + public String dest() { + return dest; + } + + /** */ + public void dest(String dest) { + this.dest = dest; + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java new file mode 100644 index 0000000000000..8dfeecbd543fd --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java @@ -0,0 +1,91 @@ +/* + * 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.ignite.internal.management.snapshot; + +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; +import java.util.UUID; +import org.apache.ignite.compute.ComputeJobResult; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager; +import org.apache.ignite.internal.processors.task.GridInternal; +import org.apache.ignite.internal.util.typedef.internal.CU; +import org.apache.ignite.internal.visor.VisorJob; +import org.apache.ignite.internal.visor.VisorMultiNodeTask; +import org.apache.ignite.internal.visor.VisorTaskArgument; + +import static org.apache.ignite.internal.util.lang.ClusterNodeFunc.nodeIds; + +/** + * Task to collect the names of all the snapshots existing in the cluster. + */ +@GridInternal +public class SnapshotListTask extends VisorMultiNodeTask, List> { + /** */ + private static final long serialVersionUID = 0L; + + /** {@inheritDoc} */ + @Override protected VisorJob> job(SnapshotListCommandArg arg) { + return new SnapshotListJob(arg, debug); + } + + /** {@inheritDoc} */ + @Override protected Collection jobNodes(VisorTaskArgument arg) { + return nodeIds(ignite.cluster().forServers().nodes()); + } + + /** {@inheritDoc} */ + @Override protected Collection reduce0(List results) { + Set res = new TreeSet<>(); + + for (ComputeJobResult jobRes : results) { + if (jobRes.getException() != null) + throw jobRes.getException(); + + if (jobRes.getData() != null) + res.addAll(jobRes.getData()); + } + + return res; + } + + /** */ + private static class SnapshotListJob extends SnapshotJob> { + /** */ + private static final long serialVersionUID = 0L; + + /** + * @param arg Snapshot list task argument. + * @param debug Flag indicating whether debug information should be printed into node log. + */ + protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { + super(arg, debug); + } + + /** {@inheritDoc} */ + @Override protected List run(SnapshotListCommandArg arg) { + if (!CU.isPersistenceEnabled(ignite.context().config()) || ignite.context().clientNode()) + return java.util.Collections.emptyList(); + + IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); + + return snpMgr.localSnapshotNames(arg.dest()); + } + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java index c6e25c2baa3ca..c45ec30925c19 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java @@ -344,6 +344,9 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter /** Snapshot validation distributed process. */ private final SnapshotCheckProcess checkSnpProc; + /** Distributed process to delete cluster snapshot. */ + private final SnapshotDeleteProcess deleteSnpProc; + /** Check previously performed snapshot operation and delete uncompleted files if we need. */ private final DistributedProcess endSnpProc; @@ -446,6 +449,8 @@ public IgniteSnapshotManager(GridKernalContext ctx) { checkSnpProc = new SnapshotCheckProcess(ctx); + deleteSnpProc = new SnapshotDeleteProcess(ctx); + // Manage remote snapshots. snpRmtMgr = new SequentialRemoteSnapshotManager(); } @@ -664,6 +669,7 @@ public IgniteSnapshotManager(GridKernalContext ctx) { restoreCacheGrpProc.interrupt(stopErr); checkSnpProc.interrupt(stopErr); + deleteSnpProc.interrupt(stopErr); // Try stop all snapshot processing if not yet. for (AbstractSnapshotFutureTask sctx : locSnpTasks.values()) @@ -696,6 +702,7 @@ public IgniteSnapshotManager(GridKernalContext ctx) { /** {@inheritDoc} */ @Override public void onDeActivate(GridKernalContext kctx) { restoreCacheGrpProc.interrupt(new IgniteCheckedException("The cluster has been deactivated.")); + deleteSnpProc.interrupt(new IgniteCheckedException("The cluster has been deactivated.")); } /** @@ -716,6 +723,33 @@ public void deleteSnapshot(File snpDir) { pdsSettings.consistentId().toString())); } + /** + * Deletes the local snapshot directory with the given name and path. + * + * @param snpName Full snapshot name. + * @param snpPath Snapshot directory path. If {@code null}, the default snapshot directory is used. + * @return {@code True} if the snapshot directory existed on the local node and was removed, {@code false} otherwise. + */ + public boolean deleteSnapshotLocal(String snpName, @Nullable String snpPath) { + if (cctx.kernalContext().clientNode()) + throw new UnsupportedOperationException("Client nodes can not perform this operation."); + + if (ft == null) + return false; + + File snpDir = snpPath == null ? new File(ft.snapshotsRoot(), snpName) : new File(snpPath, snpName); + + if (!snpDir.exists()) + return false; + + if (!snpDir.isDirectory()) + return false; + + deleteSnapshot(new SnapshotFileTree(cctx.kernalContext(), snpName, snpPath)); + + return true; + } + /** */ public void deleteSnapshot(SnapshotFileTree sft) { try { @@ -1446,6 +1480,31 @@ public boolean isSnapshotChecking(String snpName) { return checkSnpProc.isSnapshotChecking(snpName); } + /** + * @return {@code True} if a snapshot delete operation is in progress. + */ + public boolean isSnapshotDeleting() { + return deleteSnpProc.isSnapshotDeleting(); + } + + /** + * Deletes the cluster snapshot with the given name. The snapshot data is removed on all the baseline nodes. + *

+ * The operation is rejected if a snapshot operation (create, restore or check) is in progress for the snapshot. + * + * @param name Snapshot name. + * @param snpPath Snapshot directory path. If {@code null}, the default configured snapshot directory will be used. + * @return Future which will be completed when the snapshot is deleted on all the baseline nodes. + */ + public IgniteFutureImpl deleteSnapshot(String name, @Nullable String snpPath) { + A.notNullOrEmpty(name, "Snapshot name cannot be null or empty."); + A.ensure(U.alphanumericUnderscore(name), "Snapshot name must satisfy the following name pattern: a-zA-Z0-9_"); + + cctx.kernalContext().security().authorize(ADMIN_SNAPSHOT); + + return deleteSnpProc.start(name, snpPath); + } + /** * Sets the streamer warning flag to current snapshot process if it is active. */ @@ -2055,6 +2114,12 @@ public IgniteFutureImpl createSnapshot( ); } + if (isSnapshotDeleting()) { + throw new IgniteException( + "Snapshot operation has been rejected. Snapshot delete operation is currently in progress." + ); + } + snpFut0 = new ClusterSnapshotFuture(UUID.randomUUID(), name, incIdx); clusterSnpFut = snpFut0; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java new file mode 100644 index 0000000000000..8c0f7ce8d0fe0 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -0,0 +1,224 @@ +/* + * 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.ignite.internal.processors.cache.persistence.snapshot; + +import java.util.HashSet; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import org.apache.ignite.IgniteLogger; +import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.internal.GridKernalContext; +import org.apache.ignite.internal.IgniteInternalFuture; +import org.apache.ignite.internal.cluster.ClusterTopologyCheckedException; +import org.apache.ignite.internal.util.distributed.DistributedProcess; +import org.apache.ignite.internal.util.future.GridFinishedFuture; +import org.apache.ignite.internal.util.future.GridFutureAdapter; +import org.apache.ignite.internal.util.future.IgniteFutureImpl; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.internal.CU; +import org.jetbrains.annotations.Nullable; + +import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.DELETE_SNAPSHOT; +import static org.apache.ignite.internal.util.lang.ClusterNodeFunc.node2id; + +/** + * Distributed process to delete a cluster snapshot. The snapshot data is spread across the baseline nodes, + * so each node removes its local snapshot directory. The operation is rejected if any of the following + * conflicts is detected: the snapshot is being created, restored or checked. + */ +public class SnapshotDeleteProcess { + /** Reject operation messages. */ + private static final String OP_REJECT_MSG = "Snapshot deletion was rejected. "; + + /** Kernal context. */ + private final GridKernalContext ctx; + + /** Logger. */ + private final IgniteLogger log; + + /** Cluster-wide operation contexts per request id. */ + + private final Map contexts = new ConcurrentHashMap<>(); + + /** Delete snapshot phase subprocess. */ + private final DistributedProcess deleteProc; + + /** Stop node lock. */ + private boolean nodeStopping; + + /** + * @param ctx Kernal context. + */ + public SnapshotDeleteProcess(GridKernalContext ctx) { + this.ctx = ctx; + + log = ctx.log(getClass()); + + deleteProc = new DistributedProcess<>(ctx, DELETE_SNAPSHOT, this::deleteLocalSnapshot, this::reduceAndFinish); + } + + /** + * Stops all the running processes with the provided exception. + * + * @param err The interrupt reason. + */ + void interrupt(Throwable err) { + contexts.forEach((reqId, c) -> c.fut.onDone(err)); + } + + /** + * Starts the cluster snapshot delete process. + * + * @param snpName Snapshot name. + * @param snpPath Snapshot directory path (optional). + * @return Future that will be completed when the snapshot is deleted on all the baseline nodes. + */ + public IgniteFutureImpl start(String snpName, @Nullable String snpPath) { + assert !F.isEmpty(snpName); + + UUID reqId = UUID.randomUUID(); + + Set requiredNodes = new HashSet<>( + F.viewReadOnly(ctx.discovery().discoCache().aliveBaselineNodes(), node2id())); + + SnapshotDeleteProcessRequest req = new SnapshotDeleteProcessRequest(reqId, snpName, snpPath, requiredNodes); + + GridFutureAdapter clusterOpFut = new GridFutureAdapter<>(); + + DeleteContext dctx = new DeleteContext(req, clusterOpFut); + + contexts.put(reqId, dctx); + + clusterOpFut.listen(fut -> contexts.remove(reqId)); + + deleteProc.start(reqId, req); + + return new IgniteFutureImpl<>(clusterOpFut); + } + + /** + * @param snpName Snapshot name. + * @return {@code True} if a delete operation for the snapshot is in progress. + */ + boolean isSnapshotDeleting() { + return !contexts.isEmpty(); + } + + /** Local phase: delete the snapshot directory on the node. */ + private IgniteInternalFuture проверь илиdeleteLocalSnapshot( + UUID ignored, + SnapshotDeleteProcessRequest req + ) { + if (!baseline(ctx.localNodeId())) + return new GridFinishedFuture<>(new SnapshotDeleteProcessResponse(false)); + + IgniteSnapshotManager snpMgr = ctx.cache().context().snapshotMgr(); + + String snpName = req.snapshotName(); + + if (snpMgr.isSnapshotCreating()) + return new GridFinishedFuture<>(new ClusterTopologyCheckedException( + OP_REJECT_MSG + "a snapshot operation is in progress [snapshot=" + snpName + ']')); + + if (snpMgr.isRestoring(snpName)) + return new GridFinishedFuture<>(new ClusterTopologyCheckedException( + OP_REJECT_MSG + "the snapshot is being restored [snapshot=" + snpName + ']')); + + if (snpMgr.isSnapshotChecking(snpName)) + return new GridFinishedFuture<>(new ClusterTopologyCheckedException( + OP_REJECT_MSG + "the snapshot is being checked [snapshot=" + snpName + ']')); + + boolean deleted = snpMgr.deleteSnapshotLocal(snpName, req.snapshotPath()); + + if (log.isInfoEnabled()) { + log.info("Snapshot delete operation [snapshot=" + snpName + + ", snpPath=" + req.snapshotPath() + ", node=" + ctx.localNodeId() + ", deleted=" + deleted + ']'); + } + + return new GridFinishedFuture<>(new SnapshotDeleteProcessResponse(deleted)); + } + + /** Coordinator finish: aggregate node results and complete the user future. */ + private void reduceAndFinish( + UUID reqId, + Map results, + Map errors + ) { + DeleteContext dctx = contexts.get(reqId); + + if (dctx == null) + return; + + try { + if (!errors.isEmpty()) + throw F.firstValue(errors); + + ClusterTopologyCheckedException ex = checkNodeLeft(dctx.req.nodes(), results.keySet()); + + if (ex != null) + throw ex; + + dctx.fut.onDone(); + } + catch (Throwable th) { + dctx.fut.onDone(th); + } + } + + /** + * @param reqNodes Set of required topology nodes. + * @param respNodes Set of responded topology nodes. + * @return Error, if no response was received from a required topology node. + */ + private static @Nullable ClusterTopologyCheckedException checkNodeLeft(Set reqNodes, Set respNodes) { + if (!respNodes.containsAll(reqNodes)) { + Set leftNodes = new HashSet<>(reqNodes); + + leftNodes.removeAll(respNodes); + + return new ClusterTopologyCheckedException("Snapshot deletion stopped. " + + "Required node has left the cluster [nodeId=" + leftNodes + ']'); + } + + return null; + } + + /** @return {@code True} if the local node is a baseline node. */ + private boolean baseline(UUID nodeId) { + ClusterNode node = ctx.cluster().get().node(nodeId); + + return node != null && CU.baselineNode(node, ctx.state().clusterState()); + } + + /** Delete operation context. */ + private static final class DeleteContext { + /** Request. */ + private final SnapshotDeleteProcessRequest req; + + /** Cluster operation future. */ + private final GridFutureAdapter fut; + + /** */ + private DeleteContext(SnapshotDeleteProcessRequest req, GridFutureAdapter fut) { + this.req = req; + this.fut = fut; + } + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessRequest.java new file mode 100644 index 0000000000000..8370c61add273 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessRequest.java @@ -0,0 +1,53 @@ +/* + * 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.ignite.internal.processors.cache.persistence.snapshot; + +import java.util.Collection; +import java.util.UUID; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.internal.S; +import org.apache.ignite.plugin.extensions.communication.MessageFactory; + +/** + * Cluster snapshot delete distributed process request. + * + * @see SnapshotDeleteProcess + */ +public class SnapshotDeleteProcessRequest extends AbstractSnapshotOperationRequest { + /** Default constructor for {@link MessageFactory}. */ + public SnapshotDeleteProcessRequest() { + // No-op. + } + + /** + * @param reqId Request ID. + * @param snpName Snapshot name. + * @param snpPath Snapshot directory path. + * @param nodes Baseline node IDs that must be alive to complete the operation. + */ + SnapshotDeleteProcessRequest(UUID reqId, String snpName, String snpPath, Collection nodes) { + super(reqId, snpName, snpPath, null, nodes); + + assert !F.isEmpty(nodes); + } + + /** {@inheritDoc} */ + @Override public String toString() { + return S.toString(SnapshotDeleteProcessRequest.class, this, super.toString()); + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResponse.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResponse.java new file mode 100644 index 0000000000000..8beb97f0ad218 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResponse.java @@ -0,0 +1,56 @@ +/* + * 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.ignite.internal.processors.cache.persistence.snapshot; + +import org.apache.ignite.internal.Order; +import org.apache.ignite.internal.util.typedef.internal.S; +import org.apache.ignite.plugin.extensions.communication.Message; +import org.apache.ignite.plugin.extensions.communication.MessageFactory; + +/** + * Single-node result of the snapshot delete distributed process. + * + * @see SnapshotDeleteProcess + */ +public class SnapshotDeleteProcessResponse implements Message { + /** {@code True} if the snapshot directory was actually removed on the node. */ + @Order(0) + boolean deleted; + + /** Default constructor for {@link MessageFactory}. */ + public SnapshotDeleteProcessResponse() { + // No-op. + } + + /** + * @param deleted {@code True} if the snapshot directory was actually removed on the node. + */ + public SnapshotDeleteProcessResponse(boolean deleted) { + this.deleted = deleted; + } + + /** @return {@code True} if the snapshot directory was actually removed on the node. */ + public boolean deleted() { + return deleted; + } + + /** {@inheritDoc} */ + @Override public String toString() { + return S.toString(SnapshotDeleteProcessResponse.class, this); + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotRestoreProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotRestoreProcess.java index 77f56f4d4cc7f..386189ca6b3f4 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotRestoreProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotRestoreProcess.java @@ -278,6 +278,9 @@ public IgniteFutureImpl start( if (snpMgr.isSnapshotCreating()) throw new IgniteException(OP_REJECT_MSG + "A cluster snapshot operation is in progress."); + if (snpMgr.isSnapshotDeleting()) + throw new IgniteException(OP_REJECT_MSG + "A snapshot delete operation is in progress."); + synchronized (this) { if (restoringSnapshotName() != null || fut != null) throw new IgniteException(OP_REJECT_MSG + "The previous snapshot restore operation was not completed."); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/distributed/DistributedProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/util/distributed/DistributedProcess.java index 3d0ce062f4f75..29ba47e379481 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/distributed/DistributedProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/distributed/DistributedProcess.java @@ -500,6 +500,13 @@ public enum DistributedProcessType { */ CHECK_SNAPSHOT_PARTS, + /** + * Delete snapshot procedure. + * + * @see IgniteSnapshotManager + */ + DELETE_SNAPSHOT, + /** * Cluster version Rolling Upgrade enable process. */ From d9aa470487271903a195d30d3cac8c08a4913dcd Mon Sep 17 00:00:00 2001 From: Mikhail Petrov <32207922+petrov-mg@users.noreply.github.com> Date: Fri, 4 Sep 2026 16:53:40 +0300 Subject: [PATCH 02/97] IGNITE-29037 Refactored Message Factory usage in TCP Discovery implementations (#13551) --- .../ignite/spi/discovery/tcp/ClientImpl.java | 32 +++----- .../ignite/spi/discovery/tcp/ServerImpl.java | 80 ++++++++----------- .../spi/discovery/tcp/TcpDiscoveryImpl.java | 19 ++--- .../discovery/tcp/TcpDiscoveryIoSession.java | 40 +++++----- .../tcp/TcpDiscoveryMessageSerializer.java | 9 ++- .../spi/discovery/tcp/TcpDiscoverySpi.java | 21 +---- .../cache/CacheMetricsCacheSizeTest.java | 3 +- .../ignite/spi/MessagesPluginProvider.java | 13 --- .../discovery/tcp/BlockTcpDiscoverySpi.java | 2 +- .../DiscoveryUnmarshalVulnerabilityTest.java | 2 +- .../tcp/TcpClientDiscoverySpiSelfTest.java | 2 +- .../discovery/tcp/TestTcpDiscoverySpi.java | 50 +----------- 12 files changed, 85 insertions(+), 188 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ClientImpl.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ClientImpl.java index 621a234ee5428..dd1aed7a2a24f 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ClientImpl.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ClientImpl.java @@ -47,7 +47,6 @@ import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.SSLException; -import org.apache.ignite.Ignite; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteClientDisconnectedException; import org.apache.ignite.IgniteException; @@ -60,7 +59,6 @@ import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.failure.FailureContext; import org.apache.ignite.internal.IgniteClientDisconnectedCheckedException; -import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.IgniteInterruptedCheckedException; import org.apache.ignite.internal.IgniteNodeAttributes; import org.apache.ignite.internal.managers.discovery.DiscoveryServerOnlyCustomMessage; @@ -73,7 +71,6 @@ import org.apache.ignite.internal.util.typedef.internal.LT; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.internal.util.worker.GridWorker; -import org.apache.ignite.internal.worker.WorkersRegistry; import org.apache.ignite.lang.IgniteInClosure; import org.apache.ignite.lang.IgniteUuid; import org.apache.ignite.spi.IgniteSpiAdapter; @@ -208,10 +205,9 @@ class ClientImpl extends TcpDiscoveryImpl { ClientImpl(TcpDiscoverySpi adapter) { super(adapter); - String instanceName = adapter.ignite() == null || adapter.ignite().name() == null - ? "client-node" : adapter.ignite().name(); + String instanceName = ctx.igniteInstanceName(); - executorSrvc = newSingleThreadScheduledExecutor("tcp-discovery-exec", instanceName); + executorSrvc = newSingleThreadScheduledExecutor("tcp-discovery-exec", instanceName == null ? "client-node" : instanceName); } /** {@inheritDoc} */ @@ -1052,13 +1048,6 @@ private void joinError(IgniteSpiException err) { joinLatch.countDown(); } - /** */ - private WorkersRegistry getWorkersRegistry() { - Ignite ignite = spi.ignite(); - - return ignite instanceof IgniteEx ? ((IgniteEx)ignite).context().workersRegistry() : null; - } - /** */ private Collection remoteVisibleNodes() { return U.arrayList(rmtNodes.values(), TcpDiscoveryNodesRing.VISIBLE_NODES); @@ -1101,7 +1090,7 @@ private class SocketReader extends IgniteSpiThread { /** */ SocketReader() { - super(spi.ignite().name(), "tcp-client-disco-sock-reader-[]", log); + super(ctx.igniteInstanceName(), "tcp-client-disco-sock-reader-[]", log); } /** @@ -1274,7 +1263,7 @@ private class SocketWriter extends IgniteSpiThread { * */ SocketWriter() { - super(spi.ignite().name(), "tcp-client-disco-sock-writer", log); + super(ctx.igniteInstanceName(), "tcp-client-disco-sock-writer", log); sockTimeout = spi.failureDetectionTimeoutEnabled() ? spi.failureDetectionTimeout() : spi.getSocketTimeout(); @@ -1524,7 +1513,7 @@ private class Reconnector extends IgniteSpiThread { * @param prevAddr Address of the node, that this client was previously connected to. */ protected Reconnector(boolean join, InetSocketAddress prevAddr) { - super(spi.ignite().name(), "tcp-client-disco-reconnector", log); + super(ctx.igniteInstanceName(), "tcp-client-disco-reconnector", log); this.join = join; this.prevAddr = prevAddr; @@ -1689,7 +1678,7 @@ protected class MessageWorker extends GridWorker { * @param log Logger. */ private MessageWorker(IgniteLogger log) { - super(spi.ignite().name(), "tcp-client-disco-msg-worker", log, getWorkersRegistry()); + super(ctx.igniteInstanceName(), "tcp-client-disco-msg-worker", log, ctx.workersRegistry()); } /** {@inheritDoc} */ @@ -1957,8 +1946,7 @@ else if (discoMsg instanceof TcpDiscoveryCheckFailedMessage) Thread.currentThread().interrupt(); } catch (Throwable t) { - if (spi.ignite() instanceof IgniteEx) - ((IgniteEx)spi.ignite()).context().failure().process(new FailureContext(CRITICAL_ERROR, t)); + ctx.failure().process(new FailureContext(CRITICAL_ERROR, t)); } finally { TcpDiscoveryIoSession ses = this.currSes; @@ -2210,7 +2198,7 @@ else if (log.isDebugEnabled()) if (joining()) delayDiscoData.add(dataPacket); else - spi.onExchange(dataPacket, U.resolveClassLoader(spi.ignite().configuration())); + spi.onExchange(dataPacket, U.resolveClassLoader(ctx.config())); } } } @@ -2244,11 +2232,11 @@ private void processNodeAddFinishedMessage(TcpDiscoveryNodeAddFinishedMessage ms DiscoveryDataPacket dataContainer = msg.clientDiscoData(); if (dataContainer != null) - spi.onExchange(dataContainer, U.resolveClassLoader(spi.ignite().configuration())); + spi.onExchange(dataContainer, U.resolveClassLoader(ctx.config())); if (!delayDiscoData.isEmpty()) { for (DiscoveryDataPacket data : delayDiscoData) - spi.onExchange(data, U.resolveClassLoader(spi.ignite().configuration())); + spi.onExchange(data, U.resolveClassLoader(ctx.config())); delayDiscoData.clear(); } diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ServerImpl.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ServerImpl.java index e8a9e5c239400..5974e61780743 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ServerImpl.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ServerImpl.java @@ -76,7 +76,6 @@ import org.apache.ignite.events.NodeValidationFailedEvent; import org.apache.ignite.failure.FailureContext; import org.apache.ignite.internal.ClusterMetricsSnapshot; -import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.IgniteFutureTimeoutCheckedException; import org.apache.ignite.internal.IgniteInterruptedCheckedException; import org.apache.ignite.internal.IgniteNodeAttributes; @@ -107,7 +106,6 @@ import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.internal.util.worker.GridWorker; import org.apache.ignite.internal.util.worker.GridWorkerListener; -import org.apache.ignite.internal.worker.WorkersRegistry; import org.apache.ignite.lang.IgniteBiTuple; import org.apache.ignite.lang.IgniteFuture; import org.apache.ignite.lang.IgniteInClosure; @@ -362,14 +360,14 @@ class ServerImpl extends TcpDiscoveryImpl { super(adapter); utilityPool = new IgniteThreadPoolExecutor("disco-pool", - spi.ignite().name(), + ctx.igniteInstanceName(), 0, utilityPoolSize, 2000, new LinkedBlockingQueue<>()); List props = newConnectionEnabledProperty( - ((IgniteEx)spi.ignite()).context().internalSubscriptionProcessor(), + ctx.internalSubscriptionProcessor(), log, "ClientNode", "ServerNode" @@ -2249,7 +2247,7 @@ private class IpFinderCleaner extends IgniteSpiThread { * Constructor. */ private IpFinderCleaner() { - super(spi.ignite().name(), "tcp-disco-ip-finder-cleaner", log); + super(ctx.igniteInstanceName(), "tcp-disco-ip-finder-cleaner", log); setPriority(spi.threadPri); } @@ -2458,11 +2456,6 @@ private static void enrichNodeWithAttribute(TcpDiscoveryNode node, String attrNa node.setAttributes(attrs); } - /** */ - private static WorkersRegistry getWorkerRegistry(TcpDiscoverySpi spi) { - return spi.ignite() instanceof IgniteEx ? ((IgniteEx)spi.ignite()).context().workersRegistry() : null; - } - /** * Upcasts collection type. * @@ -2866,7 +2859,7 @@ protected class RingMessageWorker extends MessageWorker queue) { - super("tcp-disco-msg-worker-[]", log, 10, getWorkerRegistry(spi), queue); + super("tcp-disco-msg-worker-[]", log, 10, ctx.workersRegistry(), queue); setBeforeEachPollAction(() -> { updateHeartbeat(); @@ -3049,17 +3042,15 @@ private void addToQueue(TcpDiscoveryAbstractMessage msg, boolean addFirst) { throw e; } finally { - if (spi.ignite() instanceof IgniteEx) { - if (err == null && !spi.isNodeStopping0() && spiStateCopy() != DISCONNECTING) - err = new IllegalStateException("Worker " + name() + " is terminated unexpectedly."); + if (err == null && !spi.isNodeStopping0() && spiStateCopy() != DISCONNECTING) + err = new IllegalStateException("Worker " + name() + " is terminated unexpectedly."); - FailureProcessor failure = ((IgniteEx)spi.ignite()).context().failure(); + FailureProcessor failure = ctx.failure(); - if (err instanceof OutOfMemoryError) - failure.process(new FailureContext(CRITICAL_ERROR, err)); - else if (err != null) - failure.process(new FailureContext(SYSTEM_WORKER_TERMINATION, err)); - } + if (err instanceof OutOfMemoryError) + failure.process(new FailureContext(CRITICAL_ERROR, err)); + else if (err != null) + failure.process(new FailureContext(SYSTEM_WORKER_TERMINATION, err)); } } @@ -4591,8 +4582,7 @@ private IgniteNodeValidationResult validateByIgniteComponentsWithJoiningNodeData DiscoveryDataPacket packet = req.gridDiscoveryData(); try { - DiscoveryDataBag dataBag = packet.bagWithJoiningNodeData(spi.ignite().log(), - spi.ignite().configuration().isClientMode()); + DiscoveryDataBag dataBag = packet.bagWithJoiningNodeData(log, ctx.config().isClientMode()); return spi.getSpiContext().validateNode(req.node(), dataBag); } @@ -4936,7 +4926,7 @@ else if (!locNodeId.equals(node.id()) && ring.node(node.id()) != null) { if (dataPacket.hasJoiningNodeData()) { if (spiState == CONNECTED) { // Node already connected to the cluster can apply joining nodes' disco data immediately - spi.onExchange(dataPacket, U.resolveClassLoader(spi.ignite().configuration())); + spi.onExchange(dataPacket, U.resolveClassLoader(ctx.config())); spi.collectExchangeData(dataPacket); } @@ -5154,11 +5144,11 @@ private void processNodeAddFinishedMessage(TcpDiscoveryNodeAddFinishedMessage ms } if (gridDiscoveryData != null) - spi.onExchange(gridDiscoveryData, U.resolveClassLoader(spi.ignite().configuration())); + spi.onExchange(gridDiscoveryData, U.resolveClassLoader(ctx.config())); if (joiningNodesDiscoDataList != null) { for (DiscoveryDataPacket dataPacket : joiningNodesDiscoDataList) - spi.onExchange(dataPacket, U.resolveClassLoader(spi.ignite().configuration())); + spi.onExchange(dataPacket, U.resolveClassLoader(ctx.config())); } nullifyDiscoData(); @@ -6310,7 +6300,7 @@ private class TcpServer extends GridWorker { * @throws IgniteSpiException In case of error. */ TcpServer(IgniteLogger log) throws IgniteSpiException { - super(spi.ignite().name(), "tcp-disco-srvr-[]", log, getWorkerRegistry(spi)); + super(ctx.igniteInstanceName(), "tcp-disco-srvr-[]", log, ctx.workersRegistry()); int lastPort = spi.locPortRange == 0 ? spi.locPort : spi.locPort + spi.locPortRange - 1; @@ -6330,7 +6320,7 @@ private class TcpServer extends GridWorker { if (log.isInfoEnabled()) { log.info("Successfully bound to TCP port [port=" + port + ", localHost=" + spi.locHost + - ", locNodeId=" + spi.ignite().configuration().getNodeId() + + ", locNodeId=" + spi.cfgNodeId + ']'); } @@ -6413,17 +6403,15 @@ private class TcpServer extends GridWorker { throw t; } finally { - if (spi.ignite() instanceof IgniteEx) { - if (err == null && !spi.isNodeStopping0() && spiStateCopy() != DISCONNECTING) - err = new IllegalStateException("Worker " + name() + " is terminated unexpectedly."); + if (err == null && !spi.isNodeStopping0() && spiStateCopy() != DISCONNECTING) + err = new IllegalStateException("Worker " + name() + " is terminated unexpectedly."); - FailureProcessor failure = ((IgniteEx)spi.ignite()).context().failure(); + FailureProcessor failure = ctx.failure(); - if (err instanceof OutOfMemoryError) - failure.process(new FailureContext(CRITICAL_ERROR, err)); - else if (err != null) - failure.process(new FailureContext(SYSTEM_WORKER_TERMINATION, err)); - } + if (err instanceof OutOfMemoryError) + failure.process(new FailureContext(CRITICAL_ERROR, err)); + else if (err != null) + failure.process(new FailureContext(SYSTEM_WORKER_TERMINATION, err)); U.closeQuiet(srvrSock); } @@ -6458,11 +6446,11 @@ private class SocketReader extends IgniteSpiThread { * @param sock Socket to read data from. */ SocketReader(Socket sock) { - super(spi.ignite().name(), "tcp-disco-sock-reader-[]", log); + super(ctx.igniteInstanceName(), "tcp-disco-sock-reader-[]", log); this.sock = sock; - ses = createSession(sock); + ses = new TcpDiscoveryIoSession(ctx, sock); setPriority(spi.threadPri); } @@ -7085,11 +7073,7 @@ else if (msg instanceof TcpDiscoveryRingLatencyCheckMessage) { } } catch (UnknownMessageException e) { - if (spi.ignite() instanceof IgniteEx) { - FailureProcessor failure = ((IgniteEx)spi.ignite()).context().failure(); - - failure.process(new FailureContext(SYSTEM_WORKER_TERMINATION, e)); - } + ctx.failure().process(new FailureContext(SYSTEM_WORKER_TERMINATION, e)); } finally { if (clientMsgWrk != null) { @@ -7461,7 +7445,7 @@ private class StatisticsPrinter extends IgniteSpiThread { * Constructor. */ StatisticsPrinter() { - super(spi.ignite().name(), "tcp-disco-stats-printer", log); + super(ctx.igniteInstanceName(), "tcp-disco-stats-printer", log); assert spi.statsPrintFreq > 0; @@ -7534,7 +7518,7 @@ private ClientMessageWorker(TcpDiscoveryIoSession ses, UUID clientNodeId, Ignite this.ses = ses; this.clientNodeId = clientNodeId; - clientMsgSer = new TcpDiscoveryMessageSerializer(spi); + clientMsgSer = new TcpDiscoveryMessageSerializer(ctx); lastMetricsUpdateMsgTimeNanos = System.nanoTime(); } @@ -7878,7 +7862,7 @@ protected MessageWorker( @Nullable GridWorkerListener lsnr, BlockingDeque queue ) { - super(spi.ignite().name(), name, log, lsnr); + super(ctx.igniteInstanceName(), name, log, lsnr); this.queue = queue; this.pollingTimeout = pollingTimeout; @@ -8101,7 +8085,7 @@ void pingRemoteDCs(List nodesToPing) { rmtDcPingPool = new IgniteThreadPoolExecutor( "disco-remote-dc-ping-worker", - spi.ignite().name(), + ctx.igniteInstanceName(), pingRmtDcPoolSz, pingRmtDcPoolSz, 0, diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryImpl.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryImpl.java index b83e2130ae2e0..0f2324361d5dc 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryImpl.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryImpl.java @@ -18,7 +18,6 @@ package org.apache.ignite.spi.discovery.tcp; import java.net.InetSocketAddress; -import java.net.Socket; import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; @@ -36,6 +35,7 @@ import org.apache.ignite.cluster.ClusterMetrics; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.internal.ClusterMetricsSnapshot; +import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.processors.cache.CacheMetricsSnapshot; import org.apache.ignite.internal.processors.cluster.CacheMetricsMessage; @@ -87,6 +87,9 @@ abstract class TcpDiscoveryImpl { /** */ protected final TcpDiscoverySpi spi; + /** */ + protected final GridKernalContext ctx; + /** */ protected final IgniteLogger log; @@ -145,7 +148,9 @@ abstract class TcpDiscoveryImpl { log = spi.log; - operationCtxDispatcher = ((IgniteEx)spi.ignite()).context().operationContextDispatcher(); + ctx = ((IgniteEx)spi.ignite()).context(); + + operationCtxDispatcher = ctx.operationContextDispatcher(); } /** @@ -459,16 +464,6 @@ protected static List toOrderedList(Collection addrs) return res; } - /** - * Instantiates IO session for exchanging discovery messages with remote node. - * - * @param sock Socket to remote node. - * @return IO session for writing and reading {@link TcpDiscoveryAbstractMessage}. - */ - TcpDiscoveryIoSession createSession(Socket sock) { - return new TcpDiscoveryIoSession(sock, spi); - } - /** * @param msg Message. * @return Message logger. diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java index 71a4995eeabcb..0251712fc95b7 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java @@ -35,7 +35,6 @@ import org.apache.ignite.IgniteException; import org.apache.ignite.IgniteLogger; import org.apache.ignite.internal.GridKernalContext; -import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.direct.DirectMessageReader; import org.apache.ignite.internal.direct.DirectMessageWriter; import org.apache.ignite.internal.managers.communication.DiscoveryMarshalling; @@ -46,6 +45,7 @@ import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.marshaller.jdk.JdkMarshaller; import org.apache.ignite.plugin.extensions.communication.Message; +import org.apache.ignite.plugin.extensions.communication.MessageFactory; import org.apache.ignite.plugin.extensions.communication.MessageSerializer; import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryAbstractMessage; import org.jetbrains.annotations.NotNull; @@ -70,7 +70,13 @@ public class TcpDiscoveryIoSession implements AutoCloseable { private static final int MSG_BUFFER_SIZE = 100; /** */ - private final TcpDiscoverySpi spi; + private final GridKernalContext ctx; + + /** */ + private final MessageFactory msgFactory; + + /** */ + private final IgniteLogger log; /** */ private final Socket sock; @@ -96,19 +102,21 @@ public class TcpDiscoveryIoSession implements AutoCloseable { /** * Creates a new discovery I/O session bound to the given socket. * + * @param ctx Kernal context. * @param sock Socket connected to a remote discovery node. - * @param spi Discovery SPI instance owning this session. * @throws IgniteException If an I/O error occurs while initializing buffers. */ - TcpDiscoveryIoSession(Socket sock, TcpDiscoverySpi spi) { + TcpDiscoveryIoSession(GridKernalContext ctx, Socket sock) { this.sock = sock; - this.spi = spi; + this.ctx = ctx; + this.msgFactory = ctx.messageFactory(); + this.log = ctx.log(getClass()); readBuf = ByteBuffer.allocate(MSG_BUFFER_SIZE); writeBuf = ByteBuffer.allocate(MSG_BUFFER_SIZE); - msgWriter = new DirectMessageWriter(spi.messageFactory()); - msgReader = new DirectMessageReader(spi.messageFactory(), null); + msgWriter = new DirectMessageWriter(msgFactory); + msgReader = new DirectMessageReader(msgFactory, null); try { int sendBufSize = sock.getSendBufferSize() > 0 ? sock.getSendBufferSize() : DFLT_SOCK_BUFFER_SIZE; @@ -178,7 +186,7 @@ T readMessage() throws IgniteCheckedException, IOException { Message msg; try { - msg = spi.messageFactory().create(msgType); + msg = msgFactory.create(msgType); } catch (IgniteException e) { detectSslAlert(b0, b1); @@ -202,7 +210,7 @@ T readMessage() throws IgniteCheckedException, IOException { readBuf.limit(read); - finished = MessageSerialization.readFrom(spi.messageFactory(), msg, msgReader); + finished = MessageSerialization.readFrom(msgFactory, msg, msgReader); // Server Discovery only sends next message to next Server upon receiving a receipt for the previous one. // This behaviour guarantees that we never read a next message from the buffer right after the end of @@ -218,9 +226,7 @@ T readMessage() throws IgniteCheckedException, IOException { } while (!finished); - GridKernalContext kctx = ((IgniteEx)spi.ignite()).context(); - - DiscoveryMarshalling.unmarshal(msg, kctx); + DiscoveryMarshalling.unmarshal(msg, ctx); return (T)msg; } @@ -238,14 +244,14 @@ T readMessage() throws IgniteCheckedException, IOException { /** @return SSL certificate this session is established with. {@code null} if SSL is disabled or certificate validation failed. */ @Nullable Certificate[] extractCertificates() { - if (!spi.isSslEnabled()) + if (!(sock instanceof SSLSocket)) return null; try { return ((SSLSocket)sock).getSession().getPeerCertificates(); } catch (SSLPeerUnverifiedException e) { - U.error(spi.log, "Failed to extract discovery IO session certificates", e); + U.error(log, "Failed to extract discovery IO session certificates", e); return null; } @@ -264,9 +270,7 @@ public Socket socket() { * @throws IOException If serialization fails. */ void serializeMessage(Message m, OutputStream out) throws IOException, IgniteCheckedException { - GridKernalContext kctx = ((IgniteEx)spi.ignite()).context(); - - DiscoveryMarshalling.marshal(m, kctx, null); + DiscoveryMarshalling.marshal(m, ctx, null); msgWriter.reset(); msgWriter.setBuffer(writeBuf); @@ -277,7 +281,7 @@ void serializeMessage(Message m, OutputStream out) throws IOException, IgniteChe // Should be cleared before first operation. writeBuf.clear(); - finished = MessageSerialization.writeTo(spi.messageFactory(), m, msgWriter); + finished = MessageSerialization.writeTo(msgFactory, m, msgWriter); out.write(writeBuf.array(), 0, writeBuf.position()); } diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryMessageSerializer.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryMessageSerializer.java index 8c871f9e2eb46..ec7cdc569f0c7 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryMessageSerializer.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryMessageSerializer.java @@ -22,6 +22,7 @@ import java.io.OutputStream; import java.net.Socket; import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.plugin.extensions.communication.Message; import org.apache.ignite.plugin.extensions.communication.MessageSerializer; import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryAbstractMessage; @@ -35,10 +36,10 @@ */ class TcpDiscoveryMessageSerializer extends TcpDiscoveryIoSession { /** - * @param spi Discovery SPI instance. + * @param ctx Kernal context. */ - public TcpDiscoveryMessageSerializer(TcpDiscoverySpi spi) { - super(new Socket() { + public TcpDiscoveryMessageSerializer(GridKernalContext ctx) { + super(ctx, new Socket() { @Override public OutputStream getOutputStream() throws IOException { return null; } @@ -46,7 +47,7 @@ public TcpDiscoveryMessageSerializer(TcpDiscoverySpi spi) { @Override public InputStream getInputStream() throws IOException { return null; } - }, spi); + }); } /** diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySpi.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySpi.java index c4c74289af30b..7b8490ded8385 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySpi.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySpi.java @@ -57,7 +57,6 @@ import org.apache.ignite.internal.IgniteInterruptedCheckedException; import org.apache.ignite.internal.managers.communication.UnknownMessageException; import org.apache.ignite.internal.managers.discovery.IgniteDiscoverySpi; -import org.apache.ignite.internal.processors.failure.FailureProcessor; import org.apache.ignite.internal.processors.metric.MetricRegistryImpl; import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteComponentFeatureSet; import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteNodeFeatureSet; @@ -74,7 +73,6 @@ import org.apache.ignite.lang.IgniteUuid; import org.apache.ignite.marshaller.Marshaller; import org.apache.ignite.plugin.extensions.communication.Message; -import org.apache.ignite.plugin.extensions.communication.MessageFactory; import org.apache.ignite.resources.IgniteInstanceResource; import org.apache.ignite.resources.LoggerResource; import org.apache.ignite.spi.IgniteSpiAdapter; @@ -463,10 +461,6 @@ public class TcpDiscoverySpi extends IgniteSpiAdapter implements IgniteDiscovery @GridToStringExclude protected IgniteSpiContext spiCtx; - /** Discovery messages factory. */ - @GridToStringExclude - private MessageFactory msgFactory; - /** For test purposes. */ private boolean skipAddrsRandomization = false; @@ -600,8 +594,6 @@ public void setClientReconnectDisabled(boolean clientReconnectDisabled) { setAddressResolver(ignite.configuration().getAddressResolver()); marsh = ((IgniteEx)ignite).context().marshallerContext().jdkMarshaller(); - - msgFactory = ((IgniteEx)ignite).context().messageFactory(); } } @@ -1122,11 +1114,6 @@ public void setConnectionRecoveryTimeout(long connRecoveryTimeout) { locNodeVer = ver; } - /** @return Discovery messages factory. */ - public MessageFactory messageFactory() { - return msgFactory; - } - /** * Gets ID of the local node. * @@ -1624,7 +1611,7 @@ protected TcpDiscoveryIoSession openSession( sock.connect(resolved, (int)timeoutHelper.nextTimeoutChunk(sockTimeout)); - TcpDiscoveryIoSession ses = new TcpDiscoveryIoSession(sock, this); + TcpDiscoveryIoSession ses = new TcpDiscoveryIoSession(ignite.context(), sock); write(ses, U.IGNITE_HEADER, timeoutHelper.nextTimeoutChunk(sockTimeout)); @@ -2131,11 +2118,7 @@ protected void onExchange(DiscoveryDataPacket dataPacket, ClassLoader clsLdr) { dataBag = dataPacket.bagWithJoiningNodeData(ignite.log(), ignite.configuration().isClientMode()); } catch (IgniteCheckedException e) { - if (ignite() instanceof IgniteEx) { - FailureProcessor failure = ((IgniteEx)ignite()).context().failure(); - - failure.process(new FailureContext(CRITICAL_ERROR, e)); - } + ignite.context().failure().process(new FailureContext(CRITICAL_ERROR, e)); throw new IgniteException(e); } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/CacheMetricsCacheSizeTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/CacheMetricsCacheSizeTest.java index 9aed75febee59..a4a0db433309d 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/CacheMetricsCacheSizeTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/CacheMetricsCacheSizeTest.java @@ -35,7 +35,6 @@ import org.apache.ignite.internal.processors.cluster.CacheMetricsMessage; import org.apache.ignite.internal.util.nio.MessageSerialization; import org.apache.ignite.plugin.extensions.communication.MessageFactory; -import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi; import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryMetricsUpdateMessage; import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; import org.junit.Test; @@ -105,7 +104,7 @@ public void testCacheSize() throws Exception { msg.addServerMetrics(srvrId, new ClusterMetricsSnapshot()); msg.addServerCacheMetrics(srvrId, cacheMetrics); - MessageFactory msgFactory = ((TcpDiscoverySpi)grid(0).context().discovery().getInjectedDiscoverySpi()).messageFactory(); + MessageFactory msgFactory = grid(0).context().messageFactory(); // First time we write initial message type which is not read by the reader because the message type is known. // We have to skip this header at the further message reading. diff --git a/modules/core/src/test/java/org/apache/ignite/spi/MessagesPluginProvider.java b/modules/core/src/test/java/org/apache/ignite/spi/MessagesPluginProvider.java index 1198584d22972..0090d11cc3c56 100644 --- a/modules/core/src/test/java/org/apache/ignite/spi/MessagesPluginProvider.java +++ b/modules/core/src/test/java/org/apache/ignite/spi/MessagesPluginProvider.java @@ -17,7 +17,6 @@ package org.apache.ignite.spi; -import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.internal.CoreMessagesProvider; import org.apache.ignite.plugin.AbstractTestPluginProvider; import org.apache.ignite.plugin.ExtensionRegistry; @@ -25,8 +24,6 @@ import org.apache.ignite.plugin.extensions.communication.Message; import org.apache.ignite.plugin.extensions.communication.MessageFactoryProvider; import org.apache.ignite.plugin.extensions.communication.MessageMarshaller; -import org.apache.ignite.spi.discovery.DiscoverySpi; -import org.apache.ignite.spi.discovery.tcp.TestTcpDiscoverySpi; import org.jetbrains.annotations.Nullable; import static org.apache.ignite.testframework.GridTestUtils.loadMarshaller; @@ -78,14 +75,4 @@ public MessagesPluginProvider(Class... msgs) { // Register messages into the communication protocol. registry.registerExtension(MessageFactoryProvider.class, msgFactoryProvider); } - - /** {@inheritDoc} */ - @Override public void start(PluginContext ctx) throws IgniteCheckedException { - DiscoverySpi discoSpi = ctx.igniteConfiguration().getDiscoverySpi(); - - if (discoSpi instanceof TestTcpDiscoverySpi testDiscoSpi) { - // Register messages into the discovery protocol. - testDiscoSpi.messageFactory(msgFactoryProvider); - } - } } diff --git a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/BlockTcpDiscoverySpi.java b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/BlockTcpDiscoverySpi.java index 52e81b05df960..4904df46a40d3 100644 --- a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/BlockTcpDiscoverySpi.java +++ b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/BlockTcpDiscoverySpi.java @@ -70,7 +70,7 @@ private synchronized void apply(ClusterNode addr, TcpDiscoveryAbstractMessage ms long timeout ) throws IOException, IgniteCheckedException { if (spiCtx != null) { - TcpDiscoveryAbstractMessage msg = decodeMessage(this, data); + TcpDiscoveryAbstractMessage msg = decodeMessage(ignite.context(), data); if (msg != null) apply(spiCtx.localNode(), msg); diff --git a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/DiscoveryUnmarshalVulnerabilityTest.java b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/DiscoveryUnmarshalVulnerabilityTest.java index 5a4e18fe8d421..8e8029c295ee5 100644 --- a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/DiscoveryUnmarshalVulnerabilityTest.java +++ b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/DiscoveryUnmarshalVulnerabilityTest.java @@ -245,7 +245,7 @@ void attack(byte[] data) throws IOException { private byte[] serializedMessage() throws IgniteCheckedException { ByteBuffer buf = ByteBuffer.allocate(4096); - MessageFactory msgFactory = ((TcpDiscoverySpi)grid(0).configuration().getDiscoverySpi()).messageFactory(); + MessageFactory msgFactory = grid(0).context().messageFactory(); DirectMessageWriter writer = new DirectMessageWriter(msgFactory); writer.setBuffer(buf); diff --git a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpClientDiscoverySpiSelfTest.java b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpClientDiscoverySpiSelfTest.java index ca96f33f159cf..0d5222fca2d2a 100644 --- a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpClientDiscoverySpiSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpClientDiscoverySpiSelfTest.java @@ -2600,7 +2600,7 @@ private void pauseResumeOperation(boolean isPause, AtomicBoolean... locks) { waitFor(writeLock); - TcpDiscoveryAbstractMessage msg = decodeMessage(this, data); + TcpDiscoveryAbstractMessage msg = decodeMessage(ignite.context(), data); if (msg != null && !onMessage(sock, msg)) return; diff --git a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TestTcpDiscoverySpi.java b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TestTcpDiscoverySpi.java index 866bbc842178b..feedf6a4c6ed5 100644 --- a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TestTcpDiscoverySpi.java +++ b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TestTcpDiscoverySpi.java @@ -26,19 +26,15 @@ import java.util.Arrays; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteException; -import org.apache.ignite.internal.CoreMessagesProvider; -import org.apache.ignite.internal.managers.communication.IgniteMessageFactoryImpl; +import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.managers.discovery.IgniteDiscoverySpiInternalListener; import org.apache.ignite.internal.util.typedef.internal.U; -import org.apache.ignite.plugin.extensions.communication.MessageFactory; -import org.apache.ignite.plugin.extensions.communication.MessageFactoryProvider; import org.apache.ignite.spi.discovery.DiscoverySpiCustomMessage; import org.apache.ignite.spi.discovery.DiscoverySpiListener; import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryAbstractMessage; import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryClientReconnectMessage; import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryJoinRequestMessage; import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryPingResponse; -import org.apache.ignite.testframework.GridTestUtils; import org.apache.ignite.testframework.GridTestUtils.DiscoveryHook; import org.jetbrains.annotations.Nullable; @@ -57,12 +53,6 @@ public class TestTcpDiscoverySpi extends TcpDiscoverySpi implements IgniteDiscov /** */ private IgniteDiscoverySpiInternalListener internalLsnr; - /** */ - private MessageFactory msgFactory; - - /** */ - private MessageFactoryProvider provider; - /** {@inheritDoc} */ @Override protected void writeMessage(TcpDiscoveryIoSession ses, TcpDiscoveryAbstractMessage msg, long timeout) throws IOException, IgniteCheckedException { @@ -119,42 +109,8 @@ public void discoveryHook(DiscoveryHook discoHook) { this.discoHook = discoHook; } - /** - * Sets test discovery messages factory provider. Note that {@link MessageFactoryProvider} must be set before SPI start. - * Otherwise, this method call will take no effect. - * - * @param msgFactoryProvider Discovery messages factory provider. - */ - public void messageFactory(MessageFactoryProvider msgFactoryProvider) { - provider = msgFactoryProvider; - assert !started(); - - msgFactory = new IgniteMessageFactoryImpl(new MessageFactoryProvider[] { - new CoreMessagesProvider(), - msgFactoryProvider - }); - } - - /** {@inheritDoc} */ - @Override public MessageFactoryProvider messageFactoryProvider() { - return provider; - } - - /** {@inheritDoc} */ - @Override protected void initLocalNode(int srvPort, boolean addExtAddrAttr) { - if (msgFactory != null) - GridTestUtils.setFieldValue(this, TcpDiscoverySpi.class, "msgFactory", msgFactory); - - super.initLocalNode(srvPort, addExtAddrAttr); - } - - /** {@inheritDoc} */ - @Override public MessageFactory messageFactory() { - return msgFactory != null ? msgFactory : super.messageFactory(); - } - /** */ - public static @Nullable TcpDiscoveryAbstractMessage decodeMessage(TcpDiscoverySpi spi, byte[] data) { + public static @Nullable TcpDiscoveryAbstractMessage decodeMessage(GridKernalContext ctx, byte[] data) { if (Arrays.equals(U.IGNITE_HEADER, data)) return null; @@ -169,7 +125,7 @@ public void messageFactory(MessageFactoryProvider msgFactoryProvider) { }; try (dataSock) { - return new TcpDiscoveryIoSession(dataSock, spi).readMessage(); + return new TcpDiscoveryIoSession(ctx, dataSock).readMessage(); } catch (Exception e) { throw new IgniteException("Failed to decode a message", e); From 7042aec14cc68e4928644c211da21cc69d296c34 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Mon, 7 Sep 2026 19:42:23 +0300 Subject: [PATCH 03/97] raw --- .../management/snapshot/SnapshotDeleteCommand.java | 5 ++++- .../snapshot/SnapshotDeleteCommandArg.java | 12 ++++++------ 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java index 5433c55938c78..23874389578e9 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java @@ -17,8 +17,11 @@ package org.apache.ignite.internal.management.snapshot; +import java.util.function.Consumer; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotPartitionsVerifyResult; + /** */ -public class SnapshotDeleteCommand extends AbstractSnapshotCommand { +public class SnapshotDeleteCommand extends AbstractSnapshotCommand { /** {@inheritDoc} */ @Override public String description() { return "Delete snapshot and all its increments from the cluster"; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommandArg.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommandArg.java index 7cb0fbe11ecf3..11e864ad84bb3 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommandArg.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommandArg.java @@ -30,7 +30,7 @@ public class SnapshotDeleteCommandArg extends IgniteDataTransferObject { /** */ @Order(0) @Positional - @Argument(description = "Snapshot name") + @Argument(description = "Snapshot name.") String snapshotName; /** */ @@ -38,7 +38,7 @@ public class SnapshotDeleteCommandArg extends IgniteDataTransferObject { @Argument(example = "path", optional = true, description = "Path to the directory where the snapshot is located. " + "If not specified, the default configured snapshot directory will be used") - String dest; + String src; /** */ public String snapshotName() { @@ -51,12 +51,12 @@ public void snapshotName(String snapshotName) { } /** */ - public String dest() { - return dest; + public String src() { + return src; } /** */ - public void dest(String dest) { - this.dest = dest; + public void src(String src) { + this.src = src; } } From 682a21c4ecdd159147253eeec57aa366d89351e0 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Mon, 7 Sep 2026 19:50:42 +0300 Subject: [PATCH 04/97] raw --- .../cache/persistence/snapshot/SnapshotDeleteProcess.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index 8c0f7ce8d0fe0..baaf160cb018f 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -71,7 +71,7 @@ public SnapshotDeleteProcess(GridKernalContext ctx) { log = ctx.log(getClass()); - deleteProc = new DistributedProcess<>(ctx, DELETE_SNAPSHOT, this::deleteLocalSnapshot, this::reduceAndFinish); + deleteProc = new DistributedProcess<>(ctx, DELETE_SNAPSHOT, this::deleteDistributedSnapshot, this::reduceAndFinish); } /** @@ -122,7 +122,7 @@ boolean isSnapshotDeleting() { } /** Local phase: delete the snapshot directory on the node. */ - private IgniteInternalFuture проверь илиdeleteLocalSnapshot( + private IgniteInternalFuture deleteDistributedSnapshot( UUID ignored, SnapshotDeleteProcessRequest req ) { From 3ab443ad455b80d5469cb83a87dbd13894e31225 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Tue, 8 Sep 2026 09:37:18 +0300 Subject: [PATCH 05/97] raw --- .../snapshot/SnapshotDeleteTask.java | 5 +- .../snapshot/IgniteSnapshotManager.java | 59 +++++-------------- .../snapshot/SnapshotDeleteProcess.java | 19 ++---- .../snapshot/SnapshotRestoreProcess.java | 2 +- .../util/distributed/DistributedProcess.java | 2 - 5 files changed, 22 insertions(+), 65 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteTask.java index 515983df92687..ef660d4e8e757 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteTask.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteTask.java @@ -21,10 +21,9 @@ import org.apache.ignite.internal.processors.task.GridInternal; import org.apache.ignite.internal.visor.VisorJob; import org.apache.ignite.internal.visor.VisorOneNodeTask; -import org.jetbrains.annotations.Nullable; /** - * @see IgniteSnapshotManager#deleteSnapshot(String, String) + * @see IgniteSnapshotManager#deleteDistributedSnapshot(String, String) */ @GridInternal public class SnapshotDeleteTask extends VisorOneNodeTask { @@ -53,7 +52,7 @@ protected SnapshotDeleteJob(SnapshotDeleteCommandArg arg, boolean debug) { @Override protected Void run(SnapshotDeleteCommandArg arg) { IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); - snpMgr.deleteSnapshot(arg.snapshotName(), arg.dest()).get(); + snpMgr.deleteDistributedSnapshot(arg.snapshotName(), arg.src()).get(); return null; } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java index c45ec30925c19..bee6eb45f1132 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java @@ -708,14 +708,14 @@ public IgniteSnapshotManager(GridKernalContext ctx) { /** * @param snpDir Snapshot dir. */ - public void deleteSnapshot(File snpDir) { + public void deleteLocalSnapshot(File snpDir) { if (!snpDir.exists()) return; if (!snpDir.isDirectory()) return; - deleteSnapshot(new SnapshotFileTree( + deleteLocalSnapshot(new SnapshotFileTree( cctx.kernalContext(), snpDir.getName(), snpDir.getParent(), @@ -723,35 +723,8 @@ public void deleteSnapshot(File snpDir) { pdsSettings.consistentId().toString())); } - /** - * Deletes the local snapshot directory with the given name and path. - * - * @param snpName Full snapshot name. - * @param snpPath Snapshot directory path. If {@code null}, the default snapshot directory is used. - * @return {@code True} if the snapshot directory existed on the local node and was removed, {@code false} otherwise. - */ - public boolean deleteSnapshotLocal(String snpName, @Nullable String snpPath) { - if (cctx.kernalContext().clientNode()) - throw new UnsupportedOperationException("Client nodes can not perform this operation."); - - if (ft == null) - return false; - - File snpDir = snpPath == null ? new File(ft.snapshotsRoot(), snpName) : new File(snpPath, snpName); - - if (!snpDir.exists()) - return false; - - if (!snpDir.isDirectory()) - return false; - - deleteSnapshot(new SnapshotFileTree(cctx.kernalContext(), snpName, snpPath)); - - return true; - } - /** */ - public void deleteSnapshot(SnapshotFileTree sft) { + public void deleteLocalSnapshot(SnapshotFileTree sft) { try { U.delete(sft.binaryMeta()); sft.allStorages().forEach(U::delete); @@ -1316,7 +1289,7 @@ private IgniteInternalFuture initLocalSnapshotEndStag if (snpStartReq.incremental()) U.delete(snpOp.snapshotFileTree().incrementalSnapshotFileTree(snpStartReq.incrementIndex()).root()); else - deleteSnapshot(snpOp.snapshotFileTree()); + deleteLocalSnapshot(snpOp.snapshotFileTree()); } else if (!F.isEmpty(endReq.warnings())) { // Pass the warnings further to the next stage for the case when snapshot started from not coordinator. @@ -1481,25 +1454,23 @@ public boolean isSnapshotChecking(String snpName) { } /** - * @return {@code True} if a snapshot delete operation is in progress. + * @return {@code True} if a snapshot {@code snpName} delete operation is in progress. */ - public boolean isSnapshotDeleting() { - return deleteSnpProc.isSnapshotDeleting(); + public boolean isSnapshotDeleting(String snpName) { + return deleteSnpProc.isSnapshotDeleting(snpName); } /** - * Deletes the cluster snapshot with the given name. The snapshot data is removed on all the baseline nodes. + * Deletes the cluster-wide snapshot with the given name. *

- * The operation is rejected if a snapshot operation (create, restore or check) is in progress for the snapshot. + * The operation is rejected if a concurrent snapshot operation (create, restore, check, etc...) is in progress + * for the snapshot. * * @param name Snapshot name. - * @param snpPath Snapshot directory path. If {@code null}, the default configured snapshot directory will be used. + * @param snpPath Snapshot directory path. If {@code null}, the default configured snapshot directory is be used. * @return Future which will be completed when the snapshot is deleted on all the baseline nodes. */ - public IgniteFutureImpl deleteSnapshot(String name, @Nullable String snpPath) { - A.notNullOrEmpty(name, "Snapshot name cannot be null or empty."); - A.ensure(U.alphanumericUnderscore(name), "Snapshot name must satisfy the following name pattern: a-zA-Z0-9_"); - + public IgniteFutureImpl deleteDistributedSnapshot(String name, @Nullable String snpPath) { cctx.kernalContext().security().authorize(ADMIN_SNAPSHOT); return deleteSnpProc.start(name, snpPath); @@ -2114,7 +2085,7 @@ public IgniteFutureImpl createSnapshot( ); } - if (isSnapshotDeleting()) { + if (isSnapshotDeleting(name)) { throw new IgniteException( "Snapshot operation has been rejected. Snapshot delete operation is currently in progress." ); @@ -2328,7 +2299,7 @@ public IgniteFutureImpl restoreSnapshot( if (SnapshotFileTree.incrementSnapshotDir(snpDir)) U.delete(snpDir); else - deleteSnapshot(snpDir); + deleteLocalSnapshot(snpDir); } if (log.isInfoEnabled()) { @@ -4022,7 +3993,7 @@ public LocalSnapshotSender(SnapshotFileTree sft) { log.info("The Local snapshot sender closed. All resources released [dbNodeSnpDir=" + sft.nodeStorage() + ']'); } else { - deleteSnapshot(sft); + deleteLocalSnapshot(sft); if (log.isDebugEnabled()) log.debug("Local snapshot sender closed due to an error occurred: " + th.getMessage()); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index baaf160cb018f..f3ef47f8d716b 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -39,9 +39,8 @@ import static org.apache.ignite.internal.util.lang.ClusterNodeFunc.node2id; /** - * Distributed process to delete a cluster snapshot. The snapshot data is spread across the baseline nodes, - * so each node removes its local snapshot directory. The operation is rejected if any of the following - * conflicts is detected: the snapshot is being created, restored or checked. + * Distributed process to delete a cluster snapshot. The operation is rejected if any concurrent snapshot operation is + * active. */ public class SnapshotDeleteProcess { /** Reject operation messages. */ @@ -88,11 +87,9 @@ void interrupt(Throwable err) { * * @param snpName Snapshot name. * @param snpPath Snapshot directory path (optional). - * @return Future that will be completed when the snapshot is deleted on all the baseline nodes. + * @return Future that will be completed when the snapshot is deleted. */ - public IgniteFutureImpl start(String snpName, @Nullable String snpPath) { - assert !F.isEmpty(snpName); - + public IgniteFutureImpl start(String snpName, @Nullable String snpPath) { UUID reqId = UUID.randomUUID(); Set requiredNodes = new HashSet<>( @@ -113,14 +110,6 @@ public IgniteFutureImpl start(String snpName, @Nullable String snpPath) { return new IgniteFutureImpl<>(clusterOpFut); } - /** - * @param snpName Snapshot name. - * @return {@code True} if a delete operation for the snapshot is in progress. - */ - boolean isSnapshotDeleting() { - return !contexts.isEmpty(); - } - /** Local phase: delete the snapshot directory on the node. */ private IgniteInternalFuture deleteDistributedSnapshot( UUID ignored, diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotRestoreProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotRestoreProcess.java index 386189ca6b3f4..d3ea2ad067aaf 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotRestoreProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotRestoreProcess.java @@ -278,7 +278,7 @@ public IgniteFutureImpl start( if (snpMgr.isSnapshotCreating()) throw new IgniteException(OP_REJECT_MSG + "A cluster snapshot operation is in progress."); - if (snpMgr.isSnapshotDeleting()) + if (snpMgr.isSnapshotDeleting(snpName)) throw new IgniteException(OP_REJECT_MSG + "A snapshot delete operation is in progress."); synchronized (this) { diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/distributed/DistributedProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/util/distributed/DistributedProcess.java index 29ba47e379481..add245d24915d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/distributed/DistributedProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/distributed/DistributedProcess.java @@ -502,8 +502,6 @@ public enum DistributedProcessType { /** * Delete snapshot procedure. - * - * @see IgniteSnapshotManager */ DELETE_SNAPSHOT, From 9171c7e6cac4058fb9aa7538c253bc1cc28a83f6 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Tue, 8 Sep 2026 16:55:13 +0300 Subject: [PATCH 06/97] raw --- .../query/calcite/schema/IgniteSchema.java | 18 - .../OperatorsExtensionIntegrationTest.java | 92 +----- .../ignite/util/GridCommandHandlerTest.java | 52 +-- .../ignite/internal/CoreMessagesProvider.java | 4 + .../management/snapshot/SnapshotCommand.java | 1 - .../snapshot/SnapshotDeleteCommand.java | 52 ++- .../snapshot/SnapshotDeleteTask.java | 17 +- .../snapshot/SnapshotListCommand.java | 51 --- .../snapshot/SnapshotListCommandArg.java | 45 --- .../management/snapshot/SnapshotListTask.java | 91 ----- .../GridDhtPartitionsSingleMessage.java | 2 +- .../snapshot/IgniteSnapshotManager.java | 79 +++-- .../snapshot/SnapshotDeleteProcess.java | 239 ++++++++------ .../snapshot/SnapshotDeleteProcessResult.java | 74 +++++ ...equest.java => SnapshotDeleteRequest.java} | 32 +- ...ponse.java => SnapshotDeleteResponse.java} | 25 +- .../feature/SupportedFeatureRegistry.java | 3 + .../ignite/spi/discovery/tcp/ClientImpl.java | 66 ++-- .../ignite/spi/discovery/tcp/ServerImpl.java | 90 ++--- .../spi/discovery/tcp/TcpDiscoveryImpl.java | 19 +- .../discovery/tcp/TcpDiscoveryIoSession.java | 40 +-- .../tcp/TcpDiscoveryMessageSerializer.java | 9 +- .../spi/discovery/tcp/TcpDiscoverySpi.java | 49 ++- .../binary/BinaryClassLoaderMultiJvmTest.java | 49 +-- .../cache/CacheMetricsCacheSizeTest.java | 3 +- .../IgniteClusterSnapshotDeleteTest.java | 311 ++++++++++++++++++ .../ignite/spi/MessagesPluginProvider.java | 13 + .../discovery/tcp/BlockTcpDiscoverySpi.java | 2 +- .../DiscoveryUnmarshalVulnerabilityTest.java | 2 +- .../tcp/TcpClientDiscoverySpiSelfTest.java | 2 +- .../discovery/tcp/TestTcpDiscoverySpi.java | 50 ++- .../tests/ContinuousDataLoadApplication.java | 2 +- 32 files changed, 907 insertions(+), 677 deletions(-) delete mode 100644 modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java delete mode 100644 modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommandArg.java delete mode 100644 modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResult.java rename modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/{SnapshotDeleteProcessRequest.java => SnapshotDeleteRequest.java} (67%) rename modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/{SnapshotDeleteProcessResponse.java => SnapshotDeleteResponse.java} (63%) create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteSchema.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteSchema.java index 445002902d2d2..6d22ac2d51d33 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteSchema.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/schema/IgniteSchema.java @@ -28,7 +28,6 @@ import org.apache.calcite.schema.FunctionParameter; import org.apache.calcite.schema.SchemaPlus; import org.apache.calcite.schema.Table; -import org.apache.calcite.schema.TableMacro; import org.apache.calcite.schema.impl.AbstractSchema; import org.apache.calcite.tools.FrameworkConfig; import org.apache.ignite.IgniteException; @@ -38,12 +37,6 @@ * Ignite schema. */ public class IgniteSchema extends AbstractSchema { - /** */ - private static final String DUAL_TBL_NAME = "DUAL"; - - /** */ - private static final String DUAL_TBL_VIEW = "SELECT * FROM (VALUES ('X')) AS T(DUMMY)"; - /** */ private final String schemaName; @@ -150,17 +143,6 @@ public SchemaPlus register(SchemaPlus parent, FrameworkConfig frameworkCfg) { viewMap.forEach((name, sql) -> newSchema.add(name, new ViewTableMacroImpl(sql, newSchema, frameworkCfg))); - registerDualTableIfSupported(newSchema, frameworkCfg); - return newSchema; } - - /** */ - private static void registerDualTableIfSupported(SchemaPlus schema, FrameworkConfig frameworkCfg) { - if (frameworkCfg != null && frameworkCfg.getSqlValidatorConfig().conformance().isSupportedDualTable() - && schema.tables().get(DUAL_TBL_NAME) == null - && schema.getFunctions(DUAL_TBL_NAME).stream().noneMatch(TableMacro.class::isInstance)) { - schema.add(DUAL_TBL_NAME, new ViewTableMacroImpl(DUAL_TBL_VIEW, schema, frameworkCfg)); - } - } } diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java index 004580b915c45..35a6c1bf96cb9 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java @@ -50,8 +50,6 @@ import org.apache.calcite.sql.type.SqlTypeName; import org.apache.calcite.sql.util.ReflectiveSqlOperatorTable; import org.apache.calcite.sql.util.SqlOperatorTables; -import org.apache.calcite.sql.validate.SqlConformance; -import org.apache.calcite.sql.validate.SqlDelegatingConformance; import org.apache.calcite.sql.validate.SqlValidator; import org.apache.calcite.sql2rel.SqlRexContext; import org.apache.calcite.sql2rel.SqlRexConvertlet; @@ -59,10 +57,7 @@ import org.apache.calcite.tools.Frameworks; import org.apache.calcite.util.BuiltInMethod; import org.apache.calcite.util.Optionality; -import org.apache.ignite.configuration.CacheConfiguration; import org.apache.ignite.configuration.IgniteConfiguration; -import org.apache.ignite.internal.IgniteEx; -import org.apache.ignite.internal.processors.query.IgniteSQLException; import org.apache.ignite.internal.processors.query.calcite.CalciteQueryProcessor; import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext; import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler; @@ -84,22 +79,8 @@ * Tests SQL engine extension with plugin. */ public class OperatorsExtensionIntegrationTest extends AbstractBasicIntegrationTest { - /** */ - private static final SqlConformance TEST_CONFORMANCE = new SqlDelegatingConformance( - CalciteQueryProcessor.FRAMEWORK_CONFIG.getParserConfig().conformance()) { - /** {@inheritDoc} */ - @Override public boolean isSupportedDualTable() { - return true; - } - }; - /** {@inheritDoc} */ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { - return getConfiguration(igniteInstanceName, TEST_CONFORMANCE); - } - - /** */ - private IgniteConfiguration getConfiguration(String igniteInstanceName, SqlConformance conformance) throws Exception { return super.getConfiguration(igniteInstanceName) .setPluginProviders(new AbstractTestPluginProvider() { @Override public String name() { @@ -109,15 +90,12 @@ private IgniteConfiguration getConfiguration(String igniteInstanceName, SqlConfo @Override public @Nullable T createComponent(PluginContext ctx, Class cls) { if (FrameworkConfig.class.equals(cls)) { FrameworkConfig cfg = Frameworks.newConfigBuilder(CalciteQueryProcessor.FRAMEWORK_CONFIG) - .parserConfig(CalciteQueryProcessor.FRAMEWORK_CONFIG.getParserConfig() - .withConformance(conformance)) .convertletTable(new ConvertletTable()) .operatorTable(SqlOperatorTables.chain( new OperatorTable().init(), CalciteQueryProcessor.FRAMEWORK_CONFIG.getOperatorTable())) .sqlValidatorConfig( ((IgniteSqlValidator.Config)CalciteQueryProcessor.FRAMEWORK_CONFIG.getSqlValidatorConfig()) - .withSqlNodeRewriter(new SqlRewriter()) - .withConformance(conformance)) + .withSqlNodeRewriter(new SqlRewriter())) .context(Contexts.chain( CalciteQueryProcessor.FRAMEWORK_CONFIG.getContext(), Contexts.of(IgniteSqlSemantics.builder() @@ -260,74 +238,6 @@ public void testPaginationRoundingPolicy() { .check(); } - /** */ - @Test - public void testDualTable() { - assertQuery("SELECT 1 + 1 FROM dual").returns(2).check(); - - assertQuery("SELECT * FROM DUAL") - .columnNames("DUMMY") - .returns("X") - .check(); - - assertQuery("SELECT DUMMY FROM DUAL") - .columnNames("DUMMY") - .returns("X") - .check(); - - assertQuery("SELECT LAG(rate, 1, rate) OVER (ORDER BY period) FROM " - + "(SELECT 1 AS rate, 1 AS period FROM dual)") - .returns(1) - .check(); - } - - /** */ - @Test - public void testDualWithisFromRequired() throws Exception { - SqlConformance conformance = new SqlDelegatingConformance(TEST_CONFORMANCE) { - /** {@inheritDoc} */ - @Override public boolean isFromRequired() { - return true; - } - }; - - try (IgniteEx c = startClientGrid(getConfiguration("from-required-client", conformance))) { - assertThrows(c, "SELECT 1", IgniteSQLException.class, "SELECT must have a FROM clause"); - - assertQuery(c, "SELECT 1 + 1 FROM dual").returns(2).check(); - } - } - - /** */ - @Test - public void testDualTableInNewSchema() { - client.getOrCreateCache(new CacheConfiguration() - .setName("CUSTOM_SCHEMA_MARKER") - .setSqlSchema("CUSTOM_SCHEMA") - .setIndexedTypes(Integer.class, Integer.class)); - - assertQuery("SELECT DUMMY FROM CUSTOM_SCHEMA.DUAL") - .columnNames("DUMMY") - .returns("X") - .check(); - } - - /** */ - @Test - public void testUserDefinedDualView() { - sql("CREATE VIEW PUBLIC.DUAL AS SELECT 'USER' AS DUMMY"); - - try { - assertQuery("SELECT DUMMY FROM PUBLIC.DUAL") - .columnNames("DUMMY") - .returns("USER") - .check(); - } - finally { - sql("DROP VIEW IF EXISTS PUBLIC.DUAL"); - } - } - /** Rewrites LTRIM with 2 parameters. */ public static SqlCall rewriteLtrim(SqlValidator validator, SqlCall call) { if (call.operandCount() != 2) diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java index 15d2fefac69ea..adc8f7d8a5ef6 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java @@ -30,7 +30,6 @@ import java.util.BitSet; import java.util.Collection; import java.util.Collections; -import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; @@ -240,14 +239,8 @@ public class GridCommandHandlerTest extends GridCommandHandlerClusterPerMethodAb initDiagnosticDir(); - cleanDiagnosticDir(); - } - - /** {@inheritDoc} */ - @Override protected void afterTest() throws Exception { - super.afterTest(); - - listeningLog = null; + // Handy if other test runs interrupted. + cleanPersistenceDir(); } /** {@inheritDoc} */ @@ -3244,43 +3237,12 @@ public void testCheckSnapshot() throws Exception { assertContains(log, sb.toString(), "The check procedure has finished, no conflicts have been found"); } - /** @throws Exception If fails. */ - @Test - public void testSnapshotList() throws Exception { - IgniteEx ig = startGrid(0); - ig.cluster().state(ACTIVE); - - createCacheAndPreload(ig, 100); - - String snpName1 = "snapshot_01012024"; - String snpName2 = "snapshot_02022024"; - - snp(ig).createSnapshot(snpName2).get(getTestTimeout()); - snp(ig).createSnapshot(snpName1).get(getTestTimeout()); - - TestCommandHandler h = newCommandHandler(); - - assertEquals(EXIT_CODE_OK, execute(h, "--snapshot", "list")); - - Collection res = h.getLastOperationResult(); - - assertNotNull(res); - - assertEquals(2, res.size()); - - // Result must be sorted. - Iterator it = res.iterator(); - - assertEquals(snpName1, it.next()); - assertEquals(snpName2, it.next()); - } - - /** @throws Exception If fails. */ + /** */ @Test public void testSnapshotDelete() throws Exception { String snpName = "snapshot_03032024"; - IgniteEx ig = startGrid(0); + IgniteEx ig = (IgniteEx)startGridsMultiThreaded(3); ig.cluster().state(ACTIVE); createCacheAndPreload(ig, 100); @@ -3293,14 +3255,12 @@ public void testSnapshotDelete() throws Exception { injectTestSystemOut(); - TestCommandHandler h = newCommandHandler(); - - assertEquals(EXIT_CODE_OK, execute(h, "--snapshot", "delete", snpName)); + assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "delete", snpName)); assertTrue(waitForCondition(() -> !snpDir.exists(), getTestTimeout())); } - /** @throws Exception If fails. */ + /** */ @Test public void testSnapshotDeleteMissing() throws Exception { IgniteEx ig = startGrid(0); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/CoreMessagesProvider.java b/modules/core/src/main/java/org/apache/ignite/internal/CoreMessagesProvider.java index f7537f0b20f79..b4e1180250c86 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/CoreMessagesProvider.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/CoreMessagesProvider.java @@ -162,6 +162,8 @@ import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotCheckPartitionHashesResponse; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotCheckProcessRequest; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotCheckResponse; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotDeleteResponse; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotDeleteRequest; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotFilesFailureMessage; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotFilesRequestMessage; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotHandlerResult; @@ -437,6 +439,8 @@ public CoreMessagesProvider() { register(SnapshotFilesFailureMessage.class); register(IncrementalSnapshotVerifyResult.class); register(IncrementalSnapshotAwareMessage.class); + register(SnapshotDeleteRequest.class); + register(SnapshotDeleteResponse.class); // [6300 - 6400]: Services messages. Most of them originally come from Discovery. msgIdx = 6300; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCommand.java index 8e1bff06401d0..42b300fb34fdf 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCommand.java @@ -28,7 +28,6 @@ public SnapshotCommand() { new SnapshotCancelCommand(), new SnapshotCheckCommand(), new SnapshotDeleteCommand(), - new SnapshotListCommand(), new SnapshotRestoreCommand(), new SnapshotStatusCommand() ); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java index 23874389578e9..10d23c4cb1697 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java @@ -17,11 +17,15 @@ package org.apache.ignite.internal.management.snapshot; +import java.util.Collection; +import java.util.UUID; import java.util.function.Consumer; -import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotPartitionsVerifyResult; +import java.util.stream.Collectors; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotDeleteProcessResult; +import org.apache.ignite.internal.util.typedef.F; /** */ -public class SnapshotDeleteCommand extends AbstractSnapshotCommand { +public class SnapshotDeleteCommand extends AbstractSnapshotCommand { /** {@inheritDoc} */ @Override public String description() { return "Delete snapshot and all its increments from the cluster"; @@ -37,9 +41,49 @@ public class SnapshotDeleteCommand extends AbstractSnapshotCommand printer) { + boolean found = false; + + if (!F.isEmpty(res.uncompletedNodes())) { + found = true; + + printer.accept("WARNING, the following nodes found snapshot data but might not remove it completely " + + nodeIdsStrLst(res.uncompletedNodes())); + + printer.accept(""); + } + + if (!F.isEmpty(res.completedNodes())) { + found = true; + + printer.accept("Snapshot removed on the following nodes " + nodeIdsStrLst(res.completedNodes())); + + printer.accept(""); + } + + if (found) { + if (!F.isEmpty(res.emptyNodes())) { + printer.accept("NOTE, the following nodes didn't find any snapshot data, nothing to delete " + + nodeIdsStrLst(res.emptyNodes())); + } + } + else { + if (!F.isEmpty(res.emptyNodes())) + printer.accept("Snapshot not found on current server nodes."); + else + printer.accept("Unknown result."); + } + } + + /** */ + private static String nodeIdsStrLst(Collection uuids) { + return "[cnt=" + uuids.size() + "]: " + uuids.stream().map(UUID::toString).collect(Collectors.joining(", ")); + } + /** {@inheritDoc} */ @Override public String confirmationPrompt(SnapshotDeleteCommandArg arg) { - return "Warning: command will delete snapshot " + arg.snapshotName() + " and all its increments " + - "from all the cluster nodes. This operation is irreversible."; + return "Warning: this will delete snapshot '" + arg.snapshotName() + "' and all its increments " + + "from all online server nodes. This operation is irreversible."; } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteTask.java index ef660d4e8e757..2fe709c88c389 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteTask.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteTask.java @@ -17,26 +17,29 @@ package org.apache.ignite.internal.management.snapshot; +import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotDeleteProcessResult; import org.apache.ignite.internal.processors.task.GridInternal; import org.apache.ignite.internal.visor.VisorJob; import org.apache.ignite.internal.visor.VisorOneNodeTask; +import org.jetbrains.annotations.Nullable; /** - * @see IgniteSnapshotManager#deleteDistributedSnapshot(String, String) + * @see IgniteSnapshotManager#deleteSnapshot(String, String) */ @GridInternal -public class SnapshotDeleteTask extends VisorOneNodeTask { +public class SnapshotDeleteTask extends VisorOneNodeTask { /** Serial version uid. */ private static final long serialVersionUID = 0L; /** {@inheritDoc} */ - @Override protected VisorJob job(SnapshotDeleteCommandArg arg) { + @Override protected VisorJob job(SnapshotDeleteCommandArg arg) { return new SnapshotDeleteJob(arg, debug); } /** */ - private static class SnapshotDeleteJob extends SnapshotJob { + private static class SnapshotDeleteJob extends SnapshotJob { /** Serial version uid. */ private static final long serialVersionUID = 0L; @@ -49,12 +52,10 @@ protected SnapshotDeleteJob(SnapshotDeleteCommandArg arg, boolean debug) { } /** {@inheritDoc} */ - @Override protected Void run(SnapshotDeleteCommandArg arg) { + @Override protected SnapshotDeleteProcessResult run(SnapshotDeleteCommandArg arg) { IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); - snpMgr.deleteDistributedSnapshot(arg.snapshotName(), arg.src()).get(); - - return null; + return snpMgr.deleteSnapshot(arg.snapshotName(), arg.src()).get(); } } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java deleted file mode 100644 index 3ece964a70d47..0000000000000 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * 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.ignite.internal.management.snapshot; - -import java.util.Collection; -import java.util.function.Consumer; - -/** */ -public class SnapshotListCommand extends AbstractSnapshotCommand> { - /** {@inheritDoc} */ - @Override public String description() { - return "List all snapshots and their increments in the cluster"; - } - - /** {@inheritDoc} */ - @Override public Class argClass() { - return SnapshotListCommandArg.class; - } - - /** {@inheritDoc} */ - @Override public Class taskClass() { - return SnapshotListTask.class; - } - - /** {@inheritDoc} */ - @Override public void printResult(SnapshotListCommandArg arg, Collection snapshots, Consumer printer) { - if (snapshots.isEmpty()) { - printer.accept("There are no snapshots in the cluster."); - - return; - } - - for (String snpName : snapshots) - printer.accept(snpName); - } -} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommandArg.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommandArg.java deleted file mode 100644 index 22b221b4926b2..0000000000000 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommandArg.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * 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.ignite.internal.management.snapshot; - -import org.apache.ignite.internal.Order; -import org.apache.ignite.internal.dto.IgniteDataTransferObject; -import org.apache.ignite.internal.management.api.Argument; - -/** */ -public class SnapshotListCommandArg extends IgniteDataTransferObject { - /** */ - private static final long serialVersionUID = 0; - - /** */ - @Order(0) - @Argument(example = "path", optional = true, - description = "Path to the directory where the snapshots are located. " + - "If not specified, the default configured snapshot directory will be used") - String dest; - - /** */ - public String dest() { - return dest; - } - - /** */ - public void dest(String dest) { - this.dest = dest; - } -} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java deleted file mode 100644 index 8dfeecbd543fd..0000000000000 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * 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.ignite.internal.management.snapshot; - -import java.util.Collection; -import java.util.List; -import java.util.Set; -import java.util.TreeSet; -import java.util.UUID; -import org.apache.ignite.compute.ComputeJobResult; -import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager; -import org.apache.ignite.internal.processors.task.GridInternal; -import org.apache.ignite.internal.util.typedef.internal.CU; -import org.apache.ignite.internal.visor.VisorJob; -import org.apache.ignite.internal.visor.VisorMultiNodeTask; -import org.apache.ignite.internal.visor.VisorTaskArgument; - -import static org.apache.ignite.internal.util.lang.ClusterNodeFunc.nodeIds; - -/** - * Task to collect the names of all the snapshots existing in the cluster. - */ -@GridInternal -public class SnapshotListTask extends VisorMultiNodeTask, List> { - /** */ - private static final long serialVersionUID = 0L; - - /** {@inheritDoc} */ - @Override protected VisorJob> job(SnapshotListCommandArg arg) { - return new SnapshotListJob(arg, debug); - } - - /** {@inheritDoc} */ - @Override protected Collection jobNodes(VisorTaskArgument arg) { - return nodeIds(ignite.cluster().forServers().nodes()); - } - - /** {@inheritDoc} */ - @Override protected Collection reduce0(List results) { - Set res = new TreeSet<>(); - - for (ComputeJobResult jobRes : results) { - if (jobRes.getException() != null) - throw jobRes.getException(); - - if (jobRes.getData() != null) - res.addAll(jobRes.getData()); - } - - return res; - } - - /** */ - private static class SnapshotListJob extends SnapshotJob> { - /** */ - private static final long serialVersionUID = 0L; - - /** - * @param arg Snapshot list task argument. - * @param debug Flag indicating whether debug information should be printed into node log. - */ - protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { - super(arg, debug); - } - - /** {@inheritDoc} */ - @Override protected List run(SnapshotListCommandArg arg) { - if (!CU.isPersistenceEnabled(ignite.context().config()) || ignite.context().clientNode()) - return java.util.Collections.emptyList(); - - IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); - - return snpMgr.localSnapshotNames(arg.dest()); - } - } -} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsSingleMessage.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsSingleMessage.java index a9f1f42ca872b..1857aabd1f965 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsSingleMessage.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsSingleMessage.java @@ -34,7 +34,7 @@ /** * Information about partitions of a single node. Sent in response to {@link GridDhtPartitionsSingleRequest} and during * processing partitions exchange future.
- * Has to be completelly restored after receiving from another node. + * Has to be completely restored after receiving from another node. * * @see #afterReceive() */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java index bee6eb45f1132..ae5659ce79f39 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java @@ -60,6 +60,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.RejectedExecutionException; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; import java.util.function.BiFunction; @@ -702,7 +703,6 @@ public IgniteSnapshotManager(GridKernalContext ctx) { /** {@inheritDoc} */ @Override public void onDeActivate(GridKernalContext kctx) { restoreCacheGrpProc.interrupt(new IgniteCheckedException("The cluster has been deactivated.")); - deleteSnpProc.interrupt(new IgniteCheckedException("The cluster has been deactivated.")); } /** @@ -715,39 +715,65 @@ public void deleteLocalSnapshot(File snpDir) { if (!snpDir.isDirectory()) return; - deleteLocalSnapshot(new SnapshotFileTree( + var sft = new SnapshotFileTree( cctx.kernalContext(), snpDir.getName(), snpDir.getParent(), ft.folderName(), - pdsSettings.consistentId().toString())); + pdsSettings.consistentId().toString() + ); + + deleteLocalSnapshot(sft, null); + } + + /** + * @param sft Snapshot file tree + * @param existsFlag Flag to set {@code} if any snapshot file or directory is found (exists). If {@code null}, ignored. + * @return {@code True}, if data is found and completely deleted or if no data found; + * {@code False}, if data is found but was deleted not completely. + */ + public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean existsFlag) { + AtomicBoolean res = new AtomicBoolean(true); + + sft.allStorages().forEach(f -> deleteWithIndicators(f, res, existsFlag)); + + deleteWithIndicators(sft.binaryMeta(), res, existsFlag); + deleteWithIndicators(sft.binaryMetaRoot(), res, existsFlag); + deleteWithIndicators(sft.marshaller(), res, existsFlag); + deleteWithIndicators(sft.root(), res, existsFlag); + + return res.get(); } /** */ - public void deleteLocalSnapshot(SnapshotFileTree sft) { - try { - U.delete(sft.binaryMeta()); - sft.allStorages().forEach(U::delete); - U.delete(sft.meta()); + void deleteWithIndicators(@Nullable File f, AtomicBoolean onlyFailRes, @Nullable AtomicBoolean existsFlag) { + if (f == null) + return; + + boolean existed = f.exists(); - deleteDirectory(sft.binaryMetaRoot()); - deleteDirectory(sft.marshaller()); + if (existed && existsFlag != null) + existsFlag.set(true); - // Delete parent dir which is {snapshot_root}/db if empty. - sft.marshaller().getParentFile().delete(); - // Delete root dir which is {snapshot_root} if empty. - sft.root().delete(); + try { + if (existed && ((!f.isDirectory() && !U.delete(f)) || (f.isDirectory() && !deleteDirectory(f))) && f.exists()) + onlyFailRes.set(false); } catch (IOException e) { - throw new IgniteException(e); + onlyFailRes.set(false); } } /** Concurrently traverse the directory and delete all files. */ - private void deleteDirectory(File dir) throws IOException { - Files.walkFileTree(dir.toPath(), new SimpleFileVisitor() { + private boolean deleteDirectory(File dir) throws IOException { + AtomicBoolean res = new AtomicBoolean(true); + + Files.walkFileTree(dir.toPath(), new SimpleFileVisitor<>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { - U.delete(file); + var f = file.toFile(); + + if (f.exists() && !U.delete(file) && f.exists()) + res.set(false); return FileVisitResult.CONTINUE; } @@ -758,7 +784,10 @@ private void deleteDirectory(File dir) throws IOException { } @Override public FileVisitResult postVisitDirectory(Path dir, IOException e) { - dir.toFile().delete(); + var f = dir.toFile(); + + if (f.exists() && !f.delete() && f.exists()) + res.set(false); if (log.isInfoEnabled() && e != null) log.info("Snapshot directory cleaned with an exception [dir=" + dir + ", e=" + e.getMessage() + ']'); @@ -766,6 +795,8 @@ private void deleteDirectory(File dir) throws IOException { return FileVisitResult.CONTINUE; } }); + + return res.get(); } /** @@ -1289,7 +1320,7 @@ private IgniteInternalFuture initLocalSnapshotEndStag if (snpStartReq.incremental()) U.delete(snpOp.snapshotFileTree().incrementalSnapshotFileTree(snpStartReq.incrementIndex()).root()); else - deleteLocalSnapshot(snpOp.snapshotFileTree()); + deleteLocalSnapshot(snpOp.snapshotFileTree(), null); } else if (!F.isEmpty(endReq.warnings())) { // Pass the warnings further to the next stage for the case when snapshot started from not coordinator. @@ -1470,9 +1501,7 @@ public boolean isSnapshotDeleting(String snpName) { * @param snpPath Snapshot directory path. If {@code null}, the default configured snapshot directory is be used. * @return Future which will be completed when the snapshot is deleted on all the baseline nodes. */ - public IgniteFutureImpl deleteDistributedSnapshot(String name, @Nullable String snpPath) { - cctx.kernalContext().security().authorize(ADMIN_SNAPSHOT); - + public IgniteFuture deleteSnapshot(String name, @Nullable String snpPath) { return deleteSnpProc.start(name, snpPath); } @@ -3993,7 +4022,7 @@ public LocalSnapshotSender(SnapshotFileTree sft) { log.info("The Local snapshot sender closed. All resources released [dbNodeSnpDir=" + sft.nodeStorage() + ']'); } else { - deleteLocalSnapshot(sft); + deleteLocalSnapshot(sft, null); if (log.isDebugEnabled()) log.debug("Local snapshot sender closed due to an error occurred: " + th.getMessage()); @@ -4353,7 +4382,7 @@ public CancelSnapshotCallable(UUID reqId, String snpName) { } /** {@inheritDoc} */ - @Override public Boolean call() throws Exception { + @Override public Boolean call() { if (reqId != null) return ignite.context().cache().context().snapshotMgr().cancelLocalSnapshotOperations(reqId); else { diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index f3ef47f8d716b..aa35808c34451 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -17,26 +17,27 @@ package org.apache.ignite.internal.processors.cache.persistence.snapshot; -import java.util.HashSet; +import java.util.ArrayList; import java.util.Map; -import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicBoolean; +import org.apache.ignite.IgniteIllegalStateException; import org.apache.ignite.IgniteLogger; -import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.IgniteInternalFuture; -import org.apache.ignite.internal.cluster.ClusterTopologyCheckedException; +import org.apache.ignite.internal.NodeStoppingException; +import org.apache.ignite.internal.processors.cache.persistence.filename.SnapshotFileTree; import org.apache.ignite.internal.util.distributed.DistributedProcess; import org.apache.ignite.internal.util.future.GridFinishedFuture; import org.apache.ignite.internal.util.future.GridFutureAdapter; import org.apache.ignite.internal.util.future.IgniteFutureImpl; import org.apache.ignite.internal.util.typedef.F; -import org.apache.ignite.internal.util.typedef.internal.CU; +import org.apache.ignite.lang.IgniteFuture; import org.jetbrains.annotations.Nullable; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.DELETE_SNAPSHOT; -import static org.apache.ignite.internal.util.lang.ClusterNodeFunc.node2id; +import static org.apache.ignite.plugin.security.SecurityPermission.ADMIN_SNAPSHOT; /** * Distributed process to delete a cluster snapshot. The operation is rejected if any concurrent snapshot operation is @@ -44,42 +45,35 @@ */ public class SnapshotDeleteProcess { /** Reject operation messages. */ - private static final String OP_REJECT_MSG = "Snapshot deletion was rejected. "; + private static final String OP_REJECT_MSG = "Snapshot deletion was rejected."; /** Kernal context. */ - private final GridKernalContext ctx; + private final GridKernalContext kctx; /** Logger. */ private final IgniteLogger log; - /** Cluster-wide operation contexts per request id. */ + /** */ + private volatile boolean interrupted; - private final Map contexts = new ConcurrentHashMap<>(); + /** Cluster-wide operation futures per request id on certain node. */ + private final Map> clusterOpFuts = new ConcurrentHashMap<>(); - /** Delete snapshot phase subprocess. */ - private final DistributedProcess deleteProc; + /** Process requests per id on eah baseline node. */ + private final Map requests = new ConcurrentHashMap<>(); - /** Stop node lock. */ - private boolean nodeStopping; + /** The distributed process. */ + private final DistributedProcess distrProc; /** * @param ctx Kernal context. */ public SnapshotDeleteProcess(GridKernalContext ctx) { - this.ctx = ctx; + this.kctx = ctx; log = ctx.log(getClass()); - deleteProc = new DistributedProcess<>(ctx, DELETE_SNAPSHOT, this::deleteDistributedSnapshot, this::reduceAndFinish); - } - - /** - * Stops all the running processes with the provided exception. - * - * @param err The interrupt reason. - */ - void interrupt(Throwable err) { - contexts.forEach((reqId, c) -> c.fut.onDone(err)); + distrProc = new DistributedProcess<>(ctx, DELETE_SNAPSHOT, this::deletePhase, this::reducePhase); } /** @@ -89,125 +83,158 @@ void interrupt(Throwable err) { * @param snpPath Snapshot directory path (optional). * @return Future that will be completed when the snapshot is deleted. */ - public IgniteFutureImpl start(String snpName, @Nullable String snpPath) { + public IgniteFuture start(String snpName, @Nullable String snpPath) { UUID reqId = UUID.randomUUID(); - Set requiredNodes = new HashSet<>( - F.viewReadOnly(ctx.discovery().discoCache().aliveBaselineNodes(), node2id())); + var clusterOpFut = new GridFutureAdapter(); - SnapshotDeleteProcessRequest req = new SnapshotDeleteProcessRequest(reqId, snpName, snpPath, requiredNodes); + clusterOpFut.listen(fut -> clusterOpFuts.remove(reqId)); - GridFutureAdapter clusterOpFut = new GridFutureAdapter<>(); + try { + synchronized (clusterOpFuts) { + if (interrupted || kctx.isStopping()) + throw new NodeStoppingException("Failed to start snapshot delete process: node is stopping."); - DeleteContext dctx = new DeleteContext(req, clusterOpFut); + clusterOpFuts.put(reqId, clusterOpFut); + } - contexts.put(reqId, dctx); + SnapshotDeleteRequest req = new SnapshotDeleteRequest(reqId, snpName, snpPath); - clusterOpFut.listen(fut -> contexts.remove(reqId)); + distrProc.start(reqId, req); + } + catch (Throwable t) { + log.error("Failed to start distributed delete snapshot process [snpName=" + snpName + ", snpPath=" + snpPath + ']', t); - deleteProc.start(reqId, req); + clusterOpFut.onDone(t); + } return new IgniteFutureImpl<>(clusterOpFut); } - /** Local phase: delete the snapshot directory on the node. */ - private IgniteInternalFuture deleteDistributedSnapshot( - UUID ignored, - SnapshotDeleteProcessRequest req - ) { - if (!baseline(ctx.localNodeId())) - return new GridFinishedFuture<>(new SnapshotDeleteProcessResponse(false)); - - IgniteSnapshotManager snpMgr = ctx.cache().context().snapshotMgr(); + /** Local phase: delete the snapshot directory on a node. */ + private IgniteInternalFuture deletePhase(UUID ignored, SnapshotDeleteRequest req) { + if (kctx.isStopping()) { + return new GridFinishedFuture<>(new NodeStoppingException(OP_REJECT_MSG + + " Node is stopping [req=" + req + ']')); + } - String snpName = req.snapshotName(); + if (kctx.clientNode()) + return new GridFinishedFuture<>(new SnapshotDeleteResponse(-1)); - if (snpMgr.isSnapshotCreating()) - return new GridFinishedFuture<>(new ClusterTopologyCheckedException( - OP_REJECT_MSG + "a snapshot operation is in progress [snapshot=" + snpName + ']')); + kctx.security().authorize(ADMIN_SNAPSHOT); - if (snpMgr.isRestoring(snpName)) - return new GridFinishedFuture<>(new ClusterTopologyCheckedException( - OP_REJECT_MSG + "the snapshot is being restored [snapshot=" + snpName + ']')); + IgniteSnapshotManager snpMgr = kctx.cache().context().snapshotMgr(); - if (snpMgr.isSnapshotChecking(snpName)) - return new GridFinishedFuture<>(new ClusterTopologyCheckedException( - OP_REJECT_MSG + "the snapshot is being checked [snapshot=" + snpName + ']')); + var curCreateRq = snpMgr.currentCreateRequest(); - boolean deleted = snpMgr.deleteSnapshotLocal(snpName, req.snapshotPath()); + if (curCreateRq != null && curCreateRq.snpName.equals(req.snpName)) { + return new GridFinishedFuture<>(new IllegalStateException(OP_REJECT_MSG + + " Snapshot with this name is being created [req=" + req + ']')); + } - if (log.isInfoEnabled()) { - log.info("Snapshot delete operation [snapshot=" + snpName + - ", snpPath=" + req.snapshotPath() + ", node=" + ctx.localNodeId() + ", deleted=" + deleted + ']'); + if (snpMgr.isRestoring(req.snpName)) { + return new GridFinishedFuture<>(new IllegalStateException(OP_REJECT_MSG + + " Snapshot with this name is being restored [req=" + req + ']')); } - return new GridFinishedFuture<>(new SnapshotDeleteProcessResponse(deleted)); - } + if (snpMgr.isSnapshotChecking(req.snpName)) { + return new GridFinishedFuture<>(new IllegalStateException(OP_REJECT_MSG + + " Snapshot with this name is being checked [req=" + req + ']')); + } - /** Coordinator finish: aggregate node results and complete the user future. */ - private void reduceAndFinish( - UUID reqId, - Map results, - Map errors - ) { - DeleteContext dctx = contexts.get(reqId); - - if (dctx == null) - return; + if (requests.putIfAbsent(req.reqId, req) != null) { + return new GridFinishedFuture<>(new IllegalStateException("Deletion of the snapshot has already started [req=" + + req + ']')); + } try { - if (!errors.isEmpty()) - throw F.firstValue(errors); + var foundFlag = new AtomicBoolean(); - ClusterTopologyCheckedException ex = checkNodeLeft(dctx.req.nodes(), results.keySet()); + boolean deleted = snpMgr.deleteLocalSnapshot(new SnapshotFileTree(kctx, req.snpName, req.snpPath), foundFlag); - if (ex != null) - throw ex; + if (deleted && log.isInfoEnabled()) + log.info("Snapshot successfully deleted, req=" + req); + else if (!deleted) + log.warning("Snapshot deleted not completely, req=" + req); - dctx.fut.onDone(); + return new GridFinishedFuture<>(new SnapshotDeleteResponse(deleted ? 0 : 1)); } - catch (Throwable th) { - dctx.fut.onDone(th); + catch (Throwable t) { + log.error("An error occured during snapshot deletion, req=" + req, t); + + return new GridFinishedFuture<>(t); + } finally { + requests.remove(req.reqId); } } - /** - * @param reqNodes Set of required topology nodes. - * @param respNodes Set of responded topology nodes. - * @return Error, if no response was received from a required topology node. - */ - private static @Nullable ClusterTopologyCheckedException checkNodeLeft(Set reqNodes, Set respNodes) { - if (!respNodes.containsAll(reqNodes)) { - Set leftNodes = new HashSet<>(reqNodes); + /** Coordinator finish: aggregate node results and complete the user future. */ + private void reducePhase(UUID reqId, Map results, Map errors) { + var clusterOpFut = clusterOpFuts.get(reqId); - leftNodes.removeAll(respNodes); + if (clusterOpFut == null) + return; - return new ClusterTopologyCheckedException("Snapshot deletion stopped. " + - "Required node has left the cluster [nodeId=" + leftNodes + ']'); + try { + var errP = F.isEmpty(errors) ? null : F.first(errors.entrySet()); + + if (errP != null) { + log.warning("Snapshot deletion finished with an error [reqId=" + reqId + ", nodeId=" + + errP.getKey() + ", err='" + errP.getValue().getMessage() + "']", errP.getValue()); + + clusterOpFut.onDone(errP.getValue()); + + return; + } + + var completedNodes = new ArrayList(results.size()); + var uncompletedNodes = new ArrayList(results.size()); + var emptyNodes = new ArrayList(results.size()); + + results.forEach((nodeId, nodeRes) -> { + switch (nodeRes.deleted) { + case -1: + emptyNodes.add(nodeId); + break; + case 0: + completedNodes.add(nodeId); + break; + case 1: + uncompletedNodes.add(nodeId); + break; + default: + throw new IgniteIllegalStateException("Unknown snapshot deletion node result [nodeRes" + nodeRes + + ", nodeId=" + nodeId + ']'); + } + }); + + clusterOpFut.onDone(new SnapshotDeleteProcessResult( + completedNodes.isEmpty() ? null : completedNodes, + uncompletedNodes.isEmpty() ? null : uncompletedNodes, + emptyNodes.isEmpty() ? null : emptyNodes + )); + } + catch (Throwable t) { + clusterOpFut.onDone(t); } - - return null; } - /** @return {@code True} if the local node is a baseline node. */ - private boolean baseline(UUID nodeId) { - ClusterNode node = ctx.cluster().get().node(nodeId); - - return node != null && CU.baselineNode(node, ctx.state().clusterState()); + /** */ + public boolean isSnapshotDeleting(String name) { + return false; } - /** Delete operation context. */ - private static final class DeleteContext { - /** Request. */ - private final SnapshotDeleteProcessRequest req; + /** + * @param err The interrupt reason. + */ + void interrupt(Throwable err) { + // Prevents starting new processes in #prepareAndCheckMetas. + synchronized (clusterOpFuts) { + interrupted = true; + } - /** Cluster operation future. */ - private final GridFutureAdapter fut; + clusterOpFuts.forEach((reqId, fut) -> fut.onDone(err)); - /** */ - private DeleteContext(SnapshotDeleteProcessRequest req, GridFutureAdapter fut) { - this.req = req; - this.fut = fut; - } + clusterOpFuts.clear();; } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResult.java new file mode 100644 index 0000000000000..67cad1e5d6718 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResult.java @@ -0,0 +1,74 @@ +/* + * 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.ignite.internal.processors.cache.persistence.snapshot; + +import java.util.Collection; +import java.util.UUID; +import org.apache.ignite.internal.Order; +import org.apache.ignite.internal.dto.IgniteDataTransferObject; +import org.apache.ignite.plugin.extensions.communication.MessageFactory; +import org.jetbrains.annotations.Nullable; + +/** Result of snapshot delete process. */ +public final class SnapshotDeleteProcessResult extends IgniteDataTransferObject { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** Nodes which found shapshot data and completely removed it. */ + @Order(0) + @Nullable Collection completedNodes; + + /** Nodes which found shapshot data but didn't remove it completely. */ + @Order(1) + @Nullable Collection uncompletedNodes; + + /** Server nodes which didn't found any shapshot data. */ + @Order(2) + @Nullable Collection emptyNodes; + + /** Default constructor for {@link MessageFactory}. */ + public SnapshotDeleteProcessResult() { + // No-op. + } + + /** */ + public SnapshotDeleteProcessResult( + @Nullable Collection completedNodes, + @Nullable Collection uncompletedNodes, + @Nullable Collection emptyNodes + ) { + this.completedNodes = completedNodes; + this.uncompletedNodes = uncompletedNodes; + this.emptyNodes = emptyNodes; + } + + /** */ + public @Nullable Collection completedNodes() { + return completedNodes; + } + + /** */ + public @Nullable Collection uncompletedNodes() { + return uncompletedNodes; + } + + /** */ + public @Nullable Collection emptyNodes() { + return emptyNodes; + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteRequest.java similarity index 67% rename from modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessRequest.java rename to modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteRequest.java index 8370c61add273..ec8d3830ebed1 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessRequest.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteRequest.java @@ -17,20 +17,33 @@ package org.apache.ignite.internal.processors.cache.persistence.snapshot; -import java.util.Collection; import java.util.UUID; -import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.Order; import org.apache.ignite.internal.util.typedef.internal.S; +import org.apache.ignite.plugin.extensions.communication.Message; import org.apache.ignite.plugin.extensions.communication.MessageFactory; +import org.jetbrains.annotations.Nullable; /** * Cluster snapshot delete distributed process request. * * @see SnapshotDeleteProcess */ -public class SnapshotDeleteProcessRequest extends AbstractSnapshotOperationRequest { +public class SnapshotDeleteRequest implements Message { + /** Request ID. */ + @Order(0) + UUID reqId; + + /** Snapshot name. */ + @Order(1) + String snpName; + + /** Snapshot directory path. */ + @Order(2) + @Nullable String snpPath; + /** Default constructor for {@link MessageFactory}. */ - public SnapshotDeleteProcessRequest() { + public SnapshotDeleteRequest() { // No-op. } @@ -38,16 +51,15 @@ public SnapshotDeleteProcessRequest() { * @param reqId Request ID. * @param snpName Snapshot name. * @param snpPath Snapshot directory path. - * @param nodes Baseline node IDs that must be alive to complete the operation. */ - SnapshotDeleteProcessRequest(UUID reqId, String snpName, String snpPath, Collection nodes) { - super(reqId, snpName, snpPath, null, nodes); - - assert !F.isEmpty(nodes); + SnapshotDeleteRequest(UUID reqId, String snpName, @Nullable String snpPath) { + this.reqId = reqId; + this.snpName = snpName; + this.snpPath = snpPath; } /** {@inheritDoc} */ @Override public String toString() { - return S.toString(SnapshotDeleteProcessRequest.class, this, super.toString()); + return S.toString(SnapshotDeleteRequest.class, this, super.toString()); } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResponse.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java similarity index 63% rename from modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResponse.java rename to modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java index 8beb97f0ad218..04d5b1c2a7e5d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResponse.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java @@ -23,34 +23,33 @@ import org.apache.ignite.plugin.extensions.communication.MessageFactory; /** - * Single-node result of the snapshot delete distributed process. + * Single-node result of the snapshot deletion distributed process. * * @see SnapshotDeleteProcess */ -public class SnapshotDeleteProcessResponse implements Message { - /** {@code True} if the snapshot directory was actually removed on the node. */ +public class SnapshotDeleteResponse implements Message { + /** The result. If {@code -1}, if node isn't a server node. {@code 0} if the snapshot was completely + * removed on the node. {@code 1}, if snapshot was removed on the node not completely. */ @Order(0) - boolean deleted; + byte deleted; /** Default constructor for {@link MessageFactory}. */ - public SnapshotDeleteProcessResponse() { + public SnapshotDeleteResponse() { // No-op. } /** - * @param deleted {@code True} if the snapshot directory was actually removed on the node. + * @param deleted If {@code -1}, if node isn't a server node. {@code 1} if the snapshot was completely + * removed on the node. {@code 0}, if snapshot was removed on the node not completely. */ - public SnapshotDeleteProcessResponse(boolean deleted) { - this.deleted = deleted; - } + SnapshotDeleteResponse(int deleted) { + assert deleted >= -1 && deleted < 2; - /** @return {@code True} if the snapshot directory was actually removed on the node. */ - public boolean deleted() { - return deleted; + this.deleted = (byte)deleted; } /** {@inheritDoc} */ @Override public String toString() { - return S.toString(SnapshotDeleteProcessResponse.class, this); + return S.toString(SnapshotDeleteResponse.class, this); } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/SupportedFeatureRegistry.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/SupportedFeatureRegistry.java index 7b3e55b85d3c4..a14e52331cda0 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/SupportedFeatureRegistry.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/SupportedFeatureRegistry.java @@ -93,4 +93,7 @@ public class SupportedFeatureRegistry { /** */ public static final IgniteFeature ROLLING_UPGRADE_FEATURE = new IgniteCoreFeature(0); + + /** */ + public static final IgniteFeature SNAPSHOT_DELETE_FEATURE = new IgniteCoreFeature(1); } diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ClientImpl.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ClientImpl.java index dd1aed7a2a24f..ce47380990511 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ClientImpl.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ClientImpl.java @@ -47,6 +47,7 @@ import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.SSLException; +import org.apache.ignite.Ignite; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteClientDisconnectedException; import org.apache.ignite.IgniteException; @@ -59,6 +60,7 @@ import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.failure.FailureContext; import org.apache.ignite.internal.IgniteClientDisconnectedCheckedException; +import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.IgniteInterruptedCheckedException; import org.apache.ignite.internal.IgniteNodeAttributes; import org.apache.ignite.internal.managers.discovery.DiscoveryServerOnlyCustomMessage; @@ -71,6 +73,7 @@ import org.apache.ignite.internal.util.typedef.internal.LT; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.internal.util.worker.GridWorker; +import org.apache.ignite.internal.worker.WorkersRegistry; import org.apache.ignite.lang.IgniteInClosure; import org.apache.ignite.lang.IgniteUuid; import org.apache.ignite.spi.IgniteSpiAdapter; @@ -205,9 +208,10 @@ class ClientImpl extends TcpDiscoveryImpl { ClientImpl(TcpDiscoverySpi adapter) { super(adapter); - String instanceName = ctx.igniteInstanceName(); + String instanceName = adapter.ignite() == null || adapter.ignite().name() == null + ? "client-node" : adapter.ignite().name(); - executorSrvc = newSingleThreadScheduledExecutor("tcp-discovery-exec", instanceName == null ? "client-node" : instanceName); + executorSrvc = newSingleThreadScheduledExecutor("tcp-discovery-exec", instanceName); } /** {@inheritDoc} */ @@ -244,9 +248,9 @@ class ClientImpl extends TcpDiscoveryImpl { /** {@inheritDoc} */ @Override public void dumpRingStructure(IgniteLogger log) { ClusterNode[] serverNodes = remoteVisibleNodes().stream() - .filter(node -> !node.isClient()) - .sorted(Comparator.comparingLong(ClusterNode::order)) - .toArray(ClusterNode[]::new); + .filter(node -> !node.isClient()) + .sorted(Comparator.comparingLong(ClusterNode::order)) + .toArray(ClusterNode[]::new); U.quietAndInfo(log, Arrays.toString(serverNodes)); } @@ -556,11 +560,11 @@ else if (state == DISCONNECTED) { return null; LT.warn(log, "IP finder returned empty addresses list. " + - "Please check IP finder configuration" + - (spi.ipFinder instanceof TcpDiscoveryMulticastIpFinder ? - " and make sure multicast works on your network. " : ". ") + - "Will retry every " + spi.getReconnectDelay() + " ms. " + - "Change 'reconnectDelay' to configure the frequency of retries.", true); + "Please check IP finder configuration" + + (spi.ipFinder instanceof TcpDiscoveryMulticastIpFinder ? + " and make sure multicast works on your network. " : ". ") + + "Will retry every " + spi.getReconnectDelay() + " ms. " + + "Change 'reconnectDelay' to configure the frequency of retries.", true); sleepEx(spi.getReconnectDelay(), beforeEachSleep, afterEachSleep); } @@ -907,9 +911,9 @@ private Collection updateTopologyHistory(long topVer, @Nullable Tcp if (!topHist.containsKey(topVer)) { assert topHist.isEmpty() || topHist.lastKey() == topVer - 1 : "lastVer=" + (topHist.isEmpty() ? null : topHist.lastKey()) + - ", newVer=" + topVer + - ", locNode=" + locNode + - ", msg=" + msg; + ", newVer=" + topVer + + ", locNode=" + locNode + + ", msg=" + msg; topHist.put(topVer, allNodes); @@ -1048,6 +1052,13 @@ private void joinError(IgniteSpiException err) { joinLatch.countDown(); } + /** */ + private WorkersRegistry getWorkersRegistry() { + Ignite ignite = spi.ignite(); + + return ignite instanceof IgniteEx ? ((IgniteEx)ignite).context().workersRegistry() : null; + } + /** */ private Collection remoteVisibleNodes() { return U.arrayList(rmtNodes.values(), TcpDiscoveryNodesRing.VISIBLE_NODES); @@ -1090,7 +1101,7 @@ private class SocketReader extends IgniteSpiThread { /** */ SocketReader() { - super(ctx.igniteInstanceName(), "tcp-client-disco-sock-reader-[]", log); + super(spi.ignite().name(), "tcp-client-disco-sock-reader-[]", log); } /** @@ -1263,7 +1274,7 @@ private class SocketWriter extends IgniteSpiThread { * */ SocketWriter() { - super(ctx.igniteInstanceName(), "tcp-client-disco-sock-writer", log); + super(spi.ignite().name(), "tcp-client-disco-sock-writer", log); sockTimeout = spi.failureDetectionTimeoutEnabled() ? spi.failureDetectionTimeout() : spi.getSocketTimeout(); @@ -1438,10 +1449,10 @@ void ackReceived(TcpDiscoveryClientAckResponse res) { if (unacked != null) { if (log.isDebugEnabled()) log.debug("Failed to get acknowledge for message, will try to reconnect " + - "[msg=" + unacked + - (spi.failureDetectionTimeoutEnabled() ? - ", failureDetectionTimeout=" + spi.failureDetectionTimeout() : - ", timeout=" + spi.getAckTimeout()) + ']'); + "[msg=" + unacked + + (spi.failureDetectionTimeoutEnabled() ? + ", failureDetectionTimeout=" + spi.failureDetectionTimeout() : + ", timeout=" + spi.getAckTimeout()) + ']'); throw new IOException("Failed to get acknowledge for message: " + unacked); } @@ -1513,7 +1524,7 @@ private class Reconnector extends IgniteSpiThread { * @param prevAddr Address of the node, that this client was previously connected to. */ protected Reconnector(boolean join, InetSocketAddress prevAddr) { - super(ctx.igniteInstanceName(), "tcp-client-disco-reconnector", log); + super(spi.ignite().name(), "tcp-client-disco-reconnector", log); this.join = join; this.prevAddr = prevAddr; @@ -1678,7 +1689,7 @@ protected class MessageWorker extends GridWorker { * @param log Logger. */ private MessageWorker(IgniteLogger log) { - super(ctx.igniteInstanceName(), "tcp-client-disco-msg-worker", log, ctx.workersRegistry()); + super(spi.ignite().name(), "tcp-client-disco-msg-worker", log, getWorkersRegistry()); } /** {@inheritDoc} */ @@ -1946,7 +1957,8 @@ else if (discoMsg instanceof TcpDiscoveryCheckFailedMessage) Thread.currentThread().interrupt(); } catch (Throwable t) { - ctx.failure().process(new FailureContext(CRITICAL_ERROR, t)); + if (spi.ignite() instanceof IgniteEx) + ((IgniteEx)spi.ignite()).context().failure().process(new FailureContext(CRITICAL_ERROR, t)); } finally { TcpDiscoveryIoSession ses = this.currSes; @@ -2117,8 +2129,8 @@ else if (msg instanceof TcpDiscoveryPingRequest) spi.stats.onMessageProcessingFinished(msg); if (spi.ensured(msg) - && state == CONNECTED - && !(msg instanceof TcpDiscoveryClientReconnectMessage)) + && state == CONNECTED + && !(msg instanceof TcpDiscoveryClientReconnectMessage)) lastMsgId = msg.id(); } @@ -2198,7 +2210,7 @@ else if (log.isDebugEnabled()) if (joining()) delayDiscoData.add(dataPacket); else - spi.onExchange(dataPacket, U.resolveClassLoader(ctx.config())); + spi.onExchange(dataPacket, U.resolveClassLoader(spi.ignite().configuration())); } } } @@ -2232,11 +2244,11 @@ private void processNodeAddFinishedMessage(TcpDiscoveryNodeAddFinishedMessage ms DiscoveryDataPacket dataContainer = msg.clientDiscoData(); if (dataContainer != null) - spi.onExchange(dataContainer, U.resolveClassLoader(ctx.config())); + spi.onExchange(dataContainer, U.resolveClassLoader(spi.ignite().configuration())); if (!delayDiscoData.isEmpty()) { for (DiscoveryDataPacket data : delayDiscoData) - spi.onExchange(data, U.resolveClassLoader(ctx.config())); + spi.onExchange(data, U.resolveClassLoader(spi.ignite().configuration())); delayDiscoData.clear(); } diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ServerImpl.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ServerImpl.java index 5974e61780743..42266d8ed08ae 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ServerImpl.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ServerImpl.java @@ -76,6 +76,7 @@ import org.apache.ignite.events.NodeValidationFailedEvent; import org.apache.ignite.failure.FailureContext; import org.apache.ignite.internal.ClusterMetricsSnapshot; +import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.IgniteFutureTimeoutCheckedException; import org.apache.ignite.internal.IgniteInterruptedCheckedException; import org.apache.ignite.internal.IgniteNodeAttributes; @@ -106,6 +107,7 @@ import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.internal.util.worker.GridWorker; import org.apache.ignite.internal.util.worker.GridWorkerListener; +import org.apache.ignite.internal.worker.WorkersRegistry; import org.apache.ignite.lang.IgniteBiTuple; import org.apache.ignite.lang.IgniteFuture; import org.apache.ignite.lang.IgniteInClosure; @@ -360,14 +362,14 @@ class ServerImpl extends TcpDiscoveryImpl { super(adapter); utilityPool = new IgniteThreadPoolExecutor("disco-pool", - ctx.igniteInstanceName(), + spi.ignite().name(), 0, utilityPoolSize, 2000, new LinkedBlockingQueue<>()); List props = newConnectionEnabledProperty( - ctx.internalSubscriptionProcessor(), + ((IgniteEx)spi.ignite()).context().internalSubscriptionProcessor(), log, "ClientNode", "ServerNode" @@ -2247,7 +2249,7 @@ private class IpFinderCleaner extends IgniteSpiThread { * Constructor. */ private IpFinderCleaner() { - super(ctx.igniteInstanceName(), "tcp-disco-ip-finder-cleaner", log); + super(spi.ignite().name(), "tcp-disco-ip-finder-cleaner", log); setPriority(spi.threadPri); } @@ -2456,6 +2458,11 @@ private static void enrichNodeWithAttribute(TcpDiscoveryNode node, String attrNa node.setAttributes(attrs); } + /** */ + private static WorkersRegistry getWorkerRegistry(TcpDiscoverySpi spi) { + return spi.ignite() instanceof IgniteEx ? ((IgniteEx)spi.ignite()).context().workersRegistry() : null; + } + /** * Upcasts collection type. * @@ -2859,7 +2866,7 @@ protected class RingMessageWorker extends MessageWorker queue) { - super("tcp-disco-msg-worker-[]", log, 10, ctx.workersRegistry(), queue); + super("tcp-disco-msg-worker-[]", log, 10, getWorkerRegistry(spi), queue); setBeforeEachPollAction(() -> { updateHeartbeat(); @@ -3042,15 +3049,17 @@ private void addToQueue(TcpDiscoveryAbstractMessage msg, boolean addFirst) { throw e; } finally { - if (err == null && !spi.isNodeStopping0() && spiStateCopy() != DISCONNECTING) - err = new IllegalStateException("Worker " + name() + " is terminated unexpectedly."); + if (spi.ignite() instanceof IgniteEx) { + if (err == null && !spi.isNodeStopping0() && spiStateCopy() != DISCONNECTING) + err = new IllegalStateException("Worker " + name() + " is terminated unexpectedly."); - FailureProcessor failure = ctx.failure(); + FailureProcessor failure = ((IgniteEx)spi.ignite()).context().failure(); - if (err instanceof OutOfMemoryError) - failure.process(new FailureContext(CRITICAL_ERROR, err)); - else if (err != null) - failure.process(new FailureContext(SYSTEM_WORKER_TERMINATION, err)); + if (err instanceof OutOfMemoryError) + failure.process(new FailureContext(CRITICAL_ERROR, err)); + else if (err != null) + failure.process(new FailureContext(SYSTEM_WORKER_TERMINATION, err)); + } } } @@ -3204,7 +3213,7 @@ private void processAuthFailedMessage(TcpDiscoveryAuthFailedMessage authFailedMs catch (IgniteSpiException ex) { log.warning( "Skipping send auth failed message to client due to some trouble with connection detected: " - + ex.getMessage() + + ex.getMessage() ); } } @@ -3850,8 +3859,8 @@ else if (!failedNextNode && sndState != null && sndState.isBackward()) { } LT.warn(log, "Local node has detected failed nodes and started cluster-wide procedure. " + - "To speed up failure detection please see 'Failure Detection' section under javadoc" + - " for 'TcpDiscoverySpi'"); + "To speed up failure detection please see 'Failure Detection' section under javadoc" + + " for 'TcpDiscoverySpi'"); } } @@ -4582,7 +4591,8 @@ private IgniteNodeValidationResult validateByIgniteComponentsWithJoiningNodeData DiscoveryDataPacket packet = req.gridDiscoveryData(); try { - DiscoveryDataBag dataBag = packet.bagWithJoiningNodeData(log, ctx.config().isClientMode()); + DiscoveryDataBag dataBag = packet.bagWithJoiningNodeData(spi.ignite().log(), + spi.ignite().configuration().isClientMode()); return spi.getSpiContext().validateNode(req.node(), dataBag); } @@ -4767,7 +4777,7 @@ private void processNodeAddedMessage(TcpDiscoveryNodeAddedMessage msg) { if (node.internalOrder() < locNode.internalOrder()) { if (!locNode.id().equals(node.id())) { U.warn(log, "Discarding node added message since local node's order is greater " + - "[node=" + node + ", ring=" + ring + ", msg=" + msg + ']'); + "[node=" + node + ", ring=" + ring + ", msg=" + msg + ']'); return; } @@ -4926,7 +4936,7 @@ else if (!locNodeId.equals(node.id()) && ring.node(node.id()) != null) { if (dataPacket.hasJoiningNodeData()) { if (spiState == CONNECTED) { // Node already connected to the cluster can apply joining nodes' disco data immediately - spi.onExchange(dataPacket, U.resolveClassLoader(ctx.config())); + spi.onExchange(dataPacket, U.resolveClassLoader(spi.ignite().configuration())); spi.collectExchangeData(dataPacket); } @@ -5144,11 +5154,11 @@ private void processNodeAddFinishedMessage(TcpDiscoveryNodeAddFinishedMessage ms } if (gridDiscoveryData != null) - spi.onExchange(gridDiscoveryData, U.resolveClassLoader(ctx.config())); + spi.onExchange(gridDiscoveryData, U.resolveClassLoader(spi.ignite().configuration())); if (joiningNodesDiscoDataList != null) { for (DiscoveryDataPacket dataPacket : joiningNodesDiscoDataList) - spi.onExchange(dataPacket, U.resolveClassLoader(ctx.config())); + spi.onExchange(dataPacket, U.resolveClassLoader(spi.ignite().configuration())); } nullifyDiscoData(); @@ -6130,7 +6140,7 @@ private void notifyDiscoveryListener(TcpDiscoveryCustomEventMessage msg, boolean snapshot, hist, customMsg) - ); + ); notifiedDiscovery.set(true); @@ -6300,7 +6310,7 @@ private class TcpServer extends GridWorker { * @throws IgniteSpiException In case of error. */ TcpServer(IgniteLogger log) throws IgniteSpiException { - super(ctx.igniteInstanceName(), "tcp-disco-srvr-[]", log, ctx.workersRegistry()); + super(spi.ignite().name(), "tcp-disco-srvr-[]", log, getWorkerRegistry(spi)); int lastPort = spi.locPortRange == 0 ? spi.locPort : spi.locPort + spi.locPortRange - 1; @@ -6320,7 +6330,7 @@ private class TcpServer extends GridWorker { if (log.isInfoEnabled()) { log.info("Successfully bound to TCP port [port=" + port + ", localHost=" + spi.locHost + - ", locNodeId=" + spi.cfgNodeId + + ", locNodeId=" + spi.ignite().configuration().getNodeId() + ']'); } @@ -6403,15 +6413,17 @@ private class TcpServer extends GridWorker { throw t; } finally { - if (err == null && !spi.isNodeStopping0() && spiStateCopy() != DISCONNECTING) - err = new IllegalStateException("Worker " + name() + " is terminated unexpectedly."); + if (spi.ignite() instanceof IgniteEx) { + if (err == null && !spi.isNodeStopping0() && spiStateCopy() != DISCONNECTING) + err = new IllegalStateException("Worker " + name() + " is terminated unexpectedly."); - FailureProcessor failure = ctx.failure(); + FailureProcessor failure = ((IgniteEx)spi.ignite()).context().failure(); - if (err instanceof OutOfMemoryError) - failure.process(new FailureContext(CRITICAL_ERROR, err)); - else if (err != null) - failure.process(new FailureContext(SYSTEM_WORKER_TERMINATION, err)); + if (err instanceof OutOfMemoryError) + failure.process(new FailureContext(CRITICAL_ERROR, err)); + else if (err != null) + failure.process(new FailureContext(SYSTEM_WORKER_TERMINATION, err)); + } U.closeQuiet(srvrSock); } @@ -6446,11 +6458,11 @@ private class SocketReader extends IgniteSpiThread { * @param sock Socket to read data from. */ SocketReader(Socket sock) { - super(ctx.igniteInstanceName(), "tcp-disco-sock-reader-[]", log); + super(spi.ignite().name(), "tcp-disco-sock-reader-[]", log); this.sock = sock; - ses = new TcpDiscoveryIoSession(ctx, sock); + ses = createSession(sock); setPriority(spi.threadPri); } @@ -7073,7 +7085,11 @@ else if (msg instanceof TcpDiscoveryRingLatencyCheckMessage) { } } catch (UnknownMessageException e) { - ctx.failure().process(new FailureContext(SYSTEM_WORKER_TERMINATION, e)); + if (spi.ignite() instanceof IgniteEx) { + FailureProcessor failure = ((IgniteEx)spi.ignite()).context().failure(); + + failure.process(new FailureContext(SYSTEM_WORKER_TERMINATION, e)); + } } finally { if (clientMsgWrk != null) { @@ -7445,7 +7461,7 @@ private class StatisticsPrinter extends IgniteSpiThread { * Constructor. */ StatisticsPrinter() { - super(ctx.igniteInstanceName(), "tcp-disco-stats-printer", log); + super(spi.ignite().name(), "tcp-disco-stats-printer", log); assert spi.statsPrintFreq > 0; @@ -7518,7 +7534,7 @@ private ClientMessageWorker(TcpDiscoveryIoSession ses, UUID clientNodeId, Ignite this.ses = ses; this.clientNodeId = clientNodeId; - clientMsgSer = new TcpDiscoveryMessageSerializer(ctx); + clientMsgSer = new TcpDiscoveryMessageSerializer(spi); lastMetricsUpdateMsgTimeNanos = System.nanoTime(); } @@ -7862,7 +7878,7 @@ protected MessageWorker( @Nullable GridWorkerListener lsnr, BlockingDeque queue ) { - super(ctx.igniteInstanceName(), name, log, lsnr); + super(spi.ignite().name(), name, log, lsnr); this.queue = queue; this.pollingTimeout = pollingTimeout; @@ -8085,7 +8101,7 @@ void pingRemoteDCs(List nodesToPing) { rmtDcPingPool = new IgniteThreadPoolExecutor( "disco-remote-dc-ping-worker", - ctx.igniteInstanceName(), + spi.ignite().name(), pingRmtDcPoolSz, pingRmtDcPoolSz, 0, diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryImpl.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryImpl.java index 0f2324361d5dc..b83e2130ae2e0 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryImpl.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryImpl.java @@ -18,6 +18,7 @@ package org.apache.ignite.spi.discovery.tcp; import java.net.InetSocketAddress; +import java.net.Socket; import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; @@ -35,7 +36,6 @@ import org.apache.ignite.cluster.ClusterMetrics; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.internal.ClusterMetricsSnapshot; -import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.processors.cache.CacheMetricsSnapshot; import org.apache.ignite.internal.processors.cluster.CacheMetricsMessage; @@ -87,9 +87,6 @@ abstract class TcpDiscoveryImpl { /** */ protected final TcpDiscoverySpi spi; - /** */ - protected final GridKernalContext ctx; - /** */ protected final IgniteLogger log; @@ -148,9 +145,7 @@ abstract class TcpDiscoveryImpl { log = spi.log; - ctx = ((IgniteEx)spi.ignite()).context(); - - operationCtxDispatcher = ctx.operationContextDispatcher(); + operationCtxDispatcher = ((IgniteEx)spi.ignite()).context().operationContextDispatcher(); } /** @@ -464,6 +459,16 @@ protected static List toOrderedList(Collection addrs) return res; } + /** + * Instantiates IO session for exchanging discovery messages with remote node. + * + * @param sock Socket to remote node. + * @return IO session for writing and reading {@link TcpDiscoveryAbstractMessage}. + */ + TcpDiscoveryIoSession createSession(Socket sock) { + return new TcpDiscoveryIoSession(sock, spi); + } + /** * @param msg Message. * @return Message logger. diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java index 0251712fc95b7..71a4995eeabcb 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java @@ -35,6 +35,7 @@ import org.apache.ignite.IgniteException; import org.apache.ignite.IgniteLogger; import org.apache.ignite.internal.GridKernalContext; +import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.direct.DirectMessageReader; import org.apache.ignite.internal.direct.DirectMessageWriter; import org.apache.ignite.internal.managers.communication.DiscoveryMarshalling; @@ -45,7 +46,6 @@ import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.marshaller.jdk.JdkMarshaller; import org.apache.ignite.plugin.extensions.communication.Message; -import org.apache.ignite.plugin.extensions.communication.MessageFactory; import org.apache.ignite.plugin.extensions.communication.MessageSerializer; import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryAbstractMessage; import org.jetbrains.annotations.NotNull; @@ -70,13 +70,7 @@ public class TcpDiscoveryIoSession implements AutoCloseable { private static final int MSG_BUFFER_SIZE = 100; /** */ - private final GridKernalContext ctx; - - /** */ - private final MessageFactory msgFactory; - - /** */ - private final IgniteLogger log; + private final TcpDiscoverySpi spi; /** */ private final Socket sock; @@ -102,21 +96,19 @@ public class TcpDiscoveryIoSession implements AutoCloseable { /** * Creates a new discovery I/O session bound to the given socket. * - * @param ctx Kernal context. * @param sock Socket connected to a remote discovery node. + * @param spi Discovery SPI instance owning this session. * @throws IgniteException If an I/O error occurs while initializing buffers. */ - TcpDiscoveryIoSession(GridKernalContext ctx, Socket sock) { + TcpDiscoveryIoSession(Socket sock, TcpDiscoverySpi spi) { this.sock = sock; - this.ctx = ctx; - this.msgFactory = ctx.messageFactory(); - this.log = ctx.log(getClass()); + this.spi = spi; readBuf = ByteBuffer.allocate(MSG_BUFFER_SIZE); writeBuf = ByteBuffer.allocate(MSG_BUFFER_SIZE); - msgWriter = new DirectMessageWriter(msgFactory); - msgReader = new DirectMessageReader(msgFactory, null); + msgWriter = new DirectMessageWriter(spi.messageFactory()); + msgReader = new DirectMessageReader(spi.messageFactory(), null); try { int sendBufSize = sock.getSendBufferSize() > 0 ? sock.getSendBufferSize() : DFLT_SOCK_BUFFER_SIZE; @@ -186,7 +178,7 @@ T readMessage() throws IgniteCheckedException, IOException { Message msg; try { - msg = msgFactory.create(msgType); + msg = spi.messageFactory().create(msgType); } catch (IgniteException e) { detectSslAlert(b0, b1); @@ -210,7 +202,7 @@ T readMessage() throws IgniteCheckedException, IOException { readBuf.limit(read); - finished = MessageSerialization.readFrom(msgFactory, msg, msgReader); + finished = MessageSerialization.readFrom(spi.messageFactory(), msg, msgReader); // Server Discovery only sends next message to next Server upon receiving a receipt for the previous one. // This behaviour guarantees that we never read a next message from the buffer right after the end of @@ -226,7 +218,9 @@ T readMessage() throws IgniteCheckedException, IOException { } while (!finished); - DiscoveryMarshalling.unmarshal(msg, ctx); + GridKernalContext kctx = ((IgniteEx)spi.ignite()).context(); + + DiscoveryMarshalling.unmarshal(msg, kctx); return (T)msg; } @@ -244,14 +238,14 @@ T readMessage() throws IgniteCheckedException, IOException { /** @return SSL certificate this session is established with. {@code null} if SSL is disabled or certificate validation failed. */ @Nullable Certificate[] extractCertificates() { - if (!(sock instanceof SSLSocket)) + if (!spi.isSslEnabled()) return null; try { return ((SSLSocket)sock).getSession().getPeerCertificates(); } catch (SSLPeerUnverifiedException e) { - U.error(log, "Failed to extract discovery IO session certificates", e); + U.error(spi.log, "Failed to extract discovery IO session certificates", e); return null; } @@ -270,7 +264,9 @@ public Socket socket() { * @throws IOException If serialization fails. */ void serializeMessage(Message m, OutputStream out) throws IOException, IgniteCheckedException { - DiscoveryMarshalling.marshal(m, ctx, null); + GridKernalContext kctx = ((IgniteEx)spi.ignite()).context(); + + DiscoveryMarshalling.marshal(m, kctx, null); msgWriter.reset(); msgWriter.setBuffer(writeBuf); @@ -281,7 +277,7 @@ void serializeMessage(Message m, OutputStream out) throws IOException, IgniteChe // Should be cleared before first operation. writeBuf.clear(); - finished = MessageSerialization.writeTo(msgFactory, m, msgWriter); + finished = MessageSerialization.writeTo(spi.messageFactory(), m, msgWriter); out.write(writeBuf.array(), 0, writeBuf.position()); } diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryMessageSerializer.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryMessageSerializer.java index ec7cdc569f0c7..8c871f9e2eb46 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryMessageSerializer.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryMessageSerializer.java @@ -22,7 +22,6 @@ import java.io.OutputStream; import java.net.Socket; import org.apache.ignite.IgniteCheckedException; -import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.plugin.extensions.communication.Message; import org.apache.ignite.plugin.extensions.communication.MessageSerializer; import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryAbstractMessage; @@ -36,10 +35,10 @@ */ class TcpDiscoveryMessageSerializer extends TcpDiscoveryIoSession { /** - * @param ctx Kernal context. + * @param spi Discovery SPI instance. */ - public TcpDiscoveryMessageSerializer(GridKernalContext ctx) { - super(ctx, new Socket() { + public TcpDiscoveryMessageSerializer(TcpDiscoverySpi spi) { + super(new Socket() { @Override public OutputStream getOutputStream() throws IOException { return null; } @@ -47,7 +46,7 @@ public TcpDiscoveryMessageSerializer(GridKernalContext ctx) { @Override public InputStream getInputStream() throws IOException { return null; } - }); + }, spi); } /** diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySpi.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySpi.java index 7b8490ded8385..34dcbbe9f9b5a 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySpi.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySpi.java @@ -57,6 +57,7 @@ import org.apache.ignite.internal.IgniteInterruptedCheckedException; import org.apache.ignite.internal.managers.communication.UnknownMessageException; import org.apache.ignite.internal.managers.discovery.IgniteDiscoverySpi; +import org.apache.ignite.internal.processors.failure.FailureProcessor; import org.apache.ignite.internal.processors.metric.MetricRegistryImpl; import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteComponentFeatureSet; import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteNodeFeatureSet; @@ -73,6 +74,7 @@ import org.apache.ignite.lang.IgniteUuid; import org.apache.ignite.marshaller.Marshaller; import org.apache.ignite.plugin.extensions.communication.Message; +import org.apache.ignite.plugin.extensions.communication.MessageFactory; import org.apache.ignite.resources.IgniteInstanceResource; import org.apache.ignite.resources.LoggerResource; import org.apache.ignite.spi.IgniteSpiAdapter; @@ -461,6 +463,10 @@ public class TcpDiscoverySpi extends IgniteSpiAdapter implements IgniteDiscovery @GridToStringExclude protected IgniteSpiContext spiCtx; + /** Discovery messages factory. */ + @GridToStringExclude + private MessageFactory msgFactory; + /** For test purposes. */ private boolean skipAddrsRandomization = false; @@ -594,6 +600,8 @@ public void setClientReconnectDisabled(boolean clientReconnectDisabled) { setAddressResolver(ignite.configuration().getAddressResolver()); marsh = ((IgniteEx)ignite).context().marshallerContext().jdkMarshaller(); + + msgFactory = ((IgniteEx)ignite).context().messageFactory(); } } @@ -1114,6 +1122,11 @@ public void setConnectionRecoveryTimeout(long connRecoveryTimeout) { locNodeVer = ver; } + /** @return Discovery messages factory. */ + public MessageFactory messageFactory() { + return msgFactory; + } + /** * Gets ID of the local node. * @@ -1611,7 +1624,7 @@ protected TcpDiscoveryIoSession openSession( sock.connect(resolved, (int)timeoutHelper.nextTimeoutChunk(sockTimeout)); - TcpDiscoveryIoSession ses = new TcpDiscoveryIoSession(ignite.context(), sock); + TcpDiscoveryIoSession ses = new TcpDiscoveryIoSession(sock, this); write(ses, U.IGNITE_HEADER, timeoutHelper.nextTimeoutChunk(sockTimeout)); @@ -1936,8 +1949,8 @@ protected Collection resolvedAddresses() throws IgniteSpiExce Collection addrs; long timeout = isClientMode() && impl.getSpiState().equalsIgnoreCase("connected") - ? netTimeout - : joinTimeout; + ? netTimeout + : joinTimeout; // Get consistent addresses collection. while (true) { @@ -1948,16 +1961,16 @@ protected Collection resolvedAddresses() throws IgniteSpiExce } catch (IgniteSpiException e) { LT.error(log, e, "Failed to get registered addresses from IP finder " + - "(retrying every " + getReconnectDelay() + "ms;" + - " change 'reconnectDelay' to configure the frequency of retries) " + - "[maxTimeout=" + timeout + "]", true); + "(retrying every " + getReconnectDelay() + "ms;" + + " change 'reconnectDelay' to configure the frequency of retries) " + + "[maxTimeout=" + timeout + "]", true); } try { if (timeout > 0 && U.millisSinceNanos(resolutionStartNanos) > timeout) { LT.warn(log, "Unable to get registered addresses from IP finder, timeout is reached " + - "(consider increasing 'joinTimeout' for join process or 'netTimeout' for reconnection) " + - "[joinTimeout=" + joinTimeout + ", netTimeout=" + netTimeout + "]"); + "(consider increasing 'joinTimeout' for join process or 'netTimeout' for reconnection) " + + "[joinTimeout=" + joinTimeout + ", netTimeout=" + netTimeout + "]"); addrs = res; break; @@ -1978,7 +1991,7 @@ protected Collection resolvedAddresses() throws IgniteSpiExce continue; InetSocketAddress resolved = addr.isUnresolved() ? - new InetSocketAddress(InetAddress.getByName(addr.getHostName()), addr.getPort()) : addr; + new InetSocketAddress(InetAddress.getByName(addr.getHostName()), addr.getPort()) : addr; if (locNodeAddrs == null || !locNodeAddrs.contains(resolved)) res.add(resolved); @@ -2118,7 +2131,11 @@ protected void onExchange(DiscoveryDataPacket dataPacket, ClassLoader clsLdr) { dataBag = dataPacket.bagWithJoiningNodeData(ignite.log(), ignite.configuration().isClientMode()); } catch (IgniteCheckedException e) { - ignite.context().failure().process(new FailureContext(CRITICAL_ERROR, e)); + if (ignite() instanceof IgniteEx) { + FailureProcessor failure = ((IgniteEx)ignite()).context().failure(); + + failure.process(new FailureContext(CRITICAL_ERROR, e)); + } throw new IgniteException(e); } @@ -2540,12 +2557,12 @@ boolean cancel() { LT.warn(log, "Socket write has timed out (consider increasing " + (failureDetectionTimeoutEnabled() ? - "'IgniteConfiguration.failureDetectionTimeout' configuration property) [" + - "failureDetectionTimeout=" + failureDetectionTimeout() : - "'sockTimeout' configuration property) [sockTimeout=" + sockTimeout) + - ", rmtAddr=" + ses.socket().getRemoteSocketAddress() + - ", rmtPort=" + ses.socket().getPort() + - ", sockTimeout=" + sockTimeout + ']'); + "'IgniteConfiguration.failureDetectionTimeout' configuration property) [" + + "failureDetectionTimeout=" + failureDetectionTimeout() : + "'sockTimeout' configuration property) [sockTimeout=" + sockTimeout) + + ", rmtAddr=" + ses.socket().getRemoteSocketAddress() + + ", rmtPort=" + ses.socket().getPort() + + ", sockTimeout=" + sockTimeout + ']'); } } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/binary/BinaryClassLoaderMultiJvmTest.java b/modules/core/src/test/java/org/apache/ignite/internal/binary/BinaryClassLoaderMultiJvmTest.java index 3e62ed376ce03..e00dc4530c42c 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/binary/BinaryClassLoaderMultiJvmTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/binary/BinaryClassLoaderMultiJvmTest.java @@ -18,11 +18,9 @@ package org.apache.ignite.internal.binary; import java.lang.ref.WeakReference; -import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.net.URL; -import java.util.List; import org.apache.ignite.Ignite; import org.apache.ignite.IgniteCache; import org.apache.ignite.IgniteException; @@ -40,16 +38,12 @@ import org.apache.ignite.lang.IgniteRunnable; import org.apache.ignite.testframework.GridTestExternalClassLoader; import org.apache.ignite.testframework.config.GridTestProperties; -import org.apache.ignite.testframework.junits.WithSystemProperty; import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; import org.junit.Test; -import static org.apache.ignite.IgniteCommonsSystemProperties.IGNITE_USE_BINARY_ARRAYS; - /** * Test class covering feature that allows to unmarshal binary object with custom classloader. */ -@WithSystemProperty(key = IGNITE_USE_BINARY_ARRAYS, value = "true") public class BinaryClassLoaderMultiJvmTest extends GridCommonAbstractTest { /** Person class name. */ private static final String PERSON_CLASS_NAME = "org.apache.ignite.tests.p2p.cache.Person"; @@ -74,11 +68,6 @@ public class BinaryClassLoaderMultiJvmTest extends GridCommonAbstractTest { return true; } - /** {@inheritDoc} */ - @Override protected List additionalRemoteJvmArgs() { - return List.of("-D" + IGNITE_USE_BINARY_ARRAYS + "=true"); - } - /** {@inheritDoc} */ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { return super.getConfiguration(igniteInstanceName) @@ -88,9 +77,6 @@ public class BinaryClassLoaderMultiJvmTest extends GridCommonAbstractTest { new CacheConfiguration("SomeCache") .setAtomicityMode(CacheAtomicityMode.ATOMIC) .setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC), - new CacheConfiguration("SomeCacheArray") - .setAtomicityMode(CacheAtomicityMode.ATOMIC) - .setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC), new CacheConfiguration("SomeCacheEnum") .setAtomicityMode(CacheAtomicityMode.ATOMIC) .setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC), @@ -140,8 +126,6 @@ public void testLoadClassFromBinary() throws Exception { checkItems(testClsLdr, "SomeCache", PERSON_CLASS_NAME, ign); - checkItems(testClsLdr, "SomeCacheArray", PERSON_CLASS_NAME, ign); - checkItems(testClsLdr, "SomeCacheEnum", ENUM_CLASS_NAME, ign); checkItems(testClsLdr, "OrganizationCache", ORGANIZATION_CLASS_NAME, ign); @@ -195,8 +179,6 @@ public void testClientLoadClassFromBinary() throws Exception { checkItems(testClsLdr, "SomeCache", PERSON_CLASS_NAME, ign); - checkItems(testClsLdr, "SomeCacheArray", PERSON_CLASS_NAME, ign); - checkItems(testClsLdr, "SomeCacheEnum", ENUM_CLASS_NAME, ign); checkItems(testClsLdr, "OrganizationCache", ORGANIZATION_CLASS_NAME, ign); @@ -231,9 +213,8 @@ private void checkItems(ClassLoader testClsLdr, String cacheName, String valClsN IgniteCache binaryCache = cache.withKeepBinary(); - boolean arrVals = cacheName.endsWith("Array"); - for (int i = 0; i < 100; i++) { + BinaryObject binaryVal = binaryCache.get(i); if (i % 50 == 0) @@ -244,8 +225,7 @@ private void checkItems(ClassLoader testClsLdr, String cacheName, String valClsN info("Can not execute toString() on class " + binaryVal.type().typeName()); } - if (!arrVals) - assertEquals(binaryVal.type().typeName(), valClsName); + assertEquals(binaryVal.type().typeName(), valClsName); boolean catchEx = false; @@ -265,14 +245,7 @@ private void checkItems(ClassLoader testClsLdr, String cacheName, String valClsN Object personVal = binaryVal.deserialize(testClsLdr); - assertNotNull(personVal); - - if (!arrVals) - assertTrue(personVal.getClass().getName().equals(valClsName)); - else { - assertTrue(personVal.getClass().isArray()); - assertTrue(personVal.getClass().getComponentType().getName().equals(valClsName)); - } + assertTrue(personVal != null && personVal.getClass().getName().equals(valClsName)); } } @@ -281,9 +254,7 @@ private void checkItems(ClassLoader testClsLdr, String cacheName, String valClsN * @param ignite Ignite. */ private void loadItems(ClassLoader testClsLdr, Ignite ignite) throws Exception { - Class personCls = testClsLdr.loadClass(PERSON_CLASS_NAME); - - Constructor personConstructor = personCls.getConstructor(String.class); + Constructor personConstructor = testClsLdr.loadClass(PERSON_CLASS_NAME).getConstructor(String.class); IgniteCache cache = ignite.cache("SomeCache"); @@ -291,18 +262,6 @@ private void loadItems(ClassLoader testClsLdr, Ignite ignite) throws Exception { cache.put(i, personConstructor.newInstance("Persone name " + i)); assertEquals(cache.size(CachePeekMode.PRIMARY), 100); - - cache = ignite.cache("SomeCacheArray"); - - for (int i = 0; i < 100; i++) { - Object arr = Array.newInstance(personCls, 1); - - Array.set(arr, 0, personConstructor.newInstance("Persone name " + i)); - - cache.put(i, arr); - } - - assertEquals(cache.size(CachePeekMode.PRIMARY), 100); } /** diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/CacheMetricsCacheSizeTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/CacheMetricsCacheSizeTest.java index a4a0db433309d..9aed75febee59 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/CacheMetricsCacheSizeTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/CacheMetricsCacheSizeTest.java @@ -35,6 +35,7 @@ import org.apache.ignite.internal.processors.cluster.CacheMetricsMessage; import org.apache.ignite.internal.util.nio.MessageSerialization; import org.apache.ignite.plugin.extensions.communication.MessageFactory; +import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi; import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryMetricsUpdateMessage; import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; import org.junit.Test; @@ -104,7 +105,7 @@ public void testCacheSize() throws Exception { msg.addServerMetrics(srvrId, new ClusterMetricsSnapshot()); msg.addServerCacheMetrics(srvrId, cacheMetrics); - MessageFactory msgFactory = grid(0).context().messageFactory(); + MessageFactory msgFactory = ((TcpDiscoverySpi)grid(0).context().discovery().getInjectedDiscoverySpi()).messageFactory(); // First time we write initial message type which is not read by the reader because the message type is known. // We have to skip this header at the further message reading. diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java new file mode 100644 index 0000000000000..2cc653c0ccd9d --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java @@ -0,0 +1,311 @@ +/* + * 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.ignite.internal.processors.cache.persistence.snapshot; + +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Supplier; +import org.apache.ignite.Ignite; +import org.apache.ignite.IgniteDataStreamer; +import org.apache.ignite.IgniteException; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.cluster.ClusterTopologyCheckedException; +import org.apache.ignite.internal.util.distributed.DistributedProcess; +import org.apache.ignite.internal.util.distributed.FullMessage; +import org.apache.ignite.internal.util.future.IgniteFutureImpl; +import org.apache.ignite.internal.util.typedef.G; +import org.apache.ignite.lang.IgniteFuture; +import org.junit.Test; + +import static org.apache.ignite.cluster.ClusterState.ACTIVE; +import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.CHECK_SNAPSHOT_METAS; +import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.DELETE_SNAPSHOT; +import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_PREPARE; +import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.START_SNAPSHOT; +import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause; + +/** + * Cluster-wide snapshot delete procedure tests. In addition to the concurrent snapshot delete tests, where the + * operations are paired with the snapshot create, check and restore procedures to verify that concurrent snapshot + * operations are correctly rejected (or allowed) when a delete operation is in progress, this class also contains the + * basic snapshot delete functionality tests. + */ +public class IgniteClusterSnapshotDeleteTest extends AbstractSnapshotSelfTest { + /** Cache partitions count. */ + private static final int CACHE_PARTS_CNT = 32; + + /** Tests the basic cluster-wide snapshot delete functionality. */ + @Test + public void testClusterSnapshotDelete() throws Exception { + IgniteEx ignite = prepareGridsAndSnapshot(3, 2, 2, false); + + // Sanity check: the snapshot exists on the cluster. + assertNotNull( + "Snapshot must be available on the cluster", + snp(ignite).checkSnapshot(SNAPSHOT_NAME, null).get().idleVerifyResult() + ); + + snp(ignite).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); + + // The snapshot must not be present on any of the cluster nodes. + for (Ignite node : G.allGrids()) { + assertTrue( + "Snapshot must be deleted on node " + node.name(), + snp((IgniteEx)node).localSnapshotNames(null).isEmpty() + ); + } + + // The check procedure must report that the snapshot does not exist anymore. + assertThrowsAnyCause( + log, + () -> { + snp(ignite).checkSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); + + return null; + }, + IllegalArgumentException.class, + "Snapshot does not exists" + ); + } + + /** Tests that a snapshot delete is declined when a snapshot create operation is in progress. */ + @Test + public void testSnapshotDeleteWhenCreateInProgress() throws Exception { + prepareGridsAndSnapshot(3, 2, 2, false); + + doTestConcurrentSnpDeleteOperation( + () -> snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary), + START_SNAPSHOT, + () -> { + assertThrowsAnyCause( + log, + () -> { + snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); + + return null; + }, + ClusterTopologyCheckedException.class, + "Snapshot deletion was rejected. a snapshot operation is in progress" + ); + + return null; + } + ); + } + + /** Tests that a snapshot create is declined when a snapshot delete operation is in progress. */ + @Test + public void testSnapshotCreateWhenDeleteInProgress() throws Exception { + prepareGridsAndSnapshot(3, 2, 2, false); + + doTestConcurrentSnpDeleteOperation( + () -> snp(grid(0)).deleteSnapshot(SNAPSHOT_NAME, null), + DELETE_SNAPSHOT, + () -> { + assertThrowsAnyCause( + log, + () -> { + snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary); + + return null; + }, + IgniteException.class, + "Snapshot delete operation is currently in progress" + ); + + return null; + } + ); + } + + /** Tests that a snapshot delete is declined when a snapshot check operation is in progress. */ + @Test + public void testSnapshotDeleteWhenCheckInProgress() throws Exception { + prepareGridsAndSnapshot(3, 2, 2, false); + + doTestConcurrentSnpDeleteOperation( + () -> new IgniteFutureImpl<>(snp(grid(0)).checkSnapshot(SNAPSHOT_NAME, null)), + CHECK_SNAPSHOT_METAS, + () -> { + assertThrowsAnyCause( + log, + () -> { + snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); + + return null; + }, + ClusterTopologyCheckedException.class, + "Snapshot deletion was rejected. the snapshot is being checked" + ); + + return null; + } + ); + } + + /** Tests that a snapshot check is not declined when a snapshot delete operation is in progress. */ + @Test + public void testSnapshotCheckWhenDeleteInProgress() throws Exception { + prepareGridsAndSnapshot(3, 2, 2, false); + + doTestConcurrentSnpDeleteOperation( + () -> snp(grid(0)).deleteSnapshot(SNAPSHOT_NAME, null), + DELETE_SNAPSHOT, + () -> { + new IgniteFutureImpl<>(snp(grid(1)).checkSnapshot(SNAPSHOT_NAME, null)).get(getTestTimeout()); + + return null; + } + ); + } + + /** Tests that a snapshot delete is declined when a snapshot restore operation is in progress. */ + @Test + public void testSnapshotDeleteWhenRestoreInProgress() throws Exception { + prepareGridsAndSnapshot(3, 2, 2, true); + + doTestConcurrentSnpDeleteOperation( + () -> snp(grid(0)).restoreSnapshot(SNAPSHOT_NAME, null, null, 0, true), + RESTORE_CACHE_GROUP_SNAPSHOT_PREPARE, + () -> { + assertThrowsAnyCause( + log, + () -> { + snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); + + return null; + }, + ClusterTopologyCheckedException.class, + "Snapshot deletion was rejected. the snapshot is being restored" + ); + + return null; + } + ); + } + + /** Tests that a snapshot restore is declined when a snapshot delete operation is in progress. */ + @Test + public void testSnapshotRestoreWhenDeleteInProgress() throws Exception { + prepareGridsAndSnapshot(3, 2, 2, true); + + doTestConcurrentSnpDeleteOperation( + () -> snp(grid(0)).deleteSnapshot(SNAPSHOT_NAME, null), + DELETE_SNAPSHOT, + () -> { + assertThrowsAnyCause( + log, + () -> { + snp(grid(0)).restoreSnapshot(SNAPSHOT_NAME, null, null, 0, true).get(getTestTimeout()); + + return null; + }, + IgniteException.class, + "A snapshot delete operation is in progress" + ); + + return null; + } + ); + } + + /** + * Tests the concurrent snapshot delete procedure against another snapshot operation. + * + *

The {@code originatorOp} is blocked on the coordinator discovery, so it is kept in-progress while the + * {@code trierStep} is executed and its conflict behavior is asserted. + * + * @param originatorOp First snapshot operation on the coordinator node to keep in-progress. + * @param firstDelay First distributed process full message of {@code originatorOp} to delay on the coordinator + * to launch {@code trierStep}. + * @param trierStep Second concurrent snapshot operation with asserted result. + */ + private void doTestConcurrentSnpDeleteOperation( + Supplier> originatorOp, + DistributedProcess.DistributedProcessType firstDelay, + Callable trierStep + ) throws Exception { + try { + AtomicBoolean firstDelayed = new AtomicBoolean(); + + // Block only the first matching message so the originator operation stays in-progress. + discoSpi(grid(0)).block( + msg -> msg instanceof FullMessage + && ((FullMessage)msg).type() == firstDelay.ordinal() + && firstDelayed.compareAndSet(false, true)); + + IgniteFuture fut = originatorOp.get(); + + discoSpi(grid(0)).waitBlocked(getTestTimeout()); + + if (trierStep != null) + trierStep.call(); + + discoSpi(grid(0)).unblock(); + + fut.get(getTestTimeout()); + } + finally { + discoSpi(grid(0)).unblock(); + + awaitPartitionMapExchange(); + } + } + + /** + * @param servers Number of server nodes. + * @param baseLineCnt Number of baseline nodes. + * @param clients Number of client nodes. + * @param removeTheCache If {@code true}, the cache is destroyed after the snapshot is created (restore scenario). + * @return The last started (server) grid, which the snapshot was created on. + */ + private IgniteEx prepareGridsAndSnapshot(int servers, int baseLineCnt, int clients, boolean removeTheCache) throws Exception { + assert baseLineCnt > 0 && baseLineCnt <= servers; + + IgniteEx ignite = null; + + for (int i = 0; i < servers + clients; ++i) { + IgniteConfiguration cfg = getConfiguration(getTestIgniteInstanceName(i)); + + if (i >= servers) + cfg.setClientMode(true); + + ignite = startGrid(cfg); + + if (i == baseLineCnt - 1) { + ignite.cluster().state(ACTIVE); + + ignite.cluster().setBaselineTopology(ignite.cluster().topologyVersion()); + } + } + + try (IgniteDataStreamer ds = grid(0).dataStreamer(DEFAULT_CACHE_NAME)) { + // Ensure all the partitions are created: several records per partition. + for (int i = 0; i < CACHE_PARTS_CNT * 4; ++i) + ds.addData(i, i); + } + + ignite.snapshot().createSnapshot(SNAPSHOT_NAME).get(); + + if (removeTheCache) + ignite.destroyCache(DEFAULT_CACHE_NAME); + + return ignite; + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/spi/MessagesPluginProvider.java b/modules/core/src/test/java/org/apache/ignite/spi/MessagesPluginProvider.java index 0090d11cc3c56..1198584d22972 100644 --- a/modules/core/src/test/java/org/apache/ignite/spi/MessagesPluginProvider.java +++ b/modules/core/src/test/java/org/apache/ignite/spi/MessagesPluginProvider.java @@ -17,6 +17,7 @@ package org.apache.ignite.spi; +import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.internal.CoreMessagesProvider; import org.apache.ignite.plugin.AbstractTestPluginProvider; import org.apache.ignite.plugin.ExtensionRegistry; @@ -24,6 +25,8 @@ import org.apache.ignite.plugin.extensions.communication.Message; import org.apache.ignite.plugin.extensions.communication.MessageFactoryProvider; import org.apache.ignite.plugin.extensions.communication.MessageMarshaller; +import org.apache.ignite.spi.discovery.DiscoverySpi; +import org.apache.ignite.spi.discovery.tcp.TestTcpDiscoverySpi; import org.jetbrains.annotations.Nullable; import static org.apache.ignite.testframework.GridTestUtils.loadMarshaller; @@ -75,4 +78,14 @@ public MessagesPluginProvider(Class... msgs) { // Register messages into the communication protocol. registry.registerExtension(MessageFactoryProvider.class, msgFactoryProvider); } + + /** {@inheritDoc} */ + @Override public void start(PluginContext ctx) throws IgniteCheckedException { + DiscoverySpi discoSpi = ctx.igniteConfiguration().getDiscoverySpi(); + + if (discoSpi instanceof TestTcpDiscoverySpi testDiscoSpi) { + // Register messages into the discovery protocol. + testDiscoSpi.messageFactory(msgFactoryProvider); + } + } } diff --git a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/BlockTcpDiscoverySpi.java b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/BlockTcpDiscoverySpi.java index 4904df46a40d3..52e81b05df960 100644 --- a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/BlockTcpDiscoverySpi.java +++ b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/BlockTcpDiscoverySpi.java @@ -70,7 +70,7 @@ private synchronized void apply(ClusterNode addr, TcpDiscoveryAbstractMessage ms long timeout ) throws IOException, IgniteCheckedException { if (spiCtx != null) { - TcpDiscoveryAbstractMessage msg = decodeMessage(ignite.context(), data); + TcpDiscoveryAbstractMessage msg = decodeMessage(this, data); if (msg != null) apply(spiCtx.localNode(), msg); diff --git a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/DiscoveryUnmarshalVulnerabilityTest.java b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/DiscoveryUnmarshalVulnerabilityTest.java index 8e8029c295ee5..5a4e18fe8d421 100644 --- a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/DiscoveryUnmarshalVulnerabilityTest.java +++ b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/DiscoveryUnmarshalVulnerabilityTest.java @@ -245,7 +245,7 @@ void attack(byte[] data) throws IOException { private byte[] serializedMessage() throws IgniteCheckedException { ByteBuffer buf = ByteBuffer.allocate(4096); - MessageFactory msgFactory = grid(0).context().messageFactory(); + MessageFactory msgFactory = ((TcpDiscoverySpi)grid(0).configuration().getDiscoverySpi()).messageFactory(); DirectMessageWriter writer = new DirectMessageWriter(msgFactory); writer.setBuffer(buf); diff --git a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpClientDiscoverySpiSelfTest.java b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpClientDiscoverySpiSelfTest.java index 0d5222fca2d2a..ca96f33f159cf 100644 --- a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpClientDiscoverySpiSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpClientDiscoverySpiSelfTest.java @@ -2600,7 +2600,7 @@ private void pauseResumeOperation(boolean isPause, AtomicBoolean... locks) { waitFor(writeLock); - TcpDiscoveryAbstractMessage msg = decodeMessage(ignite.context(), data); + TcpDiscoveryAbstractMessage msg = decodeMessage(this, data); if (msg != null && !onMessage(sock, msg)) return; diff --git a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TestTcpDiscoverySpi.java b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TestTcpDiscoverySpi.java index feedf6a4c6ed5..866bbc842178b 100644 --- a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TestTcpDiscoverySpi.java +++ b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TestTcpDiscoverySpi.java @@ -26,15 +26,19 @@ import java.util.Arrays; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteException; -import org.apache.ignite.internal.GridKernalContext; +import org.apache.ignite.internal.CoreMessagesProvider; +import org.apache.ignite.internal.managers.communication.IgniteMessageFactoryImpl; import org.apache.ignite.internal.managers.discovery.IgniteDiscoverySpiInternalListener; import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.plugin.extensions.communication.MessageFactory; +import org.apache.ignite.plugin.extensions.communication.MessageFactoryProvider; import org.apache.ignite.spi.discovery.DiscoverySpiCustomMessage; import org.apache.ignite.spi.discovery.DiscoverySpiListener; import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryAbstractMessage; import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryClientReconnectMessage; import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryJoinRequestMessage; import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryPingResponse; +import org.apache.ignite.testframework.GridTestUtils; import org.apache.ignite.testframework.GridTestUtils.DiscoveryHook; import org.jetbrains.annotations.Nullable; @@ -53,6 +57,12 @@ public class TestTcpDiscoverySpi extends TcpDiscoverySpi implements IgniteDiscov /** */ private IgniteDiscoverySpiInternalListener internalLsnr; + /** */ + private MessageFactory msgFactory; + + /** */ + private MessageFactoryProvider provider; + /** {@inheritDoc} */ @Override protected void writeMessage(TcpDiscoveryIoSession ses, TcpDiscoveryAbstractMessage msg, long timeout) throws IOException, IgniteCheckedException { @@ -109,8 +119,42 @@ public void discoveryHook(DiscoveryHook discoHook) { this.discoHook = discoHook; } + /** + * Sets test discovery messages factory provider. Note that {@link MessageFactoryProvider} must be set before SPI start. + * Otherwise, this method call will take no effect. + * + * @param msgFactoryProvider Discovery messages factory provider. + */ + public void messageFactory(MessageFactoryProvider msgFactoryProvider) { + provider = msgFactoryProvider; + assert !started(); + + msgFactory = new IgniteMessageFactoryImpl(new MessageFactoryProvider[] { + new CoreMessagesProvider(), + msgFactoryProvider + }); + } + + /** {@inheritDoc} */ + @Override public MessageFactoryProvider messageFactoryProvider() { + return provider; + } + + /** {@inheritDoc} */ + @Override protected void initLocalNode(int srvPort, boolean addExtAddrAttr) { + if (msgFactory != null) + GridTestUtils.setFieldValue(this, TcpDiscoverySpi.class, "msgFactory", msgFactory); + + super.initLocalNode(srvPort, addExtAddrAttr); + } + + /** {@inheritDoc} */ + @Override public MessageFactory messageFactory() { + return msgFactory != null ? msgFactory : super.messageFactory(); + } + /** */ - public static @Nullable TcpDiscoveryAbstractMessage decodeMessage(GridKernalContext ctx, byte[] data) { + public static @Nullable TcpDiscoveryAbstractMessage decodeMessage(TcpDiscoverySpi spi, byte[] data) { if (Arrays.equals(U.IGNITE_HEADER, data)) return null; @@ -125,7 +169,7 @@ public void discoveryHook(DiscoveryHook discoHook) { }; try (dataSock) { - return new TcpDiscoveryIoSession(ctx, dataSock).readMessage(); + return new TcpDiscoveryIoSession(dataSock, spi).readMessage(); } catch (Exception e) { throw new IgniteException("Failed to decode a message", e); diff --git a/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/ContinuousDataLoadApplication.java b/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/ContinuousDataLoadApplication.java index bff6187c71532..0b743036dfebc 100644 --- a/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/ContinuousDataLoadApplication.java +++ b/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/ContinuousDataLoadApplication.java @@ -78,7 +78,7 @@ public class ContinuousDataLoadApplication extends IgniteAwareApplication { if (notifyTime + TimeUnit.MILLISECONDS.toNanos(1500) < System.nanoTime()) notifyTime = System.nanoTime(); - // Delayed notify of the initialization to make sure the data load has completelly began and + // Delayed notify of the initialization to make sure the data load has completely began and // has produced some valuable amount of data. if (!inited() && warmUpCnt == loaded) markInitialized(); From de68328ac02eb0bbc7944b7343598c47f1eb753b Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Wed, 9 Sep 2026 13:58:04 +0300 Subject: [PATCH 07/97] + snpPath --- .../ignite/util/GridCommandHandlerTest.java | 116 ++++++++++++------ .../ignite/internal/CoreMessagesProvider.java | 2 +- .../snapshot/SnapshotDeleteCommandArg.java | 2 +- .../snapshot/SnapshotDeleteTask.java | 2 - .../snapshot/SnapshotDeleteProcess.java | 61 +++++---- .../snapshot/SnapshotDeleteResponse.java | 27 ++-- 6 files changed, 135 insertions(+), 75 deletions(-) diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java index adc8f7d8a5ef6..a51574eeb932f 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java @@ -3240,70 +3240,108 @@ public void testCheckSnapshot() throws Exception { /** */ @Test public void testSnapshotDelete() throws Exception { - String snpName = "snapshot_03032024"; + doTestSnapshotDelete(false, false, false); + } - IgniteEx ig = (IgniteEx)startGridsMultiThreaded(3); - ig.cluster().state(ACTIVE); + /** */ + @Test + public void testSnapshotDeleteCustomPath() throws Exception { + doTestSnapshotDelete(false, true, false); + } - createCacheAndPreload(ig, 100); + /** */ + @Test + public void testSnapshotDeleteClient() throws Exception { + doTestSnapshotDelete(true, false, false); + } - snp(ig).createSnapshot(snpName).get(getTestTimeout()); + /** */ + @Test + public void testSnapshotDeleteCustomPathClient() throws Exception { + doTestSnapshotDelete(true, true, false); + } - File snpDir = new File(ig.context().pdsFolderResolver().fileTree().snapshotsRoot(), snpName); + /** */ + @Test + public void testSnapshotDeleteIncrementsCustomPathClient() throws Exception { + doTestSnapshotDelete(true, true, true); + } - assertTrue("Snapshot directory must exist: " + snpDir, snpDir.exists()); + /** */ + private void doTestSnapshotDelete(boolean withClientGrid, boolean customPath, boolean addIncrements) throws Exception { + int nodesCnt = 3; + int entriesCnt = 4000; - injectTestSystemOut(); + walCompactionEnabled(addIncrements); - assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "delete", snpName)); + IgniteEx ig = (IgniteEx)startGridsMultiThreaded(nodesCnt); - assertTrue(waitForCondition(() -> !snpDir.exists(), getTestTimeout())); - } + if (withClientGrid) + startGrid(CLIENT_NODE_NAME_PREFIX); - /** */ - @Test - public void testSnapshotDeleteMissing() throws Exception { - IgniteEx ig = startGrid(0); ig.cluster().state(ACTIVE); - createCacheAndPreload(ig, 100); + createCacheAndPreload(ig, entriesCnt); - injectTestSystemOut(); + File cstSnpsRoot = customPath ? new File(U.defaultWorkDirectory(), "ex_snapshots") : null; + File snpDir = new File(customPath ? cstSnpsRoot : ig.context().pdsFolderResolver().fileTree().snapshotsRoot(), "testSnapshot"); - TestCommandHandler h = newCommandHandler(); + try { + snp(ig).createSnapshot("testSnapshot", customPath ? cstSnpsRoot.getAbsolutePath() : null, false, false) + .get(getTestTimeout()); - // Deleting a non-existent snapshot is a no-op and must complete successfully. - assertEquals(EXIT_CODE_OK, execute(h, "--snapshot", "delete", "snapshot_MISSING")); - } + if (addIncrements) { + for (int i = 0; i < 3; ++i) { + int dataIdx = entriesCnt + entriesCnt / 4 * i; - /** @throws Exception If fails. */ - @Test - public void testSnapshotDeleteCustomDir() throws Exception { - String snpName = "snapshot_04042024"; - File snpDir = U.resolveWorkDirectory(U.defaultWorkDirectory(), "ex_snapshots_delete", true); + try (IgniteDataStreamer streamer = ig.dataStreamer(DEFAULT_CACHE_NAME)) { + for (int d = dataIdx; d < dataIdx + entriesCnt / 4; ++d) + streamer.addData(i, i); + } - try { - assertTrue("Target directory is not empty: " + snpDir, F.isEmpty(snpDir.list())); + snp(ig).createSnapshot("testSnapshot", customPath ? cstSnpsRoot.getAbsolutePath() : null, true, false) + .get(getTestTimeout()); + } + } - IgniteEx ig = startGrid(0); - ig.cluster().state(ACTIVE); + assertTrue("Snapshot directory must exist: " + snpDir, snpDir.exists()); - createCacheAndPreload(ig, 100); + injectTestSystemOut(); - assertEquals(EXIT_CODE_OK, - execute("--snapshot", "create", snpName, "--sync", "--dest", snpDir.getAbsolutePath())); + if (customPath) { + assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "delete", "--src", + cstSnpsRoot.getAbsolutePath(), "wrongSnapshot")); + } + else + assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "delete", "wrongSnapshot")); - File snpLocDir = new File(snpDir, snpName); + String out = testOut.toString(); - assertTrue("Snapshot directory must exist: " + snpLocDir, snpLocDir.exists()); + assertFalse(out.contains("Snapshot removed on the following nodes [cnt=%d]:".formatted(nodesCnt))); + assertFalse(out.contains("the following nodes didn't find any snapshot data, nothing to delete")); + assertTrue(out.contains("Snapshot not found on current server nodes")); - assertEquals(EXIT_CODE_OK, - execute("--snapshot", "delete", snpName, "--dest", snpDir.getAbsolutePath())); + testOut.reset(); + assertTrue(testOut.toString().isEmpty()); - assertTrue(waitForCondition(() -> !snpLocDir.exists(), getTestTimeout())); + if (customPath) { + assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "delete", "--src", + cstSnpsRoot.getAbsolutePath(), "testSnapshot")); + } + else + assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "delete", "testSnapshot")); + + out = testOut.toString(); + + assertTrue(out.contains("Snapshot removed on the following nodes [cnt=%d]:".formatted(nodesCnt))); + assertFalse(out.contains("the following nodes didn't find any snapshot data, nothing to delete")); + assertFalse(out.contains("Snapshot not found on current server nodes")); + + assertTrue(waitForCondition(() -> !snpDir.exists(), getTestTimeout())); } finally { - U.delete(snpDir); + if (cstSnpsRoot != null) + U.delete(cstSnpsRoot); } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/CoreMessagesProvider.java b/modules/core/src/main/java/org/apache/ignite/internal/CoreMessagesProvider.java index b4e1180250c86..95d28ef5ec2b9 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/CoreMessagesProvider.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/CoreMessagesProvider.java @@ -162,8 +162,8 @@ import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotCheckPartitionHashesResponse; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotCheckProcessRequest; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotCheckResponse; -import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotDeleteResponse; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotDeleteRequest; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotDeleteResponse; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotFilesFailureMessage; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotFilesRequestMessage; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotHandlerResult; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommandArg.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommandArg.java index 11e864ad84bb3..a0149e44a2d50 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommandArg.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommandArg.java @@ -37,7 +37,7 @@ public class SnapshotDeleteCommandArg extends IgniteDataTransferObject { @Order(1) @Argument(example = "path", optional = true, description = "Path to the directory where the snapshot is located. " + - "If not specified, the default configured snapshot directory will be used") + "If not specified, the default configured snapshot directory will be used.") String src; /** */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteTask.java index 2fe709c88c389..86bac6972bd2f 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteTask.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteTask.java @@ -17,13 +17,11 @@ package org.apache.ignite.internal.management.snapshot; -import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotDeleteProcessResult; import org.apache.ignite.internal.processors.task.GridInternal; import org.apache.ignite.internal.visor.VisorJob; import org.apache.ignite.internal.visor.VisorOneNodeTask; -import org.jetbrains.annotations.Nullable; /** * @see IgniteSnapshotManager#deleteSnapshot(String, String) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index aa35808c34451..cd9717ca534ba 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -118,8 +118,8 @@ private IgniteInternalFuture deletePhase(UUID ignored, S " Node is stopping [req=" + req + ']')); } - if (kctx.clientNode()) - return new GridFinishedFuture<>(new SnapshotDeleteResponse(-1)); + if (kctx.cluster().get().localNode().isClient()) + return new GridFinishedFuture<>(new SnapshotDeleteResponse(null)); kctx.security().authorize(ADMIN_SNAPSHOT); @@ -148,22 +148,37 @@ private IgniteInternalFuture deletePhase(UUID ignored, S } try { - var foundFlag = new AtomicBoolean(); + AtomicBoolean foundFlag = new AtomicBoolean(); boolean deleted = snpMgr.deleteLocalSnapshot(new SnapshotFileTree(kctx, req.snpName, req.snpPath), foundFlag); - if (deleted && log.isInfoEnabled()) - log.info("Snapshot successfully deleted, req=" + req); - else if (!deleted) - log.warning("Snapshot deleted not completely, req=" + req); + SnapshotDeleteResponse.SnapshotDeleteStatus res; - return new GridFinishedFuture<>(new SnapshotDeleteResponse(deleted ? 0 : 1)); + if (foundFlag.get()) { + if (deleted && log.isInfoEnabled()) + log.info("Snapshot successfully deleted, req=" + req); + else if (!deleted) + log.warning("Snapshot deleted not completely, req=" + req); + + res = deleted + ? SnapshotDeleteResponse.SnapshotDeleteStatus.DELETED + : SnapshotDeleteResponse.SnapshotDeleteStatus.PARTLY_DELETED; + } + else { + if (log.isInfoEnabled()) + log.info("Snapshot not found to delete, req=" + req); + + res = SnapshotDeleteResponse.SnapshotDeleteStatus.NO_FOUND; + } + + return new GridFinishedFuture<>(new SnapshotDeleteResponse(res)); } catch (Throwable t) { log.error("An error occured during snapshot deletion, req=" + req, t); return new GridFinishedFuture<>(t); - } finally { + } + finally { requests.remove(req.reqId); } } @@ -192,19 +207,21 @@ private void reducePhase(UUID reqId, Map results, var emptyNodes = new ArrayList(results.size()); results.forEach((nodeId, nodeRes) -> { - switch (nodeRes.deleted) { - case -1: - emptyNodes.add(nodeId); - break; - case 0: - completedNodes.add(nodeId); - break; - case 1: - uncompletedNodes.add(nodeId); - break; - default: - throw new IgniteIllegalStateException("Unknown snapshot deletion node result [nodeRes" + nodeRes + - ", nodeId=" + nodeId + ']'); + if (nodeRes.res != null) { + switch (nodeRes.res) { + case NO_FOUND: + emptyNodes.add(nodeId); + break; + case DELETED: + completedNodes.add(nodeId); + break; + case PARTLY_DELETED: + uncompletedNodes.add(nodeId); + break; + default: + throw new IgniteIllegalStateException("Unknown snapshot deletion node result, [nodeRes=" + + nodeRes + ", nodeId=" + nodeId + ']'); + } } }); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java index 04d5b1c2a7e5d..2650ac6d02da2 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java @@ -21,6 +21,7 @@ import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.plugin.extensions.communication.Message; import org.apache.ignite.plugin.extensions.communication.MessageFactory; +import org.jetbrains.annotations.Nullable; /** * Single-node result of the snapshot deletion distributed process. @@ -28,24 +29,30 @@ * @see SnapshotDeleteProcess */ public class SnapshotDeleteResponse implements Message { - /** The result. If {@code -1}, if node isn't a server node. {@code 0} if the snapshot was completely - * removed on the node. {@code 1}, if snapshot was removed on the node not completely. */ + /** {@code null} for client node. */ @Order(0) - byte deleted; + @Nullable SnapshotDeleteStatus res; /** Default constructor for {@link MessageFactory}. */ public SnapshotDeleteResponse() { // No-op. } - /** - * @param deleted If {@code -1}, if node isn't a server node. {@code 1} if the snapshot was completely - * removed on the node. {@code 0}, if snapshot was removed on the node not completely. - */ - SnapshotDeleteResponse(int deleted) { - assert deleted >= -1 && deleted < 2; + /** {@code null} for client node. */ + SnapshotDeleteResponse(@Nullable SnapshotDeleteStatus res) { + this.res = res; + } + + /** */ + public enum SnapshotDeleteStatus { + /** Snapshot found and completely deleted. */ + DELETED, + + /** Snapshot found but some files or directories might not be deleted (locked). */ + PARTLY_DELETED, - this.deleted = (byte)deleted; + /** Snapshot not found. */ + NO_FOUND } /** {@inheritDoc} */ From f0996e93952e9f771e81b4a55cb2b61324d91814 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Wed, 9 Sep 2026 17:07:39 +0300 Subject: [PATCH 08/97] better tests --- .../IgniteControlUtilityTestSuite.java | 2 + .../GridCommandHandlerDeleteSnapshotTest.java | 304 ++++++++++++++++++ .../ignite/util/GridCommandHandlerTest.java | 122 +------ 3 files changed, 316 insertions(+), 112 deletions(-) create mode 100644 modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java diff --git a/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite.java b/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite.java index 6528d919f6105..91a1d67d0dfc0 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite.java @@ -27,6 +27,7 @@ import org.apache.ignite.util.GridCommandHandlerCheckpointTest; import org.apache.ignite.util.GridCommandHandlerClusterByClassTest; import org.apache.ignite.util.GridCommandHandlerClusterByClassWithSSLTest; +import org.apache.ignite.util.GridCommandHandlerDeleteSnapshotTest; import org.apache.ignite.util.GridCommandHandlerIncompatibleSslConfigTest; import org.apache.ignite.util.GridCommandHandlerIndexingCheckSizeTest; import org.apache.ignite.util.GridCommandHandlerIndexingClusterByClassTest; @@ -74,6 +75,7 @@ GridCommandHandlerCheckIndexesInlineSizeTest.class, GridCommandHandlerMetadataTest.class, GridCommandHandlerCheckIncrementalSnapshotTest.class, + GridCommandHandlerDeleteSnapshotTest.class, GridCommandHandlerLegacyClientTest.class, KillCommandsControlShTest.class, diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java new file mode 100644 index 0000000000000..34dbbddc0d213 --- /dev/null +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java @@ -0,0 +1,304 @@ +/* + * 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.ignite.util; + +import java.io.File; +import java.nio.file.DirectoryStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import org.apache.ignite.IgniteDataStreamer; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.testframework.ListeningTestLogger; +import org.jetbrains.annotations.Nullable; +import org.junit.Test; + +import static java.nio.file.Files.newDirectoryStream; +import static org.apache.ignite.cluster.ClusterState.ACTIVE; +import static org.apache.ignite.internal.commandline.CommandHandler.EXIT_CODE_OK; +import static org.apache.ignite.internal.processors.cache.persistence.snapshot.AbstractSnapshotSelfTest.snp; +import static org.apache.ignite.testframework.GridTestUtils.waitForCondition; + +/** Test for the "`--snapshot delete` command". */ +public class GridCommandHandlerDeleteSnapshotTest extends GridCommandHandlerClusterPerMethodAbstractTest { + /** */ + protected @Nullable ListeningTestLogger listeningLog; + + /** */ + protected boolean separateWorkDir; + + /** {@inheritDoc} */ + @Override protected void beforeTest() throws Exception { + super.beforeTest(); + + // Handy if other test runs interrupted. + cleanPersistenceDir(); + } + + /** {@inheritDoc} */ + @Override protected void cleanPersistenceDir() throws Exception { + super.cleanPersistenceDir(); + + try (DirectoryStream files = newDirectoryStream(Paths.get(U.defaultWorkDirectory()))) { + for (Path path : files) + U.delete(path); + } + } + + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { + IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); + + if (listeningLog != null) + cfg.setGridLogger(listeningLog); + + if (separateWorkDir) + cfg.setWorkDirectory(new File(U.defaultWorkDirectory(), igniteInstanceName).getAbsolutePath()); + + return cfg; + } + + /** */ + @Test + public void testSnapshotDelete() throws Exception { + doTestSnapshotDelete(null, false, false, false, false); + } + + /** */ + @Test + public void testSnapshotDeleteAddServer() throws Exception { + doTestSnapshotDelete(true, false, false, false, false); + } + + /** */ + @Test + public void testSnapshotDeleteAddClient() throws Exception { + doTestSnapshotDelete(false, false, false, false, false); + } + + /** */ + @Test + public void testSnapshotDeleteAddServerAddIncrementals() throws Exception { + doTestSnapshotDelete(true, false, true, false, false); + } + + /** */ + @Test + public void testSnapshotDeleteChangeBaseline() throws Exception { + doTestSnapshotDelete(null, false, false, true, false); + } + + /** */ + @Test + public void testSnapshotDeleteAddServerChangeBaseline() throws Exception { + doTestSnapshotDelete(true, false, false, true, false); + } + + /** */ + @Test + public void testSnapshotDeleteAddServerIncrementalsChangeBaseline() throws Exception { + doTestSnapshotDelete(true, false, false, true, false); + } + + /** */ + @Test + public void testSnapshotDeleteAddClientChangeBaseline() throws Exception { + doTestSnapshotDelete(false, false, false, true, false); + } + + /** */ + @Test + public void testSnapshotDeleteAddClientChangeBaselineIncrementals() throws Exception { + doTestSnapshotDelete(false, false, true, true, false); + } + + /** */ + @Test + public void testSnapshotDeleteSetCustomPath() throws Exception { + doTestSnapshotDelete(null, true, false, false, false); + } + + /** */ + @Test + public void testSnapshotDeleteSetCustomPathAddClient() throws Exception { + doTestSnapshotDelete(false, true, false, false, false); + } + + /** */ + @Test + public void testSnapshotDeleteIncrementsSetCustomPathAddClient() throws Exception { + doTestSnapshotDelete(false, true, true, false, false); + } + + /** */ + @Test + public void testSnapshotDeleteSetCustomPathAddClientChangeBaseline() throws Exception { + doTestSnapshotDelete(false, true, false, true, false); + } + + /** */ + @Test + public void testSnapshotDeleteSameWorkDirectory() throws Exception { + doTestSnapshotDelete(null, false, false, false, true); + } + + /** */ + @Test + public void testSnapshotDeleteSameWorkDirectoryAddServer() throws Exception { + doTestSnapshotDelete(true, false, false, false, true); + } + + /** */ + @Test + public void testSnapshotDeleteSameWorkDirectoryAddClient() throws Exception { + doTestSnapshotDelete(false, false, false, false, true); + } + + /** */ + @Test + public void testSnapshotDeleteSameWorkDirectoryAddServerChangeBaseline() throws Exception { + doTestSnapshotDelete(true, false, false, true, true); + } + + /** */ + @Test + public void testSnapshotDeleteSameWorkDirectoryAddServerChangeBaselineSetCustomPath() throws Exception { + doTestSnapshotDelete(true, true, false, true, true); + } + + /** */ + private void doTestSnapshotDelete( + @Nullable Boolean extraNodeIsServer, + boolean customPath, + boolean addIncrements, + boolean changeBaseline, + boolean sameWorkDir + ) throws Exception { + int entriesCnt = 4000; + int initNodes = 3; + + walCompactionEnabled(addIncrements); + + separateWorkDir = !sameWorkDir; + + IgniteEx ig = (IgniteEx)startGridsMultiThreaded(initNodes); + + if (changeBaseline) { + ig.cluster().baselineAutoAdjustEnabled(false); + + ig.cluster().setBaselineTopology(ig.cluster().topologyVersion()); + } + + ig.cluster().state(ACTIVE); + + createCacheAndPreload(ig, entriesCnt); + + File cstSnpsRoot = customPath ? new File(U.defaultWorkDirectory(), "ex_snapshots") : null; + File snpDir = new File(customPath ? cstSnpsRoot : ig.context().pdsFolderResolver().fileTree().snapshotsRoot(), "testSnapshot"); + + snp(ig).createSnapshot("testSnapshot", customPath ? cstSnpsRoot.getAbsolutePath() : null, false, false) + .get(getTestTimeout()); + + if (addIncrements) { + for (int i = 0; i < 3; ++i) { + int dataIdx = entriesCnt + entriesCnt / 4 * i; + + try (IgniteDataStreamer streamer = ig.dataStreamer(DEFAULT_CACHE_NAME)) { + for (int d = dataIdx; d < dataIdx + entriesCnt / 4; ++d) + streamer.addData(i, i); + } + + snp(ig).createSnapshot("testSnapshot", customPath ? cstSnpsRoot.getAbsolutePath() : null, true, false) + .get(getTestTimeout()); + } + } + + // Optionally restarts with the same servers number, but changed baseline. The snapshot is kept on the same + // previous nodes independenlty of the baseline. + if (changeBaseline) { + ig.destroyCache(DEFAULT_CACHE_NAME); + awaitPartitionMapExchange(); + + stopAllGrids(); + + ig = (IgniteEx)startGridsMultiThreaded(initNodes - 1); + + ig.cluster().setBaselineTopology(ig.cluster().topologyVersion()); + + startGrid(initNodes - 1); + + assertEquals(initNodes - 1, ig.cluster().currentBaselineTopology().size()); + assertEquals(initNodes, ig.cluster().nodes().size()); + } + + // Optionally adds extra server or client node. + if (Boolean.TRUE.equals(extraNodeIsServer)) + startGrid(initNodes); + else if (Boolean.FALSE.equals(extraNodeIsServer)) + startGrid(CLIENT_NODE_NAME_PREFIX); + + injectTestSystemOut(); + + // Tests missing snapshot deletion. + if (customPath) { + assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "delete", "--src", + cstSnpsRoot.getAbsolutePath(), "wrongSnapshot")); + } + else + assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "delete", "wrongSnapshot")); + + String out = testOut.toString(); + + assertFalse(out.contains("Snapshot removed on the following nodes")); + assertFalse(out.contains("the following nodes didn't find any snapshot data, nothing to delete")); + assertTrue(out.contains("Snapshot not found on current server nodes")); + + testOut.reset(); + assertTrue(testOut.toString().isEmpty()); + + if (customPath) { + assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "delete", "--src", + cstSnpsRoot.getAbsolutePath(), "testSnapshot")); + } + else + assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "delete", "testSnapshot")); + + out = testOut.toString(); + + if (sameWorkDir) { + // When nodes use a shared work dirictory, there is a race for the delete operation. One node can get faster + // than anothers and remove snasphot completely quickly. The others might not find snapshot files. We can be + // only sure that at least one node removes snapshot. + assertTrue(out.contains("Snapshot removed on the following nodes [cnt=")); + } + else { + // When the nodes use own separated work dirictory, we expect a strict result. + assertTrue(out.contains("Snapshot removed on the following nodes [cnt=%d]:".formatted(initNodes))); + + if (Boolean.FALSE.equals(extraNodeIsServer)) + assertFalse(out.contains("the following nodes didn't find any snapshot data, nothing to delete")); + else if (Boolean.TRUE.equals(extraNodeIsServer)) + assertTrue(out.contains("the following nodes didn't find any snapshot data, nothing to delete [cnt=1]:")); + } + + assertFalse(out.contains("Snapshot not found on current server nodes")); + + assertTrue(waitForCondition(() -> !snpDir.exists(), getTestTimeout())); + } +} diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java index a51574eeb932f..7e407b5d5ad11 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java @@ -239,8 +239,14 @@ public class GridCommandHandlerTest extends GridCommandHandlerClusterPerMethodAb initDiagnosticDir(); - // Handy if other test runs interrupted. - cleanPersistenceDir(); + cleanDiagnosticDir(); + } + + /** {@inheritDoc} */ + @Override protected void afterTest() throws Exception { + super.afterTest(); + + listeningLog = null; } /** {@inheritDoc} */ @@ -1901,8 +1907,8 @@ public void testBaselineAddOnNotActiveCluster() throws Exception { // Ignite instase 1 can be logged only in arguments list. boolean isInstance1Found = Arrays.stream(testOutStr.split("\n")) - .filter(s -> s.contains("Arguments:")) - .noneMatch(s -> s.contains(getTestIgniteInstanceName() + "1")); + .filter(s -> s.contains("Arguments:")) + .noneMatch(s -> s.contains(getTestIgniteInstanceName() + "1")); assertTrue(testOutStr, testOutStr.contains("Node not found for consistent ID:")); @@ -3237,114 +3243,6 @@ public void testCheckSnapshot() throws Exception { assertContains(log, sb.toString(), "The check procedure has finished, no conflicts have been found"); } - /** */ - @Test - public void testSnapshotDelete() throws Exception { - doTestSnapshotDelete(false, false, false); - } - - /** */ - @Test - public void testSnapshotDeleteCustomPath() throws Exception { - doTestSnapshotDelete(false, true, false); - } - - /** */ - @Test - public void testSnapshotDeleteClient() throws Exception { - doTestSnapshotDelete(true, false, false); - } - - /** */ - @Test - public void testSnapshotDeleteCustomPathClient() throws Exception { - doTestSnapshotDelete(true, true, false); - } - - /** */ - @Test - public void testSnapshotDeleteIncrementsCustomPathClient() throws Exception { - doTestSnapshotDelete(true, true, true); - } - - /** */ - private void doTestSnapshotDelete(boolean withClientGrid, boolean customPath, boolean addIncrements) throws Exception { - int nodesCnt = 3; - int entriesCnt = 4000; - - walCompactionEnabled(addIncrements); - - IgniteEx ig = (IgniteEx)startGridsMultiThreaded(nodesCnt); - - if (withClientGrid) - startGrid(CLIENT_NODE_NAME_PREFIX); - - ig.cluster().state(ACTIVE); - - createCacheAndPreload(ig, entriesCnt); - - File cstSnpsRoot = customPath ? new File(U.defaultWorkDirectory(), "ex_snapshots") : null; - File snpDir = new File(customPath ? cstSnpsRoot : ig.context().pdsFolderResolver().fileTree().snapshotsRoot(), "testSnapshot"); - - try { - snp(ig).createSnapshot("testSnapshot", customPath ? cstSnpsRoot.getAbsolutePath() : null, false, false) - .get(getTestTimeout()); - - if (addIncrements) { - for (int i = 0; i < 3; ++i) { - int dataIdx = entriesCnt + entriesCnt / 4 * i; - - try (IgniteDataStreamer streamer = ig.dataStreamer(DEFAULT_CACHE_NAME)) { - for (int d = dataIdx; d < dataIdx + entriesCnt / 4; ++d) - streamer.addData(i, i); - } - - snp(ig).createSnapshot("testSnapshot", customPath ? cstSnpsRoot.getAbsolutePath() : null, true, false) - .get(getTestTimeout()); - } - } - - assertTrue("Snapshot directory must exist: " + snpDir, snpDir.exists()); - - injectTestSystemOut(); - - if (customPath) { - assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "delete", "--src", - cstSnpsRoot.getAbsolutePath(), "wrongSnapshot")); - } - else - assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "delete", "wrongSnapshot")); - - String out = testOut.toString(); - - assertFalse(out.contains("Snapshot removed on the following nodes [cnt=%d]:".formatted(nodesCnt))); - assertFalse(out.contains("the following nodes didn't find any snapshot data, nothing to delete")); - assertTrue(out.contains("Snapshot not found on current server nodes")); - - testOut.reset(); - assertTrue(testOut.toString().isEmpty()); - - if (customPath) { - assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "delete", "--src", - cstSnpsRoot.getAbsolutePath(), "testSnapshot")); - } - else - assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "delete", "testSnapshot")); - - out = testOut.toString(); - - assertTrue(out.contains("Snapshot removed on the following nodes [cnt=%d]:".formatted(nodesCnt))); - assertFalse(out.contains("the following nodes didn't find any snapshot data, nothing to delete")); - assertFalse(out.contains("Snapshot not found on current server nodes")); - - assertTrue(waitForCondition(() -> !snpDir.exists(), getTestTimeout())); - } - finally { - if (cstSnpsRoot != null) - U.delete(cstSnpsRoot); - } - } - /** @throws Exception If fails. */ @Test public void testSnapshotRestoreSynchronously() throws Exception { From 80c76445018377d39cc83bce76cab27b6649d74d Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Wed, 9 Sep 2026 18:13:38 +0300 Subject: [PATCH 09/97] own test --- .../GridCommandHandlerDeleteSnapshotTest.java | 199 ++++++------------ .../snapshot/SnapshotDeleteProcess.java | 4 +- .../snapshot/SnapshotDeleteResponse.java | 2 +- 3 files changed, 66 insertions(+), 139 deletions(-) diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java index 34dbbddc0d213..fc53a9db596de 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java @@ -21,27 +21,68 @@ import java.nio.file.DirectoryStream; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Collection; import org.apache.ignite.IgniteDataStreamer; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.internal.U; -import org.apache.ignite.testframework.ListeningTestLogger; -import org.jetbrains.annotations.Nullable; +import org.apache.ignite.testframework.GridTestUtils; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; import static java.nio.file.Files.newDirectoryStream; import static org.apache.ignite.cluster.ClusterState.ACTIVE; import static org.apache.ignite.internal.commandline.CommandHandler.EXIT_CODE_OK; import static org.apache.ignite.internal.processors.cache.persistence.snapshot.AbstractSnapshotSelfTest.snp; import static org.apache.ignite.testframework.GridTestUtils.waitForCondition; +import static org.junit.Assume.assumeTrue; + +/** Test for the command '--snapshot delete'. */ +@RunWith(Parameterized.class) +public class GridCommandHandlerDeleteSnapshotTest extends GridCommandHandlerAbstractTest { + /** Value: -1 - do not use, 1 - server node, 0 - client node. */ + @Parameter(1) + public int extraNodeIsServer = -1; + + /** */ + @Parameter(2) + public boolean addIncrements; -/** Test for the "`--snapshot delete` command". */ -public class GridCommandHandlerDeleteSnapshotTest extends GridCommandHandlerClusterPerMethodAbstractTest { /** */ - protected @Nullable ListeningTestLogger listeningLog; + @Parameter(3) + public boolean changeBaseline; /** */ - protected boolean separateWorkDir; + @Parameter(4) + public boolean customPath; + + /** */ + @Parameter(5) + public boolean separatedWorkDir; + + /** */ + @Parameters(name = "client={0},useExtraNode={1},inc={2},chBaseln={3},cstSnpPath={4},ownWorkDir={5}") + public static Collection parameters() { + return GridTestUtils.cartesianProduct( + commandHandlers(), + F.asList(-1, 1, 0), // Use extra node (do not use at all, server node, client node); + F.asList(false, true), // Add increments to the test snapshot; + F.asList(false, true), // Change baseline; + F.asList(false, true), // Use custom snapshot path; + F.asList(true, false) // Separated (own) work directory. + ); + } + + /** {@inheritDoc} */ + @Override protected void afterTest() throws Exception { + super.afterTest(); + + stopAllGrids(); + } /** {@inheritDoc} */ @Override protected void beforeTest() throws Exception { @@ -65,10 +106,7 @@ public class GridCommandHandlerDeleteSnapshotTest extends GridCommandHandlerClus @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); - if (listeningLog != null) - cfg.setGridLogger(listeningLog); - - if (separateWorkDir) + if (separatedWorkDir) cfg.setWorkDirectory(new File(U.defaultWorkDirectory(), igniteInstanceName).getAbsolutePath()); return cfg; @@ -77,126 +115,15 @@ public class GridCommandHandlerDeleteSnapshotTest extends GridCommandHandlerClus /** */ @Test public void testSnapshotDelete() throws Exception { - doTestSnapshotDelete(null, false, false, false, false); - } - - /** */ - @Test - public void testSnapshotDeleteAddServer() throws Exception { - doTestSnapshotDelete(true, false, false, false, false); - } - - /** */ - @Test - public void testSnapshotDeleteAddClient() throws Exception { - doTestSnapshotDelete(false, false, false, false, false); - } - - /** */ - @Test - public void testSnapshotDeleteAddServerAddIncrementals() throws Exception { - doTestSnapshotDelete(true, false, true, false, false); - } - - /** */ - @Test - public void testSnapshotDeleteChangeBaseline() throws Exception { - doTestSnapshotDelete(null, false, false, true, false); - } - - /** */ - @Test - public void testSnapshotDeleteAddServerChangeBaseline() throws Exception { - doTestSnapshotDelete(true, false, false, true, false); - } - - /** */ - @Test - public void testSnapshotDeleteAddServerIncrementalsChangeBaseline() throws Exception { - doTestSnapshotDelete(true, false, false, true, false); - } - - /** */ - @Test - public void testSnapshotDeleteAddClientChangeBaseline() throws Exception { - doTestSnapshotDelete(false, false, false, true, false); - } - - /** */ - @Test - public void testSnapshotDeleteAddClientChangeBaselineIncrementals() throws Exception { - doTestSnapshotDelete(false, false, true, true, false); - } - - /** */ - @Test - public void testSnapshotDeleteSetCustomPath() throws Exception { - doTestSnapshotDelete(null, true, false, false, false); - } - - /** */ - @Test - public void testSnapshotDeleteSetCustomPathAddClient() throws Exception { - doTestSnapshotDelete(false, true, false, false, false); - } - - /** */ - @Test - public void testSnapshotDeleteIncrementsSetCustomPathAddClient() throws Exception { - doTestSnapshotDelete(false, true, true, false, false); - } - - /** */ - @Test - public void testSnapshotDeleteSetCustomPathAddClientChangeBaseline() throws Exception { - doTestSnapshotDelete(false, true, false, true, false); - } - - /** */ - @Test - public void testSnapshotDeleteSameWorkDirectory() throws Exception { - doTestSnapshotDelete(null, false, false, false, true); - } - - /** */ - @Test - public void testSnapshotDeleteSameWorkDirectoryAddServer() throws Exception { - doTestSnapshotDelete(true, false, false, false, true); - } - - /** */ - @Test - public void testSnapshotDeleteSameWorkDirectoryAddClient() throws Exception { - doTestSnapshotDelete(false, false, false, false, true); - } + // A custom snapshot path actually puts snapshots in a shared directory. This skews the results when dedicated + // work directories are set. + assumeTrue(!customPath || !separatedWorkDir); - /** */ - @Test - public void testSnapshotDeleteSameWorkDirectoryAddServerChangeBaseline() throws Exception { - doTestSnapshotDelete(true, false, false, true, true); - } - - /** */ - @Test - public void testSnapshotDeleteSameWorkDirectoryAddServerChangeBaselineSetCustomPath() throws Exception { - doTestSnapshotDelete(true, true, false, true, true); - } - - /** */ - private void doTestSnapshotDelete( - @Nullable Boolean extraNodeIsServer, - boolean customPath, - boolean addIncrements, - boolean changeBaseline, - boolean sameWorkDir - ) throws Exception { int entriesCnt = 4000; int initNodes = 3; walCompactionEnabled(addIncrements); - separateWorkDir = !sameWorkDir; - IgniteEx ig = (IgniteEx)startGridsMultiThreaded(initNodes); if (changeBaseline) { @@ -248,9 +175,9 @@ private void doTestSnapshotDelete( } // Optionally adds extra server or client node. - if (Boolean.TRUE.equals(extraNodeIsServer)) + if (extraNodeIsServer == 1) startGrid(initNodes); - else if (Boolean.FALSE.equals(extraNodeIsServer)) + else if (extraNodeIsServer == 0) startGrid(CLIENT_NODE_NAME_PREFIX); injectTestSystemOut(); @@ -281,20 +208,20 @@ else if (Boolean.FALSE.equals(extraNodeIsServer)) out = testOut.toString(); - if (sameWorkDir) { - // When nodes use a shared work dirictory, there is a race for the delete operation. One node can get faster - // than anothers and remove snasphot completely quickly. The others might not find snapshot files. We can be - // only sure that at least one node removes snapshot. - assertTrue(out.contains("Snapshot removed on the following nodes [cnt=")); - } - else { + if (separatedWorkDir) { // When the nodes use own separated work dirictory, we expect a strict result. assertTrue(out.contains("Snapshot removed on the following nodes [cnt=%d]:".formatted(initNodes))); - if (Boolean.FALSE.equals(extraNodeIsServer)) - assertFalse(out.contains("the following nodes didn't find any snapshot data, nothing to delete")); - else if (Boolean.TRUE.equals(extraNodeIsServer)) + if (extraNodeIsServer == 1) assertTrue(out.contains("the following nodes didn't find any snapshot data, nothing to delete [cnt=1]:")); + else if (extraNodeIsServer == 0) + assertFalse(out.contains("the following nodes didn't find any snapshot data, nothing to delete")); + } + else { + // When nodes use a shared work dirictory, there is a race for the delete operation. One node can get faster + // than anothers and remove snasphot completely quickly. The others might not find snapshot files. We can be + // only sure that at least one node removes snapshot. + assertTrue(out.contains("Snapshot removed on the following nodes [cnt=")); } assertFalse(out.contains("Snapshot not found on current server nodes")); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index cd9717ca534ba..c90fbc9a579a5 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -168,7 +168,7 @@ else if (!deleted) if (log.isInfoEnabled()) log.info("Snapshot not found to delete, req=" + req); - res = SnapshotDeleteResponse.SnapshotDeleteStatus.NO_FOUND; + res = SnapshotDeleteResponse.SnapshotDeleteStatus.NOT_FOUND; } return new GridFinishedFuture<>(new SnapshotDeleteResponse(res)); @@ -209,7 +209,7 @@ private void reducePhase(UUID reqId, Map results, results.forEach((nodeId, nodeRes) -> { if (nodeRes.res != null) { switch (nodeRes.res) { - case NO_FOUND: + case NOT_FOUND: emptyNodes.add(nodeId); break; case DELETED: diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java index 2650ac6d02da2..adb6c66a096e2 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java @@ -52,7 +52,7 @@ public enum SnapshotDeleteStatus { PARTLY_DELETED, /** Snapshot not found. */ - NO_FOUND + NOT_FOUND } /** {@inheritDoc} */ From fc11a5f268fe01790e82a783d98bf333be3c901c Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Thu, 10 Sep 2026 17:03:11 +0300 Subject: [PATCH 10/97] + test --- .../OperatorsExtensionIntegrationTest.java | 92 ++++++++++++++++++- .../ignite/util/GridCommandHandlerTest.java | 4 +- .../snapshot/SnapshotDeleteCommand.java | 3 +- .../snapshot/IgniteSnapshotManager.java | 11 +-- .../snapshot/SnapshotCheckProcess.java | 10 +- .../snapshot/SnapshotDeleteProcess.java | 62 ++++++++----- .../snapshot/SnapshotRestoreProcess.java | 6 +- .../util/distributed/DistributedProcess.java | 10 +- .../ignite/spi/discovery/tcp/ClientImpl.java | 66 ++++++------- .../ignite/spi/discovery/tcp/ServerImpl.java | 90 ++++++++---------- .../spi/discovery/tcp/TcpDiscoveryImpl.java | 19 ++-- .../discovery/tcp/TcpDiscoveryIoSession.java | 40 ++++---- .../tcp/TcpDiscoveryMessageSerializer.java | 9 +- .../spi/discovery/tcp/TcpDiscoverySpi.java | 49 ++++------ .../binary/BinaryClassLoaderMultiJvmTest.java | 49 +++++++++- .../cache/CacheMetricsCacheSizeTest.java | 3 +- .../IgniteClusterSnapshotCheckTest.java | 54 +++++++++-- .../IgniteClusterSnapshotDeleteTest.java | 45 +-------- .../ignite/spi/MessagesPluginProvider.java | 13 --- .../discovery/tcp/BlockTcpDiscoverySpi.java | 2 +- .../DiscoveryUnmarshalVulnerabilityTest.java | 2 +- .../tcp/TcpClientDiscoverySpiSelfTest.java | 2 +- .../discovery/tcp/TestTcpDiscoverySpi.java | 50 +--------- .../testsuites/IgniteSnapshotTestSuite8.java | 2 + .../tests/ContinuousDataLoadApplication.java | 2 +- 25 files changed, 372 insertions(+), 323 deletions(-) diff --git a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java index 35a6c1bf96cb9..004580b915c45 100644 --- a/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java +++ b/modules/calcite/src/test/java/org/apache/ignite/internal/processors/query/calcite/integration/OperatorsExtensionIntegrationTest.java @@ -50,6 +50,8 @@ import org.apache.calcite.sql.type.SqlTypeName; import org.apache.calcite.sql.util.ReflectiveSqlOperatorTable; import org.apache.calcite.sql.util.SqlOperatorTables; +import org.apache.calcite.sql.validate.SqlConformance; +import org.apache.calcite.sql.validate.SqlDelegatingConformance; import org.apache.calcite.sql.validate.SqlValidator; import org.apache.calcite.sql2rel.SqlRexContext; import org.apache.calcite.sql2rel.SqlRexConvertlet; @@ -57,7 +59,10 @@ import org.apache.calcite.tools.Frameworks; import org.apache.calcite.util.BuiltInMethod; import org.apache.calcite.util.Optionality; +import org.apache.ignite.configuration.CacheConfiguration; import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.processors.query.IgniteSQLException; import org.apache.ignite.internal.processors.query.calcite.CalciteQueryProcessor; import org.apache.ignite.internal.processors.query.calcite.exec.ExecutionContext; import org.apache.ignite.internal.processors.query.calcite.exec.RowHandler; @@ -79,8 +84,22 @@ * Tests SQL engine extension with plugin. */ public class OperatorsExtensionIntegrationTest extends AbstractBasicIntegrationTest { + /** */ + private static final SqlConformance TEST_CONFORMANCE = new SqlDelegatingConformance( + CalciteQueryProcessor.FRAMEWORK_CONFIG.getParserConfig().conformance()) { + /** {@inheritDoc} */ + @Override public boolean isSupportedDualTable() { + return true; + } + }; + /** {@inheritDoc} */ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { + return getConfiguration(igniteInstanceName, TEST_CONFORMANCE); + } + + /** */ + private IgniteConfiguration getConfiguration(String igniteInstanceName, SqlConformance conformance) throws Exception { return super.getConfiguration(igniteInstanceName) .setPluginProviders(new AbstractTestPluginProvider() { @Override public String name() { @@ -90,12 +109,15 @@ public class OperatorsExtensionIntegrationTest extends AbstractBasicIntegrationT @Override public @Nullable T createComponent(PluginContext ctx, Class cls) { if (FrameworkConfig.class.equals(cls)) { FrameworkConfig cfg = Frameworks.newConfigBuilder(CalciteQueryProcessor.FRAMEWORK_CONFIG) + .parserConfig(CalciteQueryProcessor.FRAMEWORK_CONFIG.getParserConfig() + .withConformance(conformance)) .convertletTable(new ConvertletTable()) .operatorTable(SqlOperatorTables.chain( new OperatorTable().init(), CalciteQueryProcessor.FRAMEWORK_CONFIG.getOperatorTable())) .sqlValidatorConfig( ((IgniteSqlValidator.Config)CalciteQueryProcessor.FRAMEWORK_CONFIG.getSqlValidatorConfig()) - .withSqlNodeRewriter(new SqlRewriter())) + .withSqlNodeRewriter(new SqlRewriter()) + .withConformance(conformance)) .context(Contexts.chain( CalciteQueryProcessor.FRAMEWORK_CONFIG.getContext(), Contexts.of(IgniteSqlSemantics.builder() @@ -238,6 +260,74 @@ public void testPaginationRoundingPolicy() { .check(); } + /** */ + @Test + public void testDualTable() { + assertQuery("SELECT 1 + 1 FROM dual").returns(2).check(); + + assertQuery("SELECT * FROM DUAL") + .columnNames("DUMMY") + .returns("X") + .check(); + + assertQuery("SELECT DUMMY FROM DUAL") + .columnNames("DUMMY") + .returns("X") + .check(); + + assertQuery("SELECT LAG(rate, 1, rate) OVER (ORDER BY period) FROM " + + "(SELECT 1 AS rate, 1 AS period FROM dual)") + .returns(1) + .check(); + } + + /** */ + @Test + public void testDualWithisFromRequired() throws Exception { + SqlConformance conformance = new SqlDelegatingConformance(TEST_CONFORMANCE) { + /** {@inheritDoc} */ + @Override public boolean isFromRequired() { + return true; + } + }; + + try (IgniteEx c = startClientGrid(getConfiguration("from-required-client", conformance))) { + assertThrows(c, "SELECT 1", IgniteSQLException.class, "SELECT must have a FROM clause"); + + assertQuery(c, "SELECT 1 + 1 FROM dual").returns(2).check(); + } + } + + /** */ + @Test + public void testDualTableInNewSchema() { + client.getOrCreateCache(new CacheConfiguration() + .setName("CUSTOM_SCHEMA_MARKER") + .setSqlSchema("CUSTOM_SCHEMA") + .setIndexedTypes(Integer.class, Integer.class)); + + assertQuery("SELECT DUMMY FROM CUSTOM_SCHEMA.DUAL") + .columnNames("DUMMY") + .returns("X") + .check(); + } + + /** */ + @Test + public void testUserDefinedDualView() { + sql("CREATE VIEW PUBLIC.DUAL AS SELECT 'USER' AS DUMMY"); + + try { + assertQuery("SELECT DUMMY FROM PUBLIC.DUAL") + .columnNames("DUMMY") + .returns("USER") + .check(); + } + finally { + sql("DROP VIEW IF EXISTS PUBLIC.DUAL"); + } + } + /** Rewrites LTRIM with 2 parameters. */ public static SqlCall rewriteLtrim(SqlValidator validator, SqlCall call) { if (call.operandCount() != 2) diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java index 7e407b5d5ad11..b5f2659b1fa0b 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerTest.java @@ -1907,8 +1907,8 @@ public void testBaselineAddOnNotActiveCluster() throws Exception { // Ignite instase 1 can be logged only in arguments list. boolean isInstance1Found = Arrays.stream(testOutStr.split("\n")) - .filter(s -> s.contains("Arguments:")) - .noneMatch(s -> s.contains(getTestIgniteInstanceName() + "1")); + .filter(s -> s.contains("Arguments:")) + .noneMatch(s -> s.contains(getTestIgniteInstanceName() + "1")); assertTrue(testOutStr, testOutStr.contains("Node not found for consistent ID:")); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java index 10d23c4cb1697..8a56f82ec78ec 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java @@ -28,7 +28,7 @@ public class SnapshotDeleteCommand extends AbstractSnapshotCommand { /** {@inheritDoc} */ @Override public String description() { - return "Delete snapshot and all its increments from the cluster"; + return "Delete snapshot and all its increments from all the online server nodes."; } /** {@inheritDoc} */ @@ -58,7 +58,6 @@ public class SnapshotDeleteCommand extends AbstractSnapshotCommand initLocalSnapshotStartSt "re-encryption process is not finished yet.")); } + if (cctx.snapshotMgr().isSnapshotDeleting(req.snapshotName())) { + return new GridFinishedFuture<>(new IgniteCheckedException("Snapshot operation has been rejected. Snapshot " + + "'%s' is being deleted.".formatted(req.snapshotName()))); + } + List grpIds = new ArrayList<>(F.viewReadOnly(req.groups(), CU::cacheId)); Collection comprGrpIds = F.view(grpIds, i -> { CacheGroupDescriptor desc = cctx.cache().cacheGroupDescriptor(i); @@ -2114,12 +2119,6 @@ public IgniteFutureImpl createSnapshot( ); } - if (isSnapshotDeleting(name)) { - throw new IgniteException( - "Snapshot operation has been rejected. Snapshot delete operation is currently in progress." - ); - } - snpFut0 = new ClusterSnapshotFuture(UUID.randomUUID(), name, incIdx); clusterSnpFut = snpFut0; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java index 2f19878617a15..b3f5f1402630c 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java @@ -32,6 +32,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import org.apache.ignite.IgniteException; +import org.apache.ignite.IgniteIllegalStateException; import org.apache.ignite.IgniteLogger; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.internal.GridKernalContext; @@ -491,8 +492,13 @@ private IgniteInternalFuture prepareAndCheckMetas(UUID ig } if (!ctx.req.requestId().equals(req.requestId())) { - return new GridFinishedFuture<>(new IllegalStateException("Validation of snapshot '" + req.snapshotName() - + "' has already started [ctx=" + ctx + ']')); + return new GridFinishedFuture<>(new IgniteIllegalStateException("Validation of snapshot '" + req.snapshotName() + + "' has already started [req=" + req + ']')); + } + + if (kctx.cache().context().snapshotMgr().isSnapshotDeleting(req.snapshotName())) { + return new GridFinishedFuture<>(new IgniteIllegalStateException("Snapshot '" + req.snapshotName() + + "' is being deleted [req=" + req + ']')); } // Excludes non-baseline initiator. diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index c90fbc9a579a5..c6c5bae82427c 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -33,6 +33,7 @@ import org.apache.ignite.internal.util.future.GridFutureAdapter; import org.apache.ignite.internal.util.future.IgniteFutureImpl; import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.T2; import org.apache.ignite.lang.IgniteFuture; import org.jetbrains.annotations.Nullable; @@ -57,10 +58,10 @@ public class SnapshotDeleteProcess { private volatile boolean interrupted; /** Cluster-wide operation futures per request id on certain node. */ - private final Map> clusterOpFuts = new ConcurrentHashMap<>(); + private final Map>> clusterOpFuts = new ConcurrentHashMap<>(); - /** Process requests per id on eah baseline node. */ - private final Map requests = new ConcurrentHashMap<>(); + /** Process requests per snapshot name on each server node. */ + private final Map requests = new ConcurrentHashMap<>(); /** The distributed process. */ private final DistributedProcess distrProc; @@ -95,7 +96,7 @@ public IgniteFuture start(String snpName, @Nullable if (interrupted || kctx.isStopping()) throw new NodeStoppingException("Failed to start snapshot delete process: node is stopping."); - clusterOpFuts.put(reqId, clusterOpFut); + clusterOpFuts.put(reqId, new T2<>(snpName, clusterOpFut)); } SnapshotDeleteRequest req = new SnapshotDeleteRequest(reqId, snpName, snpPath); @@ -115,7 +116,7 @@ public IgniteFuture start(String snpName, @Nullable private IgniteInternalFuture deletePhase(UUID ignored, SnapshotDeleteRequest req) { if (kctx.isStopping()) { return new GridFinishedFuture<>(new NodeStoppingException(OP_REJECT_MSG + - " Node is stopping [req=" + req + ']')); + " Node is stopping, req=" + req)); } if (kctx.cluster().get().localNode().isClient()) @@ -128,26 +129,26 @@ private IgniteInternalFuture deletePhase(UUID ignored, S var curCreateRq = snpMgr.currentCreateRequest(); if (curCreateRq != null && curCreateRq.snpName.equals(req.snpName)) { - return new GridFinishedFuture<>(new IllegalStateException(OP_REJECT_MSG + - " Snapshot with this name is being created [req=" + req + ']')); + return new GridFinishedFuture<>(new IgniteIllegalStateException(OP_REJECT_MSG + + " Snapshot with this name is being created, req=" + req)); } if (snpMgr.isRestoring(req.snpName)) { - return new GridFinishedFuture<>(new IllegalStateException(OP_REJECT_MSG + - " Snapshot with this name is being restored [req=" + req + ']')); + return new GridFinishedFuture<>(new IgniteIllegalStateException(OP_REJECT_MSG + + " Snapshot with this name is being restored, req=" + req)); } if (snpMgr.isSnapshotChecking(req.snpName)) { - return new GridFinishedFuture<>(new IllegalStateException(OP_REJECT_MSG + - " Snapshot with this name is being checked [req=" + req + ']')); - } - - if (requests.putIfAbsent(req.reqId, req) != null) { - return new GridFinishedFuture<>(new IllegalStateException("Deletion of the snapshot has already started [req=" - + req + ']')); + return new GridFinishedFuture<>(new IgniteIllegalStateException(OP_REJECT_MSG + + " Snapshot with this name is being checked, req=" + req)); } try { + if (requests.putIfAbsent(req.snpName, req) != null) { + return new GridFinishedFuture<>(new IgniteIllegalStateException("Deletion of the snapshot has already " + + "started, req=" + req)); + } + AtomicBoolean foundFlag = new AtomicBoolean(); boolean deleted = snpMgr.deleteLocalSnapshot(new SnapshotFileTree(kctx, req.snpName, req.snpPath), foundFlag); @@ -179,17 +180,21 @@ else if (!deleted) return new GridFinishedFuture<>(t); } finally { - requests.remove(req.reqId); + requests.remove(req.snpName); } } /** Coordinator finish: aggregate node results and complete the user future. */ private void reducePhase(UUID reqId, Map results, Map errors) { - var clusterOpFut = clusterOpFuts.get(reqId); + var clusterOpFutPair = clusterOpFuts.get(reqId); - if (clusterOpFut == null) + if (clusterOpFutPair == null) return; + var clusterOpFut = clusterOpFutPair.get2(); + + assert clusterOpFut != null; + try { var errP = F.isEmpty(errors) ? null : F.first(errors.entrySet()); @@ -237,7 +242,20 @@ private void reducePhase(UUID reqId, Map results, } /** */ - public boolean isSnapshotDeleting(String name) { + public boolean isSnapshotDeleting(String snpName) { + if (requests.get(snpName) != null) + return true; + + if (!clusterOpFuts.isEmpty()) { + for (var e : clusterOpFuts.entrySet()) { + var clusterOpFutPair = e.getValue(); + var snpName0 = clusterOpFutPair.get1(); + + if (snpName.equals(snpName0)) + return true; + } + } + return false; } @@ -250,8 +268,8 @@ void interrupt(Throwable err) { interrupted = true; } - clusterOpFuts.forEach((reqId, fut) -> fut.onDone(err)); + clusterOpFuts.forEach((reqId, futPair) -> futPair.get2().onDone(err)); - clusterOpFuts.clear();; + clusterOpFuts.clear(); } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotRestoreProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotRestoreProcess.java index d3ea2ad067aaf..d19e5a20e43a3 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotRestoreProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotRestoreProcess.java @@ -278,9 +278,6 @@ public IgniteFutureImpl start( if (snpMgr.isSnapshotCreating()) throw new IgniteException(OP_REJECT_MSG + "A cluster snapshot operation is in progress."); - if (snpMgr.isSnapshotDeleting(snpName)) - throw new IgniteException(OP_REJECT_MSG + "A snapshot delete operation is in progress."); - synchronized (this) { if (restoringSnapshotName() != null || fut != null) throw new IgniteException(OP_REJECT_MSG + "The previous snapshot restore operation was not completed."); @@ -662,6 +659,9 @@ private IgniteInternalFuture prepare(UUID igno if (snpMgr.isSnapshotCreating()) throw new IgniteCheckedException(OP_REJECT_MSG + "A cluster snapshot operation is in progress."); + if (snpMgr.isSnapshotDeleting(req.snapshotName())) + throw new IgniteException(OP_REJECT_MSG + "A snapshot '" + req.snapshotName() + "' delete operation is in progress."); + if (ctx.encryption().isMasterKeyChangeInProgress()) { return new GridFinishedFuture<>(new IgniteCheckedException(OP_REJECT_MSG + "Master key changing " + "process is not finished yet.")); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/distributed/DistributedProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/util/distributed/DistributedProcess.java index add245d24915d..9c428e0fa748c 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/distributed/DistributedProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/distributed/DistributedProcess.java @@ -500,11 +500,6 @@ public enum DistributedProcessType { */ CHECK_SNAPSHOT_PARTS, - /** - * Delete snapshot procedure. - */ - DELETE_SNAPSHOT, - /** * Cluster version Rolling Upgrade enable process. */ @@ -524,5 +519,10 @@ public enum DistributedProcessType { * Cluster version finalization abort process. */ RU_ABORT_VERSION_FINALIZATION, + + /** + * Delete snapshot procedure. + */ + DELETE_SNAPSHOT } } diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ClientImpl.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ClientImpl.java index ce47380990511..dd1aed7a2a24f 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ClientImpl.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ClientImpl.java @@ -47,7 +47,6 @@ import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicReference; import javax.net.ssl.SSLException; -import org.apache.ignite.Ignite; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteClientDisconnectedException; import org.apache.ignite.IgniteException; @@ -60,7 +59,6 @@ import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.failure.FailureContext; import org.apache.ignite.internal.IgniteClientDisconnectedCheckedException; -import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.IgniteInterruptedCheckedException; import org.apache.ignite.internal.IgniteNodeAttributes; import org.apache.ignite.internal.managers.discovery.DiscoveryServerOnlyCustomMessage; @@ -73,7 +71,6 @@ import org.apache.ignite.internal.util.typedef.internal.LT; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.internal.util.worker.GridWorker; -import org.apache.ignite.internal.worker.WorkersRegistry; import org.apache.ignite.lang.IgniteInClosure; import org.apache.ignite.lang.IgniteUuid; import org.apache.ignite.spi.IgniteSpiAdapter; @@ -208,10 +205,9 @@ class ClientImpl extends TcpDiscoveryImpl { ClientImpl(TcpDiscoverySpi adapter) { super(adapter); - String instanceName = adapter.ignite() == null || adapter.ignite().name() == null - ? "client-node" : adapter.ignite().name(); + String instanceName = ctx.igniteInstanceName(); - executorSrvc = newSingleThreadScheduledExecutor("tcp-discovery-exec", instanceName); + executorSrvc = newSingleThreadScheduledExecutor("tcp-discovery-exec", instanceName == null ? "client-node" : instanceName); } /** {@inheritDoc} */ @@ -248,9 +244,9 @@ class ClientImpl extends TcpDiscoveryImpl { /** {@inheritDoc} */ @Override public void dumpRingStructure(IgniteLogger log) { ClusterNode[] serverNodes = remoteVisibleNodes().stream() - .filter(node -> !node.isClient()) - .sorted(Comparator.comparingLong(ClusterNode::order)) - .toArray(ClusterNode[]::new); + .filter(node -> !node.isClient()) + .sorted(Comparator.comparingLong(ClusterNode::order)) + .toArray(ClusterNode[]::new); U.quietAndInfo(log, Arrays.toString(serverNodes)); } @@ -560,11 +556,11 @@ else if (state == DISCONNECTED) { return null; LT.warn(log, "IP finder returned empty addresses list. " + - "Please check IP finder configuration" + - (spi.ipFinder instanceof TcpDiscoveryMulticastIpFinder ? - " and make sure multicast works on your network. " : ". ") + - "Will retry every " + spi.getReconnectDelay() + " ms. " + - "Change 'reconnectDelay' to configure the frequency of retries.", true); + "Please check IP finder configuration" + + (spi.ipFinder instanceof TcpDiscoveryMulticastIpFinder ? + " and make sure multicast works on your network. " : ". ") + + "Will retry every " + spi.getReconnectDelay() + " ms. " + + "Change 'reconnectDelay' to configure the frequency of retries.", true); sleepEx(spi.getReconnectDelay(), beforeEachSleep, afterEachSleep); } @@ -911,9 +907,9 @@ private Collection updateTopologyHistory(long topVer, @Nullable Tcp if (!topHist.containsKey(topVer)) { assert topHist.isEmpty() || topHist.lastKey() == topVer - 1 : "lastVer=" + (topHist.isEmpty() ? null : topHist.lastKey()) + - ", newVer=" + topVer + - ", locNode=" + locNode + - ", msg=" + msg; + ", newVer=" + topVer + + ", locNode=" + locNode + + ", msg=" + msg; topHist.put(topVer, allNodes); @@ -1052,13 +1048,6 @@ private void joinError(IgniteSpiException err) { joinLatch.countDown(); } - /** */ - private WorkersRegistry getWorkersRegistry() { - Ignite ignite = spi.ignite(); - - return ignite instanceof IgniteEx ? ((IgniteEx)ignite).context().workersRegistry() : null; - } - /** */ private Collection remoteVisibleNodes() { return U.arrayList(rmtNodes.values(), TcpDiscoveryNodesRing.VISIBLE_NODES); @@ -1101,7 +1090,7 @@ private class SocketReader extends IgniteSpiThread { /** */ SocketReader() { - super(spi.ignite().name(), "tcp-client-disco-sock-reader-[]", log); + super(ctx.igniteInstanceName(), "tcp-client-disco-sock-reader-[]", log); } /** @@ -1274,7 +1263,7 @@ private class SocketWriter extends IgniteSpiThread { * */ SocketWriter() { - super(spi.ignite().name(), "tcp-client-disco-sock-writer", log); + super(ctx.igniteInstanceName(), "tcp-client-disco-sock-writer", log); sockTimeout = spi.failureDetectionTimeoutEnabled() ? spi.failureDetectionTimeout() : spi.getSocketTimeout(); @@ -1449,10 +1438,10 @@ void ackReceived(TcpDiscoveryClientAckResponse res) { if (unacked != null) { if (log.isDebugEnabled()) log.debug("Failed to get acknowledge for message, will try to reconnect " + - "[msg=" + unacked + - (spi.failureDetectionTimeoutEnabled() ? - ", failureDetectionTimeout=" + spi.failureDetectionTimeout() : - ", timeout=" + spi.getAckTimeout()) + ']'); + "[msg=" + unacked + + (spi.failureDetectionTimeoutEnabled() ? + ", failureDetectionTimeout=" + spi.failureDetectionTimeout() : + ", timeout=" + spi.getAckTimeout()) + ']'); throw new IOException("Failed to get acknowledge for message: " + unacked); } @@ -1524,7 +1513,7 @@ private class Reconnector extends IgniteSpiThread { * @param prevAddr Address of the node, that this client was previously connected to. */ protected Reconnector(boolean join, InetSocketAddress prevAddr) { - super(spi.ignite().name(), "tcp-client-disco-reconnector", log); + super(ctx.igniteInstanceName(), "tcp-client-disco-reconnector", log); this.join = join; this.prevAddr = prevAddr; @@ -1689,7 +1678,7 @@ protected class MessageWorker extends GridWorker { * @param log Logger. */ private MessageWorker(IgniteLogger log) { - super(spi.ignite().name(), "tcp-client-disco-msg-worker", log, getWorkersRegistry()); + super(ctx.igniteInstanceName(), "tcp-client-disco-msg-worker", log, ctx.workersRegistry()); } /** {@inheritDoc} */ @@ -1957,8 +1946,7 @@ else if (discoMsg instanceof TcpDiscoveryCheckFailedMessage) Thread.currentThread().interrupt(); } catch (Throwable t) { - if (spi.ignite() instanceof IgniteEx) - ((IgniteEx)spi.ignite()).context().failure().process(new FailureContext(CRITICAL_ERROR, t)); + ctx.failure().process(new FailureContext(CRITICAL_ERROR, t)); } finally { TcpDiscoveryIoSession ses = this.currSes; @@ -2129,8 +2117,8 @@ else if (msg instanceof TcpDiscoveryPingRequest) spi.stats.onMessageProcessingFinished(msg); if (spi.ensured(msg) - && state == CONNECTED - && !(msg instanceof TcpDiscoveryClientReconnectMessage)) + && state == CONNECTED + && !(msg instanceof TcpDiscoveryClientReconnectMessage)) lastMsgId = msg.id(); } @@ -2210,7 +2198,7 @@ else if (log.isDebugEnabled()) if (joining()) delayDiscoData.add(dataPacket); else - spi.onExchange(dataPacket, U.resolveClassLoader(spi.ignite().configuration())); + spi.onExchange(dataPacket, U.resolveClassLoader(ctx.config())); } } } @@ -2244,11 +2232,11 @@ private void processNodeAddFinishedMessage(TcpDiscoveryNodeAddFinishedMessage ms DiscoveryDataPacket dataContainer = msg.clientDiscoData(); if (dataContainer != null) - spi.onExchange(dataContainer, U.resolveClassLoader(spi.ignite().configuration())); + spi.onExchange(dataContainer, U.resolveClassLoader(ctx.config())); if (!delayDiscoData.isEmpty()) { for (DiscoveryDataPacket data : delayDiscoData) - spi.onExchange(data, U.resolveClassLoader(spi.ignite().configuration())); + spi.onExchange(data, U.resolveClassLoader(ctx.config())); delayDiscoData.clear(); } diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ServerImpl.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ServerImpl.java index 42266d8ed08ae..5974e61780743 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ServerImpl.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/ServerImpl.java @@ -76,7 +76,6 @@ import org.apache.ignite.events.NodeValidationFailedEvent; import org.apache.ignite.failure.FailureContext; import org.apache.ignite.internal.ClusterMetricsSnapshot; -import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.IgniteFutureTimeoutCheckedException; import org.apache.ignite.internal.IgniteInterruptedCheckedException; import org.apache.ignite.internal.IgniteNodeAttributes; @@ -107,7 +106,6 @@ import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.internal.util.worker.GridWorker; import org.apache.ignite.internal.util.worker.GridWorkerListener; -import org.apache.ignite.internal.worker.WorkersRegistry; import org.apache.ignite.lang.IgniteBiTuple; import org.apache.ignite.lang.IgniteFuture; import org.apache.ignite.lang.IgniteInClosure; @@ -362,14 +360,14 @@ class ServerImpl extends TcpDiscoveryImpl { super(adapter); utilityPool = new IgniteThreadPoolExecutor("disco-pool", - spi.ignite().name(), + ctx.igniteInstanceName(), 0, utilityPoolSize, 2000, new LinkedBlockingQueue<>()); List props = newConnectionEnabledProperty( - ((IgniteEx)spi.ignite()).context().internalSubscriptionProcessor(), + ctx.internalSubscriptionProcessor(), log, "ClientNode", "ServerNode" @@ -2249,7 +2247,7 @@ private class IpFinderCleaner extends IgniteSpiThread { * Constructor. */ private IpFinderCleaner() { - super(spi.ignite().name(), "tcp-disco-ip-finder-cleaner", log); + super(ctx.igniteInstanceName(), "tcp-disco-ip-finder-cleaner", log); setPriority(spi.threadPri); } @@ -2458,11 +2456,6 @@ private static void enrichNodeWithAttribute(TcpDiscoveryNode node, String attrNa node.setAttributes(attrs); } - /** */ - private static WorkersRegistry getWorkerRegistry(TcpDiscoverySpi spi) { - return spi.ignite() instanceof IgniteEx ? ((IgniteEx)spi.ignite()).context().workersRegistry() : null; - } - /** * Upcasts collection type. * @@ -2866,7 +2859,7 @@ protected class RingMessageWorker extends MessageWorker queue) { - super("tcp-disco-msg-worker-[]", log, 10, getWorkerRegistry(spi), queue); + super("tcp-disco-msg-worker-[]", log, 10, ctx.workersRegistry(), queue); setBeforeEachPollAction(() -> { updateHeartbeat(); @@ -3049,17 +3042,15 @@ private void addToQueue(TcpDiscoveryAbstractMessage msg, boolean addFirst) { throw e; } finally { - if (spi.ignite() instanceof IgniteEx) { - if (err == null && !spi.isNodeStopping0() && spiStateCopy() != DISCONNECTING) - err = new IllegalStateException("Worker " + name() + " is terminated unexpectedly."); + if (err == null && !spi.isNodeStopping0() && spiStateCopy() != DISCONNECTING) + err = new IllegalStateException("Worker " + name() + " is terminated unexpectedly."); - FailureProcessor failure = ((IgniteEx)spi.ignite()).context().failure(); + FailureProcessor failure = ctx.failure(); - if (err instanceof OutOfMemoryError) - failure.process(new FailureContext(CRITICAL_ERROR, err)); - else if (err != null) - failure.process(new FailureContext(SYSTEM_WORKER_TERMINATION, err)); - } + if (err instanceof OutOfMemoryError) + failure.process(new FailureContext(CRITICAL_ERROR, err)); + else if (err != null) + failure.process(new FailureContext(SYSTEM_WORKER_TERMINATION, err)); } } @@ -3213,7 +3204,7 @@ private void processAuthFailedMessage(TcpDiscoveryAuthFailedMessage authFailedMs catch (IgniteSpiException ex) { log.warning( "Skipping send auth failed message to client due to some trouble with connection detected: " - + ex.getMessage() + + ex.getMessage() ); } } @@ -3859,8 +3850,8 @@ else if (!failedNextNode && sndState != null && sndState.isBackward()) { } LT.warn(log, "Local node has detected failed nodes and started cluster-wide procedure. " + - "To speed up failure detection please see 'Failure Detection' section under javadoc" + - " for 'TcpDiscoverySpi'"); + "To speed up failure detection please see 'Failure Detection' section under javadoc" + + " for 'TcpDiscoverySpi'"); } } @@ -4591,8 +4582,7 @@ private IgniteNodeValidationResult validateByIgniteComponentsWithJoiningNodeData DiscoveryDataPacket packet = req.gridDiscoveryData(); try { - DiscoveryDataBag dataBag = packet.bagWithJoiningNodeData(spi.ignite().log(), - spi.ignite().configuration().isClientMode()); + DiscoveryDataBag dataBag = packet.bagWithJoiningNodeData(log, ctx.config().isClientMode()); return spi.getSpiContext().validateNode(req.node(), dataBag); } @@ -4777,7 +4767,7 @@ private void processNodeAddedMessage(TcpDiscoveryNodeAddedMessage msg) { if (node.internalOrder() < locNode.internalOrder()) { if (!locNode.id().equals(node.id())) { U.warn(log, "Discarding node added message since local node's order is greater " + - "[node=" + node + ", ring=" + ring + ", msg=" + msg + ']'); + "[node=" + node + ", ring=" + ring + ", msg=" + msg + ']'); return; } @@ -4936,7 +4926,7 @@ else if (!locNodeId.equals(node.id()) && ring.node(node.id()) != null) { if (dataPacket.hasJoiningNodeData()) { if (spiState == CONNECTED) { // Node already connected to the cluster can apply joining nodes' disco data immediately - spi.onExchange(dataPacket, U.resolveClassLoader(spi.ignite().configuration())); + spi.onExchange(dataPacket, U.resolveClassLoader(ctx.config())); spi.collectExchangeData(dataPacket); } @@ -5154,11 +5144,11 @@ private void processNodeAddFinishedMessage(TcpDiscoveryNodeAddFinishedMessage ms } if (gridDiscoveryData != null) - spi.onExchange(gridDiscoveryData, U.resolveClassLoader(spi.ignite().configuration())); + spi.onExchange(gridDiscoveryData, U.resolveClassLoader(ctx.config())); if (joiningNodesDiscoDataList != null) { for (DiscoveryDataPacket dataPacket : joiningNodesDiscoDataList) - spi.onExchange(dataPacket, U.resolveClassLoader(spi.ignite().configuration())); + spi.onExchange(dataPacket, U.resolveClassLoader(ctx.config())); } nullifyDiscoData(); @@ -6140,7 +6130,7 @@ private void notifyDiscoveryListener(TcpDiscoveryCustomEventMessage msg, boolean snapshot, hist, customMsg) - ); + ); notifiedDiscovery.set(true); @@ -6310,7 +6300,7 @@ private class TcpServer extends GridWorker { * @throws IgniteSpiException In case of error. */ TcpServer(IgniteLogger log) throws IgniteSpiException { - super(spi.ignite().name(), "tcp-disco-srvr-[]", log, getWorkerRegistry(spi)); + super(ctx.igniteInstanceName(), "tcp-disco-srvr-[]", log, ctx.workersRegistry()); int lastPort = spi.locPortRange == 0 ? spi.locPort : spi.locPort + spi.locPortRange - 1; @@ -6330,7 +6320,7 @@ private class TcpServer extends GridWorker { if (log.isInfoEnabled()) { log.info("Successfully bound to TCP port [port=" + port + ", localHost=" + spi.locHost + - ", locNodeId=" + spi.ignite().configuration().getNodeId() + + ", locNodeId=" + spi.cfgNodeId + ']'); } @@ -6413,17 +6403,15 @@ private class TcpServer extends GridWorker { throw t; } finally { - if (spi.ignite() instanceof IgniteEx) { - if (err == null && !spi.isNodeStopping0() && spiStateCopy() != DISCONNECTING) - err = new IllegalStateException("Worker " + name() + " is terminated unexpectedly."); + if (err == null && !spi.isNodeStopping0() && spiStateCopy() != DISCONNECTING) + err = new IllegalStateException("Worker " + name() + " is terminated unexpectedly."); - FailureProcessor failure = ((IgniteEx)spi.ignite()).context().failure(); + FailureProcessor failure = ctx.failure(); - if (err instanceof OutOfMemoryError) - failure.process(new FailureContext(CRITICAL_ERROR, err)); - else if (err != null) - failure.process(new FailureContext(SYSTEM_WORKER_TERMINATION, err)); - } + if (err instanceof OutOfMemoryError) + failure.process(new FailureContext(CRITICAL_ERROR, err)); + else if (err != null) + failure.process(new FailureContext(SYSTEM_WORKER_TERMINATION, err)); U.closeQuiet(srvrSock); } @@ -6458,11 +6446,11 @@ private class SocketReader extends IgniteSpiThread { * @param sock Socket to read data from. */ SocketReader(Socket sock) { - super(spi.ignite().name(), "tcp-disco-sock-reader-[]", log); + super(ctx.igniteInstanceName(), "tcp-disco-sock-reader-[]", log); this.sock = sock; - ses = createSession(sock); + ses = new TcpDiscoveryIoSession(ctx, sock); setPriority(spi.threadPri); } @@ -7085,11 +7073,7 @@ else if (msg instanceof TcpDiscoveryRingLatencyCheckMessage) { } } catch (UnknownMessageException e) { - if (spi.ignite() instanceof IgniteEx) { - FailureProcessor failure = ((IgniteEx)spi.ignite()).context().failure(); - - failure.process(new FailureContext(SYSTEM_WORKER_TERMINATION, e)); - } + ctx.failure().process(new FailureContext(SYSTEM_WORKER_TERMINATION, e)); } finally { if (clientMsgWrk != null) { @@ -7461,7 +7445,7 @@ private class StatisticsPrinter extends IgniteSpiThread { * Constructor. */ StatisticsPrinter() { - super(spi.ignite().name(), "tcp-disco-stats-printer", log); + super(ctx.igniteInstanceName(), "tcp-disco-stats-printer", log); assert spi.statsPrintFreq > 0; @@ -7534,7 +7518,7 @@ private ClientMessageWorker(TcpDiscoveryIoSession ses, UUID clientNodeId, Ignite this.ses = ses; this.clientNodeId = clientNodeId; - clientMsgSer = new TcpDiscoveryMessageSerializer(spi); + clientMsgSer = new TcpDiscoveryMessageSerializer(ctx); lastMetricsUpdateMsgTimeNanos = System.nanoTime(); } @@ -7878,7 +7862,7 @@ protected MessageWorker( @Nullable GridWorkerListener lsnr, BlockingDeque queue ) { - super(spi.ignite().name(), name, log, lsnr); + super(ctx.igniteInstanceName(), name, log, lsnr); this.queue = queue; this.pollingTimeout = pollingTimeout; @@ -8101,7 +8085,7 @@ void pingRemoteDCs(List nodesToPing) { rmtDcPingPool = new IgniteThreadPoolExecutor( "disco-remote-dc-ping-worker", - spi.ignite().name(), + ctx.igniteInstanceName(), pingRmtDcPoolSz, pingRmtDcPoolSz, 0, diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryImpl.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryImpl.java index b83e2130ae2e0..0f2324361d5dc 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryImpl.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryImpl.java @@ -18,7 +18,6 @@ package org.apache.ignite.spi.discovery.tcp; import java.net.InetSocketAddress; -import java.net.Socket; import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; @@ -36,6 +35,7 @@ import org.apache.ignite.cluster.ClusterMetrics; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.internal.ClusterMetricsSnapshot; +import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.processors.cache.CacheMetricsSnapshot; import org.apache.ignite.internal.processors.cluster.CacheMetricsMessage; @@ -87,6 +87,9 @@ abstract class TcpDiscoveryImpl { /** */ protected final TcpDiscoverySpi spi; + /** */ + protected final GridKernalContext ctx; + /** */ protected final IgniteLogger log; @@ -145,7 +148,9 @@ abstract class TcpDiscoveryImpl { log = spi.log; - operationCtxDispatcher = ((IgniteEx)spi.ignite()).context().operationContextDispatcher(); + ctx = ((IgniteEx)spi.ignite()).context(); + + operationCtxDispatcher = ctx.operationContextDispatcher(); } /** @@ -459,16 +464,6 @@ protected static List toOrderedList(Collection addrs) return res; } - /** - * Instantiates IO session for exchanging discovery messages with remote node. - * - * @param sock Socket to remote node. - * @return IO session for writing and reading {@link TcpDiscoveryAbstractMessage}. - */ - TcpDiscoveryIoSession createSession(Socket sock) { - return new TcpDiscoveryIoSession(sock, spi); - } - /** * @param msg Message. * @return Message logger. diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java index 71a4995eeabcb..0251712fc95b7 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryIoSession.java @@ -35,7 +35,6 @@ import org.apache.ignite.IgniteException; import org.apache.ignite.IgniteLogger; import org.apache.ignite.internal.GridKernalContext; -import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.direct.DirectMessageReader; import org.apache.ignite.internal.direct.DirectMessageWriter; import org.apache.ignite.internal.managers.communication.DiscoveryMarshalling; @@ -46,6 +45,7 @@ import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.marshaller.jdk.JdkMarshaller; import org.apache.ignite.plugin.extensions.communication.Message; +import org.apache.ignite.plugin.extensions.communication.MessageFactory; import org.apache.ignite.plugin.extensions.communication.MessageSerializer; import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryAbstractMessage; import org.jetbrains.annotations.NotNull; @@ -70,7 +70,13 @@ public class TcpDiscoveryIoSession implements AutoCloseable { private static final int MSG_BUFFER_SIZE = 100; /** */ - private final TcpDiscoverySpi spi; + private final GridKernalContext ctx; + + /** */ + private final MessageFactory msgFactory; + + /** */ + private final IgniteLogger log; /** */ private final Socket sock; @@ -96,19 +102,21 @@ public class TcpDiscoveryIoSession implements AutoCloseable { /** * Creates a new discovery I/O session bound to the given socket. * + * @param ctx Kernal context. * @param sock Socket connected to a remote discovery node. - * @param spi Discovery SPI instance owning this session. * @throws IgniteException If an I/O error occurs while initializing buffers. */ - TcpDiscoveryIoSession(Socket sock, TcpDiscoverySpi spi) { + TcpDiscoveryIoSession(GridKernalContext ctx, Socket sock) { this.sock = sock; - this.spi = spi; + this.ctx = ctx; + this.msgFactory = ctx.messageFactory(); + this.log = ctx.log(getClass()); readBuf = ByteBuffer.allocate(MSG_BUFFER_SIZE); writeBuf = ByteBuffer.allocate(MSG_BUFFER_SIZE); - msgWriter = new DirectMessageWriter(spi.messageFactory()); - msgReader = new DirectMessageReader(spi.messageFactory(), null); + msgWriter = new DirectMessageWriter(msgFactory); + msgReader = new DirectMessageReader(msgFactory, null); try { int sendBufSize = sock.getSendBufferSize() > 0 ? sock.getSendBufferSize() : DFLT_SOCK_BUFFER_SIZE; @@ -178,7 +186,7 @@ T readMessage() throws IgniteCheckedException, IOException { Message msg; try { - msg = spi.messageFactory().create(msgType); + msg = msgFactory.create(msgType); } catch (IgniteException e) { detectSslAlert(b0, b1); @@ -202,7 +210,7 @@ T readMessage() throws IgniteCheckedException, IOException { readBuf.limit(read); - finished = MessageSerialization.readFrom(spi.messageFactory(), msg, msgReader); + finished = MessageSerialization.readFrom(msgFactory, msg, msgReader); // Server Discovery only sends next message to next Server upon receiving a receipt for the previous one. // This behaviour guarantees that we never read a next message from the buffer right after the end of @@ -218,9 +226,7 @@ T readMessage() throws IgniteCheckedException, IOException { } while (!finished); - GridKernalContext kctx = ((IgniteEx)spi.ignite()).context(); - - DiscoveryMarshalling.unmarshal(msg, kctx); + DiscoveryMarshalling.unmarshal(msg, ctx); return (T)msg; } @@ -238,14 +244,14 @@ T readMessage() throws IgniteCheckedException, IOException { /** @return SSL certificate this session is established with. {@code null} if SSL is disabled or certificate validation failed. */ @Nullable Certificate[] extractCertificates() { - if (!spi.isSslEnabled()) + if (!(sock instanceof SSLSocket)) return null; try { return ((SSLSocket)sock).getSession().getPeerCertificates(); } catch (SSLPeerUnverifiedException e) { - U.error(spi.log, "Failed to extract discovery IO session certificates", e); + U.error(log, "Failed to extract discovery IO session certificates", e); return null; } @@ -264,9 +270,7 @@ public Socket socket() { * @throws IOException If serialization fails. */ void serializeMessage(Message m, OutputStream out) throws IOException, IgniteCheckedException { - GridKernalContext kctx = ((IgniteEx)spi.ignite()).context(); - - DiscoveryMarshalling.marshal(m, kctx, null); + DiscoveryMarshalling.marshal(m, ctx, null); msgWriter.reset(); msgWriter.setBuffer(writeBuf); @@ -277,7 +281,7 @@ void serializeMessage(Message m, OutputStream out) throws IOException, IgniteChe // Should be cleared before first operation. writeBuf.clear(); - finished = MessageSerialization.writeTo(spi.messageFactory(), m, msgWriter); + finished = MessageSerialization.writeTo(msgFactory, m, msgWriter); out.write(writeBuf.array(), 0, writeBuf.position()); } diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryMessageSerializer.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryMessageSerializer.java index 8c871f9e2eb46..ec7cdc569f0c7 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryMessageSerializer.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryMessageSerializer.java @@ -22,6 +22,7 @@ import java.io.OutputStream; import java.net.Socket; import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.plugin.extensions.communication.Message; import org.apache.ignite.plugin.extensions.communication.MessageSerializer; import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryAbstractMessage; @@ -35,10 +36,10 @@ */ class TcpDiscoveryMessageSerializer extends TcpDiscoveryIoSession { /** - * @param spi Discovery SPI instance. + * @param ctx Kernal context. */ - public TcpDiscoveryMessageSerializer(TcpDiscoverySpi spi) { - super(new Socket() { + public TcpDiscoveryMessageSerializer(GridKernalContext ctx) { + super(ctx, new Socket() { @Override public OutputStream getOutputStream() throws IOException { return null; } @@ -46,7 +47,7 @@ public TcpDiscoveryMessageSerializer(TcpDiscoverySpi spi) { @Override public InputStream getInputStream() throws IOException { return null; } - }, spi); + }); } /** diff --git a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySpi.java b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySpi.java index 34dcbbe9f9b5a..7b8490ded8385 100644 --- a/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySpi.java +++ b/modules/core/src/main/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySpi.java @@ -57,7 +57,6 @@ import org.apache.ignite.internal.IgniteInterruptedCheckedException; import org.apache.ignite.internal.managers.communication.UnknownMessageException; import org.apache.ignite.internal.managers.discovery.IgniteDiscoverySpi; -import org.apache.ignite.internal.processors.failure.FailureProcessor; import org.apache.ignite.internal.processors.metric.MetricRegistryImpl; import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteComponentFeatureSet; import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteNodeFeatureSet; @@ -74,7 +73,6 @@ import org.apache.ignite.lang.IgniteUuid; import org.apache.ignite.marshaller.Marshaller; import org.apache.ignite.plugin.extensions.communication.Message; -import org.apache.ignite.plugin.extensions.communication.MessageFactory; import org.apache.ignite.resources.IgniteInstanceResource; import org.apache.ignite.resources.LoggerResource; import org.apache.ignite.spi.IgniteSpiAdapter; @@ -463,10 +461,6 @@ public class TcpDiscoverySpi extends IgniteSpiAdapter implements IgniteDiscovery @GridToStringExclude protected IgniteSpiContext spiCtx; - /** Discovery messages factory. */ - @GridToStringExclude - private MessageFactory msgFactory; - /** For test purposes. */ private boolean skipAddrsRandomization = false; @@ -600,8 +594,6 @@ public void setClientReconnectDisabled(boolean clientReconnectDisabled) { setAddressResolver(ignite.configuration().getAddressResolver()); marsh = ((IgniteEx)ignite).context().marshallerContext().jdkMarshaller(); - - msgFactory = ((IgniteEx)ignite).context().messageFactory(); } } @@ -1122,11 +1114,6 @@ public void setConnectionRecoveryTimeout(long connRecoveryTimeout) { locNodeVer = ver; } - /** @return Discovery messages factory. */ - public MessageFactory messageFactory() { - return msgFactory; - } - /** * Gets ID of the local node. * @@ -1624,7 +1611,7 @@ protected TcpDiscoveryIoSession openSession( sock.connect(resolved, (int)timeoutHelper.nextTimeoutChunk(sockTimeout)); - TcpDiscoveryIoSession ses = new TcpDiscoveryIoSession(sock, this); + TcpDiscoveryIoSession ses = new TcpDiscoveryIoSession(ignite.context(), sock); write(ses, U.IGNITE_HEADER, timeoutHelper.nextTimeoutChunk(sockTimeout)); @@ -1949,8 +1936,8 @@ protected Collection resolvedAddresses() throws IgniteSpiExce Collection addrs; long timeout = isClientMode() && impl.getSpiState().equalsIgnoreCase("connected") - ? netTimeout - : joinTimeout; + ? netTimeout + : joinTimeout; // Get consistent addresses collection. while (true) { @@ -1961,16 +1948,16 @@ protected Collection resolvedAddresses() throws IgniteSpiExce } catch (IgniteSpiException e) { LT.error(log, e, "Failed to get registered addresses from IP finder " + - "(retrying every " + getReconnectDelay() + "ms;" + - " change 'reconnectDelay' to configure the frequency of retries) " + - "[maxTimeout=" + timeout + "]", true); + "(retrying every " + getReconnectDelay() + "ms;" + + " change 'reconnectDelay' to configure the frequency of retries) " + + "[maxTimeout=" + timeout + "]", true); } try { if (timeout > 0 && U.millisSinceNanos(resolutionStartNanos) > timeout) { LT.warn(log, "Unable to get registered addresses from IP finder, timeout is reached " + - "(consider increasing 'joinTimeout' for join process or 'netTimeout' for reconnection) " + - "[joinTimeout=" + joinTimeout + ", netTimeout=" + netTimeout + "]"); + "(consider increasing 'joinTimeout' for join process or 'netTimeout' for reconnection) " + + "[joinTimeout=" + joinTimeout + ", netTimeout=" + netTimeout + "]"); addrs = res; break; @@ -1991,7 +1978,7 @@ protected Collection resolvedAddresses() throws IgniteSpiExce continue; InetSocketAddress resolved = addr.isUnresolved() ? - new InetSocketAddress(InetAddress.getByName(addr.getHostName()), addr.getPort()) : addr; + new InetSocketAddress(InetAddress.getByName(addr.getHostName()), addr.getPort()) : addr; if (locNodeAddrs == null || !locNodeAddrs.contains(resolved)) res.add(resolved); @@ -2131,11 +2118,7 @@ protected void onExchange(DiscoveryDataPacket dataPacket, ClassLoader clsLdr) { dataBag = dataPacket.bagWithJoiningNodeData(ignite.log(), ignite.configuration().isClientMode()); } catch (IgniteCheckedException e) { - if (ignite() instanceof IgniteEx) { - FailureProcessor failure = ((IgniteEx)ignite()).context().failure(); - - failure.process(new FailureContext(CRITICAL_ERROR, e)); - } + ignite.context().failure().process(new FailureContext(CRITICAL_ERROR, e)); throw new IgniteException(e); } @@ -2557,12 +2540,12 @@ boolean cancel() { LT.warn(log, "Socket write has timed out (consider increasing " + (failureDetectionTimeoutEnabled() ? - "'IgniteConfiguration.failureDetectionTimeout' configuration property) [" + - "failureDetectionTimeout=" + failureDetectionTimeout() : - "'sockTimeout' configuration property) [sockTimeout=" + sockTimeout) + - ", rmtAddr=" + ses.socket().getRemoteSocketAddress() + - ", rmtPort=" + ses.socket().getPort() + - ", sockTimeout=" + sockTimeout + ']'); + "'IgniteConfiguration.failureDetectionTimeout' configuration property) [" + + "failureDetectionTimeout=" + failureDetectionTimeout() : + "'sockTimeout' configuration property) [sockTimeout=" + sockTimeout) + + ", rmtAddr=" + ses.socket().getRemoteSocketAddress() + + ", rmtPort=" + ses.socket().getPort() + + ", sockTimeout=" + sockTimeout + ']'); } } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/binary/BinaryClassLoaderMultiJvmTest.java b/modules/core/src/test/java/org/apache/ignite/internal/binary/BinaryClassLoaderMultiJvmTest.java index e00dc4530c42c..3e62ed376ce03 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/binary/BinaryClassLoaderMultiJvmTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/binary/BinaryClassLoaderMultiJvmTest.java @@ -18,9 +18,11 @@ package org.apache.ignite.internal.binary; import java.lang.ref.WeakReference; +import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.net.URL; +import java.util.List; import org.apache.ignite.Ignite; import org.apache.ignite.IgniteCache; import org.apache.ignite.IgniteException; @@ -38,12 +40,16 @@ import org.apache.ignite.lang.IgniteRunnable; import org.apache.ignite.testframework.GridTestExternalClassLoader; import org.apache.ignite.testframework.config.GridTestProperties; +import org.apache.ignite.testframework.junits.WithSystemProperty; import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; import org.junit.Test; +import static org.apache.ignite.IgniteCommonsSystemProperties.IGNITE_USE_BINARY_ARRAYS; + /** * Test class covering feature that allows to unmarshal binary object with custom classloader. */ +@WithSystemProperty(key = IGNITE_USE_BINARY_ARRAYS, value = "true") public class BinaryClassLoaderMultiJvmTest extends GridCommonAbstractTest { /** Person class name. */ private static final String PERSON_CLASS_NAME = "org.apache.ignite.tests.p2p.cache.Person"; @@ -68,6 +74,11 @@ public class BinaryClassLoaderMultiJvmTest extends GridCommonAbstractTest { return true; } + /** {@inheritDoc} */ + @Override protected List additionalRemoteJvmArgs() { + return List.of("-D" + IGNITE_USE_BINARY_ARRAYS + "=true"); + } + /** {@inheritDoc} */ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { return super.getConfiguration(igniteInstanceName) @@ -77,6 +88,9 @@ public class BinaryClassLoaderMultiJvmTest extends GridCommonAbstractTest { new CacheConfiguration("SomeCache") .setAtomicityMode(CacheAtomicityMode.ATOMIC) .setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC), + new CacheConfiguration("SomeCacheArray") + .setAtomicityMode(CacheAtomicityMode.ATOMIC) + .setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC), new CacheConfiguration("SomeCacheEnum") .setAtomicityMode(CacheAtomicityMode.ATOMIC) .setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC), @@ -126,6 +140,8 @@ public void testLoadClassFromBinary() throws Exception { checkItems(testClsLdr, "SomeCache", PERSON_CLASS_NAME, ign); + checkItems(testClsLdr, "SomeCacheArray", PERSON_CLASS_NAME, ign); + checkItems(testClsLdr, "SomeCacheEnum", ENUM_CLASS_NAME, ign); checkItems(testClsLdr, "OrganizationCache", ORGANIZATION_CLASS_NAME, ign); @@ -179,6 +195,8 @@ public void testClientLoadClassFromBinary() throws Exception { checkItems(testClsLdr, "SomeCache", PERSON_CLASS_NAME, ign); + checkItems(testClsLdr, "SomeCacheArray", PERSON_CLASS_NAME, ign); + checkItems(testClsLdr, "SomeCacheEnum", ENUM_CLASS_NAME, ign); checkItems(testClsLdr, "OrganizationCache", ORGANIZATION_CLASS_NAME, ign); @@ -213,8 +231,9 @@ private void checkItems(ClassLoader testClsLdr, String cacheName, String valClsN IgniteCache binaryCache = cache.withKeepBinary(); - for (int i = 0; i < 100; i++) { + boolean arrVals = cacheName.endsWith("Array"); + for (int i = 0; i < 100; i++) { BinaryObject binaryVal = binaryCache.get(i); if (i % 50 == 0) @@ -225,7 +244,8 @@ private void checkItems(ClassLoader testClsLdr, String cacheName, String valClsN info("Can not execute toString() on class " + binaryVal.type().typeName()); } - assertEquals(binaryVal.type().typeName(), valClsName); + if (!arrVals) + assertEquals(binaryVal.type().typeName(), valClsName); boolean catchEx = false; @@ -245,7 +265,14 @@ private void checkItems(ClassLoader testClsLdr, String cacheName, String valClsN Object personVal = binaryVal.deserialize(testClsLdr); - assertTrue(personVal != null && personVal.getClass().getName().equals(valClsName)); + assertNotNull(personVal); + + if (!arrVals) + assertTrue(personVal.getClass().getName().equals(valClsName)); + else { + assertTrue(personVal.getClass().isArray()); + assertTrue(personVal.getClass().getComponentType().getName().equals(valClsName)); + } } } @@ -254,7 +281,9 @@ private void checkItems(ClassLoader testClsLdr, String cacheName, String valClsN * @param ignite Ignite. */ private void loadItems(ClassLoader testClsLdr, Ignite ignite) throws Exception { - Constructor personConstructor = testClsLdr.loadClass(PERSON_CLASS_NAME).getConstructor(String.class); + Class personCls = testClsLdr.loadClass(PERSON_CLASS_NAME); + + Constructor personConstructor = personCls.getConstructor(String.class); IgniteCache cache = ignite.cache("SomeCache"); @@ -262,6 +291,18 @@ private void loadItems(ClassLoader testClsLdr, Ignite ignite) throws Exception { cache.put(i, personConstructor.newInstance("Persone name " + i)); assertEquals(cache.size(CachePeekMode.PRIMARY), 100); + + cache = ignite.cache("SomeCacheArray"); + + for (int i = 0; i < 100; i++) { + Object arr = Array.newInstance(personCls, 1); + + Array.set(arr, 0, personConstructor.newInstance("Persone name " + i)); + + cache.put(i, arr); + } + + assertEquals(cache.size(CachePeekMode.PRIMARY), 100); } /** diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/CacheMetricsCacheSizeTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/CacheMetricsCacheSizeTest.java index 9aed75febee59..a4a0db433309d 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/CacheMetricsCacheSizeTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/CacheMetricsCacheSizeTest.java @@ -35,7 +35,6 @@ import org.apache.ignite.internal.processors.cluster.CacheMetricsMessage; import org.apache.ignite.internal.util.nio.MessageSerialization; import org.apache.ignite.plugin.extensions.communication.MessageFactory; -import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi; import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryMetricsUpdateMessage; import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest; import org.junit.Test; @@ -105,7 +104,7 @@ public void testCacheSize() throws Exception { msg.addServerMetrics(srvrId, new ClusterMetricsSnapshot()); msg.addServerCacheMetrics(srvrId, cacheMetrics); - MessageFactory msgFactory = ((TcpDiscoverySpi)grid(0).context().discovery().getInjectedDiscoverySpi()).messageFactory(); + MessageFactory msgFactory = grid(0).context().messageFactory(); // First time we write initial message type which is not read by the reader because the message type is known. // We have to skip this header at the further message reading. diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java index 306d44224a4d6..61d146f581043 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java @@ -46,6 +46,7 @@ import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteDataStreamer; import org.apache.ignite.IgniteException; +import org.apache.ignite.IgniteIllegalStateException; import org.apache.ignite.binary.BinaryObject; import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction; import org.apache.ignite.cluster.BaselineNode; @@ -57,6 +58,7 @@ import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.IgniteInternalFuture; import org.apache.ignite.internal.NodeStoppingException; +import org.apache.ignite.internal.TestRecordingCommunicationSpi; import org.apache.ignite.internal.binary.BinaryContext; import org.apache.ignite.internal.binary.BinaryUtils; import org.apache.ignite.internal.cluster.ClusterTopologyCheckedException; @@ -117,6 +119,7 @@ import static org.apache.ignite.internal.processors.metric.impl.MetricUtils.metricName; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.CHECK_SNAPSHOT_METAS; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.CHECK_SNAPSHOT_PARTS; +import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.DELETE_SNAPSHOT; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_START; import static org.apache.ignite.testframework.GridTestUtils.assertContains; import static org.apache.ignite.testframework.GridTestUtils.assertNotContains; @@ -1201,6 +1204,41 @@ public void testConcurrentFullCheckAndFullRestoreDeclined() throws Exception { ); } + /** Tests that concurrent snapshot check is declined when the same snapshot is being deleted. */ + @Test + public void testCuncurrentSnapshotDeleteOperation() throws Exception { + listeningLog = new ListeningTestLogger(log); + + prepareGridsAndSnapshot(4, 3, 1, false); + + var commSpi1 = (TestRecordingCommunicationSpi)grid(1).configuration().getCommunicationSpi(); + + commSpi1.blockMessages((bode, msg) -> msg instanceof SingleNodeMessage msg0 + && msg0.type() == DELETE_SNAPSHOT.ordinal()); + + var delFut = snp(grid(0)).deleteSnapshot(SNAPSHOT_NAME, null); + + assertThrowsAnyCause( + null, + () -> snp(grid(2)).checkSnapshot(SNAPSHOT_NAME, null).get(), + IgniteIllegalStateException.class, + "Snapshot '%s' is being deleted".formatted(SNAPSHOT_NAME) + ); + + commSpi1.stopBlock(); + + var delRes = delFut.get(getTestTimeout()); + + assertFalse(delRes.completedNodes.isEmpty()); + + assertThrowsAnyCause( + null, + () -> snp(grid(2)).checkSnapshot(SNAPSHOT_NAME, null).get(), + IllegalArgumentException.class, + "Snapshot does not exists " + ); + } + /** Tests that concurrent snapshot full check is declined when the same snapshot is being fully restored (checked). */ @Test public void testConcurrentTheSameSnpFullCheckWhenFullyRestoringDeclined() throws Exception { @@ -1409,8 +1447,8 @@ private void prepareGridsAndSnapshot(int servers, int baseLineCnt, int clients, /** * Tests concurrent snapshot operations related to the snapshot checking. * - * @param originatorOp First snapshot operation on an originator node. - * @param trierOp Second concurrent snapshot operation on a trier node. + * @param firstOp First snapshot operation on an originator node. + * @param secondOp Second concurrent snapshot operation on a trier node. * @param firstDelay First distributed process full message of {@code originatorOp} to delay on the coordinator * to launch {@code trierOp}. * @param secondDelay Second distributed process full message of {@code originatorOp} to delay on the coordinator @@ -1422,8 +1460,8 @@ private void prepareGridsAndSnapshot(int servers, int baseLineCnt, int clients, * @param cleaner If not {@code null}, is executed at the end. */ private void doTestConcurrentSnpCheckOperations( - Supplier> originatorOp, - Supplier> trierOp, + Supplier> firstOp, + Supplier> secondOp, DistributedProcess.DistributedProcessType firstDelay, @Nullable DistributedProcess.DistributedProcessType secondDelay, boolean expectFailure, @@ -1440,17 +1478,17 @@ private void doTestConcurrentSnpCheckOperations( && ((FullMessage)msg).type() == firstDelay.ordinal() && (waitForBothFirstDelays || firstDelayed.compareAndSet(false, true))); - IgniteFuture fut = originatorOp.get(); + IgniteFuture fut = firstOp.get(); discoSpi(grid(0)).waitBlocked(getTestTimeout()); - IgniteFuture fut2 = trierOp.get(); + IgniteFuture fut2 = secondOp.get(); if (expectFailure) { assertThrowsAnyCause( log, fut2::get, - IllegalStateException.class, + IgniteIllegalStateException.class, "Validation of snapshot '" + SNAPSHOT_NAME + "' has already started" ); @@ -1473,7 +1511,7 @@ private void doTestConcurrentSnpCheckOperations( assertThrowsAnyCause( log, fut2::get, - IllegalStateException.class, + IgniteIllegalStateException.class, "Validation of snapshot '" + SNAPSHOT_NAME + "' has already started" ); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java index 2cc653c0ccd9d..fc5dbd0dadcc3 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java @@ -20,7 +20,6 @@ import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; -import org.apache.ignite.Ignite; import org.apache.ignite.IgniteDataStreamer; import org.apache.ignite.IgniteException; import org.apache.ignite.configuration.IgniteConfiguration; @@ -29,7 +28,6 @@ import org.apache.ignite.internal.util.distributed.DistributedProcess; import org.apache.ignite.internal.util.distributed.FullMessage; import org.apache.ignite.internal.util.future.IgniteFutureImpl; -import org.apache.ignite.internal.util.typedef.G; import org.apache.ignite.lang.IgniteFuture; import org.junit.Test; @@ -40,50 +38,11 @@ import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.START_SNAPSHOT; import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause; -/** - * Cluster-wide snapshot delete procedure tests. In addition to the concurrent snapshot delete tests, where the - * operations are paired with the snapshot create, check and restore procedures to verify that concurrent snapshot - * operations are correctly rejected (or allowed) when a delete operation is in progress, this class also contains the - * basic snapshot delete functionality tests. - */ +/** */ public class IgniteClusterSnapshotDeleteTest extends AbstractSnapshotSelfTest { /** Cache partitions count. */ private static final int CACHE_PARTS_CNT = 32; - /** Tests the basic cluster-wide snapshot delete functionality. */ - @Test - public void testClusterSnapshotDelete() throws Exception { - IgniteEx ignite = prepareGridsAndSnapshot(3, 2, 2, false); - - // Sanity check: the snapshot exists on the cluster. - assertNotNull( - "Snapshot must be available on the cluster", - snp(ignite).checkSnapshot(SNAPSHOT_NAME, null).get().idleVerifyResult() - ); - - snp(ignite).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); - - // The snapshot must not be present on any of the cluster nodes. - for (Ignite node : G.allGrids()) { - assertTrue( - "Snapshot must be deleted on node " + node.name(), - snp((IgniteEx)node).localSnapshotNames(null).isEmpty() - ); - } - - // The check procedure must report that the snapshot does not exist anymore. - assertThrowsAnyCause( - log, - () -> { - snp(ignite).checkSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); - - return null; - }, - IllegalArgumentException.class, - "Snapshot does not exists" - ); - } - /** Tests that a snapshot delete is declined when a snapshot create operation is in progress. */ @Test public void testSnapshotDeleteWhenCreateInProgress() throws Exception { @@ -151,7 +110,7 @@ public void testSnapshotDeleteWhenCheckInProgress() throws Exception { return null; }, ClusterTopologyCheckedException.class, - "Snapshot deletion was rejected. the snapshot is being checked" + "Snapshot deletion was rejected. Snapshot with this name is being checked" ); return null; diff --git a/modules/core/src/test/java/org/apache/ignite/spi/MessagesPluginProvider.java b/modules/core/src/test/java/org/apache/ignite/spi/MessagesPluginProvider.java index 1198584d22972..0090d11cc3c56 100644 --- a/modules/core/src/test/java/org/apache/ignite/spi/MessagesPluginProvider.java +++ b/modules/core/src/test/java/org/apache/ignite/spi/MessagesPluginProvider.java @@ -17,7 +17,6 @@ package org.apache.ignite.spi; -import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.internal.CoreMessagesProvider; import org.apache.ignite.plugin.AbstractTestPluginProvider; import org.apache.ignite.plugin.ExtensionRegistry; @@ -25,8 +24,6 @@ import org.apache.ignite.plugin.extensions.communication.Message; import org.apache.ignite.plugin.extensions.communication.MessageFactoryProvider; import org.apache.ignite.plugin.extensions.communication.MessageMarshaller; -import org.apache.ignite.spi.discovery.DiscoverySpi; -import org.apache.ignite.spi.discovery.tcp.TestTcpDiscoverySpi; import org.jetbrains.annotations.Nullable; import static org.apache.ignite.testframework.GridTestUtils.loadMarshaller; @@ -78,14 +75,4 @@ public MessagesPluginProvider(Class... msgs) { // Register messages into the communication protocol. registry.registerExtension(MessageFactoryProvider.class, msgFactoryProvider); } - - /** {@inheritDoc} */ - @Override public void start(PluginContext ctx) throws IgniteCheckedException { - DiscoverySpi discoSpi = ctx.igniteConfiguration().getDiscoverySpi(); - - if (discoSpi instanceof TestTcpDiscoverySpi testDiscoSpi) { - // Register messages into the discovery protocol. - testDiscoSpi.messageFactory(msgFactoryProvider); - } - } } diff --git a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/BlockTcpDiscoverySpi.java b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/BlockTcpDiscoverySpi.java index 52e81b05df960..4904df46a40d3 100644 --- a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/BlockTcpDiscoverySpi.java +++ b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/BlockTcpDiscoverySpi.java @@ -70,7 +70,7 @@ private synchronized void apply(ClusterNode addr, TcpDiscoveryAbstractMessage ms long timeout ) throws IOException, IgniteCheckedException { if (spiCtx != null) { - TcpDiscoveryAbstractMessage msg = decodeMessage(this, data); + TcpDiscoveryAbstractMessage msg = decodeMessage(ignite.context(), data); if (msg != null) apply(spiCtx.localNode(), msg); diff --git a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/DiscoveryUnmarshalVulnerabilityTest.java b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/DiscoveryUnmarshalVulnerabilityTest.java index 5a4e18fe8d421..8e8029c295ee5 100644 --- a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/DiscoveryUnmarshalVulnerabilityTest.java +++ b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/DiscoveryUnmarshalVulnerabilityTest.java @@ -245,7 +245,7 @@ void attack(byte[] data) throws IOException { private byte[] serializedMessage() throws IgniteCheckedException { ByteBuffer buf = ByteBuffer.allocate(4096); - MessageFactory msgFactory = ((TcpDiscoverySpi)grid(0).configuration().getDiscoverySpi()).messageFactory(); + MessageFactory msgFactory = grid(0).context().messageFactory(); DirectMessageWriter writer = new DirectMessageWriter(msgFactory); writer.setBuffer(buf); diff --git a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpClientDiscoverySpiSelfTest.java b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpClientDiscoverySpiSelfTest.java index ca96f33f159cf..0d5222fca2d2a 100644 --- a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpClientDiscoverySpiSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpClientDiscoverySpiSelfTest.java @@ -2600,7 +2600,7 @@ private void pauseResumeOperation(boolean isPause, AtomicBoolean... locks) { waitFor(writeLock); - TcpDiscoveryAbstractMessage msg = decodeMessage(this, data); + TcpDiscoveryAbstractMessage msg = decodeMessage(ignite.context(), data); if (msg != null && !onMessage(sock, msg)) return; diff --git a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TestTcpDiscoverySpi.java b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TestTcpDiscoverySpi.java index 866bbc842178b..feedf6a4c6ed5 100644 --- a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TestTcpDiscoverySpi.java +++ b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TestTcpDiscoverySpi.java @@ -26,19 +26,15 @@ import java.util.Arrays; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteException; -import org.apache.ignite.internal.CoreMessagesProvider; -import org.apache.ignite.internal.managers.communication.IgniteMessageFactoryImpl; +import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.managers.discovery.IgniteDiscoverySpiInternalListener; import org.apache.ignite.internal.util.typedef.internal.U; -import org.apache.ignite.plugin.extensions.communication.MessageFactory; -import org.apache.ignite.plugin.extensions.communication.MessageFactoryProvider; import org.apache.ignite.spi.discovery.DiscoverySpiCustomMessage; import org.apache.ignite.spi.discovery.DiscoverySpiListener; import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryAbstractMessage; import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryClientReconnectMessage; import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryJoinRequestMessage; import org.apache.ignite.spi.discovery.tcp.messages.TcpDiscoveryPingResponse; -import org.apache.ignite.testframework.GridTestUtils; import org.apache.ignite.testframework.GridTestUtils.DiscoveryHook; import org.jetbrains.annotations.Nullable; @@ -57,12 +53,6 @@ public class TestTcpDiscoverySpi extends TcpDiscoverySpi implements IgniteDiscov /** */ private IgniteDiscoverySpiInternalListener internalLsnr; - /** */ - private MessageFactory msgFactory; - - /** */ - private MessageFactoryProvider provider; - /** {@inheritDoc} */ @Override protected void writeMessage(TcpDiscoveryIoSession ses, TcpDiscoveryAbstractMessage msg, long timeout) throws IOException, IgniteCheckedException { @@ -119,42 +109,8 @@ public void discoveryHook(DiscoveryHook discoHook) { this.discoHook = discoHook; } - /** - * Sets test discovery messages factory provider. Note that {@link MessageFactoryProvider} must be set before SPI start. - * Otherwise, this method call will take no effect. - * - * @param msgFactoryProvider Discovery messages factory provider. - */ - public void messageFactory(MessageFactoryProvider msgFactoryProvider) { - provider = msgFactoryProvider; - assert !started(); - - msgFactory = new IgniteMessageFactoryImpl(new MessageFactoryProvider[] { - new CoreMessagesProvider(), - msgFactoryProvider - }); - } - - /** {@inheritDoc} */ - @Override public MessageFactoryProvider messageFactoryProvider() { - return provider; - } - - /** {@inheritDoc} */ - @Override protected void initLocalNode(int srvPort, boolean addExtAddrAttr) { - if (msgFactory != null) - GridTestUtils.setFieldValue(this, TcpDiscoverySpi.class, "msgFactory", msgFactory); - - super.initLocalNode(srvPort, addExtAddrAttr); - } - - /** {@inheritDoc} */ - @Override public MessageFactory messageFactory() { - return msgFactory != null ? msgFactory : super.messageFactory(); - } - /** */ - public static @Nullable TcpDiscoveryAbstractMessage decodeMessage(TcpDiscoverySpi spi, byte[] data) { + public static @Nullable TcpDiscoveryAbstractMessage decodeMessage(GridKernalContext ctx, byte[] data) { if (Arrays.equals(U.IGNITE_HEADER, data)) return null; @@ -169,7 +125,7 @@ public void messageFactory(MessageFactoryProvider msgFactoryProvider) { }; try (dataSock) { - return new TcpDiscoveryIoSession(dataSock, spi).readMessage(); + return new TcpDiscoveryIoSession(ctx, dataSock).readMessage(); } catch (Exception e) { throw new IgniteException("Failed to decode a message", e); diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java index 5673ed2b71af3..073e515779720 100644 --- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java +++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java @@ -21,6 +21,7 @@ import java.util.Collection; import java.util.List; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotCheckTest; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotDeleteTest; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotHandlerTest; import org.apache.ignite.testframework.GridTestUtils; import org.apache.ignite.testframework.junits.DynamicSuite; @@ -48,6 +49,7 @@ public static List> suite(Collection ignoredTests) { GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotCheckTest.class, ignoredTests); GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotHandlerTest.class, ignoredTests); + GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotDeleteTest.class, ignoredTests); return suite; } diff --git a/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/ContinuousDataLoadApplication.java b/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/ContinuousDataLoadApplication.java index 0b743036dfebc..bff6187c71532 100644 --- a/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/ContinuousDataLoadApplication.java +++ b/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/ContinuousDataLoadApplication.java @@ -78,7 +78,7 @@ public class ContinuousDataLoadApplication extends IgniteAwareApplication { if (notifyTime + TimeUnit.MILLISECONDS.toNanos(1500) < System.nanoTime()) notifyTime = System.nanoTime(); - // Delayed notify of the initialization to make sure the data load has completely began and + // Delayed notify of the initialization to make sure the data load has completelly began and // has produced some valuable amount of data. if (!inited() && warmUpCnt == loaded) markInitialized(); From 8a007e347b2e95a94319bb9633f07e25c26d2094 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Thu, 10 Sep 2026 17:09:51 +0300 Subject: [PATCH 11/97] Simplification --- .../snapshot/SnapshotDeleteProcess.java | 29 ++++--------------- .../IgniteClusterSnapshotCheckTest.java | 16 ++++++---- 2 files changed, 16 insertions(+), 29 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index c6c5bae82427c..19f7b1209eb62 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -33,7 +33,6 @@ import org.apache.ignite.internal.util.future.GridFutureAdapter; import org.apache.ignite.internal.util.future.IgniteFutureImpl; import org.apache.ignite.internal.util.typedef.F; -import org.apache.ignite.internal.util.typedef.T2; import org.apache.ignite.lang.IgniteFuture; import org.jetbrains.annotations.Nullable; @@ -58,7 +57,7 @@ public class SnapshotDeleteProcess { private volatile boolean interrupted; /** Cluster-wide operation futures per request id on certain node. */ - private final Map>> clusterOpFuts = new ConcurrentHashMap<>(); + private final Map> clusterOpFuts = new ConcurrentHashMap<>(); /** Process requests per snapshot name on each server node. */ private final Map requests = new ConcurrentHashMap<>(); @@ -96,7 +95,7 @@ public IgniteFuture start(String snpName, @Nullable if (interrupted || kctx.isStopping()) throw new NodeStoppingException("Failed to start snapshot delete process: node is stopping."); - clusterOpFuts.put(reqId, new T2<>(snpName, clusterOpFut)); + clusterOpFuts.put(reqId, clusterOpFut); } SnapshotDeleteRequest req = new SnapshotDeleteRequest(reqId, snpName, snpPath); @@ -186,13 +185,11 @@ else if (!deleted) /** Coordinator finish: aggregate node results and complete the user future. */ private void reducePhase(UUID reqId, Map results, Map errors) { - var clusterOpFutPair = clusterOpFuts.get(reqId); + var clusterOpFut = clusterOpFuts.get(reqId); - if (clusterOpFutPair == null) + if (clusterOpFut == null) return; - var clusterOpFut = clusterOpFutPair.get2(); - assert clusterOpFut != null; try { @@ -243,32 +240,18 @@ private void reducePhase(UUID reqId, Map results, /** */ public boolean isSnapshotDeleting(String snpName) { - if (requests.get(snpName) != null) - return true; - - if (!clusterOpFuts.isEmpty()) { - for (var e : clusterOpFuts.entrySet()) { - var clusterOpFutPair = e.getValue(); - var snpName0 = clusterOpFutPair.get1(); - - if (snpName.equals(snpName0)) - return true; - } - } - - return false; + return requests.get(snpName) != null; } /** * @param err The interrupt reason. */ void interrupt(Throwable err) { - // Prevents starting new processes in #prepareAndCheckMetas. synchronized (clusterOpFuts) { interrupted = true; } - clusterOpFuts.forEach((reqId, futPair) -> futPair.get2().onDone(err)); + clusterOpFuts.forEach((reqId, clusterOpFut) -> clusterOpFut.onDone(err)); clusterOpFuts.clear(); } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java index 61d146f581043..c6e10a9d58e1a 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java @@ -1218,12 +1218,16 @@ public void testCuncurrentSnapshotDeleteOperation() throws Exception { var delFut = snp(grid(0)).deleteSnapshot(SNAPSHOT_NAME, null); - assertThrowsAnyCause( - null, - () -> snp(grid(2)).checkSnapshot(SNAPSHOT_NAME, null).get(), - IgniteIllegalStateException.class, - "Snapshot '%s' is being deleted".formatted(SNAPSHOT_NAME) - ); + try { + snp(grid(2)).checkSnapshot(SNAPSHOT_NAME, null).get(); + + throw new IllegalStateException("Exception is not thrown."); + } + catch (Exception e) { + if (!e.getMessage().contains("Snapshot '%s' is being deleted".formatted(SNAPSHOT_NAME)) + && !e.getMessage().contains("Snapshot does not exists")) + throw new IllegalStateException("Unexpected exception: " + e.getMessage(), e); + } commSpi1.stopBlock(); From 97ab26c23f203265b94e9cc5b6fb38ec09df4c71 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Thu, 10 Sep 2026 17:17:38 +0300 Subject: [PATCH 12/97] + restore test --- .../IgniteClusterSnapshotCheckTest.java | 2 - .../IgniteClusterSnapshotRestoreSelfTest.java | 38 +++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java index c6e10a9d58e1a..96e135405044d 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java @@ -1207,8 +1207,6 @@ public void testConcurrentFullCheckAndFullRestoreDeclined() throws Exception { /** Tests that concurrent snapshot check is declined when the same snapshot is being deleted. */ @Test public void testCuncurrentSnapshotDeleteOperation() throws Exception { - listeningLog = new ListeningTestLogger(log); - prepareGridsAndSnapshot(4, 3, 1, false); var commSpi1 = (TestRecordingCommunicationSpi)grid(1).configuration().getCommunicationSpi(); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotRestoreSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotRestoreSelfTest.java index 78ec729ec931b..c9344fbc7464b 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotRestoreSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotRestoreSelfTest.java @@ -77,6 +77,7 @@ import static org.apache.ignite.events.EventType.EVT_CLUSTER_SNAPSHOT_RESTORE_FAILED; import static org.apache.ignite.events.EventType.EVT_CLUSTER_SNAPSHOT_RESTORE_FINISHED; import static org.apache.ignite.events.EventType.EVT_CLUSTER_SNAPSHOT_RESTORE_STARTED; +import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.DELETE_SNAPSHOT; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_PRELOAD; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_PREPARE; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_START; @@ -333,6 +334,43 @@ public void testCreateSnapshotDuringRestore() throws Exception { assertCacheKeys(ignite.cache(DEFAULT_CACHE_NAME), CACHE_KEYS_RANGE); } + /** Tests that snapshot restore is declined when the same snapshot is being deleted. */ + @Test + public void testCuncurrentSnapshotDeleteOperation() throws Exception { + startGridsWithSnapshot(3, CACHE_KEYS_RANGE); + + var commSpi1 = (TestRecordingCommunicationSpi)grid(1).configuration().getCommunicationSpi(); + + commSpi1.blockMessages((bode, msg) -> msg instanceof SingleNodeMessage msg0 + && msg0.type() == DELETE_SNAPSHOT.ordinal()); + + var delFut = snp(grid(0)).deleteSnapshot(SNAPSHOT_NAME, null); + + try { + snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null).get(); + + throw new IllegalStateException("Exception is not thrown."); + } + catch (Exception e) { + if (!e.getMessage().contains("Snapshot '%s' is being deleted".formatted(SNAPSHOT_NAME)) + && !e.getMessage().contains("Snapshot does not exists")) + throw new IllegalStateException("Unexpected exception: " + e.getMessage(), e); + } + + commSpi1.stopBlock(); + + var delRes = delFut.get(getTestTimeout()); + + assertFalse(delRes.completedNodes.isEmpty()); + + assertThrowsAnyCause( + null, + () -> snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null).get(), + IllegalArgumentException.class, + "Snapshot does not exists " + ); + } + /** * Ensures that the cache doesn't start if one of the baseline nodes fails. * From fbb2cf84f482ab9133166ecc0588e5f4aa4bb818 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Thu, 10 Sep 2026 18:44:15 +0300 Subject: [PATCH 13/97] + create test --- .../IgniteClusterSnapshotCheckTest.java | 4 +- .../IgniteClusterSnapshotRestoreSelfTest.java | 4 +- .../IgniteClusterSnapshotSelfTest.java | 38 +++++++++++++++++++ 3 files changed, 44 insertions(+), 2 deletions(-) diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java index 96e135405044d..6ce60af7d2a2c 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java @@ -1206,7 +1206,7 @@ public void testConcurrentFullCheckAndFullRestoreDeclined() throws Exception { /** Tests that concurrent snapshot check is declined when the same snapshot is being deleted. */ @Test - public void testCuncurrentSnapshotDeleteOperation() throws Exception { + public void testConcurrentSnapshotDeleteOperation() throws Exception { prepareGridsAndSnapshot(4, 3, 1, false); var commSpi1 = (TestRecordingCommunicationSpi)grid(1).configuration().getCommunicationSpi(); @@ -1216,6 +1216,8 @@ public void testCuncurrentSnapshotDeleteOperation() throws Exception { var delFut = snp(grid(0)).deleteSnapshot(SNAPSHOT_NAME, null); + assertTrue(commSpi1.waitForBlocked(1, getTestTimeout())); + try { snp(grid(2)).checkSnapshot(SNAPSHOT_NAME, null).get(); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotRestoreSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotRestoreSelfTest.java index c9344fbc7464b..833aa0293d573 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotRestoreSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotRestoreSelfTest.java @@ -336,7 +336,7 @@ public void testCreateSnapshotDuringRestore() throws Exception { /** Tests that snapshot restore is declined when the same snapshot is being deleted. */ @Test - public void testCuncurrentSnapshotDeleteOperation() throws Exception { + public void testConcurrentSnapshotDeleteOperation() throws Exception { startGridsWithSnapshot(3, CACHE_KEYS_RANGE); var commSpi1 = (TestRecordingCommunicationSpi)grid(1).configuration().getCommunicationSpi(); @@ -346,6 +346,8 @@ public void testCuncurrentSnapshotDeleteOperation() throws Exception { var delFut = snp(grid(0)).deleteSnapshot(SNAPSHOT_NAME, null); + assertTrue(commSpi1.waitForBlocked(1, getTestTimeout())); + try { snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null).get(); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotSelfTest.java index 570761dac227f..7b373d4158ae7 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotSelfTest.java @@ -102,6 +102,7 @@ import static org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager.SNP_IN_PROGRESS_ERR_MSG; import static org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager.SNP_NODE_STOPPING_ERR_MSG; import static org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager.isSnapshotOperation; +import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.DELETE_SNAPSHOT; import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause; import static org.apache.ignite.testframework.GridTestUtils.assertThrowsWithCause; import static org.apache.ignite.testframework.GridTestUtils.waitForCondition; @@ -615,6 +616,43 @@ public void testSnapshotExistsException() throws Exception { waitForEvents(EVT_CLUSTER_SNAPSHOT_STARTED, EVT_CLUSTER_SNAPSHOT_FAILED); } + /** + * Tests that snapshot create detects concurrent deletion, or detects still existing snapshot or sucessfuly + * proceeds if snapshot already deleted. + */ + @Test + public void testConcurrentSnapshotDeleteOperation() throws Exception { + startGridsWithCache(3, dfltCacheCfg, CACHE_KEYS_RANGE); + + snp(grid(2)).createSnapshot(SNAPSHOT_NAME).get(); + + var commSpi1 = (TestRecordingCommunicationSpi)grid(1).configuration().getCommunicationSpi(); + + commSpi1.blockMessages((bode, msg) -> msg instanceof SingleNodeMessage msg0 + && msg0.type() == DELETE_SNAPSHOT.ordinal()); + + var delFut = snp(grid(0)).deleteSnapshot(SNAPSHOT_NAME, null); + + assertTrue(commSpi1.waitForBlocked(1, getTestTimeout())); + + try { + snp(grid(2)).createSnapshot(SNAPSHOT_NAME).get(); + + // No-op: snapshot successfuly deleted; + } + catch (Exception e) { + if (!e.getMessage().contains("Snapshot '%s' is being deleted".formatted(SNAPSHOT_NAME)) + && !e.getMessage().contains("Snapshot with given name already exists")) + throw new IllegalStateException("Unexpected exception: " + e.getMessage(), e); + } + + commSpi1.stopBlock(); + + var delRes = delFut.get(getTestTimeout()); + + assertFalse(delRes.completedNodes.isEmpty()); + } + /** @throws Exception If fails. */ @Test public void testClusterSnapshotCleanedOnLeft() throws Exception { From 2ed26adf4af6299a0269e0d2309c8eeb7d2b2437 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Thu, 10 Sep 2026 19:58:05 +0300 Subject: [PATCH 14/97] reimpl tests --- .../snapshot/SnapshotDeleteProcess.java | 57 +++++++----- .../snapshot/AbstractSnapshotSelfTest.java | 92 +++++++++++++++++++ .../IgniteClusterSnapshotCheckTest.java | 46 ++-------- .../IgniteClusterSnapshotRestoreSelfTest.java | 42 ++------- .../IgniteClusterSnapshotSelfTest.java | 39 ++------ 5 files changed, 155 insertions(+), 121 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index 19f7b1209eb62..b370c30d18e03 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -148,39 +148,52 @@ private IgniteInternalFuture deletePhase(UUID ignored, S "started, req=" + req)); } - AtomicBoolean foundFlag = new AtomicBoolean(); + GridFutureAdapter reqLocFut = new GridFutureAdapter<>(); - boolean deleted = snpMgr.deleteLocalSnapshot(new SnapshotFileTree(kctx, req.snpName, req.snpPath), foundFlag); + kctx.pools().getSnapshotExecutorService().submit(() -> { + try { + AtomicBoolean foundFlag = new AtomicBoolean(); - SnapshotDeleteResponse.SnapshotDeleteStatus res; + boolean deleted = snpMgr.deleteLocalSnapshot(new SnapshotFileTree(kctx, req.snpName, req.snpPath), foundFlag); - if (foundFlag.get()) { - if (deleted && log.isInfoEnabled()) - log.info("Snapshot successfully deleted, req=" + req); - else if (!deleted) - log.warning("Snapshot deleted not completely, req=" + req); + SnapshotDeleteResponse.SnapshotDeleteStatus res; - res = deleted - ? SnapshotDeleteResponse.SnapshotDeleteStatus.DELETED - : SnapshotDeleteResponse.SnapshotDeleteStatus.PARTLY_DELETED; - } - else { - if (log.isInfoEnabled()) - log.info("Snapshot not found to delete, req=" + req); + if (foundFlag.get()) { + if (deleted && log.isInfoEnabled()) + log.info("Snapshot successfully deleted, req=" + req); + else if (!deleted) + log.warning("Snapshot deleted not completely, req=" + req); - res = SnapshotDeleteResponse.SnapshotDeleteStatus.NOT_FOUND; - } + res = deleted + ? SnapshotDeleteResponse.SnapshotDeleteStatus.DELETED + : SnapshotDeleteResponse.SnapshotDeleteStatus.PARTLY_DELETED; + } + else { + if (log.isInfoEnabled()) + log.info("Snapshot not found to delete, req=" + req); + + res = SnapshotDeleteResponse.SnapshotDeleteStatus.NOT_FOUND; + } + + reqLocFut.onDone(new SnapshotDeleteResponse(res)); + } + finally { + requests.remove(req.snpName); + } + }); - return new GridFinishedFuture<>(new SnapshotDeleteResponse(res)); + if (log.isInfoEnabled()) + log.info("Deletion of snapshot initialized, req=" + req); + + return reqLocFut; } catch (Throwable t) { - log.error("An error occured during snapshot deletion, req=" + req, t); + requests.remove(req.snpName); + + log.warning("An error occured during snapshot deletion, req=" + req, t); return new GridFinishedFuture<>(t); } - finally { - requests.remove(req.snpName); - } } /** Coordinator finish: aggregate node results and complete the user future. */ diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java index 8857a4ddd656e..63885c43a02c3 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java @@ -36,7 +36,10 @@ import java.util.Queue; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; +import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; @@ -98,6 +101,8 @@ import org.apache.ignite.lang.IgniteFutureCancelledException; import org.apache.ignite.lang.IgniteFutureTimeoutException; import org.apache.ignite.lang.IgnitePredicate; +import org.apache.ignite.plugin.AbstractTestPluginProvider; +import org.apache.ignite.plugin.PluginContext; import org.apache.ignite.spi.discovery.DiscoverySpiCustomMessage; import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi; import org.apache.ignite.spi.discovery.tcp.TestTcpDiscoverySpi; @@ -173,6 +178,9 @@ protected Function valueBuilder() { return valBuilder; } + /** */ + protected @Nullable AbstractTestPluginProvider pluginProvider; + /** Enable encryption of all caches in {@code IgniteConfiguration} before start. */ @Parameterized.Parameter public boolean encryption; @@ -217,6 +225,9 @@ protected static Collection encryptionParameters() { if (cfg.isClientMode()) return cfg; + if (pluginProvider != null) + cfg.setPluginProviders(pluginProvider); + return cfg.setConsistentId(igniteInstanceName) .setDataStorageConfiguration(new DataStorageConfiguration() .setDefaultDataRegionConfiguration(new DataRegionConfiguration() @@ -814,6 +825,80 @@ public static void doSnapshotCancellationTest( assertEquals("Snapshot directory must be empty due to snapshot cancelled", 0, snpDir.list().length); } + /** Tests concurrent snapshot deletion. */ + protected void doTestConcurrentSnapshotDeleteOperation( + ExRunnable prepareCluster, + ExRunnable concurrentOp, + Function errValidator, + boolean rerunAtTheEnd + ) throws Exception { + CountDownLatch delProcInitLatch = new CountDownLatch(1); + CountDownLatch delProcProceedLatch = new CountDownLatch(1); + + pluginProvider = new AbstractTestPluginProvider() { + @Override public String name() { + return "TestSnpMgrProvider"; + } + + @Override public T createComponent(PluginContext ctx, Class cls) { + if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { + return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { + @Override public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean existsFlag) { + delProcInitLatch.countDown(); + + try { + assertTrue(delProcProceedLatch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); + } + catch (InterruptedException e) { + throw new RuntimeException("Interrupted.", e); + } + + return super.deleteLocalSnapshot(sft, existsFlag); + } + }; + } + + return super.createComponent(ctx, cls); + } + }; + + prepareCluster.run(); + + var delFut = snp(grid(0)).deleteSnapshot(SNAPSHOT_NAME, null); + + assertTrue(delProcInitLatch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); + + try { + concurrentOp.run(); + + throw new IllegalStateException("Exception is not thrown."); + } + catch (Exception e) { + if (!errValidator.apply(e)) + throw new IllegalStateException("Unexpected exception: " + e.getMessage(), e); + } + + delProcProceedLatch.countDown(); + + var delRes = delFut.get(getTestTimeout()); + + assertFalse(delRes.completedNodes.isEmpty()); + + if (!rerunAtTheEnd) + return; + + assertThrowsAnyCause( + null, + () -> { + concurrentOp.run(); + + return null; + }, + IllegalArgumentException.class, + "Snapshot does not exists " + ); + } + /** * @param sft Snapshot file tree. * @param parts Collection of pairs group and appropriate cache partition to be snapshot. @@ -994,6 +1079,13 @@ public void waitBlockedSize(int size, long timeout) throws IgniteInterruptedChec } } + /** */ + @FunctionalInterface + protected interface ExRunnable { + /** */ + void run() throws Exception; + } + /** */ protected static class Value { /** */ diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java index 6ce60af7d2a2c..fc46bf49d9aed 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java @@ -58,7 +58,6 @@ import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.IgniteInternalFuture; import org.apache.ignite.internal.NodeStoppingException; -import org.apache.ignite.internal.TestRecordingCommunicationSpi; import org.apache.ignite.internal.binary.BinaryContext; import org.apache.ignite.internal.binary.BinaryUtils; import org.apache.ignite.internal.cluster.ClusterTopologyCheckedException; @@ -100,6 +99,7 @@ import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.lang.IgniteFuture; import org.apache.ignite.metric.MetricRegistry; +import org.apache.ignite.plugin.AbstractTestPluginProvider; import org.apache.ignite.spi.metric.BooleanMetric; import org.apache.ignite.spi.metric.IntMetric; import org.apache.ignite.spi.metric.LongMetric; @@ -119,7 +119,6 @@ import static org.apache.ignite.internal.processors.metric.impl.MetricUtils.metricName; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.CHECK_SNAPSHOT_METAS; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.CHECK_SNAPSHOT_PARTS; -import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.DELETE_SNAPSHOT; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_START; import static org.apache.ignite.testframework.GridTestUtils.assertContains; import static org.apache.ignite.testframework.GridTestUtils.assertNotContains; @@ -151,6 +150,9 @@ public class IgniteClusterSnapshotCheckTest extends AbstractSnapshotSelfTest { @Parameterized.Parameter(2) public int snpThrdPoolSz; + /** */ + private @Nullable AbstractTestPluginProvider pluginProvider; + /** Parameters. */ @Parameterized.Parameters(name = "encryption={0}, onlyPrimary={1}, snpThrdPoolSz={2}") public static Collection params() { @@ -1204,42 +1206,14 @@ public void testConcurrentFullCheckAndFullRestoreDeclined() throws Exception { ); } - /** Tests that concurrent snapshot check is declined when the same snapshot is being deleted. */ + /** */ @Test - public void testConcurrentSnapshotDeleteOperation() throws Exception { - prepareGridsAndSnapshot(4, 3, 1, false); - - var commSpi1 = (TestRecordingCommunicationSpi)grid(1).configuration().getCommunicationSpi(); - - commSpi1.blockMessages((bode, msg) -> msg instanceof SingleNodeMessage msg0 - && msg0.type() == DELETE_SNAPSHOT.ordinal()); - - var delFut = snp(grid(0)).deleteSnapshot(SNAPSHOT_NAME, null); - - assertTrue(commSpi1.waitForBlocked(1, getTestTimeout())); - - try { - snp(grid(2)).checkSnapshot(SNAPSHOT_NAME, null).get(); - - throw new IllegalStateException("Exception is not thrown."); - } - catch (Exception e) { - if (!e.getMessage().contains("Snapshot '%s' is being deleted".formatted(SNAPSHOT_NAME)) - && !e.getMessage().contains("Snapshot does not exists")) - throw new IllegalStateException("Unexpected exception: " + e.getMessage(), e); - } - - commSpi1.stopBlock(); - - var delRes = delFut.get(getTestTimeout()); - - assertFalse(delRes.completedNodes.isEmpty()); - - assertThrowsAnyCause( - null, + public void testConcurrentSnapshotDeleteAndCheckOperations() throws Exception { + doTestConcurrentSnapshotDeleteOperation( + () -> prepareGridsAndSnapshot(4, 3, 1, false), () -> snp(grid(2)).checkSnapshot(SNAPSHOT_NAME, null).get(), - IllegalArgumentException.class, - "Snapshot does not exists " + e -> e.getMessage().contains("Snapshot '%s' is being deleted".formatted(SNAPSHOT_NAME)), + true ); } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotRestoreSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotRestoreSelfTest.java index 833aa0293d573..3d82b8f0037f0 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotRestoreSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotRestoreSelfTest.java @@ -77,7 +77,6 @@ import static org.apache.ignite.events.EventType.EVT_CLUSTER_SNAPSHOT_RESTORE_FAILED; import static org.apache.ignite.events.EventType.EVT_CLUSTER_SNAPSHOT_RESTORE_FINISHED; import static org.apache.ignite.events.EventType.EVT_CLUSTER_SNAPSHOT_RESTORE_STARTED; -import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.DELETE_SNAPSHOT; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_PRELOAD; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_PREPARE; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_START; @@ -106,6 +105,9 @@ public class IgniteClusterSnapshotRestoreSelfTest extends IgniteClusterSnapshotR if (resetConsistentId) cfg.setConsistentId(null); + if (pluginProvider != null) + cfg.setPluginProviders(pluginProvider); + return cfg; } @@ -336,40 +338,12 @@ public void testCreateSnapshotDuringRestore() throws Exception { /** Tests that snapshot restore is declined when the same snapshot is being deleted. */ @Test - public void testConcurrentSnapshotDeleteOperation() throws Exception { - startGridsWithSnapshot(3, CACHE_KEYS_RANGE); - - var commSpi1 = (TestRecordingCommunicationSpi)grid(1).configuration().getCommunicationSpi(); - - commSpi1.blockMessages((bode, msg) -> msg instanceof SingleNodeMessage msg0 - && msg0.type() == DELETE_SNAPSHOT.ordinal()); - - var delFut = snp(grid(0)).deleteSnapshot(SNAPSHOT_NAME, null); - - assertTrue(commSpi1.waitForBlocked(1, getTestTimeout())); - - try { - snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null).get(); - - throw new IllegalStateException("Exception is not thrown."); - } - catch (Exception e) { - if (!e.getMessage().contains("Snapshot '%s' is being deleted".formatted(SNAPSHOT_NAME)) - && !e.getMessage().contains("Snapshot does not exists")) - throw new IllegalStateException("Unexpected exception: " + e.getMessage(), e); - } - - commSpi1.stopBlock(); - - var delRes = delFut.get(getTestTimeout()); - - assertFalse(delRes.completedNodes.isEmpty()); - - assertThrowsAnyCause( - null, + public void testConcurrentSnapshotDeleteAndRestoreOperations() throws Exception { + doTestConcurrentSnapshotDeleteOperation( + () -> startGridsWithSnapshot(3, CACHE_KEYS_RANGE), () -> snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null).get(), - IllegalArgumentException.class, - "Snapshot does not exists " + e -> e.getMessage().contains("Snapshot '%s' is being deleted".formatted(SNAPSHOT_NAME)), + true ); } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotSelfTest.java index 7b373d4158ae7..bd5330b073f91 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotSelfTest.java @@ -102,7 +102,6 @@ import static org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager.SNP_IN_PROGRESS_ERR_MSG; import static org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager.SNP_NODE_STOPPING_ERR_MSG; import static org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager.isSnapshotOperation; -import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.DELETE_SNAPSHOT; import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause; import static org.apache.ignite.testframework.GridTestUtils.assertThrowsWithCause; import static org.apache.ignite.testframework.GridTestUtils.waitForCondition; @@ -127,6 +126,7 @@ public class IgniteClusterSnapshotSelfTest extends AbstractSnapshotSelfTest { /** Any node failed. */ private boolean failed; + /** {@inheritDoc} */ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { return super.getConfiguration(igniteInstanceName).setFailureHandler((ignite, ctx) -> failed = true); @@ -622,35 +622,16 @@ public void testSnapshotExistsException() throws Exception { */ @Test public void testConcurrentSnapshotDeleteOperation() throws Exception { - startGridsWithCache(3, dfltCacheCfg, CACHE_KEYS_RANGE); - - snp(grid(2)).createSnapshot(SNAPSHOT_NAME).get(); - - var commSpi1 = (TestRecordingCommunicationSpi)grid(1).configuration().getCommunicationSpi(); - - commSpi1.blockMessages((bode, msg) -> msg instanceof SingleNodeMessage msg0 - && msg0.type() == DELETE_SNAPSHOT.ordinal()); - - var delFut = snp(grid(0)).deleteSnapshot(SNAPSHOT_NAME, null); - - assertTrue(commSpi1.waitForBlocked(1, getTestTimeout())); - - try { - snp(grid(2)).createSnapshot(SNAPSHOT_NAME).get(); - - // No-op: snapshot successfuly deleted; - } - catch (Exception e) { - if (!e.getMessage().contains("Snapshot '%s' is being deleted".formatted(SNAPSHOT_NAME)) - && !e.getMessage().contains("Snapshot with given name already exists")) - throw new IllegalStateException("Unexpected exception: " + e.getMessage(), e); - } - - commSpi1.stopBlock(); - - var delRes = delFut.get(getTestTimeout()); + doTestConcurrentSnapshotDeleteOperation( + () -> { + startGridsWithCache(3, dfltCacheCfg, CACHE_KEYS_RANGE); - assertFalse(delRes.completedNodes.isEmpty()); + snp(grid(2)).createSnapshot(SNAPSHOT_NAME).get(); + }, + () -> snp(grid(2)).createSnapshot(SNAPSHOT_NAME).get(), + e -> e.getMessage().contains("Snapshot with given name already exists"), + false + ); } /** @throws Exception If fails. */ From 9b0e5ff48bed3e8c5621aadcac562d5466839df9 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Thu, 10 Sep 2026 22:40:10 +0300 Subject: [PATCH 15/97] better IgniteClusterSnapshotDeleteTest --- .../IgniteClusterSnapshotDeleteTest.java | 295 ++++++------------ 1 file changed, 96 insertions(+), 199 deletions(-) diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java index fc5dbd0dadcc3..15f332d7933ec 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java @@ -17,254 +17,151 @@ package org.apache.ignite.internal.processors.cache.persistence.snapshot; -import java.util.concurrent.Callable; -import java.util.concurrent.atomic.AtomicBoolean; +import java.util.Collection; import java.util.function.Supplier; -import org.apache.ignite.IgniteDataStreamer; -import org.apache.ignite.IgniteException; -import org.apache.ignite.configuration.IgniteConfiguration; -import org.apache.ignite.internal.IgniteEx; -import org.apache.ignite.internal.cluster.ClusterTopologyCheckedException; +import org.apache.ignite.IgniteIllegalStateException; +import org.apache.ignite.internal.TestRecordingCommunicationSpi; import org.apache.ignite.internal.util.distributed.DistributedProcess; -import org.apache.ignite.internal.util.distributed.FullMessage; +import org.apache.ignite.internal.util.distributed.SingleNodeMessage; import org.apache.ignite.internal.util.future.IgniteFutureImpl; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.G; import org.apache.ignite.lang.IgniteFuture; +import org.jetbrains.annotations.Nullable; import org.junit.Test; -import static org.apache.ignite.cluster.ClusterState.ACTIVE; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.CHECK_SNAPSHOT_METAS; -import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.DELETE_SNAPSHOT; +import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.CHECK_SNAPSHOT_PARTS; +import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.END_SNAPSHOT; +import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_PRELOAD; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_PREPARE; +import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_START; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.START_SNAPSHOT; import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause; /** */ public class IgniteClusterSnapshotDeleteTest extends AbstractSnapshotSelfTest { - /** Cache partitions count. */ - private static final int CACHE_PARTS_CNT = 32; + /** {@inheritDoc} */ + @Override public void afterTestSnapshot() throws Exception { + super.afterTestSnapshot(); - /** Tests that a snapshot delete is declined when a snapshot create operation is in progress. */ - @Test - public void testSnapshotDeleteWhenCreateInProgress() throws Exception { - prepareGridsAndSnapshot(3, 2, 2, false); - - doTestConcurrentSnpDeleteOperation( - () -> snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary), - START_SNAPSHOT, - () -> { - assertThrowsAnyCause( - log, - () -> { - snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); - - return null; - }, - ClusterTopologyCheckedException.class, - "Snapshot deletion was rejected. a snapshot operation is in progress" - ); + G.allGrids(); - return null; - } - ); + cleanPersistenceDir(); } - /** Tests that a snapshot create is declined when a snapshot delete operation is in progress. */ + /** Tests that a snapshot deletion is declined when a snapshot check operation is in progress. */ @Test - public void testSnapshotCreateWhenDeleteInProgress() throws Exception { - prepareGridsAndSnapshot(3, 2, 2, false); - - doTestConcurrentSnpDeleteOperation( - () -> snp(grid(0)).deleteSnapshot(SNAPSHOT_NAME, null), - DELETE_SNAPSHOT, - () -> { - assertThrowsAnyCause( - log, - () -> { - snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary); - - return null; - }, - IgniteException.class, - "Snapshot delete operation is currently in progress" - ); - - return null; - } + public void testSnapshotDeleteWhenCheckInProgress() throws Exception { + doTestConcurrentSnapshotDelete( + F.asList(CHECK_SNAPSHOT_METAS, CHECK_SNAPSHOT_PARTS), + null, + () -> new IgniteFutureImpl<>(snp(grid(2)).checkSnapshot(SNAPSHOT_NAME, null)), + "Snapshot with this name is being checked", + true ); } - /** Tests that a snapshot delete is declined when a snapshot check operation is in progress. */ + /** Tests that a snapshot deletion is declined when a snapshot create operation is in progress. */ @Test - public void testSnapshotDeleteWhenCheckInProgress() throws Exception { - prepareGridsAndSnapshot(3, 2, 2, false); - - doTestConcurrentSnpDeleteOperation( - () -> new IgniteFutureImpl<>(snp(grid(0)).checkSnapshot(SNAPSHOT_NAME, null)), - CHECK_SNAPSHOT_METAS, - () -> { - assertThrowsAnyCause( - log, - () -> { - snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); - - return null; - }, - ClusterTopologyCheckedException.class, - "Snapshot deletion was rejected. Snapshot with this name is being checked" - ); - - return null; - } + public void testSnapshotDeleteWhenCreateInProgress() throws Exception { + doTestConcurrentSnapshotDelete( + F.asList(START_SNAPSHOT, END_SNAPSHOT), + () -> snp(grid(0)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()), + () -> snp(grid(2)).createSnapshot(SNAPSHOT_NAME), + "Snapshot with this name is being created", + false ); } - /** Tests that a snapshot check is not declined when a snapshot delete operation is in progress. */ + /** Tests that a snapshot deletion is declined when a snapshot restore begins. */ @Test - public void testSnapshotCheckWhenDeleteInProgress() throws Exception { - prepareGridsAndSnapshot(3, 2, 2, false); - - doTestConcurrentSnpDeleteOperation( - () -> snp(grid(0)).deleteSnapshot(SNAPSHOT_NAME, null), - DELETE_SNAPSHOT, + public void testSnapshotDeleteWhenRestoreBegins() throws Exception { + doTestConcurrentSnapshotDelete( + F.asList(CHECK_SNAPSHOT_METAS, CHECK_SNAPSHOT_PARTS), () -> { - new IgniteFutureImpl<>(snp(grid(1)).checkSnapshot(SNAPSHOT_NAME, null)).get(getTestTimeout()); - - return null; - } + grid(0).destroyCache(DEFAULT_CACHE_NAME); + + try { + awaitPartitionMapExchange(); + } + catch (InterruptedException e) { + throw new RuntimeException("Interrupted.", e); + } + }, + () -> snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null), + "Snapshot with this name is being checked", + true ); } - /** Tests that a snapshot delete is declined when a snapshot restore operation is in progress. */ + /** Tests that a snapshot deletion is declined when a snapshot restore is in progress. */ @Test public void testSnapshotDeleteWhenRestoreInProgress() throws Exception { - prepareGridsAndSnapshot(3, 2, 2, true); - - doTestConcurrentSnpDeleteOperation( - () -> snp(grid(0)).restoreSnapshot(SNAPSHOT_NAME, null, null, 0, true), + var restoreMsgs = F.asList( RESTORE_CACHE_GROUP_SNAPSHOT_PREPARE, - () -> { - assertThrowsAnyCause( - log, - () -> { - snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); - - return null; - }, - ClusterTopologyCheckedException.class, - "Snapshot deletion was rejected. the snapshot is being restored" - ); - - return null; - } + RESTORE_CACHE_GROUP_SNAPSHOT_PRELOAD, + RESTORE_CACHE_GROUP_SNAPSHOT_START ); - } - - /** Tests that a snapshot restore is declined when a snapshot delete operation is in progress. */ - @Test - public void testSnapshotRestoreWhenDeleteInProgress() throws Exception { - prepareGridsAndSnapshot(3, 2, 2, true); - doTestConcurrentSnpDeleteOperation( - () -> snp(grid(0)).deleteSnapshot(SNAPSHOT_NAME, null), - DELETE_SNAPSHOT, + doTestConcurrentSnapshotDelete( + restoreMsgs, () -> { - assertThrowsAnyCause( - log, - () -> { - snp(grid(0)).restoreSnapshot(SNAPSHOT_NAME, null, null, 0, true).get(getTestTimeout()); - - return null; - }, - IgniteException.class, - "A snapshot delete operation is in progress" - ); - - return null; - } + grid(0).destroyCache(DEFAULT_CACHE_NAME); + + try { + awaitPartitionMapExchange(); + } + catch (InterruptedException e) { + throw new RuntimeException("Interrupted.", e); + } + }, + () -> snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null), + "Snapshot with this name is being restored", + true ); } - /** - * Tests the concurrent snapshot delete procedure against another snapshot operation. - * - *

The {@code originatorOp} is blocked on the coordinator discovery, so it is kept in-progress while the - * {@code trierStep} is executed and its conflict behavior is asserted. - * - * @param originatorOp First snapshot operation on the coordinator node to keep in-progress. - * @param firstDelay First distributed process full message of {@code originatorOp} to delay on the coordinator - * to launch {@code trierStep}. - * @param trierStep Second concurrent snapshot operation with asserted result. - */ - private void doTestConcurrentSnpDeleteOperation( - Supplier> originatorOp, - DistributedProcess.DistributedProcessType firstDelay, - Callable trierStep + /** */ + protected void doTestConcurrentSnapshotDelete( + Collection msgsToWatch, + @Nullable Runnable prepareIteration, + Supplier> firstOp, + String concurrentMsgErr, + boolean precreateSnp ) throws Exception { - try { - AtomicBoolean firstDelayed = new AtomicBoolean(); - - // Block only the first matching message so the originator operation stays in-progress. - discoSpi(grid(0)).block( - msg -> msg instanceof FullMessage - && ((FullMessage)msg).type() == firstDelay.ordinal() - && firstDelayed.compareAndSet(false, true)); - - IgniteFuture fut = originatorOp.get(); - - discoSpi(grid(0)).waitBlocked(getTestTimeout()); + if (precreateSnp) + startGridsWithSnapshot(3, CACHE_KEYS_RANGE, false, true); + else + startGridsWithCache(3, CACHE_KEYS_RANGE, i -> i); - if (trierStep != null) - trierStep.call(); + TestRecordingCommunicationSpi commSpi1 = (TestRecordingCommunicationSpi)grid(1).configuration().getCommunicationSpi(); - discoSpi(grid(0)).unblock(); + for (var nodeResMsgType : msgsToWatch) { + if (prepareIteration != null) + prepareIteration.run(); - fut.get(getTestTimeout()); - } - finally { - discoSpi(grid(0)).unblock(); - - awaitPartitionMapExchange(); - } - } - - /** - * @param servers Number of server nodes. - * @param baseLineCnt Number of baseline nodes. - * @param clients Number of client nodes. - * @param removeTheCache If {@code true}, the cache is destroyed after the snapshot is created (restore scenario). - * @return The last started (server) grid, which the snapshot was created on. - */ - private IgniteEx prepareGridsAndSnapshot(int servers, int baseLineCnt, int clients, boolean removeTheCache) throws Exception { - assert baseLineCnt > 0 && baseLineCnt <= servers; + commSpi1.blockMessages((node, msg) -> + msg instanceof SingleNodeMessage msg0 && msg0.type() == nodeResMsgType.ordinal()); - IgniteEx ignite = null; + var checkFut = firstOp.get(); - for (int i = 0; i < servers + clients; ++i) { - IgniteConfiguration cfg = getConfiguration(getTestIgniteInstanceName(i)); + commSpi1.waitForBlocked(1, getTestTimeout()); - if (i >= servers) - cfg.setClientMode(true); + assertThrowsAnyCause( + null, + () -> { + snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); - ignite = startGrid(cfg); + return null; + }, + IgniteIllegalStateException.class, + concurrentMsgErr + ); - if (i == baseLineCnt - 1) { - ignite.cluster().state(ACTIVE); + commSpi1.stopBlock(); - ignite.cluster().setBaselineTopology(ignite.cluster().topologyVersion()); - } + checkFut.get(getTestTimeout()); } - - try (IgniteDataStreamer ds = grid(0).dataStreamer(DEFAULT_CACHE_NAME)) { - // Ensure all the partitions are created: several records per partition. - for (int i = 0; i < CACHE_PARTS_CNT * 4; ++i) - ds.addData(i, i); - } - - ignite.snapshot().createSnapshot(SNAPSHOT_NAME).get(); - - if (removeTheCache) - ignite.destroyCache(DEFAULT_CACHE_NAME); - - return ignite; } } From b09e0b32e688db0568a31620f7a75ea7c513f8ce Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Fri, 11 Sep 2026 00:22:08 +0300 Subject: [PATCH 16/97] +incr test --- .../IgniteClusterSnapshotDeleteTest.java | 160 ++++++++++++++---- 1 file changed, 125 insertions(+), 35 deletions(-) diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java index 15f332d7933ec..e1ada50e4422d 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java @@ -17,6 +17,7 @@ package org.apache.ignite.internal.processors.cache.persistence.snapshot; +import java.util.ArrayList; import java.util.Collection; import java.util.function.Supplier; import org.apache.ignite.IgniteIllegalStateException; @@ -29,6 +30,9 @@ import org.apache.ignite.lang.IgniteFuture; import org.jetbrains.annotations.Nullable; import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.CHECK_SNAPSHOT_METAS; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.CHECK_SNAPSHOT_PARTS; @@ -36,11 +40,37 @@ import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_PRELOAD; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_PREPARE; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_START; +import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_INCREMENTAL_SNAPSHOT_START; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.START_SNAPSHOT; import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause; +import static org.junit.Assume.assumeTrue; /** */ +@RunWith(Parameterized.class) public class IgniteClusterSnapshotDeleteTest extends AbstractSnapshotSelfTest { + /** */ + @Parameter(2) + public boolean incremental = true; + + /** Parameters. */ + @Parameterized.Parameters(name = "encryption={0}, onlyPrimay={1}, incremental={2}") + public static Collection runParams() { + Collection res = new ArrayList<>(); + + for (boolean incremental : F.asList(false, true)) { + for (Object[] src0 : params()) { + Object[] res0 = new Object[src0.length + 1]; + System.arraycopy(src0, 0, res0, 0, src0.length); + + res0[src0.length] = incremental; + + res.add(res0); + } + } + + return res; + } + /** {@inheritDoc} */ @Override public void afterTestSnapshot() throws Exception { super.afterTestSnapshot(); @@ -50,90 +80,130 @@ public class IgniteClusterSnapshotDeleteTest extends AbstractSnapshotSelfTest { cleanPersistenceDir(); } + /** {@inheritDoc} */ + @Override public void beforeTestSnapshot() throws Exception { + super.beforeTestSnapshot(); + + // Handy + cleanPersistenceDir(); + } + /** Tests that a snapshot deletion is declined when a snapshot check operation is in progress. */ @Test public void testSnapshotDeleteWhenCheckInProgress() throws Exception { + // Incremental snapshots don't support encription. + assumeTrue(!incremental || !encryption); + doTestConcurrentSnapshotDelete( + () -> new IgniteFutureImpl<>(snp(grid(2)).checkSnapshot(SNAPSHOT_NAME, null, incremental ? 1 : 0)), F.asList(CHECK_SNAPSHOT_METAS, CHECK_SNAPSHOT_PARTS), + true, null, - () -> new IgniteFutureImpl<>(snp(grid(2)).checkSnapshot(SNAPSHOT_NAME, null)), - "Snapshot with this name is being checked", - true + "Snapshot with this name is being checked" ); } /** Tests that a snapshot deletion is declined when a snapshot create operation is in progress. */ @Test public void testSnapshotDeleteWhenCreateInProgress() throws Exception { + // Incremental snapshots don't support encription and only-primary mode. + assumeTrue(!incremental || !(encryption || onlyPrimary)); + doTestConcurrentSnapshotDelete( + () -> snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, incremental, onlyPrimary), F.asList(START_SNAPSHOT, END_SNAPSHOT), - () -> snp(grid(0)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()), - () -> snp(grid(2)).createSnapshot(SNAPSHOT_NAME), - "Snapshot with this name is being created", - false + false, + () -> { + snp(grid(0)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); + + if (incremental) + snp(grid(0)).createSnapshot(SNAPSHOT_NAME).get(getTestTimeout()); + }, + "Snapshot with this name is being created" ); } /** Tests that a snapshot deletion is declined when a snapshot restore begins. */ @Test public void testSnapshotDeleteWhenRestoreBegins() throws Exception { + // Incremental snapshots don't support encription. + assumeTrue(!incremental || !encryption); + doTestConcurrentSnapshotDelete( + () -> { + if (incremental) + return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null, 1); + else + return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null); + }, F.asList(CHECK_SNAPSHOT_METAS, CHECK_SNAPSHOT_PARTS), + true, () -> { grid(0).destroyCache(DEFAULT_CACHE_NAME); - try { - awaitPartitionMapExchange(); - } - catch (InterruptedException e) { - throw new RuntimeException("Interrupted.", e); - } + awaitPartitionMapExchange(); }, - () -> snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null), - "Snapshot with this name is being checked", - true + "Snapshot with this name is being checked" ); } /** Tests that a snapshot deletion is declined when a snapshot restore is in progress. */ @Test public void testSnapshotDeleteWhenRestoreInProgress() throws Exception { + // Incremental snapshots don't support encription. + assumeTrue(!incremental || !encryption); + var restoreMsgs = F.asList( RESTORE_CACHE_GROUP_SNAPSHOT_PREPARE, RESTORE_CACHE_GROUP_SNAPSHOT_PRELOAD, RESTORE_CACHE_GROUP_SNAPSHOT_START ); + if (incremental) { + restoreMsgs = new ArrayList<>(restoreMsgs); + restoreMsgs.add(RESTORE_INCREMENTAL_SNAPSHOT_START); + } + doTestConcurrentSnapshotDelete( + () -> { + if (incremental) + return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null, 1); + else + return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null); + }, restoreMsgs, + true, () -> { grid(0).destroyCache(DEFAULT_CACHE_NAME); - try { - awaitPartitionMapExchange(); - } - catch (InterruptedException e) { - throw new RuntimeException("Interrupted.", e); - } + awaitPartitionMapExchange(); }, - () -> snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null), - "Snapshot with this name is being restored", - true + "Snapshot with this name is being restored" ); } - /** */ + /** + * @param firstOp First cluster-wide snapshot operation. + * @param msgsToWatch {@link SingleNodeMessage#type()} relating to {@code firstOp} bo block on one node. + * @param precreateSnp If {@code true}, creates snapshot after the cluster start. + * @param prepareIteration If not {@code null}, is invoked in the beggining of test iteration at each {@code msgsToWatch}. + * @param concurrentMsgErr Test of failed concurrent to {@code firstOp} delete snapshot operation to watch. + */ protected void doTestConcurrentSnapshotDelete( + Supplier> firstOp, Collection msgsToWatch, + boolean precreateSnp, @Nullable Runnable prepareIteration, - Supplier> firstOp, - String concurrentMsgErr, - boolean precreateSnp + String concurrentMsgErr ) throws Exception { - if (precreateSnp) - startGridsWithSnapshot(3, CACHE_KEYS_RANGE, false, true); - else - startGridsWithCache(3, CACHE_KEYS_RANGE, i -> i); + startGridsWithCache(3, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); + + if (precreateSnp) { + snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(TIMEOUT); + + if (incremental) + addIncrementalSnapshot(); + } TestRecordingCommunicationSpi commSpi1 = (TestRecordingCommunicationSpi)grid(1).configuration().getCommunicationSpi(); @@ -144,7 +214,7 @@ protected void doTestConcurrentSnapshotDelete( commSpi1.blockMessages((node, msg) -> msg instanceof SingleNodeMessage msg0 && msg0.type() == nodeResMsgType.ordinal()); - var checkFut = firstOp.get(); + var firstFut = firstOp.get(); commSpi1.waitForBlocked(1, getTestTimeout()); @@ -161,7 +231,27 @@ protected void doTestConcurrentSnapshotDelete( commSpi1.stopBlock(); - checkFut.get(getTestTimeout()); + firstFut.get(getTestTimeout()); + } + } + + /** */ + private void addIncrementalSnapshot() { + try (var streamer = grid(0).dataStreamer(DEFAULT_CACHE_NAME)) { + for (int i = CACHE_KEYS_RANGE; i < CACHE_KEYS_RANGE + CACHE_KEYS_RANGE / 4; ++i) + streamer.addData(i, i); + } + + snp(grid(0)).createIncrementalSnapshot(SNAPSHOT_NAME).get(getTestTimeout()); + } + + /** {@inheritDoc} */ + @Override protected void awaitPartitionMapExchange() { + try { + super.awaitPartitionMapExchange(); + } + catch (InterruptedException e) { + throw new RuntimeException("Interrupted.", e); } } } From 71717c293a6ced217e27f5da329370f54629dcd8 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Fri, 11 Sep 2026 00:42:22 +0300 Subject: [PATCH 17/97] raw --- .../IgniteClusterSnapshotDeleteTest.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java index e1ada50e4422d..3b12e209727e9 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java @@ -28,6 +28,8 @@ import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.G; import org.apache.ignite.lang.IgniteFuture; +import org.apache.ignite.plugin.AbstractTestPluginProvider; +import org.apache.ignite.plugin.PluginContext; import org.jetbrains.annotations.Nullable; import org.junit.Test; import org.junit.runner.RunWith; @@ -88,6 +90,26 @@ public static Collection runParams() { cleanPersistenceDir(); } + /** */ + @Test + public void testNodeNotSupportingSnapshotDeleteFeature() throws Exception { + startGrid(0); + + pluginProvider = new AbstractTestPluginProvider() { + @Override public String name() { + return "TestPluginProvider"; + } + + @Override public T createComponent(PluginContext ctx, Class cls) { + if() + } + }; + + pluginProvider = null; + + startGrid(2); + } + /** Tests that a snapshot deletion is declined when a snapshot check operation is in progress. */ @Test public void testSnapshotDeleteWhenCheckInProgress() throws Exception { From 7148da61f6cd78bcef10b02cba027ba0183eacd3 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Fri, 11 Sep 2026 01:07:38 +0300 Subject: [PATCH 18/97] + feature support test --- .../snapshot/SnapshotDeleteProcess.java | 8 ++++ .../IgniteClusterSnapshotDeleteTest.java | 39 +++++++++++++++---- 2 files changed, 40 insertions(+), 7 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index b370c30d18e03..96187a230c905 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -27,6 +27,7 @@ import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.IgniteInternalFuture; import org.apache.ignite.internal.NodeStoppingException; +import org.apache.ignite.internal.managers.discovery.IgniteClusterNode; import org.apache.ignite.internal.processors.cache.persistence.filename.SnapshotFileTree; import org.apache.ignite.internal.util.distributed.DistributedProcess; import org.apache.ignite.internal.util.future.GridFinishedFuture; @@ -36,6 +37,7 @@ import org.apache.ignite.lang.IgniteFuture; import org.jetbrains.annotations.Nullable; +import static org.apache.ignite.internal.processors.rollingupgrade.feature.SupportedFeatureRegistry.SNAPSHOT_DELETE_FEATURE; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.DELETE_SNAPSHOT; import static org.apache.ignite.plugin.security.SecurityPermission.ADMIN_SNAPSHOT; @@ -142,6 +144,12 @@ private IgniteInternalFuture deletePhase(UUID ignored, S " Snapshot with this name is being checked, req=" + req)); } + for (var node : kctx.cluster().get().forServers().nodes()) { + if (!(node instanceof IgniteClusterNode in) || !in.features().contains(SNAPSHOT_DELETE_FEATURE)) + return new GridFinishedFuture<>(new IgniteIllegalStateException(OP_REJECT_MSG + + " Node " + node.id() + " doesn't support snapshot deletion, req=" + req)); + } + try { if (requests.putIfAbsent(req.snpName, req) != null) { return new GridFinishedFuture<>(new IgniteIllegalStateException("Deletion of the snapshot has already " + diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java index 3b12e209727e9..8206e2375ee7d 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java @@ -21,7 +21,14 @@ import java.util.Collection; import java.util.function.Supplier; import org.apache.ignite.IgniteIllegalStateException; +import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.IgniteVersionUtils; import org.apache.ignite.internal.TestRecordingCommunicationSpi; +import org.apache.ignite.internal.processors.nodevalidation.DiscoveryNodeValidationProcessor; +import org.apache.ignite.internal.processors.rollingupgrade.RollingUpgradeProcessor; +import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteCoreFeatureSet; +import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteFeatureSet; import org.apache.ignite.internal.util.distributed.DistributedProcess; import org.apache.ignite.internal.util.distributed.SingleNodeMessage; import org.apache.ignite.internal.util.future.IgniteFutureImpl; @@ -30,6 +37,7 @@ import org.apache.ignite.lang.IgniteFuture; import org.apache.ignite.plugin.AbstractTestPluginProvider; import org.apache.ignite.plugin.PluginContext; +import org.apache.ignite.spi.IgniteNodeValidationResult; import org.jetbrains.annotations.Nullable; import org.junit.Test; import org.junit.runner.RunWith; @@ -93,21 +101,38 @@ public static Collection runParams() { /** */ @Test public void testNodeNotSupportingSnapshotDeleteFeature() throws Exception { - startGrid(0); - + // Creates empty feature set unsupporting the snapshot deletion if required. pluginProvider = new AbstractTestPluginProvider() { @Override public String name() { - return "TestPluginProvider"; + return "Test Ignite features provider"; } - @Override public T createComponent(PluginContext ctx, Class cls) { - if() + @Override public @Nullable T createComponent(PluginContext ctx, Class cls) { + if (!cls.equals(DiscoveryNodeValidationProcessor.class)) + return null; + + boolean doNotSupport = ctx.igniteConfiguration().getIgniteInstanceName().equals(getTestIgniteInstanceName(1)); + + return (T)new RollingUpgradeProcessor( + ((IgniteEx)ctx.grid()).context(), + doNotSupport ? new IgniteCoreFeatureSet(IgniteVersionUtils.VER, new IgniteFeatureSet()) : IgniteCoreFeatureSet.local() + ) { + @Override public @Nullable IgniteNodeValidationResult validateNode(ClusterNode joiningNode) { + // Simulates started rolling updrade allowing node with other features join cluster. + return null; + } + }; } }; - pluginProvider = null; + startGridsMultiThreaded(3); - startGrid(2); + assertThrowsAnyCause( + null, + () -> snp(grid(0)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()), + IgniteIllegalStateException.class, + "Node " + grid(1).localNode().id() + " doesn't support snapshot deletion" + ); } /** Tests that a snapshot deletion is declined when a snapshot check operation is in progress. */ From d36c64225f8f583a5d08042690553d143086fca6 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Fri, 11 Sep 2026 01:49:54 +0300 Subject: [PATCH 19/97] + IgniteClusterSnapshotDeleteTest --- .../management/snapshot/SnapshotCommand.java | 4 +- .../snapshot/SnapshotDeleteCommand.java | 21 +- .../snapshot/IgniteSnapshotManager.java | 12 +- .../snapshot/SnapshotDeleteProcess.java | 4 +- .../snapshot/SnapshotDeleteProcessResult.java | 2 +- ...ClusterSnapshotDeleteParametrizedTest.java | 257 ++++++++++++++++++ .../IgniteClusterSnapshotDeleteTest.java | 223 +-------------- .../testsuites/IgniteSnapshotTestSuite8.java | 2 + 8 files changed, 289 insertions(+), 236 deletions(-) create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteParametrizedTest.java diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCommand.java index 42b300fb34fdf..8979958bd8fb6 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCommand.java @@ -27,9 +27,9 @@ public SnapshotCommand() { new SnapshotCreateCommand(), new SnapshotCancelCommand(), new SnapshotCheckCommand(), - new SnapshotDeleteCommand(), new SnapshotRestoreCommand(), - new SnapshotStatusCommand() + new SnapshotStatusCommand(), + new SnapshotDeleteCommand() ); } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java index 8a56f82ec78ec..b3cdf6f560f64 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java @@ -21,14 +21,22 @@ import java.util.UUID; import java.util.function.Consumer; import java.util.stream.Collectors; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotDeleteProcess; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotDeleteProcessResult; +import org.apache.ignite.internal.processors.rollingupgrade.feature.SupportedFeatureRegistry; import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.internal.U; -/** */ +/** + * Snapshot deletion command. + * + * @see SupportedFeatureRegistry#SNAPSHOT_DELETE_FEATURE + * @see SnapshotDeleteProcess + */ public class SnapshotDeleteCommand extends AbstractSnapshotCommand { /** {@inheritDoc} */ @Override public String description() { - return "Delete snapshot and all its increments from all the online server nodes."; + return "Deletes snapshot and all its increments from all the online server nodes."; } /** {@inheritDoc} */ @@ -82,7 +90,12 @@ private static String nodeIdsStrLst(Collection uuids) { /** {@inheritDoc} */ @Override public String confirmationPrompt(SnapshotDeleteCommandArg arg) { - return "Warning: this will delete snapshot '" + arg.snapshotName() + "' and all its increments " + - "from all online server nodes. This operation is irreversible."; + return "This will delete snapshot '" + arg.snapshotName() + + "' and all its increments from all online server nodes." + + U.nl() + U.nl() + + "WARNING: the snapshot integrity, initial topology and correctness aren't checked." + + " Snapshot parts on offline server nodes aren't deleted." + + U.nl() + U.nl() + + "The operation is irreversible."; } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java index 3032a3fa31fe2..aeed87534d16d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java @@ -735,18 +735,18 @@ public void deleteLocalSnapshot(File snpDir) { public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean existsFlag) { AtomicBoolean res = new AtomicBoolean(true); - sft.allStorages().forEach(f -> deleteWithIndicators(f, res, existsFlag)); + sft.allStorages().forEach(f -> deleteWithExistence(f, res, existsFlag)); - deleteWithIndicators(sft.binaryMeta(), res, existsFlag); - deleteWithIndicators(sft.binaryMetaRoot(), res, existsFlag); - deleteWithIndicators(sft.marshaller(), res, existsFlag); - deleteWithIndicators(sft.root(), res, existsFlag); + deleteWithExistence(sft.binaryMeta(), res, existsFlag); + deleteWithExistence(sft.binaryMetaRoot(), res, existsFlag); + deleteWithExistence(sft.marshaller(), res, existsFlag); + deleteWithExistence(sft.root(), res, existsFlag); return res.get(); } /** */ - void deleteWithIndicators(@Nullable File f, AtomicBoolean onlyFailRes, @Nullable AtomicBoolean existsFlag) { + void deleteWithExistence(@Nullable File f, AtomicBoolean onlyFailRes, @Nullable AtomicBoolean existsFlag) { if (f == null) return; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index 96187a230c905..4b552f5f2822a 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -113,7 +113,7 @@ public IgniteFuture start(String snpName, @Nullable return new IgniteFutureImpl<>(clusterOpFut); } - /** Local phase: delete the snapshot directory on a node. */ + /** */ private IgniteInternalFuture deletePhase(UUID ignored, SnapshotDeleteRequest req) { if (kctx.isStopping()) { return new GridFinishedFuture<>(new NodeStoppingException(OP_REJECT_MSG + @@ -204,7 +204,7 @@ else if (!deleted) } } - /** Coordinator finish: aggregate node results and complete the user future. */ + /** */ private void reducePhase(UUID reqId, Map results, Map errors) { var clusterOpFut = clusterOpFuts.get(reqId); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResult.java index 67cad1e5d6718..74625d3a166c6 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResult.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResult.java @@ -24,7 +24,7 @@ import org.apache.ignite.plugin.extensions.communication.MessageFactory; import org.jetbrains.annotations.Nullable; -/** Result of snapshot delete process. */ +/** Result of {@link SnapshotDeleteProcess}. */ public final class SnapshotDeleteProcessResult extends IgniteDataTransferObject { /** Serial version uid. */ private static final long serialVersionUID = 0L; diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteParametrizedTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteParametrizedTest.java new file mode 100644 index 0000000000000..7c212b834385a --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteParametrizedTest.java @@ -0,0 +1,257 @@ +/* + * 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.ignite.internal.processors.cache.persistence.snapshot; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.function.Supplier; +import org.apache.ignite.IgniteIllegalStateException; +import org.apache.ignite.internal.TestRecordingCommunicationSpi; +import org.apache.ignite.internal.util.distributed.DistributedProcess; +import org.apache.ignite.internal.util.distributed.SingleNodeMessage; +import org.apache.ignite.internal.util.future.IgniteFutureImpl; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.G; +import org.apache.ignite.lang.IgniteFuture; +import org.jetbrains.annotations.Nullable; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameter; + +import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.CHECK_SNAPSHOT_METAS; +import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.CHECK_SNAPSHOT_PARTS; +import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.END_SNAPSHOT; +import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_PRELOAD; +import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_PREPARE; +import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_START; +import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_INCREMENTAL_SNAPSHOT_START; +import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.START_SNAPSHOT; +import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause; +import static org.junit.Assume.assumeTrue; + +/** */ +@RunWith(Parameterized.class) +public class IgniteClusterSnapshotDeleteParametrizedTest extends AbstractSnapshotSelfTest { + /** */ + @Parameter(2) + public boolean incremental = true; + + /** Parameters. */ + @Parameterized.Parameters(name = "encryption={0}, onlyPrimay={1}, incremental={2}") + public static Collection runParams() { + Collection res = new ArrayList<>(); + + for (boolean incremental : F.asList(false, true)) { + for (Object[] src0 : params()) { + Object[] res0 = new Object[src0.length + 1]; + System.arraycopy(src0, 0, res0, 0, src0.length); + + res0[src0.length] = incremental; + + res.add(res0); + } + } + + return res; + } + + /** {@inheritDoc} */ + @Override public void afterTestSnapshot() throws Exception { + super.afterTestSnapshot(); + + G.allGrids(); + + cleanPersistenceDir(); + } + + /** {@inheritDoc} */ + @Override public void beforeTestSnapshot() throws Exception { + super.beforeTestSnapshot(); + + // Handy + cleanPersistenceDir(); + } + + /** Tests that a snapshot deletion is declined when a snapshot check operation is in progress. */ + @Test + public void testSnapshotDeleteWhenCheckInProgress() throws Exception { + // Incremental snapshots don't support encription. + assumeTrue(!incremental || !encryption); + + doTestConcurrentSnapshotDelete( + () -> new IgniteFutureImpl<>(snp(grid(2)).checkSnapshot(SNAPSHOT_NAME, null, incremental ? 1 : 0)), + F.asList(CHECK_SNAPSHOT_METAS, CHECK_SNAPSHOT_PARTS), + true, + null, + "Snapshot with this name is being checked" + ); + } + + /** Tests that a snapshot deletion is declined when a snapshot create operation is in progress. */ + @Test + public void testSnapshotDeleteWhenCreateInProgress() throws Exception { + // Incremental snapshots don't support encription and only-primary mode. + assumeTrue(!incremental || !(encryption || onlyPrimary)); + + doTestConcurrentSnapshotDelete( + () -> snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, incremental, onlyPrimary), + F.asList(START_SNAPSHOT, END_SNAPSHOT), + false, + () -> { + snp(grid(0)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); + + if (incremental) + snp(grid(0)).createSnapshot(SNAPSHOT_NAME).get(getTestTimeout()); + }, + "Snapshot with this name is being created" + ); + } + + /** Tests that a snapshot deletion is declined when a snapshot restore begins. */ + @Test + public void testSnapshotDeleteWhenRestoreBegins() throws Exception { + // Incremental snapshots don't support encription. + assumeTrue(!incremental || !encryption); + + doTestConcurrentSnapshotDelete( + () -> { + if (incremental) + return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null, 1); + else + return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null); + }, + F.asList(CHECK_SNAPSHOT_METAS, CHECK_SNAPSHOT_PARTS), + true, + () -> { + grid(0).destroyCache(DEFAULT_CACHE_NAME); + + awaitPartitionMapExchange(); + }, + "Snapshot with this name is being checked" + ); + } + + /** Tests that a snapshot deletion is declined when a snapshot restore is in progress. */ + @Test + public void testSnapshotDeleteWhenRestoreInProgress() throws Exception { + // Incremental snapshots don't support encription. + assumeTrue(!incremental || !encryption); + + var restoreMsgs = F.asList( + RESTORE_CACHE_GROUP_SNAPSHOT_PREPARE, + RESTORE_CACHE_GROUP_SNAPSHOT_PRELOAD, + RESTORE_CACHE_GROUP_SNAPSHOT_START + ); + + if (incremental) { + restoreMsgs = new ArrayList<>(restoreMsgs); + restoreMsgs.add(RESTORE_INCREMENTAL_SNAPSHOT_START); + } + + doTestConcurrentSnapshotDelete( + () -> { + if (incremental) + return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null, 1); + else + return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null); + }, + restoreMsgs, + true, + () -> { + grid(0).destroyCache(DEFAULT_CACHE_NAME); + + awaitPartitionMapExchange(); + }, + "Snapshot with this name is being restored" + ); + } + + /** + * @param firstOp First cluster-wide snapshot operation. + * @param msgsToWatch {@link SingleNodeMessage#type()} relating to {@code firstOp} bo block on one node. + * @param precreateSnp If {@code true}, creates snapshot after the cluster start. + * @param prepareIteration If not {@code null}, is invoked in the beggining of test iteration at each {@code msgsToWatch}. + * @param concurrentMsgErr Test of failed concurrent to {@code firstOp} delete snapshot operation to watch. + */ + protected void doTestConcurrentSnapshotDelete( + Supplier> firstOp, + Collection msgsToWatch, + boolean precreateSnp, + @Nullable Runnable prepareIteration, + String concurrentMsgErr + ) throws Exception { + startGridsWithCache(3, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); + + if (precreateSnp) { + snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(TIMEOUT); + + if (incremental) + addIncrementalSnapshot(); + } + + TestRecordingCommunicationSpi commSpi1 = (TestRecordingCommunicationSpi)grid(1).configuration().getCommunicationSpi(); + + for (var nodeResMsgType : msgsToWatch) { + if (prepareIteration != null) + prepareIteration.run(); + + commSpi1.blockMessages((node, msg) -> + msg instanceof SingleNodeMessage msg0 && msg0.type() == nodeResMsgType.ordinal()); + + var firstFut = firstOp.get(); + + commSpi1.waitForBlocked(1, getTestTimeout()); + + assertThrowsAnyCause( + null, + () -> { + snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); + + return null; + }, + IgniteIllegalStateException.class, + concurrentMsgErr + ); + + commSpi1.stopBlock(); + + firstFut.get(getTestTimeout()); + } + } + + /** */ + private void addIncrementalSnapshot() { + try (var streamer = grid(0).dataStreamer(DEFAULT_CACHE_NAME)) { + for (int i = CACHE_KEYS_RANGE; i < CACHE_KEYS_RANGE + CACHE_KEYS_RANGE / 4; ++i) + streamer.addData(i, i); + } + + snp(grid(0)).createIncrementalSnapshot(SNAPSHOT_NAME).get(getTestTimeout()); + } + + /** {@inheritDoc} */ + @Override protected void awaitPartitionMapExchange() { + try { + super.awaitPartitionMapExchange(); + } + catch (InterruptedException e) { + throw new RuntimeException("Interrupted.", e); + } + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java index 8206e2375ee7d..afa54fc688b8e 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java @@ -17,85 +17,33 @@ package org.apache.ignite.internal.processors.cache.persistence.snapshot; -import java.util.ArrayList; -import java.util.Collection; -import java.util.function.Supplier; import org.apache.ignite.IgniteIllegalStateException; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.IgniteVersionUtils; -import org.apache.ignite.internal.TestRecordingCommunicationSpi; import org.apache.ignite.internal.processors.nodevalidation.DiscoveryNodeValidationProcessor; import org.apache.ignite.internal.processors.rollingupgrade.RollingUpgradeProcessor; import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteCoreFeatureSet; import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteFeatureSet; -import org.apache.ignite.internal.util.distributed.DistributedProcess; -import org.apache.ignite.internal.util.distributed.SingleNodeMessage; -import org.apache.ignite.internal.util.future.IgniteFutureImpl; -import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.G; -import org.apache.ignite.lang.IgniteFuture; import org.apache.ignite.plugin.AbstractTestPluginProvider; import org.apache.ignite.plugin.PluginContext; import org.apache.ignite.spi.IgniteNodeValidationResult; import org.jetbrains.annotations.Nullable; import org.junit.Test; import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.JUnit4; -import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.CHECK_SNAPSHOT_METAS; -import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.CHECK_SNAPSHOT_PARTS; -import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.END_SNAPSHOT; -import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_PRELOAD; -import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_PREPARE; -import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_START; -import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_INCREMENTAL_SNAPSHOT_START; -import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.START_SNAPSHOT; import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause; -import static org.junit.Assume.assumeTrue; /** */ -@RunWith(Parameterized.class) +@RunWith(JUnit4.class) public class IgniteClusterSnapshotDeleteTest extends AbstractSnapshotSelfTest { - /** */ - @Parameter(2) - public boolean incremental = true; - - /** Parameters. */ - @Parameterized.Parameters(name = "encryption={0}, onlyPrimay={1}, incremental={2}") - public static Collection runParams() { - Collection res = new ArrayList<>(); - - for (boolean incremental : F.asList(false, true)) { - for (Object[] src0 : params()) { - Object[] res0 = new Object[src0.length + 1]; - System.arraycopy(src0, 0, res0, 0, src0.length); - - res0[src0.length] = incremental; - - res.add(res0); - } - } - - return res; - } - /** {@inheritDoc} */ @Override public void afterTestSnapshot() throws Exception { super.afterTestSnapshot(); G.allGrids(); - - cleanPersistenceDir(); - } - - /** {@inheritDoc} */ - @Override public void beforeTestSnapshot() throws Exception { - super.beforeTestSnapshot(); - - // Handy - cleanPersistenceDir(); } /** */ @@ -134,171 +82,4 @@ public void testNodeNotSupportingSnapshotDeleteFeature() throws Exception { "Node " + grid(1).localNode().id() + " doesn't support snapshot deletion" ); } - - /** Tests that a snapshot deletion is declined when a snapshot check operation is in progress. */ - @Test - public void testSnapshotDeleteWhenCheckInProgress() throws Exception { - // Incremental snapshots don't support encription. - assumeTrue(!incremental || !encryption); - - doTestConcurrentSnapshotDelete( - () -> new IgniteFutureImpl<>(snp(grid(2)).checkSnapshot(SNAPSHOT_NAME, null, incremental ? 1 : 0)), - F.asList(CHECK_SNAPSHOT_METAS, CHECK_SNAPSHOT_PARTS), - true, - null, - "Snapshot with this name is being checked" - ); - } - - /** Tests that a snapshot deletion is declined when a snapshot create operation is in progress. */ - @Test - public void testSnapshotDeleteWhenCreateInProgress() throws Exception { - // Incremental snapshots don't support encription and only-primary mode. - assumeTrue(!incremental || !(encryption || onlyPrimary)); - - doTestConcurrentSnapshotDelete( - () -> snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, incremental, onlyPrimary), - F.asList(START_SNAPSHOT, END_SNAPSHOT), - false, - () -> { - snp(grid(0)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); - - if (incremental) - snp(grid(0)).createSnapshot(SNAPSHOT_NAME).get(getTestTimeout()); - }, - "Snapshot with this name is being created" - ); - } - - /** Tests that a snapshot deletion is declined when a snapshot restore begins. */ - @Test - public void testSnapshotDeleteWhenRestoreBegins() throws Exception { - // Incremental snapshots don't support encription. - assumeTrue(!incremental || !encryption); - - doTestConcurrentSnapshotDelete( - () -> { - if (incremental) - return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null, 1); - else - return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null); - }, - F.asList(CHECK_SNAPSHOT_METAS, CHECK_SNAPSHOT_PARTS), - true, - () -> { - grid(0).destroyCache(DEFAULT_CACHE_NAME); - - awaitPartitionMapExchange(); - }, - "Snapshot with this name is being checked" - ); - } - - /** Tests that a snapshot deletion is declined when a snapshot restore is in progress. */ - @Test - public void testSnapshotDeleteWhenRestoreInProgress() throws Exception { - // Incremental snapshots don't support encription. - assumeTrue(!incremental || !encryption); - - var restoreMsgs = F.asList( - RESTORE_CACHE_GROUP_SNAPSHOT_PREPARE, - RESTORE_CACHE_GROUP_SNAPSHOT_PRELOAD, - RESTORE_CACHE_GROUP_SNAPSHOT_START - ); - - if (incremental) { - restoreMsgs = new ArrayList<>(restoreMsgs); - restoreMsgs.add(RESTORE_INCREMENTAL_SNAPSHOT_START); - } - - doTestConcurrentSnapshotDelete( - () -> { - if (incremental) - return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null, 1); - else - return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null); - }, - restoreMsgs, - true, - () -> { - grid(0).destroyCache(DEFAULT_CACHE_NAME); - - awaitPartitionMapExchange(); - }, - "Snapshot with this name is being restored" - ); - } - - /** - * @param firstOp First cluster-wide snapshot operation. - * @param msgsToWatch {@link SingleNodeMessage#type()} relating to {@code firstOp} bo block on one node. - * @param precreateSnp If {@code true}, creates snapshot after the cluster start. - * @param prepareIteration If not {@code null}, is invoked in the beggining of test iteration at each {@code msgsToWatch}. - * @param concurrentMsgErr Test of failed concurrent to {@code firstOp} delete snapshot operation to watch. - */ - protected void doTestConcurrentSnapshotDelete( - Supplier> firstOp, - Collection msgsToWatch, - boolean precreateSnp, - @Nullable Runnable prepareIteration, - String concurrentMsgErr - ) throws Exception { - startGridsWithCache(3, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); - - if (precreateSnp) { - snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(TIMEOUT); - - if (incremental) - addIncrementalSnapshot(); - } - - TestRecordingCommunicationSpi commSpi1 = (TestRecordingCommunicationSpi)grid(1).configuration().getCommunicationSpi(); - - for (var nodeResMsgType : msgsToWatch) { - if (prepareIteration != null) - prepareIteration.run(); - - commSpi1.blockMessages((node, msg) -> - msg instanceof SingleNodeMessage msg0 && msg0.type() == nodeResMsgType.ordinal()); - - var firstFut = firstOp.get(); - - commSpi1.waitForBlocked(1, getTestTimeout()); - - assertThrowsAnyCause( - null, - () -> { - snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); - - return null; - }, - IgniteIllegalStateException.class, - concurrentMsgErr - ); - - commSpi1.stopBlock(); - - firstFut.get(getTestTimeout()); - } - } - - /** */ - private void addIncrementalSnapshot() { - try (var streamer = grid(0).dataStreamer(DEFAULT_CACHE_NAME)) { - for (int i = CACHE_KEYS_RANGE; i < CACHE_KEYS_RANGE + CACHE_KEYS_RANGE / 4; ++i) - streamer.addData(i, i); - } - - snp(grid(0)).createIncrementalSnapshot(SNAPSHOT_NAME).get(getTestTimeout()); - } - - /** {@inheritDoc} */ - @Override protected void awaitPartitionMapExchange() { - try { - super.awaitPartitionMapExchange(); - } - catch (InterruptedException e) { - throw new RuntimeException("Interrupted.", e); - } - } } diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java index 073e515779720..aa1af8cf59eb2 100644 --- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java +++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java @@ -21,6 +21,7 @@ import java.util.Collection; import java.util.List; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotCheckTest; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotDeleteParametrizedTest; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotDeleteTest; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotHandlerTest; import org.apache.ignite.testframework.GridTestUtils; @@ -50,6 +51,7 @@ public static List> suite(Collection ignoredTests) { GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotCheckTest.class, ignoredTests); GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotHandlerTest.class, ignoredTests); GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotDeleteTest.class, ignoredTests); + GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotDeleteParametrizedTest.class, ignoredTests); return suite; } From 9a658df391000ea95ff91cc2fc41c28ff354d95c Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Fri, 11 Sep 2026 11:09:38 +0300 Subject: [PATCH 20/97] fix --- .../cache/persistence/snapshot/SnapshotDeleteProcess.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index 4b552f5f2822a..1eabae7146fa5 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -27,7 +27,6 @@ import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.IgniteInternalFuture; import org.apache.ignite.internal.NodeStoppingException; -import org.apache.ignite.internal.managers.discovery.IgniteClusterNode; import org.apache.ignite.internal.processors.cache.persistence.filename.SnapshotFileTree; import org.apache.ignite.internal.util.distributed.DistributedProcess; import org.apache.ignite.internal.util.future.GridFinishedFuture; @@ -144,10 +143,9 @@ private IgniteInternalFuture deletePhase(UUID ignored, S " Snapshot with this name is being checked, req=" + req)); } - for (var node : kctx.cluster().get().forServers().nodes()) { - if (!(node instanceof IgniteClusterNode in) || !in.features().contains(SNAPSHOT_DELETE_FEATURE)) - return new GridFinishedFuture<>(new IgniteIllegalStateException(OP_REJECT_MSG + - " Node " + node.id() + " doesn't support snapshot deletion, req=" + req)); + if (!kctx.rollingUpgrade().features().isActive(SNAPSHOT_DELETE_FEATURE)) { + return new GridFinishedFuture<>(new IgniteIllegalStateException(OP_REJECT_MSG + + " Snapshot deletion feature isn't enabled, req=" + req)); } try { From d715c66c2c401ae724d90568d26216beb666d390 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Fri, 11 Sep 2026 14:19:39 +0300 Subject: [PATCH 21/97] test fix + doc --- docs/_docs/snapshots/snapshots.adoc | 42 +++++++++++++++++++ .../snapshot/SnapshotDeleteCommand.java | 6 +-- .../snapshot/SnapshotDeleteCommandArg.java | 4 +- ...mmandHandlerClusterByClassTest_help.output | 7 ++++ ...ndlerClusterByClassWithSSLTest_help.output | 7 ++++ 5 files changed, 61 insertions(+), 5 deletions(-) diff --git a/docs/_docs/snapshots/snapshots.adoc b/docs/_docs/snapshots/snapshots.adoc index fe61b3c49234a..154fd57192136 100644 --- a/docs/_docs/snapshots/snapshots.adoc +++ b/docs/_docs/snapshots/snapshots.adoc @@ -287,6 +287,48 @@ control.(sh|bat) --snapshot restore snapshot_09062021 --groups cache-group1,cach control.(sh|bat) --snapshot restore snapshot_09062021 --increment 1 ---- +== Deleting Snapshot + +You can delete a snapshot using the `control.sh|bat` script. + +The deletion is performed on all *online* server nodes of the cluster. +[NOTE] +==== +The snapshot integrity, topology and correctness aren't checked. Snapshot data on offline server nodes aren't deleted. +==== + +[tabs] +-- +tab:Unix[] +[source,shell] +---- +# Delete the snapshot "snapshot_09062021". +control.sh --snapshot delete snapshot_09062021 + +# Delete the snapshot "snapshot_09062021" located in the "/tmp/ignite/snapshots" folder. +control.sh --snapshot delete snapshot_09062021 --src /tmp/ignite/snapshots +---- + +tab:Windows[] +[source,shell] +---- +# Delete the snapshot "snapshot_09062021". +control.bat --snapshot delete snapshot_09062021 + +# Delete the snapshot "snapshot_09062021" located in the "/tmp/ignite/snapshots" folder. +control.bat --snapshot delete snapshot_09062021 --src /tmp/ignite/snapshots +---- +-- + +=== Delete operation limitations + +The delete operation is subject to the following limitations: + +* The deletion is rejected if any concurrent snapshot operation (create, restore, check, or a delete with the same name) is + active for the snapshot. +* The operation is irreversible. It cannot be undone, and the deleted snapshot cannot be restored. +* The command prompts for a confirmation before the deletion because the operation is irreversible and cannot be undone. + == Getting Snapshot Operation Status The status of the current snapshot operation in the cluster can be obtained using the `control.sh|bat` script or JMX interface: diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java index b3cdf6f560f64..54450f3a31e6d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java @@ -36,7 +36,7 @@ public class SnapshotDeleteCommand extends AbstractSnapshotCommand { /** {@inheritDoc} */ @Override public String description() { - return "Deletes snapshot and all its increments from all the online server nodes."; + return "Deletes snapshot and all its increments from all the online server nodes"; } /** {@inheritDoc} */ @@ -93,8 +93,8 @@ private static String nodeIdsStrLst(Collection uuids) { return "This will delete snapshot '" + arg.snapshotName() + "' and all its increments from all online server nodes." + U.nl() + U.nl() + - "WARNING: the snapshot integrity, initial topology and correctness aren't checked." + - " Snapshot parts on offline server nodes aren't deleted." + + "WARNING: the snapshot integrity, topology and correctness aren't checked." + + " Snapshot daya on offline server nodes aren't deleted." + U.nl() + U.nl() + "The operation is irreversible."; } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommandArg.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommandArg.java index a0149e44a2d50..2b7063de333bb 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommandArg.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommandArg.java @@ -30,14 +30,14 @@ public class SnapshotDeleteCommandArg extends IgniteDataTransferObject { /** */ @Order(0) @Positional - @Argument(description = "Snapshot name.") + @Argument(description = "Snapshot name") String snapshotName; /** */ @Order(1) @Argument(example = "path", optional = true, description = "Path to the directory where the snapshot is located. " + - "If not specified, the default configured snapshot directory will be used.") + "If not specified, the default configured snapshot directory will be used") String src; /** */ diff --git a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output index dd6ff4e43caf5..c122892ee1f66 100644 --- a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output +++ b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output @@ -269,6 +269,13 @@ This utility can do the following commands: Get the status of the current snapshot operation: control.(sh|bat) --snapshot status + Deletes snapshot and all its increments from all the online server nodes: + control.(sh|bat) --snapshot delete snapshot_name [--src path] + + Parameters: + snapshot_name - Snapshot name. + --src path - Path to the directory where the snapshot is located. If not specified, the default configured snapshot directory will be used. + Change cluster tag to new value: control.(sh|bat) --change-tag newTagValue [--yes] diff --git a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output index 76f0f8c7e0e37..30a77600c6efa 100644 --- a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output +++ b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output @@ -269,6 +269,13 @@ This utility can do the following commands: Get the status of the current snapshot operation: control.(sh|bat) --snapshot status + Deletes snapshot and all its increments from all the online server nodes: + control.(sh|bat) --snapshot delete snapshot_name [--src path] + + Parameters: + snapshot_name - Snapshot name. + --src path - Path to the directory where the snapshot is located. If not specified, the default configured snapshot directory will be used. + Change cluster tag to new value: control.(sh|bat) --change-tag newTagValue [--yes] From e6aa3301f20713a39df407ada7561f1a0c3defa4 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Fri, 11 Sep 2026 16:48:19 +0300 Subject: [PATCH 22/97] + RU test. + many small fixes. --- .../GridCommandHandlerDeleteSnapshotTest.java | 8 +- .../snapshot/SnapshotDeleteCommand.java | 2 +- .../snapshot/IgniteSnapshotManager.java | 4 +- .../snapshot/SnapshotDeleteProcess.java | 22 +-- .../snapshot/SnapshotDeleteProcessResult.java | 6 +- .../snapshot/AbstractSnapshotSelfTest.java | 2 +- ...ClusterSnapshotDeleteConcurrencyTest.java} | 16 +- ...usterSnapshotDeleteRollingUpgardeTest.java | 146 ++++++++++++++++++ .../IgniteClusterSnapshotDeleteTest.java | 85 ---------- .../IgniteClusterSnapshotSelfTest.java | 3 +- .../TestIgniteReleaseFeatures_2_19_1.java | 3 + .../testsuites/IgniteSnapshotTestSuite8.java | 8 +- 12 files changed, 184 insertions(+), 121 deletions(-) rename modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/{IgniteClusterSnapshotDeleteParametrizedTest.java => IgniteClusterSnapshotDeleteConcurrencyTest.java} (94%) create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgardeTest.java delete mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java index fc53a9db596de..65eceec51a60b 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java @@ -157,7 +157,7 @@ public void testSnapshotDelete() throws Exception { } // Optionally restarts with the same servers number, but changed baseline. The snapshot is kept on the same - // previous nodes independenlty of the baseline. + // previous nodes independently of the baseline. if (changeBaseline) { ig.destroyCache(DEFAULT_CACHE_NAME); awaitPartitionMapExchange(); @@ -209,7 +209,7 @@ else if (extraNodeIsServer == 0) out = testOut.toString(); if (separatedWorkDir) { - // When the nodes use own separated work dirictory, we expect a strict result. + // When the nodes use own separated work directory, we expect a strict result. assertTrue(out.contains("Snapshot removed on the following nodes [cnt=%d]:".formatted(initNodes))); if (extraNodeIsServer == 1) @@ -218,8 +218,8 @@ else if (extraNodeIsServer == 0) assertFalse(out.contains("the following nodes didn't find any snapshot data, nothing to delete")); } else { - // When nodes use a shared work dirictory, there is a race for the delete operation. One node can get faster - // than anothers and remove snasphot completely quickly. The others might not find snapshot files. We can be + // When nodes use a shared work directory, there is a race for the delete operation. One node can get faster + // than others and remove snapshot completely quickly. The others might not find snapshot files. We can be // only sure that at least one node removes snapshot. assertTrue(out.contains("Snapshot removed on the following nodes [cnt=")); } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java index 54450f3a31e6d..e7262ce26942c 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java @@ -94,7 +94,7 @@ private static String nodeIdsStrLst(Collection uuids) { "' and all its increments from all online server nodes." + U.nl() + U.nl() + "WARNING: the snapshot integrity, topology and correctness aren't checked." + - " Snapshot daya on offline server nodes aren't deleted." + + " Snapshot data on offline server nodes aren't deleted." + U.nl() + U.nl() + "The operation is irreversible."; } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java index aeed87534d16d..efdf99b6e133b 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java @@ -728,7 +728,7 @@ public void deleteLocalSnapshot(File snpDir) { /** * @param sft Snapshot file tree - * @param existsFlag Flag to set {@code} if any snapshot file or directory is found (exists). If {@code null}, ignored. + * @param existsFlag Flag to set {@code true} if any snapshot file or directory is found (exists). If {@code null}, ignored. * @return {@code True}, if data is found and completely deleted or if no data found; * {@code False}, if data is found but was deleted not completely. */ @@ -1503,7 +1503,7 @@ public boolean isSnapshotDeleting(String snpName) { * for the snapshot. * * @param name Snapshot name. - * @param snpPath Snapshot directory path. If {@code null}, the default configured snapshot directory is be used. + * @param snpPath Snapshot directory path. If {@code null}, the default configured snapshot directory will be used. * @return Future which will be completed when the snapshot is deleted on all the baseline nodes. */ public IgniteFuture deleteSnapshot(String name, @Nullable String snpPath) { diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index 1eabae7146fa5..6bcdbe413d9c3 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -116,7 +116,7 @@ public IgniteFuture start(String snpName, @Nullable private IgniteInternalFuture deletePhase(UUID ignored, SnapshotDeleteRequest req) { if (kctx.isStopping()) { return new GridFinishedFuture<>(new NodeStoppingException(OP_REJECT_MSG + - " Node is stopping, req=" + req)); + " Node is stopping [req=" + req + ']')); } if (kctx.cluster().get().localNode().isClient()) @@ -130,28 +130,28 @@ private IgniteInternalFuture deletePhase(UUID ignored, S if (curCreateRq != null && curCreateRq.snpName.equals(req.snpName)) { return new GridFinishedFuture<>(new IgniteIllegalStateException(OP_REJECT_MSG + - " Snapshot with this name is being created, req=" + req)); + " Snapshot with this name is being created [req=" + req + ']')); } if (snpMgr.isRestoring(req.snpName)) { return new GridFinishedFuture<>(new IgniteIllegalStateException(OP_REJECT_MSG + - " Snapshot with this name is being restored, req=" + req)); + " Snapshot with this name is being restored [req=" + req + ']')); } if (snpMgr.isSnapshotChecking(req.snpName)) { return new GridFinishedFuture<>(new IgniteIllegalStateException(OP_REJECT_MSG + - " Snapshot with this name is being checked, req=" + req)); + " Snapshot with this name is being checked [req=" + req + ']')); } if (!kctx.rollingUpgrade().features().isActive(SNAPSHOT_DELETE_FEATURE)) { return new GridFinishedFuture<>(new IgniteIllegalStateException(OP_REJECT_MSG + - " Snapshot deletion feature isn't enabled, req=" + req)); + " The snapshot deletion feature isn't activated yet [req=" + req + ']')); } try { if (requests.putIfAbsent(req.snpName, req) != null) { return new GridFinishedFuture<>(new IgniteIllegalStateException("Deletion of the snapshot has already " + - "started, req=" + req)); + "started [req=" + req + ']')); } GridFutureAdapter reqLocFut = new GridFutureAdapter<>(); @@ -166,9 +166,9 @@ private IgniteInternalFuture deletePhase(UUID ignored, S if (foundFlag.get()) { if (deleted && log.isInfoEnabled()) - log.info("Snapshot successfully deleted, req=" + req); + log.info("Snapshot successfully deleted [req=" + req + ']'); else if (!deleted) - log.warning("Snapshot deleted not completely, req=" + req); + log.warning("Snapshot deleted not completely [req=" + req + ']'); res = deleted ? SnapshotDeleteResponse.SnapshotDeleteStatus.DELETED @@ -176,7 +176,7 @@ else if (!deleted) } else { if (log.isInfoEnabled()) - log.info("Snapshot not found to delete, req=" + req); + log.info("Snapshot not found to delete [req=" + req + ']'); res = SnapshotDeleteResponse.SnapshotDeleteStatus.NOT_FOUND; } @@ -189,14 +189,14 @@ else if (!deleted) }); if (log.isInfoEnabled()) - log.info("Deletion of snapshot initialized, req=" + req); + log.info("Deletion of snapshot initialized [req=" + req + ']'); return reqLocFut; } catch (Throwable t) { requests.remove(req.snpName); - log.warning("An error occured during snapshot deletion, req=" + req, t); + log.warning("An error occurred during snapshot deletion [req=" + req + ']', t); return new GridFinishedFuture<>(t); } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResult.java index 74625d3a166c6..95a904dc6d55b 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResult.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResult.java @@ -29,15 +29,15 @@ public final class SnapshotDeleteProcessResult extends IgniteDataTransferObject /** Serial version uid. */ private static final long serialVersionUID = 0L; - /** Nodes which found shapshot data and completely removed it. */ + /** Nodes which found snapshot data and completely removed it. */ @Order(0) @Nullable Collection completedNodes; - /** Nodes which found shapshot data but didn't remove it completely. */ + /** Nodes which found snapshot data but didn't remove it completely. */ @Order(1) @Nullable Collection uncompletedNodes; - /** Server nodes which didn't found any shapshot data. */ + /** Server nodes which didn't find any snapshot data. */ @Order(2) @Nullable Collection emptyNodes; diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java index 63885c43a02c3..b7a511b7ca285 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java @@ -190,7 +190,7 @@ protected Function valueBuilder() { public boolean onlyPrimary; /** Parameters. */ - @Parameterized.Parameters(name = "encryption={0}, onlyPrimay={1}") + @Parameterized.Parameters(name = "encryption={0}, onlyPrimary={1}") public static Collection params() { List res = new ArrayList<>(); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteParametrizedTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteConcurrencyTest.java similarity index 94% rename from modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteParametrizedTest.java rename to modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteConcurrencyTest.java index 7c212b834385a..8cff3a8332b87 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteParametrizedTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteConcurrencyTest.java @@ -47,13 +47,13 @@ /** */ @RunWith(Parameterized.class) -public class IgniteClusterSnapshotDeleteParametrizedTest extends AbstractSnapshotSelfTest { +public class IgniteClusterSnapshotDeleteConcurrencyTest extends AbstractSnapshotSelfTest { /** */ @Parameter(2) public boolean incremental = true; /** Parameters. */ - @Parameterized.Parameters(name = "encryption={0}, onlyPrimay={1}, incremental={2}") + @Parameterized.Parameters(name = "encryption={0}, onlyPrimary={1}, incremental={2}") public static Collection runParams() { Collection res = new ArrayList<>(); @@ -91,7 +91,7 @@ public static Collection runParams() { /** Tests that a snapshot deletion is declined when a snapshot check operation is in progress. */ @Test public void testSnapshotDeleteWhenCheckInProgress() throws Exception { - // Incremental snapshots don't support encription. + // Incremental snapshots don't support encryption. assumeTrue(!incremental || !encryption); doTestConcurrentSnapshotDelete( @@ -106,7 +106,7 @@ public void testSnapshotDeleteWhenCheckInProgress() throws Exception { /** Tests that a snapshot deletion is declined when a snapshot create operation is in progress. */ @Test public void testSnapshotDeleteWhenCreateInProgress() throws Exception { - // Incremental snapshots don't support encription and only-primary mode. + // Incremental snapshots don't support encryption and only-primary mode. assumeTrue(!incremental || !(encryption || onlyPrimary)); doTestConcurrentSnapshotDelete( @@ -126,7 +126,7 @@ public void testSnapshotDeleteWhenCreateInProgress() throws Exception { /** Tests that a snapshot deletion is declined when a snapshot restore begins. */ @Test public void testSnapshotDeleteWhenRestoreBegins() throws Exception { - // Incremental snapshots don't support encription. + // Incremental snapshots don't support encryption. assumeTrue(!incremental || !encryption); doTestConcurrentSnapshotDelete( @@ -150,7 +150,7 @@ public void testSnapshotDeleteWhenRestoreBegins() throws Exception { /** Tests that a snapshot deletion is declined when a snapshot restore is in progress. */ @Test public void testSnapshotDeleteWhenRestoreInProgress() throws Exception { - // Incremental snapshots don't support encription. + // Incremental snapshots don't support encryption. assumeTrue(!incremental || !encryption); var restoreMsgs = F.asList( @@ -184,9 +184,9 @@ public void testSnapshotDeleteWhenRestoreInProgress() throws Exception { /** * @param firstOp First cluster-wide snapshot operation. - * @param msgsToWatch {@link SingleNodeMessage#type()} relating to {@code firstOp} bo block on one node. + * @param msgsToWatch {@link SingleNodeMessage#type()} relating to {@code firstOp} to block on one node. * @param precreateSnp If {@code true}, creates snapshot after the cluster start. - * @param prepareIteration If not {@code null}, is invoked in the beggining of test iteration at each {@code msgsToWatch}. + * @param prepareIteration If not {@code null}, is invoked in the beginning of test iteration at each {@code msgsToWatch}. * @param concurrentMsgErr Test of failed concurrent to {@code firstOp} delete snapshot operation to watch. */ protected void doTestConcurrentSnapshotDelete( diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgardeTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgardeTest.java new file mode 100644 index 0000000000000..f69f63c926e3a --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgardeTest.java @@ -0,0 +1,146 @@ +/* + * 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.ignite.internal.processors.cache.persistence.snapshot; + +import org.apache.ignite.IgniteIllegalStateException; +import org.apache.ignite.cache.CacheAtomicityMode; +import org.apache.ignite.cache.CacheMode; +import org.apache.ignite.cache.CacheWriteSynchronizationMode; +import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction; +import org.apache.ignite.configuration.CacheConfiguration; +import org.apache.ignite.configuration.DataRegionConfiguration; +import org.apache.ignite.configuration.DataStorageConfiguration; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.processors.rollingupgrade.AbstractRollingUpgradeTest; +import org.apache.ignite.internal.util.typedef.F; +import org.junit.Test; + +import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause; + +/** */ +public class IgniteClusterSnapshotDeleteRollingUpgardeTest extends AbstractRollingUpgradeTest { + /** */ + private static final int ALL_GRIDS = 4; + + /** */ + private static final int CLIENTS = 1; + + /** */ + private static final String SNP_NAME = "testSnapshot"; + + /** {@inheritDoc} */ + @Override protected void afterTest() throws Exception { + super.afterTest(); + + cleanPersistenceDir(); + } + + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName, String ver) throws Exception { + var cfg = super.getConfiguration(igniteInstanceName, ver); + + cfg.setDataStorageConfiguration( + new DataStorageConfiguration() + .setDefaultDataRegionConfiguration( + new DataRegionConfiguration() + .setPersistenceEnabled(true) + .setMaxSize(DataStorageConfiguration.DFLT_DATA_REGION_INITIAL_SIZE) + ) + ); + + return cfg; + } + + /** */ + @Test + public void testNodeNotSupportingSnapshotDeleteFeature() throws Exception { + for (int i = 0; i < ALL_GRIDS; i++) + startGrid(i, "2.19.0", i >= ALL_GRIDS - CLIENTS); + + grid(0).cluster().active(true); + + createCacheAndSnasphot(1); + + ensureSnapshotDeletionFailed(); + + ru(grid(0)).enableVersionUpgrade(); + + for (int i = 0; i < ALL_GRIDS; i++) { + assertTrue(ru(grid(i)).isVersionUpgradeEnabled()); + + upgradeNodeVersion(i, "2.19.1"); + + ensureSnapshotDeletionFailed(); + } + + ru(grid(1)).finalizeClusterVersion(); + + for (int i = 0; i < ALL_GRIDS; i++) { + assertFalse(ru(grid(i)).isVersionUpgradeEnabled()); + + assertFalse(F.isEmpty(snp(i).deleteSnapshot(SNP_NAME, null).get().completedNodes)); + + if (i < ALL_GRIDS - 1) + createSnapshot(i); + } + } + + /** */ + private void createCacheAndSnasphot(int gridIdx) { + int partsCnt = 32; + int keysCnt = partsCnt * 10; + + grid(gridIdx).createCache(new CacheConfiguration<>(DEFAULT_CACHE_NAME) + .setCacheMode(CacheMode.REPLICATED) + .setBackups(1) + .setAffinity(new RendezvousAffinityFunction().setPartitions(32)) + .setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC) + .setAtomicityMode(CacheAtomicityMode.ATOMIC)); + + try (var ds = grid(gridIdx).dataStreamer(DEFAULT_CACHE_NAME)) { + for (int i = 0; i < keysCnt; i++) + ds.addData(i, i); + } + + createSnapshot(gridIdx); + } + + /** */ + private void createSnapshot(int gridIdx) { + snp(gridIdx).createSnapshot(SNP_NAME).get(getTestTimeout()); + } + + /** */ + private void ensureSnapshotDeletionFailed() { + for (int i = 0; i < ALL_GRIDS; i++) { + int i0 = i; + + assertThrowsAnyCause( + null, + () -> snp(i0).deleteSnapshot(SNP_NAME, null).get(), + IgniteIllegalStateException.class, + "The snapshot deletion feature isn't activated yet" + ); + } + } + + /** */ + private IgniteSnapshotManager snp(int gridIdx) { + return grid(gridIdx).context().cache().context().snapshotMgr(); + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java deleted file mode 100644 index afa54fc688b8e..0000000000000 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * 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.ignite.internal.processors.cache.persistence.snapshot; - -import org.apache.ignite.IgniteIllegalStateException; -import org.apache.ignite.cluster.ClusterNode; -import org.apache.ignite.internal.IgniteEx; -import org.apache.ignite.internal.IgniteVersionUtils; -import org.apache.ignite.internal.processors.nodevalidation.DiscoveryNodeValidationProcessor; -import org.apache.ignite.internal.processors.rollingupgrade.RollingUpgradeProcessor; -import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteCoreFeatureSet; -import org.apache.ignite.internal.processors.rollingupgrade.feature.IgniteFeatureSet; -import org.apache.ignite.internal.util.typedef.G; -import org.apache.ignite.plugin.AbstractTestPluginProvider; -import org.apache.ignite.plugin.PluginContext; -import org.apache.ignite.spi.IgniteNodeValidationResult; -import org.jetbrains.annotations.Nullable; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause; - -/** */ -@RunWith(JUnit4.class) -public class IgniteClusterSnapshotDeleteTest extends AbstractSnapshotSelfTest { - /** {@inheritDoc} */ - @Override public void afterTestSnapshot() throws Exception { - super.afterTestSnapshot(); - - G.allGrids(); - } - - /** */ - @Test - public void testNodeNotSupportingSnapshotDeleteFeature() throws Exception { - // Creates empty feature set unsupporting the snapshot deletion if required. - pluginProvider = new AbstractTestPluginProvider() { - @Override public String name() { - return "Test Ignite features provider"; - } - - @Override public @Nullable T createComponent(PluginContext ctx, Class cls) { - if (!cls.equals(DiscoveryNodeValidationProcessor.class)) - return null; - - boolean doNotSupport = ctx.igniteConfiguration().getIgniteInstanceName().equals(getTestIgniteInstanceName(1)); - - return (T)new RollingUpgradeProcessor( - ((IgniteEx)ctx.grid()).context(), - doNotSupport ? new IgniteCoreFeatureSet(IgniteVersionUtils.VER, new IgniteFeatureSet()) : IgniteCoreFeatureSet.local() - ) { - @Override public @Nullable IgniteNodeValidationResult validateNode(ClusterNode joiningNode) { - // Simulates started rolling updrade allowing node with other features join cluster. - return null; - } - }; - } - }; - - startGridsMultiThreaded(3); - - assertThrowsAnyCause( - null, - () -> snp(grid(0)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()), - IgniteIllegalStateException.class, - "Node " + grid(1).localNode().id() + " doesn't support snapshot deletion" - ); - } -} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotSelfTest.java index bd5330b073f91..9745679852f06 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotSelfTest.java @@ -126,7 +126,6 @@ public class IgniteClusterSnapshotSelfTest extends AbstractSnapshotSelfTest { /** Any node failed. */ private boolean failed; - /** {@inheritDoc} */ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { return super.getConfiguration(igniteInstanceName).setFailureHandler((ignite, ctx) -> failed = true); @@ -617,7 +616,7 @@ public void testSnapshotExistsException() throws Exception { } /** - * Tests that snapshot create detects concurrent deletion, or detects still existing snapshot or sucessfuly + * Tests that snapshot create detects concurrent deletion, or detects still existing snapshot or successfully * proceeds if snapshot already deleted. */ @Test diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_1.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_1.java index 38056daa0b428..037753dcaa2c1 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_1.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_1.java @@ -21,4 +21,7 @@ public class TestIgniteReleaseFeatures_2_19_1 { /** */ public static final IgniteFeature ROLLING_UPGRADE_FEATURE = TestIgniteReleaseFeatures_2_19_0.ROLLING_UPGRADE_FEATURE; + + /** */ + public static final IgniteFeature SNAPSHOT_DELETE_FEATURE = new IgniteCoreFeature(1); } diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java index aa1af8cf59eb2..092bec88e3e80 100644 --- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java +++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java @@ -21,8 +21,8 @@ import java.util.Collection; import java.util.List; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotCheckTest; -import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotDeleteParametrizedTest; -import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotDeleteTest; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotDeleteConcurrencyTest; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotDeleteRollingUpgardeTest; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotHandlerTest; import org.apache.ignite.testframework.GridTestUtils; import org.apache.ignite.testframework.junits.DynamicSuite; @@ -50,8 +50,8 @@ public static List> suite(Collection ignoredTests) { GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotCheckTest.class, ignoredTests); GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotHandlerTest.class, ignoredTests); - GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotDeleteTest.class, ignoredTests); - GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotDeleteParametrizedTest.class, ignoredTests); + GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotDeleteRollingUpgardeTest.class, ignoredTests); + GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotDeleteConcurrencyTest.class, ignoredTests); return suite; } From 4233322d23dbb6b88d13d3cda0498ca76aa963db Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Fri, 11 Sep 2026 18:51:04 +0300 Subject: [PATCH 23/97] review fixes --- .../util/GridCommandHandlerDeleteSnapshotTest.java | 3 +++ .../persistence/snapshot/IgniteSnapshotManager.java | 3 ++- .../snapshot/IgniteClusterSnapshotCheckTest.java | 4 ---- ...iteClusterSnapshotDeleteRollingUpgradeTest.java} | 6 +++--- ...st.java => IgniteClusterSnapshotDeleteTest.java} | 13 ++++++++++++- .../ignite/testsuites/IgniteSnapshotTestSuite8.java | 8 ++++---- 6 files changed, 24 insertions(+), 13 deletions(-) rename modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/{IgniteClusterSnapshotDeleteRollingUpgardeTest.java => IgniteClusterSnapshotDeleteRollingUpgradeTest.java} (96%) rename modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/{IgniteClusterSnapshotDeleteConcurrencyTest.java => IgniteClusterSnapshotDeleteTest.java} (94%) diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java index 65eceec51a60b..f5cb8d70aa621 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java @@ -186,6 +186,9 @@ else if (extraNodeIsServer == 0) if (customPath) { assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "delete", "--src", cstSnpsRoot.getAbsolutePath(), "wrongSnapshot")); + + assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "delete", "--src", + cstSnpsRoot.getAbsolutePath() + "_wrongPath", "testSnapshot")); } else assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "delete", "wrongSnapshot")); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java index efdf99b6e133b..e907c6464a2b1 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java @@ -746,7 +746,7 @@ public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean } /** */ - void deleteWithExistence(@Nullable File f, AtomicBoolean onlyFailRes, @Nullable AtomicBoolean existsFlag) { + private void deleteWithExistence(@Nullable File f, AtomicBoolean onlyFailRes, @Nullable AtomicBoolean existsFlag) { if (f == null) return; @@ -756,6 +756,7 @@ void deleteWithExistence(@Nullable File f, AtomicBoolean onlyFailRes, @Nullable existsFlag.set(true); try { + // Additionally checks the existence for the case of concurrent deletion. if (existed && ((!f.isDirectory() && !U.delete(f)) || (f.isDirectory() && !deleteDirectory(f))) && f.exists()) onlyFailRes.set(false); } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java index fc46bf49d9aed..b278b721a3fd2 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java @@ -99,7 +99,6 @@ import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.lang.IgniteFuture; import org.apache.ignite.metric.MetricRegistry; -import org.apache.ignite.plugin.AbstractTestPluginProvider; import org.apache.ignite.spi.metric.BooleanMetric; import org.apache.ignite.spi.metric.IntMetric; import org.apache.ignite.spi.metric.LongMetric; @@ -150,9 +149,6 @@ public class IgniteClusterSnapshotCheckTest extends AbstractSnapshotSelfTest { @Parameterized.Parameter(2) public int snpThrdPoolSz; - /** */ - private @Nullable AbstractTestPluginProvider pluginProvider; - /** Parameters. */ @Parameterized.Parameters(name = "encryption={0}, onlyPrimary={1}, snpThrdPoolSz={2}") public static Collection params() { diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgardeTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgradeTest.java similarity index 96% rename from modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgardeTest.java rename to modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgradeTest.java index f69f63c926e3a..083ea832f6b16 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgardeTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgradeTest.java @@ -33,7 +33,7 @@ import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause; /** */ -public class IgniteClusterSnapshotDeleteRollingUpgardeTest extends AbstractRollingUpgradeTest { +public class IgniteClusterSnapshotDeleteRollingUpgradeTest extends AbstractRollingUpgradeTest { /** */ private static final int ALL_GRIDS = 4; @@ -74,7 +74,7 @@ public void testNodeNotSupportingSnapshotDeleteFeature() throws Exception { grid(0).cluster().active(true); - createCacheAndSnasphot(1); + createCacheAndSnapshot(1); ensureSnapshotDeletionFailed(); @@ -101,7 +101,7 @@ public void testNodeNotSupportingSnapshotDeleteFeature() throws Exception { } /** */ - private void createCacheAndSnasphot(int gridIdx) { + private void createCacheAndSnapshot(int gridIdx) { int partsCnt = 32; int keysCnt = partsCnt * 10; diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteConcurrencyTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java similarity index 94% rename from modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteConcurrencyTest.java rename to modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java index 8cff3a8332b87..789bde041adb9 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteConcurrencyTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java @@ -47,7 +47,7 @@ /** */ @RunWith(Parameterized.class) -public class IgniteClusterSnapshotDeleteConcurrencyTest extends AbstractSnapshotSelfTest { +public class IgniteClusterSnapshotDeleteTest extends AbstractSnapshotSelfTest { /** */ @Parameter(2) public boolean incremental = true; @@ -88,6 +88,17 @@ public static Collection runParams() { cleanPersistenceDir(); } + /** Tests that a concurrent deletion of the same snapshot is declined. */ + @Test + public void testConcurrentDeleteOfTheSameSnapshot() throws Exception { + doTestConcurrentSnapshotDeleteOperation( + () -> startGridsWithSnapshot(3, CACHE_KEYS_RANGE, false), + () -> snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()), + e -> e.getMessage().contains("Deletion of the snapshot has already started"), + false + ); + } + /** Tests that a snapshot deletion is declined when a snapshot check operation is in progress. */ @Test public void testSnapshotDeleteWhenCheckInProgress() throws Exception { diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java index 092bec88e3e80..56627c71e5b8f 100644 --- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java +++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java @@ -21,8 +21,8 @@ import java.util.Collection; import java.util.List; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotCheckTest; -import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotDeleteConcurrencyTest; -import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotDeleteRollingUpgardeTest; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotDeleteTest; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotDeleteRollingUpgradeTest; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotHandlerTest; import org.apache.ignite.testframework.GridTestUtils; import org.apache.ignite.testframework.junits.DynamicSuite; @@ -50,8 +50,8 @@ public static List> suite(Collection ignoredTests) { GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotCheckTest.class, ignoredTests); GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotHandlerTest.class, ignoredTests); - GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotDeleteRollingUpgardeTest.class, ignoredTests); - GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotDeleteConcurrencyTest.class, ignoredTests); + GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotDeleteRollingUpgradeTest.class, ignoredTests); + GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotDeleteTest.class, ignoredTests); return suite; } From b11f3897aa66b305d7e0284f2a70722f50082977 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Mon, 14 Sep 2026 19:41:49 +0300 Subject: [PATCH 24/97] test fix --- .../ignite/internal/commandline/CommandHandlerParsingTest.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/modules/control-utility/src/test/java/org/apache/ignite/internal/commandline/CommandHandlerParsingTest.java b/modules/control-utility/src/test/java/org/apache/ignite/internal/commandline/CommandHandlerParsingTest.java index 0fc350ab7c8fc..99882f4ccc6ff 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/internal/commandline/CommandHandlerParsingTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/internal/commandline/CommandHandlerParsingTest.java @@ -81,6 +81,7 @@ import org.apache.ignite.internal.management.performancestatistics.PerformanceStatisticsCommand; import org.apache.ignite.internal.management.property.PropertyCommand; import org.apache.ignite.internal.management.snapshot.SnapshotCommand; +import org.apache.ignite.internal.management.snapshot.SnapshotDeleteCommand; import org.apache.ignite.internal.management.snapshot.SnapshotRestoreCommand; import org.apache.ignite.internal.management.tx.TxCommand; import org.apache.ignite.internal.management.tx.TxCommandArg; @@ -529,6 +530,8 @@ else if (cmd.getClass() == EncryptionChangeCacheKeyCommand.class) cmdText = F.concat(cmdText, "cacheGroup1"); else if (cmd.getClass() == SnapshotRestoreCommand.class) cmdText = F.concat(cmdText, "snp1"); + else if (cmd.getClass() == SnapshotDeleteCommand.class) + cmdText = F.concat(cmdText, "snp1"); else if (cmd.getClass() == MetaUpdateCommand.class) return; else if (cmd.getClass() == MetaRemoveCommand.class) From f00d2669cabf2b651fdf685df9b0a83d3dfffecf Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Mon, 14 Sep 2026 20:38:03 +0300 Subject: [PATCH 25/97] + tests --- .../IgniteClusterSnapshotDeleteTest.java | 80 +++++++++++++++++++ .../IgniteClusterSnapshotRestoreSelfTest.java | 3 - .../testsuites/IgniteSnapshotTestSuite8.java | 2 +- 3 files changed, 81 insertions(+), 4 deletions(-) diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java index 789bde041adb9..75b03724d9a39 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java @@ -17,17 +17,25 @@ package org.apache.ignite.internal.processors.cache.persistence.snapshot; +import java.io.File; import java.util.ArrayList; import java.util.Collection; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; import org.apache.ignite.IgniteIllegalStateException; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.TestRecordingCommunicationSpi; +import org.apache.ignite.internal.processors.cache.persistence.filename.SnapshotFileTree; import org.apache.ignite.internal.util.distributed.DistributedProcess; import org.apache.ignite.internal.util.distributed.SingleNodeMessage; import org.apache.ignite.internal.util.future.IgniteFutureImpl; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.G; +import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.lang.IgniteFuture; +import org.apache.ignite.plugin.AbstractTestPluginProvider; +import org.apache.ignite.plugin.PluginContext; import org.jetbrains.annotations.Nullable; import org.junit.Test; import org.junit.runner.RunWith; @@ -48,10 +56,23 @@ /** */ @RunWith(Parameterized.class) public class IgniteClusterSnapshotDeleteTest extends AbstractSnapshotSelfTest { + /** */ + private boolean separatedWorkDir; + /** */ @Parameter(2) public boolean incremental = true; + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { + var cfg = super.getConfiguration(igniteInstanceName); + + if (separatedWorkDir) + cfg.setWorkDirectory(new File(U.defaultWorkDirectory(), igniteInstanceName).getAbsolutePath()); + + return cfg; + } + /** Parameters. */ @Parameterized.Parameters(name = "encryption={0}, onlyPrimary={1}, incremental={2}") public static Collection runParams() { @@ -88,6 +109,65 @@ public static Collection runParams() { cleanPersistenceDir(); } + /** Tests snapshot deletion when one node finds snapshot but failes to delete its data. */ + @Test + public void testUncompletedNodes() throws Exception { + separatedWorkDir = true; + + // Simulates a deletion error on some node. + pluginProvider = new AbstractTestPluginProvider() { + @Override public String name() { + return "TestSnpMgrProvider"; + } + + @Override public T createComponent(PluginContext ctx, Class cls) { + if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { + return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { + @Override public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean existsFlag) { + if (ctx.localNode().id().equals(grid(1).localNode().id())) { + existsFlag.set(true); + + return false; + } + + return super.deleteLocalSnapshot(sft, existsFlag); + } + }; + } + + return super.createComponent(ctx, cls); + } + }; + + startGridsWithCache(3, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); + + snp(grid(0)).createSnapshot(SNAPSHOT_NAME).get(getTestTimeout()); + + var delSnpRes = snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); + + assertTrue(F.isEmpty(delSnpRes.emptyNodes)); + assertFalse(F.isEmpty(delSnpRes.uncompletedNodes)); + assertTrue(delSnpRes.uncompletedNodes.contains(grid(1).localNode().id())); + } + + /** Tests snapshot deletion when one node has no snapshot data. */ + @Test + public void testEmptyNodes() throws Exception { + separatedWorkDir = true; + + startGridsWithCache(2, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); + + snp(grid(0)).createSnapshot(SNAPSHOT_NAME).get(getTestTimeout()); + + startGrid(G.allGrids().size()); + + var delSnpRes = snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); + + assertFalse(F.isEmpty(delSnpRes.emptyNodes)); + assertTrue(delSnpRes.emptyNodes.contains(grid(G.allGrids().size() - 1).localNode().id())); + assertTrue(F.isEmpty(delSnpRes.uncompletedNodes)); + } + /** Tests that a concurrent deletion of the same snapshot is declined. */ @Test public void testConcurrentDeleteOfTheSameSnapshot() throws Exception { diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotRestoreSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotRestoreSelfTest.java index 3d82b8f0037f0..99c8a99b05d7d 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotRestoreSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotRestoreSelfTest.java @@ -105,9 +105,6 @@ public class IgniteClusterSnapshotRestoreSelfTest extends IgniteClusterSnapshotR if (resetConsistentId) cfg.setConsistentId(null); - if (pluginProvider != null) - cfg.setPluginProviders(pluginProvider); - return cfg; } diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java index 56627c71e5b8f..857e317599640 100644 --- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java +++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java @@ -21,8 +21,8 @@ import java.util.Collection; import java.util.List; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotCheckTest; -import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotDeleteTest; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotDeleteRollingUpgradeTest; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotDeleteTest; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotHandlerTest; import org.apache.ignite.testframework.GridTestUtils; import org.apache.ignite.testframework.junits.DynamicSuite; From 57909f4a8312a9a2a7add800def03f469fbe59a7 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Mon, 14 Sep 2026 22:56:40 +0300 Subject: [PATCH 26/97] + tests --- .../GridCommandHandlerDeleteSnapshotTest.java | 3 +- .../IgniteClusterSnapshotDeleteTest.java | 98 +++++++++++++++++-- 2 files changed, 93 insertions(+), 8 deletions(-) diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java index f5cb8d70aa621..ce3ab0089fb0d 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java @@ -88,7 +88,7 @@ public static Collection parameters() { @Override protected void beforeTest() throws Exception { super.beforeTest(); - // Handy if other test runs interrupted. + /** Handy if test running is interrupted and {@link #afterTest()} isn't invoked. */ cleanPersistenceDir(); } @@ -96,6 +96,7 @@ public static Collection parameters() { @Override protected void cleanPersistenceDir() throws Exception { super.cleanPersistenceDir(); + // Also cleans separated snapshot working directories. try (DirectoryStream files = newDirectoryStream(Paths.get(U.defaultWorkDirectory()))) { for (Path path : files) U.delete(path); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java index 75b03724d9a39..52780d9c5b378 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java @@ -18,6 +18,9 @@ package org.apache.ignite.internal.processors.cache.persistence.snapshot; import java.io.File; +import java.nio.file.DirectoryStream; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.concurrent.atomic.AtomicBoolean; @@ -26,6 +29,8 @@ import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.TestRecordingCommunicationSpi; +import org.apache.ignite.internal.processors.cache.persistence.file.FileIO; +import org.apache.ignite.internal.processors.cache.persistence.file.RandomAccessFileIOFactory; import org.apache.ignite.internal.processors.cache.persistence.filename.SnapshotFileTree; import org.apache.ignite.internal.util.distributed.DistributedProcess; import org.apache.ignite.internal.util.distributed.SingleNodeMessage; @@ -42,15 +47,18 @@ import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameter; +import static java.nio.file.Files.newDirectoryStream; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.CHECK_SNAPSHOT_METAS; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.CHECK_SNAPSHOT_PARTS; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.END_SNAPSHOT; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_PRELOAD; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_PREPARE; +import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_ROLLBACK; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_START; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_INCREMENTAL_SNAPSHOT_START; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.START_SNAPSHOT; import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause; +import static org.junit.Assume.assumeFalse; import static org.junit.Assume.assumeTrue; /** */ @@ -105,10 +113,21 @@ public static Collection runParams() { @Override public void beforeTestSnapshot() throws Exception { super.beforeTestSnapshot(); - // Handy + /** Handy if test running is interrupted and {@link #afterTestSnapshot()} isn't invoked. */ cleanPersistenceDir(); } + /** {@inheritDoc} */ + @Override protected void cleanPersistenceDir() throws Exception { + super.cleanPersistenceDir(); + + // Also cleans separated snapshot working directories. + try (DirectoryStream files = newDirectoryStream(Paths.get(U.defaultWorkDirectory()))) { + for (Path path : files) + U.delete(path); + } + } + /** Tests snapshot deletion when one node finds snapshot but failes to delete its data. */ @Test public void testUncompletedNodes() throws Exception { @@ -190,7 +209,8 @@ public void testSnapshotDeleteWhenCheckInProgress() throws Exception { F.asList(CHECK_SNAPSHOT_METAS, CHECK_SNAPSHOT_PARTS), true, null, - "Snapshot with this name is being checked" + "Snapshot with this name is being checked", + false ); } @@ -210,7 +230,8 @@ public void testSnapshotDeleteWhenCreateInProgress() throws Exception { if (incremental) snp(grid(0)).createSnapshot(SNAPSHOT_NAME).get(getTestTimeout()); }, - "Snapshot with this name is being created" + "Snapshot with this name is being created", + false ); } @@ -234,7 +255,8 @@ public void testSnapshotDeleteWhenRestoreBegins() throws Exception { awaitPartitionMapExchange(); }, - "Snapshot with this name is being checked" + "Snapshot with this name is being checked", + false ); } @@ -269,7 +291,54 @@ public void testSnapshotDeleteWhenRestoreInProgress() throws Exception { awaitPartitionMapExchange(); }, - "Snapshot with this name is being restored" + "Snapshot with this name is being restored", + false + ); + } + + /** Tests that a snapshot deletion is declined when a snapshot restore is in progress but fails. */ + @Test + public void testSnapshotDeleteWhenRestoreProgressFails() throws Exception { + // An in-the-middle failure won't allow to start restoring the incrementals. + assumeFalse(incremental); + + var restoreMsgs = F.asList(RESTORE_CACHE_GROUP_SNAPSHOT_ROLLBACK); + + if (incremental) { + restoreMsgs = new ArrayList<>(restoreMsgs); + restoreMsgs.add(RESTORE_INCREMENTAL_SNAPSHOT_START); + } + + doTestConcurrentSnapshotDelete( + () -> { + if (incremental) + return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null, 1); + else + return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null); + }, + restoreMsgs, + true, + () -> { + grid(0).destroyCache(DEFAULT_CACHE_NAME); + + awaitPartitionMapExchange(); + + SnapshotFileTree sft = snapshotFileTree(grid(1), SNAPSHOT_NAME); + + String failingFilePath = sft.partitionFile(dfltCacheCfg, primaries[0]).getAbsolutePath() + .replace(sft.nodeStorage().getAbsolutePath(), ""); + + grid(1).context().cache().context().snapshotMgr().ioFactory((file, modes) -> { + FileIO delegate = new RandomAccessFileIOFactory().create(file, modes); + + if (file.getPath().endsWith(failingFilePath)) + throw new RuntimeException("Test exception"); + + return delegate; + }); + }, + "Snapshot with this name is being restored", + true ); } @@ -279,13 +348,15 @@ public void testSnapshotDeleteWhenRestoreInProgress() throws Exception { * @param precreateSnp If {@code true}, creates snapshot after the cluster start. * @param prepareIteration If not {@code null}, is invoked in the beginning of test iteration at each {@code msgsToWatch}. * @param concurrentMsgErr Test of failed concurrent to {@code firstOp} delete snapshot operation to watch. + * @param ignoreFirstOpFailure If {@code true}, possible failure of {@code firstOp} is ignored. */ protected void doTestConcurrentSnapshotDelete( Supplier> firstOp, Collection msgsToWatch, boolean precreateSnp, @Nullable Runnable prepareIteration, - String concurrentMsgErr + String concurrentMsgErr, + boolean ignoreFirstOpFailure ) throws Exception { startGridsWithCache(3, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); @@ -299,6 +370,9 @@ protected void doTestConcurrentSnapshotDelete( TestRecordingCommunicationSpi commSpi1 = (TestRecordingCommunicationSpi)grid(1).configuration().getCommunicationSpi(); for (var nodeResMsgType : msgsToWatch) { + if (log.isInfoEnabled()) + log.info("Iteration with message-to-wait-for type: " + nodeResMsgType); + if (prepareIteration != null) prepareIteration.run(); @@ -322,7 +396,17 @@ protected void doTestConcurrentSnapshotDelete( commSpi1.stopBlock(); - firstFut.get(getTestTimeout()); + if (ignoreFirstOpFailure) { + try { + firstFut.get(getTestTimeout()); + } + catch (Exception e) { + if (log.isDebugEnabled()) + log.debug("The first operation failed but a failure is expected. Failure: " + e.getMessage()); + } + } + else + firstFut.get(getTestTimeout()); } } From 7e82295c95eae5acf39b8db3edd8d772d9ee0b85 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Tue, 15 Sep 2026 14:27:32 +0300 Subject: [PATCH 27/97] + delete-deletePath test --- .../snapshot/IgniteSnapshotManager.java | 8 +-- .../snapshot/SnapshotCheckProcess.java | 2 +- .../snapshot/SnapshotDeleteProcess.java | 9 +-- .../snapshot/SnapshotDeleteRequest.java | 16 +++++ .../snapshot/SnapshotRestoreProcess.java | 2 +- .../IgniteClusterSnapshotDeleteTest.java | 64 ++++++++++++++++--- .../TestIgniteReleaseFeatures_2_19_1.java | 2 +- 7 files changed, 83 insertions(+), 20 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java index e907c6464a2b1..1dd5b9b2301db 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java @@ -852,7 +852,7 @@ private IgniteInternalFuture initLocalSnapshotStartSt "re-encryption process is not finished yet.")); } - if (cctx.snapshotMgr().isSnapshotDeleting(req.snapshotName())) { + if (cctx.snapshotMgr().isSnapshotDeleting(req.snapshotName(), req.snapshotPath())) { return new GridFinishedFuture<>(new IgniteCheckedException("Snapshot operation has been rejected. Snapshot " + "'%s' is being deleted.".formatted(req.snapshotName()))); } @@ -1493,8 +1493,8 @@ public boolean isSnapshotChecking(String snpName) { /** * @return {@code True} if a snapshot {@code snpName} delete operation is in progress. */ - public boolean isSnapshotDeleting(String snpName) { - return deleteSnpProc.isSnapshotDeleting(snpName); + public boolean isSnapshotDeleting(String snpName, @Nullable String snpPath) { + return deleteSnpProc.isSnapshotDeleting(snpName, snpPath); } /** @@ -2097,7 +2097,7 @@ public IgniteFutureImpl createSnapshot( if (!incremental && snpExists) { throw new IgniteException("Create snapshot request has been rejected. " + - "Snapshot with given name already exists on local node."); + "Snapshot with given name already exists on local node or the path is not empty."); } if (incremental) { diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java index b3f5f1402630c..1337ff8541e99 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java @@ -496,7 +496,7 @@ private IgniteInternalFuture prepareAndCheckMetas(UUID ig + "' has already started [req=" + req + ']')); } - if (kctx.cache().context().snapshotMgr().isSnapshotDeleting(req.snapshotName())) { + if (kctx.cache().context().snapshotMgr().isSnapshotDeleting(req.snapshotName(), req.snapshotPath())) { return new GridFinishedFuture<>(new IgniteIllegalStateException("Snapshot '" + req.snapshotName() + "' is being deleted [req=" + req + ']')); } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index 6bcdbe413d9c3..bc4b00351a60f 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicBoolean; @@ -61,7 +62,7 @@ public class SnapshotDeleteProcess { private final Map> clusterOpFuts = new ConcurrentHashMap<>(); /** Process requests per snapshot name on each server node. */ - private final Map requests = new ConcurrentHashMap<>(); + private final Set requests = ConcurrentHashMap.newKeySet(); /** The distributed process. */ private final DistributedProcess distrProc; @@ -149,7 +150,7 @@ private IgniteInternalFuture deletePhase(UUID ignored, S } try { - if (requests.putIfAbsent(req.snpName, req) != null) { + if (!requests.add(req)) { return new GridFinishedFuture<>(new IgniteIllegalStateException("Deletion of the snapshot has already " + "started [req=" + req + ']')); } @@ -258,8 +259,8 @@ private void reducePhase(UUID reqId, Map results, } /** */ - public boolean isSnapshotDeleting(String snpName) { - return requests.get(snpName) != null; + public boolean isSnapshotDeleting(String snpName, @Nullable String snpPath) { + return requests.contains(new SnapshotDeleteRequest(null, snpName, snpPath)); } /** diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteRequest.java index ec8d3830ebed1..37577c1b2eb60 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteRequest.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteRequest.java @@ -17,6 +17,7 @@ package org.apache.ignite.internal.processors.cache.persistence.snapshot; +import java.util.Objects; import java.util.UUID; import org.apache.ignite.internal.Order; import org.apache.ignite.internal.util.typedef.internal.S; @@ -58,6 +59,21 @@ public SnapshotDeleteRequest() { this.snpPath = snpPath; } + /** {@inheritDoc} */ + @Override public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) + return false; + + SnapshotDeleteRequest other = (SnapshotDeleteRequest)o; + + return snpName.equals(other.snpName) && Objects.equals(snpPath, other.snpPath); + } + + /** {@inheritDoc} */ + @Override public int hashCode() { + return Objects.hash(snpName, snpPath); + } + /** {@inheritDoc} */ @Override public String toString() { return S.toString(SnapshotDeleteRequest.class, this, super.toString()); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotRestoreProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotRestoreProcess.java index d19e5a20e43a3..f4fe684eba584 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotRestoreProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotRestoreProcess.java @@ -659,7 +659,7 @@ private IgniteInternalFuture prepare(UUID igno if (snpMgr.isSnapshotCreating()) throw new IgniteCheckedException(OP_REJECT_MSG + "A cluster snapshot operation is in progress."); - if (snpMgr.isSnapshotDeleting(req.snapshotName())) + if (snpMgr.isSnapshotDeleting(req.snapshotName(), req.snapshotPath())) throw new IgniteException(OP_REJECT_MSG + "A snapshot '" + req.snapshotName() + "' delete operation is in progress."); if (ctx.encryption().isMasterKeyChangeInProgress()) { diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java index 52780d9c5b378..e98328e53860a 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java @@ -50,6 +50,7 @@ import static java.nio.file.Files.newDirectoryStream; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.CHECK_SNAPSHOT_METAS; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.CHECK_SNAPSHOT_PARTS; +import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.DELETE_SNAPSHOT; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.END_SNAPSHOT; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_PRELOAD; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_PREPARE; @@ -128,7 +129,7 @@ public static Collection runParams() { } } - /** Tests snapshot deletion when one node finds snapshot but failes to delete its data. */ + /** Tests snapshot deletion when one node finds snapshot but fails to delete its data. */ @Test public void testUncompletedNodes() throws Exception { separatedWorkDir = true; @@ -160,7 +161,10 @@ public void testUncompletedNodes() throws Exception { startGridsWithCache(3, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); - snp(grid(0)).createSnapshot(SNAPSHOT_NAME).get(getTestTimeout()); + snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(getTestTimeout()); + + if (incremental) + addIncrementalSnapshot(null); var delSnpRes = snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); @@ -176,7 +180,10 @@ public void testEmptyNodes() throws Exception { startGridsWithCache(2, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); - snp(grid(0)).createSnapshot(SNAPSHOT_NAME).get(getTestTimeout()); + snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(getTestTimeout()); + + if (incremental) + addIncrementalSnapshot(null); startGrid(G.allGrids().size()); @@ -187,6 +194,45 @@ public void testEmptyNodes() throws Exception { assertTrue(F.isEmpty(delSnpRes.uncompletedNodes)); } + /** Tests that a concurrent deletion of a snapshot with the same name but different path is allowed. */ + @Test + public void testConcurrentDeleteOfTheSameSnapshotDifferentPath() throws Exception { + // Incremental snapshots don't support encryption and only-primary mode. + assumeTrue(!incremental || !(encryption || onlyPrimary)); + + startGridsWithCache(3, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); + + snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(TIMEOUT); + + if (incremental) + addIncrementalSnapshot(null); + + String snpPath = new File(U.defaultWorkDirectory(), "ex_snapshots").getAbsolutePath(); + + snp(grid(0)).createSnapshot(SNAPSHOT_NAME, snpPath, false, onlyPrimary).get(getTestTimeout()); + + if (incremental) + addIncrementalSnapshot(snpPath); + + TestRecordingCommunicationSpi commSpi1 = (TestRecordingCommunicationSpi)grid(1).configuration().getCommunicationSpi(); + + commSpi1.blockMessages((node, msg) -> + msg instanceof SingleNodeMessage msg0 && msg0.type() == DELETE_SNAPSHOT.ordinal()); + + var delFut0 = snp(grid(0)).deleteSnapshot(SNAPSHOT_NAME, null); + var delFut1 = snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, snpPath); + + commSpi1.waitForBlocked(2, getTestTimeout()); + + commSpi1.stopBlock(); + + var delRes0 = delFut0.get(getTestTimeout()); + var delRes1 = delFut1.get(getTestTimeout()); + + assertFalse((delRes0.completedNodes().isEmpty())); + assertFalse(delRes1.completedNodes().isEmpty()); + } + /** Tests that a concurrent deletion of the same snapshot is declined. */ @Test public void testConcurrentDeleteOfTheSameSnapshot() throws Exception { @@ -364,7 +410,7 @@ protected void doTestConcurrentSnapshotDelete( snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(TIMEOUT); if (incremental) - addIncrementalSnapshot(); + addIncrementalSnapshot(null); } TestRecordingCommunicationSpi commSpi1 = (TestRecordingCommunicationSpi)grid(1).configuration().getCommunicationSpi(); @@ -411,13 +457,13 @@ protected void doTestConcurrentSnapshotDelete( } /** */ - private void addIncrementalSnapshot() { - try (var streamer = grid(0).dataStreamer(DEFAULT_CACHE_NAME)) { - for (int i = CACHE_KEYS_RANGE; i < CACHE_KEYS_RANGE + CACHE_KEYS_RANGE / 4; ++i) - streamer.addData(i, i); + private void addIncrementalSnapshot(@Nullable String path) { + try (var ds = grid(0).dataStreamer(DEFAULT_CACHE_NAME)) { + for (int i = CACHE_KEYS_RANGE; i < CACHE_KEYS_RANGE + CACHE_KEYS_RANGE / 4; i++) + ds.addData(i, i); } - snp(grid(0)).createIncrementalSnapshot(SNAPSHOT_NAME).get(getTestTimeout()); + snp(grid(0)).createSnapshot(SNAPSHOT_NAME, path, true, onlyPrimary).get(getTestTimeout()); } /** {@inheritDoc} */ diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_1.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_1.java index 037753dcaa2c1..c6108f4a28fb8 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_1.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_1.java @@ -23,5 +23,5 @@ public class TestIgniteReleaseFeatures_2_19_1 { public static final IgniteFeature ROLLING_UPGRADE_FEATURE = TestIgniteReleaseFeatures_2_19_0.ROLLING_UPGRADE_FEATURE; /** */ - public static final IgniteFeature SNAPSHOT_DELETE_FEATURE = new IgniteCoreFeature(1); + public static final IgniteFeature SNAPSHOT_DELETE_FEATURE = SupportedFeatureRegistry.SNAPSHOT_DELETE_FEATURE; } From ed9cc3a8c54f7d32dc64f897a10ebd683d60f19a Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Tue, 15 Sep 2026 15:22:46 +0300 Subject: [PATCH 28/97] + another delete-deletePath tests --- .../GridCommandHandlerDeleteSnapshotTest.java | 2 +- .../snapshot/AbstractSnapshotSelfTest.java | 18 ++++++++++-- .../IgniteClusterSnapshotCheckTest.java | 21 ++++++++++++++ .../IgniteClusterSnapshotDeleteTest.java | 15 ---------- .../IgniteClusterSnapshotRestoreSelfTest.java | 28 +++++++++++++++++++ .../IgniteClusterSnapshotSelfTest.java | 19 +++++++++++++ 6 files changed, 84 insertions(+), 19 deletions(-) diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java index ce3ab0089fb0d..967f7b141bf33 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java @@ -96,7 +96,7 @@ public static Collection parameters() { @Override protected void cleanPersistenceDir() throws Exception { super.cleanPersistenceDir(); - // Also cleans separated snapshot working directories. + // Also cleans separated snapshot working directories and custom snapshot pacthes. try (DirectoryStream files = newDirectoryStream(Paths.get(U.defaultWorkDirectory()))) { for (Path path : files) U.delete(path); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java index b7a511b7ca285..206bad2c7a63f 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java @@ -294,6 +294,17 @@ public void afterTestSnapshot() throws Exception { cleanPersistenceDir(); } + /** {@inheritDoc} */ + @Override protected void cleanPersistenceDir() throws Exception { + super.cleanPersistenceDir(); + + // Clean all: also separated snapshot working directories and custom snapshot pathes. + try (DirectoryStream files = newDirectoryStream(Paths.get(U.defaultWorkDirectory()))) { + for (Path path : files) + U.delete(path); + } + } + /** * @param evts Events to check. * @throws IgniteInterruptedCheckedException If interrupted. @@ -829,7 +840,7 @@ public static void doSnapshotCancellationTest( protected void doTestConcurrentSnapshotDeleteOperation( ExRunnable prepareCluster, ExRunnable concurrentOp, - Function errValidator, + @Nullable Function errValidator, boolean rerunAtTheEnd ) throws Exception { CountDownLatch delProcInitLatch = new CountDownLatch(1); @@ -871,10 +882,11 @@ protected void doTestConcurrentSnapshotDeleteOperation( try { concurrentOp.run(); - throw new IllegalStateException("Exception is not thrown."); + if (errValidator != null) + throw new IllegalStateException("Exception is not thrown."); } catch (Exception e) { - if (!errValidator.apply(e)) + if (errValidator == null || !errValidator.apply(e)) throw new IllegalStateException("Unexpected exception: " + e.getMessage(), e); } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java index b278b721a3fd2..268ff54775232 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java @@ -125,6 +125,7 @@ import static org.apache.ignite.testframework.GridTestUtils.cartesianProduct; import static org.apache.ignite.testframework.GridTestUtils.waitForCondition; import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; /** * Cluster-wide snapshot check procedure tests. @@ -1213,6 +1214,26 @@ public void testConcurrentSnapshotDeleteAndCheckOperations() throws Exception { ); } + /** */ + @Test + public void testConcurrentSnapshotDeleteAndCheckOperationsWithDifferentPath() throws Exception { + // The test uses thread blocking. + assumeTrue(snpThrdPoolSz > 1); + + String snpPath = new File(U.defaultWorkDirectory(), "ex_snapshots").getAbsolutePath(); + + doTestConcurrentSnapshotDeleteOperation( + () -> { + prepareGridsAndSnapshot(4, 3, 1, false); + + snp(grid(0)).createSnapshot(SNAPSHOT_NAME, snpPath, false, false).get(getTestTimeout()); + }, + () -> snp(grid(2)).checkSnapshot(SNAPSHOT_NAME, snpPath).get(), + null, + false + ); + } + /** Tests that concurrent snapshot full check is declined when the same snapshot is being fully restored (checked). */ @Test public void testConcurrentTheSameSnpFullCheckWhenFullyRestoringDeclined() throws Exception { diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java index e98328e53860a..c3ceb598d38da 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java @@ -18,9 +18,6 @@ package org.apache.ignite.internal.processors.cache.persistence.snapshot; import java.io.File; -import java.nio.file.DirectoryStream; -import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.concurrent.atomic.AtomicBoolean; @@ -47,7 +44,6 @@ import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameter; -import static java.nio.file.Files.newDirectoryStream; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.CHECK_SNAPSHOT_METAS; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.CHECK_SNAPSHOT_PARTS; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.DELETE_SNAPSHOT; @@ -118,17 +114,6 @@ public static Collection runParams() { cleanPersistenceDir(); } - /** {@inheritDoc} */ - @Override protected void cleanPersistenceDir() throws Exception { - super.cleanPersistenceDir(); - - // Also cleans separated snapshot working directories. - try (DirectoryStream files = newDirectoryStream(Paths.get(U.defaultWorkDirectory()))) { - for (Path path : files) - U.delete(path); - } - } - /** Tests snapshot deletion when one node finds snapshot but fails to delete its data. */ @Test public void testUncompletedNodes() throws Exception { diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotRestoreSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotRestoreSelfTest.java index 99c8a99b05d7d..84dd1f57c05d8 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotRestoreSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotRestoreSelfTest.java @@ -344,6 +344,34 @@ public void testConcurrentSnapshotDeleteAndRestoreOperations() throws Exception ); } + /** */ + @Test + public void testConcurrentSnapshotDeleteAndRestoreOperationsWithDifferentPath() throws Exception { + String snpPath = new File(U.defaultWorkDirectory(), "ex_snapshots").getAbsolutePath(); + + doTestConcurrentSnapshotDeleteOperation( + () -> { + startGridsWithSnapshot(3, CACHE_KEYS_RANGE); + + grid(0).createCache(DEFAULT_CACHE_NAME); + + try (var ds = grid(0).dataStreamer(DEFAULT_CACHE_NAME)) { + for (int i = 0; i < CACHE_KEYS_RANGE; ++i) + ds.addData(i, i); + } + + snp(grid(0)).createSnapshot(SNAPSHOT_NAME, snpPath, false, false).get(getTestTimeout()); + + grid(0).destroyCache(DEFAULT_CACHE_NAME); + + awaitPartitionMapExchange(); + }, + () -> snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, snpPath, null).get(), + null, + false + ); + } + /** * Ensures that the cache doesn't start if one of the baseline nodes fails. * diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotSelfTest.java index 9745679852f06..1270c77394d93 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotSelfTest.java @@ -633,6 +633,25 @@ public void testConcurrentSnapshotDeleteOperation() throws Exception { ); } + /** + * Tests that a concurrent deletion of a same-named snapshot is allowed if it has a different path. + */ + @Test + public void testConcurrentSnapshotDeleteOperationWithDifferentPath() throws Exception { + String snpPath = new File(U.defaultWorkDirectory(), "ex_snapshots").getAbsolutePath(); + + doTestConcurrentSnapshotDeleteOperation( + () -> { + startGridsWithCache(3, dfltCacheCfg, CACHE_KEYS_RANGE); + + snp(grid(2)).createSnapshot(SNAPSHOT_NAME).get(); + }, + () -> snp(grid(2)).createSnapshot(SNAPSHOT_NAME, snpPath, false, false).get(), + null, + false + ); + } + /** @throws Exception If fails. */ @Test public void testClusterSnapshotCleanedOnLeft() throws Exception { From bb1dfecaac641731f72cdfd6f20eb0432e7f99c0 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Tue, 15 Sep 2026 15:56:47 +0300 Subject: [PATCH 29/97] + test pf deletion repeat on restarted offline node --- .../snapshot/SnapshotDeleteProcess.java | 4 +- .../IgniteClusterSnapshotDeleteTest.java | 40 +++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index bc4b00351a60f..a75c08f392485 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -185,7 +185,7 @@ else if (!deleted) reqLocFut.onDone(new SnapshotDeleteResponse(res)); } finally { - requests.remove(req.snpName); + requests.remove(req); } }); @@ -195,7 +195,7 @@ else if (!deleted) return reqLocFut; } catch (Throwable t) { - requests.remove(req.snpName); + requests.remove(req); log.warning("An error occurred during snapshot deletion [req=" + req + ']', t); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java index c3ceb598d38da..10d98efe8c786 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java @@ -20,6 +20,7 @@ import java.io.File; import java.util.ArrayList; import java.util.Collection; +import java.util.UUID; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; import org.apache.ignite.IgniteIllegalStateException; @@ -179,6 +180,45 @@ public void testEmptyNodes() throws Exception { assertTrue(F.isEmpty(delSnpRes.uncompletedNodes)); } + /** Tests snapshot deletion repeat after an offline node restarts. */ + @Test + public void testLeftPart() throws Exception { + separatedWorkDir = true; + + startGridsWithCache(3, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); + + snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(getTestTimeout()); + + if (incremental) + addIncrementalSnapshot(null); + + int stoppedNodeIdx = G.allGrids().size() - 1; + + UUID stoppedNodeId = grid(stoppedNodeIdx).localNode().id(); + + stopGrid(stoppedNodeIdx); + + var delSnpRes = snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); + + assertEquals(2, delSnpRes.completedNodes.size()); + assertFalse(delSnpRes.completedNodes.contains(stoppedNodeId)); + + assertTrue(F.isEmpty(delSnpRes.uncompletedNodes)); + assertTrue(F.isEmpty(delSnpRes.emptyNodes)); + + startGrid(stoppedNodeIdx); + + stoppedNodeId = grid(stoppedNodeIdx).localNode().id(); + + delSnpRes = snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); + + assertEquals(1, delSnpRes.completedNodes.size()); + assertTrue(delSnpRes.completedNodes.contains(stoppedNodeId)); + + assertTrue(F.isEmpty(delSnpRes.uncompletedNodes)); + assertEquals(2, delSnpRes.emptyNodes.size()); + } + /** Tests that a concurrent deletion of a snapshot with the same name but different path is allowed. */ @Test public void testConcurrentDeleteOfTheSameSnapshotDifferentPath() throws Exception { From 129b090df0f9b5a58fa0200e99c2da3895293caa Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Tue, 15 Sep 2026 17:02:02 +0300 Subject: [PATCH 30/97] + stop-node test, fixes --- .../snapshot/IgniteSnapshotManager.java | 70 +++++++++++---- .../snapshot/SnapshotDeleteProcess.java | 8 +- .../snapshot/AbstractSnapshotSelfTest.java | 9 +- .../IgniteClusterSnapshotDeleteTest.java | 87 ++++++++++++++++++- 4 files changed, 152 insertions(+), 22 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java index 1dd5b9b2301db..b9b078144916d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java @@ -67,6 +67,7 @@ import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.function.Function; +import java.util.function.Supplier; import java.util.stream.Collectors; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteException; @@ -723,30 +724,69 @@ public void deleteLocalSnapshot(File snpDir) { pdsSettings.consistentId().toString() ); - deleteLocalSnapshot(sft, null); + deleteLocalSnapshot(sft); } /** + * Deletes local shapshot data. + * + * @param sft Snapshot file tree + */ + public boolean deleteLocalSnapshot(SnapshotFileTree sft) { + return deleteLocalSnapshot(sft, null, null); + } + + /** + * Deletes local shapshot data. + * * @param sft Snapshot file tree - * @param existsFlag Flag to set {@code true} if any snapshot file or directory is found (exists). If {@code null}, ignored. + * @param existsFlag Flag to set {@code true} if any snapshot file or directory was found (existed). If {@code null}, ignored. + * @param cancel If not {@code null}, is being periodically checked to stop deletion. * @return {@code True}, if data is found and completely deleted or if no data found; - * {@code False}, if data is found but was deleted not completely. + * {@code False}, if data is found but might not be deleted completely or if was canceled. */ - public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean existsFlag) { + public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean existsFlag, @Nullable Supplier cancel) { AtomicBoolean res = new AtomicBoolean(true); - sft.allStorages().forEach(f -> deleteWithExistence(f, res, existsFlag)); + sft.allStorages().forEach(f -> { + if (cancel == null || !cancel.get()) + deleteAndCheckExisted(f, res, existsFlag); + }); + + if (cancel != null && cancel.get()) + return false; + + deleteAndCheckExisted(sft.binaryMeta(), res, existsFlag); - deleteWithExistence(sft.binaryMeta(), res, existsFlag); - deleteWithExistence(sft.binaryMetaRoot(), res, existsFlag); - deleteWithExistence(sft.marshaller(), res, existsFlag); - deleteWithExistence(sft.root(), res, existsFlag); + if (cancel != null && cancel.get()) + return false; + + deleteAndCheckExisted(sft.binaryMetaRoot(), res, existsFlag); + + if (cancel != null && cancel.get()) + return false; + + deleteAndCheckExisted(sft.marshaller(), res, existsFlag); + + if (cancel != null && cancel.get()) + return false; + + deleteAndCheckExisted(sft.root(), res, existsFlag); + + if (cancel != null && cancel.get()) + return false; return res.get(); } - /** */ - private void deleteWithExistence(@Nullable File f, AtomicBoolean onlyFailRes, @Nullable AtomicBoolean existsFlag) { + /** + * Deletes file/directory and sets existence and deletion failure flags. + * + * @param f File/directory to delete. If {@code null}, does nothing. + * @param failRes Is set to {@code False} if at least one existed file or directory was denied to delete. + * @param existsFlag Is set to {@code True} if not {@code null} and if at least one file or directory was found (existed). + */ + private void deleteAndCheckExisted(@Nullable File f, AtomicBoolean failRes, @Nullable AtomicBoolean existsFlag) { if (f == null) return; @@ -758,10 +798,10 @@ private void deleteWithExistence(@Nullable File f, AtomicBoolean onlyFailRes, @N try { // Additionally checks the existence for the case of concurrent deletion. if (existed && ((!f.isDirectory() && !U.delete(f)) || (f.isDirectory() && !deleteDirectory(f))) && f.exists()) - onlyFailRes.set(false); + failRes.set(false); } catch (IOException e) { - onlyFailRes.set(false); + failRes.set(false); } } @@ -1326,7 +1366,7 @@ private IgniteInternalFuture initLocalSnapshotEndStag if (snpStartReq.incremental()) U.delete(snpOp.snapshotFileTree().incrementalSnapshotFileTree(snpStartReq.incrementIndex()).root()); else - deleteLocalSnapshot(snpOp.snapshotFileTree(), null); + deleteLocalSnapshot(snpOp.snapshotFileTree()); } else if (!F.isEmpty(endReq.warnings())) { // Pass the warnings further to the next stage for the case when snapshot started from not coordinator. @@ -4022,7 +4062,7 @@ public LocalSnapshotSender(SnapshotFileTree sft) { log.info("The Local snapshot sender closed. All resources released [dbNodeSnpDir=" + sft.nodeStorage() + ']'); } else { - deleteLocalSnapshot(sft, null); + deleteLocalSnapshot(sft); if (log.isDebugEnabled()) log.debug("Local snapshot sender closed due to an error occurred: " + th.getMessage()); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index a75c08f392485..cbdceaa217c70 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -115,7 +115,7 @@ public IgniteFuture start(String snpName, @Nullable /** */ private IgniteInternalFuture deletePhase(UUID ignored, SnapshotDeleteRequest req) { - if (kctx.isStopping()) { + if (interrupted || kctx.isStopping()) { return new GridFinishedFuture<>(new NodeStoppingException(OP_REJECT_MSG + " Node is stopping [req=" + req + ']')); } @@ -161,7 +161,11 @@ private IgniteInternalFuture deletePhase(UUID ignored, S try { AtomicBoolean foundFlag = new AtomicBoolean(); - boolean deleted = snpMgr.deleteLocalSnapshot(new SnapshotFileTree(kctx, req.snpName, req.snpPath), foundFlag); + boolean deleted = snpMgr.deleteLocalSnapshot( + new SnapshotFileTree(kctx, req.snpName, req.snpPath), + foundFlag, + () -> interrupted || kctx.isStopping() + ); SnapshotDeleteResponse.SnapshotDeleteStatus res; diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java index 206bad2c7a63f..1fe375a84b6d3 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java @@ -44,6 +44,7 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; +import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.IntStream; import org.apache.ignite.Ignite; @@ -854,7 +855,11 @@ protected void doTestConcurrentSnapshotDeleteOperation( @Override public T createComponent(PluginContext ctx, Class cls) { if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { - @Override public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean existsFlag) { + @Override public boolean deleteLocalSnapshot( + SnapshotFileTree sft, + @Nullable AtomicBoolean existsFlag, + @Nullable Supplier cancel + ) { delProcInitLatch.countDown(); try { @@ -864,7 +869,7 @@ protected void doTestConcurrentSnapshotDeleteOperation( throw new RuntimeException("Interrupted.", e); } - return super.deleteLocalSnapshot(sft, existsFlag); + return super.deleteLocalSnapshot(sft, existsFlag, cancel); } }; } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java index 10d98efe8c786..2c5bde6c70515 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java @@ -21,6 +21,8 @@ import java.util.ArrayList; import java.util.Collection; import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Supplier; import org.apache.ignite.IgniteIllegalStateException; @@ -129,14 +131,18 @@ public void testUncompletedNodes() throws Exception { @Override public T createComponent(PluginContext ctx, Class cls) { if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { - @Override public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean existsFlag) { + @Override public boolean deleteLocalSnapshot( + SnapshotFileTree sft, + @Nullable AtomicBoolean existsFlag, + @Nullable Supplier cancel + ) { if (ctx.localNode().id().equals(grid(1).localNode().id())) { existsFlag.set(true); return false; } - return super.deleteLocalSnapshot(sft, existsFlag); + return super.deleteLocalSnapshot(sft, existsFlag, cancel); } }; } @@ -182,7 +188,7 @@ public void testEmptyNodes() throws Exception { /** Tests snapshot deletion repeat after an offline node restarts. */ @Test - public void testLeftPart() throws Exception { + public void testDeletionRepeatAfterOfflineNodeStarts() throws Exception { separatedWorkDir = true; startGridsWithCache(3, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); @@ -219,6 +225,81 @@ public void testLeftPart() throws Exception { assertEquals(2, delSnpRes.emptyNodes.size()); } + /** Test snapshot deletion process when one node leaves. */ + @Test + public void testNodeStopsInTheMiddle() throws Exception { + // Incremental snapshots don't support only-primary and encryption mode. + assumeTrue(!incremental || !(onlyPrimary || encryption)); + + separatedWorkDir = true; + + CountDownLatch beginLatch = new CountDownLatch(1); + CountDownLatch proceedLatch = new CountDownLatch(1); + + // Simulates a deletion error on some node. + pluginProvider = new AbstractTestPluginProvider() { + @Override public String name() { + return "TestSnpMgrProvider"; + } + + @Override public T createComponent(PluginContext ctx, Class cls) { + if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { + return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { + @Override public boolean deleteLocalSnapshot( + SnapshotFileTree sft, + @Nullable AtomicBoolean existsFlag, + @Nullable Supplier cancel + ) { + if (ctx.localNode().id().equals(grid(1).localNode().id())) { + beginLatch.countDown(); + + try { + assertTrue(proceedLatch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); + } + catch (InterruptedException e) { + throw new RuntimeException("Interrupted.", e); + } + } + + return super.deleteLocalSnapshot(sft, existsFlag, cancel); + } + }; + } + + return super.createComponent(ctx, cls); + } + }; + + startGridsWithCache(3, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); + + snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(getTestTimeout()); + + if (incremental) + addIncrementalSnapshot(null); + + var delFut = snp(grid(2)).deleteSnapshot(SNAPSHOT_NAME, null); + + assertTrue(beginLatch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); + + UUID stoppedGridId = grid(1).localNode().id(); + + stopGrid(1); + + proceedLatch.countDown(); + + var delRes = delFut.get(getTestTimeout()); + + assertEquals(2, delRes.completedNodes.size()); + assertFalse(delRes.completedNodes.contains(stoppedGridId)); + + startGrid(1); + + delRes = snp(grid(2)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); + + assertEquals(1, delRes.completedNodes.size()); + assertTrue(delRes.completedNodes.contains(grid(1).localNode().id())); + } + /** Tests that a concurrent deletion of a snapshot with the same name but different path is allowed. */ @Test public void testConcurrentDeleteOfTheSameSnapshotDifferentPath() throws Exception { From 42492050d2aa4ffe8553989468f719248c7c191e Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Tue, 15 Sep 2026 17:19:02 +0300 Subject: [PATCH 31/97] revert delete cancel flag --- .../snapshot/IgniteSnapshotManager.java | 29 ++----------------- .../snapshot/SnapshotDeleteProcess.java | 6 +--- .../snapshot/AbstractSnapshotSelfTest.java | 9 ++---- .../IgniteClusterSnapshotDeleteTest.java | 16 +++------- 4 files changed, 10 insertions(+), 50 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java index b9b078144916d..a389fb1349cd9 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java @@ -67,7 +67,6 @@ import java.util.function.BooleanSupplier; import java.util.function.Consumer; import java.util.function.Function; -import java.util.function.Supplier; import java.util.stream.Collectors; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteException; @@ -733,7 +732,7 @@ public void deleteLocalSnapshot(File snpDir) { * @param sft Snapshot file tree */ public boolean deleteLocalSnapshot(SnapshotFileTree sft) { - return deleteLocalSnapshot(sft, null, null); + return deleteLocalSnapshot(sft, null); } /** @@ -741,41 +740,19 @@ public boolean deleteLocalSnapshot(SnapshotFileTree sft) { * * @param sft Snapshot file tree * @param existsFlag Flag to set {@code true} if any snapshot file or directory was found (existed). If {@code null}, ignored. - * @param cancel If not {@code null}, is being periodically checked to stop deletion. * @return {@code True}, if data is found and completely deleted or if no data found; * {@code False}, if data is found but might not be deleted completely or if was canceled. */ - public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean existsFlag, @Nullable Supplier cancel) { + public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean existsFlag) { AtomicBoolean res = new AtomicBoolean(true); - sft.allStorages().forEach(f -> { - if (cancel == null || !cancel.get()) - deleteAndCheckExisted(f, res, existsFlag); - }); - - if (cancel != null && cancel.get()) - return false; + sft.allStorages().forEach(f -> deleteAndCheckExisted(f, res, existsFlag)); deleteAndCheckExisted(sft.binaryMeta(), res, existsFlag); - - if (cancel != null && cancel.get()) - return false; - deleteAndCheckExisted(sft.binaryMetaRoot(), res, existsFlag); - - if (cancel != null && cancel.get()) - return false; - deleteAndCheckExisted(sft.marshaller(), res, existsFlag); - - if (cancel != null && cancel.get()) - return false; - deleteAndCheckExisted(sft.root(), res, existsFlag); - if (cancel != null && cancel.get()) - return false; - return res.get(); } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index cbdceaa217c70..7bbd13cf20b8c 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -161,11 +161,7 @@ private IgniteInternalFuture deletePhase(UUID ignored, S try { AtomicBoolean foundFlag = new AtomicBoolean(); - boolean deleted = snpMgr.deleteLocalSnapshot( - new SnapshotFileTree(kctx, req.snpName, req.snpPath), - foundFlag, - () -> interrupted || kctx.isStopping() - ); + boolean deleted = snpMgr.deleteLocalSnapshot(new SnapshotFileTree(kctx, req.snpName, req.snpPath), foundFlag); SnapshotDeleteResponse.SnapshotDeleteStatus res; diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java index 1fe375a84b6d3..206bad2c7a63f 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java @@ -44,7 +44,6 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; -import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.IntStream; import org.apache.ignite.Ignite; @@ -855,11 +854,7 @@ protected void doTestConcurrentSnapshotDeleteOperation( @Override public T createComponent(PluginContext ctx, Class cls) { if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { - @Override public boolean deleteLocalSnapshot( - SnapshotFileTree sft, - @Nullable AtomicBoolean existsFlag, - @Nullable Supplier cancel - ) { + @Override public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean existsFlag) { delProcInitLatch.countDown(); try { @@ -869,7 +864,7 @@ protected void doTestConcurrentSnapshotDeleteOperation( throw new RuntimeException("Interrupted.", e); } - return super.deleteLocalSnapshot(sft, existsFlag, cancel); + return super.deleteLocalSnapshot(sft, existsFlag); } }; } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java index 2c5bde6c70515..f1c840883b98f 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java @@ -131,18 +131,14 @@ public void testUncompletedNodes() throws Exception { @Override public T createComponent(PluginContext ctx, Class cls) { if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { - @Override public boolean deleteLocalSnapshot( - SnapshotFileTree sft, - @Nullable AtomicBoolean existsFlag, - @Nullable Supplier cancel - ) { + @Override public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean existsFlag) { if (ctx.localNode().id().equals(grid(1).localNode().id())) { existsFlag.set(true); return false; } - return super.deleteLocalSnapshot(sft, existsFlag, cancel); + return super.deleteLocalSnapshot(sft, existsFlag); } }; } @@ -245,11 +241,7 @@ public void testNodeStopsInTheMiddle() throws Exception { @Override public T createComponent(PluginContext ctx, Class cls) { if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { - @Override public boolean deleteLocalSnapshot( - SnapshotFileTree sft, - @Nullable AtomicBoolean existsFlag, - @Nullable Supplier cancel - ) { + @Override public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean existsFlag) { if (ctx.localNode().id().equals(grid(1).localNode().id())) { beginLatch.countDown(); @@ -261,7 +253,7 @@ public void testNodeStopsInTheMiddle() throws Exception { } } - return super.deleteLocalSnapshot(sft, existsFlag, cancel); + return super.deleteLocalSnapshot(sft, existsFlag); } }; } From b7c182dc3d5410e6c78ccbc617c21aaeb18044f7 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Wed, 16 Sep 2026 13:32:31 +0300 Subject: [PATCH 32/97] + RU test --- ...usterSnapshotDeleteRollingUpgradeTest.java | 45 +++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgradeTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgradeTest.java index 083ea832f6b16..6c608f25a07a5 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgradeTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgradeTest.java @@ -27,9 +27,13 @@ import org.apache.ignite.configuration.DataStorageConfiguration; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.processors.rollingupgrade.AbstractRollingUpgradeTest; +import org.apache.ignite.internal.util.distributed.SingleNodeMessage; import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.testframework.GridTestUtils; import org.junit.Test; +import static org.apache.ignite.internal.TestRecordingCommunicationSpi.spi; +import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RU_PREPARE_VERSION_FINALIZATION; import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause; /** */ @@ -66,6 +70,47 @@ public class IgniteClusterSnapshotDeleteRollingUpgradeTest extends AbstractRolli return cfg; } + /** */ + @Test + public void testConcurrentUnfinishedRU() throws Exception { + for (int i = 0; i < ALL_GRIDS; i++) + startGrid(i, "2.19.0", i >= ALL_GRIDS - CLIENTS); + + grid(0).cluster().active(true); + + int testNodeIx = ALL_GRIDS - CLIENTS - 1; + + createCacheAndSnapshot(testNodeIx); + + ru(grid(testNodeIx)).enableVersionUpgrade(); + + for (int i = 0; i < ALL_GRIDS; i++) { + assertTrue(ru(grid(i)).isVersionUpgradeEnabled()); + + upgradeNodeVersion(i, "2.19.1"); + } + + spi(grid(testNodeIx)).blockMessages((node, msg) -> msg instanceof SingleNodeMessage snm && + snm.type() == RU_PREPARE_VERSION_FINALIZATION.ordinal()); + + var finalizeFut = GridTestUtils.runAsync(() -> ru(testNodeIx).finalizeClusterVersion()); + + assertTrue(spi(grid(testNodeIx)).waitForBlocked(1, getTestTimeout())); + + ensureSnapshotDeletionFailed(); + + spi(grid(testNodeIx)).stopBlock(); + + assertFalse(spi(grid(testNodeIx)).hasBlockedMessages()); + + finalizeFut.get(getTestTimeout()); + + for (int i = 0; i < ALL_GRIDS; i++) + assertFalse(ru(grid(i)).isVersionUpgradeEnabled()); + + assertFalse(F.isEmpty(snp(1).deleteSnapshot(SNP_NAME, null).get(getTestTimeout()).completedNodes)); + } + /** */ @Test public void testNodeNotSupportingSnapshotDeleteFeature() throws Exception { From a0425cbbc5a29ce6e8b57e272c56b715f8a2f054 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Fri, 18 Sep 2026 07:49:27 +0300 Subject: [PATCH 33/97] + raw testDeletionInIgnite --- .../snapshot/SnapshotDeleteProcess.java | 59 +++++++++++-- .../IgniteClusterSnapshotDeleteTest.java | 87 +++++++++++++++++++ 2 files changed, 140 insertions(+), 6 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index 7bbd13cf20b8c..551b37e9c626b 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -17,6 +17,7 @@ package org.apache.ignite.internal.processors.cache.persistence.snapshot; +import java.io.File; import java.util.ArrayList; import java.util.Map; import java.util.Set; @@ -47,7 +48,10 @@ */ public class SnapshotDeleteProcess { /** Reject operation messages. */ - private static final String OP_REJECT_MSG = "Snapshot deletion was rejected."; + private static final String OP_REJECT_MSG = "Snapshot deletion was rejected. "; + + /** */ + private static final String SNP_PATH_ERR_PREF = "Provided snapshot path "; /** Kernal context. */ private final GridKernalContext kctx; @@ -131,22 +135,38 @@ private IgniteInternalFuture deletePhase(UUID ignored, S if (curCreateRq != null && curCreateRq.snpName.equals(req.snpName)) { return new GridFinishedFuture<>(new IgniteIllegalStateException(OP_REJECT_MSG + - " Snapshot with this name is being created [req=" + req + ']')); + "Snapshot with this name is being created [req=" + req + ']')); } if (snpMgr.isRestoring(req.snpName)) { return new GridFinishedFuture<>(new IgniteIllegalStateException(OP_REJECT_MSG + - " Snapshot with this name is being restored [req=" + req + ']')); + "Snapshot with this name is being restored [req=" + req + ']')); } if (snpMgr.isSnapshotChecking(req.snpName)) { return new GridFinishedFuture<>(new IgniteIllegalStateException(OP_REJECT_MSG + - " Snapshot with this name is being checked [req=" + req + ']')); + "Snapshot with this name is being checked [req=" + req + ']')); } if (!kctx.rollingUpgrade().features().isActive(SNAPSHOT_DELETE_FEATURE)) { return new GridFinishedFuture<>(new IgniteIllegalStateException(OP_REJECT_MSG + - " The snapshot deletion feature isn't activated yet [req=" + req + ']')); + "The snapshot deletion feature isn't activated yet [req=" + req + ']')); + } + + File path = null; + + if (!F.isEmpty(req.snpPath)) { + path = new File(req.snpPath); + + if (!path.isAbsolute()) + path = new File(kctx.pdsFolderResolver().fileTree().snapshotsRoot(), req.snpPath); + + String pathValidationErr = validateAbsoluteSnapshotRoot(path); + + if (pathValidationErr != null) { + return new GridFinishedFuture<>(new IllegalArgumentException(OP_REJECT_MSG + + pathValidationErr + " [req=" + req + ']')); + } } try { @@ -157,11 +177,15 @@ private IgniteInternalFuture deletePhase(UUID ignored, S GridFutureAdapter reqLocFut = new GridFutureAdapter<>(); + File path0 = path; + kctx.pools().getSnapshotExecutorService().submit(() -> { try { AtomicBoolean foundFlag = new AtomicBoolean(); - boolean deleted = snpMgr.deleteLocalSnapshot(new SnapshotFileTree(kctx, req.snpName, req.snpPath), foundFlag); + var sft = new SnapshotFileTree(kctx, req.snpName, path0 == null ? null : path0.getAbsolutePath()); + + boolean deleted = snpMgr.deleteLocalSnapshot(sft, foundFlag); SnapshotDeleteResponse.SnapshotDeleteStatus res; @@ -203,6 +227,29 @@ else if (!deleted) } } + /** */ + private @Nullable String validateAbsoluteSnapshotRoot(@Nullable File path) { + if (path == null) + return null; + + assert path.isAbsolute(); + + var ignWorkRoot = kctx.pdsFolderResolver().fileTree(); + var ignWorkRootStr = kctx.pdsFolderResolver().fileTree().root().getAbsolutePath(); + var pathStr = path.getAbsolutePath(); + + if (pathStr.startsWith(ignWorkRootStr) && !pathStr.startsWith(ignWorkRoot.snapshotsRoot().getAbsolutePath())) + return "belongs to Ignite's working directory"; + + if (!path.exists()) + return SNP_PATH_ERR_PREF + "doesn't exist"; + + if (!path.isDirectory()) + return SNP_PATH_ERR_PREF + "is not a directory"; + + return null; + } + /** */ private void reducePhase(UUID reqId, Map results, Map errors) { var clusterOpFut = clusterOpFuts.get(reqId); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java index f1c840883b98f..3b3063bdc88a4 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java @@ -161,6 +161,93 @@ public void testUncompletedNodes() throws Exception { assertTrue(delSnpRes.uncompletedNodes.contains(grid(1).localNode().id())); } + /** Test delete snapshot directly in Ignite. */ + @Test + public void testDeletionInIgnite() throws Exception { + /** No need to multiply this test. */ + assumeFalse(onlyPrimary || encryption || incremental); + + startGridsMultiThreaded(3); + + var ignRootWork = grid(0).context().pdsFolderResolver().fileTree(); + + IgniteSnapshotManager snp = snp(grid(0)); + + var sep = File.separator; + + var belongsToErrMsg = "belongs to Ignite's working directory"; + + assertThrowsAnyCause( + null, + () -> snp.deleteSnapshot(SNAPSHOT_NAME, ignRootWork.root().getAbsolutePath() + ';') + .get(getTestTimeout()), + IllegalArgumentException.class, + belongsToErrMsg + ); + + assertThrowsAnyCause( + null, + () -> snp.deleteSnapshot(SNAPSHOT_NAME, ignRootWork.root().getAbsolutePath()).get(getTestTimeout()), + IllegalArgumentException.class, + belongsToErrMsg + ); + + assertThrowsAnyCause( + null, + () -> snp.deleteSnapshot(SNAPSHOT_NAME, ignRootWork.root().getAbsolutePath() + sep) + .get(getTestTimeout()), + IllegalArgumentException.class, + belongsToErrMsg + ); + + assertThrowsAnyCause( + null, + () -> snp.deleteSnapshot(SNAPSHOT_NAME, ignRootWork.root().getAbsolutePath() + sep + sep) + .get(getTestTimeout()), + IllegalArgumentException.class, + belongsToErrMsg + ); + + assertThrowsAnyCause( + null, + () -> snp.deleteSnapshot(SNAPSHOT_NAME, ignRootWork.root().getAbsolutePath() + '_' + UUID.randomUUID()) + .get(getTestTimeout()), + IllegalArgumentException.class, + belongsToErrMsg + ); + + assertThrowsAnyCause( + null, + () -> snp.deleteSnapshot(SNAPSHOT_NAME, ignRootWork.root().getAbsolutePath() + sep + UUID.randomUUID()) + .get(getTestTimeout()), + IllegalArgumentException.class, + belongsToErrMsg + ); + + for(var ignSubRoot : ignRootWork.root().listFiles()) { + assert ignSubRoot.isDirectory(); + + if(ignSubRoot.compareTo(ignRootWork.snapshotsRoot()) == 0) + continue; + + assertThrowsAnyCause( + null, + () -> snp.deleteSnapshot(SNAPSHOT_NAME, ignSubRoot.getAbsolutePath()) + .get(getTestTimeout()), + IllegalArgumentException.class, + belongsToErrMsg + ); + + assertThrowsAnyCause( + null, + () -> snp.deleteSnapshot(SNAPSHOT_NAME, ignSubRoot.getAbsolutePath() + sep + UUID.randomUUID()) + .get(getTestTimeout()), + IllegalArgumentException.class, + belongsToErrMsg + ); + } + } + /** Tests snapshot deletion when one node has no snapshot data. */ @Test public void testEmptyNodes() throws Exception { From d30d4f9cf4cc4fcee90e7af2d08e46fa027a5974 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Fri, 18 Sep 2026 11:23:52 +0300 Subject: [PATCH 34/97] in-ignite deleteion test fix --- .../persistence/filename/NodeFileTree.java | 38 +++--- .../persistence/filename/SharedFileTree.java | 20 ++- .../snapshot/SnapshotDeleteProcess.java | 28 ++-- .../IgniteClusterSnapshotDeleteTest.java | 121 +++++++++--------- 4 files changed, 113 insertions(+), 94 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/NodeFileTree.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/NodeFileTree.java index 159eeb77db7c9..96c03fd4573a1 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/NodeFileTree.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/NodeFileTree.java @@ -333,12 +333,12 @@ public NodeFileTree(File root, String folderName) { this.folderName = folderName; - binaryMeta = new File(binaryMetaRoot, folderName); - nodeStorage = rootRelative(DB_DIR); - checkpoint = new File(nodeStorage, CHECKPOINT_DIR); - wal = rootRelative(DFLT_WAL_PATH); - walArchive = rootRelative(DFLT_WAL_ARCHIVE_PATH); - walCdc = rootRelative(DFLT_WAL_CDC_PATH); + all.add(binaryMeta = new File(binaryMetaRoot, folderName)); + all.add(nodeStorage = rootRelative(DB_DIR)); + all.add(checkpoint = new File(nodeStorage, CHECKPOINT_DIR)); + all.add(wal = rootRelative(DFLT_WAL_PATH)); + all.add(walArchive = rootRelative(DFLT_WAL_ARCHIVE_PATH)); + all.add(walCdc = rootRelative(DFLT_WAL_CDC_PATH)); extraStorages = Collections.emptyMap(); } @@ -381,29 +381,31 @@ protected NodeFileTree(IgniteConfiguration cfg, File root, String folderName, bo this.folderName = folderName; - binaryMeta = new File(binaryMetaRoot, folderName); + all.add(binaryMeta = new File(binaryMetaRoot, folderName)); DataStorageConfiguration dsCfg = cfg.getDataStorageConfiguration(); if (CU.isPersistenceEnabled(cfg) || CU.isCdcEnabled(cfg)) { // Snapshots MUST use root relative node storage path. - nodeStorage = (dsCfg.getStoragePath() == null || isSnapshot) + all.add(nodeStorage = (dsCfg.getStoragePath() == null || isSnapshot) ? rootRelative(DB_DIR) - : resolveDirectory(dsCfg.getStoragePath()); - checkpoint = new File(nodeStorage, CHECKPOINT_DIR); - wal = resolveDirectory(dsCfg.getWalPath()); - walArchive = resolveDirectory(dsCfg.getWalArchivePath()); - walCdc = resolveDirectory(dsCfg.getCdcWalPath()); + : resolveDirectory(dsCfg.getStoragePath())); + all.add(checkpoint = new File(nodeStorage, CHECKPOINT_DIR)); + all.add(wal = resolveDirectory(dsCfg.getWalPath())); + all.add(walArchive = resolveDirectory(dsCfg.getWalArchivePath())); + all.add(walCdc = resolveDirectory(dsCfg.getCdcWalPath())); } else { - nodeStorage = rootRelative(DB_DIR); - checkpoint = new File(nodeStorage, CHECKPOINT_DIR); - wal = rootRelative(DFLT_WAL_PATH); - walArchive = rootRelative(DFLT_WAL_ARCHIVE_PATH); - walCdc = rootRelative(DFLT_WAL_CDC_PATH); + all.add(nodeStorage = rootRelative(DB_DIR)); + all.add(checkpoint = new File(nodeStorage, CHECKPOINT_DIR)); + all.add(wal = rootRelative(DFLT_WAL_PATH)); + all.add(walArchive = rootRelative(DFLT_WAL_ARCHIVE_PATH)); + all.add(walCdc = rootRelative(DFLT_WAL_CDC_PATH)); } extraStorages = extraStorages(dsCfg); + + all.addAll(extraStorages.values()); } /** @return Node storage directory. */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SharedFileTree.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SharedFileTree.java index 33df1ed636040..75a9562c4e4a3 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SharedFileTree.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SharedFileTree.java @@ -19,6 +19,10 @@ import java.io.File; import java.nio.file.Paths; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteException; import org.apache.ignite.configuration.IgniteConfiguration; @@ -63,6 +67,9 @@ public class SharedFileTree { /** Path to the snapshot root directory. */ private final File snpsRoot; + /** All knows working pathes. */ + protected final Set all = new HashSet<>(); + /** * @param root Root directory. * @param snpsRoot Snapshot path. @@ -70,13 +77,13 @@ public class SharedFileTree { protected SharedFileTree(File root, String snpsRoot) { A.notNull(root, "Root directory"); - this.root = root; - this.snpsRoot = resolveDirectory(snpsRoot); + all.add(this.root = root); + all.add(this.snpsRoot = resolveDirectory(snpsRoot)); String rootStr = root.getAbsolutePath(); - marshaller = Paths.get(rootStr, DB_DIR, MARSHALLER_DIR).toFile(); - binaryMetaRoot = Paths.get(rootStr, DB_DIR, BINARY_METADATA_DIR).toFile(); + all.add(marshaller = Paths.get(rootStr, DB_DIR, MARSHALLER_DIR).toFile()); + all.add(binaryMetaRoot = Paths.get(rootStr, DB_DIR, BINARY_METADATA_DIR).toFile()); } /** @@ -132,6 +139,11 @@ public File snapshotsRoot() { return snpsRoot; } + /** @return All known main pathes used. */ + public Collection all() { + return Collections.unmodifiableSet(all); + } + /** * Creates {@link #binaryMetaRoot()} directory. * @return Created directory. diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index 551b37e9c626b..cc1c0066f4bb2 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -18,6 +18,7 @@ package org.apache.ignite.internal.processors.cache.persistence.snapshot; import java.io.File; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Map; import java.util.Set; @@ -165,7 +166,7 @@ private IgniteInternalFuture deletePhase(UUID ignored, S if (pathValidationErr != null) { return new GridFinishedFuture<>(new IllegalArgumentException(OP_REJECT_MSG + - pathValidationErr + " [req=" + req + ']')); + SNP_PATH_ERR_PREF + pathValidationErr + " [req=" + req + ']')); } } @@ -234,18 +235,21 @@ else if (!deleted) assert path.isAbsolute(); - var ignWorkRoot = kctx.pdsFolderResolver().fileTree(); - var ignWorkRootStr = kctx.pdsFolderResolver().fileTree().root().getAbsolutePath(); - var pathStr = path.getAbsolutePath(); + var ignFileTree = kctx.pdsFolderResolver().fileTree(); - if (pathStr.startsWith(ignWorkRootStr) && !pathStr.startsWith(ignWorkRoot.snapshotsRoot().getAbsolutePath())) - return "belongs to Ignite's working directory"; + for (var ignPath : ignFileTree.all()) { + if (contains(ignPath, path)) { + return ignPath.equals(ignFileTree.snapshotsRoot()) + ? null + : "belongs to a an Ignite's directory"; + } + } if (!path.exists()) - return SNP_PATH_ERR_PREF + "doesn't exist"; + return "doesn't exist"; if (!path.isDirectory()) - return SNP_PATH_ERR_PREF + "is not a directory"; + return "is not a directory"; return null; } @@ -322,4 +326,12 @@ void interrupt(Throwable err) { clusterOpFuts.clear(); } + + /** */ + public static boolean contains(File root, File candidate) { + Path root0 = root.toPath().toAbsolutePath().normalize(); + Path candidate0 = candidate.toPath().toAbsolutePath().normalize(); + + return candidate0.startsWith(root0); + } } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java index 3b3063bdc88a4..19b07848d9cef 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java @@ -20,6 +20,7 @@ import java.io.File; import java.util.ArrayList; import java.util.Collection; +import java.util.List; import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -37,6 +38,7 @@ import org.apache.ignite.internal.util.future.IgniteFutureImpl; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.G; +import org.apache.ignite.internal.util.typedef.X; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.lang.IgniteFuture; import org.apache.ignite.plugin.AbstractTestPluginProvider; @@ -169,83 +171,74 @@ public void testDeletionInIgnite() throws Exception { startGridsMultiThreaded(3); - var ignRootWork = grid(0).context().pdsFolderResolver().fileTree(); + List dirsToTest = new ArrayList<>(30); - IgniteSnapshotManager snp = snp(grid(0)); + var ignFileTree = grid(0).context().pdsFolderResolver().fileTree(); - var sep = File.separator; + dirsToTest.addAll(ignFileTree.all().stream().filter(f -> f.compareTo(ignFileTree.snapshotsRoot()) != 0) + .map(File::getAbsolutePath).toList()); - var belongsToErrMsg = "belongs to Ignite's working directory"; + IgniteSnapshotManager snpMgr = snp(grid(0)); + var fileSep = File.separator; + var belongsToErrMsg = "belongs to a an Ignite's directory"; - assertThrowsAnyCause( - null, - () -> snp.deleteSnapshot(SNAPSHOT_NAME, ignRootWork.root().getAbsolutePath() + ';') - .get(getTestTimeout()), - IllegalArgumentException.class, - belongsToErrMsg - ); + for (var dir : dirsToTest) { + List tests = new ArrayList<>(20); - assertThrowsAnyCause( - null, - () -> snp.deleteSnapshot(SNAPSHOT_NAME, ignRootWork.root().getAbsolutePath()).get(getTestTimeout()), - IllegalArgumentException.class, - belongsToErrMsg - ); + tests.add(dir + fileSep); + tests.add(dir + fileSep + fileSep); + tests.add(dir.replaceAll(fileSep, fileSep + fileSep)); + tests.add(dir + fileSep + "unexisting"); - assertThrowsAnyCause( - null, - () -> snp.deleteSnapshot(SNAPSHOT_NAME, ignRootWork.root().getAbsolutePath() + sep) - .get(getTestTimeout()), - IllegalArgumentException.class, - belongsToErrMsg - ); + for (var test : tests) { + if (log.isInfoEnabled()) + log.info("Testing path: " + test); - assertThrowsAnyCause( - null, - () -> snp.deleteSnapshot(SNAPSHOT_NAME, ignRootWork.root().getAbsolutePath() + sep + sep) - .get(getTestTimeout()), - IllegalArgumentException.class, - belongsToErrMsg - ); + assertThrowsAnyCause( + null, + () -> snpMgr.deleteSnapshot(SNAPSHOT_NAME, test).get(getTestTimeout()), + IllegalArgumentException.class, + belongsToErrMsg + ); + } - assertThrowsAnyCause( - null, - () -> snp.deleteSnapshot(SNAPSHOT_NAME, ignRootWork.root().getAbsolutePath() + '_' + UUID.randomUUID()) - .get(getTestTimeout()), - IllegalArgumentException.class, - belongsToErrMsg - ); + tests.clear(); + tests.add(dir + File.pathSeparator); + tests.add(dir + "_unexisting"); - assertThrowsAnyCause( - null, - () -> snp.deleteSnapshot(SNAPSHOT_NAME, ignRootWork.root().getAbsolutePath() + sep + UUID.randomUUID()) - .get(getTestTimeout()), - IllegalArgumentException.class, - belongsToErrMsg - ); - - for(var ignSubRoot : ignRootWork.root().listFiles()) { - assert ignSubRoot.isDirectory(); + for (var test : tests) { + if (log.isInfoEnabled()) + log.info("Testing path: " + test); - if(ignSubRoot.compareTo(ignRootWork.snapshotsRoot()) == 0) - continue; + try { + snpMgr.deleteSnapshot(SNAPSHOT_NAME, test).get(getTestTimeout()); - assertThrowsAnyCause( - null, - () -> snp.deleteSnapshot(SNAPSHOT_NAME, ignSubRoot.getAbsolutePath()) - .get(getTestTimeout()), - IllegalArgumentException.class, - belongsToErrMsg - ); + throw new IllegalStateException("An exception wasn't thrown."); + } + catch (Exception e) { + var m = e.getMessage(); - assertThrowsAnyCause( - null, - () -> snp.deleteSnapshot(SNAPSHOT_NAME, ignSubRoot.getAbsolutePath() + sep + UUID.randomUUID()) - .get(getTestTimeout()), - IllegalArgumentException.class, - belongsToErrMsg - ); + if (!X.hasCause(e, IllegalArgumentException.class) + || (!m.contains(belongsToErrMsg) && !m.contains("Provided snapshot path doesn't exist")) + ) + throw new IllegalStateException("Unexpected exception.", e); + } + } } + + var snpRoot = ignFileTree.snapshotsRoot(); + + var delRes = snpMgr.deleteSnapshot(SNAPSHOT_NAME, snpRoot.getAbsolutePath()).get(getTestTimeout()); + + assertTrue(F.isEmpty(delRes.completedNodes)); + assertTrue(F.isEmpty(delRes.uncompletedNodes)); + assertEquals(3, delRes.emptyNodes.size()); + + delRes = snpMgr.deleteSnapshot(SNAPSHOT_NAME, new File(snpRoot, "unexisting").getAbsolutePath()).get(getTestTimeout()); + + assertTrue(F.isEmpty(delRes.completedNodes)); + assertTrue(F.isEmpty(delRes.uncompletedNodes)); + assertEquals(3, delRes.emptyNodes.size()); } /** Tests snapshot deletion when one node has no snapshot data. */ From 852904fe94d188743a578fed69e790e641b4691e Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Fri, 18 Sep 2026 18:02:24 +0300 Subject: [PATCH 35/97] Major fix of testDeleteNotSnapshotSharedDirectory(), reimpl of snp tree deletion --- .../snapshot/IgniteSnapshotManager.java | 84 ++++++++----- .../snapshot/SnapshotDeleteProcess.java | 117 +++++++++++++----- .../snapshot/SnapshotDeleteResponse.java | 16 +-- .../SnapshotMetadataVerificationTask.java | 5 +- .../snapshot/AbstractSnapshotSelfTest.java | 8 +- .../IgniteClusterSnapshotDeleteTest.java | 91 +++++++++++++- 6 files changed, 242 insertions(+), 79 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java index a389fb1349cd9..98a9d8e6b2afb 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java @@ -30,6 +30,7 @@ import java.nio.channels.FileChannel; import java.nio.file.FileVisitResult; import java.nio.file.Files; +import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.StandardCopyOption; @@ -723,35 +724,55 @@ public void deleteLocalSnapshot(File snpDir) { pdsSettings.consistentId().toString() ); - deleteLocalSnapshot(sft); - } - - /** - * Deletes local shapshot data. - * - * @param sft Snapshot file tree - */ - public boolean deleteLocalSnapshot(SnapshotFileTree sft) { - return deleteLocalSnapshot(sft, null); + deleteLocalSnapshot(sft, ft.folderName(), null); } /** * Deletes local shapshot data. * * @param sft Snapshot file tree + * @param nodeFolderName Exact node's data subdirectory name usually taken from the consistent id. * @param existsFlag Flag to set {@code true} if any snapshot file or directory was found (existed). If {@code null}, ignored. - * @return {@code True}, if data is found and completely deleted or if no data found; - * {@code False}, if data is found but might not be deleted completely or if was canceled. + * @return {@code True}, if data is found and completely deleted; + * {@code False}, if nothing found or if data is found but might not be deleted completely. */ - public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean existsFlag) { + public boolean deleteLocalSnapshot(SnapshotFileTree sft, String nodeFolderName, @Nullable AtomicBoolean existsFlag) { + if (existsFlag != null) + existsFlag.set(sft.root().exists()); + + if (!sft.root().exists()) + return false; + AtomicBoolean res = new AtomicBoolean(true); - sft.allStorages().forEach(f -> deleteAndCheckExisted(f, res, existsFlag)); + for (var dir : F.asList(sft.binaryMeta(), sft.binaryMetaRoot(), sft.marshaller(), sft.db())) { + if (!dir.exists()) + continue; + + var nodeDir = new File(dir, nodeFolderName); + + if (nodeDir.exists()) + deleteSnapshotDataCompletely(nodeDir, res); + + // Recheck for the case of concurrent deletion. + try { + Files.delete(dir.toPath()); + } + catch (NoSuchFileException ne) { + // No-op: someone else deleted. + } + catch (Exception e) { + res.set(false); + } + } + + deleteSnapshotDataCompletely(sft.meta(), res); - deleteAndCheckExisted(sft.binaryMeta(), res, existsFlag); - deleteAndCheckExisted(sft.binaryMetaRoot(), res, existsFlag); - deleteAndCheckExisted(sft.marshaller(), res, existsFlag); - deleteAndCheckExisted(sft.root(), res, existsFlag); + try { + Files.delete(sft.root().toPath()); + } catch (Exception e) { + res.set(false); + } return res.get(); } @@ -761,29 +782,26 @@ public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean * * @param f File/directory to delete. If {@code null}, does nothing. * @param failRes Is set to {@code False} if at least one existed file or directory was denied to delete. - * @param existsFlag Is set to {@code True} if not {@code null} and if at least one file or directory was found (existed). */ - private void deleteAndCheckExisted(@Nullable File f, AtomicBoolean failRes, @Nullable AtomicBoolean existsFlag) { - if (f == null) + private void deleteSnapshotDataCompletely(@Nullable File f, AtomicBoolean failRes) { + if (f == null || !f.exists()) return; - boolean existed = f.exists(); - - if (existed && existsFlag != null) - existsFlag.set(true); - try { - // Additionally checks the existence for the case of concurrent deletion. - if (existed && ((!f.isDirectory() && !U.delete(f)) || (f.isDirectory() && !deleteDirectory(f))) && f.exists()) - failRes.set(false); + if (f.isDirectory()) { + if (!deleteDirectoryWithContent(f)) + failRes.set(false); + } + else + Files.delete(f.toPath()); } - catch (IOException e) { + catch (Exception e) { failRes.set(false); } } /** Concurrently traverse the directory and delete all files. */ - private boolean deleteDirectory(File dir) throws IOException { + private boolean deleteDirectoryWithContent(File dir) throws IOException { AtomicBoolean res = new AtomicBoolean(true); Files.walkFileTree(dir.toPath(), new SimpleFileVisitor<>() { @@ -1343,7 +1361,7 @@ private IgniteInternalFuture initLocalSnapshotEndStag if (snpStartReq.incremental()) U.delete(snpOp.snapshotFileTree().incrementalSnapshotFileTree(snpStartReq.incrementIndex()).root()); else - deleteLocalSnapshot(snpOp.snapshotFileTree()); + deleteLocalSnapshot(snpOp.snapshotFileTree(), cctx.kernalContext().pdsFolderResolver().fileTree().folderName(), null); } else if (!F.isEmpty(endReq.warnings())) { // Pass the warnings further to the next stage for the case when snapshot started from not coordinator. @@ -4039,7 +4057,7 @@ public LocalSnapshotSender(SnapshotFileTree sft) { log.info("The Local snapshot sender closed. All resources released [dbNodeSnpDir=" + sft.nodeStorage() + ']'); } else { - deleteLocalSnapshot(sft); + deleteLocalSnapshot(sft, cctx.kernalContext().pdsFolderResolver().fileTree().folderName(), null); if (log.isDebugEnabled()) log.debug("Local snapshot sender closed due to an error occurred: " + th.getMessage()); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index cc1c0066f4bb2..46349aaafc406 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -20,6 +20,7 @@ import java.io.File; import java.nio.file.Path; import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; @@ -32,11 +33,13 @@ import org.apache.ignite.internal.NodeStoppingException; import org.apache.ignite.internal.processors.cache.persistence.filename.SnapshotFileTree; import org.apache.ignite.internal.util.distributed.DistributedProcess; +import org.apache.ignite.internal.util.future.GridCompoundFuture; import org.apache.ignite.internal.util.future.GridFinishedFuture; import org.apache.ignite.internal.util.future.GridFutureAdapter; import org.apache.ignite.internal.util.future.IgniteFutureImpl; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.lang.IgniteFuture; +import org.apache.ignite.lang.IgniteReducer; import org.jetbrains.annotations.Nullable; import static org.apache.ignite.internal.processors.rollingupgrade.feature.SupportedFeatureRegistry.SNAPSHOT_DELETE_FEATURE; @@ -154,13 +157,14 @@ private IgniteInternalFuture deletePhase(UUID ignored, S "The snapshot deletion feature isn't activated yet [req=" + req + ']')); } - File path = null; + File path = kctx.pdsFolderResolver().fileTree().snapshotsRoot(); if (!F.isEmpty(req.snpPath)) { - path = new File(req.snpPath); + File reqPath = new File(req.snpPath); - if (!path.isAbsolute()) - path = new File(kctx.pdsFolderResolver().fileTree().snapshotsRoot(), req.snpPath); + path = reqPath.isAbsolute() + ? reqPath + : new File(path, req.snpPath); String pathValidationErr = validateAbsoluteSnapshotRoot(path); @@ -176,48 +180,73 @@ private IgniteInternalFuture deletePhase(UUID ignored, S "started [req=" + req + ']')); } - GridFutureAdapter reqLocFut = new GridFutureAdapter<>(); + SnapshotFileTree snpFiles = new SnapshotFileTree(kctx, req.snpName, path.getAbsolutePath()); + + // We need to find and read snapshot metas to ensure the content is a snapshot. Also, the metas contain + // initial cluster topology and actual snasphot folder names. + List locMetas = kctx.cache().context().snapshotMgr().readSnapshotMetadatas(snpFiles); + + if (locMetas.isEmpty()) { + log.warning("Snapshot deletion won't process, no snapshot metadata found [req=" + req + ']'); + + return new GridFinishedFuture<>(new SnapshotDeleteResponse(SnapshotDeleteResponse.SnapshotDeleteStatus.NOT_FOUND)); + } + + // Future to delete snapshot contents according to snapshot metadatas. + GridCompoundFuture resultFut = + new GridCompoundFuture<>(new MetaFuturesReducer()); + + resultFut.listen(fut->requests.remove(req)); File path0 = path; - kctx.pools().getSnapshotExecutorService().submit(() -> { - try { - AtomicBoolean foundFlag = new AtomicBoolean(); + for(var meta : locMetas) { + GridFutureAdapter perMetaFut = new GridFutureAdapter<>(); - var sft = new SnapshotFileTree(kctx, req.snpName, path0 == null ? null : path0.getAbsolutePath()); + kctx.pools().getSnapshotExecutorService().submit(() -> { + try { + AtomicBoolean foundFlag = new AtomicBoolean(); - boolean deleted = snpMgr.deleteLocalSnapshot(sft, foundFlag); + // Read file tree of the snapshot. + var byMetaSft = new SnapshotFileTree(kctx, req.snpName, path0.getAbsolutePath(), meta.folderName(), + meta.consId); - SnapshotDeleteResponse.SnapshotDeleteStatus res; + boolean deleted = snpMgr.deleteLocalSnapshot(byMetaSft, meta.folderName(), foundFlag); - if (foundFlag.get()) { - if (deleted && log.isInfoEnabled()) - log.info("Snapshot successfully deleted [req=" + req + ']'); - else if (!deleted) - log.warning("Snapshot deleted not completely [req=" + req + ']'); + SnapshotDeleteResponse.SnapshotDeleteStatus res; - res = deleted - ? SnapshotDeleteResponse.SnapshotDeleteStatus.DELETED - : SnapshotDeleteResponse.SnapshotDeleteStatus.PARTLY_DELETED; - } - else { - if (log.isInfoEnabled()) - log.info("Snapshot not found to delete [req=" + req + ']'); + if (foundFlag.get()) { + if (deleted && log.isInfoEnabled()) + log.info("Snapshot successfully deleted [req=" + req + ']'); + else if (!deleted) + log.warning("Snapshot deleted not completely [req=" + req + ']'); + + res = deleted + ? SnapshotDeleteResponse.SnapshotDeleteStatus.DELETED + : SnapshotDeleteResponse.SnapshotDeleteStatus.PARTLY_DELETED; + } + else { + if (log.isInfoEnabled()) + log.info("Snapshot not found to delete [req=" + req + ']'); + + res = SnapshotDeleteResponse.SnapshotDeleteStatus.NOT_FOUND; + } - res = SnapshotDeleteResponse.SnapshotDeleteStatus.NOT_FOUND; + perMetaFut.onDone(res); + } catch (Throwable e) { + perMetaFut.onDone(e); } + }); - reqLocFut.onDone(new SnapshotDeleteResponse(res)); - } - finally { - requests.remove(req); - } - }); + resultFut.add(perMetaFut); + } + + resultFut.markInitialized(); if (log.isInfoEnabled()) log.info("Deletion of snapshot initialized [req=" + req + ']'); - return reqLocFut; + return resultFut; } catch (Throwable t) { requests.remove(req); @@ -334,4 +363,30 @@ public static boolean contains(File root, File candidate) { return candidate0.startsWith(root0); } + + /** */ + private static class MetaFuturesReducer implements IgniteReducer { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** */ + private @Nullable SnapshotDeleteResponse.SnapshotDeleteStatus res = null; + + /** {@inheritDoc} */ + @Override public boolean collect(@Nullable SnapshotDeleteResponse.SnapshotDeleteStatus status) { + synchronized (this) { + if (res == null || res == status) + res = status; + else + res = SnapshotDeleteResponse.SnapshotDeleteStatus.PARTLY_DELETED; + } + + return true; + } + + /** {@inheritDoc} */ + @Override public SnapshotDeleteResponse reduce() { + return new SnapshotDeleteResponse(res == null ? SnapshotDeleteResponse.SnapshotDeleteStatus.NOT_FOUND : res); + } + } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java index adb6c66a096e2..917d23da2d3cb 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java @@ -31,7 +31,7 @@ public class SnapshotDeleteResponse implements Message { /** {@code null} for client node. */ @Order(0) - @Nullable SnapshotDeleteStatus res; + @Nullable SnapshotDeleteResponse.SnapshotDeleteStatus res; /** Default constructor for {@link MessageFactory}. */ public SnapshotDeleteResponse() { @@ -39,10 +39,15 @@ public SnapshotDeleteResponse() { } /** {@code null} for client node. */ - SnapshotDeleteResponse(@Nullable SnapshotDeleteStatus res) { + SnapshotDeleteResponse(@Nullable SnapshotDeleteResponse.SnapshotDeleteStatus res) { this.res = res; } + /** {@inheritDoc} */ + @Override public String toString() { + return S.toString(SnapshotDeleteResponse.class, this); + } + /** */ public enum SnapshotDeleteStatus { /** Snapshot found and completely deleted. */ @@ -52,11 +57,6 @@ public enum SnapshotDeleteStatus { PARTLY_DELETED, /** Snapshot not found. */ - NOT_FOUND - } - - /** {@inheritDoc} */ - @Override public String toString() { - return S.toString(SnapshotDeleteResponse.class, this); + NOT_FOUND; } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMetadataVerificationTask.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMetadataVerificationTask.java index 643838ac3c445..4a8fea2b43b73 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMetadataVerificationTask.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMetadataVerificationTask.java @@ -35,7 +35,10 @@ import org.apache.ignite.internal.processors.cache.persistence.wal.reader.IgniteWalIteratorFactory; import org.apache.ignite.internal.util.typedef.F; -/** Snapshot task to verify snapshot metadata on the baseline nodes for given snapshot name. */ +/** + * Snapshot task to verify snapshot metadata on the baseline nodes for given snapshot name. + * TODO : Revise in https://issues.apache.org/jira/browse/IGNITE-29062 + */ public class SnapshotMetadataVerificationTask implements Supplier> { /** */ private final IgniteEx ignite; diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java index 206bad2c7a63f..2c960ec62d49c 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java @@ -854,7 +854,11 @@ protected void doTestConcurrentSnapshotDeleteOperation( @Override public T createComponent(PluginContext ctx, Class cls) { if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { - @Override public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean existsFlag) { + @Override public boolean deleteLocalSnapshot( + SnapshotFileTree sft, + String nodeFolderName, + @Nullable AtomicBoolean existsFlag + ) { delProcInitLatch.countDown(); try { @@ -864,7 +868,7 @@ protected void doTestConcurrentSnapshotDeleteOperation( throw new RuntimeException("Interrupted.", e); } - return super.deleteLocalSnapshot(sft, existsFlag); + return super.deleteLocalSnapshot(sft, nodeFolderName, existsFlag); } }; } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java index 19b07848d9cef..5befd3af8adfd 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java @@ -133,14 +133,18 @@ public void testUncompletedNodes() throws Exception { @Override public T createComponent(PluginContext ctx, Class cls) { if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { - @Override public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean existsFlag) { + @Override public boolean deleteLocalSnapshot( + SnapshotFileTree sft, + String nodeFolderName, + @Nullable AtomicBoolean existsFlag + ) { if (ctx.localNode().id().equals(grid(1).localNode().id())) { existsFlag.set(true); return false; } - return super.deleteLocalSnapshot(sft, existsFlag); + return super.deleteLocalSnapshot(sft, nodeFolderName, existsFlag); } }; } @@ -163,6 +167,81 @@ public void testUncompletedNodes() throws Exception { assertTrue(delSnpRes.uncompletedNodes.contains(grid(1).localNode().id())); } + /** */ + @Test + public void testDeleteNotSnapshotSharedDirectory() throws Exception { + doTestDeleteNotSnapshot(false); + } + + /** */ + @Test + public void testDeleteNotSnapshotDedicatedDirectories() throws Exception { + doTestDeleteNotSnapshot(true); + } + + /** */ + protected void doTestDeleteNotSnapshot(boolean separatedWorkDir) throws Exception { + this.separatedWorkDir = separatedWorkDir; + + startGridsWithCache(3, CACHE_KEYS_RANGE, valueBuilder(), dfltCacheCfg); + + snp(grid(1)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(getTestTimeout()); + + var snpSft = new SnapshotFileTree(grid(1).context(), SNAPSHOT_NAME, null); + + // Ensure that all the snapshot node folders exist. + assertTrue(snpSft.binaryMeta().exists()); + assertTrue(new SnapshotFileTree(grid(0).context(), SNAPSHOT_NAME, null, folderName(0), consistentId(0)) + .binaryMeta().exists()); + assertTrue(new SnapshotFileTree(grid(2).context(), SNAPSHOT_NAME, null, folderName(2), consistentId(2)) + .binaryMeta().exists()); + + assertTrue(snpSft.meta().exists()); + assertTrue(U.delete(snpSft.meta())); + assertFalse(snpSft.meta().exists()); + + var delSnpRes = snp(grid(2)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); + + // Check the result. + if(separatedWorkDir) { + // One node doesn't find meta, decided not a snapshot. + assertTrue(F.isEmpty(delSnpRes.uncompletedNodes)); + assertEquals(2, delSnpRes.completedNodes.size()); + assertEquals(1, delSnpRes.emptyNodes.size()); + assertTrue(delSnpRes.emptyNodes.contains(grid(1).localNode().id())); + } else { + // All nodes may see some metas, may try to delete snapshot by the metas, but see that the snapshot directory isn't empty. + // Some node may not get any meta to process. But node of the nodes can say the snapshot 100% deleted. + assertTrue(F.isEmpty(delSnpRes.completedNodes)); + + int cnt = 0; + + if (!F.isEmpty(delSnpRes.emptyNodes)) + cnt += delSnpRes.emptyNodes.size(); + + if (!F.isEmpty(delSnpRes.uncompletedNodes)) + cnt += delSnpRes.uncompletedNodes.size(); + + assertEquals(3, cnt); + } + + assertTrue(snpSft.binaryMeta().exists()); + assertFalse(new SnapshotFileTree(grid(0).context(), SNAPSHOT_NAME, null, folderName(0), consistentId(0)) + .binaryMeta().exists()); + assertFalse(new SnapshotFileTree(grid(2).context(), SNAPSHOT_NAME, null, folderName(2), consistentId(2)) + .binaryMeta().exists()); + } + + /** */ + private String consistentId(int gridIdx) { + return grid(gridIdx).configuration().getConsistentId().toString(); + } + + /** */ + private String folderName(int gridIdx) { + return grid(gridIdx).context().pdsFolderResolver().fileTree().folderName(); + } + /** Test delete snapshot directly in Ignite. */ @Test public void testDeletionInIgnite() throws Exception { @@ -321,7 +400,11 @@ public void testNodeStopsInTheMiddle() throws Exception { @Override public T createComponent(PluginContext ctx, Class cls) { if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { - @Override public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean existsFlag) { + @Override public boolean deleteLocalSnapshot( + SnapshotFileTree sft, + String nodeFolderName, + @Nullable AtomicBoolean existsFlag + ) { if (ctx.localNode().id().equals(grid(1).localNode().id())) { beginLatch.countDown(); @@ -333,7 +416,7 @@ public void testNodeStopsInTheMiddle() throws Exception { } } - return super.deleteLocalSnapshot(sft, existsFlag); + return super.deleteLocalSnapshot(sft, nodeFolderName, existsFlag); } }; } From f416646594d3218ef9e592b84329ab4d677bd308 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Mon, 21 Sep 2026 13:46:47 +0300 Subject: [PATCH 36/97] + delete tests .Checkstyle --- .../filename/SnapshotFileTree.java | 26 +++++++++++++++ .../snapshot/IgniteSnapshotManager.java | 17 +++++++--- .../snapshot/SnapshotDeleteProcess.java | 7 ++-- .../IgniteClusterSnapshotDeleteTest.java | 33 +++++++++++++++++-- 4 files changed, 73 insertions(+), 10 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java index f53abc94ba480..5dba8081dd450 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java @@ -457,6 +457,32 @@ private NodeFileTree tempFileTree(GridKernalContext ctx) { return res; } + /** */ + public static boolean isSnapshotFile(File f) { + if (f.isDirectory()) + return true; + + int idx = f.getName().indexOf('.'); + + if (idx < 1) + return false; + + String ext = f.getName().substring(idx); + + switch (ext) { + case DELTA_IDX_SUFFIX: + case DELTA_SUFFIX: + case SNAPSHOT_METAFILE_EXT: + case DUMP_FILE_EXT: + case DUMP_LOCK: + case WAL_SEGMENT_FILE_EXT: + case FILE_SUFFIX: + case ".dat": + return true; + } + + return false; + } /** {@inheritDoc} */ @Override public String toString() { diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java index 98a9d8e6b2afb..3779d14b22b16 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java @@ -736,7 +736,8 @@ public void deleteLocalSnapshot(File snpDir) { * @return {@code True}, if data is found and completely deleted; * {@code False}, if nothing found or if data is found but might not be deleted completely. */ - public boolean deleteLocalSnapshot(SnapshotFileTree sft, String nodeFolderName, @Nullable AtomicBoolean existsFlag) { + public boolean deleteLocalSnapshot(SnapshotFileTree sft, String nodeFolderName, + @Nullable AtomicBoolean existsFlag) { if (existsFlag != null) existsFlag.set(sft.root().exists()); @@ -770,7 +771,8 @@ public boolean deleteLocalSnapshot(SnapshotFileTree sft, String nodeFolderName, try { Files.delete(sft.root().toPath()); - } catch (Exception e) { + } + catch (Exception e) { res.set(false); } @@ -784,7 +786,7 @@ public boolean deleteLocalSnapshot(SnapshotFileTree sft, String nodeFolderName, * @param failRes Is set to {@code False} if at least one existed file or directory was denied to delete. */ private void deleteSnapshotDataCompletely(@Nullable File f, AtomicBoolean failRes) { - if (f == null || !f.exists()) + if (f == null || !f.exists() || !SnapshotFileTree.isSnapshotFile(f)) return; try { @@ -1360,8 +1362,13 @@ private IgniteInternalFuture initLocalSnapshotEndStag if (snpStartReq.incremental()) U.delete(snpOp.snapshotFileTree().incrementalSnapshotFileTree(snpStartReq.incrementIndex()).root()); - else - deleteLocalSnapshot(snpOp.snapshotFileTree(), cctx.kernalContext().pdsFolderResolver().fileTree().folderName(), null); + else { + deleteLocalSnapshot( + snpOp.snapshotFileTree(), + cctx.kernalContext().pdsFolderResolver().fileTree().folderName(), + null + ); + } } else if (!F.isEmpty(endReq.warnings())) { // Pass the warnings further to the next stage for the case when snapshot started from not coordinator. diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index 46349aaafc406..4a970f1628d7e 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -196,11 +196,11 @@ private IgniteInternalFuture deletePhase(UUID ignored, S GridCompoundFuture resultFut = new GridCompoundFuture<>(new MetaFuturesReducer()); - resultFut.listen(fut->requests.remove(req)); + resultFut.listen(fut -> requests.remove(req)); File path0 = path; - for(var meta : locMetas) { + for (var meta : locMetas) { GridFutureAdapter perMetaFut = new GridFutureAdapter<>(); kctx.pools().getSnapshotExecutorService().submit(() -> { @@ -233,7 +233,8 @@ else if (!deleted) } perMetaFut.onDone(res); - } catch (Throwable e) { + } + catch (Throwable e) { perMetaFut.onDone(e); } }); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java index 5befd3af8adfd..655fb3c20c45b 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java @@ -18,6 +18,7 @@ package org.apache.ignite.internal.processors.cache.persistence.snapshot; import java.io.File; +import java.nio.file.Files; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -73,6 +74,9 @@ public class IgniteClusterSnapshotDeleteTest extends AbstractSnapshotSelfTest { @Parameter(2) public boolean incremental = true; + /** */ + private @Nullable String cstIdSuffix; + /** {@inheritDoc} */ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { var cfg = super.getConfiguration(igniteInstanceName); @@ -80,6 +84,9 @@ public class IgniteClusterSnapshotDeleteTest extends AbstractSnapshotSelfTest { if (separatedWorkDir) cfg.setWorkDirectory(new File(U.defaultWorkDirectory(), igniteInstanceName).getAbsolutePath()); + if (cstIdSuffix != null) + cfg.setConsistentId(cfg.getConsistentId().toString() + '_' + cstIdSuffix); + return cfg; } @@ -167,6 +174,27 @@ public void testUncompletedNodes() throws Exception { assertTrue(delSnpRes.uncompletedNodes.contains(grid(1).localNode().id())); } + /** */ + @Test + public void testDeleteOtherConsistentId() throws Exception { + startGridsWithSnapshot(3, CACHE_KEYS_RANGE, false); + + stopAllGrids(); + + cstIdSuffix = "_ext"; + + startGridsMultiThreaded(3); + + var delRes = snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); + + assertTrue(F.isEmpty(delRes.emptyNodes)); + + for (var ig : G.allGrids()) { + assertTrue(Files.list(((IgniteEx)ig).context().pdsFolderResolver().fileTree().snapshotsRoot().toPath()) + .findFirst().isEmpty()); + } + } + /** */ @Test public void testDeleteNotSnapshotSharedDirectory() throws Exception { @@ -203,13 +231,14 @@ protected void doTestDeleteNotSnapshot(boolean separatedWorkDir) throws Exceptio var delSnpRes = snp(grid(2)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); // Check the result. - if(separatedWorkDir) { + if (separatedWorkDir) { // One node doesn't find meta, decided not a snapshot. assertTrue(F.isEmpty(delSnpRes.uncompletedNodes)); assertEquals(2, delSnpRes.completedNodes.size()); assertEquals(1, delSnpRes.emptyNodes.size()); assertTrue(delSnpRes.emptyNodes.contains(grid(1).localNode().id())); - } else { + } + else { // All nodes may see some metas, may try to delete snapshot by the metas, but see that the snapshot directory isn't empty. // Some node may not get any meta to process. But node of the nodes can say the snapshot 100% deleted. assertTrue(F.isEmpty(delSnpRes.completedNodes)); From bb9d4869240cad346af2f1211562a7ba3a5fe580 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Mon, 21 Sep 2026 13:53:47 +0300 Subject: [PATCH 37/97] - all in node tree --- .../persistence/filename/NodeFileTree.java | 38 +++++++++---------- .../persistence/filename/SharedFileTree.java | 20 ++-------- .../snapshot/SnapshotDeleteProcess.java | 2 +- .../IgniteClusterSnapshotDeleteTest.java | 2 +- 4 files changed, 24 insertions(+), 38 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/NodeFileTree.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/NodeFileTree.java index 96c03fd4573a1..159eeb77db7c9 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/NodeFileTree.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/NodeFileTree.java @@ -333,12 +333,12 @@ public NodeFileTree(File root, String folderName) { this.folderName = folderName; - all.add(binaryMeta = new File(binaryMetaRoot, folderName)); - all.add(nodeStorage = rootRelative(DB_DIR)); - all.add(checkpoint = new File(nodeStorage, CHECKPOINT_DIR)); - all.add(wal = rootRelative(DFLT_WAL_PATH)); - all.add(walArchive = rootRelative(DFLT_WAL_ARCHIVE_PATH)); - all.add(walCdc = rootRelative(DFLT_WAL_CDC_PATH)); + binaryMeta = new File(binaryMetaRoot, folderName); + nodeStorage = rootRelative(DB_DIR); + checkpoint = new File(nodeStorage, CHECKPOINT_DIR); + wal = rootRelative(DFLT_WAL_PATH); + walArchive = rootRelative(DFLT_WAL_ARCHIVE_PATH); + walCdc = rootRelative(DFLT_WAL_CDC_PATH); extraStorages = Collections.emptyMap(); } @@ -381,31 +381,29 @@ protected NodeFileTree(IgniteConfiguration cfg, File root, String folderName, bo this.folderName = folderName; - all.add(binaryMeta = new File(binaryMetaRoot, folderName)); + binaryMeta = new File(binaryMetaRoot, folderName); DataStorageConfiguration dsCfg = cfg.getDataStorageConfiguration(); if (CU.isPersistenceEnabled(cfg) || CU.isCdcEnabled(cfg)) { // Snapshots MUST use root relative node storage path. - all.add(nodeStorage = (dsCfg.getStoragePath() == null || isSnapshot) + nodeStorage = (dsCfg.getStoragePath() == null || isSnapshot) ? rootRelative(DB_DIR) - : resolveDirectory(dsCfg.getStoragePath())); - all.add(checkpoint = new File(nodeStorage, CHECKPOINT_DIR)); - all.add(wal = resolveDirectory(dsCfg.getWalPath())); - all.add(walArchive = resolveDirectory(dsCfg.getWalArchivePath())); - all.add(walCdc = resolveDirectory(dsCfg.getCdcWalPath())); + : resolveDirectory(dsCfg.getStoragePath()); + checkpoint = new File(nodeStorage, CHECKPOINT_DIR); + wal = resolveDirectory(dsCfg.getWalPath()); + walArchive = resolveDirectory(dsCfg.getWalArchivePath()); + walCdc = resolveDirectory(dsCfg.getCdcWalPath()); } else { - all.add(nodeStorage = rootRelative(DB_DIR)); - all.add(checkpoint = new File(nodeStorage, CHECKPOINT_DIR)); - all.add(wal = rootRelative(DFLT_WAL_PATH)); - all.add(walArchive = rootRelative(DFLT_WAL_ARCHIVE_PATH)); - all.add(walCdc = rootRelative(DFLT_WAL_CDC_PATH)); + nodeStorage = rootRelative(DB_DIR); + checkpoint = new File(nodeStorage, CHECKPOINT_DIR); + wal = rootRelative(DFLT_WAL_PATH); + walArchive = rootRelative(DFLT_WAL_ARCHIVE_PATH); + walCdc = rootRelative(DFLT_WAL_CDC_PATH); } extraStorages = extraStorages(dsCfg); - - all.addAll(extraStorages.values()); } /** @return Node storage directory. */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SharedFileTree.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SharedFileTree.java index 75a9562c4e4a3..33df1ed636040 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SharedFileTree.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SharedFileTree.java @@ -19,10 +19,6 @@ import java.io.File; import java.nio.file.Paths; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.Set; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteException; import org.apache.ignite.configuration.IgniteConfiguration; @@ -67,9 +63,6 @@ public class SharedFileTree { /** Path to the snapshot root directory. */ private final File snpsRoot; - /** All knows working pathes. */ - protected final Set all = new HashSet<>(); - /** * @param root Root directory. * @param snpsRoot Snapshot path. @@ -77,13 +70,13 @@ public class SharedFileTree { protected SharedFileTree(File root, String snpsRoot) { A.notNull(root, "Root directory"); - all.add(this.root = root); - all.add(this.snpsRoot = resolveDirectory(snpsRoot)); + this.root = root; + this.snpsRoot = resolveDirectory(snpsRoot); String rootStr = root.getAbsolutePath(); - all.add(marshaller = Paths.get(rootStr, DB_DIR, MARSHALLER_DIR).toFile()); - all.add(binaryMetaRoot = Paths.get(rootStr, DB_DIR, BINARY_METADATA_DIR).toFile()); + marshaller = Paths.get(rootStr, DB_DIR, MARSHALLER_DIR).toFile(); + binaryMetaRoot = Paths.get(rootStr, DB_DIR, BINARY_METADATA_DIR).toFile(); } /** @@ -139,11 +132,6 @@ public File snapshotsRoot() { return snpsRoot; } - /** @return All known main pathes used. */ - public Collection all() { - return Collections.unmodifiableSet(all); - } - /** * Creates {@link #binaryMetaRoot()} directory. * @return Created directory. diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index 4a970f1628d7e..caa3e10f13f6b 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -267,7 +267,7 @@ else if (!deleted) var ignFileTree = kctx.pdsFolderResolver().fileTree(); - for (var ignPath : ignFileTree.all()) { + for (var ignPath : ignFileTree.allStorages().toList()) { if (contains(ignPath, path)) { return ignPath.equals(ignFileTree.snapshotsRoot()) ? null diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java index 655fb3c20c45b..dd67d02fda6f2 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java @@ -283,7 +283,7 @@ public void testDeletionInIgnite() throws Exception { var ignFileTree = grid(0).context().pdsFolderResolver().fileTree(); - dirsToTest.addAll(ignFileTree.all().stream().filter(f -> f.compareTo(ignFileTree.snapshotsRoot()) != 0) + dirsToTest.addAll(ignFileTree.allStorages().filter(f -> f.compareTo(ignFileTree.snapshotsRoot()) != 0) .map(File::getAbsolutePath).toList()); IgniteSnapshotManager snpMgr = snp(grid(0)); From eae08929c97fbfc4192da59718ffb5e6be058972 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Mon, 21 Sep 2026 15:11:20 +0300 Subject: [PATCH 38/97] better delete tests --- .../snapshot/IgniteSnapshotManager.java | 2 +- .../snapshot/SnapshotDeleteProcess.java | 15 +++--- .../IgniteClusterSnapshotDeleteTest.java | 50 +++++++++---------- 3 files changed, 33 insertions(+), 34 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java index 3779d14b22b16..0b1f223074ca7 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java @@ -1536,7 +1536,7 @@ public boolean isSnapshotChecking(String snpName) { * @return {@code True} if a snapshot {@code snpName} delete operation is in progress. */ public boolean isSnapshotDeleting(String snpName, @Nullable String snpPath) { - return deleteSnpProc.isSnapshotDeleting(snpName, snpPath); + return deleteSnpProc.isDeleting(snpName, snpPath); } /** diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index caa3e10f13f6b..2fa0faf7fefb6 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -32,6 +32,7 @@ import org.apache.ignite.internal.IgniteInternalFuture; import org.apache.ignite.internal.NodeStoppingException; import org.apache.ignite.internal.processors.cache.persistence.filename.SnapshotFileTree; +import org.apache.ignite.internal.util.CommonUtils; import org.apache.ignite.internal.util.distributed.DistributedProcess; import org.apache.ignite.internal.util.future.GridCompoundFuture; import org.apache.ignite.internal.util.future.GridFinishedFuture; @@ -267,12 +268,12 @@ else if (!deleted) var ignFileTree = kctx.pdsFolderResolver().fileTree(); - for (var ignPath : ignFileTree.allStorages().toList()) { - if (contains(ignPath, path)) { - return ignPath.equals(ignFileTree.snapshotsRoot()) - ? null - : "belongs to a an Ignite's directory"; - } + if (ignFileTree.snapshotsRoot().compareTo(path) == 0 || contains(ignFileTree.snapshotsRoot(), path)) + return null; + + for (var ignPath : List.of(new File(CommonUtils.getIgniteHome()), ignFileTree.root())) { + if (ignPath.compareTo(path) == 0 || contains(ignPath, path)) + return "belongs to a an Ignite's directory"; } if (!path.exists()) @@ -340,7 +341,7 @@ private void reducePhase(UUID reqId, Map results, } /** */ - public boolean isSnapshotDeleting(String snpName, @Nullable String snpPath) { + public boolean isDeleting(String snpName, @Nullable String snpPath) { return requests.contains(new SnapshotDeleteRequest(null, snpName, snpPath)); } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java index dd67d02fda6f2..bde5ae462ccde 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java @@ -34,12 +34,12 @@ import org.apache.ignite.internal.processors.cache.persistence.file.FileIO; import org.apache.ignite.internal.processors.cache.persistence.file.RandomAccessFileIOFactory; import org.apache.ignite.internal.processors.cache.persistence.filename.SnapshotFileTree; +import org.apache.ignite.internal.util.CommonUtils; import org.apache.ignite.internal.util.distributed.DistributedProcess; import org.apache.ignite.internal.util.distributed.SingleNodeMessage; import org.apache.ignite.internal.util.future.IgniteFutureImpl; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.G; -import org.apache.ignite.internal.util.typedef.X; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.lang.IgniteFuture; import org.apache.ignite.plugin.AbstractTestPluginProvider; @@ -283,8 +283,17 @@ public void testDeletionInIgnite() throws Exception { var ignFileTree = grid(0).context().pdsFolderResolver().fileTree(); - dirsToTest.addAll(ignFileTree.allStorages().filter(f -> f.compareTo(ignFileTree.snapshotsRoot()) != 0) - .map(File::getAbsolutePath).toList()); + dirsToTest.add(CommonUtils.getIgniteHome()); + dirsToTest.add(ignFileTree.root().getAbsolutePath()); + + ignFileTree.allStorages().forEach(s -> { + for (var sub : s.listFiles()) { + if (!sub.isDirectory() || sub.compareTo(ignFileTree.snapshotsRoot()) == 0) + continue; + + dirsToTest.add(sub.getAbsolutePath()); + } + }); IgniteSnapshotManager snpMgr = snp(grid(0)); var fileSep = File.separator; @@ -294,8 +303,8 @@ public void testDeletionInIgnite() throws Exception { List tests = new ArrayList<>(20); tests.add(dir + fileSep); - tests.add(dir + fileSep + fileSep); - tests.add(dir.replaceAll(fileSep, fileSep + fileSep)); + // Double path separator. + tests.add(dir.replaceAll("\\".equals(fileSep) ? "\\\\" : fileSep, "\\".equals(fileSep) ? "\\\\\\\\" : fileSep)); tests.add(dir + fileSep + "unexisting"); for (var test : tests) { @@ -309,30 +318,19 @@ public void testDeletionInIgnite() throws Exception { belongsToErrMsg ); } + } - tests.clear(); - tests.add(dir + File.pathSeparator); - tests.add(dir + "_unexisting"); + var testDir = new File(fileSep + "unexisting_" + UUID.randomUUID()).getAbsolutePath(); - for (var test : tests) { - if (log.isInfoEnabled()) - log.info("Testing path: " + test); - - try { - snpMgr.deleteSnapshot(SNAPSHOT_NAME, test).get(getTestTimeout()); + if (log.isInfoEnabled()) + log.info("Testing path: " + testDir); - throw new IllegalStateException("An exception wasn't thrown."); - } - catch (Exception e) { - var m = e.getMessage(); - - if (!X.hasCause(e, IllegalArgumentException.class) - || (!m.contains(belongsToErrMsg) && !m.contains("Provided snapshot path doesn't exist")) - ) - throw new IllegalStateException("Unexpected exception.", e); - } - } - } + assertThrowsAnyCause( + null, + () -> snpMgr.deleteSnapshot(SNAPSHOT_NAME, testDir).get(getTestTimeout()), + IllegalArgumentException.class, + "snapshot path doesn't exist" + ); var snpRoot = ignFileTree.snapshotsRoot(); From c04edd7c17cbe47f6fe70f1b121d6c4c4001ab60 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Mon, 21 Sep 2026 19:27:31 +0300 Subject: [PATCH 39/97] fixes --- .../snapshot/IgniteSnapshotManager.java | 33 ++++++++++++++++--- .../snapshot/SnapshotDeleteRequest.java | 2 +- ...shotCreationNonDefaultStoragePathTest.java | 3 +- .../snapshot/AbstractSnapshotSelfTest.java | 16 ++++++++- .../snapshot/EncryptedSnapshotTest.java | 2 ++ .../IgniteClusterSnapshotSelfTest.java | 2 +- 6 files changed, 48 insertions(+), 10 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java index 0b1f223074ca7..86f549e668bab 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java @@ -736,14 +736,15 @@ public void deleteLocalSnapshot(File snpDir) { * @return {@code True}, if data is found and completely deleted; * {@code False}, if nothing found or if data is found but might not be deleted completely. */ - public boolean deleteLocalSnapshot(SnapshotFileTree sft, String nodeFolderName, - @Nullable AtomicBoolean existsFlag) { + public boolean deleteLocalSnapshot(SnapshotFileTree sft, String nodeFolderName, @Nullable AtomicBoolean existsFlag) { if (existsFlag != null) existsFlag.set(sft.root().exists()); if (!sft.root().exists()) return false; + Exception err = null; + AtomicBoolean res = new AtomicBoolean(true); for (var dir : F.asList(sft.binaryMeta(), sft.binaryMetaRoot(), sft.marshaller(), sft.db())) { @@ -763,7 +764,12 @@ public boolean deleteLocalSnapshot(SnapshotFileTree sft, String nodeFolderName, // No-op: someone else deleted. } catch (Exception e) { - res.set(false); + if (dir.exists()) { + res.set(false); + + if (err == null) + err = e; + } } } @@ -773,7 +779,20 @@ public boolean deleteLocalSnapshot(SnapshotFileTree sft, String nodeFolderName, Files.delete(sft.root().toPath()); } catch (Exception e) { - res.set(false); + if (sft.root().exists()) { + res.set(false); + + if (err == null) + err = e; + } + } + + if (err != null) { + log.warning("Failed to delete snapshot '%s', error: %s - %s".formatted( + sft.root().getName(), + err.getClass().getSimpleName(), + err.getMessage()) + ); } return res.get(); @@ -797,8 +816,12 @@ private void deleteSnapshotDataCompletely(@Nullable File f, AtomicBoolean failRe else Files.delete(f.toPath()); } + catch (NoSuchFileException ignored) { + // No-op. + } catch (Exception e) { - failRes.set(false); + if (f.exists()) + failRes.set(false); } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteRequest.java index 37577c1b2eb60..91351584aeede 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteRequest.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteRequest.java @@ -76,6 +76,6 @@ public SnapshotDeleteRequest() { /** {@inheritDoc} */ @Override public String toString() { - return S.toString(SnapshotDeleteRequest.class, this, super.toString()); + return S.toString(SnapshotDeleteRequest.class, this); } } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotCreationNonDefaultStoragePathTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotCreationNonDefaultStoragePathTest.java index 739f23d3c2a30..c72bf0500f93e 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotCreationNonDefaultStoragePathTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotCreationNonDefaultStoragePathTest.java @@ -88,9 +88,8 @@ public void testSnapshotThrowsIfExtraRootExists() throws Exception { assertTrue(sft.nodeStorage().getAbsolutePath() + " must not extists", !sft.nodeStorage().exists()); - for (File es : sft.extraStorages().values()) { + for (File es : sft.extraStorages().values()) assertTrue(es.getAbsolutePath() + " must not extists", !es.exists()); - } } U.delete(srvExtraSnpRoot); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java index 2c960ec62d49c..7a8442f5e5460 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java @@ -181,6 +181,9 @@ protected Function valueBuilder() { /** */ protected @Nullable AbstractTestPluginProvider pluginProvider; + /** */ + protected boolean fullCleanPersistentDir = true; + /** Enable encryption of all caches in {@code IgniteConfiguration} before start. */ @Parameterized.Parameter public boolean encryption; @@ -298,6 +301,9 @@ public void afterTestSnapshot() throws Exception { @Override protected void cleanPersistenceDir() throws Exception { super.cleanPersistenceDir(); + if (!fullCleanPersistentDir) + return; + // Clean all: also separated snapshot working directories and custom snapshot pathes. try (DirectoryStream files = newDirectoryStream(Paths.get(U.defaultWorkDirectory()))) { for (Path path : files) @@ -898,7 +904,15 @@ protected void doTestConcurrentSnapshotDeleteOperation( var delRes = delFut.get(getTestTimeout()); - assertFalse(delRes.completedNodes.isEmpty()); + // Concurrent deletion on the same path may cause exeptions like 'directory is not empty' or 'access denied'. + int nonEmptyCnt = (F.isEmpty(delRes.completedNodes) + ? 0 + : delRes.completedNodes.size()) + (F.isEmpty(delRes.uncompletedNodes) ? 0 : delRes.uncompletedNodes.size()); + + assertEquals(grid(0).cluster().forServers().nodes().size(), nonEmptyCnt); + + for (var node : G.allGrids()) + assertFalse(new SnapshotFileTree(((IgniteEx)node).context(), SNAPSHOT_NAME, null).root().exists()); if (!rerunAtTheEnd) return; diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/EncryptedSnapshotTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/EncryptedSnapshotTest.java index f6ae22c2db4ca..28b99c5d537c3 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/EncryptedSnapshotTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/EncryptedSnapshotTest.java @@ -232,6 +232,8 @@ public void testValidatingSnapshotFailsWithNoEncryption() throws Exception { assertTrue(snpDir.renameTo(tmpSnpDir)); + fullCleanPersistentDir = false; + cleanPersistenceDir(); assertTrue(tmpSnpDir.renameTo(snpDir)); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotSelfTest.java index 1270c77394d93..31bd938b79f43 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotSelfTest.java @@ -603,7 +603,7 @@ public void testSnapshotExistsException() throws Exception { assertThrowsAnyCause(log, () -> snp(ignite).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(), IgniteException.class, - "Snapshot with given name already exists on local node."); + "Snapshot with given name already exists on local node"); stopAllGrids(); From 65964322af7d507d63b7eb097e50830a29842edd Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Mon, 21 Sep 2026 20:08:17 +0300 Subject: [PATCH 40/97] test fixes, fixes --- .../GridCommandHandlerDeleteSnapshotTest.java | 7 +++++-- .../snapshot/SnapshotDeleteProcess.java | 21 +++++++------------ 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java index 967f7b141bf33..074a290f136bb 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java @@ -137,7 +137,9 @@ public void testSnapshotDelete() throws Exception { createCacheAndPreload(ig, entriesCnt); - File cstSnpsRoot = customPath ? new File(U.defaultWorkDirectory(), "ex_snapshots") : null; + File cstSnpsRoot = customPath + ? new File(grid(0).context().pdsFolderResolver().fileTree().snapshotsRoot(), "ex_snapshots") + : null; File snpDir = new File(customPath ? cstSnpsRoot : ig.context().pdsFolderResolver().fileTree().snapshotsRoot(), "testSnapshot"); snp(ig).createSnapshot("testSnapshot", customPath ? cstSnpsRoot.getAbsolutePath() : null, false, false) @@ -225,7 +227,8 @@ else if (extraNodeIsServer == 0) // When nodes use a shared work directory, there is a race for the delete operation. One node can get faster // than others and remove snapshot completely quickly. The others might not find snapshot files. We can be // only sure that at least one node removes snapshot. - assertTrue(out.contains("Snapshot removed on the following nodes [cnt=")); + assertTrue(out.contains("Snapshot removed on the following nodes [cnt=") + || out.contains("the following nodes found snapshot data but might not remove it completely [cnt=")); } assertFalse(out.contains("Snapshot not found on current server nodes")); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index 2fa0faf7fefb6..7fe7340d847b7 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -167,7 +167,7 @@ private IgniteInternalFuture deletePhase(UUID ignored, S ? reqPath : new File(path, req.snpPath); - String pathValidationErr = validateAbsoluteSnapshotRoot(path); + String pathValidationErr = validateAbsoluteSnapshotRoot(path, req.snpName); if (pathValidationErr != null) { return new GridFinishedFuture<>(new IllegalArgumentException(OP_REJECT_MSG + @@ -260,7 +260,7 @@ else if (!deleted) } /** */ - private @Nullable String validateAbsoluteSnapshotRoot(@Nullable File path) { + private @Nullable String validateAbsoluteSnapshotRoot(@Nullable File path, String snpName) { if (path == null) return null; @@ -268,20 +268,13 @@ else if (!deleted) var ignFileTree = kctx.pdsFolderResolver().fileTree(); - if (ignFileTree.snapshotsRoot().compareTo(path) == 0 || contains(ignFileTree.snapshotsRoot(), path)) - return null; - - for (var ignPath : List.of(new File(CommonUtils.getIgniteHome()), ignFileTree.root())) { - if (ignPath.compareTo(path) == 0 || contains(ignPath, path)) - return "belongs to a an Ignite's directory"; + if (ignFileTree.snapshotsRoot().compareTo(path) != 0 && !contains(ignFileTree.snapshotsRoot(), path)) { + for (var ignPath : List.of(new File(CommonUtils.getIgniteHome()), ignFileTree.root())) { + if (ignPath.compareTo(path) == 0 || contains(ignPath, path)) + return "belongs to a an Ignite's directory"; + } } - if (!path.exists()) - return "doesn't exist"; - - if (!path.isDirectory()) - return "is not a directory"; - return null; } From 4977c26fd106f695ccd0c163d6cd33faaeefb8ca Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Mon, 21 Sep 2026 20:46:54 +0300 Subject: [PATCH 41/97] test fixes, fixes --- .../persistence/snapshot/AbstractSnapshotSelfTest.java | 7 ++++++- .../snapshot/IgniteSnapshotRestoreFromRemoteTest.java | 5 +++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java index 7a8442f5e5460..c8338a2d04127 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java @@ -301,7 +301,7 @@ public void afterTestSnapshot() throws Exception { @Override protected void cleanPersistenceDir() throws Exception { super.cleanPersistenceDir(); - if (!fullCleanPersistentDir) + if (!fullCleanPersistentDir()) return; // Clean all: also separated snapshot working directories and custom snapshot pathes. @@ -311,6 +311,11 @@ public void afterTestSnapshot() throws Exception { } } + /** */ + protected boolean fullCleanPersistentDir() { + return fullCleanPersistentDir; + } + /** * @param evts Events to check. * @throws IgniteInterruptedCheckedException If interrupted. diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotRestoreFromRemoteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotRestoreFromRemoteTest.java index a31cd9a71376c..d21c24b290f34 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotRestoreFromRemoteTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotRestoreFromRemoteTest.java @@ -127,6 +127,11 @@ public class IgniteSnapshotRestoreFromRemoteTest extends IgniteClusterSnapshotRe return cfg; } + /** {@inheritDoc} */ + @Override protected boolean fullCleanPersistentDir() { + return false; + } + /** @throws Exception If fails. */ @Before public void prepareDedicatedSnapshot() throws Exception { From 3da6b6ab91f952d9fd3fa240a97002cdd20627f4 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Tue, 22 Sep 2026 15:21:12 +0300 Subject: [PATCH 42/97] review fixes, checkstyle. --- .../query/calcite/hint/HintUtils.java | 2 +- .../query/calcite/prepare/PlannerHelper.java | 2 +- .../GridCommandHandlerDeleteSnapshotTest.java | 19 +++-- .../filename/SnapshotFileTree.java | 4 +- .../AbstractSnapshotOperationRequest.java | 2 +- .../snapshot/IgniteSnapshotManager.java | 70 ++++++++++++------- .../snapshot/SnapshotCheckProcess.java | 12 ++-- .../snapshot/SnapshotDeleteProcess.java | 63 ++++++++++------- .../snapshot/SnapshotDeleteProcessResult.java | 19 +++-- .../snapshot/SnapshotDeleteRequest.java | 18 ++++- .../DistributedMetaStorageImpl.java | 2 +- .../snapshot/AbstractSnapshotSelfTest.java | 2 +- ...usterSnapshotDeleteRollingUpgradeTest.java | 31 ++++---- .../TestIgniteReleaseFeatures_2_19_2.java | 5 +- 14 files changed, 151 insertions(+), 100 deletions(-) diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/hint/HintUtils.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/hint/HintUtils.java index 40c267ded097a..5597b4c776a1b 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/hint/HintUtils.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/hint/HintUtils.java @@ -99,7 +99,7 @@ public static List nonInheritedRelHints(RelNode rel) { /** * @return Distinct hints within {@code hints} filtered with {@code hintDefs}, {@link HintOptionsChecker} and - * removed inherit pathes. + * removed inherit paths. * @see HintOptionsChecker * @see RelHint#inheritPath */ diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerHelper.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerHelper.java index d88275fb786e4..b5b094cda316c 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerHelper.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerHelper.java @@ -134,7 +134,7 @@ public static IgniteRel optimize(SqlNode sqlNode, IgnitePlanner planner, IgniteL rel = planner.trimUnusedFields(root.withRel(rel)).rel; // The following pushed down project can erase top-level hints. We store them to reassign hints for join nodes. - // Clear the inherit pathes to consider the hints as not propogated ones. + // Clear the inherit paths to consider the hints as not propogated ones. List topHints = HintUtils.allRelHints(rel).stream().map(h -> h.inheritPath.isEmpty() ? h : h.copy(Collections.emptyList())).collect(Collectors.toList()); diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java index 074a290f136bb..f9319207dcb9e 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java @@ -29,8 +29,6 @@ import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.testframework.GridTestUtils; import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; @@ -42,7 +40,6 @@ import static org.junit.Assume.assumeTrue; /** Test for the command '--snapshot delete'. */ -@RunWith(Parameterized.class) public class GridCommandHandlerDeleteSnapshotTest extends GridCommandHandlerAbstractTest { /** Value: -1 - do not use, 1 - server node, 0 - client node. */ @Parameter(1) @@ -50,7 +47,7 @@ public class GridCommandHandlerDeleteSnapshotTest extends GridCommandHandlerAbst /** */ @Parameter(2) - public boolean addIncrements; + public boolean incremental; /** */ @Parameter(3) @@ -70,10 +67,10 @@ public static Collection parameters() { return GridTestUtils.cartesianProduct( commandHandlers(), F.asList(-1, 1, 0), // Use extra node (do not use at all, server node, client node); - F.asList(false, true), // Add increments to the test snapshot; + F.asList(false, true), // Add incremental snapshot; F.asList(false, true), // Change baseline; F.asList(false, true), // Use custom snapshot path; - F.asList(true, false) // Separated (own) work directory. + F.asList(false, true) // Separated (own) work directory. ); } @@ -82,6 +79,8 @@ public static Collection parameters() { super.afterTest(); stopAllGrids(); + + cleanPersistenceDir(); } /** {@inheritDoc} */ @@ -96,7 +95,7 @@ public static Collection parameters() { @Override protected void cleanPersistenceDir() throws Exception { super.cleanPersistenceDir(); - // Also cleans separated snapshot working directories and custom snapshot pacthes. + // Also cleans separated snapshot working directories and custom snapshot patches. try (DirectoryStream files = newDirectoryStream(Paths.get(U.defaultWorkDirectory()))) { for (Path path : files) U.delete(path); @@ -120,10 +119,10 @@ public void testSnapshotDelete() throws Exception { // work directories are set. assumeTrue(!customPath || !separatedWorkDir); - int entriesCnt = 4000; + int entriesCnt = 100; int initNodes = 3; - walCompactionEnabled(addIncrements); + walCompactionEnabled(incremental); IgniteEx ig = (IgniteEx)startGridsMultiThreaded(initNodes); @@ -145,7 +144,7 @@ public void testSnapshotDelete() throws Exception { snp(ig).createSnapshot("testSnapshot", customPath ? cstSnpsRoot.getAbsolutePath() : null, false, false) .get(getTestTimeout()); - if (addIncrements) { + if (incremental) { for (int i = 0; i < 3; ++i) { int dataIdx = entriesCnt + entriesCnt / 4 * i; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java index 5dba8081dd450..878d8e6d6839d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java @@ -51,7 +51,7 @@ public class SnapshotFileTree extends NodeFileTree { private static final String DELTA_IDX_SUFFIX = ".idx"; /** Snapshot metafile extension. */ - private static final String SNAPSHOT_METAFILE_EXT = ".smf"; + public static final String SNAPSHOT_METAFILE_EXT = ".smf"; /** File name template consists of delta pages. */ private static final String PART_DELTA_TEMPLATE = PART_FILE_TEMPLATE + DELTA_SUFFIX; @@ -477,6 +477,8 @@ public static boolean isSnapshotFile(File f) { case DUMP_LOCK: case WAL_SEGMENT_FILE_EXT: case FILE_SUFFIX: + case ZIP_SUFFIX: + case TMP_SUFFIX: case ".dat": return true; } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotOperationRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotOperationRequest.java index a12210c29cb77..43eb5706b92f3 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotOperationRequest.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotOperationRequest.java @@ -79,7 +79,7 @@ protected AbstractSnapshotOperationRequest( Collection nodes ) { this.reqId = reqId; - this.snpName = snpName; + this.snpName = snpName.trim(); this.grps = grps; this.snpPath = snpPath; this.nodes = new HashSet<>(nodes); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java index 86f549e668bab..5898ca51c58f8 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java @@ -728,7 +728,7 @@ public void deleteLocalSnapshot(File snpDir) { } /** - * Deletes local shapshot data. + * Deletes local snapshot data. * * @param sft Snapshot file tree * @param nodeFolderName Exact node's data subdirectory name usually taken from the consistent id. @@ -747,24 +747,43 @@ public boolean deleteLocalSnapshot(SnapshotFileTree sft, String nodeFolderName, AtomicBoolean res = new AtomicBoolean(true); - for (var dir : F.asList(sft.binaryMeta(), sft.binaryMetaRoot(), sft.marshaller(), sft.db())) { - if (!dir.exists()) - continue; + Collection nodesData = new ArrayList<>(30); + Collection sharedData = new ArrayList<>(30); - var nodeDir = new File(dir, nodeFolderName); + nodesData.addAll(F.asList(sft.binaryMetaRoot(), sft.marshaller(), sft.db())); - if (nodeDir.exists()) - deleteSnapshotDataCompletely(nodeDir, res); + File[] incrementals = sft.incrementsRoot().listFiles(); - // Recheck for the case of concurrent deletion. - try { - Files.delete(dir.toPath()); + if (!F.isEmpty(incrementals)) { + for (var inc : incrementals) { + if (inc.isDirectory()) { + // Incremental_root/db/[wal,marshaller,binary] - nodes' data. + var incDb = new File(inc, "db"); + + nodesData.addAll(F.asList(incDb.listFiles())); + + // Incremental snp. metafile. + sharedData.add(new File(inc, nodeFolderName + SnapshotFileTree.SNAPSHOT_METAFILE_EXT)); + + sharedData.add(incDb); + sharedData.add(inc); + } } - catch (NoSuchFileException ne) { - // No-op: someone else deleted. + } + + sharedData.add(sft.meta()); + + for (var f : nodesData) { + if (!f.exists()) + continue; + + deleteSnapshotDataCompletely(new File(f, nodeFolderName), res); + + try { + Files.deleteIfExists(f.toPath()); } catch (Exception e) { - if (dir.exists()) { + if (f.exists()) { res.set(false); if (err == null) @@ -773,17 +792,20 @@ public boolean deleteLocalSnapshot(SnapshotFileTree sft, String nodeFolderName, } } - deleteSnapshotDataCompletely(sft.meta(), res); + sharedData.add(sft.incrementsRoot()); + sharedData.add(sft.root()); - try { - Files.delete(sft.root().toPath()); - } - catch (Exception e) { - if (sft.root().exists()) { - res.set(false); + for (var f : sharedData) { + try { + Files.deleteIfExists(f.toPath()); + } + catch (Exception e) { + if (f.exists()) { + res.set(false); - if (err == null) - err = e; + if (err == null) + err = e; + } } } @@ -1570,7 +1592,7 @@ public boolean isSnapshotDeleting(String snpName, @Nullable String snpPath) { * * @param name Snapshot name. * @param snpPath Snapshot directory path. If {@code null}, the default configured snapshot directory will be used. - * @return Future which will be completed when the snapshot is deleted on all the baseline nodes. + * @return Future which will be completed when the snapshot is deleted on all the online server nodes. */ public IgniteFuture deleteSnapshot(String name, @Nullable String snpPath) { return deleteSnpProc.start(name, snpPath); @@ -1609,7 +1631,7 @@ public boolean isRestoring() { * @return {@code True} if the snapshot restore operation from the specified snapshot is in progress locally. */ public boolean isRestoring(String snpName) { - return snpName.equals(restoreCacheGrpProc.restoringSnapshotName()); + return snpName.equalsIgnoreCase(restoreCacheGrpProc.restoringSnapshotName()); } /** diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java index 1337ff8541e99..6793f5fd8ec71 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java @@ -132,7 +132,7 @@ private IgniteInternalFuture reduceValidatePartsAndFinish( return new GridFinishedFuture<>(); try { - contexts.remove(ctx.req.snapshotName()); + contexts.remove(ctx.req.snapshotName().toLowerCase()); GridFutureAdapter clusterOpFut = clusterOpFuts.get(reqId); @@ -474,7 +474,7 @@ private static Exception asException(Throwable th) { private @Nullable SnapshotCheckContext context(@Nullable String snpName, UUID reqId) { return snpName == null ? contexts.values().stream().filter(ctx0 -> ctx0.req.requestId().equals(reqId)).findFirst().orElse(null) - : contexts.get(snpName); + : contexts.get(snpName.toLowerCase()); } /** Phase 1 beginning: prepare, collect and check local metas. */ @@ -488,7 +488,7 @@ private IgniteInternalFuture prepareAndCheckMetas(UUID ig if (nodeStopping) return new GridFinishedFuture<>(new NodeStoppingException("The node is stopping: " + kctx.localNodeId())); - ctx = contexts.computeIfAbsent(req.snapshotName(), snpName -> new SnapshotCheckContext(req)); + ctx = contexts.computeIfAbsent(req.snapshotName().toLowerCase(), snpName -> new SnapshotCheckContext(req)); } if (!ctx.req.requestId().equals(req.requestId())) { @@ -594,7 +594,7 @@ private void reducePreparationAndMetasCheck( if (ctx != null) { unregisterMetrics(ctx.req.snapshotName()); - contexts.remove(ctx.req.snapshotName()); + contexts.remove(ctx.req.snapshotName().toLowerCase()); } if (clusterOpFut != null) @@ -700,7 +700,7 @@ public IgniteInternalFuture start( /** @return {@code True} if snapshot with specified name is checking. */ boolean isSnapshotChecking(String snpName) { - return contexts.get(snpName) != null; + return contexts.get(snpName.toLowerCase()) != null; } /** @return {@code True} if node with the provided id is in the cluster and is a baseline node. {@code False} otherwise. */ @@ -776,7 +776,7 @@ private static final class SnapshotCheckContext { */ @Nullable private volatile List metas; - /** Map of snapshot pathes per consistent id for {@link #metas}. */ + /** Map of snapshot paths per consistent id for {@link #metas}. */ @GridToStringInclude @Nullable private Map locFileTree; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index 7fe7340d847b7..634a2f56bbfe5 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -95,10 +95,17 @@ public SnapshotDeleteProcess(GridKernalContext ctx) { * @return Future that will be completed when the snapshot is deleted. */ public IgniteFuture start(String snpName, @Nullable String snpPath) { - UUID reqId = UUID.randomUUID(); - var clusterOpFut = new GridFutureAdapter(); + if (!kctx.rollingUpgrade().features().isActive(SNAPSHOT_DELETE_FEATURE)) { + clusterOpFut.onDone(new IgniteIllegalStateException(OP_REJECT_MSG + + "The snapshot deletion feature isn't activated yet [snpName=" + snpName + ", snpPath=" + snpPath + ']')); + + return new IgniteFutureImpl<>(clusterOpFut); + } + + UUID reqId = UUID.randomUUID(); + clusterOpFut.listen(fut -> clusterOpFuts.remove(reqId)); try { @@ -138,7 +145,7 @@ private IgniteInternalFuture deletePhase(UUID ignored, S var curCreateRq = snpMgr.currentCreateRequest(); - if (curCreateRq != null && curCreateRq.snpName.equals(req.snpName)) { + if (curCreateRq != null && curCreateRq.snpName.equalsIgnoreCase(req.snpName)) { return new GridFinishedFuture<>(new IgniteIllegalStateException(OP_REJECT_MSG + "Snapshot with this name is being created [req=" + req + ']')); } @@ -153,28 +160,17 @@ private IgniteInternalFuture deletePhase(UUID ignored, S "Snapshot with this name is being checked [req=" + req + ']')); } - if (!kctx.rollingUpgrade().features().isActive(SNAPSHOT_DELETE_FEATURE)) { - return new GridFinishedFuture<>(new IgniteIllegalStateException(OP_REJECT_MSG + - "The snapshot deletion feature isn't activated yet [req=" + req + ']')); - } - - File path = kctx.pdsFolderResolver().fileTree().snapshotsRoot(); - - if (!F.isEmpty(req.snpPath)) { - File reqPath = new File(req.snpPath); + File path = resolvePath(req.snpPath); - path = reqPath.isAbsolute() - ? reqPath - : new File(path, req.snpPath); + String pathValidationErr = validateAbsoluteSnapshotRoot(path, req.snpName); - String pathValidationErr = validateAbsoluteSnapshotRoot(path, req.snpName); - - if (pathValidationErr != null) { - return new GridFinishedFuture<>(new IllegalArgumentException(OP_REJECT_MSG + - SNP_PATH_ERR_PREF + pathValidationErr + " [req=" + req + ']')); - } + if (pathValidationErr != null) { + return new GridFinishedFuture<>(new IllegalArgumentException(OP_REJECT_MSG + + SNP_PATH_ERR_PREF + pathValidationErr + " [req=" + req + ']')); } + req.resolvedPath = path; + try { if (!requests.add(req)) { return new GridFinishedFuture<>(new IgniteIllegalStateException("Deletion of the snapshot has already " + @@ -259,6 +255,19 @@ else if (!deleted) } } + /** */ + private File resolvePath(@Nullable String path) { + var res = kctx.pdsFolderResolver().fileTree().snapshotsRoot(); + + if (path != null) { + File reqPath = new File(path); + + res = reqPath.isAbsolute() ? reqPath : new File(res, path); + } + + return res; + } + /** */ private @Nullable String validateAbsoluteSnapshotRoot(@Nullable File path, String snpName) { if (path == null) @@ -322,11 +331,7 @@ private void reducePhase(UUID reqId, Map results, } }); - clusterOpFut.onDone(new SnapshotDeleteProcessResult( - completedNodes.isEmpty() ? null : completedNodes, - uncompletedNodes.isEmpty() ? null : uncompletedNodes, - emptyNodes.isEmpty() ? null : emptyNodes - )); + clusterOpFut.onDone(new SnapshotDeleteProcessResult(completedNodes, uncompletedNodes, emptyNodes)); } catch (Throwable t) { clusterOpFut.onDone(t); @@ -335,7 +340,11 @@ private void reducePhase(UUID reqId, Map results, /** */ public boolean isDeleting(String snpName, @Nullable String snpPath) { - return requests.contains(new SnapshotDeleteRequest(null, snpName, snpPath)); + var rq = new SnapshotDeleteRequest(null, snpName, snpPath); + + rq.resolvedPath = resolvePath(rq.snpPath); + + return requests.contains(rq); } /** diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResult.java index 95a904dc6d55b..4431854008e8c 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResult.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResult.java @@ -22,7 +22,6 @@ import org.apache.ignite.internal.Order; import org.apache.ignite.internal.dto.IgniteDataTransferObject; import org.apache.ignite.plugin.extensions.communication.MessageFactory; -import org.jetbrains.annotations.Nullable; /** Result of {@link SnapshotDeleteProcess}. */ public final class SnapshotDeleteProcessResult extends IgniteDataTransferObject { @@ -31,15 +30,15 @@ public final class SnapshotDeleteProcessResult extends IgniteDataTransferObject /** Nodes which found snapshot data and completely removed it. */ @Order(0) - @Nullable Collection completedNodes; + Collection completedNodes; /** Nodes which found snapshot data but didn't remove it completely. */ @Order(1) - @Nullable Collection uncompletedNodes; + Collection uncompletedNodes; /** Server nodes which didn't find any snapshot data. */ @Order(2) - @Nullable Collection emptyNodes; + Collection emptyNodes; /** Default constructor for {@link MessageFactory}. */ public SnapshotDeleteProcessResult() { @@ -48,9 +47,9 @@ public SnapshotDeleteProcessResult() { /** */ public SnapshotDeleteProcessResult( - @Nullable Collection completedNodes, - @Nullable Collection uncompletedNodes, - @Nullable Collection emptyNodes + Collection completedNodes, + Collection uncompletedNodes, + Collection emptyNodes ) { this.completedNodes = completedNodes; this.uncompletedNodes = uncompletedNodes; @@ -58,17 +57,17 @@ public SnapshotDeleteProcessResult( } /** */ - public @Nullable Collection completedNodes() { + public Collection completedNodes() { return completedNodes; } /** */ - public @Nullable Collection uncompletedNodes() { + public Collection uncompletedNodes() { return uncompletedNodes; } /** */ - public @Nullable Collection emptyNodes() { + public Collection emptyNodes() { return emptyNodes; } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteRequest.java index 91351584aeede..86bbc41003b14 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteRequest.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteRequest.java @@ -17,9 +17,11 @@ package org.apache.ignite.internal.processors.cache.persistence.snapshot; +import java.io.File; import java.util.Objects; import java.util.UUID; import org.apache.ignite.internal.Order; +import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.plugin.extensions.communication.Message; import org.apache.ignite.plugin.extensions.communication.MessageFactory; @@ -43,6 +45,9 @@ public class SnapshotDeleteRequest implements Message { @Order(2) @Nullable String snpPath; + /** Resolved absolute path. Transient */ + @Nullable File resolvedPath; + /** Default constructor for {@link MessageFactory}. */ public SnapshotDeleteRequest() { // No-op. @@ -55,7 +60,14 @@ public SnapshotDeleteRequest() { */ SnapshotDeleteRequest(UUID reqId, String snpName, @Nullable String snpPath) { this.reqId = reqId; - this.snpName = snpName; + this.snpName = snpName.trim(); + + // A protection against empty relative paths like " ". + if (!F.isEmpty(snpPath)) + snpPath = snpPath.trim(); + + snpPath = F.isEmpty(snpPath) ? null : snpPath; + this.snpPath = snpPath; } @@ -66,12 +78,12 @@ public SnapshotDeleteRequest() { SnapshotDeleteRequest other = (SnapshotDeleteRequest)o; - return snpName.equals(other.snpName) && Objects.equals(snpPath, other.snpPath); + return snpName.equalsIgnoreCase(other.snpName) && Objects.equals(resolvedPath, other.resolvedPath); } /** {@inheritDoc} */ @Override public int hashCode() { - return Objects.hash(snpName, snpPath); + return Objects.hash(snpName.toLowerCase(), resolvedPath); } /** {@inheritDoc} */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/metastorage/persistence/DistributedMetaStorageImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/metastorage/persistence/DistributedMetaStorageImpl.java index 575300772a905..bf7f6f34c2566 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/metastorage/persistence/DistributedMetaStorageImpl.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/metastorage/persistence/DistributedMetaStorageImpl.java @@ -103,7 +103,7 @@ *

*

Whole updates history until some point in the past is stored along with the data, so when an outdated node * connects to the cluster it will receive all the missing data and apply it locally. Listeners will also be invoked - * after such updates. If there's not enough history stored or joining node is clear then it'll receive shapshot of + * after such updates. If there's not enough history stored or joining node is clear then it'll receive snapshot of * distributed metastorage (usually called {@code fullData} in code) so there won't be inconsistencies. *

* diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java index c8338a2d04127..b34df3bec831d 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java @@ -304,7 +304,7 @@ public void afterTestSnapshot() throws Exception { if (!fullCleanPersistentDir()) return; - // Clean all: also separated snapshot working directories and custom snapshot pathes. + // Clean all: also separated snapshot working directories and custom snapshot paths. try (DirectoryStream files = newDirectoryStream(Paths.get(U.defaultWorkDirectory()))) { for (Path path : files) U.delete(path); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgradeTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgradeTest.java index 6c608f25a07a5..9f2e12bae90e2 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgradeTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgradeTest.java @@ -17,10 +17,13 @@ package org.apache.ignite.internal.processors.cache.persistence.snapshot; +import java.io.File; +import java.nio.file.DirectoryStream; +import java.nio.file.Path; +import java.nio.file.Paths; import org.apache.ignite.IgniteIllegalStateException; import org.apache.ignite.cache.CacheAtomicityMode; import org.apache.ignite.cache.CacheMode; -import org.apache.ignite.cache.CacheWriteSynchronizationMode; import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction; import org.apache.ignite.configuration.CacheConfiguration; import org.apache.ignite.configuration.DataRegionConfiguration; @@ -29,9 +32,11 @@ import org.apache.ignite.internal.processors.rollingupgrade.AbstractRollingUpgradeTest; import org.apache.ignite.internal.util.distributed.SingleNodeMessage; import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.testframework.GridTestUtils; import org.junit.Test; +import static java.nio.file.Files.newDirectoryStream; import static org.apache.ignite.internal.TestRecordingCommunicationSpi.spi; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RU_PREPARE_VERSION_FINALIZATION; import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause; @@ -52,6 +57,12 @@ public class IgniteClusterSnapshotDeleteRollingUpgradeTest extends AbstractRolli super.afterTest(); cleanPersistenceDir(); + + // Clean all: also separated snapshot working directories. + try (DirectoryStream files = newDirectoryStream(Paths.get(U.defaultWorkDirectory()))) { + for (Path path : files) + U.delete(path); + } } /** {@inheritDoc} */ @@ -67,6 +78,8 @@ public class IgniteClusterSnapshotDeleteRollingUpgradeTest extends AbstractRolli ) ); + cfg.setWorkDirectory(new File(U.defaultWorkDirectory(), igniteInstanceName).getAbsolutePath()); + return cfg; } @@ -113,7 +126,7 @@ public void testConcurrentUnfinishedRU() throws Exception { /** */ @Test - public void testNodeNotSupportingSnapshotDeleteFeature() throws Exception { + public void testSnapshotDeleteFeature() throws Exception { for (int i = 0; i < ALL_GRIDS; i++) startGrid(i, "2.19.0", i >= ALL_GRIDS - CLIENTS); @@ -135,26 +148,20 @@ public void testNodeNotSupportingSnapshotDeleteFeature() throws Exception { ru(grid(1)).finalizeClusterVersion(); - for (int i = 0; i < ALL_GRIDS; i++) { + for (int i = 0; i < ALL_GRIDS; i++) assertFalse(ru(grid(i)).isVersionUpgradeEnabled()); - assertFalse(F.isEmpty(snp(i).deleteSnapshot(SNP_NAME, null).get().completedNodes)); - - if (i < ALL_GRIDS - 1) - createSnapshot(i); - } + assertEquals(3, snp(1).deleteSnapshot(SNP_NAME, null).get().completedNodes().size()); } /** */ private void createCacheAndSnapshot(int gridIdx) { - int partsCnt = 32; + int partsCnt = 5; int keysCnt = partsCnt * 10; grid(gridIdx).createCache(new CacheConfiguration<>(DEFAULT_CACHE_NAME) .setCacheMode(CacheMode.REPLICATED) - .setBackups(1) - .setAffinity(new RendezvousAffinityFunction().setPartitions(32)) - .setWriteSynchronizationMode(CacheWriteSynchronizationMode.FULL_SYNC) + .setAffinity(new RendezvousAffinityFunction().setPartitions(partsCnt)) .setAtomicityMode(CacheAtomicityMode.ATOMIC)); try (var ds = grid(gridIdx).dataStreamer(DEFAULT_CACHE_NAME)) { diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_2.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_2.java index 0ad70357e73a9..cdf502e7e567f 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_2.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_2.java @@ -23,8 +23,9 @@ public class TestIgniteReleaseFeatures_2_19_2 { public static final IgniteFeature ROLLING_UPGRADE_FEATURE = TestIgniteReleaseFeatures_2_19_1.ROLLING_UPGRADE_FEATURE; /** */ - public static final IgniteFeature VER_2_19_2_ID_1_FEATURE = new IgniteCoreFeature(1); + public static final IgniteFeature VER_2_19_2_ID_1_FEATURE = + new IgniteCoreFeature(TestIgniteReleaseFeatures_2_19_1.SNAPSHOT_DELETE_FEATURE.id() + 1); /** */ - public static final IgniteFeature VER_2_19_2_ID_2_FEATURE = new IgniteCoreFeature(2); + public static final IgniteFeature VER_2_19_2_ID_2_FEATURE = new IgniteCoreFeature(VER_2_19_2_ID_1_FEATURE.id() + 1); } From 6c076ae365b4792e65cedaf6bbbc7b9a05b81332 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Tue, 22 Sep 2026 18:51:03 +0300 Subject: [PATCH 43/97] fixes --- .../filename/SnapshotFileTree.java | 29 ---- .../snapshot/IgniteSnapshotManager.java | 164 ++++++------------ .../snapshot/SnapshotDeleteProcess.java | 4 +- ...shotCreationNonDefaultStoragePathTest.java | 3 +- .../snapshot/AbstractSnapshotSelfTest.java | 8 +- .../IgniteClusterSnapshotDeleteTest.java | 52 ++---- parent/pom.xml | 2 +- 7 files changed, 78 insertions(+), 184 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java index 878d8e6d6839d..ddeb8a4ea2707 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java @@ -457,35 +457,6 @@ private NodeFileTree tempFileTree(GridKernalContext ctx) { return res; } - /** */ - public static boolean isSnapshotFile(File f) { - if (f.isDirectory()) - return true; - - int idx = f.getName().indexOf('.'); - - if (idx < 1) - return false; - - String ext = f.getName().substring(idx); - - switch (ext) { - case DELTA_IDX_SUFFIX: - case DELTA_SUFFIX: - case SNAPSHOT_METAFILE_EXT: - case DUMP_FILE_EXT: - case DUMP_LOCK: - case WAL_SEGMENT_FILE_EXT: - case FILE_SUFFIX: - case ZIP_SUFFIX: - case TMP_SUFFIX: - case ".dat": - return true; - } - - return false; - } - /** {@inheritDoc} */ @Override public String toString() { return S.toString(SnapshotFileTree.class, this); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java index 5898ca51c58f8..f9acc93cbca46 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java @@ -30,7 +30,6 @@ import java.nio.channels.FileChannel; import java.nio.file.FileVisitResult; import java.nio.file.Files; -import java.nio.file.NoSuchFileException; import java.nio.file.Path; import java.nio.file.SimpleFileVisitor; import java.nio.file.StandardCopyOption; @@ -724,138 +723,92 @@ public void deleteLocalSnapshot(File snpDir) { pdsSettings.consistentId().toString() ); - deleteLocalSnapshot(sft, ft.folderName(), null); + deleteLocalSnapshot(sft, null); } /** - * Deletes local snapshot data. + * Tries to delete local snapshot data. * - * @param sft Snapshot file tree - * @param nodeFolderName Exact node's data subdirectory name usually taken from the consistent id. + * @param sft Snapshot file tree. * @param existsFlag Flag to set {@code true} if any snapshot file or directory was found (existed). If {@code null}, ignored. * @return {@code True}, if data is found and completely deleted; * {@code False}, if nothing found or if data is found but might not be deleted completely. */ - public boolean deleteLocalSnapshot(SnapshotFileTree sft, String nodeFolderName, @Nullable AtomicBoolean existsFlag) { - if (existsFlag != null) - existsFlag.set(sft.root().exists()); - - if (!sft.root().exists()) - return false; + public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean existsFlag) { + var exFlag0 = new AtomicBoolean(); - Exception err = null; - - AtomicBoolean res = new AtomicBoolean(true); - - Collection nodesData = new ArrayList<>(30); - Collection sharedData = new ArrayList<>(30); + sft.allStorages().forEach(s -> { + if (s.exists()) + exFlag0.set(true); + }); - nodesData.addAll(F.asList(sft.binaryMetaRoot(), sft.marshaller(), sft.db())); + if (sft.root().exists()) + exFlag0.set(true); - File[] incrementals = sft.incrementsRoot().listFiles(); + if (existsFlag != null) + existsFlag.set(exFlag0.get()); - if (!F.isEmpty(incrementals)) { - for (var inc : incrementals) { - if (inc.isDirectory()) { - // Incremental_root/db/[wal,marshaller,binary] - nodes' data. - var incDb = new File(inc, "db"); + // Nothing to delete. + if (!exFlag0.get()) + return false; - nodesData.addAll(F.asList(incDb.listFiles())); + boolean res = true; - // Incremental snp. metafile. - sharedData.add(new File(inc, nodeFolderName + SnapshotFileTree.SNAPSHOT_METAFILE_EXT)); + try { + if (sft.binaryMeta().exists() && !U.delete(sft.binaryMeta()) && sft.binaryMeta().exists()) + res = false; - sharedData.add(incDb); - sharedData.add(inc); - } + for (var s : sft.allStorages().toList()) { + if (s.exists() && !U.delete(s) && s.exists()) + res = false; } - } - - sharedData.add(sft.meta()); - for (var f : nodesData) { - if (!f.exists()) - continue; + if (sft.meta().exists() && !U.delete(sft.meta()) && sft.meta().exists()) + res = false; - deleteSnapshotDataCompletely(new File(f, nodeFolderName), res); + if (sft.binaryMetaRoot().exists() && !deleteDirectory(sft.binaryMetaRoot()) && sft.binaryMetaRoot().exists()) + res = false; - try { - Files.deleteIfExists(f.toPath()); - } - catch (Exception e) { - if (f.exists()) { - res.set(false); + if (sft.marshaller().exists() && !deleteDirectory(sft.marshaller()) && sft.marshaller().exists()) + res = false; - if (err == null) - err = e; - } - } - } - - sharedData.add(sft.incrementsRoot()); - sharedData.add(sft.root()); + if (sft.incrementsRoot().exists() && !deleteDirectory(sft.incrementsRoot()) && sft.incrementsRoot().exists()) + res = false; - for (var f : sharedData) { - try { - Files.deleteIfExists(f.toPath()); - } - catch (Exception e) { - if (f.exists()) { - res.set(false); + // Delete parent dir which is {snapshot_root}/db if empty. + if (!sft.marshaller().getParentFile().delete()) + res = false; - if (err == null) - err = e; - } - } + // Delete root dir which is {snapshot_root} if empty. + if (!sft.root().delete()) + res = false; } + catch (Exception e) { + log.warning("Failed to delete local snapshot [snpName=" + sft.name() + ']', e); - if (err != null) { - log.warning("Failed to delete snapshot '%s', error: %s - %s".formatted( - sft.root().getName(), - err.getClass().getSimpleName(), - err.getMessage()) - ); + return false; } - return res.get(); - } + for (var s : sft.allStorages().toList()) { + if (s.exists()) + return false; + } - /** - * Deletes file/directory and sets existence and deletion failure flags. - * - * @param f File/directory to delete. If {@code null}, does nothing. - * @param failRes Is set to {@code False} if at least one existed file or directory was denied to delete. - */ - private void deleteSnapshotDataCompletely(@Nullable File f, AtomicBoolean failRes) { - if (f == null || !f.exists() || !SnapshotFileTree.isSnapshotFile(f)) - return; + if (sft.root().exists()) + return false; - try { - if (f.isDirectory()) { - if (!deleteDirectoryWithContent(f)) - failRes.set(false); - } - else - Files.delete(f.toPath()); - } - catch (NoSuchFileException ignored) { - // No-op. - } - catch (Exception e) { - if (f.exists()) - failRes.set(false); - } + return res; } /** Concurrently traverse the directory and delete all files. */ - private boolean deleteDirectoryWithContent(File dir) throws IOException { - AtomicBoolean res = new AtomicBoolean(true); + private boolean deleteDirectory(File dir) throws IOException { + var res = new AtomicBoolean(); Files.walkFileTree(dir.toPath(), new SimpleFileVisitor<>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { - var f = file.toFile(); + File f0 = file.toFile(); - if (f.exists() && !U.delete(file) && f.exists()) + if (f0.exists() && !U.delete(f0) && f0.exists()) res.set(false); return FileVisitResult.CONTINUE; @@ -867,9 +820,9 @@ private boolean deleteDirectoryWithContent(File dir) throws IOException { } @Override public FileVisitResult postVisitDirectory(Path dir, IOException e) { - var f = dir.toFile(); + File f0 = dir.toFile(); - if (f.exists() && !f.delete() && f.exists()) + if (f0.exists() && !f0.delete() && f0.exists()) res.set(false); if (log.isInfoEnabled() && e != null) @@ -1407,13 +1360,8 @@ private IgniteInternalFuture initLocalSnapshotEndStag if (snpStartReq.incremental()) U.delete(snpOp.snapshotFileTree().incrementalSnapshotFileTree(snpStartReq.incrementIndex()).root()); - else { - deleteLocalSnapshot( - snpOp.snapshotFileTree(), - cctx.kernalContext().pdsFolderResolver().fileTree().folderName(), - null - ); - } + else + deleteLocalSnapshot(snpOp.snapshotFileTree(), null); } else if (!F.isEmpty(endReq.warnings())) { // Pass the warnings further to the next stage for the case when snapshot started from not coordinator. @@ -4109,7 +4057,7 @@ public LocalSnapshotSender(SnapshotFileTree sft) { log.info("The Local snapshot sender closed. All resources released [dbNodeSnpDir=" + sft.nodeStorage() + ']'); } else { - deleteLocalSnapshot(sft, cctx.kernalContext().pdsFolderResolver().fileTree().folderName(), null); + deleteLocalSnapshot(sft, null); if (log.isDebugEnabled()) log.debug("Local snapshot sender closed due to an error occurred: " + th.getMessage()); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index 634a2f56bbfe5..b737bf39d30e5 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -184,6 +184,8 @@ private IgniteInternalFuture deletePhase(UUID ignored, S List locMetas = kctx.cache().context().snapshotMgr().readSnapshotMetadatas(snpFiles); if (locMetas.isEmpty()) { + requests.remove(req); + log.warning("Snapshot deletion won't process, no snapshot metadata found [req=" + req + ']'); return new GridFinishedFuture<>(new SnapshotDeleteResponse(SnapshotDeleteResponse.SnapshotDeleteStatus.NOT_FOUND)); @@ -208,7 +210,7 @@ private IgniteInternalFuture deletePhase(UUID ignored, S var byMetaSft = new SnapshotFileTree(kctx, req.snpName, path0.getAbsolutePath(), meta.folderName(), meta.consId); - boolean deleted = snpMgr.deleteLocalSnapshot(byMetaSft, meta.folderName(), foundFlag); + boolean deleted = snpMgr.deleteLocalSnapshot(byMetaSft, foundFlag); SnapshotDeleteResponse.SnapshotDeleteStatus res; diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotCreationNonDefaultStoragePathTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotCreationNonDefaultStoragePathTest.java index c72bf0500f93e..739f23d3c2a30 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotCreationNonDefaultStoragePathTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotCreationNonDefaultStoragePathTest.java @@ -88,8 +88,9 @@ public void testSnapshotThrowsIfExtraRootExists() throws Exception { assertTrue(sft.nodeStorage().getAbsolutePath() + " must not extists", !sft.nodeStorage().exists()); - for (File es : sft.extraStorages().values()) + for (File es : sft.extraStorages().values()) { assertTrue(es.getAbsolutePath() + " must not extists", !es.exists()); + } } U.delete(srvExtraSnpRoot); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java index b34df3bec831d..768fbba8f4259 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java @@ -865,11 +865,7 @@ protected void doTestConcurrentSnapshotDeleteOperation( @Override public T createComponent(PluginContext ctx, Class cls) { if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { - @Override public boolean deleteLocalSnapshot( - SnapshotFileTree sft, - String nodeFolderName, - @Nullable AtomicBoolean existsFlag - ) { + @Override public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean existsFlag) { delProcInitLatch.countDown(); try { @@ -879,7 +875,7 @@ protected void doTestConcurrentSnapshotDeleteOperation( throw new RuntimeException("Interrupted.", e); } - return super.deleteLocalSnapshot(sft, nodeFolderName, existsFlag); + return super.deleteLocalSnapshot(sft, existsFlag); } }; } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java index bde5ae462ccde..5b9b1a84950e4 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java @@ -140,18 +140,14 @@ public void testUncompletedNodes() throws Exception { @Override public T createComponent(PluginContext ctx, Class cls) { if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { - @Override public boolean deleteLocalSnapshot( - SnapshotFileTree sft, - String nodeFolderName, - @Nullable AtomicBoolean existsFlag - ) { + @Override public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean existsFlag) { if (ctx.localNode().id().equals(grid(1).localNode().id())) { existsFlag.set(true); return false; } - return super.deleteLocalSnapshot(sft, nodeFolderName, existsFlag); + return super.deleteLocalSnapshot(sft, existsFlag); } }; } @@ -237,24 +233,11 @@ protected void doTestDeleteNotSnapshot(boolean separatedWorkDir) throws Exceptio assertEquals(2, delSnpRes.completedNodes.size()); assertEquals(1, delSnpRes.emptyNodes.size()); assertTrue(delSnpRes.emptyNodes.contains(grid(1).localNode().id())); + assertTrue(snpSft.binaryMeta().exists()); } - else { - // All nodes may see some metas, may try to delete snapshot by the metas, but see that the snapshot directory isn't empty. - // Some node may not get any meta to process. But node of the nodes can say the snapshot 100% deleted. - assertTrue(F.isEmpty(delSnpRes.completedNodes)); + else + assertEquals(3, delSnpRes.uncompletedNodes.size() + delSnpRes.completedNodes.size() + delSnpRes.emptyNodes.size()); - int cnt = 0; - - if (!F.isEmpty(delSnpRes.emptyNodes)) - cnt += delSnpRes.emptyNodes.size(); - - if (!F.isEmpty(delSnpRes.uncompletedNodes)) - cnt += delSnpRes.uncompletedNodes.size(); - - assertEquals(3, cnt); - } - - assertTrue(snpSft.binaryMeta().exists()); assertFalse(new SnapshotFileTree(grid(0).context(), SNAPSHOT_NAME, null, folderName(0), consistentId(0)) .binaryMeta().exists()); assertFalse(new SnapshotFileTree(grid(2).context(), SNAPSHOT_NAME, null, folderName(2), consistentId(2)) @@ -325,16 +308,13 @@ public void testDeletionInIgnite() throws Exception { if (log.isInfoEnabled()) log.info("Testing path: " + testDir); - assertThrowsAnyCause( - null, - () -> snpMgr.deleteSnapshot(SNAPSHOT_NAME, testDir).get(getTestTimeout()), - IllegalArgumentException.class, - "snapshot path doesn't exist" - ); + var delRes = snpMgr.deleteSnapshot(SNAPSHOT_NAME, testDir).get(getTestTimeout()); + + assertEquals(3, delRes.emptyNodes.size()); var snpRoot = ignFileTree.snapshotsRoot(); - var delRes = snpMgr.deleteSnapshot(SNAPSHOT_NAME, snpRoot.getAbsolutePath()).get(getTestTimeout()); + delRes = snpMgr.deleteSnapshot(SNAPSHOT_NAME, snpRoot.getAbsolutePath()).get(getTestTimeout()); assertTrue(F.isEmpty(delRes.completedNodes)); assertTrue(F.isEmpty(delRes.uncompletedNodes)); @@ -427,11 +407,7 @@ public void testNodeStopsInTheMiddle() throws Exception { @Override public T createComponent(PluginContext ctx, Class cls) { if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { - @Override public boolean deleteLocalSnapshot( - SnapshotFileTree sft, - String nodeFolderName, - @Nullable AtomicBoolean existsFlag - ) { + @Override public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean existsFlag) { if (ctx.localNode().id().equals(grid(1).localNode().id())) { beginLatch.countDown(); @@ -443,7 +419,7 @@ public void testNodeStopsInTheMiddle() throws Exception { } } - return super.deleteLocalSnapshot(sft, nodeFolderName, existsFlag); + return super.deleteLocalSnapshot(sft, existsFlag); } }; } @@ -495,7 +471,7 @@ public void testConcurrentDeleteOfTheSameSnapshotDifferentPath() throws Exceptio if (incremental) addIncrementalSnapshot(null); - String snpPath = new File(U.defaultWorkDirectory(), "ex_snapshots").getAbsolutePath(); + String snpPath = new File(grid(0).context().pdsFolderResolver().fileTree().snapshotsRoot(), "ex_snapshots").getAbsolutePath(); snp(grid(0)).createSnapshot(SNAPSHOT_NAME, snpPath, false, onlyPrimary).get(getTestTimeout()); @@ -517,8 +493,8 @@ public void testConcurrentDeleteOfTheSameSnapshotDifferentPath() throws Exceptio var delRes0 = delFut0.get(getTestTimeout()); var delRes1 = delFut1.get(getTestTimeout()); - assertFalse((delRes0.completedNodes().isEmpty())); - assertFalse(delRes1.completedNodes().isEmpty()); + assertTrue(!delRes0.completedNodes().isEmpty() || !delRes0.uncompletedNodes().isEmpty()); + assertTrue(!delRes1.completedNodes().isEmpty() || !delRes1.uncompletedNodes().isEmpty()); } /** Tests that a concurrent deletion of the same snapshot is declined. */ diff --git a/parent/pom.xml b/parent/pom.xml index 7c886098b1700..aa5d04828ef77 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -101,7 +101,7 @@ 12.1.10 6.1.0 1.37 - 5.19.1 + 4.5.2 3.1.15 2.3.1-M1 1.0.5 From 0f09da96493e8a099586c52b308ee07f70230ca5 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Tue, 22 Sep 2026 19:41:14 +0300 Subject: [PATCH 44/97] + baseline check and warning --- .../GridCommandHandlerDeleteSnapshotTest.java | 7 +++ .../snapshot/SnapshotDeleteCommand.java | 21 ++++--- .../snapshot/IgniteSnapshotManager.java | 4 +- .../snapshot/SnapshotDeleteProcess.java | 57 ++++++++++++------- .../snapshot/SnapshotDeleteProcessResult.java | 20 +++++-- .../snapshot/SnapshotDeleteResponse.java | 12 +++- 6 files changed, 83 insertions(+), 38 deletions(-) diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java index f9319207dcb9e..04529999c7e9a 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java @@ -158,9 +158,13 @@ public void testSnapshotDelete() throws Exception { } } + String baselineGone = null; + // Optionally restarts with the same servers number, but changed baseline. The snapshot is kept on the same // previous nodes independently of the baseline. if (changeBaseline) { + baselineGone = grid(initNodes - 1).name(); + ig.destroyCache(DEFAULT_CACHE_NAME); awaitPartitionMapExchange(); @@ -213,6 +217,9 @@ else if (extraNodeIsServer == 0) out = testOut.toString(); + if (changeBaseline) + assertTrue(out.contains("consistent ids are missing in current cluster [cnt=1]: " + baselineGone)); + if (separatedWorkDir) { // When the nodes use own separated work directory, we expect a strict result. assertTrue(out.contains("Snapshot removed on the following nodes [cnt=%d]:".formatted(initNodes))); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java index e7262ce26942c..fc1c9f976c3d3 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java @@ -18,7 +18,6 @@ package org.apache.ignite.internal.management.snapshot; import java.util.Collection; -import java.util.UUID; import java.util.function.Consumer; import java.util.stream.Collectors; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotDeleteProcess; @@ -56,7 +55,7 @@ public class SnapshotDeleteCommand extends AbstractSnapshotCommand uuids) { - return "[cnt=" + uuids.size() + "]: " + uuids.stream().map(UUID::toString).collect(Collectors.joining(", ")); + private static String nodeIdsStrLst(Collection uuids) { + return "[cnt=" + uuids.size() + "]: " + uuids.stream().map(Object::toString).collect(Collectors.joining(", ")); } /** {@inheritDoc} */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java index f9acc93cbca46..401d349aaa610 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java @@ -776,11 +776,11 @@ public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean res = false; // Delete parent dir which is {snapshot_root}/db if empty. - if (!sft.marshaller().getParentFile().delete()) + if (!sft.marshaller().getParentFile().delete() && sft.marshaller().getParentFile().exists()) res = false; // Delete root dir which is {snapshot_root} if empty. - if (!sft.root().delete()) + if (!sft.root().delete() && sft.root().exists()) res = false; } catch (Exception e) { diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index b737bf39d30e5..e4dff90627457 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -20,6 +20,8 @@ import java.io.File; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -137,7 +139,7 @@ private IgniteInternalFuture deletePhase(UUID ignored, S } if (kctx.cluster().get().localNode().isClient()) - return new GridFinishedFuture<>(new SnapshotDeleteResponse(null)); + return new GridFinishedFuture<>(new SnapshotDeleteResponse()); kctx.security().authorize(ADMIN_SNAPSHOT); @@ -162,7 +164,7 @@ private IgniteInternalFuture deletePhase(UUID ignored, S File path = resolvePath(req.snpPath); - String pathValidationErr = validateAbsoluteSnapshotRoot(path, req.snpName); + String pathValidationErr = validateAbsoluteSnapshotRoot(path); if (pathValidationErr != null) { return new GridFinishedFuture<>(new IllegalArgumentException(OP_REJECT_MSG + @@ -188,11 +190,11 @@ private IgniteInternalFuture deletePhase(UUID ignored, S log.warning("Snapshot deletion won't process, no snapshot metadata found [req=" + req + ']'); - return new GridFinishedFuture<>(new SnapshotDeleteResponse(SnapshotDeleteResponse.SnapshotDeleteStatus.NOT_FOUND)); + return new GridFinishedFuture<>(new SnapshotDeleteResponse(SnapshotDeleteResponse.SnapshotDeleteStatus.NOT_FOUND, null)); } // Future to delete snapshot contents according to snapshot metadatas. - GridCompoundFuture resultFut = + GridCompoundFuture resultFut = new GridCompoundFuture<>(new MetaFuturesReducer()); resultFut.listen(fut -> requests.remove(req)); @@ -200,7 +202,7 @@ private IgniteInternalFuture deletePhase(UUID ignored, S File path0 = path; for (var meta : locMetas) { - GridFutureAdapter perMetaFut = new GridFutureAdapter<>(); + GridFutureAdapter perMetaFut = new GridFutureAdapter<>(); kctx.pools().getSnapshotExecutorService().submit(() -> { try { @@ -212,7 +214,7 @@ private IgniteInternalFuture deletePhase(UUID ignored, S boolean deleted = snpMgr.deleteLocalSnapshot(byMetaSft, foundFlag); - SnapshotDeleteResponse.SnapshotDeleteStatus res; + SnapshotDeleteResponse.SnapshotDeleteStatus status; if (foundFlag.get()) { if (deleted && log.isInfoEnabled()) @@ -220,7 +222,7 @@ private IgniteInternalFuture deletePhase(UUID ignored, S else if (!deleted) log.warning("Snapshot deleted not completely [req=" + req + ']'); - res = deleted + status = deleted ? SnapshotDeleteResponse.SnapshotDeleteStatus.DELETED : SnapshotDeleteResponse.SnapshotDeleteStatus.PARTLY_DELETED; } @@ -228,10 +230,10 @@ else if (!deleted) if (log.isInfoEnabled()) log.info("Snapshot not found to delete [req=" + req + ']'); - res = SnapshotDeleteResponse.SnapshotDeleteStatus.NOT_FOUND; + status = SnapshotDeleteResponse.SnapshotDeleteStatus.NOT_FOUND; } - perMetaFut.onDone(res); + perMetaFut.onDone(new SnapshotDeleteResponse(status, meta.bltNodes)); } catch (Throwable e) { perMetaFut.onDone(e); @@ -271,7 +273,7 @@ private File resolvePath(@Nullable String path) { } /** */ - private @Nullable String validateAbsoluteSnapshotRoot(@Nullable File path, String snpName) { + private @Nullable String validateAbsoluteSnapshotRoot(@Nullable File path) { if (path == null) return null; @@ -313,10 +315,14 @@ private void reducePhase(UUID reqId, Map results, var completedNodes = new ArrayList(results.size()); var uncompletedNodes = new ArrayList(results.size()); var emptyNodes = new ArrayList(results.size()); + var snpNodes = new HashSet(); results.forEach((nodeId, nodeRes) -> { - if (nodeRes.res != null) { - switch (nodeRes.res) { + if (!F.isEmpty(nodeRes.nodeIds)) + snpNodes.addAll(nodeRes.nodeIds); + + if (nodeRes.status != null) { + switch (nodeRes.status) { case NOT_FOUND: emptyNodes.add(nodeId); break; @@ -333,7 +339,10 @@ private void reducePhase(UUID reqId, Map results, } }); - clusterOpFut.onDone(new SnapshotDeleteProcessResult(completedNodes, uncompletedNodes, emptyNodes)); + kctx.discovery().baselineNodes(kctx.discovery().topologyVersionEx()).stream() + .map(bn -> bn.consistentId().toString()).toList().forEach(snpNodes::remove); + + clusterOpFut.onDone(new SnapshotDeleteProcessResult(completedNodes, uncompletedNodes, emptyNodes, snpNodes)); } catch (Throwable t) { clusterOpFut.onDone(t); @@ -371,20 +380,28 @@ public static boolean contains(File root, File candidate) { } /** */ - private static class MetaFuturesReducer implements IgniteReducer { + private static class MetaFuturesReducer implements IgniteReducer { /** Serial version uid. */ private static final long serialVersionUID = 0L; /** */ - private @Nullable SnapshotDeleteResponse.SnapshotDeleteStatus res = null; + private @Nullable SnapshotDeleteResponse.SnapshotDeleteStatus status; + + /** */ + private final Collection nodeIds = new HashSet<>(); /** {@inheritDoc} */ - @Override public boolean collect(@Nullable SnapshotDeleteResponse.SnapshotDeleteStatus status) { + @Override public boolean collect(SnapshotDeleteResponse res) { + assert res != null; + synchronized (this) { - if (res == null || res == status) - res = status; + if (!F.isEmpty(res.nodeIds)) + nodeIds.addAll(res.nodeIds); + + if (status == null || status == res.status) + status = res.status; else - res = SnapshotDeleteResponse.SnapshotDeleteStatus.PARTLY_DELETED; + status = SnapshotDeleteResponse.SnapshotDeleteStatus.PARTLY_DELETED; } return true; @@ -392,7 +409,7 @@ private static class MetaFuturesReducer implements IgniteReducer emptyNodes; + /** Snapshot's baseline nodes which aren't found in current cluster. */ + @Order(3) + Collection absentBaselines; + /** Default constructor for {@link MessageFactory}. */ public SnapshotDeleteProcessResult() { // No-op. @@ -49,25 +54,32 @@ public SnapshotDeleteProcessResult() { public SnapshotDeleteProcessResult( Collection completedNodes, Collection uncompletedNodes, - Collection emptyNodes + Collection emptyNodes, + Collection absentBaselines ) { this.completedNodes = completedNodes; this.uncompletedNodes = uncompletedNodes; this.emptyNodes = emptyNodes; + this.absentBaselines = absentBaselines; } /** */ public Collection completedNodes() { - return completedNodes; + return Collections.unmodifiableCollection(completedNodes); } /** */ public Collection uncompletedNodes() { - return uncompletedNodes; + return Collections.unmodifiableCollection(uncompletedNodes); } /** */ public Collection emptyNodes() { - return emptyNodes; + return Collections.unmodifiableCollection(emptyNodes); + } + + /** */ + public Collection absentBaselines() { + return Collections.unmodifiableCollection(absentBaselines); } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java index 917d23da2d3cb..f2a00f49d9055 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java @@ -17,6 +17,7 @@ package org.apache.ignite.internal.processors.cache.persistence.snapshot; +import java.util.Collection; import org.apache.ignite.internal.Order; import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.plugin.extensions.communication.Message; @@ -31,7 +32,11 @@ public class SnapshotDeleteResponse implements Message { /** {@code null} for client node. */ @Order(0) - @Nullable SnapshotDeleteResponse.SnapshotDeleteStatus res; + @Nullable SnapshotDeleteResponse.SnapshotDeleteStatus status; + + /** Snapshot's node ids. */ + @Order(1) + @Nullable Collection nodeIds; /** Default constructor for {@link MessageFactory}. */ public SnapshotDeleteResponse() { @@ -39,8 +44,9 @@ public SnapshotDeleteResponse() { } /** {@code null} for client node. */ - SnapshotDeleteResponse(@Nullable SnapshotDeleteResponse.SnapshotDeleteStatus res) { - this.res = res; + SnapshotDeleteResponse(SnapshotDeleteResponse.SnapshotDeleteStatus status, Collection nodeIds) { + this.status = status; + this.nodeIds = nodeIds; } /** {@inheritDoc} */ From 219c572d86ffb9c91cf4711ef7012393e91f811e Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Tue, 22 Sep 2026 22:12:50 +0300 Subject: [PATCH 45/97] + baseline check and warning --- .../GridCommandHandlerDeleteSnapshotTest.java | 2 +- .../snapshot/SnapshotDeleteCommand.java | 30 ++++++++++++------- .../snapshot/SnapshotDeleteProcess.java | 24 ++++++++++----- .../snapshot/SnapshotDeleteProcessResult.java | 25 ++++++++-------- ...usterSnapshotDeleteRollingUpgradeTest.java | 2 +- .../IgniteClusterSnapshotDeleteTest.java | 14 ++++----- .../tests/ContinuousDataLoadApplication.java | 2 +- 7 files changed, 59 insertions(+), 40 deletions(-) diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java index 04529999c7e9a..13d852e58cbc3 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java @@ -222,7 +222,7 @@ else if (extraNodeIsServer == 0) if (separatedWorkDir) { // When the nodes use own separated work directory, we expect a strict result. - assertTrue(out.contains("Snapshot removed on the following nodes [cnt=%d]:".formatted(initNodes))); + assertTrue(out.contains("Snapshot removal is completed on [cnt=%d]:".formatted(initNodes))); if (extraNodeIsServer == 1) assertTrue(out.contains("the following nodes didn't find any snapshot data, nothing to delete [cnt=1]:")); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java index fc1c9f976c3d3..50682eed74c53 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java @@ -18,6 +18,8 @@ package org.apache.ignite.internal.management.snapshot; import java.util.Collection; +import java.util.Map; +import java.util.UUID; import java.util.function.Consumer; import java.util.stream.Collectors; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotDeleteProcess; @@ -35,7 +37,7 @@ public class SnapshotDeleteCommand extends AbstractSnapshotCommand { /** {@inheritDoc} */ @Override public String description() { - return "Deletes snapshot and all its increments from all the online server nodes"; + return "Deletes snapshot and all its incrementals from all the online server nodes"; } /** {@inheritDoc} */ @@ -56,7 +58,7 @@ public class SnapshotDeleteCommand extends AbstractSnapshotCommand uuids) { - return "[cnt=" + uuids.size() + "]: " + uuids.stream().map(Object::toString).collect(Collectors.joining(", ")); + private static String nodeIdPairsStrLst(Map uuids) { + return "[cnt=" + uuids.size() + "]: " + uuids.entrySet().stream() + .map(e -> e.getValue() + " [uuid=" + e.getKey() + ']') + .collect(Collectors.joining(", ")); + } + + /** */ + private static String nodeIdsStrLst(Collection uuids) { + return "[cnt=" + uuids.size() + "]: " + String.join(", ", uuids); } /** {@inheritDoc} */ @Override public String confirmationPrompt(SnapshotDeleteCommandArg arg) { - return "This will delete snapshot '" + arg.snapshotName() + - "' and all its increments from all online server nodes." + + return "This operation will completely remove snapshot: '" + arg.snapshotName() + + "' and all its incrementals from all online server nodes." + U.nl() + U.nl() + - "WARNING: the snapshot integrity, topology and correctness aren't checked." + - " Snapshot data on offline server nodes aren't deleted." + + "NOTE: Snapshot data on offline server nodes will remain untouched." + U.nl() + U.nl() + - "The operation is irreversible."; + "The operation cannot be reverted."; } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index e4dff90627457..ed9836dd5e53d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -19,7 +19,6 @@ import java.io.File; import java.nio.file.Path; -import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -41,6 +40,7 @@ import org.apache.ignite.internal.util.future.GridFutureAdapter; import org.apache.ignite.internal.util.future.IgniteFutureImpl; import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.lang.IgniteFuture; import org.apache.ignite.lang.IgniteReducer; import org.jetbrains.annotations.Nullable; @@ -312,9 +312,9 @@ private void reducePhase(UUID reqId, Map results, return; } - var completedNodes = new ArrayList(results.size()); - var uncompletedNodes = new ArrayList(results.size()); - var emptyNodes = new ArrayList(results.size()); + Map completedNodes = U.newHashMap(results.size()); + Map uncompletedNodes = U.newHashMap(results.size()); + Map emptyNodes = U.newHashMap(results.size()); var snpNodes = new HashSet(); results.forEach((nodeId, nodeRes) -> { @@ -324,13 +324,13 @@ private void reducePhase(UUID reqId, Map results, if (nodeRes.status != null) { switch (nodeRes.status) { case NOT_FOUND: - emptyNodes.add(nodeId); + emptyNodes.put(nodeId, consistentId(nodeId)); break; case DELETED: - completedNodes.add(nodeId); + completedNodes.put(nodeId, consistentId(nodeId)); break; case PARTLY_DELETED: - uncompletedNodes.add(nodeId); + uncompletedNodes.put(nodeId, consistentId(nodeId)); break; default: throw new IgniteIllegalStateException("Unknown snapshot deletion node result, [nodeRes=" + @@ -349,6 +349,16 @@ private void reducePhase(UUID reqId, Map results, } } + /** */ + private String consistentId(UUID nodeId) { + var node = kctx.discovery().node(nodeId); + + if (node == null) + node = kctx.discovery().historicalNode(nodeId); + + return node == null ? "" : node.consistentId().toString(); + } + /** */ public boolean isDeleting(String snpName, @Nullable String snpPath) { var rq = new SnapshotDeleteRequest(null, snpName, snpPath); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResult.java index 7a714e711050d..9ad76dc314a76 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResult.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResult.java @@ -19,6 +19,7 @@ import java.util.Collection; import java.util.Collections; +import java.util.Map; import java.util.UUID; import org.apache.ignite.internal.Order; import org.apache.ignite.internal.dto.IgniteDataTransferObject; @@ -31,15 +32,15 @@ public final class SnapshotDeleteProcessResult extends IgniteDataTransferObject /** Nodes which found snapshot data and completely removed it. */ @Order(0) - Collection completedNodes; + Map completedNodes; /** Nodes which found snapshot data but didn't remove it completely. */ @Order(1) - Collection uncompletedNodes; + Map uncompletedNodes; /** Server nodes which didn't find any snapshot data. */ @Order(2) - Collection emptyNodes; + Map emptyNodes; /** Snapshot's baseline nodes which aren't found in current cluster. */ @Order(3) @@ -52,9 +53,9 @@ public SnapshotDeleteProcessResult() { /** */ public SnapshotDeleteProcessResult( - Collection completedNodes, - Collection uncompletedNodes, - Collection emptyNodes, + Map completedNodes, + Map uncompletedNodes, + Map emptyNodes, Collection absentBaselines ) { this.completedNodes = completedNodes; @@ -64,18 +65,18 @@ public SnapshotDeleteProcessResult( } /** */ - public Collection completedNodes() { - return Collections.unmodifiableCollection(completedNodes); + public Map completedNodes() { + return Collections.unmodifiableMap(completedNodes); } /** */ - public Collection uncompletedNodes() { - return Collections.unmodifiableCollection(uncompletedNodes); + public Map uncompletedNodes() { + return Collections.unmodifiableMap(uncompletedNodes); } /** */ - public Collection emptyNodes() { - return Collections.unmodifiableCollection(emptyNodes); + public Map emptyNodes() { + return Collections.unmodifiableMap(emptyNodes); } /** */ diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgradeTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgradeTest.java index 9f2e12bae90e2..76cd9a97de859 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgradeTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgradeTest.java @@ -85,7 +85,7 @@ public class IgniteClusterSnapshotDeleteRollingUpgradeTest extends AbstractRolli /** */ @Test - public void testConcurrentUnfinishedRU() throws Exception { + public void testParallelRollingUpgradeInProgress() throws Exception { for (int i = 0; i < ALL_GRIDS; i++) startGrid(i, "2.19.0", i >= ALL_GRIDS - CLIENTS); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java index 5b9b1a84950e4..cedaa5e7c25fb 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java @@ -167,7 +167,7 @@ public void testUncompletedNodes() throws Exception { assertTrue(F.isEmpty(delSnpRes.emptyNodes)); assertFalse(F.isEmpty(delSnpRes.uncompletedNodes)); - assertTrue(delSnpRes.uncompletedNodes.contains(grid(1).localNode().id())); + assertTrue(delSnpRes.uncompletedNodes.containsKey(grid(1).localNode().id())); } /** */ @@ -232,7 +232,7 @@ protected void doTestDeleteNotSnapshot(boolean separatedWorkDir) throws Exceptio assertTrue(F.isEmpty(delSnpRes.uncompletedNodes)); assertEquals(2, delSnpRes.completedNodes.size()); assertEquals(1, delSnpRes.emptyNodes.size()); - assertTrue(delSnpRes.emptyNodes.contains(grid(1).localNode().id())); + assertTrue(delSnpRes.emptyNodes.containsKey(grid(1).localNode().id())); assertTrue(snpSft.binaryMeta().exists()); } else @@ -344,7 +344,7 @@ public void testEmptyNodes() throws Exception { var delSnpRes = snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); assertFalse(F.isEmpty(delSnpRes.emptyNodes)); - assertTrue(delSnpRes.emptyNodes.contains(grid(G.allGrids().size() - 1).localNode().id())); + assertTrue(delSnpRes.emptyNodes.containsKey(grid(G.allGrids().size() - 1).localNode().id())); assertTrue(F.isEmpty(delSnpRes.uncompletedNodes)); } @@ -369,7 +369,7 @@ public void testDeletionRepeatAfterOfflineNodeStarts() throws Exception { var delSnpRes = snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); assertEquals(2, delSnpRes.completedNodes.size()); - assertFalse(delSnpRes.completedNodes.contains(stoppedNodeId)); + assertFalse(delSnpRes.completedNodes.containsKey(stoppedNodeId)); assertTrue(F.isEmpty(delSnpRes.uncompletedNodes)); assertTrue(F.isEmpty(delSnpRes.emptyNodes)); @@ -381,7 +381,7 @@ public void testDeletionRepeatAfterOfflineNodeStarts() throws Exception { delSnpRes = snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); assertEquals(1, delSnpRes.completedNodes.size()); - assertTrue(delSnpRes.completedNodes.contains(stoppedNodeId)); + assertTrue(delSnpRes.completedNodes.containsKey(stoppedNodeId)); assertTrue(F.isEmpty(delSnpRes.uncompletedNodes)); assertEquals(2, delSnpRes.emptyNodes.size()); @@ -448,14 +448,14 @@ public void testNodeStopsInTheMiddle() throws Exception { var delRes = delFut.get(getTestTimeout()); assertEquals(2, delRes.completedNodes.size()); - assertFalse(delRes.completedNodes.contains(stoppedGridId)); + assertFalse(delRes.completedNodes.containsKey(stoppedGridId)); startGrid(1); delRes = snp(grid(2)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); assertEquals(1, delRes.completedNodes.size()); - assertTrue(delRes.completedNodes.contains(grid(1).localNode().id())); + assertTrue(delRes.completedNodes.containsKey(grid(1).localNode().id())); } /** Tests that a concurrent deletion of a snapshot with the same name but different path is allowed. */ diff --git a/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/ContinuousDataLoadApplication.java b/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/ContinuousDataLoadApplication.java index bff6187c71532..0b743036dfebc 100644 --- a/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/ContinuousDataLoadApplication.java +++ b/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/ContinuousDataLoadApplication.java @@ -78,7 +78,7 @@ public class ContinuousDataLoadApplication extends IgniteAwareApplication { if (notifyTime + TimeUnit.MILLISECONDS.toNanos(1500) < System.nanoTime()) notifyTime = System.nanoTime(); - // Delayed notify of the initialization to make sure the data load has completelly began and + // Delayed notify of the initialization to make sure the data load has completely began and // has produced some valuable amount of data. if (!inited() && warmUpCnt == loaded) markInitialized(); From d8ce7d80a2cad47f2457079397900e8a653a4daa Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Tue, 22 Sep 2026 22:13:06 +0300 Subject: [PATCH 46/97] review fixes --- docs/_docs/snapshots/snapshots.adoc | 5 +- .../snapshot/SnapshotDeleteCommand.java | 59 +++++++++++-------- .../snapshot/SnapshotDeleteCommandArg.java | 3 +- .../GridDhtPartitionsSingleMessage.java | 2 +- .../snapshot/SnapshotDeleteResponse.java | 2 +- ...usterSnapshotDeleteRollingUpgradeTest.java | 4 +- 6 files changed, 42 insertions(+), 33 deletions(-) diff --git a/docs/_docs/snapshots/snapshots.adoc b/docs/_docs/snapshots/snapshots.adoc index 154fd57192136..61da73cc679ba 100644 --- a/docs/_docs/snapshots/snapshots.adoc +++ b/docs/_docs/snapshots/snapshots.adoc @@ -324,10 +324,9 @@ control.bat --snapshot delete snapshot_09062021 --src /tmp/ignite/snapshots The delete operation is subject to the following limitations: -* The deletion is rejected if any concurrent snapshot operation (create, restore, check, or a delete with the same name) is +* The deletion is rejected if any snapshot operation with the same name (create, restore, check, or a delete with the same name) is active for the snapshot. -* The operation is irreversible. It cannot be undone, and the deleted snapshot cannot be restored. -* The command prompts for a confirmation before the deletion because the operation is irreversible and cannot be undone. +* The operation cannot be cancelled. It cannot be undone, and the deleted snapshot cannot be restored. The command prompts for a confirmation. == Getting Snapshot Operation Status diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java index e7262ce26942c..672b063872966 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java @@ -17,10 +17,7 @@ package org.apache.ignite.internal.management.snapshot; -import java.util.Collection; -import java.util.UUID; import java.util.function.Consumer; -import java.util.stream.Collectors; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotDeleteProcess; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotDeleteProcessResult; import org.apache.ignite.internal.processors.rollingupgrade.feature.SupportedFeatureRegistry; @@ -34,9 +31,26 @@ * @see SnapshotDeleteProcess */ public class SnapshotDeleteCommand extends AbstractSnapshotCommand { + /** */ + public static final String DESC = "Deletes snapshot and all its incrementals from all the online server nodes"; + + /** */ + public static final String UNSURED_DELETION_PREF = "WARNING: the following nodes found snapshot data but might not " + + "remove it completely "; + + /** */ + public static final String REMOVED_PREF = "Snapshot removal is completed on: "; + + /** */ + public static final String SOME_NODES_NOT_FOUND_PREF = "NOTE: the following nodes can't find any snapshot data, " + + "operation skipped "; + + /** */ + public static final String NOT_FOUND_PREF = "Snapshot not found on current server nodes."; + /** {@inheritDoc} */ @Override public String description() { - return "Deletes snapshot and all its increments from all the online server nodes"; + return DESC; } /** {@inheritDoc} */ @@ -56,8 +70,9 @@ public class SnapshotDeleteCommand extends AbstractSnapshotCommand uuids) { - return "[cnt=" + uuids.size() + "]: " + uuids.stream().map(UUID::toString).collect(Collectors.joining(", ")); + printer.accept(NOT_FOUND_PREF); + } } /** {@inheritDoc} */ @Override public String confirmationPrompt(SnapshotDeleteCommandArg arg) { - return "This will delete snapshot '" + arg.snapshotName() + - "' and all its increments from all online server nodes." + + return "This operation will completelly remove snapshot: '" + arg.snapshotName() + + "' and all its incrementals from all online server nodes." + U.nl() + U.nl() + - "WARNING: the snapshot integrity, topology and correctness aren't checked." + - " Snapshot data on offline server nodes aren't deleted." + + "NOTE: Snapshot data on offline server nodes will remain untouched." + U.nl() + U.nl() + "The operation is irreversible."; } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommandArg.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommandArg.java index 2b7063de333bb..b83e38e29220d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommandArg.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommandArg.java @@ -36,8 +36,7 @@ public class SnapshotDeleteCommandArg extends IgniteDataTransferObject { /** */ @Order(1) @Argument(example = "path", optional = true, - description = "Path to the directory where the snapshot is located. " + - "If not specified, the default configured snapshot directory will be used") + description = "Path to snapshot location directory. If not specified, the default snapshot configuratuion directory will be used.") String src; /** */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsSingleMessage.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsSingleMessage.java index 1857aabd1f965..a9f1f42ca872b 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsSingleMessage.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsSingleMessage.java @@ -34,7 +34,7 @@ /** * Information about partitions of a single node. Sent in response to {@link GridDhtPartitionsSingleRequest} and during * processing partitions exchange future.
- * Has to be completely restored after receiving from another node. + * Has to be completelly restored after receiving from another node. * * @see #afterReceive() */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java index 917d23da2d3cb..fe85481c710ba 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java @@ -49,7 +49,7 @@ public SnapshotDeleteResponse() { } /** */ - public enum SnapshotDeleteStatus { + enum SnapshotDeleteStatus { /** Snapshot found and completely deleted. */ DELETED, diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgradeTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgradeTest.java index 6c608f25a07a5..d282244b316d6 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgradeTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgradeTest.java @@ -72,7 +72,7 @@ public class IgniteClusterSnapshotDeleteRollingUpgradeTest extends AbstractRolli /** */ @Test - public void testConcurrentUnfinishedRU() throws Exception { + public void testParallelRollingUpgradeInProgress() throws Exception { for (int i = 0; i < ALL_GRIDS; i++) startGrid(i, "2.19.0", i >= ALL_GRIDS - CLIENTS); @@ -138,7 +138,7 @@ public void testNodeNotSupportingSnapshotDeleteFeature() throws Exception { for (int i = 0; i < ALL_GRIDS; i++) { assertFalse(ru(grid(i)).isVersionUpgradeEnabled()); - assertFalse(F.isEmpty(snp(i).deleteSnapshot(SNP_NAME, null).get().completedNodes)); + assertTrue(F.isEmpty(snp(i).deleteSnapshot(SNP_NAME, null).get().completedNodes)); if (i < ALL_GRIDS - 1) createSnapshot(i); From 95bc7a105d8592309defe4786638a125498d0bd0 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Tue, 22 Sep 2026 23:57:49 +0300 Subject: [PATCH 47/97] fixes --- .../GridCommandHandlerDeleteSnapshotTest.java | 18 ++++++------ .../snapshot/SnapshotDeleteCommand.java | 28 ++++++++++--------- .../snapshot/SnapshotDeleteCommandArg.java | 4 +-- .../IgniteClusterSnapshotDeleteTest.java | 14 ++++++++-- ...mmandHandlerClusterByClassTest_help.output | 4 +-- ...ndlerClusterByClassWithSSLTest_help.output | 4 +-- 6 files changed, 42 insertions(+), 30 deletions(-) diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java index 13d852e58cbc3..59f2e3e6de62b 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java @@ -25,6 +25,7 @@ import org.apache.ignite.IgniteDataStreamer; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.management.snapshot.SnapshotDeleteCommand; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.testframework.GridTestUtils; @@ -201,9 +202,9 @@ else if (extraNodeIsServer == 0) String out = testOut.toString(); - assertFalse(out.contains("Snapshot removed on the following nodes")); - assertFalse(out.contains("the following nodes didn't find any snapshot data, nothing to delete")); - assertTrue(out.contains("Snapshot not found on current server nodes")); + assertFalse(out.contains(SnapshotDeleteCommand.REMOVED_PREF)); + assertFalse(out.contains(SnapshotDeleteCommand.NODE_NOT_FOUND_PREF)); + assertTrue(out.contains(SnapshotDeleteCommand.NOT_FOUND_PREF)); testOut.reset(); assertTrue(testOut.toString().isEmpty()); @@ -218,23 +219,22 @@ else if (extraNodeIsServer == 0) out = testOut.toString(); if (changeBaseline) - assertTrue(out.contains("consistent ids are missing in current cluster [cnt=1]: " + baselineGone)); + assertTrue(out.contains(SnapshotDeleteCommand.MISSING_BASELINES + "[cnt=1]: " + baselineGone)); if (separatedWorkDir) { // When the nodes use own separated work directory, we expect a strict result. - assertTrue(out.contains("Snapshot removal is completed on [cnt=%d]:".formatted(initNodes))); + assertTrue(out.contains(SnapshotDeleteCommand.REMOVED_PREF + "[cnt=%d]:".formatted(initNodes))); if (extraNodeIsServer == 1) - assertTrue(out.contains("the following nodes didn't find any snapshot data, nothing to delete [cnt=1]:")); + assertTrue(out.contains(SnapshotDeleteCommand.NODE_NOT_FOUND_PREF + "[cnt=1]:")); else if (extraNodeIsServer == 0) - assertFalse(out.contains("the following nodes didn't find any snapshot data, nothing to delete")); + assertFalse(out.contains(SnapshotDeleteCommand.NODE_NOT_FOUND_PREF)); } else { // When nodes use a shared work directory, there is a race for the delete operation. One node can get faster // than others and remove snapshot completely quickly. The others might not find snapshot files. We can be // only sure that at least one node removes snapshot. - assertTrue(out.contains("Snapshot removed on the following nodes [cnt=") - || out.contains("the following nodes found snapshot data but might not remove it completely [cnt=")); + assertTrue(out.contains(SnapshotDeleteCommand.REMOVED_PREF) || out.contains(SnapshotDeleteCommand.UNSURED_DELETION_PREF)); } assertFalse(out.contains("Snapshot not found on current server nodes")); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java index 6b1c61f216042..bd54538cae4a9 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java @@ -21,10 +21,10 @@ import java.util.Map; import java.util.UUID; import java.util.function.Consumer; +import java.util.stream.Collectors; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotDeleteProcess; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotDeleteProcessResult; import org.apache.ignite.internal.processors.rollingupgrade.feature.SupportedFeatureRegistry; -import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.internal.U; /** @@ -42,20 +42,22 @@ public class SnapshotDeleteCommand extends AbstractSnapshotCommand printer) { boolean found = false; - if (!F.isEmpty(res.uncompletedNodes())) { + if (!res.uncompletedNodes().isEmpty()) { found = true; printer.accept(UNSURED_DELETION_PREF + nodeIdPairsStrLst(res.uncompletedNodes())); @@ -82,7 +84,7 @@ public class SnapshotDeleteCommand extends AbstractSnapshotCommand uuids) { /** {@inheritDoc} */ @Override public String confirmationPrompt(SnapshotDeleteCommandArg arg) { - return "This operation will completely remove snapshot: '" + arg.snapshotName() + - "' and all its incrementals from all online server nodes." + + return "This operation will completely remove snapshot: '" + arg.snapshotName() + "' and all its incrementals " + + "from all online server nodes." + U.nl() + U.nl() + "NOTE: Snapshot data on offline server nodes will remain untouched." + U.nl() + U.nl() + diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommandArg.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommandArg.java index b83e38e29220d..3539a556fb420 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommandArg.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommandArg.java @@ -35,8 +35,8 @@ public class SnapshotDeleteCommandArg extends IgniteDataTransferObject { /** */ @Order(1) - @Argument(example = "path", optional = true, - description = "Path to snapshot location directory. If not specified, the default snapshot configuratuion directory will be used.") + @Argument(example = "path", optional = true, description = "Path to snapshot location directory. If not specified " + + "or specified a relative path, the default snapshot configuration directory will be used") String src; /** */ diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java index cedaa5e7c25fb..2308465de5f99 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java @@ -129,6 +129,10 @@ public static Collection runParams() { /** Tests snapshot deletion when one node finds snapshot but fails to delete its data. */ @Test public void testUncompletedNodes() throws Exception { + // Incremental snapshots don't support encryption and only-primary mode. + assumeTrue(!incremental || !encryption); + assumeTrue(!incremental || !onlyPrimary); + separatedWorkDir = true; // Simulates a deletion error on some node. @@ -330,6 +334,10 @@ public void testDeletionInIgnite() throws Exception { /** Tests snapshot deletion when one node has no snapshot data. */ @Test public void testEmptyNodes() throws Exception { + // Incremental snapshots don't support encryption and only-primary mode. + assumeTrue(!incremental || !encryption); + assumeTrue(!incremental || !onlyPrimary); + separatedWorkDir = true; startGridsWithCache(2, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); @@ -511,8 +519,9 @@ public void testConcurrentDeleteOfTheSameSnapshot() throws Exception { /** Tests that a snapshot deletion is declined when a snapshot check operation is in progress. */ @Test public void testSnapshotDeleteWhenCheckInProgress() throws Exception { - // Incremental snapshots don't support encryption. + // Incremental snapshots don't support encryption and only-primary mode. assumeTrue(!incremental || !encryption); + assumeTrue(!incremental || !onlyPrimary); doTestConcurrentSnapshotDelete( () -> new IgniteFutureImpl<>(snp(grid(2)).checkSnapshot(SNAPSHOT_NAME, null, incremental ? 1 : 0)), @@ -573,8 +582,9 @@ public void testSnapshotDeleteWhenRestoreBegins() throws Exception { /** Tests that a snapshot deletion is declined when a snapshot restore is in progress. */ @Test public void testSnapshotDeleteWhenRestoreInProgress() throws Exception { - // Incremental snapshots don't support encryption. + // Incremental snapshots don't support encryption and only-primary mode. assumeTrue(!incremental || !encryption); + assumeTrue(!incremental || !onlyPrimary); var restoreMsgs = F.asList( RESTORE_CACHE_GROUP_SNAPSHOT_PREPARE, diff --git a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output index c122892ee1f66..e0ddc44cf24d7 100644 --- a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output +++ b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output @@ -269,12 +269,12 @@ This utility can do the following commands: Get the status of the current snapshot operation: control.(sh|bat) --snapshot status - Deletes snapshot and all its increments from all the online server nodes: + Deletes snapshot and all its incrementals from all the online server nodes: control.(sh|bat) --snapshot delete snapshot_name [--src path] Parameters: snapshot_name - Snapshot name. - --src path - Path to the directory where the snapshot is located. If not specified, the default configured snapshot directory will be used. + --src path - Path to snapshot location directory. If not specified or specified a relative path, the default snapshot configuration directory will be used. Change cluster tag to new value: control.(sh|bat) --change-tag newTagValue [--yes] diff --git a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output index 30a77600c6efa..6ddf5d9106536 100644 --- a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output +++ b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output @@ -269,12 +269,12 @@ This utility can do the following commands: Get the status of the current snapshot operation: control.(sh|bat) --snapshot status - Deletes snapshot and all its increments from all the online server nodes: + Deletes snapshot and all its incrementals from all the online server nodes: control.(sh|bat) --snapshot delete snapshot_name [--src path] Parameters: snapshot_name - Snapshot name. - --src path - Path to the directory where the snapshot is located. If not specified, the default configured snapshot directory will be used. + --src path - Path to snapshot location directory. If not specified or specified a relative path, the default snapshot configuration directory will be used. Change cluster tag to new value: control.(sh|bat) --change-tag newTagValue [--yes] From 501a6b98d1c0c0cce055117ac05998ce6a592098 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Wed, 23 Sep 2026 00:38:15 +0300 Subject: [PATCH 48/97] ru fixes --- .../feature/TestIgniteReleaseFeatures_2_20_0.java | 7 +++++-- .../feature/TestIgniteReleaseFeatures_2_20_1.java | 2 +- .../feature/TestIgniteReleaseFeatures_2_21_0.java | 4 ++-- .../feature/TestIgniteReleaseFeatures_2_21_1.java | 5 ++++- parent/pom.xml | 2 +- 5 files changed, 13 insertions(+), 7 deletions(-) diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_20_0.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_20_0.java index 94719dfd6aabb..eb59adf5afa95 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_20_0.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_20_0.java @@ -22,6 +22,9 @@ public class TestIgniteReleaseFeatures_2_20_0 { /** */ public static final IgniteFeature ROLLING_UPGRADE_FEATURE = TestIgniteReleaseFeatures_2_19_2.ROLLING_UPGRADE_FEATURE; + /** */ + public static final IgniteFeature SNAPSHOT_DELETE_FEATURE = TestIgniteReleaseFeatures_2_19_1.SNAPSHOT_DELETE_FEATURE; + /** */ public static final IgniteFeature VER_2_19_2_ID_1_FEATURE = TestIgniteReleaseFeatures_2_19_2.VER_2_19_2_ID_1_FEATURE; @@ -29,8 +32,8 @@ public class TestIgniteReleaseFeatures_2_20_0 { public static final IgniteFeature VER_2_19_2_ID_2_FEATURE = TestIgniteReleaseFeatures_2_19_2.VER_2_19_2_ID_2_FEATURE; /** */ - public static final IgniteFeature VER_2_20_0_ID_3_FEATURE = new IgniteCoreFeature(3); + public static final IgniteFeature VER_2_20_0_ID_3_FEATURE = new IgniteCoreFeature(VER_2_19_2_ID_2_FEATURE.id() + 1); /** */ - public static final IgniteFeature VER_2_20_0_ID_4_FEATURE = new IgniteCoreFeature(4); + public static final IgniteFeature VER_2_20_0_ID_4_FEATURE = new IgniteCoreFeature(VER_2_20_0_ID_3_FEATURE.id() + 1); } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_20_1.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_20_1.java index 4bb051bb631ab..dab682dfaa758 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_20_1.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_20_1.java @@ -35,5 +35,5 @@ public class TestIgniteReleaseFeatures_2_20_1 { public static final IgniteFeature VER_2_20_0_ID_4_FEATURE = TestIgniteReleaseFeatures_2_20_0.VER_2_20_0_ID_4_FEATURE; /** */ - public static final IgniteFeature VER_2_20_1_ID_6_FEATURE = new IgniteCoreFeature(6); + public static final IgniteFeature VER_2_20_1_ID_6_FEATURE = new IgniteCoreFeature(VER_2_20_0_ID_4_FEATURE.id() + 1); } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_21_0.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_21_0.java index fe6b190119774..875581f1f6034 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_21_0.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_21_0.java @@ -26,8 +26,8 @@ public class TestIgniteReleaseFeatures_2_21_0 { public static final IgniteFeature VER_2_20_0_ID_4_FEATURE = TestIgniteReleaseFeatures_2_20_1.VER_2_20_0_ID_4_FEATURE; /** */ - public static final IgniteFeature VER_2_21_0_ID_5_FEATURE = new IgniteCoreFeature(5); + public static final IgniteFeature VER_2_21_0_ID_5_FEATURE = new IgniteCoreFeature(VER_2_20_0_ID_4_FEATURE.id() + 1); /** */ - public static final IgniteFeature VER_2_21_0_ID_6_FEATURE = new IgniteCoreFeature(6); + public static final IgniteFeature VER_2_21_0_ID_6_FEATURE = new IgniteCoreFeature(VER_2_21_0_ID_5_FEATURE.id() + 1); } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_21_1.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_21_1.java index 6ec00046d1b51..3cae85f97a55d 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_21_1.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_21_1.java @@ -19,6 +19,9 @@ /** */ public class TestIgniteReleaseFeatures_2_21_1 { + /** */ + public static final IgniteFeature SNAPSHOT_DELETE_FEATURE = TestIgniteReleaseFeatures_2_19_1.SNAPSHOT_DELETE_FEATURE; + /** */ public static final IgniteFeature VER_2_20_0_ID_3_FEATURE = TestIgniteReleaseFeatures_2_21_0.VER_2_20_0_ID_3_FEATURE; @@ -32,5 +35,5 @@ public class TestIgniteReleaseFeatures_2_21_1 { public static final IgniteFeature VER_2_21_0_ID_6_FEATURE = TestIgniteReleaseFeatures_2_21_0.VER_2_21_0_ID_6_FEATURE; /** */ - public static final IgniteFeature VER_2_21_1_ID_7_FEATURE = new IgniteCoreFeature(7); + public static final IgniteFeature VER_2_21_1_ID_7_FEATURE = new IgniteCoreFeature(VER_2_21_0_ID_6_FEATURE.id() + 1); } diff --git a/parent/pom.xml b/parent/pom.xml index aa5d04828ef77..7c886098b1700 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -101,7 +101,7 @@ 12.1.10 6.1.0 1.37 - 4.5.2 + 5.19.1 3.1.15 2.3.1-M1 1.0.5 From 23fe41cb3ddcbe58bcd278dae6b554c07a133b49 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Wed, 23 Sep 2026 01:12:33 +0300 Subject: [PATCH 49/97] ru fixes --- .../feature/TestIgniteReleaseFeatures_2_19_2.java | 5 ++--- .../feature/TestIgniteReleaseFeatures_2_20_0.java | 7 ++----- .../feature/TestIgniteReleaseFeatures_2_20_1.java | 2 +- .../feature/TestIgniteReleaseFeatures_2_21_0.java | 4 ++-- .../feature/TestIgniteReleaseFeatures_2_21_1.java | 5 +---- 5 files changed, 8 insertions(+), 15 deletions(-) diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_2.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_2.java index cdf502e7e567f..0ad70357e73a9 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_2.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_2.java @@ -23,9 +23,8 @@ public class TestIgniteReleaseFeatures_2_19_2 { public static final IgniteFeature ROLLING_UPGRADE_FEATURE = TestIgniteReleaseFeatures_2_19_1.ROLLING_UPGRADE_FEATURE; /** */ - public static final IgniteFeature VER_2_19_2_ID_1_FEATURE = - new IgniteCoreFeature(TestIgniteReleaseFeatures_2_19_1.SNAPSHOT_DELETE_FEATURE.id() + 1); + public static final IgniteFeature VER_2_19_2_ID_1_FEATURE = new IgniteCoreFeature(1); /** */ - public static final IgniteFeature VER_2_19_2_ID_2_FEATURE = new IgniteCoreFeature(VER_2_19_2_ID_1_FEATURE.id() + 1); + public static final IgniteFeature VER_2_19_2_ID_2_FEATURE = new IgniteCoreFeature(2); } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_20_0.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_20_0.java index eb59adf5afa95..94719dfd6aabb 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_20_0.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_20_0.java @@ -22,9 +22,6 @@ public class TestIgniteReleaseFeatures_2_20_0 { /** */ public static final IgniteFeature ROLLING_UPGRADE_FEATURE = TestIgniteReleaseFeatures_2_19_2.ROLLING_UPGRADE_FEATURE; - /** */ - public static final IgniteFeature SNAPSHOT_DELETE_FEATURE = TestIgniteReleaseFeatures_2_19_1.SNAPSHOT_DELETE_FEATURE; - /** */ public static final IgniteFeature VER_2_19_2_ID_1_FEATURE = TestIgniteReleaseFeatures_2_19_2.VER_2_19_2_ID_1_FEATURE; @@ -32,8 +29,8 @@ public class TestIgniteReleaseFeatures_2_20_0 { public static final IgniteFeature VER_2_19_2_ID_2_FEATURE = TestIgniteReleaseFeatures_2_19_2.VER_2_19_2_ID_2_FEATURE; /** */ - public static final IgniteFeature VER_2_20_0_ID_3_FEATURE = new IgniteCoreFeature(VER_2_19_2_ID_2_FEATURE.id() + 1); + public static final IgniteFeature VER_2_20_0_ID_3_FEATURE = new IgniteCoreFeature(3); /** */ - public static final IgniteFeature VER_2_20_0_ID_4_FEATURE = new IgniteCoreFeature(VER_2_20_0_ID_3_FEATURE.id() + 1); + public static final IgniteFeature VER_2_20_0_ID_4_FEATURE = new IgniteCoreFeature(4); } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_20_1.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_20_1.java index dab682dfaa758..4bb051bb631ab 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_20_1.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_20_1.java @@ -35,5 +35,5 @@ public class TestIgniteReleaseFeatures_2_20_1 { public static final IgniteFeature VER_2_20_0_ID_4_FEATURE = TestIgniteReleaseFeatures_2_20_0.VER_2_20_0_ID_4_FEATURE; /** */ - public static final IgniteFeature VER_2_20_1_ID_6_FEATURE = new IgniteCoreFeature(VER_2_20_0_ID_4_FEATURE.id() + 1); + public static final IgniteFeature VER_2_20_1_ID_6_FEATURE = new IgniteCoreFeature(6); } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_21_0.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_21_0.java index 875581f1f6034..fe6b190119774 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_21_0.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_21_0.java @@ -26,8 +26,8 @@ public class TestIgniteReleaseFeatures_2_21_0 { public static final IgniteFeature VER_2_20_0_ID_4_FEATURE = TestIgniteReleaseFeatures_2_20_1.VER_2_20_0_ID_4_FEATURE; /** */ - public static final IgniteFeature VER_2_21_0_ID_5_FEATURE = new IgniteCoreFeature(VER_2_20_0_ID_4_FEATURE.id() + 1); + public static final IgniteFeature VER_2_21_0_ID_5_FEATURE = new IgniteCoreFeature(5); /** */ - public static final IgniteFeature VER_2_21_0_ID_6_FEATURE = new IgniteCoreFeature(VER_2_21_0_ID_5_FEATURE.id() + 1); + public static final IgniteFeature VER_2_21_0_ID_6_FEATURE = new IgniteCoreFeature(6); } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_21_1.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_21_1.java index 3cae85f97a55d..6ec00046d1b51 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_21_1.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_21_1.java @@ -19,9 +19,6 @@ /** */ public class TestIgniteReleaseFeatures_2_21_1 { - /** */ - public static final IgniteFeature SNAPSHOT_DELETE_FEATURE = TestIgniteReleaseFeatures_2_19_1.SNAPSHOT_DELETE_FEATURE; - /** */ public static final IgniteFeature VER_2_20_0_ID_3_FEATURE = TestIgniteReleaseFeatures_2_21_0.VER_2_20_0_ID_3_FEATURE; @@ -35,5 +32,5 @@ public class TestIgniteReleaseFeatures_2_21_1 { public static final IgniteFeature VER_2_21_0_ID_6_FEATURE = TestIgniteReleaseFeatures_2_21_0.VER_2_21_0_ID_6_FEATURE; /** */ - public static final IgniteFeature VER_2_21_1_ID_7_FEATURE = new IgniteCoreFeature(VER_2_21_0_ID_6_FEATURE.id() + 1); + public static final IgniteFeature VER_2_21_1_ID_7_FEATURE = new IgniteCoreFeature(7); } From 0e967408cd325fd793feca50f0c33fc0b3804021 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Wed, 23 Sep 2026 01:18:48 +0300 Subject: [PATCH 50/97] doc fix --- docs/_docs/snapshots/snapshots.adoc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/_docs/snapshots/snapshots.adoc b/docs/_docs/snapshots/snapshots.adoc index 61da73cc679ba..f44a1e7b7dc02 100644 --- a/docs/_docs/snapshots/snapshots.adoc +++ b/docs/_docs/snapshots/snapshots.adoc @@ -326,7 +326,9 @@ The delete operation is subject to the following limitations: * The deletion is rejected if any snapshot operation with the same name (create, restore, check, or a delete with the same name) is active for the snapshot. -* The operation cannot be cancelled. It cannot be undone, and the deleted snapshot cannot be restored. The command prompts for a confirmation. +* The operation cannot be undone and the deleted snapshot cannot be restored. The command prompts for a confirmation. +* Before deletion, no snapshot validation is done except finding and reading its metadata. If the metadata isn't found or cannot be read, snapshot isn't deleted. +* If the provided snapshot path belongs to Ignite's home or to Ignite's a not snapshot work directory, the operation fails. == Getting Snapshot Operation Status From 204acaa24275b4b08026dce55e9329b7d0a09a81 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Wed, 23 Sep 2026 01:39:53 +0300 Subject: [PATCH 51/97] + corrupted metafile test --- .../snapshot/IgniteSnapshotManager.java | 30 +++++++++++--- .../snapshot/SnapshotDeleteProcess.java | 2 +- .../IgniteClusterSnapshotDeleteTest.java | 40 +++++++++++++++---- 3 files changed, 59 insertions(+), 13 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java index 401d349aaa610..b3400dacdfc0b 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java @@ -1930,6 +1930,21 @@ public T readFromFile(File smf) throws IgniteCheckedException, IOException { * local node will be placed on the first place. */ public List readSnapshotMetadatas(SnapshotFileTree sft) { + return readSnapshotMetadatas(sft, true); + } + + /** + * Note, there can be snapshots from other nodes. + * This method will read all metadata. + * Some instances can return {@link SnapshotMetadata#folderName()} and {@link SnapshotMetadata#consistentId()} that differs from local. + * + * @param sft Snapshot file tree. + * @param failIfCantRead If {@code true}, throws an exeption if cant read a metadata file. + * @return List of snapshot metadata for the given snapshot name on local node. + * If snapshot has been taken from local node the snapshot metadata for given + * local node will be placed on the first place. + */ + public List readSnapshotMetadatas(SnapshotFileTree sft, boolean failIfCantRead) { if (!(sft.root().exists() && sft.root().isDirectory())) return Collections.emptyList(); @@ -1941,8 +1956,8 @@ public List readSnapshotMetadatas(SnapshotFileTree sft) { Map metasMap = new HashMap<>(); SnapshotMetadata prev = null; - try { - for (File smf : smfs) { + for (File smf : smfs) { + try { SnapshotMetadata curr = readSnapshotMetadata(smf); if (prev != null && !prev.sameSnapshot(curr)) { @@ -1954,9 +1969,14 @@ public List readSnapshotMetadatas(SnapshotFileTree sft) { prev = curr; } - } - catch (IgniteCheckedException | IOException e) { - throw new IgniteException(e); + catch (Exception e) { + String err = "Fail to read snapshot metadata [meta=" + smf + ']'; + + if (failIfCantRead) + throw new IgniteException(err, e); + else + log.error(err, e); + } } SnapshotMetadata currNodeSmf = metasMap.remove(cctx.localNode().consistentId().toString()); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index ed9836dd5e53d..9a52f27f43e50 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -183,7 +183,7 @@ private IgniteInternalFuture deletePhase(UUID ignored, S // We need to find and read snapshot metas to ensure the content is a snapshot. Also, the metas contain // initial cluster topology and actual snasphot folder names. - List locMetas = kctx.cache().context().snapshotMgr().readSnapshotMetadatas(snpFiles); + List locMetas = kctx.cache().context().snapshotMgr().readSnapshotMetadatas(snpFiles, false); if (locMetas.isEmpty()) { requests.remove(req); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java index 2308465de5f99..933d4150d9df1 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java @@ -18,10 +18,12 @@ package org.apache.ignite.internal.processors.cache.persistence.snapshot; import java.io.File; +import java.io.RandomAccessFile; import java.nio.file.Files; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Random; import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -197,18 +199,30 @@ public void testDeleteOtherConsistentId() throws Exception { /** */ @Test - public void testDeleteNotSnapshotSharedDirectory() throws Exception { - doTestDeleteNotSnapshot(false); + public void testDeleteSnapshotNoMetaSharedDirectory() throws Exception { + doTestDeleteNotSnapshot(false, false); } /** */ @Test - public void testDeleteNotSnapshotDedicatedDirectories() throws Exception { - doTestDeleteNotSnapshot(true); + public void testDeleteSnapshotNoMetaDedicatedDirectories() throws Exception { + doTestDeleteNotSnapshot(true, false); } /** */ - protected void doTestDeleteNotSnapshot(boolean separatedWorkDir) throws Exception { + @Test + public void testDeleteSnapshotCorruptedMetaSharedDirectory() throws Exception { + doTestDeleteNotSnapshot(false, true); + } + + /** */ + @Test + public void testDeleteSnapshotCorruptedMetaDedicatedDirectories() throws Exception { + doTestDeleteNotSnapshot(true, true); + } + + /** */ + protected void doTestDeleteNotSnapshot(boolean separatedWorkDir, boolean corruptFile) throws Exception { this.separatedWorkDir = separatedWorkDir; startGridsWithCache(3, CACHE_KEYS_RANGE, valueBuilder(), dfltCacheCfg); @@ -225,8 +239,20 @@ protected void doTestDeleteNotSnapshot(boolean separatedWorkDir) throws Exceptio .binaryMeta().exists()); assertTrue(snpSft.meta().exists()); - assertTrue(U.delete(snpSft.meta())); - assertFalse(snpSft.meta().exists()); + + if (corruptFile) { + try (var rwf = new RandomAccessFile(snpSft.meta(), "rw")) { + byte[] slop = new byte[128]; + + new Random().nextBytes(slop); + + rwf.write(slop); + } + } + else { + assertTrue(U.delete(snpSft.meta())); + assertFalse(snpSft.meta().exists()); + } var delSnpRes = snp(grid(2)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); From e6086d6921bdc5702bc265583a5d29c77a8d3e04 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Wed, 23 Sep 2026 01:55:27 +0300 Subject: [PATCH 52/97] manual review, review fixes --- .../snapshot/SnapshotDeleteTask.java | 2 ++ .../persistence/filename/SnapshotFileTree.java | 2 +- .../snapshot/SnapshotDeleteProcess.java | 18 +++++++++--------- .../snapshot/SnapshotDeleteProcessResult.java | 2 +- .../snapshot/SnapshotDeleteRequest.java | 2 ++ .../snapshot/SnapshotDeleteResponse.java | 8 ++++---- ...lusterSnapshotDeleteRollingUpgradeTest.java | 15 +++++++++++---- 7 files changed, 30 insertions(+), 19 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteTask.java index 86bac6972bd2f..f90c9f4d78a4c 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteTask.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteTask.java @@ -18,6 +18,7 @@ package org.apache.ignite.internal.management.snapshot; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotDeleteProcess; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotDeleteProcessResult; import org.apache.ignite.internal.processors.task.GridInternal; import org.apache.ignite.internal.visor.VisorJob; @@ -25,6 +26,7 @@ /** * @see IgniteSnapshotManager#deleteSnapshot(String, String) + * @see SnapshotDeleteProcess */ @GridInternal public class SnapshotDeleteTask extends VisorOneNodeTask { diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java index ddeb8a4ea2707..ff9e58d5a9cde 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java @@ -51,7 +51,7 @@ public class SnapshotFileTree extends NodeFileTree { private static final String DELTA_IDX_SUFFIX = ".idx"; /** Snapshot metafile extension. */ - public static final String SNAPSHOT_METAFILE_EXT = ".smf"; + private static final String SNAPSHOT_METAFILE_EXT = ".smf"; /** File name template consists of delta pages. */ private static final String PART_DELTA_TEMPLATE = PART_FILE_TEMPLATE + DELTA_SUFFIX; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index 9a52f27f43e50..13a9b87e9a174 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java @@ -190,7 +190,7 @@ private IgniteInternalFuture deletePhase(UUID ignored, S log.warning("Snapshot deletion won't process, no snapshot metadata found [req=" + req + ']'); - return new GridFinishedFuture<>(new SnapshotDeleteResponse(SnapshotDeleteResponse.SnapshotDeleteStatus.NOT_FOUND, null)); + return new GridFinishedFuture<>(new SnapshotDeleteResponse(SnapshotDeleteResponse.DeleteStatus.NOT_FOUND, null)); } // Future to delete snapshot contents according to snapshot metadatas. @@ -214,7 +214,7 @@ private IgniteInternalFuture deletePhase(UUID ignored, S boolean deleted = snpMgr.deleteLocalSnapshot(byMetaSft, foundFlag); - SnapshotDeleteResponse.SnapshotDeleteStatus status; + SnapshotDeleteResponse.DeleteStatus status; if (foundFlag.get()) { if (deleted && log.isInfoEnabled()) @@ -223,14 +223,14 @@ else if (!deleted) log.warning("Snapshot deleted not completely [req=" + req + ']'); status = deleted - ? SnapshotDeleteResponse.SnapshotDeleteStatus.DELETED - : SnapshotDeleteResponse.SnapshotDeleteStatus.PARTLY_DELETED; + ? SnapshotDeleteResponse.DeleteStatus.DELETED + : SnapshotDeleteResponse.DeleteStatus.PARTLY; } else { if (log.isInfoEnabled()) log.info("Snapshot not found to delete [req=" + req + ']'); - status = SnapshotDeleteResponse.SnapshotDeleteStatus.NOT_FOUND; + status = SnapshotDeleteResponse.DeleteStatus.NOT_FOUND; } perMetaFut.onDone(new SnapshotDeleteResponse(status, meta.bltNodes)); @@ -329,7 +329,7 @@ private void reducePhase(UUID reqId, Map results, case DELETED: completedNodes.put(nodeId, consistentId(nodeId)); break; - case PARTLY_DELETED: + case PARTLY: uncompletedNodes.put(nodeId, consistentId(nodeId)); break; default: @@ -395,7 +395,7 @@ private static class MetaFuturesReducer implements IgniteReducer nodeIds = new HashSet<>(); @@ -411,7 +411,7 @@ private static class MetaFuturesReducer implements IgniteReducer emptyNodes; - /** Snapshot's baseline nodes which aren't found in current cluster. */ + /** Snapshot's baseline nodes missing in current cluster. */ @Order(3) Collection absentBaselines; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteRequest.java index 86bbc41003b14..735909bb7f9ec 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteRequest.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteRequest.java @@ -21,6 +21,7 @@ import java.util.Objects; import java.util.UUID; import org.apache.ignite.internal.Order; +import org.apache.ignite.internal.util.tostring.GridToStringExclude; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.plugin.extensions.communication.Message; @@ -46,6 +47,7 @@ public class SnapshotDeleteRequest implements Message { @Nullable String snpPath; /** Resolved absolute path. Transient */ + @GridToStringExclude @Nullable File resolvedPath; /** Default constructor for {@link MessageFactory}. */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java index 8788585f59749..24a7878e3dbf4 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java @@ -32,7 +32,7 @@ public class SnapshotDeleteResponse implements Message { /** {@code null} for client node. */ @Order(0) - @Nullable SnapshotDeleteResponse.SnapshotDeleteStatus status; + @Nullable SnapshotDeleteResponse.DeleteStatus status; /** Snapshot's node ids. */ @Order(1) @@ -44,7 +44,7 @@ public SnapshotDeleteResponse() { } /** {@code null} for client node. */ - SnapshotDeleteResponse(SnapshotDeleteResponse.SnapshotDeleteStatus status, Collection nodeIds) { + SnapshotDeleteResponse(DeleteStatus status, Collection nodeIds) { this.status = status; this.nodeIds = nodeIds; } @@ -55,12 +55,12 @@ public SnapshotDeleteResponse() { } /** */ - enum SnapshotDeleteStatus { + enum DeleteStatus { /** Snapshot found and completely deleted. */ DELETED, /** Snapshot found but some files or directories might not be deleted (locked). */ - PARTLY_DELETED, + PARTLY, /** Snapshot not found. */ NOT_FOUND; diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgradeTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgradeTest.java index 76cd9a97de859..7a9e24a38ce21 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgradeTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgradeTest.java @@ -40,6 +40,7 @@ import static org.apache.ignite.internal.TestRecordingCommunicationSpi.spi; import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RU_PREPARE_VERSION_FINALIZATION; import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause; +import static org.apache.ignite.testframework.GridTestUtils.waitForCondition; /** */ public class IgniteClusterSnapshotDeleteRollingUpgradeTest extends AbstractRollingUpgradeTest { @@ -118,8 +119,11 @@ public void testParallelRollingUpgradeInProgress() throws Exception { finalizeFut.get(getTestTimeout()); - for (int i = 0; i < ALL_GRIDS; i++) - assertFalse(ru(grid(i)).isVersionUpgradeEnabled()); + for (int i = 0; i < ALL_GRIDS; i++) { + int i0 = i; + + assertTrue(waitForCondition(() -> !ru(grid(i0)).isVersionUpgradeEnabled(), getTestTimeout())); + } assertFalse(F.isEmpty(snp(1).deleteSnapshot(SNP_NAME, null).get(getTestTimeout()).completedNodes)); } @@ -148,8 +152,11 @@ public void testSnapshotDeleteFeature() throws Exception { ru(grid(1)).finalizeClusterVersion(); - for (int i = 0; i < ALL_GRIDS; i++) - assertFalse(ru(grid(i)).isVersionUpgradeEnabled()); + for (int i = 0; i < ALL_GRIDS; i++) { + int i0 = i; + + assertTrue(waitForCondition(() -> !ru(grid(i0)).isVersionUpgradeEnabled(), getTestTimeout())); + } assertEquals(3, snp(1).deleteSnapshot(SNP_NAME, null).get().completedNodes().size()); } From d4c7e88d30af36843081101db09811ff530e7f97 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Wed, 23 Sep 2026 02:10:00 +0300 Subject: [PATCH 53/97] test fixes --- .../IgniteClusterSnapshotDeleteTest.java | 29 +++++++++---------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java index 933d4150d9df1..12ec8f8d400f3 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java @@ -131,9 +131,8 @@ public static Collection runParams() { /** Tests snapshot deletion when one node finds snapshot but fails to delete its data. */ @Test public void testUncompletedNodes() throws Exception { - // Incremental snapshots don't support encryption and only-primary mode. - assumeTrue(!incremental || !encryption); - assumeTrue(!incremental || !onlyPrimary); + // Incremental snapshots don't support only-primary and encryption modes. + assumeTrue(!incremental || !(onlyPrimary || encryption)); separatedWorkDir = true; @@ -360,9 +359,8 @@ public void testDeletionInIgnite() throws Exception { /** Tests snapshot deletion when one node has no snapshot data. */ @Test public void testEmptyNodes() throws Exception { - // Incremental snapshots don't support encryption and only-primary mode. - assumeTrue(!incremental || !encryption); - assumeTrue(!incremental || !onlyPrimary); + // Incremental snapshots don't support only-primary and encryption modes. + assumeTrue(!incremental || !(onlyPrimary || encryption)); separatedWorkDir = true; @@ -385,6 +383,9 @@ public void testEmptyNodes() throws Exception { /** Tests snapshot deletion repeat after an offline node restarts. */ @Test public void testDeletionRepeatAfterOfflineNodeStarts() throws Exception { + // Incremental snapshots don't support only-primary and encryption modes. + assumeTrue(!incremental || !(onlyPrimary || encryption)); + separatedWorkDir = true; startGridsWithCache(3, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); @@ -424,7 +425,7 @@ public void testDeletionRepeatAfterOfflineNodeStarts() throws Exception { /** Test snapshot deletion process when one node leaves. */ @Test public void testNodeStopsInTheMiddle() throws Exception { - // Incremental snapshots don't support only-primary and encryption mode. + // Incremental snapshots don't support only-primary and encryption modes. assumeTrue(!incremental || !(onlyPrimary || encryption)); separatedWorkDir = true; @@ -545,9 +546,8 @@ public void testConcurrentDeleteOfTheSameSnapshot() throws Exception { /** Tests that a snapshot deletion is declined when a snapshot check operation is in progress. */ @Test public void testSnapshotDeleteWhenCheckInProgress() throws Exception { - // Incremental snapshots don't support encryption and only-primary mode. - assumeTrue(!incremental || !encryption); - assumeTrue(!incremental || !onlyPrimary); + // Incremental snapshots don't support only-primary and encryption modes. + assumeTrue(!incremental || !(onlyPrimary || encryption)); doTestConcurrentSnapshotDelete( () -> new IgniteFutureImpl<>(snp(grid(2)).checkSnapshot(SNAPSHOT_NAME, null, incremental ? 1 : 0)), @@ -583,8 +583,8 @@ public void testSnapshotDeleteWhenCreateInProgress() throws Exception { /** Tests that a snapshot deletion is declined when a snapshot restore begins. */ @Test public void testSnapshotDeleteWhenRestoreBegins() throws Exception { - // Incremental snapshots don't support encryption. - assumeTrue(!incremental || !encryption); + // Incremental snapshots don't support only-primary and encryption modes. + assumeTrue(!incremental || !(onlyPrimary || encryption)); doTestConcurrentSnapshotDelete( () -> { @@ -608,9 +608,8 @@ public void testSnapshotDeleteWhenRestoreBegins() throws Exception { /** Tests that a snapshot deletion is declined when a snapshot restore is in progress. */ @Test public void testSnapshotDeleteWhenRestoreInProgress() throws Exception { - // Incremental snapshots don't support encryption and only-primary mode. - assumeTrue(!incremental || !encryption); - assumeTrue(!incremental || !onlyPrimary); + // Incremental snapshots don't support only-primary and encryption modes. + assumeTrue(!incremental || !(onlyPrimary || encryption)); var restoreMsgs = F.asList( RESTORE_CACHE_GROUP_SNAPSHOT_PREPARE, From e576e2b5e5ac693606acb8768d35646de5b3fd97 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Wed, 23 Sep 2026 10:29:38 +0300 Subject: [PATCH 54/97] flaky fixes --- .../snapshot/AbstractSnapshotSelfTest.java | 9 +------ .../IgniteClusterSnapshotDeleteTest.java | 26 +++++++++---------- 2 files changed, 14 insertions(+), 21 deletions(-) diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java index 768fbba8f4259..300321f6e848c 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java @@ -903,14 +903,7 @@ protected void doTestConcurrentSnapshotDeleteOperation( delProcProceedLatch.countDown(); - var delRes = delFut.get(getTestTimeout()); - - // Concurrent deletion on the same path may cause exeptions like 'directory is not empty' or 'access denied'. - int nonEmptyCnt = (F.isEmpty(delRes.completedNodes) - ? 0 - : delRes.completedNodes.size()) + (F.isEmpty(delRes.uncompletedNodes) ? 0 : delRes.uncompletedNodes.size()); - - assertEquals(grid(0).cluster().forServers().nodes().size(), nonEmptyCnt); + delFut.get(getTestTimeout()); for (var node : G.allGrids()) assertFalse(new SnapshotFileTree(((IgniteEx)node).context(), SNAPSHOT_NAME, null).root().exists()); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java index 12ec8f8d400f3..aa1e1763e2599 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java @@ -79,19 +79,6 @@ public class IgniteClusterSnapshotDeleteTest extends AbstractSnapshotSelfTest { /** */ private @Nullable String cstIdSuffix; - /** {@inheritDoc} */ - @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { - var cfg = super.getConfiguration(igniteInstanceName); - - if (separatedWorkDir) - cfg.setWorkDirectory(new File(U.defaultWorkDirectory(), igniteInstanceName).getAbsolutePath()); - - if (cstIdSuffix != null) - cfg.setConsistentId(cfg.getConsistentId().toString() + '_' + cstIdSuffix); - - return cfg; - } - /** Parameters. */ @Parameterized.Parameters(name = "encryption={0}, onlyPrimary={1}, incremental={2}") public static Collection runParams() { @@ -111,6 +98,19 @@ public static Collection runParams() { return res; } + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { + var cfg = super.getConfiguration(igniteInstanceName); + + if (separatedWorkDir) + cfg.setWorkDirectory(new File(U.defaultWorkDirectory(), igniteInstanceName).getAbsolutePath()); + + if (cstIdSuffix != null) + cfg.setConsistentId(cfg.getConsistentId().toString() + '_' + cstIdSuffix); + + return cfg; + } + /** {@inheritDoc} */ @Override public void afterTestSnapshot() throws Exception { super.afterTestSnapshot(); From 2d2f6c4f52abb6454e43d007f72cf7802a59b1bb Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Wed, 23 Sep 2026 11:24:02 +0300 Subject: [PATCH 55/97] neuroslop --- docs/_docs/snapshots/snapshots.adoc | 53 ++++ .../GridCommandHandlerListSnapshotTest.java | 235 ++++++++++++++ .../management/snapshot/SnapshotCommand.java | 3 +- .../snapshot/SnapshotListCommand.java | 113 +++++++ .../snapshot/SnapshotListCommandArg.java | 44 +++ .../management/snapshot/SnapshotListTask.java | 61 ++++ .../snapshot/IgniteSnapshotManager.java | 16 + .../snapshot/SnapshotListProcess.java | 296 ++++++++++++++++++ .../snapshot/SnapshotListProcessResult.java | 74 +++++ .../snapshot/SnapshotListRequest.java | 70 +++++ .../snapshot/SnapshotListResponse.java | 59 ++++ .../util/distributed/DistributedProcess.java | 7 +- 12 files changed, 1029 insertions(+), 2 deletions(-) create mode 100644 modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommandArg.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListProcess.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListProcessResult.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListRequest.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListResponse.java diff --git a/docs/_docs/snapshots/snapshots.adoc b/docs/_docs/snapshots/snapshots.adoc index f44a1e7b7dc02..49406d90dbba3 100644 --- a/docs/_docs/snapshots/snapshots.adoc +++ b/docs/_docs/snapshots/snapshots.adoc @@ -330,6 +330,59 @@ The delete operation is subject to the following limitations: * Before deletion, no snapshot validation is done except finding and reading its metadata. If the metadata isn't found or cannot be read, snapshot isn't deleted. * If the provided snapshot path belongs to Ignite's home or to Ignite's a not snapshot work directory, the operation fails. +== Listing Snapshots + +You can list all snapshots and their sizes on all online server nodes using the `control.sh|bat` script. + +The list operation is performed on all *online* server nodes of the cluster. The output shows each snapshot found on each node along with its disk usage. If a snapshot is currently being created or deleted, the command reports its transient status instead of the size. + +[tabs] +-- +tab:Unix[] +[source,shell] +---- +# List all snapshots in the default snapshot directory. +control.sh --snapshot list + +# List all snapshots in the "/tmp/ignite/snapshots" folder. +control.sh --snapshot list --src /tmp/ignite/snapshots +---- + +tab:Windows[] +[source,shell] +---- +# List all snapshots in the default snapshot directory. +control.bat --snapshot list + +# List all snapshots in the "/tmp/ignite/snapshots" folder. +control.bat --snapshot list --src /tmp/ignite/snapshots +---- +-- + +=== Output format + +The command output displays per-node information: + +[source,text] +---- +Node consistent-id [node-uuid]: + snapshot_name: + snapshot_name: CREATING + snapshot_name: DELETING +---- + +Where: + +* `` - human-readable size of the snapshot on disk (e.g., `1.5 GB`). +* `CREATING` - the snapshot is in the process of being created on this node. The size is not calculated. +* `DELETING` - the snapshot is in the process of being deleted on this node. The size is not calculated. + +=== List operation notes + +* The list operation is read-only and does not modify any snapshot data. +* If a snapshot is in a transient state (being created or deleted), its size is not reported. Instead, the command shows the corresponding status. +* Snapshot data on offline server nodes is not reported. + == Getting Snapshot Operation Status The status of the current snapshot operation in the cluster can be obtained using the `control.sh|bat` script or JMX interface: diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java new file mode 100644 index 0000000000000..0603e5d1466d8 --- /dev/null +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java @@ -0,0 +1,235 @@ +/* + * 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.ignite.util; + +import java.io.File; +import java.nio.file.DirectoryStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collection; +import org.apache.ignite.IgniteDataStreamer; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.management.snapshot.SnapshotListCommand; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.testframework.GridTestUtils; +import org.junit.Test; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import static java.nio.file.Files.newDirectoryStream; +import static org.apache.ignite.cluster.ClusterState.ACTIVE; +import static org.apache.ignite.internal.commandline.CommandHandler.EXIT_CODE_OK; +import static org.apache.ignite.internal.processors.cache.persistence.snapshot.AbstractSnapshotSelfTest.snp; +import static org.junit.Assume.assumeTrue; + +/** Test for the command '--snapshot list'. */ +public class GridCommandHandlerListSnapshotTest extends GridCommandHandlerAbstractTest { + /** */ + @Parameter(1) + public boolean customPath; + + /** */ + @Parameter(2) + public boolean separatedWorkDir; + + /** */ + @Parameters(name = "customPath={1},ownWorkDir={2}") + public static Collection parameters() { + return GridTestUtils.cartesianProduct( + commandHandlers(), + F.asList(false, true), // Use custom snapshot path; + F.asList(false, true) // Separated (own) work directory. + ); + } + + /** {@inheritDoc} */ + @Override protected void afterTest() throws Exception { + super.afterTest(); + + stopAllGrids(); + + cleanPersistenceDir(); + } + + /** {@inheritDoc} */ + @Override protected void beforeTest() throws Exception { + super.beforeTest(); + + /** Handy if test running is interrupted and {@link #afterTest()} isn't invoked. */ + cleanPersistenceDir(); + } + + /** {@inheritDoc} */ + @Override protected void cleanPersistenceDir() throws Exception { + super.cleanPersistenceDir(); + + // Also cleans separated snapshot working directories and custom snapshot paths. + try (DirectoryStream files = newDirectoryStream(Paths.get(U.defaultWorkDirectory()))) { + for (Path path : files) + U.delete(path); + } + } + + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { + IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); + + if (separatedWorkDir) + cfg.setWorkDirectory(new File(U.defaultWorkDirectory(), igniteInstanceName).getAbsolutePath()); + + return cfg; + } + + /** */ + @Test + public void testSnapshotList() throws Exception { + // A custom snapshot path actually puts snapshots in a shared directory. This skews the results when dedicated + // work directories are set. + assumeTrue(!customPath || !separatedWorkDir); + + int entriesCnt = 100; + int initNodes = 3; + + IgniteEx ig = (IgniteEx)startGridsMultiThreaded(initNodes); + + ig.cluster().state(ACTIVE); + + createCacheAndPreload(ig, entriesCnt); + + File cstSnpsRoot = customPath + ? new File(grid(0).context().pdsFolderResolver().fileTree().snapshotsRoot(), "ex_snapshots") + : null; + + // Test listing when no snapshots exist. + injectTestSystemOut(); + + if (customPath) + assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "list", "--src", + cstSnpsRoot.getAbsolutePath())); + else + assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "list")); + + String out = testOut.toString(); + + assertTrue("Expected 'No snapshots found' message, got: " + out, + out.contains(SnapshotListCommand.NO_SNAPSHOTS_PREF)); + + testOut.reset(); + + // Create a snapshot. + snp(ig).createSnapshot("testSnapshot", customPath ? cstSnpsRoot.getAbsolutePath() : null, false, false) + .get(getTestTimeout()); + + // Add some data and create an incremental snapshot. + try (IgniteDataStreamer streamer = ig.dataStreamer(DEFAULT_CACHE_NAME)) { + for (int i = entriesCnt; i < entriesCnt + 50; ++i) + streamer.addData(i, i); + } + + snp(ig).createSnapshot("testSnapshot", customPath ? cstSnpsRoot.getAbsolutePath() : null, true, false) + .get(getTestTimeout()); + + // Now list snapshots - should find "testSnapshot" on all server nodes. + if (customPath) + assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "list", "--src", + cstSnpsRoot.getAbsolutePath())); + else + assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "list")); + + out = testOut.toString(); + + assertFalse("Expected snapshot to be listed, got: " + out, + out.contains(SnapshotListCommand.NO_SNAPSHOTS_PREF)); + assertTrue("Expected 'Node' in output, got: " + out, + out.contains(SnapshotListCommand.NODE_PREF)); + assertTrue("Expected 'testSnapshot' in output, got: " + out, + out.contains("testSnapshot")); + + // With separated work directories, all nodes should have the snapshot. + if (separatedWorkDir) { + assertTrue("Expected [cnt=" + initNodes + "] nodes in output, got: " + out, + countNodeOccurrences(out) == initNodes); + } + + testOut.reset(); + + // Test with non-existent snapshot path. + assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "list", "--src", + new File(U.defaultWorkDirectory(), "non_existent").getAbsolutePath())); + + out = testOut.toString(); + + assertTrue("Expected 'No snapshots found' for non-existent path, got: " + out, + out.contains(SnapshotListCommand.NO_SNAPSHOTS_PREF)); + } + + /** */ + @Test + public void testSnapshotListMultipleSnapshots() throws Exception { + int entriesCnt = 100; + int initNodes = 2; + + IgniteEx ig = (IgniteEx)startGridsMultiThreaded(initNodes); + + ig.cluster().state(ACTIVE); + + createCacheAndPreload(ig, entriesCnt); + + // Create first snapshot. + snp(ig).createSnapshot("snp1", null, false, false).get(getTestTimeout()); + + // Add data. + try (IgniteDataStreamer streamer = ig.dataStreamer(DEFAULT_CACHE_NAME)) { + for (int i = entriesCnt; i < entriesCnt + 50; ++i) + streamer.addData(i, i); + } + + // Create second snapshot. + snp(ig).createSnapshot("snp2", null, false, false).get(getTestTimeout()); + + injectTestSystemOut(); + + assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "list")); + + String out = testOut.toString(); + + assertFalse("Expected snapshots to be listed, got: " + out, + out.contains(SnapshotListCommand.NO_SNAPSHOTS_PREF)); + assertTrue("Expected 'snp1' in output, got: " + out, + out.contains("snp1")); + assertTrue("Expected 'snp2' in output, got: " + out, + out.contains("snp2")); + } + + /** + * Counts occurrences of the node prefix in the output. + */ + private int countNodeOccurrences(String output) { + int count = 0; + int idx = 0; + + while ((idx = output.indexOf(SnapshotListCommand.NODE_PREF, idx)) != -1) { + count++; + idx += SnapshotListCommand.NODE_PREF.length(); + } + + return count; + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCommand.java index 8979958bd8fb6..0b062eb2027b1 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCommand.java @@ -29,7 +29,8 @@ public SnapshotCommand() { new SnapshotCheckCommand(), new SnapshotRestoreCommand(), new SnapshotStatusCommand(), - new SnapshotDeleteCommand() + new SnapshotDeleteCommand(), + new SnapshotListCommand() ); } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java new file mode 100644 index 0000000000000..82f1211bc2e4a --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java @@ -0,0 +1,113 @@ +/* + * 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.ignite.internal.management.snapshot; + +import java.util.Map; +import java.util.UUID; +import java.util.function.Consumer; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListProcessResult; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.internal.U; + +/** Snapshot list command. */ +public class SnapshotListCommand extends AbstractSnapshotCommand { + /** */ + public static final String DESC = "Lists all snapshots on all online server nodes with their sizes"; + + /** */ + public static final String NO_SNAPSHOTS_PREF = "No snapshots found on current server nodes"; + + /** */ + public static final String NODE_PREF = "Node "; + + /** */ + public static final String SNP_PREF = " "; + + /** */ + public static final String SIZE_SUFFIX = ": "; + + /** + * {@inheritDoc} + */ + @Override public String description() { + return DESC; + } + + /** {@inheritDoc} */ + @Override public Class argClass() { + return SnapshotListCommandArg.class; + } + + /** {@inheritDoc} */ + @Override public Class taskClass() { + return SnapshotListTask.class; + } + + /** {@inheritDoc} */ + @Override public void printResult(SnapshotListCommandArg arg, SnapshotListProcessResult res, Consumer printer) { + boolean anyFound = false; + + for (Map.Entry entry : res.nodeConsistentIds().entrySet()) { + UUID nodeId = entry.getKey(); + String consId = entry.getValue(); + + Map sizes = res.nodeSnapshotSizes().get(nodeId); + Map statuses = res.nodeSnapshotStatuses().get(nodeId); + + boolean hasSnapshots = (!F.isEmpty(sizes)) || (!F.isEmpty(statuses)); + + if (hasSnapshots) + anyFound = true; + } + + if (!anyFound) { + printer.accept(NO_SNAPSHOTS_PREF); + + return; + } + + for (Map.Entry entry : res.nodeConsistentIds().entrySet()) { + UUID nodeId = entry.getKey(); + String consId = entry.getValue(); + + Map sizes = res.nodeSnapshotSizes().get(nodeId); + Map statuses = res.nodeSnapshotStatuses().get(nodeId); + + boolean hasSnapshots = (!F.isEmpty(sizes)) || (!F.isEmpty(statuses)); + + if (!hasSnapshots) + continue; + + printer.accept(NODE_PREF + consId + " [" + nodeId + "]:"); + + if (sizes != null) { + for (Map.Entry snp : sizes.entrySet()) { + printer.accept(SNP_PREF + snp.getKey() + SIZE_SUFFIX + U.humanReadableByteCount(snp.getValue())); + } + } + + if (statuses != null) { + for (Map.Entry snp : statuses.entrySet()) { + printer.accept(SNP_PREF + snp.getKey() + SIZE_SUFFIX + snp.getValue()); + } + } + + printer.accept(U.nl()); + } + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommandArg.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommandArg.java new file mode 100644 index 0000000000000..3fd420661abcf --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommandArg.java @@ -0,0 +1,44 @@ +/* + * 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.ignite.internal.management.snapshot; + +import org.apache.ignite.internal.Order; +import org.apache.ignite.internal.dto.IgniteDataTransferObject; +import org.apache.ignite.internal.management.api.Argument; + +/** */ +public class SnapshotListCommandArg extends IgniteDataTransferObject { + /** */ + private static final long serialVersionUID = 0; + + /** */ + @Order(0) + @Argument(example = "path", optional = true, description = "Path to snapshot location directory. If not specified " + + "or specified a relative path, the default snapshot configuration directory will be used") + String src; + + /** */ + public String src() { + return src; + } + + /** */ + public void src(String src) { + this.src = src; + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java new file mode 100644 index 0000000000000..1772201df5e9b --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java @@ -0,0 +1,61 @@ +/* + * 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.ignite.internal.management.snapshot; + +import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListProcess; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListProcessResult; +import org.apache.ignite.internal.processors.task.GridInternal; +import org.apache.ignite.internal.visor.VisorJob; +import org.apache.ignite.internal.visor.VisorOneNodeTask; + +/** + * @see IgniteSnapshotManager#listSnapshots(String) + * @see SnapshotListProcess + */ +@GridInternal +public class SnapshotListTask extends VisorOneNodeTask { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** {@inheritDoc} */ + @Override protected VisorJob job(SnapshotListCommandArg arg) { + return new SnapshotListJob(arg, debug); + } + + /** */ + private static class SnapshotListJob extends SnapshotJob { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** + * @param arg Snapshot list task argument. + * @param debug Flag indicating whether debug information should be printed into node log. + */ + protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { + super(arg, debug); + } + + /** {@inheritDoc} */ + @Override protected SnapshotListProcessResult run(SnapshotListCommandArg arg) { + IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); + + return snpMgr.listSnapshots(arg.src()).get(); + } + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java index b3400dacdfc0b..6e88f5c2f10ff 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java @@ -348,6 +348,9 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter /** Distributed process to delete cluster snapshot. */ private final SnapshotDeleteProcess deleteSnpProc; + /** Distributed process to list cluster snapshots. */ + private final SnapshotListProcess listSnpProc; + /** Check previously performed snapshot operation and delete uncompleted files if we need. */ private final DistributedProcess endSnpProc; @@ -452,6 +455,8 @@ public IgniteSnapshotManager(GridKernalContext ctx) { deleteSnpProc = new SnapshotDeleteProcess(ctx); + listSnpProc = new SnapshotListProcess(ctx); + // Manage remote snapshots. snpRmtMgr = new SequentialRemoteSnapshotManager(); } @@ -671,6 +676,7 @@ public IgniteSnapshotManager(GridKernalContext ctx) { restoreCacheGrpProc.interrupt(stopErr); checkSnpProc.interrupt(stopErr); deleteSnpProc.interrupt(stopErr); + listSnpProc.interrupt(stopErr); // Try stop all snapshot processing if not yet. for (AbstractSnapshotFutureTask sctx : locSnpTasks.values()) @@ -1546,6 +1552,16 @@ public IgniteFuture deleteSnapshot(String name, @Nu return deleteSnpProc.start(name, snpPath); } + /** + * Provides list of snapshots on all online server nodes. + * + * @param snpPath Snapshot directory path. If {@code null}, the default configured snapshot directory will be used. + * @return Future which will be completed when the snapshot list is gathered from all the online server nodes. + */ + public IgniteFuture listSnapshots(@Nullable String snpPath) { + return listSnpProc.start(snpPath); + } + /** * Sets the streamer warning flag to current snapshot process if it is active. */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListProcess.java new file mode 100644 index 0000000000000..3000dcd30ce07 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListProcess.java @@ -0,0 +1,296 @@ +/* + * 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.ignite.internal.processors.cache.persistence.snapshot; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import org.apache.ignite.IgniteLogger; +import org.apache.ignite.internal.GridKernalContext; +import org.apache.ignite.internal.IgniteInternalFuture; +import org.apache.ignite.internal.NodeStoppingException; +import org.apache.ignite.internal.util.distributed.DistributedProcess; +import org.apache.ignite.internal.util.future.GridFutureAdapter; +import org.apache.ignite.internal.util.future.GridFinishedFuture; +import org.apache.ignite.internal.util.future.IgniteFutureImpl; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.lang.IgniteFuture; +import org.jetbrains.annotations.Nullable; + +import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.LIST_SNAPSHOTS; +import static org.apache.ignite.plugin.security.SecurityPermission.ADMIN_SNAPSHOT; + +/** + * Distributed process to list all snapshots and their sizes on all online server nodes. + */ +public class SnapshotListProcess { + /** Status: snapshot is in process of creation. */ + static final String STATUS_CREATING = "CREATING"; + + /** Status: snapshot is in process of deletion. */ + static final String STATUS_DELETING = "DELETING"; + + /** Status: snapshot is in process of restore. */ + static final String STATUS_RESTORING = "RESTORING"; + + /** Kernal context. */ + private final GridKernalContext kctx; + + /** Logger. */ + private final IgniteLogger log; + + /** */ + private volatile boolean interrupted; + + /** Cluster-wide operation futures per request id on certain node. */ + private final Map> clusterOpFuts = new ConcurrentHashMap<>(); + + /** The distributed process. */ + private final DistributedProcess distrProc; + + /** + * @param ctx Kernal context. + */ + public SnapshotListProcess(GridKernalContext ctx) { + this.kctx = ctx; + + log = ctx.log(getClass()); + + distrProc = new DistributedProcess<>(ctx, LIST_SNAPSHOTS, this::listPhase, this::reducePhase); + } + + /** + * Starts the cluster snapshot list process. + * + * @param snpPath Snapshot directory path (optional). + * @return Future that will be completed when the snapshot list is gathered from all online server nodes. + */ + public IgniteFuture start(@Nullable String snpPath) { + var clusterOpFut = new GridFutureAdapter(); + + UUID reqId = UUID.randomUUID(); + + clusterOpFut.listen(fut -> clusterOpFuts.remove(reqId)); + + try { + synchronized (clusterOpFuts) { + if (interrupted || kctx.isStopping()) + throw new NodeStoppingException("Failed to start snapshot list process: node is stopping."); + + clusterOpFuts.put(reqId, clusterOpFut); + } + + SnapshotListRequest req = new SnapshotListRequest(reqId, snpPath); + + distrProc.start(reqId, req); + } + catch (Throwable t) { + log.error("Failed to start distributed list snapshot process [snpPath=" + snpPath + ']', t); + + clusterOpFut.onDone(t); + } + + return new IgniteFutureImpl<>(clusterOpFut); + } + + /** */ + private IgniteInternalFuture listPhase(UUID ignored, SnapshotListRequest req) { + if (interrupted || kctx.isStopping()) { + return new GridFinishedFuture<>(new NodeStoppingException( + "Snapshot list was rejected. Node is stopping [req=" + req + ']')); + } + + if (kctx.cluster().get().localNode().isClient()) + return new GridFinishedFuture<>(new SnapshotListResponse()); + + kctx.security().authorize(ADMIN_SNAPSHOT); + + IgniteSnapshotManager snpMgr = kctx.cache().context().snapshotMgr(); + + try { + File path = resolvePath(req.snpPath); + + File[] dirs; + + if (path.exists() && path.isDirectory()) + dirs = path.listFiles(File::isDirectory); + else + dirs = null; + + Map sizes = new HashMap<>(); + Map statuses = new HashMap<>(); + + if (dirs != null) { + for (File dir : dirs) { + String snpName = dir.getName(); + + if (isSnapshotCreating(snpMgr, snpName) || isSnapshotRestoring(snpMgr, snpName)) { + statuses.put(snpName, STATUS_CREATING); + } + else if (snpMgr.isSnapshotDeleting(snpName, req.snpPath)) { + statuses.put(snpName, STATUS_DELETING); + } + else { + try { + sizes.put(snpName, directorySize(dir)); + } + catch (IOException e) { + // Snapshot might be modified between listing and size calculation. + statuses.put(snpName, STATUS_DELETING); + } + } + } + } + + return new GridFinishedFuture<>(new SnapshotListResponse(sizes, statuses)); + } + catch (Throwable t) { + log.warning("An error occurred during snapshot listing [req=" + req + ']', t); + + return new GridFinishedFuture<>(t); + } + } + + /** */ + private boolean isSnapshotCreating(IgniteSnapshotManager snpMgr, String snpName) { + var curCreateRq = snpMgr.currentCreateRequest(); + + return curCreateRq != null && curCreateRq.snpName.equalsIgnoreCase(snpName); + } + + /** */ + private boolean isSnapshotRestoring(IgniteSnapshotManager snpMgr, String snpName) { + return snpMgr.isRestoring(snpName); + } + + /** */ + private File resolvePath(@Nullable String path) { + var res = kctx.pdsFolderResolver().fileTree().snapshotsRoot(); + + if (path != null) { + File reqPath = new File(path); + + res = reqPath.isAbsolute() ? reqPath : new File(res, path); + } + + return res; + } + + /** + * Calculates total size of all files in the given directory recursively. + * + * @param dir Directory to measure. + * @return Total size in bytes. + * @throws IOException If an I/O error occurs during traversal. + */ + private long directorySize(File dir) throws IOException { + Path path = dir.toPath(); + + long[] size = {0L}; + + Files.walkFileTree(path, new SimpleFileVisitor<>() { + /** {@inheritDoc} */ + @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + size[0] += attrs.size(); + + return FileVisitResult.CONTINUE; + } + + /** {@inheritDoc} */ + @Override public FileVisitResult visitFileFailed(Path file, IOException exc) { + // File might have been deleted between listing and traversal. + return FileVisitResult.CONTINUE; + } + }); + + return size[0]; + } + + /** */ + private void reducePhase(UUID reqId, Map results, Map errors) { + var clusterOpFut = clusterOpFuts.get(reqId); + + if (clusterOpFut == null) + return; + + assert clusterOpFut != null; + + try { + var errP = F.isEmpty(errors) ? null : F.first(errors.entrySet()); + + if (errP != null) { + log.warning("Snapshot listing finished with an error [reqId=" + reqId + ", nodeId=" + + errP.getKey() + ", err='" + errP.getValue().getMessage() + "']", errP.getValue()); + + clusterOpFut.onDone(errP.getValue()); + + return; + } + + Map> nodeSizes = new HashMap<>(); + Map> nodeStatuses = new HashMap<>(); + Map nodeConsistentIds = new HashMap<>(); + + results.forEach((nodeId, nodeRes) -> { + nodeConsistentIds.put(nodeId, consistentId(nodeId)); + + if (nodeRes.snapshotSizes != null && !nodeRes.snapshotSizes.isEmpty()) + nodeSizes.put(nodeId, nodeRes.snapshotSizes); + + if (nodeRes.snapshotStatuses != null && !nodeRes.snapshotStatuses.isEmpty()) + nodeStatuses.put(nodeId, nodeRes.snapshotStatuses); + }); + + clusterOpFut.onDone(new SnapshotListProcessResult(nodeSizes, nodeStatuses, nodeConsistentIds)); + } + catch (Throwable t) { + clusterOpFut.onDone(t); + } + } + + /** */ + private String consistentId(UUID nodeId) { + var node = kctx.discovery().node(nodeId); + + if (node == null) + node = kctx.discovery().historicalNode(nodeId); + + return node == null ? "" : node.consistentId().toString(); + } + + /** + * @param err The interrupt reason. + */ + void interrupt(Throwable err) { + synchronized (clusterOpFuts) { + interrupted = true; + } + + clusterOpFuts.forEach((reqId, clusterOpFut) -> clusterOpFut.onDone(err)); + + clusterOpFuts.clear(); + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListProcessResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListProcessResult.java new file mode 100644 index 0000000000000..69cec245db55a --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListProcessResult.java @@ -0,0 +1,74 @@ +/* + * 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.ignite.internal.processors.cache.persistence.snapshot; + +import java.util.Collections; +import java.util.Map; +import java.util.UUID; +import org.apache.ignite.internal.Order; +import org.apache.ignite.internal.dto.IgniteDataTransferObject; +import org.apache.ignite.plugin.extensions.communication.MessageFactory; + +/** Result of {@link SnapshotListProcess}. */ +public final class SnapshotListProcessResult extends IgniteDataTransferObject { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** Per-node snapshot sizes: nodeId -> (snapshotName -> size in bytes). */ + @Order(0) + Map> nodeSnapshotSizes; + + /** Per-node snapshot statuses: nodeId -> (snapshotName -> status) for transient snapshots. */ + @Order(1) + Map> nodeSnapshotStatuses; + + /** Node consistent IDs: nodeId -> consistentId. */ + @Order(2) + Map nodeConsistentIds; + + /** Default constructor for {@link MessageFactory}. */ + public SnapshotListProcessResult() { + // No-op. + } + + /** */ + public SnapshotListProcessResult( + Map> nodeSnapshotSizes, + Map> nodeSnapshotStatuses, + Map nodeConsistentIds + ) { + this.nodeSnapshotSizes = nodeSnapshotSizes; + this.nodeSnapshotStatuses = nodeSnapshotStatuses; + this.nodeConsistentIds = nodeConsistentIds; + } + + /** */ + public Map> nodeSnapshotSizes() { + return Collections.unmodifiableMap(nodeSnapshotSizes); + } + + /** */ + public Map> nodeSnapshotStatuses() { + return Collections.unmodifiableMap(nodeSnapshotStatuses); + } + + /** */ + public Map nodeConsistentIds() { + return Collections.unmodifiableMap(nodeConsistentIds); + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListRequest.java new file mode 100644 index 0000000000000..75709aee75982 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListRequest.java @@ -0,0 +1,70 @@ +/* + * 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.ignite.internal.processors.cache.persistence.snapshot; + +import java.io.File; +import java.util.UUID; +import org.apache.ignite.internal.Order; +import org.apache.ignite.internal.util.tostring.GridToStringExclude; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.internal.S; +import org.apache.ignite.plugin.extensions.communication.Message; +import org.apache.ignite.plugin.extensions.communication.MessageFactory; +import org.jetbrains.annotations.Nullable; + +/** + * Cluster snapshot list distributed process request. + * + * @see SnapshotListProcess + */ +public class SnapshotListRequest implements Message { + /** Request ID. */ + @Order(0) + UUID reqId; + + /** Snapshot directory path. */ + @Order(1) + @Nullable String snpPath; + + /** Resolved absolute path. Transient. */ + @GridToStringExclude + @Nullable File resolvedPath; + + /** Default constructor for {@link MessageFactory}. */ + public SnapshotListRequest() { + // No-op. + } + + /** + * @param reqId Request ID. + * @param snpPath Snapshot directory path. + */ + SnapshotListRequest(UUID reqId, @Nullable String snpPath) { + this.reqId = reqId; + + if (!F.isEmpty(snpPath)) + snpPath = snpPath.trim(); + + this.snpPath = F.isEmpty(snpPath) ? null : snpPath; + } + + /** {@inheritDoc} */ + @Override public String toString() { + return S.toString(SnapshotListRequest.class, this); + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListResponse.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListResponse.java new file mode 100644 index 0000000000000..fea67c5610af2 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListResponse.java @@ -0,0 +1,59 @@ +/* + * 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.ignite.internal.processors.cache.persistence.snapshot; + +import java.util.Map; +import org.apache.ignite.internal.Order; +import org.apache.ignite.internal.util.typedef.internal.S; +import org.apache.ignite.plugin.extensions.communication.Message; +import org.apache.ignite.plugin.extensions.communication.MessageFactory; +import org.jetbrains.annotations.Nullable; + +/** + * Single-node result of the snapshot list distributed process. + * + * @see SnapshotListProcess + */ +public class SnapshotListResponse implements Message { + /** Snapshot name -> size in bytes (for available snapshots). */ + @Order(0) + @Nullable Map snapshotSizes; + + /** Snapshot name -> status string (for snapshots in transient state: CREATING, DELETING). */ + @Order(1) + @Nullable Map snapshotStatuses; + + /** Default constructor for {@link MessageFactory}. */ + public SnapshotListResponse() { + // No-op. + } + + /** + * @param sizes Snapshot sizes (name -> size bytes). + * @param statuses Snapshot statuses (name -> status) for transient snapshots. + */ + SnapshotListResponse(@Nullable Map sizes, @Nullable Map statuses) { + this.snapshotSizes = sizes; + this.snapshotStatuses = statuses; + } + + /** {@inheritDoc} */ + @Override public String toString() { + return S.toString(SnapshotListResponse.class, this); + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/distributed/DistributedProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/util/distributed/DistributedProcess.java index 9c428e0fa748c..8518a143a85f3 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/distributed/DistributedProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/distributed/DistributedProcess.java @@ -523,6 +523,11 @@ public enum DistributedProcessType { /** * Delete snapshot procedure. */ - DELETE_SNAPSHOT + DELETE_SNAPSHOT, + + /** + * List snapshots procedure. + */ + LIST_SNAPSHOTS } } From 669a8fd7a12f666928363c6c3243def140aa83a1 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Wed, 23 Sep 2026 12:01:24 +0300 Subject: [PATCH 56/97] raw fixes of neuroslop, cleaning --- docs/_docs/snapshots/snapshots.adoc | 57 +- .../CommandHandlerParsingTest.java | 3 - .../IgniteControlUtilityTestSuite.java | 2 - .../GridCommandHandlerDeleteSnapshotTest.java | 244 ------ .../GridCommandHandlerListSnapshotTest.java | 6 +- .../ignite/internal/CoreMessagesProvider.java | 8 +- .../management/snapshot/SnapshotCommand.java | 1 - .../snapshot/SnapshotDeleteCommand.java | 129 --- .../snapshot/SnapshotDeleteCommandArg.java | 61 -- .../snapshot/SnapshotDeleteTask.java | 61 -- .../snapshot/IgniteSnapshotManager.java | 146 +--- .../snapshot/SnapshotCheckProcess.java | 5 - .../snapshot/SnapshotDeleteProcess.java | 425 ---------- .../snapshot/SnapshotDeleteProcessResult.java | 86 -- .../snapshot/SnapshotDeleteRequest.java | 95 --- .../snapshot/SnapshotDeleteResponse.java | 68 -- .../snapshot/SnapshotListProcess.java | 23 +- .../snapshot/SnapshotRestoreProcess.java | 3 - .../feature/SupportedFeatureRegistry.java | 2 +- .../util/distributed/DistributedProcess.java | 5 - .../snapshot/AbstractSnapshotSelfTest.java | 87 -- .../IgniteClusterSnapshotCheckTest.java | 32 - ...usterSnapshotDeleteRollingUpgradeTest.java | 205 ----- .../IgniteClusterSnapshotDeleteTest.java | 777 ------------------ .../IgniteClusterSnapshotRestoreSelfTest.java | 39 - .../IgniteClusterSnapshotSelfTest.java | 39 +- .../TestIgniteReleaseFeatures_2_19_1.java | 2 +- .../testsuites/IgniteSnapshotTestSuite8.java | 4 - ...mmandHandlerClusterByClassTest_help.output | 7 - ...ndlerClusterByClassWithSSLTest_help.output | 7 - 30 files changed, 41 insertions(+), 2588 deletions(-) delete mode 100644 modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java delete mode 100644 modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java delete mode 100644 modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommandArg.java delete mode 100644 modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteTask.java delete mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java delete mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResult.java delete mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteRequest.java delete mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java delete mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgradeTest.java delete mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java diff --git a/docs/_docs/snapshots/snapshots.adoc b/docs/_docs/snapshots/snapshots.adoc index 49406d90dbba3..1c04ab5c16a82 100644 --- a/docs/_docs/snapshots/snapshots.adoc +++ b/docs/_docs/snapshots/snapshots.adoc @@ -287,54 +287,9 @@ control.(sh|bat) --snapshot restore snapshot_09062021 --groups cache-group1,cach control.(sh|bat) --snapshot restore snapshot_09062021 --increment 1 ---- -== Deleting Snapshot - -You can delete a snapshot using the `control.sh|bat` script. - -The deletion is performed on all *online* server nodes of the cluster. -[NOTE] -==== -The snapshot integrity, topology and correctness aren't checked. Snapshot data on offline server nodes aren't deleted. -==== - -[tabs] --- -tab:Unix[] -[source,shell] ----- -# Delete the snapshot "snapshot_09062021". -control.sh --snapshot delete snapshot_09062021 - -# Delete the snapshot "snapshot_09062021" located in the "/tmp/ignite/snapshots" folder. -control.sh --snapshot delete snapshot_09062021 --src /tmp/ignite/snapshots ----- - -tab:Windows[] -[source,shell] ----- -# Delete the snapshot "snapshot_09062021". -control.bat --snapshot delete snapshot_09062021 - -# Delete the snapshot "snapshot_09062021" located in the "/tmp/ignite/snapshots" folder. -control.bat --snapshot delete snapshot_09062021 --src /tmp/ignite/snapshots ----- --- - -=== Delete operation limitations - -The delete operation is subject to the following limitations: - -* The deletion is rejected if any snapshot operation with the same name (create, restore, check, or a delete with the same name) is - active for the snapshot. -* The operation cannot be undone and the deleted snapshot cannot be restored. The command prompts for a confirmation. -* Before deletion, no snapshot validation is done except finding and reading its metadata. If the metadata isn't found or cannot be read, snapshot isn't deleted. -* If the provided snapshot path belongs to Ignite's home or to Ignite's a not snapshot work directory, the operation fails. - == Listing Snapshots -You can list all snapshots and their sizes on all online server nodes using the `control.sh|bat` script. - -The list operation is performed on all *online* server nodes of the cluster. The output shows each snapshot found on each node along with its disk usage. If a snapshot is currently being created or deleted, the command reports its transient status instead of the size. +To list snapshots created on cluster, use the following command. [tabs] -- @@ -367,21 +322,21 @@ The command output displays per-node information: ---- Node consistent-id [node-uuid]: snapshot_name: - snapshot_name: CREATING - snapshot_name: DELETING + snapshot_name: CREATING (DELETING) ---- Where: * `` - human-readable size of the snapshot on disk (e.g., `1.5 GB`). * `CREATING` - the snapshot is in the process of being created on this node. The size is not calculated. -* `DELETING` - the snapshot is in the process of being deleted on this node. The size is not calculated. === List operation notes * The list operation is read-only and does not modify any snapshot data. -* If a snapshot is in a transient state (being created or deleted), its size is not reported. Instead, the command shows the corresponding status. -* Snapshot data on offline server nodes is not reported. +* If a snapshot is in a transient state (being created or deleted), its size is not reported. +* Snapshot is being detected by its metadata. If metadata is missing or cannot be read, snapshot directory is ignored. +* When snapshot listing and size calculating, no additional snapshot validation is done. Files, its' headers aren't read, +no checksums are calculated, etc. == Getting Snapshot Operation Status diff --git a/modules/control-utility/src/test/java/org/apache/ignite/internal/commandline/CommandHandlerParsingTest.java b/modules/control-utility/src/test/java/org/apache/ignite/internal/commandline/CommandHandlerParsingTest.java index 99882f4ccc6ff..0fc350ab7c8fc 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/internal/commandline/CommandHandlerParsingTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/internal/commandline/CommandHandlerParsingTest.java @@ -81,7 +81,6 @@ import org.apache.ignite.internal.management.performancestatistics.PerformanceStatisticsCommand; import org.apache.ignite.internal.management.property.PropertyCommand; import org.apache.ignite.internal.management.snapshot.SnapshotCommand; -import org.apache.ignite.internal.management.snapshot.SnapshotDeleteCommand; import org.apache.ignite.internal.management.snapshot.SnapshotRestoreCommand; import org.apache.ignite.internal.management.tx.TxCommand; import org.apache.ignite.internal.management.tx.TxCommandArg; @@ -530,8 +529,6 @@ else if (cmd.getClass() == EncryptionChangeCacheKeyCommand.class) cmdText = F.concat(cmdText, "cacheGroup1"); else if (cmd.getClass() == SnapshotRestoreCommand.class) cmdText = F.concat(cmdText, "snp1"); - else if (cmd.getClass() == SnapshotDeleteCommand.class) - cmdText = F.concat(cmdText, "snp1"); else if (cmd.getClass() == MetaUpdateCommand.class) return; else if (cmd.getClass() == MetaRemoveCommand.class) diff --git a/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite.java b/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite.java index 91a1d67d0dfc0..6528d919f6105 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite.java @@ -27,7 +27,6 @@ import org.apache.ignite.util.GridCommandHandlerCheckpointTest; import org.apache.ignite.util.GridCommandHandlerClusterByClassTest; import org.apache.ignite.util.GridCommandHandlerClusterByClassWithSSLTest; -import org.apache.ignite.util.GridCommandHandlerDeleteSnapshotTest; import org.apache.ignite.util.GridCommandHandlerIncompatibleSslConfigTest; import org.apache.ignite.util.GridCommandHandlerIndexingCheckSizeTest; import org.apache.ignite.util.GridCommandHandlerIndexingClusterByClassTest; @@ -75,7 +74,6 @@ GridCommandHandlerCheckIndexesInlineSizeTest.class, GridCommandHandlerMetadataTest.class, GridCommandHandlerCheckIncrementalSnapshotTest.class, - GridCommandHandlerDeleteSnapshotTest.class, GridCommandHandlerLegacyClientTest.class, KillCommandsControlShTest.class, diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java deleted file mode 100644 index 59f2e3e6de62b..0000000000000 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerDeleteSnapshotTest.java +++ /dev/null @@ -1,244 +0,0 @@ -/* - * 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.ignite.util; - -import java.io.File; -import java.nio.file.DirectoryStream; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Collection; -import org.apache.ignite.IgniteDataStreamer; -import org.apache.ignite.configuration.IgniteConfiguration; -import org.apache.ignite.internal.IgniteEx; -import org.apache.ignite.internal.management.snapshot.SnapshotDeleteCommand; -import org.apache.ignite.internal.util.typedef.F; -import org.apache.ignite.internal.util.typedef.internal.U; -import org.apache.ignite.testframework.GridTestUtils; -import org.junit.Test; -import org.junit.runners.Parameterized.Parameter; -import org.junit.runners.Parameterized.Parameters; - -import static java.nio.file.Files.newDirectoryStream; -import static org.apache.ignite.cluster.ClusterState.ACTIVE; -import static org.apache.ignite.internal.commandline.CommandHandler.EXIT_CODE_OK; -import static org.apache.ignite.internal.processors.cache.persistence.snapshot.AbstractSnapshotSelfTest.snp; -import static org.apache.ignite.testframework.GridTestUtils.waitForCondition; -import static org.junit.Assume.assumeTrue; - -/** Test for the command '--snapshot delete'. */ -public class GridCommandHandlerDeleteSnapshotTest extends GridCommandHandlerAbstractTest { - /** Value: -1 - do not use, 1 - server node, 0 - client node. */ - @Parameter(1) - public int extraNodeIsServer = -1; - - /** */ - @Parameter(2) - public boolean incremental; - - /** */ - @Parameter(3) - public boolean changeBaseline; - - /** */ - @Parameter(4) - public boolean customPath; - - /** */ - @Parameter(5) - public boolean separatedWorkDir; - - /** */ - @Parameters(name = "client={0},useExtraNode={1},inc={2},chBaseln={3},cstSnpPath={4},ownWorkDir={5}") - public static Collection parameters() { - return GridTestUtils.cartesianProduct( - commandHandlers(), - F.asList(-1, 1, 0), // Use extra node (do not use at all, server node, client node); - F.asList(false, true), // Add incremental snapshot; - F.asList(false, true), // Change baseline; - F.asList(false, true), // Use custom snapshot path; - F.asList(false, true) // Separated (own) work directory. - ); - } - - /** {@inheritDoc} */ - @Override protected void afterTest() throws Exception { - super.afterTest(); - - stopAllGrids(); - - cleanPersistenceDir(); - } - - /** {@inheritDoc} */ - @Override protected void beforeTest() throws Exception { - super.beforeTest(); - - /** Handy if test running is interrupted and {@link #afterTest()} isn't invoked. */ - cleanPersistenceDir(); - } - - /** {@inheritDoc} */ - @Override protected void cleanPersistenceDir() throws Exception { - super.cleanPersistenceDir(); - - // Also cleans separated snapshot working directories and custom snapshot patches. - try (DirectoryStream files = newDirectoryStream(Paths.get(U.defaultWorkDirectory()))) { - for (Path path : files) - U.delete(path); - } - } - - /** {@inheritDoc} */ - @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { - IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); - - if (separatedWorkDir) - cfg.setWorkDirectory(new File(U.defaultWorkDirectory(), igniteInstanceName).getAbsolutePath()); - - return cfg; - } - - /** */ - @Test - public void testSnapshotDelete() throws Exception { - // A custom snapshot path actually puts snapshots in a shared directory. This skews the results when dedicated - // work directories are set. - assumeTrue(!customPath || !separatedWorkDir); - - int entriesCnt = 100; - int initNodes = 3; - - walCompactionEnabled(incremental); - - IgniteEx ig = (IgniteEx)startGridsMultiThreaded(initNodes); - - if (changeBaseline) { - ig.cluster().baselineAutoAdjustEnabled(false); - - ig.cluster().setBaselineTopology(ig.cluster().topologyVersion()); - } - - ig.cluster().state(ACTIVE); - - createCacheAndPreload(ig, entriesCnt); - - File cstSnpsRoot = customPath - ? new File(grid(0).context().pdsFolderResolver().fileTree().snapshotsRoot(), "ex_snapshots") - : null; - File snpDir = new File(customPath ? cstSnpsRoot : ig.context().pdsFolderResolver().fileTree().snapshotsRoot(), "testSnapshot"); - - snp(ig).createSnapshot("testSnapshot", customPath ? cstSnpsRoot.getAbsolutePath() : null, false, false) - .get(getTestTimeout()); - - if (incremental) { - for (int i = 0; i < 3; ++i) { - int dataIdx = entriesCnt + entriesCnt / 4 * i; - - try (IgniteDataStreamer streamer = ig.dataStreamer(DEFAULT_CACHE_NAME)) { - for (int d = dataIdx; d < dataIdx + entriesCnt / 4; ++d) - streamer.addData(i, i); - } - - snp(ig).createSnapshot("testSnapshot", customPath ? cstSnpsRoot.getAbsolutePath() : null, true, false) - .get(getTestTimeout()); - } - } - - String baselineGone = null; - - // Optionally restarts with the same servers number, but changed baseline. The snapshot is kept on the same - // previous nodes independently of the baseline. - if (changeBaseline) { - baselineGone = grid(initNodes - 1).name(); - - ig.destroyCache(DEFAULT_CACHE_NAME); - awaitPartitionMapExchange(); - - stopAllGrids(); - - ig = (IgniteEx)startGridsMultiThreaded(initNodes - 1); - - ig.cluster().setBaselineTopology(ig.cluster().topologyVersion()); - - startGrid(initNodes - 1); - - assertEquals(initNodes - 1, ig.cluster().currentBaselineTopology().size()); - assertEquals(initNodes, ig.cluster().nodes().size()); - } - - // Optionally adds extra server or client node. - if (extraNodeIsServer == 1) - startGrid(initNodes); - else if (extraNodeIsServer == 0) - startGrid(CLIENT_NODE_NAME_PREFIX); - - injectTestSystemOut(); - - // Tests missing snapshot deletion. - if (customPath) { - assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "delete", "--src", - cstSnpsRoot.getAbsolutePath(), "wrongSnapshot")); - - assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "delete", "--src", - cstSnpsRoot.getAbsolutePath() + "_wrongPath", "testSnapshot")); - } - else - assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "delete", "wrongSnapshot")); - - String out = testOut.toString(); - - assertFalse(out.contains(SnapshotDeleteCommand.REMOVED_PREF)); - assertFalse(out.contains(SnapshotDeleteCommand.NODE_NOT_FOUND_PREF)); - assertTrue(out.contains(SnapshotDeleteCommand.NOT_FOUND_PREF)); - - testOut.reset(); - assertTrue(testOut.toString().isEmpty()); - - if (customPath) { - assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "delete", "--src", - cstSnpsRoot.getAbsolutePath(), "testSnapshot")); - } - else - assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "delete", "testSnapshot")); - - out = testOut.toString(); - - if (changeBaseline) - assertTrue(out.contains(SnapshotDeleteCommand.MISSING_BASELINES + "[cnt=1]: " + baselineGone)); - - if (separatedWorkDir) { - // When the nodes use own separated work directory, we expect a strict result. - assertTrue(out.contains(SnapshotDeleteCommand.REMOVED_PREF + "[cnt=%d]:".formatted(initNodes))); - - if (extraNodeIsServer == 1) - assertTrue(out.contains(SnapshotDeleteCommand.NODE_NOT_FOUND_PREF + "[cnt=1]:")); - else if (extraNodeIsServer == 0) - assertFalse(out.contains(SnapshotDeleteCommand.NODE_NOT_FOUND_PREF)); - } - else { - // When nodes use a shared work directory, there is a race for the delete operation. One node can get faster - // than others and remove snapshot completely quickly. The others might not find snapshot files. We can be - // only sure that at least one node removes snapshot. - assertTrue(out.contains(SnapshotDeleteCommand.REMOVED_PREF) || out.contains(SnapshotDeleteCommand.UNSURED_DELETION_PREF)); - } - - assertFalse(out.contains("Snapshot not found on current server nodes")); - - assertTrue(waitForCondition(() -> !snpDir.exists(), getTestTimeout())); - } -} diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java index 0603e5d1466d8..752e7ce7f5b01 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java @@ -222,14 +222,14 @@ public void testSnapshotListMultipleSnapshots() throws Exception { * Counts occurrences of the node prefix in the output. */ private int countNodeOccurrences(String output) { - int count = 0; + int cnt = 0; int idx = 0; while ((idx = output.indexOf(SnapshotListCommand.NODE_PREF, idx)) != -1) { - count++; + cnt++; idx += SnapshotListCommand.NODE_PREF.length(); } - return count; + return cnt; } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/CoreMessagesProvider.java b/modules/core/src/main/java/org/apache/ignite/internal/CoreMessagesProvider.java index 95d28ef5ec2b9..12e6c214bf488 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/CoreMessagesProvider.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/CoreMessagesProvider.java @@ -162,11 +162,11 @@ import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotCheckPartitionHashesResponse; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotCheckProcessRequest; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotCheckResponse; -import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotDeleteRequest; -import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotDeleteResponse; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotFilesFailureMessage; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotFilesRequestMessage; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotHandlerResult; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListRequest; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListResponse; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotMetadata; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotMetadataResponse; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotOperationEndRequest; @@ -439,8 +439,8 @@ public CoreMessagesProvider() { register(SnapshotFilesFailureMessage.class); register(IncrementalSnapshotVerifyResult.class); register(IncrementalSnapshotAwareMessage.class); - register(SnapshotDeleteRequest.class); - register(SnapshotDeleteResponse.class); + register(SnapshotListRequest.class); + register(SnapshotListResponse.class); // [6300 - 6400]: Services messages. Most of them originally come from Discovery. msgIdx = 6300; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCommand.java index 0b062eb2027b1..e9d4e7504351d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCommand.java @@ -29,7 +29,6 @@ public SnapshotCommand() { new SnapshotCheckCommand(), new SnapshotRestoreCommand(), new SnapshotStatusCommand(), - new SnapshotDeleteCommand(), new SnapshotListCommand() ); } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java deleted file mode 100644 index bd54538cae4a9..0000000000000 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommand.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * 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.ignite.internal.management.snapshot; - -import java.util.Collection; -import java.util.Map; -import java.util.UUID; -import java.util.function.Consumer; -import java.util.stream.Collectors; -import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotDeleteProcess; -import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotDeleteProcessResult; -import org.apache.ignite.internal.processors.rollingupgrade.feature.SupportedFeatureRegistry; -import org.apache.ignite.internal.util.typedef.internal.U; - -/** - * Snapshot deletion command. - * - * @see SupportedFeatureRegistry#SNAPSHOT_DELETE_FEATURE - * @see SnapshotDeleteProcess - */ -public class SnapshotDeleteCommand extends AbstractSnapshotCommand { - /** */ - public static final String DESC = "Deletes snapshot and all its incrementals from all the online server nodes"; - - /** */ - public static final String UNSURED_DELETION_PREF = "WARNING: the following nodes found snapshot data but might not " + - "remove it completely "; - - /** */ - public static final String REMOVED_PREF = "Snapshot removal is completed on "; - - /** */ - public static final String NODE_NOT_FOUND_PREF = "NOTE: the following nodes can't find any snapshot data, " + - "operation skipped "; - - /** */ - public static final String NOT_FOUND_PREF = "Snapshot not found on current server nodes "; - - /** */ - public static final String MISSING_BASELINES = "WARNING: the snapshot's baseline nodes with the following consistent " + - "ids are missing in current cluster "; - - /** - * {@inheritDoc} - */ - @Override public String description() { - return DESC; - } - - /** {@inheritDoc} */ - @Override public Class argClass() { - return SnapshotDeleteCommandArg.class; - } - - /** {@inheritDoc} */ - @Override public Class taskClass() { - return SnapshotDeleteTask.class; - } - - /** {@inheritDoc} */ - @Override public void printResult(SnapshotDeleteCommandArg arg, SnapshotDeleteProcessResult res, Consumer printer) { - boolean found = false; - - if (!res.uncompletedNodes().isEmpty()) { - found = true; - - printer.accept(UNSURED_DELETION_PREF + nodeIdPairsStrLst(res.uncompletedNodes())); - - printer.accept(""); - } - - if (!res.completedNodes().isEmpty()) { - found = true; - - printer.accept(REMOVED_PREF + nodeIdPairsStrLst(res.completedNodes())); - printer.accept(""); - } - - if (found) { - if (!res.emptyNodes().isEmpty()) - printer.accept(NODE_NOT_FOUND_PREF + nodeIdPairsStrLst(res.emptyNodes())); - - if (!res.absentBaselines().isEmpty()) - printer.accept(MISSING_BASELINES + nodeIdsStrLst(res.absentBaselines())); - } - else { - assert !res.emptyNodes().isEmpty(); - - printer.accept(NOT_FOUND_PREF); - } - } - - /** */ - private static String nodeIdPairsStrLst(Map uuids) { - return "[cnt=" + uuids.size() + "]: " + uuids.entrySet().stream() - .map(e -> e.getValue() + " [uuid=" + e.getKey() + ']') - .collect(Collectors.joining(", ")); - } - - /** */ - private static String nodeIdsStrLst(Collection uuids) { - return "[cnt=" + uuids.size() + "]: " + String.join(", ", uuids); - } - - /** {@inheritDoc} */ - @Override public String confirmationPrompt(SnapshotDeleteCommandArg arg) { - return "This operation will completely remove snapshot: '" + arg.snapshotName() + "' and all its incrementals " + - "from all online server nodes." + - U.nl() + U.nl() + - "NOTE: Snapshot data on offline server nodes will remain untouched." + - U.nl() + U.nl() + - "The operation cannot be reverted."; - } -} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommandArg.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommandArg.java deleted file mode 100644 index 3539a556fb420..0000000000000 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteCommandArg.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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.ignite.internal.management.snapshot; - -import org.apache.ignite.internal.Order; -import org.apache.ignite.internal.dto.IgniteDataTransferObject; -import org.apache.ignite.internal.management.api.Argument; -import org.apache.ignite.internal.management.api.Positional; - -/** */ -public class SnapshotDeleteCommandArg extends IgniteDataTransferObject { - /** */ - private static final long serialVersionUID = 0; - - /** */ - @Order(0) - @Positional - @Argument(description = "Snapshot name") - String snapshotName; - - /** */ - @Order(1) - @Argument(example = "path", optional = true, description = "Path to snapshot location directory. If not specified " + - "or specified a relative path, the default snapshot configuration directory will be used") - String src; - - /** */ - public String snapshotName() { - return snapshotName; - } - - /** */ - public void snapshotName(String snapshotName) { - this.snapshotName = snapshotName; - } - - /** */ - public String src() { - return src; - } - - /** */ - public void src(String src) { - this.src = src; - } -} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteTask.java deleted file mode 100644 index f90c9f4d78a4c..0000000000000 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotDeleteTask.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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.ignite.internal.management.snapshot; - -import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager; -import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotDeleteProcess; -import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotDeleteProcessResult; -import org.apache.ignite.internal.processors.task.GridInternal; -import org.apache.ignite.internal.visor.VisorJob; -import org.apache.ignite.internal.visor.VisorOneNodeTask; - -/** - * @see IgniteSnapshotManager#deleteSnapshot(String, String) - * @see SnapshotDeleteProcess - */ -@GridInternal -public class SnapshotDeleteTask extends VisorOneNodeTask { - /** Serial version uid. */ - private static final long serialVersionUID = 0L; - - /** {@inheritDoc} */ - @Override protected VisorJob job(SnapshotDeleteCommandArg arg) { - return new SnapshotDeleteJob(arg, debug); - } - - /** */ - private static class SnapshotDeleteJob extends SnapshotJob { - /** Serial version uid. */ - private static final long serialVersionUID = 0L; - - /** - * @param arg Snapshot delete task argument. - * @param debug Flag indicating whether debug information should be printed into node log. - */ - protected SnapshotDeleteJob(SnapshotDeleteCommandArg arg, boolean debug) { - super(arg, debug); - } - - /** {@inheritDoc} */ - @Override protected SnapshotDeleteProcessResult run(SnapshotDeleteCommandArg arg) { - IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); - - return snpMgr.deleteSnapshot(arg.snapshotName(), arg.src()).get(); - } - } -} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java index 6e88f5c2f10ff..5568a1ab0071d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java @@ -60,7 +60,6 @@ import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.RejectedExecutionException; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; import java.util.function.BiFunction; @@ -345,9 +344,6 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter /** Snapshot validation distributed process. */ private final SnapshotCheckProcess checkSnpProc; - /** Distributed process to delete cluster snapshot. */ - private final SnapshotDeleteProcess deleteSnpProc; - /** Distributed process to list cluster snapshots. */ private final SnapshotListProcess listSnpProc; @@ -453,8 +449,6 @@ public IgniteSnapshotManager(GridKernalContext ctx) { checkSnpProc = new SnapshotCheckProcess(ctx); - deleteSnpProc = new SnapshotDeleteProcess(ctx); - listSnpProc = new SnapshotListProcess(ctx); // Manage remote snapshots. @@ -675,7 +669,6 @@ public IgniteSnapshotManager(GridKernalContext ctx) { restoreCacheGrpProc.interrupt(stopErr); checkSnpProc.interrupt(stopErr); - deleteSnpProc.interrupt(stopErr); listSnpProc.interrupt(stopErr); // Try stop all snapshot processing if not yet. @@ -714,108 +707,46 @@ public IgniteSnapshotManager(GridKernalContext ctx) { /** * @param snpDir Snapshot dir. */ - public void deleteLocalSnapshot(File snpDir) { + public void deleteSnapshot(File snpDir) { if (!snpDir.exists()) return; if (!snpDir.isDirectory()) return; - var sft = new SnapshotFileTree( + deleteSnapshot(new SnapshotFileTree( cctx.kernalContext(), snpDir.getName(), snpDir.getParent(), ft.folderName(), - pdsSettings.consistentId().toString() - ); - - deleteLocalSnapshot(sft, null); + pdsSettings.consistentId().toString())); } - /** - * Tries to delete local snapshot data. - * - * @param sft Snapshot file tree. - * @param existsFlag Flag to set {@code true} if any snapshot file or directory was found (existed). If {@code null}, ignored. - * @return {@code True}, if data is found and completely deleted; - * {@code False}, if nothing found or if data is found but might not be deleted completely. - */ - public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean existsFlag) { - var exFlag0 = new AtomicBoolean(); - - sft.allStorages().forEach(s -> { - if (s.exists()) - exFlag0.set(true); - }); - - if (sft.root().exists()) - exFlag0.set(true); - - if (existsFlag != null) - existsFlag.set(exFlag0.get()); - - // Nothing to delete. - if (!exFlag0.get()) - return false; - - boolean res = true; - + /** */ + public void deleteSnapshot(SnapshotFileTree sft) { try { - if (sft.binaryMeta().exists() && !U.delete(sft.binaryMeta()) && sft.binaryMeta().exists()) - res = false; - - for (var s : sft.allStorages().toList()) { - if (s.exists() && !U.delete(s) && s.exists()) - res = false; - } - - if (sft.meta().exists() && !U.delete(sft.meta()) && sft.meta().exists()) - res = false; + U.delete(sft.binaryMeta()); + sft.allStorages().forEach(U::delete); + U.delete(sft.meta()); - if (sft.binaryMetaRoot().exists() && !deleteDirectory(sft.binaryMetaRoot()) && sft.binaryMetaRoot().exists()) - res = false; - - if (sft.marshaller().exists() && !deleteDirectory(sft.marshaller()) && sft.marshaller().exists()) - res = false; - - if (sft.incrementsRoot().exists() && !deleteDirectory(sft.incrementsRoot()) && sft.incrementsRoot().exists()) - res = false; + deleteDirectory(sft.binaryMetaRoot()); + deleteDirectory(sft.marshaller()); // Delete parent dir which is {snapshot_root}/db if empty. - if (!sft.marshaller().getParentFile().delete() && sft.marshaller().getParentFile().exists()) - res = false; - + sft.marshaller().getParentFile().delete(); // Delete root dir which is {snapshot_root} if empty. - if (!sft.root().delete() && sft.root().exists()) - res = false; - } - catch (Exception e) { - log.warning("Failed to delete local snapshot [snpName=" + sft.name() + ']', e); - - return false; + sft.root().delete(); } - - for (var s : sft.allStorages().toList()) { - if (s.exists()) - return false; + catch (IOException e) { + throw new IgniteException(e); } - - if (sft.root().exists()) - return false; - - return res; } /** Concurrently traverse the directory and delete all files. */ - private boolean deleteDirectory(File dir) throws IOException { - var res = new AtomicBoolean(); - - Files.walkFileTree(dir.toPath(), new SimpleFileVisitor<>() { + private void deleteDirectory(File dir) throws IOException { + Files.walkFileTree(dir.toPath(), new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { - File f0 = file.toFile(); - - if (f0.exists() && !U.delete(f0) && f0.exists()) - res.set(false); + U.delete(file); return FileVisitResult.CONTINUE; } @@ -826,10 +757,7 @@ private boolean deleteDirectory(File dir) throws IOException { } @Override public FileVisitResult postVisitDirectory(Path dir, IOException e) { - File f0 = dir.toFile(); - - if (f0.exists() && !f0.delete() && f0.exists()) - res.set(false); + dir.toFile().delete(); if (log.isInfoEnabled() && e != null) log.info("Snapshot directory cleaned with an exception [dir=" + dir + ", e=" + e.getMessage() + ']'); @@ -837,8 +765,6 @@ private boolean deleteDirectory(File dir) throws IOException { return FileVisitResult.CONTINUE; } }); - - return res.get(); } /** @@ -893,11 +819,6 @@ private IgniteInternalFuture initLocalSnapshotStartSt "re-encryption process is not finished yet.")); } - if (cctx.snapshotMgr().isSnapshotDeleting(req.snapshotName(), req.snapshotPath())) { - return new GridFinishedFuture<>(new IgniteCheckedException("Snapshot operation has been rejected. Snapshot " + - "'%s' is being deleted.".formatted(req.snapshotName()))); - } - List grpIds = new ArrayList<>(F.viewReadOnly(req.groups(), CU::cacheId)); Collection comprGrpIds = F.view(grpIds, i -> { CacheGroupDescriptor desc = cctx.cache().cacheGroupDescriptor(i); @@ -1367,7 +1288,7 @@ private IgniteInternalFuture initLocalSnapshotEndStag if (snpStartReq.incremental()) U.delete(snpOp.snapshotFileTree().incrementalSnapshotFileTree(snpStartReq.incrementIndex()).root()); else - deleteLocalSnapshot(snpOp.snapshotFileTree(), null); + deleteSnapshot(snpOp.snapshotFileTree()); } else if (!F.isEmpty(endReq.warnings())) { // Pass the warnings further to the next stage for the case when snapshot started from not coordinator. @@ -1531,27 +1452,6 @@ public boolean isSnapshotChecking(String snpName) { return checkSnpProc.isSnapshotChecking(snpName); } - /** - * @return {@code True} if a snapshot {@code snpName} delete operation is in progress. - */ - public boolean isSnapshotDeleting(String snpName, @Nullable String snpPath) { - return deleteSnpProc.isDeleting(snpName, snpPath); - } - - /** - * Deletes the cluster-wide snapshot with the given name. - *

- * The operation is rejected if a concurrent snapshot operation (create, restore, check, etc...) is in progress - * for the snapshot. - * - * @param name Snapshot name. - * @param snpPath Snapshot directory path. If {@code null}, the default configured snapshot directory will be used. - * @return Future which will be completed when the snapshot is deleted on all the online server nodes. - */ - public IgniteFuture deleteSnapshot(String name, @Nullable String snpPath) { - return deleteSnpProc.start(name, snpPath); - } - /** * Provides list of snapshots on all online server nodes. * @@ -2168,7 +2068,7 @@ public IgniteFutureImpl createSnapshot( if (!incremental && snpExists) { throw new IgniteException("Create snapshot request has been rejected. " + - "Snapshot with given name already exists on local node or the path is not empty."); + "Snapshot with given name already exists on local node."); } if (incremental) { @@ -2399,7 +2299,7 @@ public IgniteFutureImpl restoreSnapshot( if (SnapshotFileTree.incrementSnapshotDir(snpDir)) U.delete(snpDir); else - deleteLocalSnapshot(snpDir); + deleteSnapshot(snpDir); } if (log.isInfoEnabled()) { @@ -4093,7 +3993,7 @@ public LocalSnapshotSender(SnapshotFileTree sft) { log.info("The Local snapshot sender closed. All resources released [dbNodeSnpDir=" + sft.nodeStorage() + ']'); } else { - deleteLocalSnapshot(sft, null); + deleteSnapshot(sft); if (log.isDebugEnabled()) log.debug("Local snapshot sender closed due to an error occurred: " + th.getMessage()); @@ -4453,7 +4353,7 @@ public CancelSnapshotCallable(UUID reqId, String snpName) { } /** {@inheritDoc} */ - @Override public Boolean call() { + @Override public Boolean call() throws Exception { if (reqId != null) return ignite.context().cache().context().snapshotMgr().cancelLocalSnapshotOperations(reqId); else { diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java index 6793f5fd8ec71..4226483224a20 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java @@ -496,11 +496,6 @@ private IgniteInternalFuture prepareAndCheckMetas(UUID ig + "' has already started [req=" + req + ']')); } - if (kctx.cache().context().snapshotMgr().isSnapshotDeleting(req.snapshotName(), req.snapshotPath())) { - return new GridFinishedFuture<>(new IgniteIllegalStateException("Snapshot '" + req.snapshotName() - + "' is being deleted [req=" + req + ']')); - } - // Excludes non-baseline initiator. if (!baseline(kctx.localNodeId())) return new GridFinishedFuture<>(); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java deleted file mode 100644 index 13a9b87e9a174..0000000000000 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java +++ /dev/null @@ -1,425 +0,0 @@ -/* - * 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.ignite.internal.processors.cache.persistence.snapshot; - -import java.io.File; -import java.nio.file.Path; -import java.util.Collection; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.atomic.AtomicBoolean; -import org.apache.ignite.IgniteIllegalStateException; -import org.apache.ignite.IgniteLogger; -import org.apache.ignite.internal.GridKernalContext; -import org.apache.ignite.internal.IgniteInternalFuture; -import org.apache.ignite.internal.NodeStoppingException; -import org.apache.ignite.internal.processors.cache.persistence.filename.SnapshotFileTree; -import org.apache.ignite.internal.util.CommonUtils; -import org.apache.ignite.internal.util.distributed.DistributedProcess; -import org.apache.ignite.internal.util.future.GridCompoundFuture; -import org.apache.ignite.internal.util.future.GridFinishedFuture; -import org.apache.ignite.internal.util.future.GridFutureAdapter; -import org.apache.ignite.internal.util.future.IgniteFutureImpl; -import org.apache.ignite.internal.util.typedef.F; -import org.apache.ignite.internal.util.typedef.internal.U; -import org.apache.ignite.lang.IgniteFuture; -import org.apache.ignite.lang.IgniteReducer; -import org.jetbrains.annotations.Nullable; - -import static org.apache.ignite.internal.processors.rollingupgrade.feature.SupportedFeatureRegistry.SNAPSHOT_DELETE_FEATURE; -import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.DELETE_SNAPSHOT; -import static org.apache.ignite.plugin.security.SecurityPermission.ADMIN_SNAPSHOT; - -/** - * Distributed process to delete a cluster snapshot. The operation is rejected if any concurrent snapshot operation is - * active. - */ -public class SnapshotDeleteProcess { - /** Reject operation messages. */ - private static final String OP_REJECT_MSG = "Snapshot deletion was rejected. "; - - /** */ - private static final String SNP_PATH_ERR_PREF = "Provided snapshot path "; - - /** Kernal context. */ - private final GridKernalContext kctx; - - /** Logger. */ - private final IgniteLogger log; - - /** */ - private volatile boolean interrupted; - - /** Cluster-wide operation futures per request id on certain node. */ - private final Map> clusterOpFuts = new ConcurrentHashMap<>(); - - /** Process requests per snapshot name on each server node. */ - private final Set requests = ConcurrentHashMap.newKeySet(); - - /** The distributed process. */ - private final DistributedProcess distrProc; - - /** - * @param ctx Kernal context. - */ - public SnapshotDeleteProcess(GridKernalContext ctx) { - this.kctx = ctx; - - log = ctx.log(getClass()); - - distrProc = new DistributedProcess<>(ctx, DELETE_SNAPSHOT, this::deletePhase, this::reducePhase); - } - - /** - * Starts the cluster snapshot delete process. - * - * @param snpName Snapshot name. - * @param snpPath Snapshot directory path (optional). - * @return Future that will be completed when the snapshot is deleted. - */ - public IgniteFuture start(String snpName, @Nullable String snpPath) { - var clusterOpFut = new GridFutureAdapter(); - - if (!kctx.rollingUpgrade().features().isActive(SNAPSHOT_DELETE_FEATURE)) { - clusterOpFut.onDone(new IgniteIllegalStateException(OP_REJECT_MSG + - "The snapshot deletion feature isn't activated yet [snpName=" + snpName + ", snpPath=" + snpPath + ']')); - - return new IgniteFutureImpl<>(clusterOpFut); - } - - UUID reqId = UUID.randomUUID(); - - clusterOpFut.listen(fut -> clusterOpFuts.remove(reqId)); - - try { - synchronized (clusterOpFuts) { - if (interrupted || kctx.isStopping()) - throw new NodeStoppingException("Failed to start snapshot delete process: node is stopping."); - - clusterOpFuts.put(reqId, clusterOpFut); - } - - SnapshotDeleteRequest req = new SnapshotDeleteRequest(reqId, snpName, snpPath); - - distrProc.start(reqId, req); - } - catch (Throwable t) { - log.error("Failed to start distributed delete snapshot process [snpName=" + snpName + ", snpPath=" + snpPath + ']', t); - - clusterOpFut.onDone(t); - } - - return new IgniteFutureImpl<>(clusterOpFut); - } - - /** */ - private IgniteInternalFuture deletePhase(UUID ignored, SnapshotDeleteRequest req) { - if (interrupted || kctx.isStopping()) { - return new GridFinishedFuture<>(new NodeStoppingException(OP_REJECT_MSG + - " Node is stopping [req=" + req + ']')); - } - - if (kctx.cluster().get().localNode().isClient()) - return new GridFinishedFuture<>(new SnapshotDeleteResponse()); - - kctx.security().authorize(ADMIN_SNAPSHOT); - - IgniteSnapshotManager snpMgr = kctx.cache().context().snapshotMgr(); - - var curCreateRq = snpMgr.currentCreateRequest(); - - if (curCreateRq != null && curCreateRq.snpName.equalsIgnoreCase(req.snpName)) { - return new GridFinishedFuture<>(new IgniteIllegalStateException(OP_REJECT_MSG + - "Snapshot with this name is being created [req=" + req + ']')); - } - - if (snpMgr.isRestoring(req.snpName)) { - return new GridFinishedFuture<>(new IgniteIllegalStateException(OP_REJECT_MSG + - "Snapshot with this name is being restored [req=" + req + ']')); - } - - if (snpMgr.isSnapshotChecking(req.snpName)) { - return new GridFinishedFuture<>(new IgniteIllegalStateException(OP_REJECT_MSG + - "Snapshot with this name is being checked [req=" + req + ']')); - } - - File path = resolvePath(req.snpPath); - - String pathValidationErr = validateAbsoluteSnapshotRoot(path); - - if (pathValidationErr != null) { - return new GridFinishedFuture<>(new IllegalArgumentException(OP_REJECT_MSG + - SNP_PATH_ERR_PREF + pathValidationErr + " [req=" + req + ']')); - } - - req.resolvedPath = path; - - try { - if (!requests.add(req)) { - return new GridFinishedFuture<>(new IgniteIllegalStateException("Deletion of the snapshot has already " + - "started [req=" + req + ']')); - } - - SnapshotFileTree snpFiles = new SnapshotFileTree(kctx, req.snpName, path.getAbsolutePath()); - - // We need to find and read snapshot metas to ensure the content is a snapshot. Also, the metas contain - // initial cluster topology and actual snasphot folder names. - List locMetas = kctx.cache().context().snapshotMgr().readSnapshotMetadatas(snpFiles, false); - - if (locMetas.isEmpty()) { - requests.remove(req); - - log.warning("Snapshot deletion won't process, no snapshot metadata found [req=" + req + ']'); - - return new GridFinishedFuture<>(new SnapshotDeleteResponse(SnapshotDeleteResponse.DeleteStatus.NOT_FOUND, null)); - } - - // Future to delete snapshot contents according to snapshot metadatas. - GridCompoundFuture resultFut = - new GridCompoundFuture<>(new MetaFuturesReducer()); - - resultFut.listen(fut -> requests.remove(req)); - - File path0 = path; - - for (var meta : locMetas) { - GridFutureAdapter perMetaFut = new GridFutureAdapter<>(); - - kctx.pools().getSnapshotExecutorService().submit(() -> { - try { - AtomicBoolean foundFlag = new AtomicBoolean(); - - // Read file tree of the snapshot. - var byMetaSft = new SnapshotFileTree(kctx, req.snpName, path0.getAbsolutePath(), meta.folderName(), - meta.consId); - - boolean deleted = snpMgr.deleteLocalSnapshot(byMetaSft, foundFlag); - - SnapshotDeleteResponse.DeleteStatus status; - - if (foundFlag.get()) { - if (deleted && log.isInfoEnabled()) - log.info("Snapshot successfully deleted [req=" + req + ']'); - else if (!deleted) - log.warning("Snapshot deleted not completely [req=" + req + ']'); - - status = deleted - ? SnapshotDeleteResponse.DeleteStatus.DELETED - : SnapshotDeleteResponse.DeleteStatus.PARTLY; - } - else { - if (log.isInfoEnabled()) - log.info("Snapshot not found to delete [req=" + req + ']'); - - status = SnapshotDeleteResponse.DeleteStatus.NOT_FOUND; - } - - perMetaFut.onDone(new SnapshotDeleteResponse(status, meta.bltNodes)); - } - catch (Throwable e) { - perMetaFut.onDone(e); - } - }); - - resultFut.add(perMetaFut); - } - - resultFut.markInitialized(); - - if (log.isInfoEnabled()) - log.info("Deletion of snapshot initialized [req=" + req + ']'); - - return resultFut; - } - catch (Throwable t) { - requests.remove(req); - - log.warning("An error occurred during snapshot deletion [req=" + req + ']', t); - - return new GridFinishedFuture<>(t); - } - } - - /** */ - private File resolvePath(@Nullable String path) { - var res = kctx.pdsFolderResolver().fileTree().snapshotsRoot(); - - if (path != null) { - File reqPath = new File(path); - - res = reqPath.isAbsolute() ? reqPath : new File(res, path); - } - - return res; - } - - /** */ - private @Nullable String validateAbsoluteSnapshotRoot(@Nullable File path) { - if (path == null) - return null; - - assert path.isAbsolute(); - - var ignFileTree = kctx.pdsFolderResolver().fileTree(); - - if (ignFileTree.snapshotsRoot().compareTo(path) != 0 && !contains(ignFileTree.snapshotsRoot(), path)) { - for (var ignPath : List.of(new File(CommonUtils.getIgniteHome()), ignFileTree.root())) { - if (ignPath.compareTo(path) == 0 || contains(ignPath, path)) - return "belongs to a an Ignite's directory"; - } - } - - return null; - } - - /** */ - private void reducePhase(UUID reqId, Map results, Map errors) { - var clusterOpFut = clusterOpFuts.get(reqId); - - if (clusterOpFut == null) - return; - - assert clusterOpFut != null; - - try { - var errP = F.isEmpty(errors) ? null : F.first(errors.entrySet()); - - if (errP != null) { - log.warning("Snapshot deletion finished with an error [reqId=" + reqId + ", nodeId=" - + errP.getKey() + ", err='" + errP.getValue().getMessage() + "']", errP.getValue()); - - clusterOpFut.onDone(errP.getValue()); - - return; - } - - Map completedNodes = U.newHashMap(results.size()); - Map uncompletedNodes = U.newHashMap(results.size()); - Map emptyNodes = U.newHashMap(results.size()); - var snpNodes = new HashSet(); - - results.forEach((nodeId, nodeRes) -> { - if (!F.isEmpty(nodeRes.nodeIds)) - snpNodes.addAll(nodeRes.nodeIds); - - if (nodeRes.status != null) { - switch (nodeRes.status) { - case NOT_FOUND: - emptyNodes.put(nodeId, consistentId(nodeId)); - break; - case DELETED: - completedNodes.put(nodeId, consistentId(nodeId)); - break; - case PARTLY: - uncompletedNodes.put(nodeId, consistentId(nodeId)); - break; - default: - throw new IgniteIllegalStateException("Unknown snapshot deletion node result, [nodeRes=" + - nodeRes + ", nodeId=" + nodeId + ']'); - } - } - }); - - kctx.discovery().baselineNodes(kctx.discovery().topologyVersionEx()).stream() - .map(bn -> bn.consistentId().toString()).toList().forEach(snpNodes::remove); - - clusterOpFut.onDone(new SnapshotDeleteProcessResult(completedNodes, uncompletedNodes, emptyNodes, snpNodes)); - } - catch (Throwable t) { - clusterOpFut.onDone(t); - } - } - - /** */ - private String consistentId(UUID nodeId) { - var node = kctx.discovery().node(nodeId); - - if (node == null) - node = kctx.discovery().historicalNode(nodeId); - - return node == null ? "" : node.consistentId().toString(); - } - - /** */ - public boolean isDeleting(String snpName, @Nullable String snpPath) { - var rq = new SnapshotDeleteRequest(null, snpName, snpPath); - - rq.resolvedPath = resolvePath(rq.snpPath); - - return requests.contains(rq); - } - - /** - * @param err The interrupt reason. - */ - void interrupt(Throwable err) { - synchronized (clusterOpFuts) { - interrupted = true; - } - - clusterOpFuts.forEach((reqId, clusterOpFut) -> clusterOpFut.onDone(err)); - - clusterOpFuts.clear(); - } - - /** */ - public static boolean contains(File root, File candidate) { - Path root0 = root.toPath().toAbsolutePath().normalize(); - Path candidate0 = candidate.toPath().toAbsolutePath().normalize(); - - return candidate0.startsWith(root0); - } - - /** */ - private static class MetaFuturesReducer implements IgniteReducer { - /** Serial version uid. */ - private static final long serialVersionUID = 0L; - - /** */ - private @Nullable SnapshotDeleteResponse.DeleteStatus status; - - /** */ - private final Collection nodeIds = new HashSet<>(); - - /** {@inheritDoc} */ - @Override public boolean collect(SnapshotDeleteResponse res) { - assert res != null; - - synchronized (this) { - if (!F.isEmpty(res.nodeIds)) - nodeIds.addAll(res.nodeIds); - - if (status == null || status == res.status) - status = res.status; - else - status = SnapshotDeleteResponse.DeleteStatus.PARTLY; - } - - return true; - } - - /** {@inheritDoc} */ - @Override public SnapshotDeleteResponse reduce() { - return new SnapshotDeleteResponse(status == null ? SnapshotDeleteResponse.DeleteStatus.NOT_FOUND : status, nodeIds); - } - } -} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResult.java deleted file mode 100644 index 5bf62b5373cc1..0000000000000 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcessResult.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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.ignite.internal.processors.cache.persistence.snapshot; - -import java.util.Collection; -import java.util.Collections; -import java.util.Map; -import java.util.UUID; -import org.apache.ignite.internal.Order; -import org.apache.ignite.internal.dto.IgniteDataTransferObject; -import org.apache.ignite.plugin.extensions.communication.MessageFactory; - -/** Result of {@link SnapshotDeleteProcess}. */ -public final class SnapshotDeleteProcessResult extends IgniteDataTransferObject { - /** Serial version uid. */ - private static final long serialVersionUID = 0L; - - /** Nodes which found snapshot data and completely removed it. */ - @Order(0) - Map completedNodes; - - /** Nodes which found snapshot data but didn't remove it completely. */ - @Order(1) - Map uncompletedNodes; - - /** Server nodes which didn't find any snapshot data. */ - @Order(2) - Map emptyNodes; - - /** Snapshot's baseline nodes missing in current cluster. */ - @Order(3) - Collection absentBaselines; - - /** Default constructor for {@link MessageFactory}. */ - public SnapshotDeleteProcessResult() { - // No-op. - } - - /** */ - public SnapshotDeleteProcessResult( - Map completedNodes, - Map uncompletedNodes, - Map emptyNodes, - Collection absentBaselines - ) { - this.completedNodes = completedNodes; - this.uncompletedNodes = uncompletedNodes; - this.emptyNodes = emptyNodes; - this.absentBaselines = absentBaselines; - } - - /** */ - public Map completedNodes() { - return Collections.unmodifiableMap(completedNodes); - } - - /** */ - public Map uncompletedNodes() { - return Collections.unmodifiableMap(uncompletedNodes); - } - - /** */ - public Map emptyNodes() { - return Collections.unmodifiableMap(emptyNodes); - } - - /** */ - public Collection absentBaselines() { - return Collections.unmodifiableCollection(absentBaselines); - } -} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteRequest.java deleted file mode 100644 index 735909bb7f9ec..0000000000000 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteRequest.java +++ /dev/null @@ -1,95 +0,0 @@ -/* - * 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.ignite.internal.processors.cache.persistence.snapshot; - -import java.io.File; -import java.util.Objects; -import java.util.UUID; -import org.apache.ignite.internal.Order; -import org.apache.ignite.internal.util.tostring.GridToStringExclude; -import org.apache.ignite.internal.util.typedef.F; -import org.apache.ignite.internal.util.typedef.internal.S; -import org.apache.ignite.plugin.extensions.communication.Message; -import org.apache.ignite.plugin.extensions.communication.MessageFactory; -import org.jetbrains.annotations.Nullable; - -/** - * Cluster snapshot delete distributed process request. - * - * @see SnapshotDeleteProcess - */ -public class SnapshotDeleteRequest implements Message { - /** Request ID. */ - @Order(0) - UUID reqId; - - /** Snapshot name. */ - @Order(1) - String snpName; - - /** Snapshot directory path. */ - @Order(2) - @Nullable String snpPath; - - /** Resolved absolute path. Transient */ - @GridToStringExclude - @Nullable File resolvedPath; - - /** Default constructor for {@link MessageFactory}. */ - public SnapshotDeleteRequest() { - // No-op. - } - - /** - * @param reqId Request ID. - * @param snpName Snapshot name. - * @param snpPath Snapshot directory path. - */ - SnapshotDeleteRequest(UUID reqId, String snpName, @Nullable String snpPath) { - this.reqId = reqId; - this.snpName = snpName.trim(); - - // A protection against empty relative paths like " ". - if (!F.isEmpty(snpPath)) - snpPath = snpPath.trim(); - - snpPath = F.isEmpty(snpPath) ? null : snpPath; - - this.snpPath = snpPath; - } - - /** {@inheritDoc} */ - @Override public boolean equals(Object o) { - if (o == null || getClass() != o.getClass()) - return false; - - SnapshotDeleteRequest other = (SnapshotDeleteRequest)o; - - return snpName.equalsIgnoreCase(other.snpName) && Objects.equals(resolvedPath, other.resolvedPath); - } - - /** {@inheritDoc} */ - @Override public int hashCode() { - return Objects.hash(snpName.toLowerCase(), resolvedPath); - } - - /** {@inheritDoc} */ - @Override public String toString() { - return S.toString(SnapshotDeleteRequest.class, this); - } -} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java deleted file mode 100644 index 24a7878e3dbf4..0000000000000 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteResponse.java +++ /dev/null @@ -1,68 +0,0 @@ -/* - * 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.ignite.internal.processors.cache.persistence.snapshot; - -import java.util.Collection; -import org.apache.ignite.internal.Order; -import org.apache.ignite.internal.util.typedef.internal.S; -import org.apache.ignite.plugin.extensions.communication.Message; -import org.apache.ignite.plugin.extensions.communication.MessageFactory; -import org.jetbrains.annotations.Nullable; - -/** - * Single-node result of the snapshot deletion distributed process. - * - * @see SnapshotDeleteProcess - */ -public class SnapshotDeleteResponse implements Message { - /** {@code null} for client node. */ - @Order(0) - @Nullable SnapshotDeleteResponse.DeleteStatus status; - - /** Snapshot's node ids. */ - @Order(1) - @Nullable Collection nodeIds; - - /** Default constructor for {@link MessageFactory}. */ - public SnapshotDeleteResponse() { - // No-op. - } - - /** {@code null} for client node. */ - SnapshotDeleteResponse(DeleteStatus status, Collection nodeIds) { - this.status = status; - this.nodeIds = nodeIds; - } - - /** {@inheritDoc} */ - @Override public String toString() { - return S.toString(SnapshotDeleteResponse.class, this); - } - - /** */ - enum DeleteStatus { - /** Snapshot found and completely deleted. */ - DELETED, - - /** Snapshot found but some files or directories might not be deleted (locked). */ - PARTLY, - - /** Snapshot not found. */ - NOT_FOUND; - } -} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListProcess.java index 3000dcd30ce07..fe11b42e9b5e3 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListProcess.java @@ -33,8 +33,8 @@ import org.apache.ignite.internal.IgniteInternalFuture; import org.apache.ignite.internal.NodeStoppingException; import org.apache.ignite.internal.util.distributed.DistributedProcess; -import org.apache.ignite.internal.util.future.GridFutureAdapter; import org.apache.ignite.internal.util.future.GridFinishedFuture; +import org.apache.ignite.internal.util.future.GridFutureAdapter; import org.apache.ignite.internal.util.future.IgniteFutureImpl; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.lang.IgniteFuture; @@ -50,12 +50,6 @@ public class SnapshotListProcess { /** Status: snapshot is in process of creation. */ static final String STATUS_CREATING = "CREATING"; - /** Status: snapshot is in process of deletion. */ - static final String STATUS_DELETING = "DELETING"; - - /** Status: snapshot is in process of restore. */ - static final String STATUS_RESTORING = "RESTORING"; - /** Kernal context. */ private final GridKernalContext kctx; @@ -147,21 +141,8 @@ private IgniteInternalFuture listPhase(UUID ignored, Snaps for (File dir : dirs) { String snpName = dir.getName(); - if (isSnapshotCreating(snpMgr, snpName) || isSnapshotRestoring(snpMgr, snpName)) { + if (isSnapshotCreating(snpMgr, snpName) || isSnapshotRestoring(snpMgr, snpName)) statuses.put(snpName, STATUS_CREATING); - } - else if (snpMgr.isSnapshotDeleting(snpName, req.snpPath)) { - statuses.put(snpName, STATUS_DELETING); - } - else { - try { - sizes.put(snpName, directorySize(dir)); - } - catch (IOException e) { - // Snapshot might be modified between listing and size calculation. - statuses.put(snpName, STATUS_DELETING); - } - } } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotRestoreProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotRestoreProcess.java index f4fe684eba584..77f56f4d4cc7f 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotRestoreProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotRestoreProcess.java @@ -659,9 +659,6 @@ private IgniteInternalFuture prepare(UUID igno if (snpMgr.isSnapshotCreating()) throw new IgniteCheckedException(OP_REJECT_MSG + "A cluster snapshot operation is in progress."); - if (snpMgr.isSnapshotDeleting(req.snapshotName(), req.snapshotPath())) - throw new IgniteException(OP_REJECT_MSG + "A snapshot '" + req.snapshotName() + "' delete operation is in progress."); - if (ctx.encryption().isMasterKeyChangeInProgress()) { return new GridFinishedFuture<>(new IgniteCheckedException(OP_REJECT_MSG + "Master key changing " + "process is not finished yet.")); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/SupportedFeatureRegistry.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/SupportedFeatureRegistry.java index a14e52331cda0..2d0de561cf250 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/SupportedFeatureRegistry.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/SupportedFeatureRegistry.java @@ -95,5 +95,5 @@ public class SupportedFeatureRegistry { public static final IgniteFeature ROLLING_UPGRADE_FEATURE = new IgniteCoreFeature(0); /** */ - public static final IgniteFeature SNAPSHOT_DELETE_FEATURE = new IgniteCoreFeature(1); + public static final IgniteFeature SNAPSHOT_LIST_FEATURE = new IgniteCoreFeature(1); } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/distributed/DistributedProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/util/distributed/DistributedProcess.java index 8518a143a85f3..4fda2fa2fe15e 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/distributed/DistributedProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/distributed/DistributedProcess.java @@ -520,11 +520,6 @@ public enum DistributedProcessType { */ RU_ABORT_VERSION_FINALIZATION, - /** - * Delete snapshot procedure. - */ - DELETE_SNAPSHOT, - /** * List snapshots procedure. */ diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java index 300321f6e848c..06e0e90093bb1 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java @@ -36,10 +36,7 @@ import java.util.Queue; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; -import java.util.concurrent.CountDownLatch; import java.util.concurrent.Executor; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Function; @@ -102,7 +99,6 @@ import org.apache.ignite.lang.IgniteFutureTimeoutException; import org.apache.ignite.lang.IgnitePredicate; import org.apache.ignite.plugin.AbstractTestPluginProvider; -import org.apache.ignite.plugin.PluginContext; import org.apache.ignite.spi.discovery.DiscoverySpiCustomMessage; import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi; import org.apache.ignite.spi.discovery.tcp.TestTcpDiscoverySpi; @@ -847,82 +843,6 @@ public static void doSnapshotCancellationTest( assertEquals("Snapshot directory must be empty due to snapshot cancelled", 0, snpDir.list().length); } - /** Tests concurrent snapshot deletion. */ - protected void doTestConcurrentSnapshotDeleteOperation( - ExRunnable prepareCluster, - ExRunnable concurrentOp, - @Nullable Function errValidator, - boolean rerunAtTheEnd - ) throws Exception { - CountDownLatch delProcInitLatch = new CountDownLatch(1); - CountDownLatch delProcProceedLatch = new CountDownLatch(1); - - pluginProvider = new AbstractTestPluginProvider() { - @Override public String name() { - return "TestSnpMgrProvider"; - } - - @Override public T createComponent(PluginContext ctx, Class cls) { - if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { - return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { - @Override public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean existsFlag) { - delProcInitLatch.countDown(); - - try { - assertTrue(delProcProceedLatch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); - } - catch (InterruptedException e) { - throw new RuntimeException("Interrupted.", e); - } - - return super.deleteLocalSnapshot(sft, existsFlag); - } - }; - } - - return super.createComponent(ctx, cls); - } - }; - - prepareCluster.run(); - - var delFut = snp(grid(0)).deleteSnapshot(SNAPSHOT_NAME, null); - - assertTrue(delProcInitLatch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); - - try { - concurrentOp.run(); - - if (errValidator != null) - throw new IllegalStateException("Exception is not thrown."); - } - catch (Exception e) { - if (errValidator == null || !errValidator.apply(e)) - throw new IllegalStateException("Unexpected exception: " + e.getMessage(), e); - } - - delProcProceedLatch.countDown(); - - delFut.get(getTestTimeout()); - - for (var node : G.allGrids()) - assertFalse(new SnapshotFileTree(((IgniteEx)node).context(), SNAPSHOT_NAME, null).root().exists()); - - if (!rerunAtTheEnd) - return; - - assertThrowsAnyCause( - null, - () -> { - concurrentOp.run(); - - return null; - }, - IllegalArgumentException.class, - "Snapshot does not exists " - ); - } - /** * @param sft Snapshot file tree. * @param parts Collection of pairs group and appropriate cache partition to be snapshot. @@ -1103,13 +1023,6 @@ public void waitBlockedSize(int size, long timeout) throws IgniteInterruptedChec } } - /** */ - @FunctionalInterface - protected interface ExRunnable { - /** */ - void run() throws Exception; - } - /** */ protected static class Value { /** */ diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java index 268ff54775232..3564dd3bdbf26 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java @@ -125,7 +125,6 @@ import static org.apache.ignite.testframework.GridTestUtils.cartesianProduct; import static org.apache.ignite.testframework.GridTestUtils.waitForCondition; import static org.junit.Assume.assumeFalse; -import static org.junit.Assume.assumeTrue; /** * Cluster-wide snapshot check procedure tests. @@ -1203,37 +1202,6 @@ public void testConcurrentFullCheckAndFullRestoreDeclined() throws Exception { ); } - /** */ - @Test - public void testConcurrentSnapshotDeleteAndCheckOperations() throws Exception { - doTestConcurrentSnapshotDeleteOperation( - () -> prepareGridsAndSnapshot(4, 3, 1, false), - () -> snp(grid(2)).checkSnapshot(SNAPSHOT_NAME, null).get(), - e -> e.getMessage().contains("Snapshot '%s' is being deleted".formatted(SNAPSHOT_NAME)), - true - ); - } - - /** */ - @Test - public void testConcurrentSnapshotDeleteAndCheckOperationsWithDifferentPath() throws Exception { - // The test uses thread blocking. - assumeTrue(snpThrdPoolSz > 1); - - String snpPath = new File(U.defaultWorkDirectory(), "ex_snapshots").getAbsolutePath(); - - doTestConcurrentSnapshotDeleteOperation( - () -> { - prepareGridsAndSnapshot(4, 3, 1, false); - - snp(grid(0)).createSnapshot(SNAPSHOT_NAME, snpPath, false, false).get(getTestTimeout()); - }, - () -> snp(grid(2)).checkSnapshot(SNAPSHOT_NAME, snpPath).get(), - null, - false - ); - } - /** Tests that concurrent snapshot full check is declined when the same snapshot is being fully restored (checked). */ @Test public void testConcurrentTheSameSnpFullCheckWhenFullyRestoringDeclined() throws Exception { diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgradeTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgradeTest.java deleted file mode 100644 index 7a9e24a38ce21..0000000000000 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgradeTest.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * 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.ignite.internal.processors.cache.persistence.snapshot; - -import java.io.File; -import java.nio.file.DirectoryStream; -import java.nio.file.Path; -import java.nio.file.Paths; -import org.apache.ignite.IgniteIllegalStateException; -import org.apache.ignite.cache.CacheAtomicityMode; -import org.apache.ignite.cache.CacheMode; -import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction; -import org.apache.ignite.configuration.CacheConfiguration; -import org.apache.ignite.configuration.DataRegionConfiguration; -import org.apache.ignite.configuration.DataStorageConfiguration; -import org.apache.ignite.configuration.IgniteConfiguration; -import org.apache.ignite.internal.processors.rollingupgrade.AbstractRollingUpgradeTest; -import org.apache.ignite.internal.util.distributed.SingleNodeMessage; -import org.apache.ignite.internal.util.typedef.F; -import org.apache.ignite.internal.util.typedef.internal.U; -import org.apache.ignite.testframework.GridTestUtils; -import org.junit.Test; - -import static java.nio.file.Files.newDirectoryStream; -import static org.apache.ignite.internal.TestRecordingCommunicationSpi.spi; -import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RU_PREPARE_VERSION_FINALIZATION; -import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause; -import static org.apache.ignite.testframework.GridTestUtils.waitForCondition; - -/** */ -public class IgniteClusterSnapshotDeleteRollingUpgradeTest extends AbstractRollingUpgradeTest { - /** */ - private static final int ALL_GRIDS = 4; - - /** */ - private static final int CLIENTS = 1; - - /** */ - private static final String SNP_NAME = "testSnapshot"; - - /** {@inheritDoc} */ - @Override protected void afterTest() throws Exception { - super.afterTest(); - - cleanPersistenceDir(); - - // Clean all: also separated snapshot working directories. - try (DirectoryStream files = newDirectoryStream(Paths.get(U.defaultWorkDirectory()))) { - for (Path path : files) - U.delete(path); - } - } - - /** {@inheritDoc} */ - @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName, String ver) throws Exception { - var cfg = super.getConfiguration(igniteInstanceName, ver); - - cfg.setDataStorageConfiguration( - new DataStorageConfiguration() - .setDefaultDataRegionConfiguration( - new DataRegionConfiguration() - .setPersistenceEnabled(true) - .setMaxSize(DataStorageConfiguration.DFLT_DATA_REGION_INITIAL_SIZE) - ) - ); - - cfg.setWorkDirectory(new File(U.defaultWorkDirectory(), igniteInstanceName).getAbsolutePath()); - - return cfg; - } - - /** */ - @Test - public void testParallelRollingUpgradeInProgress() throws Exception { - for (int i = 0; i < ALL_GRIDS; i++) - startGrid(i, "2.19.0", i >= ALL_GRIDS - CLIENTS); - - grid(0).cluster().active(true); - - int testNodeIx = ALL_GRIDS - CLIENTS - 1; - - createCacheAndSnapshot(testNodeIx); - - ru(grid(testNodeIx)).enableVersionUpgrade(); - - for (int i = 0; i < ALL_GRIDS; i++) { - assertTrue(ru(grid(i)).isVersionUpgradeEnabled()); - - upgradeNodeVersion(i, "2.19.1"); - } - - spi(grid(testNodeIx)).blockMessages((node, msg) -> msg instanceof SingleNodeMessage snm && - snm.type() == RU_PREPARE_VERSION_FINALIZATION.ordinal()); - - var finalizeFut = GridTestUtils.runAsync(() -> ru(testNodeIx).finalizeClusterVersion()); - - assertTrue(spi(grid(testNodeIx)).waitForBlocked(1, getTestTimeout())); - - ensureSnapshotDeletionFailed(); - - spi(grid(testNodeIx)).stopBlock(); - - assertFalse(spi(grid(testNodeIx)).hasBlockedMessages()); - - finalizeFut.get(getTestTimeout()); - - for (int i = 0; i < ALL_GRIDS; i++) { - int i0 = i; - - assertTrue(waitForCondition(() -> !ru(grid(i0)).isVersionUpgradeEnabled(), getTestTimeout())); - } - - assertFalse(F.isEmpty(snp(1).deleteSnapshot(SNP_NAME, null).get(getTestTimeout()).completedNodes)); - } - - /** */ - @Test - public void testSnapshotDeleteFeature() throws Exception { - for (int i = 0; i < ALL_GRIDS; i++) - startGrid(i, "2.19.0", i >= ALL_GRIDS - CLIENTS); - - grid(0).cluster().active(true); - - createCacheAndSnapshot(1); - - ensureSnapshotDeletionFailed(); - - ru(grid(0)).enableVersionUpgrade(); - - for (int i = 0; i < ALL_GRIDS; i++) { - assertTrue(ru(grid(i)).isVersionUpgradeEnabled()); - - upgradeNodeVersion(i, "2.19.1"); - - ensureSnapshotDeletionFailed(); - } - - ru(grid(1)).finalizeClusterVersion(); - - for (int i = 0; i < ALL_GRIDS; i++) { - int i0 = i; - - assertTrue(waitForCondition(() -> !ru(grid(i0)).isVersionUpgradeEnabled(), getTestTimeout())); - } - - assertEquals(3, snp(1).deleteSnapshot(SNP_NAME, null).get().completedNodes().size()); - } - - /** */ - private void createCacheAndSnapshot(int gridIdx) { - int partsCnt = 5; - int keysCnt = partsCnt * 10; - - grid(gridIdx).createCache(new CacheConfiguration<>(DEFAULT_CACHE_NAME) - .setCacheMode(CacheMode.REPLICATED) - .setAffinity(new RendezvousAffinityFunction().setPartitions(partsCnt)) - .setAtomicityMode(CacheAtomicityMode.ATOMIC)); - - try (var ds = grid(gridIdx).dataStreamer(DEFAULT_CACHE_NAME)) { - for (int i = 0; i < keysCnt; i++) - ds.addData(i, i); - } - - createSnapshot(gridIdx); - } - - /** */ - private void createSnapshot(int gridIdx) { - snp(gridIdx).createSnapshot(SNP_NAME).get(getTestTimeout()); - } - - /** */ - private void ensureSnapshotDeletionFailed() { - for (int i = 0; i < ALL_GRIDS; i++) { - int i0 = i; - - assertThrowsAnyCause( - null, - () -> snp(i0).deleteSnapshot(SNP_NAME, null).get(), - IgniteIllegalStateException.class, - "The snapshot deletion feature isn't activated yet" - ); - } - } - - /** */ - private IgniteSnapshotManager snp(int gridIdx) { - return grid(gridIdx).context().cache().context().snapshotMgr(); - } -} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java deleted file mode 100644 index aa1e1763e2599..0000000000000 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java +++ /dev/null @@ -1,777 +0,0 @@ -/* - * 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.ignite.internal.processors.cache.persistence.snapshot; - -import java.io.File; -import java.io.RandomAccessFile; -import java.nio.file.Files; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Random; -import java.util.UUID; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.function.Supplier; -import org.apache.ignite.IgniteIllegalStateException; -import org.apache.ignite.configuration.IgniteConfiguration; -import org.apache.ignite.internal.IgniteEx; -import org.apache.ignite.internal.TestRecordingCommunicationSpi; -import org.apache.ignite.internal.processors.cache.persistence.file.FileIO; -import org.apache.ignite.internal.processors.cache.persistence.file.RandomAccessFileIOFactory; -import org.apache.ignite.internal.processors.cache.persistence.filename.SnapshotFileTree; -import org.apache.ignite.internal.util.CommonUtils; -import org.apache.ignite.internal.util.distributed.DistributedProcess; -import org.apache.ignite.internal.util.distributed.SingleNodeMessage; -import org.apache.ignite.internal.util.future.IgniteFutureImpl; -import org.apache.ignite.internal.util.typedef.F; -import org.apache.ignite.internal.util.typedef.G; -import org.apache.ignite.internal.util.typedef.internal.U; -import org.apache.ignite.lang.IgniteFuture; -import org.apache.ignite.plugin.AbstractTestPluginProvider; -import org.apache.ignite.plugin.PluginContext; -import org.jetbrains.annotations.Nullable; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.Parameterized; -import org.junit.runners.Parameterized.Parameter; - -import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.CHECK_SNAPSHOT_METAS; -import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.CHECK_SNAPSHOT_PARTS; -import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.DELETE_SNAPSHOT; -import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.END_SNAPSHOT; -import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_PRELOAD; -import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_PREPARE; -import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_ROLLBACK; -import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_CACHE_GROUP_SNAPSHOT_START; -import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RESTORE_INCREMENTAL_SNAPSHOT_START; -import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.START_SNAPSHOT; -import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause; -import static org.junit.Assume.assumeFalse; -import static org.junit.Assume.assumeTrue; - -/** */ -@RunWith(Parameterized.class) -public class IgniteClusterSnapshotDeleteTest extends AbstractSnapshotSelfTest { - /** */ - private boolean separatedWorkDir; - - /** */ - @Parameter(2) - public boolean incremental = true; - - /** */ - private @Nullable String cstIdSuffix; - - /** Parameters. */ - @Parameterized.Parameters(name = "encryption={0}, onlyPrimary={1}, incremental={2}") - public static Collection runParams() { - Collection res = new ArrayList<>(); - - for (boolean incremental : F.asList(false, true)) { - for (Object[] src0 : params()) { - Object[] res0 = new Object[src0.length + 1]; - System.arraycopy(src0, 0, res0, 0, src0.length); - - res0[src0.length] = incremental; - - res.add(res0); - } - } - - return res; - } - - /** {@inheritDoc} */ - @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { - var cfg = super.getConfiguration(igniteInstanceName); - - if (separatedWorkDir) - cfg.setWorkDirectory(new File(U.defaultWorkDirectory(), igniteInstanceName).getAbsolutePath()); - - if (cstIdSuffix != null) - cfg.setConsistentId(cfg.getConsistentId().toString() + '_' + cstIdSuffix); - - return cfg; - } - - /** {@inheritDoc} */ - @Override public void afterTestSnapshot() throws Exception { - super.afterTestSnapshot(); - - G.allGrids(); - - cleanPersistenceDir(); - } - - /** {@inheritDoc} */ - @Override public void beforeTestSnapshot() throws Exception { - super.beforeTestSnapshot(); - - /** Handy if test running is interrupted and {@link #afterTestSnapshot()} isn't invoked. */ - cleanPersistenceDir(); - } - - /** Tests snapshot deletion when one node finds snapshot but fails to delete its data. */ - @Test - public void testUncompletedNodes() throws Exception { - // Incremental snapshots don't support only-primary and encryption modes. - assumeTrue(!incremental || !(onlyPrimary || encryption)); - - separatedWorkDir = true; - - // Simulates a deletion error on some node. - pluginProvider = new AbstractTestPluginProvider() { - @Override public String name() { - return "TestSnpMgrProvider"; - } - - @Override public T createComponent(PluginContext ctx, Class cls) { - if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { - return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { - @Override public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean existsFlag) { - if (ctx.localNode().id().equals(grid(1).localNode().id())) { - existsFlag.set(true); - - return false; - } - - return super.deleteLocalSnapshot(sft, existsFlag); - } - }; - } - - return super.createComponent(ctx, cls); - } - }; - - startGridsWithCache(3, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); - - snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(getTestTimeout()); - - if (incremental) - addIncrementalSnapshot(null); - - var delSnpRes = snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); - - assertTrue(F.isEmpty(delSnpRes.emptyNodes)); - assertFalse(F.isEmpty(delSnpRes.uncompletedNodes)); - assertTrue(delSnpRes.uncompletedNodes.containsKey(grid(1).localNode().id())); - } - - /** */ - @Test - public void testDeleteOtherConsistentId() throws Exception { - startGridsWithSnapshot(3, CACHE_KEYS_RANGE, false); - - stopAllGrids(); - - cstIdSuffix = "_ext"; - - startGridsMultiThreaded(3); - - var delRes = snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); - - assertTrue(F.isEmpty(delRes.emptyNodes)); - - for (var ig : G.allGrids()) { - assertTrue(Files.list(((IgniteEx)ig).context().pdsFolderResolver().fileTree().snapshotsRoot().toPath()) - .findFirst().isEmpty()); - } - } - - /** */ - @Test - public void testDeleteSnapshotNoMetaSharedDirectory() throws Exception { - doTestDeleteNotSnapshot(false, false); - } - - /** */ - @Test - public void testDeleteSnapshotNoMetaDedicatedDirectories() throws Exception { - doTestDeleteNotSnapshot(true, false); - } - - /** */ - @Test - public void testDeleteSnapshotCorruptedMetaSharedDirectory() throws Exception { - doTestDeleteNotSnapshot(false, true); - } - - /** */ - @Test - public void testDeleteSnapshotCorruptedMetaDedicatedDirectories() throws Exception { - doTestDeleteNotSnapshot(true, true); - } - - /** */ - protected void doTestDeleteNotSnapshot(boolean separatedWorkDir, boolean corruptFile) throws Exception { - this.separatedWorkDir = separatedWorkDir; - - startGridsWithCache(3, CACHE_KEYS_RANGE, valueBuilder(), dfltCacheCfg); - - snp(grid(1)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(getTestTimeout()); - - var snpSft = new SnapshotFileTree(grid(1).context(), SNAPSHOT_NAME, null); - - // Ensure that all the snapshot node folders exist. - assertTrue(snpSft.binaryMeta().exists()); - assertTrue(new SnapshotFileTree(grid(0).context(), SNAPSHOT_NAME, null, folderName(0), consistentId(0)) - .binaryMeta().exists()); - assertTrue(new SnapshotFileTree(grid(2).context(), SNAPSHOT_NAME, null, folderName(2), consistentId(2)) - .binaryMeta().exists()); - - assertTrue(snpSft.meta().exists()); - - if (corruptFile) { - try (var rwf = new RandomAccessFile(snpSft.meta(), "rw")) { - byte[] slop = new byte[128]; - - new Random().nextBytes(slop); - - rwf.write(slop); - } - } - else { - assertTrue(U.delete(snpSft.meta())); - assertFalse(snpSft.meta().exists()); - } - - var delSnpRes = snp(grid(2)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); - - // Check the result. - if (separatedWorkDir) { - // One node doesn't find meta, decided not a snapshot. - assertTrue(F.isEmpty(delSnpRes.uncompletedNodes)); - assertEquals(2, delSnpRes.completedNodes.size()); - assertEquals(1, delSnpRes.emptyNodes.size()); - assertTrue(delSnpRes.emptyNodes.containsKey(grid(1).localNode().id())); - assertTrue(snpSft.binaryMeta().exists()); - } - else - assertEquals(3, delSnpRes.uncompletedNodes.size() + delSnpRes.completedNodes.size() + delSnpRes.emptyNodes.size()); - - assertFalse(new SnapshotFileTree(grid(0).context(), SNAPSHOT_NAME, null, folderName(0), consistentId(0)) - .binaryMeta().exists()); - assertFalse(new SnapshotFileTree(grid(2).context(), SNAPSHOT_NAME, null, folderName(2), consistentId(2)) - .binaryMeta().exists()); - } - - /** */ - private String consistentId(int gridIdx) { - return grid(gridIdx).configuration().getConsistentId().toString(); - } - - /** */ - private String folderName(int gridIdx) { - return grid(gridIdx).context().pdsFolderResolver().fileTree().folderName(); - } - - /** Test delete snapshot directly in Ignite. */ - @Test - public void testDeletionInIgnite() throws Exception { - /** No need to multiply this test. */ - assumeFalse(onlyPrimary || encryption || incremental); - - startGridsMultiThreaded(3); - - List dirsToTest = new ArrayList<>(30); - - var ignFileTree = grid(0).context().pdsFolderResolver().fileTree(); - - dirsToTest.add(CommonUtils.getIgniteHome()); - dirsToTest.add(ignFileTree.root().getAbsolutePath()); - - ignFileTree.allStorages().forEach(s -> { - for (var sub : s.listFiles()) { - if (!sub.isDirectory() || sub.compareTo(ignFileTree.snapshotsRoot()) == 0) - continue; - - dirsToTest.add(sub.getAbsolutePath()); - } - }); - - IgniteSnapshotManager snpMgr = snp(grid(0)); - var fileSep = File.separator; - var belongsToErrMsg = "belongs to a an Ignite's directory"; - - for (var dir : dirsToTest) { - List tests = new ArrayList<>(20); - - tests.add(dir + fileSep); - // Double path separator. - tests.add(dir.replaceAll("\\".equals(fileSep) ? "\\\\" : fileSep, "\\".equals(fileSep) ? "\\\\\\\\" : fileSep)); - tests.add(dir + fileSep + "unexisting"); - - for (var test : tests) { - if (log.isInfoEnabled()) - log.info("Testing path: " + test); - - assertThrowsAnyCause( - null, - () -> snpMgr.deleteSnapshot(SNAPSHOT_NAME, test).get(getTestTimeout()), - IllegalArgumentException.class, - belongsToErrMsg - ); - } - } - - var testDir = new File(fileSep + "unexisting_" + UUID.randomUUID()).getAbsolutePath(); - - if (log.isInfoEnabled()) - log.info("Testing path: " + testDir); - - var delRes = snpMgr.deleteSnapshot(SNAPSHOT_NAME, testDir).get(getTestTimeout()); - - assertEquals(3, delRes.emptyNodes.size()); - - var snpRoot = ignFileTree.snapshotsRoot(); - - delRes = snpMgr.deleteSnapshot(SNAPSHOT_NAME, snpRoot.getAbsolutePath()).get(getTestTimeout()); - - assertTrue(F.isEmpty(delRes.completedNodes)); - assertTrue(F.isEmpty(delRes.uncompletedNodes)); - assertEquals(3, delRes.emptyNodes.size()); - - delRes = snpMgr.deleteSnapshot(SNAPSHOT_NAME, new File(snpRoot, "unexisting").getAbsolutePath()).get(getTestTimeout()); - - assertTrue(F.isEmpty(delRes.completedNodes)); - assertTrue(F.isEmpty(delRes.uncompletedNodes)); - assertEquals(3, delRes.emptyNodes.size()); - } - - /** Tests snapshot deletion when one node has no snapshot data. */ - @Test - public void testEmptyNodes() throws Exception { - // Incremental snapshots don't support only-primary and encryption modes. - assumeTrue(!incremental || !(onlyPrimary || encryption)); - - separatedWorkDir = true; - - startGridsWithCache(2, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); - - snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(getTestTimeout()); - - if (incremental) - addIncrementalSnapshot(null); - - startGrid(G.allGrids().size()); - - var delSnpRes = snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); - - assertFalse(F.isEmpty(delSnpRes.emptyNodes)); - assertTrue(delSnpRes.emptyNodes.containsKey(grid(G.allGrids().size() - 1).localNode().id())); - assertTrue(F.isEmpty(delSnpRes.uncompletedNodes)); - } - - /** Tests snapshot deletion repeat after an offline node restarts. */ - @Test - public void testDeletionRepeatAfterOfflineNodeStarts() throws Exception { - // Incremental snapshots don't support only-primary and encryption modes. - assumeTrue(!incremental || !(onlyPrimary || encryption)); - - separatedWorkDir = true; - - startGridsWithCache(3, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); - - snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(getTestTimeout()); - - if (incremental) - addIncrementalSnapshot(null); - - int stoppedNodeIdx = G.allGrids().size() - 1; - - UUID stoppedNodeId = grid(stoppedNodeIdx).localNode().id(); - - stopGrid(stoppedNodeIdx); - - var delSnpRes = snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); - - assertEquals(2, delSnpRes.completedNodes.size()); - assertFalse(delSnpRes.completedNodes.containsKey(stoppedNodeId)); - - assertTrue(F.isEmpty(delSnpRes.uncompletedNodes)); - assertTrue(F.isEmpty(delSnpRes.emptyNodes)); - - startGrid(stoppedNodeIdx); - - stoppedNodeId = grid(stoppedNodeIdx).localNode().id(); - - delSnpRes = snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); - - assertEquals(1, delSnpRes.completedNodes.size()); - assertTrue(delSnpRes.completedNodes.containsKey(stoppedNodeId)); - - assertTrue(F.isEmpty(delSnpRes.uncompletedNodes)); - assertEquals(2, delSnpRes.emptyNodes.size()); - } - - /** Test snapshot deletion process when one node leaves. */ - @Test - public void testNodeStopsInTheMiddle() throws Exception { - // Incremental snapshots don't support only-primary and encryption modes. - assumeTrue(!incremental || !(onlyPrimary || encryption)); - - separatedWorkDir = true; - - CountDownLatch beginLatch = new CountDownLatch(1); - CountDownLatch proceedLatch = new CountDownLatch(1); - - // Simulates a deletion error on some node. - pluginProvider = new AbstractTestPluginProvider() { - @Override public String name() { - return "TestSnpMgrProvider"; - } - - @Override public T createComponent(PluginContext ctx, Class cls) { - if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { - return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { - @Override public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean existsFlag) { - if (ctx.localNode().id().equals(grid(1).localNode().id())) { - beginLatch.countDown(); - - try { - assertTrue(proceedLatch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); - } - catch (InterruptedException e) { - throw new RuntimeException("Interrupted.", e); - } - } - - return super.deleteLocalSnapshot(sft, existsFlag); - } - }; - } - - return super.createComponent(ctx, cls); - } - }; - - startGridsWithCache(3, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); - - snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(getTestTimeout()); - - if (incremental) - addIncrementalSnapshot(null); - - var delFut = snp(grid(2)).deleteSnapshot(SNAPSHOT_NAME, null); - - assertTrue(beginLatch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); - - UUID stoppedGridId = grid(1).localNode().id(); - - stopGrid(1); - - proceedLatch.countDown(); - - var delRes = delFut.get(getTestTimeout()); - - assertEquals(2, delRes.completedNodes.size()); - assertFalse(delRes.completedNodes.containsKey(stoppedGridId)); - - startGrid(1); - - delRes = snp(grid(2)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); - - assertEquals(1, delRes.completedNodes.size()); - assertTrue(delRes.completedNodes.containsKey(grid(1).localNode().id())); - } - - /** Tests that a concurrent deletion of a snapshot with the same name but different path is allowed. */ - @Test - public void testConcurrentDeleteOfTheSameSnapshotDifferentPath() throws Exception { - // Incremental snapshots don't support encryption and only-primary mode. - assumeTrue(!incremental || !(encryption || onlyPrimary)); - - startGridsWithCache(3, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); - - snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(TIMEOUT); - - if (incremental) - addIncrementalSnapshot(null); - - String snpPath = new File(grid(0).context().pdsFolderResolver().fileTree().snapshotsRoot(), "ex_snapshots").getAbsolutePath(); - - snp(grid(0)).createSnapshot(SNAPSHOT_NAME, snpPath, false, onlyPrimary).get(getTestTimeout()); - - if (incremental) - addIncrementalSnapshot(snpPath); - - TestRecordingCommunicationSpi commSpi1 = (TestRecordingCommunicationSpi)grid(1).configuration().getCommunicationSpi(); - - commSpi1.blockMessages((node, msg) -> - msg instanceof SingleNodeMessage msg0 && msg0.type() == DELETE_SNAPSHOT.ordinal()); - - var delFut0 = snp(grid(0)).deleteSnapshot(SNAPSHOT_NAME, null); - var delFut1 = snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, snpPath); - - commSpi1.waitForBlocked(2, getTestTimeout()); - - commSpi1.stopBlock(); - - var delRes0 = delFut0.get(getTestTimeout()); - var delRes1 = delFut1.get(getTestTimeout()); - - assertTrue(!delRes0.completedNodes().isEmpty() || !delRes0.uncompletedNodes().isEmpty()); - assertTrue(!delRes1.completedNodes().isEmpty() || !delRes1.uncompletedNodes().isEmpty()); - } - - /** Tests that a concurrent deletion of the same snapshot is declined. */ - @Test - public void testConcurrentDeleteOfTheSameSnapshot() throws Exception { - doTestConcurrentSnapshotDeleteOperation( - () -> startGridsWithSnapshot(3, CACHE_KEYS_RANGE, false), - () -> snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()), - e -> e.getMessage().contains("Deletion of the snapshot has already started"), - false - ); - } - - /** Tests that a snapshot deletion is declined when a snapshot check operation is in progress. */ - @Test - public void testSnapshotDeleteWhenCheckInProgress() throws Exception { - // Incremental snapshots don't support only-primary and encryption modes. - assumeTrue(!incremental || !(onlyPrimary || encryption)); - - doTestConcurrentSnapshotDelete( - () -> new IgniteFutureImpl<>(snp(grid(2)).checkSnapshot(SNAPSHOT_NAME, null, incremental ? 1 : 0)), - F.asList(CHECK_SNAPSHOT_METAS, CHECK_SNAPSHOT_PARTS), - true, - null, - "Snapshot with this name is being checked", - false - ); - } - - /** Tests that a snapshot deletion is declined when a snapshot create operation is in progress. */ - @Test - public void testSnapshotDeleteWhenCreateInProgress() throws Exception { - // Incremental snapshots don't support encryption and only-primary mode. - assumeTrue(!incremental || !(encryption || onlyPrimary)); - - doTestConcurrentSnapshotDelete( - () -> snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, incremental, onlyPrimary), - F.asList(START_SNAPSHOT, END_SNAPSHOT), - false, - () -> { - snp(grid(0)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); - - if (incremental) - snp(grid(0)).createSnapshot(SNAPSHOT_NAME).get(getTestTimeout()); - }, - "Snapshot with this name is being created", - false - ); - } - - /** Tests that a snapshot deletion is declined when a snapshot restore begins. */ - @Test - public void testSnapshotDeleteWhenRestoreBegins() throws Exception { - // Incremental snapshots don't support only-primary and encryption modes. - assumeTrue(!incremental || !(onlyPrimary || encryption)); - - doTestConcurrentSnapshotDelete( - () -> { - if (incremental) - return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null, 1); - else - return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null); - }, - F.asList(CHECK_SNAPSHOT_METAS, CHECK_SNAPSHOT_PARTS), - true, - () -> { - grid(0).destroyCache(DEFAULT_CACHE_NAME); - - awaitPartitionMapExchange(); - }, - "Snapshot with this name is being checked", - false - ); - } - - /** Tests that a snapshot deletion is declined when a snapshot restore is in progress. */ - @Test - public void testSnapshotDeleteWhenRestoreInProgress() throws Exception { - // Incremental snapshots don't support only-primary and encryption modes. - assumeTrue(!incremental || !(onlyPrimary || encryption)); - - var restoreMsgs = F.asList( - RESTORE_CACHE_GROUP_SNAPSHOT_PREPARE, - RESTORE_CACHE_GROUP_SNAPSHOT_PRELOAD, - RESTORE_CACHE_GROUP_SNAPSHOT_START - ); - - if (incremental) { - restoreMsgs = new ArrayList<>(restoreMsgs); - restoreMsgs.add(RESTORE_INCREMENTAL_SNAPSHOT_START); - } - - doTestConcurrentSnapshotDelete( - () -> { - if (incremental) - return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null, 1); - else - return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null); - }, - restoreMsgs, - true, - () -> { - grid(0).destroyCache(DEFAULT_CACHE_NAME); - - awaitPartitionMapExchange(); - }, - "Snapshot with this name is being restored", - false - ); - } - - /** Tests that a snapshot deletion is declined when a snapshot restore is in progress but fails. */ - @Test - public void testSnapshotDeleteWhenRestoreProgressFails() throws Exception { - // An in-the-middle failure won't allow to start restoring the incrementals. - assumeFalse(incremental); - - var restoreMsgs = F.asList(RESTORE_CACHE_GROUP_SNAPSHOT_ROLLBACK); - - if (incremental) { - restoreMsgs = new ArrayList<>(restoreMsgs); - restoreMsgs.add(RESTORE_INCREMENTAL_SNAPSHOT_START); - } - - doTestConcurrentSnapshotDelete( - () -> { - if (incremental) - return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null, 1); - else - return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null); - }, - restoreMsgs, - true, - () -> { - grid(0).destroyCache(DEFAULT_CACHE_NAME); - - awaitPartitionMapExchange(); - - SnapshotFileTree sft = snapshotFileTree(grid(1), SNAPSHOT_NAME); - - String failingFilePath = sft.partitionFile(dfltCacheCfg, primaries[0]).getAbsolutePath() - .replace(sft.nodeStorage().getAbsolutePath(), ""); - - grid(1).context().cache().context().snapshotMgr().ioFactory((file, modes) -> { - FileIO delegate = new RandomAccessFileIOFactory().create(file, modes); - - if (file.getPath().endsWith(failingFilePath)) - throw new RuntimeException("Test exception"); - - return delegate; - }); - }, - "Snapshot with this name is being restored", - true - ); - } - - /** - * @param firstOp First cluster-wide snapshot operation. - * @param msgsToWatch {@link SingleNodeMessage#type()} relating to {@code firstOp} to block on one node. - * @param precreateSnp If {@code true}, creates snapshot after the cluster start. - * @param prepareIteration If not {@code null}, is invoked in the beginning of test iteration at each {@code msgsToWatch}. - * @param concurrentMsgErr Test of failed concurrent to {@code firstOp} delete snapshot operation to watch. - * @param ignoreFirstOpFailure If {@code true}, possible failure of {@code firstOp} is ignored. - */ - protected void doTestConcurrentSnapshotDelete( - Supplier> firstOp, - Collection msgsToWatch, - boolean precreateSnp, - @Nullable Runnable prepareIteration, - String concurrentMsgErr, - boolean ignoreFirstOpFailure - ) throws Exception { - startGridsWithCache(3, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); - - if (precreateSnp) { - snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(TIMEOUT); - - if (incremental) - addIncrementalSnapshot(null); - } - - TestRecordingCommunicationSpi commSpi1 = (TestRecordingCommunicationSpi)grid(1).configuration().getCommunicationSpi(); - - for (var nodeResMsgType : msgsToWatch) { - if (log.isInfoEnabled()) - log.info("Iteration with message-to-wait-for type: " + nodeResMsgType); - - if (prepareIteration != null) - prepareIteration.run(); - - commSpi1.blockMessages((node, msg) -> - msg instanceof SingleNodeMessage msg0 && msg0.type() == nodeResMsgType.ordinal()); - - var firstFut = firstOp.get(); - - commSpi1.waitForBlocked(1, getTestTimeout()); - - assertThrowsAnyCause( - null, - () -> { - snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); - - return null; - }, - IgniteIllegalStateException.class, - concurrentMsgErr - ); - - commSpi1.stopBlock(); - - if (ignoreFirstOpFailure) { - try { - firstFut.get(getTestTimeout()); - } - catch (Exception e) { - if (log.isDebugEnabled()) - log.debug("The first operation failed but a failure is expected. Failure: " + e.getMessage()); - } - } - else - firstFut.get(getTestTimeout()); - } - } - - /** */ - private void addIncrementalSnapshot(@Nullable String path) { - try (var ds = grid(0).dataStreamer(DEFAULT_CACHE_NAME)) { - for (int i = CACHE_KEYS_RANGE; i < CACHE_KEYS_RANGE + CACHE_KEYS_RANGE / 4; i++) - ds.addData(i, i); - } - - snp(grid(0)).createSnapshot(SNAPSHOT_NAME, path, true, onlyPrimary).get(getTestTimeout()); - } - - /** {@inheritDoc} */ - @Override protected void awaitPartitionMapExchange() { - try { - super.awaitPartitionMapExchange(); - } - catch (InterruptedException e) { - throw new RuntimeException("Interrupted.", e); - } - } -} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotRestoreSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotRestoreSelfTest.java index 84dd1f57c05d8..78ec729ec931b 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotRestoreSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotRestoreSelfTest.java @@ -333,45 +333,6 @@ public void testCreateSnapshotDuringRestore() throws Exception { assertCacheKeys(ignite.cache(DEFAULT_CACHE_NAME), CACHE_KEYS_RANGE); } - /** Tests that snapshot restore is declined when the same snapshot is being deleted. */ - @Test - public void testConcurrentSnapshotDeleteAndRestoreOperations() throws Exception { - doTestConcurrentSnapshotDeleteOperation( - () -> startGridsWithSnapshot(3, CACHE_KEYS_RANGE), - () -> snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, null).get(), - e -> e.getMessage().contains("Snapshot '%s' is being deleted".formatted(SNAPSHOT_NAME)), - true - ); - } - - /** */ - @Test - public void testConcurrentSnapshotDeleteAndRestoreOperationsWithDifferentPath() throws Exception { - String snpPath = new File(U.defaultWorkDirectory(), "ex_snapshots").getAbsolutePath(); - - doTestConcurrentSnapshotDeleteOperation( - () -> { - startGridsWithSnapshot(3, CACHE_KEYS_RANGE); - - grid(0).createCache(DEFAULT_CACHE_NAME); - - try (var ds = grid(0).dataStreamer(DEFAULT_CACHE_NAME)) { - for (int i = 0; i < CACHE_KEYS_RANGE; ++i) - ds.addData(i, i); - } - - snp(grid(0)).createSnapshot(SNAPSHOT_NAME, snpPath, false, false).get(getTestTimeout()); - - grid(0).destroyCache(DEFAULT_CACHE_NAME); - - awaitPartitionMapExchange(); - }, - () -> snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME, snpPath, null).get(), - null, - false - ); - } - /** * Ensures that the cache doesn't start if one of the baseline nodes fails. * diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotSelfTest.java index 31bd938b79f43..570761dac227f 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotSelfTest.java @@ -603,7 +603,7 @@ public void testSnapshotExistsException() throws Exception { assertThrowsAnyCause(log, () -> snp(ignite).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(), IgniteException.class, - "Snapshot with given name already exists on local node"); + "Snapshot with given name already exists on local node."); stopAllGrids(); @@ -615,43 +615,6 @@ public void testSnapshotExistsException() throws Exception { waitForEvents(EVT_CLUSTER_SNAPSHOT_STARTED, EVT_CLUSTER_SNAPSHOT_FAILED); } - /** - * Tests that snapshot create detects concurrent deletion, or detects still existing snapshot or successfully - * proceeds if snapshot already deleted. - */ - @Test - public void testConcurrentSnapshotDeleteOperation() throws Exception { - doTestConcurrentSnapshotDeleteOperation( - () -> { - startGridsWithCache(3, dfltCacheCfg, CACHE_KEYS_RANGE); - - snp(grid(2)).createSnapshot(SNAPSHOT_NAME).get(); - }, - () -> snp(grid(2)).createSnapshot(SNAPSHOT_NAME).get(), - e -> e.getMessage().contains("Snapshot with given name already exists"), - false - ); - } - - /** - * Tests that a concurrent deletion of a same-named snapshot is allowed if it has a different path. - */ - @Test - public void testConcurrentSnapshotDeleteOperationWithDifferentPath() throws Exception { - String snpPath = new File(U.defaultWorkDirectory(), "ex_snapshots").getAbsolutePath(); - - doTestConcurrentSnapshotDeleteOperation( - () -> { - startGridsWithCache(3, dfltCacheCfg, CACHE_KEYS_RANGE); - - snp(grid(2)).createSnapshot(SNAPSHOT_NAME).get(); - }, - () -> snp(grid(2)).createSnapshot(SNAPSHOT_NAME, snpPath, false, false).get(), - null, - false - ); - } - /** @throws Exception If fails. */ @Test public void testClusterSnapshotCleanedOnLeft() throws Exception { diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_1.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_1.java index c6108f4a28fb8..571e5f3da734b 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_1.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_1.java @@ -23,5 +23,5 @@ public class TestIgniteReleaseFeatures_2_19_1 { public static final IgniteFeature ROLLING_UPGRADE_FEATURE = TestIgniteReleaseFeatures_2_19_0.ROLLING_UPGRADE_FEATURE; /** */ - public static final IgniteFeature SNAPSHOT_DELETE_FEATURE = SupportedFeatureRegistry.SNAPSHOT_DELETE_FEATURE; + public static final IgniteFeature SNAPSHOT_LIST_FEATURE = SupportedFeatureRegistry.SNAPSHOT_LIST_FEATURE; } diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java index 857e317599640..5673ed2b71af3 100644 --- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java +++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java @@ -21,8 +21,6 @@ import java.util.Collection; import java.util.List; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotCheckTest; -import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotDeleteRollingUpgradeTest; -import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotDeleteTest; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotHandlerTest; import org.apache.ignite.testframework.GridTestUtils; import org.apache.ignite.testframework.junits.DynamicSuite; @@ -50,8 +48,6 @@ public static List> suite(Collection ignoredTests) { GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotCheckTest.class, ignoredTests); GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotHandlerTest.class, ignoredTests); - GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotDeleteRollingUpgradeTest.class, ignoredTests); - GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotDeleteTest.class, ignoredTests); return suite; } diff --git a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output index e0ddc44cf24d7..dd6ff4e43caf5 100644 --- a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output +++ b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output @@ -269,13 +269,6 @@ This utility can do the following commands: Get the status of the current snapshot operation: control.(sh|bat) --snapshot status - Deletes snapshot and all its incrementals from all the online server nodes: - control.(sh|bat) --snapshot delete snapshot_name [--src path] - - Parameters: - snapshot_name - Snapshot name. - --src path - Path to snapshot location directory. If not specified or specified a relative path, the default snapshot configuration directory will be used. - Change cluster tag to new value: control.(sh|bat) --change-tag newTagValue [--yes] diff --git a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output index 6ddf5d9106536..76f0f8c7e0e37 100644 --- a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output +++ b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output @@ -269,13 +269,6 @@ This utility can do the following commands: Get the status of the current snapshot operation: control.(sh|bat) --snapshot status - Deletes snapshot and all its incrementals from all the online server nodes: - control.(sh|bat) --snapshot delete snapshot_name [--src path] - - Parameters: - snapshot_name - Snapshot name. - --src path - Path to snapshot location directory. If not specified or specified a relative path, the default snapshot configuration directory will be used. - Change cluster tag to new value: control.(sh|bat) --change-tag newTagValue [--yes] From 399393c49b4b30b9c347b9ee4a28fa5c2e93f1c7 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Wed, 23 Sep 2026 18:36:23 +0300 Subject: [PATCH 57/97] raw --- .../snapshot/AbstractSnapshotSelfTest.java | 29 ------------------- 1 file changed, 29 deletions(-) diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java index 06e0e90093bb1..50096e5a7c8f4 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java @@ -98,7 +98,6 @@ import org.apache.ignite.lang.IgniteFutureCancelledException; import org.apache.ignite.lang.IgniteFutureTimeoutException; import org.apache.ignite.lang.IgnitePredicate; -import org.apache.ignite.plugin.AbstractTestPluginProvider; import org.apache.ignite.spi.discovery.DiscoverySpiCustomMessage; import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi; import org.apache.ignite.spi.discovery.tcp.TestTcpDiscoverySpi; @@ -174,12 +173,6 @@ protected Function valueBuilder() { return valBuilder; } - /** */ - protected @Nullable AbstractTestPluginProvider pluginProvider; - - /** */ - protected boolean fullCleanPersistentDir = true; - /** Enable encryption of all caches in {@code IgniteConfiguration} before start. */ @Parameterized.Parameter public boolean encryption; @@ -224,9 +217,6 @@ protected static Collection encryptionParameters() { if (cfg.isClientMode()) return cfg; - if (pluginProvider != null) - cfg.setPluginProviders(pluginProvider); - return cfg.setConsistentId(igniteInstanceName) .setDataStorageConfiguration(new DataStorageConfiguration() .setDefaultDataRegionConfiguration(new DataRegionConfiguration() @@ -293,25 +283,6 @@ public void afterTestSnapshot() throws Exception { cleanPersistenceDir(); } - /** {@inheritDoc} */ - @Override protected void cleanPersistenceDir() throws Exception { - super.cleanPersistenceDir(); - - if (!fullCleanPersistentDir()) - return; - - // Clean all: also separated snapshot working directories and custom snapshot paths. - try (DirectoryStream files = newDirectoryStream(Paths.get(U.defaultWorkDirectory()))) { - for (Path path : files) - U.delete(path); - } - } - - /** */ - protected boolean fullCleanPersistentDir() { - return fullCleanPersistentDir; - } - /** * @param evts Events to check. * @throws IgniteInterruptedCheckedException If interrupted. From 9d830c398591bbf90252d8b911730718dcf939cc Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Thu, 24 Sep 2026 15:01:02 +0300 Subject: [PATCH 58/97] in progress --- docs/_docs/snapshots/snapshots.adoc | 20 ++----------------- .../query/calcite/hint/HintUtils.java | 2 +- .../query/calcite/prepare/PlannerHelper.java | 2 +- .../IgniteControlUtilityTestSuite.java | 2 ++ .../snapshot/AbstractSnapshotSelfTest.java | 2 +- .../snapshot/EncryptedSnapshotTest.java | 2 -- .../IgniteClusterSnapshotCheckTest.java | 16 +++++++-------- .../IgniteSnapshotRestoreFromRemoteTest.java | 5 ----- 8 files changed, 15 insertions(+), 36 deletions(-) diff --git a/docs/_docs/snapshots/snapshots.adoc b/docs/_docs/snapshots/snapshots.adoc index 1c04ab5c16a82..27e1f5964ed7b 100644 --- a/docs/_docs/snapshots/snapshots.adoc +++ b/docs/_docs/snapshots/snapshots.adoc @@ -314,29 +314,13 @@ control.bat --snapshot list --src /tmp/ignite/snapshots ---- -- -=== Output format - -The command output displays per-node information: - -[source,text] ----- -Node consistent-id [node-uuid]: - snapshot_name: - snapshot_name: CREATING (DELETING) ----- - -Where: - -* `` - human-readable size of the snapshot on disk (e.g., `1.5 GB`). -* `CREATING` - the snapshot is in the process of being created on this node. The size is not calculated. - === List operation notes * The list operation is read-only and does not modify any snapshot data. * If a snapshot is in a transient state (being created or deleted), its size is not reported. * Snapshot is being detected by its metadata. If metadata is missing or cannot be read, snapshot directory is ignored. -* When snapshot listing and size calculating, no additional snapshot validation is done. Files, its' headers aren't read, -no checksums are calculated, etc. +* When snapshot listing and size calculating, no additional snapshot validation is done. Files aren't read, no checksums +are calculated. == Getting Snapshot Operation Status diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/hint/HintUtils.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/hint/HintUtils.java index 5597b4c776a1b..40c267ded097a 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/hint/HintUtils.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/hint/HintUtils.java @@ -99,7 +99,7 @@ public static List nonInheritedRelHints(RelNode rel) { /** * @return Distinct hints within {@code hints} filtered with {@code hintDefs}, {@link HintOptionsChecker} and - * removed inherit paths. + * removed inherit pathes. * @see HintOptionsChecker * @see RelHint#inheritPath */ diff --git a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerHelper.java b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerHelper.java index b5b094cda316c..d88275fb786e4 100644 --- a/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerHelper.java +++ b/modules/calcite/src/main/java/org/apache/ignite/internal/processors/query/calcite/prepare/PlannerHelper.java @@ -134,7 +134,7 @@ public static IgniteRel optimize(SqlNode sqlNode, IgnitePlanner planner, IgniteL rel = planner.trimUnusedFields(root.withRel(rel)).rel; // The following pushed down project can erase top-level hints. We store them to reassign hints for join nodes. - // Clear the inherit paths to consider the hints as not propogated ones. + // Clear the inherit pathes to consider the hints as not propogated ones. List topHints = HintUtils.allRelHints(rel).stream().map(h -> h.inheritPath.isEmpty() ? h : h.copy(Collections.emptyList())).collect(Collectors.toList()); diff --git a/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite.java b/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite.java index 6528d919f6105..9d1fea3a68feb 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite.java @@ -34,6 +34,7 @@ import org.apache.ignite.util.GridCommandHandlerIndexingTest; import org.apache.ignite.util.GridCommandHandlerIndexingWithSSLTest; import org.apache.ignite.util.GridCommandHandlerLegacyClientTest; +import org.apache.ignite.util.GridCommandHandlerListSnapshotTest; import org.apache.ignite.util.GridCommandHandlerManagementPoolTest; import org.apache.ignite.util.GridCommandHandlerMetadataTest; import org.apache.ignite.util.GridCommandHandlerSslTest; @@ -74,6 +75,7 @@ GridCommandHandlerCheckIndexesInlineSizeTest.class, GridCommandHandlerMetadataTest.class, GridCommandHandlerCheckIncrementalSnapshotTest.class, + GridCommandHandlerListSnapshotTest.class, GridCommandHandlerLegacyClientTest.class, KillCommandsControlShTest.class, diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java index 50096e5a7c8f4..8857a4ddd656e 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java @@ -182,7 +182,7 @@ protected Function valueBuilder() { public boolean onlyPrimary; /** Parameters. */ - @Parameterized.Parameters(name = "encryption={0}, onlyPrimary={1}") + @Parameterized.Parameters(name = "encryption={0}, onlyPrimay={1}") public static Collection params() { List res = new ArrayList<>(); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/EncryptedSnapshotTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/EncryptedSnapshotTest.java index 28b99c5d537c3..f6ae22c2db4ca 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/EncryptedSnapshotTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/EncryptedSnapshotTest.java @@ -232,8 +232,6 @@ public void testValidatingSnapshotFailsWithNoEncryption() throws Exception { assertTrue(snpDir.renameTo(tmpSnpDir)); - fullCleanPersistentDir = false; - cleanPersistenceDir(); assertTrue(tmpSnpDir.renameTo(snpDir)); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java index 3564dd3bdbf26..10565adf43f64 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java @@ -1410,8 +1410,8 @@ private void prepareGridsAndSnapshot(int servers, int baseLineCnt, int clients, /** * Tests concurrent snapshot operations related to the snapshot checking. * - * @param firstOp First snapshot operation on an originator node. - * @param secondOp Second concurrent snapshot operation on a trier node. + * @param originatorOp First snapshot operation on an originator node. + * @param trierOp Second concurrent snapshot operation on a trier node. * @param firstDelay First distributed process full message of {@code originatorOp} to delay on the coordinator * to launch {@code trierOp}. * @param secondDelay Second distributed process full message of {@code originatorOp} to delay on the coordinator @@ -1423,8 +1423,8 @@ private void prepareGridsAndSnapshot(int servers, int baseLineCnt, int clients, * @param cleaner If not {@code null}, is executed at the end. */ private void doTestConcurrentSnpCheckOperations( - Supplier> firstOp, - Supplier> secondOp, + Supplier> originatorOp, + Supplier> trierOp, DistributedProcess.DistributedProcessType firstDelay, @Nullable DistributedProcess.DistributedProcessType secondDelay, boolean expectFailure, @@ -1441,17 +1441,17 @@ private void doTestConcurrentSnpCheckOperations( && ((FullMessage)msg).type() == firstDelay.ordinal() && (waitForBothFirstDelays || firstDelayed.compareAndSet(false, true))); - IgniteFuture fut = firstOp.get(); + IgniteFuture fut = originatorOp.get(); discoSpi(grid(0)).waitBlocked(getTestTimeout()); - IgniteFuture fut2 = secondOp.get(); + IgniteFuture fut2 = trierOp.get(); if (expectFailure) { assertThrowsAnyCause( log, fut2::get, - IgniteIllegalStateException.class, + IllegalStateException.class, "Validation of snapshot '" + SNAPSHOT_NAME + "' has already started" ); @@ -1474,7 +1474,7 @@ private void doTestConcurrentSnpCheckOperations( assertThrowsAnyCause( log, fut2::get, - IgniteIllegalStateException.class, + IllegalStateException.class, "Validation of snapshot '" + SNAPSHOT_NAME + "' has already started" ); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotRestoreFromRemoteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotRestoreFromRemoteTest.java index d21c24b290f34..a31cd9a71376c 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotRestoreFromRemoteTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotRestoreFromRemoteTest.java @@ -127,11 +127,6 @@ public class IgniteSnapshotRestoreFromRemoteTest extends IgniteClusterSnapshotRe return cfg; } - /** {@inheritDoc} */ - @Override protected boolean fullCleanPersistentDir() { - return false; - } - /** @throws Exception If fails. */ @Before public void prepareDedicatedSnapshot() throws Exception { From 758d6205d2584289dd293ac64cd0f617f0f34e33 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Thu, 24 Sep 2026 17:16:13 +0300 Subject: [PATCH 59/97] in-progress --- .../GridCommandHandlerListSnapshotTest.java | 48 +-- .../ignite/internal/CoreMessagesProvider.java | 4 - .../snapshot/SnapshotListCommand.java | 65 +--- .../snapshot/SnapshotListCommandArg.java | 11 +- .../management/snapshot/SnapshotListTask.java | 140 ++++++++- .../snapshot/IgniteSnapshotManager.java | 16 - .../snapshot/SnapshotListJobResult.java | 101 +++++++ .../snapshot/SnapshotListProcess.java | 277 ------------------ .../snapshot/SnapshotListProcessResult.java | 74 ----- .../snapshot/SnapshotListRequest.java | 70 ----- .../snapshot/SnapshotListResponse.java | 59 ---- .../snapshot/SnapshotListTaskResult.java | 32 ++ 12 files changed, 297 insertions(+), 600 deletions(-) create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java delete mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListProcess.java delete mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListProcessResult.java delete mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListRequest.java delete mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListResponse.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListTaskResult.java diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java index 752e7ce7f5b01..88affb35cb8ed 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java @@ -118,33 +118,37 @@ public void testSnapshotList() throws Exception { : null; // Test listing when no snapshots exist. - injectTestSystemOut(); - - if (customPath) - assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "list", "--src", - cstSnpsRoot.getAbsolutePath())); - else - assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "list")); - - String out = testOut.toString(); - - assertTrue("Expected 'No snapshots found' message, got: " + out, - out.contains(SnapshotListCommand.NO_SNAPSHOTS_PREF)); - - testOut.reset(); + // injectTestSystemOut(); + +// if (customPath) +// assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "list", "--src", +// cstSnpsRoot.getAbsolutePath())); +// else +// assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "list")); +// +// String out = testOut.toString(); +// +// assertTrue("Expected 'No snapshots found' message, got: " + out, +// out.contains(SnapshotListCommand.NO_SNAPSHOTS_PREF)); +// +// testOut.reset(); // Create a snapshot. snp(ig).createSnapshot("testSnapshot", customPath ? cstSnpsRoot.getAbsolutePath() : null, false, false) .get(getTestTimeout()); - // Add some data and create an incremental snapshot. - try (IgniteDataStreamer streamer = ig.dataStreamer(DEFAULT_CACHE_NAME)) { - for (int i = entriesCnt; i < entriesCnt + 50; ++i) - streamer.addData(i, i); - } + // TODO: add incremental - snp(ig).createSnapshot("testSnapshot", customPath ? cstSnpsRoot.getAbsolutePath() : null, true, false) - .get(getTestTimeout()); +// // Add some data and create an incremental snapshot. +// try (IgniteDataStreamer streamer = ig.dataStreamer(DEFAULT_CACHE_NAME)) { +// for (int i = entriesCnt; i < entriesCnt + 50; ++i) +// streamer.addData(i, i); +// } + +// snp(ig).createSnapshot("testSnapshot", customPath ? cstSnpsRoot.getAbsolutePath() : null, true, false) +// .get(getTestTimeout()); + + injectTestSystemOut(); // Now list snapshots - should find "testSnapshot" on all server nodes. if (customPath) @@ -153,7 +157,7 @@ public void testSnapshotList() throws Exception { else assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "list")); - out = testOut.toString(); + var out = testOut.toString(); assertFalse("Expected snapshot to be listed, got: " + out, out.contains(SnapshotListCommand.NO_SNAPSHOTS_PREF)); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/CoreMessagesProvider.java b/modules/core/src/main/java/org/apache/ignite/internal/CoreMessagesProvider.java index 12e6c214bf488..f7537f0b20f79 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/CoreMessagesProvider.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/CoreMessagesProvider.java @@ -165,8 +165,6 @@ import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotFilesFailureMessage; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotFilesRequestMessage; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotHandlerResult; -import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListRequest; -import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListResponse; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotMetadata; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotMetadataResponse; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotOperationEndRequest; @@ -439,8 +437,6 @@ public CoreMessagesProvider() { register(SnapshotFilesFailureMessage.class); register(IncrementalSnapshotVerifyResult.class); register(IncrementalSnapshotAwareMessage.class); - register(SnapshotListRequest.class); - register(SnapshotListResponse.class); // [6300 - 6400]: Services messages. Most of them originally come from Discovery. msgIdx = 6300; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java index 82f1211bc2e4a..9b8f639756552 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java @@ -17,15 +17,12 @@ package org.apache.ignite.internal.management.snapshot; -import java.util.Map; -import java.util.UUID; import java.util.function.Consumer; -import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListProcessResult; -import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListTaskResult; import org.apache.ignite.internal.util.typedef.internal.U; /** Snapshot list command. */ -public class SnapshotListCommand extends AbstractSnapshotCommand { +public class SnapshotListCommand extends AbstractSnapshotCommand { /** */ public static final String DESC = "Lists all snapshots on all online server nodes with their sizes"; @@ -35,12 +32,6 @@ public class SnapshotListCommand extends AbstractSnapshotCommand printer) { - boolean anyFound = false; - - for (Map.Entry entry : res.nodeConsistentIds().entrySet()) { - UUID nodeId = entry.getKey(); - String consId = entry.getValue(); - - Map sizes = res.nodeSnapshotSizes().get(nodeId); - Map statuses = res.nodeSnapshotStatuses().get(nodeId); - - boolean hasSnapshots = (!F.isEmpty(sizes)) || (!F.isEmpty(statuses)); - - if (hasSnapshots) - anyFound = true; - } - - if (!anyFound) { - printer.accept(NO_SNAPSHOTS_PREF); - - return; - } - - for (Map.Entry entry : res.nodeConsistentIds().entrySet()) { - UUID nodeId = entry.getKey(); - String consId = entry.getValue(); - - Map sizes = res.nodeSnapshotSizes().get(nodeId); - Map statuses = res.nodeSnapshotStatuses().get(nodeId); - - boolean hasSnapshots = (!F.isEmpty(sizes)) || (!F.isEmpty(statuses)); - - if (!hasSnapshots) - continue; - - printer.accept(NODE_PREF + consId + " [" + nodeId + "]:"); - - if (sizes != null) { - for (Map.Entry snp : sizes.entrySet()) { - printer.accept(SNP_PREF + snp.getKey() + SIZE_SUFFIX + U.humanReadableByteCount(snp.getValue())); - } - } - - if (statuses != null) { - for (Map.Entry snp : statuses.entrySet()) { - printer.accept(SNP_PREF + snp.getKey() + SIZE_SUFFIX + snp.getValue()); - } - } - - printer.accept(U.nl()); - } + @Override public void printResult(SnapshotListCommandArg arg, SnapshotListTaskResult res, Consumer printer) { + printer.accept(U.nl()); } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommandArg.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommandArg.java index 3fd420661abcf..4967052f7351f 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommandArg.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommandArg.java @@ -20,6 +20,7 @@ import org.apache.ignite.internal.Order; import org.apache.ignite.internal.dto.IgniteDataTransferObject; import org.apache.ignite.internal.management.api.Argument; +import org.jetbrains.annotations.Nullable; /** */ public class SnapshotListCommandArg extends IgniteDataTransferObject { @@ -28,17 +29,17 @@ public class SnapshotListCommandArg extends IgniteDataTransferObject { /** */ @Order(0) - @Argument(example = "path", optional = true, description = "Path to snapshot location directory. If not specified " + - "or specified a relative path, the default snapshot configuration directory will be used") - String src; + @Argument(example = "path/to/snapshots", optional = true, description = "Path to snapshot location directory. " + + "If not specified or specified a relative path, the default snapshot configuration directory will be used") + @Nullable String src; /** */ - public String src() { + public @Nullable String src() { return src; } /** */ - public void src(String src) { + public void src(@Nullable String src) { this.src = src; } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java index 1772201df5e9b..b073719063789 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java @@ -17,29 +17,86 @@ package org.apache.ignite.internal.management.snapshot; +import java.io.File; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileTime; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.atomic.AtomicReference; +import org.apache.ignite.IgniteException; +import org.apache.ignite.compute.ComputeJobResult; +import org.apache.ignite.internal.processors.cache.persistence.filename.SnapshotFileTree; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager; -import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListProcess; -import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListProcessResult; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListJobResult; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListTaskResult; import org.apache.ignite.internal.processors.task.GridInternal; +import org.apache.ignite.internal.util.typedef.T2; import org.apache.ignite.internal.visor.VisorJob; -import org.apache.ignite.internal.visor.VisorOneNodeTask; +import org.apache.ignite.internal.visor.VisorMultiNodeTask; +import org.jetbrains.annotations.Nullable; -/** - * @see IgniteSnapshotManager#listSnapshots(String) - * @see SnapshotListProcess - */ +/** */ @GridInternal -public class SnapshotListTask extends VisorOneNodeTask { +public class SnapshotListTask extends VisorMultiNodeTask { /** Serial version uid. */ private static final long serialVersionUID = 0L; /** {@inheritDoc} */ - @Override protected VisorJob job(SnapshotListCommandArg arg) { + @Override protected VisorJob job(SnapshotListCommandArg arg) { return new SnapshotListJob(arg, debug); } + /** {@inheritDoc} */ + @Override protected SnapshotListTaskResult reduce0(List list) throws IgniteException { + return null; + } + + /** */ + public static T2 calculateDirectorySizeVisitor(File dirPath, AtomicInteger skippedCnt) throws IOException { + AtomicLong totalSize = new AtomicLong(0); + + AtomicReference createTime = new AtomicReference<>(); + + Files.walkFileTree(dirPath.toPath(), new SimpleFileVisitor<>() { + @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { + createTime.compareAndSet(null, attrs.creationTime()); + + return super.preVisitDirectory(dir, attrs); + } + + @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + try { + // Use attrs instead of Files.size() for efficiency. + if (attrs.isRegularFile()) + totalSize.addAndGet(attrs.size()); + } + catch (Exception e) { + // File may have been deleted between walk start and here. + skippedCnt.incrementAndGet(); + } + + return FileVisitResult.CONTINUE; + } + + @Override public FileVisitResult visitFileFailed(Path file, IOException exc) { + // File/directory became inaccessible (permission denied, deleted, etc.) + skippedCnt.incrementAndGet(); + + return FileVisitResult.CONTINUE; + } + }); + + return new T2<>(totalSize.get(), createTime.get()); + } + /** */ - private static class SnapshotListJob extends SnapshotJob { + private static class SnapshotListJob extends SnapshotJob { /** Serial version uid. */ private static final long serialVersionUID = 0L; @@ -52,10 +109,69 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { } /** {@inheritDoc} */ - @Override protected SnapshotListProcessResult run(SnapshotListCommandArg arg) { + @Override protected SnapshotListJobResult run(SnapshotListCommandArg arg) { IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); - return snpMgr.listSnapshots(arg.src()).get(); + SnapshotListJobResult jobRes = new SnapshotListJobResult(); + + try { + File resolvedPath = resolveSnapshotsPath(arg.src()); + + // TODO: support being deleted too. + String ignoreName = detectIgnoredName(resolvedPath); + + AtomicInteger skippedCnt = new AtomicInteger(); + + for (String snpName : snpMgr.localSnapshotNames(resolvedPath.getAbsolutePath())) { + if(snpName.equals(ignoreName)) { + jobRes.acceptCreating(snpName); + + continue; + } + + File snpPath = new File(resolvedPath, snpName); + + skippedCnt.set(0); + + var snpRes = calculateDirectorySizeVisitor(snpPath, skippedCnt); + + jobRes.accept(snpName, snpRes.get1(), snpRes.get2()); + } + } catch (Exception e) { + throw new IgniteException("Failed to list local snapshots [src=" + arg.src() + ']', e); + } + + return jobRes; + } + + /** */ + private @Nullable String detectIgnoredName(File curPath) { + var createRq = ignite.context().cache().context().snapshotMgr().currentCreateRequest(); + + if (createRq != null) { + File createRoot = new SnapshotFileTree(ignite.context(), createRq.snapshotName(), createRq.snapshotPath()).root(); + + if (createRoot.equals(curPath)) + return createRq.snapshotName(); + } + + return null; + } + + /** TODO: move to a sahred with the deletion process place. */ + private File resolveSnapshotsPath(@Nullable String src) throws IOException { + File res = ignite.context().pdsFolderResolver().fileTree().snapshotsRoot(); + + if (src != null) { + var srcF = new File(src); + + if (srcF.isAbsolute()) + res = srcF; + else + res = new File(res, src); + } + + return res.getCanonicalFile(); } } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java index 5568a1ab0071d..c7127333867cd 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java @@ -344,9 +344,6 @@ public class IgniteSnapshotManager extends GridCacheSharedManagerAdapter /** Snapshot validation distributed process. */ private final SnapshotCheckProcess checkSnpProc; - /** Distributed process to list cluster snapshots. */ - private final SnapshotListProcess listSnpProc; - /** Check previously performed snapshot operation and delete uncompleted files if we need. */ private final DistributedProcess endSnpProc; @@ -449,8 +446,6 @@ public IgniteSnapshotManager(GridKernalContext ctx) { checkSnpProc = new SnapshotCheckProcess(ctx); - listSnpProc = new SnapshotListProcess(ctx); - // Manage remote snapshots. snpRmtMgr = new SequentialRemoteSnapshotManager(); } @@ -669,7 +664,6 @@ public IgniteSnapshotManager(GridKernalContext ctx) { restoreCacheGrpProc.interrupt(stopErr); checkSnpProc.interrupt(stopErr); - listSnpProc.interrupt(stopErr); // Try stop all snapshot processing if not yet. for (AbstractSnapshotFutureTask sctx : locSnpTasks.values()) @@ -1452,16 +1446,6 @@ public boolean isSnapshotChecking(String snpName) { return checkSnpProc.isSnapshotChecking(snpName); } - /** - * Provides list of snapshots on all online server nodes. - * - * @param snpPath Snapshot directory path. If {@code null}, the default configured snapshot directory will be used. - * @return Future which will be completed when the snapshot list is gathered from all the online server nodes. - */ - public IgniteFuture listSnapshots(@Nullable String snpPath) { - return listSnpProc.start(snpPath); - } - /** * Sets the streamer warning flag to current snapshot process if it is active. */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java new file mode 100644 index 0000000000000..4f54688dcf8c3 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java @@ -0,0 +1,101 @@ +/* + * 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.ignite.internal.processors.cache.persistence.snapshot; + +import java.util.ArrayList; +import java.util.List; +import org.apache.ignite.internal.dto.IgniteDataTransferObject; +import org.jetbrains.annotations.Nullable; + +/** Result of {@link SnapshotListTaskResult}. */ +public final class SnapshotListJobResult extends IgniteDataTransferObject { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** */ + private @Nullable List snapshotsData; + + /** Default constructor for serialization purposes. */ + public SnapshotListJobResult() { + // No-op. + } + + /** */ + public void accept(String snpName, long size, long creationTime) { + if (snapshotsData == null) + snapshotsData = new ArrayList<>(); + + snapshotsData.add(new SnapshotData(snpName, size, creationTime)); + } + + /** */ + public void acceptCreating(String snpName) { + if (snapshotsData == null) + snapshotsData = new ArrayList<>(); + + snapshotsData.add(new SnapshotData(snpName, "creating")); + } + + /** */ + public static class SnapshotData { + /** */ + private String name; + + /** */ + private long size; + + /** */ + private long creationTime; + + /** */ + private @Nullable String status; + + /** */ + public SnapshotData(String name, long size, long creationTime) { + this.name = name; + this.size = size; + this.creationTime = creationTime; + } + + /** */ + public SnapshotData(String name, String status) { + this.name = name; + this.status = status; + } + + /** */ + public String name() { + return name; + } + + /** */ + public long size() { + return size; + } + + /** */ + public long creationTime() { + return creationTime; + } + + /** */ + public @Nullable String status() { + return status; + } + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListProcess.java deleted file mode 100644 index fe11b42e9b5e3..0000000000000 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListProcess.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * 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.ignite.internal.processors.cache.persistence.snapshot; - -import java.io.File; -import java.io.IOException; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import org.apache.ignite.IgniteLogger; -import org.apache.ignite.internal.GridKernalContext; -import org.apache.ignite.internal.IgniteInternalFuture; -import org.apache.ignite.internal.NodeStoppingException; -import org.apache.ignite.internal.util.distributed.DistributedProcess; -import org.apache.ignite.internal.util.future.GridFinishedFuture; -import org.apache.ignite.internal.util.future.GridFutureAdapter; -import org.apache.ignite.internal.util.future.IgniteFutureImpl; -import org.apache.ignite.internal.util.typedef.F; -import org.apache.ignite.lang.IgniteFuture; -import org.jetbrains.annotations.Nullable; - -import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.LIST_SNAPSHOTS; -import static org.apache.ignite.plugin.security.SecurityPermission.ADMIN_SNAPSHOT; - -/** - * Distributed process to list all snapshots and their sizes on all online server nodes. - */ -public class SnapshotListProcess { - /** Status: snapshot is in process of creation. */ - static final String STATUS_CREATING = "CREATING"; - - /** Kernal context. */ - private final GridKernalContext kctx; - - /** Logger. */ - private final IgniteLogger log; - - /** */ - private volatile boolean interrupted; - - /** Cluster-wide operation futures per request id on certain node. */ - private final Map> clusterOpFuts = new ConcurrentHashMap<>(); - - /** The distributed process. */ - private final DistributedProcess distrProc; - - /** - * @param ctx Kernal context. - */ - public SnapshotListProcess(GridKernalContext ctx) { - this.kctx = ctx; - - log = ctx.log(getClass()); - - distrProc = new DistributedProcess<>(ctx, LIST_SNAPSHOTS, this::listPhase, this::reducePhase); - } - - /** - * Starts the cluster snapshot list process. - * - * @param snpPath Snapshot directory path (optional). - * @return Future that will be completed when the snapshot list is gathered from all online server nodes. - */ - public IgniteFuture start(@Nullable String snpPath) { - var clusterOpFut = new GridFutureAdapter(); - - UUID reqId = UUID.randomUUID(); - - clusterOpFut.listen(fut -> clusterOpFuts.remove(reqId)); - - try { - synchronized (clusterOpFuts) { - if (interrupted || kctx.isStopping()) - throw new NodeStoppingException("Failed to start snapshot list process: node is stopping."); - - clusterOpFuts.put(reqId, clusterOpFut); - } - - SnapshotListRequest req = new SnapshotListRequest(reqId, snpPath); - - distrProc.start(reqId, req); - } - catch (Throwable t) { - log.error("Failed to start distributed list snapshot process [snpPath=" + snpPath + ']', t); - - clusterOpFut.onDone(t); - } - - return new IgniteFutureImpl<>(clusterOpFut); - } - - /** */ - private IgniteInternalFuture listPhase(UUID ignored, SnapshotListRequest req) { - if (interrupted || kctx.isStopping()) { - return new GridFinishedFuture<>(new NodeStoppingException( - "Snapshot list was rejected. Node is stopping [req=" + req + ']')); - } - - if (kctx.cluster().get().localNode().isClient()) - return new GridFinishedFuture<>(new SnapshotListResponse()); - - kctx.security().authorize(ADMIN_SNAPSHOT); - - IgniteSnapshotManager snpMgr = kctx.cache().context().snapshotMgr(); - - try { - File path = resolvePath(req.snpPath); - - File[] dirs; - - if (path.exists() && path.isDirectory()) - dirs = path.listFiles(File::isDirectory); - else - dirs = null; - - Map sizes = new HashMap<>(); - Map statuses = new HashMap<>(); - - if (dirs != null) { - for (File dir : dirs) { - String snpName = dir.getName(); - - if (isSnapshotCreating(snpMgr, snpName) || isSnapshotRestoring(snpMgr, snpName)) - statuses.put(snpName, STATUS_CREATING); - } - } - - return new GridFinishedFuture<>(new SnapshotListResponse(sizes, statuses)); - } - catch (Throwable t) { - log.warning("An error occurred during snapshot listing [req=" + req + ']', t); - - return new GridFinishedFuture<>(t); - } - } - - /** */ - private boolean isSnapshotCreating(IgniteSnapshotManager snpMgr, String snpName) { - var curCreateRq = snpMgr.currentCreateRequest(); - - return curCreateRq != null && curCreateRq.snpName.equalsIgnoreCase(snpName); - } - - /** */ - private boolean isSnapshotRestoring(IgniteSnapshotManager snpMgr, String snpName) { - return snpMgr.isRestoring(snpName); - } - - /** */ - private File resolvePath(@Nullable String path) { - var res = kctx.pdsFolderResolver().fileTree().snapshotsRoot(); - - if (path != null) { - File reqPath = new File(path); - - res = reqPath.isAbsolute() ? reqPath : new File(res, path); - } - - return res; - } - - /** - * Calculates total size of all files in the given directory recursively. - * - * @param dir Directory to measure. - * @return Total size in bytes. - * @throws IOException If an I/O error occurs during traversal. - */ - private long directorySize(File dir) throws IOException { - Path path = dir.toPath(); - - long[] size = {0L}; - - Files.walkFileTree(path, new SimpleFileVisitor<>() { - /** {@inheritDoc} */ - @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { - size[0] += attrs.size(); - - return FileVisitResult.CONTINUE; - } - - /** {@inheritDoc} */ - @Override public FileVisitResult visitFileFailed(Path file, IOException exc) { - // File might have been deleted between listing and traversal. - return FileVisitResult.CONTINUE; - } - }); - - return size[0]; - } - - /** */ - private void reducePhase(UUID reqId, Map results, Map errors) { - var clusterOpFut = clusterOpFuts.get(reqId); - - if (clusterOpFut == null) - return; - - assert clusterOpFut != null; - - try { - var errP = F.isEmpty(errors) ? null : F.first(errors.entrySet()); - - if (errP != null) { - log.warning("Snapshot listing finished with an error [reqId=" + reqId + ", nodeId=" - + errP.getKey() + ", err='" + errP.getValue().getMessage() + "']", errP.getValue()); - - clusterOpFut.onDone(errP.getValue()); - - return; - } - - Map> nodeSizes = new HashMap<>(); - Map> nodeStatuses = new HashMap<>(); - Map nodeConsistentIds = new HashMap<>(); - - results.forEach((nodeId, nodeRes) -> { - nodeConsistentIds.put(nodeId, consistentId(nodeId)); - - if (nodeRes.snapshotSizes != null && !nodeRes.snapshotSizes.isEmpty()) - nodeSizes.put(nodeId, nodeRes.snapshotSizes); - - if (nodeRes.snapshotStatuses != null && !nodeRes.snapshotStatuses.isEmpty()) - nodeStatuses.put(nodeId, nodeRes.snapshotStatuses); - }); - - clusterOpFut.onDone(new SnapshotListProcessResult(nodeSizes, nodeStatuses, nodeConsistentIds)); - } - catch (Throwable t) { - clusterOpFut.onDone(t); - } - } - - /** */ - private String consistentId(UUID nodeId) { - var node = kctx.discovery().node(nodeId); - - if (node == null) - node = kctx.discovery().historicalNode(nodeId); - - return node == null ? "" : node.consistentId().toString(); - } - - /** - * @param err The interrupt reason. - */ - void interrupt(Throwable err) { - synchronized (clusterOpFuts) { - interrupted = true; - } - - clusterOpFuts.forEach((reqId, clusterOpFut) -> clusterOpFut.onDone(err)); - - clusterOpFuts.clear(); - } -} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListProcessResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListProcessResult.java deleted file mode 100644 index 69cec245db55a..0000000000000 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListProcessResult.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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.ignite.internal.processors.cache.persistence.snapshot; - -import java.util.Collections; -import java.util.Map; -import java.util.UUID; -import org.apache.ignite.internal.Order; -import org.apache.ignite.internal.dto.IgniteDataTransferObject; -import org.apache.ignite.plugin.extensions.communication.MessageFactory; - -/** Result of {@link SnapshotListProcess}. */ -public final class SnapshotListProcessResult extends IgniteDataTransferObject { - /** Serial version uid. */ - private static final long serialVersionUID = 0L; - - /** Per-node snapshot sizes: nodeId -> (snapshotName -> size in bytes). */ - @Order(0) - Map> nodeSnapshotSizes; - - /** Per-node snapshot statuses: nodeId -> (snapshotName -> status) for transient snapshots. */ - @Order(1) - Map> nodeSnapshotStatuses; - - /** Node consistent IDs: nodeId -> consistentId. */ - @Order(2) - Map nodeConsistentIds; - - /** Default constructor for {@link MessageFactory}. */ - public SnapshotListProcessResult() { - // No-op. - } - - /** */ - public SnapshotListProcessResult( - Map> nodeSnapshotSizes, - Map> nodeSnapshotStatuses, - Map nodeConsistentIds - ) { - this.nodeSnapshotSizes = nodeSnapshotSizes; - this.nodeSnapshotStatuses = nodeSnapshotStatuses; - this.nodeConsistentIds = nodeConsistentIds; - } - - /** */ - public Map> nodeSnapshotSizes() { - return Collections.unmodifiableMap(nodeSnapshotSizes); - } - - /** */ - public Map> nodeSnapshotStatuses() { - return Collections.unmodifiableMap(nodeSnapshotStatuses); - } - - /** */ - public Map nodeConsistentIds() { - return Collections.unmodifiableMap(nodeConsistentIds); - } -} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListRequest.java deleted file mode 100644 index 75709aee75982..0000000000000 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListRequest.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * 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.ignite.internal.processors.cache.persistence.snapshot; - -import java.io.File; -import java.util.UUID; -import org.apache.ignite.internal.Order; -import org.apache.ignite.internal.util.tostring.GridToStringExclude; -import org.apache.ignite.internal.util.typedef.F; -import org.apache.ignite.internal.util.typedef.internal.S; -import org.apache.ignite.plugin.extensions.communication.Message; -import org.apache.ignite.plugin.extensions.communication.MessageFactory; -import org.jetbrains.annotations.Nullable; - -/** - * Cluster snapshot list distributed process request. - * - * @see SnapshotListProcess - */ -public class SnapshotListRequest implements Message { - /** Request ID. */ - @Order(0) - UUID reqId; - - /** Snapshot directory path. */ - @Order(1) - @Nullable String snpPath; - - /** Resolved absolute path. Transient. */ - @GridToStringExclude - @Nullable File resolvedPath; - - /** Default constructor for {@link MessageFactory}. */ - public SnapshotListRequest() { - // No-op. - } - - /** - * @param reqId Request ID. - * @param snpPath Snapshot directory path. - */ - SnapshotListRequest(UUID reqId, @Nullable String snpPath) { - this.reqId = reqId; - - if (!F.isEmpty(snpPath)) - snpPath = snpPath.trim(); - - this.snpPath = F.isEmpty(snpPath) ? null : snpPath; - } - - /** {@inheritDoc} */ - @Override public String toString() { - return S.toString(SnapshotListRequest.class, this); - } -} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListResponse.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListResponse.java deleted file mode 100644 index fea67c5610af2..0000000000000 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListResponse.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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.ignite.internal.processors.cache.persistence.snapshot; - -import java.util.Map; -import org.apache.ignite.internal.Order; -import org.apache.ignite.internal.util.typedef.internal.S; -import org.apache.ignite.plugin.extensions.communication.Message; -import org.apache.ignite.plugin.extensions.communication.MessageFactory; -import org.jetbrains.annotations.Nullable; - -/** - * Single-node result of the snapshot list distributed process. - * - * @see SnapshotListProcess - */ -public class SnapshotListResponse implements Message { - /** Snapshot name -> size in bytes (for available snapshots). */ - @Order(0) - @Nullable Map snapshotSizes; - - /** Snapshot name -> status string (for snapshots in transient state: CREATING, DELETING). */ - @Order(1) - @Nullable Map snapshotStatuses; - - /** Default constructor for {@link MessageFactory}. */ - public SnapshotListResponse() { - // No-op. - } - - /** - * @param sizes Snapshot sizes (name -> size bytes). - * @param statuses Snapshot statuses (name -> status) for transient snapshots. - */ - SnapshotListResponse(@Nullable Map sizes, @Nullable Map statuses) { - this.snapshotSizes = sizes; - this.snapshotStatuses = statuses; - } - - /** {@inheritDoc} */ - @Override public String toString() { - return S.toString(SnapshotListResponse.class, this); - } -} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListTaskResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListTaskResult.java new file mode 100644 index 0000000000000..9334c112b28bd --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListTaskResult.java @@ -0,0 +1,32 @@ +/* + * 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.ignite.internal.processors.cache.persistence.snapshot; + +import org.apache.ignite.internal.dto.IgniteDataTransferObject; +import org.apache.ignite.plugin.extensions.communication.MessageFactory; + +/** Result of {@link SnapshotListTaskResult}. */ +public final class SnapshotListTaskResult extends IgniteDataTransferObject { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** Default constructor for serialization porposes. */ + public SnapshotListTaskResult() { + // No-op. + } +} From 08b44c737288f1a308c8a425c0d7f877596b5db1 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Fri, 25 Sep 2026 14:20:09 +0300 Subject: [PATCH 60/97] in progress --- .../GridCommandHandlerListSnapshotTest.java | 2 +- .../management/snapshot/SnapshotListTask.java | 90 +++++++--------- .../snapshot/SnapshotListJobResult.java | 101 ------------------ .../snapshot/SnapshotListTaskResult.java | 78 +++++++++++++- 4 files changed, 117 insertions(+), 154 deletions(-) delete mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java index 88affb35cb8ed..ba3d0aada247b 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java @@ -99,7 +99,7 @@ public static Collection parameters() { /** */ @Test - public void testSnapshotList() throws Exception { + public void testSnapshotListSingleSnapshot() throws Exception { // A custom snapshot path actually puts snapshots in a shared directory. This skews the results when dedicated // work directories are set. assumeTrue(!customPath || !separatedWorkDir); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java index b073719063789..e28c129fe9664 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java @@ -26,14 +26,11 @@ import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileTime; import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; import org.apache.ignite.IgniteException; import org.apache.ignite.compute.ComputeJobResult; -import org.apache.ignite.internal.processors.cache.persistence.filename.SnapshotFileTree; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager; -import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListJobResult; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListTaskResult; import org.apache.ignite.internal.processors.task.GridInternal; import org.apache.ignite.internal.util.typedef.T2; @@ -43,27 +40,45 @@ /** */ @GridInternal -public class SnapshotListTask extends VisorMultiNodeTask { +public class SnapshotListTask extends VisorMultiNodeTask { /** Serial version uid. */ private static final long serialVersionUID = 0L; /** {@inheritDoc} */ - @Override protected VisorJob job(SnapshotListCommandArg arg) { + @Override protected VisorJob job(SnapshotListCommandArg arg) { return new SnapshotListJob(arg, debug); } /** {@inheritDoc} */ - @Override protected SnapshotListTaskResult reduce0(List list) throws IgniteException { - return null; + @Override protected SnapshotListTaskResult reduce0(List nodesJobsResults) throws IgniteException { + SnapshotListTaskResult taskResult = new SnapshotListTaskResult(); + + for (ComputeJobResult nodeJobRes : nodesJobsResults) { + if (nodeJobRes.getException() != null) { + throw new IgniteException("Failed to execute snapshot list job on node [uuid=" + nodeJobRes.getNode().id() + ']', + nodeJobRes.getException()); + } + + // Clients provide no data. + if (nodeJobRes.getData() == null) + continue; + + taskResult.compose(nodeJobRes.getData()); + } + + return taskResult; } - /** */ - public static T2 calculateDirectorySizeVisitor(File dirPath, AtomicInteger skippedCnt) throws IOException { + /** + * Walk though a directory. Doesn't lock it. Tries to find files and summarize their size. + * Tolerates and skips access errors (permission denied, concurrent deletion). + */ + public static T2 calculateDirectorySizeVisitor(File path) throws IOException { AtomicLong totalSize = new AtomicLong(0); AtomicReference createTime = new AtomicReference<>(); - Files.walkFileTree(dirPath.toPath(), new SimpleFileVisitor<>() { + Files.walkFileTree(path.toPath(), new SimpleFileVisitor<>() { @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { createTime.compareAndSet(null, attrs.creationTime()); @@ -76,18 +91,15 @@ public static T2 calculateDirectorySizeVisitor(File dirPath, Ato if (attrs.isRegularFile()) totalSize.addAndGet(attrs.size()); } - catch (Exception e) { - // File may have been deleted between walk start and here. - skippedCnt.incrementAndGet(); + catch (Exception ignored) { + // No-op: file may have been deleted between walk start and here. } return FileVisitResult.CONTINUE; } + /** File/directory became inaccessible (permission denied, deleted, etc.) */ @Override public FileVisitResult visitFileFailed(Path file, IOException exc) { - // File/directory became inaccessible (permission denied, deleted, etc.) - skippedCnt.incrementAndGet(); - return FileVisitResult.CONTINUE; } }); @@ -95,8 +107,11 @@ public static T2 calculateDirectorySizeVisitor(File dirPath, Ato return new T2<>(totalSize.get(), createTime.get()); } - /** */ - private static class SnapshotListJob extends SnapshotJob { + /** + * Per-node job for {@link SnapshotListTask}. Uses the same {@link SnapshotListTaskResult} but only with the local + * node data. Clients return {@code null}. + */ + private static class SnapshotListJob extends SnapshotJob { /** Serial version uid. */ private static final long serialVersionUID = 0L; @@ -109,33 +124,24 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { } /** {@inheritDoc} */ - @Override protected SnapshotListJobResult run(SnapshotListCommandArg arg) { + @Override protected @Nullable SnapshotListTaskResult run(SnapshotListCommandArg arg) { + if (ignite.localNode().isClient()) + return null; + IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); - SnapshotListJobResult jobRes = new SnapshotListJobResult(); + SnapshotListTaskResult jobRes = new SnapshotListTaskResult(); try { File resolvedPath = resolveSnapshotsPath(arg.src()); - // TODO: support being deleted too. - String ignoreName = detectIgnoredName(resolvedPath); - - AtomicInteger skippedCnt = new AtomicInteger(); - for (String snpName : snpMgr.localSnapshotNames(resolvedPath.getAbsolutePath())) { - if(snpName.equals(ignoreName)) { - jobRes.acceptCreating(snpName); - - continue; - } - File snpPath = new File(resolvedPath, snpName); - skippedCnt.set(0); - - var snpRes = calculateDirectorySizeVisitor(snpPath, skippedCnt); + T2 snpRes = calculateDirectorySizeVisitor(snpPath); - jobRes.accept(snpName, snpRes.get1(), snpRes.get2()); + // TODO: check time conversion. + jobRes.add(ignite.localNode().id(), snpName, snpRes.get1(), snpRes.get2().toInstant().getEpochSecond()); } } catch (Exception e) { throw new IgniteException("Failed to list local snapshots [src=" + arg.src() + ']', e); @@ -144,20 +150,6 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { return jobRes; } - /** */ - private @Nullable String detectIgnoredName(File curPath) { - var createRq = ignite.context().cache().context().snapshotMgr().currentCreateRequest(); - - if (createRq != null) { - File createRoot = new SnapshotFileTree(ignite.context(), createRq.snapshotName(), createRq.snapshotPath()).root(); - - if (createRoot.equals(curPath)) - return createRq.snapshotName(); - } - - return null; - } - /** TODO: move to a sahred with the deletion process place. */ private File resolveSnapshotsPath(@Nullable String src) throws IOException { File res = ignite.context().pdsFolderResolver().fileTree().snapshotsRoot(); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java deleted file mode 100644 index 4f54688dcf8c3..0000000000000 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * 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.ignite.internal.processors.cache.persistence.snapshot; - -import java.util.ArrayList; -import java.util.List; -import org.apache.ignite.internal.dto.IgniteDataTransferObject; -import org.jetbrains.annotations.Nullable; - -/** Result of {@link SnapshotListTaskResult}. */ -public final class SnapshotListJobResult extends IgniteDataTransferObject { - /** Serial version uid. */ - private static final long serialVersionUID = 0L; - - /** */ - private @Nullable List snapshotsData; - - /** Default constructor for serialization purposes. */ - public SnapshotListJobResult() { - // No-op. - } - - /** */ - public void accept(String snpName, long size, long creationTime) { - if (snapshotsData == null) - snapshotsData = new ArrayList<>(); - - snapshotsData.add(new SnapshotData(snpName, size, creationTime)); - } - - /** */ - public void acceptCreating(String snpName) { - if (snapshotsData == null) - snapshotsData = new ArrayList<>(); - - snapshotsData.add(new SnapshotData(snpName, "creating")); - } - - /** */ - public static class SnapshotData { - /** */ - private String name; - - /** */ - private long size; - - /** */ - private long creationTime; - - /** */ - private @Nullable String status; - - /** */ - public SnapshotData(String name, long size, long creationTime) { - this.name = name; - this.size = size; - this.creationTime = creationTime; - } - - /** */ - public SnapshotData(String name, String status) { - this.name = name; - this.status = status; - } - - /** */ - public String name() { - return name; - } - - /** */ - public long size() { - return size; - } - - /** */ - public long creationTime() { - return creationTime; - } - - /** */ - public @Nullable String status() { - return status; - } - } -} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListTaskResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListTaskResult.java index 9334c112b28bd..3d794be738ba5 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListTaskResult.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListTaskResult.java @@ -17,16 +17,88 @@ package org.apache.ignite.internal.processors.cache.persistence.snapshot; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.apache.ignite.internal.Order; import org.apache.ignite.internal.dto.IgniteDataTransferObject; -import org.apache.ignite.plugin.extensions.communication.MessageFactory; +import org.apache.ignite.internal.management.snapshot.SnapshotListTask; -/** Result of {@link SnapshotListTaskResult}. */ +/** Accumulated result of {@link SnapshotListTask}. */ public final class SnapshotListTaskResult extends IgniteDataTransferObject { /** Serial version uid. */ private static final long serialVersionUID = 0L; - /** Default constructor for serialization porposes. */ + /** Lists of snapshots per node. */ + @Order(0) + Map> nodesResults = new HashMap<>(); + + /** Default constructor for serialization purposes. */ public SnapshotListTaskResult() { // No-op. } + + /** */ + public void add(UUID nodeId, String snpName, long size, long creationTime) { + nodesResults.compute(nodeId, (nid, nodeSnps) -> { + if(nodeSnps == null) + nodeSnps = new ArrayList<>(); + + nodeSnps.add(new SnapshotData(snpName, size, creationTime)); + + return nodeSnps; + }); + } + + /** */ + public void compose(SnapshotListTaskResult other) { + nodesResults.putAll(other.nodesResults); + } + + /** */ + public static class SnapshotData extends IgniteDataTransferObject { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** */ + @Order(0) + String name; + + /** */ + @Order(1) + long size; + + /** */ + @Order(2) + long creationTime; + + /** Empty constructor for serialization purposes. */ + public SnapshotData() { + // No-op. + } + + /** */ + private SnapshotData(String name, long size, long creationTime) { + this.name = name; + this.size = size; + this.creationTime = creationTime; + } + + /** */ + public String name() { + return name; + } + + /** */ + public long size() { + return size; + } + + /** */ + public long creationTime() { + return creationTime; + } + } } From 0d803957bc7a91a34b28c25d84706b121612cc6c Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Fri, 25 Sep 2026 19:11:29 +0300 Subject: [PATCH 61/97] first trivail results --- .../GridCommandHandlerListSnapshotTest.java | 3 + .../snapshot/SnapshotListCommand.java | 27 ++++- .../management/snapshot/SnapshotListTask.java | 104 ++++++++++++------ .../snapshot/SnapshotListJobResult.java | 67 +++++++++++ .../snapshot/SnapshotListTaskResult.java | 87 +++++---------- 5 files changed, 190 insertions(+), 98 deletions(-) create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java index ba3d0aada247b..e30c02d3b225b 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java @@ -22,6 +22,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collection; +import org.apache.ignite.Ignite; import org.apache.ignite.IgniteDataStreamer; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.IgniteEx; @@ -109,6 +110,8 @@ public void testSnapshotListSingleSnapshot() throws Exception { IgniteEx ig = (IgniteEx)startGridsMultiThreaded(initNodes); + startGrid(CLIENT_NODE_NAME_PREFIX); + ig.cluster().state(ACTIVE); createCacheAndPreload(ig, entriesCnt); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java index 9b8f639756552..6406fb7c7fe80 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java @@ -17,7 +17,10 @@ package org.apache.ignite.internal.management.snapshot; +import java.sql.Date; +import java.time.Instant; import java.util.function.Consumer; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListJobResult; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListTaskResult; import org.apache.ignite.internal.util.typedef.internal.U; @@ -51,6 +54,28 @@ public class SnapshotListCommand extends AbstractSnapshotCommand printer) { - printer.accept(U.nl()); + printer.accept("The following snapshots are found:"); + printer.accept(""); + + for (int n = 0; n < res.nodesIds().length; n++) { + printer.accept("\tNode '%s' [uuid=%s]:".formatted(res.consistentIds()[n], res.nodesIds()[n])); + + SnapshotListJobResult nodeSnps = res.snapshots()[n]; + + for (int s = 0; s < nodeSnps.snapshotNames().length; s++) { + String name = nodeSnps.snapshotNames()[s]; + long size = nodeSnps.sizes()[s]; + long epochTime = nodeSnps.creationTimes()[s]; + + printer.accept("\t\tSnapshot '%s' [size=%s, created=%s (epochTime=%d)]".formatted( + name, + U.humanReadableByteCount(size), + Date.from(Instant.ofEpochSecond(epochTime)).toString(), + epochTime + )); + + printer.accept(""); + } + } } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java index e28c129fe9664..ea07d6a6a718d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java @@ -18,6 +18,7 @@ package org.apache.ignite.internal.management.snapshot; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.FileVisitResult; import java.nio.file.Files; @@ -25,55 +26,81 @@ import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileTime; +import java.util.Collection; import java.util.List; +import java.util.UUID; import java.util.concurrent.atomic.AtomicLong; import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; import org.apache.ignite.IgniteException; +import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.compute.ComputeJobResult; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListJobResult; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListTaskResult; import org.apache.ignite.internal.processors.task.GridInternal; import org.apache.ignite.internal.util.typedef.T2; import org.apache.ignite.internal.visor.VisorJob; import org.apache.ignite.internal.visor.VisorMultiNodeTask; +import org.apache.ignite.internal.visor.VisorTaskArgument; import org.jetbrains.annotations.Nullable; /** */ @GridInternal -public class SnapshotListTask extends VisorMultiNodeTask { +public class SnapshotListTask extends VisorMultiNodeTask { /** Serial version uid. */ private static final long serialVersionUID = 0L; /** {@inheritDoc} */ - @Override protected VisorJob job(SnapshotListCommandArg arg) { + @Override protected VisorJob job(SnapshotListCommandArg arg) { return new SnapshotListJob(arg, debug); } + /** {@inheritDoc} */ + @Override protected Collection jobNodes(VisorTaskArgument arg) { + /** Allows {@link #map0(List, VisorTaskArgument)} to use the entire subgrid. */ + return ignite.cluster().forServers().nodes().stream().map(ClusterNode::id).collect(Collectors.toList()); + } + /** {@inheritDoc} */ @Override protected SnapshotListTaskResult reduce0(List nodesJobsResults) throws IgniteException { - SnapshotListTaskResult taskResult = new SnapshotListTaskResult(); + String[] cstIds = new String[nodesJobsResults.size()]; + UUID[] nodesIds = new UUID[nodesJobsResults.size()]; + SnapshotListJobResult[] nodesResults = new SnapshotListJobResult[nodesJobsResults.size()]; + + for (int i = 0; i < nodesJobsResults.size(); i++) { + ComputeJobResult nodeJobRes = nodesJobsResults.get(i); - for (ComputeJobResult nodeJobRes : nodesJobsResults) { if (nodeJobRes.getException() != null) { throw new IgniteException("Failed to execute snapshot list job on node [uuid=" + nodeJobRes.getNode().id() + ']', nodeJobRes.getException()); } - // Clients provide no data. - if (nodeJobRes.getData() == null) - continue; + assert nodeJobRes.getData() != null; - taskResult.compose(nodeJobRes.getData()); + cstIds[i] = nodeConsistentId(nodeJobRes.getNode().id()); + nodesIds[i] = nodeJobRes.getNode().id(); + nodesResults[i] = nodeJobRes.getData(); } - return taskResult; + return new SnapshotListTaskResult(cstIds, nodesIds, nodesResults); + } + + /** */ + private String nodeConsistentId(UUID id) { + ClusterNode n = ignite.context().discovery().node(id); + + if (n == null) + n = ignite.context().discovery().historicalNode(id); + + return n == null ? "" : n.consistentId().toString(); } /** - * Walk though a directory. Doesn't lock it. Tries to find files and summarize their size. - * Tolerates and skips access errors (permission denied, concurrent deletion). + * Walk though a directory. Doesn't lock it or its content. Tries to find files and summarize their size. + * Tolerates and skips concurrent deletion errors. */ - public static T2 calculateDirectorySizeVisitor(File path) throws IOException { + public static T2 calculateDirectorySize(File path) throws IOException { AtomicLong totalSize = new AtomicLong(0); AtomicReference createTime = new AtomicReference<>(); @@ -86,32 +113,27 @@ public static T2 calculateDirectorySizeVisitor(File path) throws } @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { - try { - // Use attrs instead of Files.size() for efficiency. - if (attrs.isRegularFile()) - totalSize.addAndGet(attrs.size()); - } - catch (Exception ignored) { - // No-op: file may have been deleted between walk start and here. - } + // Use attrs instead of Files.size() for efficiency. + if (attrs.isRegularFile()) + totalSize.addAndGet(attrs.size()); return FileVisitResult.CONTINUE; } /** File/directory became inaccessible (permission denied, deleted, etc.) */ @Override public FileVisitResult visitFileFailed(Path file, IOException exc) { - return FileVisitResult.CONTINUE; + if(exc instanceof FileNotFoundException) + return FileVisitResult.CONTINUE; + + throw new IgniteException("Failed to calculate snapshot size [name=" + path.getName() + ']', exc); } }); return new T2<>(totalSize.get(), createTime.get()); } - /** - * Per-node job for {@link SnapshotListTask}. Uses the same {@link SnapshotListTaskResult} but only with the local - * node data. Clients return {@code null}. - */ - private static class SnapshotListJob extends SnapshotJob { + /** */ + private static class SnapshotListJob extends SnapshotJob { /** Serial version uid. */ private static final long serialVersionUID = 0L; @@ -124,30 +146,42 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { } /** {@inheritDoc} */ - @Override protected @Nullable SnapshotListTaskResult run(SnapshotListCommandArg arg) { - if (ignite.localNode().isClient()) - return null; + @Override protected SnapshotListJobResult run(SnapshotListCommandArg arg) { + assert !ignite.localNode().isClient(); IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); - SnapshotListTaskResult jobRes = new SnapshotListTaskResult(); + String[] snpNames; + long[] sizes; + long[] creationTimes; try { File resolvedPath = resolveSnapshotsPath(arg.src()); - for (String snpName : snpMgr.localSnapshotNames(resolvedPath.getAbsolutePath())) { + List locSnps = snpMgr.localSnapshotNames(resolvedPath.getAbsolutePath()); + + snpNames = new String[locSnps.size()]; + sizes = new long[locSnps.size()]; + creationTimes = new long[locSnps.size()]; + + for (int s = 0; s < locSnps.size(); s++) { + String snpName = locSnps.get(s); + + snpNames[s] = snpName; + File snpPath = new File(resolvedPath, snpName); - T2 snpRes = calculateDirectorySizeVisitor(snpPath); + T2 snpRes = calculateDirectorySize(snpPath); - // TODO: check time conversion. - jobRes.add(ignite.localNode().id(), snpName, snpRes.get1(), snpRes.get2().toInstant().getEpochSecond()); + sizes[s] = snpRes.get1(); + // TODO: check the time conversion. + creationTimes[s] = snpRes.get2().toInstant().getEpochSecond(); } } catch (Exception e) { throw new IgniteException("Failed to list local snapshots [src=" + arg.src() + ']', e); } - return jobRes; + return new SnapshotListJobResult(snpNames, sizes, creationTimes); } /** TODO: move to a sahred with the deletion process place. */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java new file mode 100644 index 0000000000000..2af3bfb807305 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java @@ -0,0 +1,67 @@ +/* + * 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.ignite.internal.processors.cache.persistence.snapshot; + +import org.apache.ignite.internal.Order; +import org.apache.ignite.internal.dto.IgniteDataTransferObject; +import org.apache.ignite.internal.management.snapshot.SnapshotListTask; + +/** Accumulated result of {@link SnapshotListTask}. */ +public final class SnapshotListJobResult extends IgniteDataTransferObject { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** */ + @Order(0) + String[] snpNames; + + /** */ + @Order(1) + long[] sizes; + + /** */ + @Order(2) + long[] creationTimes; + + /** Default constructor for serialization purposes. */ + public SnapshotListJobResult() { + // No-op. + } + + /** */ + public SnapshotListJobResult(String[] snpNames, long[] sizes, long[] creationTimes) { + this.snpNames = snpNames; + this.sizes = sizes; + this.creationTimes = creationTimes; + } + + /** */ + public String[] snapshotNames() { + return snpNames; + } + + /** */ + public long[] sizes() { + return sizes; + } + + /** */ + public long[] creationTimes() { + return creationTimes; + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListTaskResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListTaskResult.java index 3d794be738ba5..439e524b466b2 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListTaskResult.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListTaskResult.java @@ -17,10 +17,6 @@ package org.apache.ignite.internal.processors.cache.persistence.snapshot; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import java.util.UUID; import org.apache.ignite.internal.Order; import org.apache.ignite.internal.dto.IgniteDataTransferObject; @@ -31,74 +27,41 @@ public final class SnapshotListTaskResult extends IgniteDataTransferObject { /** Serial version uid. */ private static final long serialVersionUID = 0L; - /** Lists of snapshots per node. */ + /** Nodes consistent ids. */ @Order(0) - Map> nodesResults = new HashMap<>(); + String[] cstIds; + + /** Nodes UUIDs. */ + @Order(1) + UUID[] nodesIds; + + /** Results. */ + @Order(2) + SnapshotListJobResult[] snapshots; /** Default constructor for serialization purposes. */ public SnapshotListTaskResult() { // No-op. } - /** */ - public void add(UUID nodeId, String snpName, long size, long creationTime) { - nodesResults.compute(nodeId, (nid, nodeSnps) -> { - if(nodeSnps == null) - nodeSnps = new ArrayList<>(); - - nodeSnps.add(new SnapshotData(snpName, size, creationTime)); - - return nodeSnps; - }); + /** */ + public SnapshotListTaskResult(String[] cstIds, UUID[] nodesIds, SnapshotListJobResult[] snapshots) { + this.cstIds = cstIds; + this.nodesIds = nodesIds; + this.snapshots = snapshots; } - - /** */ - public void compose(SnapshotListTaskResult other) { - nodesResults.putAll(other.nodesResults); + /** @return Nodes consistent ids. */ + public String[] consistentIds() { + return cstIds; } - /** */ - public static class SnapshotData extends IgniteDataTransferObject { - /** Serial version uid. */ - private static final long serialVersionUID = 0L; - - /** */ - @Order(0) - String name; - - /** */ - @Order(1) - long size; - - /** */ - @Order(2) - long creationTime; - - /** Empty constructor for serialization purposes. */ - public SnapshotData() { - // No-op. - } - - /** */ - private SnapshotData(String name, long size, long creationTime) { - this.name = name; - this.size = size; - this.creationTime = creationTime; - } - - /** */ - public String name() { - return name; - } - - /** */ - public long size() { - return size; - } + /** @return Nodes UUIDs. */ + public UUID[] nodesIds() { + return nodesIds; + } - /** */ - public long creationTime() { - return creationTime; - } + /** @return The results. */ + public SnapshotListJobResult[] snapshots() { + return snapshots; } } From 11b9a94cbe8cb072e44345590613fb5fc226b813 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Tue, 29 Sep 2026 18:38:50 +0300 Subject: [PATCH 62/97] + master, in progress --- docs/_docs/snapshots/snapshots.adoc | 4 +- .../GridCommandHandlerListSnapshotTest.java | 144 ++++++------------ .../snapshot/SnapshotListCommand.java | 22 ++- .../management/snapshot/SnapshotListTask.java | 9 +- .../AbstractSnapshotOperationRequest.java | 2 +- .../snapshot/IgniteSnapshotManager.java | 2 +- .../snapshot/SnapshotCheckProcess.java | 15 +- .../snapshot/SnapshotListTaskResult.java | 1 + .../IgniteClusterSnapshotCheckTest.java | 1 - 9 files changed, 79 insertions(+), 121 deletions(-) diff --git a/docs/_docs/snapshots/snapshots.adoc b/docs/_docs/snapshots/snapshots.adoc index 27e1f5964ed7b..9bfe35c4e68c5 100644 --- a/docs/_docs/snapshots/snapshots.adoc +++ b/docs/_docs/snapshots/snapshots.adoc @@ -309,8 +309,8 @@ tab:Windows[] # List all snapshots in the default snapshot directory. control.bat --snapshot list -# List all snapshots in the "/tmp/ignite/snapshots" folder. -control.bat --snapshot list --src /tmp/ignite/snapshots +# List all snapshots in the "C:\\tmp\\ignite\\snapshots" folder. +control.bat --snapshot list --src C:\\tmp\\ignite\\snapshots ---- -- diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java index e30c02d3b225b..e33747cf83506 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java @@ -22,12 +22,12 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collection; -import org.apache.ignite.Ignite; -import org.apache.ignite.IgniteDataStreamer; +import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.management.snapshot.SnapshotListCommand; import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.G; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.testframework.GridTestUtils; import org.junit.Test; @@ -50,8 +50,9 @@ public class GridCommandHandlerListSnapshotTest extends GridCommandHandlerAbstra @Parameter(2) public boolean separatedWorkDir; + /** */ - @Parameters(name = "customPath={1},ownWorkDir={2}") + @Parameters(name = "cmdHnd={0},customPath={1},ownWorkDir={2}") public static Collection parameters() { return GridTestUtils.cartesianProduct( commandHandlers(), @@ -100,129 +101,65 @@ public static Collection parameters() { /** */ @Test - public void testSnapshotListSingleSnapshot() throws Exception { + public void testNoSnapshots() throws Exception { + doTestSnapshotsLists(0); + } + + /** */ + @Test + public void testSingleSnapshot() throws Exception { + doTestSnapshotsLists(1); + } + + /** */ + private void doTestSnapshotsLists(int snpCnt) throws Exception { // A custom snapshot path actually puts snapshots in a shared directory. This skews the results when dedicated // work directories are set. assumeTrue(!customPath || !separatedWorkDir); - int entriesCnt = 100; - int initNodes = 3; - - IgniteEx ig = (IgniteEx)startGridsMultiThreaded(initNodes); + IgniteEx ig = (IgniteEx)startGridsMultiThreaded(3); startGrid(CLIENT_NODE_NAME_PREFIX); ig.cluster().state(ACTIVE); - createCacheAndPreload(ig, entriesCnt); - File cstSnpsRoot = customPath ? new File(grid(0).context().pdsFolderResolver().fileTree().snapshotsRoot(), "ex_snapshots") : null; - // Test listing when no snapshots exist. - // injectTestSystemOut(); - -// if (customPath) -// assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "list", "--src", -// cstSnpsRoot.getAbsolutePath())); -// else -// assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "list")); -// -// String out = testOut.toString(); -// -// assertTrue("Expected 'No snapshots found' message, got: " + out, -// out.contains(SnapshotListCommand.NO_SNAPSHOTS_PREF)); -// -// testOut.reset(); - - // Create a snapshot. - snp(ig).createSnapshot("testSnapshot", customPath ? cstSnpsRoot.getAbsolutePath() : null, false, false) - .get(getTestTimeout()); - - // TODO: add incremental - -// // Add some data and create an incremental snapshot. -// try (IgniteDataStreamer streamer = ig.dataStreamer(DEFAULT_CACHE_NAME)) { -// for (int i = entriesCnt; i < entriesCnt + 50; ++i) -// streamer.addData(i, i); -// } - -// snp(ig).createSnapshot("testSnapshot", customPath ? cstSnpsRoot.getAbsolutePath() : null, true, false) -// .get(getTestTimeout()); + if (snpCnt > 0) { + createCacheAndPreload(ig, 100); + + snp(ig).createSnapshot("testSnapshot", customPath ? cstSnpsRoot.getAbsolutePath() : null, false, false) + .get(getTestTimeout()); + } injectTestSystemOut(); - // Now list snapshots - should find "testSnapshot" on all server nodes. - if (customPath) - assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "list", "--src", + if (customPath) { + assertEquals(EXIT_CODE_OK, execute(newCommandHandler(createTestLogger()), "--snapshot", "list", "--src", cstSnpsRoot.getAbsolutePath())); + } else - assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "list")); + assertEquals(EXIT_CODE_OK, execute(newCommandHandler(createTestLogger()), "--snapshot", "list")); - var out = testOut.toString(); + String out = testOut.toString(); - assertFalse("Expected snapshot to be listed, got: " + out, - out.contains(SnapshotListCommand.NO_SNAPSHOTS_PREF)); - assertTrue("Expected 'Node' in output, got: " + out, - out.contains(SnapshotListCommand.NODE_PREF)); - assertTrue("Expected 'testSnapshot' in output, got: " + out, - out.contains("testSnapshot")); + for (var g : G.allGrids()) { + ClusterNode n = g.cluster().localNode(); - // With separated work directories, all nodes should have the snapshot. - if (separatedWorkDir) { - assertTrue("Expected [cnt=" + initNodes + "] nodes in output, got: " + out, - countNodeOccurrences(out) == initNodes); + assertEquals(n.isClient() ? 0 : 1, countEntries(out, "Node '%s'".formatted(n.consistentId().toString()))); } - testOut.reset(); - - // Test with non-existent snapshot path. - assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "list", "--src", - new File(U.defaultWorkDirectory(), "non_existent").getAbsolutePath())); - - out = testOut.toString(); - - assertTrue("Expected 'No snapshots found' for non-existent path, got: " + out, - out.contains(SnapshotListCommand.NO_SNAPSHOTS_PREF)); - } - - /** */ - @Test - public void testSnapshotListMultipleSnapshots() throws Exception { - int entriesCnt = 100; - int initNodes = 2; + if (snpCnt == 0) { + assertFalse(out.contains("Snapshot '")); - IgniteEx ig = (IgniteEx)startGridsMultiThreaded(initNodes); - - ig.cluster().state(ACTIVE); + assertEquals(3, countEntries(out, SnapshotListCommand.NO_SNAPSHOTS)); - createCacheAndPreload(ig, entriesCnt); - - // Create first snapshot. - snp(ig).createSnapshot("snp1", null, false, false).get(getTestTimeout()); - - // Add data. - try (IgniteDataStreamer streamer = ig.dataStreamer(DEFAULT_CACHE_NAME)) { - for (int i = entriesCnt; i < entriesCnt + 50; ++i) - streamer.addData(i, i); + return; } - // Create second snapshot. - snp(ig).createSnapshot("snp2", null, false, false).get(getTestTimeout()); - - injectTestSystemOut(); - - assertEquals(EXIT_CODE_OK, execute(newCommandHandler(), "--snapshot", "list")); - - String out = testOut.toString(); - - assertFalse("Expected snapshots to be listed, got: " + out, - out.contains(SnapshotListCommand.NO_SNAPSHOTS_PREF)); - assertTrue("Expected 'snp1' in output, got: " + out, - out.contains("snp1")); - assertTrue("Expected 'snp2' in output, got: " + out, - out.contains("snp2")); + assertFalse(out.contains(SnapshotListCommand.NO_SNAPSHOTS)); } /** @@ -239,4 +176,13 @@ private int countNodeOccurrences(String output) { return cnt; } + + /** */ + private static int countEntries(String txt, String entry) { + String prev = txt; + + txt = txt.replaceAll(entry, ""); + + return (prev.length() - txt.length()) / entry.length(); + } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java index 6406fb7c7fe80..453a27dee8677 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java @@ -26,11 +26,14 @@ /** Snapshot list command. */ public class SnapshotListCommand extends AbstractSnapshotCommand { + /** */ + public static final String HEADER = "Snapshots lists on the following nodes:"; + /** */ public static final String DESC = "Lists all snapshots on all online server nodes with their sizes"; /** */ - public static final String NO_SNAPSHOTS_PREF = "No snapshots found on current server nodes"; + public static final String NO_SNAPSHOTS = "No snapshots found."; /** */ public static final String NODE_PREF = "Node "; @@ -54,14 +57,22 @@ public class SnapshotListCommand extends AbstractSnapshotCommand printer) { - printer.accept("The following snapshots are found:"); - printer.accept(""); + printer.accept(HEADER); for (int n = 0; n < res.nodesIds().length; n++) { + // Skip line before node. + printer.accept(""); + printer.accept("\tNode '%s' [uuid=%s]:".formatted(res.consistentIds()[n], res.nodesIds()[n])); SnapshotListJobResult nodeSnps = res.snapshots()[n]; + if(nodeSnps.snapshotNames().length==0) { + printer.accept("\t\t" + NO_SNAPSHOTS); + + continue; + } + for (int s = 0; s < nodeSnps.snapshotNames().length; s++) { String name = nodeSnps.snapshotNames()[s]; long size = nodeSnps.sizes()[s]; @@ -73,9 +84,10 @@ public class SnapshotListCommand extends AbstractSnapshotCommand calculateDirectorySize(File path) throws IOExce /** File/directory became inaccessible (permission denied, deleted, etc.) */ @Override public FileVisitResult visitFileFailed(Path file, IOException exc) { - if(exc instanceof FileNotFoundException) + if (exc instanceof FileNotFoundException) return FileVisitResult.CONTINUE; throw new IgniteException("Failed to calculate snapshot size [name=" + path.getName() + ']', exc); @@ -177,19 +177,20 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { // TODO: check the time conversion. creationTimes[s] = snpRes.get2().toInstant().getEpochSecond(); } - } catch (Exception e) { + } + catch (Exception e) { throw new IgniteException("Failed to list local snapshots [src=" + arg.src() + ']', e); } return new SnapshotListJobResult(snpNames, sizes, creationTimes); } - /** TODO: move to a sahred with the deletion process place. */ + /** TODO: move to a shared with the deletion process place. */ private File resolveSnapshotsPath(@Nullable String src) throws IOException { File res = ignite.context().pdsFolderResolver().fileTree().snapshotsRoot(); if (src != null) { - var srcF = new File(src); + File srcF = new File(src); if (srcF.isAbsolute()) res = srcF; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotOperationRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotOperationRequest.java index 43eb5706b92f3..a12210c29cb77 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotOperationRequest.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotOperationRequest.java @@ -79,7 +79,7 @@ protected AbstractSnapshotOperationRequest( Collection nodes ) { this.reqId = reqId; - this.snpName = snpName.trim(); + this.snpName = snpName; this.grps = grps; this.snpPath = snpPath; this.nodes = new HashSet<>(nodes); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java index b89d60001afe6..2237b0ffe8acf 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java @@ -1479,7 +1479,7 @@ public boolean isRestoring() { * @return {@code True} if the snapshot restore operation from the specified snapshot is in progress locally. */ public boolean isRestoring(String snpName) { - return snpName.equalsIgnoreCase(restoreCacheGrpProc.restoringSnapshotName()); + return snpName.equals(restoreCacheGrpProc.restoringSnapshotName()); } /** diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java index 4226483224a20..1b9bde12f691b 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java @@ -32,7 +32,6 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.stream.Collectors; import org.apache.ignite.IgniteException; -import org.apache.ignite.IgniteIllegalStateException; import org.apache.ignite.IgniteLogger; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.internal.GridKernalContext; @@ -132,7 +131,7 @@ private IgniteInternalFuture reduceValidatePartsAndFinish( return new GridFinishedFuture<>(); try { - contexts.remove(ctx.req.snapshotName().toLowerCase()); + contexts.remove(ctx.req.snapshotName()); GridFutureAdapter clusterOpFut = clusterOpFuts.get(reqId); @@ -474,7 +473,7 @@ private static Exception asException(Throwable th) { private @Nullable SnapshotCheckContext context(@Nullable String snpName, UUID reqId) { return snpName == null ? contexts.values().stream().filter(ctx0 -> ctx0.req.requestId().equals(reqId)).findFirst().orElse(null) - : contexts.get(snpName.toLowerCase()); + : contexts.get(snpName); } /** Phase 1 beginning: prepare, collect and check local metas. */ @@ -488,12 +487,12 @@ private IgniteInternalFuture prepareAndCheckMetas(UUID ig if (nodeStopping) return new GridFinishedFuture<>(new NodeStoppingException("The node is stopping: " + kctx.localNodeId())); - ctx = contexts.computeIfAbsent(req.snapshotName().toLowerCase(), snpName -> new SnapshotCheckContext(req)); + ctx = contexts.computeIfAbsent(req.snapshotName(), snpName -> new SnapshotCheckContext(req)); } if (!ctx.req.requestId().equals(req.requestId())) { - return new GridFinishedFuture<>(new IgniteIllegalStateException("Validation of snapshot '" + req.snapshotName() - + "' has already started [req=" + req + ']')); + return new GridFinishedFuture<>(new IllegalStateException("Validation of snapshot '" + req.snapshotName() + + "' has already started [ctx=" + ctx + ']')); } // Excludes non-baseline initiator. @@ -589,7 +588,7 @@ private void reducePreparationAndMetasCheck( if (ctx != null) { unregisterMetrics(ctx.req.snapshotName()); - contexts.remove(ctx.req.snapshotName().toLowerCase()); + contexts.remove(ctx.req.snapshotName()); } if (clusterOpFut != null) @@ -695,7 +694,7 @@ public IgniteInternalFuture start( /** @return {@code True} if snapshot with specified name is checking. */ boolean isSnapshotChecking(String snpName) { - return contexts.get(snpName.toLowerCase()) != null; + return contexts.get(snpName) != null; } /** @return {@code True} if node with the provided id is in the cluster and is a baseline node. {@code False} otherwise. */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListTaskResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListTaskResult.java index 439e524b466b2..6e347b9e7340a 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListTaskResult.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListTaskResult.java @@ -50,6 +50,7 @@ public SnapshotListTaskResult(String[] cstIds, UUID[] nodesIds, SnapshotListJobR this.nodesIds = nodesIds; this.snapshots = snapshots; } + /** @return Nodes consistent ids. */ public String[] consistentIds() { return cstIds; diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java index 10565adf43f64..306d44224a4d6 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotCheckTest.java @@ -46,7 +46,6 @@ import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteDataStreamer; import org.apache.ignite.IgniteException; -import org.apache.ignite.IgniteIllegalStateException; import org.apache.ignite.binary.BinaryObject; import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction; import org.apache.ignite.cluster.BaselineNode; From d7a8bb2f7d93fec545a6c203144d033415b4c107 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Thu, 1 Oct 2026 12:44:09 +0300 Subject: [PATCH 63/97] hnd test in progress --- .../GridCommandHandlerListSnapshotTest.java | 48 +++++++++++++++---- .../snapshot/SnapshotListCommand.java | 3 +- .../TestIgniteReleaseFeatures_2_19_1.java | 2 +- 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java index e33747cf83506..95ffbdebe1cd1 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java @@ -22,6 +22,7 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collection; +import org.apache.ignite.Ignite; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.IgniteEx; @@ -50,14 +51,19 @@ public class GridCommandHandlerListSnapshotTest extends GridCommandHandlerAbstra @Parameter(2) public boolean separatedWorkDir; + /** */ + @Parameter(3) + public boolean addExtraSrvr; + /** */ - @Parameters(name = "cmdHnd={0},customPath={1},ownWorkDir={2}") + @Parameters(name = "cmdHnd={0},customPath={1},ownWorkDir={2},addExtraSrvr={3}") public static Collection parameters() { return GridTestUtils.cartesianProduct( commandHandlers(), - F.asList(false, true), // Use custom snapshot path; - F.asList(false, true) // Separated (own) work directory. + F.asList(false, true), // Use custom snapshot path + F.asList(false, true), // Separated (own) work directory + F.asList(true) // Add server node ); } @@ -111,13 +117,21 @@ public void testSingleSnapshot() throws Exception { doTestSnapshotsLists(1); } + /** */ + @Test + public void testSeveralSnapshots() throws Exception { + doTestSnapshotsLists(4); + } + /** */ private void doTestSnapshotsLists(int snpCnt) throws Exception { // A custom snapshot path actually puts snapshots in a shared directory. This skews the results when dedicated // work directories are set. assumeTrue(!customPath || !separatedWorkDir); - IgniteEx ig = (IgniteEx)startGridsMultiThreaded(3); + int srvrsCnt = 3; + + IgniteEx ig = (IgniteEx)startGridsMultiThreaded(srvrsCnt); startGrid(CLIENT_NODE_NAME_PREFIX); @@ -128,12 +142,17 @@ private void doTestSnapshotsLists(int snpCnt) throws Exception { : null; if (snpCnt > 0) { - createCacheAndPreload(ig, 100); + createCacheAndPreload(ig, 10); - snp(ig).createSnapshot("testSnapshot", customPath ? cstSnpsRoot.getAbsolutePath() : null, false, false) - .get(getTestTimeout()); + for (int i = 0; i < snpCnt; ++i) { + snp(ig).createSnapshot("testSnapshot" + i, customPath ? cstSnpsRoot.getAbsolutePath() : null, false, false) + .get(getTestTimeout()); + } } + if (addExtraSrvr) + startGrid(G.allGrids().size()); + injectTestSystemOut(); if (customPath) { @@ -145,7 +164,7 @@ private void doTestSnapshotsLists(int snpCnt) throws Exception { String out = testOut.toString(); - for (var g : G.allGrids()) { + for (Ignite g : G.allGrids()) { ClusterNode n = g.cluster().localNode(); assertEquals(n.isClient() ? 0 : 1, countEntries(out, "Node '%s'".formatted(n.consistentId().toString()))); @@ -154,12 +173,21 @@ private void doTestSnapshotsLists(int snpCnt) throws Exception { if (snpCnt == 0) { assertFalse(out.contains("Snapshot '")); - assertEquals(3, countEntries(out, SnapshotListCommand.NO_SNAPSHOTS)); + assertEquals(srvrsCnt + (addExtraSrvr ? 1 : 0), countEntries(out, SnapshotListCommand.NO_SNAPSHOTS)); return; } - assertFalse(out.contains(SnapshotListCommand.NO_SNAPSHOTS)); + assertEquals((addExtraSrvr && separatedWorkDir ? 1 : 0), countEntries(out, SnapshotListCommand.NO_SNAPSHOTS)); + + // The additional server node doesn't have snapshots. But it can see them if shared the snapshot directory. + int snpsRecordsCnt = srvrsCnt + (addExtraSrvr + ? separatedWorkDir ? 0 : 1 + : 0 + ); + + for (int i = 0; i < snpCnt; ++i) + assertEquals(snpsRecordsCnt, countEntries(out, "Snapshot 'testSnapshot" + i + "'")); } /** diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java index 453a27dee8677..cd051fcc812b6 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java @@ -78,9 +78,10 @@ public class SnapshotListCommand extends AbstractSnapshotCommand Date: Thu, 1 Oct 2026 13:58:22 +0300 Subject: [PATCH 64/97] in progress --- .../GridCommandHandlerListSnapshotTest.java | 36 ++++++- .../management/snapshot/SnapshotListTask.java | 97 +++++++++++++++---- .../snapshot/SnapshotListJobResult.java | 22 ++++- 3 files changed, 131 insertions(+), 24 deletions(-) diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java index 95ffbdebe1cd1..1549d9b611375 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java @@ -23,6 +23,7 @@ import java.nio.file.Paths; import java.util.Collection; import org.apache.ignite.Ignite; +import org.apache.ignite.IgniteDataStreamer; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.IgniteEx; @@ -55,15 +56,19 @@ public class GridCommandHandlerListSnapshotTest extends GridCommandHandlerAbstra @Parameter(3) public boolean addExtraSrvr; + /** */ + @Parameter(4) + public int incCnt; /** */ - @Parameters(name = "cmdHnd={0},customPath={1},ownWorkDir={2},addExtraSrvr={3}") + @Parameters(name = "cmdHnd={0},customPath={1},ownWorkDir={2},addExtraSrvr={3},incCnt={4}") public static Collection parameters() { return GridTestUtils.cartesianProduct( commandHandlers(), F.asList(false, true), // Use custom snapshot path F.asList(false, true), // Separated (own) work directory - F.asList(true) // Add server node + F.asList(false, true), // Add server node + F.asList(2) // TODO : Number of incremental snapshots ); } @@ -102,6 +107,8 @@ public static Collection parameters() { if (separatedWorkDir) cfg.setWorkDirectory(new File(U.defaultWorkDirectory(), igniteInstanceName).getAbsolutePath()); + cfg.getDataStorageConfiguration().setWalCompactionEnabled(incCnt > 0); + return cfg; } @@ -129,7 +136,10 @@ private void doTestSnapshotsLists(int snpCnt) throws Exception { // work directories are set. assumeTrue(!customPath || !separatedWorkDir); + assumeTrue(incCnt < 1 || snpCnt > 0); + int srvrsCnt = 3; + int entriesCnt = 20; IgniteEx ig = (IgniteEx)startGridsMultiThreaded(srvrsCnt); @@ -141,20 +151,33 @@ private void doTestSnapshotsLists(int snpCnt) throws Exception { ? new File(grid(0).context().pdsFolderResolver().fileTree().snapshotsRoot(), "ex_snapshots") : null; + // Create shapshots. if (snpCnt > 0) { createCacheAndPreload(ig, 10); - for (int i = 0; i < snpCnt; ++i) { - snp(ig).createSnapshot("testSnapshot" + i, customPath ? cstSnpsRoot.getAbsolutePath() : null, false, false) + for (int s = 0; s < snpCnt; ++s) { + snp(ig).createSnapshot("testSnapshot" + s, customPath ? cstSnpsRoot.getAbsolutePath() : null, false, false) .get(getTestTimeout()); + + for (int i = 0; i < incCnt; ++i) { + try (IgniteDataStreamer ds = grid(0).dataStreamer(DEFAULT_CACHE_NAME)) { + for (int k = (i + 1) * entriesCnt; k < (i + 2) * entriesCnt; ++k) + ds.addData(k, k); + } + + snp(ig).createSnapshot("testSnapshot" + s, customPath ? cstSnpsRoot.getAbsolutePath() : null, true, false) + .get(getTestTimeout()); + } } } + // Add a server. if (addExtraSrvr) startGrid(G.allGrids().size()); injectTestSystemOut(); + // Requests snapshots. if (customPath) { assertEquals(EXIT_CODE_OK, execute(newCommandHandler(createTestLogger()), "--snapshot", "list", "--src", cstSnpsRoot.getAbsolutePath())); @@ -164,12 +187,14 @@ private void doTestSnapshotsLists(int snpCnt) throws Exception { String out = testOut.toString(); + // Find the nodes in the output. for (Ignite g : G.allGrids()) { ClusterNode n = g.cluster().localNode(); assertEquals(n.isClient() ? 0 : 1, countEntries(out, "Node '%s'".formatted(n.consistentId().toString()))); } + // Ensure that there are no snapshots. if (snpCnt == 0) { assertFalse(out.contains("Snapshot '")); @@ -182,10 +207,11 @@ private void doTestSnapshotsLists(int snpCnt) throws Exception { // The additional server node doesn't have snapshots. But it can see them if shared the snapshot directory. int snpsRecordsCnt = srvrsCnt + (addExtraSrvr - ? separatedWorkDir ? 0 : 1 + ? (separatedWorkDir ? 0 : 1) : 0 ); + // Find the snapshots in the output. for (int i = 0; i < snpCnt; ++i) assertEquals(snpsRecordsCnt, countEntries(out, "Snapshot 'testSnapshot" + i + "'")); } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java index ff8c0aa30dc15..e9f87679998c1 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java @@ -26,6 +26,7 @@ import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.FileTime; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.UUID; @@ -33,16 +34,20 @@ import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Collectors; import org.apache.ignite.IgniteException; +import org.apache.ignite.IgniteLogger; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.compute.ComputeJobResult; +import org.apache.ignite.internal.processors.cache.persistence.filename.SnapshotFileTree; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListJobResult; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListTaskResult; import org.apache.ignite.internal.processors.task.GridInternal; +import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.T2; import org.apache.ignite.internal.visor.VisorJob; import org.apache.ignite.internal.visor.VisorMultiNodeTask; import org.apache.ignite.internal.visor.VisorTaskArgument; +import org.apache.ignite.resources.LoggerResource; import org.jetbrains.annotations.Nullable; /** */ @@ -137,6 +142,10 @@ private static class SnapshotListJob extends SnapshotJob locSnps = snpMgr.localSnapshotNames(resolvedPath.getAbsolutePath()); + List locSnps = readMetas(resolvedPath, snpMgr.localSnapshotNames(resolvedPath.getAbsolutePath())); snpNames = new String[locSnps.size()]; sizes = new long[locSnps.size()]; creationTimes = new long[locSnps.size()]; + incCnts = new int[locSnps.size()]; + incSizes = new long[locSnps.size()]; for (int s = 0; s < locSnps.size(); s++) { - String snpName = locSnps.get(s); + SnapshotFileTree sft = locSnps.get(s); - snpNames[s] = snpName; + String snpName = sft.name(); - File snpPath = new File(resolvedPath, snpName); + snpNames[s] = snpName; - T2 snpRes = calculateDirectorySize(snpPath); + T2 snpRes = calculateDirectorySize(sft.root()); sizes[s] = snpRes.get1(); - // TODO: check the time conversion. + creationTimes[s] = snpRes.get2().toInstant().getEpochSecond(); + + T2 incRes = incrementalsNumbersAndSize(sft); + + if (incRes != null) { + incCnts[s] = incRes.get1(); + incSizes[s] = incRes.get2(); + } } } catch (Exception e) { throw new IgniteException("Failed to list local snapshots [src=" + arg.src() + ']', e); } - return new SnapshotListJobResult(snpNames, sizes, creationTimes); + return new SnapshotListJobResult(snpNames, sizes, creationTimes, incCnts, incSizes); + } + + /** */ + private List readMetas(File path, List folderNames) { + List res = new ArrayList<>(folderNames.size()); + + IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); + + folderNames.forEach(fn -> { + SnapshotFileTree sft = new SnapshotFileTree(ignite.context(), fn, path.getAbsolutePath()); + + if (!snpMgr.readSnapshotMetadatas(sft, false).isEmpty()) + res.add(sft); + }); + + return res; } - /** TODO: move to a shared with the deletion process place. */ - private File resolveSnapshotsPath(@Nullable String src) throws IOException { - File res = ignite.context().pdsFolderResolver().fileTree().snapshotsRoot(); + /** */ + private @Nullable T2 incrementalsNumbersAndSize(SnapshotFileTree sft) { + File[] incs = sft.incrementsRoot().listFiles(); - if (src != null) { - File srcF = new File(src); + if (F.isEmpty(incs)) + return null; + + IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); + + int incIdx; + + for(File incDir : incs) { + try { + incIdx = Integer.parseInt(incDir.getName()); + } + catch (NumberFormatException e) { + log.warning("Failed to calculate incremental snapshot size, wrong folder name [name=" + + incDir.getName() + ']', e); + + continue; + } + + SnapshotFileTree.IncrementalSnapshotFileTree incTree = sft.incrementalSnapshotFileTree(incIdx); + + try { + snpMgr.readIncrementalSnapshotMetadata(incTree.meta()); + } + catch (Exception e) { + log.warning("Failed to calculate incremental snapshot size, unable to read the metadata [name=" + + incDir.getName() + ']', e); + + continue; + } - if (srcF.isAbsolute()) - res = srcF; - else - res = new File(res, src); + } - return res.getCanonicalFile(); + return null; } } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java index 2af3bfb807305..7d0711cac3327 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java @@ -38,16 +38,26 @@ public final class SnapshotListJobResult extends IgniteDataTransferObject { @Order(2) long[] creationTimes; + /** */ + @Order(3) + int[] incCnt; + + /** */ + @Order(4) + long[] incSizes; + /** Default constructor for serialization purposes. */ public SnapshotListJobResult() { // No-op. } /** */ - public SnapshotListJobResult(String[] snpNames, long[] sizes, long[] creationTimes) { + public SnapshotListJobResult(String[] snpNames, long[] sizes, long[] creationTimes, int[] incCnt, long[] incSizes) { this.snpNames = snpNames; this.sizes = sizes; this.creationTimes = creationTimes; + this.incCnt = incCnt; + this.incSizes = incSizes; } /** */ @@ -64,4 +74,14 @@ public long[] sizes() { public long[] creationTimes() { return creationTimes; } + + /** */ + public int[] incrementalsCount() { + return incCnt; + } + + /** */ + public long[] incrementalsSizes() { + return incSizes; + } } From 378b18e3053e4cbad1ff404402af7f80acbfbb9f Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Mon, 5 Oct 2026 14:15:47 +0300 Subject: [PATCH 65/97] raw inc snps --- .../GridCommandHandlerListSnapshotTest.java | 88 ++++++++---- .../snapshot/SnapshotListCommand.java | 46 +++++-- .../management/snapshot/SnapshotListTask.java | 128 ++++++++++-------- .../snapshot/SnapshotListJobResult.java | 48 ++++--- .../snapshot/SnapshotMetadata.java | 3 +- 5 files changed, 198 insertions(+), 115 deletions(-) diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java index 1549d9b611375..0c4df4536a489 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java @@ -28,6 +28,7 @@ import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.management.snapshot.SnapshotListCommand; +import org.apache.ignite.internal.processors.cache.persistence.filename.SnapshotFileTree; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.G; import org.apache.ignite.internal.util.typedef.internal.U; @@ -40,6 +41,7 @@ import static org.apache.ignite.cluster.ClusterState.ACTIVE; import static org.apache.ignite.internal.commandline.CommandHandler.EXIT_CODE_OK; import static org.apache.ignite.internal.processors.cache.persistence.snapshot.AbstractSnapshotSelfTest.snp; +import static org.junit.Assume.assumeFalse; import static org.junit.Assume.assumeTrue; /** Test for the command '--snapshot list'. */ @@ -65,10 +67,10 @@ public class GridCommandHandlerListSnapshotTest extends GridCommandHandlerAbstra public static Collection parameters() { return GridTestUtils.cartesianProduct( commandHandlers(), - F.asList(false, true), // Use custom snapshot path - F.asList(false, true), // Separated (own) work directory - F.asList(false, true), // Add server node - F.asList(2) // TODO : Number of incremental snapshots + F.asList(false, true), // Custom snapshot path + F.asList(false, true), // Separated (own) work directories + F.asList(false, true), // Add a server node after the snapshot creation + F.asList(0, 2) // Number of incremental snapshots ); } @@ -115,27 +117,43 @@ public static Collection parameters() { /** */ @Test public void testNoSnapshots() throws Exception { - doTestSnapshotsLists(0); + // Doesn't matter here. + assumeFalse(incCnt > 0 || addExtraSrvr); + + doTestSnapshotsLists(0, false); } /** */ @Test public void testSingleSnapshot() throws Exception { - doTestSnapshotsLists(1); + doTestSnapshotsLists(1, false); } /** */ @Test public void testSeveralSnapshots() throws Exception { - doTestSnapshotsLists(4); + doTestSnapshotsLists(4, false); } /** */ - private void doTestSnapshotsLists(int snpCnt) throws Exception { + @Test + public void testSeveralSnapshotsOneNodeMisses() throws Exception { + // Doesn't matter here. + assumeFalse(incCnt > 0); + + doTestSnapshotsLists(2, true); + } + + /** */ + private void doTestSnapshotsLists(int snpCnt, boolean deleteOnOneNode) throws Exception { + assert !deleteOnOneNode || snpCnt > 0; + // A custom snapshot path actually puts snapshots in a shared directory. This skews the results when dedicated // work directories are set. assumeTrue(!customPath || !separatedWorkDir); - + // Let's make just one node doesn't see the snapshot. + assumeTrue(!deleteOnOneNode || separatedWorkDir); + // No need to create incremental snapshots if no snapshots are required at all. assumeTrue(incCnt < 1 || snpCnt > 0); int srvrsCnt = 3; @@ -151,24 +169,39 @@ private void doTestSnapshotsLists(int snpCnt) throws Exception { ? new File(grid(0).context().pdsFolderResolver().fileTree().snapshotsRoot(), "ex_snapshots") : null; + // Flag if 'testSnapshot0' deleted on node0. + boolean grid0HasNoSnp0 = false; + // Create shapshots. if (snpCnt > 0) { - createCacheAndPreload(ig, 10); + createCacheAndPreload(ig, entriesCnt); - for (int s = 0; s < snpCnt; ++s) { - snp(ig).createSnapshot("testSnapshot" + s, customPath ? cstSnpsRoot.getAbsolutePath() : null, false, false) - .get(getTestTimeout()); + String path = null; - for (int i = 0; i < incCnt; ++i) { + for (int snpIdx = 0; snpIdx < snpCnt; snpIdx++) { + path = customPath ? cstSnpsRoot.getAbsolutePath() : null; + + snp(ig).createSnapshot("testSnapshot" + snpIdx, path, false, false).get(getTestTimeout()); + + for (int incIdx = 0; incIdx < incCnt; incIdx++) { try (IgniteDataStreamer ds = grid(0).dataStreamer(DEFAULT_CACHE_NAME)) { - for (int k = (i + 1) * entriesCnt; k < (i + 2) * entriesCnt; ++k) - ds.addData(k, k); + for (int val = (snpIdx + incIdx + 1) * entriesCnt; val < (snpIdx + incIdx + 2) * entriesCnt; val++) + ds.addData(val, val); } - snp(ig).createSnapshot("testSnapshot" + s, customPath ? cstSnpsRoot.getAbsolutePath() : null, true, false) - .get(getTestTimeout()); + snp(ig).createSnapshot("testSnapshot" + snpIdx, path, true, false).get(getTestTimeout()); } } + + if (deleteOnOneNode) { + SnapshotFileTree sft = new SnapshotFileTree(grid(0).context(), "testSnapshot0", path); + + assertTrue(sft.root().exists()); + assertTrue(U.delete(sft.root())); + assertFalse(sft.root().exists()); + + grid0HasNoSnp0 = true; + } } // Add a server. @@ -212,13 +245,20 @@ private void doTestSnapshotsLists(int snpCnt) throws Exception { ); // Find the snapshots in the output. - for (int i = 0; i < snpCnt; ++i) - assertEquals(snpsRecordsCnt, countEntries(out, "Snapshot 'testSnapshot" + i + "'")); + for (int snpIdx = 0; snpIdx < snpCnt; snpIdx++) { + // 'testSnapshot0' has fewer records if was deleted on one node. + int certainSnpRecordsCnt = snpIdx == 0 && grid0HasNoSnp0 + ? snpsRecordsCnt - 1 + : snpsRecordsCnt; + + assertEquals(certainSnpRecordsCnt, countEntries(out, "Snapshot 'testSnapshot" + snpIdx + "'")); + + for (int i = 0; i < incCnt; i++) + assertEquals(certainSnpRecordsCnt * snpCnt, countEntries(out, "incremental snapshots: cnt=" + incCnt)); + } } - /** - * Counts occurrences of the node prefix in the output. - */ + /** Counts occurrences of the node prefix in the output. */ private int countNodeOccurrences(String output) { int cnt = 0; int idx = 0; @@ -237,6 +277,6 @@ private static int countEntries(String txt, String entry) { txt = txt.replaceAll(entry, ""); - return (prev.length() - txt.length()) / entry.length(); + return (prev.length() - txt.length()) / entry.length(); } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java index cd051fcc812b6..d76c83ca59499 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java @@ -17,8 +17,9 @@ package org.apache.ignite.internal.management.snapshot; -import java.sql.Date; import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; import java.util.function.Consumer; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListJobResult; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListTaskResult; @@ -38,9 +39,13 @@ public class SnapshotListCommand extends AbstractSnapshotCommand calculateDirectorySize(File path) throws IOException { + public static long calculateDirectorySize(File path) throws IOException { AtomicLong totalSize = new AtomicLong(0); - AtomicReference createTime = new AtomicReference<>(); - Files.walkFileTree(path.toPath(), new SimpleFileVisitor<>() { - @Override public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException { - createTime.compareAndSet(null, attrs.creationTime()); - - return super.preVisitDirectory(dir, attrs); - } - @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { // Use attrs instead of Files.size() for efficiency. if (attrs.isRegularFile()) @@ -135,7 +126,7 @@ public static T2 calculateDirectorySize(File path) throws IOExce } }); - return new T2<>(totalSize.get(), createTime.get()); + return totalSize.get(); } /** */ @@ -164,6 +155,7 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { String[] snpNames; long[] sizes; long[] creationTimes; + long[] editTimes; int[] incCnts; long[] incSizes; @@ -171,32 +163,32 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { File resolvedPath = new SnapshotFileTree(ignite.context(), "snpName", arg.src()).root().getParentFile() .getCanonicalFile(); - List locSnps = readMetas(resolvedPath, snpMgr.localSnapshotNames(resolvedPath.getAbsolutePath())); + List> locSnps = readMetas( + resolvedPath, + snpMgr.localSnapshotNames(resolvedPath.getAbsolutePath()) + ); snpNames = new String[locSnps.size()]; sizes = new long[locSnps.size()]; creationTimes = new long[locSnps.size()]; + editTimes = new long[locSnps.size()]; incCnts = new int[locSnps.size()]; incSizes = new long[locSnps.size()]; for (int s = 0; s < locSnps.size(); s++) { - SnapshotFileTree sft = locSnps.get(s); - - String snpName = sft.name(); - - snpNames[s] = snpName; + SnapshotFileTree sft = locSnps.get(s).get1(); - T2 snpRes = calculateDirectorySize(sft.root()); + snpNames[s] = sft.name(); + sizes[s] = calculateDirectorySize(sft.root()); + creationTimes[s] = locSnps.get(s).get2(); - sizes[s] = snpRes.get1(); + T3 incRes = incrementalsData(sft); - creationTimes[s] = snpRes.get2().toInstant().getEpochSecond(); - - T2 incRes = incrementalsNumbersAndSize(sft); - - if (incRes != null) { + // Found incremental snapshots. + if (incRes.get1() > 0) { incCnts[s] = incRes.get1(); incSizes[s] = incRes.get2(); + editTimes[s] = incRes.get3(); } } } @@ -204,63 +196,81 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { throw new IgniteException("Failed to list local snapshots [src=" + arg.src() + ']', e); } - return new SnapshotListJobResult(snpNames, sizes, creationTimes, incCnts, incSizes); + return new SnapshotListJobResult(snpNames, sizes, creationTimes, editTimes, incCnts, incSizes); } - /** */ - private List readMetas(File path, List folderNames) { - List res = new ArrayList<>(folderNames.size()); + /** @return Snapshot file tree and creation time from the snapshot metadata. */ + private List> readMetas(File path, List folderNames) { + List> res = new ArrayList<>(folderNames.size()); IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); folderNames.forEach(fn -> { + // Snapshot tree being used as a path, to read the metas only. SnapshotFileTree sft = new SnapshotFileTree(ignite.context(), fn, path.getAbsolutePath()); - if (!snpMgr.readSnapshotMetadatas(sft, false).isEmpty()) - res.add(sft); + List metas = snpMgr.readSnapshotMetadatas(sft, false); + + if (!metas.isEmpty()) { + // Get the last-created time snapshot metadata. + SnapshotMetadata snpMeta = metas.stream() + .max((m0, m1) -> Math.toIntExact(m0.snapshotTime() - m1.snapshotTime())) + .orElse(new SnapshotMetadata()); + + // Real, meta-based snapshot file tree. Can belong to other cluster, other baseline. + sft = new SnapshotFileTree(ignite.context(), fn, path.getAbsolutePath(), snpMeta.folderName(), snpMeta.consistentId()); + + res.add(new T2<>(sft, snpMeta.snapshotTime())); + } }); return res; } - /** */ - private @Nullable T2 incrementalsNumbersAndSize(SnapshotFileTree sft) { + /** @return Number, total size and last creation time of incremental snapshots. */ + private T3 incrementalsData(SnapshotFileTree sft) throws IOException { File[] incs = sft.incrementsRoot().listFiles(); - if (F.isEmpty(incs)) - return null; + long size = 0L; + int cnt = 0; + long intCreateTime = 0L; - IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); + if (!F.isEmpty(incs)) { + IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); - int incIdx; + int incIdx; - for(File incDir : incs) { - try { - incIdx = Integer.parseInt(incDir.getName()); - } - catch (NumberFormatException e) { - log.warning("Failed to calculate incremental snapshot size, wrong folder name [name=" - + incDir.getName() + ']', e); + for (File incDir : incs) { + try { + incIdx = Integer.parseInt(incDir.getName()); + } + catch (NumberFormatException e) { + log.warning("Failed to calculate incremental snapshot size, wrong folder name [name=" + + incDir.getName() + ']', e); - continue; - } + continue; + } - SnapshotFileTree.IncrementalSnapshotFileTree incTree = sft.incrementalSnapshotFileTree(incIdx); + SnapshotFileTree.IncrementalSnapshotFileTree incTree = sft.incrementalSnapshotFileTree(incIdx); + IncrementalSnapshotMetadata incMeta; - try { - snpMgr.readIncrementalSnapshotMetadata(incTree.meta()); - } - catch (Exception e) { - log.warning("Failed to calculate incremental snapshot size, unable to read the metadata [name=" - + incDir.getName() + ']', e); + try { + incMeta = snpMgr.readIncrementalSnapshotMetadata(incTree.meta()); + } + catch (Exception e) { + log.warning("Failed to calculate incremental snapshot size, unable to read the metadata [name=" + + incDir.getName() + ']', e); - continue; - } + continue; + } - return calculateDirectorySize(incDir); + cnt++; + size += calculateDirectorySize(incDir); + intCreateTime = Math.max(intCreateTime, incMeta.snapshotTime()); + } } - return null; + return new T3<>(cnt, size, intCreateTime); } } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java index 7d0711cac3327..a61e30d4f2509 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java @@ -26,25 +26,29 @@ public final class SnapshotListJobResult extends IgniteDataTransferObject { /** Serial version uid. */ private static final long serialVersionUID = 0L; - /** */ + /** Snapshot names. */ @Order(0) - String[] snpNames; + String[] snpName; - /** */ + /** Snapshot sizes. */ @Order(1) - long[] sizes; + long[] sz; - /** */ + /** Creation times. */ @Order(2) - long[] creationTimes; + long[] creationTime; - /** */ + /** Last modified times. Actual if {@link #incCnt}[n] > 0. */ @Order(3) - int[] incCnt; + long[] editTime; - /** */ + /** Numbers of related incremental snapshots. */ @Order(4) - long[] incSizes; + int[] incCnt; + + /** Total sizes of related incremental snapshots. */ + @Order(5) + long[] incSize; /** Default constructor for serialization purposes. */ public SnapshotListJobResult() { @@ -52,27 +56,33 @@ public SnapshotListJobResult() { } /** */ - public SnapshotListJobResult(String[] snpNames, long[] sizes, long[] creationTimes, int[] incCnt, long[] incSizes) { - this.snpNames = snpNames; - this.sizes = sizes; - this.creationTimes = creationTimes; + public SnapshotListJobResult(String[] name, long[] sz, long[] createTime, long[] editTime, int[] incCnt, long[] incSize) { + this.snpName = name; + this.sz = sz; + this.creationTime = createTime; + this.editTime = editTime; this.incCnt = incCnt; - this.incSizes = incSizes; + this.incSize = incSize; } /** */ public String[] snapshotNames() { - return snpNames; + return snpName; } /** */ public long[] sizes() { - return sizes; + return sz; } /** */ public long[] creationTimes() { - return creationTimes; + return creationTime; + } + + /** */ + public long[] editTimes() { + return editTime; } /** */ @@ -82,6 +92,6 @@ public int[] incrementalsCount() { /** */ public long[] incrementalsSizes() { - return incSizes; + return incSize; } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMetadata.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMetadata.java index b09d0d798c7cb..4f9a8d2093640 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMetadata.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMetadata.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Random; import java.util.Set; import java.util.UUID; import org.apache.ignite.internal.Order; @@ -186,7 +187,7 @@ public SnapshotMetadata( this.comprParts = comprParts; this.pageSize = pageSize; this.grpIds = grpIds; - this.snapshotTime = snapshotTime; + this.snapshotTime = snapshotTime + new Random().nextLong(15000); this.bltNodes = bltNodes; this.snpRecPtr = snpRecPtr; this.masterKeyDigest = masterKeyDigest; From a61a572a13106309029d37eca533a234651e3750 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Mon, 5 Oct 2026 21:47:08 +0300 Subject: [PATCH 66/97] extra strorages in progress --- .../util/GridCommandHandlerAbstractTest.java | 23 +++- .../GridCommandHandlerListSnapshotTest.java | 103 ++++++++++++++---- .../snapshot/SnapshotListCommand.java | 10 +- .../management/snapshot/SnapshotListTask.java | 42 ++++--- .../snapshot/SnapshotListJobResult.java | 59 ++++++---- 5 files changed, 173 insertions(+), 64 deletions(-) diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerAbstractTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerAbstractTest.java index 092fe78903246..7d5f2e14c289d 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerAbstractTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerAbstractTest.java @@ -499,6 +499,22 @@ protected void createCacheAndPreload( ) { assert nonNull(ignite); + CacheConfiguration ccfg = testCacheConfiguration(cacheName, partitions, filter); + + ignite.createCache(ccfg); + + try (IgniteDataStreamer streamer = ignite.dataStreamer(cacheName)) { + for (int i = 0; i < countEntries; i++) + streamer.addData(i, i); + } + } + + /** */ + protected CacheConfiguration testCacheConfiguration( + String cacheName, + int partitions, + @Nullable IgnitePredicate filter + ) { CacheConfiguration ccfg = new CacheConfiguration<>(cacheName) .setAffinity(new RendezvousAffinityFunction(false, partitions)) .setBackups(1) @@ -507,12 +523,7 @@ protected void createCacheAndPreload( if (filter != null) ccfg.setNodeFilter(filter); - ignite.createCache(ccfg); - - try (IgniteDataStreamer streamer = ignite.dataStreamer(cacheName)) { - for (int i = 0; i < countEntries; i++) - streamer.addData(i, i); - } + return ccfg; } /** diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java index 0c4df4536a489..c83fb785336ff 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java @@ -25,6 +25,7 @@ import org.apache.ignite.Ignite; import org.apache.ignite.IgniteDataStreamer; import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.configuration.CacheConfiguration; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.management.snapshot.SnapshotListCommand; @@ -32,7 +33,9 @@ import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.G; import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.lang.IgnitePredicate; import org.apache.ignite.testframework.GridTestUtils; +import org.jetbrains.annotations.Nullable; import org.junit.Test; import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; @@ -46,6 +49,9 @@ /** Test for the command '--snapshot list'. */ public class GridCommandHandlerListSnapshotTest extends GridCommandHandlerAbstractTest { + /** Extra storage path. */ + private static final String EXT_STORAGE_PATH = "extStorage"; + /** */ @Parameter(1) public boolean customPath; @@ -62,6 +68,12 @@ public class GridCommandHandlerListSnapshotTest extends GridCommandHandlerAbstra @Parameter(4) public int incCnt; + /** */ + private boolean extraStorages; + + /** */ + private @Nullable String[] extStoragePaths; + /** */ @Parameters(name = "cmdHnd={0},customPath={1},ownWorkDir={2},addExtraSrvr={3},incCnt={4}") public static Collection parameters() { @@ -106,11 +118,25 @@ public static Collection parameters() { @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); - if (separatedWorkDir) - cfg.setWorkDirectory(new File(U.defaultWorkDirectory(), igniteInstanceName).getAbsolutePath()); + String workDir = separatedWorkDir + ? new File(U.defaultWorkDirectory(), igniteInstanceName).getAbsolutePath() + : U.defaultWorkDirectory(); + + cfg.setWorkDirectory(workDir); cfg.getDataStorageConfiguration().setWalCompactionEnabled(incCnt > 0); + if (extraStorages) { + cfg.getDataStorageConfiguration().setExtraStoragePaths( + workDir + File.separator, + workDir + File.separator + EXT_STORAGE_PATH + ); + + extStoragePaths = cfg.getDataStorageConfiguration().getExtraStoragePaths(); + + cfg.getDataStorageConfiguration().setExtraSnapshotPaths("", EXT_STORAGE_PATH); + } + return cfg; } @@ -145,16 +171,16 @@ public void testSeveralSnapshotsOneNodeMisses() throws Exception { } /** */ - private void doTestSnapshotsLists(int snpCnt, boolean deleteOnOneNode) throws Exception { - assert !deleteOnOneNode || snpCnt > 0; + @Test + public void testExtraStorages() throws Exception { + extraStorages = true; - // A custom snapshot path actually puts snapshots in a shared directory. This skews the results when dedicated - // work directories are set. - assumeTrue(!customPath || !separatedWorkDir); - // Let's make just one node doesn't see the snapshot. - assumeTrue(!deleteOnOneNode || separatedWorkDir); - // No need to create incremental snapshots if no snapshots are required at all. - assumeTrue(incCnt < 1 || snpCnt > 0); + doTestSnapshotsLists(2, false); + } + + /** */ + private void doTestSnapshotsLists(int snpCnt, boolean deleteOnOneNode) throws Exception { + filterTests(snpCnt, deleteOnOneNode, extraStorages); int srvrsCnt = 3; int entriesCnt = 20; @@ -165,23 +191,23 @@ private void doTestSnapshotsLists(int snpCnt, boolean deleteOnOneNode) throws Ex ig.cluster().state(ACTIVE); - File cstSnpsRoot = customPath + File snpsRootFile = customPath ? new File(grid(0).context().pdsFolderResolver().fileTree().snapshotsRoot(), "ex_snapshots") : null; // Flag if 'testSnapshot0' deleted on node0. - boolean grid0HasNoSnp0 = false; + boolean grid0HasNoSnapshot0 = false; // Create shapshots. if (snpCnt > 0) { createCacheAndPreload(ig, entriesCnt); - String path = null; + String absPathStr = null; for (int snpIdx = 0; snpIdx < snpCnt; snpIdx++) { - path = customPath ? cstSnpsRoot.getAbsolutePath() : null; + absPathStr = customPath ? snpsRootFile.getAbsolutePath() : null; - snp(ig).createSnapshot("testSnapshot" + snpIdx, path, false, false).get(getTestTimeout()); + snp(ig).createSnapshot("testSnapshot" + snpIdx, absPathStr, false, false).get(getTestTimeout()); for (int incIdx = 0; incIdx < incCnt; incIdx++) { try (IgniteDataStreamer ds = grid(0).dataStreamer(DEFAULT_CACHE_NAME)) { @@ -189,18 +215,18 @@ private void doTestSnapshotsLists(int snpCnt, boolean deleteOnOneNode) throws Ex ds.addData(val, val); } - snp(ig).createSnapshot("testSnapshot" + snpIdx, path, true, false).get(getTestTimeout()); + snp(ig).createSnapshot("testSnapshot" + snpIdx, absPathStr, true, false).get(getTestTimeout()); } } if (deleteOnOneNode) { - SnapshotFileTree sft = new SnapshotFileTree(grid(0).context(), "testSnapshot0", path); + SnapshotFileTree sft = new SnapshotFileTree(grid(0).context(), "testSnapshot0", absPathStr); assertTrue(sft.root().exists()); assertTrue(U.delete(sft.root())); assertFalse(sft.root().exists()); - grid0HasNoSnp0 = true; + grid0HasNoSnapshot0 = true; } } @@ -213,7 +239,7 @@ private void doTestSnapshotsLists(int snpCnt, boolean deleteOnOneNode) throws Ex // Requests snapshots. if (customPath) { assertEquals(EXIT_CODE_OK, execute(newCommandHandler(createTestLogger()), "--snapshot", "list", "--src", - cstSnpsRoot.getAbsolutePath())); + snpsRootFile.getAbsolutePath())); } else assertEquals(EXIT_CODE_OK, execute(newCommandHandler(createTestLogger()), "--snapshot", "list")); @@ -247,7 +273,7 @@ private void doTestSnapshotsLists(int snpCnt, boolean deleteOnOneNode) throws Ex // Find the snapshots in the output. for (int snpIdx = 0; snpIdx < snpCnt; snpIdx++) { // 'testSnapshot0' has fewer records if was deleted on one node. - int certainSnpRecordsCnt = snpIdx == 0 && grid0HasNoSnp0 + int certainSnpRecordsCnt = snpIdx == 0 && grid0HasNoSnapshot0 ? snpsRecordsCnt - 1 : snpsRecordsCnt; @@ -258,6 +284,41 @@ private void doTestSnapshotsLists(int snpCnt, boolean deleteOnOneNode) throws Ex } } + + + /** */ + @Override protected CacheConfiguration testCacheConfiguration( + String cacheName, + int partitions, + @Nullable IgnitePredicate filter + ) { + CacheConfiguration ccfg = super.testCacheConfiguration(cacheName, partitions, filter); + + if (extraStorages) + ccfg.setStoragePaths(extStoragePaths); + + return ccfg; + } + + /** */ + private void filterTests(int snpCnt, boolean deleteOnOneNode, boolean extraStorages) { + assert !deleteOnOneNode || snpCnt > 0; + assert !extraStorages || snpCnt > 0; + + // A custom snapshot path actually puts snapshots in a shared directory. This skews the results when dedicated + // work directories are set. + assumeFalse(customPath && separatedWorkDir); + + // Let's keep just one node doesn't see the snapshot. + assumeTrue(!deleteOnOneNode || separatedWorkDir); + + // No need to create incremental snapshots if no snapshots are required at all. + assumeTrue(incCnt < 1 || snpCnt > 0); + + // TODO: comment + assumeFalse(extraStorages && separatedWorkDir); + } + /** Counts occurrences of the node prefix in the output. */ private int countNodeOccurrences(String output) { int cnt = 0; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java index d76c83ca59499..14839db224f2b 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java @@ -28,7 +28,7 @@ /** Snapshot list command. */ public class SnapshotListCommand extends AbstractSnapshotCommand { /** */ - public static final String HEADER = "Snapshots lists on the following nodes:"; + public static final String HEADER = "Snapshots found on the following nodes:"; /** */ public static final String DESC = "Lists all snapshots on all online server nodes with their sizes"; @@ -64,13 +64,13 @@ public class SnapshotListCommand extends AbstractSnapshotCommand printer) { printer.accept(HEADER); - for (int n = 0; n < res.nodesIds().length; n++) { + for (int nodeIdx = 0; nodeIdx < res.nodesIds().length; nodeIdx++) { // Skip line before node. printer.accept(""); - printer.accept("\tNode '%s' [uuid=%s]:".formatted(res.consistentIds()[n], res.nodesIds()[n])); + printer.accept("\tNode '%s' [uuid=%s]:".formatted(res.consistentIds()[nodeIdx], res.nodesIds()[nodeIdx])); - SnapshotListJobResult nodeSnps = res.snapshots()[n]; + SnapshotListJobResult nodeSnps = res.snapshots()[nodeIdx]; if (nodeSnps.snapshotNames().length == 0) { printer.accept("\t\t" + NO_SNAPSHOTS); @@ -92,7 +92,7 @@ public class SnapshotListCommand extends AbstractSnapshotCommand> locSnps = readMetas( - resolvedPath, - snpMgr.localSnapshotNames(resolvedPath.getAbsolutePath()) + arg.src(), + snpMgr.localSnapshotNames(readMetasDir.getAbsolutePath()) ); snpNames = new String[locSnps.size()]; sizes = new long[locSnps.size()]; creationTimes = new long[locSnps.size()]; - editTimes = new long[locSnps.size()]; - incCnts = new int[locSnps.size()]; - incSizes = new long[locSnps.size()]; + + extSizes = new long[locSnps.size()]; for (int s = 0; s < locSnps.size(); s++) { SnapshotFileTree sft = locSnps.get(s).get1(); @@ -186,6 +189,12 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { // Found incremental snapshots. if (incRes.get1() > 0) { + if (incCnts == null) { + incCnts = new int[locSnps.size()]; + incSizes = new long[locSnps.size()]; + editTimes = new long[locSnps.size()]; + } + incCnts[s] = incRes.get1(); incSizes[s] = incRes.get2(); editTimes[s] = incRes.get3(); @@ -196,18 +205,18 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { throw new IgniteException("Failed to list local snapshots [src=" + arg.src() + ']', e); } - return new SnapshotListJobResult(snpNames, sizes, creationTimes, editTimes, incCnts, incSizes); + return new SnapshotListJobResult(snpNames, sizes, creationTimes, incCnts, incSizes, editTimes, extSizes); } /** @return Snapshot file tree and creation time from the snapshot metadata. */ - private List> readMetas(File path, List folderNames) { + private List> readMetas(@Nullable String customSnpRoot, List folderNames) { List> res = new ArrayList<>(folderNames.size()); IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); folderNames.forEach(fn -> { // Snapshot tree being used as a path, to read the metas only. - SnapshotFileTree sft = new SnapshotFileTree(ignite.context(), fn, path.getAbsolutePath()); + SnapshotFileTree sft = new SnapshotFileTree(ignite.context(), fn, customSnpRoot); List metas = snpMgr.readSnapshotMetadatas(sft, false); @@ -218,7 +227,14 @@ private List> readMetas(File path, List folde .orElse(new SnapshotMetadata()); // Real, meta-based snapshot file tree. Can belong to other cluster, other baseline. - sft = new SnapshotFileTree(ignite.context(), fn, path.getAbsolutePath(), snpMeta.folderName(), snpMeta.consistentId()); + sft = new SnapshotFileTree( + ignite.configuration(), + ignite.context().pdsFolderResolver().fileTree(), + fn, + customSnpRoot, + snpMeta.folderName(), + snpMeta.consistentId() + ); res.add(new T2<>(sft, snpMeta.snapshotTime())); } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java index a61e30d4f2509..b63e4dbd94096 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java @@ -20,6 +20,7 @@ import org.apache.ignite.internal.Order; import org.apache.ignite.internal.dto.IgniteDataTransferObject; import org.apache.ignite.internal.management.snapshot.SnapshotListTask; +import org.jetbrains.annotations.Nullable; /** Accumulated result of {@link SnapshotListTask}. */ public final class SnapshotListJobResult extends IgniteDataTransferObject { @@ -32,23 +33,27 @@ public final class SnapshotListJobResult extends IgniteDataTransferObject { /** Snapshot sizes. */ @Order(1) - long[] sz; + long[] size; /** Creation times. */ @Order(2) long[] creationTime; - /** Last modified times. Actual if {@link #incCnt}[n] > 0. */ + /** Numbers of related incremental snapshots. */ @Order(3) - long[] editTime; + int[] incCnt; - /** Numbers of related incremental snapshots. */ + /** Total sizes of incremental snapshots. */ @Order(4) - int[] incCnt; + long[] incSize; - /** Total sizes of related incremental snapshots. */ + /** Last modified times. Actual if {@link #incCnt}[n] > 0. */ @Order(5) - long[] incSize; + long[] editTime; + + /** Total sizes of external-storage snapshots. */ + @Order(6) + long[] extSize; /** Default constructor for serialization purposes. */ public SnapshotListJobResult() { @@ -56,13 +61,24 @@ public SnapshotListJobResult() { } /** */ - public SnapshotListJobResult(String[] name, long[] sz, long[] createTime, long[] editTime, int[] incCnt, long[] incSize) { - this.snpName = name; - this.sz = sz; - this.creationTime = createTime; - this.editTime = editTime; + public SnapshotListJobResult( + String[] name, + long[] size, + long[] createTime, + @Nullable int[] incCnt, + @Nullable long[] incSize, + @Nullable long[] editTime, + @Nullable long[] extSize + ) { + snpName = name; + this.size = size; + creationTime = createTime; + this.incCnt = incCnt; this.incSize = incSize; + this.editTime = editTime; + + this.extSize = extSize; } /** */ @@ -72,7 +88,7 @@ public String[] snapshotNames() { /** */ public long[] sizes() { - return sz; + return size; } /** */ @@ -81,17 +97,22 @@ public long[] creationTimes() { } /** */ - public long[] editTimes() { - return editTime; + public @Nullable int[] incrementalsCount() { + return incCnt; } /** */ - public int[] incrementalsCount() { - return incCnt; + public @Nullable long[] incrementalsSizes() { + return incSize; } /** */ - public long[] incrementalsSizes() { - return incSize; + public @Nullable long[] editTimes() { + return editTime; + } + + /** */ + public @Nullable long[] externalSizes() { + return extSize; } } From 2a3b6cca822077be97ca138c4c8f4fb7202c138c Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Tue, 6 Oct 2026 11:39:36 +0300 Subject: [PATCH 67/97] in progress --- .../snapshot/SnapshotListCommand.java | 1 - .../management/snapshot/SnapshotListTask.java | 33 +++++++++++-------- 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java index 14839db224f2b..9bf99135eaf07 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java @@ -91,7 +91,6 @@ public class SnapshotListCommand extends AbstractSnapshotCommand> locSnps = readMetas( + List> locSnps = findLocalSnapshots( arg.src(), - snpMgr.localSnapshotNames(readMetasDir.getAbsolutePath()) + ignite.context().cache().context().snapshotMgr().localSnapshotNames(arg.src()) ); snpNames = new String[locSnps.size()]; sizes = new long[locSnps.size()]; creationTimes = new long[locSnps.size()]; - extSizes = new long[locSnps.size()]; - for (int s = 0; s < locSnps.size(); s++) { SnapshotFileTree sft = locSnps.get(s).get1(); snpNames[s] = sft.name(); - sizes[s] = calculateDirectorySize(sft.root()); creationTimes[s] = locSnps.get(s).get2(); + sizes[s] = calculateDirectorySize(sft.root()); + + for(File es : sft.extraStorages().values()) { + if (sft.root().equals(es)) + continue; + + if (extSizes == null) + extSizes = new long[locSnps.size()]; + + sizes[s] += calculateDirectorySize(es); + } + T3 incRes = incrementalsData(sft); // Found incremental snapshots. @@ -209,16 +216,14 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { } /** @return Snapshot file tree and creation time from the snapshot metadata. */ - private List> readMetas(@Nullable String customSnpRoot, List folderNames) { + private List> findLocalSnapshots(@Nullable String customSnpRoot, List folderNames) { List> res = new ArrayList<>(folderNames.size()); - IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); - folderNames.forEach(fn -> { // Snapshot tree being used as a path, to read the metas only. SnapshotFileTree sft = new SnapshotFileTree(ignite.context(), fn, customSnpRoot); - List metas = snpMgr.readSnapshotMetadatas(sft, false); + List metas = ignite.context().cache().context().snapshotMgr().readSnapshotMetadatas(sft, false); if (!metas.isEmpty()) { // Get the last-created time snapshot metadata. From 75f8004e1f1c1760a82fd051d797a8459cf36939 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Tue, 6 Oct 2026 12:26:21 +0300 Subject: [PATCH 68/97] + extra storages data --- .../snapshot/SnapshotListCommand.java | 15 ++++- .../management/snapshot/SnapshotListTask.java | 53 ++++++++++------ .../snapshot/SnapshotListJobResult.java | 60 +++++++++++++++++-- 3 files changed, 105 insertions(+), 23 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java index 9bf99135eaf07..f632737fd86aa 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java @@ -83,7 +83,7 @@ public class SnapshotListCommand extends AbstractSnapshotCommand> locSnps = findLocalSnapshots( @@ -174,27 +174,30 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { sizes = new long[locSnps.size()]; creationTimes = new long[locSnps.size()]; - for (int s = 0; s < locSnps.size(); s++) { - SnapshotFileTree sft = locSnps.get(s).get1(); + for (int snpIdx = 0; snpIdx < locSnps.size(); snpIdx++) { + SnapshotFileTree sft = locSnps.get(snpIdx).get1(); - snpNames[s] = sft.name(); - creationTimes[s] = locSnps.get(s).get2(); + // Main snapshot data. + snpNames[snpIdx] = sft.name(); + creationTimes[snpIdx] = locSnps.get(snpIdx).get2(); - sizes[s] = calculateDirectorySize(sft.root()); + sizes[snpIdx] = calculateDirectorySize(sft.root()); - for(File es : sft.extraStorages().values()) { - if (sft.root().equals(es)) - continue; + // Optional extra storages data. + SnapshotListJobResult.ExtraRecordsData snpExtStors = findExtraStorages(sft); - if (extSizes == null) - extSizes = new long[locSnps.size()]; + if (snpExtStors != null) { + if (extraStorages == null) + extraStorages = new SnapshotListJobResult.ExtraRecordsData[locSnps.size()]; - sizes[s] += calculateDirectorySize(es); + extraStorages[snpIdx] = snpExtStors; + + sizes[snpIdx] += snpExtStors.size(); } + // Optiona incremental snapshots data. T3 incRes = incrementalsData(sft); - // Found incremental snapshots. if (incRes.get1() > 0) { if (incCnts == null) { incCnts = new int[locSnps.size()]; @@ -202,9 +205,9 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { editTimes = new long[locSnps.size()]; } - incCnts[s] = incRes.get1(); - incSizes[s] = incRes.get2(); - editTimes[s] = incRes.get3(); + incCnts[snpIdx] = incRes.get1(); + incSizes[snpIdx] = incRes.get2(); + editTimes[snpIdx] = incRes.get3(); } } } @@ -212,7 +215,23 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { throw new IgniteException("Failed to list local snapshots [src=" + arg.src() + ']', e); } - return new SnapshotListJobResult(snpNames, sizes, creationTimes, incCnts, incSizes, editTimes, extSizes); + return new SnapshotListJobResult(snpNames, sizes, creationTimes, incCnts, incSizes, editTimes, extraStorages); + } + + /** */ + private static @Nullable SnapshotListJobResult.ExtraRecordsData findExtraStorages(SnapshotFileTree sft) throws IOException { + int extStoragesCnt = 0; + long extStoragesSize = 0; + + for (File es : sft.allStorages().toList()) { + if (sft.nodeStorage().equals(es)) + continue; + + extStoragesCnt++; + extStoragesSize += calculateDirectorySize(es); + } + + return extStoragesCnt == 0 ? null : new SnapshotListJobResult.ExtraRecordsData(extStoragesCnt, extStoragesSize); } /** @return Snapshot file tree and creation time from the snapshot metadata. */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java index b63e4dbd94096..2e4e1399f9b78 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java @@ -53,7 +53,7 @@ public final class SnapshotListJobResult extends IgniteDataTransferObject { /** Total sizes of external-storage snapshots. */ @Order(6) - long[] extSize; + ExtraRecordsData[] extStorages; /** Default constructor for serialization purposes. */ public SnapshotListJobResult() { @@ -68,7 +68,7 @@ public SnapshotListJobResult( @Nullable int[] incCnt, @Nullable long[] incSize, @Nullable long[] editTime, - @Nullable long[] extSize + @Nullable ExtraRecordsData[] extStorages ) { snpName = name; this.size = size; @@ -78,7 +78,7 @@ public SnapshotListJobResult( this.incSize = incSize; this.editTime = editTime; - this.extSize = extSize; + this.extStorages = extStorages; } /** */ @@ -112,7 +112,57 @@ public long[] creationTimes() { } /** */ - public @Nullable long[] externalSizes() { - return extSize; + public @Nullable ExtraRecordsData[] externalStorages() { + return extStorages; + } + + /** Additional, optional snapshot data like about incremental snapshots or external storages. */ + public static class ExtraRecordsData extends IgniteDataTransferObject { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** Number of related records or storages. */ + @Order(0) + int cnt; + + /** Total size of related records or storages. */ + @Order(1) + long size; + + /** Optional creation date of related records or storages. */ + @Order(2) + @Nullable Long date; + + /** Empty constructor for serialization purposes. */ + public ExtraRecordsData() { + // No-op. + } + + /** */ + private ExtraRecordsData(int cnt, long size, @Nullable Long date) { + this.cnt = cnt; + this.size = size; + this.date = date; + } + + /** */ + public ExtraRecordsData(int cnt, long size) { + this(cnt, size, null); + } + + /** @return Number of related records or storages. */ + public int number() { + return cnt; + } + + /** @return Total size of related records or storages. */ + public long size() { + return size; + } + + /** @return Optional creation date of related records or storages. */ + public @Nullable Long date() { + return date; + } } } From 06e9e4f5f16f283a97496a0d5e515c3cfe03b5b1 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Tue, 6 Oct 2026 12:49:26 +0300 Subject: [PATCH 69/97] refactor data transfer --- .../snapshot/SnapshotListCommand.java | 41 +++++++------ .../management/snapshot/SnapshotListTask.java | 50 +++++++--------- .../snapshot/SnapshotListJobResult.java | 60 ++++++------------- 3 files changed, 64 insertions(+), 87 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java index f632737fd86aa..a0f65c42551ae 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java @@ -78,6 +78,11 @@ public class SnapshotListCommand extends AbstractSnapshotCommand> locSnps = findLocalSnapshots( @@ -184,11 +181,11 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { sizes[snpIdx] = calculateDirectorySize(sft.root()); // Optional extra storages data. - SnapshotListJobResult.ExtraRecordsData snpExtStors = findExtraStorages(sft); + SnapshotListJobResult.SnapshotExtraInfo snpExtStors = findExtraStorages(sft); if (snpExtStors != null) { if (extraStorages == null) - extraStorages = new SnapshotListJobResult.ExtraRecordsData[locSnps.size()]; + extraStorages = new SnapshotListJobResult.SnapshotExtraInfo[locSnps.size()]; extraStorages[snpIdx] = snpExtStors; @@ -196,18 +193,13 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { } // Optiona incremental snapshots data. - T3 incRes = incrementalsData(sft); - - if (incRes.get1() > 0) { - if (incCnts == null) { - incCnts = new int[locSnps.size()]; - incSizes = new long[locSnps.size()]; - editTimes = new long[locSnps.size()]; - } - - incCnts[snpIdx] = incRes.get1(); - incSizes[snpIdx] = incRes.get2(); - editTimes[snpIdx] = incRes.get3(); + SnapshotListJobResult.SnapshotExtraInfo incRes = incrementalsData(sft); + + if (incRes != null) { + if (incrementalSnps == null) + incrementalSnps = new SnapshotListJobResult.SnapshotExtraInfo[locSnps.size()]; + + incrementalSnps[snpIdx] = incRes; } } } @@ -215,11 +207,11 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { throw new IgniteException("Failed to list local snapshots [src=" + arg.src() + ']', e); } - return new SnapshotListJobResult(snpNames, sizes, creationTimes, incCnts, incSizes, editTimes, extraStorages); + return new SnapshotListJobResult(snpNames, sizes, creationTimes, extraStorages, incrementalSnps); } /** */ - private static @Nullable SnapshotListJobResult.ExtraRecordsData findExtraStorages(SnapshotFileTree sft) throws IOException { + private static @Nullable SnapshotListJobResult.SnapshotExtraInfo findExtraStorages(SnapshotFileTree sft) throws IOException { int extStoragesCnt = 0; long extStoragesSize = 0; @@ -231,7 +223,7 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { extStoragesSize += calculateDirectorySize(es); } - return extStoragesCnt == 0 ? null : new SnapshotListJobResult.ExtraRecordsData(extStoragesCnt, extStoragesSize); + return extStoragesCnt == 0 ? null : new SnapshotListJobResult.SnapshotExtraInfo(extStoragesCnt, extStoragesSize); } /** @return Snapshot file tree and creation time from the snapshot metadata. */ @@ -268,12 +260,12 @@ private List> findLocalSnapshots(@Nullable String cus } /** @return Number, total size and last creation time of incremental snapshots. */ - private T3 incrementalsData(SnapshotFileTree sft) throws IOException { + private @Nullable SnapshotListJobResult.SnapshotExtraInfo incrementalsData(SnapshotFileTree sft) throws IOException { File[] incs = sft.incrementsRoot().listFiles(); - long size = 0L; int cnt = 0; - long intCreateTime = 0L; + long size = 0L; + long createTime = 0L; if (!F.isEmpty(incs)) { IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); @@ -306,11 +298,11 @@ private T3 incrementalsData(SnapshotFileTree sft) throws IO cnt++; size += calculateDirectorySize(incDir); - intCreateTime = Math.max(intCreateTime, incMeta.snapshotTime()); + createTime = Math.max(createTime, incMeta.snapshotTime()); } } - return new T3<>(cnt, size, intCreateTime); + return cnt == 0 ? null : new SnapshotListJobResult.SnapshotExtraInfo(cnt, size, createTime); } } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java index 2e4e1399f9b78..09987ae599d63 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java @@ -19,10 +19,9 @@ import org.apache.ignite.internal.Order; import org.apache.ignite.internal.dto.IgniteDataTransferObject; -import org.apache.ignite.internal.management.snapshot.SnapshotListTask; import org.jetbrains.annotations.Nullable; -/** Accumulated result of {@link SnapshotListTask}. */ +/** Per-node result of the snapshot lists command. Contains information of the snapshots found on current node. */ public final class SnapshotListJobResult extends IgniteDataTransferObject { /** Serial version uid. */ private static final long serialVersionUID = 0L; @@ -39,21 +38,13 @@ public final class SnapshotListJobResult extends IgniteDataTransferObject { @Order(2) long[] creationTime; - /** Numbers of related incremental snapshots. */ + /** Optional information of external snapshot storages. */ @Order(3) - int[] incCnt; + SnapshotExtraInfo[] extStorages; - /** Total sizes of incremental snapshots. */ + /** Optional information of incremental snapshots. */ @Order(4) - long[] incSize; - - /** Last modified times. Actual if {@link #incCnt}[n] > 0. */ - @Order(5) - long[] editTime; - - /** Total sizes of external-storage snapshots. */ - @Order(6) - ExtraRecordsData[] extStorages; + SnapshotExtraInfo[] incrementalSnps; /** Default constructor for serialization purposes. */ public SnapshotListJobResult() { @@ -65,20 +56,16 @@ public SnapshotListJobResult( String[] name, long[] size, long[] createTime, - @Nullable int[] incCnt, - @Nullable long[] incSize, - @Nullable long[] editTime, - @Nullable ExtraRecordsData[] extStorages + @Nullable SnapshotExtraInfo[] extStorages, + @Nullable SnapshotExtraInfo[] incrementalSnps ) { snpName = name; this.size = size; creationTime = createTime; - this.incCnt = incCnt; - this.incSize = incSize; - this.editTime = editTime; - this.extStorages = extStorages; + + this.incrementalSnps = incrementalSnps; } /** */ @@ -97,27 +84,17 @@ public long[] creationTimes() { } /** */ - public @Nullable int[] incrementalsCount() { - return incCnt; - } - - /** */ - public @Nullable long[] incrementalsSizes() { - return incSize; - } - - /** */ - public @Nullable long[] editTimes() { - return editTime; + public @Nullable SnapshotExtraInfo[] incrementalSnapshots() { + return incrementalSnps; } /** */ - public @Nullable ExtraRecordsData[] externalStorages() { + public @Nullable SnapshotExtraInfo[] externalStorages() { return extStorages; } - /** Additional, optional snapshot data like about incremental snapshots or external storages. */ - public static class ExtraRecordsData extends IgniteDataTransferObject { + /** Additional, optional snapshot information like about incremental snapshots or external storages. */ + public static class SnapshotExtraInfo extends IgniteDataTransferObject { /** Serial version uid. */ private static final long serialVersionUID = 0L; @@ -134,20 +111,21 @@ public static class ExtraRecordsData extends IgniteDataTransferObject { @Nullable Long date; /** Empty constructor for serialization purposes. */ - public ExtraRecordsData() { + public SnapshotExtraInfo() { // No-op. } /** */ - private ExtraRecordsData(int cnt, long size, @Nullable Long date) { + public SnapshotExtraInfo(int cnt, long size, long date) { this.cnt = cnt; this.size = size; this.date = date; } /** */ - public ExtraRecordsData(int cnt, long size) { - this(cnt, size, null); + public SnapshotExtraInfo(int cnt, long size) { + this.cnt = cnt; + this.size = size; } /** @return Number of related records or storages. */ From 5a3d30941c3acd532c200e70837b317662696230 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Tue, 6 Oct 2026 13:32:55 +0300 Subject: [PATCH 70/97] refactor data transfer --- .../snapshot/SnapshotListCommand.java | 28 ++--- .../management/snapshot/SnapshotListTask.java | 49 ++++---- .../snapshot/SnapshotListJobResult.java | 116 +++++++++--------- 3 files changed, 93 insertions(+), 100 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java index a0f65c42551ae..addb584a35263 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java @@ -72,32 +72,30 @@ public class SnapshotListCommand extends AbstractSnapshotCommand> locSnps = findLocalSnapshots( @@ -167,37 +163,38 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { ignite.context().cache().context().snapshotMgr().localSnapshotNames(arg.src()) ); - snpNames = new String[locSnps.size()]; - sizes = new long[locSnps.size()]; - creationTimes = new long[locSnps.size()]; + snapshots = new SnapshotListJobResult.SnapshotInfo[locSnps.size()]; for (int snpIdx = 0; snpIdx < locSnps.size(); snpIdx++) { SnapshotFileTree sft = locSnps.get(snpIdx).get1(); - // Main snapshot data. - snpNames[snpIdx] = sft.name(); - creationTimes[snpIdx] = locSnps.get(snpIdx).get2(); - - sizes[snpIdx] = calculateDirectorySize(sft.root()); + long extSize = 0L; // Optional extra storages data. - SnapshotListJobResult.SnapshotExtraInfo snpExtStors = findExtraStorages(sft); + SnapshotListJobResult.SnapshotInfo snpExtStors = findExtraStorages(sft); if (snpExtStors != null) { if (extraStorages == null) - extraStorages = new SnapshotListJobResult.SnapshotExtraInfo[locSnps.size()]; + extraStorages = new SnapshotListJobResult.SnapshotInfo[locSnps.size()]; extraStorages[snpIdx] = snpExtStors; - sizes[snpIdx] += snpExtStors.size(); + extSize += snpExtStors.size(); } + // Main snapshot data. + snapshots[snpIdx] = new SnapshotListJobResult.SnapshotInfo( + sft.name(), + calculateDirectorySize(sft.root()) + extSize, + locSnps.get(snpIdx).get2() + ); + // Optiona incremental snapshots data. - SnapshotListJobResult.SnapshotExtraInfo incRes = incrementalsData(sft); + SnapshotListJobResult.SnapshotInfo incRes = incrementalsData(sft); if (incRes != null) { if (incrementalSnps == null) - incrementalSnps = new SnapshotListJobResult.SnapshotExtraInfo[locSnps.size()]; + incrementalSnps = new SnapshotListJobResult.SnapshotInfo[locSnps.size()]; incrementalSnps[snpIdx] = incRes; } @@ -207,11 +204,11 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { throw new IgniteException("Failed to list local snapshots [src=" + arg.src() + ']', e); } - return new SnapshotListJobResult(snpNames, sizes, creationTimes, extraStorages, incrementalSnps); + return new SnapshotListJobResult(snapshots, extraStorages, incrementalSnps); } /** */ - private static @Nullable SnapshotListJobResult.SnapshotExtraInfo findExtraStorages(SnapshotFileTree sft) throws IOException { + private static @Nullable SnapshotListJobResult.SnapshotInfo findExtraStorages(SnapshotFileTree sft) throws IOException { int extStoragesCnt = 0; long extStoragesSize = 0; @@ -223,7 +220,7 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { extStoragesSize += calculateDirectorySize(es); } - return extStoragesCnt == 0 ? null : new SnapshotListJobResult.SnapshotExtraInfo(extStoragesCnt, extStoragesSize); + return extStoragesCnt == 0 ? null : new SnapshotListJobResult.SnapshotInfo(extStoragesCnt, extStoragesSize); } /** @return Snapshot file tree and creation time from the snapshot metadata. */ @@ -260,7 +257,7 @@ private List> findLocalSnapshots(@Nullable String cus } /** @return Number, total size and last creation time of incremental snapshots. */ - private @Nullable SnapshotListJobResult.SnapshotExtraInfo incrementalsData(SnapshotFileTree sft) throws IOException { + private @Nullable SnapshotListJobResult.SnapshotInfo incrementalsData(SnapshotFileTree sft) throws IOException { File[] incs = sft.incrementsRoot().listFiles(); int cnt = 0; @@ -302,7 +299,7 @@ private List> findLocalSnapshots(@Nullable String cus } } - return cnt == 0 ? null : new SnapshotListJobResult.SnapshotExtraInfo(cnt, size, createTime); + return cnt == 0 ? null : new SnapshotListJobResult.SnapshotInfo(cnt, size, createTime); } } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java index 09987ae599d63..a8a531fcfa99d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java @@ -26,25 +26,17 @@ public final class SnapshotListJobResult extends IgniteDataTransferObject { /** Serial version uid. */ private static final long serialVersionUID = 0L; - /** Snapshot names. */ + /** Snapshot main information (names, sizes, etc.). */ @Order(0) - String[] snpName; + SnapshotInfo[] snapshots; - /** Snapshot sizes. */ + /** Optional information of external snapshots storages. */ @Order(1) - long[] size; + SnapshotInfo[] extStorages; - /** Creation times. */ + /** Optional information of incrementals snapshots. */ @Order(2) - long[] creationTime; - - /** Optional information of external snapshot storages. */ - @Order(3) - SnapshotExtraInfo[] extStorages; - - /** Optional information of incremental snapshots. */ - @Order(4) - SnapshotExtraInfo[] incrementalSnps; + SnapshotInfo[] incrementalSnps; /** Default constructor for serialization purposes. */ public SnapshotListJobResult() { @@ -53,94 +45,100 @@ public SnapshotListJobResult() { /** */ public SnapshotListJobResult( - String[] name, - long[] size, - long[] createTime, - @Nullable SnapshotExtraInfo[] extStorages, - @Nullable SnapshotExtraInfo[] incrementalSnps + SnapshotInfo[] snapshots, + @Nullable SnapshotInfo[] extStorages, + @Nullable SnapshotInfo[] incrementalSnps ) { - snpName = name; - this.size = size; - creationTime = createTime; - + this.snapshots = snapshots; this.extStorages = extStorages; - this.incrementalSnps = incrementalSnps; } - /** */ - public String[] snapshotNames() { - return snpName; + /** @return The snapshots main information (names, sizes, etc.). */ + public @Nullable SnapshotInfo[] snapshots() { + return snapshots; } - /** */ - public long[] sizes() { - return size; - } - - /** */ - public long[] creationTimes() { - return creationTime; + /** @return Optional information of external snapshots storages. */ + public @Nullable SnapshotInfo[] externalStorages() { + return extStorages; } - /** */ - public @Nullable SnapshotExtraInfo[] incrementalSnapshots() { + /** @return Optional information of incrementals snapshots. */ + public @Nullable SnapshotInfo[] incrementalSnapshots() { return incrementalSnps; } - /** */ - public @Nullable SnapshotExtraInfo[] externalStorages() { - return extStorages; - } - - /** Additional, optional snapshot information like about incremental snapshots or external storages. */ - public static class SnapshotExtraInfo extends IgniteDataTransferObject { + /** Hold combined snapshot data information: name, size, creation/modification time, incremental parts, external storages. */ + public static class SnapshotInfo extends IgniteDataTransferObject { /** Serial version uid. */ private static final long serialVersionUID = 0L; - /** Number of related records or storages. */ + /** Snapshot name. Is {@code null} for its external storages or incremental parts. */ @Order(0) - int cnt; + @Nullable String name; - /** Total size of related records or storages. */ + /** Total size of a snapshot. Or size of its external storages or incremental parts. */ @Order(1) long size; - /** Optional creation date of related records or storages. */ + /** Number of snapshot external storages or incremental parts. Is {@code null} for the main snapshot data. */ @Order(2) + @Nullable Integer cnt; + + /** + * Creation or modification date of snapshot or of its incremental parts. + * Is {@code null} for snapshot external storages' data. + */ + @Order(3) @Nullable Long date; /** Empty constructor for serialization purposes. */ - public SnapshotExtraInfo() { + public SnapshotInfo() { // No-op. } - /** */ - public SnapshotExtraInfo(int cnt, long size, long date) { - this.cnt = cnt; + /** Creates snapshot main information. */ + public SnapshotInfo(String name, long size, long date) { + this.name = name; this.size = size; this.date = date; } - /** */ - public SnapshotExtraInfo(int cnt, long size) { + /** Creates external storages' information. */ + public SnapshotInfo(int cnt, long size) { this.cnt = cnt; this.size = size; } - /** @return Number of related records or storages. */ - public int number() { - return cnt; + /** Creates incremental parts information. */ + public SnapshotInfo(int cnt, long size, long date) { + this.cnt = cnt; + this.size = size; + this.date = date; + } + + /** @return Snapshot name. Or {@code null} for its external storages or incremental parts. */ + public @Nullable String name() { + return name; } - /** @return Total size of related records or storages. */ + /** @return Total size of a snapshot. Or size of its external storages or incremental parts. */ public long size() { return size; } - /** @return Optional creation date of related records or storages. */ + /** + * @return Creation or modification date of a snapshot or of its incremental parts. Or {@code null} for snapshot + * external storages' data. + */ public @Nullable Long date() { return date; } + + /** @return Number of snapshot external storages or incremental parts. Or {@code null} for the main snapshot data.*/ + public @Nullable Integer number() { + return cnt; + } } } From 5dabb976acce311409119cdc15818ba8dc30358b Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Tue, 6 Oct 2026 13:50:10 +0300 Subject: [PATCH 71/97] yet more refactor data transfer --- .../snapshot/SnapshotListCommand.java | 56 +++++---------- .../management/snapshot/SnapshotListTask.java | 43 +++-------- .../snapshot/SnapshotListJobResult.java | 72 ++++++++++--------- 3 files changed, 67 insertions(+), 104 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java index addb584a35263..ff018ebfa37de 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java @@ -78,52 +78,34 @@ public class SnapshotListCommand extends AbstractSnapshotCommand> locSnps = findLocalSnapshots( @@ -163,48 +158,32 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { ignite.context().cache().context().snapshotMgr().localSnapshotNames(arg.src()) ); - snapshots = new SnapshotListJobResult.SnapshotInfo[locSnps.size()]; + snpInfos = new SnapshotListJobResult.SnapshotInfo[locSnps.size()]; for (int snpIdx = 0; snpIdx < locSnps.size(); snpIdx++) { SnapshotFileTree sft = locSnps.get(snpIdx).get1(); - long extSize = 0L; - // Optional extra storages data. - SnapshotListJobResult.SnapshotInfo snpExtStors = findExtraStorages(sft); - - if (snpExtStors != null) { - if (extraStorages == null) - extraStorages = new SnapshotListJobResult.SnapshotInfo[locSnps.size()]; - - extraStorages[snpIdx] = snpExtStors; + SnapshotListJobResult.SnapshotInfo extStors = findExtraStorages(sft); - extSize += snpExtStors.size(); - } + // Optiona incremental snapshots data. + SnapshotListJobResult.SnapshotInfo incs = incrementalsData(sft); // Main snapshot data. - snapshots[snpIdx] = new SnapshotListJobResult.SnapshotInfo( + snpInfos[snpIdx] = new SnapshotListJobResult.SnapshotInfo( sft.name(), - calculateDirectorySize(sft.root()) + extSize, - locSnps.get(snpIdx).get2() + calculateDirectorySize(sft.root()), + locSnps.get(snpIdx).get2(), + extStors, + incs ); - - // Optiona incremental snapshots data. - SnapshotListJobResult.SnapshotInfo incRes = incrementalsData(sft); - - if (incRes != null) { - if (incrementalSnps == null) - incrementalSnps = new SnapshotListJobResult.SnapshotInfo[locSnps.size()]; - - incrementalSnps[snpIdx] = incRes; - } } } catch (Exception e) { throw new IgniteException("Failed to list local snapshots [src=" + arg.src() + ']', e); } - return new SnapshotListJobResult(snapshots, extraStorages, incrementalSnps); + return new SnapshotListJobResult(snpInfos); } /** */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java index a8a531fcfa99d..8bfa2cb0feb41 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java @@ -26,49 +26,25 @@ public final class SnapshotListJobResult extends IgniteDataTransferObject { /** Serial version uid. */ private static final long serialVersionUID = 0L; - /** Snapshot main information (names, sizes, etc.). */ + /** Local node's snapshot descriptions. */ @Order(0) SnapshotInfo[] snapshots; - /** Optional information of external snapshots storages. */ - @Order(1) - SnapshotInfo[] extStorages; - - /** Optional information of incrementals snapshots. */ - @Order(2) - SnapshotInfo[] incrementalSnps; - /** Default constructor for serialization purposes. */ public SnapshotListJobResult() { // No-op. } /** */ - public SnapshotListJobResult( - SnapshotInfo[] snapshots, - @Nullable SnapshotInfo[] extStorages, - @Nullable SnapshotInfo[] incrementalSnps - ) { + public SnapshotListJobResult(SnapshotInfo[] snapshots) { this.snapshots = snapshots; - this.extStorages = extStorages; - this.incrementalSnps = incrementalSnps; } /** @return The snapshots main information (names, sizes, etc.). */ - public @Nullable SnapshotInfo[] snapshots() { + public SnapshotInfo[] snapshots() { return snapshots; } - /** @return Optional information of external snapshots storages. */ - public @Nullable SnapshotInfo[] externalStorages() { - return extStorages; - } - - /** @return Optional information of incrementals snapshots. */ - public @Nullable SnapshotInfo[] incrementalSnapshots() { - return incrementalSnps; - } - /** Hold combined snapshot data information: name, size, creation/modification time, incremental parts, external storages. */ public static class SnapshotInfo extends IgniteDataTransferObject { /** Serial version uid. */ @@ -82,27 +58,43 @@ public static class SnapshotInfo extends IgniteDataTransferObject { @Order(1) long size; - /** Number of snapshot external storages or incremental parts. Is {@code null} for the main snapshot data. */ - @Order(2) - @Nullable Integer cnt; - /** * Creation or modification date of snapshot or of its incremental parts. * Is {@code null} for snapshot external storages' data. */ - @Order(3) + @Order(2) @Nullable Long date; + /** Snapshot external storages' information. Is {@code null} for not the main snapshot data. */ + @Order(3) + @Nullable SnapshotInfo extStors; + + /** Snapshot incremental parts information. Is {@code null} for not the main snapshot data. */ + @Order(4) + @Nullable SnapshotInfo incs; + + /** Number of snapshot external storages or incremental parts. Is {@code null} for the main snapshot data. */ + @Order(5) + @Nullable Integer cnt; + /** Empty constructor for serialization purposes. */ public SnapshotInfo() { // No-op. } /** Creates snapshot main information. */ - public SnapshotInfo(String name, long size, long date) { + public SnapshotInfo( + String name, + long size, + long date, + @Nullable SnapshotInfo extStors, + @Nullable SnapshotInfo incs + ) { this.name = name; - this.size = size; + this.size = size + (extStors == null ? 0L : extStors.size()); this.date = date; + this.extStors = extStors; + this.incs = incs; } /** Creates external storages' information. */ @@ -129,13 +121,23 @@ public long size() { } /** - * @return Creation or modification date of a snapshot or of its incremental parts. Or {@code null} for snapshot + * @return Creation date of a snapshot or of its incremental parts. Or {@code null} for snapshot * external storages' data. */ public @Nullable Long date() { return date; } + /** @return Snapshot external storages' information. Is {@code null} for not the main snapshot information. */ + public @Nullable SnapshotInfo externalStorages() { + return extStors; + } + + /** @return Snapshot incremental parts information. Is {@code null} for not the main snapshot information. */ + public @Nullable SnapshotInfo incrementals() { + return incs; + } + /** @return Number of snapshot external storages or incremental parts. Or {@code null} for the main snapshot data.*/ public @Nullable Integer number() { return cnt; From d0f9ebd16869fdf0298d4e5d7d91da5e9da9dc34 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Tue, 6 Oct 2026 14:35:56 +0300 Subject: [PATCH 72/97] descriptions to a map --- .../snapshot/SnapshotListCommand.java | 35 ++++++------ .../management/snapshot/SnapshotListTask.java | 19 ++++--- .../snapshot/SnapshotListJobResult.java | 57 +++++++------------ 3 files changed, 51 insertions(+), 60 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java index ff018ebfa37de..c0f6b762bebd7 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java @@ -70,43 +70,44 @@ public class SnapshotListCommand extends AbstractSnapshotCommand { printer.accept("\t\tSnapshot '%s': totalSize=%s (%db), created='%s' (epoch=%d)".formatted( - snpInfo.name(), + snpName, U.humanReadableByteCount(snpInfo.size()), snpInfo.size(), DATE_FORMATTER.format(Instant.ofEpochMilli(snpInfo.date())), snpInfo.date() )); - if (snpInfo.externalStorages() != null) { + SnapshotListJobResult.SnapshotInfo extStors = snpInfo.externalStorages(); + SnapshotListJobResult.SnapshotInfo incs = snpInfo.incrementals(); + + if (extStors != null) { printer.accept("\t\t\texternal storages: cnt=%d, size=%s (%db)".formatted( - snpInfo.externalStorages().number(), - U.humanReadableByteCount(snpInfo.externalStorages().size()), - snpInfo.externalStorages().size() + extStors.number(), + U.humanReadableByteCount(extStors.size()), + extStors.size() )); } - if (snpInfo.incrementals() != null) { + if (incs != null) { printer.accept("\t\t\tincremental snapshots: cnt=%d, size=%s (%db), modified='%s' (epoch=%d)".formatted( - snpInfo.incrementals().number(), - U.humanReadableByteCount(snpInfo.incrementals().size()), - snpInfo.incrementals().size(), - DATE_FORMATTER.format(Instant.ofEpochMilli(snpInfo.incrementals().date())), - snpInfo.incrementals().date() + incs.number(), + U.humanReadableByteCount(incs.size()), + incs.size(), + DATE_FORMATTER.format(Instant.ofEpochMilli(incs.date())), + incs.date() )); } - } + }); } // Drop a line. diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java index 138f4abb2bbe0..577a765e09572 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java @@ -28,6 +28,7 @@ import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.UUID; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; @@ -44,6 +45,7 @@ import org.apache.ignite.internal.processors.task.GridInternal; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.T2; +import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.internal.visor.VisorJob; import org.apache.ignite.internal.visor.VisorMultiNodeTask; import org.apache.ignite.internal.visor.VisorTaskArgument; @@ -150,7 +152,7 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { @Override protected SnapshotListJobResult run(SnapshotListCommandArg arg) { assert !ignite.localNode().isClient(); - SnapshotListJobResult.SnapshotInfo[] snpInfos; + Map snpDescsRes; try { List> locSnps = findLocalSnapshots( @@ -158,32 +160,33 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { ignite.context().cache().context().snapshotMgr().localSnapshotNames(arg.src()) ); - snpInfos = new SnapshotListJobResult.SnapshotInfo[locSnps.size()]; + snpDescsRes = U.newHashMap(locSnps.size()); for (int snpIdx = 0; snpIdx < locSnps.size(); snpIdx++) { SnapshotFileTree sft = locSnps.get(snpIdx).get1(); - // Optional extra storages data. + // Optional extra storages description. SnapshotListJobResult.SnapshotInfo extStors = findExtraStorages(sft); - // Optiona incremental snapshots data. + // Optiona incremental snapshots description. SnapshotListJobResult.SnapshotInfo incs = incrementalsData(sft); - // Main snapshot data. - snpInfos[snpIdx] = new SnapshotListJobResult.SnapshotInfo( - sft.name(), + // Main snapshot description. + SnapshotListJobResult.SnapshotInfo snpDesc = new SnapshotListJobResult.SnapshotInfo( calculateDirectorySize(sft.root()), locSnps.get(snpIdx).get2(), extStors, incs ); + + snpDescsRes.put(sft.name(), snpDesc); } } catch (Exception e) { throw new IgniteException("Failed to list local snapshots [src=" + arg.src() + ']', e); } - return new SnapshotListJobResult(snpInfos); + return new SnapshotListJobResult(snpDescsRes); } /** */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java index 8bfa2cb0feb41..295b22b8fe927 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java @@ -17,6 +17,7 @@ package org.apache.ignite.internal.processors.cache.persistence.snapshot; +import java.util.Map; import org.apache.ignite.internal.Order; import org.apache.ignite.internal.dto.IgniteDataTransferObject; import org.jetbrains.annotations.Nullable; @@ -28,7 +29,7 @@ public final class SnapshotListJobResult extends IgniteDataTransferObject { /** Local node's snapshot descriptions. */ @Order(0) - SnapshotInfo[] snapshots; + Map snapshots; /** Default constructor for serialization purposes. */ public SnapshotListJobResult() { @@ -36,45 +37,38 @@ public SnapshotListJobResult() { } /** */ - public SnapshotListJobResult(SnapshotInfo[] snapshots) { + public SnapshotListJobResult(Map snapshots) { this.snapshots = snapshots; } - /** @return The snapshots main information (names, sizes, etc.). */ - public SnapshotInfo[] snapshots() { + /** @return The snapshots descriptions. */ + public Map snapshots() { return snapshots; } - /** Hold combined snapshot data information: name, size, creation/modification time, incremental parts, external storages. */ + /** Holds combined snapshot data information: size, creation time, number of incremental parts or external storages. */ public static class SnapshotInfo extends IgniteDataTransferObject { /** Serial version uid. */ private static final long serialVersionUID = 0L; - /** Snapshot name. Is {@code null} for its external storages or incremental parts. */ - @Order(0) - @Nullable String name; - /** Total size of a snapshot. Or size of its external storages or incremental parts. */ - @Order(1) + @Order(0) long size; - /** - * Creation or modification date of snapshot or of its incremental parts. - * Is {@code null} for snapshot external storages' data. - */ - @Order(2) + /** Creation date of snapshot or of its incremental parts. Is {@code null} for snapshot external storages description. */ + @Order(1) @Nullable Long date; - /** Snapshot external storages' information. Is {@code null} for not the main snapshot data. */ - @Order(3) + /** Snapshot external storages description if exists. Is always {@code null} for not the main snapshot description. */ + @Order(2) @Nullable SnapshotInfo extStors; - /** Snapshot incremental parts information. Is {@code null} for not the main snapshot data. */ - @Order(4) + /** Snapshot incremental parts description if exists. Is always {@code null} for not the main snapshot description. */ + @Order(3) @Nullable SnapshotInfo incs; - /** Number of snapshot external storages or incremental parts. Is {@code null} for the main snapshot data. */ - @Order(5) + /** Number of snapshot external storages or incremental parts. Is {@code null} for the main snapshot description. */ + @Order(4) @Nullable Integer cnt; /** Empty constructor for serialization purposes. */ @@ -82,58 +76,51 @@ public SnapshotInfo() { // No-op. } - /** Creates snapshot main information. */ + /** Creates snapshot main description. */ public SnapshotInfo( - String name, long size, long date, @Nullable SnapshotInfo extStors, @Nullable SnapshotInfo incs ) { - this.name = name; this.size = size + (extStors == null ? 0L : extStors.size()); this.date = date; this.extStors = extStors; this.incs = incs; } - /** Creates external storages' information. */ + /** Creates external storages description. */ public SnapshotInfo(int cnt, long size) { this.cnt = cnt; this.size = size; } - /** Creates incremental parts information. */ + /** Creates incremental parts description. */ public SnapshotInfo(int cnt, long size, long date) { this.cnt = cnt; this.size = size; this.date = date; } - /** @return Snapshot name. Or {@code null} for its external storages or incremental parts. */ - public @Nullable String name() { - return name; - } - /** @return Total size of a snapshot. Or size of its external storages or incremental parts. */ public long size() { return size; } /** - * @return Creation date of a snapshot or of its incremental parts. Or {@code null} for snapshot - * external storages' data. + * @return Creation date of a snapshot or of its incremental parts. {@code Null} for snapshot + * external storages description. */ public @Nullable Long date() { return date; } - /** @return Snapshot external storages' information. Is {@code null} for not the main snapshot information. */ + /** @return Snapshot external storages description if exists. Is always {@code Null} for not the main snapshot description. */ public @Nullable SnapshotInfo externalStorages() { return extStors; } - /** @return Snapshot incremental parts information. Is {@code null} for not the main snapshot information. */ + /** @return Snapshot incremental parts description if exists. Is always {@code Nul} for not the main snapshot description. */ public @Nullable SnapshotInfo incrementals() { return incs; } From acda20802606d15ee0be06d0c157e34b23d73790 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Tue, 6 Oct 2026 15:12:30 +0300 Subject: [PATCH 73/97] extra storages test fix --- .../GridCommandHandlerListSnapshotTest.java | 44 ++++++++++--------- .../management/snapshot/SnapshotListTask.java | 11 ++--- 2 files changed, 29 insertions(+), 26 deletions(-) diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java index c83fb785336ff..b049ae50aff9b 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java @@ -180,7 +180,7 @@ public void testExtraStorages() throws Exception { /** */ private void doTestSnapshotsLists(int snpCnt, boolean deleteOnOneNode) throws Exception { - filterTests(snpCnt, deleteOnOneNode, extraStorages); + filterTests(snpCnt, deleteOnOneNode); int srvrsCnt = 3; int entriesCnt = 20; @@ -281,27 +281,14 @@ private void doTestSnapshotsLists(int snpCnt, boolean deleteOnOneNode) throws Ex for (int i = 0; i < incCnt; i++) assertEquals(certainSnpRecordsCnt * snpCnt, countEntries(out, "incremental snapshots: cnt=" + incCnt)); - } - } - - - - /** */ - @Override protected CacheConfiguration testCacheConfiguration( - String cacheName, - int partitions, - @Nullable IgnitePredicate filter - ) { - CacheConfiguration ccfg = super.testCacheConfiguration(cacheName, partitions, filter); - if (extraStorages) - ccfg.setStoragePaths(extStoragePaths); - - return ccfg; + if (extraStorages) + assertEquals(certainSnpRecordsCnt * snpCnt, countEntries(out, "external storages: cnt=1, size=")); + } } - /** */ - private void filterTests(int snpCnt, boolean deleteOnOneNode, boolean extraStorages) { + /** Excludes incompatible parameters values and fastens the tests. */ + private void filterTests(int snpCnt, boolean deleteOnOneNode) { assert !deleteOnOneNode || snpCnt > 0; assert !extraStorages || snpCnt > 0; @@ -315,8 +302,9 @@ private void filterTests(int snpCnt, boolean deleteOnOneNode, boolean extraStora // No need to create incremental snapshots if no snapshots are required at all. assumeTrue(incCnt < 1 || snpCnt > 0); - // TODO: comment - assumeFalse(extraStorages && separatedWorkDir); + // Extra storeages are required to be the same as configured in the node's PDS storages. Thus, we skip different work folders. + // Also, extra snapshot storages aren't used if snapshot is created with a custom path. + assumeFalse(extraStorages && (separatedWorkDir || customPath)); } /** Counts occurrences of the node prefix in the output. */ @@ -340,4 +328,18 @@ private static int countEntries(String txt, String entry) { return (prev.length() - txt.length()) / entry.length(); } + + /** */ + @Override protected CacheConfiguration testCacheConfiguration( + String cacheName, + int partitions, + @Nullable IgnitePredicate filter + ) { + CacheConfiguration ccfg = super.testCacheConfiguration(cacheName, partitions, filter); + + if (extraStorages) + ccfg.setStoragePaths(extStoragePaths); + + return ccfg; + } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java index 577a765e09572..9ad5dde22ab9b 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java @@ -165,11 +165,12 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { for (int snpIdx = 0; snpIdx < locSnps.size(); snpIdx++) { SnapshotFileTree sft = locSnps.get(snpIdx).get1(); - // Optional extra storages description. - SnapshotListJobResult.SnapshotInfo extStors = findExtraStorages(sft); + // Optional extra storages description. Uses another snapshot file tree which ignores the path and + // can find snapshot extra storages. + SnapshotListJobResult.SnapshotInfo extStors = externalStorages(sft); // Optiona incremental snapshots description. - SnapshotListJobResult.SnapshotInfo incs = incrementalsData(sft); + SnapshotListJobResult.SnapshotInfo incs = incrementals(sft); // Main snapshot description. SnapshotListJobResult.SnapshotInfo snpDesc = new SnapshotListJobResult.SnapshotInfo( @@ -190,7 +191,7 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { } /** */ - private static @Nullable SnapshotListJobResult.SnapshotInfo findExtraStorages(SnapshotFileTree sft) throws IOException { + private static @Nullable SnapshotListJobResult.SnapshotInfo externalStorages(SnapshotFileTree sft) throws IOException { int extStoragesCnt = 0; long extStoragesSize = 0; @@ -239,7 +240,7 @@ private List> findLocalSnapshots(@Nullable String cus } /** @return Number, total size and last creation time of incremental snapshots. */ - private @Nullable SnapshotListJobResult.SnapshotInfo incrementalsData(SnapshotFileTree sft) throws IOException { + private @Nullable SnapshotListJobResult.SnapshotInfo incrementals(SnapshotFileTree sft) throws IOException { File[] incs = sft.incrementsRoot().listFiles(); int cnt = 0; From 001326324e1b350ac74f7d06f0fd3d378f54d75c Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Tue, 6 Oct 2026 16:13:50 +0300 Subject: [PATCH 74/97] major test fixes. + changed consistent id --- .../GridCommandHandlerListSnapshotTest.java | 104 ++++++++++-------- .../management/snapshot/SnapshotListTask.java | 2 - .../snapshot/SnapshotMetadata.java | 3 +- 3 files changed, 61 insertions(+), 48 deletions(-) diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java index b049ae50aff9b..c9c1ad4ab7af5 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java @@ -26,6 +26,7 @@ import org.apache.ignite.IgniteDataStreamer; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.configuration.CacheConfiguration; +import org.apache.ignite.configuration.DataStorageConfiguration; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.management.snapshot.SnapshotListCommand; @@ -45,35 +46,37 @@ import static org.apache.ignite.internal.commandline.CommandHandler.EXIT_CODE_OK; import static org.apache.ignite.internal.processors.cache.persistence.snapshot.AbstractSnapshotSelfTest.snp; import static org.junit.Assume.assumeFalse; -import static org.junit.Assume.assumeTrue; /** Test for the command '--snapshot list'. */ public class GridCommandHandlerListSnapshotTest extends GridCommandHandlerAbstractTest { /** Extra storage path. */ private static final String EXT_STORAGE_PATH = "extStorage"; - /** */ + /** Flag to use {@link DataStorageConfiguration#setExtraSnapshotPaths(String...)}. */ + private boolean extraStorages; + + /** Resolved extra storages paths. {@code null} if {@code extraStorages} is {@code null}. */ + private @Nullable String[] extStoragePaths; + + /** Node consistent id postfix. */ + private @Nullable String cstId_postfix = ""; + + /** Flag setting the usage of a custom snapshot path. */ @Parameter(1) public boolean customPath; - /** */ + /** Flag setting the usage of dedicated, own node working directories. */ @Parameter(2) public boolean separatedWorkDir; - /** */ + /** Flag to add extra server node after the snapshot creation. */ @Parameter(3) public boolean addExtraSrvr; - /** */ + /** Number of incremental snapshots to add to the main test snapshots. */ @Parameter(4) public int incCnt; - /** */ - private boolean extraStorages; - - /** */ - private @Nullable String[] extStoragePaths; - /** */ @Parameters(name = "cmdHnd={0},customPath={1},ownWorkDir={2},addExtraSrvr={3},incCnt={4}") public static Collection parameters() { @@ -140,47 +143,70 @@ public static Collection parameters() { return cfg; } + /** {@inheritDoc} */ + @Override public String getTestIgniteInstanceName() { + return super.getTestIgniteInstanceName() + cstId_postfix; + } + /** */ @Test public void testNoSnapshots() throws Exception { - // Doesn't matter here. - assumeFalse(incCnt > 0 || addExtraSrvr); + assumeFalse(addExtraSrvr || incCnt > 0); - doTestSnapshotsLists(0, false); + doTestSnapshotsLists(0, false, false); } /** */ @Test public void testSingleSnapshot() throws Exception { - doTestSnapshotsLists(1, false); + doTestSnapshotsLists(1, false, false); } /** */ @Test public void testSeveralSnapshots() throws Exception { - doTestSnapshotsLists(4, false); + doTestSnapshotsLists(4, false, false); } /** */ @Test - public void testSeveralSnapshotsOneNodeMisses() throws Exception { + public void testOneNodeMisses() throws Exception { // Doesn't matter here. assumeFalse(incCnt > 0); + // Let's keep just one node not seeing the snapshot. + assumeFalse(separatedWorkDir); - doTestSnapshotsLists(2, true); + doTestSnapshotsLists(2, true, false); } /** */ @Test public void testExtraStorages() throws Exception { + // Extra storeages are required to be the same as configured in the node's PDS storages. Thus, we skip different work folders. + // Also, extra snapshot storages aren't used if snapshot is created with a custom path. + assumeFalse(separatedWorkDir || customPath); + extraStorages = true; - doTestSnapshotsLists(2, false); + doTestSnapshotsLists(2, false, false); } /** */ - private void doTestSnapshotsLists(int snpCnt, boolean deleteOnOneNode) throws Exception { - filterTests(snpCnt, deleteOnOneNode); + @Test + public void testChangedConsistentId() throws Exception { + // In new created working directories there will be obviously no snapshots. + assumeFalse(separatedWorkDir); + // Fastens the tests + assumeFalse(incCnt > 0); + + doTestSnapshotsLists(1, false, true); + } + + /** */ + private void doTestSnapshotsLists(int snpCnt, boolean deleteOnOneNode, boolean restartWithChangedCstIds) throws Exception { + // A custom snapshot path actually puts snapshots in a shared directory. This skews the results when dedicated + // work directories are set. + assumeFalse(customPath && separatedWorkDir); int srvrsCnt = 3; int entriesCnt = 20; @@ -192,7 +218,7 @@ private void doTestSnapshotsLists(int snpCnt, boolean deleteOnOneNode) throws Ex ig.cluster().state(ACTIVE); File snpsRootFile = customPath - ? new File(grid(0).context().pdsFolderResolver().fileTree().snapshotsRoot(), "ex_snapshots") + ? new File(ig.context().pdsFolderResolver().fileTree().snapshotsRoot(), "ex_snapshots") : null; // Flag if 'testSnapshot0' deleted on node0. @@ -210,7 +236,7 @@ private void doTestSnapshotsLists(int snpCnt, boolean deleteOnOneNode) throws Ex snp(ig).createSnapshot("testSnapshot" + snpIdx, absPathStr, false, false).get(getTestTimeout()); for (int incIdx = 0; incIdx < incCnt; incIdx++) { - try (IgniteDataStreamer ds = grid(0).dataStreamer(DEFAULT_CACHE_NAME)) { + try (IgniteDataStreamer ds = ig.dataStreamer(DEFAULT_CACHE_NAME)) { for (int val = (snpIdx + incIdx + 1) * entriesCnt; val < (snpIdx + incIdx + 2) * entriesCnt; val++) ds.addData(val, val); } @@ -220,7 +246,7 @@ private void doTestSnapshotsLists(int snpCnt, boolean deleteOnOneNode) throws Ex } if (deleteOnOneNode) { - SnapshotFileTree sft = new SnapshotFileTree(grid(0).context(), "testSnapshot0", absPathStr); + SnapshotFileTree sft = new SnapshotFileTree(ig.context(), "testSnapshot0", absPathStr); assertTrue(sft.root().exists()); assertTrue(U.delete(sft.root())); @@ -230,6 +256,16 @@ private void doTestSnapshotsLists(int snpCnt, boolean deleteOnOneNode) throws Ex } } + if (restartWithChangedCstIds) { + stopAllGrids(); + + cstId_postfix = "_changed"; + + startGridsMultiThreaded(srvrsCnt); + + startGrid(CLIENT_NODE_NAME_PREFIX); + } + // Add a server. if (addExtraSrvr) startGrid(G.allGrids().size()); @@ -287,26 +323,6 @@ private void doTestSnapshotsLists(int snpCnt, boolean deleteOnOneNode) throws Ex } } - /** Excludes incompatible parameters values and fastens the tests. */ - private void filterTests(int snpCnt, boolean deleteOnOneNode) { - assert !deleteOnOneNode || snpCnt > 0; - assert !extraStorages || snpCnt > 0; - - // A custom snapshot path actually puts snapshots in a shared directory. This skews the results when dedicated - // work directories are set. - assumeFalse(customPath && separatedWorkDir); - - // Let's keep just one node doesn't see the snapshot. - assumeTrue(!deleteOnOneNode || separatedWorkDir); - - // No need to create incremental snapshots if no snapshots are required at all. - assumeTrue(incCnt < 1 || snpCnt > 0); - - // Extra storeages are required to be the same as configured in the node's PDS storages. Thus, we skip different work folders. - // Also, extra snapshot storages aren't used if snapshot is created with a custom path. - assumeFalse(extraStorages && (separatedWorkDir || customPath)); - } - /** Counts occurrences of the node prefix in the output. */ private int countNodeOccurrences(String output) { int cnt = 0; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java index 9ad5dde22ab9b..ce991b42c4e43 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java @@ -165,8 +165,6 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { for (int snpIdx = 0; snpIdx < locSnps.size(); snpIdx++) { SnapshotFileTree sft = locSnps.get(snpIdx).get1(); - // Optional extra storages description. Uses another snapshot file tree which ignores the path and - // can find snapshot extra storages. SnapshotListJobResult.SnapshotInfo extStors = externalStorages(sft); // Optiona incremental snapshots description. diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMetadata.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMetadata.java index 4f9a8d2093640..b09d0d798c7cb 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMetadata.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMetadata.java @@ -28,7 +28,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Random; import java.util.Set; import java.util.UUID; import org.apache.ignite.internal.Order; @@ -187,7 +186,7 @@ public SnapshotMetadata( this.comprParts = comprParts; this.pageSize = pageSize; this.grpIds = grpIds; - this.snapshotTime = snapshotTime + new Random().nextLong(15000); + this.snapshotTime = snapshotTime; this.bltNodes = bltNodes; this.snpRecPtr = snpRecPtr; this.masterKeyDigest = masterKeyDigest; From d372913ebf32b44272161ef0522a9384d630cb51 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Tue, 6 Oct 2026 16:33:59 +0300 Subject: [PATCH 75/97] + empty snp cluster func test --- .../management/snapshot/SnapshotListTask.java | 2 +- .../IgniteClusterSnapshotListTest.java | 23 +++++++++++++++++++ .../testsuites/IgniteSnapshotTestSuite8.java | 2 ++ 3 files changed, 26 insertions(+), 1 deletion(-) create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java index ce991b42c4e43..2e02284a6e655 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java @@ -220,7 +220,7 @@ private List> findLocalSnapshots(@Nullable String cus .max((m0, m1) -> Math.toIntExact(m0.snapshotTime() - m1.snapshotTime())) .orElse(new SnapshotMetadata()); - // Real, meta-based snapshot file tree. Can belong to other cluster, other baseline. + // Real, meta-based snapshot file tree. Can belong to other cluster, other consistent id. sft = new SnapshotFileTree( ignite.configuration(), ignite.context().pdsFolderResolver().fileTree(), diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java new file mode 100644 index 0000000000000..23a46483a530e --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java @@ -0,0 +1,23 @@ +/* + * 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.ignite.internal.processors.cache.persistence.snapshot; + +/** Cluster-wide snapshot list procedure tests.*/ +public class IgniteClusterSnapshotListTest extends AbstractSnapshotSelfTest { + +} diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java index 5673ed2b71af3..06a3600721b1c 100644 --- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java +++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java @@ -22,6 +22,7 @@ import java.util.List; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotCheckTest; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotHandlerTest; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotListTest; import org.apache.ignite.testframework.GridTestUtils; import org.apache.ignite.testframework.junits.DynamicSuite; import org.junit.runner.RunWith; @@ -47,6 +48,7 @@ public static List> suite(Collection ignoredTests) { List> suite = new ArrayList<>(); GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotCheckTest.class, ignoredTests); + GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotListTest.class, ignoredTests); GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotHandlerTest.class, ignoredTests); return suite; From 3a90272cd678c7123dd4a999e739c3e70009c088 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Tue, 6 Oct 2026 17:08:56 +0300 Subject: [PATCH 76/97] + 1st parallel --- .../GridCommandHandlerListSnapshotTest.java | 3 +- .../management/snapshot/SnapshotListTask.java | 89 +++++++++++++------ 2 files changed, 62 insertions(+), 30 deletions(-) diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java index c9c1ad4ab7af5..f1c3a2951d759 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java @@ -46,6 +46,7 @@ import static org.apache.ignite.internal.commandline.CommandHandler.EXIT_CODE_OK; import static org.apache.ignite.internal.processors.cache.persistence.snapshot.AbstractSnapshotSelfTest.snp; import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; /** Test for the command '--snapshot list'. */ public class GridCommandHandlerListSnapshotTest extends GridCommandHandlerAbstractTest { @@ -174,7 +175,7 @@ public void testOneNodeMisses() throws Exception { // Doesn't matter here. assumeFalse(incCnt > 0); // Let's keep just one node not seeing the snapshot. - assumeFalse(separatedWorkDir); + assumeTrue(separatedWorkDir); doTestSnapshotsLists(2, true, false); } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java index 2e02284a6e655..2edaf24ce54d0 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java @@ -27,15 +27,18 @@ import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; import org.apache.ignite.IgniteException; import org.apache.ignite.IgniteLogger; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.compute.ComputeJobResult; +import org.apache.ignite.internal.NodeStoppingException; import org.apache.ignite.internal.processors.cache.persistence.filename.SnapshotFileTree; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IncrementalSnapshotMetadata; @@ -152,13 +155,13 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { @Override protected SnapshotListJobResult run(SnapshotListCommandArg arg) { assert !ignite.localNode().isClient(); + if (ignite.context().isStopping()) + throw new IgniteException("Won't search for local snapshots.", new NodeStoppingException("Node is stopping.")); + Map snpDescsRes; try { - List> locSnps = findLocalSnapshots( - arg.src(), - ignite.context().cache().context().snapshotMgr().localSnapshotNames(arg.src()) - ); + List> locSnps = findLocalSnapshots(arg.src()); snpDescsRes = U.newHashMap(locSnps.size()); @@ -205,32 +208,60 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { } /** @return Snapshot file tree and creation time from the snapshot metadata. */ - private List> findLocalSnapshots(@Nullable String customSnpRoot, List folderNames) { - List> res = new ArrayList<>(folderNames.size()); - - folderNames.forEach(fn -> { - // Snapshot tree being used as a path, to read the metas only. - SnapshotFileTree sft = new SnapshotFileTree(ignite.context(), fn, customSnpRoot); - - List metas = ignite.context().cache().context().snapshotMgr().readSnapshotMetadatas(sft, false); - - if (!metas.isEmpty()) { - // Get the last-created time snapshot metadata. - SnapshotMetadata snpMeta = metas.stream() - .max((m0, m1) -> Math.toIntExact(m0.snapshotTime() - m1.snapshotTime())) - .orElse(new SnapshotMetadata()); - - // Real, meta-based snapshot file tree. Can belong to other cluster, other consistent id. - sft = new SnapshotFileTree( - ignite.configuration(), - ignite.context().pdsFolderResolver().fileTree(), - fn, - customSnpRoot, - snpMeta.folderName(), - snpMeta.consistentId() - ); + private List> findLocalSnapshots(@Nullable String snpPath) { + // The tree is used only to extract the snapshots root directory. The snapshot name isn't used. + File[] dirsToParse = new SnapshotFileTree(ignite.context(), "snp", snpPath).root().getParentFile().listFiles(); + + if (F.isEmpty(dirsToParse)) + return Collections.emptyList(); + + List>> futs = new ArrayList<>(dirsToParse.length); + + for (File snpDir : dirsToParse) { + Future> snpDirFut = ignite.context().pools().getSnapshotExecutorService().submit(() -> { + String snpName = snpDir.getName(); + + // Snapshot tree being used as a path, to read the metas only. + SnapshotFileTree sft = new SnapshotFileTree(ignite.context(), snpName, snpPath); + + List metas = ignite.context().cache().context().snapshotMgr().readSnapshotMetadatas(sft, false); + + if (!metas.isEmpty()) { + // Get the last-created time snapshot metadata. + SnapshotMetadata snpMeta = metas.stream() + .max((m0, m1) -> Math.toIntExact(m0.snapshotTime() - m1.snapshotTime())) + .orElse(new SnapshotMetadata()); + + // Real, meta-based snapshot file tree. Can belong to other cluster, other consistent id. + sft = new SnapshotFileTree( + ignite.configuration(), + ignite.context().pdsFolderResolver().fileTree(), + snpName, + snpPath, + snpMeta.folderName(), + snpMeta.consistentId() + ); - res.add(new T2<>(sft, snpMeta.snapshotTime())); + return new T2<>(sft, snpMeta.snapshotTime()); + } + + return null; + }); + + futs.add(snpDirFut); + } + + List> res = new ArrayList<>(dirsToParse.length); + + futs.forEach(f -> { + try { + T2 snpDirRes = f.get(); + + if (snpDirRes != null) + res.add(snpDirRes); + } + catch (Exception e) { + log.warning("Failed to calculate snapshot size, ignoring snapshot [path=" + snpPath + ']', e); } }); From 1d072431795159bad9fe14ae3a18fdc90e782d90 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Tue, 6 Oct 2026 17:38:10 +0300 Subject: [PATCH 77/97] revert --- .../management/snapshot/SnapshotListTask.java | 88 ++++++++++--------- 1 file changed, 48 insertions(+), 40 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java index 2edaf24ce54d0..abb801aa76005 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java @@ -32,6 +32,7 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; import org.apache.ignite.IgniteException; @@ -165,23 +166,27 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { snpDescsRes = U.newHashMap(locSnps.size()); - for (int snpIdx = 0; snpIdx < locSnps.size(); snpIdx++) { - SnapshotFileTree sft = locSnps.get(snpIdx).get1(); + for (T2 snpPair : locSnps) { + SnapshotFileTree sft = snpPair.get1(); SnapshotListJobResult.SnapshotInfo extStors = externalStorages(sft); - // Optiona incremental snapshots description. - SnapshotListJobResult.SnapshotInfo incs = incrementals(sft); + try { + // Optiona incremental snapshots description. + SnapshotListJobResult.SnapshotInfo incs = incrementals(sft); + + // Main snapshot description. + SnapshotListJobResult.SnapshotInfo snpDesc = new SnapshotListJobResult.SnapshotInfo( + calculateDirectorySize(sft.root()), + snpPair.get2(), + extStors, + incs + ); - // Main snapshot description. - SnapshotListJobResult.SnapshotInfo snpDesc = new SnapshotListJobResult.SnapshotInfo( - calculateDirectorySize(sft.root()), - locSnps.get(snpIdx).get2(), - extStors, - incs - ); + snpDescsRes.put(sft.name(), snpDesc); + } catch (Exception e) { - snpDescsRes.put(sft.name(), snpDesc); + } } } catch (Exception e) { @@ -269,49 +274,52 @@ private List> findLocalSnapshots(@Nullable String snp } /** @return Number, total size and last creation time of incremental snapshots. */ - private @Nullable SnapshotListJobResult.SnapshotInfo incrementals(SnapshotFileTree sft) throws IOException { + private @Nullable SnapshotListJobResult.SnapshotInfo incrementals(SnapshotFileTree sft) throws Exception { File[] incs = sft.incrementsRoot().listFiles(); - int cnt = 0; - long size = 0L; - long createTime = 0L; + if (F.isEmpty(incs)) + return null; + + AtomicInteger cnt = new AtomicInteger(); + AtomicLong size = new AtomicLong(); + long[] createTime = new long[1]; - if (!F.isEmpty(incs)) { - IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); + IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); - int incIdx; + List> futs = new ArrayList<>(incs.length); - for (File incDir : incs) { + for (File incDir : incs) { + Future fut = ignite.context().pools().getSnapshotExecutorService().submit(() -> { try { - incIdx = Integer.parseInt(incDir.getName()); - } - catch (NumberFormatException e) { - log.warning("Failed to calculate incremental snapshot size, wrong folder name [name=" - + incDir.getName() + ']', e); + int incIdx = Integer.parseInt(incDir.getName()); - continue; - } + SnapshotFileTree.IncrementalSnapshotFileTree incTree = sft.incrementalSnapshotFileTree(incIdx); - SnapshotFileTree.IncrementalSnapshotFileTree incTree = sft.incrementalSnapshotFileTree(incIdx); - IncrementalSnapshotMetadata incMeta; + IncrementalSnapshotMetadata incMeta = snpMgr.readIncrementalSnapshotMetadata(incTree.meta()); - try { - incMeta = snpMgr.readIncrementalSnapshotMetadata(incTree.meta()); + size.addAndGet(calculateDirectorySize(incDir)); + + cnt.incrementAndGet(); + + if(true) throw new IgniteException("test"); + + synchronized (createTime) { + createTime[0] = Math.max(createTime[0], incMeta.snapshotTime()); + } } catch (Exception e) { - log.warning("Failed to calculate incremental snapshot size, unable to read the metadata [name=" - + incDir.getName() + ']', e); - - continue; + throw new IgniteException("Failed to calculate size of an incremental snapshot [snp=" + sft.root() + ']', e); } + }); - cnt++; - size += calculateDirectorySize(incDir); - createTime = Math.max(createTime, incMeta.snapshotTime()); - } + futs.add(fut); + } + + for(Future fut : futs) { + fut.get(); } - return cnt == 0 ? null : new SnapshotListJobResult.SnapshotInfo(cnt, size, createTime); + return new SnapshotListJobResult.SnapshotInfo(cnt.get(), size.get(), createTime[0]); } } } From 98274c9db987b2cbb6c57c8f37f8b05d3a27aa6d Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Tue, 6 Oct 2026 17:38:20 +0300 Subject: [PATCH 78/97] Revert "revert" This reverts commit 1d072431795159bad9fe14ae3a18fdc90e782d90. --- .../management/snapshot/SnapshotListTask.java | 88 +++++++++---------- 1 file changed, 40 insertions(+), 48 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java index abb801aa76005..2edaf24ce54d0 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java @@ -32,7 +32,6 @@ import java.util.Map; import java.util.UUID; import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; import org.apache.ignite.IgniteException; @@ -166,27 +165,23 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { snpDescsRes = U.newHashMap(locSnps.size()); - for (T2 snpPair : locSnps) { - SnapshotFileTree sft = snpPair.get1(); + for (int snpIdx = 0; snpIdx < locSnps.size(); snpIdx++) { + SnapshotFileTree sft = locSnps.get(snpIdx).get1(); SnapshotListJobResult.SnapshotInfo extStors = externalStorages(sft); - try { - // Optiona incremental snapshots description. - SnapshotListJobResult.SnapshotInfo incs = incrementals(sft); - - // Main snapshot description. - SnapshotListJobResult.SnapshotInfo snpDesc = new SnapshotListJobResult.SnapshotInfo( - calculateDirectorySize(sft.root()), - snpPair.get2(), - extStors, - incs - ); + // Optiona incremental snapshots description. + SnapshotListJobResult.SnapshotInfo incs = incrementals(sft); - snpDescsRes.put(sft.name(), snpDesc); - } catch (Exception e) { + // Main snapshot description. + SnapshotListJobResult.SnapshotInfo snpDesc = new SnapshotListJobResult.SnapshotInfo( + calculateDirectorySize(sft.root()), + locSnps.get(snpIdx).get2(), + extStors, + incs + ); - } + snpDescsRes.put(sft.name(), snpDesc); } } catch (Exception e) { @@ -274,52 +269,49 @@ private List> findLocalSnapshots(@Nullable String snp } /** @return Number, total size and last creation time of incremental snapshots. */ - private @Nullable SnapshotListJobResult.SnapshotInfo incrementals(SnapshotFileTree sft) throws Exception { + private @Nullable SnapshotListJobResult.SnapshotInfo incrementals(SnapshotFileTree sft) throws IOException { File[] incs = sft.incrementsRoot().listFiles(); - if (F.isEmpty(incs)) - return null; - - AtomicInteger cnt = new AtomicInteger(); - AtomicLong size = new AtomicLong(); - long[] createTime = new long[1]; + int cnt = 0; + long size = 0L; + long createTime = 0L; - IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); + if (!F.isEmpty(incs)) { + IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); - List> futs = new ArrayList<>(incs.length); + int incIdx; - for (File incDir : incs) { - Future fut = ignite.context().pools().getSnapshotExecutorService().submit(() -> { + for (File incDir : incs) { try { - int incIdx = Integer.parseInt(incDir.getName()); - - SnapshotFileTree.IncrementalSnapshotFileTree incTree = sft.incrementalSnapshotFileTree(incIdx); - - IncrementalSnapshotMetadata incMeta = snpMgr.readIncrementalSnapshotMetadata(incTree.meta()); - - size.addAndGet(calculateDirectorySize(incDir)); + incIdx = Integer.parseInt(incDir.getName()); + } + catch (NumberFormatException e) { + log.warning("Failed to calculate incremental snapshot size, wrong folder name [name=" + + incDir.getName() + ']', e); - cnt.incrementAndGet(); + continue; + } - if(true) throw new IgniteException("test"); + SnapshotFileTree.IncrementalSnapshotFileTree incTree = sft.incrementalSnapshotFileTree(incIdx); + IncrementalSnapshotMetadata incMeta; - synchronized (createTime) { - createTime[0] = Math.max(createTime[0], incMeta.snapshotTime()); - } + try { + incMeta = snpMgr.readIncrementalSnapshotMetadata(incTree.meta()); } catch (Exception e) { - throw new IgniteException("Failed to calculate size of an incremental snapshot [snp=" + sft.root() + ']', e); - } - }); + log.warning("Failed to calculate incremental snapshot size, unable to read the metadata [name=" + + incDir.getName() + ']', e); - futs.add(fut); - } + continue; + } - for(Future fut : futs) { - fut.get(); + cnt++; + size += calculateDirectorySize(incDir); + createTime = Math.max(createTime, incMeta.snapshotTime()); + } } - return new SnapshotListJobResult.SnapshotInfo(cnt.get(), size.get(), createTime[0]); + return cnt == 0 ? null : new SnapshotListJobResult.SnapshotInfo(cnt, size, createTime); } } } From ff50f3d275a7baab05b7b17567286afae3404ca6 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Tue, 6 Oct 2026 18:33:33 +0300 Subject: [PATCH 79/97] revert --- .../management/snapshot/SnapshotListTask.java | 223 +++++++++--------- 1 file changed, 114 insertions(+), 109 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java index 2edaf24ce54d0..e05b66b56573e 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java @@ -31,7 +31,9 @@ import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; import org.apache.ignite.IgniteException; @@ -40,15 +42,13 @@ import org.apache.ignite.compute.ComputeJobResult; import org.apache.ignite.internal.NodeStoppingException; import org.apache.ignite.internal.processors.cache.persistence.filename.SnapshotFileTree; -import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IncrementalSnapshotMetadata; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListJobResult; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListTaskResult; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotMetadata; import org.apache.ignite.internal.processors.task.GridInternal; +import org.apache.ignite.internal.thread.pool.IgniteThreadPoolExecutor; import org.apache.ignite.internal.util.typedef.F; -import org.apache.ignite.internal.util.typedef.T2; -import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.internal.visor.VisorJob; import org.apache.ignite.internal.visor.VisorMultiNodeTask; import org.apache.ignite.internal.visor.VisorTaskArgument; @@ -136,7 +136,7 @@ public static long calculateDirectorySize(File path) throws IOException { /** */ private static class SnapshotListJob extends SnapshotJob { - /** Serial version uid. */ + /** erial version uid. */ private static final long serialVersionUID = 0L; /** */ @@ -144,7 +144,7 @@ private static class SnapshotListJob extends SnapshotJob snpDescsRes; - - try { - List> locSnps = findLocalSnapshots(arg.src()); - - snpDescsRes = U.newHashMap(locSnps.size()); - - for (int snpIdx = 0; snpIdx < locSnps.size(); snpIdx++) { - SnapshotFileTree sft = locSnps.get(snpIdx).get1(); - - SnapshotListJobResult.SnapshotInfo extStors = externalStorages(sft); - - // Optiona incremental snapshots description. - SnapshotListJobResult.SnapshotInfo incs = incrementals(sft); - - // Main snapshot description. - SnapshotListJobResult.SnapshotInfo snpDesc = new SnapshotListJobResult.SnapshotInfo( - calculateDirectorySize(sft.root()), - locSnps.get(snpIdx).get2(), - extStors, - incs - ); - - snpDescsRes.put(sft.name(), snpDesc); - } - } - catch (Exception e) { - throw new IgniteException("Failed to list local snapshots [src=" + arg.src() + ']', e); - } - - return new SnapshotListJobResult(snpDescsRes); + return new SnapshotListJobResult(findLocalSnapshots(arg.src())); } /** */ - private static @Nullable SnapshotListJobResult.SnapshotInfo externalStorages(SnapshotFileTree sft) throws IOException { + private static @Nullable SnapshotListJobResult.SnapshotInfo externalStorages( + SnapshotFileTree sft) throws IOException { int extStoragesCnt = 0; long extStoragesSize = 0; @@ -208,57 +179,35 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { } /** @return Snapshot file tree and creation time from the snapshot metadata. */ - private List> findLocalSnapshots(@Nullable String snpPath) { + private Map findLocalSnapshots(@Nullable String snpPath) { // The tree is used only to extract the snapshots root directory. The snapshot name isn't used. File[] dirsToParse = new SnapshotFileTree(ignite.context(), "snp", snpPath).root().getParentFile().listFiles(); if (F.isEmpty(dirsToParse)) - return Collections.emptyList(); + return Collections.emptyMap(); + + // Per snapshot/directory futures. + List> futs = new ArrayList<>(dirsToParse.length); - List>> futs = new ArrayList<>(dirsToParse.length); + Map res = new ConcurrentHashMap<>(dirsToParse.length, 1.0f); for (File snpDir : dirsToParse) { - Future> snpDirFut = ignite.context().pools().getSnapshotExecutorService().submit(() -> { - String snpName = snpDir.getName(); - - // Snapshot tree being used as a path, to read the metas only. - SnapshotFileTree sft = new SnapshotFileTree(ignite.context(), snpName, snpPath); - - List metas = ignite.context().cache().context().snapshotMgr().readSnapshotMetadatas(sft, false); - - if (!metas.isEmpty()) { - // Get the last-created time snapshot metadata. - SnapshotMetadata snpMeta = metas.stream() - .max((m0, m1) -> Math.toIntExact(m0.snapshotTime() - m1.snapshotTime())) - .orElse(new SnapshotMetadata()); - - // Real, meta-based snapshot file tree. Can belong to other cluster, other consistent id. - sft = new SnapshotFileTree( - ignite.configuration(), - ignite.context().pdsFolderResolver().fileTree(), - snpName, - snpPath, - snpMeta.folderName(), - snpMeta.consistentId() - ); - - return new T2<>(sft, snpMeta.snapshotTime()); + // Single snapshot/directory future. + Future snpDirFut = ignite.context().pools().getSnapshotExecutorService().submit(() -> { + try { + res.put(snpDir.getName(), readSingleSnapshot(snpDir, snpPath)); + } + catch (Exception e) { + throw new IgniteException("Failed to read snapshot [dir=" + snpDir + ']', e); } - - return null; }); futs.add(snpDirFut); } - List> res = new ArrayList<>(dirsToParse.length); - futs.forEach(f -> { try { - T2 snpDirRes = f.get(); - - if (snpDirRes != null) - res.add(snpDirRes); + f.get(); } catch (Exception e) { log.warning("Failed to calculate snapshot size, ignoring snapshot [path=" + snpPath + ']', e); @@ -268,50 +217,106 @@ private List> findLocalSnapshots(@Nullable String snp return res; } - /** @return Number, total size and last creation time of incremental snapshots. */ - private @Nullable SnapshotListJobResult.SnapshotInfo incrementals(SnapshotFileTree sft) throws IOException { - File[] incs = sft.incrementsRoot().listFiles(); - - int cnt = 0; - long size = 0L; - long createTime = 0L; - - if (!F.isEmpty(incs)) { - IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); + /** */ + private @Nullable SnapshotListJobResult.SnapshotInfo readSingleSnapshot(File snpDir, @Nullable String snpPath) throws Exception { + String snpName = snpDir.getName(); + + // Snapshot tree being used as a path, to read the metas only. + SnapshotFileTree sft = new SnapshotFileTree(ignite.context(), snpName, snpPath); + + List metas = ignite.context().cache().context().snapshotMgr().readSnapshotMetadatas(sft, false); + + if (metas.isEmpty()) + return null; + + // Get the last-created time snapshot metadata. + SnapshotMetadata snpMeta = metas.stream() + .max((m0, m1) -> Math.toIntExact(m0.snapshotTime() - m1.snapshotTime())) + .orElse(new SnapshotMetadata()); + + // Real, meta-based snapshot file tree. Can belong to other cluster, other consistent id. + sft = new SnapshotFileTree( + ignite.configuration(), + ignite.context().pdsFolderResolver().fileTree(), + snpName, + snpPath, + snpMeta.folderName(), + snpMeta.consistentId() + ); + + SnapshotListJobResult.SnapshotInfo incs = readIncrementals(sft); + + SnapshotListJobResult.SnapshotInfo extStors = externalStorages(sft); + + return new SnapshotListJobResult.SnapshotInfo( + calculateDirectorySize(sft.root()), + snpMeta.snapshotTime(), + extStors, + incs + ); + } - int incIdx; + /** */ + private @Nullable SnapshotListJobResult.SnapshotInfo readIncrementals(SnapshotFileTree sft) throws Exception { + // Optional incremental parts. + File[] incsFiles = sft.incrementsRoot().listFiles(); + + if (F.isEmpty(incsFiles)) + return null; + + IgniteThreadPoolExecutor exeSrvc = ignite.context().pools().getSnapshotExecutorService(); + + AtomicInteger cnt = new AtomicInteger(); + AtomicLong size = new AtomicLong(); + AtomicLong createTime = new AtomicLong(); + + List> futs = Collections.emptyList(); + + for (File incDir : incsFiles) { + if (exeSrvc.getMaximumPoolSize() == 1) + incrementalPartWorkUnit(sft, incDir, cnt, size, createTime); + else { + Future fut = exeSrvc.submit(() -> { + try { + incrementalPartWorkUnit(sft, incDir, cnt, size, createTime); + } + catch (Exception e) { + throw new IgniteException("Failed to calculate incremental snapshot size [dir=" + incDir + ']', e); + } + }); + + if (futs == Collections.EMPTY_LIST) + futs = new ArrayList<>(incsFiles.length); + + futs.add(fut); + } + } - for (File incDir : incs) { - try { - incIdx = Integer.parseInt(incDir.getName()); - } - catch (NumberFormatException e) { - log.warning("Failed to calculate incremental snapshot size, wrong folder name [name=" - + incDir.getName() + ']', e); + for (Future fut : futs) + fut.get(); - continue; - } + return new SnapshotListJobResult.SnapshotInfo(cnt.get(), size.get(), createTime.get()); + } - SnapshotFileTree.IncrementalSnapshotFileTree incTree = sft.incrementalSnapshotFileTree(incIdx); - IncrementalSnapshotMetadata incMeta; + /** */ + private void incrementalPartWorkUnit( + SnapshotFileTree sft, + File incDir, + AtomicInteger cnt, + AtomicLong size, + AtomicLong createTime + ) throws Exception { + int incIdx = Integer.parseInt(incDir.getName()); - try { - incMeta = snpMgr.readIncrementalSnapshotMetadata(incTree.meta()); - } - catch (Exception e) { - log.warning("Failed to calculate incremental snapshot size, unable to read the metadata [name=" - + incDir.getName() + ']', e); + SnapshotFileTree.IncrementalSnapshotFileTree incTree = sft.incrementalSnapshotFileTree(incIdx); - continue; - } + IncrementalSnapshotMetadata incMeta = ignite.context().cache().context().snapshotMgr() + .readIncrementalSnapshotMetadata(incTree.meta()); - cnt++; - size += calculateDirectorySize(incDir); - createTime = Math.max(createTime, incMeta.snapshotTime()); - } - } + cnt.incrementAndGet(); + size.addAndGet(calculateDirectorySize(incDir)); - return cnt == 0 ? null : new SnapshotListJobResult.SnapshotInfo(cnt, size, createTime); + createTime.accumulateAndGet(incMeta.snapshotTime(), (l, r)->Math.max(l, r)); } } } From ca46fcac76bb1b96da585e6e70f3eb45cc246462 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Tue, 6 Oct 2026 18:33:45 +0300 Subject: [PATCH 80/97] Revert "revert" This reverts commit ff50f3d275a7baab05b7b17567286afae3404ca6. --- .../management/snapshot/SnapshotListTask.java | 223 +++++++++--------- 1 file changed, 109 insertions(+), 114 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java index e05b66b56573e..2edaf24ce54d0 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java @@ -31,9 +31,7 @@ import java.util.List; import java.util.Map; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; import org.apache.ignite.IgniteException; @@ -42,13 +40,15 @@ import org.apache.ignite.compute.ComputeJobResult; import org.apache.ignite.internal.NodeStoppingException; import org.apache.ignite.internal.processors.cache.persistence.filename.SnapshotFileTree; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IncrementalSnapshotMetadata; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListJobResult; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListTaskResult; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotMetadata; import org.apache.ignite.internal.processors.task.GridInternal; -import org.apache.ignite.internal.thread.pool.IgniteThreadPoolExecutor; import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.T2; +import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.internal.visor.VisorJob; import org.apache.ignite.internal.visor.VisorMultiNodeTask; import org.apache.ignite.internal.visor.VisorTaskArgument; @@ -136,7 +136,7 @@ public static long calculateDirectorySize(File path) throws IOException { /** */ private static class SnapshotListJob extends SnapshotJob { - /** erial version uid. */ + /** Serial version uid. */ private static final long serialVersionUID = 0L; /** */ @@ -144,7 +144,7 @@ private static class SnapshotListJob extends SnapshotJob snpDescsRes; + + try { + List> locSnps = findLocalSnapshots(arg.src()); + + snpDescsRes = U.newHashMap(locSnps.size()); + + for (int snpIdx = 0; snpIdx < locSnps.size(); snpIdx++) { + SnapshotFileTree sft = locSnps.get(snpIdx).get1(); + + SnapshotListJobResult.SnapshotInfo extStors = externalStorages(sft); + + // Optiona incremental snapshots description. + SnapshotListJobResult.SnapshotInfo incs = incrementals(sft); + + // Main snapshot description. + SnapshotListJobResult.SnapshotInfo snpDesc = new SnapshotListJobResult.SnapshotInfo( + calculateDirectorySize(sft.root()), + locSnps.get(snpIdx).get2(), + extStors, + incs + ); + + snpDescsRes.put(sft.name(), snpDesc); + } + } + catch (Exception e) { + throw new IgniteException("Failed to list local snapshots [src=" + arg.src() + ']', e); + } + + return new SnapshotListJobResult(snpDescsRes); } /** */ - private static @Nullable SnapshotListJobResult.SnapshotInfo externalStorages( - SnapshotFileTree sft) throws IOException { + private static @Nullable SnapshotListJobResult.SnapshotInfo externalStorages(SnapshotFileTree sft) throws IOException { int extStoragesCnt = 0; long extStoragesSize = 0; @@ -179,35 +208,57 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { } /** @return Snapshot file tree and creation time from the snapshot metadata. */ - private Map findLocalSnapshots(@Nullable String snpPath) { + private List> findLocalSnapshots(@Nullable String snpPath) { // The tree is used only to extract the snapshots root directory. The snapshot name isn't used. File[] dirsToParse = new SnapshotFileTree(ignite.context(), "snp", snpPath).root().getParentFile().listFiles(); if (F.isEmpty(dirsToParse)) - return Collections.emptyMap(); - - // Per snapshot/directory futures. - List> futs = new ArrayList<>(dirsToParse.length); + return Collections.emptyList(); - Map res = new ConcurrentHashMap<>(dirsToParse.length, 1.0f); + List>> futs = new ArrayList<>(dirsToParse.length); for (File snpDir : dirsToParse) { - // Single snapshot/directory future. - Future snpDirFut = ignite.context().pools().getSnapshotExecutorService().submit(() -> { - try { - res.put(snpDir.getName(), readSingleSnapshot(snpDir, snpPath)); - } - catch (Exception e) { - throw new IgniteException("Failed to read snapshot [dir=" + snpDir + ']', e); + Future> snpDirFut = ignite.context().pools().getSnapshotExecutorService().submit(() -> { + String snpName = snpDir.getName(); + + // Snapshot tree being used as a path, to read the metas only. + SnapshotFileTree sft = new SnapshotFileTree(ignite.context(), snpName, snpPath); + + List metas = ignite.context().cache().context().snapshotMgr().readSnapshotMetadatas(sft, false); + + if (!metas.isEmpty()) { + // Get the last-created time snapshot metadata. + SnapshotMetadata snpMeta = metas.stream() + .max((m0, m1) -> Math.toIntExact(m0.snapshotTime() - m1.snapshotTime())) + .orElse(new SnapshotMetadata()); + + // Real, meta-based snapshot file tree. Can belong to other cluster, other consistent id. + sft = new SnapshotFileTree( + ignite.configuration(), + ignite.context().pdsFolderResolver().fileTree(), + snpName, + snpPath, + snpMeta.folderName(), + snpMeta.consistentId() + ); + + return new T2<>(sft, snpMeta.snapshotTime()); } + + return null; }); futs.add(snpDirFut); } + List> res = new ArrayList<>(dirsToParse.length); + futs.forEach(f -> { try { - f.get(); + T2 snpDirRes = f.get(); + + if (snpDirRes != null) + res.add(snpDirRes); } catch (Exception e) { log.warning("Failed to calculate snapshot size, ignoring snapshot [path=" + snpPath + ']', e); @@ -217,106 +268,50 @@ private Map findLocalSnapshots(@Null return res; } - /** */ - private @Nullable SnapshotListJobResult.SnapshotInfo readSingleSnapshot(File snpDir, @Nullable String snpPath) throws Exception { - String snpName = snpDir.getName(); - - // Snapshot tree being used as a path, to read the metas only. - SnapshotFileTree sft = new SnapshotFileTree(ignite.context(), snpName, snpPath); - - List metas = ignite.context().cache().context().snapshotMgr().readSnapshotMetadatas(sft, false); - - if (metas.isEmpty()) - return null; - - // Get the last-created time snapshot metadata. - SnapshotMetadata snpMeta = metas.stream() - .max((m0, m1) -> Math.toIntExact(m0.snapshotTime() - m1.snapshotTime())) - .orElse(new SnapshotMetadata()); - - // Real, meta-based snapshot file tree. Can belong to other cluster, other consistent id. - sft = new SnapshotFileTree( - ignite.configuration(), - ignite.context().pdsFolderResolver().fileTree(), - snpName, - snpPath, - snpMeta.folderName(), - snpMeta.consistentId() - ); - - SnapshotListJobResult.SnapshotInfo incs = readIncrementals(sft); - - SnapshotListJobResult.SnapshotInfo extStors = externalStorages(sft); - - return new SnapshotListJobResult.SnapshotInfo( - calculateDirectorySize(sft.root()), - snpMeta.snapshotTime(), - extStors, - incs - ); - } + /** @return Number, total size and last creation time of incremental snapshots. */ + private @Nullable SnapshotListJobResult.SnapshotInfo incrementals(SnapshotFileTree sft) throws IOException { + File[] incs = sft.incrementsRoot().listFiles(); - /** */ - private @Nullable SnapshotListJobResult.SnapshotInfo readIncrementals(SnapshotFileTree sft) throws Exception { - // Optional incremental parts. - File[] incsFiles = sft.incrementsRoot().listFiles(); - - if (F.isEmpty(incsFiles)) - return null; - - IgniteThreadPoolExecutor exeSrvc = ignite.context().pools().getSnapshotExecutorService(); - - AtomicInteger cnt = new AtomicInteger(); - AtomicLong size = new AtomicLong(); - AtomicLong createTime = new AtomicLong(); - - List> futs = Collections.emptyList(); - - for (File incDir : incsFiles) { - if (exeSrvc.getMaximumPoolSize() == 1) - incrementalPartWorkUnit(sft, incDir, cnt, size, createTime); - else { - Future fut = exeSrvc.submit(() -> { - try { - incrementalPartWorkUnit(sft, incDir, cnt, size, createTime); - } - catch (Exception e) { - throw new IgniteException("Failed to calculate incremental snapshot size [dir=" + incDir + ']', e); - } - }); - - if (futs == Collections.EMPTY_LIST) - futs = new ArrayList<>(incsFiles.length); - - futs.add(fut); - } - } + int cnt = 0; + long size = 0L; + long createTime = 0L; - for (Future fut : futs) - fut.get(); + if (!F.isEmpty(incs)) { + IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); - return new SnapshotListJobResult.SnapshotInfo(cnt.get(), size.get(), createTime.get()); - } + int incIdx; - /** */ - private void incrementalPartWorkUnit( - SnapshotFileTree sft, - File incDir, - AtomicInteger cnt, - AtomicLong size, - AtomicLong createTime - ) throws Exception { - int incIdx = Integer.parseInt(incDir.getName()); + for (File incDir : incs) { + try { + incIdx = Integer.parseInt(incDir.getName()); + } + catch (NumberFormatException e) { + log.warning("Failed to calculate incremental snapshot size, wrong folder name [name=" + + incDir.getName() + ']', e); - SnapshotFileTree.IncrementalSnapshotFileTree incTree = sft.incrementalSnapshotFileTree(incIdx); + continue; + } + + SnapshotFileTree.IncrementalSnapshotFileTree incTree = sft.incrementalSnapshotFileTree(incIdx); + IncrementalSnapshotMetadata incMeta; - IncrementalSnapshotMetadata incMeta = ignite.context().cache().context().snapshotMgr() - .readIncrementalSnapshotMetadata(incTree.meta()); + try { + incMeta = snpMgr.readIncrementalSnapshotMetadata(incTree.meta()); + } + catch (Exception e) { + log.warning("Failed to calculate incremental snapshot size, unable to read the metadata [name=" + + incDir.getName() + ']', e); - cnt.incrementAndGet(); - size.addAndGet(calculateDirectorySize(incDir)); + continue; + } + + cnt++; + size += calculateDirectorySize(incDir); + createTime = Math.max(createTime, incMeta.snapshotTime()); + } + } - createTime.accumulateAndGet(incMeta.snapshotTime(), (l, r)->Math.max(l, r)); + return cnt == 0 ? null : new SnapshotListJobResult.SnapshotInfo(cnt, size, createTime); } } } From b52666ad3ded148dda43d7d6fa56a384b6f8fe0e Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Tue, 6 Oct 2026 19:13:21 +0300 Subject: [PATCH 81/97] better parallelism --- .../GridCommandHandlerListSnapshotTest.java | 9 +- .../management/snapshot/SnapshotListTask.java | 166 ++++++++++++------ 2 files changed, 121 insertions(+), 54 deletions(-) diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java index f1c3a2951d759..3c4d08f2e8ec6 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java @@ -166,7 +166,7 @@ public void testSingleSnapshot() throws Exception { /** */ @Test public void testSeveralSnapshots() throws Exception { - doTestSnapshotsLists(4, false, false); + doTestSnapshotsLists(3, false, false); } /** */ @@ -176,6 +176,8 @@ public void testOneNodeMisses() throws Exception { assumeFalse(incCnt > 0); // Let's keep just one node not seeing the snapshot. assumeTrue(separatedWorkDir); + // Almost the same tests. + assumeFalse(addExtraSrvr); doTestSnapshotsLists(2, true, false); } @@ -210,7 +212,8 @@ private void doTestSnapshotsLists(int snpCnt, boolean deleteOnOneNode, boolean r assumeFalse(customPath && separatedWorkDir); int srvrsCnt = 3; - int entriesCnt = 20; + int entriesCnt = 10; + int partitions = 4; IgniteEx ig = (IgniteEx)startGridsMultiThreaded(srvrsCnt); @@ -227,7 +230,7 @@ private void doTestSnapshotsLists(int snpCnt, boolean deleteOnOneNode, boolean r // Create shapshots. if (snpCnt > 0) { - createCacheAndPreload(ig, entriesCnt); + createCacheAndPreload(ig, DEFAULT_CACHE_NAME, entriesCnt, partitions, null); String absPathStr = null; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java index 2edaf24ce54d0..a1609bc9ab6fe 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java @@ -30,7 +30,9 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; @@ -46,9 +48,9 @@ import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListTaskResult; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotMetadata; import org.apache.ignite.internal.processors.task.GridInternal; +import org.apache.ignite.internal.thread.pool.IgniteThreadPoolExecutor; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.T2; -import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.internal.visor.VisorJob; import org.apache.ignite.internal.visor.VisorMultiNodeTask; import org.apache.ignite.internal.visor.VisorTaskArgument; @@ -158,37 +160,114 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { if (ignite.context().isStopping()) throw new IgniteException("Won't search for local snapshots.", new NodeStoppingException("Node is stopping.")); - Map snpDescsRes; + // Read local snapshots. + List> locSnps = findLocalSnapshots(arg.src()); - try { - List> locSnps = findLocalSnapshots(arg.src()); + if (locSnps.isEmpty()) + return new SnapshotListJobResult(Collections.emptyMap()); - snpDescsRes = U.newHashMap(locSnps.size()); + // Optional external storages descriptions (per snapshot name). + Map extStors = new ConcurrentHashMap<>(locSnps.size(), 1.0f); + // Optional incremental parts descriptions (per snapshot name). + Map incs = new ConcurrentHashMap<>(locSnps.size(), 1.0f); - for (int snpIdx = 0; snpIdx < locSnps.size(); snpIdx++) { - SnapshotFileTree sft = locSnps.get(snpIdx).get1(); + // Incremental parts and external storages futures. + List> futs = new ArrayList<>(locSnps.size() * 2); + // Names of snapshots of with read failures. + Set failedSnps = ConcurrentHashMap.newKeySet(locSnps.size() / 2); - SnapshotListJobResult.SnapshotInfo extStors = externalStorages(sft); + IgniteThreadPoolExecutor exec = ignite.context().pools().getSnapshotExecutorService(); - // Optiona incremental snapshots description. - SnapshotListJobResult.SnapshotInfo incs = incrementals(sft); + // Optional descriptions of snapshot external storages and incremental parts. + for (T2 snpPair : locSnps) { + SnapshotFileTree sft = snpPair.get1(); + String snpName = sft.name(); - // Main snapshot description. - SnapshotListJobResult.SnapshotInfo snpDesc = new SnapshotListJobResult.SnapshotInfo( - calculateDirectorySize(sft.root()), - locSnps.get(snpIdx).get2(), - extStors, - incs - ); + // Future for optional exernal storages. + Future fut = exec.submit(() -> { + try { + SnapshotListJobResult.SnapshotInfo extDesc = externalStorages(sft); + + if (extDesc != null) + extStors.put(snpName, extDesc); + } + catch (Throwable t) { + // Skip snapshot in case on any failure. + failedSnps.add(snpName); + + log.warning("Failed to read snapshot's extra storages, snapshot ignored [snpName=" + snpName + ']', t); + } + }); - snpDescsRes.put(sft.name(), snpDesc); + futs.add(fut); + + // Future for optional incremental parts. + fut = exec.submit(() -> { + if (failedSnps.contains(snpName)) + return; + + try { + SnapshotListJobResult.SnapshotInfo incDesc = incrementals(sft); + + if (incDesc != null) + incs.put(snpName, incDesc); + } + catch (Throwable t) { + // Skip snapshot in case on any failure. + failedSnps.add(snpName); + + log.warning("Failed to read snapshot's incremental parts, snapshot ignored [snpName=" + snpName + ']', t); + } + }); + + futs.add(fut); + } + + // Wait for the snapshot optional description futures. + for (Future fut : futs) { + try { + fut.get(); + } + catch (Throwable t) { + // All the futures has internal exceptions being logged. No errors expected. + throw new IgniteException("Failed to read local nodes snapshots.", t); } } - catch (Exception e) { - throw new IgniteException("Failed to list local snapshots [src=" + arg.src() + ']', e); + + // Result snapshot descriptions. + Map resMap = new ConcurrentHashMap<>(locSnps.size() - failedSnps.size(), 1.0f); + + // Reduce results. + for (T2 snpPair : locSnps) { + SnapshotFileTree sft = snpPair.get1(); + String snpName = sft.name(); + + if (failedSnps.contains(snpName)) + continue; + + long size; + + try { + size = calculateDirectorySize(sft.root()); + } + catch (Exception e) { + log.warning("Failed to calculate snapshot' size, snapshot ignored [snpName=" + snpName + ']', e); + + // Skip snapshot in case on any failure. + continue; + } + + SnapshotListJobResult.SnapshotInfo snpDesc = new SnapshotListJobResult.SnapshotInfo( + size, + snpPair.get2(), + extStors.get(snpName), + incs.get(snpName) + ); + + resMap.put(snpName, snpDesc); } - return new SnapshotListJobResult(snpDescsRes); + return new SnapshotListJobResult(resMap); } /** */ @@ -261,7 +340,8 @@ private List> findLocalSnapshots(@Nullable String snp res.add(snpDirRes); } catch (Exception e) { - log.warning("Failed to calculate snapshot size, ignoring snapshot [path=" + snpPath + ']', e); + // Skip snapshot in case on any failure. + log.warning("Failed to read snapshot, snapshot ignored [path=" + snpPath + ']', e); } }); @@ -269,49 +349,33 @@ private List> findLocalSnapshots(@Nullable String snp } /** @return Number, total size and last creation time of incremental snapshots. */ - private @Nullable SnapshotListJobResult.SnapshotInfo incrementals(SnapshotFileTree sft) throws IOException { + private @Nullable SnapshotListJobResult.SnapshotInfo incrementals(SnapshotFileTree sft) throws Exception { File[] incs = sft.incrementsRoot().listFiles(); + if (F.isEmpty(incs)) + return null; + int cnt = 0; long size = 0L; long createTime = 0L; - if (!F.isEmpty(incs)) { - IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); - - int incIdx; + IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); - for (File incDir : incs) { - try { - incIdx = Integer.parseInt(incDir.getName()); - } - catch (NumberFormatException e) { - log.warning("Failed to calculate incremental snapshot size, wrong folder name [name=" - + incDir.getName() + ']', e); + for (File incDir : incs) { + int incIdx = Integer.parseInt(incDir.getName()); - continue; - } + SnapshotFileTree.IncrementalSnapshotFileTree incTree = sft.incrementalSnapshotFileTree(incIdx); - SnapshotFileTree.IncrementalSnapshotFileTree incTree = sft.incrementalSnapshotFileTree(incIdx); - IncrementalSnapshotMetadata incMeta; + IncrementalSnapshotMetadata incMeta = snpMgr.readIncrementalSnapshotMetadata(incTree.meta()); - try { - incMeta = snpMgr.readIncrementalSnapshotMetadata(incTree.meta()); - } - catch (Exception e) { - log.warning("Failed to calculate incremental snapshot size, unable to read the metadata [name=" - + incDir.getName() + ']', e); + size += calculateDirectorySize(incDir); - continue; - } + cnt++; - cnt++; - size += calculateDirectorySize(incDir); - createTime = Math.max(createTime, incMeta.snapshotTime()); - } + createTime = Math.max(createTime, incMeta.snapshotTime()); } - return cnt == 0 ? null : new SnapshotListJobResult.SnapshotInfo(cnt, size, createTime); + return new SnapshotListJobResult.SnapshotInfo(cnt, size, createTime); } } } From ec8aaf3feb0db5f1ddbb17d6cea8ade775ebb3c7 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Wed, 7 Oct 2026 14:40:15 +0300 Subject: [PATCH 82/97] dic fix. + no-meta-test --- docs/_docs/snapshots/snapshots.adoc | 4 +- .../snapshot/IgniteSnapshotManager.java | 6 +- .../IgniteClusterSnapshotListTest.java | 127 ++++++++++++++++++ 3 files changed, 131 insertions(+), 6 deletions(-) diff --git a/docs/_docs/snapshots/snapshots.adoc b/docs/_docs/snapshots/snapshots.adoc index 9bfe35c4e68c5..e41020a280e82 100644 --- a/docs/_docs/snapshots/snapshots.adoc +++ b/docs/_docs/snapshots/snapshots.adoc @@ -317,8 +317,8 @@ control.bat --snapshot list --src C:\\tmp\\ignite\\snapshots === List operation notes * The list operation is read-only and does not modify any snapshot data. -* If a snapshot is in a transient state (being created or deleted), its size is not reported. -* Snapshot is being detected by its metadata. If metadata is missing or cannot be read, snapshot directory is ignored. +* If a snapshot is being modified (created or deleted), its size might not be reported. +* Snapshot is being detected by its metadata. If metadata is missing or cannot be read, snapshot is ignored. * When snapshot listing and size calculating, no additional snapshot validation is done. Files aren't read, no checksums are calculated. diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java index 2237b0ffe8acf..2a0aeffc6023e 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java @@ -1870,12 +1870,10 @@ public List readSnapshotMetadatas(SnapshotFileTree sft, boolea prev = curr; } catch (Exception e) { - String err = "Fail to read snapshot metadata [meta=" + smf + ']'; - if (failIfCantRead) - throw new IgniteException(err, e); + throw new IgniteException("Fail to read snapshot metadata [meta=" + smf + ']', e); else - log.error(err, e); + log.error("Fail to read snapshot metadata, skipped [meta=" + smf + ']', e); } } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java index 23a46483a530e..57a8e26c46818 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java @@ -17,7 +17,134 @@ package org.apache.ignite.internal.processors.cache.persistence.snapshot; +import java.io.File; +import java.io.RandomAccessFile; +import java.nio.file.DirectoryStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.UUID; +import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction; +import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.configuration.CacheConfiguration; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.management.snapshot.SnapshotListCommandArg; +import org.apache.ignite.internal.management.snapshot.SnapshotListTask; +import org.apache.ignite.internal.processors.cache.persistence.filename.SnapshotFileTree; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.internal.visor.VisorTaskArgument; +import org.junit.Test; +import org.junit.runners.Parameterized; + +import static java.nio.file.Files.newDirectoryStream; +import static org.apache.ignite.configuration.IgniteConfiguration.DFLT_SNAPSHOT_THREAD_POOL_SIZE; +import static org.apache.ignite.testframework.GridTestUtils.cartesianProduct; + /** Cluster-wide snapshot list procedure tests.*/ public class IgniteClusterSnapshotListTest extends AbstractSnapshotSelfTest { + /** Number of cache keys to pre-create at node start. */ + private static final int CACHE_KEYS_RANGE = 10; + + /** Number of partitions within a snapshot cache group. */ + private static final int CACHE_PARTITIONS_COUNT = 4; + + /** */ + @Parameterized.Parameter(2) + public int snpThrdPoolSz; + + /** Parameters. */ + @Parameterized.Parameters(name = "encryption={0}, onlyPrimary={1}, snpThrdPoolSz={2}") + public static Collection params() { + return cartesianProduct( + encryptionParameters(), + F.asList(false, true), + F.asList(DFLT_SNAPSHOT_THREAD_POOL_SIZE, 1) + ); + } + + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { + return super.getConfiguration(igniteInstanceName) + .setWorkDirectory(new File(U.defaultWorkDirectory(), igniteInstanceName).getAbsolutePath()); + } + + /** {@inheritDoc} */ + @Override protected CacheConfiguration txCacheConfig(CacheConfiguration ccfg) { + return super.txCacheConfig(ccfg).setAffinity(new RendezvousAffinityFunction(false, CACHE_PARTITIONS_COUNT)); + } + + /** {@inheritDoc} */ + @Override protected void cleanPersistenceDir() throws Exception { + super.cleanPersistenceDir(); + + // Also cleans separated snapshot working directories and custom snapshot paths. + try (DirectoryStream files = newDirectoryStream(Paths.get(U.defaultWorkDirectory()))) { + for (Path path : files) + U.delete(path); + } + } + + /** */ + @Test + public void testMissingMeta() throws Exception { + doTestWithWrongMeta(false); + } + + /** */ + @Test + public void testCorruptedMeta() throws Exception { + doTestWithWrongMeta(true); + } + + /** + * Tests snapshot lists when one snapshot metadata cannot be read. + * + * @param corruptFile If {@code true}, corrupts metadata. Otherwise, deletes metadata. + */ + private void doTestWithWrongMeta(boolean corruptFile) throws Exception { + startGridsWithSnapshot(3, CACHE_KEYS_RANGE, true, false); + + SnapshotFileTree sftNode1 = new SnapshotFileTree(grid(1).context(), SNAPSHOT_NAME, null); + + assertTrue(sftNode1.meta().exists()); + + if (corruptFile) { + try (RandomAccessFile raf = new RandomAccessFile(sftNode1.meta(), "rw")) { + raf.write(UUID.randomUUID().toString().getBytes()); + } + } + else + assertTrue(sftNode1.meta().delete() && !sftNode1.meta().exists()); + + SnapshotListTaskResult res = listSnapshots(grid(2)); + + int foundSnpsCnt = 0; + boolean victimNodeFound = false; + + for (int i = 0; i < res.nodesIds().length; i++) { + UUID nid = res.nodesIds()[i]; + + foundSnpsCnt += res.snapshots()[i].snapshots().size(); + + if (nid.equals(grid(1).localNode().id())) { + victimNodeFound = true; + + assertTrue(res.snapshots()[i].snapshots().isEmpty()); + } + } + + assertTrue(victimNodeFound); + assertEquals(2, foundSnpsCnt); + } + + /** */ + private static SnapshotListTaskResult listSnapshots(IgniteEx grid) throws Exception { + SnapshotListCommandArg arg = new SnapshotListCommandArg(); + + Collection nodes = grid.cluster().forServers().nodes().stream().map(ClusterNode::id).toList(); + return grid.compute().execute(SnapshotListTask.class, new VisorTaskArgument<>(nodes, arg, false)).result(); + } } From 5f3c9aa9f43037228e71d361a275f9617cc2fbea Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Wed, 7 Oct 2026 15:24:44 +0300 Subject: [PATCH 83/97] raw --- .../snapshot/IgniteClusterSnapshotListTest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java index 57a8e26c46818..099687272d554 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java @@ -86,6 +86,16 @@ public static Collection params() { } } + /** */ + @Test + public void testParallelDeletion() throws Exception { + startGridsWithSnapshot(3, CACHE_KEYS_RANGE, true, false); + + SnapshotFileTree sftNode1 = new SnapshotFileTree(grid(1).context(), SNAPSHOT_NAME, null); + + + } + /** */ @Test public void testMissingMeta() throws Exception { From 695ecb8ee61e074129ba94557f4c6d336ee63529 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Thu, 8 Oct 2026 04:34:43 +0300 Subject: [PATCH 84/97] + concurrent op tests --- .../management/snapshot/SnapshotListTask.java | 72 +++--- .../IgniteClusterSnapshotListTest.java | 242 +++++++++++++++++- 2 files changed, 278 insertions(+), 36 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java index a1609bc9ab6fe..2357d1e93d202 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java @@ -18,7 +18,6 @@ package org.apache.ignite.internal.management.snapshot; import java.io.File; -import java.io.FileNotFoundException; import java.io.IOException; import java.nio.file.FileVisitResult; import java.nio.file.Files; @@ -55,6 +54,7 @@ import org.apache.ignite.internal.visor.VisorMultiNodeTask; import org.apache.ignite.internal.visor.VisorTaskArgument; import org.apache.ignite.resources.LoggerResource; +import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** */ @@ -80,6 +80,11 @@ public class SnapshotListTask extends VisorMultiNodeTask nodeConsistentId(jr0.getNode()).compareTo(nodeConsistentId(jr1.getNode()))) + .toList(); + for (int i = 0; i < nodesJobsResults.size(); i++) { ComputeJobResult nodeJobRes = nodesJobsResults.get(i); @@ -90,7 +95,7 @@ public class SnapshotListTask extends VisorMultiNodeTask() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { // Use attrs instead of Files.size() for efficiency. if (attrs.isRegularFile()) - totalSize.addAndGet(attrs.size()); + size.addAndGet(attrs.size()); return FileVisitResult.CONTINUE; } - /** File/directory became inaccessible (permission denied, deleted, etc.) */ @Override public FileVisitResult visitFileFailed(Path file, IOException exc) { - if (exc instanceof FileNotFoundException) - return FileVisitResult.CONTINUE; + return FileVisitResult.CONTINUE; + } - throw new IgniteException("Failed to calculate snapshot size [name=" + path.getName() + ']', exc); + @Override public FileVisitResult postVisitDirectory(@NotNull Path dir, @Nullable IOException exc) { + return FileVisitResult.CONTINUE; } }); - return totalSize.get(); + return size.get(); } /** */ @@ -305,26 +312,25 @@ private List> findLocalSnapshots(@Nullable String snp List metas = ignite.context().cache().context().snapshotMgr().readSnapshotMetadatas(sft, false); - if (!metas.isEmpty()) { - // Get the last-created time snapshot metadata. - SnapshotMetadata snpMeta = metas.stream() - .max((m0, m1) -> Math.toIntExact(m0.snapshotTime() - m1.snapshotTime())) - .orElse(new SnapshotMetadata()); - - // Real, meta-based snapshot file tree. Can belong to other cluster, other consistent id. - sft = new SnapshotFileTree( - ignite.configuration(), - ignite.context().pdsFolderResolver().fileTree(), - snpName, - snpPath, - snpMeta.folderName(), - snpMeta.consistentId() - ); - - return new T2<>(sft, snpMeta.snapshotTime()); - } - - return null; + if (metas.isEmpty()) + return null; + + // Get the last-created time snapshot metadata. + SnapshotMetadata snpMeta = metas.stream() + .max((m0, m1) -> Math.toIntExact(m0.snapshotTime() - m1.snapshotTime())) + .orElse(new SnapshotMetadata()); + + // Real, meta-based snapshot file tree. Can belong to other cluster, other consistent id. + sft = new SnapshotFileTree( + ignite.configuration(), + ignite.context().pdsFolderResolver().fileTree(), + snpName, + snpPath, + snpMeta.folderName(), + snpMeta.consistentId() + ); + + return new T2<>(sft, snpMeta.snapshotTime()); }); futs.add(snpDirFut); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java index 099687272d554..caf865a9496a2 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java @@ -19,28 +19,43 @@ import java.io.File; import java.io.RandomAccessFile; +import java.io.Serializable; import java.nio.file.DirectoryStream; import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collection; +import java.util.List; +import java.util.Map; import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Stream; import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.configuration.CacheConfiguration; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.IgniteInternalFuture; import org.apache.ignite.internal.management.snapshot.SnapshotListCommandArg; import org.apache.ignite.internal.management.snapshot.SnapshotListTask; import org.apache.ignite.internal.processors.cache.persistence.filename.SnapshotFileTree; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.internal.visor.VisorTaskArgument; +import org.apache.ignite.lang.IgniteFuture; +import org.apache.ignite.plugin.AbstractTestPluginProvider; +import org.apache.ignite.plugin.PluginConfiguration; +import org.apache.ignite.plugin.PluginContext; +import org.apache.ignite.plugin.PluginProvider; import org.junit.Test; import org.junit.runners.Parameterized; import static java.nio.file.Files.newDirectoryStream; import static org.apache.ignite.configuration.IgniteConfiguration.DFLT_SNAPSHOT_THREAD_POOL_SIZE; import static org.apache.ignite.testframework.GridTestUtils.cartesianProduct; +import static org.apache.ignite.testframework.GridTestUtils.runAsync; +import static org.junit.Assume.assumeFalse; /** Cluster-wide snapshot list procedure tests.*/ public class IgniteClusterSnapshotListTest extends AbstractSnapshotSelfTest { @@ -54,6 +69,9 @@ public class IgniteClusterSnapshotListTest extends AbstractSnapshotSelfTest { @Parameterized.Parameter(2) public int snpThrdPoolSz; + /** */ + private PluginProvider pluginProvider; + /** Parameters. */ @Parameterized.Parameters(name = "encryption={0}, onlyPrimary={1}, snpThrdPoolSz={2}") public static Collection params() { @@ -66,12 +84,18 @@ public static Collection params() { /** {@inheritDoc} */ @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { - return super.getConfiguration(igniteInstanceName) + IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName) .setWorkDirectory(new File(U.defaultWorkDirectory(), igniteInstanceName).getAbsolutePath()); + + if (pluginProvider != null) + cfg.setPluginProviders(pluginProvider); + + return cfg; } /** {@inheritDoc} */ @Override protected CacheConfiguration txCacheConfig(CacheConfiguration ccfg) { + // Fastent the tests. return super.txCacheConfig(ccfg).setAffinity(new RendezvousAffinityFunction(false, CACHE_PARTITIONS_COUNT)); } @@ -88,12 +112,224 @@ public static Collection params() { /** */ @Test - public void testParallelDeletion() throws Exception { + public void testConcurrentCreation() throws Exception { + CountDownLatch proceedSnpCreation = new CountDownLatch(1); + CountDownLatch beginSnpCreation = new CountDownLatch(1); + + int grids = 3; + int testNodeIdx = 1; + int testNodeOrder = testNodeIdx + 1; + + // Delays snapshot creation after its metadata is written. + pluginProvider = new AbstractTestPluginProvider() { + @Override public String name() { + return "TestSnpMgrProvider"; + } + + @Override public T createComponent(PluginContext ctx, Class cls) { + if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { + return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { + @Override public void storeSnapshotMeta(M meta, File smf) { + beginSnpCreation.countDown(); + + super.storeSnapshotMeta(meta, smf); + + if (((IgniteEx)ctx.grid()).localNode().order() == testNodeOrder) { + try { + assertTrue(proceedSnpCreation.await(getTestTimeout(), TimeUnit.MILLISECONDS)); + } + catch (InterruptedException e) { + throw new IllegalStateException(e); + } + } + } + }; + } + + return super.createComponent(ctx, cls); + } + }; + + startGridsWithCache(grids, txCacheConfig(defaultCacheConfiguration()), CACHE_KEYS_RANGE); + + IgniteFuture createSnpFut = snp(grid(0)).createSnapshot(SNAPSHOT_NAME); + + assertTrue(beginSnpCreation.await(getTestTimeout(), TimeUnit.MILLISECONDS)); + + SnapshotListTaskResult lstOpRes = listSnapshots(grid(2)); + + int snpsCnt = Stream.of(lstOpRes.snapshots()).mapToInt(jr -> jr.snapshots().size()).sum(); + + assertEquals(grids, snpsCnt); + + SnapshotFileTree testSft = new SnapshotFileTree(grid(testNodeIdx).context(), SNAPSHOT_NAME, null); + + assertTrue(SnapshotListTask.calculateDirectorySize(testSft.root()) > 0L); + + proceedSnpCreation.countDown(); + + createSnpFut.get(getTestTimeout()); + } + + /** */ + @Test + public void testCompletelyDeletedAfterMetaRead() throws Exception { + doTestDeletionAfterMetaRead(true); + } + + /** */ + @Test + public void testPartlyDeletedAfterMetaRead() throws Exception { + doTestDeletionAfterMetaRead(false); + } + + /** */ + private void doTestDeletionAfterMetaRead(boolean completeDeletion) throws Exception { + CountDownLatch metaReadProceedLatch = new CountDownLatch(1); + CountDownLatch metaReadBeginLatch = new CountDownLatch(1); + + pluginProvider = new AbstractTestPluginProvider() { + @Override public String name() { + return "TestSnpMgrProvider"; + } + + @Override public T createComponent(PluginContext ctx, Class cls) { + if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { + return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { + @Override public List readSnapshotMetadatas(SnapshotFileTree sft, + boolean failIfCantRead) { + List metas = super.readSnapshotMetadatas(sft, failIfCantRead); + + if (cctx.localNode().order() != 2) + return metas; + + metaReadBeginLatch.countDown(); + + try { + assertTrue(metaReadProceedLatch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); + } + catch (InterruptedException e) { + throw new IllegalStateException(e); + } + + return metas; + } + }; + } + + return super.createComponent(ctx, cls); + } + }; + startGridsWithSnapshot(3, CACHE_KEYS_RANGE, true, false); - SnapshotFileTree sftNode1 = new SnapshotFileTree(grid(1).context(), SNAPSHOT_NAME, null); + SnapshotFileTree grid1SnpSft = new SnapshotFileTree(grid(1).context(), SNAPSHOT_NAME, null); + + long testSnpSz = SnapshotListTask.calculateDirectorySize(grid1SnpSft.root()); + + assertTrue(testSnpSz > 0); + + IgniteInternalFuture lstOpFut = runAsync(() -> listSnapshots(grid(0))); + + assertTrue(metaReadBeginLatch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); + + if (completeDeletion) + assertTrue(grid1SnpSft.root().exists() && U.delete(grid1SnpSft.root()) && !grid1SnpSft.root().exists()); + else + assertTrue(grid1SnpSft.nodeStorage().exists() && U.delete(grid1SnpSft.nodeStorage()) && !grid1SnpSft.nodeStorage().exists()); + + metaReadProceedLatch.countDown(); + + SnapshotListTaskResult lstOpRes = lstOpFut.get(); + + if (completeDeletion) { + int snpsCnt = Stream.of(lstOpRes.snapshots()).mapToInt(jr -> jr.snapshots().size()).sum(); + + assertEquals(2, snpsCnt); + } + else { + int snpsCnt = 0; + boolean victimNodeFound = false; + + for (int i = 0; i < lstOpRes.nodesIds().length; i++) { + UUID nid = lstOpRes.nodesIds()[i]; + + Map nodeSnps = lstOpRes.snapshots()[i].snapshots(); + + snpsCnt += nodeSnps.size(); + + if (nid.equals(grid(1).localNode().id())) { + victimNodeFound = true; + + assertEquals(1, nodeSnps.size()); + + long curSnpSz = SnapshotListTask.calculateDirectorySize(grid1SnpSft.root()); + + assertTrue(curSnpSz < testSnpSz); + } + } + + assertTrue(victimNodeFound); + assertEquals(3, snpsCnt); + } + } + + /** + * Ensures that a file can be read without locking or other issues while bieng cuncurently written on curren OS and + * file system. This behavior is important for the case when snapshots is being read while creation. + */ + @Test + public void testReadWhileCreating() throws Exception { + // Doesn't matter here. Fastens the tests. + assumeFalse(encryption || onlyPrimary || snpThrdPoolSz > 1); + + assertTrue(new File(U.defaultWorkDirectory()).exists()); + + File testF = new File(U.defaultWorkDirectory(), "test.out"); + + assumeFalse(testF.exists()); + + CountDownLatch proceedWriteLatch = new CountDownLatch(1); + AtomicBoolean readFlag = new AtomicBoolean(); + + try { + try (RandomAccessFile raf = new RandomAccessFile(testF, "rw");) { + raf.write(1); + raf.write(2); + Thread t = new Thread(() -> { + try (RandomAccessFile raf0 = new RandomAccessFile(testF, "r")) { + assertEquals(1, raf0.read()); + assertEquals(2, raf0.read()); + + readFlag.set(true); + + proceedWriteLatch.countDown(); + } + catch (Throwable e) { + log.error("Failed to concurrently read the test file.", e); + } + }); + + t.setDaemon(true); + t.start(); + + assertTrue(proceedWriteLatch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); + + raf.write(3); + } + + try (RandomAccessFile raf = new RandomAccessFile(testF, "r");) { + assertEquals(1, raf.read()); + assertEquals(2, raf.read()); + assertEquals(3, raf.read()); + } + } + finally { + assertTrue(!testF.exists() || testF.delete()); + } + assertTrue(readFlag.get()); } /** */ From 361dc42bc1e0f6ada45d0b3b05b35fb9f405c61f Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Thu, 8 Oct 2026 14:16:56 +0300 Subject: [PATCH 85/97] raw impl --- docs/_docs/snapshots/snapshots.adoc | 35 ++ .../IgniteControlUtilityTestSuite6.java | 32 ++ .../util/GridCommandHandlerAbstractTest.java | 23 +- .../GridCommandHandlerListSnapshotTest.java | 365 ++++++++++++++++ .../management/snapshot/SnapshotCommand.java | 3 +- .../snapshot/SnapshotListCommand.java | 116 +++++ .../snapshot/SnapshotListCommandArg.java | 45 ++ .../management/snapshot/SnapshotListTask.java | 410 ++++++++++++++++++ .../filename/SnapshotFileTree.java | 1 - .../snapshot/IgniteSnapshotManager.java | 28 +- .../snapshot/SnapshotCheckProcess.java | 2 +- .../snapshot/SnapshotListJobResult.java | 133 ++++++ .../snapshot/SnapshotListTaskResult.java | 68 +++ .../SnapshotMetadataVerificationTask.java | 5 +- .../DistributedMetaStorageImpl.java | 2 +- .../feature/CoreFeatureRegistry.java | 3 + ...ClusterSnapshotListRollingUpgradeTest.java | 245 +++++++++++ .../IgniteClusterSnapshotListTest.java | 402 +++++++++++++++++ .../AbstractRollingUpgradeTest.java | 2 + .../TestIgniteReleaseFeatures_2_19_4.java | 24 + .../TestIgniteReleaseFeatures_2_19_5.java | 27 ++ .../testsuites/IgniteSnapshotTestSuite9.java | 54 +++ .../tests/ContinuousDataLoadApplication.java | 2 +- 23 files changed, 2010 insertions(+), 17 deletions(-) create mode 100644 modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite6.java create mode 100644 modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommandArg.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListTaskResult.java create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListRollingUpgradeTest.java create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_4.java create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_5.java create mode 100644 modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite9.java diff --git a/docs/_docs/snapshots/snapshots.adoc b/docs/_docs/snapshots/snapshots.adoc index fe61b3c49234a..e41020a280e82 100644 --- a/docs/_docs/snapshots/snapshots.adoc +++ b/docs/_docs/snapshots/snapshots.adoc @@ -287,6 +287,41 @@ control.(sh|bat) --snapshot restore snapshot_09062021 --groups cache-group1,cach control.(sh|bat) --snapshot restore snapshot_09062021 --increment 1 ---- +== Listing Snapshots + +To list snapshots created on cluster, use the following command. + +[tabs] +-- +tab:Unix[] +[source,shell] +---- +# List all snapshots in the default snapshot directory. +control.sh --snapshot list + +# List all snapshots in the "/tmp/ignite/snapshots" folder. +control.sh --snapshot list --src /tmp/ignite/snapshots +---- + +tab:Windows[] +[source,shell] +---- +# List all snapshots in the default snapshot directory. +control.bat --snapshot list + +# List all snapshots in the "C:\\tmp\\ignite\\snapshots" folder. +control.bat --snapshot list --src C:\\tmp\\ignite\\snapshots +---- +-- + +=== List operation notes + +* The list operation is read-only and does not modify any snapshot data. +* If a snapshot is being modified (created or deleted), its size might not be reported. +* Snapshot is being detected by its metadata. If metadata is missing or cannot be read, snapshot is ignored. +* When snapshot listing and size calculating, no additional snapshot validation is done. Files aren't read, no checksums +are calculated. + == Getting Snapshot Operation Status The status of the current snapshot operation in the cluster can be obtained using the `control.sh|bat` script or JMX interface: diff --git a/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite6.java b/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite6.java new file mode 100644 index 0000000000000..c2c5a848f6d2f --- /dev/null +++ b/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite6.java @@ -0,0 +1,32 @@ +/* + * 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.ignite.testsuites; + +import org.apache.ignite.util.GridCommandHandlerListSnapshotTest; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +/** + * Test suite for control utility. + */ +@RunWith(Suite.class) +@Suite.SuiteClasses({ + GridCommandHandlerListSnapshotTest.class +}) +public class IgniteControlUtilityTestSuite6 { +} diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerAbstractTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerAbstractTest.java index 092fe78903246..7d5f2e14c289d 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerAbstractTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerAbstractTest.java @@ -499,6 +499,22 @@ protected void createCacheAndPreload( ) { assert nonNull(ignite); + CacheConfiguration ccfg = testCacheConfiguration(cacheName, partitions, filter); + + ignite.createCache(ccfg); + + try (IgniteDataStreamer streamer = ignite.dataStreamer(cacheName)) { + for (int i = 0; i < countEntries; i++) + streamer.addData(i, i); + } + } + + /** */ + protected CacheConfiguration testCacheConfiguration( + String cacheName, + int partitions, + @Nullable IgnitePredicate filter + ) { CacheConfiguration ccfg = new CacheConfiguration<>(cacheName) .setAffinity(new RendezvousAffinityFunction(false, partitions)) .setBackups(1) @@ -507,12 +523,7 @@ protected void createCacheAndPreload( if (filter != null) ccfg.setNodeFilter(filter); - ignite.createCache(ccfg); - - try (IgniteDataStreamer streamer = ignite.dataStreamer(cacheName)) { - for (int i = 0; i < countEntries; i++) - streamer.addData(i, i); - } + return ccfg; } /** diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java new file mode 100644 index 0000000000000..3c4d08f2e8ec6 --- /dev/null +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java @@ -0,0 +1,365 @@ +/* + * 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.ignite.util; + +import java.io.File; +import java.nio.file.DirectoryStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collection; +import org.apache.ignite.Ignite; +import org.apache.ignite.IgniteDataStreamer; +import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.configuration.CacheConfiguration; +import org.apache.ignite.configuration.DataStorageConfiguration; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.management.snapshot.SnapshotListCommand; +import org.apache.ignite.internal.processors.cache.persistence.filename.SnapshotFileTree; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.G; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.lang.IgnitePredicate; +import org.apache.ignite.testframework.GridTestUtils; +import org.jetbrains.annotations.Nullable; +import org.junit.Test; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import static java.nio.file.Files.newDirectoryStream; +import static org.apache.ignite.cluster.ClusterState.ACTIVE; +import static org.apache.ignite.internal.commandline.CommandHandler.EXIT_CODE_OK; +import static org.apache.ignite.internal.processors.cache.persistence.snapshot.AbstractSnapshotSelfTest.snp; +import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; + +/** Test for the command '--snapshot list'. */ +public class GridCommandHandlerListSnapshotTest extends GridCommandHandlerAbstractTest { + /** Extra storage path. */ + private static final String EXT_STORAGE_PATH = "extStorage"; + + /** Flag to use {@link DataStorageConfiguration#setExtraSnapshotPaths(String...)}. */ + private boolean extraStorages; + + /** Resolved extra storages paths. {@code null} if {@code extraStorages} is {@code null}. */ + private @Nullable String[] extStoragePaths; + + /** Node consistent id postfix. */ + private @Nullable String cstId_postfix = ""; + + /** Flag setting the usage of a custom snapshot path. */ + @Parameter(1) + public boolean customPath; + + /** Flag setting the usage of dedicated, own node working directories. */ + @Parameter(2) + public boolean separatedWorkDir; + + /** Flag to add extra server node after the snapshot creation. */ + @Parameter(3) + public boolean addExtraSrvr; + + /** Number of incremental snapshots to add to the main test snapshots. */ + @Parameter(4) + public int incCnt; + + /** */ + @Parameters(name = "cmdHnd={0},customPath={1},ownWorkDir={2},addExtraSrvr={3},incCnt={4}") + public static Collection parameters() { + return GridTestUtils.cartesianProduct( + commandHandlers(), + F.asList(false, true), // Custom snapshot path + F.asList(false, true), // Separated (own) work directories + F.asList(false, true), // Add a server node after the snapshot creation + F.asList(0, 2) // Number of incremental snapshots + ); + } + + /** {@inheritDoc} */ + @Override protected void afterTest() throws Exception { + super.afterTest(); + + stopAllGrids(); + + cleanPersistenceDir(); + } + + /** {@inheritDoc} */ + @Override protected void beforeTest() throws Exception { + super.beforeTest(); + + /** Handy if test running is interrupted and {@link #afterTest()} isn't invoked. */ + cleanPersistenceDir(); + } + + /** {@inheritDoc} */ + @Override protected void cleanPersistenceDir() throws Exception { + super.cleanPersistenceDir(); + + // Also cleans separated snapshot working directories and custom snapshot paths. + try (DirectoryStream files = newDirectoryStream(Paths.get(U.defaultWorkDirectory()))) { + for (Path path : files) + U.delete(path); + } + } + + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { + IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); + + String workDir = separatedWorkDir + ? new File(U.defaultWorkDirectory(), igniteInstanceName).getAbsolutePath() + : U.defaultWorkDirectory(); + + cfg.setWorkDirectory(workDir); + + cfg.getDataStorageConfiguration().setWalCompactionEnabled(incCnt > 0); + + if (extraStorages) { + cfg.getDataStorageConfiguration().setExtraStoragePaths( + workDir + File.separator, + workDir + File.separator + EXT_STORAGE_PATH + ); + + extStoragePaths = cfg.getDataStorageConfiguration().getExtraStoragePaths(); + + cfg.getDataStorageConfiguration().setExtraSnapshotPaths("", EXT_STORAGE_PATH); + } + + return cfg; + } + + /** {@inheritDoc} */ + @Override public String getTestIgniteInstanceName() { + return super.getTestIgniteInstanceName() + cstId_postfix; + } + + /** */ + @Test + public void testNoSnapshots() throws Exception { + assumeFalse(addExtraSrvr || incCnt > 0); + + doTestSnapshotsLists(0, false, false); + } + + /** */ + @Test + public void testSingleSnapshot() throws Exception { + doTestSnapshotsLists(1, false, false); + } + + /** */ + @Test + public void testSeveralSnapshots() throws Exception { + doTestSnapshotsLists(3, false, false); + } + + /** */ + @Test + public void testOneNodeMisses() throws Exception { + // Doesn't matter here. + assumeFalse(incCnt > 0); + // Let's keep just one node not seeing the snapshot. + assumeTrue(separatedWorkDir); + // Almost the same tests. + assumeFalse(addExtraSrvr); + + doTestSnapshotsLists(2, true, false); + } + + /** */ + @Test + public void testExtraStorages() throws Exception { + // Extra storeages are required to be the same as configured in the node's PDS storages. Thus, we skip different work folders. + // Also, extra snapshot storages aren't used if snapshot is created with a custom path. + assumeFalse(separatedWorkDir || customPath); + + extraStorages = true; + + doTestSnapshotsLists(2, false, false); + } + + /** */ + @Test + public void testChangedConsistentId() throws Exception { + // In new created working directories there will be obviously no snapshots. + assumeFalse(separatedWorkDir); + // Fastens the tests + assumeFalse(incCnt > 0); + + doTestSnapshotsLists(1, false, true); + } + + /** */ + private void doTestSnapshotsLists(int snpCnt, boolean deleteOnOneNode, boolean restartWithChangedCstIds) throws Exception { + // A custom snapshot path actually puts snapshots in a shared directory. This skews the results when dedicated + // work directories are set. + assumeFalse(customPath && separatedWorkDir); + + int srvrsCnt = 3; + int entriesCnt = 10; + int partitions = 4; + + IgniteEx ig = (IgniteEx)startGridsMultiThreaded(srvrsCnt); + + startGrid(CLIENT_NODE_NAME_PREFIX); + + ig.cluster().state(ACTIVE); + + File snpsRootFile = customPath + ? new File(ig.context().pdsFolderResolver().fileTree().snapshotsRoot(), "ex_snapshots") + : null; + + // Flag if 'testSnapshot0' deleted on node0. + boolean grid0HasNoSnapshot0 = false; + + // Create shapshots. + if (snpCnt > 0) { + createCacheAndPreload(ig, DEFAULT_CACHE_NAME, entriesCnt, partitions, null); + + String absPathStr = null; + + for (int snpIdx = 0; snpIdx < snpCnt; snpIdx++) { + absPathStr = customPath ? snpsRootFile.getAbsolutePath() : null; + + snp(ig).createSnapshot("testSnapshot" + snpIdx, absPathStr, false, false).get(getTestTimeout()); + + for (int incIdx = 0; incIdx < incCnt; incIdx++) { + try (IgniteDataStreamer ds = ig.dataStreamer(DEFAULT_CACHE_NAME)) { + for (int val = (snpIdx + incIdx + 1) * entriesCnt; val < (snpIdx + incIdx + 2) * entriesCnt; val++) + ds.addData(val, val); + } + + snp(ig).createSnapshot("testSnapshot" + snpIdx, absPathStr, true, false).get(getTestTimeout()); + } + } + + if (deleteOnOneNode) { + SnapshotFileTree sft = new SnapshotFileTree(ig.context(), "testSnapshot0", absPathStr); + + assertTrue(sft.root().exists()); + assertTrue(U.delete(sft.root())); + assertFalse(sft.root().exists()); + + grid0HasNoSnapshot0 = true; + } + } + + if (restartWithChangedCstIds) { + stopAllGrids(); + + cstId_postfix = "_changed"; + + startGridsMultiThreaded(srvrsCnt); + + startGrid(CLIENT_NODE_NAME_PREFIX); + } + + // Add a server. + if (addExtraSrvr) + startGrid(G.allGrids().size()); + + injectTestSystemOut(); + + // Requests snapshots. + if (customPath) { + assertEquals(EXIT_CODE_OK, execute(newCommandHandler(createTestLogger()), "--snapshot", "list", "--src", + snpsRootFile.getAbsolutePath())); + } + else + assertEquals(EXIT_CODE_OK, execute(newCommandHandler(createTestLogger()), "--snapshot", "list")); + + String out = testOut.toString(); + + // Find the nodes in the output. + for (Ignite g : G.allGrids()) { + ClusterNode n = g.cluster().localNode(); + + assertEquals(n.isClient() ? 0 : 1, countEntries(out, "Node '%s'".formatted(n.consistentId().toString()))); + } + + // Ensure that there are no snapshots. + if (snpCnt == 0) { + assertFalse(out.contains("Snapshot '")); + + assertEquals(srvrsCnt + (addExtraSrvr ? 1 : 0), countEntries(out, SnapshotListCommand.NO_SNAPSHOTS)); + + return; + } + + assertEquals((addExtraSrvr && separatedWorkDir ? 1 : 0), countEntries(out, SnapshotListCommand.NO_SNAPSHOTS)); + + // The additional server node doesn't have snapshots. But it can see them if shared the snapshot directory. + int snpsRecordsCnt = srvrsCnt + (addExtraSrvr + ? (separatedWorkDir ? 0 : 1) + : 0 + ); + + // Find the snapshots in the output. + for (int snpIdx = 0; snpIdx < snpCnt; snpIdx++) { + // 'testSnapshot0' has fewer records if was deleted on one node. + int certainSnpRecordsCnt = snpIdx == 0 && grid0HasNoSnapshot0 + ? snpsRecordsCnt - 1 + : snpsRecordsCnt; + + assertEquals(certainSnpRecordsCnt, countEntries(out, "Snapshot 'testSnapshot" + snpIdx + "'")); + + for (int i = 0; i < incCnt; i++) + assertEquals(certainSnpRecordsCnt * snpCnt, countEntries(out, "incremental snapshots: cnt=" + incCnt)); + + if (extraStorages) + assertEquals(certainSnpRecordsCnt * snpCnt, countEntries(out, "external storages: cnt=1, size=")); + } + } + + /** Counts occurrences of the node prefix in the output. */ + private int countNodeOccurrences(String output) { + int cnt = 0; + int idx = 0; + + while ((idx = output.indexOf(SnapshotListCommand.NODE_PREF, idx)) != -1) { + cnt++; + idx += SnapshotListCommand.NODE_PREF.length(); + } + + return cnt; + } + + /** */ + private static int countEntries(String txt, String entry) { + String prev = txt; + + txt = txt.replaceAll(entry, ""); + + return (prev.length() - txt.length()) / entry.length(); + } + + /** */ + @Override protected CacheConfiguration testCacheConfiguration( + String cacheName, + int partitions, + @Nullable IgnitePredicate filter + ) { + CacheConfiguration ccfg = super.testCacheConfiguration(cacheName, partitions, filter); + + if (extraStorages) + ccfg.setStoragePaths(extStoragePaths); + + return ccfg; + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCommand.java index deb5416b8e28b..e9d4e7504351d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCommand.java @@ -28,7 +28,8 @@ public SnapshotCommand() { new SnapshotCancelCommand(), new SnapshotCheckCommand(), new SnapshotRestoreCommand(), - new SnapshotStatusCommand() + new SnapshotStatusCommand(), + new SnapshotListCommand() ); } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java new file mode 100644 index 0000000000000..c0f6b762bebd7 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java @@ -0,0 +1,116 @@ +/* + * 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.ignite.internal.management.snapshot; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.function.Consumer; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListJobResult; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListTaskResult; +import org.apache.ignite.internal.util.typedef.internal.U; + +/** Snapshot list command. */ +public class SnapshotListCommand extends AbstractSnapshotCommand { + /** */ + public static final String HEADER = "Snapshots found on the following nodes:"; + + /** */ + public static final String DESC = "Lists all snapshots on all online server nodes with their sizes"; + + /** */ + public static final String NO_SNAPSHOTS = "No snapshots found."; + + /** */ + public static final String NODE_PREF = "Node "; + + /** */ + private static final String PATTERN_FORMAT = "yyyy-MM-dd HH:mm:ss Z"; + + /** */ + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(PATTERN_FORMAT).withZone(ZoneId.systemDefault()); + + /** {@inheritDoc} */ + @Override public String description() { + return DESC; + } + + /** {@inheritDoc} */ + @Override public Class argClass() { + return SnapshotListCommandArg.class; + } + + /** {@inheritDoc} */ + @Override public Class taskClass() { + return SnapshotListTask.class; + } + + /** {@inheritDoc} */ + @Override public void printResult(SnapshotListCommandArg arg, SnapshotListTaskResult res, Consumer printer) { + printer.accept(HEADER); + + for (int nodeIdx = 0; nodeIdx < res.nodesIds().length; nodeIdx++) { + // Skip line before node. + printer.accept(""); + + printer.accept("\tNode '%s' [uuid=%s]:".formatted(res.consistentIds()[nodeIdx], res.nodesIds()[nodeIdx])); + + SnapshotListJobResult nodeResult = res.snapshots()[nodeIdx]; + + if (nodeResult.snapshots().isEmpty()) { + printer.accept("\t\t" + NO_SNAPSHOTS); + + continue; + } + + nodeResult.snapshots().forEach((snpName, snpInfo) -> { + printer.accept("\t\tSnapshot '%s': totalSize=%s (%db), created='%s' (epoch=%d)".formatted( + snpName, + U.humanReadableByteCount(snpInfo.size()), + snpInfo.size(), + DATE_FORMATTER.format(Instant.ofEpochMilli(snpInfo.date())), + snpInfo.date() + )); + + SnapshotListJobResult.SnapshotInfo extStors = snpInfo.externalStorages(); + SnapshotListJobResult.SnapshotInfo incs = snpInfo.incrementals(); + + if (extStors != null) { + printer.accept("\t\t\texternal storages: cnt=%d, size=%s (%db)".formatted( + extStors.number(), + U.humanReadableByteCount(extStors.size()), + extStors.size() + )); + } + + if (incs != null) { + printer.accept("\t\t\tincremental snapshots: cnt=%d, size=%s (%db), modified='%s' (epoch=%d)".formatted( + incs.number(), + U.humanReadableByteCount(incs.size()), + incs.size(), + DATE_FORMATTER.format(Instant.ofEpochMilli(incs.date())), + incs.date() + )); + } + }); + } + + // Drop a line. + printer.accept(""); + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommandArg.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommandArg.java new file mode 100644 index 0000000000000..4967052f7351f --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommandArg.java @@ -0,0 +1,45 @@ +/* + * 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.ignite.internal.management.snapshot; + +import org.apache.ignite.internal.Order; +import org.apache.ignite.internal.dto.IgniteDataTransferObject; +import org.apache.ignite.internal.management.api.Argument; +import org.jetbrains.annotations.Nullable; + +/** */ +public class SnapshotListCommandArg extends IgniteDataTransferObject { + /** */ + private static final long serialVersionUID = 0; + + /** */ + @Order(0) + @Argument(example = "path/to/snapshots", optional = true, description = "Path to snapshot location directory. " + + "If not specified or specified a relative path, the default snapshot configuration directory will be used") + @Nullable String src; + + /** */ + public @Nullable String src() { + return src; + } + + /** */ + public void src(@Nullable String src) { + this.src = src; + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java new file mode 100644 index 0000000000000..ef3909f74d44d --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java @@ -0,0 +1,410 @@ +/* + * 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.ignite.internal.management.snapshot; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; +import org.apache.ignite.IgniteException; +import org.apache.ignite.IgniteLogger; +import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.compute.ComputeJobResult; +import org.apache.ignite.internal.NodeStoppingException; +import org.apache.ignite.internal.processors.cache.persistence.filename.SnapshotFileTree; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.IncrementalSnapshotMetadata; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListJobResult; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListTaskResult; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotMetadata; +import org.apache.ignite.internal.processors.rollingupgrade.feature.CoreFeatureRegistry; +import org.apache.ignite.internal.processors.task.GridInternal; +import org.apache.ignite.internal.thread.pool.IgniteThreadPoolExecutor; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.T2; +import org.apache.ignite.internal.visor.VisorJob; +import org.apache.ignite.internal.visor.VisorMultiNodeTask; +import org.apache.ignite.internal.visor.VisorTaskArgument; +import org.apache.ignite.resources.LoggerResource; +import org.jetbrains.annotations.Nullable; + +/** */ +@GridInternal +public class SnapshotListTask extends VisorMultiNodeTask { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** {@inheritDoc} */ + @Override protected VisorJob job(SnapshotListCommandArg arg) { + return new SnapshotListJob(arg, debug); + } + + /** {@inheritDoc} */ + @Override protected Collection jobNodes(VisorTaskArgument arg) { + if (!ignite.context().rollingUpgrade().features().isActive(CoreFeatureRegistry.SNAPSHOT_LIST_FEATURE)) + throw new IgniteException("Won't search for local snapshots. The snapshot list feature isn't activated yet."); + + /** Allows {@link #map0(List, VisorTaskArgument)} to use the entire subgrid. */ + return ignite.cluster().forServers().nodes().stream().map(ClusterNode::id).collect(Collectors.toList()); + } + + /** {@inheritDoc} */ + @Override protected SnapshotListTaskResult reduce0(List nodesJobsResults) throws IgniteException { + String[] cstIds = new String[nodesJobsResults.size()]; + UUID[] nodesIds = new UUID[nodesJobsResults.size()]; + SnapshotListJobResult[] nodesResults = new SnapshotListJobResult[nodesJobsResults.size()]; + + // Sorting the results by consistend by for better reading. + nodesJobsResults = nodesJobsResults.stream() + .sorted((jr0, jr1) -> nodeConsistentId(jr0.getNode()).compareTo(nodeConsistentId(jr1.getNode()))) + .toList(); + + for (int i = 0; i < nodesJobsResults.size(); i++) { + ComputeJobResult nodeJobRes = nodesJobsResults.get(i); + + if (nodeJobRes.getException() != null) { + throw new IgniteException("Failed to execute snapshot list job on node [uuid=" + nodeJobRes.getNode().id() + ']', + nodeJobRes.getException()); + } + + assert nodeJobRes.getData() != null; + + cstIds[i] = nodeConsistentId(nodeJobRes.getNode()); + nodesIds[i] = nodeJobRes.getNode().id(); + nodesResults[i] = nodeJobRes.getData(); + } + + return new SnapshotListTaskResult(cstIds, nodesIds, nodesResults); + } + + /** */ + private String nodeConsistentId(ClusterNode n) { + UUID nodeId = n.id(); + + n = ignite.context().discovery().node(nodeId); + + if (n == null) + n = ignite.context().discovery().historicalNode(nodeId); + + return n == null ? "" : n.consistentId().toString(); + } + + /** + * Walk though a directory. Doesn't lock it or its content. Tries to find files and summarize their size. + * Tolerates and skips concurrent modification errors. + */ + public static long calculateDirectorySize(File path) throws IOException { + AtomicLong size = new AtomicLong(0); + AtomicBoolean entered = new AtomicBoolean(); + + Files.walkFileTree(path.toPath(), new SimpleFileVisitor<>() { + @Override public FileVisitResult postVisitDirectory(Path dir, IOException err) { + return FileVisitResult.CONTINUE; + } + + @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + // Use attrs instead of Files.size() for efficiency. + if (attrs.isRegularFile()) + size.addAndGet(attrs.size()); + else if (!entered.get() && file.toFile().equals(path)) + entered.set(true); + + return FileVisitResult.CONTINUE; + } + + @Override public FileVisitResult visitFileFailed(Path file, IOException err) throws IOException { + if (!entered.get() && file.toFile().equals(path)) { + // Cant even start shapshot size calculation - can't enter snapshot directory. + throw err; + } + + return FileVisitResult.CONTINUE; + } + }); + + return size.get(); + } + + /** */ + private static class SnapshotListJob extends SnapshotJob { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** */ + @LoggerResource + private IgniteLogger log; + + /** + * @param arg Snapshot list task argument. + * @param debug Flag indicating whether debug information should be printed into node log. + */ + protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { + super(arg, debug); + } + + /** {@inheritDoc} */ + @Override protected SnapshotListJobResult run(SnapshotListCommandArg arg) { + assert !ignite.localNode().isClient(); + + if (ignite.context().isStopping()) + throw new IgniteException("Won't search for local snapshots.", new NodeStoppingException("Node is stopping.")); + + // Read local snapshots. + List> locSnps = findLocalSnapshots(arg.src()); + + if (locSnps.isEmpty()) + return new SnapshotListJobResult(Collections.emptyMap()); + + // Optional external storages descriptions (per snapshot name). + Map extStors = new ConcurrentHashMap<>(locSnps.size(), 1.0f); + // Optional incremental parts descriptions (per snapshot name). + Map incs = new ConcurrentHashMap<>(locSnps.size(), 1.0f); + + // Incremental parts and external storages futures. + List> futs = new ArrayList<>(locSnps.size() * 2); + // Names of snapshots of with read failures. + Set failedSnps = ConcurrentHashMap.newKeySet(locSnps.size() / 2); + + IgniteThreadPoolExecutor exec = ignite.context().pools().getSnapshotExecutorService(); + + // Optional descriptions of snapshot external storages and incremental parts. + for (T2 snpPair : locSnps) { + SnapshotFileTree sft = snpPair.get1(); + String snpName = sft.name(); + + // Future for optional exernal storages. + Future fut = exec.submit(() -> { + if (ignite.context().isStopping()) + throw new IgniteException("Won't search for local snapshots.", new NodeStoppingException("Node is stopping.")); + + try { + SnapshotListJobResult.SnapshotInfo extDesc = externalStorages(sft); + + if (extDesc != null) + extStors.put(snpName, extDesc); + } + catch (Throwable t) { + // Skip snapshot in case on any failure. + failedSnps.add(snpName); + + log.warning("Failed to read snapshot's extra storages, snapshot ignored [snpName=" + snpName + ']', t); + } + }); + + futs.add(fut); + + // Future for optional incremental parts. + fut = exec.submit(() -> { + if (failedSnps.contains(snpName)) + return; + + if (ignite.context().isStopping()) + throw new IgniteException("Won't search for local snapshots.", new NodeStoppingException("Node is stopping.")); + + try { + SnapshotListJobResult.SnapshotInfo incDesc = incrementals(sft); + + if (incDesc != null) + incs.put(snpName, incDesc); + } + catch (Throwable t) { + // Skip snapshot in case on any failure. + failedSnps.add(snpName); + + log.warning("Failed to read snapshot's incremental parts, snapshot ignored [snpName=" + snpName + ']', t); + } + }); + + futs.add(fut); + } + + // Wait for the snapshot optional description futures. + for (Future fut : futs) { + try { + fut.get(); + } + catch (Throwable t) { + // All the futures has internal exceptions being logged. No errors expected. + throw new IgniteException("Failed to read local nodes snapshots.", t); + } + } + + // Result snapshot descriptions. + Map resMap = new ConcurrentHashMap<>(locSnps.size() - failedSnps.size(), 1.0f); + + // Reduce results. + for (T2 snpPair : locSnps) { + SnapshotFileTree sft = snpPair.get1(); + String snpName = sft.name(); + + if (failedSnps.contains(snpName)) + continue; + + long size; + + try { + size = calculateDirectorySize(sft.root()); + } + catch (Exception e) { + log.warning("Failed to calculate snapshot' size, snapshot ignored [snpName=" + snpName + ']', e); + + // Skip snapshot in case on any failure. + continue; + } + + SnapshotListJobResult.SnapshotInfo snpDesc = new SnapshotListJobResult.SnapshotInfo( + size, + snpPair.get2(), + extStors.get(snpName), + incs.get(snpName) + ); + + resMap.put(snpName, snpDesc); + } + + return new SnapshotListJobResult(resMap); + } + + /** */ + private static @Nullable SnapshotListJobResult.SnapshotInfo externalStorages(SnapshotFileTree sft) throws IOException { + int extStoragesCnt = 0; + long extStoragesSize = 0; + + for (File es : sft.allStorages().toList()) { + if (sft.nodeStorage().equals(es)) + continue; + + extStoragesCnt++; + extStoragesSize += calculateDirectorySize(es); + } + + return extStoragesCnt == 0 ? null : new SnapshotListJobResult.SnapshotInfo(extStoragesCnt, extStoragesSize); + } + + /** @return Snapshot file tree and creation time from the snapshot metadata. */ + private List> findLocalSnapshots(@Nullable String snpPath) { + // The tree is used only to extract the snapshots root directory. The snapshot name isn't used. + File[] dirsToParse = new SnapshotFileTree(ignite.context(), "snp", snpPath).root().getParentFile().listFiles(); + + if (F.isEmpty(dirsToParse)) + return Collections.emptyList(); + + List>> futs = new ArrayList<>(dirsToParse.length); + + IgniteThreadPoolExecutor exec = ignite.context().pools().getSnapshotExecutorService(); + + for (File snpDir : dirsToParse) { + Future> snpDirFut = exec.submit(() -> { + if (ignite.context().isStopping()) + throw new IgniteException("Won't search for local snapshots.", new NodeStoppingException("Node is stopping.")); + + String snpName = snpDir.getName(); + + // Snapshot tree being used as a path, to read the metas only. + SnapshotFileTree sft = new SnapshotFileTree(ignite.context(), snpName, snpPath); + + List metas = ignite.context().cache().context().snapshotMgr().readSnapshotMetadatas(sft, false); + + if (metas.isEmpty()) + return null; + + // Get the last-created time snapshot metadata. + SnapshotMetadata snpMeta = metas.stream() + .max((m0, m1) -> Math.toIntExact(m0.snapshotTime() - m1.snapshotTime())) + .orElse(new SnapshotMetadata()); + + // Real, meta-based snapshot file tree. Can belong to other cluster, other consistent id. + sft = new SnapshotFileTree( + ignite.configuration(), + ignite.context().pdsFolderResolver().fileTree(), + snpName, + snpPath, + snpMeta.folderName(), + snpMeta.consistentId() + ); + + return new T2<>(sft, snpMeta.snapshotTime()); + }); + + futs.add(snpDirFut); + } + + List> res = new ArrayList<>(dirsToParse.length); + + futs.forEach(f -> { + try { + T2 snpDirRes = f.get(); + + if (snpDirRes != null) + res.add(snpDirRes); + } + catch (Exception e) { + // Skip snapshot in case on any failure. + log.warning("Failed to read snapshot, snapshot ignored [path=" + snpPath + ']', e); + } + }); + + return res; + } + + /** @return Number, total size and last creation time of incremental snapshots. */ + private @Nullable SnapshotListJobResult.SnapshotInfo incrementals(SnapshotFileTree sft) throws Exception { + File[] incs = sft.incrementsRoot().listFiles(); + + if (F.isEmpty(incs)) + return null; + + int cnt = 0; + long size = 0L; + long createTime = 0L; + + IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); + + for (File incDir : incs) { + int incIdx = Integer.parseInt(incDir.getName()); + + SnapshotFileTree.IncrementalSnapshotFileTree incTree = sft.incrementalSnapshotFileTree(incIdx); + + IncrementalSnapshotMetadata incMeta = snpMgr.readIncrementalSnapshotMetadata(incTree.meta()); + + size += calculateDirectorySize(incDir); + + cnt++; + + createTime = Math.max(createTime, incMeta.snapshotTime()); + } + + return new SnapshotListJobResult.SnapshotInfo(cnt, size, createTime); + } + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java index f53abc94ba480..ff9e58d5a9cde 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java @@ -457,7 +457,6 @@ private NodeFileTree tempFileTree(GridKernalContext ctx) { return res; } - /** {@inheritDoc} */ @Override public String toString() { return S.toString(SnapshotFileTree.class, this); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java index 4d32bc1d38c09..2a0aeffc6023e 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java @@ -1830,6 +1830,21 @@ public T readFromFile(File smf) throws IgniteCheckedException, IOException { * local node will be placed on the first place. */ public List readSnapshotMetadatas(SnapshotFileTree sft) { + return readSnapshotMetadatas(sft, true); + } + + /** + * Note, there can be snapshots from other nodes. + * This method will read all metadata. + * Some instances can return {@link SnapshotMetadata#folderName()} and {@link SnapshotMetadata#consistentId()} that differs from local. + * + * @param sft Snapshot file tree. + * @param failIfCantRead If {@code true}, throws an exeption if cant read a metadata file. + * @return List of snapshot metadata for the given snapshot name on local node. + * If snapshot has been taken from local node the snapshot metadata for given + * local node will be placed on the first place. + */ + public List readSnapshotMetadatas(SnapshotFileTree sft, boolean failIfCantRead) { if (!(sft.root().exists() && sft.root().isDirectory())) return Collections.emptyList(); @@ -1841,8 +1856,8 @@ public List readSnapshotMetadatas(SnapshotFileTree sft) { Map metasMap = new HashMap<>(); SnapshotMetadata prev = null; - try { - for (File smf : smfs) { + for (File smf : smfs) { + try { SnapshotMetadata curr = readSnapshotMetadata(smf); if (prev != null && !prev.sameSnapshot(curr)) { @@ -1854,9 +1869,12 @@ public List readSnapshotMetadatas(SnapshotFileTree sft) { prev = curr; } - } - catch (IgniteCheckedException | IOException e) { - throw new IgniteException(e); + catch (Exception e) { + if (failIfCantRead) + throw new IgniteException("Fail to read snapshot metadata [meta=" + smf + ']', e); + else + log.error("Fail to read snapshot metadata, skipped [meta=" + smf + ']', e); + } } SnapshotMetadata currNodeSmf = metasMap.remove(cctx.localNode().consistentId().toString()); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java index 2f19878617a15..1b9bde12f691b 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java @@ -770,7 +770,7 @@ private static final class SnapshotCheckContext { */ @Nullable private volatile List metas; - /** Map of snapshot pathes per consistent id for {@link #metas}. */ + /** Map of snapshot paths per consistent id for {@link #metas}. */ @GridToStringInclude @Nullable private Map locFileTree; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java new file mode 100644 index 0000000000000..295b22b8fe927 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java @@ -0,0 +1,133 @@ +/* + * 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.ignite.internal.processors.cache.persistence.snapshot; + +import java.util.Map; +import org.apache.ignite.internal.Order; +import org.apache.ignite.internal.dto.IgniteDataTransferObject; +import org.jetbrains.annotations.Nullable; + +/** Per-node result of the snapshot lists command. Contains information of the snapshots found on current node. */ +public final class SnapshotListJobResult extends IgniteDataTransferObject { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** Local node's snapshot descriptions. */ + @Order(0) + Map snapshots; + + /** Default constructor for serialization purposes. */ + public SnapshotListJobResult() { + // No-op. + } + + /** */ + public SnapshotListJobResult(Map snapshots) { + this.snapshots = snapshots; + } + + /** @return The snapshots descriptions. */ + public Map snapshots() { + return snapshots; + } + + /** Holds combined snapshot data information: size, creation time, number of incremental parts or external storages. */ + public static class SnapshotInfo extends IgniteDataTransferObject { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** Total size of a snapshot. Or size of its external storages or incremental parts. */ + @Order(0) + long size; + + /** Creation date of snapshot or of its incremental parts. Is {@code null} for snapshot external storages description. */ + @Order(1) + @Nullable Long date; + + /** Snapshot external storages description if exists. Is always {@code null} for not the main snapshot description. */ + @Order(2) + @Nullable SnapshotInfo extStors; + + /** Snapshot incremental parts description if exists. Is always {@code null} for not the main snapshot description. */ + @Order(3) + @Nullable SnapshotInfo incs; + + /** Number of snapshot external storages or incremental parts. Is {@code null} for the main snapshot description. */ + @Order(4) + @Nullable Integer cnt; + + /** Empty constructor for serialization purposes. */ + public SnapshotInfo() { + // No-op. + } + + /** Creates snapshot main description. */ + public SnapshotInfo( + long size, + long date, + @Nullable SnapshotInfo extStors, + @Nullable SnapshotInfo incs + ) { + this.size = size + (extStors == null ? 0L : extStors.size()); + this.date = date; + this.extStors = extStors; + this.incs = incs; + } + + /** Creates external storages description. */ + public SnapshotInfo(int cnt, long size) { + this.cnt = cnt; + this.size = size; + } + + /** Creates incremental parts description. */ + public SnapshotInfo(int cnt, long size, long date) { + this.cnt = cnt; + this.size = size; + this.date = date; + } + + /** @return Total size of a snapshot. Or size of its external storages or incremental parts. */ + public long size() { + return size; + } + + /** + * @return Creation date of a snapshot or of its incremental parts. {@code Null} for snapshot + * external storages description. + */ + public @Nullable Long date() { + return date; + } + + /** @return Snapshot external storages description if exists. Is always {@code Null} for not the main snapshot description. */ + public @Nullable SnapshotInfo externalStorages() { + return extStors; + } + + /** @return Snapshot incremental parts description if exists. Is always {@code Nul} for not the main snapshot description. */ + public @Nullable SnapshotInfo incrementals() { + return incs; + } + + /** @return Number of snapshot external storages or incremental parts. Or {@code null} for the main snapshot data.*/ + public @Nullable Integer number() { + return cnt; + } + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListTaskResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListTaskResult.java new file mode 100644 index 0000000000000..6e347b9e7340a --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListTaskResult.java @@ -0,0 +1,68 @@ +/* + * 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.ignite.internal.processors.cache.persistence.snapshot; + +import java.util.UUID; +import org.apache.ignite.internal.Order; +import org.apache.ignite.internal.dto.IgniteDataTransferObject; +import org.apache.ignite.internal.management.snapshot.SnapshotListTask; + +/** Accumulated result of {@link SnapshotListTask}. */ +public final class SnapshotListTaskResult extends IgniteDataTransferObject { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** Nodes consistent ids. */ + @Order(0) + String[] cstIds; + + /** Nodes UUIDs. */ + @Order(1) + UUID[] nodesIds; + + /** Results. */ + @Order(2) + SnapshotListJobResult[] snapshots; + + /** Default constructor for serialization purposes. */ + public SnapshotListTaskResult() { + // No-op. + } + + /** */ + public SnapshotListTaskResult(String[] cstIds, UUID[] nodesIds, SnapshotListJobResult[] snapshots) { + this.cstIds = cstIds; + this.nodesIds = nodesIds; + this.snapshots = snapshots; + } + + /** @return Nodes consistent ids. */ + public String[] consistentIds() { + return cstIds; + } + + /** @return Nodes UUIDs. */ + public UUID[] nodesIds() { + return nodesIds; + } + + /** @return The results. */ + public SnapshotListJobResult[] snapshots() { + return snapshots; + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMetadataVerificationTask.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMetadataVerificationTask.java index 643838ac3c445..4a8fea2b43b73 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMetadataVerificationTask.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMetadataVerificationTask.java @@ -35,7 +35,10 @@ import org.apache.ignite.internal.processors.cache.persistence.wal.reader.IgniteWalIteratorFactory; import org.apache.ignite.internal.util.typedef.F; -/** Snapshot task to verify snapshot metadata on the baseline nodes for given snapshot name. */ +/** + * Snapshot task to verify snapshot metadata on the baseline nodes for given snapshot name. + * TODO : Revise in https://issues.apache.org/jira/browse/IGNITE-29062 + */ public class SnapshotMetadataVerificationTask implements Supplier> { /** */ private final IgniteEx ignite; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/metastorage/persistence/DistributedMetaStorageImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/metastorage/persistence/DistributedMetaStorageImpl.java index 575300772a905..bf7f6f34c2566 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/metastorage/persistence/DistributedMetaStorageImpl.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/metastorage/persistence/DistributedMetaStorageImpl.java @@ -103,7 +103,7 @@ *

*

Whole updates history until some point in the past is stored along with the data, so when an outdated node * connects to the cluster it will receive all the missing data and apply it locally. Listeners will also be invoked - * after such updates. If there's not enough history stored or joining node is clear then it'll receive shapshot of + * after such updates. If there's not enough history stored or joining node is clear then it'll receive snapshot of * distributed metastorage (usually called {@code fullData} in code) so there won't be inconsistencies. *

* diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/CoreFeatureRegistry.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/CoreFeatureRegistry.java index 92b7eeb1a8c5b..b6d79ee0f6d84 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/CoreFeatureRegistry.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/CoreFeatureRegistry.java @@ -93,4 +93,7 @@ public class CoreFeatureRegistry { /** */ public static final IgniteFeature ROLLING_UPGRADE_FEATURE = new IgniteCoreFeature(0); + + /** */ + public static final IgniteFeature SNAPSHOT_LIST_FEATURE = new IgniteCoreFeature(1); } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListRollingUpgradeTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListRollingUpgradeTest.java new file mode 100644 index 0000000000000..97b1bed7751df --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListRollingUpgradeTest.java @@ -0,0 +1,245 @@ +/* + * 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.ignite.internal.processors.cache.persistence.snapshot; + +import java.io.File; +import java.nio.file.DirectoryStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.UUID; +import org.apache.ignite.IgniteDataStreamer; +import org.apache.ignite.IgniteException; +import org.apache.ignite.cache.CacheAtomicityMode; +import org.apache.ignite.cache.CacheMode; +import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction; +import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.configuration.CacheConfiguration; +import org.apache.ignite.configuration.DataRegionConfiguration; +import org.apache.ignite.configuration.DataStorageConfiguration; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.IgniteInternalFuture; +import org.apache.ignite.internal.management.snapshot.SnapshotListCommandArg; +import org.apache.ignite.internal.management.snapshot.SnapshotListTask; +import org.apache.ignite.internal.processors.rollingupgrade.AbstractRollingUpgradeTest; +import org.apache.ignite.internal.util.distributed.SingleNodeMessage; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.internal.visor.VisorTaskArgument; +import org.apache.ignite.testframework.GridTestUtils; +import org.junit.Test; + +import static java.nio.file.Files.newDirectoryStream; +import static org.apache.ignite.internal.TestRecordingCommunicationSpi.spi; +import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RU_PREPARE_VERSION_FINALIZATION; +import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause; +import static org.apache.ignite.testframework.GridTestUtils.waitForCondition; + +/** */ +public class IgniteClusterSnapshotListRollingUpgradeTest extends AbstractRollingUpgradeTest { + /** */ + private static final int ALL_GRIDS = 4; + + /** */ + private static final int CLIENTS = 1; + + /** */ + private static final String SNP_NAME = "testSnapshot"; + + /** {@inheritDoc} */ + @Override protected void afterTest() throws Exception { + super.afterTest(); + + cleanPersistenceDir(); + + // Clean all: also separated snapshot working directories. + try (DirectoryStream files = newDirectoryStream(Paths.get(U.defaultWorkDirectory()))) { + for (Path path : files) + U.delete(path); + } + } + + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName, String ver) throws Exception { + IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName, ver); + + cfg.setDataStorageConfiguration( + new DataStorageConfiguration() + .setDefaultDataRegionConfiguration( + new DataRegionConfiguration() + .setPersistenceEnabled(true) + .setMaxSize(DataStorageConfiguration.DFLT_DATA_REGION_INITIAL_SIZE) + ) + ); + + cfg.setWorkDirectory(new File(U.defaultWorkDirectory(), igniteInstanceName).getAbsolutePath()); + + return cfg; + } + + /** */ + @Test + public void testParallelRollingUpgradeInProgress() throws Exception { + for (int i = 0; i < ALL_GRIDS; i++) + startGrid(i, "2.19.4", i >= ALL_GRIDS - CLIENTS); + + grid(0).cluster().active(true); + + int testNodeIx = ALL_GRIDS - CLIENTS - 1; + + createCacheAndSnapshot(testNodeIx); + + ru(grid(testNodeIx)).enableVersionUpgrade(); + + for (int i = 0; i < ALL_GRIDS; i++) { + assertTrue(ru(grid(i)).isVersionUpgradeEnabled()); + + upgradeNodeVersion(i, "2.19.4", "2.19.5"); + } + + spi(grid(testNodeIx)).blockMessages((node, msg) -> msg instanceof SingleNodeMessage snm && + snm.type() == RU_PREPARE_VERSION_FINALIZATION.ordinal()); + + IgniteInternalFuture finalizeFut = GridTestUtils.runAsync(() -> ru(testNodeIx).finalizeClusterVersion()); + + assertTrue(spi(grid(testNodeIx)).waitForBlocked(1, getTestTimeout())); + + ensureSnapshotListFailed(); + + spi(grid(testNodeIx)).stopBlock(); + + assertFalse(spi(grid(testNodeIx)).hasBlockedMessages()); + + finalizeFut.get(getTestTimeout()); + + for (int i = 0; i < ALL_GRIDS; i++) { + int i0 = i; + + assertTrue(waitForCondition(() -> !ru(grid(i0)).isVersionUpgradeEnabled(), getTestTimeout())); + } + + ensureSnapshotListSucceeds(); + } + + /** */ + @Test + public void testSnapshotListFeature() throws Exception { + for (int i = 0; i < ALL_GRIDS; i++) + startGrid(i, "2.19.4", i >= ALL_GRIDS - CLIENTS); + + grid(0).cluster().active(true); + + createCacheAndSnapshot(1); + + ensureSnapshotListFailed(); + + ru(grid(0)).enableVersionUpgrade(); + + for (int i = 0; i < ALL_GRIDS; i++) { + assertTrue(ru(grid(i)).isVersionUpgradeEnabled()); + + upgradeNodeVersion(i, "2.19.4", "2.19.5"); + + ensureSnapshotListFailed(); + } + + ru(grid(1)).finalizeClusterVersion(); + + for (int i = 0; i < ALL_GRIDS; i++) { + int i0 = i; + + assertTrue(waitForCondition(() -> !ru(grid(i0)).isVersionUpgradeEnabled(), getTestTimeout())); + } + + ensureSnapshotListSucceeds(); + } + + /** */ + private void ensureSnapshotListSucceeds() throws Exception { + for (int g = 0; g < ALL_GRIDS; g++) { + SnapshotListTaskResult listOpRes = listSnapshots(grid(g)); + + assertEquals(3, listOpRes.snapshots().length); + + // Tests client exclusion. + if (CLIENTS > 0) { + UUID cliId = grid(ALL_GRIDS - CLIENTS).cluster().localNode().id(); + + assertFalse(new HashSet<>(Arrays.asList(listOpRes.nodesIds())).contains(cliId)); + } + + for (SnapshotListJobResult nodeRes : listOpRes.snapshots()) + assertEquals(1, nodeRes.snapshots().size()); + } + } + + /** */ + private void createCacheAndSnapshot(int gridIdx) { + int partsCnt = 4; + int keysCnt = partsCnt * 5; + + grid(gridIdx).createCache(new CacheConfiguration<>(DEFAULT_CACHE_NAME) + .setCacheMode(CacheMode.REPLICATED) + .setAffinity(new RendezvousAffinityFunction().setPartitions(partsCnt)) + .setAtomicityMode(CacheAtomicityMode.ATOMIC)); + + try (IgniteDataStreamer ds = grid(gridIdx).dataStreamer(DEFAULT_CACHE_NAME)) { + for (int i = 0; i < keysCnt; i++) + ds.addData(i, i); + } + + createSnapshot(gridIdx); + } + + /** */ + private void createSnapshot(int gridIdx) { + snp(gridIdx).createSnapshot(SNP_NAME).get(getTestTimeout()); + } + + /** */ + private void ensureSnapshotListFailed() { + String err = "The snapshot list feature isn't activated yet"; + + for (int i = 0; i < ALL_GRIDS; i++) { + int i0 = i; + + assertThrowsAnyCause( + null, + () -> listSnapshots(grid(i0)), + IgniteException.class, + err + ); + } + } + + /** */ + private IgniteSnapshotManager snp(int gridIdx) { + return grid(gridIdx).context().cache().context().snapshotMgr(); + } + + /** */ + private static SnapshotListTaskResult listSnapshots(IgniteEx grid) throws Exception { + SnapshotListCommandArg arg = new SnapshotListCommandArg(); + + Collection nodes = grid.cluster().forServers().nodes().stream().map(ClusterNode::id).toList(); + + return grid.compute().execute(SnapshotListTask.class, new VisorTaskArgument<>(nodes, arg, false)).result(); + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java new file mode 100644 index 0000000000000..98330ddb753e5 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java @@ -0,0 +1,402 @@ +/* + * 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.ignite.internal.processors.cache.persistence.snapshot; + +import java.io.File; +import java.io.RandomAccessFile; +import java.io.Serializable; +import java.nio.file.DirectoryStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Stream; +import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction; +import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.configuration.CacheConfiguration; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.IgniteInternalFuture; +import org.apache.ignite.internal.management.snapshot.SnapshotListCommandArg; +import org.apache.ignite.internal.management.snapshot.SnapshotListTask; +import org.apache.ignite.internal.processors.cache.persistence.filename.SnapshotFileTree; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.internal.visor.VisorTaskArgument; +import org.apache.ignite.lang.IgniteFuture; +import org.apache.ignite.plugin.AbstractTestPluginProvider; +import org.apache.ignite.plugin.PluginConfiguration; +import org.apache.ignite.plugin.PluginContext; +import org.apache.ignite.plugin.PluginProvider; +import org.junit.Test; +import org.junit.runners.Parameterized; + +import static java.nio.file.Files.newDirectoryStream; +import static org.apache.ignite.configuration.IgniteConfiguration.DFLT_SNAPSHOT_THREAD_POOL_SIZE; +import static org.apache.ignite.testframework.GridTestUtils.cartesianProduct; +import static org.apache.ignite.testframework.GridTestUtils.runAsync; +import static org.junit.Assume.assumeFalse; + +/** Cluster-wide snapshot list procedure tests.*/ +public class IgniteClusterSnapshotListTest extends AbstractSnapshotSelfTest { + /** Number of cache keys to pre-create at node start. */ + private static final int CACHE_KEYS_RANGE = 10; + + /** Number of partitions within a snapshot cache group. */ + private static final int CACHE_PARTITIONS_COUNT = 4; + + /** */ + @Parameterized.Parameter(2) + public int snpThrdPoolSz; + + /** */ + private PluginProvider pluginProvider; + + /** Parameters. */ + @Parameterized.Parameters(name = "encryption={0}, onlyPrimary={1}, snpThrdPoolSz={2}") + public static Collection params() { + return cartesianProduct( + encryptionParameters(), // Encryption + F.asList(false, true), // Only primary + F.asList(DFLT_SNAPSHOT_THREAD_POOL_SIZE, 1) // Snapshots thread pool size + ); + } + + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { + IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName) + .setWorkDirectory(new File(U.defaultWorkDirectory(), igniteInstanceName).getAbsolutePath()); + + if (pluginProvider != null) + cfg.setPluginProviders(pluginProvider); + + return cfg; + } + + /** {@inheritDoc} */ + @Override protected CacheConfiguration txCacheConfig(CacheConfiguration ccfg) { + // Fastent the tests. + return super.txCacheConfig(ccfg).setAffinity(new RendezvousAffinityFunction(false, CACHE_PARTITIONS_COUNT)); + } + + /** {@inheritDoc} */ + @Override protected void cleanPersistenceDir() throws Exception { + super.cleanPersistenceDir(); + + // Also cleans separated snapshot working directories and custom snapshot paths. + try (DirectoryStream files = newDirectoryStream(Paths.get(U.defaultWorkDirectory()))) { + for (Path path : files) + U.delete(path); + } + } + + /** */ + @Test + public void testConcurrentCreation() throws Exception { + CountDownLatch proceedSnpCreation = new CountDownLatch(1); + CountDownLatch beginSnpCreation = new CountDownLatch(1); + + int grids = 3; + int testNodeIdx = 1; + int testNodeOrder = testNodeIdx + 1; + + // Delays snapshot creation after its metadata is written. + pluginProvider = new AbstractTestPluginProvider() { + @Override public String name() { + return "TestSnpMgrProvider"; + } + + @Override public T createComponent(PluginContext ctx, Class cls) { + if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { + return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { + @Override public void storeSnapshotMeta(M meta, File smf) { + beginSnpCreation.countDown(); + + super.storeSnapshotMeta(meta, smf); + + if (((IgniteEx)ctx.grid()).localNode().order() == testNodeOrder) { + try { + assertTrue(proceedSnpCreation.await(getTestTimeout(), TimeUnit.MILLISECONDS)); + } + catch (InterruptedException e) { + throw new IllegalStateException(e); + } + } + } + }; + } + + return super.createComponent(ctx, cls); + } + }; + + startGridsWithCache(grids, txCacheConfig(defaultCacheConfiguration()), CACHE_KEYS_RANGE); + + IgniteFuture createSnpFut = snp(grid(0)).createSnapshot(SNAPSHOT_NAME); + + assertTrue(beginSnpCreation.await(getTestTimeout(), TimeUnit.MILLISECONDS)); + + SnapshotListTaskResult lstOpRes = listSnapshots(grid(2)); + + int snpsCnt = Stream.of(lstOpRes.snapshots()).mapToInt(jr -> jr.snapshots().size()).sum(); + + assertEquals(grids, snpsCnt); + + SnapshotFileTree testSft = new SnapshotFileTree(grid(testNodeIdx).context(), SNAPSHOT_NAME, null); + + assertTrue(SnapshotListTask.calculateDirectorySize(testSft.root()) > 0L); + + proceedSnpCreation.countDown(); + + createSnpFut.get(getTestTimeout()); + } + + /** */ + @Test + public void testCompletelyDeletedAfterMetaRead() throws Exception { + doTestDeletionAfterMetaRead(true); + } + + /** */ + @Test + public void testPartlyDeletedAfterMetaRead() throws Exception { + doTestDeletionAfterMetaRead(false); + } + + /** */ + private void doTestDeletionAfterMetaRead(boolean completeDeletion) throws Exception { + CountDownLatch metaReadProceedLatch = new CountDownLatch(1); + CountDownLatch metaReadBeginLatch = new CountDownLatch(1); + + int grids = 3; + int testGridIdx = 1; + + pluginProvider = new AbstractTestPluginProvider() { + @Override public String name() { + return "TestSnpMgrProvider"; + } + + @Override public T createComponent(PluginContext ctx, Class cls) { + if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { + return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { + @Override public List readSnapshotMetadatas(SnapshotFileTree sft, + boolean failIfCantRead) { + List metas = super.readSnapshotMetadatas(sft, failIfCantRead); + + if (cctx.localNode().order() != 2) + return metas; + + metaReadBeginLatch.countDown(); + + try { + assertTrue(metaReadProceedLatch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); + } + catch (InterruptedException e) { + throw new IllegalStateException(e); + } + + return metas; + } + }; + } + + return super.createComponent(ctx, cls); + } + }; + + startGridsWithSnapshot(grids, CACHE_KEYS_RANGE, true, false); + + SnapshotFileTree testSnpFt = new SnapshotFileTree(grid(testGridIdx).context(), SNAPSHOT_NAME, null); + + long testSnpSz = SnapshotListTask.calculateDirectorySize(testSnpFt.root()); + + assertTrue(testSnpSz > 0); + + IgniteInternalFuture lstOpFut = runAsync(() -> listSnapshots(grid(0))); + + assertTrue(metaReadBeginLatch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); + + if (completeDeletion) + assertTrue(testSnpFt.root().exists() && U.delete(testSnpFt.root()) && !testSnpFt.root().exists()); + else + assertTrue(testSnpFt.nodeStorage().exists() && U.delete(testSnpFt.nodeStorage()) && !testSnpFt.nodeStorage().exists()); + + metaReadProceedLatch.countDown(); + + SnapshotListTaskResult lstOpRes = lstOpFut.get(); + + if (completeDeletion) { + int snpsCnt = Stream.of(lstOpRes.snapshots()).mapToInt(jr -> jr.snapshots().size()).sum(); + + assertEquals(grids - 1, snpsCnt); + } + else { + int snpsCnt = 0; + boolean victimNodeFound = false; + + for (int i = 0; i < lstOpRes.nodesIds().length; i++) { + UUID nid = lstOpRes.nodesIds()[i]; + + Map nodeSnps = lstOpRes.snapshots()[i].snapshots(); + + snpsCnt += nodeSnps.size(); + + if (nid.equals(grid(testGridIdx).localNode().id())) { + victimNodeFound = true; + + assertEquals(1, nodeSnps.size()); + + long curSnpSz = SnapshotListTask.calculateDirectorySize(testSnpFt.root()); + + assertTrue(curSnpSz < testSnpSz); + } + } + + assertTrue(victimNodeFound); + assertEquals(grids, snpsCnt); + } + } + + /** + * Ensures that a file can be read without locking or other issues while bieng cuncurently written on curren OS and + * file system. This behavior is important for the case when snapshots is being read while creation. + */ + @Test + public void testReadWhileCreating() throws Exception { + // Doesn't matter here. Fastens the tests. + assumeFalse(encryption || onlyPrimary || snpThrdPoolSz > 1); + + assertTrue(new File(U.defaultWorkDirectory()).exists()); + + File testF = new File(U.defaultWorkDirectory(), "test.out"); + + assumeFalse(testF.exists()); + + CountDownLatch proceedWriteLatch = new CountDownLatch(1); + AtomicBoolean readFlag = new AtomicBoolean(); + + try { + try (RandomAccessFile raf = new RandomAccessFile(testF, "rw");) { + raf.write(1); + raf.write(2); + + Thread t = new Thread(() -> { + try (RandomAccessFile raf0 = new RandomAccessFile(testF, "r")) { + assertEquals(1, raf0.read()); + assertEquals(2, raf0.read()); + + readFlag.set(true); + + proceedWriteLatch.countDown(); + } + catch (Throwable e) { + log.error("Failed to concurrently read the test file.", e); + } + }); + + t.setDaemon(true); + t.start(); + + assertTrue(proceedWriteLatch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); + + raf.write(3); + } + + try (RandomAccessFile raf = new RandomAccessFile(testF, "r");) { + assertEquals(1, raf.read()); + assertEquals(2, raf.read()); + assertEquals(3, raf.read()); + } + } + finally { + assertTrue(!testF.exists() || testF.delete()); + } + + assertTrue(readFlag.get()); + } + + /** */ + @Test + public void testMissingMeta() throws Exception { + doTestWithWrongMeta(false); + } + + /** */ + @Test + public void testCorruptedMeta() throws Exception { + doTestWithWrongMeta(true); + } + + /** + * Tests snapshot lists when one snapshot metadata cannot be read. + * + * @param corruptFile If {@code true}, corrupts metadata. Otherwise, deletes metadata. + */ + private void doTestWithWrongMeta(boolean corruptFile) throws Exception { + int grids = 3; + int testGridIdx = 1; + + startGridsWithSnapshot(grids, CACHE_KEYS_RANGE, true, false); + + SnapshotFileTree testSnpFt = new SnapshotFileTree(grid(testGridIdx).context(), SNAPSHOT_NAME, null); + + assertTrue(testSnpFt.meta().exists()); + + if (corruptFile) { + try (RandomAccessFile raf = new RandomAccessFile(testSnpFt.meta(), "rw")) { + raf.write(UUID.randomUUID().toString().getBytes()); + } + } + else + assertTrue(testSnpFt.meta().delete() && !testSnpFt.meta().exists()); + + SnapshotListTaskResult res = listSnapshots(grid(0)); + + int foundSnpsCnt = 0; + boolean victimNodeFound = false; + + for (int i = 0; i < res.nodesIds().length; i++) { + UUID nid = res.nodesIds()[i]; + + foundSnpsCnt += res.snapshots()[i].snapshots().size(); + + if (nid.equals(grid(testGridIdx).localNode().id())) { + victimNodeFound = true; + + assertTrue(res.snapshots()[i].snapshots().isEmpty()); + } + } + + assertTrue(victimNodeFound); + assertEquals(grids - 1, foundSnpsCnt); + } + + /** */ + private static SnapshotListTaskResult listSnapshots(IgniteEx grid) throws Exception { + SnapshotListCommandArg arg = new SnapshotListCommandArg(); + + Collection nodes = grid.cluster().forServers().nodes().stream().map(ClusterNode::id).toList(); + + return grid.compute().execute(SnapshotListTask.class, new VisorTaskArgument<>(nodes, arg, false)).result(); + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/AbstractRollingUpgradeTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/AbstractRollingUpgradeTest.java index 3d7f76477c251..bd21d6923ec93 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/AbstractRollingUpgradeTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/AbstractRollingUpgradeTest.java @@ -91,6 +91,8 @@ * 2.19.1 0-1 * 2.19.2 0-2 * 2.19.3 0-2,6 + * 2.19.4 1 + * 2.19.5 1-2 * 2.20.0 2-5 * 2.20.1 2-6 * 2.21.0 6 diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_4.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_4.java new file mode 100644 index 0000000000000..30a8a8ae030ee --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_4.java @@ -0,0 +1,24 @@ +/* + * 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.ignite.internal.processors.rollingupgrade.feature; + +/** */ +public class TestIgniteReleaseFeatures_2_19_4 { + /** */ + public static final IgniteFeature ROLLING_UPGRADE_FEATURE = new IgniteCoreFeature(0); +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_5.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_5.java new file mode 100644 index 0000000000000..aeccf2e733eda --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_5.java @@ -0,0 +1,27 @@ +/* + * 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.ignite.internal.processors.rollingupgrade.feature; + +/** */ +public class TestIgniteReleaseFeatures_2_19_5 { + /** */ + public static final IgniteFeature ROLLING_UPGRADE_FEATURE = new IgniteCoreFeature(0); + + /** */ + public static final IgniteFeature SNAPSHOT_LIST_FEATURE = CoreFeatureRegistry.SNAPSHOT_LIST_FEATURE; +} diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite9.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite9.java new file mode 100644 index 0000000000000..f707a6fa7311c --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite9.java @@ -0,0 +1,54 @@ +/* + * 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.ignite.testsuites; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotListRollingUpgradeTest; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotListTest; +import org.apache.ignite.testframework.GridTestUtils; +import org.apache.ignite.testframework.junits.DynamicSuite; +import org.junit.runner.RunWith; + +/** + * Split off from {@link IgniteSnapshotTestSuite8} to reduce the single-suite runtime in CI; + * contains an independent subset of the same test classes. + */ +@RunWith(DynamicSuite.class) +public class IgniteSnapshotTestSuite9 { + /** + * @return Test suite. + */ + public static List> suite() { + return suite(null); + } + + /** + * @param ignoredTests Tests to ignore. + * @return Test suite. + */ + public static List> suite(Collection ignoredTests) { + List> suite = new ArrayList<>(); + + GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotListTest.class, ignoredTests); + GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotListRollingUpgradeTest.class, ignoredTests); + + return suite; + } +} diff --git a/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/ContinuousDataLoadApplication.java b/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/ContinuousDataLoadApplication.java index bff6187c71532..0b743036dfebc 100644 --- a/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/ContinuousDataLoadApplication.java +++ b/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/ContinuousDataLoadApplication.java @@ -78,7 +78,7 @@ public class ContinuousDataLoadApplication extends IgniteAwareApplication { if (notifyTime + TimeUnit.MILLISECONDS.toNanos(1500) < System.nanoTime()) notifyTime = System.nanoTime(); - // Delayed notify of the initialization to make sure the data load has completelly began and + // Delayed notify of the initialization to make sure the data load has completely began and // has produced some valuable amount of data. if (!inited() && warmUpCnt == loaded) markInitialized(); From 7a998f791603830313f82402cc1fb9c034226315 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Fri, 9 Oct 2026 11:06:45 +0300 Subject: [PATCH 86/97] fix --- .../IgniteControlUtilityTestSuite.java | 2 - .../IgniteControlUtilityTestSuite6.java | 32 +++ .../management/snapshot/SnapshotListTask.java | 35 ++- ...ClusterSnapshotListRollingUpgradeTest.java | 245 ++++++++++++++++++ .../IgniteClusterSnapshotListTest.java | 52 ++-- .../AbstractRollingUpgradeTest.java | 2 + .../TestIgniteReleaseFeatures_2_19_4.java | 24 ++ .../TestIgniteReleaseFeatures_2_19_5.java | 27 ++ .../testsuites/IgniteSnapshotTestSuite8.java | 2 - .../testsuites/IgniteSnapshotTestSuite9.java | 54 ++++ 10 files changed, 443 insertions(+), 32 deletions(-) create mode 100644 modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite6.java create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListRollingUpgradeTest.java create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_4.java create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_5.java create mode 100644 modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite9.java diff --git a/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite.java b/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite.java index 9d1fea3a68feb..6528d919f6105 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite.java @@ -34,7 +34,6 @@ import org.apache.ignite.util.GridCommandHandlerIndexingTest; import org.apache.ignite.util.GridCommandHandlerIndexingWithSSLTest; import org.apache.ignite.util.GridCommandHandlerLegacyClientTest; -import org.apache.ignite.util.GridCommandHandlerListSnapshotTest; import org.apache.ignite.util.GridCommandHandlerManagementPoolTest; import org.apache.ignite.util.GridCommandHandlerMetadataTest; import org.apache.ignite.util.GridCommandHandlerSslTest; @@ -75,7 +74,6 @@ GridCommandHandlerCheckIndexesInlineSizeTest.class, GridCommandHandlerMetadataTest.class, GridCommandHandlerCheckIncrementalSnapshotTest.class, - GridCommandHandlerListSnapshotTest.class, GridCommandHandlerLegacyClientTest.class, KillCommandsControlShTest.class, diff --git a/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite6.java b/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite6.java new file mode 100644 index 0000000000000..c2c5a848f6d2f --- /dev/null +++ b/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite6.java @@ -0,0 +1,32 @@ +/* + * 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.ignite.testsuites; + +import org.apache.ignite.util.GridCommandHandlerListSnapshotTest; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +/** + * Test suite for control utility. + */ +@RunWith(Suite.class) +@Suite.SuiteClasses({ + GridCommandHandlerListSnapshotTest.class +}) +public class IgniteControlUtilityTestSuite6 { +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java index 2357d1e93d202..ef3909f74d44d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java @@ -33,6 +33,7 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; import org.apache.ignite.IgniteException; @@ -46,6 +47,7 @@ import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListJobResult; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListTaskResult; import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotMetadata; +import org.apache.ignite.internal.processors.rollingupgrade.feature.CoreFeatureRegistry; import org.apache.ignite.internal.processors.task.GridInternal; import org.apache.ignite.internal.thread.pool.IgniteThreadPoolExecutor; import org.apache.ignite.internal.util.typedef.F; @@ -54,7 +56,6 @@ import org.apache.ignite.internal.visor.VisorMultiNodeTask; import org.apache.ignite.internal.visor.VisorTaskArgument; import org.apache.ignite.resources.LoggerResource; -import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; /** */ @@ -70,6 +71,9 @@ public class SnapshotListTask extends VisorMultiNodeTask jobNodes(VisorTaskArgument arg) { + if (!ignite.context().rollingUpgrade().features().isActive(CoreFeatureRegistry.SNAPSHOT_LIST_FEATURE)) + throw new IgniteException("Won't search for local snapshots. The snapshot list feature isn't activated yet."); + /** Allows {@link #map0(List, VisorTaskArgument)} to use the entire subgrid. */ return ignite.cluster().forServers().nodes().stream().map(ClusterNode::id).collect(Collectors.toList()); } @@ -121,21 +125,29 @@ private String nodeConsistentId(ClusterNode n) { */ public static long calculateDirectorySize(File path) throws IOException { AtomicLong size = new AtomicLong(0); + AtomicBoolean entered = new AtomicBoolean(); Files.walkFileTree(path.toPath(), new SimpleFileVisitor<>() { + @Override public FileVisitResult postVisitDirectory(Path dir, IOException err) { + return FileVisitResult.CONTINUE; + } + @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { // Use attrs instead of Files.size() for efficiency. if (attrs.isRegularFile()) size.addAndGet(attrs.size()); + else if (!entered.get() && file.toFile().equals(path)) + entered.set(true); return FileVisitResult.CONTINUE; } - @Override public FileVisitResult visitFileFailed(Path file, IOException exc) { - return FileVisitResult.CONTINUE; - } + @Override public FileVisitResult visitFileFailed(Path file, IOException err) throws IOException { + if (!entered.get() && file.toFile().equals(path)) { + // Cant even start shapshot size calculation - can't enter snapshot directory. + throw err; + } - @Override public FileVisitResult postVisitDirectory(@NotNull Path dir, @Nullable IOException exc) { return FileVisitResult.CONTINUE; } }); @@ -192,6 +204,9 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { // Future for optional exernal storages. Future fut = exec.submit(() -> { + if (ignite.context().isStopping()) + throw new IgniteException("Won't search for local snapshots.", new NodeStoppingException("Node is stopping.")); + try { SnapshotListJobResult.SnapshotInfo extDesc = externalStorages(sft); @@ -213,6 +228,9 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { if (failedSnps.contains(snpName)) return; + if (ignite.context().isStopping()) + throw new IgniteException("Won't search for local snapshots.", new NodeStoppingException("Node is stopping.")); + try { SnapshotListJobResult.SnapshotInfo incDesc = incrementals(sft); @@ -303,8 +321,13 @@ private List> findLocalSnapshots(@Nullable String snp List>> futs = new ArrayList<>(dirsToParse.length); + IgniteThreadPoolExecutor exec = ignite.context().pools().getSnapshotExecutorService(); + for (File snpDir : dirsToParse) { - Future> snpDirFut = ignite.context().pools().getSnapshotExecutorService().submit(() -> { + Future> snpDirFut = exec.submit(() -> { + if (ignite.context().isStopping()) + throw new IgniteException("Won't search for local snapshots.", new NodeStoppingException("Node is stopping.")); + String snpName = snpDir.getName(); // Snapshot tree being used as a path, to read the metas only. diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListRollingUpgradeTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListRollingUpgradeTest.java new file mode 100644 index 0000000000000..97b1bed7751df --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListRollingUpgradeTest.java @@ -0,0 +1,245 @@ +/* + * 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.ignite.internal.processors.cache.persistence.snapshot; + +import java.io.File; +import java.nio.file.DirectoryStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.UUID; +import org.apache.ignite.IgniteDataStreamer; +import org.apache.ignite.IgniteException; +import org.apache.ignite.cache.CacheAtomicityMode; +import org.apache.ignite.cache.CacheMode; +import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction; +import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.configuration.CacheConfiguration; +import org.apache.ignite.configuration.DataRegionConfiguration; +import org.apache.ignite.configuration.DataStorageConfiguration; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.IgniteInternalFuture; +import org.apache.ignite.internal.management.snapshot.SnapshotListCommandArg; +import org.apache.ignite.internal.management.snapshot.SnapshotListTask; +import org.apache.ignite.internal.processors.rollingupgrade.AbstractRollingUpgradeTest; +import org.apache.ignite.internal.util.distributed.SingleNodeMessage; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.internal.visor.VisorTaskArgument; +import org.apache.ignite.testframework.GridTestUtils; +import org.junit.Test; + +import static java.nio.file.Files.newDirectoryStream; +import static org.apache.ignite.internal.TestRecordingCommunicationSpi.spi; +import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RU_PREPARE_VERSION_FINALIZATION; +import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause; +import static org.apache.ignite.testframework.GridTestUtils.waitForCondition; + +/** */ +public class IgniteClusterSnapshotListRollingUpgradeTest extends AbstractRollingUpgradeTest { + /** */ + private static final int ALL_GRIDS = 4; + + /** */ + private static final int CLIENTS = 1; + + /** */ + private static final String SNP_NAME = "testSnapshot"; + + /** {@inheritDoc} */ + @Override protected void afterTest() throws Exception { + super.afterTest(); + + cleanPersistenceDir(); + + // Clean all: also separated snapshot working directories. + try (DirectoryStream files = newDirectoryStream(Paths.get(U.defaultWorkDirectory()))) { + for (Path path : files) + U.delete(path); + } + } + + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName, String ver) throws Exception { + IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName, ver); + + cfg.setDataStorageConfiguration( + new DataStorageConfiguration() + .setDefaultDataRegionConfiguration( + new DataRegionConfiguration() + .setPersistenceEnabled(true) + .setMaxSize(DataStorageConfiguration.DFLT_DATA_REGION_INITIAL_SIZE) + ) + ); + + cfg.setWorkDirectory(new File(U.defaultWorkDirectory(), igniteInstanceName).getAbsolutePath()); + + return cfg; + } + + /** */ + @Test + public void testParallelRollingUpgradeInProgress() throws Exception { + for (int i = 0; i < ALL_GRIDS; i++) + startGrid(i, "2.19.4", i >= ALL_GRIDS - CLIENTS); + + grid(0).cluster().active(true); + + int testNodeIx = ALL_GRIDS - CLIENTS - 1; + + createCacheAndSnapshot(testNodeIx); + + ru(grid(testNodeIx)).enableVersionUpgrade(); + + for (int i = 0; i < ALL_GRIDS; i++) { + assertTrue(ru(grid(i)).isVersionUpgradeEnabled()); + + upgradeNodeVersion(i, "2.19.4", "2.19.5"); + } + + spi(grid(testNodeIx)).blockMessages((node, msg) -> msg instanceof SingleNodeMessage snm && + snm.type() == RU_PREPARE_VERSION_FINALIZATION.ordinal()); + + IgniteInternalFuture finalizeFut = GridTestUtils.runAsync(() -> ru(testNodeIx).finalizeClusterVersion()); + + assertTrue(spi(grid(testNodeIx)).waitForBlocked(1, getTestTimeout())); + + ensureSnapshotListFailed(); + + spi(grid(testNodeIx)).stopBlock(); + + assertFalse(spi(grid(testNodeIx)).hasBlockedMessages()); + + finalizeFut.get(getTestTimeout()); + + for (int i = 0; i < ALL_GRIDS; i++) { + int i0 = i; + + assertTrue(waitForCondition(() -> !ru(grid(i0)).isVersionUpgradeEnabled(), getTestTimeout())); + } + + ensureSnapshotListSucceeds(); + } + + /** */ + @Test + public void testSnapshotListFeature() throws Exception { + for (int i = 0; i < ALL_GRIDS; i++) + startGrid(i, "2.19.4", i >= ALL_GRIDS - CLIENTS); + + grid(0).cluster().active(true); + + createCacheAndSnapshot(1); + + ensureSnapshotListFailed(); + + ru(grid(0)).enableVersionUpgrade(); + + for (int i = 0; i < ALL_GRIDS; i++) { + assertTrue(ru(grid(i)).isVersionUpgradeEnabled()); + + upgradeNodeVersion(i, "2.19.4", "2.19.5"); + + ensureSnapshotListFailed(); + } + + ru(grid(1)).finalizeClusterVersion(); + + for (int i = 0; i < ALL_GRIDS; i++) { + int i0 = i; + + assertTrue(waitForCondition(() -> !ru(grid(i0)).isVersionUpgradeEnabled(), getTestTimeout())); + } + + ensureSnapshotListSucceeds(); + } + + /** */ + private void ensureSnapshotListSucceeds() throws Exception { + for (int g = 0; g < ALL_GRIDS; g++) { + SnapshotListTaskResult listOpRes = listSnapshots(grid(g)); + + assertEquals(3, listOpRes.snapshots().length); + + // Tests client exclusion. + if (CLIENTS > 0) { + UUID cliId = grid(ALL_GRIDS - CLIENTS).cluster().localNode().id(); + + assertFalse(new HashSet<>(Arrays.asList(listOpRes.nodesIds())).contains(cliId)); + } + + for (SnapshotListJobResult nodeRes : listOpRes.snapshots()) + assertEquals(1, nodeRes.snapshots().size()); + } + } + + /** */ + private void createCacheAndSnapshot(int gridIdx) { + int partsCnt = 4; + int keysCnt = partsCnt * 5; + + grid(gridIdx).createCache(new CacheConfiguration<>(DEFAULT_CACHE_NAME) + .setCacheMode(CacheMode.REPLICATED) + .setAffinity(new RendezvousAffinityFunction().setPartitions(partsCnt)) + .setAtomicityMode(CacheAtomicityMode.ATOMIC)); + + try (IgniteDataStreamer ds = grid(gridIdx).dataStreamer(DEFAULT_CACHE_NAME)) { + for (int i = 0; i < keysCnt; i++) + ds.addData(i, i); + } + + createSnapshot(gridIdx); + } + + /** */ + private void createSnapshot(int gridIdx) { + snp(gridIdx).createSnapshot(SNP_NAME).get(getTestTimeout()); + } + + /** */ + private void ensureSnapshotListFailed() { + String err = "The snapshot list feature isn't activated yet"; + + for (int i = 0; i < ALL_GRIDS; i++) { + int i0 = i; + + assertThrowsAnyCause( + null, + () -> listSnapshots(grid(i0)), + IgniteException.class, + err + ); + } + } + + /** */ + private IgniteSnapshotManager snp(int gridIdx) { + return grid(gridIdx).context().cache().context().snapshotMgr(); + } + + /** */ + private static SnapshotListTaskResult listSnapshots(IgniteEx grid) throws Exception { + SnapshotListCommandArg arg = new SnapshotListCommandArg(); + + Collection nodes = grid.cluster().forServers().nodes().stream().map(ClusterNode::id).toList(); + + return grid.compute().execute(SnapshotListTask.class, new VisorTaskArgument<>(nodes, arg, false)).result(); + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java index caf865a9496a2..18ee2c9daf1ef 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java @@ -65,7 +65,7 @@ public class IgniteClusterSnapshotListTest extends AbstractSnapshotSelfTest { /** Number of partitions within a snapshot cache group. */ private static final int CACHE_PARTITIONS_COUNT = 4; - /** */ + /** Size of the snapshot utility thread pool. */ @Parameterized.Parameter(2) public int snpThrdPoolSz; @@ -76,9 +76,9 @@ public class IgniteClusterSnapshotListTest extends AbstractSnapshotSelfTest { @Parameterized.Parameters(name = "encryption={0}, onlyPrimary={1}, snpThrdPoolSz={2}") public static Collection params() { return cartesianProduct( - encryptionParameters(), - F.asList(false, true), - F.asList(DFLT_SNAPSHOT_THREAD_POOL_SIZE, 1) + encryptionParameters(), // Encryption + F.asList(false, true), // Only primary + F.asList(DFLT_SNAPSHOT_THREAD_POOL_SIZE, 1) // Snapshots thread pool size ); } @@ -90,6 +90,8 @@ public static Collection params() { if (pluginProvider != null) cfg.setPluginProviders(pluginProvider); + cfg.setSnapshotThreadPoolSize(snpThrdPoolSz); + return cfg; } @@ -188,6 +190,9 @@ private void doTestDeletionAfterMetaRead(boolean completeDeletion) throws Except CountDownLatch metaReadProceedLatch = new CountDownLatch(1); CountDownLatch metaReadBeginLatch = new CountDownLatch(1); + int grids = 3; + int testGridIdx = 1; + pluginProvider = new AbstractTestPluginProvider() { @Override public String name() { return "TestSnpMgrProvider"; @@ -221,11 +226,11 @@ private void doTestDeletionAfterMetaRead(boolean completeDeletion) throws Except } }; - startGridsWithSnapshot(3, CACHE_KEYS_RANGE, true, false); + startGridsWithSnapshot(grids, CACHE_KEYS_RANGE, true, false); - SnapshotFileTree grid1SnpSft = new SnapshotFileTree(grid(1).context(), SNAPSHOT_NAME, null); + SnapshotFileTree testSnpFt = new SnapshotFileTree(grid(testGridIdx).context(), SNAPSHOT_NAME, null); - long testSnpSz = SnapshotListTask.calculateDirectorySize(grid1SnpSft.root()); + long testSnpSz = SnapshotListTask.calculateDirectorySize(testSnpFt.root()); assertTrue(testSnpSz > 0); @@ -234,9 +239,9 @@ private void doTestDeletionAfterMetaRead(boolean completeDeletion) throws Except assertTrue(metaReadBeginLatch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); if (completeDeletion) - assertTrue(grid1SnpSft.root().exists() && U.delete(grid1SnpSft.root()) && !grid1SnpSft.root().exists()); + assertTrue(testSnpFt.root().exists() && U.delete(testSnpFt.root()) && !testSnpFt.root().exists()); else - assertTrue(grid1SnpSft.nodeStorage().exists() && U.delete(grid1SnpSft.nodeStorage()) && !grid1SnpSft.nodeStorage().exists()); + assertTrue(testSnpFt.nodeStorage().exists() && U.delete(testSnpFt.nodeStorage()) && !testSnpFt.nodeStorage().exists()); metaReadProceedLatch.countDown(); @@ -245,7 +250,7 @@ private void doTestDeletionAfterMetaRead(boolean completeDeletion) throws Except if (completeDeletion) { int snpsCnt = Stream.of(lstOpRes.snapshots()).mapToInt(jr -> jr.snapshots().size()).sum(); - assertEquals(2, snpsCnt); + assertEquals(grids - 1, snpsCnt); } else { int snpsCnt = 0; @@ -258,19 +263,19 @@ private void doTestDeletionAfterMetaRead(boolean completeDeletion) throws Except snpsCnt += nodeSnps.size(); - if (nid.equals(grid(1).localNode().id())) { + if (nid.equals(grid(testGridIdx).localNode().id())) { victimNodeFound = true; assertEquals(1, nodeSnps.size()); - long curSnpSz = SnapshotListTask.calculateDirectorySize(grid1SnpSft.root()); + long curSnpSz = SnapshotListTask.calculateDirectorySize(testSnpFt.root()); assertTrue(curSnpSz < testSnpSz); } } assertTrue(victimNodeFound); - assertEquals(3, snpsCnt); + assertEquals(grids, snpsCnt); } } @@ -281,7 +286,7 @@ private void doTestDeletionAfterMetaRead(boolean completeDeletion) throws Except @Test public void testReadWhileCreating() throws Exception { // Doesn't matter here. Fastens the tests. - assumeFalse(encryption || onlyPrimary || snpThrdPoolSz > 1); + assumeFalse(encryption || onlyPrimary); assertTrue(new File(U.defaultWorkDirectory()).exists()); @@ -350,21 +355,24 @@ public void testCorruptedMeta() throws Exception { * @param corruptFile If {@code true}, corrupts metadata. Otherwise, deletes metadata. */ private void doTestWithWrongMeta(boolean corruptFile) throws Exception { - startGridsWithSnapshot(3, CACHE_KEYS_RANGE, true, false); + int grids = 3; + int testGridIdx = 1; + + startGridsWithSnapshot(grids, CACHE_KEYS_RANGE, true, false); - SnapshotFileTree sftNode1 = new SnapshotFileTree(grid(1).context(), SNAPSHOT_NAME, null); + SnapshotFileTree testSnpFt = new SnapshotFileTree(grid(testGridIdx).context(), SNAPSHOT_NAME, null); - assertTrue(sftNode1.meta().exists()); + assertTrue(testSnpFt.meta().exists()); if (corruptFile) { - try (RandomAccessFile raf = new RandomAccessFile(sftNode1.meta(), "rw")) { + try (RandomAccessFile raf = new RandomAccessFile(testSnpFt.meta(), "rw")) { raf.write(UUID.randomUUID().toString().getBytes()); } } else - assertTrue(sftNode1.meta().delete() && !sftNode1.meta().exists()); + assertTrue(testSnpFt.meta().delete() && !testSnpFt.meta().exists()); - SnapshotListTaskResult res = listSnapshots(grid(2)); + SnapshotListTaskResult res = listSnapshots(grid(0)); int foundSnpsCnt = 0; boolean victimNodeFound = false; @@ -374,7 +382,7 @@ private void doTestWithWrongMeta(boolean corruptFile) throws Exception { foundSnpsCnt += res.snapshots()[i].snapshots().size(); - if (nid.equals(grid(1).localNode().id())) { + if (nid.equals(grid(testGridIdx).localNode().id())) { victimNodeFound = true; assertTrue(res.snapshots()[i].snapshots().isEmpty()); @@ -382,7 +390,7 @@ private void doTestWithWrongMeta(boolean corruptFile) throws Exception { } assertTrue(victimNodeFound); - assertEquals(2, foundSnpsCnt); + assertEquals(grids - 1, foundSnpsCnt); } /** */ diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/AbstractRollingUpgradeTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/AbstractRollingUpgradeTest.java index 3d7f76477c251..bd21d6923ec93 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/AbstractRollingUpgradeTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/AbstractRollingUpgradeTest.java @@ -91,6 +91,8 @@ * 2.19.1 0-1 * 2.19.2 0-2 * 2.19.3 0-2,6 + * 2.19.4 1 + * 2.19.5 1-2 * 2.20.0 2-5 * 2.20.1 2-6 * 2.21.0 6 diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_4.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_4.java new file mode 100644 index 0000000000000..30a8a8ae030ee --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_4.java @@ -0,0 +1,24 @@ +/* + * 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.ignite.internal.processors.rollingupgrade.feature; + +/** */ +public class TestIgniteReleaseFeatures_2_19_4 { + /** */ + public static final IgniteFeature ROLLING_UPGRADE_FEATURE = new IgniteCoreFeature(0); +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_5.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_5.java new file mode 100644 index 0000000000000..aeccf2e733eda --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_5.java @@ -0,0 +1,27 @@ +/* + * 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.ignite.internal.processors.rollingupgrade.feature; + +/** */ +public class TestIgniteReleaseFeatures_2_19_5 { + /** */ + public static final IgniteFeature ROLLING_UPGRADE_FEATURE = new IgniteCoreFeature(0); + + /** */ + public static final IgniteFeature SNAPSHOT_LIST_FEATURE = CoreFeatureRegistry.SNAPSHOT_LIST_FEATURE; +} diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java index 06a3600721b1c..5673ed2b71af3 100644 --- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java +++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite8.java @@ -22,7 +22,6 @@ import java.util.List; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotCheckTest; import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotHandlerTest; -import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotListTest; import org.apache.ignite.testframework.GridTestUtils; import org.apache.ignite.testframework.junits.DynamicSuite; import org.junit.runner.RunWith; @@ -48,7 +47,6 @@ public static List> suite(Collection ignoredTests) { List> suite = new ArrayList<>(); GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotCheckTest.class, ignoredTests); - GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotListTest.class, ignoredTests); GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotHandlerTest.class, ignoredTests); return suite; diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite9.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite9.java new file mode 100644 index 0000000000000..f707a6fa7311c --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite9.java @@ -0,0 +1,54 @@ +/* + * 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.ignite.testsuites; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotListRollingUpgradeTest; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotListTest; +import org.apache.ignite.testframework.GridTestUtils; +import org.apache.ignite.testframework.junits.DynamicSuite; +import org.junit.runner.RunWith; + +/** + * Split off from {@link IgniteSnapshotTestSuite8} to reduce the single-suite runtime in CI; + * contains an independent subset of the same test classes. + */ +@RunWith(DynamicSuite.class) +public class IgniteSnapshotTestSuite9 { + /** + * @return Test suite. + */ + public static List> suite() { + return suite(null); + } + + /** + * @param ignoredTests Tests to ignore. + * @return Test suite. + */ + public static List> suite(Collection ignoredTests) { + List> suite = new ArrayList<>(); + + GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotListTest.class, ignoredTests); + GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotListRollingUpgradeTest.class, ignoredTests); + + return suite; + } +} From e730649dd485f03558fc84b8cf2157df9b88cf9a Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Fri, 9 Oct 2026 13:11:47 +0300 Subject: [PATCH 87/97] LLM review fixes --- docs/_docs/snapshots/snapshots.adoc | 3 +- .../GridCommandHandlerListSnapshotTest.java | 15 +--- .../snapshot/SnapshotListCommand.java | 2 +- .../snapshot/SnapshotListCommandArg.java | 2 +- .../management/snapshot/SnapshotListTask.java | 12 ++-- .../filename/SnapshotFileTree.java | 1 + .../snapshot/IgniteSnapshotManager.java | 2 +- .../snapshot/SnapshotListJobResult.java | 4 +- .../snapshot/SnapshotListTaskResult.java | 12 ++-- ...ClusterSnapshotListRollingUpgradeTest.java | 4 +- .../IgniteClusterSnapshotListTest.java | 71 ++++++++++++++++--- 11 files changed, 85 insertions(+), 43 deletions(-) diff --git a/docs/_docs/snapshots/snapshots.adoc b/docs/_docs/snapshots/snapshots.adoc index e41020a280e82..8650862821e06 100644 --- a/docs/_docs/snapshots/snapshots.adoc +++ b/docs/_docs/snapshots/snapshots.adoc @@ -319,8 +319,7 @@ control.bat --snapshot list --src C:\\tmp\\ignite\\snapshots * The list operation is read-only and does not modify any snapshot data. * If a snapshot is being modified (created or deleted), its size might not be reported. * Snapshot is being detected by its metadata. If metadata is missing or cannot be read, snapshot is ignored. -* When snapshot listing and size calculating, no additional snapshot validation is done. Files aren't read, no checksums -are calculated. +* When snapshot listing and size calculating, no additional snapshot validation, checksums calculation are done. == Getting Snapshot Operation Status diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java index 3c4d08f2e8ec6..9eae9e894cbc1 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java @@ -228,7 +228,7 @@ private void doTestSnapshotsLists(int snpCnt, boolean deleteOnOneNode, boolean r // Flag if 'testSnapshot0' deleted on node0. boolean grid0HasNoSnapshot0 = false; - // Create shapshots. + // Create snapshots. if (snpCnt > 0) { createCacheAndPreload(ig, DEFAULT_CACHE_NAME, entriesCnt, partitions, null); @@ -327,19 +327,6 @@ private void doTestSnapshotsLists(int snpCnt, boolean deleteOnOneNode, boolean r } } - /** Counts occurrences of the node prefix in the output. */ - private int countNodeOccurrences(String output) { - int cnt = 0; - int idx = 0; - - while ((idx = output.indexOf(SnapshotListCommand.NODE_PREF, idx)) != -1) { - cnt++; - idx += SnapshotListCommand.NODE_PREF.length(); - } - - return cnt; - } - /** */ private static int countEntries(String txt, String entry) { String prev = txt; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java index c0f6b762bebd7..4ac27435dffdd 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java @@ -70,7 +70,7 @@ public class SnapshotListCommand extends AbstractSnapshotCommand nodeConsistentId(jr0.getNode()).compareTo(nodeConsistentId(jr1.getNode()))) .toList(); @@ -144,7 +145,7 @@ else if (!entered.get() && file.toFile().equals(path)) @Override public FileVisitResult visitFileFailed(Path file, IOException err) throws IOException { if (!entered.get() && file.toFile().equals(path)) { - // Cant even start shapshot size calculation - can't enter snapshot directory. + // Cannot even start snapshot size calculation - can't enter snapshot directory. throw err; } @@ -202,7 +203,7 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { SnapshotFileTree sft = snpPair.get1(); String snpName = sft.name(); - // Future for optional exernal storages. + // Future for optional external storages. Future fut = exec.submit(() -> { if (ignite.context().isStopping()) throw new IgniteException("Won't search for local snapshots.", new NodeStoppingException("Node is stopping.")); @@ -276,7 +277,7 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { size = calculateDirectorySize(sft.root()); } catch (Exception e) { - log.warning("Failed to calculate snapshot' size, snapshot ignored [snpName=" + snpName + ']', e); + log.warning("Failed to calculate snapshot's size, snapshot ignored [snpName=" + snpName + ']', e); // Skip snapshot in case on any failure. continue; @@ -339,8 +340,7 @@ private List> findLocalSnapshots(@Nullable String snp return null; // Get the last-created time snapshot metadata. - SnapshotMetadata snpMeta = metas.stream() - .max((m0, m1) -> Math.toIntExact(m0.snapshotTime() - m1.snapshotTime())) + SnapshotMetadata snpMeta = metas.stream().max(Comparator.comparingLong(SnapshotMetadata::snapshotTime)) .orElse(new SnapshotMetadata()); // Real, meta-based snapshot file tree. Can belong to other cluster, other consistent id. diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java index ff9e58d5a9cde..5c75e231b35d9 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java @@ -332,6 +332,7 @@ public static String snapshotMetaFileName(String consId) { public static File root(SharedFileTree ft, String name, @Nullable String path) { assert name != null : "Snapshot name cannot be empty or null."; + // TODO: use the default snapshots directory for relative {@code path} https://issues.apache.org/jira/browse/IGNITE-29126 return path == null ? new File(ft.snapshotsRoot(), name) : new File(path, name); } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java index 2a0aeffc6023e..d12c62777ad8e 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java @@ -1839,7 +1839,7 @@ public List readSnapshotMetadatas(SnapshotFileTree sft) { * Some instances can return {@link SnapshotMetadata#folderName()} and {@link SnapshotMetadata#consistentId()} that differs from local. * * @param sft Snapshot file tree. - * @param failIfCantRead If {@code true}, throws an exeption if cant read a metadata file. + * @param failIfCantRead If {@code true}, throws exception if cannot read a metadata file. * @return List of snapshot metadata for the given snapshot name on local node. * If snapshot has been taken from local node the snapshot metadata for given * local node will be placed on the first place. diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java index 295b22b8fe927..03e3131920854 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java @@ -115,12 +115,12 @@ public long size() { return date; } - /** @return Snapshot external storages description if exists. Is always {@code Null} for not the main snapshot description. */ + /** @return Snapshot external storages description if exists. Is always {@code null} for not the main snapshot description. */ public @Nullable SnapshotInfo externalStorages() { return extStors; } - /** @return Snapshot incremental parts description if exists. Is always {@code Nul} for not the main snapshot description. */ + /** @return Snapshot incremental parts description if exists. Is always {@code null} for not the main snapshot description. */ public @Nullable SnapshotInfo incrementals() { return incs; } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListTaskResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListTaskResult.java index 6e347b9e7340a..f00ba2543aacc 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListTaskResult.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListTaskResult.java @@ -37,7 +37,7 @@ public final class SnapshotListTaskResult extends IgniteDataTransferObject { /** Results. */ @Order(2) - SnapshotListJobResult[] snapshots; + SnapshotListJobResult[] nodesSnapshots; /** Default constructor for serialization purposes. */ public SnapshotListTaskResult() { @@ -45,10 +45,10 @@ public SnapshotListTaskResult() { } /** */ - public SnapshotListTaskResult(String[] cstIds, UUID[] nodesIds, SnapshotListJobResult[] snapshots) { - this.cstIds = cstIds; + public SnapshotListTaskResult(String[] consistentIds, UUID[] nodesIds, SnapshotListJobResult[] nodesSnapshots) { + this.cstIds = consistentIds; this.nodesIds = nodesIds; - this.snapshots = snapshots; + this.nodesSnapshots = nodesSnapshots; } /** @return Nodes consistent ids. */ @@ -62,7 +62,7 @@ public UUID[] nodesIds() { } /** @return The results. */ - public SnapshotListJobResult[] snapshots() { - return snapshots; + public SnapshotListJobResult[] nodesSnapshots() { + return nodesSnapshots; } } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListRollingUpgradeTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListRollingUpgradeTest.java index 97b1bed7751df..e335c8989711a 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListRollingUpgradeTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListRollingUpgradeTest.java @@ -176,7 +176,7 @@ private void ensureSnapshotListSucceeds() throws Exception { for (int g = 0; g < ALL_GRIDS; g++) { SnapshotListTaskResult listOpRes = listSnapshots(grid(g)); - assertEquals(3, listOpRes.snapshots().length); + assertEquals(3, listOpRes.nodesSnapshots().length); // Tests client exclusion. if (CLIENTS > 0) { @@ -185,7 +185,7 @@ private void ensureSnapshotListSucceeds() throws Exception { assertFalse(new HashSet<>(Arrays.asList(listOpRes.nodesIds())).contains(cliId)); } - for (SnapshotListJobResult nodeRes : listOpRes.snapshots()) + for (SnapshotListJobResult nodeRes : listOpRes.nodesSnapshots()) assertEquals(1, nodeRes.snapshots().size()); } } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java index 18ee2c9daf1ef..857898469a199 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java @@ -48,6 +48,8 @@ import org.apache.ignite.plugin.PluginConfiguration; import org.apache.ignite.plugin.PluginContext; import org.apache.ignite.plugin.PluginProvider; +import org.jetbrains.annotations.Nullable; +import org.junit.Ignore; import org.junit.Test; import org.junit.runners.Parameterized; @@ -160,7 +162,7 @@ public void testConcurrentCreation() throws Exception { SnapshotListTaskResult lstOpRes = listSnapshots(grid(2)); - int snpsCnt = Stream.of(lstOpRes.snapshots()).mapToInt(jr -> jr.snapshots().size()).sum(); + int snpsCnt = Stream.of(lstOpRes.nodesSnapshots()).mapToInt(jr -> jr.snapshots().size()).sum(); assertEquals(grids, snpsCnt); @@ -248,7 +250,7 @@ private void doTestDeletionAfterMetaRead(boolean completeDeletion) throws Except SnapshotListTaskResult lstOpRes = lstOpFut.get(); if (completeDeletion) { - int snpsCnt = Stream.of(lstOpRes.snapshots()).mapToInt(jr -> jr.snapshots().size()).sum(); + int snpsCnt = Stream.of(lstOpRes.nodesSnapshots()).mapToInt(jr -> jr.snapshots().size()).sum(); assertEquals(grids - 1, snpsCnt); } @@ -259,7 +261,7 @@ private void doTestDeletionAfterMetaRead(boolean completeDeletion) throws Except for (int i = 0; i < lstOpRes.nodesIds().length; i++) { UUID nid = lstOpRes.nodesIds()[i]; - Map nodeSnps = lstOpRes.snapshots()[i].snapshots(); + Map nodeSnps = lstOpRes.nodesSnapshots()[i].snapshots(); snpsCnt += nodeSnps.size(); @@ -280,8 +282,8 @@ private void doTestDeletionAfterMetaRead(boolean completeDeletion) throws Except } /** - * Ensures that a file can be read without locking or other issues while bieng cuncurently written on curren OS and - * file system. This behavior is important for the case when snapshots is being read while creation. + * Ensures that a file can be read without locking or other issues while being concurrently written on current OS and + * file system. This behavior is important for the case when snapshots are being read while creation. */ @Test public void testReadWhileCreating() throws Exception { @@ -337,6 +339,52 @@ public void testReadWhileCreating() throws Exception { assertTrue(readFlag.get()); } + /** */ + @Test + public void testWongSnapshotPath() throws Exception { + doTestSnapshotPath(null); + + SnapshotListJobResult[] lstOpRes = listSnapshots(grid(0), "wrong_path").nodesSnapshots(); + + int cnt = Stream.of(lstOpRes).mapToInt(nodeRes -> nodeRes.snapshots().size()).sum(); + + assertEquals(0, cnt); + } + + /** */ + @Test + public void testDefaultSnapshotPath() throws Exception { + doTestSnapshotPath(null); + } + + /** */ + @Test + @Ignore("https://issues.apache.org/jira/browse/IGNITE-29126") + public void testRelativeSnapshotPath() throws Exception { + doTestSnapshotPath("ex_snapshots"); + } + + /** */ + @Test + public void testAbsoluteSnapshotPath() throws Exception { + doTestSnapshotPath(new File(U.defaultWorkDirectory(), "ex_snapshots").getAbsolutePath()); + } + + /** */ + private void doTestSnapshotPath(@Nullable String path) throws Exception { + int grids = 3; + + startGridsWithCache(grids, txCacheConfig(defaultCacheConfiguration()), CACHE_KEYS_RANGE); + + snp(grid(0)).createSnapshot(SNAPSHOT_NAME, path, false, onlyPrimary).get(getTestTimeout()); + + SnapshotListJobResult[] lstOpRes = listSnapshots(grid(0), path).nodesSnapshots(); + + int cnt = Stream.of(lstOpRes).mapToInt(nodeRes -> nodeRes.snapshots().size()).sum(); + + assertEquals(grids, cnt); + } + /** */ @Test public void testMissingMeta() throws Exception { @@ -380,12 +428,12 @@ private void doTestWithWrongMeta(boolean corruptFile) throws Exception { for (int i = 0; i < res.nodesIds().length; i++) { UUID nid = res.nodesIds()[i]; - foundSnpsCnt += res.snapshots()[i].snapshots().size(); + foundSnpsCnt += res.nodesSnapshots()[i].snapshots().size(); if (nid.equals(grid(testGridIdx).localNode().id())) { victimNodeFound = true; - assertTrue(res.snapshots()[i].snapshots().isEmpty()); + assertTrue(res.nodesSnapshots()[i].snapshots().isEmpty()); } } @@ -394,11 +442,18 @@ private void doTestWithWrongMeta(boolean corruptFile) throws Exception { } /** */ - private static SnapshotListTaskResult listSnapshots(IgniteEx grid) throws Exception { + private static SnapshotListTaskResult listSnapshots(IgniteEx grid, @Nullable String src) throws Exception { SnapshotListCommandArg arg = new SnapshotListCommandArg(); + arg.src(src); + Collection nodes = grid.cluster().forServers().nodes().stream().map(ClusterNode::id).toList(); return grid.compute().execute(SnapshotListTask.class, new VisorTaskArgument<>(nodes, arg, false)).result(); } + + /** */ + private static SnapshotListTaskResult listSnapshots(IgniteEx grid) throws Exception { + return listSnapshots(grid, null); + } } From 4be409dc5c66ba5cc34f51781800e070949472cd Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Fri, 9 Oct 2026 13:56:52 +0300 Subject: [PATCH 88/97] doc test fix --- .../GridCommandHandlerClusterByClassTest_help.output | 6 ++++++ .../GridCommandHandlerClusterByClassWithSSLTest_help.output | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output index dd6ff4e43caf5..b86b79a73834f 100644 --- a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output +++ b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output @@ -269,6 +269,12 @@ This utility can do the following commands: Get the status of the current snapshot operation: control.(sh|bat) --snapshot status + Lists all snapshots on all online server nodes with their sizes: + control.(sh|bat) --snapshot list [--src path/to/snapshots] + + Parameters: + --src path/to/snapshots - Path to snapshot location directory. If not specified, the default configured snapshot directory will be used. + Change cluster tag to new value: control.(sh|bat) --change-tag newTagValue [--yes] diff --git a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output index 76f0f8c7e0e37..54b21e4ef88bf 100644 --- a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output +++ b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output @@ -269,6 +269,12 @@ This utility can do the following commands: Get the status of the current snapshot operation: control.(sh|bat) --snapshot status + Lists all snapshots on all online server nodes with their sizes: + control.(sh|bat) --snapshot list [--src path/to/snapshots] + + Parameters: + --src path/to/snapshots - Path to snapshot location directory. If not specified, the default configured snapshot directory will be used. + Change cluster tag to new value: control.(sh|bat) --change-tag newTagValue [--yes] From 77fafa80735e63d78b3a3f6ab3713efc3af9391f Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Fri, 9 Oct 2026 14:04:19 +0300 Subject: [PATCH 89/97] review fixes --- .../IgniteControlUtilityTestSuite6.java | 6 +- .../GridCommandHandlerListSnapshotTest.java | 8 +- .../DataStorageConfiguration.java | 2 + .../snapshot/SnapshotListCommand.java | 3 - .../management/snapshot/SnapshotListTask.java | 49 ++-- .../snapshot/IgniteSnapshotManager.java | 2 +- .../IgniteClusterSnapshotListTest.java | 273 ++++++++++++++++-- .../AbstractRollingUpgradeTest.java | 4 +- .../testsuites/IgniteSnapshotTestSuite9.java | 5 +- 9 files changed, 287 insertions(+), 65 deletions(-) diff --git a/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite6.java b/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite6.java index c2c5a848f6d2f..925394f4639b8 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite6.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite6.java @@ -1,12 +1,12 @@ /* * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with + * 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 + * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java index 9eae9e894cbc1..b62f5f58411f1 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java @@ -59,8 +59,8 @@ public class GridCommandHandlerListSnapshotTest extends GridCommandHandlerAbstra /** Resolved extra storages paths. {@code null} if {@code extraStorages} is {@code null}. */ private @Nullable String[] extStoragePaths; - /** Node consistent id postfix. */ - private @Nullable String cstId_postfix = ""; + /** Consistent id postfix. */ + private @Nullable String cstIdPostfix = ""; /** Flag setting the usage of a custom snapshot path. */ @Parameter(1) @@ -146,7 +146,7 @@ public static Collection parameters() { /** {@inheritDoc} */ @Override public String getTestIgniteInstanceName() { - return super.getTestIgniteInstanceName() + cstId_postfix; + return super.getTestIgniteInstanceName() + cstIdPostfix; } /** */ @@ -263,7 +263,7 @@ private void doTestSnapshotsLists(int snpCnt, boolean deleteOnOneNode, boolean r if (restartWithChangedCstIds) { stopAllGrids(); - cstId_postfix = "_changed"; + cstIdPostfix = "_changed"; startGridsMultiThreaded(srvrsCnt); diff --git a/modules/core/src/main/java/org/apache/ignite/configuration/DataStorageConfiguration.java b/modules/core/src/main/java/org/apache/ignite/configuration/DataStorageConfiguration.java index 10bd7b1fb869e..499c76f06b60a 100644 --- a/modules/core/src/main/java/org/apache/ignite/configuration/DataStorageConfiguration.java +++ b/modules/core/src/main/java/org/apache/ignite/configuration/DataStorageConfiguration.java @@ -624,6 +624,8 @@ public DataStorageConfiguration setExtraStoragePaths(String... extraStoragePaths } /** + * TODO: support the extra storages for incremental snapshots https://issues.apache.org/jira/browse/IGNITE-29128 + * * Sets a paths to the root directories where the snapshot files stored. * By default, {@link IgniteConfiguration#getSnapshotPath()} used. * Length of {@code extraSnapshotPaths} must be equal to the length of {@link #getExtraStoragePaths()}. diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java index 4ac27435dffdd..cfc6c6c0e0386 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java @@ -36,9 +36,6 @@ public class SnapshotListCommand extends AbstractSnapshotCommand() { - @Override public FileVisitResult postVisitDirectory(Path dir, IOException err) { - return FileVisitResult.CONTINUE; - } - @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + entered.compareAndSet(false, true); + // Use attrs instead of Files.size() for efficiency. if (attrs.isRegularFile()) size.addAndGet(attrs.size()); - else if (!entered.get() && file.toFile().equals(path)) - entered.set(true); return FileVisitResult.CONTINUE; } @@ -181,6 +177,7 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { throw new IgniteException("Won't search for local snapshots.", new NodeStoppingException("Node is stopping.")); // Read local snapshots. + // TODO: use the default snapshots directory for relative {@code arg.src()} https://issues.apache.org/jira/browse/IGNITE-29126 List> locSnps = findLocalSnapshots(arg.src()); if (locSnps.isEmpty()) @@ -208,17 +205,20 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { if (ignite.context().isStopping()) throw new IgniteException("Won't search for local snapshots.", new NodeStoppingException("Node is stopping.")); + if (failedSnps.contains(snpName)) + return; + try { SnapshotListJobResult.SnapshotInfo extDesc = externalStorages(sft); if (extDesc != null) extStors.put(snpName, extDesc); } - catch (Throwable t) { + catch (Exception e) { // Skip snapshot in case on any failure. failedSnps.add(snpName); - log.warning("Failed to read snapshot's extra storages, snapshot ignored [snpName=" + snpName + ']', t); + log.warning("Failed to read snapshot's extra storages, snapshot ignored [snpName=" + snpName + ']', e); } }); @@ -226,23 +226,23 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { // Future for optional incremental parts. fut = exec.submit(() -> { - if (failedSnps.contains(snpName)) - return; - if (ignite.context().isStopping()) throw new IgniteException("Won't search for local snapshots.", new NodeStoppingException("Node is stopping.")); + if (failedSnps.contains(snpName)) + return; + try { SnapshotListJobResult.SnapshotInfo incDesc = incrementals(sft); if (incDesc != null) incs.put(snpName, incDesc); } - catch (Throwable t) { + catch (Exception e) { // Skip snapshot in case on any failure. failedSnps.add(snpName); - log.warning("Failed to read snapshot's incremental parts, snapshot ignored [snpName=" + snpName + ']', t); + log.warning("Failed to read snapshot's incremental parts, snapshot ignored [snpName=" + snpName + ']', e); } }); @@ -378,7 +378,7 @@ private List> findLocalSnapshots(@Nullable String snp } /** @return Number, total size and last creation time of incremental snapshots. */ - private @Nullable SnapshotListJobResult.SnapshotInfo incrementals(SnapshotFileTree sft) throws Exception { + private @Nullable SnapshotListJobResult.SnapshotInfo incrementals(SnapshotFileTree sft) { File[] incs = sft.incrementsRoot().listFiles(); if (F.isEmpty(incs)) @@ -390,21 +390,28 @@ private List> findLocalSnapshots(@Nullable String snp IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); + int incIdx; + for (File incDir : incs) { - int incIdx = Integer.parseInt(incDir.getName()); + try { + incIdx = Integer.parseInt(incDir.getName()); - SnapshotFileTree.IncrementalSnapshotFileTree incTree = sft.incrementalSnapshotFileTree(incIdx); + SnapshotFileTree.IncrementalSnapshotFileTree incTree = sft.incrementalSnapshotFileTree(incIdx); - IncrementalSnapshotMetadata incMeta = snpMgr.readIncrementalSnapshotMetadata(incTree.meta()); + IncrementalSnapshotMetadata incMeta = snpMgr.readIncrementalSnapshotMetadata(incTree.meta()); - size += calculateDirectorySize(incDir); + size += calculateDirectorySize(incDir); - cnt++; + createTime = Math.max(createTime, incMeta.snapshotTime()); - createTime = Math.max(createTime, incMeta.snapshotTime()); + cnt++; + } + catch (Exception e) { + log.warning("Failed to read incremental snapshot, skipped [dir=" + incDir + ']', e); + } } - return new SnapshotListJobResult.SnapshotInfo(cnt, size, createTime); + return cnt == 0 ? null : new SnapshotListJobResult.SnapshotInfo(cnt, size, createTime); } } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java index d12c62777ad8e..3365e0dd812f1 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java @@ -1873,7 +1873,7 @@ public List readSnapshotMetadatas(SnapshotFileTree sft, boolea if (failIfCantRead) throw new IgniteException("Fail to read snapshot metadata [meta=" + smf + ']', e); else - log.error("Fail to read snapshot metadata, skipped [meta=" + smf + ']', e); + log.error("Fail to read snapshot metadata, snapshot skipped [meta=" + smf + ']', e); } } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java index 857898469a199..340fe2d569eec 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java @@ -1,12 +1,12 @@ /* * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with + * 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 + * the License. You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * 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, @@ -31,12 +31,14 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Stream; +import org.apache.ignite.IgniteDataStreamer; import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.configuration.CacheConfiguration; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.IgniteInternalFuture; +import org.apache.ignite.internal.NodeStoppingException; import org.apache.ignite.internal.management.snapshot.SnapshotListCommandArg; import org.apache.ignite.internal.management.snapshot.SnapshotListTask; import org.apache.ignite.internal.processors.cache.persistence.filename.SnapshotFileTree; @@ -55,11 +57,13 @@ import static java.nio.file.Files.newDirectoryStream; import static org.apache.ignite.configuration.IgniteConfiguration.DFLT_SNAPSHOT_THREAD_POOL_SIZE; +import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause; import static org.apache.ignite.testframework.GridTestUtils.cartesianProduct; import static org.apache.ignite.testframework.GridTestUtils.runAsync; import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; -/** Cluster-wide snapshot list procedure tests.*/ +/** Cluster-wide snapshot list procedure tests. */ public class IgniteClusterSnapshotListTest extends AbstractSnapshotSelfTest { /** Number of cache keys to pre-create at node start. */ private static final int CACHE_KEYS_RANGE = 10; @@ -117,13 +121,16 @@ public static Collection params() { /** */ @Test public void testConcurrentCreation() throws Exception { - CountDownLatch proceedSnpCreation = new CountDownLatch(1); - CountDownLatch beginSnpCreation = new CountDownLatch(1); + // The test uses the thread blocking and conditional waitings. Won't proceed with 1 thread. + assumeTrue(snpThrdPoolSz > 1); int grids = 3; int testNodeIdx = 1; int testNodeOrder = testNodeIdx + 1; + CountDownLatch beginSnpCreation = new CountDownLatch(grids); + CountDownLatch proceedSnpCreation = new CountDownLatch(1); + // Delays snapshot creation after its metadata is written. pluginProvider = new AbstractTestPluginProvider() { @Override public String name() { @@ -134,9 +141,9 @@ public void testConcurrentCreation() throws Exception { if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { @Override public void storeSnapshotMeta(M meta, File smf) { - beginSnpCreation.countDown(); + super.storeSnapshotMeta(meta, smf);; - super.storeSnapshotMeta(meta, smf); + beginSnpCreation.countDown(); if (((IgniteEx)ctx.grid()).localNode().order() == testNodeOrder) { try { @@ -156,7 +163,7 @@ public void testConcurrentCreation() throws Exception { startGridsWithCache(grids, txCacheConfig(defaultCacheConfiguration()), CACHE_KEYS_RANGE); - IgniteFuture createSnpFut = snp(grid(0)).createSnapshot(SNAPSHOT_NAME); + IgniteFuture createSnpFut = snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary); assertTrue(beginSnpCreation.await(getTestTimeout(), TimeUnit.MILLISECONDS)); @@ -189,12 +196,12 @@ public void testPartlyDeletedAfterMetaRead() throws Exception { /** */ private void doTestDeletionAfterMetaRead(boolean completeDeletion) throws Exception { - CountDownLatch metaReadProceedLatch = new CountDownLatch(1); - CountDownLatch metaReadBeginLatch = new CountDownLatch(1); - int grids = 3; int testGridIdx = 1; + CountDownLatch metaReadBeginLatch = new CountDownLatch(grids); + CountDownLatch metaReadProceedLatch = new CountDownLatch(1); + pluginProvider = new AbstractTestPluginProvider() { @Override public String name() { return "TestSnpMgrProvider"; @@ -203,23 +210,19 @@ private void doTestDeletionAfterMetaRead(boolean completeDeletion) throws Except @Override public T createComponent(PluginContext ctx, Class cls) { if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { - @Override public List readSnapshotMetadatas(SnapshotFileTree sft, - boolean failIfCantRead) { - List metas = super.readSnapshotMetadatas(sft, failIfCantRead); - - if (cctx.localNode().order() != 2) - return metas; - + @Override public List readSnapshotMetadatas(SnapshotFileTree sft, boolean failIfCantRead) { metaReadBeginLatch.countDown(); - try { - assertTrue(metaReadProceedLatch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); - } - catch (InterruptedException e) { - throw new IllegalStateException(e); + if (cctx.localNode().order() == testGridIdx + 1) { + try { + assertTrue(metaReadProceedLatch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); + } + catch (InterruptedException e) { + throw new IllegalStateException(e); + } } - return metas; + return super.readSnapshotMetadatas(sft, failIfCantRead); } }; } @@ -341,10 +344,29 @@ public void testReadWhileCreating() throws Exception { /** */ @Test - public void testWongSnapshotPath() throws Exception { + public void testMissingSnapshotPath() throws Exception { + doTestWrongSnapshotPath(true); + } + + /** */ + @Test + public void testEmptySnapshotPath() throws Exception { + doTestWrongSnapshotPath(false); + } + + /** */ + private void doTestWrongSnapshotPath(boolean missing) throws Exception { + // Doesn't matter here, fastens the tests. + assumeFalse(encryption || onlyPrimary || snpThrdPoolSz < 2); + doTestSnapshotPath(null); - SnapshotListJobResult[] lstOpRes = listSnapshots(grid(0), "wrong_path").nodesSnapshots(); + File path = new File(U.defaultWorkDirectory(), "not_snapshots"); + + if (!missing) + assertTrue(new File(path, SNAPSHOT_NAME).mkdirs()); + + SnapshotListJobResult[] lstOpRes = listSnapshots(grid(0), path.getAbsolutePath()).nodesSnapshots(); int cnt = Stream.of(lstOpRes).mapToInt(nodeRes -> nodeRes.snapshots().size()).sum(); @@ -403,6 +425,9 @@ public void testCorruptedMeta() throws Exception { * @param corruptFile If {@code true}, corrupts metadata. Otherwise, deletes metadata. */ private void doTestWithWrongMeta(boolean corruptFile) throws Exception { + // Fastens the tests. + assumeFalse(encryption); + int grids = 3; int testGridIdx = 1; @@ -441,6 +466,200 @@ private void doTestWithWrongMeta(boolean corruptFile) throws Exception { assertEquals(grids - 1, foundSnpsCnt); } + /** */ + @Test + public void testMissingIncrementalMeta() throws Exception { + doTestWithWrongIncrementalMeta(false); + } + + /** */ + @Test + public void testCorruptedIncrementalMeta() throws Exception { + doTestWithWrongIncrementalMeta(true); + } + + /** + * Tests snapshot list when incremental snapshot metadata cannot be read. + * The main snapshot should still be listed, but without incremental info on the affected node. + * + * @param corruptFile If {@code true}, corrupts metadata. Otherwise, deletes metadata. + */ + private void doTestWithWrongIncrementalMeta(boolean corruptFile) throws Exception { + // Incremental snapshots do not support the only-primary mode or encryption. + assumeFalse(onlyPrimary || encryption); + + int grids = 3; + int testGridIdx = 1; + int incsCnt = 3; + + IgniteEx ig = startGridsWithCache(grids, txCacheConfig(defaultCacheConfiguration()), CACHE_KEYS_RANGE); + + snp(ig).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(getTestTimeout()); + + for (int i = 0; i < incsCnt; ++i) { + try (IgniteDataStreamer ds = grid(0).dataStreamer(DEFAULT_CACHE_NAME)) { + for (int kv = (i + 1) * CACHE_KEYS_RANGE; kv < (i + 1) * CACHE_KEYS_RANGE * 2; ++kv) + ds.addData(kv, kv); + } + + snp(ig).createSnapshot(SNAPSHOT_NAME, null, true, onlyPrimary).get(getTestTimeout()); + } + + SnapshotFileTree testSnpFt = new SnapshotFileTree(grid(testGridIdx).context(), SNAPSHOT_NAME, null); + SnapshotFileTree.IncrementalSnapshotFileTree incFt = testSnpFt.incrementalSnapshotFileTree(1); + File incMeta = incFt.meta(); + + assertTrue(incMeta.exists()); + + if (corruptFile) { + try (RandomAccessFile raf = new RandomAccessFile(incMeta, "rw")) { + raf.write(UUID.randomUUID().toString().getBytes()); + } + } + else + assertTrue(incMeta.delete() && !incMeta.exists()); + + SnapshotListTaskResult res = listSnapshots(grid(0)); + + for (int i = 0; i < res.nodesIds().length; i++) { + UUID nid = res.nodesIds()[i]; + + Map snps = res.nodesSnapshots()[i].snapshots(); + + assertEquals(1, snps.size()); + + SnapshotListJobResult.SnapshotInfo info = snps.get(SNAPSHOT_NAME); + + assertNotNull(info); + assertNotNull(info.incrementals()); + + if (nid.equals(grid(testGridIdx).localNode().id())) + assertEquals(incsCnt - 1, info.incrementals().number().intValue()); + else + assertEquals(incsCnt, info.incrementals().number().intValue()); + } + } + + /** */ + @Test + public void testSnapshotListsDates() throws Exception { + // Incremental snapshots do not support the only-primary mode or encryption. + assumeFalse(onlyPrimary || encryption); + + int grids = 3; + + IgniteEx ig = startGridsWithCache(grids, txCacheConfig(defaultCacheConfiguration()), CACHE_KEYS_RANGE); + + long time0 = U.currentTimeMillis(); + + // Wait for a while, spend some time. + U.sleep(300L); + + snp(ig).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(getTestTimeout()); + + SnapshotListTaskResult res = listSnapshots(grid(0)); + + for (int i = 0; i < res.nodesIds().length; i++) { + Map snps = res.nodesSnapshots()[i].snapshots(); + + assertEquals(1, snps.size()); + + SnapshotListJobResult.SnapshotInfo info = snps.get(SNAPSHOT_NAME); + + assertTrue(info.date() > time0); + } + + // Wait for a while, spend some time. + U.sleep(300L); + + long time1 = U.currentTimeMillis(); + + try (IgniteDataStreamer ds = ig.dataStreamer(DEFAULT_CACHE_NAME)) { + for (int kv = CACHE_KEYS_RANGE; kv < CACHE_KEYS_RANGE * 2; kv++) + ds.addData(kv, kv); + } + + snp(ig).createSnapshot(SNAPSHOT_NAME, null, true, onlyPrimary).get(getTestTimeout()); + + SnapshotListTaskResult res2 = listSnapshots(grid(0)); + + for (int i = 0; i < res2.nodesIds().length; i++) { + Map snps = res2.nodesSnapshots()[i].snapshots(); + + assertEquals(1, snps.size()); + + SnapshotListJobResult.SnapshotInfo info = snps.get(SNAPSHOT_NAME); + + assertNotNull(info.incrementals()); + + assertTrue(info.date() < time1); + assertTrue(info.date() < info.incrementals().date()); + assertTrue(info.incrementals().date() > time1); + } + } + + /** */ + @Test + public void testNodeStopDuringSnapshotList() throws Exception { + // Doesn't matter here, fastens the tests. + assumeFalse(encryption || onlyPrimary); + + int grids = 3; + int testGridIdx = 1; + + CountDownLatch snpLstBeginLatch = new CountDownLatch(grids); + CountDownLatch snpLstProceedLatch = new CountDownLatch(1); + + // Delays snapshot creation after its metadata is written. + pluginProvider = new AbstractTestPluginProvider() { + @Override public String name() { + return "TestSnpMgrProvider"; + } + + @Override public T createComponent(PluginContext ctx, Class cls) { + if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { + return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { + @Override public List readSnapshotMetadatas(SnapshotFileTree sft, boolean failIfCantRead) { + snpLstBeginLatch.countDown(); + + if (((IgniteEx)ctx.grid()).localNode().order() == testGridIdx + 1) { + try { + assertTrue(snpLstProceedLatch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); + } + catch (InterruptedException e) { + throw new IllegalStateException(e); + } + } + + return super.readSnapshotMetadatas(sft, failIfCantRead); + } + }; + } + + return super.createComponent(ctx, cls); + } + }; + + startGridsWithSnapshot(grids, CACHE_KEYS_RANGE, true); + + IgniteInternalFuture lstOpFut = runAsync(() -> listSnapshots(grid(0))); + + assertTrue(snpLstBeginLatch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); + + IgniteInternalFuture stopFut = runAsync(() -> stopGrid(testGridIdx, true)); + + snpLstProceedLatch.countDown(); + + assertThrowsAnyCause( + null, + () -> lstOpFut.get(getTestTimeout()), + NodeStoppingException.class, + "Node is stopping" + ); + + stopFut.get(getTestTimeout()); + } + /** */ private static SnapshotListTaskResult listSnapshots(IgniteEx grid, @Nullable String src) throws Exception { SnapshotListCommandArg arg = new SnapshotListCommandArg(); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/AbstractRollingUpgradeTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/AbstractRollingUpgradeTest.java index bd21d6923ec93..4371c77ae4c17 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/AbstractRollingUpgradeTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/AbstractRollingUpgradeTest.java @@ -91,8 +91,8 @@ * 2.19.1 0-1 * 2.19.2 0-2 * 2.19.3 0-2,6 - * 2.19.4 1 - * 2.19.5 1-2 + * 2.19.4 0 + * 2.19.5 0-1 * 2.20.0 2-5 * 2.20.1 2-6 * 2.21.0 6 diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite9.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite9.java index f707a6fa7311c..1ad9acf650aa6 100644 --- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite9.java +++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite9.java @@ -26,10 +26,7 @@ import org.apache.ignite.testframework.junits.DynamicSuite; import org.junit.runner.RunWith; -/** - * Split off from {@link IgniteSnapshotTestSuite8} to reduce the single-suite runtime in CI; - * contains an independent subset of the same test classes. - */ +/** Split off from {@link IgniteSnapshotTestSuite8} to reduce the single-suite runtime in CI. */ @RunWith(DynamicSuite.class) public class IgniteSnapshotTestSuite9 { /** From 38fe6a3eb846113ed34e1fe0d6bbfce24b8c24f0 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Fri, 9 Oct 2026 18:40:21 +0300 Subject: [PATCH 90/97] fixes. + the permissions test --- .../DataStorageConfiguration.java | 2 - .../management/snapshot/SnapshotListTask.java | 30 +-- .../filename/SnapshotFileTree.java | 2 + .../snapshot/IgniteSnapshotManager.java | 2 +- .../IgniteClusterSnapshotListTest.java | 182 +++++++++++++++++- 5 files changed, 194 insertions(+), 24 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/configuration/DataStorageConfiguration.java b/modules/core/src/main/java/org/apache/ignite/configuration/DataStorageConfiguration.java index 499c76f06b60a..10bd7b1fb869e 100644 --- a/modules/core/src/main/java/org/apache/ignite/configuration/DataStorageConfiguration.java +++ b/modules/core/src/main/java/org/apache/ignite/configuration/DataStorageConfiguration.java @@ -624,8 +624,6 @@ public DataStorageConfiguration setExtraStoragePaths(String... extraStoragePaths } /** - * TODO: support the extra storages for incremental snapshots https://issues.apache.org/jira/browse/IGNITE-29128 - * * Sets a paths to the root directories where the snapshot files stored. * By default, {@link IgniteConfiguration#getSnapshotPath()} used. * Length of {@code extraSnapshotPaths} must be equal to the length of {@link #getExtraStoragePaths()}. diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java index dbf992328a7bb..25fe5d959cbb3 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java @@ -121,7 +121,7 @@ private String nodeConsistentId(ClusterNode n) { } /** - * Walk though a directory. Doesn't lock it or its content. Tries to find files and summarize their size. + * Walk through a directory. Doesn't lock it or its content. Tries to find files and summarize their size. * Tolerates and skips concurrent modification errors. */ public static long calculateDirectorySize(File path) throws IOException { @@ -177,7 +177,6 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { throw new IgniteException("Won't search for local snapshots.", new NodeStoppingException("Node is stopping.")); // Read local snapshots. - // TODO: use the default snapshots directory for relative {@code arg.src()} https://issues.apache.org/jira/browse/IGNITE-29126 List> locSnps = findLocalSnapshots(arg.src()); if (locSnps.isEmpty()) @@ -190,7 +189,7 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { // Incremental parts and external storages futures. List> futs = new ArrayList<>(locSnps.size() * 2); - // Names of snapshots of with read failures. + // Names of snapshots with read failures. Set failedSnps = ConcurrentHashMap.newKeySet(locSnps.size() / 2); IgniteThreadPoolExecutor exec = ignite.context().pools().getSnapshotExecutorService(); @@ -215,7 +214,6 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { extStors.put(snpName, extDesc); } catch (Exception e) { - // Skip snapshot in case on any failure. failedSnps.add(snpName); log.warning("Failed to read snapshot's extra storages, snapshot ignored [snpName=" + snpName + ']', e); @@ -239,7 +237,6 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { incs.put(snpName, incDesc); } catch (Exception e) { - // Skip snapshot in case on any failure. failedSnps.add(snpName); log.warning("Failed to read snapshot's incremental parts, snapshot ignored [snpName=" + snpName + ']', e); @@ -255,7 +252,7 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { fut.get(); } catch (Throwable t) { - // All the futures has internal exceptions being logged. No errors expected. + // All the futures have internal exceptions being logged. No errors expected. throw new IgniteException("Failed to read local nodes snapshots.", t); } } @@ -276,10 +273,9 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { try { size = calculateDirectorySize(sft.root()); } - catch (Exception e) { + catch (IOException e) { log.warning("Failed to calculate snapshot's size, snapshot ignored [snpName=" + snpName + ']', e); - // Skip snapshot in case on any failure. continue; } @@ -297,16 +293,25 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { } /** */ - private static @Nullable SnapshotListJobResult.SnapshotInfo externalStorages(SnapshotFileTree sft) throws IOException { + private @Nullable SnapshotListJobResult.SnapshotInfo externalStorages(SnapshotFileTree sft) { int extStoragesCnt = 0; long extStoragesSize = 0; - for (File es : sft.allStorages().toList()) { - if (sft.nodeStorage().equals(es)) + for (File extraStorage : sft.allStorages().toList()) { + if (sft.nodeStorage().equals(extraStorage)) continue; + try { + extStoragesSize += calculateDirectorySize(extraStorage); + } + catch (IOException e) { + log.warning("Failed to calculate snapshot's extra storage size, storage ignored [extraStorage=" + + extraStorage + ']', e); + + continue; + } + extStoragesCnt++; - extStoragesSize += calculateDirectorySize(es); } return extStoragesCnt == 0 ? null : new SnapshotListJobResult.SnapshotInfo(extStoragesCnt, extStoragesSize); @@ -369,7 +374,6 @@ private List> findLocalSnapshots(@Nullable String snp res.add(snpDirRes); } catch (Exception e) { - // Skip snapshot in case on any failure. log.warning("Failed to read snapshot, snapshot ignored [path=" + snpPath + ']', e); } }); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java index 5c75e231b35d9..d686e6ade95a7 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java @@ -374,6 +374,8 @@ public File meta() { } /** + * TODO: support the extra storages for incremental snapshots https://issues.apache.org/jira/browse/IGNITE-29128 + * * Modifies {@link #extraStorages} for this tree to reflect snapshot options. * In case {@link IgniteConfiguration#getSnapshotPath()} points to absolute directory or {@link #path} for snapshot provided * then all snapshot files must be stored inside one folder. diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java index 3365e0dd812f1..aa5cc959aa904 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java @@ -1873,7 +1873,7 @@ public List readSnapshotMetadatas(SnapshotFileTree sft, boolea if (failIfCantRead) throw new IgniteException("Fail to read snapshot metadata [meta=" + smf + ']', e); else - log.error("Fail to read snapshot metadata, snapshot skipped [meta=" + smf + ']', e); + log.warning("Fail to read snapshot metadata, snapshot skipped [meta=" + smf + ']', e); } } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java index 340fe2d569eec..736cee436df3d 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java @@ -18,20 +18,27 @@ package org.apache.ignite.internal.processors.cache.persistence.snapshot; import java.io.File; +import java.io.IOException; import java.io.RandomAccessFile; import java.io.Serializable; import java.nio.file.DirectoryStream; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Stream; import org.apache.ignite.IgniteDataStreamer; +import org.apache.ignite.IgniteException; import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction; import org.apache.ignite.cluster.ClusterNode; import org.apache.ignite.configuration.CacheConfiguration; @@ -71,6 +78,9 @@ public class IgniteClusterSnapshotListTest extends AbstractSnapshotSelfTest { /** Number of partitions within a snapshot cache group. */ private static final int CACHE_PARTITIONS_COUNT = 4; + /** */ + private static boolean posixPermissions; + /** Size of the snapshot utility thread pool. */ @Parameterized.Parameter(2) public int snpThrdPoolSz; @@ -101,12 +111,6 @@ public static Collection params() { return cfg; } - /** {@inheritDoc} */ - @Override protected CacheConfiguration txCacheConfig(CacheConfiguration ccfg) { - // Fastent the tests. - return super.txCacheConfig(ccfg).setAffinity(new RendezvousAffinityFunction(false, CACHE_PARTITIONS_COUNT)); - } - /** {@inheritDoc} */ @Override protected void cleanPersistenceDir() throws Exception { super.cleanPersistenceDir(); @@ -118,6 +122,30 @@ public static Collection params() { } } + /** {@inheritDoc} */ + @Override protected void beforeTestsStarted() throws Exception { + super.beforeTestsStarted(); + + File workDir = new File(U.defaultWorkDirectory()); + + assertTrue(workDir.exists()); + + try { + Files.getPosixFilePermissions(workDir.toPath()); + + posixPermissions = true; + } + catch (UnsupportedOperationException ignored) { + // No-op. + } + } + + /** {@inheritDoc} */ + @Override protected CacheConfiguration txCacheConfig(CacheConfiguration ccfg) { + // Speeds up the tests. + return super.txCacheConfig(ccfg).setAffinity(new RendezvousAffinityFunction(false, CACHE_PARTITIONS_COUNT)); + } + /** */ @Test public void testConcurrentCreation() throws Exception { @@ -141,7 +169,7 @@ public void testConcurrentCreation() throws Exception { if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { @Override public void storeSnapshotMeta(M meta, File smf) { - super.storeSnapshotMeta(meta, smf);; + super.storeSnapshotMeta(meta, smf); beginSnpCreation.countDown(); @@ -425,7 +453,7 @@ public void testCorruptedMeta() throws Exception { * @param corruptFile If {@code true}, corrupts metadata. Otherwise, deletes metadata. */ private void doTestWithWrongMeta(boolean corruptFile) throws Exception { - // Fastens the tests. + // Speeds up the tests. assumeFalse(encryption); int grids = 3; @@ -540,6 +568,144 @@ private void doTestWithWrongIncrementalMeta(boolean corruptFile) throws Exceptio } } + /** + * Test snapshot list operation when a node can't read some snapshot part due to insufficient permissions. + * I.e. a test node is able to read snapshot meta but can't read some the snapshot's data. + */ + @Test + public void testDeniedPermissions() throws Exception { + assumeTrue(posixPermissions); + // We rely on sizes here. Better to avoid empty data nodes not to become flaky. + assumeFalse(onlyPrimary); + + int grids = 3; + int testGridIdx = 1; + + // Permissions to restore. + Map> oldPerms = new ConcurrentHashMap<>(); + // The 'change permissions' flag. + AtomicBoolean changePermissions = new AtomicBoolean(true); + + // Deny reading on a couple of incremental snapshot metadata files on one node. + pluginProvider = new AbstractTestPluginProvider() { + @Override public String name() { + return "TestSnpMgrProvider"; + } + + @Override public T createComponent(PluginContext ctx, Class cls) { + if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { + return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { + @Override public List readSnapshotMetadatas(SnapshotFileTree sft, + boolean failIfCantRead) { + if (changePermissions.get() && ctx.localNode().order() == testGridIdx + 1) { + File victimDir = sft.nodeStorage(); + + assertTrue(victimDir.exists()); + assertTrue(victimDir.isDirectory()); + + // Ensure that blocked snapshot part has some size. We use sizes to compare later. + try { + assertTrue(SnapshotListTask.calculateDirectorySize(victimDir) > 0L); + } + catch (IOException e) { + throw new IllegalStateException(e); + } + + Path victimDirPath = victimDir.toPath(); + + try { + Set perms = Files.getPosixFilePermissions(victimDirPath); + + assertFalse(perms.isEmpty()); + + // Deny reading. + Files.setPosixFilePermissions(victimDirPath, PosixFilePermissions.fromString("---------")); + + // Save actual permissions to restore. + oldPerms.put(victimDirPath, perms); + } + catch (Exception e) { + throw new IgniteException("Unable to set the test posix permissions.", e); + } + } + + return super.readSnapshotMetadatas(sft, failIfCantRead); + } + }; + } + + return super.createComponent(ctx, cls); + } + }; + + startGridsWithSnapshot(grids, CACHE_KEYS_RANGE, true, true); + + UUID testNodeId = grid(testGridIdx).localNode().id(); + + // Snapshot size with restricted permissions. + long testSize0 = 0L; + // Snapshot size with normal permissions. + long testSize1 = 0L; + + SnapshotListTaskResult snpLstOpRes; + + try { + // First run. + snpLstOpRes = listSnapshots(grid(0)); + + assertFalse(oldPerms.isEmpty()); + + for (int i = 0; i < snpLstOpRes.nodesIds().length; i++) { + UUID nodeId = snpLstOpRes.nodesIds()[i]; + + // Store size of the partly read snapshot. + if (nodeId.equals(testNodeId)) { + Map snps = snpLstOpRes.nodesSnapshots()[i].snapshots(); + + assertEquals(1, snps.size()); + + testSize0 = snps.get(SNAPSHOT_NAME).size(); + } + } + + // Ensure that we've found and read snapshot on the test node. + assertTrue(testSize0 > 0L); + } + finally { + // Restore the permissions in any case. + oldPerms.forEach((path, perms) -> { + try { + Files.setPosixFilePermissions(path, perms); + } + catch (IOException e) { + throw new IllegalStateException(e); + } + }); + } + + // Relaunch the operation. + changePermissions.set(false); + + snpLstOpRes = listSnapshots(grid(0)); + + for (int i = 0; i < snpLstOpRes.nodesIds().length; i++) { + UUID nodeId = snpLstOpRes.nodesIds()[i]; + + // Store size of the partly read snapshot. + if (nodeId.equals(testNodeId)) { + Map snps = snpLstOpRes.nodesSnapshots()[i].snapshots(); + + assertEquals(1, snps.size()); + + testSize1 = snps.get(SNAPSHOT_NAME).size(); + } + } + + // Ensure that the calculated anew size is bigger than in the previous run. + assertTrue(testSize1 > 0L); + assertTrue(testSize1 > testSize0); + } + /** */ @Test public void testSnapshotListsDates() throws Exception { From 2a675e56fd1e4225e5d8a2547cf7f5a388bd2170 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Fri, 9 Oct 2026 19:04:05 +0300 Subject: [PATCH 91/97] merge dev branch --- .../dht/preloader/GridDhtPartitionsExchangeFuture.java | 3 +-- .../spi/discovery/tcp/TcpDiscoveryClientTopologyGapTest.java | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsExchangeFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsExchangeFuture.java index ef1dd7419e7cd..92bfca434e95a 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsExchangeFuture.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsExchangeFuture.java @@ -4954,8 +4954,7 @@ public void onNodeLeft(final ClusterNode node) { t ); - if (!cctx.kernalContext().isStopping()) - cctx.kernalContext().failure().process(new FailureContext(FailureType.CRITICAL_ERROR, t)); + cctx.kernalContext().failure().process(new FailureContext(FailureType.CRITICAL_ERROR, t)); throw t; } diff --git a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryClientTopologyGapTest.java b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryClientTopologyGapTest.java index 68a239c7cde38..ba86e441f2f87 100644 --- a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryClientTopologyGapTest.java +++ b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryClientTopologyGapTest.java @@ -41,7 +41,7 @@ public class TcpDiscoveryClientTopologyGapTest extends GridCommonAbstractTest { private static final int CLIENTS = 4; /** */ - private static final int ITERS = GridTestUtils.SF.apply(200); + private static final int ITERS = 300; /** Critical failures captured on nodes, by instance name. */ private static final Map failures = new ConcurrentHashMap<>(); From 569fd93713aaedb6d23f8a2d41822d53fca42058 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Fri, 9 Oct 2026 19:08:39 +0300 Subject: [PATCH 92/97] merge dev branch --- docs/_docs/snapshots/snapshots.adoc | 34 + .../IgniteControlUtilityTestSuite6.java | 32 + .../util/GridCommandHandlerAbstractTest.java | 23 +- .../GridCommandHandlerListSnapshotTest.java | 352 ++++++++ .../management/snapshot/SnapshotCommand.java | 3 +- .../snapshot/SnapshotListCommand.java | 113 +++ .../snapshot/SnapshotListCommandArg.java | 45 + .../management/snapshot/SnapshotListTask.java | 421 +++++++++ .../GridDhtPartitionsExchangeFuture.java | 3 +- .../filename/SnapshotFileTree.java | 4 +- .../snapshot/IgniteSnapshotManager.java | 28 +- .../snapshot/SnapshotCheckProcess.java | 2 +- .../snapshot/SnapshotListJobResult.java | 133 +++ .../snapshot/SnapshotListTaskResult.java | 68 ++ .../SnapshotMetadataVerificationTask.java | 5 +- .../DistributedMetaStorageImpl.java | 2 +- .../feature/CoreFeatureRegistry.java | 3 + ...ClusterSnapshotListRollingUpgradeTest.java | 245 +++++ .../IgniteClusterSnapshotListTest.java | 844 ++++++++++++++++++ .../AbstractRollingUpgradeTest.java | 2 + .../TestIgniteReleaseFeatures_2_19_4.java | 24 + .../TestIgniteReleaseFeatures_2_19_5.java | 27 + .../TcpDiscoveryClientTopologyGapTest.java | 2 +- .../testsuites/IgniteSnapshotTestSuite9.java | 51 ++ ...mmandHandlerClusterByClassTest_help.output | 6 + ...ndlerClusterByClassWithSSLTest_help.output | 6 + .../tests/ContinuousDataLoadApplication.java | 2 +- 27 files changed, 2461 insertions(+), 19 deletions(-) create mode 100644 modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite6.java create mode 100644 modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommandArg.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java create mode 100644 modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListTaskResult.java create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListRollingUpgradeTest.java create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_4.java create mode 100644 modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_5.java create mode 100644 modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite9.java diff --git a/docs/_docs/snapshots/snapshots.adoc b/docs/_docs/snapshots/snapshots.adoc index fe61b3c49234a..8650862821e06 100644 --- a/docs/_docs/snapshots/snapshots.adoc +++ b/docs/_docs/snapshots/snapshots.adoc @@ -287,6 +287,40 @@ control.(sh|bat) --snapshot restore snapshot_09062021 --groups cache-group1,cach control.(sh|bat) --snapshot restore snapshot_09062021 --increment 1 ---- +== Listing Snapshots + +To list snapshots created on cluster, use the following command. + +[tabs] +-- +tab:Unix[] +[source,shell] +---- +# List all snapshots in the default snapshot directory. +control.sh --snapshot list + +# List all snapshots in the "/tmp/ignite/snapshots" folder. +control.sh --snapshot list --src /tmp/ignite/snapshots +---- + +tab:Windows[] +[source,shell] +---- +# List all snapshots in the default snapshot directory. +control.bat --snapshot list + +# List all snapshots in the "C:\\tmp\\ignite\\snapshots" folder. +control.bat --snapshot list --src C:\\tmp\\ignite\\snapshots +---- +-- + +=== List operation notes + +* The list operation is read-only and does not modify any snapshot data. +* If a snapshot is being modified (created or deleted), its size might not be reported. +* Snapshot is being detected by its metadata. If metadata is missing or cannot be read, snapshot is ignored. +* When snapshot listing and size calculating, no additional snapshot validation, checksums calculation are done. + == Getting Snapshot Operation Status The status of the current snapshot operation in the cluster can be obtained using the `control.sh|bat` script or JMX interface: diff --git a/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite6.java b/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite6.java new file mode 100644 index 0000000000000..925394f4639b8 --- /dev/null +++ b/modules/control-utility/src/test/java/org/apache/ignite/testsuites/IgniteControlUtilityTestSuite6.java @@ -0,0 +1,32 @@ +/* + * 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.ignite.testsuites; + +import org.apache.ignite.util.GridCommandHandlerListSnapshotTest; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +/** + * Test suite for control utility. + */ +@RunWith(Suite.class) +@Suite.SuiteClasses({ + GridCommandHandlerListSnapshotTest.class +}) +public class IgniteControlUtilityTestSuite6 { +} diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerAbstractTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerAbstractTest.java index 092fe78903246..7d5f2e14c289d 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerAbstractTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerAbstractTest.java @@ -499,6 +499,22 @@ protected void createCacheAndPreload( ) { assert nonNull(ignite); + CacheConfiguration ccfg = testCacheConfiguration(cacheName, partitions, filter); + + ignite.createCache(ccfg); + + try (IgniteDataStreamer streamer = ignite.dataStreamer(cacheName)) { + for (int i = 0; i < countEntries; i++) + streamer.addData(i, i); + } + } + + /** */ + protected CacheConfiguration testCacheConfiguration( + String cacheName, + int partitions, + @Nullable IgnitePredicate filter + ) { CacheConfiguration ccfg = new CacheConfiguration<>(cacheName) .setAffinity(new RendezvousAffinityFunction(false, partitions)) .setBackups(1) @@ -507,12 +523,7 @@ protected void createCacheAndPreload( if (filter != null) ccfg.setNodeFilter(filter); - ignite.createCache(ccfg); - - try (IgniteDataStreamer streamer = ignite.dataStreamer(cacheName)) { - for (int i = 0; i < countEntries; i++) - streamer.addData(i, i); - } + return ccfg; } /** diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java new file mode 100644 index 0000000000000..b62f5f58411f1 --- /dev/null +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java @@ -0,0 +1,352 @@ +/* + * 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.ignite.util; + +import java.io.File; +import java.nio.file.DirectoryStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collection; +import org.apache.ignite.Ignite; +import org.apache.ignite.IgniteDataStreamer; +import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.configuration.CacheConfiguration; +import org.apache.ignite.configuration.DataStorageConfiguration; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.management.snapshot.SnapshotListCommand; +import org.apache.ignite.internal.processors.cache.persistence.filename.SnapshotFileTree; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.G; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.lang.IgnitePredicate; +import org.apache.ignite.testframework.GridTestUtils; +import org.jetbrains.annotations.Nullable; +import org.junit.Test; +import org.junit.runners.Parameterized.Parameter; +import org.junit.runners.Parameterized.Parameters; + +import static java.nio.file.Files.newDirectoryStream; +import static org.apache.ignite.cluster.ClusterState.ACTIVE; +import static org.apache.ignite.internal.commandline.CommandHandler.EXIT_CODE_OK; +import static org.apache.ignite.internal.processors.cache.persistence.snapshot.AbstractSnapshotSelfTest.snp; +import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; + +/** Test for the command '--snapshot list'. */ +public class GridCommandHandlerListSnapshotTest extends GridCommandHandlerAbstractTest { + /** Extra storage path. */ + private static final String EXT_STORAGE_PATH = "extStorage"; + + /** Flag to use {@link DataStorageConfiguration#setExtraSnapshotPaths(String...)}. */ + private boolean extraStorages; + + /** Resolved extra storages paths. {@code null} if {@code extraStorages} is {@code null}. */ + private @Nullable String[] extStoragePaths; + + /** Consistent id postfix. */ + private @Nullable String cstIdPostfix = ""; + + /** Flag setting the usage of a custom snapshot path. */ + @Parameter(1) + public boolean customPath; + + /** Flag setting the usage of dedicated, own node working directories. */ + @Parameter(2) + public boolean separatedWorkDir; + + /** Flag to add extra server node after the snapshot creation. */ + @Parameter(3) + public boolean addExtraSrvr; + + /** Number of incremental snapshots to add to the main test snapshots. */ + @Parameter(4) + public int incCnt; + + /** */ + @Parameters(name = "cmdHnd={0},customPath={1},ownWorkDir={2},addExtraSrvr={3},incCnt={4}") + public static Collection parameters() { + return GridTestUtils.cartesianProduct( + commandHandlers(), + F.asList(false, true), // Custom snapshot path + F.asList(false, true), // Separated (own) work directories + F.asList(false, true), // Add a server node after the snapshot creation + F.asList(0, 2) // Number of incremental snapshots + ); + } + + /** {@inheritDoc} */ + @Override protected void afterTest() throws Exception { + super.afterTest(); + + stopAllGrids(); + + cleanPersistenceDir(); + } + + /** {@inheritDoc} */ + @Override protected void beforeTest() throws Exception { + super.beforeTest(); + + /** Handy if test running is interrupted and {@link #afterTest()} isn't invoked. */ + cleanPersistenceDir(); + } + + /** {@inheritDoc} */ + @Override protected void cleanPersistenceDir() throws Exception { + super.cleanPersistenceDir(); + + // Also cleans separated snapshot working directories and custom snapshot paths. + try (DirectoryStream files = newDirectoryStream(Paths.get(U.defaultWorkDirectory()))) { + for (Path path : files) + U.delete(path); + } + } + + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { + IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName); + + String workDir = separatedWorkDir + ? new File(U.defaultWorkDirectory(), igniteInstanceName).getAbsolutePath() + : U.defaultWorkDirectory(); + + cfg.setWorkDirectory(workDir); + + cfg.getDataStorageConfiguration().setWalCompactionEnabled(incCnt > 0); + + if (extraStorages) { + cfg.getDataStorageConfiguration().setExtraStoragePaths( + workDir + File.separator, + workDir + File.separator + EXT_STORAGE_PATH + ); + + extStoragePaths = cfg.getDataStorageConfiguration().getExtraStoragePaths(); + + cfg.getDataStorageConfiguration().setExtraSnapshotPaths("", EXT_STORAGE_PATH); + } + + return cfg; + } + + /** {@inheritDoc} */ + @Override public String getTestIgniteInstanceName() { + return super.getTestIgniteInstanceName() + cstIdPostfix; + } + + /** */ + @Test + public void testNoSnapshots() throws Exception { + assumeFalse(addExtraSrvr || incCnt > 0); + + doTestSnapshotsLists(0, false, false); + } + + /** */ + @Test + public void testSingleSnapshot() throws Exception { + doTestSnapshotsLists(1, false, false); + } + + /** */ + @Test + public void testSeveralSnapshots() throws Exception { + doTestSnapshotsLists(3, false, false); + } + + /** */ + @Test + public void testOneNodeMisses() throws Exception { + // Doesn't matter here. + assumeFalse(incCnt > 0); + // Let's keep just one node not seeing the snapshot. + assumeTrue(separatedWorkDir); + // Almost the same tests. + assumeFalse(addExtraSrvr); + + doTestSnapshotsLists(2, true, false); + } + + /** */ + @Test + public void testExtraStorages() throws Exception { + // Extra storeages are required to be the same as configured in the node's PDS storages. Thus, we skip different work folders. + // Also, extra snapshot storages aren't used if snapshot is created with a custom path. + assumeFalse(separatedWorkDir || customPath); + + extraStorages = true; + + doTestSnapshotsLists(2, false, false); + } + + /** */ + @Test + public void testChangedConsistentId() throws Exception { + // In new created working directories there will be obviously no snapshots. + assumeFalse(separatedWorkDir); + // Fastens the tests + assumeFalse(incCnt > 0); + + doTestSnapshotsLists(1, false, true); + } + + /** */ + private void doTestSnapshotsLists(int snpCnt, boolean deleteOnOneNode, boolean restartWithChangedCstIds) throws Exception { + // A custom snapshot path actually puts snapshots in a shared directory. This skews the results when dedicated + // work directories are set. + assumeFalse(customPath && separatedWorkDir); + + int srvrsCnt = 3; + int entriesCnt = 10; + int partitions = 4; + + IgniteEx ig = (IgniteEx)startGridsMultiThreaded(srvrsCnt); + + startGrid(CLIENT_NODE_NAME_PREFIX); + + ig.cluster().state(ACTIVE); + + File snpsRootFile = customPath + ? new File(ig.context().pdsFolderResolver().fileTree().snapshotsRoot(), "ex_snapshots") + : null; + + // Flag if 'testSnapshot0' deleted on node0. + boolean grid0HasNoSnapshot0 = false; + + // Create snapshots. + if (snpCnt > 0) { + createCacheAndPreload(ig, DEFAULT_CACHE_NAME, entriesCnt, partitions, null); + + String absPathStr = null; + + for (int snpIdx = 0; snpIdx < snpCnt; snpIdx++) { + absPathStr = customPath ? snpsRootFile.getAbsolutePath() : null; + + snp(ig).createSnapshot("testSnapshot" + snpIdx, absPathStr, false, false).get(getTestTimeout()); + + for (int incIdx = 0; incIdx < incCnt; incIdx++) { + try (IgniteDataStreamer ds = ig.dataStreamer(DEFAULT_CACHE_NAME)) { + for (int val = (snpIdx + incIdx + 1) * entriesCnt; val < (snpIdx + incIdx + 2) * entriesCnt; val++) + ds.addData(val, val); + } + + snp(ig).createSnapshot("testSnapshot" + snpIdx, absPathStr, true, false).get(getTestTimeout()); + } + } + + if (deleteOnOneNode) { + SnapshotFileTree sft = new SnapshotFileTree(ig.context(), "testSnapshot0", absPathStr); + + assertTrue(sft.root().exists()); + assertTrue(U.delete(sft.root())); + assertFalse(sft.root().exists()); + + grid0HasNoSnapshot0 = true; + } + } + + if (restartWithChangedCstIds) { + stopAllGrids(); + + cstIdPostfix = "_changed"; + + startGridsMultiThreaded(srvrsCnt); + + startGrid(CLIENT_NODE_NAME_PREFIX); + } + + // Add a server. + if (addExtraSrvr) + startGrid(G.allGrids().size()); + + injectTestSystemOut(); + + // Requests snapshots. + if (customPath) { + assertEquals(EXIT_CODE_OK, execute(newCommandHandler(createTestLogger()), "--snapshot", "list", "--src", + snpsRootFile.getAbsolutePath())); + } + else + assertEquals(EXIT_CODE_OK, execute(newCommandHandler(createTestLogger()), "--snapshot", "list")); + + String out = testOut.toString(); + + // Find the nodes in the output. + for (Ignite g : G.allGrids()) { + ClusterNode n = g.cluster().localNode(); + + assertEquals(n.isClient() ? 0 : 1, countEntries(out, "Node '%s'".formatted(n.consistentId().toString()))); + } + + // Ensure that there are no snapshots. + if (snpCnt == 0) { + assertFalse(out.contains("Snapshot '")); + + assertEquals(srvrsCnt + (addExtraSrvr ? 1 : 0), countEntries(out, SnapshotListCommand.NO_SNAPSHOTS)); + + return; + } + + assertEquals((addExtraSrvr && separatedWorkDir ? 1 : 0), countEntries(out, SnapshotListCommand.NO_SNAPSHOTS)); + + // The additional server node doesn't have snapshots. But it can see them if shared the snapshot directory. + int snpsRecordsCnt = srvrsCnt + (addExtraSrvr + ? (separatedWorkDir ? 0 : 1) + : 0 + ); + + // Find the snapshots in the output. + for (int snpIdx = 0; snpIdx < snpCnt; snpIdx++) { + // 'testSnapshot0' has fewer records if was deleted on one node. + int certainSnpRecordsCnt = snpIdx == 0 && grid0HasNoSnapshot0 + ? snpsRecordsCnt - 1 + : snpsRecordsCnt; + + assertEquals(certainSnpRecordsCnt, countEntries(out, "Snapshot 'testSnapshot" + snpIdx + "'")); + + for (int i = 0; i < incCnt; i++) + assertEquals(certainSnpRecordsCnt * snpCnt, countEntries(out, "incremental snapshots: cnt=" + incCnt)); + + if (extraStorages) + assertEquals(certainSnpRecordsCnt * snpCnt, countEntries(out, "external storages: cnt=1, size=")); + } + } + + /** */ + private static int countEntries(String txt, String entry) { + String prev = txt; + + txt = txt.replaceAll(entry, ""); + + return (prev.length() - txt.length()) / entry.length(); + } + + /** */ + @Override protected CacheConfiguration testCacheConfiguration( + String cacheName, + int partitions, + @Nullable IgnitePredicate filter + ) { + CacheConfiguration ccfg = super.testCacheConfiguration(cacheName, partitions, filter); + + if (extraStorages) + ccfg.setStoragePaths(extStoragePaths); + + return ccfg; + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCommand.java index deb5416b8e28b..e9d4e7504351d 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCommand.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCommand.java @@ -28,7 +28,8 @@ public SnapshotCommand() { new SnapshotCancelCommand(), new SnapshotCheckCommand(), new SnapshotRestoreCommand(), - new SnapshotStatusCommand() + new SnapshotStatusCommand(), + new SnapshotListCommand() ); } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java new file mode 100644 index 0000000000000..cfc6c6c0e0386 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommand.java @@ -0,0 +1,113 @@ +/* + * 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.ignite.internal.management.snapshot; + +import java.time.Instant; +import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import java.util.function.Consumer; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListJobResult; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListTaskResult; +import org.apache.ignite.internal.util.typedef.internal.U; + +/** Snapshot list command. */ +public class SnapshotListCommand extends AbstractSnapshotCommand { + /** */ + public static final String HEADER = "Snapshots found on the following nodes:"; + + /** */ + public static final String DESC = "Lists all snapshots on all online server nodes with their sizes"; + + /** */ + public static final String NO_SNAPSHOTS = "No snapshots found."; + + /** */ + private static final String PATTERN_FORMAT = "yyyy-MM-dd HH:mm:ss Z"; + + /** */ + private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(PATTERN_FORMAT).withZone(ZoneId.systemDefault()); + + /** {@inheritDoc} */ + @Override public String description() { + return DESC; + } + + /** {@inheritDoc} */ + @Override public Class argClass() { + return SnapshotListCommandArg.class; + } + + /** {@inheritDoc} */ + @Override public Class taskClass() { + return SnapshotListTask.class; + } + + /** {@inheritDoc} */ + @Override public void printResult(SnapshotListCommandArg arg, SnapshotListTaskResult res, Consumer printer) { + printer.accept(HEADER); + + for (int nodeIdx = 0; nodeIdx < res.nodesIds().length; nodeIdx++) { + // Skip line before node. + printer.accept(""); + + printer.accept("\tNode '%s' [uuid=%s]:".formatted(res.consistentIds()[nodeIdx], res.nodesIds()[nodeIdx])); + + SnapshotListJobResult nodeResult = res.nodesSnapshots()[nodeIdx]; + + if (nodeResult.snapshots().isEmpty()) { + printer.accept("\t\t" + NO_SNAPSHOTS); + + continue; + } + + nodeResult.snapshots().forEach((snpName, snpInfo) -> { + printer.accept("\t\tSnapshot '%s': totalSize=%s (%db), created='%s' (epoch=%d)".formatted( + snpName, + U.humanReadableByteCount(snpInfo.size()), + snpInfo.size(), + DATE_FORMATTER.format(Instant.ofEpochMilli(snpInfo.date())), + snpInfo.date() + )); + + SnapshotListJobResult.SnapshotInfo extStors = snpInfo.externalStorages(); + SnapshotListJobResult.SnapshotInfo incs = snpInfo.incrementals(); + + if (extStors != null) { + printer.accept("\t\t\texternal storages: cnt=%d, size=%s (%db)".formatted( + extStors.number(), + U.humanReadableByteCount(extStors.size()), + extStors.size() + )); + } + + if (incs != null) { + printer.accept("\t\t\tincremental snapshots: cnt=%d, size=%s (%db), modified='%s' (epoch=%d)".formatted( + incs.number(), + U.humanReadableByteCount(incs.size()), + incs.size(), + DATE_FORMATTER.format(Instant.ofEpochMilli(incs.date())), + incs.date() + )); + } + }); + } + + // Drop a line. + printer.accept(""); + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommandArg.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommandArg.java new file mode 100644 index 0000000000000..af94efaab54ff --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListCommandArg.java @@ -0,0 +1,45 @@ +/* + * 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.ignite.internal.management.snapshot; + +import org.apache.ignite.internal.Order; +import org.apache.ignite.internal.dto.IgniteDataTransferObject; +import org.apache.ignite.internal.management.api.Argument; +import org.jetbrains.annotations.Nullable; + +/** */ +public class SnapshotListCommandArg extends IgniteDataTransferObject { + /** */ + private static final long serialVersionUID = 0; + + /** */ + @Order(0) + @Argument(example = "path/to/snapshots", optional = true, description = "Path to snapshot location directory. " + + "If not specified, the default configured snapshot directory will be used") + @Nullable String src; + + /** */ + public @Nullable String src() { + return src; + } + + /** */ + public void src(@Nullable String src) { + this.src = src; + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java new file mode 100644 index 0000000000000..25fe5d959cbb3 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java @@ -0,0 +1,421 @@ +/* + * 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.ignite.internal.management.snapshot; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Future; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.stream.Collectors; +import org.apache.ignite.IgniteException; +import org.apache.ignite.IgniteLogger; +import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.compute.ComputeJobResult; +import org.apache.ignite.internal.NodeStoppingException; +import org.apache.ignite.internal.processors.cache.persistence.filename.SnapshotFileTree; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.IncrementalSnapshotMetadata; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListJobResult; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotListTaskResult; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.SnapshotMetadata; +import org.apache.ignite.internal.processors.rollingupgrade.feature.CoreFeatureRegistry; +import org.apache.ignite.internal.processors.task.GridInternal; +import org.apache.ignite.internal.thread.pool.IgniteThreadPoolExecutor; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.T2; +import org.apache.ignite.internal.visor.VisorJob; +import org.apache.ignite.internal.visor.VisorMultiNodeTask; +import org.apache.ignite.internal.visor.VisorTaskArgument; +import org.apache.ignite.resources.LoggerResource; +import org.jetbrains.annotations.Nullable; + +/** */ +@GridInternal +public class SnapshotListTask extends VisorMultiNodeTask { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** {@inheritDoc} */ + @Override protected VisorJob job(SnapshotListCommandArg arg) { + return new SnapshotListJob(arg, debug); + } + + /** {@inheritDoc} */ + @Override protected Collection jobNodes(VisorTaskArgument arg) { + if (!ignite.context().rollingUpgrade().features().isActive(CoreFeatureRegistry.SNAPSHOT_LIST_FEATURE)) + throw new IgniteException("Won't search for local snapshots. The snapshot list feature isn't activated yet."); + + /** Allows {@link #map0(List, VisorTaskArgument)} to use the entire subgrid. */ + return ignite.cluster().forServers().nodes().stream().map(ClusterNode::id).collect(Collectors.toList()); + } + + /** {@inheritDoc} */ + @Override protected SnapshotListTaskResult reduce0(List nodesJobsResults) throws IgniteException { + String[] cstIds = new String[nodesJobsResults.size()]; + UUID[] nodesIds = new UUID[nodesJobsResults.size()]; + SnapshotListJobResult[] nodesResults = new SnapshotListJobResult[nodesJobsResults.size()]; + + // Sorting the results by consistent id for better reading. + nodesJobsResults = nodesJobsResults.stream() + .sorted((jr0, jr1) -> nodeConsistentId(jr0.getNode()).compareTo(nodeConsistentId(jr1.getNode()))) + .toList(); + + for (int i = 0; i < nodesJobsResults.size(); i++) { + ComputeJobResult nodeJobRes = nodesJobsResults.get(i); + + if (nodeJobRes.getException() != null) { + throw new IgniteException("Failed to execute snapshot list job on node [uuid=" + nodeJobRes.getNode().id() + ']', + nodeJobRes.getException()); + } + + assert nodeJobRes.getData() != null; + + cstIds[i] = nodeConsistentId(nodeJobRes.getNode()); + nodesIds[i] = nodeJobRes.getNode().id(); + nodesResults[i] = nodeJobRes.getData(); + } + + return new SnapshotListTaskResult(cstIds, nodesIds, nodesResults); + } + + /** */ + private String nodeConsistentId(ClusterNode n) { + UUID nodeId = n.id(); + + n = ignite.context().discovery().node(nodeId); + + if (n == null) + n = ignite.context().discovery().historicalNode(nodeId); + + return n == null ? "" : n.consistentId().toString(); + } + + /** + * Walk through a directory. Doesn't lock it or its content. Tries to find files and summarize their size. + * Tolerates and skips concurrent modification errors. + */ + public static long calculateDirectorySize(File path) throws IOException { + AtomicLong size = new AtomicLong(0); + AtomicBoolean entered = new AtomicBoolean(); + + Files.walkFileTree(path.toPath(), new SimpleFileVisitor<>() { + @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { + entered.compareAndSet(false, true); + + // Use attrs instead of Files.size() for efficiency. + if (attrs.isRegularFile()) + size.addAndGet(attrs.size()); + + return FileVisitResult.CONTINUE; + } + + @Override public FileVisitResult visitFileFailed(Path file, IOException err) throws IOException { + if (!entered.get() && file.toFile().equals(path)) { + // Cannot even start snapshot size calculation - can't enter snapshot directory. + throw err; + } + + return FileVisitResult.CONTINUE; + } + }); + + return size.get(); + } + + /** */ + private static class SnapshotListJob extends SnapshotJob { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** */ + @LoggerResource + private IgniteLogger log; + + /** + * @param arg Snapshot list task argument. + * @param debug Flag indicating whether debug information should be printed into node log. + */ + protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { + super(arg, debug); + } + + /** {@inheritDoc} */ + @Override protected SnapshotListJobResult run(SnapshotListCommandArg arg) { + assert !ignite.localNode().isClient(); + + if (ignite.context().isStopping()) + throw new IgniteException("Won't search for local snapshots.", new NodeStoppingException("Node is stopping.")); + + // Read local snapshots. + List> locSnps = findLocalSnapshots(arg.src()); + + if (locSnps.isEmpty()) + return new SnapshotListJobResult(Collections.emptyMap()); + + // Optional external storages descriptions (per snapshot name). + Map extStors = new ConcurrentHashMap<>(locSnps.size(), 1.0f); + // Optional incremental parts descriptions (per snapshot name). + Map incs = new ConcurrentHashMap<>(locSnps.size(), 1.0f); + + // Incremental parts and external storages futures. + List> futs = new ArrayList<>(locSnps.size() * 2); + // Names of snapshots with read failures. + Set failedSnps = ConcurrentHashMap.newKeySet(locSnps.size() / 2); + + IgniteThreadPoolExecutor exec = ignite.context().pools().getSnapshotExecutorService(); + + // Optional descriptions of snapshot external storages and incremental parts. + for (T2 snpPair : locSnps) { + SnapshotFileTree sft = snpPair.get1(); + String snpName = sft.name(); + + // Future for optional external storages. + Future fut = exec.submit(() -> { + if (ignite.context().isStopping()) + throw new IgniteException("Won't search for local snapshots.", new NodeStoppingException("Node is stopping.")); + + if (failedSnps.contains(snpName)) + return; + + try { + SnapshotListJobResult.SnapshotInfo extDesc = externalStorages(sft); + + if (extDesc != null) + extStors.put(snpName, extDesc); + } + catch (Exception e) { + failedSnps.add(snpName); + + log.warning("Failed to read snapshot's extra storages, snapshot ignored [snpName=" + snpName + ']', e); + } + }); + + futs.add(fut); + + // Future for optional incremental parts. + fut = exec.submit(() -> { + if (ignite.context().isStopping()) + throw new IgniteException("Won't search for local snapshots.", new NodeStoppingException("Node is stopping.")); + + if (failedSnps.contains(snpName)) + return; + + try { + SnapshotListJobResult.SnapshotInfo incDesc = incrementals(sft); + + if (incDesc != null) + incs.put(snpName, incDesc); + } + catch (Exception e) { + failedSnps.add(snpName); + + log.warning("Failed to read snapshot's incremental parts, snapshot ignored [snpName=" + snpName + ']', e); + } + }); + + futs.add(fut); + } + + // Wait for the snapshot optional description futures. + for (Future fut : futs) { + try { + fut.get(); + } + catch (Throwable t) { + // All the futures have internal exceptions being logged. No errors expected. + throw new IgniteException("Failed to read local nodes snapshots.", t); + } + } + + // Result snapshot descriptions. + Map resMap = new ConcurrentHashMap<>(locSnps.size() - failedSnps.size(), 1.0f); + + // Reduce results. + for (T2 snpPair : locSnps) { + SnapshotFileTree sft = snpPair.get1(); + String snpName = sft.name(); + + if (failedSnps.contains(snpName)) + continue; + + long size; + + try { + size = calculateDirectorySize(sft.root()); + } + catch (IOException e) { + log.warning("Failed to calculate snapshot's size, snapshot ignored [snpName=" + snpName + ']', e); + + continue; + } + + SnapshotListJobResult.SnapshotInfo snpDesc = new SnapshotListJobResult.SnapshotInfo( + size, + snpPair.get2(), + extStors.get(snpName), + incs.get(snpName) + ); + + resMap.put(snpName, snpDesc); + } + + return new SnapshotListJobResult(resMap); + } + + /** */ + private @Nullable SnapshotListJobResult.SnapshotInfo externalStorages(SnapshotFileTree sft) { + int extStoragesCnt = 0; + long extStoragesSize = 0; + + for (File extraStorage : sft.allStorages().toList()) { + if (sft.nodeStorage().equals(extraStorage)) + continue; + + try { + extStoragesSize += calculateDirectorySize(extraStorage); + } + catch (IOException e) { + log.warning("Failed to calculate snapshot's extra storage size, storage ignored [extraStorage=" + + extraStorage + ']', e); + + continue; + } + + extStoragesCnt++; + } + + return extStoragesCnt == 0 ? null : new SnapshotListJobResult.SnapshotInfo(extStoragesCnt, extStoragesSize); + } + + /** @return Snapshot file tree and creation time from the snapshot metadata. */ + private List> findLocalSnapshots(@Nullable String snpPath) { + // The tree is used only to extract the snapshots root directory. The snapshot name isn't used. + File[] dirsToParse = new SnapshotFileTree(ignite.context(), "snp", snpPath).root().getParentFile().listFiles(); + + if (F.isEmpty(dirsToParse)) + return Collections.emptyList(); + + List>> futs = new ArrayList<>(dirsToParse.length); + + IgniteThreadPoolExecutor exec = ignite.context().pools().getSnapshotExecutorService(); + + for (File snpDir : dirsToParse) { + Future> snpDirFut = exec.submit(() -> { + if (ignite.context().isStopping()) + throw new IgniteException("Won't search for local snapshots.", new NodeStoppingException("Node is stopping.")); + + String snpName = snpDir.getName(); + + // Snapshot tree being used as a path, to read the metas only. + SnapshotFileTree sft = new SnapshotFileTree(ignite.context(), snpName, snpPath); + + List metas = ignite.context().cache().context().snapshotMgr().readSnapshotMetadatas(sft, false); + + if (metas.isEmpty()) + return null; + + // Get the last-created time snapshot metadata. + SnapshotMetadata snpMeta = metas.stream().max(Comparator.comparingLong(SnapshotMetadata::snapshotTime)) + .orElse(new SnapshotMetadata()); + + // Real, meta-based snapshot file tree. Can belong to other cluster, other consistent id. + sft = new SnapshotFileTree( + ignite.configuration(), + ignite.context().pdsFolderResolver().fileTree(), + snpName, + snpPath, + snpMeta.folderName(), + snpMeta.consistentId() + ); + + return new T2<>(sft, snpMeta.snapshotTime()); + }); + + futs.add(snpDirFut); + } + + List> res = new ArrayList<>(dirsToParse.length); + + futs.forEach(f -> { + try { + T2 snpDirRes = f.get(); + + if (snpDirRes != null) + res.add(snpDirRes); + } + catch (Exception e) { + log.warning("Failed to read snapshot, snapshot ignored [path=" + snpPath + ']', e); + } + }); + + return res; + } + + /** @return Number, total size and last creation time of incremental snapshots. */ + private @Nullable SnapshotListJobResult.SnapshotInfo incrementals(SnapshotFileTree sft) { + File[] incs = sft.incrementsRoot().listFiles(); + + if (F.isEmpty(incs)) + return null; + + int cnt = 0; + long size = 0L; + long createTime = 0L; + + IgniteSnapshotManager snpMgr = ignite.context().cache().context().snapshotMgr(); + + int incIdx; + + for (File incDir : incs) { + try { + incIdx = Integer.parseInt(incDir.getName()); + + SnapshotFileTree.IncrementalSnapshotFileTree incTree = sft.incrementalSnapshotFileTree(incIdx); + + IncrementalSnapshotMetadata incMeta = snpMgr.readIncrementalSnapshotMetadata(incTree.meta()); + + size += calculateDirectorySize(incDir); + + createTime = Math.max(createTime, incMeta.snapshotTime()); + + cnt++; + } + catch (Exception e) { + log.warning("Failed to read incremental snapshot, skipped [dir=" + incDir + ']', e); + } + } + + return cnt == 0 ? null : new SnapshotListJobResult.SnapshotInfo(cnt, size, createTime); + } + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsExchangeFuture.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsExchangeFuture.java index 92bfca434e95a..ef1dd7419e7cd 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsExchangeFuture.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsExchangeFuture.java @@ -4954,7 +4954,8 @@ public void onNodeLeft(final ClusterNode node) { t ); - cctx.kernalContext().failure().process(new FailureContext(FailureType.CRITICAL_ERROR, t)); + if (!cctx.kernalContext().isStopping()) + cctx.kernalContext().failure().process(new FailureContext(FailureType.CRITICAL_ERROR, t)); throw t; } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java index f53abc94ba480..d686e6ade95a7 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/filename/SnapshotFileTree.java @@ -332,6 +332,7 @@ public static String snapshotMetaFileName(String consId) { public static File root(SharedFileTree ft, String name, @Nullable String path) { assert name != null : "Snapshot name cannot be empty or null."; + // TODO: use the default snapshots directory for relative {@code path} https://issues.apache.org/jira/browse/IGNITE-29126 return path == null ? new File(ft.snapshotsRoot(), name) : new File(path, name); } @@ -373,6 +374,8 @@ public File meta() { } /** + * TODO: support the extra storages for incremental snapshots https://issues.apache.org/jira/browse/IGNITE-29128 + * * Modifies {@link #extraStorages} for this tree to reflect snapshot options. * In case {@link IgniteConfiguration#getSnapshotPath()} points to absolute directory or {@link #path} for snapshot provided * then all snapshot files must be stored inside one folder. @@ -457,7 +460,6 @@ private NodeFileTree tempFileTree(GridKernalContext ctx) { return res; } - /** {@inheritDoc} */ @Override public String toString() { return S.toString(SnapshotFileTree.class, this); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java index 4d32bc1d38c09..aa5cc959aa904 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java @@ -1830,6 +1830,21 @@ public T readFromFile(File smf) throws IgniteCheckedException, IOException { * local node will be placed on the first place. */ public List readSnapshotMetadatas(SnapshotFileTree sft) { + return readSnapshotMetadatas(sft, true); + } + + /** + * Note, there can be snapshots from other nodes. + * This method will read all metadata. + * Some instances can return {@link SnapshotMetadata#folderName()} and {@link SnapshotMetadata#consistentId()} that differs from local. + * + * @param sft Snapshot file tree. + * @param failIfCantRead If {@code true}, throws exception if cannot read a metadata file. + * @return List of snapshot metadata for the given snapshot name on local node. + * If snapshot has been taken from local node the snapshot metadata for given + * local node will be placed on the first place. + */ + public List readSnapshotMetadatas(SnapshotFileTree sft, boolean failIfCantRead) { if (!(sft.root().exists() && sft.root().isDirectory())) return Collections.emptyList(); @@ -1841,8 +1856,8 @@ public List readSnapshotMetadatas(SnapshotFileTree sft) { Map metasMap = new HashMap<>(); SnapshotMetadata prev = null; - try { - for (File smf : smfs) { + for (File smf : smfs) { + try { SnapshotMetadata curr = readSnapshotMetadata(smf); if (prev != null && !prev.sameSnapshot(curr)) { @@ -1854,9 +1869,12 @@ public List readSnapshotMetadatas(SnapshotFileTree sft) { prev = curr; } - } - catch (IgniteCheckedException | IOException e) { - throw new IgniteException(e); + catch (Exception e) { + if (failIfCantRead) + throw new IgniteException("Fail to read snapshot metadata [meta=" + smf + ']', e); + else + log.warning("Fail to read snapshot metadata, snapshot skipped [meta=" + smf + ']', e); + } } SnapshotMetadata currNodeSmf = metasMap.remove(cctx.localNode().consistentId().toString()); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java index 533f3e1a44d60..9ca188fa4c076 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java @@ -772,7 +772,7 @@ private static final class SnapshotCheckContext { */ @Nullable private volatile List metas; - /** Map of snapshot pathes per consistent id for {@link #metas}. */ + /** Map of snapshot paths per consistent id for {@link #metas}. */ @GridToStringInclude @Nullable private Map locFileTree; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java new file mode 100644 index 0000000000000..03e3131920854 --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java @@ -0,0 +1,133 @@ +/* + * 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.ignite.internal.processors.cache.persistence.snapshot; + +import java.util.Map; +import org.apache.ignite.internal.Order; +import org.apache.ignite.internal.dto.IgniteDataTransferObject; +import org.jetbrains.annotations.Nullable; + +/** Per-node result of the snapshot lists command. Contains information of the snapshots found on current node. */ +public final class SnapshotListJobResult extends IgniteDataTransferObject { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** Local node's snapshot descriptions. */ + @Order(0) + Map snapshots; + + /** Default constructor for serialization purposes. */ + public SnapshotListJobResult() { + // No-op. + } + + /** */ + public SnapshotListJobResult(Map snapshots) { + this.snapshots = snapshots; + } + + /** @return The snapshots descriptions. */ + public Map snapshots() { + return snapshots; + } + + /** Holds combined snapshot data information: size, creation time, number of incremental parts or external storages. */ + public static class SnapshotInfo extends IgniteDataTransferObject { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** Total size of a snapshot. Or size of its external storages or incremental parts. */ + @Order(0) + long size; + + /** Creation date of snapshot or of its incremental parts. Is {@code null} for snapshot external storages description. */ + @Order(1) + @Nullable Long date; + + /** Snapshot external storages description if exists. Is always {@code null} for not the main snapshot description. */ + @Order(2) + @Nullable SnapshotInfo extStors; + + /** Snapshot incremental parts description if exists. Is always {@code null} for not the main snapshot description. */ + @Order(3) + @Nullable SnapshotInfo incs; + + /** Number of snapshot external storages or incremental parts. Is {@code null} for the main snapshot description. */ + @Order(4) + @Nullable Integer cnt; + + /** Empty constructor for serialization purposes. */ + public SnapshotInfo() { + // No-op. + } + + /** Creates snapshot main description. */ + public SnapshotInfo( + long size, + long date, + @Nullable SnapshotInfo extStors, + @Nullable SnapshotInfo incs + ) { + this.size = size + (extStors == null ? 0L : extStors.size()); + this.date = date; + this.extStors = extStors; + this.incs = incs; + } + + /** Creates external storages description. */ + public SnapshotInfo(int cnt, long size) { + this.cnt = cnt; + this.size = size; + } + + /** Creates incremental parts description. */ + public SnapshotInfo(int cnt, long size, long date) { + this.cnt = cnt; + this.size = size; + this.date = date; + } + + /** @return Total size of a snapshot. Or size of its external storages or incremental parts. */ + public long size() { + return size; + } + + /** + * @return Creation date of a snapshot or of its incremental parts. {@code Null} for snapshot + * external storages description. + */ + public @Nullable Long date() { + return date; + } + + /** @return Snapshot external storages description if exists. Is always {@code null} for not the main snapshot description. */ + public @Nullable SnapshotInfo externalStorages() { + return extStors; + } + + /** @return Snapshot incremental parts description if exists. Is always {@code null} for not the main snapshot description. */ + public @Nullable SnapshotInfo incrementals() { + return incs; + } + + /** @return Number of snapshot external storages or incremental parts. Or {@code null} for the main snapshot data.*/ + public @Nullable Integer number() { + return cnt; + } + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListTaskResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListTaskResult.java new file mode 100644 index 0000000000000..f00ba2543aacc --- /dev/null +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListTaskResult.java @@ -0,0 +1,68 @@ +/* + * 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.ignite.internal.processors.cache.persistence.snapshot; + +import java.util.UUID; +import org.apache.ignite.internal.Order; +import org.apache.ignite.internal.dto.IgniteDataTransferObject; +import org.apache.ignite.internal.management.snapshot.SnapshotListTask; + +/** Accumulated result of {@link SnapshotListTask}. */ +public final class SnapshotListTaskResult extends IgniteDataTransferObject { + /** Serial version uid. */ + private static final long serialVersionUID = 0L; + + /** Nodes consistent ids. */ + @Order(0) + String[] cstIds; + + /** Nodes UUIDs. */ + @Order(1) + UUID[] nodesIds; + + /** Results. */ + @Order(2) + SnapshotListJobResult[] nodesSnapshots; + + /** Default constructor for serialization purposes. */ + public SnapshotListTaskResult() { + // No-op. + } + + /** */ + public SnapshotListTaskResult(String[] consistentIds, UUID[] nodesIds, SnapshotListJobResult[] nodesSnapshots) { + this.cstIds = consistentIds; + this.nodesIds = nodesIds; + this.nodesSnapshots = nodesSnapshots; + } + + /** @return Nodes consistent ids. */ + public String[] consistentIds() { + return cstIds; + } + + /** @return Nodes UUIDs. */ + public UUID[] nodesIds() { + return nodesIds; + } + + /** @return The results. */ + public SnapshotListJobResult[] nodesSnapshots() { + return nodesSnapshots; + } +} diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMetadataVerificationTask.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMetadataVerificationTask.java index 643838ac3c445..4a8fea2b43b73 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMetadataVerificationTask.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotMetadataVerificationTask.java @@ -35,7 +35,10 @@ import org.apache.ignite.internal.processors.cache.persistence.wal.reader.IgniteWalIteratorFactory; import org.apache.ignite.internal.util.typedef.F; -/** Snapshot task to verify snapshot metadata on the baseline nodes for given snapshot name. */ +/** + * Snapshot task to verify snapshot metadata on the baseline nodes for given snapshot name. + * TODO : Revise in https://issues.apache.org/jira/browse/IGNITE-29062 + */ public class SnapshotMetadataVerificationTask implements Supplier> { /** */ private final IgniteEx ignite; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/metastorage/persistence/DistributedMetaStorageImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/metastorage/persistence/DistributedMetaStorageImpl.java index 575300772a905..bf7f6f34c2566 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/metastorage/persistence/DistributedMetaStorageImpl.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/metastorage/persistence/DistributedMetaStorageImpl.java @@ -103,7 +103,7 @@ *

*

Whole updates history until some point in the past is stored along with the data, so when an outdated node * connects to the cluster it will receive all the missing data and apply it locally. Listeners will also be invoked - * after such updates. If there's not enough history stored or joining node is clear then it'll receive shapshot of + * after such updates. If there's not enough history stored or joining node is clear then it'll receive snapshot of * distributed metastorage (usually called {@code fullData} in code) so there won't be inconsistencies. *

* diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/CoreFeatureRegistry.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/CoreFeatureRegistry.java index 92b7eeb1a8c5b..b6d79ee0f6d84 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/CoreFeatureRegistry.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rollingupgrade/feature/CoreFeatureRegistry.java @@ -93,4 +93,7 @@ public class CoreFeatureRegistry { /** */ public static final IgniteFeature ROLLING_UPGRADE_FEATURE = new IgniteCoreFeature(0); + + /** */ + public static final IgniteFeature SNAPSHOT_LIST_FEATURE = new IgniteCoreFeature(1); } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListRollingUpgradeTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListRollingUpgradeTest.java new file mode 100644 index 0000000000000..e335c8989711a --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListRollingUpgradeTest.java @@ -0,0 +1,245 @@ +/* + * 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.ignite.internal.processors.cache.persistence.snapshot; + +import java.io.File; +import java.nio.file.DirectoryStream; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.UUID; +import org.apache.ignite.IgniteDataStreamer; +import org.apache.ignite.IgniteException; +import org.apache.ignite.cache.CacheAtomicityMode; +import org.apache.ignite.cache.CacheMode; +import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction; +import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.configuration.CacheConfiguration; +import org.apache.ignite.configuration.DataRegionConfiguration; +import org.apache.ignite.configuration.DataStorageConfiguration; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.IgniteInternalFuture; +import org.apache.ignite.internal.management.snapshot.SnapshotListCommandArg; +import org.apache.ignite.internal.management.snapshot.SnapshotListTask; +import org.apache.ignite.internal.processors.rollingupgrade.AbstractRollingUpgradeTest; +import org.apache.ignite.internal.util.distributed.SingleNodeMessage; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.internal.visor.VisorTaskArgument; +import org.apache.ignite.testframework.GridTestUtils; +import org.junit.Test; + +import static java.nio.file.Files.newDirectoryStream; +import static org.apache.ignite.internal.TestRecordingCommunicationSpi.spi; +import static org.apache.ignite.internal.util.distributed.DistributedProcess.DistributedProcessType.RU_PREPARE_VERSION_FINALIZATION; +import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause; +import static org.apache.ignite.testframework.GridTestUtils.waitForCondition; + +/** */ +public class IgniteClusterSnapshotListRollingUpgradeTest extends AbstractRollingUpgradeTest { + /** */ + private static final int ALL_GRIDS = 4; + + /** */ + private static final int CLIENTS = 1; + + /** */ + private static final String SNP_NAME = "testSnapshot"; + + /** {@inheritDoc} */ + @Override protected void afterTest() throws Exception { + super.afterTest(); + + cleanPersistenceDir(); + + // Clean all: also separated snapshot working directories. + try (DirectoryStream files = newDirectoryStream(Paths.get(U.defaultWorkDirectory()))) { + for (Path path : files) + U.delete(path); + } + } + + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName, String ver) throws Exception { + IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName, ver); + + cfg.setDataStorageConfiguration( + new DataStorageConfiguration() + .setDefaultDataRegionConfiguration( + new DataRegionConfiguration() + .setPersistenceEnabled(true) + .setMaxSize(DataStorageConfiguration.DFLT_DATA_REGION_INITIAL_SIZE) + ) + ); + + cfg.setWorkDirectory(new File(U.defaultWorkDirectory(), igniteInstanceName).getAbsolutePath()); + + return cfg; + } + + /** */ + @Test + public void testParallelRollingUpgradeInProgress() throws Exception { + for (int i = 0; i < ALL_GRIDS; i++) + startGrid(i, "2.19.4", i >= ALL_GRIDS - CLIENTS); + + grid(0).cluster().active(true); + + int testNodeIx = ALL_GRIDS - CLIENTS - 1; + + createCacheAndSnapshot(testNodeIx); + + ru(grid(testNodeIx)).enableVersionUpgrade(); + + for (int i = 0; i < ALL_GRIDS; i++) { + assertTrue(ru(grid(i)).isVersionUpgradeEnabled()); + + upgradeNodeVersion(i, "2.19.4", "2.19.5"); + } + + spi(grid(testNodeIx)).blockMessages((node, msg) -> msg instanceof SingleNodeMessage snm && + snm.type() == RU_PREPARE_VERSION_FINALIZATION.ordinal()); + + IgniteInternalFuture finalizeFut = GridTestUtils.runAsync(() -> ru(testNodeIx).finalizeClusterVersion()); + + assertTrue(spi(grid(testNodeIx)).waitForBlocked(1, getTestTimeout())); + + ensureSnapshotListFailed(); + + spi(grid(testNodeIx)).stopBlock(); + + assertFalse(spi(grid(testNodeIx)).hasBlockedMessages()); + + finalizeFut.get(getTestTimeout()); + + for (int i = 0; i < ALL_GRIDS; i++) { + int i0 = i; + + assertTrue(waitForCondition(() -> !ru(grid(i0)).isVersionUpgradeEnabled(), getTestTimeout())); + } + + ensureSnapshotListSucceeds(); + } + + /** */ + @Test + public void testSnapshotListFeature() throws Exception { + for (int i = 0; i < ALL_GRIDS; i++) + startGrid(i, "2.19.4", i >= ALL_GRIDS - CLIENTS); + + grid(0).cluster().active(true); + + createCacheAndSnapshot(1); + + ensureSnapshotListFailed(); + + ru(grid(0)).enableVersionUpgrade(); + + for (int i = 0; i < ALL_GRIDS; i++) { + assertTrue(ru(grid(i)).isVersionUpgradeEnabled()); + + upgradeNodeVersion(i, "2.19.4", "2.19.5"); + + ensureSnapshotListFailed(); + } + + ru(grid(1)).finalizeClusterVersion(); + + for (int i = 0; i < ALL_GRIDS; i++) { + int i0 = i; + + assertTrue(waitForCondition(() -> !ru(grid(i0)).isVersionUpgradeEnabled(), getTestTimeout())); + } + + ensureSnapshotListSucceeds(); + } + + /** */ + private void ensureSnapshotListSucceeds() throws Exception { + for (int g = 0; g < ALL_GRIDS; g++) { + SnapshotListTaskResult listOpRes = listSnapshots(grid(g)); + + assertEquals(3, listOpRes.nodesSnapshots().length); + + // Tests client exclusion. + if (CLIENTS > 0) { + UUID cliId = grid(ALL_GRIDS - CLIENTS).cluster().localNode().id(); + + assertFalse(new HashSet<>(Arrays.asList(listOpRes.nodesIds())).contains(cliId)); + } + + for (SnapshotListJobResult nodeRes : listOpRes.nodesSnapshots()) + assertEquals(1, nodeRes.snapshots().size()); + } + } + + /** */ + private void createCacheAndSnapshot(int gridIdx) { + int partsCnt = 4; + int keysCnt = partsCnt * 5; + + grid(gridIdx).createCache(new CacheConfiguration<>(DEFAULT_CACHE_NAME) + .setCacheMode(CacheMode.REPLICATED) + .setAffinity(new RendezvousAffinityFunction().setPartitions(partsCnt)) + .setAtomicityMode(CacheAtomicityMode.ATOMIC)); + + try (IgniteDataStreamer ds = grid(gridIdx).dataStreamer(DEFAULT_CACHE_NAME)) { + for (int i = 0; i < keysCnt; i++) + ds.addData(i, i); + } + + createSnapshot(gridIdx); + } + + /** */ + private void createSnapshot(int gridIdx) { + snp(gridIdx).createSnapshot(SNP_NAME).get(getTestTimeout()); + } + + /** */ + private void ensureSnapshotListFailed() { + String err = "The snapshot list feature isn't activated yet"; + + for (int i = 0; i < ALL_GRIDS; i++) { + int i0 = i; + + assertThrowsAnyCause( + null, + () -> listSnapshots(grid(i0)), + IgniteException.class, + err + ); + } + } + + /** */ + private IgniteSnapshotManager snp(int gridIdx) { + return grid(gridIdx).context().cache().context().snapshotMgr(); + } + + /** */ + private static SnapshotListTaskResult listSnapshots(IgniteEx grid) throws Exception { + SnapshotListCommandArg arg = new SnapshotListCommandArg(); + + Collection nodes = grid.cluster().forServers().nodes().stream().map(ClusterNode::id).toList(); + + return grid.compute().execute(SnapshotListTask.class, new VisorTaskArgument<>(nodes, arg, false)).result(); + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java new file mode 100644 index 0000000000000..736cee436df3d --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java @@ -0,0 +1,844 @@ +/* + * 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.ignite.internal.processors.cache.persistence.snapshot; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.io.Serializable; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.attribute.PosixFilePermission; +import java.nio.file.attribute.PosixFilePermissions; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Stream; +import org.apache.ignite.IgniteDataStreamer; +import org.apache.ignite.IgniteException; +import org.apache.ignite.cache.affinity.rendezvous.RendezvousAffinityFunction; +import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.configuration.CacheConfiguration; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.IgniteInternalFuture; +import org.apache.ignite.internal.NodeStoppingException; +import org.apache.ignite.internal.management.snapshot.SnapshotListCommandArg; +import org.apache.ignite.internal.management.snapshot.SnapshotListTask; +import org.apache.ignite.internal.processors.cache.persistence.filename.SnapshotFileTree; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.internal.visor.VisorTaskArgument; +import org.apache.ignite.lang.IgniteFuture; +import org.apache.ignite.plugin.AbstractTestPluginProvider; +import org.apache.ignite.plugin.PluginConfiguration; +import org.apache.ignite.plugin.PluginContext; +import org.apache.ignite.plugin.PluginProvider; +import org.jetbrains.annotations.Nullable; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runners.Parameterized; + +import static java.nio.file.Files.newDirectoryStream; +import static org.apache.ignite.configuration.IgniteConfiguration.DFLT_SNAPSHOT_THREAD_POOL_SIZE; +import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause; +import static org.apache.ignite.testframework.GridTestUtils.cartesianProduct; +import static org.apache.ignite.testframework.GridTestUtils.runAsync; +import static org.junit.Assume.assumeFalse; +import static org.junit.Assume.assumeTrue; + +/** Cluster-wide snapshot list procedure tests. */ +public class IgniteClusterSnapshotListTest extends AbstractSnapshotSelfTest { + /** Number of cache keys to pre-create at node start. */ + private static final int CACHE_KEYS_RANGE = 10; + + /** Number of partitions within a snapshot cache group. */ + private static final int CACHE_PARTITIONS_COUNT = 4; + + /** */ + private static boolean posixPermissions; + + /** Size of the snapshot utility thread pool. */ + @Parameterized.Parameter(2) + public int snpThrdPoolSz; + + /** */ + private PluginProvider pluginProvider; + + /** Parameters. */ + @Parameterized.Parameters(name = "encryption={0}, onlyPrimary={1}, snpThrdPoolSz={2}") + public static Collection params() { + return cartesianProduct( + encryptionParameters(), // Encryption + F.asList(false, true), // Only primary + F.asList(DFLT_SNAPSHOT_THREAD_POOL_SIZE, 1) // Snapshots thread pool size + ); + } + + /** {@inheritDoc} */ + @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { + IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName) + .setWorkDirectory(new File(U.defaultWorkDirectory(), igniteInstanceName).getAbsolutePath()); + + if (pluginProvider != null) + cfg.setPluginProviders(pluginProvider); + + cfg.setSnapshotThreadPoolSize(snpThrdPoolSz); + + return cfg; + } + + /** {@inheritDoc} */ + @Override protected void cleanPersistenceDir() throws Exception { + super.cleanPersistenceDir(); + + // Also cleans separated snapshot working directories and custom snapshot paths. + try (DirectoryStream files = newDirectoryStream(Paths.get(U.defaultWorkDirectory()))) { + for (Path path : files) + U.delete(path); + } + } + + /** {@inheritDoc} */ + @Override protected void beforeTestsStarted() throws Exception { + super.beforeTestsStarted(); + + File workDir = new File(U.defaultWorkDirectory()); + + assertTrue(workDir.exists()); + + try { + Files.getPosixFilePermissions(workDir.toPath()); + + posixPermissions = true; + } + catch (UnsupportedOperationException ignored) { + // No-op. + } + } + + /** {@inheritDoc} */ + @Override protected CacheConfiguration txCacheConfig(CacheConfiguration ccfg) { + // Speeds up the tests. + return super.txCacheConfig(ccfg).setAffinity(new RendezvousAffinityFunction(false, CACHE_PARTITIONS_COUNT)); + } + + /** */ + @Test + public void testConcurrentCreation() throws Exception { + // The test uses the thread blocking and conditional waitings. Won't proceed with 1 thread. + assumeTrue(snpThrdPoolSz > 1); + + int grids = 3; + int testNodeIdx = 1; + int testNodeOrder = testNodeIdx + 1; + + CountDownLatch beginSnpCreation = new CountDownLatch(grids); + CountDownLatch proceedSnpCreation = new CountDownLatch(1); + + // Delays snapshot creation after its metadata is written. + pluginProvider = new AbstractTestPluginProvider() { + @Override public String name() { + return "TestSnpMgrProvider"; + } + + @Override public T createComponent(PluginContext ctx, Class cls) { + if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { + return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { + @Override public void storeSnapshotMeta(M meta, File smf) { + super.storeSnapshotMeta(meta, smf); + + beginSnpCreation.countDown(); + + if (((IgniteEx)ctx.grid()).localNode().order() == testNodeOrder) { + try { + assertTrue(proceedSnpCreation.await(getTestTimeout(), TimeUnit.MILLISECONDS)); + } + catch (InterruptedException e) { + throw new IllegalStateException(e); + } + } + } + }; + } + + return super.createComponent(ctx, cls); + } + }; + + startGridsWithCache(grids, txCacheConfig(defaultCacheConfiguration()), CACHE_KEYS_RANGE); + + IgniteFuture createSnpFut = snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary); + + assertTrue(beginSnpCreation.await(getTestTimeout(), TimeUnit.MILLISECONDS)); + + SnapshotListTaskResult lstOpRes = listSnapshots(grid(2)); + + int snpsCnt = Stream.of(lstOpRes.nodesSnapshots()).mapToInt(jr -> jr.snapshots().size()).sum(); + + assertEquals(grids, snpsCnt); + + SnapshotFileTree testSft = new SnapshotFileTree(grid(testNodeIdx).context(), SNAPSHOT_NAME, null); + + assertTrue(SnapshotListTask.calculateDirectorySize(testSft.root()) > 0L); + + proceedSnpCreation.countDown(); + + createSnpFut.get(getTestTimeout()); + } + + /** */ + @Test + public void testCompletelyDeletedAfterMetaRead() throws Exception { + doTestDeletionAfterMetaRead(true); + } + + /** */ + @Test + public void testPartlyDeletedAfterMetaRead() throws Exception { + doTestDeletionAfterMetaRead(false); + } + + /** */ + private void doTestDeletionAfterMetaRead(boolean completeDeletion) throws Exception { + int grids = 3; + int testGridIdx = 1; + + CountDownLatch metaReadBeginLatch = new CountDownLatch(grids); + CountDownLatch metaReadProceedLatch = new CountDownLatch(1); + + pluginProvider = new AbstractTestPluginProvider() { + @Override public String name() { + return "TestSnpMgrProvider"; + } + + @Override public T createComponent(PluginContext ctx, Class cls) { + if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { + return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { + @Override public List readSnapshotMetadatas(SnapshotFileTree sft, boolean failIfCantRead) { + metaReadBeginLatch.countDown(); + + if (cctx.localNode().order() == testGridIdx + 1) { + try { + assertTrue(metaReadProceedLatch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); + } + catch (InterruptedException e) { + throw new IllegalStateException(e); + } + } + + return super.readSnapshotMetadatas(sft, failIfCantRead); + } + }; + } + + return super.createComponent(ctx, cls); + } + }; + + startGridsWithSnapshot(grids, CACHE_KEYS_RANGE, true, false); + + SnapshotFileTree testSnpFt = new SnapshotFileTree(grid(testGridIdx).context(), SNAPSHOT_NAME, null); + + long testSnpSz = SnapshotListTask.calculateDirectorySize(testSnpFt.root()); + + assertTrue(testSnpSz > 0); + + IgniteInternalFuture lstOpFut = runAsync(() -> listSnapshots(grid(0))); + + assertTrue(metaReadBeginLatch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); + + if (completeDeletion) + assertTrue(testSnpFt.root().exists() && U.delete(testSnpFt.root()) && !testSnpFt.root().exists()); + else + assertTrue(testSnpFt.nodeStorage().exists() && U.delete(testSnpFt.nodeStorage()) && !testSnpFt.nodeStorage().exists()); + + metaReadProceedLatch.countDown(); + + SnapshotListTaskResult lstOpRes = lstOpFut.get(); + + if (completeDeletion) { + int snpsCnt = Stream.of(lstOpRes.nodesSnapshots()).mapToInt(jr -> jr.snapshots().size()).sum(); + + assertEquals(grids - 1, snpsCnt); + } + else { + int snpsCnt = 0; + boolean victimNodeFound = false; + + for (int i = 0; i < lstOpRes.nodesIds().length; i++) { + UUID nid = lstOpRes.nodesIds()[i]; + + Map nodeSnps = lstOpRes.nodesSnapshots()[i].snapshots(); + + snpsCnt += nodeSnps.size(); + + if (nid.equals(grid(testGridIdx).localNode().id())) { + victimNodeFound = true; + + assertEquals(1, nodeSnps.size()); + + long curSnpSz = SnapshotListTask.calculateDirectorySize(testSnpFt.root()); + + assertTrue(curSnpSz < testSnpSz); + } + } + + assertTrue(victimNodeFound); + assertEquals(grids, snpsCnt); + } + } + + /** + * Ensures that a file can be read without locking or other issues while being concurrently written on current OS and + * file system. This behavior is important for the case when snapshots are being read while creation. + */ + @Test + public void testReadWhileCreating() throws Exception { + // Doesn't matter here. Fastens the tests. + assumeFalse(encryption || onlyPrimary); + + assertTrue(new File(U.defaultWorkDirectory()).exists()); + + File testF = new File(U.defaultWorkDirectory(), "test.out"); + + assumeFalse(testF.exists()); + + CountDownLatch proceedWriteLatch = new CountDownLatch(1); + AtomicBoolean readFlag = new AtomicBoolean(); + + try { + try (RandomAccessFile raf = new RandomAccessFile(testF, "rw");) { + raf.write(1); + raf.write(2); + + Thread t = new Thread(() -> { + try (RandomAccessFile raf0 = new RandomAccessFile(testF, "r")) { + assertEquals(1, raf0.read()); + assertEquals(2, raf0.read()); + + readFlag.set(true); + + proceedWriteLatch.countDown(); + } + catch (Throwable e) { + log.error("Failed to concurrently read the test file.", e); + } + }); + + t.setDaemon(true); + t.start(); + + assertTrue(proceedWriteLatch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); + + raf.write(3); + } + + try (RandomAccessFile raf = new RandomAccessFile(testF, "r");) { + assertEquals(1, raf.read()); + assertEquals(2, raf.read()); + assertEquals(3, raf.read()); + } + } + finally { + assertTrue(!testF.exists() || testF.delete()); + } + + assertTrue(readFlag.get()); + } + + /** */ + @Test + public void testMissingSnapshotPath() throws Exception { + doTestWrongSnapshotPath(true); + } + + /** */ + @Test + public void testEmptySnapshotPath() throws Exception { + doTestWrongSnapshotPath(false); + } + + /** */ + private void doTestWrongSnapshotPath(boolean missing) throws Exception { + // Doesn't matter here, fastens the tests. + assumeFalse(encryption || onlyPrimary || snpThrdPoolSz < 2); + + doTestSnapshotPath(null); + + File path = new File(U.defaultWorkDirectory(), "not_snapshots"); + + if (!missing) + assertTrue(new File(path, SNAPSHOT_NAME).mkdirs()); + + SnapshotListJobResult[] lstOpRes = listSnapshots(grid(0), path.getAbsolutePath()).nodesSnapshots(); + + int cnt = Stream.of(lstOpRes).mapToInt(nodeRes -> nodeRes.snapshots().size()).sum(); + + assertEquals(0, cnt); + } + + /** */ + @Test + public void testDefaultSnapshotPath() throws Exception { + doTestSnapshotPath(null); + } + + /** */ + @Test + @Ignore("https://issues.apache.org/jira/browse/IGNITE-29126") + public void testRelativeSnapshotPath() throws Exception { + doTestSnapshotPath("ex_snapshots"); + } + + /** */ + @Test + public void testAbsoluteSnapshotPath() throws Exception { + doTestSnapshotPath(new File(U.defaultWorkDirectory(), "ex_snapshots").getAbsolutePath()); + } + + /** */ + private void doTestSnapshotPath(@Nullable String path) throws Exception { + int grids = 3; + + startGridsWithCache(grids, txCacheConfig(defaultCacheConfiguration()), CACHE_KEYS_RANGE); + + snp(grid(0)).createSnapshot(SNAPSHOT_NAME, path, false, onlyPrimary).get(getTestTimeout()); + + SnapshotListJobResult[] lstOpRes = listSnapshots(grid(0), path).nodesSnapshots(); + + int cnt = Stream.of(lstOpRes).mapToInt(nodeRes -> nodeRes.snapshots().size()).sum(); + + assertEquals(grids, cnt); + } + + /** */ + @Test + public void testMissingMeta() throws Exception { + doTestWithWrongMeta(false); + } + + /** */ + @Test + public void testCorruptedMeta() throws Exception { + doTestWithWrongMeta(true); + } + + /** + * Tests snapshot lists when one snapshot metadata cannot be read. + * + * @param corruptFile If {@code true}, corrupts metadata. Otherwise, deletes metadata. + */ + private void doTestWithWrongMeta(boolean corruptFile) throws Exception { + // Speeds up the tests. + assumeFalse(encryption); + + int grids = 3; + int testGridIdx = 1; + + startGridsWithSnapshot(grids, CACHE_KEYS_RANGE, true, false); + + SnapshotFileTree testSnpFt = new SnapshotFileTree(grid(testGridIdx).context(), SNAPSHOT_NAME, null); + + assertTrue(testSnpFt.meta().exists()); + + if (corruptFile) { + try (RandomAccessFile raf = new RandomAccessFile(testSnpFt.meta(), "rw")) { + raf.write(UUID.randomUUID().toString().getBytes()); + } + } + else + assertTrue(testSnpFt.meta().delete() && !testSnpFt.meta().exists()); + + SnapshotListTaskResult res = listSnapshots(grid(0)); + + int foundSnpsCnt = 0; + boolean victimNodeFound = false; + + for (int i = 0; i < res.nodesIds().length; i++) { + UUID nid = res.nodesIds()[i]; + + foundSnpsCnt += res.nodesSnapshots()[i].snapshots().size(); + + if (nid.equals(grid(testGridIdx).localNode().id())) { + victimNodeFound = true; + + assertTrue(res.nodesSnapshots()[i].snapshots().isEmpty()); + } + } + + assertTrue(victimNodeFound); + assertEquals(grids - 1, foundSnpsCnt); + } + + /** */ + @Test + public void testMissingIncrementalMeta() throws Exception { + doTestWithWrongIncrementalMeta(false); + } + + /** */ + @Test + public void testCorruptedIncrementalMeta() throws Exception { + doTestWithWrongIncrementalMeta(true); + } + + /** + * Tests snapshot list when incremental snapshot metadata cannot be read. + * The main snapshot should still be listed, but without incremental info on the affected node. + * + * @param corruptFile If {@code true}, corrupts metadata. Otherwise, deletes metadata. + */ + private void doTestWithWrongIncrementalMeta(boolean corruptFile) throws Exception { + // Incremental snapshots do not support the only-primary mode or encryption. + assumeFalse(onlyPrimary || encryption); + + int grids = 3; + int testGridIdx = 1; + int incsCnt = 3; + + IgniteEx ig = startGridsWithCache(grids, txCacheConfig(defaultCacheConfiguration()), CACHE_KEYS_RANGE); + + snp(ig).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(getTestTimeout()); + + for (int i = 0; i < incsCnt; ++i) { + try (IgniteDataStreamer ds = grid(0).dataStreamer(DEFAULT_CACHE_NAME)) { + for (int kv = (i + 1) * CACHE_KEYS_RANGE; kv < (i + 1) * CACHE_KEYS_RANGE * 2; ++kv) + ds.addData(kv, kv); + } + + snp(ig).createSnapshot(SNAPSHOT_NAME, null, true, onlyPrimary).get(getTestTimeout()); + } + + SnapshotFileTree testSnpFt = new SnapshotFileTree(grid(testGridIdx).context(), SNAPSHOT_NAME, null); + SnapshotFileTree.IncrementalSnapshotFileTree incFt = testSnpFt.incrementalSnapshotFileTree(1); + File incMeta = incFt.meta(); + + assertTrue(incMeta.exists()); + + if (corruptFile) { + try (RandomAccessFile raf = new RandomAccessFile(incMeta, "rw")) { + raf.write(UUID.randomUUID().toString().getBytes()); + } + } + else + assertTrue(incMeta.delete() && !incMeta.exists()); + + SnapshotListTaskResult res = listSnapshots(grid(0)); + + for (int i = 0; i < res.nodesIds().length; i++) { + UUID nid = res.nodesIds()[i]; + + Map snps = res.nodesSnapshots()[i].snapshots(); + + assertEquals(1, snps.size()); + + SnapshotListJobResult.SnapshotInfo info = snps.get(SNAPSHOT_NAME); + + assertNotNull(info); + assertNotNull(info.incrementals()); + + if (nid.equals(grid(testGridIdx).localNode().id())) + assertEquals(incsCnt - 1, info.incrementals().number().intValue()); + else + assertEquals(incsCnt, info.incrementals().number().intValue()); + } + } + + /** + * Test snapshot list operation when a node can't read some snapshot part due to insufficient permissions. + * I.e. a test node is able to read snapshot meta but can't read some the snapshot's data. + */ + @Test + public void testDeniedPermissions() throws Exception { + assumeTrue(posixPermissions); + // We rely on sizes here. Better to avoid empty data nodes not to become flaky. + assumeFalse(onlyPrimary); + + int grids = 3; + int testGridIdx = 1; + + // Permissions to restore. + Map> oldPerms = new ConcurrentHashMap<>(); + // The 'change permissions' flag. + AtomicBoolean changePermissions = new AtomicBoolean(true); + + // Deny reading on a couple of incremental snapshot metadata files on one node. + pluginProvider = new AbstractTestPluginProvider() { + @Override public String name() { + return "TestSnpMgrProvider"; + } + + @Override public T createComponent(PluginContext ctx, Class cls) { + if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { + return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { + @Override public List readSnapshotMetadatas(SnapshotFileTree sft, + boolean failIfCantRead) { + if (changePermissions.get() && ctx.localNode().order() == testGridIdx + 1) { + File victimDir = sft.nodeStorage(); + + assertTrue(victimDir.exists()); + assertTrue(victimDir.isDirectory()); + + // Ensure that blocked snapshot part has some size. We use sizes to compare later. + try { + assertTrue(SnapshotListTask.calculateDirectorySize(victimDir) > 0L); + } + catch (IOException e) { + throw new IllegalStateException(e); + } + + Path victimDirPath = victimDir.toPath(); + + try { + Set perms = Files.getPosixFilePermissions(victimDirPath); + + assertFalse(perms.isEmpty()); + + // Deny reading. + Files.setPosixFilePermissions(victimDirPath, PosixFilePermissions.fromString("---------")); + + // Save actual permissions to restore. + oldPerms.put(victimDirPath, perms); + } + catch (Exception e) { + throw new IgniteException("Unable to set the test posix permissions.", e); + } + } + + return super.readSnapshotMetadatas(sft, failIfCantRead); + } + }; + } + + return super.createComponent(ctx, cls); + } + }; + + startGridsWithSnapshot(grids, CACHE_KEYS_RANGE, true, true); + + UUID testNodeId = grid(testGridIdx).localNode().id(); + + // Snapshot size with restricted permissions. + long testSize0 = 0L; + // Snapshot size with normal permissions. + long testSize1 = 0L; + + SnapshotListTaskResult snpLstOpRes; + + try { + // First run. + snpLstOpRes = listSnapshots(grid(0)); + + assertFalse(oldPerms.isEmpty()); + + for (int i = 0; i < snpLstOpRes.nodesIds().length; i++) { + UUID nodeId = snpLstOpRes.nodesIds()[i]; + + // Store size of the partly read snapshot. + if (nodeId.equals(testNodeId)) { + Map snps = snpLstOpRes.nodesSnapshots()[i].snapshots(); + + assertEquals(1, snps.size()); + + testSize0 = snps.get(SNAPSHOT_NAME).size(); + } + } + + // Ensure that we've found and read snapshot on the test node. + assertTrue(testSize0 > 0L); + } + finally { + // Restore the permissions in any case. + oldPerms.forEach((path, perms) -> { + try { + Files.setPosixFilePermissions(path, perms); + } + catch (IOException e) { + throw new IllegalStateException(e); + } + }); + } + + // Relaunch the operation. + changePermissions.set(false); + + snpLstOpRes = listSnapshots(grid(0)); + + for (int i = 0; i < snpLstOpRes.nodesIds().length; i++) { + UUID nodeId = snpLstOpRes.nodesIds()[i]; + + // Store size of the partly read snapshot. + if (nodeId.equals(testNodeId)) { + Map snps = snpLstOpRes.nodesSnapshots()[i].snapshots(); + + assertEquals(1, snps.size()); + + testSize1 = snps.get(SNAPSHOT_NAME).size(); + } + } + + // Ensure that the calculated anew size is bigger than in the previous run. + assertTrue(testSize1 > 0L); + assertTrue(testSize1 > testSize0); + } + + /** */ + @Test + public void testSnapshotListsDates() throws Exception { + // Incremental snapshots do not support the only-primary mode or encryption. + assumeFalse(onlyPrimary || encryption); + + int grids = 3; + + IgniteEx ig = startGridsWithCache(grids, txCacheConfig(defaultCacheConfiguration()), CACHE_KEYS_RANGE); + + long time0 = U.currentTimeMillis(); + + // Wait for a while, spend some time. + U.sleep(300L); + + snp(ig).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(getTestTimeout()); + + SnapshotListTaskResult res = listSnapshots(grid(0)); + + for (int i = 0; i < res.nodesIds().length; i++) { + Map snps = res.nodesSnapshots()[i].snapshots(); + + assertEquals(1, snps.size()); + + SnapshotListJobResult.SnapshotInfo info = snps.get(SNAPSHOT_NAME); + + assertTrue(info.date() > time0); + } + + // Wait for a while, spend some time. + U.sleep(300L); + + long time1 = U.currentTimeMillis(); + + try (IgniteDataStreamer ds = ig.dataStreamer(DEFAULT_CACHE_NAME)) { + for (int kv = CACHE_KEYS_RANGE; kv < CACHE_KEYS_RANGE * 2; kv++) + ds.addData(kv, kv); + } + + snp(ig).createSnapshot(SNAPSHOT_NAME, null, true, onlyPrimary).get(getTestTimeout()); + + SnapshotListTaskResult res2 = listSnapshots(grid(0)); + + for (int i = 0; i < res2.nodesIds().length; i++) { + Map snps = res2.nodesSnapshots()[i].snapshots(); + + assertEquals(1, snps.size()); + + SnapshotListJobResult.SnapshotInfo info = snps.get(SNAPSHOT_NAME); + + assertNotNull(info.incrementals()); + + assertTrue(info.date() < time1); + assertTrue(info.date() < info.incrementals().date()); + assertTrue(info.incrementals().date() > time1); + } + } + + /** */ + @Test + public void testNodeStopDuringSnapshotList() throws Exception { + // Doesn't matter here, fastens the tests. + assumeFalse(encryption || onlyPrimary); + + int grids = 3; + int testGridIdx = 1; + + CountDownLatch snpLstBeginLatch = new CountDownLatch(grids); + CountDownLatch snpLstProceedLatch = new CountDownLatch(1); + + // Delays snapshot creation after its metadata is written. + pluginProvider = new AbstractTestPluginProvider() { + @Override public String name() { + return "TestSnpMgrProvider"; + } + + @Override public T createComponent(PluginContext ctx, Class cls) { + if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { + return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { + @Override public List readSnapshotMetadatas(SnapshotFileTree sft, boolean failIfCantRead) { + snpLstBeginLatch.countDown(); + + if (((IgniteEx)ctx.grid()).localNode().order() == testGridIdx + 1) { + try { + assertTrue(snpLstProceedLatch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); + } + catch (InterruptedException e) { + throw new IllegalStateException(e); + } + } + + return super.readSnapshotMetadatas(sft, failIfCantRead); + } + }; + } + + return super.createComponent(ctx, cls); + } + }; + + startGridsWithSnapshot(grids, CACHE_KEYS_RANGE, true); + + IgniteInternalFuture lstOpFut = runAsync(() -> listSnapshots(grid(0))); + + assertTrue(snpLstBeginLatch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); + + IgniteInternalFuture stopFut = runAsync(() -> stopGrid(testGridIdx, true)); + + snpLstProceedLatch.countDown(); + + assertThrowsAnyCause( + null, + () -> lstOpFut.get(getTestTimeout()), + NodeStoppingException.class, + "Node is stopping" + ); + + stopFut.get(getTestTimeout()); + } + + /** */ + private static SnapshotListTaskResult listSnapshots(IgniteEx grid, @Nullable String src) throws Exception { + SnapshotListCommandArg arg = new SnapshotListCommandArg(); + + arg.src(src); + + Collection nodes = grid.cluster().forServers().nodes().stream().map(ClusterNode::id).toList(); + + return grid.compute().execute(SnapshotListTask.class, new VisorTaskArgument<>(nodes, arg, false)).result(); + } + + /** */ + private static SnapshotListTaskResult listSnapshots(IgniteEx grid) throws Exception { + return listSnapshots(grid, null); + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/AbstractRollingUpgradeTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/AbstractRollingUpgradeTest.java index 3d7f76477c251..4371c77ae4c17 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/AbstractRollingUpgradeTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/AbstractRollingUpgradeTest.java @@ -91,6 +91,8 @@ * 2.19.1 0-1 * 2.19.2 0-2 * 2.19.3 0-2,6 + * 2.19.4 0 + * 2.19.5 0-1 * 2.20.0 2-5 * 2.20.1 2-6 * 2.21.0 6 diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_4.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_4.java new file mode 100644 index 0000000000000..30a8a8ae030ee --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_4.java @@ -0,0 +1,24 @@ +/* + * 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.ignite.internal.processors.rollingupgrade.feature; + +/** */ +public class TestIgniteReleaseFeatures_2_19_4 { + /** */ + public static final IgniteFeature ROLLING_UPGRADE_FEATURE = new IgniteCoreFeature(0); +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_5.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_5.java new file mode 100644 index 0000000000000..aeccf2e733eda --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/rollingupgrade/feature/TestIgniteReleaseFeatures_2_19_5.java @@ -0,0 +1,27 @@ +/* + * 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.ignite.internal.processors.rollingupgrade.feature; + +/** */ +public class TestIgniteReleaseFeatures_2_19_5 { + /** */ + public static final IgniteFeature ROLLING_UPGRADE_FEATURE = new IgniteCoreFeature(0); + + /** */ + public static final IgniteFeature SNAPSHOT_LIST_FEATURE = CoreFeatureRegistry.SNAPSHOT_LIST_FEATURE; +} diff --git a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryClientTopologyGapTest.java b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryClientTopologyGapTest.java index ba86e441f2f87..68a239c7cde38 100644 --- a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryClientTopologyGapTest.java +++ b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoveryClientTopologyGapTest.java @@ -41,7 +41,7 @@ public class TcpDiscoveryClientTopologyGapTest extends GridCommonAbstractTest { private static final int CLIENTS = 4; /** */ - private static final int ITERS = 300; + private static final int ITERS = GridTestUtils.SF.apply(200); /** Critical failures captured on nodes, by instance name. */ private static final Map failures = new ConcurrentHashMap<>(); diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite9.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite9.java new file mode 100644 index 0000000000000..1ad9acf650aa6 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteSnapshotTestSuite9.java @@ -0,0 +1,51 @@ +/* + * 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.ignite.testsuites; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotListRollingUpgradeTest; +import org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteClusterSnapshotListTest; +import org.apache.ignite.testframework.GridTestUtils; +import org.apache.ignite.testframework.junits.DynamicSuite; +import org.junit.runner.RunWith; + +/** Split off from {@link IgniteSnapshotTestSuite8} to reduce the single-suite runtime in CI. */ +@RunWith(DynamicSuite.class) +public class IgniteSnapshotTestSuite9 { + /** + * @return Test suite. + */ + public static List> suite() { + return suite(null); + } + + /** + * @param ignoredTests Tests to ignore. + * @return Test suite. + */ + public static List> suite(Collection ignoredTests) { + List> suite = new ArrayList<>(); + + GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotListTest.class, ignoredTests); + GridTestUtils.addTestIfNeeded(suite, IgniteClusterSnapshotListRollingUpgradeTest.class, ignoredTests); + + return suite; + } +} diff --git a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output index dd6ff4e43caf5..b86b79a73834f 100644 --- a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output +++ b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output @@ -269,6 +269,12 @@ This utility can do the following commands: Get the status of the current snapshot operation: control.(sh|bat) --snapshot status + Lists all snapshots on all online server nodes with their sizes: + control.(sh|bat) --snapshot list [--src path/to/snapshots] + + Parameters: + --src path/to/snapshots - Path to snapshot location directory. If not specified, the default configured snapshot directory will be used. + Change cluster tag to new value: control.(sh|bat) --change-tag newTagValue [--yes] diff --git a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output index 76f0f8c7e0e37..54b21e4ef88bf 100644 --- a/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output +++ b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output @@ -269,6 +269,12 @@ This utility can do the following commands: Get the status of the current snapshot operation: control.(sh|bat) --snapshot status + Lists all snapshots on all online server nodes with their sizes: + control.(sh|bat) --snapshot list [--src path/to/snapshots] + + Parameters: + --src path/to/snapshots - Path to snapshot location directory. If not specified, the default configured snapshot directory will be used. + Change cluster tag to new value: control.(sh|bat) --change-tag newTagValue [--yes] diff --git a/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/ContinuousDataLoadApplication.java b/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/ContinuousDataLoadApplication.java index bff6187c71532..0b743036dfebc 100644 --- a/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/ContinuousDataLoadApplication.java +++ b/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/ContinuousDataLoadApplication.java @@ -78,7 +78,7 @@ public class ContinuousDataLoadApplication extends IgniteAwareApplication { if (notifyTime + TimeUnit.MILLISECONDS.toNanos(1500) < System.nanoTime()) notifyTime = System.nanoTime(); - // Delayed notify of the initialization to make sure the data load has completelly began and + // Delayed notify of the initialization to make sure the data load has completely began and // has produced some valuable amount of data. if (!inited() && warmUpCnt == loaded) markInitialized(); From f15ba58d92349325fd001558f8e0ec082e1b08f8 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Sat, 10 Oct 2026 09:22:48 +0300 Subject: [PATCH 93/97] kimi k3 review fixes --- docs/_docs/snapshots/snapshots.adoc | 4 +-- .../GridCommandHandlerListSnapshotTest.java | 22 ++++++------ .../management/snapshot/SnapshotListTask.java | 34 +++++++++++++++---- .../GridDhtPartitionsSingleMessage.java | 2 +- .../snapshot/IgniteSnapshotManager.java | 4 +-- .../snapshot/SnapshotListJobResult.java | 18 ++++++---- .../IgniteClusterSnapshotListTest.java | 5 ++- .../tests/ContinuousDataLoadApplication.java | 2 +- 8 files changed, 60 insertions(+), 31 deletions(-) diff --git a/docs/_docs/snapshots/snapshots.adoc b/docs/_docs/snapshots/snapshots.adoc index 8650862821e06..80a826844e6e4 100644 --- a/docs/_docs/snapshots/snapshots.adoc +++ b/docs/_docs/snapshots/snapshots.adoc @@ -289,7 +289,7 @@ control.(sh|bat) --snapshot restore snapshot_09062021 --increment 1 == Listing Snapshots -To list snapshots created on cluster, use the following command. +To list snapshots created on the cluster, use the following command. [tabs] -- @@ -319,7 +319,7 @@ control.bat --snapshot list --src C:\\tmp\\ignite\\snapshots * The list operation is read-only and does not modify any snapshot data. * If a snapshot is being modified (created or deleted), its size might not be reported. * Snapshot is being detected by its metadata. If metadata is missing or cannot be read, snapshot is ignored. -* When snapshot listing and size calculating, no additional snapshot validation, checksums calculation are done. +* When listing snapshot and calculating size, no additional snapshot validation or checksum calculation is performed. == Getting Snapshot Operation Status diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java index b62f5f58411f1..a0417f7708e90 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java @@ -50,13 +50,13 @@ /** Test for the command '--snapshot list'. */ public class GridCommandHandlerListSnapshotTest extends GridCommandHandlerAbstractTest { - /** Extra storage path. */ + /** External storage path. */ private static final String EXT_STORAGE_PATH = "extStorage"; /** Flag to use {@link DataStorageConfiguration#setExtraSnapshotPaths(String...)}. */ - private boolean extraStorages; + private boolean extStorages; - /** Resolved extra storages paths. {@code null} if {@code extraStorages} is {@code null}. */ + /** Resolved external storages paths. {@code null} if {@code extStorages} is {@code null}. */ private @Nullable String[] extStoragePaths; /** Consistent id postfix. */ @@ -70,7 +70,7 @@ public class GridCommandHandlerListSnapshotTest extends GridCommandHandlerAbstra @Parameter(2) public boolean separatedWorkDir; - /** Flag to add extra server node after the snapshot creation. */ + /** Flag to add external server node after the snapshot creation. */ @Parameter(3) public boolean addExtraSrvr; @@ -130,7 +130,7 @@ public static Collection parameters() { cfg.getDataStorageConfiguration().setWalCompactionEnabled(incCnt > 0); - if (extraStorages) { + if (extStorages) { cfg.getDataStorageConfiguration().setExtraStoragePaths( workDir + File.separator, workDir + File.separator + EXT_STORAGE_PATH @@ -184,12 +184,12 @@ public void testOneNodeMisses() throws Exception { /** */ @Test - public void testExtraStorages() throws Exception { - // Extra storeages are required to be the same as configured in the node's PDS storages. Thus, we skip different work folders. + public void testExternalStorages() throws Exception { + // External storages are required to be the same as configured in the node's PDS storages. Thus, we skip different work folders. // Also, extra snapshot storages aren't used if snapshot is created with a custom path. assumeFalse(separatedWorkDir || customPath); - extraStorages = true; + extStorages = true; doTestSnapshotsLists(2, false, false); } @@ -319,10 +319,10 @@ private void doTestSnapshotsLists(int snpCnt, boolean deleteOnOneNode, boolean r assertEquals(certainSnpRecordsCnt, countEntries(out, "Snapshot 'testSnapshot" + snpIdx + "'")); - for (int i = 0; i < incCnt; i++) + if (incCnt > 0) assertEquals(certainSnpRecordsCnt * snpCnt, countEntries(out, "incremental snapshots: cnt=" + incCnt)); - if (extraStorages) + if (extStorages) assertEquals(certainSnpRecordsCnt * snpCnt, countEntries(out, "external storages: cnt=1, size=")); } } @@ -344,7 +344,7 @@ private static int countEntries(String txt, String entry) { ) { CacheConfiguration ccfg = super.testCacheConfiguration(cacheName, partitions, filter); - if (extraStorages) + if (extStorages) ccfg.setStoragePaths(extStoragePaths); return ccfg; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java index 25fe5d959cbb3..17bbbe30c6bbe 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotListTask.java @@ -33,6 +33,7 @@ import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; @@ -53,6 +54,7 @@ import org.apache.ignite.internal.thread.pool.IgniteThreadPoolExecutor; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.T2; +import org.apache.ignite.internal.util.typedef.X; import org.apache.ignite.internal.visor.VisorJob; import org.apache.ignite.internal.visor.VisorMultiNodeTask; import org.apache.ignite.internal.visor.VisorTaskArgument; @@ -216,7 +218,7 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { catch (Exception e) { failedSnps.add(snpName); - log.warning("Failed to read snapshot's extra storages, snapshot ignored [snpName=" + snpName + ']', e); + log.warning("Failed to read snapshot's external storages, snapshot ignored [snpName=" + snpName + ']', e); } }); @@ -251,9 +253,17 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { try { fut.get(); } - catch (Throwable t) { + catch (ExecutionException e) { + if (X.hasCause(e, NodeStoppingException.class)) + throw new IgniteException("Won't search for local snapshots.", e.getCause()); + // All the futures have internal exceptions being logged. No errors expected. - throw new IgniteException("Failed to read local nodes snapshots.", t); + throw new IgniteException("Failed to read local nodes' snapshots.", e); + } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + + throw new IgniteException("Interrupted while reading local snapshots.", e); } } @@ -305,7 +315,7 @@ protected SnapshotListJob(SnapshotListCommandArg arg, boolean debug) { extStoragesSize += calculateDirectorySize(extraStorage); } catch (IOException e) { - log.warning("Failed to calculate snapshot's extra storage size, storage ignored [extraStorage=" + + log.warning("Failed to calculate snapshot's external storage size, storage ignored [extraStorage=" + extraStorage + ']', e); continue; @@ -345,8 +355,7 @@ private List> findLocalSnapshots(@Nullable String snp return null; // Get the last-created time snapshot metadata. - SnapshotMetadata snpMeta = metas.stream().max(Comparator.comparingLong(SnapshotMetadata::snapshotTime)) - .orElse(new SnapshotMetadata()); + SnapshotMetadata snpMeta = metas.stream().max(Comparator.comparingLong(SnapshotMetadata::snapshotTime)).get(); // Real, meta-based snapshot file tree. Can belong to other cluster, other consistent id. sft = new SnapshotFileTree( @@ -373,9 +382,17 @@ private List> findLocalSnapshots(@Nullable String snp if (snpDirRes != null) res.add(snpDirRes); } - catch (Exception e) { + catch (ExecutionException e) { + if (X.hasCause(e, NodeStoppingException.class)) + throw new IgniteException("Won't search for local snapshots.", e.getCause()); + log.warning("Failed to read snapshot, snapshot ignored [path=" + snpPath + ']', e); } + catch (InterruptedException e) { + Thread.currentThread().interrupt(); + + throw new IgniteException("Interrupted while reading local snapshots.", e); + } }); return res; @@ -397,6 +414,9 @@ private List> findLocalSnapshots(@Nullable String snp int incIdx; for (File incDir : incs) { + if (!SnapshotFileTree.incrementSnapshotDir(incDir) || !incDir.exists()) + continue; + try { incIdx = Integer.parseInt(incDir.getName()); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsSingleMessage.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsSingleMessage.java index a9f1f42ca872b..1857aabd1f965 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsSingleMessage.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/preloader/GridDhtPartitionsSingleMessage.java @@ -34,7 +34,7 @@ /** * Information about partitions of a single node. Sent in response to {@link GridDhtPartitionsSingleRequest} and during * processing partitions exchange future.
- * Has to be completelly restored after receiving from another node. + * Has to be completely restored after receiving from another node. * * @see #afterReceive() */ diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java index aa5cc959aa904..34d6167271cf6 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteSnapshotManager.java @@ -1871,9 +1871,9 @@ public List readSnapshotMetadatas(SnapshotFileTree sft, boolea } catch (Exception e) { if (failIfCantRead) - throw new IgniteException("Fail to read snapshot metadata [meta=" + smf + ']', e); + throw new IgniteException("Failed to read snapshot metadata [meta=" + smf + ']', e); else - log.warning("Fail to read snapshot metadata, snapshot skipped [meta=" + smf + ']', e); + log.warning("Failed to read snapshot metadata, snapshot skipped [meta=" + smf + ']', e); } } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java index 03e3131920854..9d01cfdf92eab 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java @@ -22,7 +22,7 @@ import org.apache.ignite.internal.dto.IgniteDataTransferObject; import org.jetbrains.annotations.Nullable; -/** Per-node result of the snapshot lists command. Contains information of the snapshots found on current node. */ +/** Per-node result of the snapshot list command. Contains information of the snapshots found on current node. */ public final class SnapshotListJobResult extends IgniteDataTransferObject { /** Serial version uid. */ private static final long serialVersionUID = 0L; @@ -78,12 +78,12 @@ public SnapshotInfo() { /** Creates snapshot main description. */ public SnapshotInfo( - long size, + long mainSize, long date, @Nullable SnapshotInfo extStors, @Nullable SnapshotInfo incs ) { - this.size = size + (extStors == null ? 0L : extStors.size()); + size = mainSize + (extStors == null ? 0L : extStors.size()); this.date = date; this.extStors = extStors; this.incs = incs; @@ -115,17 +115,23 @@ public long size() { return date; } - /** @return Snapshot external storages description if exists. Is always {@code null} for not the main snapshot description. */ + /** + * @return Snapshot external storages description if exists. Is always {@code null} for anything other than + * the main snapshot description. + */ public @Nullable SnapshotInfo externalStorages() { return extStors; } - /** @return Snapshot incremental parts description if exists. Is always {@code null} for not the main snapshot description. */ + /** + * @return Snapshot incremental parts description if exists. Is always {@code null} for anything other than + * the main snapshot description. + */ public @Nullable SnapshotInfo incrementals() { return incs; } - /** @return Number of snapshot external storages or incremental parts. Or {@code null} for the main snapshot data.*/ + /** @return Number of snapshot external storages or incremental parts. Or {@code null} for the main snapshot data. */ public @Nullable Integer number() { return cnt; } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java index 736cee436df3d..2de05bc4b88e6 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java @@ -67,6 +67,7 @@ import static org.apache.ignite.testframework.GridTestUtils.assertThrowsAnyCause; import static org.apache.ignite.testframework.GridTestUtils.cartesianProduct; import static org.apache.ignite.testframework.GridTestUtils.runAsync; +import static org.apache.ignite.testframework.GridTestUtils.waitForCondition; import static org.junit.Assume.assumeFalse; import static org.junit.Assume.assumeTrue; @@ -812,7 +813,9 @@ public void testNodeStopDuringSnapshotList() throws Exception { assertTrue(snpLstBeginLatch.await(getTestTimeout(), TimeUnit.MILLISECONDS)); - IgniteInternalFuture stopFut = runAsync(() -> stopGrid(testGridIdx, true)); + IgniteInternalFuture stopFut = runAsync(() -> stopGrid(testGridIdx)); + + assertTrue(waitForCondition(() -> grid(testGridIdx).context().isStopping(), getTestTimeout())); snpLstProceedLatch.countDown(); diff --git a/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/ContinuousDataLoadApplication.java b/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/ContinuousDataLoadApplication.java index 0b743036dfebc..5df1a59847a60 100644 --- a/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/ContinuousDataLoadApplication.java +++ b/modules/ducktests/src/main/java/org/apache/ignite/internal/ducktest/tests/ContinuousDataLoadApplication.java @@ -78,7 +78,7 @@ public class ContinuousDataLoadApplication extends IgniteAwareApplication { if (notifyTime + TimeUnit.MILLISECONDS.toNanos(1500) < System.nanoTime()) notifyTime = System.nanoTime(); - // Delayed notify of the initialization to make sure the data load has completely began and + // Delayed notify of the initialization to make sure the data load has completely begun and // has produced some valuable amount of data. if (!inited() && warmUpCnt == loaded) markInitialized(); From b0a5683717f6cdc8fd89ecbe7efb4efc958ff0f2 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Sat, 10 Oct 2026 10:36:42 +0300 Subject: [PATCH 94/97] + sizes tests --- .../snapshot/AbstractSnapshotSelfTest.java | 6 +- .../IgniteClusterSnapshotListTest.java | 158 +++++++++++++++++- .../AbstractIncrementalSnapshotTest.java | 2 +- .../IncrementalSnapshotNodeFailureTest.java | 2 +- 4 files changed, 162 insertions(+), 6 deletions(-) diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java index 8857a4ddd656e..8aca648f0e019 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/AbstractSnapshotSelfTest.java @@ -701,8 +701,8 @@ protected void checkSnapshot(String snpName, String snpPath) { * @param ignite Ignite instance. * @return Snapshot manager related to given ignite instance. */ - public static IgniteSnapshotManager snp(IgniteEx ignite) { - return ignite.context().cache().context().snapshotMgr(); + public static IgniteSnapshotManager snp(Ignite ignite) { + return ((IgniteEx)ignite).context().cache().context().snapshotMgr(); } /** @@ -745,7 +745,7 @@ protected static List setBlockingSnapshotExecutor(List execs = new ArrayList<>(); for (Ignite grid : grids) { - IgniteSnapshotManager mgr = snp((IgniteEx)grid); + IgniteSnapshotManager mgr = snp(grid); Function old = mgr.localSnapshotSenderFactory(); BlockingExecutor block = new BlockingExecutor(mgr.snapshotExecutorService()); diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java index 2de05bc4b88e6..a0bd756b605c3 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java @@ -28,6 +28,7 @@ import java.nio.file.attribute.PosixFilePermission; import java.nio.file.attribute.PosixFilePermissions; import java.util.Collection; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -89,6 +90,12 @@ public class IgniteClusterSnapshotListTest extends AbstractSnapshotSelfTest { /** */ private PluginProvider pluginProvider; + /** Flag to spread the test cache data over the external storages. */ + private boolean extStorages; + + /** Resolved external storages paths. {@code null} if {@link #extStorages} is {@code false}. */ + private @Nullable String[] extStoragePaths; + /** Parameters. */ @Parameterized.Parameters(name = "encryption={0}, onlyPrimary={1}, snpThrdPoolSz={2}") public static Collection params() { @@ -107,6 +114,21 @@ public static Collection params() { if (pluginProvider != null) cfg.setPluginProviders(pluginProvider); + if (extStorages) { + // External storage paths must be identical on all the nodes (a cache has the single storage paths + // setting). Thus, a shared work directory is used, like in GridCommandHandlerListSnapshotTest. + cfg.setWorkDirectory(U.defaultWorkDirectory()); + + cfg.getDataStorageConfiguration().setExtraStoragePaths( + U.defaultWorkDirectory() + File.separator, + U.defaultWorkDirectory() + File.separator + "extStorage" + ); + + extStoragePaths = cfg.getDataStorageConfiguration().getExtraStoragePaths(); + + cfg.getDataStorageConfiguration().setExtraSnapshotPaths("", "extStorage"); + } + cfg.setSnapshotThreadPoolSize(snpThrdPoolSz); return cfg; @@ -144,7 +166,15 @@ public static Collection params() { /** {@inheritDoc} */ @Override protected CacheConfiguration txCacheConfig(CacheConfiguration ccfg) { // Speeds up the tests. - return super.txCacheConfig(ccfg).setAffinity(new RendezvousAffinityFunction(false, CACHE_PARTITIONS_COUNT)); + ccfg = super.txCacheConfig(ccfg).setAffinity(new RendezvousAffinityFunction(false, CACHE_PARTITIONS_COUNT)); + + if (extStorages) { + assert !F.isEmpty(extStoragePaths); + + ccfg.setStoragePaths(extStoragePaths); + } + + return ccfg; } /** */ @@ -765,6 +795,107 @@ public void testSnapshotListsDates() throws Exception { } } + /** */ + @Test + public void testSnapshotListsSizes() throws Exception { + // Incremental snapshots do not support the only-primary mode or encryption. + assumeFalse(onlyPrimary || encryption); + // Speeds up the tests. + assumeTrue(snpThrdPoolSz > 1); + + int grids = 3; + + IgniteEx ig = startGridsWithCache(grids, txCacheConfig(defaultCacheConfiguration()), CACHE_KEYS_RANGE); + + snp(ig).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(getTestTimeout()); + + SnapshotListTaskResult res = listSnapshots(grid(0)); + + Map sizes0 = collectSizes(res); + + // Check the sizes. + for (int g = 0; g < grids; g++) { + long sz = SnapshotListTask.calculateDirectorySize(new SnapshotFileTree(grid(g).context(), SNAPSHOT_NAME, null).root()); + + assertEquals(sz, sizes0.get(grid(g).localNode().id()).longValue()); + + assertNull(snapshotInfo(res, grid(g)).externalStorages()); + assertNull(snapshotInfo(res, grid(g)).incrementals()); + } + + // Add some data and create an incremental snapshot. + try (IgniteDataStreamer ds = ig.dataStreamer(DEFAULT_CACHE_NAME)) { + for (int kv = CACHE_KEYS_RANGE; kv < CACHE_KEYS_RANGE * 2; ++kv) + ds.addData(kv, kv); + } + + snp(ig).createSnapshot(SNAPSHOT_NAME, null, true, onlyPrimary).get(getTestTimeout()); + + // Repeat the operation. + res = listSnapshots(grid(0)); + + Map sizes1 = collectSizes(res); + + for (int g = 0; g < grids; g++) { + UUID nodeId = grid(g).localNode().id(); + + SnapshotListJobResult.SnapshotInfo info = snapshotInfo(res, grid(g)); + + assertNotNull(info.incrementals()); + assertEquals(1, info.incrementals().number().intValue()); + + // The size must grow, but exactly to the actual directory size: the incremental part + // is placed inside the snapshot root and must not be counted twice. + assertTrue(sizes1.get(nodeId) > sizes0.get(nodeId)); + + long sz = SnapshotListTask.calculateDirectorySize(new SnapshotFileTree(grid(g).context(), SNAPSHOT_NAME, null).root()); + + assertEquals(sz, sizes1.get(nodeId).longValue()); + } + } + + /** */ + @Test + public void testSnapshotListsSizesExternalStorages() throws Exception { + // Speeds up the tests. + assumeTrue(snpThrdPoolSz > 1); + assumeFalse(onlyPrimary); + + int grids = 3; + + extStorages = true; + + // Properly delays the test cache creation with the configured external storages. + dfltCacheCfg = null; + + startGridsMultiThreaded(3); + + grid(0).createCache(txCacheConfig(defaultCacheConfiguration())); + + try (IgniteDataStreamer ds = grid(0).dataStreamer(DEFAULT_CACHE_NAME)) { + for (int i = 0; i < CACHE_KEYS_RANGE; i++) + ds.addData(i, i); + } + + snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(getTestTimeout()); + + SnapshotListTaskResult res = listSnapshots(grid(0)); + + for (int g = 0; g < grids; g++) { + SnapshotListJobResult.SnapshotInfo info = snapshotInfo(res, grid(g)); + + assertNotNull(info); + assertNotNull(info.externalStorages()); + assertTrue(info.externalStorages().size() > 0L); + + long rootSize = SnapshotListTask.calculateDirectorySize(new SnapshotFileTree(grid(g).context(), SNAPSHOT_NAME, null).root()); + + // The external storages are placed outside the snapshot root and must be added to the total size. + assertTrue(info.size() > rootSize); + assertEquals(rootSize + info.externalStorages().size(), info.size()); + } + } + /** */ @Test public void testNodeStopDuringSnapshotList() throws Exception { @@ -844,4 +975,29 @@ private static SnapshotListTaskResult listSnapshots(IgniteEx grid, @Nullable Str private static SnapshotListTaskResult listSnapshots(IgniteEx grid) throws Exception { return listSnapshots(grid, null); } + + /** @return The test snapshot info reported for the node. */ + private static @Nullable SnapshotListJobResult.SnapshotInfo snapshotInfo(SnapshotListTaskResult res, IgniteEx node) { + for (int i = 0; i < res.nodesIds().length; i++) { + if (res.nodesIds()[i].equals(node.localNode().id())) + return res.nodesSnapshots()[i].snapshots().get(SNAPSHOT_NAME); + } + + return null; + } + + /** @return The test snapshot sizes reported by the list operation per node id. */ + private static Map collectSizes(SnapshotListTaskResult res) { + Map sizes = new HashMap<>(); + + for (int i = 0; i < res.nodesIds().length; i++) { + SnapshotListJobResult.SnapshotInfo info = res.nodesSnapshots()[i].snapshots().get(SNAPSHOT_NAME); + + assertNotNull(info); + + sizes.put(res.nodesIds()[i], info.size()); + } + + return sizes; + } } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/incremental/AbstractIncrementalSnapshotTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/incremental/AbstractIncrementalSnapshotTest.java index fcf65fa9b30ed..8055e7795e396 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/incremental/AbstractIncrementalSnapshotTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/incremental/AbstractIncrementalSnapshotTest.java @@ -132,7 +132,7 @@ protected void awaitSnapshotResourcesCleaned() { try { assertTrue(GridTestUtils.waitForCondition(() -> { for (Ignite g: G.allGrids()) { - if (snp((IgniteEx)g).currentCreateRequest() != null) + if (snp(g).currentCreateRequest() != null) return false; } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/incremental/IncrementalSnapshotNodeFailureTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/incremental/IncrementalSnapshotNodeFailureTest.java index 88aad62a4985f..68ff0e443740f 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/incremental/IncrementalSnapshotNodeFailureTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/incremental/IncrementalSnapshotNodeFailureTest.java @@ -109,7 +109,7 @@ private void runIncrementalSnapshotAndBreak(Supplier breakSnpWithExcp) t awaitSnapshotResourcesCleaned(); for (Ignite g: G.allGrids()) - assertNull(snp((IgniteEx)g).incrementalSnapshotId()); + assertNull(snp(g).incrementalSnapshotId()); stopAllGrids(); From 9b7f4b48b224551f3449154ff2bdd49b8a507342 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Sat, 10 Oct 2026 12:39:40 +0300 Subject: [PATCH 95/97] kimi k3 review --- docs/_docs/snapshots/snapshots.adoc | 4 ++-- .../ignite/util/GridCommandHandlerListSnapshotTest.java | 4 ++-- .../cache/persistence/snapshot/SnapshotListJobResult.java | 2 +- .../persistence/snapshot/IgniteClusterSnapshotListTest.java | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/docs/_docs/snapshots/snapshots.adoc b/docs/_docs/snapshots/snapshots.adoc index 80a826844e6e4..a8129b4b998f1 100644 --- a/docs/_docs/snapshots/snapshots.adoc +++ b/docs/_docs/snapshots/snapshots.adoc @@ -318,8 +318,8 @@ control.bat --snapshot list --src C:\\tmp\\ignite\\snapshots * The list operation is read-only and does not modify any snapshot data. * If a snapshot is being modified (created or deleted), its size might not be reported. -* Snapshot is being detected by its metadata. If metadata is missing or cannot be read, snapshot is ignored. -* When listing snapshot and calculating size, no additional snapshot validation or checksum calculation is performed. +* A snapshot is being detected by its metadata. If metadata is missing or cannot be read, snapshot is ignored. +* When listing snapshots and calculating size, no additional snapshot validation or checksum calculation is performed. == Getting Snapshot Operation Status diff --git a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java index a0417f7708e90..fea71bb75c72e 100644 --- a/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java +++ b/modules/control-utility/src/test/java/org/apache/ignite/util/GridCommandHandlerListSnapshotTest.java @@ -56,7 +56,7 @@ public class GridCommandHandlerListSnapshotTest extends GridCommandHandlerAbstra /** Flag to use {@link DataStorageConfiguration#setExtraSnapshotPaths(String...)}. */ private boolean extStorages; - /** Resolved external storages paths. {@code null} if {@code extStorages} is {@code null}. */ + /** Resolved external storages paths. {@code null} if {@code extStorages} is {@code false}. */ private @Nullable String[] extStoragePaths; /** Consistent id postfix. */ @@ -331,7 +331,7 @@ private void doTestSnapshotsLists(int snpCnt, boolean deleteOnOneNode, boolean r private static int countEntries(String txt, String entry) { String prev = txt; - txt = txt.replaceAll(entry, ""); + txt = txt.replace(entry, ""); return (prev.length() - txt.length()) / entry.length(); } diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java index 9d01cfdf92eab..83e839e2c7179 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotListJobResult.java @@ -22,7 +22,7 @@ import org.apache.ignite.internal.dto.IgniteDataTransferObject; import org.jetbrains.annotations.Nullable; -/** Per-node result of the snapshot list command. Contains information of the snapshots found on current node. */ +/** Per-node result of the snapshot list command. Contains information of the snapshots found on the current node. */ public final class SnapshotListJobResult extends IgniteDataTransferObject { /** Serial version uid. */ private static final long serialVersionUID = 0L; diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java index a0bd756b605c3..6840648057a34 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java @@ -601,7 +601,7 @@ private void doTestWithWrongIncrementalMeta(boolean corruptFile) throws Exceptio /** * Test snapshot list operation when a node can't read some snapshot part due to insufficient permissions. - * I.e. a test node is able to read snapshot meta but can't read some the snapshot's data. + * I.e. a test node is able to read snapshot meta but can't read some of the snapshot's data. */ @Test public void testDeniedPermissions() throws Exception { @@ -908,7 +908,7 @@ public void testNodeStopDuringSnapshotList() throws Exception { CountDownLatch snpLstBeginLatch = new CountDownLatch(grids); CountDownLatch snpLstProceedLatch = new CountDownLatch(1); - // Delays snapshot creation after its metadata is written. + // Delays snapshot reading. pluginProvider = new AbstractTestPluginProvider() { @Override public String name() { return "TestSnpMgrProvider"; From 88d8c9c0b3453a67ddfd77c0c37fd5c56cf433e5 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Sat, 10 Oct 2026 13:15:22 +0300 Subject: [PATCH 96/97] minority --- .../persistence/snapshot/IgniteClusterSnapshotListTest.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java index 6840648057a34..b6cf42480da40 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java @@ -166,7 +166,9 @@ public static Collection params() { /** {@inheritDoc} */ @Override protected CacheConfiguration txCacheConfig(CacheConfiguration ccfg) { // Speeds up the tests. - ccfg = super.txCacheConfig(ccfg).setAffinity(new RendezvousAffinityFunction(false, CACHE_PARTITIONS_COUNT)); + ccfg = super.txCacheConfig(ccfg) + .setAffinity(new RendezvousAffinityFunction(false, CACHE_PARTITIONS_COUNT)) + .setBackups(1); if (extStorages) { assert !F.isEmpty(extStoragePaths); From 47e87b94f24b334b7161671c504ef2bbbf5741c6 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Sat, 10 Oct 2026 13:34:03 +0300 Subject: [PATCH 97/97] fixes --- .../snapshot/IgniteClusterSnapshotListTest.java | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java index b6cf42480da40..8fc18e641cd96 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotListTest.java @@ -75,7 +75,7 @@ /** Cluster-wide snapshot list procedure tests. */ public class IgniteClusterSnapshotListTest extends AbstractSnapshotSelfTest { /** Number of cache keys to pre-create at node start. */ - private static final int CACHE_KEYS_RANGE = 10; + private static final int CACHE_KEYS_RANGE = 15; /** Number of partitions within a snapshot cache group. */ private static final int CACHE_PARTITIONS_COUNT = 4; @@ -351,7 +351,7 @@ private void doTestDeletionAfterMetaRead(boolean completeDeletion) throws Except */ @Test public void testReadWhileCreating() throws Exception { - // Doesn't matter here. Fastens the tests. + // Doesn't matter here. speeds up the tests. assumeFalse(encryption || onlyPrimary); assertTrue(new File(U.defaultWorkDirectory()).exists()); @@ -417,7 +417,7 @@ public void testEmptySnapshotPath() throws Exception { /** */ private void doTestWrongSnapshotPath(boolean missing) throws Exception { - // Doesn't matter here, fastens the tests. + // Doesn't matter here, speeds up the tests. assumeFalse(encryption || onlyPrimary || snpThrdPoolSz < 2); doTestSnapshotPath(null); @@ -861,7 +861,7 @@ public void testSnapshotListsSizes() throws Exception { public void testSnapshotListsSizesExternalStorages() throws Exception { // Speeds up the tests. assumeTrue(snpThrdPoolSz > 1); - assumeFalse(onlyPrimary); + assumeTrue(onlyPrimary); int grids = 3; @@ -870,7 +870,7 @@ public void testSnapshotListsSizesExternalStorages() throws Exception { // Properly delays the test cache creation with the configured external storages. dfltCacheCfg = null; - startGridsMultiThreaded(3); + startGridsMultiThreaded(grids); grid(0).createCache(txCacheConfig(defaultCacheConfiguration())); @@ -888,12 +888,11 @@ public void testSnapshotListsSizesExternalStorages() throws Exception { assertNotNull(info); assertNotNull(info.externalStorages()); - assertTrue(info.externalStorages().size() > 0L); long rootSize = SnapshotListTask.calculateDirectorySize(new SnapshotFileTree(grid(g).context(), SNAPSHOT_NAME, null).root()); - // The external storages are placed outside the snapshot root and must be added to the total size. - assertTrue(info.size() > rootSize); + // If there is a data withing the snapshot's external storage, its size must be added to the total size. + assertTrue(info.externalStorages().size() > 0L ? info.size() > rootSize : info.size() == rootSize); assertEquals(rootSize + info.externalStorages().size(), info.size()); } } @@ -901,7 +900,7 @@ public void testSnapshotListsSizesExternalStorages() throws Exception { /** */ @Test public void testNodeStopDuringSnapshotList() throws Exception { - // Doesn't matter here, fastens the tests. + // Doesn't matter here, speeds up the tests. assumeFalse(encryption || onlyPrimary); int grids = 3;