org.apache.solr.handler.TestReplicationHandlerBackup.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.solr.handler.TestReplicationHandlerBackup.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.handler;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.invoke.MethodHandles;
import java.net.URL;
import java.nio.file.DirectoryStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Iterator;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.commons.io.IOUtils;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.IndexSearcher;
import org.apache.lucene.search.MatchAllDocsQuery;
import org.apache.lucene.search.TopDocs;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.SimpleFSDirectory;
import org.apache.lucene.util.TestUtil;
import org.apache.solr.SolrJettyTestBase;
import org.apache.solr.SolrTestCaseJ4;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.embedded.JettyConfig;
import org.apache.solr.client.solrj.embedded.JettySolrRunner;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.util.FileUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@SolrTestCaseJ4.SuppressSSL // Currently unknown why SSL does not work with this test
public class TestReplicationHandlerBackup extends SolrJettyTestBase {

    JettySolrRunner masterJetty;
    TestReplicationHandler.SolrInstance master = null;
    SolrClient masterClient;

    private static final String CONF_DIR = "solr" + File.separator + "collection1" + File.separator + "conf"
            + File.separator;

    private static String context = "/solr";

    boolean addNumberToKeepInRequest = true;
    String backupKeepParamName = ReplicationHandler.NUMBER_BACKUPS_TO_KEEP_REQUEST_PARAM;
    private static long docsSeed; // see indexDocs()
    private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    private static JettySolrRunner createJetty(TestReplicationHandler.SolrInstance instance) throws Exception {
        FileUtils.copyFile(new File(SolrTestCaseJ4.TEST_HOME(), "solr.xml"),
                new File(instance.getHomeDir(), "solr.xml"));
        Properties nodeProperties = new Properties();
        nodeProperties.setProperty("solr.data.dir", instance.getDataDir());
        JettyConfig jettyConfig = JettyConfig.builder().setContext("/solr").setPort(0).build();
        JettySolrRunner jetty = new JettySolrRunner(instance.getHomeDir(), nodeProperties, jettyConfig);
        jetty.start();
        return jetty;
    }

    private static SolrClient createNewSolrClient(int port) {
        try {
            // setup the client...
            final String baseUrl = buildUrl(port, context);
            HttpSolrClient client = getHttpSolrClient(baseUrl);
            client.setConnectionTimeout(15000);
            client.setSoTimeout(60000);
            return client;
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    }

    @Before
    public void setUp() throws Exception {
        super.setUp();
        String configFile = "solrconfig-master1.xml";

        if (random().nextBoolean()) {
            configFile = "solrconfig-master1-keepOneBackup.xml";
            addNumberToKeepInRequest = false;
            backupKeepParamName = ReplicationHandler.NUMBER_BACKUPS_TO_KEEP_INIT_PARAM;
        }
        master = new TestReplicationHandler.SolrInstance(createTempDir("solr-instance").toFile(), "master", null);
        master.setUp();
        master.copyConfigFile(CONF_DIR + configFile, "solrconfig.xml");

        masterJetty = createJetty(master);
        masterClient = createNewSolrClient(masterJetty.getLocalPort());
        docsSeed = random().nextLong();
    }

    @Override
    @After
    public void tearDown() throws Exception {
        super.tearDown();
        masterClient.close();
        masterClient = null;
        masterJetty.stop();
        masterJetty = null;
        master = null;
    }

    @Test
    public void testBackupOnCommit() throws Exception {
        //Index
        int nDocs = BackupRestoreUtils.indexDocs(masterClient, DEFAULT_TEST_COLLECTION_NAME, docsSeed);

        //Confirm if completed
        CheckBackupStatus checkBackupStatus = new CheckBackupStatus((HttpSolrClient) masterClient,
                DEFAULT_TEST_CORENAME);
        while (!checkBackupStatus.success) {
            checkBackupStatus.fetchStatus();
            Thread.sleep(1000);
        }

        //Validate
        try (DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get(master.getDataDir()), "snapshot*")) {
            Path snapDir = stream.iterator().next();
            verify(snapDir, nDocs);
        }
    }

    private void verify(Path backup, int nDocs) throws IOException {
        try (Directory dir = new SimpleFSDirectory(backup); IndexReader reader = DirectoryReader.open(dir)) {
            IndexSearcher searcher = new IndexSearcher(reader);
            TopDocs hits = searcher.search(new MatchAllDocsQuery(), 1);
            assertEquals(nDocs, hits.totalHits);
        }
    }

    @Test
    public void doTestBackup() throws Exception {

        int nDocs = BackupRestoreUtils.indexDocs(masterClient, DEFAULT_TEST_COLLECTION_NAME, docsSeed);

        //Confirm if completed
        CheckBackupStatus checkBackupStatus = new CheckBackupStatus((HttpSolrClient) masterClient,
                DEFAULT_TEST_CORENAME);
        while (!checkBackupStatus.success) {
            checkBackupStatus.fetchStatus();
            Thread.sleep(1000);
        }

        Path[] snapDir = new Path[5]; //One extra for the backup on commit
        //First snapshot location
        try (DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get(master.getDataDir()), "snapshot*")) {
            snapDir[0] = stream.iterator().next();
        }

        boolean namedBackup = random().nextBoolean();
        String firstBackupTimestamp = null;

        String[] backupNames = null;
        if (namedBackup) {
            backupNames = new String[4];
        }
        for (int i = 0; i < 4; i++) {
            final String backupName = TestUtil.randomSimpleString(random(), 1, 20);
            if (!namedBackup) {
                if (addNumberToKeepInRequest) {
                    runBackupCommand(masterJetty, ReplicationHandler.CMD_BACKUP, "&" + backupKeepParamName + "=2");
                } else {
                    runBackupCommand(masterJetty, ReplicationHandler.CMD_BACKUP, "");
                }
            } else {
                runBackupCommand(masterJetty, ReplicationHandler.CMD_BACKUP, "&name=" + backupName);
                backupNames[i] = backupName;
            }

            checkBackupStatus = new CheckBackupStatus((HttpSolrClient) masterClient, DEFAULT_TEST_CORENAME,
                    firstBackupTimestamp);
            while (!checkBackupStatus.success) {
                checkBackupStatus.fetchStatus();
                Thread.sleep(1000);
            }
            if (i == 0) {
                firstBackupTimestamp = checkBackupStatus.backupTimestamp;
            }

            if (!namedBackup) {
                try (DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get(master.getDataDir()),
                        "snapshot*")) {
                    snapDir[i + 1] = stream.iterator().next();
                }
            } else {
                try (DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get(master.getDataDir()),
                        "snapshot." + backupName)) {
                    snapDir[i + 1] = stream.iterator().next();
                }
            }
            verify(snapDir[i + 1], nDocs);

        }

        //Test Deletion of named backup
        if (namedBackup) {
            testDeleteNamedBackup(backupNames);
        } else {
            //5 backups got created. 4 explicitly and one because a commit was called.
            // Only the last two should still exist.
            int count = 0;
            try (DirectoryStream<Path> stream = Files.newDirectoryStream(Paths.get(master.getDataDir()),
                    "snapshot*")) {
                Iterator<Path> iter = stream.iterator();
                while (iter.hasNext()) {
                    iter.next();
                    count++;
                }
            }

            //There will be 2 backups, otherwise 1
            if (backupKeepParamName.equals(ReplicationHandler.NUMBER_BACKUPS_TO_KEEP_REQUEST_PARAM)) {
                assertEquals(2, count);

                if (Files.exists(snapDir[0]) || Files.exists(snapDir[1]) || Files.exists(snapDir[2])) {
                    fail("Backup should have been cleaned up because " + backupKeepParamName + " was set to 2.");
                }
            } else {
                assertEquals(1, count);

                if (Files.exists(snapDir[0]) || Files.exists(snapDir[1]) || Files.exists(snapDir[2])
                        || Files.exists(snapDir[3])) {
                    fail("Backup should have been cleaned up because " + backupKeepParamName + " was set to 1.");
                }
            }

        }
    }

    private void testDeleteNamedBackup(String backupNames[]) throws InterruptedException, IOException {
        String lastTimestamp = null;
        for (int i = 0; i < 2; i++) {
            runBackupCommand(masterJetty, ReplicationHandler.CMD_DELETE_BACKUP, "&name=" + backupNames[i]);
            CheckDeleteBackupStatus checkDeleteBackupStatus = new CheckDeleteBackupStatus(backupNames[i],
                    lastTimestamp);
            while (true) {
                boolean success = checkDeleteBackupStatus.fetchStatus();
                if (success) {
                    lastTimestamp = checkDeleteBackupStatus.lastTimestamp;
                    if (i == 0) {
                        Thread.sleep(1000); //make the timestamp change
                    }
                    break;
                }
                Thread.sleep(200);
            }
        }
    }

    public static void runBackupCommand(JettySolrRunner masterJetty, String cmd, String params) throws IOException {
        String masterUrl = buildUrl(masterJetty.getLocalPort(), context) + "/" + DEFAULT_TEST_CORENAME
                + ReplicationHandler.PATH + "?command=" + cmd + params;
        InputStream stream = null;
        try {
            URL url = new URL(masterUrl);
            stream = url.openStream();
            stream.close();
        } finally {
            IOUtils.closeQuietly(stream);
        }
    }

    private class CheckDeleteBackupStatus {
        String response = null;
        private String backupName;
        final Pattern p = Pattern.compile("<str name=\"snapshotDeletedAt\">(.*?)</str>");
        String lastTimestamp;

        private CheckDeleteBackupStatus(String backupName, String lastTimestamp) {
            this.backupName = backupName;
            this.lastTimestamp = lastTimestamp;
        }

        public boolean fetchStatus() throws IOException {
            String masterUrl = buildUrl(masterJetty.getLocalPort(), context) + "/" + DEFAULT_TEST_CORENAME
                    + ReplicationHandler.PATH + "?command=" + ReplicationHandler.CMD_DETAILS;
            URL url;
            InputStream stream = null;
            try {
                url = new URL(masterUrl);
                stream = url.openStream();
                response = IOUtils.toString(stream, "UTF-8");
                if (response.contains("<str name=\"status\">success</str>")) {
                    Matcher m = p.matcher(response);
                    if (m.find() && (lastTimestamp == null || !lastTimestamp.equals(m.group(1)))) {
                        lastTimestamp = m.group(1);
                        return true;
                    }
                } else if (response
                        .contains("<str name=\"status\">Unable to delete snapshot: " + backupName + "</str>")) {
                    return false;
                }
                stream.close();
            } finally {
                IOUtils.closeQuietly(stream);
            }
            return false;
        }
    }
}