timoninmaxim commented on code in PR #11423:
URL: https://github.com/apache/ignite/pull/11423#discussion_r1668565684


##########
modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/dump/Dump.java:
##########
@@ -392,7 +392,7 @@ public GridKernalContext context() {
     }
 
     /** {@inheritDoc} */
-    @Override public void close() throws Exception {
+    @Override public void close() throws IgniteCheckedException {

Review Comment:
   Useless change



##########
modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/dump/Dump.java:
##########
@@ -199,7 +199,7 @@ public List<String> nodesDirectories() {
     }
 
     /** @return List of snapshot metadata saved in {@link #dumpDir}. */
-    public List<SnapshotMetadata> metadata() throws IOException, 
IgniteCheckedException {

Review Comment:
   Useless change



##########
modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotChecker.java:
##########
@@ -0,0 +1,698 @@
+/*
+ * 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.BufferedInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.dump.DumpEntry;
+import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.management.cache.PartitionKeyV2;
+import 
org.apache.ignite.internal.managers.encryption.EncryptionCacheKeyProvider;
+import org.apache.ignite.internal.managers.encryption.GroupKey;
+import org.apache.ignite.internal.managers.encryption.GroupKeyEncrypted;
+import org.apache.ignite.internal.pagemem.store.PageStore;
+import org.apache.ignite.internal.processors.cache.CacheObject;
+import org.apache.ignite.internal.processors.cache.KeyCacheObject;
+import org.apache.ignite.internal.processors.cache.StoredCacheData;
+import 
org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtPartitionState;
+import 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStore;
+import 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager;
+import 
org.apache.ignite.internal.processors.cache.persistence.metastorage.MetaStorage;
+import 
org.apache.ignite.internal.processors.cache.persistence.snapshot.dump.Dump;
+import org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusIO;
+import 
org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusMetaIO;
+import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
+import 
org.apache.ignite.internal.processors.cache.persistence.tree.io.PagePartitionMetaIO;
+import org.apache.ignite.internal.processors.cache.verify.IdleVerifyUtility;
+import 
org.apache.ignite.internal.processors.cache.verify.PartitionHashRecordV2;
+import org.apache.ignite.internal.processors.compress.CompressionProcessor;
+import org.apache.ignite.internal.util.GridUnsafe;
+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.U;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.apache.ignite.marshaller.Marshaller;
+import org.apache.ignite.spi.encryption.EncryptionSpi;
+import org.apache.ignite.spi.encryption.noop.NoopEncryptionSpi;
+import org.jetbrains.annotations.Nullable;
+
+import static org.apache.ignite.internal.pagemem.PageIdAllocator.FLAG_DATA;
+import static org.apache.ignite.internal.pagemem.PageIdAllocator.FLAG_IDX;
+import static 
org.apache.ignite.internal.pagemem.PageIdAllocator.INDEX_PARTITION;
+import static 
org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtPartitionState.OWNING;
+import static 
org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtPartitionState.fromOrdinal;
+import static 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.CACHE_DATA_FILENAME;
+import static 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.FILE_SUFFIX;
+import static 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.ZIP_SUFFIX;
+import static 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.cacheDirectories;
+import static 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.cacheGroupName;
+import static 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.cachePartitionFiles;
+import static 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.partId;
+import static 
org.apache.ignite.internal.processors.cache.persistence.partstate.GroupPartitionId.getTypeByPartId;
+import static 
org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager.SNAPSHOT_METAFILE_EXT;
+import static 
org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager.databaseRelativePath;
+import static 
org.apache.ignite.internal.processors.cache.persistence.snapshot.dump.CreateDumpFutureTask.DUMP_FILE_EXT;
+import static 
org.apache.ignite.internal.processors.cache.persistence.wal.reader.StandaloneGridKernalContext.closeAllComponents;
+import static 
org.apache.ignite.internal.processors.cache.persistence.wal.reader.StandaloneGridKernalContext.startAllComponents;
+import static 
org.apache.ignite.internal.processors.cache.verify.IdleVerifyUtility.calculatePartitionHash;
+import static 
org.apache.ignite.internal.processors.cache.verify.IdleVerifyUtility.checkPartitionsPageCrcSum;
+
+/** */
+public class SnapshotChecker {
+    /** */
+    protected final IgniteLogger log;
+
+    /** */
+    @Nullable protected final GridKernalContext kctx;
+
+    /** */
+    protected final Marshaller marshaller;
+
+    /** */
+    @Nullable protected final ClassLoader marshallerClsLdr;
+
+    /** */
+    protected final EncryptionSpi encryptionSpi;
+
+    /** */
+    @Nullable protected final CompressionProcessor compression;
+
+    /** */
+    protected final ExecutorService executor;
+
+    /** */
+    public SnapshotChecker(
+        GridKernalContext kctx,
+        Marshaller marshaller,
+        ExecutorService executorSrvc,
+        @Nullable ClassLoader marshallerClsLdr
+    ) {
+        this.kctx = kctx;
+
+        this.marshaller = marshaller;

Review Comment:
   It looks like `marshaller` and `marshallerClsLdr` is used only in single 
method. Should we move then this params from constructor to those methods?



##########
modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotChecker.java:
##########
@@ -0,0 +1,698 @@
+/*
+ * 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.BufferedInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.dump.DumpEntry;
+import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.management.cache.PartitionKeyV2;
+import 
org.apache.ignite.internal.managers.encryption.EncryptionCacheKeyProvider;
+import org.apache.ignite.internal.managers.encryption.GroupKey;
+import org.apache.ignite.internal.managers.encryption.GroupKeyEncrypted;
+import org.apache.ignite.internal.pagemem.store.PageStore;
+import org.apache.ignite.internal.processors.cache.CacheObject;
+import org.apache.ignite.internal.processors.cache.KeyCacheObject;
+import org.apache.ignite.internal.processors.cache.StoredCacheData;
+import 
org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtPartitionState;
+import 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStore;
+import 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager;
+import 
org.apache.ignite.internal.processors.cache.persistence.metastorage.MetaStorage;
+import 
org.apache.ignite.internal.processors.cache.persistence.snapshot.dump.Dump;
+import org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusIO;
+import 
org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusMetaIO;
+import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
+import 
org.apache.ignite.internal.processors.cache.persistence.tree.io.PagePartitionMetaIO;
+import org.apache.ignite.internal.processors.cache.verify.IdleVerifyUtility;
+import 
org.apache.ignite.internal.processors.cache.verify.PartitionHashRecordV2;
+import org.apache.ignite.internal.processors.compress.CompressionProcessor;
+import org.apache.ignite.internal.util.GridUnsafe;
+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.U;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.apache.ignite.marshaller.Marshaller;
+import org.apache.ignite.spi.encryption.EncryptionSpi;
+import org.apache.ignite.spi.encryption.noop.NoopEncryptionSpi;
+import org.jetbrains.annotations.Nullable;
+
+import static org.apache.ignite.internal.pagemem.PageIdAllocator.FLAG_DATA;
+import static org.apache.ignite.internal.pagemem.PageIdAllocator.FLAG_IDX;
+import static 
org.apache.ignite.internal.pagemem.PageIdAllocator.INDEX_PARTITION;
+import static 
org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtPartitionState.OWNING;
+import static 
org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtPartitionState.fromOrdinal;
+import static 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.CACHE_DATA_FILENAME;
+import static 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.FILE_SUFFIX;
+import static 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.ZIP_SUFFIX;
+import static 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.cacheDirectories;
+import static 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.cacheGroupName;
+import static 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.cachePartitionFiles;
+import static 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.partId;
+import static 
org.apache.ignite.internal.processors.cache.persistence.partstate.GroupPartitionId.getTypeByPartId;
+import static 
org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager.SNAPSHOT_METAFILE_EXT;
+import static 
org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager.databaseRelativePath;
+import static 
org.apache.ignite.internal.processors.cache.persistence.snapshot.dump.CreateDumpFutureTask.DUMP_FILE_EXT;
+import static 
org.apache.ignite.internal.processors.cache.persistence.wal.reader.StandaloneGridKernalContext.closeAllComponents;
+import static 
org.apache.ignite.internal.processors.cache.persistence.wal.reader.StandaloneGridKernalContext.startAllComponents;
+import static 
org.apache.ignite.internal.processors.cache.verify.IdleVerifyUtility.calculatePartitionHash;
+import static 
org.apache.ignite.internal.processors.cache.verify.IdleVerifyUtility.checkPartitionsPageCrcSum;
+
+/** */
+public class SnapshotChecker {
+    /** */
+    protected final IgniteLogger log;
+
+    /** */
+    @Nullable protected final GridKernalContext kctx;
+
+    /** */
+    protected final Marshaller marshaller;
+
+    /** */
+    @Nullable protected final ClassLoader marshallerClsLdr;
+
+    /** */
+    protected final EncryptionSpi encryptionSpi;
+
+    /** */
+    @Nullable protected final CompressionProcessor compression;
+
+    /** */
+    protected final ExecutorService executor;
+
+    /** */
+    public SnapshotChecker(
+        GridKernalContext kctx,
+        Marshaller marshaller,
+        ExecutorService executorSrvc,
+        @Nullable ClassLoader marshallerClsLdr
+    ) {
+        this.kctx = kctx;
+
+        this.marshaller = marshaller;
+        this.marshallerClsLdr = marshallerClsLdr;
+
+        this.encryptionSpi = kctx.config().getEncryptionSpi() == null ? new 
NoopEncryptionSpi() : kctx.config().getEncryptionSpi();
+
+        this.compression = kctx.compress();
+
+        this.executor = executorSrvc;
+
+        this.log = kctx.log(getClass());
+    }
+
+    /** */
+    protected List<SnapshotMetadata> readSnapshotMetadatas(File snpFullPath, 
@Nullable Object nodeConstId) {
+        if (!(snpFullPath.exists() && snpFullPath.isDirectory()))
+            return Collections.emptyList();
+
+        List<File> smfs = new ArrayList<>();
+
+        try (DirectoryStream<Path> ds = 
Files.newDirectoryStream(snpFullPath.toPath())) {
+            for (Path d : ds) {
+                if (Files.isRegularFile(d) && 
d.getFileName().toString().toLowerCase().endsWith(SNAPSHOT_METAFILE_EXT))
+                    smfs.add(d.toFile());
+            }
+        }
+        catch (IOException e) {
+            throw new IgniteException(e);
+        }
+
+        if (smfs.isEmpty())
+            return Collections.emptyList();
+
+        Map<String, SnapshotMetadata> metasMap = new HashMap<>();
+        SnapshotMetadata prev = null;
+
+        try {
+            for (File smf : smfs) {
+                SnapshotMetadata curr = readSnapshotMetadata(smf);
+
+                if (prev != null && !prev.sameSnapshot(curr)) {
+                    throw new IgniteException("Snapshot metadata files are 
from different snapshots " +
+                        "[prev=" + prev + ", curr=" + curr + ']');
+                }
+
+                metasMap.put(curr.consistentId(), curr);
+
+                prev = curr;
+            }
+        }
+        catch (IgniteCheckedException | IOException e) {
+            throw new IgniteException(e);
+        }
+
+        SnapshotMetadata currNodeSmf = nodeConstId == null ? null : 
metasMap.remove(nodeConstId.toString());
+
+        // Snapshot metadata for the local node must be first in the result 
map.
+        if (currNodeSmf == null)
+            return new ArrayList<>(metasMap.values());
+        else {
+            List<SnapshotMetadata> result = new ArrayList<>();
+
+            result.add(currNodeSmf);
+            result.addAll(metasMap.values());
+
+            return result;
+        }
+    }
+
+    /** */
+    public SnapshotMetadata readSnapshotMetadata(File smf)
+        throws IgniteCheckedException, IOException {
+        SnapshotMetadata meta = readFromFile(smf);
+
+        String smfName = smf.getName().substring(0, smf.getName().length() - 
SNAPSHOT_METAFILE_EXT.length());
+
+        if (!U.maskForFileName(meta.consistentId()).equals(smfName)) {
+            throw new IgniteException("Error reading snapshot metadata 
[smfName=" + smfName + ", consId="
+                + U.maskForFileName(meta.consistentId()));
+        }
+
+        return meta;
+    }
+
+    /** */
+    public <T> T readFromFile(File smf)
+        throws IOException, IgniteCheckedException {
+        if (!smf.exists())
+            throw new IgniteCheckedException("Snapshot metafile cannot be read 
due to it doesn't exist: " + smf);
+
+        try (InputStream in = new 
BufferedInputStream(Files.newInputStream(smf.toPath()))) {
+            return marshaller.unmarshal(in, marshallerClsLdr);
+        }
+    }
+
+    /** */
+    public List<SnapshotMetadata> checkLocalMetas(File snpFullPath, @Nullable 
Collection<Integer> cacheGrpIds,
+        @Nullable Object locNodeConsistId) {
+        List<SnapshotMetadata> snpMetas = readSnapshotMetadatas(snpFullPath, 
locNodeConsistId);
+
+        for (SnapshotMetadata meta : snpMetas) {
+            byte[] snpMasterKeyDigest = meta.masterKeyDigest();
+
+            if (encryptionSpi.masterKeyDigest() == null && snpMasterKeyDigest 
!= null) {
+                throw new IllegalStateException("Snapshot '" + 
meta.snapshotName() + "' has encrypted caches " +
+                    "while encryption is disabled. To restore this snapshot, 
start Ignite with configured " +
+                    "encryption and the same master key.");
+            }
+
+            if (snpMasterKeyDigest != null && 
!Arrays.equals(snpMasterKeyDigest, encryptionSpi.masterKeyDigest())) {
+                throw new IllegalStateException("Snapshot '" + 
meta.snapshotName() + "' has different master " +
+                    "key digest. To restore this snapshot, start Ignite with 
the same master key.");
+            }
+
+            Collection<Integer> grpIdsToFind = new 
HashSet<>(F.isEmpty(cacheGrpIds) ? meta.cacheGroupIds() : cacheGrpIds);
+
+            if (meta.hasCompressedGroups() && 
grpIdsToFind.stream().anyMatch(meta::isGroupWithCompression)) {
+                try {
+                    compression.checkPageCompressionSupported();
+                }
+                catch (NullPointerException | IgniteCheckedException e) {
+                    String grpWithCompr = 
grpIdsToFind.stream().filter(meta::isGroupWithCompression)
+                        .map(String::valueOf).collect(Collectors.joining(", 
"));
+
+                    String msg = "Requested cache groups [" + grpWithCompr + 
"] for check " +
+                        "from snapshot '" + meta.snapshotName() + "' are 
compressed while " +
+                        "disk page compression is disabled. To check these 
groups please " +
+                        "start Ignite with ignite-compress module in 
classpath";
+
+                    throw new IllegalStateException(msg);
+                }
+            }
+
+            grpIdsToFind.removeAll(meta.partitions().keySet());
+
+            if (!grpIdsToFind.isEmpty() && !new 
HashSet<>(meta.cacheGroupIds()).containsAll(grpIdsToFind)) {
+                throw new IllegalArgumentException("Cache group(s) was not 
found in the snapshot [groups=" + grpIdsToFind +
+                    ", snapshot=" + meta.snapshotName() + ']');
+            }
+        }
+
+        return snpMetas;
+    }
+
+    /** */
+    public static Map<ClusterNode, Exception> checkClusterMetas(
+        String snpName,
+        @Nullable String snpPath,
+        Map<ClusterNode, List<SnapshotMetadata>> allMetas,
+        Map<ClusterNode, Exception> knownExceptions
+    ) {
+        Map<ClusterNode, Exception> resultExceptions = new 
HashMap<>(knownExceptions);
+
+        SnapshotMetadata firstMeta = null;
+        Set<String> baselineNodes = Collections.emptySet();
+
+        for (Map.Entry<ClusterNode, List<SnapshotMetadata>> nme : 
allMetas.entrySet()) {
+            ClusterNode node = nme.getKey();
+            Exception e = knownExceptions.get(node);
+
+            if (e != null) {
+                resultExceptions.put(node, e);
+
+                continue;
+            }
+
+            for (SnapshotMetadata meta : nme.getValue()) {
+                if (firstMeta == null) {
+                    firstMeta = meta;
+
+                    baselineNodes = new HashSet<>(firstMeta.baselineNodes());
+                }
+
+                baselineNodes.remove(meta.consistentId());
+
+                if (!firstMeta.sameSnapshot(meta)) {
+                    resultExceptions.put(node, new IgniteException("An error 
occurred during comparing snapshot metadata "
+                        + "from cluster nodes [firstMeta=" + firstMeta + ", 
meta=" + meta + ", nodeId=" + node.id() + ']'));
+                }
+            }
+        }
+
+        if (firstMeta == null && resultExceptions.isEmpty()) {
+            assert !allMetas.isEmpty();
+
+            for (ClusterNode node : allMetas.keySet()) {
+                Exception e = new IllegalArgumentException("Snapshot does not 
exists [snapshot=" + snpName
+                    + (snpPath != null ? ", baseDir=" + snpPath : "") + ", 
consistentId=" + node.consistentId() + ']');
+
+                resultExceptions.put(node, e);
+            }
+        }
+
+        if (!F.isEmpty(baselineNodes) && F.isEmpty(knownExceptions)) {
+            throw new IgniteException("No snapshot metadatas found for the 
baseline nodes " +
+                "with consistent ids: " + String.join(", ", baselineNodes));
+        }
+
+        return resultExceptions;
+    }
+
+    /** */
+    protected IgniteBiTuple<Map<Integer, File>, Set<File>> 
preparePartitions(SnapshotMetadata meta, Collection<Integer> grps, File snpDir) 
{
+        Map<Integer, File> grpDirs = new HashMap<>();
+        Set<File> partFiles = new HashSet<>();
+
+        Set<Integer> grpsLeft = new HashSet<>(F.isEmpty(grps) ? 
meta.partitions().keySet() : grps);
+
+        for (File dir : cacheDirectories(new File(snpDir, 
databaseRelativePath(meta.folderName())), name -> true)) {
+            int grpId = CU.cacheId(cacheGroupName(dir));
+
+            if (!grpsLeft.remove(grpId))
+                continue;
+
+            grpDirs.put(grpId, dir);
+
+            Set<Integer> parts = new HashSet<>(meta.partitions().get(grpId) == 
null ? Collections.emptySet()
+                : meta.partitions().get(grpId));
+
+            for (File partFile : cachePartitionFiles(dir,
+                (meta.dump() ? DUMP_FILE_EXT : FILE_SUFFIX) + 
(meta.compressPartitions() ? ZIP_SUFFIX : "")
+            )) {
+                int partId = partId(partFile.getName());
+
+                if (!parts.remove(partId))
+                    continue;
+
+                partFiles.add(partFile);
+            }
+
+            if (!parts.isEmpty()) {
+                throw new IgniteException("Snapshot data doesn't contain 
required cache group partition " +
+                    "[grpId=" + grpId + ", snpName=" + meta.snapshotName() + 
", consId=" + meta.consistentId() +
+                    ", missed=" + parts + ", meta=" + meta + ']');
+            }
+        }
+
+        if (!grpsLeft.isEmpty()) {
+            throw new IgniteException("Snapshot data doesn't contain required 
cache groups " +
+                "[grps=" + grpsLeft + ", snpName=" + meta.snapshotName() + ", 
consId=" + meta.consistentId() +
+                ", meta=" + meta + ']');
+        }
+
+        return new IgniteBiTuple<>(grpDirs, partFiles);
+    }
+
+    /** */
+    public Map<PartitionKeyV2, PartitionHashRecordV2> checkSnapshotFiles(
+        File snpDir,
+        Set<Integer> grpIds,
+        SnapshotMetadata meta,
+        boolean forCreation,
+        boolean skipHash,
+        boolean punchHoleEnabled
+    ) throws IgniteCheckedException {
+        IgniteBiTuple<Map<Integer, File>, Set<File>> grpAndPartFiles = 
preparePartitions(meta, grpIds, snpDir);
+
+        Map<PartitionKeyV2, PartitionHashRecordV2> res = new 
ConcurrentHashMap<>();
+        ThreadLocal<ByteBuffer> buff = ThreadLocal.withInitial(() -> 
ByteBuffer.allocateDirect(meta.pageSize())
+            .order(ByteOrder.nativeOrder()));
+
+        GridKernalContext snpCtx = 
IgniteSnapshotManager.createStandaloneKernalContext(log, compression, snpDir, 
meta.folderName());

Review Comment:
   There is already GridKernalContext in the class. I suppose user of the class 
is responsible for providing the context



##########
modules/core/src/main/java/org/apache/ignite/internal/management/snapshot/SnapshotCreateCommandArg.java:
##########
@@ -67,6 +72,17 @@ public class SnapshotCreateCommandArg extends 
IgniteDataTransferObject {
         dest = U.readString(in);
         sync = in.readBoolean();
         incremental = in.readBoolean();
+        onlyPrimary = in.readBoolean();
+    }
+
+    /** */
+    public boolean onlyPrimary() {

Review Comment:
   There is not only the test, it also changes this class, let's move this test 
to a separate ticket



##########
modules/core/src/main/java/org/apache/ignite/internal/processors/cache/persistence/snapshot/SnapshotChecker.java:
##########
@@ -0,0 +1,698 @@
+/*
+ * 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.BufferedInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ExecutorService;
+import java.util.stream.Collectors;
+import org.apache.ignite.IgniteCheckedException;
+import org.apache.ignite.IgniteException;
+import org.apache.ignite.IgniteLogger;
+import org.apache.ignite.cluster.ClusterNode;
+import org.apache.ignite.dump.DumpEntry;
+import org.apache.ignite.internal.GridKernalContext;
+import org.apache.ignite.internal.management.cache.PartitionKeyV2;
+import 
org.apache.ignite.internal.managers.encryption.EncryptionCacheKeyProvider;
+import org.apache.ignite.internal.managers.encryption.GroupKey;
+import org.apache.ignite.internal.managers.encryption.GroupKeyEncrypted;
+import org.apache.ignite.internal.pagemem.store.PageStore;
+import org.apache.ignite.internal.processors.cache.CacheObject;
+import org.apache.ignite.internal.processors.cache.KeyCacheObject;
+import org.apache.ignite.internal.processors.cache.StoredCacheData;
+import 
org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtPartitionState;
+import 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStore;
+import 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager;
+import 
org.apache.ignite.internal.processors.cache.persistence.metastorage.MetaStorage;
+import 
org.apache.ignite.internal.processors.cache.persistence.snapshot.dump.Dump;
+import org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusIO;
+import 
org.apache.ignite.internal.processors.cache.persistence.tree.io.BPlusMetaIO;
+import org.apache.ignite.internal.processors.cache.persistence.tree.io.PageIO;
+import 
org.apache.ignite.internal.processors.cache.persistence.tree.io.PagePartitionMetaIO;
+import org.apache.ignite.internal.processors.cache.verify.IdleVerifyUtility;
+import 
org.apache.ignite.internal.processors.cache.verify.PartitionHashRecordV2;
+import org.apache.ignite.internal.processors.compress.CompressionProcessor;
+import org.apache.ignite.internal.util.GridUnsafe;
+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.U;
+import org.apache.ignite.lang.IgniteBiTuple;
+import org.apache.ignite.marshaller.Marshaller;
+import org.apache.ignite.spi.encryption.EncryptionSpi;
+import org.apache.ignite.spi.encryption.noop.NoopEncryptionSpi;
+import org.jetbrains.annotations.Nullable;
+
+import static org.apache.ignite.internal.pagemem.PageIdAllocator.FLAG_DATA;
+import static org.apache.ignite.internal.pagemem.PageIdAllocator.FLAG_IDX;
+import static 
org.apache.ignite.internal.pagemem.PageIdAllocator.INDEX_PARTITION;
+import static 
org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtPartitionState.OWNING;
+import static 
org.apache.ignite.internal.processors.cache.distributed.dht.topology.GridDhtPartitionState.fromOrdinal;
+import static 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.CACHE_DATA_FILENAME;
+import static 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.FILE_SUFFIX;
+import static 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.ZIP_SUFFIX;
+import static 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.cacheDirectories;
+import static 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.cacheGroupName;
+import static 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.cachePartitionFiles;
+import static 
org.apache.ignite.internal.processors.cache.persistence.file.FilePageStoreManager.partId;
+import static 
org.apache.ignite.internal.processors.cache.persistence.partstate.GroupPartitionId.getTypeByPartId;
+import static 
org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager.SNAPSHOT_METAFILE_EXT;
+import static 
org.apache.ignite.internal.processors.cache.persistence.snapshot.IgniteSnapshotManager.databaseRelativePath;
+import static 
org.apache.ignite.internal.processors.cache.persistence.snapshot.dump.CreateDumpFutureTask.DUMP_FILE_EXT;
+import static 
org.apache.ignite.internal.processors.cache.persistence.wal.reader.StandaloneGridKernalContext.closeAllComponents;
+import static 
org.apache.ignite.internal.processors.cache.persistence.wal.reader.StandaloneGridKernalContext.startAllComponents;
+import static 
org.apache.ignite.internal.processors.cache.verify.IdleVerifyUtility.calculatePartitionHash;
+import static 
org.apache.ignite.internal.processors.cache.verify.IdleVerifyUtility.checkPartitionsPageCrcSum;
+
+/** */
+public class SnapshotChecker {
+    /** */
+    protected final IgniteLogger log;
+
+    /** */
+    @Nullable protected final GridKernalContext kctx;
+
+    /** */
+    protected final Marshaller marshaller;
+
+    /** */
+    @Nullable protected final ClassLoader marshallerClsLdr;
+
+    /** */
+    protected final EncryptionSpi encryptionSpi;
+
+    /** */
+    @Nullable protected final CompressionProcessor compression;
+
+    /** */
+    protected final ExecutorService executor;
+
+    /** */
+    public SnapshotChecker(
+        GridKernalContext kctx,
+        Marshaller marshaller,
+        ExecutorService executorSrvc,
+        @Nullable ClassLoader marshallerClsLdr
+    ) {
+        this.kctx = kctx;
+
+        this.marshaller = marshaller;
+        this.marshallerClsLdr = marshallerClsLdr;
+
+        this.encryptionSpi = kctx.config().getEncryptionSpi() == null ? new 
NoopEncryptionSpi() : kctx.config().getEncryptionSpi();
+
+        this.compression = kctx.compress();
+
+        this.executor = executorSrvc;
+
+        this.log = kctx.log(getClass());
+    }
+
+    /** */
+    protected List<SnapshotMetadata> readSnapshotMetadatas(File snpFullPath, 
@Nullable Object nodeConstId) {
+        if (!(snpFullPath.exists() && snpFullPath.isDirectory()))
+            return Collections.emptyList();
+
+        List<File> smfs = new ArrayList<>();
+
+        try (DirectoryStream<Path> ds = 
Files.newDirectoryStream(snpFullPath.toPath())) {
+            for (Path d : ds) {
+                if (Files.isRegularFile(d) && 
d.getFileName().toString().toLowerCase().endsWith(SNAPSHOT_METAFILE_EXT))
+                    smfs.add(d.toFile());
+            }
+        }
+        catch (IOException e) {
+            throw new IgniteException(e);
+        }
+
+        if (smfs.isEmpty())
+            return Collections.emptyList();
+
+        Map<String, SnapshotMetadata> metasMap = new HashMap<>();
+        SnapshotMetadata prev = null;
+
+        try {
+            for (File smf : smfs) {
+                SnapshotMetadata curr = readSnapshotMetadata(smf);
+
+                if (prev != null && !prev.sameSnapshot(curr)) {
+                    throw new IgniteException("Snapshot metadata files are 
from different snapshots " +
+                        "[prev=" + prev + ", curr=" + curr + ']');
+                }
+
+                metasMap.put(curr.consistentId(), curr);
+
+                prev = curr;
+            }
+        }
+        catch (IgniteCheckedException | IOException e) {
+            throw new IgniteException(e);
+        }
+
+        SnapshotMetadata currNodeSmf = nodeConstId == null ? null : 
metasMap.remove(nodeConstId.toString());
+
+        // Snapshot metadata for the local node must be first in the result 
map.
+        if (currNodeSmf == null)
+            return new ArrayList<>(metasMap.values());
+        else {
+            List<SnapshotMetadata> result = new ArrayList<>();
+
+            result.add(currNodeSmf);
+            result.addAll(metasMap.values());
+
+            return result;
+        }
+    }
+
+    /** */
+    public SnapshotMetadata readSnapshotMetadata(File smf)
+        throws IgniteCheckedException, IOException {
+        SnapshotMetadata meta = readFromFile(smf);
+
+        String smfName = smf.getName().substring(0, smf.getName().length() - 
SNAPSHOT_METAFILE_EXT.length());
+
+        if (!U.maskForFileName(meta.consistentId()).equals(smfName)) {
+            throw new IgniteException("Error reading snapshot metadata 
[smfName=" + smfName + ", consId="
+                + U.maskForFileName(meta.consistentId()));
+        }
+
+        return meta;
+    }
+
+    /** */
+    public <T> T readFromFile(File smf)
+        throws IOException, IgniteCheckedException {
+        if (!smf.exists())
+            throw new IgniteCheckedException("Snapshot metafile cannot be read 
due to it doesn't exist: " + smf);
+
+        try (InputStream in = new 
BufferedInputStream(Files.newInputStream(smf.toPath()))) {
+            return marshaller.unmarshal(in, marshallerClsLdr);
+        }
+    }
+
+    /** */
+    public List<SnapshotMetadata> checkLocalMetas(File snpFullPath, @Nullable 
Collection<Integer> cacheGrpIds,
+        @Nullable Object locNodeConsistId) {
+        List<SnapshotMetadata> snpMetas = readSnapshotMetadatas(snpFullPath, 
locNodeConsistId);
+
+        for (SnapshotMetadata meta : snpMetas) {
+            byte[] snpMasterKeyDigest = meta.masterKeyDigest();
+
+            if (encryptionSpi.masterKeyDigest() == null && snpMasterKeyDigest 
!= null) {
+                throw new IllegalStateException("Snapshot '" + 
meta.snapshotName() + "' has encrypted caches " +
+                    "while encryption is disabled. To restore this snapshot, 
start Ignite with configured " +
+                    "encryption and the same master key.");
+            }
+
+            if (snpMasterKeyDigest != null && 
!Arrays.equals(snpMasterKeyDigest, encryptionSpi.masterKeyDigest())) {
+                throw new IllegalStateException("Snapshot '" + 
meta.snapshotName() + "' has different master " +
+                    "key digest. To restore this snapshot, start Ignite with 
the same master key.");
+            }
+
+            Collection<Integer> grpIdsToFind = new 
HashSet<>(F.isEmpty(cacheGrpIds) ? meta.cacheGroupIds() : cacheGrpIds);
+
+            if (meta.hasCompressedGroups() && 
grpIdsToFind.stream().anyMatch(meta::isGroupWithCompression)) {
+                try {
+                    compression.checkPageCompressionSupported();
+                }
+                catch (NullPointerException | IgniteCheckedException e) {
+                    String grpWithCompr = 
grpIdsToFind.stream().filter(meta::isGroupWithCompression)
+                        .map(String::valueOf).collect(Collectors.joining(", 
"));
+
+                    String msg = "Requested cache groups [" + grpWithCompr + 
"] for check " +
+                        "from snapshot '" + meta.snapshotName() + "' are 
compressed while " +
+                        "disk page compression is disabled. To check these 
groups please " +
+                        "start Ignite with ignite-compress module in 
classpath";
+
+                    throw new IllegalStateException(msg);
+                }
+            }
+
+            grpIdsToFind.removeAll(meta.partitions().keySet());
+
+            if (!grpIdsToFind.isEmpty() && !new 
HashSet<>(meta.cacheGroupIds()).containsAll(grpIdsToFind)) {
+                throw new IllegalArgumentException("Cache group(s) was not 
found in the snapshot [groups=" + grpIdsToFind +
+                    ", snapshot=" + meta.snapshotName() + ']');
+            }
+        }
+
+        return snpMetas;
+    }
+
+    /** */
+    public static Map<ClusterNode, Exception> checkClusterMetas(
+        String snpName,
+        @Nullable String snpPath,
+        Map<ClusterNode, List<SnapshotMetadata>> allMetas,
+        Map<ClusterNode, Exception> knownExceptions
+    ) {
+        Map<ClusterNode, Exception> resultExceptions = new 
HashMap<>(knownExceptions);
+
+        SnapshotMetadata firstMeta = null;
+        Set<String> baselineNodes = Collections.emptySet();
+
+        for (Map.Entry<ClusterNode, List<SnapshotMetadata>> nme : 
allMetas.entrySet()) {
+            ClusterNode node = nme.getKey();
+            Exception e = knownExceptions.get(node);
+
+            if (e != null) {
+                resultExceptions.put(node, e);
+
+                continue;
+            }
+
+            for (SnapshotMetadata meta : nme.getValue()) {
+                if (firstMeta == null) {
+                    firstMeta = meta;
+
+                    baselineNodes = new HashSet<>(firstMeta.baselineNodes());
+                }
+
+                baselineNodes.remove(meta.consistentId());
+
+                if (!firstMeta.sameSnapshot(meta)) {
+                    resultExceptions.put(node, new IgniteException("An error 
occurred during comparing snapshot metadata "
+                        + "from cluster nodes [firstMeta=" + firstMeta + ", 
meta=" + meta + ", nodeId=" + node.id() + ']'));
+                }
+            }
+        }
+
+        if (firstMeta == null && resultExceptions.isEmpty()) {
+            assert !allMetas.isEmpty();
+
+            for (ClusterNode node : allMetas.keySet()) {
+                Exception e = new IllegalArgumentException("Snapshot does not 
exists [snapshot=" + snpName
+                    + (snpPath != null ? ", baseDir=" + snpPath : "") + ", 
consistentId=" + node.consistentId() + ']');
+
+                resultExceptions.put(node, e);
+            }
+        }
+
+        if (!F.isEmpty(baselineNodes) && F.isEmpty(knownExceptions)) {
+            throw new IgniteException("No snapshot metadatas found for the 
baseline nodes " +
+                "with consistent ids: " + String.join(", ", baselineNodes));
+        }
+
+        return resultExceptions;
+    }
+
+    /** */
+    protected IgniteBiTuple<Map<Integer, File>, Set<File>> 
preparePartitions(SnapshotMetadata meta, Collection<Integer> grps, File snpDir) 
{

Review Comment:
   Why is it protected?



-- 
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: notifications-unsubscr...@ignite.apache.org

For queries about this service, please contact Infrastructure at:
us...@infra.apache.org


Reply via email to