From eb6273cbe3d140aa686a432e8fbeccc6dad57723 Mon Sep 17 00:00:00 2001 From: Vladimir Steshin Date: Mon, 7 Sep 2026 19:35:45 +0300 Subject: [PATCH 01/32] 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/32] 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/32] 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/32] 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/32] 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/32] 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/32] + 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/32] 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/32] 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/32] + 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/32] 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/32] + 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/32] + 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/32] 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/32] 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/32] +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/32] 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/32] + 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/32] + 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/32] 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/32] 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/32] + 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/32] 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/32] 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/32] + 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/32] + 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/32] + 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/32] + 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/32] + 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/32] + 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/32] 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/32] + 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 {