org.codice.ddf.commands.solr.RestoreCommand.java Source code

Java tutorial

Introduction

Here is the source code for org.codice.ddf.commands.solr.RestoreCommand.java

Source

/**
 * Copyright (c) Codice Foundation
 *
 * <p>This is free software: you can redistribute it and/or modify it under the terms of the GNU
 * Lesser General Public License as published by the Free Software Foundation, either version 3 of
 * the License, or any later version.
 *
 * <p>This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details. A copy of the GNU Lesser General Public
 * License is distributed along with this program and can be found at
 * <http://www.gnu.org/licenses/lgpl.html>.
 */
package org.codice.ddf.commands.solr;

import com.google.common.annotations.VisibleForTesting;
import ddf.security.encryption.EncryptionService;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang3.StringUtils;
import org.apache.cxf.jaxrs.ext.Nullable;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.StatusLine;
import org.apache.http.client.utils.URIBuilder;
import org.apache.karaf.shell.api.action.Command;
import org.apache.karaf.shell.api.action.Option;
import org.apache.karaf.shell.api.action.lifecycle.Reference;
import org.apache.karaf.shell.api.action.lifecycle.Service;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.response.CollectionAdminResponse;
import org.apache.solr.client.solrj.response.RequestStatusState;
import org.codice.solr.factory.impl.ConfigurationStore;
import org.codice.solr.factory.impl.HttpSolrClientFactory;

@Service
@Command(scope = SolrCommands.NAMESPACE, name = "restore", description = "Restores a selected Solr core/collection from backup.")
public class RestoreCommand extends SolrCommands {
    @Reference
    private EncryptionService encryptionService;

    @Option(name = "-d", aliases = { "--dir" }, description = "The location of the backup files to be restored.")
    @VisibleForTesting
    protected String backupLocation;

    @Option(name = "-n", aliases = {
            "--name" }, description = "The name of the backed up index snapshot to be restored. If the name is not provided it looks for backups with snapshot.<timestamp> format in the location directory. It picks the latest timestamp backup in that case.")
    @VisibleForTesting
    protected String backupName;

    @Option(name = "-c", aliases = {
            "--core" }, description = "Specify the Solr core to operate on (e.g. catalog). Defaults to catalog if one isn't provided.")
    @VisibleForTesting
    protected String coreName = DEFAULT_CORE_NAME;

    @Option(name = "-s", aliases = {
            "--restoreStatus" }, description = "Get the status of a SolrCloud asynchronous restore. Used in conjunction with --requestId.")
    @VisibleForTesting
    protected boolean status;

    @Option(name = "-i", aliases = {
            "--requestId" }, description = "Request Id returned after performing a restore. This request Id is used to track"
                    + " the status of a given restore. When requesting a restore status, --restoreStatus and --requestId"
                    + " are both required options.")
    @VisibleForTesting
    protected String requestId;

    @Option(name = "-a", aliases = { "--asyncRestore" }, description = "Perform an asynchronous restore.")
    @VisibleForTesting
    protected boolean asyncRestore;

    @Option(name = "-f", aliases = {
            "--force" }, description = "Forces the restore. Will delete the collection if it already exists.")
    @VisibleForTesting
    protected boolean force = false;

    @Override
    public Object execute() throws Exception {
        if (isSystemConfiguredWithSolrCloud()) {
            performSolrCloudRestore();
        } else {
            performSingleNodeSolrRestore();
        }

        return null;
    }

    private void performSingleNodeSolrRestore() throws URISyntaxException {
        String restoreUrl = getReplicationUrl(coreName);

        try {
            createSolrCore();

            httpBuilder = new org.codice.solr.factory.impl.HttpClientBuilder(encryptionService);
            URIBuilder uriBuilder = new URIBuilder(restoreUrl);
            uriBuilder.addParameter("command", "restore");

            if (StringUtils.isNotBlank(backupLocation)) {
                uriBuilder.addParameter("location", backupLocation);
            }

            URI restoreUri = uriBuilder.build();
            LOGGER.debug("Sending request to {}", restoreUri);

            printResponse(sendGetRequest(restoreUri));
        } catch (IOException | SolrServerException e) {
            LOGGER.info("Unable to perform single node Solr restore, core: {}", coreName, e);
        }
    }

    private void performSolrCloudRestore() throws IOException {
        SolrClient client = null;
        try {
            client = getCloudSolrClient();
            if (status) {
                verifyStatusInput();
                printStatus(client, requestId);
            } else {
                verifyRestoreInput();
                performSolrCloudRestore(client);
            }
        } finally {
            shutdown(client);
        }
    }

    private void printResponse(@Nullable HttpResponse response) {
        if (response != null) {
            StatusLine statusLine = response.getStatusLine();
            if (statusLine.getStatusCode() == HttpStatus.SC_OK) {
                printSuccessMessage(String.format("%nRestore of [%s] complete.%n", coreName));
            } else {
                printErrorMessage(String.format("Error restoring Solr core: [%s]", coreName));
                printErrorMessage(String.format("Restore command failed due to: %d - %s",
                        statusLine.getStatusCode(), statusLine.getReasonPhrase()));
                printErrorMessage(String.format("Request: %s", getReplicationUrl(coreName)));
            }
        }
    }

    private boolean restore(SolrClient client, String collection, String backupLocation, String backupName)
            throws IOException, InterruptedException, SolrServerException {
        if (!canRestore(client, collection)) {
            LOGGER.warn("Unable to restore collection {}", collection);
            return false;
        }

        CollectionAdminRequest.Restore restore = CollectionAdminRequest.AsyncCollectionAdminRequest
                .restoreCollection(collection, backupName).setLocation(backupLocation);

        String syncReqId = restore.processAsync(client);

        boolean restoreComplete = false;

        while (true) {
            CollectionAdminRequest.RequestStatusResponse requestStatusResponse = CollectionAdminRequest
                    .requestStatus(syncReqId).process(client);
            RequestStatusState requestStatus = requestStatusResponse.getRequestStatus();
            if (requestStatus == RequestStatusState.COMPLETED) {
                LOGGER.debug("Restore status: {}", requestStatus);
                restoreComplete = true;
                break;
            } else if (requestStatus == RequestStatusState.FAILED
                    || requestStatus == RequestStatusState.NOT_FOUND) {
                LOGGER.debug("Restore status: {}", requestStatus);
                printErrorMessage("Restore failed. ");
                printResponseErrorMessages(requestStatusResponse);
                break;
            }
            TimeUnit.SECONDS.sleep(1);
        }

        return restoreComplete;
    }

    private String restoreAsync(SolrClient client, String collection, String backupLocation, String backupName)
            throws IOException, SolrServerException {

        if (canRestore(client, collection)) {
            CollectionAdminRequest.Restore restore = CollectionAdminRequest.AsyncCollectionAdminRequest
                    .restoreCollection(collection, backupName).setLocation(backupLocation);

            String requestId = restore.processAsync(client);
            LOGGER.debug("Restore request Id: {}", requestId);
            return requestId;
        } else {
            printErrorMessage(String.format("Unable to restore %s, collection already exists.", collection));
            LOGGER.debug("Unable to restore: {}, collection already exists", collection);
            return null;
        }
    }

    private void performSolrCloudRestore(SolrClient client) {
        LOGGER.debug("Restoring collection {} from {} / {}.", coreName, backupLocation, backupName);
        printInfoMessage(
                String.format("Restoring collection [%s] from [%s] / [%s].", coreName, backupLocation, backupName));
        try {
            if (asyncRestore) {
                String requestId = restoreAsync(client, coreName, backupLocation, backupName);
                printInfoMessage("Restore request Id: " + requestId);
            } else {
                boolean isSuccess = restore(client, coreName, backupLocation, backupName);
                if (isSuccess) {
                    printInfoMessage("Restore complete.");
                }
            }
        } catch (InterruptedException | InterruptedIOException ie) {
            printErrorMessage("Restore was interrupted");
            LOGGER.debug("Solr cloud restore was interrupted", ie);
            Thread.currentThread().interrupt();
        } catch (Exception e) {
            printErrorMessage("Restore failed.");
            LOGGER.debug("Restore failed for core: {}.", coreName, e);
        }
    }

    private boolean canRestore(SolrClient client, String collection) throws IOException, SolrServerException {

        LOGGER.trace("Checking restoration capability of collection {}", collection);
        if (collectionExists(client, collection)) {
            LOGGER.trace("Collection {} already exists", collection);
            if (force) {
                LOGGER.trace("Force option set, deleting existing {} collection", collection);
                CollectionAdminRequest.Delete delete = CollectionAdminRequest.deleteCollection(collection);
                CollectionAdminResponse response = delete.process(client);
                return response.isSuccess();
            } else {
                printErrorMessage(String.format(
                        "Force option not set, cannot restore %s collection. Use --force to restore when the collection exists.",
                        collection));
                LOGGER.trace("Force option not set, cannot restore {} collection", collection);
                return false;
            }
        }
        return true;
    }

    private void createSolrCore() throws IOException, SolrServerException {
        httpBuilder = new org.codice.solr.factory.impl.HttpClientBuilder(encryptionService);
        String url = getBackupUrl();
        final String solrDataDir = getSolrDataDir();
        ConfigurationStore.getInstance().setDataDirectoryPath(solrDataDir);
        HttpSolrClientFactory.createSolrCore(url, coreName, null, httpBuilder.get().build());
    }

    private void verifyStatusInput() {
        if (status && StringUtils.isBlank(requestId)) {
            throw new IllegalArgumentException("Status request is missing requestId." + SEE_COMMAND_USAGE_MESSAGE);
        }

        if (asyncRestore) {
            throw new IllegalArgumentException(
                    "asyncRestore passed to status request." + SEE_COMMAND_USAGE_MESSAGE);
        }
    }

    private void verifyRestoreInput() {

        if (StringUtils.isNotBlank(requestId)) {
            LOGGER.debug("requestId option given without asyncStatus option, ignoring.");
        }

        if (StringUtils.isBlank(backupLocation)) {
            throw new IllegalArgumentException("backupLocation must be provided. " + SEE_COMMAND_USAGE_MESSAGE);
        }

        if (StringUtils.isBlank(backupName)) {
            throw new IllegalArgumentException("backupName must be provided. " + SEE_COMMAND_USAGE_MESSAGE);
        }
    }
}