timoninmaxim commented on code in PR #11897: URL: https://github.com/apache/ignite/pull/11897#discussion_r2301665354
########## modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotCheckProcess.java: ########## @@ -0,0 +1,752 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.ignite.internal.processors.cache.persistence.snapshot; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Collectors; +import org.apache.ignite.IgniteException; +import org.apache.ignite.IgniteLogger; +import org.apache.ignite.cluster.ClusterNode; +import org.apache.ignite.internal.GridKernalContext; +import org.apache.ignite.internal.IgniteInternalFuture; +import org.apache.ignite.internal.NodeStoppingException; +import org.apache.ignite.internal.cluster.ClusterTopologyCheckedException; +import org.apache.ignite.internal.management.cache.IdleVerifyResult; +import org.apache.ignite.internal.management.cache.PartitionKey; +import org.apache.ignite.internal.processors.cache.persistence.filename.SnapshotFileTree; +import org.apache.ignite.internal.processors.cache.verify.PartitionHashRecord; +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.tostring.GridToStringInclude; +import org.apache.ignite.internal.util.typedef.F; +import org.apache.ignite.internal.util.typedef.internal.CU; +import org.apache.ignite.internal.util.typedef.internal.S; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.jetbrains.annotations.Nullable; + +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.lang.ClusterNodeFunc.node2id; + +/** Distributed process of snapshot checking (with the partition hashes). */ +public class SnapshotCheckProcess { + /** */ + private final IgniteLogger log; + + /** */ + private final GridKernalContext kctx; + + /** Snapshot checker. */ + private final SnapshotChecker snpChecker; + + /** Operation contexts by name. */ + private final Map<String, SnapshotCheckContext> contexts = new ConcurrentHashMap<>(); + + /** Cluster-wide operation futures per snapshot called from current node. */ + private final Map<UUID, GridFutureAdapter<SnapshotPartitionsVerifyTaskResult>> clusterOpFuts = new ConcurrentHashMap<>(); + + /** Check metas first phase subprocess. */ + private final DistributedProcess<SnapshotCheckProcessRequest, SnapshotCheckResponse> phase1CheckMetas; + + /** Partition hashes second phase subprocess. */ + private final DistributedProcess<SnapshotCheckProcessRequest, SnapshotCheckResponse> phase2PartsHashes; + + /** Stop node lock. */ + private boolean nodeStopping; + + /** */ + public SnapshotCheckProcess(GridKernalContext kctx) { + this.kctx = kctx; + + snpChecker = new SnapshotChecker(kctx); + + log = kctx.log(getClass()); + + phase1CheckMetas = new DistributedProcess<>(kctx, CHECK_SNAPSHOT_METAS, this::prepareAndCheckMetas, + this::reducePreparationAndMetasCheck); + + phase2PartsHashes = new DistributedProcess<>(kctx, CHECK_SNAPSHOT_PARTS, this::validateParts, + this::reduceValidatePartsAndFinish); + } + + /** + * Stops all the processes with the passed exception. + * + * @param err The interrupt reason. + */ + void interrupt(Throwable err) { + // Prevents starting new processes in #prepareAndCheckMetas. + synchronized (contexts) { + nodeStopping = true; + } + + contexts.forEach((snpName, ctx) -> ctx.locProcFut.onDone(err)); + + clusterOpFuts.forEach((reqId, fut) -> fut.onDone(err)); + } + + /** Phase 2 and process finish. */ + private IgniteInternalFuture<?> reduceValidatePartsAndFinish( + UUID reqId, + Map<UUID, SnapshotCheckResponse> results, + Map<UUID, Throwable> errors + ) { + SnapshotCheckContext ctx = context(null, reqId); + + if (ctx == null) + return new GridFinishedFuture<>(); + + contexts.remove(ctx.req.snapshotName()); + + if (log.isInfoEnabled()) + log.info("Finished snapshot validation [req=" + ctx.req + ']'); + + GridFutureAdapter<SnapshotPartitionsVerifyTaskResult> clusterOpFut = clusterOpFuts.get(reqId); + + if (clusterOpFut == null) + return new GridFinishedFuture<>(); + + if (F.isEmpty(errors)) { + ClusterTopologyCheckedException ex = checkNodeLeft(ctx.req.nodes(), results.keySet()); + + if (ex != null) { + clusterOpFut.onDone(ex); + + return new GridFinishedFuture<>(); + } + } + + if (ctx.req.incrementalIndex() > 0) { + SnapshotFileTree sft; + + if (ctx.locFileTree == null) + sft = null; + else if (kctx.config().getConsistentId() == null) + sft = null; + else + sft = ctx.locFileTree.get(kctx.config().getConsistentId().toString()); + + reduceIncrementalResults(sft, ctx.req.incrementalIndex(), ctx.req.nodes(), ctx.clusterMetas, results, errors, clusterOpFut); + } + else if (ctx.req.allRestoreHandlers()) + reduceCustomHandlersResults(ctx, results, errors, clusterOpFut); + else + reducePartitionsHashesResults(ctx.clusterMetas, results, errors, clusterOpFut); + + return new GridFinishedFuture<>(); + } + + /** */ + private void reduceIncrementalResults( + SnapshotFileTree sft, + int incIdx, + Set<UUID> requiredNodes, + Map<ClusterNode, List<SnapshotMetadata>> clusterMetas, + Map<UUID, SnapshotCheckResponse> results, + Map<UUID, Throwable> errors, + GridFutureAdapter<SnapshotPartitionsVerifyTaskResult> fut + ) { + Map<ClusterNode, IncrementalSnapshotVerificationTaskResult> perNodeResults = new HashMap<>(); + + for (Map.Entry<UUID, SnapshotCheckResponse> resEntry : results.entrySet()) { + UUID nodeId = resEntry.getKey(); + + SnapshotCheckResponse incResp = resEntry.getValue(); + + if (incResp == null || !requiredNodes.contains(nodeId)) + continue; + + perNodeResults.put(kctx.cluster().get().node(nodeId), incResp.result()); + + if (F.isEmpty(incResp.exceptions())) + continue; + + errors.putIfAbsent(nodeId, asException(F.firstValue(incResp.exceptions()))); + } + + IdleVerifyResult chkRes = snpChecker.reduceIncrementalResults(sft, incIdx, perNodeResults, mapErrors(errors)); + + fut.onDone(new SnapshotPartitionsVerifyTaskResult(clusterMetas, chkRes)); + } + + /** */ + private void reduceCustomHandlersResults( + SnapshotCheckContext ctx, + Map<UUID, SnapshotCheckResponse> results, + Map<UUID, Throwable> errors, + GridFutureAdapter<SnapshotPartitionsVerifyTaskResult> fut + ) { + try { + if (!errors.isEmpty()) + throw F.firstValue(errors); + + // Check responses: checking node -> snapshot part's consistent id -> handler name -> handler result. + Map<ClusterNode, Map<Object, Map<String, SnapshotHandlerResult<?>>>> reduced = new HashMap<>(); + + for (Map.Entry<UUID, SnapshotCheckResponse> respEntry : results.entrySet()) { + SnapshotCheckResponse nodeResp = respEntry.getValue(); + + if (nodeResp == null) + continue; + + if (!F.isEmpty(nodeResp.exceptions())) + throw F.firstValue(nodeResp.exceptions()); + + UUID nodeId = respEntry.getKey(); + + Map<String, Map<String, SnapshotHandlerResult<Object>>> cstHndRes = nodeResp.result(); + + cstHndRes.forEach((consId, respPerConsIdMap) -> { + // Reduced map of the handlers results per snapshot part's consistent id for certain node. + Map<Object, Map<String, SnapshotHandlerResult<?>>> nodePerConsIdResultMap + = reduced.computeIfAbsent(kctx.cluster().get().node(nodeId), n -> new HashMap<>()); + + respPerConsIdMap.forEach((hndId, hndRes) -> + nodePerConsIdResultMap.computeIfAbsent(consId, cstId -> new HashMap<>()).put(hndId, hndRes)); + }); + } + + snpChecker.checkCustomHandlersResults(ctx.req.snapshotName(), reduced); + + fut.onDone(new SnapshotPartitionsVerifyTaskResult(ctx.clusterMetas, null)); + } + catch (Throwable err) { + fut.onDone(err); + } + } + + /** */ + private void reducePartitionsHashesResults( + Map<ClusterNode, List<SnapshotMetadata>> clusterMetas, + Map<UUID, SnapshotCheckResponse> results, + Map<UUID, Throwable> errors, + GridFutureAdapter<SnapshotPartitionsVerifyTaskResult> fut + ) { + IdleVerifyResult.Builder bldr = IdleVerifyResult.builder(); + + Map<ClusterNode, Exception> errors0 = mapErrors(errors); + + if (!results.isEmpty()) { + if (!errors0.isEmpty()) + bldr.exceptions(errors0); + + for (Map.Entry<UUID, SnapshotCheckResponse> respEntry : results.entrySet()) { + SnapshotCheckResponse resp = respEntry.getValue(); + + if (resp == null) + continue; + + if (!F.isEmpty(resp.exceptions())) { + ClusterNode node = kctx.cluster().get().node(respEntry.getKey()); + + bldr.addException(node, asException(F.firstValue(resp.exceptions()))); + } + + Map<String, Map<PartitionKey, PartitionHashRecord>> partsHashesRes = resp.result(); + + partsHashesRes.forEach((consId, partsPerConsId) -> bldr.addPartitionHashes(partsPerConsId)); + } + + fut.onDone(new SnapshotPartitionsVerifyTaskResult(clusterMetas, bldr.build())); + } + else + fut.onDone(new IgniteSnapshotVerifyException(errors0)); + } + + /** Phase 2 beginning. */ + private IgniteInternalFuture<SnapshotCheckResponse> validateParts(SnapshotCheckProcessRequest req) { + if (!req.nodes().contains(kctx.localNodeId())) + return new GridFinishedFuture<>(); + + SnapshotCheckContext ctx = context(req.snapshotName(), req.requestId()); + + assert ctx != null; + + if (F.isEmpty(ctx.metas)) + return new GridFinishedFuture<>(); + + GridFutureAdapter<SnapshotCheckResponse> phaseFut = ctx.phaseFuture(); + + // Might be already finished by asynchronous leave of a required node. + if (!phaseFut.isDone()) { + CompletableFuture<SnapshotCheckResponse> workingFut; + + if (req.incrementalIndex() > 0) { + assert !req.allRestoreHandlers() : "Snapshot handlers aren't supported for incremental snapshot."; + + workingFut = incrementalFuture(ctx); + } + else if (req.allRestoreHandlers()) + workingFut = allHandlersFuture(ctx); + else + workingFut = partitionsHashesFuture(ctx); + + workingFut.whenComplete((res, err) -> { + if (err != null) + phaseFut.onDone(err); + else + phaseFut.onDone(res); + }); + } + + return phaseFut; + } + + /** @return A composed future of increment checks for each consistent id regarding {@link SnapshotCheckContext#metas}. */ + private CompletableFuture<SnapshotCheckResponse> incrementalFuture(SnapshotCheckContext ctx) { + // Incremental snapshots do not support working on other topology. Only single meta and snapshot part can be processed. + SnapshotMetadata meta = ctx.metas.get(0); + + CompletableFuture<SnapshotCheckResponse> resFut = new CompletableFuture<>(); + + CompletableFuture<IncrementalSnapshotVerificationTaskResult> workingFut = snpChecker.checkIncrementalSnapshot( + ctx.locFileTree.get(meta.consistentId()), ctx.req.incrementalIndex()); + + workingFut.whenComplete((res, err) -> { + if (err != null) + resFut.completeExceptionally(err); + else + resFut.complete(new SnapshotCheckResponse(res, null)); + }); + + return resFut; + } + + /** @return A composed future of partitions checks for each consistent id regarding {@link SnapshotCheckContext#metas}. */ + private CompletableFuture<SnapshotCheckResponse> partitionsHashesFuture(SnapshotCheckContext ctx) { + // Per metas result: consistent id -> check results per partition key. + Map<String, Map<PartitionKey, PartitionHashRecord>> perMetaResults = new ConcurrentHashMap<>(ctx.metas.size(), 1.0f); + // Per consistent id. + Map<String, Throwable> exceptions = new ConcurrentHashMap<>(ctx.metas.size(), 1.0f); + CompletableFuture<SnapshotCheckResponse> composedFut = new CompletableFuture<>(); + AtomicInteger metasProcessed = new AtomicInteger(ctx.metas.size()); + + for (SnapshotMetadata meta : ctx.metas) { + CompletableFuture<Map<PartitionKey, PartitionHashRecord>> metaFut = snpChecker.checkPartitions( + meta, + ctx.locFileTree.get(meta.consistentId()), + ctx.req.groups(), + false, + ctx.req.fullCheck() + ); + + metaFut.whenComplete((res, err) -> { + if (err != null) + exceptions.put(meta.consistentId(), err); + else if (!F.isEmpty(res)) + perMetaResults.put(meta.consistentId(), res); + + if (metasProcessed.decrementAndGet() == 0) + composedFut.complete(new SnapshotCheckResponse(perMetaResults, exceptions)); + }); + } + + return composedFut; + } + + /** + * @return A composed future of all the snapshot handlers for each consistent id regarding {@link SnapshotCheckContext#metas}. + * @see IgniteSnapshotManager#handlers() + */ + private CompletableFuture<SnapshotCheckResponse> allHandlersFuture(SnapshotCheckContext ctx) { + // Per metas result: snapshot part's consistent id -> check result per handler name. + Map<String, Map<String, SnapshotHandlerResult<Object>>> perMetaResults = new ConcurrentHashMap<>(ctx.metas.size(), 1.0f); + // Per consistent id. + Map<String, Throwable> exceptions = new ConcurrentHashMap<>(ctx.metas.size(), 1.0f); + CompletableFuture<SnapshotCheckResponse> composedFut = new CompletableFuture<>(); + AtomicInteger metasProcessed = new AtomicInteger(ctx.metas.size()); + + for (SnapshotMetadata meta : ctx.metas) { + CompletableFuture<Map<String, SnapshotHandlerResult<Object>>> metaFut = snpChecker.invokeCustomHandlers(meta, + ctx.locFileTree.get(meta.consistentId()), ctx.req.groups(), ctx.req.fullCheck()); + + metaFut.whenComplete((res, err) -> { + if (err != null) + exceptions.put(meta.consistentId(), err); + else if (!F.isEmpty(res)) + perMetaResults.put(meta.consistentId(), res); + + if (metasProcessed.decrementAndGet() == 0) + composedFut.complete(new SnapshotCheckResponse(perMetaResults, exceptions)); + }); + } + + return composedFut; + } + + /** */ + private Map<ClusterNode, Exception> mapErrors(Map<UUID, Throwable> errors) { + return errors.entrySet().stream().collect(Collectors.toMap(e -> kctx.cluster().get().node(e.getKey()), + e -> asException(e.getValue()))); + } + + /** */ + private static Exception asException(Throwable th) { + return th instanceof Exception ? (Exception)th : new IgniteException(th); + } + + /** + * @param snpName Snapshot name. If {@code null}, ignored. + * @param reqId If {@code ctxId} is {@code null}, is used to find the operation context. + * @return Current snapshot checking context by {@code ctxId} or {@code reqId}. + */ + private @Nullable SnapshotCheckContext context(@Nullable String snpName, UUID reqId) { + return snpName == null + ? contexts.values().stream().filter(ctx0 -> ctx0.req.requestId().equals(reqId)).findFirst().orElse(null) + : contexts.get(snpName); + } + + /** Phase 1 beginning: prepare, collect and check local metas. */ + private IgniteInternalFuture<SnapshotCheckResponse> prepareAndCheckMetas(SnapshotCheckProcessRequest req) { + if (!req.nodes().contains(kctx.localNodeId()) && clusterOpFuts.get(req.requestId()) == null) + return new GridFinishedFuture<>(); + + SnapshotCheckContext ctx; + + synchronized (contexts) { + if (nodeStopping) + return new GridFinishedFuture<>(new NodeStoppingException("The node is stopping: " + kctx.localNodeId())); + + ctx = contexts.computeIfAbsent(req.snapshotName(), snpName -> new SnapshotCheckContext(req)); + } + + if (!ctx.req.requestId().equals(req.requestId())) { + return new GridFinishedFuture<>(new IllegalStateException("Validation of snapshot '" + req.snapshotName() + + "' has already started [ctx=" + ctx + ']')); + } + + // Excludes non-baseline initiator. + if (!baseline(kctx.localNodeId())) + return new GridFinishedFuture<>(); + + Collection<Integer> grpIds = F.isEmpty(req.groups()) ? null : F.viewReadOnly(req.groups(), CU::cacheId); + + GridFutureAdapter<SnapshotCheckResponse> phaseFut = ctx.phaseFuture(); + + // Might be already finished by asynchronous leave of a required node. + if (!phaseFut.isDone()) { + snpChecker.checkLocalMetas( + new SnapshotFileTree(kctx, req.snapshotName(), req.snapshotPath()), + req.incrementalIndex(), + grpIds + ).whenComplete((locMetas, err) -> { + if (err != null) + phaseFut.onDone(err); + else + phaseFut.onDone(new SnapshotCheckResponse(locMetas, null)); + }); + } + + return phaseFut; + } + + /** Phase 1 end. */ + private void reducePreparationAndMetasCheck( + UUID reqId, + Map<UUID, SnapshotCheckResponse> results, + Map<UUID, Throwable> errors + ) { + SnapshotCheckContext ctx = context(null, reqId); + + // The context is not stored in the case of concurrent check of the same snapshot but the operation future is registered. + GridFutureAdapter<SnapshotPartitionsVerifyTaskResult> clusterOpFut = clusterOpFuts.get(reqId); + + try { + if (!errors.isEmpty()) + throw new IgniteSnapshotVerifyException(mapErrors(errors)); + + if (ctx == null) { + assert clusterOpFut == null; + + return; + } + + ClusterTopologyCheckedException ex; + + if ((ex = checkNodeLeft(ctx.req.nodes(), results.keySet())) != null) + throw ex; + + Map<ClusterNode, List<SnapshotMetadata>> metas = new HashMap<>(); + + results.forEach((nodeId, nodeRes) -> { + // A node might be not required. It gives null result. But a required node might have invalid empty result + // which must be validated. + if (ctx.req.nodes().contains(nodeId) && baseline(nodeId) && !F.isEmpty((Collection<?>)nodeRes.result())) { + assert nodeRes != null; + + metas.put(kctx.cluster().get().node(nodeId), nodeRes.result()); + } + }); + + Map<ClusterNode, Exception> metasCheck = snpChecker.reduceMetasResults( + new SnapshotFileTree(kctx, ctx.req.snapshotName(), ctx.req.snapshotPath()), metas); + + if (!metasCheck.isEmpty()) + throw new IgniteSnapshotVerifyException(metasCheck); + + // If the topology is lesser that the snapshot's, we have to check another partitions parts. + ctx.metas = assingMetas(metas); + + if (!F.isEmpty(ctx.metas)) { + ctx.locFileTree = new HashMap<>(ctx.metas.size(), 1.0f); + + for (SnapshotMetadata metaToProc : ctx.metas) { + SnapshotFileTree sft = new SnapshotFileTree(kctx, ctx.req.snapshotName(), ctx.req.snapshotPath(), + metaToProc.folderName(), metaToProc.consistentId()); + + ctx.locFileTree.put(metaToProc.consistentId(), sft); + } + } + + if (clusterOpFut != null) + ctx.clusterMetas = metas; + + if (U.isLocalNodeCoordinator(kctx.discovery())) + phase2PartsHashes.start(reqId, ctx.req); + } + catch (Throwable th) { + if (ctx != null) { + contexts.remove(ctx.req.snapshotName()); + + if (log.isInfoEnabled()) + log.info("Finished snapshot validation [req=" + ctx.req + ", err=" + th.getMessage() + ']'); + } + + if (clusterOpFut != null) + clusterOpFut.onDone(th); + } + } + + /** + * Assigns snapshot metadatas to process. A snapshot can be checked on a smaller topology compared to the original one. + * In this case, some node has to check not only own meta and partitions. + * + * @return Metadatas to process on current node. + */ + private @Nullable List<SnapshotMetadata> assingMetas(Map<ClusterNode, List<SnapshotMetadata>> clusterMetas) { + ClusterNode locNode = kctx.cluster().get().localNode(); + List<SnapshotMetadata> locMetas = clusterMetas.get(locNode); + + if (F.isEmpty(locMetas)) + return null; + + Set<String> onlineNodesConstIdsStr = new HashSet<>(clusterMetas.size()); + // The nodes are sorted with lesser order. + Map<String, Collection<ClusterNode>> metasPerRespondedNodes = new HashMap<>(); + + clusterMetas.forEach((node, nodeMetas) -> { + if (!F.isEmpty(nodeMetas)) { + onlineNodesConstIdsStr.add(node.consistentId().toString()); + + nodeMetas.forEach(nodeMeta -> metasPerRespondedNodes.computeIfAbsent(nodeMeta.consistentId(), + m -> new TreeSet<>(Comparator.comparingLong(ClusterNode::order))).add(node)); + } + }); + + String locNodeConsIdStr = locNode.consistentId().toString(); + List<SnapshotMetadata> metasToProc = new ArrayList<>(); + + for (SnapshotMetadata meta : locMetas) { + if (meta.consistentId().equals(locNodeConsIdStr)) { + assert !metasToProc.contains(meta) : "Local snapshot metadata is already assigned to process"; + + metasToProc.add(meta); + + continue; + } + + if (!onlineNodesConstIdsStr.contains(meta.consistentId()) + && F.first(metasPerRespondedNodes.get(meta.consistentId())).id().equals(kctx.localNodeId())) + metasToProc.add(meta); + } + + return metasToProc; + } + + /** + * Starts the snapshot validation process. + * + * @param snpName Snapshot name. + * @param snpPath Snapshot directory path. + * @param grpNames List of cache group names. + * @param fullCheck If {@code true}, additionally calculates partition hashes. Otherwise, checks only snapshot integrity + * and partition counters. + * @param incIdx Incremental snapshot index. If not positive, snapshot is not considered as incremental. + * @param allRestoreHandlers If {@code true}, all the registered {@link IgniteSnapshotManager#handlers()} of type + * {@link SnapshotHandlerType#RESTORE} are invoked. Otherwise, only snapshot metadatas and partition + * hashes are validated. + */ + public IgniteInternalFuture<SnapshotPartitionsVerifyTaskResult> start( + String snpName, + @Nullable String snpPath, + @Nullable Collection<String> grpNames, + boolean fullCheck, + int incIdx, + boolean allRestoreHandlers + ) { + assert !F.isEmpty(snpName); + + UUID reqId = UUID.randomUUID(); + + Set<UUID> requiredNodes = new HashSet<>(F.viewReadOnly(kctx.discovery().discoCache().aliveBaselineNodes(), node2id())); + + SnapshotCheckProcessRequest req = new SnapshotCheckProcessRequest( + reqId, + requiredNodes, + snpName, + snpPath, + grpNames == null ? null : new HashSet<>(grpNames), + fullCheck, + incIdx, + allRestoreHandlers + ); + + GridFutureAdapter<SnapshotPartitionsVerifyTaskResult> clusterOpFut = new GridFutureAdapter<>(); + + clusterOpFut.listen(fut -> { + clusterOpFuts.remove(reqId); + + if (log.isInfoEnabled()) + log.info("Finished snapshot validation [req=" + req + ']'); + }); + + clusterOpFuts.put(reqId, clusterOpFut); + + phase1CheckMetas.start(req.requestId(), req); + + return clusterOpFut; + } + + /** @return {@code True} if snapshot with specified name is checking. */ + boolean isSnapshotChecking(String snpName) { + return contexts.get(snpName) != null; + } + + /** @return {@code True} if node with the provided id is in the cluster and is a baseline node. {@code False} otherwise. */ + private boolean baseline(UUID nodeId) { + ClusterNode node = kctx.cluster().get().node(nodeId); + + return node != null && CU.baselineNode(node, kctx.state().clusterState()); + } + + /** + * @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(Collection<UUID> reqNodes, Set<UUID> respNodes) { + if (!respNodes.containsAll(reqNodes)) { + Set<UUID> leftNodes = new HashSet<>(reqNodes); + + leftNodes.removeAll(respNodes); + + return new ClusterTopologyCheckedException("Snapshot validation stopped. " + + "Required node has left the cluster [nodeId=" + leftNodes + ']'); + } + + return null; + } + + /** Operation context. */ + private static final class SnapshotCheckContext { + /** Request. */ + @GridToStringInclude + private final SnapshotCheckProcessRequest req; + + /** Current process' future. Listens error, stop requests, etc. */ + private final GridFutureAdapter<SnapshotCheckResponse> locProcFut = new GridFutureAdapter<>(); + + /** + * Metadatas to process on this node. Also indicates the snapshot parts to check on this node. + * @see #partitionsHashesFuture(SnapshotCheckContext) + */ + @Nullable private List<SnapshotMetadata> metas; + + /** Map of snapshot pathes per consistent id for {@link #metas}. */ + @GridToStringInclude + @Nullable private Map<String, SnapshotFileTree> locFileTree; + + /** All the snapshot metadatas. */ + @Nullable private Map<ClusterNode, List<SnapshotMetadata>> clusterMetas; + + /** Creates operation context. */ + private SnapshotCheckContext(SnapshotCheckProcessRequest req) { + this.req = req; + } + + /** Gives a future for current process phase. The future can be stopped by asynchronous leave of a required node. */ Review Comment: Let's completely remove this fail-fast and implement it in a separate ticket. Logic in the master branch now doesn't contain fail-fast for the calculations. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
