org.apache.solr.cloud.autoscaling.ExecutePlanAction.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.solr.cloud.autoscaling.ExecutePlanAction.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.cloud.autoscaling;

import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.solr.client.solrj.SolrRequest;
import org.apache.solr.client.solrj.SolrResponse;
import org.apache.solr.client.solrj.SolrServerException;
import org.apache.solr.client.solrj.cloud.autoscaling.AlreadyExistsException;
import org.apache.solr.client.solrj.cloud.autoscaling.DistribStateManager;
import org.apache.solr.client.solrj.cloud.autoscaling.SolrCloudManager;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.response.RequestStatusState;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.cloud.ZkStateReader;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.Utils;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * This class is responsible for executing cluster operations read from the {@link ActionContext}'s properties
 * with the key name "operations"
 */
public class ExecutePlanAction extends TriggerActionBase {
    private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
    private static final String PREFIX = "op-";

    static final int DEFAULT_TASK_TIMEOUT_SECONDS = 120;

    @Override
    public void process(TriggerEvent event, ActionContext context) throws Exception {
        log.debug("-- processing event: {} with context properties: {}", event, context.getProperties());
        SolrCloudManager dataProvider = context.getCloudManager();
        List<SolrRequest> operations = (List<SolrRequest>) context.getProperty("operations");
        if (operations == null || operations.isEmpty()) {
            log.info("No operations to execute for event: {}", event);
            return;
        }
        try {
            for (SolrRequest operation : operations) {
                log.info("Executing operation: {}", operation.getParams());
                try {
                    SolrResponse response = null;
                    int counter = 0;
                    if (operation instanceof CollectionAdminRequest.AsyncCollectionAdminRequest) {
                        CollectionAdminRequest.AsyncCollectionAdminRequest req = (CollectionAdminRequest.AsyncCollectionAdminRequest) operation;
                        // waitForFinalState so that the end effects of operations are visible
                        req.setWaitForFinalState(true);
                        String asyncId = event.getSource() + '/' + event.getId() + '/' + counter;
                        String znode = saveAsyncId(dataProvider.getDistribStateManager(), event, asyncId);
                        log.debug("Saved requestId: {} in znode: {}", asyncId, znode);
                        // TODO: find a better way of using async calls using dataProvider API !!!
                        req.setAsyncId(asyncId);
                        SolrResponse asyncResponse = dataProvider.request(req);
                        if (asyncResponse.getResponse().get("error") != null) {
                            throw new IOException("" + asyncResponse.getResponse().get("error"));
                        }
                        asyncId = (String) asyncResponse.getResponse().get("requestid");
                        CollectionAdminRequest.RequestStatusResponse statusResponse = waitForTaskToFinish(
                                dataProvider, asyncId, DEFAULT_TASK_TIMEOUT_SECONDS, TimeUnit.SECONDS);
                        if (statusResponse != null) {
                            RequestStatusState state = statusResponse.getRequestStatus();
                            if (state == RequestStatusState.COMPLETED || state == RequestStatusState.FAILED
                                    || state == RequestStatusState.NOT_FOUND) {
                                try {
                                    dataProvider.getDistribStateManager().removeData(znode, -1);
                                } catch (Exception e) {
                                    log.warn("Unexpected exception while trying to delete znode: " + znode, e);
                                }
                            }
                            response = statusResponse;
                        }
                    } else {
                        response = dataProvider.request(operation);
                    }
                    NamedList<Object> result = response.getResponse();
                    context.getProperties().compute("responses", (s, o) -> {
                        List<NamedList<Object>> responses = (List<NamedList<Object>>) o;
                        if (responses == null)
                            responses = new ArrayList<>(operations.size());
                        responses.add(result);
                        return responses;
                    });
                } catch (IOException e) {
                    throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "Unable to talk to ZooKeeper", e);
                    //        } catch (InterruptedException e) {
                    //          Thread.currentThread().interrupt();
                    //          throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, "ExecutePlanAction was interrupted", e);
                } catch (Exception e) {
                    throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
                            "Unexpected exception executing operation: " + operation.getParams(), e);
                }

                //        counter++;
            }
        } catch (Exception e) {
            throw new SolrException(SolrException.ErrorCode.SERVER_ERROR,
                    "Unexpected exception while processing event: " + event, e);
        }
    }

    static CollectionAdminRequest.RequestStatusResponse waitForTaskToFinish(SolrCloudManager dataProvider,
            String requestId, long duration, TimeUnit timeUnit) throws IOException, InterruptedException {
        long timeoutSeconds = timeUnit.toSeconds(duration);
        RequestStatusState state = RequestStatusState.NOT_FOUND;
        CollectionAdminRequest.RequestStatusResponse statusResponse = null;
        for (int i = 0; i < timeoutSeconds; i++) {
            try {
                statusResponse = (CollectionAdminRequest.RequestStatusResponse) dataProvider
                        .request(CollectionAdminRequest.requestStatus(requestId));
                state = statusResponse.getRequestStatus();
                if (state == RequestStatusState.COMPLETED || state == RequestStatusState.FAILED) {
                    log.info("Task with requestId={} finished with state={} in {}s", requestId, state, i * 5);
                    dataProvider.request(CollectionAdminRequest.deleteAsyncId(requestId));
                    return statusResponse;
                } else if (state == RequestStatusState.NOT_FOUND) {
                    // the request for this id was never actually submitted! no harm done, just bail out
                    log.warn("Task with requestId={} was not found on overseer", requestId);
                    dataProvider.request(CollectionAdminRequest.deleteAsyncId(requestId));
                    return statusResponse;
                }
            } catch (Exception e) {
                Throwable rootCause = ExceptionUtils.getRootCause(e);
                if (rootCause instanceof IllegalStateException
                        && rootCause.getMessage().contains("Connection pool shut down")) {
                    throw e;
                }
                if (rootCause instanceof TimeoutException
                        && rootCause.getMessage().contains("Could not connect to ZooKeeper")) {
                    throw e;
                }
                if (rootCause instanceof SolrServerException) {
                    throw e;
                }
                log.error("Unexpected Exception while querying status of requestId=" + requestId, e);
            }
            if (i > 0 && i % 5 == 0) {
                log.debug("Task with requestId={} still not complete after {}s. Last state={}", requestId, i * 5,
                        state);
            }
            TimeUnit.SECONDS.sleep(5);
        }
        log.debug("Task with requestId={} did not complete within 5 minutes. Last state={}", requestId, state);
        return statusResponse;
    }

    /**
     * Saves the given asyncId in ZK as a persistent sequential node.
     *
     * @return the path of the newly created node in ZooKeeper
     */
    private String saveAsyncId(DistribStateManager stateManager, TriggerEvent event, String asyncId)
            throws InterruptedException, AlreadyExistsException, IOException, KeeperException {
        String parentPath = ZkStateReader.SOLR_AUTOSCALING_TRIGGER_STATE_PATH + "/" + event.getSource() + "/"
                + getName();
        try {
            stateManager.makePath(parentPath);
        } catch (AlreadyExistsException e) {
            // ignore
        }
        return stateManager.createData(parentPath + "/" + PREFIX,
                Utils.toJSON(Collections.singletonMap("requestid", asyncId)), CreateMode.PERSISTENT_SEQUENTIAL);
    }

}