org.apache.solr.core.snapshots.SolrSnapshotManager.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.solr.core.snapshots.SolrSnapshotManager.java

Source

/*
 * 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 {
            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()) {
                        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())) {
                        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.
     */
    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;
    }
}