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

com.inin.analytics.elasticsearch.index.rotation.ElasticsearchIndexRotationManagerZookeeper Maven / Gradle / Ivy

There is a newer version: 1.2.1
Show newest version
package com.inin.analytics.elasticsearch.index.rotation;

import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;

import org.apache.commons.lang.StringUtils;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.listen.Listenable;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.NodeCache;
import org.apache.curator.framework.state.ConnectionState;
import org.apache.curator.framework.state.ConnectionStateListener;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.KeeperException.Code;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.base.Preconditions;
import com.google.gson.Gson;
import com.inin.analytics.elasticsearch.util.GsonFactory;

public abstract class ElasticsearchIndexRotationManagerZookeeper implements ElasticsearchIndexRotationManager {

	protected abstract String getBaseZnode();
	protected abstract String getRebuildStateZnode();
	
	protected CuratorFramework curator;
	
	protected static transient Logger logger = LoggerFactory.getLogger(ElasticsearchIndexRotationManagerZookeeper.class);
	protected Map indexNameCache = new HashMap<>();
	protected NodeCache rebuildStateCache;
	protected Gson gson;
	protected static final String FAIL_MESSAGE = "Failed getting routing strategy from zookeeper for ";
	protected Listenable connectionStateListener;
	


	public void setCurator(CuratorFramework curator) {
		this.curator = curator;
	}

	public void init() {
		Preconditions.checkNotNull(curator, "curator is a required dependency");
		gson = GsonFactory.buildGsonBuilder().excludeFieldsWithoutExposeAnnotation().create();

		// AR-1785 Create watcher to rebuild nodeCache after ZK reconnects from a connection blip 
		connectionStateListener = curator.getConnectionStateListenable();
		connectionStateListener.addListener(new ConnectionStateListener() {
			@Override
			public void stateChanged(CuratorFramework curator, ConnectionState state) {
				if (state.equals(ConnectionState.RECONNECTED) && indexNameCache != null) {
					for(Entry nodeCachePair : indexNameCache.entrySet()) {
						try {
							// nodeCache stops updating after a connection blip nukes its listener. I'd almost consider that a bug in curator, but for now this is the advised workaround.  
							logger.info("ZK connection reconnect detected, rebuilding curator nodeCache for " + nodeCachePair.getKey());
							nodeCachePair.getValue().rebuild();
						} catch (Exception e) {
							logger.warn("Failed to rebuild nodeCache after ZK reconnect ", e);
						}
					}
				}
			}
		});
	}

	protected void ensureNodePathExists(String zkPath) throws Exception {
		try {
			String[] pieces = StringUtils.split(zkPath, "/");
			String znode = "";
			for(String piece : pieces) {
				znode = znode + "/" + piece;
				try {
					curator.getData().forPath(znode);
				} catch (KeeperException e) {
					Code errorCode = e.code();
					if(errorCode.equals(Code.NONODE)) {
						curator.create().forPath(znode);
					}
				}
			}
		} catch (Exception e) {
			throw new IllegalStateException("Failed to initialize ElasticsearchIndexRotationStrategy because base ZK nodes could not be created", e);
		}
	}


	/**
	 * Register that an index is on the hadoop rebuild train. We'll store the index name that the live data
	 * would normally flow into with the alias to the rebuilt index. EG
	 * 
	 * New index at birth looks like
	 * c140101
	 * 
	 * Rebuilt index 
	 * c140101_reubild_82389238923
	 * 
	 * Rebuilt index alias 
	 * c140101_r
	 * 
	 * @param bucket
	 */
	
	@Override
	public void registerIndexAvailableOnRotation(ElasticSearchIndexMetadata rotatedIndexMetadata) {
		String indexNameZnode = getBaseZnode() + rotatedIndexMetadata.getIndexNameAtBirth();
		try {
			ensureNodePathExists(indexNameZnode);
			// Persisting metadata in json
			curator.setData().forPath(indexNameZnode, gson.toJson(rotatedIndexMetadata).getBytes());
		} catch (Exception e) {
			throw new IllegalStateException("Unable to register znode " + indexNameZnode, e);
		}
	}
	
	public ElasticSearchIndexMetadata getElasticSearchIndexMetadata(String indexNameAtBirth) {
		String znode = getBaseZnode() + indexNameAtBirth;
		try {
			if(!indexNameCache.containsKey(indexNameAtBirth)) {
				createNodeCacheForName(znode, indexNameAtBirth);
			}
			ChildData cd = indexNameCache.get(indexNameAtBirth).getCurrentData();
			
			if(cd != null) {
				String json = new String(cd.getData());
				return gson.fromJson(json, ElasticSearchIndexMetadata.class);
			}
		} catch (Exception e) {
			throw new IllegalStateException("Error retrieving znode, unable to maintain index metadata ", e);
		}

		ElasticSearchIndexMetadata metadata = new ElasticSearchIndexMetadata();
		metadata.setIndexNameAtBirth(indexNameAtBirth);
		return metadata;
	}
	

	protected synchronized void createNodeCacheForName(String zkPath, final String indexName) throws Exception {
		final NodeCache nodeCache = new NodeCache(curator, zkPath);
		nodeCache.start(true);
		indexNameCache.put(indexName, nodeCache);
	}

	@Override
	public void updateRebuildPipelineState(RebuildPipelineState state) {
		try {
			ensureNodePathExists(getRebuildStateZnode());
			// Persisting metadata in json
			curator.setData().forPath(getRebuildStateZnode(), state.name().getBytes());
		} catch (Exception e) {
			throw new IllegalStateException("Unable to register state " + state, e);
		}
	}

	@Override
	public RebuildPipelineState getRebuildPipelineState() {
		if(rebuildStateCache == null) {
			rebuildStateCache = new NodeCache(curator, getRebuildStateZnode());
			try {
				rebuildStateCache.start(true);
			} catch (Exception e) {
				throw new IllegalStateException("Unable to get pipeline rebuild state", e);
			}
		}
		ChildData cd = rebuildStateCache.getCurrentData();
		if(cd != null) {
			return RebuildPipelineState.valueOf(new String(cd.getData()));
		} else {
			// COMPLETE ~= NOT_RUNNING, so if it's never been ran that's what we default to. At some point we'll want to fix the enum.
			return RebuildPipelineState.COMPLETE;
		}
	}


}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy