/* * Copyright 2017 Cognitree Technologies * * 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.cognitree.flume.sink.elasticsearch; import com.cognitree.flume.sink.elasticsearch.client.BulkProcessorBulider; import com.cognitree.flume.sink.elasticsearch.client.ElasticsearchClientBuilder; import com.google.common.base.Charsets; import com.google.common.base.Strings; import com.google.common.base.Throwables; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.StringUtils; import org.apache.flume.Channel; import org.apache.flume.Context; import org.apache.flume.Event; import org.apache.flume.Transaction; import org.apache.flume.conf.Configurable; import org.apache.flume.sink.AbstractSink; import org.elasticsearch.action.bulk.BulkProcessor; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.client.RequestOptions; import org.elasticsearch.client.RestHighLevelClient; import org.elasticsearch.client.transport.NoNodeAvailableException; import org.elasticsearch.common.xcontent.XContentBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.atomic.AtomicBoolean; import static com.cognitree.flume.sink.elasticsearch.Constants.*; /** * This sink will read the events from a channel and add them to the bulk processor. * <p> * This sink must be configured with mandatory parameters detailed in * {@link Constants} */ public class ElasticSearchSink extends AbstractSink implements Configurable { private static final Logger logger = LoggerFactory.getLogger(ElasticSearchSink.class); private static final int CHECK_CONNECTION_PERIOD = 3000; private BulkProcessor bulkProcessor; private IndexBuilder indexBuilder; private Serializer serializer; private RestHighLevelClient client; private AtomicBoolean shouldBackOff = new AtomicBoolean(false); public RestHighLevelClient getClient() { return client; } @Override public void configure(Context context) { String[] hosts = getHosts(context); if (ArrayUtils.isNotEmpty(hosts)) { client = new ElasticsearchClientBuilder( context.getString(PREFIX + ES_CLUSTER_NAME, DEFAULT_ES_CLUSTER_NAME), hosts) .build(); buildIndexBuilder(context); buildSerializer(context); bulkProcessor = new BulkProcessorBulider().buildBulkProcessor(context, this); } else { logger.error("Could not create Rest client, No host exist"); } } @Override public Status process() { if (shouldBackOff.get()) { throw new NoNodeAvailableException("Check whether Elasticsearch is down or not."); } Channel channel = getChannel(); Transaction txn = channel.getTransaction(); txn.begin(); try { Event event = channel.take(); if (event != null) { String body = new String(event.getBody(), Charsets.UTF_8); if (!Strings.isNullOrEmpty(body)) { logger.debug("start to sink event [{}].", body); String index = indexBuilder.getIndex(event); String type = indexBuilder.getType(event); String id = indexBuilder.getId(event); XContentBuilder xContentBuilder = serializer.serialize(event); if (xContentBuilder != null) { if (!(Strings.isNullOrEmpty(id))) { bulkProcessor.add(new IndexRequest(index, type, id) .source(xContentBuilder)); } else { bulkProcessor.add(new IndexRequest(index, type) .source(xContentBuilder)); } } else { logger.error("Could not serialize the event body [{}] for index [{}], type[{}] and id [{}] ", new Object[]{body, index, type, id}); } } logger.debug("sink event [{}] successfully.", body); } txn.commit(); return Status.READY; } catch (Throwable tx) { try { txn.rollback(); } catch (Exception ex) { logger.error("exception in rollback.", ex); } logger.error("transaction rolled back.", tx); return Status.BACKOFF; } finally { txn.close(); } } @Override public void stop() { if (bulkProcessor != null) { bulkProcessor.close(); } } /** * builds Index builder */ private void buildIndexBuilder(Context context) { String indexBuilderClass = DEFAULT_ES_INDEX_BUILDER; if (StringUtils.isNotBlank(context.getString(ES_INDEX_BUILDER))) { indexBuilderClass = context.getString(ES_INDEX_BUILDER); } this.indexBuilder = instantiateClass(indexBuilderClass); if (this.indexBuilder != null) { this.indexBuilder.configure(context); } } /** * builds Serializer */ private void buildSerializer(Context context) { String serializerClass = DEFAULT_ES_SERIALIZER; if (StringUtils.isNotEmpty(context.getString(ES_SERIALIZER))) { serializerClass = context.getString(ES_SERIALIZER); } this.serializer = instantiateClass(serializerClass); if (this.serializer != null) { this.serializer.configure(context); } } private <T> T instantiateClass(String className) { try { @SuppressWarnings("unchecked") Class<T> aClass = (Class<T>) Class.forName(className); return aClass.newInstance(); } catch (Exception e) { logger.error("Could not instantiate class " + className, e); Throwables.propagate(e); return null; } } /** * returns hosts */ private String[] getHosts(Context context) { String[] hosts = null; if (StringUtils.isNotBlank(context.getString(ES_HOSTS))) { hosts = context.getString(ES_HOSTS).split(","); } return hosts; } /** * Checks for elasticsearch connection * Sets shouldBackOff to true if bulkProcessor failed to deliver the request. * Resets shouldBackOff to false once the connection to elasticsearch is established. */ public void assertConnection() { shouldBackOff.set(true); final Timer timer = new Timer(); final TimerTask task = new TimerTask() { @Override public void run() { try { if (checkConnection()) { shouldBackOff.set(false); timer.cancel(); timer.purge(); } } catch (IOException e) { logger.error("ping request for elasticsearch failed " + e.getMessage(), e); } } }; timer.scheduleAtFixedRate(task, 0, CHECK_CONNECTION_PERIOD); } private boolean checkConnection() throws IOException { return client.ping(RequestOptions.DEFAULT); } }