| /* |
| * 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.solr.core.snapshots; |
| |
| import java.io.IOException; |
| import java.lang.invoke.MethodHandles; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Optional; |
| import java.util.Set; |
| import org.apache.lucene.index.IndexCommit; |
| import org.apache.lucene.index.IndexDeletionPolicy; |
| import org.apache.lucene.index.IndexWriterConfig; |
| import org.apache.lucene.index.IndexWriterConfig.OpenMode; |
| import org.apache.lucene.index.NoMergePolicy; |
| import org.apache.lucene.store.Directory; |
| import org.apache.solr.common.cloud.SolrZkClient; |
| import org.apache.solr.common.util.Utils; |
| import org.apache.solr.core.SolrCore; |
| import org.apache.solr.core.snapshots.SolrSnapshotMetaDataManager.SnapshotMetaData; |
| import org.apache.solr.update.SolrIndexWriter; |
| import org.apache.zookeeper.CreateMode; |
| import org.apache.zookeeper.KeeperException; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| /** |
| * This class provides functionality required to handle the data files corresponding to Solr snapshots. |
| */ |
| public class SolrSnapshotManager { |
| private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); |
| |
| public static final String INDEX_DIR_PATH = "indexDirPath"; |
| public static final String GENERATION_NUM = "generation"; |
| public static final String SNAPSHOT_STATUS = "status"; |
| public static final String CREATION_DATE = "creationDate"; |
| public static final String SNAPSHOT_REPLICAS = "replicas"; |
| public static final String SNAPSHOTS_INFO = "snapshots"; |
| public static final String LEADER = "leader"; |
| public static final String SHARD_ID = "shard_id"; |
| public static final String FILE_LIST = "files"; |
| |
| /** |
| * This method returns if a named snapshot exists for the specified collection. |
| * |
| * @param zkClient Zookeeper client |
| * @param collectionName The name of the collection |
| * @param commitName The name of the snapshot |
| * @return true if the named snapshot exists |
| * false Otherwise |
| * @throws KeeperException In case of Zookeeper error |
| * @throws InterruptedException In case of thread interruption. |
| */ |
| public static boolean snapshotExists(SolrZkClient zkClient, String collectionName, String commitName) |
| throws KeeperException, InterruptedException { |
| String zkPath = getSnapshotMetaDataZkPath(collectionName, Optional.ofNullable(commitName)); |
| return zkClient.exists(zkPath, true); |
| } |
| |
| /** |
| * This method creates an entry for the named snapshot for the specified collection in Zookeeper. |
| * |
| * @param zkClient Zookeeper client |
| * @param collectionName The name of the collection |
| * @param meta The {@linkplain CollectionSnapshotMetaData} corresponding to named snapshot |
| * @throws KeeperException In case of Zookeeper error |
| * @throws InterruptedException In case of thread interruption. |
| */ |
| public static void createCollectionLevelSnapshot(SolrZkClient zkClient, String collectionName, |
| CollectionSnapshotMetaData meta) throws KeeperException, InterruptedException { |
| String zkPath = getSnapshotMetaDataZkPath(collectionName, Optional.of(meta.getName())); |
| zkClient.makePath(zkPath, Utils.toJSON(meta), CreateMode.PERSISTENT, true); |
| } |
| |
| /** |
| * This method updates an entry for the named snapshot for the specified collection in Zookeeper. |
| * |
| * @param zkClient Zookeeper client |
| * @param collectionName The name of the collection |
| * @param meta The {@linkplain CollectionSnapshotMetaData} corresponding to named snapshot |
| * @throws KeeperException In case of Zookeeper error |
| * @throws InterruptedException In case of thread interruption. |
| */ |
| public static void updateCollectionLevelSnapshot(SolrZkClient zkClient, String collectionName, |
| CollectionSnapshotMetaData meta) throws KeeperException, InterruptedException { |
| String zkPath = getSnapshotMetaDataZkPath(collectionName, Optional.of(meta.getName())); |
| zkClient.setData(zkPath, Utils.toJSON(meta), -1, true); |
| } |
| |
| /** |
| * This method deletes an entry for the named snapshot for the specified collection in Zookeeper. |
| * |
| * @param zkClient Zookeeper client |
| * @param collectionName The name of the collection |
| * @param commitName The name of the snapshot |
| * @throws InterruptedException In case of thread interruption. |
| * @throws KeeperException In case of Zookeeper error |
| */ |
| public static void deleteCollectionLevelSnapshot(SolrZkClient zkClient, String collectionName, String commitName) |
| throws InterruptedException, KeeperException { |
| String zkPath = getSnapshotMetaDataZkPath(collectionName, Optional.of(commitName)); |
| zkClient.delete(zkPath, -1, true); |
| } |
| |
| /** |
| * This method deletes all snapshots for the specified collection in Zookeeper. |
| * |
| * @param zkClient Zookeeper client |
| * @param collectionName The name of the collection |
| * @throws InterruptedException In case of thread interruption. |
| * @throws KeeperException In case of Zookeeper error |
| */ |
| public static void cleanupCollectionLevelSnapshots(SolrZkClient zkClient, String collectionName) |
| throws InterruptedException, KeeperException { |
| String zkPath = getSnapshotMetaDataZkPath(collectionName, Optional.empty()); |
| try { |
| // Delete the meta-data for each snapshot. |
| Collection<String> snapshots = zkClient.getChildren(zkPath, null, true); |
| for (String snapshot : snapshots) { |
| String path = getSnapshotMetaDataZkPath(collectionName, Optional.of(snapshot)); |
| try { |
| zkClient.delete(path, -1, true); |
| } catch (KeeperException ex) { |
| // Gracefully handle the case when the zk node doesn't exist |
| if ( ex.code() != KeeperException.Code.NONODE ) { |
| throw ex; |
| } |
| } |
| } |
| |
| // Delete the parent node. |
| zkClient.delete(zkPath, -1, true); |
| } catch (KeeperException ex) { |
| // Gracefully handle the case when the zk node doesn't exist (e.g. if no snapshots were created for this collection). |
| if ( ex.code() != KeeperException.Code.NONODE ) { |
| throw ex; |
| } |
| } |
| } |
| |
| /** |
| * This method returns the {@linkplain CollectionSnapshotMetaData} for the named snapshot for the specified collection in Zookeeper. |
| * |
| * @param zkClient Zookeeper client |
| * @param collectionName The name of the collection |
| * @param commitName The name of the snapshot |
| * @return (Optional) the {@linkplain CollectionSnapshotMetaData} |
| * @throws InterruptedException In case of thread interruption. |
| * @throws KeeperException In case of Zookeeper error |
| */ |
| public static Optional<CollectionSnapshotMetaData> getCollectionLevelSnapshot(SolrZkClient zkClient, String collectionName, String commitName) |
| throws InterruptedException, KeeperException { |
| String zkPath = getSnapshotMetaDataZkPath(collectionName, Optional.of(commitName)); |
| try { |
| @SuppressWarnings({"unchecked"}) |
| Map<String, Object> data = (Map<String, Object>)Utils.fromJSON(zkClient.getData(zkPath, null, null, true)); |
| return Optional.of(new CollectionSnapshotMetaData(data)); |
| } catch (KeeperException ex) { |
| // Gracefully handle the case when the zk node for a specific |
| // snapshot doesn't exist (e.g. due to a concurrent delete operation). |
| if ( ex.code() == KeeperException.Code.NONODE ) { |
| return Optional.empty(); |
| } |
| throw ex; |
| } |
| } |
| |
| /** |
| * This method returns the {@linkplain CollectionSnapshotMetaData} for each named snapshot for the specified collection in Zookeeper. |
| * |
| * @param zkClient Zookeeper client |
| * @param collectionName The name of the collection |
| * @return the {@linkplain CollectionSnapshotMetaData} for each named snapshot |
| * @throws InterruptedException In case of thread interruption. |
| * @throws KeeperException In case of Zookeeper error |
| */ |
| public static Collection<CollectionSnapshotMetaData> listSnapshots(SolrZkClient zkClient, String collectionName) |
| throws InterruptedException, KeeperException { |
| Collection<CollectionSnapshotMetaData> result = new ArrayList<>(); |
| String zkPath = getSnapshotMetaDataZkPath(collectionName, Optional.empty()); |
| |
| try { |
| Collection<String> snapshots = zkClient.getChildren(zkPath, null, true); |
| for (String snapshot : snapshots) { |
| Optional<CollectionSnapshotMetaData> s = getCollectionLevelSnapshot(zkClient, collectionName, snapshot); |
| if (s.isPresent()) { |
| result.add(s.get()); |
| } |
| } |
| } catch (KeeperException ex) { |
| // Gracefully handle the case when the zk node doesn't exist (e.g. due to a concurrent delete collection operation). |
| if ( ex.code() != KeeperException.Code.NONODE ) { |
| throw ex; |
| } |
| } |
| return result; |
| } |
| |
| |
| /** |
| * This method deletes index files of the {@linkplain IndexCommit} for the specified generation number. |
| * |
| * @param core The Solr core |
| * @param dir The index directory storing the snapshot. |
| * @param gen The generation number of the {@linkplain IndexCommit} to be deleted. |
| * @throws IOException in case of I/O errors. |
| */ |
| public static void deleteSnapshotIndexFiles(SolrCore core, Directory dir, final long gen) throws IOException { |
| deleteSnapshotIndexFiles(core, dir, new IndexDeletionPolicy() { |
| @Override |
| public void onInit(List<? extends IndexCommit> commits) throws IOException { |
| for (IndexCommit ic : commits) { |
| if (gen == ic.getGeneration()) { |
| if (log.isInfoEnabled()) { |
| log.info("Deleting non-snapshotted index commit with generation {}", ic.getGeneration()); |
| } |
| ic.delete(); |
| } |
| } |
| } |
| |
| @Override |
| public void onCommit(List<? extends IndexCommit> commits) |
| throws IOException {} |
| }); |
| } |
| |
| /** |
| * This method deletes index files not associated with the specified <code>snapshots</code>. |
| * |
| * @param core The Solr core |
| * @param dir The index directory storing the snapshot. |
| * @param snapshots The snapshots to be preserved. |
| * @throws IOException in case of I/O errors. |
| */ |
| public static void deleteNonSnapshotIndexFiles(SolrCore core, Directory dir, Collection<SnapshotMetaData> snapshots) throws IOException { |
| final Set<Long> genNumbers = new HashSet<>(); |
| for (SnapshotMetaData m : snapshots) { |
| genNumbers.add(m.getGenerationNumber()); |
| } |
| |
| deleteSnapshotIndexFiles(core, dir, new IndexDeletionPolicy() { |
| @Override |
| public void onInit(List<? extends IndexCommit> commits) throws IOException { |
| for (IndexCommit ic : commits) { |
| if (!genNumbers.contains(ic.getGeneration())) { |
| if (log.isInfoEnabled()) { |
| log.info("Deleting non-snapshotted index commit with generation {}", ic.getGeneration()); |
| } |
| ic.delete(); |
| } |
| } |
| } |
| |
| @Override |
| public void onCommit(List<? extends IndexCommit> commits) |
| throws IOException {} |
| }); |
| } |
| |
| /** |
| * This method deletes index files of the {@linkplain IndexCommit} for the specified generation number. |
| * |
| * @param core The Solr core |
| * @param dir The index directory storing the snapshot. |
| * @throws IOException in case of I/O errors. |
| */ |
| |
| @SuppressWarnings({"try", "unused"}) |
| private static void deleteSnapshotIndexFiles(SolrCore core, Directory dir, IndexDeletionPolicy delPolicy) throws IOException { |
| IndexWriterConfig conf = core.getSolrConfig().indexConfig.toIndexWriterConfig(core); |
| conf.setOpenMode(OpenMode.APPEND); |
| conf.setMergePolicy(NoMergePolicy.INSTANCE);//Don't want to merge any commits here! |
| conf.setIndexDeletionPolicy(delPolicy); |
| conf.setCodec(core.getCodec()); |
| try (SolrIndexWriter iw = new SolrIndexWriter("SolrSnapshotCleaner", dir, conf)) { |
| // Do nothing. The only purpose of opening index writer is to invoke the Lucene IndexDeletionPolicy#onInit |
| // method so that we can cleanup the files associated with specified index commit. |
| // Note the index writer creates a new commit during the close() operation (which is harmless). |
| } |
| } |
| |
| private static String getSnapshotMetaDataZkPath(String collectionName, Optional<String> commitName) { |
| if (commitName.isPresent()) { |
| return "/snapshots/"+collectionName+"/"+commitName.get(); |
| } |
| return "/snapshots/"+collectionName; |
| } |
| } |