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

org.bitsofinfo.hazelcast.discovery.consul.ConsulDiscoveryStrategy Maven / Gradle / Ivy

Go to download

hazelcast-consul-discovery-spi - Consul based discovery strategy SPI for Hazelcast enabled applications

The newest version!
package org.bitsofinfo.hazelcast.discovery.consul;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hazelcast.logging.ILogger;
import com.hazelcast.cluster.Address;
import com.hazelcast.spi.discovery.AbstractDiscoveryStrategy;
import com.hazelcast.spi.discovery.DiscoveryNode;
import com.hazelcast.spi.discovery.SimpleDiscoveryNode;
import com.orbitz.consul.CatalogClient;
import com.orbitz.consul.Consul;
import com.orbitz.consul.HealthClient;
import com.orbitz.consul.model.ConsulResponse;
import com.orbitz.consul.model.catalog.CatalogService;
import com.orbitz.consul.model.health.ServiceHealth;


/**
 * DiscoveryStrategy for Consul
 * 
 * 
 * 
 * @author bitsofinfo
 *
 */
public class ConsulDiscoveryStrategy extends AbstractDiscoveryStrategy implements Runnable {

	// how we connect to consul
	private final String consulHost;  
	private final Integer consulPort;
	
	// service name we will register under, and tags to apply
	private String[] consulServiceTags = null;
	private String consulServiceName = null;
	
	// if we only discover healthy nodes...
	private boolean consulHealthyOnly = true;
	
	// How we register with Consul
	private ConsulRegistrator registrator = null;
	
	// Consul clients
	private CatalogClient consulCatalogClient = null;
	private HealthClient consulHealthClient = null;
	
	// we set this to track if discoverNodes was ever invoked
	private boolean discoverNodesInvoked = false;
	
	// ACL token to be used for agent, health and catalog clients
	private String consulAclToken  = null;
	

	/**
	 * Constructor
	 * 
	 * @param localDiscoveryNode
	 * @param logger
	 * @param properties
	 */
	public ConsulDiscoveryStrategy(DiscoveryNode localDiscoveryNode, ILogger logger, Map properties ) {

		super( logger, properties );
		
		// get basic properites for the strategy
		this.consulHost = getOrDefault("consul-host",  ConsulDiscoveryConfiguration.CONSUL_HOST, "localhost");
		this.consulPort = getOrDefault("consul-port",  ConsulDiscoveryConfiguration.CONSUL_PORT, 8500);
		this.consulServiceTags = getOrDefault("consul-service-tags",  ConsulDiscoveryConfiguration.CONSUL_SERVICE_TAGS, "").split(",");		
		this.consulServiceName = getOrDefault("consul-service-name",  ConsulDiscoveryConfiguration.CONSUL_SERVICE_NAME, "");		
		this.consulHealthyOnly = getOrDefault("consul-healthy-only",  ConsulDiscoveryConfiguration.CONSUL_HEALTHY_ONLY, true);		
		long discoveryDelayMS = getOrDefault("consul-discovery-delay-ms",  ConsulDiscoveryConfiguration.CONSUL_DISCOVERY_DELAY_MS, 30000);		
		this.consulAclToken = getOrDefault("consul-acl-token", ConsulDiscoveryConfiguration.CONSUL_ACL_TOKEN, null);
		
		boolean consulSslEnabled = getOrDefault("consul-ssl-enabled", ConsulDiscoveryConfiguration.CONSUL_SSL_ENABLED, false);
		String consulSslServerCertFilePath = getOrDefault("consul-ssl-server-cert-file-path", ConsulDiscoveryConfiguration.CONSUL_SSL_SERVER_CERT_FILE_PATH, null);
		String consulSslServerCertBase64 = getOrDefault("consul-ssl-server-cert-base64", ConsulDiscoveryConfiguration.CONSUL_SSL_SERVER_CERT_BASE64, null);
		boolean consulServerHostnameVerify = getOrDefault("consul-ssl-server-hostname-verify", ConsulDiscoveryConfiguration.CONSUL_SSL_SERVER_HOSTNAME_VERIFY, true);
		
		
		
		
		
		// our ConsulRegistrator default is DoNothingRegistrator
		String registratorClassName = getOrDefault("consul-registrator",  
													ConsulDiscoveryConfiguration.CONSUL_REGISTRATOR, 
													DoNothingRegistrator.class.getCanonicalName());
		
		// this is optional, custom properties to configure a registrator
		// @see the ConsulRegistrator for a description of supported options 
		String registratorConfigJSON = getOrDefault("consul-registrator-config",  
													ConsulDiscoveryConfiguration.CONSUL_REGISTRATOR_CONFIG, 
													null);
		
		// if JSON config is present attempt to parse it into a map
		Map registratorConfig = null;
		if (registratorConfigJSON != null && !registratorConfigJSON.trim().isEmpty()) {
			try {
				
				JsonFactory factory = new JsonFactory(); 
			    ObjectMapper mapper = new ObjectMapper(factory); 
			    TypeReference> typeRef 
			            = new TypeReference>() {};
		
			    registratorConfig = mapper.readValue(registratorConfigJSON.getBytes(), typeRef);
			    
			} catch(Exception e) {
				logger.severe("Unexpected error parsing 'consul-registrator-config' JSON: " + 
							registratorConfigJSON + " error="+e.getMessage(),e);
			}
		}

		
		// Ok, now construct our registrator and register with Consul
		try {
			registrator = (ConsulRegistrator)Class.forName(registratorClassName).newInstance();
			
			logger.info("Using ConsulRegistrator: " + registratorClassName);
			
			registrator.init(consulHost, 
					consulPort, 
					consulServiceName, 
					consulServiceTags, 
					consulAclToken,
					consulSslEnabled,
					consulSslServerCertFilePath,
					consulSslServerCertBase64,
					consulServerHostnameVerify,
					localDiscoveryNode, 
					registratorConfig, 
					logger);
			registrator.register();
			
		} catch(Exception e) {
			logger.severe("Unexpected error attempting to init() ConsulRegistrator and register(): " +e.getMessage(),e);
		}


		try{
					
			ConsulBuilder builder = ConsulClientBuilder.class.newInstance();
			Consul consul = builder.buildConsul(consulHost, 
												consulPort, 
												consulSslEnabled, 
												consulSslServerCertFilePath, 
												consulSslServerCertBase64, 
												consulServerHostnameVerify,
												consulAclToken);
			
			// build our clients
			this.consulCatalogClient = consul.catalogClient();
			this.consulHealthClient = consul.healthClient();
			
		}catch(Exception e) {
			String msg = "Unexpected error in configuring discovery: " + e.getMessage();
			logger.severe(msg,e);
		}
		
		// register our shutdown hook for deregisteration on shutdown...
		Thread shutdownThread = new Thread(this);
		Runtime.getRuntime().addShutdownHook(shutdownThread);
		
		// finally sleep a bit according to the configured discoveryDelayMS
		try {
			logger.info("Registered our service instance w/ Consul OK.. delaying Hazelcast discovery, sleeping: " + discoveryDelayMS + "ms");
			Thread.sleep(discoveryDelayMS);
		} catch(Exception e) {
			logger.severe("Unexpected error sleeping prior to discovery: " + e.getMessage(),e);
		}
									
	}                              

	@Override
	public Iterable discoverNodes() {
		
		List toReturn = new ArrayList();
		
		try {
			// discover healthy nodes only? (and its NOT the first invocation...)
			if (this.consulHealthyOnly && discoverNodesInvoked) {
				
				List nodes = consulHealthClient.getHealthyServiceInstances(consulServiceName, ConsulUtility.getAclToken(this.consulAclToken)).getResponse();
				
				for (ServiceHealth node : nodes) {
					toReturn.add(new SimpleDiscoveryNode(
									new Address(node.getService().getAddress(),node.getService().getPort())));
					getLogger().info("Discovered healthy node: " + node.getService().getAddress()+":"+node.getService().getPort());
				}
				
			// discover all services, regardless of health or this is the first invocation
			} else {
				
				ConsulResponse> response = this.consulCatalogClient.getService(consulServiceName, ConsulUtility.getAclToken(this.consulAclToken));
				
				for (CatalogService service : response.getResponse()) {
					
					String discoveredAddress = null;
					String rawServiceAddress = service.getServiceAddress();
					String rawAddress = service.getAddress();
					
					if (rawServiceAddress != null && !rawServiceAddress.trim().isEmpty()) {
					    discoveredAddress = rawServiceAddress;
						
					} else if (rawAddress != null && !rawAddress.trim().isEmpty()) {
					    getLogger().warning("discoverNodes() ServiceAddress was null/blank! " +
							     "for service: " + service.getServiceName() + 
							     " falling back to Address value");
					    discoveredAddress = rawAddress;
						
					} else {
					    getLogger().warning("discoverNodes() could not discover an address, " +
							     "both ServiceAddress and Address were null/blank! " +
							     "for service: " + service.getServiceName());
					}
					
					toReturn.add(new SimpleDiscoveryNode(
							new Address(discoveredAddress, service.getServicePort())));
					getLogger().info("Discovered healthy node: " + discoveredAddress+":"+service.getServicePort());
				}
			}
			
		} catch(Exception e) {
			getLogger().severe("discoverNodes() unexpected error: " + e.getMessage(),e);
		}

		// flag we were invoked
		discoverNodesInvoked = true;
		
		return toReturn;
	}

	@Override
	public void run() {
		try {
			if (registrator != null) {
				getLogger().info("Deregistering myself from Consul: " + this.registrator.getMyServiceId());
				registrator.deregister();
			}
		} catch(Throwable e) {
			this.getLogger().severe("Unexpected error in ConsulRegistrator.deregister(): " + e.getMessage(),e);
		}
		
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy