org.apache.solr.handler.admin.ConfigSetsHandler.java Source code

Java tutorial

Introduction

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

import java.io.InputStream;
import java.lang.invoke.MethodHandles;
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;

import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.hadoop.fs.Path;
import org.apache.solr.api.Api;
import org.apache.solr.client.solrj.SolrResponse;
import org.apache.solr.cloud.OverseerSolrResponse;
import org.apache.solr.cloud.OverseerTaskQueue.QueueEvent;
import org.apache.solr.common.SolrException;
import org.apache.solr.common.SolrException.ErrorCode;
import org.apache.solr.common.cloud.SolrZkClient;
import org.apache.solr.common.cloud.ZkConfigManager;
import org.apache.solr.common.cloud.ZkNodeProps;
import org.apache.solr.common.params.ConfigSetParams;
import org.apache.solr.common.params.ConfigSetParams.ConfigSetAction;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.ContentStream;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.common.util.Utils;
import org.apache.solr.core.CoreContainer;
import org.apache.solr.handler.RequestHandlerBase;
import org.apache.solr.request.SolrQueryRequest;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.security.AuthenticationPlugin;
import org.apache.solr.security.AuthorizationContext;
import org.apache.solr.security.PermissionNameProvider;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import static org.apache.solr.cloud.OverseerConfigSetMessageHandler.BASE_CONFIGSET;
import static org.apache.solr.cloud.OverseerConfigSetMessageHandler.CONFIGSETS_ACTION_PREFIX;
import static org.apache.solr.cloud.OverseerConfigSetMessageHandler.PROPERTY_PREFIX;
import static org.apache.solr.common.params.CommonParams.NAME;
import static org.apache.solr.common.params.ConfigSetParams.ConfigSetAction.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static org.apache.solr.cloud.Overseer.QUEUE_OPERATION;

/**
 * A {@link org.apache.solr.request.SolrRequestHandler} for ConfigSets API requests.
 */
public class ConfigSetsHandler extends RequestHandlerBase implements PermissionNameProvider {
    private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
    protected final CoreContainer coreContainer;
    public static long DEFAULT_ZK_TIMEOUT = 300 * 1000;
    private final ConfigSetsHandlerApi configSetsHandlerApi = new ConfigSetsHandlerApi(this);

    /**
     * Overloaded ctor to inject CoreContainer into the handler.
     *
     * @param coreContainer Core Container of the solr webapp installed.
     */
    public ConfigSetsHandler(final CoreContainer coreContainer) {
        this.coreContainer = coreContainer;
    }

    @Override
    public void handleRequestBody(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
        if (coreContainer == null) {
            throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Core container instance missing");
        }

        // Make sure that the core is ZKAware
        if (!coreContainer.isZooKeeperAware()) {
            throw new SolrException(ErrorCode.BAD_REQUEST, "Solr instance is not running in SolrCloud mode.");
        }

        // Pick the action
        SolrParams params = req.getParams();
        String a = params.get(ConfigSetParams.ACTION);
        if (a != null) {
            ConfigSetAction action = ConfigSetAction.get(a);
            if (action == null)
                throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "Unknown action: " + a);
            if (action == ConfigSetAction.UPLOAD) {
                handleConfigUploadRequest(req, rsp);
                return;
            }
            invokeAction(req, rsp, action);
        } else {
            throw new SolrException(ErrorCode.BAD_REQUEST, "action is a required param");
        }

        rsp.setHttpCaching(false);
    }

    void invokeAction(SolrQueryRequest req, SolrQueryResponse rsp, ConfigSetAction action) throws Exception {
        ConfigSetOperation operation = ConfigSetOperation.get(action);
        log.info("Invoked ConfigSet Action :{} with params {} ", action.toLower(), req.getParamString());
        Map<String, Object> result = operation.call(req, rsp, this);
        sendToZk(rsp, operation, result);
    }

    protected void sendToZk(SolrQueryResponse rsp, ConfigSetOperation operation, Map<String, Object> result)
            throws KeeperException, InterruptedException {
        if (result != null) {
            // We need to differentiate between collection and configsets actions since they currently
            // use the same underlying queue.
            result.put(QUEUE_OPERATION, CONFIGSETS_ACTION_PREFIX + operation.action.toLower());
            ZkNodeProps props = new ZkNodeProps(result);
            handleResponse(operation.action.toLower(), props, rsp, DEFAULT_ZK_TIMEOUT);
        }
    }

    private void handleConfigUploadRequest(SolrQueryRequest req, SolrQueryResponse rsp) throws Exception {
        String configSetName = req.getParams().get(NAME);
        if (StringUtils.isBlank(configSetName)) {
            throw new SolrException(ErrorCode.BAD_REQUEST,
                    "The configuration name should be provided in the \"name\" parameter");
        }

        SolrZkClient zkClient = coreContainer.getZkController().getZkClient();
        String configPathInZk = ZkConfigManager.CONFIGS_ZKNODE + Path.SEPARATOR + configSetName;

        if (zkClient.exists(configPathInZk, true)) {
            throw new SolrException(ErrorCode.BAD_REQUEST,
                    "The configuration " + configSetName + " already exists in zookeeper");
        }

        Iterator<ContentStream> contentStreamsIterator = req.getContentStreams().iterator();

        if (!contentStreamsIterator.hasNext()) {
            throw new SolrException(ErrorCode.BAD_REQUEST, "No stream found for the config data to be uploaded");
        }

        InputStream inputStream = contentStreamsIterator.next().getStream();

        // Create a node for the configuration in zookeeper
        boolean trusted = getTrusted(req);
        zkClient.makePath(configPathInZk,
                ("{\"trusted\": " + Boolean.toString(trusted) + "}").getBytes(StandardCharsets.UTF_8), true);

        ZipInputStream zis = new ZipInputStream(inputStream, StandardCharsets.UTF_8);
        ZipEntry zipEntry = null;
        while ((zipEntry = zis.getNextEntry()) != null) {
            String filePathInZk = configPathInZk + "/" + zipEntry.getName();
            if (zipEntry.isDirectory()) {
                zkClient.makePath(filePathInZk, true);
            } else {
                createZkNodeIfNotExistsAndSetData(zkClient, filePathInZk, IOUtils.toByteArray(zis));
            }
        }
        zis.close();
    }

    boolean getTrusted(SolrQueryRequest req) {
        AuthenticationPlugin authcPlugin = coreContainer.getAuthenticationPlugin();
        log.info("Trying to upload a configset. authcPlugin: {}, user principal: {}", authcPlugin,
                req.getUserPrincipal());
        if (authcPlugin != null && req.getUserPrincipal() != null) {
            return true;
        }
        return false;
    }

    private void createZkNodeIfNotExistsAndSetData(SolrZkClient zkClient, String filePathInZk, byte[] data)
            throws Exception {
        if (!zkClient.exists(filePathInZk, true)) {
            zkClient.create(filePathInZk, data, CreateMode.PERSISTENT, true);
        } else {
            zkClient.setData(filePathInZk, data, true);
        }
    }

    private void handleResponse(String operation, ZkNodeProps m, SolrQueryResponse rsp, long timeout)
            throws KeeperException, InterruptedException {
        long time = System.nanoTime();

        QueueEvent event = coreContainer.getZkController().getOverseerConfigSetQueue().offer(Utils.toJSON(m),
                timeout);
        if (event.getBytes() != null) {
            SolrResponse response = SolrResponse.deserialize(event.getBytes());
            rsp.getValues().addAll(response.getResponse());
            SimpleOrderedMap exp = (SimpleOrderedMap) response.getResponse().get("exception");
            if (exp != null) {
                Integer code = (Integer) exp.get("rspCode");
                rsp.setException(new SolrException(
                        code != null && code != -1 ? ErrorCode.getErrorCode(code) : ErrorCode.SERVER_ERROR,
                        (String) exp.get("msg")));
            }
        } else {
            if (System.nanoTime() - time >= TimeUnit.NANOSECONDS.convert(timeout, TimeUnit.MILLISECONDS)) {
                throw new SolrException(ErrorCode.SERVER_ERROR,
                        operation + " the configset time out:" + timeout / 1000 + "s");
            } else if (event.getWatchedEvent() != null) {
                throw new SolrException(ErrorCode.SERVER_ERROR,
                        operation + " the configset error [Watcher fired on path: "
                                + event.getWatchedEvent().getPath() + " state: "
                                + event.getWatchedEvent().getState() + " type " + event.getWatchedEvent().getType()
                                + "]");
            } else {
                throw new SolrException(ErrorCode.SERVER_ERROR, operation + " the configset unknown case");
            }
        }
    }

    private static Map<String, Object> copyPropertiesWithPrefix(SolrParams params, Map<String, Object> props,
            String prefix) {
        Iterator<String> iter = params.getParameterNamesIterator();
        while (iter.hasNext()) {
            String param = iter.next();
            if (param.startsWith(prefix)) {
                props.put(param, params.get(param));
            }
        }
        return props;
    }

    @Override
    public String getDescription() {
        return "Manage SolrCloud ConfigSets";
    }

    @Override
    public Category getCategory() {
        return Category.ADMIN;
    }

    enum ConfigSetOperation {
        CREATE_OP(CREATE) {
            @Override
            Map<String, Object> call(SolrQueryRequest req, SolrQueryResponse rsp, ConfigSetsHandler h)
                    throws Exception {
                Map<String, Object> props = req.getParams().required().getAll(null, NAME, BASE_CONFIGSET);
                return copyPropertiesWithPrefix(req.getParams(), props, PROPERTY_PREFIX + ".");
            }
        },
        DELETE_OP(DELETE) {
            @Override
            Map<String, Object> call(SolrQueryRequest req, SolrQueryResponse rsp, ConfigSetsHandler h)
                    throws Exception {
                return req.getParams().required().getAll(null, NAME);
            }
        },
        LIST_OP(LIST) {
            @Override
            Map<String, Object> call(SolrQueryRequest req, SolrQueryResponse rsp, ConfigSetsHandler h)
                    throws Exception {
                NamedList<Object> results = new NamedList<>();
                SolrZkClient zk = h.coreContainer.getZkController().getZkStateReader().getZkClient();
                ZkConfigManager zkConfigManager = new ZkConfigManager(zk);
                List<String> configSetsList = zkConfigManager.listConfigs();
                results.add("configSets", configSetsList);
                SolrResponse response = new OverseerSolrResponse(results);
                rsp.getValues().addAll(response.getResponse());
                return null;
            }
        };

        ConfigSetAction action;

        ConfigSetOperation(ConfigSetAction action) {
            this.action = action;
        }

        abstract Map<String, Object> call(SolrQueryRequest req, SolrQueryResponse rsp, ConfigSetsHandler h)
                throws Exception;

        public static ConfigSetOperation get(ConfigSetAction action) {
            for (ConfigSetOperation op : values()) {
                if (op.action == action)
                    return op;
            }
            throw new SolrException(ErrorCode.SERVER_ERROR, "No such action" + action);
        }
    }

    @Override
    public Collection<Api> getApis() {
        return configSetsHandlerApi.getApis();
    }

    @Override
    public Boolean registerV2() {
        return Boolean.TRUE;
    }

    @Override
    public Name getPermissionName(AuthorizationContext ctx) {
        String a = ctx.getParams().get(ConfigSetParams.ACTION);
        if (a != null) {
            ConfigSetAction action = ConfigSetAction.get(a);
            if (action == ConfigSetAction.CREATE || action == ConfigSetAction.DELETE
                    || action == ConfigSetAction.UPLOAD) {
                return Name.CONFIG_EDIT_PERM;
            } else if (action == ConfigSetAction.LIST) {
                return Name.CONFIG_READ_PERM;
            }
        }
        return null;
    }
}