From eb6273cbe3d140aa686a432e8fbeccc6dad57723 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Mon, 7 Sep 2026 19:35:45 +0300 Subject: [PATCH 01/77] 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/77] 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/77] 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/77] 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/77] 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/77] 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/77] + 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/77] 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/77] 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/77] + 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/77] 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/77] + 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/77] + 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/77] 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/77] 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/77] +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/77] 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/77] + 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/77] + 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/77] 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/77] 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/77] + 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/77] 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/77] 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/77] + 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/77] + 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/77] + 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/77] + 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/77] + 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/77] + 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/77] 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/77] + 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/77] + 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/77] 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/77] 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/77] + 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/77] - 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/77] 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/77] 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/77] 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/77] 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/77] 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/77] 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/77] + 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/77] + 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/77] 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/77] 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/77] 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/77] 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/77] 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/77] + 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/77] 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/77] 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 2b2cc21f509689830f2164167305fe480fe8b456 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Wed, 23 Sep 2026 11:26:57 +0300 Subject: [PATCH 54/77] 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 b1e8d988b5654fdb812ffb2138eaf648d5a11635 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Wed, 23 Sep 2026 13:40:50 +0300 Subject: [PATCH 55/77] + doc, + test fix --- docs/_docs/snapshots/snapshots.adoc | 5 ++++- .../snapshot/IgniteClusterSnapshotDeleteTest.java | 4 +--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/_docs/snapshots/snapshots.adoc b/docs/_docs/snapshots/snapshots.adoc index f44a1e7b7dc02..ad89f7dad3f80 100644 --- a/docs/_docs/snapshots/snapshots.adoc +++ b/docs/_docs/snapshots/snapshots.adoc @@ -325,10 +325,13 @@ 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 snapshot operation with the same name (create, restore, check, or a delete with the same name) is - active for the snapshot. + active for the snapshot or if snapshot name is incorrect. +* The operation requires the snapshot administration pesmissins via `IgniteSecurity` (if configured). * 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. +* If nodes share the same snapshot directory, a deletion concurrency may apper. Some nodes may not find snapshot at all +or decide that the removal wasn't complete. Snapshot is deleted anyway. == Getting Snapshot Operation Status 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 aa1e1763e2599..77b52eb15ad8d 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 @@ -186,9 +186,7 @@ public void testDeleteOtherConsistentId() throws Exception { startGridsMultiThreaded(3); - var delRes = snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); - - assertTrue(F.isEmpty(delRes.emptyNodes)); + snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); for (var ig : G.allGrids()) { assertTrue(Files.list(((IgniteEx)ig).context().pdsFolderResolver().fileTree().snapshotsRoot().toPath()) From 9d493a0af014c7e8c9f2b5253663de98415083fd Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Wed, 23 Sep 2026 15:05:19 +0300 Subject: [PATCH 56/77] doc fix --- docs/_docs/snapshots/snapshots.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/_docs/snapshots/snapshots.adoc b/docs/_docs/snapshots/snapshots.adoc index ad89f7dad3f80..e6a89d8b0df11 100644 --- a/docs/_docs/snapshots/snapshots.adoc +++ b/docs/_docs/snapshots/snapshots.adoc @@ -324,14 +324,14 @@ 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 snapshot operation with the same name (create, restore, check, or a delete with the same name) is - active for the snapshot or if snapshot name is incorrect. +* The deletion is rejected if any snapshot operation (create, restore, check, delete) is active for the snapshot. * The operation requires the snapshot administration pesmissins via `IgniteSecurity` (if configured). * 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. * If nodes share the same snapshot directory, a deletion concurrency may apper. Some nodes may not find snapshot at all or decide that the removal wasn't complete. Snapshot is deleted anyway. +* The removal operation is not subject for the snapshot operation status and cancel requests. == Getting Snapshot Operation Status From 1dd3d535e4f92cf17c395012ba98ad59fea2da86 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Wed, 23 Sep 2026 15:33:58 +0300 Subject: [PATCH 57/77] fix of the confirmation promt. --- .../management/snapshot/SnapshotDeleteCommand.java | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 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 bd54538cae4a9..4a581f89ca4c1 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 @@ -119,11 +119,15 @@ private static String nodeIdsStrLst(Collection 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." + U.nl() + U.nl() + - "NOTE: Snapshot data on offline server nodes will remain untouched." + + "If the security is enabled, the operation requires the snapshot administration permissions." + U.nl() + U.nl() + - "The operation cannot be reverted."; + "Deletion in not snapshots Ignite's directories and deletion of any data without or corrupted snapshot " + + "metadata are prohibited." + + U.nl() + U.nl() + + "The operation cannot be reverted." + + U.nl() + U.nl() + + "NOTE: Snapshot data on offline server nodes will remain untouched."; } } From 2092624aec3b48eb7ee73b41806a89d2f5a5841b Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Thu, 24 Sep 2026 14:30:07 +0300 Subject: [PATCH 58/77] review fixes --- docs/_docs/snapshots/snapshots.adoc | 11 ++++--- .../query/calcite/prepare/PlannerHelper.java | 2 +- .../GridCommandHandlerDeleteSnapshotTest.java | 4 +-- .../snapshot/SnapshotDeleteCommand.java | 6 ++-- .../snapshot/SnapshotDeleteCommandArg.java | 7 +++-- .../snapshot/IgniteSnapshotManager.java | 25 +++++++++------- .../snapshot/SnapshotCheckProcess.java | 11 +++---- .../snapshot/SnapshotDeleteProcess.java | 30 +++++++++++-------- .../snapshot/SnapshotDeleteRequest.java | 4 ++- .../snapshot/SnapshotRestoreProcess.java | 6 ++-- .../IgniteClusterSnapshotDeleteTest.java | 4 +-- 11 files changed, 62 insertions(+), 48 deletions(-) diff --git a/docs/_docs/snapshots/snapshots.adoc b/docs/_docs/snapshots/snapshots.adoc index e6a89d8b0df11..4f420e6a59b28 100644 --- a/docs/_docs/snapshots/snapshots.adoc +++ b/docs/_docs/snapshots/snapshots.adoc @@ -325,13 +325,12 @@ 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 snapshot operation (create, restore, check, delete) is active for the snapshot. -* The operation requires the snapshot administration pesmissins via `IgniteSecurity` (if configured). +* The operation requires the snapshot administration permissions via `IgniteSecurity` (if configured). * 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. -* If nodes share the same snapshot directory, a deletion concurrency may apper. Some nodes may not find snapshot at all -or decide that the removal wasn't complete. Snapshot is deleted anyway. -* The removal operation is not subject for the snapshot operation status and cancel requests. +* 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 non-snapshot work directory, the operation fails. +* The removal operation is not subject to the snapshot operation status and cancel requests. == Getting Snapshot Operation Status 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..39fb078f6b219 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 paths to consider the hints as not propagated 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 59f2e3e6de62b..b867bf211e8ae 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 @@ -151,7 +151,7 @@ public void testSnapshotDelete() throws Exception { try (IgniteDataStreamer streamer = ig.dataStreamer(DEFAULT_CACHE_NAME)) { for (int d = dataIdx; d < dataIdx + entriesCnt / 4; ++d) - streamer.addData(i, i); + streamer.addData(d, d); } snp(ig).createSnapshot("testSnapshot", customPath ? cstSnpsRoot.getAbsolutePath() : null, true, false) @@ -204,7 +204,7 @@ else if (extraNodeIsServer == 0) assertFalse(out.contains(SnapshotDeleteCommand.REMOVED_PREF)); assertFalse(out.contains(SnapshotDeleteCommand.NODE_NOT_FOUND_PREF)); - assertTrue(out.contains(SnapshotDeleteCommand.NOT_FOUND_PREF)); + assertTrue(out.contains(SnapshotDeleteCommand.NOT_FOUND)); testOut.reset(); assertTrue(testOut.toString().isEmpty()); 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 4a581f89ca4c1..ba766df6bc26a 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 @@ -35,7 +35,7 @@ */ 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 DESC = "Deletes the snapshot and all its incremental snapshots from all online server nodes"; /** */ public static final String UNSURED_DELETION_PREF = "WARNING: the following nodes found snapshot data but might not " + @@ -49,7 +49,7 @@ public class SnapshotDeleteCommand extends AbstractSnapshotCommand() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { File f0 = file.toFile(); - if (f0.exists() && !U.delete(f0) && f0.exists()) + // Check for a concurrent deletion. + if (!U.delete(f0) && f0.exists()) res.set(false); return FileVisitResult.CONTINUE; @@ -822,7 +826,8 @@ 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()) + // Check for a concurrent deletion. + if (!f0.delete() && f0.exists()) res.set(false); if (log.isInfoEnabled() && e != null) @@ -1939,7 +1944,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 an 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/SnapshotCheckProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java index 6793f5fd8ec71..77c7c8eb0b743 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 @@ -24,6 +24,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.TreeSet; @@ -132,7 +133,7 @@ private IgniteInternalFuture reduceValidatePartsAndFinish( return new GridFinishedFuture<>(); try { - contexts.remove(ctx.req.snapshotName().toLowerCase()); + contexts.remove(ctx.req.snapshotName().toLowerCase(Locale.ROOT)); GridFutureAdapter clusterOpFut = clusterOpFuts.get(reqId); @@ -474,7 +475,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.toLowerCase(Locale.ROOT)); } /** Phase 1 beginning: prepare, collect and check local metas. */ @@ -488,7 +489,7 @@ 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().toLowerCase(Locale.ROOT), snpName -> new SnapshotCheckContext(req)); } if (!ctx.req.requestId().equals(req.requestId())) { @@ -594,7 +595,7 @@ private void reducePreparationAndMetasCheck( if (ctx != null) { unregisterMetrics(ctx.req.snapshotName()); - contexts.remove(ctx.req.snapshotName().toLowerCase()); + contexts.remove(ctx.req.snapshotName().toLowerCase(Locale.ROOT)); } if (clusterOpFut != null) @@ -700,7 +701,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.toLowerCase(Locale.ROOT)) != 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/SnapshotDeleteProcess.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteProcess.java index 13a9b87e9a174..e66b2046be6d8 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.io.IOException; import java.nio.file.Path; import java.util.Collection; import java.util.HashSet; @@ -162,18 +163,18 @@ private IgniteInternalFuture deletePhase(UUID ignored, S "Snapshot with this name is being checked [req=" + req + ']')); } - File path = resolvePath(req.snpPath); + try { + File path = resolvePath(req.snpPath); - String pathValidationErr = validateAbsoluteSnapshotRoot(path); + String pathValidationErr = validateAbsoluteSnapshotRoot(path); - 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; + req.resolvedPath = path; - try { if (!requests.add(req)) { return new GridFinishedFuture<>(new IgniteIllegalStateException("Deletion of the snapshot has already " + "started [req=" + req + ']')); @@ -260,7 +261,7 @@ else if (!deleted) } /** */ - private File resolvePath(@Nullable String path) { + private File resolvePath(@Nullable String path) throws IOException { var res = kctx.pdsFolderResolver().fileTree().snapshotsRoot(); if (path != null) { @@ -269,7 +270,7 @@ private File resolvePath(@Nullable String path) { res = reqPath.isAbsolute() ? reqPath : new File(res, path); } - return res; + return res.getCanonicalFile(); } /** */ @@ -284,7 +285,7 @@ private File resolvePath(@Nullable String path) { 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 "belongs to an Ignite directory"; } } @@ -363,7 +364,12 @@ private String consistentId(UUID nodeId) { public boolean isDeleting(String snpName, @Nullable String snpPath) { var rq = new SnapshotDeleteRequest(null, snpName, snpPath); - rq.resolvedPath = resolvePath(rq.snpPath); + try { + rq.resolvedPath = resolvePath(rq.snpPath); + } + catch (IOException ignored) { + return false; + } return requests.contains(rq); } 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 735909bb7f9ec..30fcfc07ba3ea 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 @@ -18,6 +18,7 @@ package org.apache.ignite.internal.processors.cache.persistence.snapshot; import java.io.File; +import java.util.Locale; import java.util.Objects; import java.util.UUID; import org.apache.ignite.internal.Order; @@ -85,7 +86,8 @@ public SnapshotDeleteRequest() { /** {@inheritDoc} */ @Override public int hashCode() { - return Objects.hash(snpName.toLowerCase(), resolvedPath); + // Lower-cased to prevent concurrent snapshot operation ff the name typed with diffent cases. + return Objects.hash(snpName.toLowerCase(Locale.ROOT), resolvedPath); } /** {@inheritDoc} */ 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..5c4804208fdcb 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,8 +659,10 @@ 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 (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 " + 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 77b52eb15ad8d..fd8a38d2253cf 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 @@ -115,8 +115,6 @@ public static Collection runParams() { @Override public void afterTestSnapshot() throws Exception { super.afterTestSnapshot(); - G.allGrids(); - cleanPersistenceDir(); } @@ -307,7 +305,7 @@ public void testDeletionInIgnite() throws Exception { IgniteSnapshotManager snpMgr = snp(grid(0)); var fileSep = File.separator; - var belongsToErrMsg = "belongs to a an Ignite's directory"; + var belongsToErrMsg = "belongs to an Ignite directory"; for (var dir : dirsToTest) { List tests = new ArrayList<>(20); From 0dd70d410bd41b03ac048bf33f4c106f7cd0c0fd Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Thu, 24 Sep 2026 15:05:39 +0300 Subject: [PATCH 59/77] review fix --- .../snapshot/AbstractSnapshotOperationRequest.java | 2 +- .../cache/persistence/snapshot/SnapshotDeleteRequest.java | 8 -------- 2 files changed, 1 insertion(+), 9 deletions(-) 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/SnapshotDeleteRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotDeleteRequest.java index 30fcfc07ba3ea..3d0eed00ddbd5 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 @@ -23,7 +23,6 @@ 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; @@ -64,13 +63,6 @@ public SnapshotDeleteRequest() { 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; } From 3b0da460d996cfc3cb5ecce901f198a77ea86ea9 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Thu, 24 Sep 2026 15:09:27 +0300 Subject: [PATCH 60/77] review fix --- .../snapshot/SnapshotDeleteProcess.java | 39 ------------------- 1 file changed, 39 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 e66b2046be6d8..93c5a6f1b7c1a 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.io.IOException; -import java.nio.file.Path; import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -34,7 +33,6 @@ 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; @@ -58,9 +56,6 @@ 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; @@ -166,13 +161,6 @@ private IgniteInternalFuture deletePhase(UUID ignored, S try { 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; if (!requests.add(req)) { @@ -273,25 +261,6 @@ private File resolvePath(@Nullable String path) throws IOException { return res.getCanonicalFile(); } - /** */ - 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 an Ignite directory"; - } - } - - return null; - } - /** */ private void reducePhase(UUID reqId, Map results, Map errors) { var clusterOpFut = clusterOpFuts.get(reqId); @@ -387,14 +356,6 @@ 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); - } - /** */ private static class MetaFuturesReducer implements IgniteReducer { /** Serial version uid. */ From be040de74ee53659438688cba7eabe41ef7daf5f Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Thu, 24 Sep 2026 15:13:43 +0300 Subject: [PATCH 61/77] revert test --- .../IgniteClusterSnapshotDeleteTest.java | 75 ------------------- 1 file changed, 75 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 fd8a38d2253cf..c928d1cbf48b5 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 @@ -22,7 +22,6 @@ 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; @@ -36,7 +35,6 @@ 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; @@ -279,79 +277,6 @@ 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 an Ignite 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 { From 4a0f8b2f46e0ba149cfad9e71cda25ce3d4fbf87 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Thu, 24 Sep 2026 18:29:02 +0300 Subject: [PATCH 62/77] review fixes --- .../snapshot/IgniteClusterSnapshotSelfTest.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) 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..6cddfdcdac838 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,22 @@ public void testConcurrentSnapshotDeleteOperation() throws Exception { ); } + /** + * Tests that snapshot create detects concurrent deletion, or detects still existing snapshot or successfully + * proceeds if snapshot already deleted. + */ + @Test + public void testConcurrentSnapshotDeleteOperation2() throws Exception { + doTestConcurrentSnapshotDeleteOperation( + () -> { + startGridsMultiThreaded(3); + }, + () -> 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. */ From b3f121331edafcd6b552a2b368ab4e8d35a67d1f Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Thu, 24 Sep 2026 19:28:28 +0300 Subject: [PATCH 63/77] review fixes --- docs/_docs/snapshots/snapshots.adoc | 1 - .../snapshot/SnapshotDeleteCommand.java | 3 +-- .../snapshot/IgniteClusterSnapshotSelfTest.java | 16 ---------------- 3 files changed, 1 insertion(+), 19 deletions(-) diff --git a/docs/_docs/snapshots/snapshots.adoc b/docs/_docs/snapshots/snapshots.adoc index 4f420e6a59b28..0e0240a66e9dc 100644 --- a/docs/_docs/snapshots/snapshots.adoc +++ b/docs/_docs/snapshots/snapshots.adoc @@ -329,7 +329,6 @@ The delete operation is subject to the following limitations: * 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 non-snapshot work directory, the operation fails. * The removal operation is not subject to the snapshot operation status and cancel requests. == 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 ba766df6bc26a..37cc826881784 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 @@ -123,8 +123,7 @@ private static String nodeIdsStrLst(Collection uuids) { U.nl() + U.nl() + "If the security is enabled, the operation requires the snapshot administration permissions." + U.nl() + U.nl() + - "Deletion in not snapshots Ignite's directories and deletion of any data without or corrupted snapshot " + - "metadata are prohibited." + + "Deletion of any data without (valid) snapshot metadata is prohibited." + U.nl() + U.nl() + "The operation cannot be reverted." + U.nl() + U.nl() + 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 6cddfdcdac838..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 @@ -633,22 +633,6 @@ public void testConcurrentSnapshotDeleteOperation() throws Exception { ); } - /** - * Tests that snapshot create detects concurrent deletion, or detects still existing snapshot or successfully - * proceeds if snapshot already deleted. - */ - @Test - public void testConcurrentSnapshotDeleteOperation2() throws Exception { - doTestConcurrentSnapshotDeleteOperation( - () -> { - startGridsMultiThreaded(3); - }, - () -> 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. */ From 67122e06c76bcf7a944b206fe49733660e39e92a Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Fri, 25 Sep 2026 11:13:10 +0300 Subject: [PATCH 64/77] review fixes --- .../management/snapshot/SnapshotDeleteCommand.java | 4 ++-- .../snapshot/SnapshotDeleteCommandArg.java | 12 ++++++------ 2 files changed, 8 insertions(+), 8 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 37cc826881784..adf1731bbc736 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 @@ -119,9 +119,9 @@ private static String nodeIdsStrLst(Collection uuids) { /** {@inheritDoc} */ @Override public String confirmationPrompt(SnapshotDeleteCommandArg arg) { - return "This operation will completely remove snapshot: '" + arg.snapshotName() + "' and all its incrementals." + + return "This operation will completely remove snapshot: '" + arg.snapshotName() + "' and all its incremental snapshots." + U.nl() + U.nl() + - "If the security is enabled, the operation requires the snapshot administration permissions." + + "The operation may require the snapshot administration permissions." + U.nl() + U.nl() + "Deletion of any data without (valid) snapshot metadata is prohibited." + 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 f038d5cf1b3ba..ee251f7afd741 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 @@ -32,31 +32,31 @@ public class SnapshotDeleteCommandArg extends IgniteDataTransferObject { @Order(0) @Positional @Argument(description = "Snapshot name") - String snapshotName; + @Nullable String snapshotName; /** */ @Order(1) - @Argument(example = "path/to/directory", optional = true, description = "Path to snapshot location directory. If not specified " + + @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 snapshotName() { + public @Nullable String snapshotName() { return snapshotName; } /** */ - public void snapshotName(String snapshotName) { + public void snapshotName(@Nullable String snapshotName) { this.snapshotName = snapshotName; } /** */ - @Nullable public String src() { + public @Nullable String src() { return src; } /** */ - public void src(String src) { + public void src(@Nullable String src) { this.src = src; } } From 6fe41e1f05c048ec6ffc65ab672291ddb05d9308 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Fri, 25 Sep 2026 11:49:57 +0300 Subject: [PATCH 65/77] review fixes --- docs/_docs/snapshots/snapshots.adoc | 2 +- .../cache/persistence/snapshot/SnapshotDeleteProcess.java | 4 ++-- .../cache/persistence/snapshot/SnapshotDeleteRequest.java | 6 ++---- .../snapshot/IgniteClusterSnapshotDeleteTest.java | 2 -- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/docs/_docs/snapshots/snapshots.adoc b/docs/_docs/snapshots/snapshots.adoc index 0e0240a66e9dc..b24f88f41a8e3 100644 --- a/docs/_docs/snapshots/snapshots.adoc +++ b/docs/_docs/snapshots/snapshots.adoc @@ -329,7 +329,7 @@ The delete operation is subject to the following limitations: * 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. -* The removal operation is not subject to the snapshot operation status and cancel requests. +* The deletion operation is independent of the status of the snapshot operation and cancellation requests. == Getting Snapshot Operation Status 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 93c5a6f1b7c1a..3beba8a6bbe5d 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 @@ -78,7 +78,7 @@ public class SnapshotDeleteProcess { * @param ctx Kernal context. */ public SnapshotDeleteProcess(GridKernalContext ctx) { - this.kctx = ctx; + kctx = ctx; log = ctx.log(getClass()); @@ -250,7 +250,7 @@ else if (!deleted) /** */ private File resolvePath(@Nullable String path) throws IOException { - var res = kctx.pdsFolderResolver().fileTree().snapshotsRoot(); + File res = kctx.pdsFolderResolver().fileTree().snapshotsRoot(); if (path != null) { File reqPath = new File(path); 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 3d0eed00ddbd5..3e4874b66bdec 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 @@ -18,7 +18,6 @@ package org.apache.ignite.internal.processors.cache.persistence.snapshot; import java.io.File; -import java.util.Locale; import java.util.Objects; import java.util.UUID; import org.apache.ignite.internal.Order; @@ -73,13 +72,12 @@ public SnapshotDeleteRequest() { SnapshotDeleteRequest other = (SnapshotDeleteRequest)o; - return snpName.equalsIgnoreCase(other.snpName) && Objects.equals(resolvedPath, other.resolvedPath); + return Objects.equals(resolvedPath, other.resolvedPath); } /** {@inheritDoc} */ @Override public int hashCode() { - // Lower-cased to prevent concurrent snapshot operation ff the name typed with diffent cases. - return Objects.hash(snpName.toLowerCase(Locale.ROOT), resolvedPath); + return Objects.hash(resolvedPath); } /** {@inheritDoc} */ 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 c928d1cbf48b5..5008e42980709 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 @@ -46,7 +46,6 @@ 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; @@ -65,7 +64,6 @@ import static org.junit.Assume.assumeTrue; /** */ -@RunWith(Parameterized.class) public class IgniteClusterSnapshotDeleteTest extends AbstractSnapshotSelfTest { /** */ private boolean separatedWorkDir; From e4ae1dbbaca59f59777f621d9b4df0d1cf70283a Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Fri, 25 Sep 2026 12:12:07 +0300 Subject: [PATCH 66/77] - sync --- .../persistence/snapshot/SnapshotDeleteProcess.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 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 3beba8a6bbe5d..f87d20841e63f 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 @@ -107,12 +107,10 @@ public IgniteFuture start(String snpName, @Nullable 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."); + if (interrupted || kctx.isStopping()) + throw new NodeStoppingException("Failed to start snapshot delete process: node is stopping."); - clusterOpFuts.put(reqId, clusterOpFut); - } + clusterOpFuts.put(reqId, clusterOpFut); SnapshotDeleteRequest req = new SnapshotDeleteRequest(reqId, snpName, snpPath); @@ -347,9 +345,7 @@ public boolean isDeleting(String snpName, @Nullable String snpPath) { * @param err The interrupt reason. */ void interrupt(Throwable err) { - synchronized (clusterOpFuts) { - interrupted = true; - } + interrupted = true; clusterOpFuts.forEach((reqId, clusterOpFut) -> clusterOpFut.onDone(err)); From ddf9c1c074959634d7f666a450afc9166c09a387 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Fri, 25 Sep 2026 12:51:04 +0300 Subject: [PATCH 67/77] review fix --- .../cache/persistence/snapshot/SnapshotDeleteProcess.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 f87d20841e63f..9bbecd82ffae4 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 @@ -97,7 +97,7 @@ public IgniteFuture start(String snpName, @Nullable 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 + ']')); + "The snapshot deletion feature isn't activated yet.")); return new IgniteFutureImpl<>(clusterOpFut); } From 4a29b493a6fbc68f48726fd6ae94942d5c6d5390 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Fri, 25 Sep 2026 14:35:32 +0300 Subject: [PATCH 68/77] review fixes --- docs/_docs/snapshots/snapshots.adoc | 4 ++-- .../util/GridCommandHandlerDeleteSnapshotTest.java | 2 +- .../persistence/snapshot/SnapshotDeleteProcess.java | 8 ++++---- .../persistence/snapshot/SnapshotDeleteRequest.java | 4 ++-- .../snapshot/IgniteClusterSnapshotDeleteTest.java | 10 +++++----- .../ducktest/tests/ContinuousDataLoadApplication.java | 2 +- 6 files changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/_docs/snapshots/snapshots.adoc b/docs/_docs/snapshots/snapshots.adoc index b24f88f41a8e3..4106b932ce418 100644 --- a/docs/_docs/snapshots/snapshots.adoc +++ b/docs/_docs/snapshots/snapshots.adoc @@ -315,8 +315,8 @@ tab:Windows[] # 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 the snapshot "snapshot_09062021" located in the "C:\tmp\ignite\snapshots" folder. +control.bat --snapshot delete snapshot_09062021 --src C:\tmp\ignite\snapshots ---- -- 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 b867bf211e8ae..a6ba921c07eab 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 and custom snapshot patches. + // 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); 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 9bbecd82ffae4..19d2f9e52e39f 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 @@ -143,17 +143,17 @@ private IgniteInternalFuture deletePhase(UUID ignored, S 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 + ']')); + "Snapshot with the same 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 the same 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 the same name is being checked [req=" + req + ']')); } try { @@ -169,7 +169,7 @@ private IgniteInternalFuture deletePhase(UUID ignored, S 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. + // initial cluster topology and actual snapshot folder names. List locMetas = kctx.cache().context().snapshotMgr().readSnapshotMetadatas(snpFiles, false); if (locMetas.isEmpty()) { 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 3e4874b66bdec..790851a5e4b11 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 @@ -72,12 +72,12 @@ public SnapshotDeleteRequest() { SnapshotDeleteRequest other = (SnapshotDeleteRequest)o; - return Objects.equals(resolvedPath, other.resolvedPath); + return snpName.equalsIgnoreCase(other.snpName) && Objects.equals(resolvedPath, other.resolvedPath); } /** {@inheritDoc} */ @Override public int hashCode() { - return Objects.hash(resolvedPath); + return Objects.hash(snpName.toLowerCase(java.util.Locale.ROOT), resolvedPath); } /** {@inheritDoc} */ 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 5008e42980709..048cc1108bffb 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 @@ -473,7 +473,7 @@ public void testSnapshotDeleteWhenCheckInProgress() throws Exception { F.asList(CHECK_SNAPSHOT_METAS, CHECK_SNAPSHOT_PARTS), true, null, - "Snapshot with this name is being checked", + "Snapshot with the same name is being checked", false ); } @@ -494,7 +494,7 @@ public void testSnapshotDeleteWhenCreateInProgress() throws Exception { if (incremental) snp(grid(0)).createSnapshot(SNAPSHOT_NAME).get(getTestTimeout()); }, - "Snapshot with this name is being created", + "Snapshot with the same name is being created", false ); } @@ -519,7 +519,7 @@ public void testSnapshotDeleteWhenRestoreBegins() throws Exception { awaitPartitionMapExchange(); }, - "Snapshot with this name is being checked", + "Snapshot with the same name is being checked", false ); } @@ -555,7 +555,7 @@ public void testSnapshotDeleteWhenRestoreInProgress() throws Exception { awaitPartitionMapExchange(); }, - "Snapshot with this name is being restored", + "Snapshot with the same name is being restored", false ); } @@ -601,7 +601,7 @@ public void testSnapshotDeleteWhenRestoreProgressFails() throws Exception { return delegate; }); }, - "Snapshot with this name is being restored", + "Snapshot with the same name is being restored", true ); } 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 e491071501f10735d13d5e6768c6ba2bb254798a Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Fri, 25 Sep 2026 15:18:02 +0300 Subject: [PATCH 69/77] review fixes --- .../snapshot/IgniteSnapshotManager.java | 83 +++++++------------ .../snapshot/SnapshotDeleteProcess.java | 15 ++-- .../snapshot/AbstractSnapshotSelfTest.java | 6 +- .../IgniteClusterSnapshotDeleteTest.java | 17 ++-- 4 files changed, 47 insertions(+), 74 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 307768452c8d0..ba704b54d5634 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 @@ -723,83 +723,60 @@ public void deleteLocalSnapshot(File snpDir) { pdsSettings.consistentId().toString() ); - deleteLocalSnapshot(sft, null); + deleteLocalSnapshot(sft); } /** * 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. + * @return A pair of {@code boolean} values. The first indicates whether snapshot was completely deleted. The second + * indicates whether the snapshot was found at all. */ - public boolean deleteLocalSnapshot(SnapshotFileTree sft, @Nullable AtomicBoolean existsFlag) { - var exFlag0 = new AtomicBoolean(); - - sft.allStorages().forEach(s -> { - if (s.exists()) - exFlag0.set(true); - }); + public T2 deleteLocalSnapshot(SnapshotFileTree sft) { + T2 res = new T2<>(false, false); if (sft.root().exists()) - exFlag0.set(true); - - if (existsFlag != null) - existsFlag.set(exFlag0.get()); + res.set2(true); + else { + for (File storage : sft.allStorages().toList()) { + if (storage.exists()) + res.set2(true); + } + } - // Nothing to delete. - if (!exFlag0.get()) - return false; + // Not found at all - nothing to delete. + if (!res.get2()) + return res; - boolean res = true; + // Assume we'll successed. + res.set1(true); // The 'exists' checks are for a concurrent deletion when nodes share their working and snapshot directories. // Nodes may steal removal jobs and the files aren't synchronized. There are gaps between and `exists()` and `delete()`. - // We try to delete first. If snapshot data wasn't deleted because it doesn't already exist is not a delete error here. + // We try to delete first. If snapshot data wasn't deleted because it doesn't already exist is not a removal error here. try { - if (!U.delete(sft.binaryMeta()) && sft.binaryMeta().exists()) - res = false; + for (var f : F.asList(sft.meta(), sft.marshaller().getParentFile())) { + if (!f.delete() && f.exists()) + res.set1(false); + } for (var s : sft.allStorages().toList()) { if (!U.delete(s) && s.exists()) - res = false; + res.set1(false); } - if (!U.delete(sft.meta()) && sft.meta().exists()) - res = false; - - if (!deleteDirectory(sft.binaryMetaRoot()) && sft.binaryMetaRoot().exists()) - res = false; - - if (!deleteDirectory(sft.marshaller()) && sft.marshaller().exists()) - res = false; - - if (!deleteDirectory(sft.incrementsRoot()) && sft.incrementsRoot().exists()) - res = false; - - // Delete parent dir which is {snapshot_root}/db if empty. - if (!sft.marshaller().getParentFile().delete() && sft.marshaller().getParentFile().exists()) - res = false; - - // Delete root dir which is {snapshot_root} if empty. - if (!sft.root().delete() && sft.root().exists()) - res = false; + for (File p : F.asList(sft.binaryMeta(), sft.binaryMetaRoot(), sft.marshaller(), sft.incrementsRoot(), sft.root())) { + if (!deleteDirectory(p) && p.exists()) + res.set1(false); + } } catch (Exception e) { log.warning("Failed to delete local snapshot [snpName=" + sft.name() + ']', e); - return false; + res.set1(false); } - for (var s : sft.allStorages().toList()) { - if (s.exists()) - return false; - } - - if (sft.root().exists()) - return false; - return res; } @@ -1366,7 +1343,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. @@ -4082,7 +4059,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 19d2f9e52e39f..822bda9f7dbd4 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 @@ -26,7 +26,6 @@ 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; @@ -39,6 +38,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.internal.util.typedef.internal.U; import org.apache.ignite.lang.IgniteFuture; import org.apache.ignite.lang.IgniteReducer; @@ -193,23 +193,22 @@ private IgniteInternalFuture deletePhase(UUID ignored, S 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); + T2 deleted = snpMgr.deleteLocalSnapshot(byMetaSft); SnapshotDeleteResponse.DeleteStatus status; - if (foundFlag.get()) { - if (deleted && log.isInfoEnabled()) + // If found. + if (deleted.get2()) { + if (deleted.get1() && log.isInfoEnabled()) log.info("Snapshot successfully deleted [req=" + req + ']'); - else if (!deleted) + else if (!deleted.get1()) log.warning("Snapshot deleted not completely [req=" + req + ']'); - status = deleted + status = deleted.get1() ? SnapshotDeleteResponse.DeleteStatus.DELETED : SnapshotDeleteResponse.DeleteStatus.PARTLY; } 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..d0f0af2fe7681 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 @@ -39,7 +39,6 @@ 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; @@ -93,6 +92,7 @@ import org.apache.ignite.internal.util.tostring.GridToStringInclude; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.G; +import org.apache.ignite.internal.util.typedef.T2; import org.apache.ignite.internal.util.typedef.internal.CU; import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.internal.util.typedef.internal.U; @@ -865,7 +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, @Nullable AtomicBoolean existsFlag) { + @Override public T2 deleteLocalSnapshot(SnapshotFileTree sft) { delProcInitLatch.countDown(); try { @@ -875,7 +875,7 @@ protected void doTestConcurrentSnapshotDeleteOperation( throw new RuntimeException("Interrupted.", e); } - return super.deleteLocalSnapshot(sft, existsFlag); + return super.deleteLocalSnapshot(sft); } }; } 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 048cc1108bffb..a970d083d1323 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 @@ -26,7 +26,6 @@ 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; @@ -40,6 +39,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.T2; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.lang.IgniteFuture; import org.apache.ignite.plugin.AbstractTestPluginProvider; @@ -139,14 +139,11 @@ 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) { - if (ctx.localNode().id().equals(grid(1).localNode().id())) { - existsFlag.set(true); - - return false; - } + @Override public T2 deleteLocalSnapshot(SnapshotFileTree sft) { + if (ctx.localNode().id().equals(grid(1).localNode().id())) + return new T2<>(false, true); - return super.deleteLocalSnapshot(sft, existsFlag); + return super.deleteLocalSnapshot(sft); } }; } @@ -361,7 +358,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) { + @Override public T2 deleteLocalSnapshot(SnapshotFileTree sft) { if (ctx.localNode().id().equals(grid(1).localNode().id())) { beginLatch.countDown(); @@ -373,7 +370,7 @@ public void testNodeStopsInTheMiddle() throws Exception { } } - return super.deleteLocalSnapshot(sft, existsFlag); + return super.deleteLocalSnapshot(sft); } }; } From 16cc24cfaeef255d94ad2a1e65c5cf206de69a06 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Fri, 25 Sep 2026 16:36:57 +0300 Subject: [PATCH 70/77] review fixes --- .../snapshot/SnapshotDeleteTask.java | 32 +++++++++++++++++++ .../snapshot/IgniteSnapshotManager.java | 9 +++--- .../snapshot/SnapshotDeleteProcess.java | 8 +++-- 3 files changed, 42 insertions(+), 7 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 f90c9f4d78a4c..d4c84738ec429 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,12 +17,24 @@ package org.apache.ignite.internal.management.snapshot; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import org.apache.ignite.IgniteException; +import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.compute.ComputeJob; +import org.apache.ignite.internal.IgniteEx; 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; +import org.apache.ignite.internal.visor.VisorTaskArgument; +import org.apache.ignite.resources.IgniteInstanceResource; + +import static org.apache.ignite.internal.processors.rollingupgrade.feature.SupportedFeatureRegistry.SNAPSHOT_DELETE_FEATURE; /** * @see IgniteSnapshotManager#deleteSnapshot(String, String) @@ -33,11 +45,31 @@ public class SnapshotDeleteTask extends VisorOneNodeTask job(SnapshotDeleteCommandArg arg) { return new SnapshotDeleteJob(arg, debug); } + /** {@inheritDoc} */ + @Override protected Collection jobNodes(VisorTaskArgument arg) { + return super.jobNodes(arg); + } + + /** {@inheritDoc} */ + @Override protected Map map0( + List subgrid, + VisorTaskArgument arg + ) { + if (!ignite.context().rollingUpgrade().features().isActive(SNAPSHOT_DELETE_FEATURE)) + throw new IgniteException(SnapshotDeleteProcess.OP_REJECT_FEATURE_MSG); + + return super.map0(subgrid, arg); + } + /** */ private static class SnapshotDeleteJob extends SnapshotJob { /** Serial version uid. */ 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 ba704b54d5634..650e50c5235ae 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 @@ -756,17 +756,16 @@ public T2 deleteLocalSnapshot(SnapshotFileTree sft) { // Nodes may steal removal jobs and the files aren't synchronized. There are gaps between and `exists()` and `delete()`. // We try to delete first. If snapshot data wasn't deleted because it doesn't already exist is not a removal error here. try { - for (var f : F.asList(sft.meta(), sft.marshaller().getParentFile())) { - if (!f.delete() && f.exists()) - res.set1(false); - } + if (!sft.meta().delete() && sft.meta().exists()) + res.set1(false); for (var s : sft.allStorages().toList()) { if (!U.delete(s) && s.exists()) res.set1(false); } - for (File p : F.asList(sft.binaryMeta(), sft.binaryMetaRoot(), sft.marshaller(), sft.incrementsRoot(), sft.root())) { + for (File p : F.asList(sft.binaryMeta(), sft.binaryMetaRoot(), sft.marshaller(), sft.incrementsRoot(), + sft.marshaller().getParentFile(), sft.root())) { if (!deleteDirectory(p) && p.exists()) res.set1(false); } 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 822bda9f7dbd4..4768663072a0a 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 @@ -56,6 +56,9 @@ public class SnapshotDeleteProcess { /** Reject operation messages. */ private static final String OP_REJECT_MSG = "Snapshot deletion was rejected. "; + /** */ + public static final String OP_REJECT_FEATURE_MSG = OP_REJECT_MSG + "The snapshot deletion feature isn't activated yet."; + /** Kernal context. */ private final GridKernalContext kctx; @@ -96,8 +99,7 @@ public IgniteFuture start(String snpName, @Nullable 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.")); + clusterOpFut.onDone(new IgniteIllegalStateException(OP_REJECT_FEATURE_MSG)); return new IgniteFutureImpl<>(clusterOpFut); } @@ -366,6 +368,8 @@ private static class MetaFuturesReducer implements IgniteReducer Date: Fri, 25 Sep 2026 16:43:31 +0300 Subject: [PATCH 71/77] review fixes --- .../cache/persistence/snapshot/SnapshotDeleteProcess.java | 4 +--- 1 file changed, 1 insertion(+), 3 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 4768663072a0a..6d11972a9f106 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 @@ -367,9 +367,7 @@ private static class MetaFuturesReducer implements IgniteReducer Date: Fri, 25 Sep 2026 17:03:12 +0300 Subject: [PATCH 72/77] + RU task test --- ...usterSnapshotDeleteRollingUpgradeTest.java | 72 ++++++++++++++++--- 1 file changed, 61 insertions(+), 11 deletions(-) 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 7a9e24a38ce21..c1d8360a32fb3 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 @@ -21,6 +21,7 @@ import java.nio.file.DirectoryStream; import java.nio.file.Path; import java.nio.file.Paths; +import org.apache.ignite.IgniteException; import org.apache.ignite.IgniteIllegalStateException; import org.apache.ignite.cache.CacheAtomicityMode; import org.apache.ignite.cache.CacheMode; @@ -29,10 +30,14 @@ 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.management.snapshot.SnapshotDeleteCommandArg; +import org.apache.ignite.internal.management.snapshot.SnapshotDeleteTask; 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.internal.visor.VisorTaskArgument; import org.apache.ignite.testframework.GridTestUtils; import org.junit.Test; @@ -111,7 +116,7 @@ public void testParallelRollingUpgradeInProgress() throws Exception { assertTrue(spi(grid(testNodeIx)).waitForBlocked(1, getTestTimeout())); - ensureSnapshotDeletionFailed(); + ensureSnapshotDeletionFailed(false); spi(grid(testNodeIx)).stopBlock(); @@ -131,6 +136,17 @@ public void testParallelRollingUpgradeInProgress() throws Exception { /** */ @Test public void testSnapshotDeleteFeature() throws Exception { + doTestSnapshotDeleteFeature(false); + } + + /** */ + @Test + public void testSnapshotDeleteFeatureWithTask() throws Exception { + doTestSnapshotDeleteFeature(true); + } + + /** */ + private void doTestSnapshotDeleteFeature(boolean useTask) throws Exception { for (int i = 0; i < ALL_GRIDS; i++) startGrid(i, "2.19.0", i >= ALL_GRIDS - CLIENTS); @@ -138,7 +154,7 @@ public void testSnapshotDeleteFeature() throws Exception { createCacheAndSnapshot(1); - ensureSnapshotDeletionFailed(); + ensureSnapshotDeletionFailed(useTask); ru(grid(0)).enableVersionUpgrade(); @@ -147,7 +163,7 @@ public void testSnapshotDeleteFeature() throws Exception { upgradeNodeVersion(i, "2.19.1"); - ensureSnapshotDeletionFailed(); + ensureSnapshotDeletionFailed(useTask); } ru(grid(1)).finalizeClusterVersion(); @@ -158,7 +174,23 @@ public void testSnapshotDeleteFeature() throws Exception { assertTrue(waitForCondition(() -> !ru(grid(i0)).isVersionUpgradeEnabled(), getTestTimeout())); } - assertEquals(3, snp(1).deleteSnapshot(SNP_NAME, null).get().completedNodes().size()); + SnapshotDeleteProcessResult delRes; + + if (useTask) { + SnapshotDeleteCommandArg args = new SnapshotDeleteCommandArg(); + + args.snapshotName(SNP_NAME); + + IgniteEx ig = grid(1); + + delRes = ig.compute().execute(new SnapshotDeleteTask(), new VisorTaskArgument<>(ig.localNode().id(), args, false)).result(); + + assertFalse(delRes == null); + } + else + delRes = snp(1).deleteSnapshot(SNP_NAME, null).get(getTestTimeout()); + + assertEquals(3, delRes.completedNodes().size()); } /** */ @@ -185,16 +217,34 @@ private void createSnapshot(int gridIdx) { } /** */ - private void ensureSnapshotDeletionFailed() { + private void ensureSnapshotDeletionFailed(boolean useTask) { + String err = "The snapshot deletion feature isn't activated yet"; + 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" - ); + if (useTask) { + SnapshotDeleteCommandArg args = new SnapshotDeleteCommandArg(); + + args.snapshotName(SNP_NAME); + + IgniteEx ig = grid(i0); + + assertThrowsAnyCause( + null, + () -> ig.compute().execute(new SnapshotDeleteTask(), new VisorTaskArgument<>(ig.localNode().id(), args, false)), + IgniteException.class, + err + ); + } + else { + assertThrowsAnyCause( + null, + () -> snp(i0).deleteSnapshot(SNP_NAME, null).get(), + IgniteIllegalStateException.class, + err + ); + } } } From e01aadfa73054c2513765fc6fafb02cc30ff5bc4 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Mon, 28 Sep 2026 14:43:41 +0300 Subject: [PATCH 73/77] + character case name tests --- .../snapshot/IgniteSnapshotManager.java | 7 +- .../snapshot/SnapshotDeleteRequest.java | 5 +- .../IgniteClusterSnapshotDeleteTest.java | 247 +++++++++++++++--- 3 files changed, 220 insertions(+), 39 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 650e50c5235ae..94709f6296cf1 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 @@ -1502,7 +1502,7 @@ public boolean isSnapshotCreating() { /** * @return {@code True} if checking of a snapshot with specified name is in progress. */ - public boolean isSnapshotChecking(String snpName) { + public boolean isSnapshotChecking(@Nullable String snpName) { return checkSnpProc.isSnapshotChecking(snpName); } @@ -1559,7 +1559,10 @@ public boolean isRestoring() { * @param snpName Snapshot name. * @return {@code True} if the snapshot restore operation from the specified snapshot is in progress locally. */ - public boolean isRestoring(String snpName) { + public boolean isRestoring(@Nullable String snpName) { + if (snpName == null) + return false; + return snpName.equalsIgnoreCase(restoreCacheGrpProc.restoringSnapshotName()); } 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 790851a5e4b11..4d227a8b2075c 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 @@ -18,6 +18,7 @@ package org.apache.ignite.internal.processors.cache.persistence.snapshot; import java.io.File; +import java.util.Locale; import java.util.Objects; import java.util.UUID; import org.apache.ignite.internal.Order; @@ -61,7 +62,7 @@ public SnapshotDeleteRequest() { */ SnapshotDeleteRequest(UUID reqId, String snpName, @Nullable String snpPath) { this.reqId = reqId; - this.snpName = snpName.trim(); + this.snpName = snpName; this.snpPath = snpPath; } @@ -77,7 +78,7 @@ public SnapshotDeleteRequest() { /** {@inheritDoc} */ @Override public int hashCode() { - return Objects.hash(snpName.toLowerCase(java.util.Locale.ROOT), resolvedPath); + return Objects.hash(snpName.toLowerCase(Locale.ROOT), resolvedPath); } /** {@inheritDoc} */ 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 a970d083d1323..bf2438862f1a9 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 @@ -22,6 +22,7 @@ import java.nio.file.Files; import java.util.ArrayList; import java.util.Collection; +import java.util.Locale; import java.util.Random; import java.util.UUID; import java.util.concurrent.CountDownLatch; @@ -65,6 +66,9 @@ /** */ public class IgniteClusterSnapshotDeleteTest extends AbstractSnapshotSelfTest { + /** */ + private static boolean caseInsensitiveFS; + /** */ private boolean separatedWorkDir; @@ -122,11 +126,24 @@ public static Collection runParams() { cleanPersistenceDir(); } + /** {@inheritDoc} */ + @Override protected void beforeTestsStarted() throws Exception { + super.beforeTestsStarted(); + + File workDir = new File(U.defaultWorkDirectory()); + + workDir.exists(); + + assertTrue(new File(U.defaultWorkDirectory()).exists()); + + caseInsensitiveFS = new File(workDir.getAbsolutePath().toLowerCase()).exists() && + new File(workDir.getAbsolutePath().toUpperCase()).exists(); + } + /** 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)); + assumeCompatibleParameters(); separatedWorkDir = true; @@ -275,8 +292,7 @@ private String folderName(int gridIdx) { /** 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)); + assumeCompatibleParameters(); separatedWorkDir = true; @@ -299,8 +315,7 @@ 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)); + assumeCompatibleParameters(); separatedWorkDir = true; @@ -341,8 +356,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 modes. - assumeTrue(!incremental || !(onlyPrimary || encryption)); + assumeCompatibleParameters(); separatedWorkDir = true; @@ -412,8 +426,7 @@ public void testNodeStopsInTheMiddle() throws Exception { /** 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)); + assumeCompatibleParameters(); startGridsWithCache(3, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); @@ -462,8 +475,7 @@ 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 only-primary and encryption modes. - assumeTrue(!incremental || !(onlyPrimary || encryption)); + assumeCompatibleParameters(); doTestConcurrentSnapshotDelete( () -> new IgniteFutureImpl<>(snp(grid(2)).checkSnapshot(SNAPSHOT_NAME, null, incremental ? 1 : 0)), @@ -471,15 +483,42 @@ public void testSnapshotDeleteWhenCheckInProgress() throws Exception { true, null, "Snapshot with the same name is being checked", + false, false ); } + /** + * Tests that a snapshot deletion is declined when a snapshot check operation is in progress when the delete operation + * uses changed character case in the snapshot name. + * + * @see SnapshotCheckProcess#isSnapshotChecking(String) + */ + @Test + public void testSnapshotDeleteWhenCheckInProgressNameCase() throws Exception { + // For case-insensitive file systems only. + assertTrue(caseInsensitiveFS); + + assumeCompatibleParameters(); + + SnapshotPartitionsVerifyResult res = doTestConcurrentSnapshotDelete( + () -> new IgniteFutureImpl<>(snp(grid(2)).checkSnapshot(SNAPSHOT_NAME.toLowerCase(), null, incremental ? 1 : 0)), + F.asList(CHECK_SNAPSHOT_METAS), + true, + null, + "Snapshot with the same name is being checked", + false, + true + ); + + assertFalse(res == null); + assertTrue(F.isEmpty(res.exceptions())); + } + /** 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)); + assumeCompatibleParameters(); doTestConcurrentSnapshotDelete( () -> snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, incremental, onlyPrimary), @@ -492,15 +531,39 @@ public void testSnapshotDeleteWhenCreateInProgress() throws Exception { snp(grid(0)).createSnapshot(SNAPSHOT_NAME).get(getTestTimeout()); }, "Snapshot with the same name is being created", + false, false ); } + /** + * Tests that a snapshot deletion is declined when a snapshot create operation is in progress when the delete operation + * uses changed character case in the snapshot name. + */ + @Test + public void testSnapshotDeleteWhenCreateInProgressNameCase() throws Exception { + assumeCompatibleParameters(); + + doTestConcurrentSnapshotDelete( + () -> snp(grid(0)).createSnapshot(SNAPSHOT_NAME.toLowerCase(), null, incremental, onlyPrimary), + F.asList(START_SNAPSHOT), + false, + () -> { + snp(grid(0)).deleteSnapshot(SNAPSHOT_NAME.toLowerCase(), null).get(getTestTimeout()); + + if (incremental) + snp(grid(0)).createSnapshot(SNAPSHOT_NAME.toLowerCase()).get(getTestTimeout()); + }, + "Snapshot with the same name is being created", + false, + true + ); + } + /** 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)); + assumeCompatibleParameters(); doTestConcurrentSnapshotDelete( () -> { @@ -517,15 +580,51 @@ public void testSnapshotDeleteWhenRestoreBegins() throws Exception { awaitPartitionMapExchange(); }, "Snapshot with the same name is being checked", + false, false ); } + /** + * Tests that a snapshot deletion is declined when a snapshot restore begins when the delete operation uses + * changed character case in the snapshot name. + * + * @see SnapshotCheckProcess#isSnapshotChecking(String) + */ + @Test + public void testSnapshotDeleteWhenRestoreBeginsNameCase() throws Exception { + // For case-insensitive file systems only. + assumeTrue(caseInsensitiveFS); + + assumeCompatibleParameters(); + + doTestConcurrentSnapshotDelete( + () -> { + if (incremental) + return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME.toLowerCase(Locale.ROOT), null, 1); + else + return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME.toLowerCase(Locale.ROOT), null); + }, + F.asList(CHECK_SNAPSHOT_METAS, CHECK_SNAPSHOT_PARTS), + true, + () -> { + grid(0).destroyCache(DEFAULT_CACHE_NAME); + + awaitPartitionMapExchange(); + }, + "Snapshot with the same name is being checked", + false, + true + ); + + // Ensure that the test exists. + assertEquals(CACHE_KEYS_RANGE, grid(0).cache(DEFAULT_CACHE_NAME).size()); + } + /** 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)); + assumeCompatibleParameters(); var restoreMsgs = F.asList( RESTORE_CACHE_GROUP_SNAPSHOT_PREPARE, @@ -553,10 +652,57 @@ public void testSnapshotDeleteWhenRestoreInProgress() throws Exception { awaitPartitionMapExchange(); }, "Snapshot with the same name is being restored", + false, false ); } + /** + * Tests that a snapshot deletion is declined when a snapshot restore is in progress when the delete operation uses + * changed character case in the snapshot name. + * + * @see IgniteSnapshotManager#isRestoring(String) + */ + @Test + public void testSnapshotDeleteWhenRestoreInProgressNameCase() throws Exception { + // For case-insensitive file systems only. + assumeTrue(caseInsensitiveFS); + assumeCompatibleParameters(); + + 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.toLowerCase(), null, 1); + else + return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME.toLowerCase(), null); + }, + restoreMsgs, + true, + () -> { + grid(0).destroyCache(DEFAULT_CACHE_NAME); + + awaitPartitionMapExchange(); + }, + "Snapshot with the same name is being restored", + false, + true + ); + + // Ensure that the test exists. + assertEquals(CACHE_KEYS_RANGE, grid(0).cache(DEFAULT_CACHE_NAME).size()); + } + /** Tests that a snapshot deletion is declined when a snapshot restore is in progress but fails. */ @Test public void testSnapshotDeleteWhenRestoreProgressFails() throws Exception { @@ -599,7 +745,8 @@ public void testSnapshotDeleteWhenRestoreProgressFails() throws Exception { }); }, "Snapshot with the same name is being restored", - true + true, + false ); } @@ -609,15 +756,18 @@ public void testSnapshotDeleteWhenRestoreProgressFails() 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. + * @param ignoreFirstOpFailure If {@code true}, possible failure of {@code firstOp} is ignored. + * @param ignoreDeleteOpFailure If {@code true}, possible failure of the deletion operation is ignored. Allows to + * focus on the first operation's result. */ - protected void doTestConcurrentSnapshotDelete( - Supplier> firstOp, + protected @Nullable T doTestConcurrentSnapshotDelete( + Supplier> firstOp, Collection msgsToWatch, boolean precreateSnp, @Nullable Runnable prepareIteration, String concurrentMsgErr, - boolean ignoreFirstOpFailure + boolean ignoreFirstOpFailure, + boolean ignoreDeleteOpFailure ) throws Exception { startGridsWithCache(3, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); @@ -626,10 +776,21 @@ protected void doTestConcurrentSnapshotDelete( if (incremental) addIncrementalSnapshot(null); + + if (caseInsensitiveFS) { + // Ensure that if any operation uses variating case in snapshot paths/names, + // they will actually target the same directory. + File snpRoot = new SnapshotFileTree(grid(0).context(), SNAPSHOT_NAME, null).root(); + + assertTrue(snpRoot.equals(new File(snpRoot.getAbsolutePath().toLowerCase()))); + assertTrue(snpRoot.equals(new File(snpRoot.getAbsolutePath().toUpperCase()))); + } } TestRecordingCommunicationSpi commSpi1 = (TestRecordingCommunicationSpi)grid(1).configuration().getCommunicationSpi(); + T res = null; + for (var nodeResMsgType : msgsToWatch) { if (log.isInfoEnabled()) log.info("Iteration with message-to-wait-for type: " + nodeResMsgType); @@ -644,31 +805,42 @@ protected void doTestConcurrentSnapshotDelete( commSpi1.waitForBlocked(1, getTestTimeout()); - assertThrowsAnyCause( - null, - () -> { + if (ignoreDeleteOpFailure) { + try { snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); - - return null; - }, - IgniteIllegalStateException.class, - concurrentMsgErr - ); + } + catch (Exception e) { + if (log.isDebugEnabled()) + log.debug("The deletion operation failed but a failure is expected, err: " + e.getMessage()); + } + } + else { + assertThrowsAnyCause( + null, + () -> snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()), + IgniteIllegalStateException.class, + concurrentMsgErr + ); + } commSpi1.stopBlock(); if (ignoreFirstOpFailure) { try { - firstFut.get(getTestTimeout()); + res = firstFut.get(getTestTimeout()); } catch (Exception e) { if (log.isDebugEnabled()) - log.debug("The first operation failed but a failure is expected. Failure: " + e.getMessage()); + log.debug("The first operation failed but a failure is expected, err: " + e.getMessage()); + + return null; } } else - firstFut.get(getTestTimeout()); + res = firstFut.get(getTestTimeout()); } + + return res; } /** */ @@ -690,4 +862,9 @@ private void addIncrementalSnapshot(@Nullable String path) { throw new RuntimeException("Interrupted.", e); } } + + /** Incremental snapshots don't support only-primary and encryption modes. */ + private void assumeCompatibleParameters() { + assumeTrue(!incremental || !(onlyPrimary || encryption)); + } } From b887fcdb7f1701435e366b248c40528620cb12a8 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Mon, 28 Sep 2026 15:32:09 +0300 Subject: [PATCH 74/77] fixes --- .../snapshot/IgniteSnapshotManager.java | 2 +- .../IgniteClusterSnapshotDeleteTest.java | 69 ++++++------------- 2 files changed, 22 insertions(+), 49 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 94709f6296cf1..2643efc33af4d 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 @@ -1502,7 +1502,7 @@ public boolean isSnapshotCreating() { /** * @return {@code True} if checking of a snapshot with specified name is in progress. */ - public boolean isSnapshotChecking(@Nullable String snpName) { + public boolean isSnapshotChecking(String snpName) { return checkSnpProc.isSnapshotChecking(snpName); } 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 bf2438862f1a9..42a5e765ca191 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 @@ -22,7 +22,6 @@ import java.nio.file.Files; import java.util.ArrayList; import java.util.Collection; -import java.util.Locale; import java.util.Random; import java.util.UUID; import java.util.concurrent.CountDownLatch; @@ -79,23 +78,17 @@ public class IgniteClusterSnapshotDeleteTest extends AbstractSnapshotSelfTest { /** */ private @Nullable String cstIdSuffix; + /** */ + private boolean lowerCasedSnpName; + /** 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; + /** Use {@link #incremental} only. */ + return F.asList( + new Object[] {false, false, false}, + new Object[] {false, false, true} + ); } /** {@inheritDoc} */ @@ -143,8 +136,6 @@ public static Collection runParams() { /** Tests snapshot deletion when one node finds snapshot but fails to delete its data. */ @Test public void testUncompletedNodes() throws Exception { - assumeCompatibleParameters(); - separatedWorkDir = true; // Simulates a deletion error on some node. @@ -292,8 +283,6 @@ private String folderName(int gridIdx) { /** Tests snapshot deletion when one node has no snapshot data. */ @Test public void testEmptyNodes() throws Exception { - assumeCompatibleParameters(); - separatedWorkDir = true; startGridsWithCache(2, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); @@ -315,8 +304,6 @@ public void testEmptyNodes() throws Exception { /** Tests snapshot deletion repeat after an offline node restarts. */ @Test public void testDeletionRepeatAfterOfflineNodeStarts() throws Exception { - assumeCompatibleParameters(); - separatedWorkDir = true; startGridsWithCache(3, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); @@ -356,8 +343,6 @@ public void testDeletionRepeatAfterOfflineNodeStarts() throws Exception { /** Test snapshot deletion process when one node leaves. */ @Test public void testNodeStopsInTheMiddle() throws Exception { - assumeCompatibleParameters(); - separatedWorkDir = true; CountDownLatch beginLatch = new CountDownLatch(1); @@ -426,8 +411,6 @@ public void testNodeStopsInTheMiddle() throws Exception { /** Tests that a concurrent deletion of a snapshot with the same name but different path is allowed. */ @Test public void testConcurrentDeleteOfTheSameSnapshotDifferentPath() throws Exception { - assumeCompatibleParameters(); - startGridsWithCache(3, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(TIMEOUT); @@ -475,8 +458,6 @@ 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 { - assumeCompatibleParameters(); - doTestConcurrentSnapshotDelete( () -> new IgniteFutureImpl<>(snp(grid(2)).checkSnapshot(SNAPSHOT_NAME, null, incremental ? 1 : 0)), F.asList(CHECK_SNAPSHOT_METAS, CHECK_SNAPSHOT_PARTS), @@ -499,7 +480,7 @@ public void testSnapshotDeleteWhenCheckInProgressNameCase() throws Exception { // For case-insensitive file systems only. assertTrue(caseInsensitiveFS); - assumeCompatibleParameters(); + lowerCasedSnpName = true; SnapshotPartitionsVerifyResult res = doTestConcurrentSnapshotDelete( () -> new IgniteFutureImpl<>(snp(grid(2)).checkSnapshot(SNAPSHOT_NAME.toLowerCase(), null, incremental ? 1 : 0)), @@ -518,8 +499,6 @@ public void testSnapshotDeleteWhenCheckInProgressNameCase() throws Exception { /** Tests that a snapshot deletion is declined when a snapshot create operation is in progress. */ @Test public void testSnapshotDeleteWhenCreateInProgress() throws Exception { - assumeCompatibleParameters(); - doTestConcurrentSnapshotDelete( () -> snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, incremental, onlyPrimary), F.asList(START_SNAPSHOT, END_SNAPSHOT), @@ -542,7 +521,7 @@ public void testSnapshotDeleteWhenCreateInProgress() throws Exception { */ @Test public void testSnapshotDeleteWhenCreateInProgressNameCase() throws Exception { - assumeCompatibleParameters(); + lowerCasedSnpName = true; doTestConcurrentSnapshotDelete( () -> snp(grid(0)).createSnapshot(SNAPSHOT_NAME.toLowerCase(), null, incremental, onlyPrimary), @@ -563,8 +542,6 @@ public void testSnapshotDeleteWhenCreateInProgressNameCase() throws Exception { /** Tests that a snapshot deletion is declined when a snapshot restore begins. */ @Test public void testSnapshotDeleteWhenRestoreBegins() throws Exception { - assumeCompatibleParameters(); - doTestConcurrentSnapshotDelete( () -> { if (incremental) @@ -596,14 +573,14 @@ public void testSnapshotDeleteWhenRestoreBeginsNameCase() throws Exception { // For case-insensitive file systems only. assumeTrue(caseInsensitiveFS); - assumeCompatibleParameters(); + lowerCasedSnpName = true; doTestConcurrentSnapshotDelete( () -> { if (incremental) - return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME.toLowerCase(Locale.ROOT), null, 1); + return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME.toLowerCase(), null, 1); else - return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME.toLowerCase(Locale.ROOT), null); + return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME.toLowerCase(), null); }, F.asList(CHECK_SNAPSHOT_METAS, CHECK_SNAPSHOT_PARTS), true, @@ -618,14 +595,12 @@ public void testSnapshotDeleteWhenRestoreBeginsNameCase() throws Exception { ); // Ensure that the test exists. - assertEquals(CACHE_KEYS_RANGE, grid(0).cache(DEFAULT_CACHE_NAME).size()); + assertEquals(incremental ? CACHE_KEYS_RANGE + CACHE_KEYS_RANGE / 4 : CACHE_KEYS_RANGE, grid(0).cache(DEFAULT_CACHE_NAME).size()); } /** Tests that a snapshot deletion is declined when a snapshot restore is in progress. */ @Test public void testSnapshotDeleteWhenRestoreInProgress() throws Exception { - assumeCompatibleParameters(); - var restoreMsgs = F.asList( RESTORE_CACHE_GROUP_SNAPSHOT_PREPARE, RESTORE_CACHE_GROUP_SNAPSHOT_PRELOAD, @@ -667,7 +642,8 @@ public void testSnapshotDeleteWhenRestoreInProgress() throws Exception { public void testSnapshotDeleteWhenRestoreInProgressNameCase() throws Exception { // For case-insensitive file systems only. assumeTrue(caseInsensitiveFS); - assumeCompatibleParameters(); + + lowerCasedSnpName = true; var restoreMsgs = F.asList( RESTORE_CACHE_GROUP_SNAPSHOT_PREPARE, @@ -700,7 +676,7 @@ public void testSnapshotDeleteWhenRestoreInProgressNameCase() throws Exception { ); // Ensure that the test exists. - assertEquals(CACHE_KEYS_RANGE, grid(0).cache(DEFAULT_CACHE_NAME).size()); + assertEquals(incremental ? CACHE_KEYS_RANGE + CACHE_KEYS_RANGE / 4 : CACHE_KEYS_RANGE, grid(0).cache(DEFAULT_CACHE_NAME).size()); } /** Tests that a snapshot deletion is declined when a snapshot restore is in progress but fails. */ @@ -772,7 +748,8 @@ public void testSnapshotDeleteWhenRestoreProgressFails() throws Exception { startGridsWithCache(3, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); if (precreateSnp) { - snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(TIMEOUT); + snp(grid(0)).createSnapshot(lowerCasedSnpName ? SNAPSHOT_NAME.toLowerCase() : SNAPSHOT_NAME, null, false, onlyPrimary) + .get(TIMEOUT); if (incremental) addIncrementalSnapshot(null); @@ -850,7 +827,8 @@ private void addIncrementalSnapshot(@Nullable String path) { ds.addData(i, i); } - snp(grid(0)).createSnapshot(SNAPSHOT_NAME, path, true, onlyPrimary).get(getTestTimeout()); + snp(grid(0)).createSnapshot(lowerCasedSnpName ? SNAPSHOT_NAME.toLowerCase() : SNAPSHOT_NAME, path, true, onlyPrimary) + .get(getTestTimeout()); } /** {@inheritDoc} */ @@ -862,9 +840,4 @@ private void addIncrementalSnapshot(@Nullable String path) { throw new RuntimeException("Interrupted.", e); } } - - /** Incremental snapshots don't support only-primary and encryption modes. */ - private void assumeCompatibleParameters() { - assumeTrue(!incremental || !(onlyPrimary || encryption)); - } } From 8db6b4a4b605370b2028999a58bffcfd2e65c831 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Mon, 28 Sep 2026 15:32:09 +0300 Subject: [PATCH 75/77] review fixes --- .../snapshot/IgniteSnapshotManager.java | 2 +- .../snapshot/SnapshotDeleteProcess.java | 24 ++- ...shotCreationNonDefaultStoragePathTest.java | 3 +- ...usterSnapshotDeleteRollingUpgradeTest.java | 3 +- .../IgniteClusterSnapshotDeleteTest.java | 166 ++++++++---------- 5 files changed, 95 insertions(+), 103 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 94709f6296cf1..2643efc33af4d 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 @@ -1502,7 +1502,7 @@ public boolean isSnapshotCreating() { /** * @return {@code True} if checking of a snapshot with specified name is in progress. */ - public boolean isSnapshotChecking(@Nullable String snpName) { + public boolean isSnapshotChecking(String snpName) { return checkSnpProc.isSnapshotChecking(snpName); } 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 6d11972a9f106..3192a2848546c 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 @@ -59,6 +59,18 @@ public class SnapshotDeleteProcess { /** */ public static final String OP_REJECT_FEATURE_MSG = OP_REJECT_MSG + "The snapshot deletion feature isn't activated yet."; + /** */ + private static final String CONCURRENT_OP_PREF = "Snapshot with the same name is being "; + + /** */ + public static final String BEING_CREATED_PREF = CONCURRENT_OP_PREF + "created "; + + /** */ + public static final String BEING_RESTORED_PREF = CONCURRENT_OP_PREF + "restored "; + + /** */ + public static final String BEING_CHECKED_PREF = CONCURRENT_OP_PREF + "checked "; + /** Kernal context. */ private final GridKernalContext kctx; @@ -144,18 +156,18 @@ private IgniteInternalFuture deletePhase(UUID ignored, S var curCreateRq = snpMgr.currentCreateRequest(); if (curCreateRq != null && curCreateRq.snpName.equalsIgnoreCase(req.snpName)) { - return new GridFinishedFuture<>(new IgniteIllegalStateException(OP_REJECT_MSG + - "Snapshot with the same name is being created [req=" + req + ']')); + return new GridFinishedFuture<>(new IgniteIllegalStateException(OP_REJECT_MSG + BEING_CREATED_PREF + + "[req=" + req + ']')); } if (snpMgr.isRestoring(req.snpName)) { - return new GridFinishedFuture<>(new IgniteIllegalStateException(OP_REJECT_MSG + - "Snapshot with the same name is being restored [req=" + req + ']')); + return new GridFinishedFuture<>(new IgniteIllegalStateException(OP_REJECT_MSG + BEING_RESTORED_PREF + + "[req=" + req + ']')); } if (snpMgr.isSnapshotChecking(req.snpName)) { - return new GridFinishedFuture<>(new IgniteIllegalStateException(OP_REJECT_MSG + - "Snapshot with the same name is being checked [req=" + req + ']')); + return new GridFinishedFuture<>(new IgniteIllegalStateException(OP_REJECT_MSG + BEING_CHECKED_PREF + + "[req=" + req + ']')); } try { 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/IgniteClusterSnapshotDeleteRollingUpgradeTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteRollingUpgradeTest.java index c1d8360a32fb3..5d98357453cb1 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 @@ -35,7 +35,6 @@ import org.apache.ignite.internal.management.snapshot.SnapshotDeleteTask; 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.internal.visor.VisorTaskArgument; import org.apache.ignite.testframework.GridTestUtils; @@ -130,7 +129,7 @@ public void testParallelRollingUpgradeInProgress() throws Exception { assertTrue(waitForCondition(() -> !ru(grid(i0)).isVersionUpgradeEnabled(), getTestTimeout())); } - assertFalse(F.isEmpty(snp(1).deleteSnapshot(SNP_NAME, null).get(getTestTimeout()).completedNodes)); + assertFalse(snp(1).deleteSnapshot(SNP_NAME, null).get(getTestTimeout()).completedNodes().isEmpty()); } /** */ 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 bf2438862f1a9..8db1f79814fb5 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 @@ -22,7 +22,6 @@ import java.nio.file.Files; import java.util.ArrayList; import java.util.Collection; -import java.util.Locale; import java.util.Random; import java.util.UUID; import java.util.concurrent.CountDownLatch; @@ -79,23 +78,17 @@ public class IgniteClusterSnapshotDeleteTest extends AbstractSnapshotSelfTest { /** */ private @Nullable String cstIdSuffix; + /** */ + private boolean lowerCasedSnpName; + /** 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; + /** Use {@link #incremental} only. */ + return F.asList( + new Object[] {false, false, false}, + new Object[] {false, false, true} + ); } /** {@inheritDoc} */ @@ -143,8 +136,6 @@ public static Collection runParams() { /** Tests snapshot deletion when one node finds snapshot but fails to delete its data. */ @Test public void testUncompletedNodes() throws Exception { - assumeCompatibleParameters(); - separatedWorkDir = true; // Simulates a deletion error on some node. @@ -176,14 +167,14 @@ public void testUncompletedNodes() throws Exception { if (incremental) addIncrementalSnapshot(null); - var delSnpRes = snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); + SnapshotDeleteProcessResult 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())); + assertTrue(delSnpRes.emptyNodes().isEmpty()); + assertEquals(1, delSnpRes.uncompletedNodes().size()); + assertTrue(delSnpRes.uncompletedNodes().containsKey(grid(1).localNode().id())); } - /** */ + /** Tests the snapshot deletion when cluster restarts with changed nodes consistent ids. */ @Test public void testDeleteOtherConsistentId() throws Exception { startGridsWithSnapshot(3, CACHE_KEYS_RANGE, false); @@ -194,40 +185,49 @@ public void testDeleteOtherConsistentId() throws Exception { startGridsMultiThreaded(3); - snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); + SnapshotDeleteProcessResult delRes = snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); for (var ig : G.allGrids()) { assertTrue(Files.list(((IgniteEx)ig).context().pdsFolderResolver().fileTree().snapshotsRoot().toPath()) .findFirst().isEmpty()); } + + assertEquals(3, delRes.absentBaselines().size()); } - /** */ + /** Tests that snapshot is not deleted if snaphot metadata is not found. Shared working directory. */ @Test public void testDeleteSnapshotNoMetaSharedDirectory() throws Exception { doTestDeleteNotSnapshot(false, false); } - /** */ + /** Tests that snapshot is not deleted if snaphot metadata is not found. Dedicated working directories. */ @Test public void testDeleteSnapshotNoMetaDedicatedDirectories() throws Exception { doTestDeleteNotSnapshot(true, false); } - /** */ + /** Tests that snapshot is not deleted if snaphot metadata cannot be read (corrupted). Shared working directory. */ @Test public void testDeleteSnapshotCorruptedMetaSharedDirectory() throws Exception { doTestDeleteNotSnapshot(false, true); } - /** */ + /** Tests that snapshot is not deleted if snaphot metadata cannot be read (corrupted). Dedicated working directories. */ @Test public void testDeleteSnapshotCorruptedMetaDedicatedDirectories() throws Exception { doTestDeleteNotSnapshot(true, true); } - /** */ - protected void doTestDeleteNotSnapshot(boolean separatedWorkDir, boolean corruptFile) throws Exception { + /** + * Tests that snapshot is not deleted if snaphot metadata isn't found or cannot be read (corrupted). + * + * @param separatedWorkDir If {@code true}, the dedicated dirictories is used for the nodes. If {@code false}, + * the nodes use a shared working directory. + * @param corruptSnpMeta If {@code true}, corrupts the snapshot metadata file. If {@code false}, removes the snapshot + * metadata file + */ + protected void doTestDeleteNotSnapshot(boolean separatedWorkDir, boolean corruptSnpMeta) throws Exception { this.separatedWorkDir = separatedWorkDir; startGridsWithCache(3, CACHE_KEYS_RANGE, valueBuilder(), dfltCacheCfg); @@ -245,7 +245,7 @@ protected void doTestDeleteNotSnapshot(boolean separatedWorkDir, boolean corrupt assertTrue(snpSft.meta().exists()); - if (corruptFile) { + if (corruptSnpMeta) { try (var rwf = new RandomAccessFile(snpSft.meta(), "rw")) { byte[] slop = new byte[128]; @@ -264,14 +264,14 @@ protected void doTestDeleteNotSnapshot(boolean separatedWorkDir, boolean corrupt // 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(delSnpRes.uncompletedNodes().isEmpty()); + 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()); + 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()); @@ -292,8 +292,6 @@ private String folderName(int gridIdx) { /** Tests snapshot deletion when one node has no snapshot data. */ @Test public void testEmptyNodes() throws Exception { - assumeCompatibleParameters(); - separatedWorkDir = true; startGridsWithCache(2, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); @@ -303,20 +301,18 @@ public void testEmptyNodes() throws Exception { if (incremental) addIncrementalSnapshot(null); - startGrid(G.allGrids().size()); + IgniteEx additionalNode = startGrid(G.allGrids().size()); - var delSnpRes = snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); + SnapshotDeleteProcessResult 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)); + assertFalse(delSnpRes.emptyNodes().isEmpty()); + assertTrue(delSnpRes.emptyNodes().containsKey(additionalNode.localNode().id())); + assertTrue(delSnpRes.uncompletedNodes().isEmpty()); } /** Tests snapshot deletion repeat after an offline node restarts. */ @Test public void testDeletionRepeatAfterOfflineNodeStarts() throws Exception { - assumeCompatibleParameters(); - separatedWorkDir = true; startGridsWithCache(3, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); @@ -332,13 +328,13 @@ public void testDeletionRepeatAfterOfflineNodeStarts() throws Exception { stopGrid(stoppedNodeIdx); - var delSnpRes = snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); + SnapshotDeleteProcessResult delSnpRes = snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); - assertEquals(2, delSnpRes.completedNodes.size()); - assertFalse(delSnpRes.completedNodes.containsKey(stoppedNodeId)); + assertEquals(2, delSnpRes.completedNodes().size()); + assertFalse(delSnpRes.completedNodes().containsKey(stoppedNodeId)); - assertTrue(F.isEmpty(delSnpRes.uncompletedNodes)); - assertTrue(F.isEmpty(delSnpRes.emptyNodes)); + assertTrue(delSnpRes.uncompletedNodes().isEmpty()); + assertTrue(delSnpRes.emptyNodes().isEmpty()); startGrid(stoppedNodeIdx); @@ -346,18 +342,16 @@ public void testDeletionRepeatAfterOfflineNodeStarts() throws Exception { delSnpRes = snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); - assertEquals(1, delSnpRes.completedNodes.size()); - assertTrue(delSnpRes.completedNodes.containsKey(stoppedNodeId)); + assertEquals(1, delSnpRes.completedNodes().size()); + assertTrue(delSnpRes.completedNodes().containsKey(stoppedNodeId)); - assertTrue(F.isEmpty(delSnpRes.uncompletedNodes)); - assertEquals(2, delSnpRes.emptyNodes.size()); + assertTrue(delSnpRes.uncompletedNodes().isEmpty()); + assertEquals(2, delSnpRes.emptyNodes().size()); } /** Test snapshot deletion process when one node leaves. */ @Test public void testNodeStopsInTheMiddle() throws Exception { - assumeCompatibleParameters(); - separatedWorkDir = true; CountDownLatch beginLatch = new CountDownLatch(1); @@ -412,22 +406,20 @@ public void testNodeStopsInTheMiddle() throws Exception { var delRes = delFut.get(getTestTimeout()); - assertEquals(2, delRes.completedNodes.size()); - assertFalse(delRes.completedNodes.containsKey(stoppedGridId)); + 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())); + 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 { - assumeCompatibleParameters(); - startGridsWithCache(3, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(TIMEOUT); @@ -475,14 +467,12 @@ 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 { - assumeCompatibleParameters(); - 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 the same name is being checked", + SnapshotDeleteProcess.BEING_CHECKED_PREF, false, false ); @@ -499,14 +489,14 @@ public void testSnapshotDeleteWhenCheckInProgressNameCase() throws Exception { // For case-insensitive file systems only. assertTrue(caseInsensitiveFS); - assumeCompatibleParameters(); + lowerCasedSnpName = true; SnapshotPartitionsVerifyResult res = doTestConcurrentSnapshotDelete( () -> new IgniteFutureImpl<>(snp(grid(2)).checkSnapshot(SNAPSHOT_NAME.toLowerCase(), null, incremental ? 1 : 0)), F.asList(CHECK_SNAPSHOT_METAS), true, null, - "Snapshot with the same name is being checked", + SnapshotDeleteProcess.BEING_CREATED_PREF, false, true ); @@ -518,8 +508,6 @@ public void testSnapshotDeleteWhenCheckInProgressNameCase() throws Exception { /** Tests that a snapshot deletion is declined when a snapshot create operation is in progress. */ @Test public void testSnapshotDeleteWhenCreateInProgress() throws Exception { - assumeCompatibleParameters(); - doTestConcurrentSnapshotDelete( () -> snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, incremental, onlyPrimary), F.asList(START_SNAPSHOT, END_SNAPSHOT), @@ -542,7 +530,7 @@ public void testSnapshotDeleteWhenCreateInProgress() throws Exception { */ @Test public void testSnapshotDeleteWhenCreateInProgressNameCase() throws Exception { - assumeCompatibleParameters(); + lowerCasedSnpName = true; doTestConcurrentSnapshotDelete( () -> snp(grid(0)).createSnapshot(SNAPSHOT_NAME.toLowerCase(), null, incremental, onlyPrimary), @@ -563,8 +551,6 @@ public void testSnapshotDeleteWhenCreateInProgressNameCase() throws Exception { /** Tests that a snapshot deletion is declined when a snapshot restore begins. */ @Test public void testSnapshotDeleteWhenRestoreBegins() throws Exception { - assumeCompatibleParameters(); - doTestConcurrentSnapshotDelete( () -> { if (incremental) @@ -579,7 +565,7 @@ public void testSnapshotDeleteWhenRestoreBegins() throws Exception { awaitPartitionMapExchange(); }, - "Snapshot with the same name is being checked", + SnapshotDeleteProcess.BEING_CHECKED_PREF, false, false ); @@ -596,14 +582,14 @@ public void testSnapshotDeleteWhenRestoreBeginsNameCase() throws Exception { // For case-insensitive file systems only. assumeTrue(caseInsensitiveFS); - assumeCompatibleParameters(); + lowerCasedSnpName = true; doTestConcurrentSnapshotDelete( () -> { if (incremental) - return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME.toLowerCase(Locale.ROOT), null, 1); + return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME.toLowerCase(), null, 1); else - return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME.toLowerCase(Locale.ROOT), null); + return snp(grid(2)).restoreSnapshot(SNAPSHOT_NAME.toLowerCase(), null); }, F.asList(CHECK_SNAPSHOT_METAS, CHECK_SNAPSHOT_PARTS), true, @@ -612,20 +598,18 @@ public void testSnapshotDeleteWhenRestoreBeginsNameCase() throws Exception { awaitPartitionMapExchange(); }, - "Snapshot with the same name is being checked", + SnapshotDeleteProcess.BEING_CREATED_PREF, false, true ); // Ensure that the test exists. - assertEquals(CACHE_KEYS_RANGE, grid(0).cache(DEFAULT_CACHE_NAME).size()); + assertEquals(incremental ? CACHE_KEYS_RANGE + CACHE_KEYS_RANGE / 4 : CACHE_KEYS_RANGE, grid(0).cache(DEFAULT_CACHE_NAME).size()); } /** Tests that a snapshot deletion is declined when a snapshot restore is in progress. */ @Test public void testSnapshotDeleteWhenRestoreInProgress() throws Exception { - assumeCompatibleParameters(); - var restoreMsgs = F.asList( RESTORE_CACHE_GROUP_SNAPSHOT_PREPARE, RESTORE_CACHE_GROUP_SNAPSHOT_PRELOAD, @@ -651,7 +635,7 @@ public void testSnapshotDeleteWhenRestoreInProgress() throws Exception { awaitPartitionMapExchange(); }, - "Snapshot with the same name is being restored", + SnapshotDeleteProcess.BEING_RESTORED_PREF, false, false ); @@ -667,7 +651,8 @@ public void testSnapshotDeleteWhenRestoreInProgress() throws Exception { public void testSnapshotDeleteWhenRestoreInProgressNameCase() throws Exception { // For case-insensitive file systems only. assumeTrue(caseInsensitiveFS); - assumeCompatibleParameters(); + + lowerCasedSnpName = true; var restoreMsgs = F.asList( RESTORE_CACHE_GROUP_SNAPSHOT_PREPARE, @@ -694,13 +679,13 @@ public void testSnapshotDeleteWhenRestoreInProgressNameCase() throws Exception { awaitPartitionMapExchange(); }, - "Snapshot with the same name is being restored", + SnapshotDeleteProcess.BEING_RESTORED_PREF, false, true ); // Ensure that the test exists. - assertEquals(CACHE_KEYS_RANGE, grid(0).cache(DEFAULT_CACHE_NAME).size()); + assertEquals(incremental ? CACHE_KEYS_RANGE + CACHE_KEYS_RANGE / 4 : CACHE_KEYS_RANGE, grid(0).cache(DEFAULT_CACHE_NAME).size()); } /** Tests that a snapshot deletion is declined when a snapshot restore is in progress but fails. */ @@ -744,7 +729,7 @@ public void testSnapshotDeleteWhenRestoreProgressFails() throws Exception { return delegate; }); }, - "Snapshot with the same name is being restored", + SnapshotDeleteProcess.BEING_RESTORED_PREF, true, false ); @@ -772,7 +757,8 @@ public void testSnapshotDeleteWhenRestoreProgressFails() throws Exception { startGridsWithCache(3, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); if (precreateSnp) { - snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(TIMEOUT); + snp(grid(0)).createSnapshot(lowerCasedSnpName ? SNAPSHOT_NAME.toLowerCase() : SNAPSHOT_NAME, null, false, onlyPrimary) + .get(TIMEOUT); if (incremental) addIncrementalSnapshot(null); @@ -801,7 +787,7 @@ public void testSnapshotDeleteWhenRestoreProgressFails() throws Exception { commSpi1.blockMessages((node, msg) -> msg instanceof SingleNodeMessage msg0 && msg0.type() == nodeResMsgType.ordinal()); - var firstFut = firstOp.get(); + IgniteFuture firstFut = firstOp.get(); commSpi1.waitForBlocked(1, getTestTimeout()); @@ -850,7 +836,8 @@ private void addIncrementalSnapshot(@Nullable String path) { ds.addData(i, i); } - snp(grid(0)).createSnapshot(SNAPSHOT_NAME, path, true, onlyPrimary).get(getTestTimeout()); + snp(grid(0)).createSnapshot(lowerCasedSnpName ? SNAPSHOT_NAME.toLowerCase() : SNAPSHOT_NAME, path, true, onlyPrimary) + .get(getTestTimeout()); } /** {@inheritDoc} */ @@ -862,9 +849,4 @@ private void addIncrementalSnapshot(@Nullable String path) { throw new RuntimeException("Interrupted.", e); } } - - /** Incremental snapshots don't support only-primary and encryption modes. */ - private void assumeCompatibleParameters() { - assumeTrue(!incremental || !(onlyPrimary || encryption)); - } } From e22ddf754b4edb82e1a7d1e59ddd77d4fd03b3d6 Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Mon, 28 Sep 2026 20:46:16 +0300 Subject: [PATCH 76/77] review fixes --- .../persistence/filename/SharedFileTree.java | 2 +- .../snapshot/IgniteSnapshotManager.java | 17 ++- .../snapshot/SnapshotDeleteProcess.java | 17 ++- ...shotCreationNonDefaultStoragePathTest.java | 12 ++- .../snapshot/AbstractSnapshotSelfTest.java | 6 +- .../IgniteClusterSnapshotDeleteTest.java | 100 ++++++++++++++++-- .../AbstractIncrementalSnapshotTest.java | 2 +- .../IncrementalSnapshotNodeFailureTest.java | 2 +- 8 files changed, 138 insertions(+), 20 deletions(-) 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..b4224b9077ef5 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 @@ -49,7 +49,7 @@ public class SharedFileTree { public static final String MARSHALLER_DIR = "marshaller"; /** Database default folder. */ - protected static final String DB_DIR = "db"; + public static final String DB_DIR = "db"; /** Root(work) directory. */ protected final File root; 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 2643efc33af4d..ba21d9e1efcc0 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 @@ -760,12 +760,23 @@ public T2 deleteLocalSnapshot(SnapshotFileTree sft) { res.set1(false); for (var s : sft.allStorages().toList()) { - if (!U.delete(s) && s.exists()) + if (!deleteDirectory(s) && s.exists()) + res.set1(false); + + // Deletes "db" directory also beyond the snapshot root in the case when extra snapshot storages are configured. + s = s.getParentFile(); + + if (!deleteDirectory(s) && s.exists()) + res.set1(false); + + s = s.getParentFile(); + + // Deletes the snapshot root directory beyond the snapshot root in the case when extra snapshot storages are configured. + if (!sft.root().equals(s) && !s.delete() && s.exists()) res.set1(false); } - for (File p : F.asList(sft.binaryMeta(), sft.binaryMetaRoot(), sft.marshaller(), sft.incrementsRoot(), - sft.marshaller().getParentFile(), sft.root())) { + for (File p : F.asList(sft.binaryMeta(), sft.binaryMetaRoot(), sft.marshaller(), sft.incrementsRoot(), sft.root())) { if (!deleteDirectory(p) && p.exists()) res.set1(false); } 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 3192a2848546c..db8302d6af3cc 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 @@ -28,6 +28,7 @@ import java.util.concurrent.ConcurrentHashMap; 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.NodeStoppingException; @@ -208,8 +209,14 @@ private IgniteInternalFuture deletePhase(UUID ignored, S kctx.pools().getSnapshotExecutorService().submit(() -> { try { // Read file tree of the snapshot. - var byMetaSft = new SnapshotFileTree(kctx, req.snpName, path0.getAbsolutePath(), meta.folderName(), - meta.consId); + var byMetaSft = new SnapshotFileTree( + kctx.config(), + kctx.pdsFolderResolver().fileTree(), + req.snpName, + req.snpPath == null ? null : path0.getAbsolutePath(), + meta.folderName(), + meta.consId + ); T2 deleted = snpMgr.deleteLocalSnapshot(byMetaSft); @@ -320,8 +327,10 @@ private void reducePhase(UUID reqId, Map results, } }); - kctx.discovery().baselineNodes(kctx.discovery().topologyVersionEx()).stream() - .map(bn -> bn.consistentId().toString()).toList().forEach(snpNodes::remove); + Collection curBaseline = kctx.discovery().discoCache().aliveBaselineNodes(); + + if (!F.isEmpty(curBaseline)) + curBaseline.stream().map(bn -> bn.consistentId().toString()).toList().forEach(snpNodes::remove); clusterOpFut.onDone(new SnapshotDeleteProcessResult(completedNodes, uncompletedNodes, emptyNodes, snpNodes)); } 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..5e9fc29e0aea7 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 @@ -54,9 +54,19 @@ public class SnapshotCreationNonDefaultStoragePathTest extends AbstractDataRegio /** {@inheritDoc} */ @Override protected DataStorageConfiguration dataStorageConfiguration() { - return new DataStorageConfiguration() + // TODO: revert + extraSnpPaths = new String[] { + "snp_path", + "snp_path2" + }; + + DataStorageConfiguration res = new DataStorageConfiguration() .setStoragePath(storagePath(STORAGE_PATH)) .setExtraStoragePaths(storagePath(STORAGE_PATH_2), storagePath(IDX_PATH)); + + res.setExtraSnapshotPaths(extraSnpPaths); + + return res; } /** {@inheritDoc} */ 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 d0f0af2fe7681..7ef08a37cb882 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 @@ -734,8 +734,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(); } /** @@ -778,7 +778,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/IgniteClusterSnapshotDeleteTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/IgniteClusterSnapshotDeleteTest.java index 8db1f79814fb5..e27646cf1790d 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 @@ -27,7 +27,9 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; +import org.apache.ignite.Ignite; import org.apache.ignite.IgniteIllegalStateException; +import org.apache.ignite.configuration.DataStorageConfiguration; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.IgniteEx; import org.apache.ignite.internal.TestRecordingCommunicationSpi; @@ -65,12 +67,21 @@ /** */ public class IgniteClusterSnapshotDeleteTest extends AbstractSnapshotSelfTest { + /** */ + private static final int INC_CACHE_KEYS_RANGE = CACHE_KEYS_RANGE + CACHE_KEYS_RANGE / 4; + + /** Extra storage path. */ + private static final String EXT_STORAGE_PATH = "extStorage"; + /** */ private static boolean caseInsensitiveFS; /** */ private boolean separatedWorkDir; + /** */ + private boolean extraStorages; + /** */ @Parameter(2) public boolean incremental = true; @@ -78,9 +89,12 @@ public class IgniteClusterSnapshotDeleteTest extends AbstractSnapshotSelfTest { /** */ private @Nullable String cstIdSuffix; - /** */ + /** Sets the extra snapshot storage to {@link DataStorageConfiguration#setExtraSnapshotPaths(String...)}. */ private boolean lowerCasedSnpName; + /** */ + private @Nullable String[] extStoragePaths; + /** Parameters. */ @Parameterized.Parameters(name = "encryption={0}, onlyPrimary={1}, incremental={2}") public static Collection runParams() { @@ -95,12 +109,29 @@ public static Collection runParams() { @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception { var 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); if (cstIdSuffix != null) cfg.setConsistentId(cfg.getConsistentId().toString() + '_' + cstIdSuffix); + 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; } @@ -133,6 +164,62 @@ public static Collection runParams() { new File(workDir.getAbsolutePath().toUpperCase()).exists(); } + /** */ + @Test + public void testExtraStoragesDeleted() throws Exception { + extraStorages = true; + + dfltCacheCfg = null; + + startGridsMultiThreaded(3); + + assertFalse(F.isEmpty(extStoragePaths)); + + assertTrue(grid(0).cache(DEFAULT_CACHE_NAME) == null); + + dfltCacheCfg = defaultCacheConfiguration(); + + // Works only with a shared work directory. + dfltCacheCfg.setStoragePaths(extStoragePaths); + + grid(0).createCache(dfltCacheCfg); + awaitPartitionMapExchange(); + + // Fills the cache. + startGridsWithCache(0, CACHE_KEYS_RANGE, i -> i, dfltCacheCfg); + + snp(grid(0)).createSnapshot(SNAPSHOT_NAME, null, false, onlyPrimary).get(getTestTimeout()); + + if (incremental) + addIncrementalSnapshot(null); + + for (Ignite ig : G.allGrids()) { + SnapshotFileTree snpTree = new SnapshotFileTree(((IgniteEx)ig).context(), SNAPSHOT_NAME, null); + + assertEquals(2, snpTree.extraStorages().size()); + + for (File extSnpStorage : snpTree.allStorages().toList()) { + assertTrue(extSnpStorage.exists()); + assertTrue(extSnpStorage.isDirectory()); + } + } + + snp(grid(1)).deleteSnapshot(SNAPSHOT_NAME, null).get(getTestTimeout()); + + for (Ignite ig : G.allGrids()) { + SnapshotFileTree snpTree = new SnapshotFileTree(((IgniteEx)ig).context(), SNAPSHOT_NAME, null); + + for (File extSnpStorage : snpTree.allStorages().toList()) { + // Snapshot root, even in an external storage. + extSnpStorage = extSnpStorage.getParentFile().getParentFile(); + + assertEquals(SNAPSHOT_NAME, extSnpStorage.getName()); + + assertFalse(extSnpStorage.exists()); + } + } + } + /** Tests snapshot deletion when one node finds snapshot but fails to delete its data. */ @Test public void testUncompletedNodes() throws Exception { @@ -144,6 +231,7 @@ public void testUncompletedNodes() throws Exception { return "TestSnpMgrProvider"; } + // Simulates the deletion failure. @Override public T createComponent(PluginContext ctx, Class cls) { if (IgniteSnapshotManager.class.isAssignableFrom(cls)) { return (T)new IgniteSnapshotManager(((IgniteEx)ctx.grid()).context()) { @@ -604,7 +692,7 @@ public void testSnapshotDeleteWhenRestoreBeginsNameCase() throws Exception { ); // Ensure that the test exists. - assertEquals(incremental ? CACHE_KEYS_RANGE + CACHE_KEYS_RANGE / 4 : CACHE_KEYS_RANGE, grid(0).cache(DEFAULT_CACHE_NAME).size()); + assertEquals(incremental ? INC_CACHE_KEYS_RANGE : CACHE_KEYS_RANGE, grid(0).cache(DEFAULT_CACHE_NAME).size()); } /** Tests that a snapshot deletion is declined when a snapshot restore is in progress. */ @@ -685,7 +773,7 @@ public void testSnapshotDeleteWhenRestoreInProgressNameCase() throws Exception { ); // Ensure that the test exists. - assertEquals(incremental ? CACHE_KEYS_RANGE + CACHE_KEYS_RANGE / 4 : CACHE_KEYS_RANGE, grid(0).cache(DEFAULT_CACHE_NAME).size()); + assertEquals(incremental ? INC_CACHE_KEYS_RANGE : CACHE_KEYS_RANGE, grid(0).cache(DEFAULT_CACHE_NAME).size()); } /** Tests that a snapshot deletion is declined when a snapshot restore is in progress but fails. */ @@ -832,7 +920,7 @@ public void testSnapshotDeleteWhenRestoreProgressFails() throws Exception { /** */ 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++) + for (int i = CACHE_KEYS_RANGE; i < INC_CACHE_KEYS_RANGE; i++) ds.addData(i, i); } 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 5ea197bbe8cbaf6da29d76b866cd0575819d801f Mon Sep 17 00:00:00 2001 From: Steshin Vladimir Date: Mon, 28 Sep 2026 20:50:26 +0300 Subject: [PATCH 77/77] revert todo --- ...SnapshotCreationNonDefaultStoragePathTest.java | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) 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 5e9fc29e0aea7..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 @@ -54,19 +54,9 @@ public class SnapshotCreationNonDefaultStoragePathTest extends AbstractDataRegio /** {@inheritDoc} */ @Override protected DataStorageConfiguration dataStorageConfiguration() { - // TODO: revert - extraSnpPaths = new String[] { - "snp_path", - "snp_path2" - }; - - DataStorageConfiguration res = new DataStorageConfiguration() + return new DataStorageConfiguration() .setStoragePath(storagePath(STORAGE_PATH)) .setExtraStoragePaths(storagePath(STORAGE_PATH_2), storagePath(IDX_PATH)); - - res.setExtraSnapshotPaths(extraSnpPaths); - - return res; } /** {@inheritDoc} */ @@ -98,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);