/* * Copyright (c) 2008-2014 EMC Corporation * All Rights Reserved */ package com.emc.storageos.coordinator.client.service.impl; import java.net.URI; import java.net.UnknownHostException; import java.util.Iterator; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.commons.lang.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.coordinator.client.service.CoordinatorClient; import com.emc.storageos.coordinator.exceptions.CoordinatorException; import com.emc.storageos.coordinator.exceptions.NotConnectableException; import com.emc.storageos.services.util.NamedScheduledThreadPoolExecutor; /** * Coordinator-client private mapping from ViPR nodeId to DualInetAddress Note that the Controller * nodes are inserted statically through a XML bean config file created at the boot time. The extra * nodes (e.g. datanodes) are inserted (registered) dynamically through an internal REST API call - * There is no removal (unregistration) of extra nodes. Thus, the map may contain expired entries. */ public class CoordinatorClientInetAddressMap { private static final Logger _logger = LoggerFactory .getLogger(CoordinatorClientInetAddressMap.class); // initial delay and wait interval in minutes for map cleaning thread private static final int INITIAL_DELAY_MINUTES = 5;// 5 min private static final int WAIT_INTERVAL_MINUTES = 5;// 5 min // Wait before forcing terminate of the cleaning task private static final int TERMINATE_DELAY = 1; // second private static final String POOL_NAME = "CoordinatorClientInetAddressMap"; // This is the node id where the coordinator client is running // Coordinator client has reference to this map private String nodeId; /** * This holds the ipv4 and ipv6 addresses of this node. Connectable version is chosen while * connecting to other nodes. */ private DualInetAddress dualInetAddress; /** * stores<node_id/DualInetAddress{ipv4,ipv6}> info of the controller nodes. This is initialized * from the bean xml file. */ private Map<String, DualInetAddress> controllerNodeIPLookupMap; /** * This map maintains the extra nodes' ip address info. */ private Map<String, DualInetAddress> extraNodeInetAddressMap = new ConcurrentHashMap<String, DualInetAddress>(); private CoordinatorClient coordinatorClient; private ScheduledExecutorService executor = new NamedScheduledThreadPoolExecutor(POOL_NAME, 1); public Map<String, DualInetAddress> getControllerNodeIPLookupMap() { return controllerNodeIPLookupMap; } public void setControllerNodeIPLookupMap(Map<String, DualInetAddress> controllerNodeIPLookupMap) { this.controllerNodeIPLookupMap = controllerNodeIPLookupMap; } public Map<String, DualInetAddress> getExternalInetAddressLookupMap() { return extraNodeInetAddressMap; } public void setExternalInetAddressLookupMap( Map<String, DualInetAddress> externalInetAddressLookupMap) { this.extraNodeInetAddressMap = externalInetAddressLookupMap; } /** * Get the node id where the coordinator client is on. * * @return */ public String getNodeId() { return nodeId; } /** * Setter - set the node id of the client. * * @param node * the name to be set to */ public void setNodeId(String node) { _logger.debug("Setting local node id: " + node); this.nodeId = node; } public DualInetAddress getDualInetAddress() { return dualInetAddress; } public void setDualInetAddress(DualInetAddress dualInetAddress) { _logger.debug("Setting local node DualInetAddress: " + dualInetAddress.toString()); this.dualInetAddress = dualInetAddress; } /** * Prints out records in the map */ public String toString() { StringBuilder sb = new StringBuilder(); if (getControllerNodeIPLookupMap() != null && (getControllerNodeIPLookupMap().size() > 0)) { Iterator<String> itr = getControllerNodeIPLookupMap().keySet().iterator(); while (itr.hasNext()) { String key = itr.next().toString(); DualInetAddress record = getControllerNodeIPLookupMap().get(key); if (StringUtils.isNotBlank(key)) { sb.append(key).append("-").append(record.toString()).append("\n"); } } } return sb.toString(); } /** * Wrapper- add entry to the map. This is needed for data node ip changes. * * @param nodeId * - data node id - data1. data2, etc * @param value * - the DualInetAddress that has v4 and/or v6 ip */ public void put(String nodeId, DualInetAddress value) { _logger.info("Adding external node: " + nodeId + " and DualInetAddress: " + dualInetAddress.toString() + " to CoordinatorClientInetAddressMap."); getExternalInetAddressLookupMap().put(nodeId, value); } /** * Get IP address record based on node_id to lookup * * @param nodeId * - data node id * @return data node address information, see @DualInetAddress. */ public DualInetAddress get(String nodeId) { DualInetAddress address = getControllerNodeIPLookupMap().get(nodeId); if (address == null) { address = getExternalInetAddressLookupMap().get(nodeId); } return address; } /** * Given endpoint uri, Replace the URI's host/ip with matching version of IP address * * @param uri * - the endpoint to look up. * @return new URI with resolved IP address of correct version. * */ public URI expandURI(URI uri) { String node = uri.getHost();// get node_id _logger.debug("Expand uri: " + uri); URI newUri = null; // NOTE: this is needed for backward compatiblity // TODO:verify - we probably need to add exclusion for 'localhost' as well. if (uri.getHost().compareToIgnoreCase("localhost") == 0) { return uri; } if (node.indexOf('.') > 0 || node.indexOf(':') > 0) { return uri; } try { // this is a node_id format, find its ip String ip = getConnectableInternalAddress(node); newUri = new URI(uri.getScheme(), uri.getUserInfo(), ip, uri.getPort(), uri.getPath(), uri.getQuery(), uri.getFragment()); _logger.debug("New expanded uri: " + newUri); } catch (Exception e) { _logger.error("Failed expanding URI. ", e); return uri; } return newUri; } /** * Given server nodeid, find compatible ip based assuming caller is the client. * * @param nodeId node id of the server * @return a connectable ip address as string. * @throws NotConnectableException */ public String getConnectableInternalAddress(String nodeId) { DualInetAddress client = getDualInetAddress(); _logger.debug("local address: " + client); DualInetAddress address = null; if (nodeId.compareToIgnoreCase(getNodeId()) == 0) { address = getDualInetAddress(); } else { address = getControllerNodeIPLookupMap().get(nodeId); if (address == null) { // lookup in extra nodes map address = getExternalInetAddressLookupMap().get(nodeId); if (address == null) { address = ((CoordinatorClientImpl) coordinatorClient) .loadInetAddressFromCoordinator(nodeId); if (address == null) { throw CoordinatorException.fatals .notConnectableError("Node lookup failed: " + nodeId); } } } } return client.getConnectableAddress(address); } public CoordinatorClient getCoordinatorClient() { return coordinatorClient; } public void setCoordinatorClient(CoordinatorClient coordinatorClient) { this.coordinatorClient = coordinatorClient; startCleaningTask(); } /** * Check if specific node is a controller node. * * @return true if controller node map has it */ public boolean isControllerNode() { return (controllerNodeIPLookupMap.get(getNodeId()) != null); } /** * Start cache cleaning thread. */ public void startCleaningTask() { executor.scheduleAtFixedRate(new Runnable() { @Override public void run() { _logger.debug("Entering {}", Thread.currentThread().getStackTrace()[1].getMethodName()); getExternalInetAddressLookupMap().clear(); _logger.debug("Exiting {}", Thread.currentThread().getStackTrace()[1].getMethodName()); } }, INITIAL_DELAY_MINUTES, WAIT_INTERVAL_MINUTES, TimeUnit.SECONDS); } /** * Stop cache cleaning thread. */ public void stop() { executor.shutdown(); try { executor.awaitTermination(TERMINATE_DELAY, TimeUnit.SECONDS); } catch (Exception e) { _logger.error("TimeOut occured while waiting for Cleaning thread to end."); if (!executor.isShutdown()) { executor.shutdownNow(); } } } /** * Get the connectable ip version of the external server. * Local node is used as a client to determine the coonnectable address pair. * * @param server external server that we are trying to connect * @return connectable ip address string * @throws UnknownHostException */ public String getExternalConnectableAddress(String server) throws UnknownHostException { return getDualInetAddress().getConnectableAddress(server); } /** * Get the connectable ip address string for the external client. * Local node is used as a server to determine the coonnectable * address pair. * * @param client the external client that is requesting a connection * @return the connectable ip address for external client. * @throws UnknownHostException */ public String getLocalConnectableAddress(String client) throws UnknownHostException { return getDualInetAddress().getConnectableLocalAddress(client); } }