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