All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.netflix.ndbench.plugin.es.EsRestPlugin Maven / Gradle / Ivy

/*
 *  Copyright 2016 Netflix, Inc.
 *
 *  Licensed 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 com.netflix.ndbench.plugin.es;

import com.google.common.collect.ImmutableList;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.netflix.ndbench.api.plugin.DataGenerator;
import com.netflix.ndbench.api.plugin.NdBenchAbstractClient;
import com.netflix.ndbench.api.plugin.NdBenchMonitor;
import com.netflix.ndbench.api.plugin.annotations.NdBenchClientPlugin;
import com.netflix.ndbench.core.config.IConfiguration;
import com.netflix.ndbench.core.discovery.IClusterDiscovery;
import org.apache.commons.lang.StringUtils;
import org.apache.http.HttpHost;
import org.elasticsearch.client.Response;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;

@Singleton
@NdBenchClientPlugin("ES_REST")
public class EsRestPlugin implements NdBenchAbstractClient {
    static final String RESULT_OK = "Ok";
    public static final int MAX_INDEX_ROLLS_PER_HOUR = 60;

    private final IClusterDiscovery discoverer;
    private final IEsConfig esConfig;
    private final IConfiguration coreConfig;

    // package scope for fields below --  so their values can be used in tests
    private RestClient restClient;

    private EsWriter writer;
    private String ES_HOST_PORT;
    private String ES_INDEX_TYPE_RESOURCE_PATH;
    private String CONNECTION_INFO;
    private Boolean randomizeKeys;        // for testing we don't randomize so we can pick up what we wrote


    @Nullable
    private EsAutoTuner autoTuner;

    private static final Logger logger = LoggerFactory.getLogger(EsRestPlugin.class);
    private static final String DEFAULT_DOC_TYPE = "default";


    @Inject
    public EsRestPlugin(IConfiguration coreConfig, IEsConfig esConfig, IClusterDiscovery discoverer) {

        this(coreConfig, esConfig, discoverer, true);
    }

    EsRestPlugin(IConfiguration coreConfig, IEsConfig esConfig, IClusterDiscovery discoverer, Boolean randomizeKeys) {
        this.esConfig = esConfig;
        this.coreConfig = coreConfig;
        this.discoverer = discoverer;
        this.randomizeKeys = randomizeKeys;
    }


    public String getEsRestEndpoint() {
        return ES_HOST_PORT;
    }


    /**
     * Returns hostname that the benchmark will target if {@link IEsConfig#getHostName()}
     * is defined, otherwise returns result of calling {@link IEsConfig#getCluster() }
     */

    public String getClusterOrHostName() {
        if (StringUtils.isNotBlank(esConfig.getHostName()))
            return esConfig.getHostName();
        else
            return esConfig.getCluster();
    }


    /**
     * Initialize key data structures for plugin, using 'synchronized' to ensure other threads are guaranteed
     * visibility of end result of initializing said structures.
     *
     * @throws Exception
     */
    @Override
    public synchronized void init(DataGenerator dataGenerator) throws Exception {
        if (esConfig.getRestClientPort() == 443 && !esConfig.isHttps()) {
            throw new IllegalArgumentException(
                    "You must set the configuration property 'https' to true if you use the https default port");
        }

        Integer indexRollsPerHour = esConfig.getIndexRollsPerDay();
        if (indexRollsPerHour < 0 || indexRollsPerHour > MAX_INDEX_ROLLS_PER_HOUR) {
            throw new IllegalArgumentException(
                    "The configuration property 'indexRollsPerHour' must be > 0 and <= " + MAX_INDEX_ROLLS_PER_HOUR);
        }

        if (indexRollsPerHour > 0 && 60 % indexRollsPerHour != 0) {
            throw new IllegalArgumentException( "The configuration property 'indexRollsPerHour' must evenly divide 60");
        }
        if (esConfig.getBulkWriteBatchSize() < 0) {
            throw new IllegalArgumentException( "bulkWriteBatchSize can't be negative'");
        }

        RestClientBuilder.RequestConfigCallback callback = requestConfigBuilder -> {
            requestConfigBuilder.setConnectTimeout(esConfig.getConnectTimeoutSeconds() * 1000);
            requestConfigBuilder.setConnectionRequestTimeout(esConfig.getConnectionRequestTimeoutSeconds() * 1000);
            requestConfigBuilder.setSocketTimeout(esConfig.getSocketTimeoutSeconds()* 1000) ;
            return requestConfigBuilder;
        };
        List endpoints = getEndpoints(discoverer, esConfig);
        HttpHost[] hosts = endpoints.toArray(new HttpHost[0]);
        restClient =
                RestClient.builder(hosts).
                        setMaxRetryTimeoutMillis(esConfig.getMaxRetryTimeoutSeconds() * 1000).
                        setRequestConfigCallback( callback ).
                        build();

        String hostname = endpoints.get(0).getHostName();
        ES_HOST_PORT = String.format("%s://%s:%s", getScheme(), hostname, esConfig.getRestClientPort());
        ES_INDEX_TYPE_RESOURCE_PATH = String.format("/%s/%s", esConfig.getIndexName(), DEFAULT_DOC_TYPE);
        CONNECTION_INFO =
                "Cluster: " + this.getClusterOrHostName() + "\n" +
                        "Test Index URL: " + ES_HOST_PORT + ES_INDEX_TYPE_RESOURCE_PATH;

        writer = new EsWriter(
                esConfig.getIndexName(),
                DEFAULT_DOC_TYPE,
                esConfig.getBulkWriteBatchSize() > 0,
                indexRollsPerHour,
                esConfig.getBulkWriteBatchSize(), esConfig.isRandomizeStrings() ?
                dataGenerator :
                new FakeWordDictionaryBasedDataGenerator(dataGenerator, coreConfig.getDataSize()));

        if (coreConfig.isAutoTuneEnabled()) {
            this.autoTuner = new EsAutoTuner(
                    coreConfig.getAutoTuneRampPeriodMillisecs(),
                    coreConfig.getAutoTuneIncrementIntervalMillisecs(),
                    coreConfig.getWriteRateLimit(),
                    coreConfig.getAutoTuneFinalWriteRate(),
                    coreConfig.getAutoTuneWriteFailureRatioThreshold());
        } else {
            // OK if it is null because it will never be used if ! isAutoTuneEnabled
            // In fact if we initialized it when auto tune is not enabled, then we would enforce needless checks
            // on related parameters that would impose more hassle on the user to configure
            this.autoTuner = null;
        }

        this.randomizeKeys = randomizeKeys;
        logger.info("ES_REST plugin initialized: " + CONNECTION_INFO);
    }

    private String getScheme() {
        return esConfig.isHttps() ? "https" : "http";
    }

    /**
     * Writes either one or many documents to Elasticsearch -- multiple documents will be written
     * if {@link IEsConfig#getBulkWriteBatchSize()}()} is greater than 0, and in this case the
     * exact number to be written per call is defined by the return value of that same method:
     * {@link IEsConfig#getBulkWriteBatchSize()}.
     * 

* Note that the passed-in key will be appended with random string values -- if we were to choose the * ids of the docs we write to elastic search from the set of keys that is allocated per numKeys we would * end up PUT'ing the same document to elastic search multiple times, and the subsequent time the document * were written to elasticearch it would be counted as an update -- not a new document -- with the result that * the deleted doc count (as given by /_cat/indices) would go up, and the document count would stay the * same, which would likely be confusing to whoever is running a benchmark. */ @Override public WriteResult writeSingle(String key) throws Exception { logger.debug("writeSingle: {}", key); return writer.writeDocument(restClient, key, randomizeKeys); } @Override public String readSingle(String key) throws Exception { logger.debug("readSingle: {}", key); String url = getUrlToDocGivenId(key); Response response = restClient.performRequest("GET", url); logger.debug("http GET to {} resulted in response: {}", url, response); int responseCode = response.getStatusLine().getStatusCode(); if (responseCode != 200) { throw new RuntimeException("write operation failed [" + key + "]. response: " + response); } return RESULT_OK; } /** * Perform a bulk read operation * @return a list of response codes * @throws Exception */ public List readBulk(final List keys) throws Exception { throw new UnsupportedOperationException("bulk operation is not supported"); } /** * Perform a bulk write operation * @return a list of response codes * @throws Exception */ public List writeBulk(final List keys) throws Exception { throw new UnsupportedOperationException("bulk operation is not supported"); } @Override public void shutdown() throws Exception { restClient.close(); } @Override public String getConnectionInfo() throws Exception { return CONNECTION_INFO; } @Override public String runWorkFlow() throws Exception { return null; } // Will never be called by driver if isAutoTuneEnabled=false -- for that reason autoTuner is allowed to be null. // See constructor for details. // // Note: this method will only be called after the ndbench driver tries to perform a writeSingle operation // @Override public Double autoTuneWriteRateLimit(Double currentRateLimit, List event, NdBenchMonitor runStats) { assert autoTuner != null; return autoTuner.recommendNewRate(currentRateLimit, event, runStats); } private String getUrlToDocGivenId(String key) { return ES_INDEX_TYPE_RESOURCE_PATH + "/" + key; } private List getEndpoints(IClusterDiscovery discoverer, IEsConfig config) { String hostname = config.getHostName(); List retval; if (StringUtils.isNotBlank(hostname)) { retval = ImmutableList.of(new HttpHost(hostname, config.getRestClientPort(), getScheme())); } else { ArrayList hosts = new ArrayList<>(); logger.debug("discovering endpoints of cluster: {}", config.getCluster()); for (String endpoint : discoverer.getEndpoints(config.getCluster(), config.getRestClientPort())) { String[] hostPort = endpoint.split(":"); hosts.add(new HttpHost(hostPort[0], Integer.parseInt(hostPort[1]), getScheme())); } if (hosts.isEmpty()) { throw new IllegalArgumentException( "failed to discover any endpoints of cluster: " + config.getCluster() + ". Are you sure it is valid? Maybe check Spinnaker?"); } retval = hosts; } return retval; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy