<commit_before>/*
 * 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.cloud.api.collections;

import org.apache.commons.io.IOUtils;
import org.apache.lucene.codecs.CodecUtil;
import org.apache.lucene.index.IndexCommit;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.IOContext;
import org.apache.lucene.store.IndexInput;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.embedded.JettySolrRunner;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.request.QueryRequest;
import org.apache.solr.client.solrj.request.UpdateRequest;
import org.apache.solr.client.solrj.response.CollectionAdminResponse;
import org.apache.solr.client.solrj.response.RequestStatusState;
import org.apache.solr.cloud.AbstractDistribZkTestBase;
import org.apache.solr.cloud.SolrCloudTestCase;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.cloud.Replica;
import org.apache.solr.common.cloud.Slice;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.core.DirectoryFactory;
import org.apache.solr.core.SolrCore;
import org.apache.solr.core.TrackingBackupRepository;
import org.apache.solr.core.backup.BackupFilePaths;
import org.apache.solr.core.backup.BackupId;
import org.apache.solr.core.backup.BackupProperties;
import org.apache.solr.core.backup.Checksum;
import org.apache.solr.core.backup.ShardBackupId;
import org.apache.solr.core.backup.ShardBackupMetadata;
import org.apache.solr.core.backup.repository.BackupRepository;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.invoke.MethodHandles;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.apache.solr.core.TrackingBackupRepository.copiedFiles;

/**
 * Used to test the incremental method of backup/restoration (as opposed to the deprecated 'full snapshot' method).
 *
 * For a similar test harness for snapshot backup/restoration see {@link AbstractCloudBackupRestoreTestCase}
 */
public abstract class AbstractIncrementalBackupTest extends SolrCloudTestCase {
    private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    private static long docsSeed; // see indexDocs()
    protected static final int NUM_SHARDS = 2;//granted we sometimes shard split to get more
    protected static final int REPL_FACTOR = 2;
    protected static final String BACKUPNAME_PREFIX = "mytestbackup";
    protected static final String BACKUP_REPO_NAME = "trackingBackupRepository";

    protected String testSuffix = "test1";

    @BeforeClass
    public static void createCluster() throws Exception {
        docsSeed = random().nextLong();
        System.setProperty("solr.directoryFactory", "solr.StandardDirectoryFactory");
    }

    @Before
    public void setUpTrackingRepo() {
        TrackingBackupRepository.clear();
    }

    /**
     * @return The name of the collection to use.
     */
    public abstract String getCollectionNamePrefix();

    public String getCollectionName(){
        return getCollectionNamePrefix() + "_" + testSuffix;
    }

    public void setTestSuffix(String testSuffix) {
        this.testSuffix = testSuffix;
    }

    /**
     * @return The absolute path for the backup location.
     *         Could return null.
     */
    public abstract String getBackupLocation();

    @Test
    public void testSimple() throws Exception {
        setTestSuffix("testbackupincsimple");
        final String backupCollectionName = getCollectionName();
        final String restoreCollectionName = backupCollectionName + "_restore";

        CloudSolrClient solrClient = cluster.getSolrClient();

        CollectionAdminRequest
                .createCollection(backupCollectionName, "conf1", NUM_SHARDS, 1)
                .process(solrClient);
        int totalIndexedDocs = indexDocs(backupCollectionName, true);
        String backupName = BACKUPNAME_PREFIX + testSuffix;
        try (BackupRepository repository = cluster.getJettySolrRunner(0).getCoreContainer()
                .newBackupRepository(BACKUP_REPO_NAME)) {
            String backupLocation = repository.getBackupLocation(getBackupLocation());
            long t = System.nanoTime();
            int expectedDocsForFirstBackup = totalIndexedDocs;
            CollectionAdminRequest.backupCollection(backupCollectionName, backupName)
                    .setLocation(backupLocation)
                    .setIncremental(true)
                    .setRepositoryName(BACKUP_REPO_NAME)
                    .processAndWait(cluster.getSolrClient(), 100);
            long timeTaken = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t);
            log.info("Created backup with {} docs, took {}ms", totalIndexedDocs, timeTaken);
            totalIndexedDocs += indexDocs(backupCollectionName, true);

            t = System.nanoTime();
            CollectionAdminRequest.backupCollection(backupCollectionName, backupName)
                    .setLocation(backupLocation)
                    .setIncremental(true)
                    .setRepositoryName(BACKUP_REPO_NAME)
                    .processAndWait(cluster.getSolrClient(), 100);
            timeTaken = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t);
            long numFound = cluster.getSolrClient().query(backupCollectionName,
                    new SolrQuery("*:*")).getResults().getNumFound();
            log.info("Created backup with {} docs, took {}ms", numFound, timeTaken);

            t = System.nanoTime();
            randomlyPrecreateRestoreCollection(restoreCollectionName, "conf1", NUM_SHARDS, 1);
            CollectionAdminRequest.restoreCollection(restoreCollectionName, backupName)
                    .setBackupId(0)
                    .setLocation(backupLocation)
                    .setRepositoryName(BACKUP_REPO_NAME)
                    .processAndWait(solrClient, 500);
            timeTaken = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t);
            log.info("Restored from backup, took {}ms", timeTaken);
            numFound = cluster.getSolrClient().query(restoreCollectionName,
                    new SolrQuery("*:*")).getResults().getNumFound();
            assertEquals(expectedDocsForFirstBackup, numFound);
        }
    }

    @Test
    public void testRestoreToOriginalCollection() throws Exception {
        setTestSuffix("testbackuprestoretooriginal");
        final String backupCollectionName = getCollectionName();
        final String backupName = BACKUPNAME_PREFIX + testSuffix;

        // Bootstrap the backup collection with seed docs
        CollectionAdminRequest
                .createCollection(backupCollectionName, "conf1", NUM_SHARDS, REPL_FACTOR)
                .process(cluster.getSolrClient());
        final int firstBatchNumDocs = indexDocs(backupCollectionName, true);<commit_msg>Add recovery check before querying in backup-restore test.<commit_after>/*
 * 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.cloud.api.collections;

import org.apache.commons.io.IOUtils;
import org.apache.lucene.codecs.CodecUtil;
import org.apache.lucene.index.IndexCommit;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.IOContext;
import org.apache.lucene.store.IndexInput;
import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.embedded.JettySolrRunner;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.request.QueryRequest;
import org.apache.solr.client.solrj.request.UpdateRequest;
import org.apache.solr.client.solrj.response.CollectionAdminResponse;
import org.apache.solr.client.solrj.response.RequestStatusState;
import org.apache.solr.cloud.AbstractDistribZkTestBase;
import org.apache.solr.cloud.SolrCloudTestCase;
import org.apache.solr.common.SolrInputDocument;
import org.apache.solr.common.cloud.Replica;
import org.apache.solr.common.cloud.Slice;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.core.DirectoryFactory;
import org.apache.solr.core.SolrCore;
import org.apache.solr.core.TrackingBackupRepository;
import org.apache.solr.core.backup.BackupFilePaths;
import org.apache.solr.core.backup.BackupId;
import org.apache.solr.core.backup.BackupProperties;
import org.apache.solr.core.backup.Checksum;
import org.apache.solr.core.backup.ShardBackupId;
import org.apache.solr.core.backup.ShardBackupMetadata;
import org.apache.solr.core.backup.repository.BackupRepository;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.lang.invoke.MethodHandles;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static org.apache.solr.core.TrackingBackupRepository.copiedFiles;

/**
 * Used to test the incremental method of backup/restoration (as opposed to the deprecated 'full snapshot' method).
 *
 * For a similar test harness for snapshot backup/restoration see {@link AbstractCloudBackupRestoreTestCase}
 */
public abstract class AbstractIncrementalBackupTest extends SolrCloudTestCase {
    private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

    private static long docsSeed; // see indexDocs()
    protected static final int NUM_SHARDS = 2;//granted we sometimes shard split to get more
    protected static final int REPL_FACTOR = 2;
    protected static final String BACKUPNAME_PREFIX = "mytestbackup";
    protected static final String BACKUP_REPO_NAME = "trackingBackupRepository";

    protected String testSuffix = "test1";

    @BeforeClass
    public static void createCluster() throws Exception {
        docsSeed = random().nextLong();
        System.setProperty("solr.directoryFactory", "solr.StandardDirectoryFactory");
    }

    @Before
    public void setUpTrackingRepo() {
        TrackingBackupRepository.clear();
    }

    /**
     * @return The name of the collection to use.
     */
    public abstract String getCollectionNamePrefix();

    public String getCollectionName(){
        return getCollectionNamePrefix() + "_" + testSuffix;
    }

    public void setTestSuffix(String testSuffix) {
        this.testSuffix = testSuffix;
    }

    /**
     * @return The absolute path for the backup location.
     *         Could return null.
     */
    public abstract String getBackupLocation();

    @Test
    public void testSimple() throws Exception {
        setTestSuffix("testbackupincsimple");
        final String backupCollectionName = getCollectionName();
        final String restoreCollectionName = backupCollectionName + "_restore";

        CloudSolrClient solrClient = cluster.getSolrClient();

        CollectionAdminRequest
                .createCollection(backupCollectionName, "conf1", NUM_SHARDS, 1)
                .process(solrClient);
        int totalIndexedDocs = indexDocs(backupCollectionName, true);
        String backupName = BACKUPNAME_PREFIX + testSuffix;
        try (BackupRepository repository = cluster.getJettySolrRunner(0).getCoreContainer()
                .newBackupRepository(BACKUP_REPO_NAME)) {
            String backupLocation = repository.getBackupLocation(getBackupLocation());
            long t = System.nanoTime();
            int expectedDocsForFirstBackup = totalIndexedDocs;
            CollectionAdminRequest.backupCollection(backupCollectionName, backupName)
                    .setLocation(backupLocation)
                    .setIncremental(true)
                    .setRepositoryName(BACKUP_REPO_NAME)
                    .processAndWait(cluster.getSolrClient(), 100);
            long timeTaken = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t);
            log.info("Created backup with {} docs, took {}ms", totalIndexedDocs, timeTaken);
            totalIndexedDocs += indexDocs(backupCollectionName, true);

            t = System.nanoTime();
            CollectionAdminRequest.backupCollection(backupCollectionName, backupName)
                    .setLocation(backupLocation)
                    .setIncremental(true)
                    .setRepositoryName(BACKUP_REPO_NAME)
                    .processAndWait(cluster.getSolrClient(), 100);
            timeTaken = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t);
            long numFound = cluster.getSolrClient().query(backupCollectionName,
                    new SolrQuery("*:*")).getResults().getNumFound();
            log.info("Created backup with {} docs, took {}ms", numFound, timeTaken);

            t = System.nanoTime();
            randomlyPrecreateRestoreCollection(restoreCollectionName, "conf1", NUM_SHARDS, 1);
            CollectionAdminRequest.restoreCollection(restoreCollectionName, backupName)
                    .setBackupId(0)
                    .setLocation(backupLocation)
                    .setRepositoryName(BACKUP_REPO_NAME)
                    .processAndWait(solrClient, 500);
            timeTaken = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t);
            log.info("Restored from backup, took {}ms", timeTaken);
            t = System.nanoTime();
            AbstractDistribZkTestBase.waitForRecoveriesToFinish(
                restoreCollectionName, cluster.getSolrClient().getZkStateReader(), log.isDebugEnabled(), false, 3);
            timeTaken = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - t);
            log.info("Restored collection healthy, took {}ms", timeTaken);
            numFound = cluster.getSolrClient().query(restoreCollectionName,
                    new SolrQuery("*:*")).getResults().getNumFound();
            assertEquals(expectedDocsForFirstBackup, numFound);
        }
    }

    @Test
    public void testRestoreToOriginalCollection() throws Exception {
        setTestSuffix("testbackuprestoretooriginal");
        final String backupCollectionName = getCollectionName();
        final String backupName = BACKUPNAME_PREFIX + testSuffix;

        // Bootstrap the backup collection with seed docs
        CollectionAdminRequest
                .createCollection(backupCollectionName, "conf1", NUM_SHARDS, REPL_FACTOR)
                .process(cluster.getSolrClient());
        final int firstBatchNumDocs = indexDocs(backupCollectionName, true);
{'fraction_non_alphanumeric': 0.09140853302162479, 'fraction_numerical': 0.0029807130333138515, 'mean_word_length': 3.507639620653319, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7665859', 'n_tokens_mistral': 4791, 'n_tokens_neox': 4664, 'n_words': 1069}