/* * Copyright 2014-2016 CyberVision, 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 org.kaaproject.kaa.server.common.zk; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.recipes.cache.ChildData; import org.apache.curator.framework.recipes.cache.PathChildrenCache; import org.apache.curator.framework.recipes.cache.PathChildrenCache.StartMode; import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener; import org.kaaproject.kaa.server.common.zk.bootstrap.BootstrapNodeListener; import org.kaaproject.kaa.server.common.zk.gen.BootstrapNodeInfo; import org.kaaproject.kaa.server.common.zk.gen.OperationsNodeInfo; import org.kaaproject.kaa.server.common.zk.operations.OperationsNodeListener; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; public abstract class WorkerNodeTracker extends ControlNodeTracker { private static final Logger LOG = LoggerFactory.getLogger(WorkerNodeTracker.class); private PathChildrenCache endpointCache; private PathChildrenCache bootstrapCache; private List<OperationsNodeListener> endpointListeners; private List<BootstrapNodeListener> bootstrapListeners; /** * Correspondence between each functioning operation node's thriftHost+thriftPort * string and its start time. */ private Map<String, Long> operationNodesStartTimes; /** * Correspondence between each functioning bootstrap node's thriftHost+thriftPort * string and its start time. */ private Map<String, Long> bootstrapNodesStartTimes; public WorkerNodeTracker() { super(); init(); } /** * Instantiates a new WorkerNodeTracker. * * @param zkClient the Zookeeper client */ public WorkerNodeTracker(CuratorFramework zkClient) { super(); this.zkClient = zkClient; init(); } private void init() { endpointCache = new PathChildrenCache(zkClient, OPERATIONS_SERVER_NODE_PATH, true); bootstrapCache = new PathChildrenCache(zkClient, BOOTSTRAP_SERVER_NODE_PATH, true); endpointListeners = new CopyOnWriteArrayList<OperationsNodeListener>(); bootstrapListeners = new CopyOnWriteArrayList<BootstrapNodeListener>(); operationNodesStartTimes = new HashMap<String, Long>(); bootstrapNodesStartTimes = new HashMap<String, Long>(); } /* * (non-Javadoc) * * @see org.kaaproject.kaa.server.common.zk.ControlNodeTracker#start() */ @Override public void start() throws Exception { super.start(); endpointCache.getListenable().addListener(new PathChildrenCacheListener() { @Override public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception { switch (event.getType()) { case CHILD_ADDED: endpointAdded(event.getData()); break; case CHILD_UPDATED: endpointUpdated(event.getData()); break; case CHILD_REMOVED: endpointRemoved(event.getData()); break; default: break; } } }); endpointCache.start(StartMode.NORMAL); bootstrapCache.getListenable().addListener(new PathChildrenCacheListener() { @Override public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception { LOG.info("Bootstrap node event: " + event.getType()); switch (event.getType()) { case CHILD_ADDED: bootstrapAdded(event.getData()); break; case CHILD_UPDATED: bootstrapUpdated(event.getData()); break; case CHILD_REMOVED: bootstrapRemoved(event.getData()); break; default: break; } } }); bootstrapCache.start(StartMode.NORMAL); } /** * Gets the current endpoint nodes. * * @return the current endpoint nodes */ public List<OperationsNodeInfo> getCurrentOperationServerNodes() { List<ChildData> nodesData = endpointCache != null ? endpointCache .getCurrentData() : new ArrayList<ChildData>(); Map<ConnectionInfoKey, OperationsNodeInfo> uniqueMap = new HashMap<>(); for (ChildData data : nodesData) { OperationsNodeInfo newNodeInfo = extractOperationServerInfo(data); ConnectionInfoKey key = new ConnectionInfoKey(newNodeInfo.getConnectionInfo()); OperationsNodeInfo oldNodeInfo = uniqueMap.get(key); if (oldNodeInfo != null) { if (newNodeInfo.getTimeStarted() >= oldNodeInfo.getTimeStarted()) { uniqueMap.put(key, newNodeInfo); } } else { uniqueMap.put(key, newNodeInfo); } } return new ArrayList<>(uniqueMap.values()); } /** * Gets the current bootstrap nodes. * * @return the current bootstrap nodes */ public List<BootstrapNodeInfo> getCurrentBootstrapNodes() { List<ChildData> nodesData = bootstrapCache != null ? bootstrapCache .getCurrentData() : new ArrayList<ChildData>(); List<BootstrapNodeInfo> result = new ArrayList<>(nodesData.size()); for (ChildData data : nodesData) { result.add(extractBootstrapServerInfo(data)); } return result; } /** * Operations Node added. * * @param data the data */ protected void endpointAdded(ChildData data) { OperationsNodeInfo nodeInfo = extractOperationServerInfo(data); String endpointAddress = constructEndpointAddress(nodeInfo); operationNodesStartTimes.put(endpointAddress, nodeInfo.getTimeStarted()); for (OperationsNodeListener listener : endpointListeners) { listener.onNodeAdded(nodeInfo); } } /** * Operations Node updated. * * @param data the data */ protected void endpointUpdated(ChildData data) { OperationsNodeInfo nodeInfo = extractOperationServerInfo(data); String endpointAddress = constructEndpointAddress(nodeInfo); operationNodesStartTimes.put(endpointAddress, nodeInfo.getTimeStarted()); for (OperationsNodeListener listener : endpointListeners) { listener.onNodeUpdated(nodeInfo); } } /** * Operations Node removed. * * @param data the data */ protected void endpointRemoved(ChildData data) { OperationsNodeInfo nodeInfo = extractOperationServerInfo(data); String endpointAddress = constructEndpointAddress(nodeInfo); Long removeTime = nodeInfo.getTimeStarted(); Long updateTime = operationNodesStartTimes.get(endpointAddress); if (updateTime == null || removeTime >= updateTime) { operationNodesStartTimes.remove(endpointAddress); for (OperationsNodeListener listener : endpointListeners) { listener.onNodeRemoved(nodeInfo); } } else { LOG.debug("Ignoring [{}] endpoint removal, as it was before add/update", endpointAddress); } } /** * Bootstrap added. * * @param data the data */ protected void bootstrapAdded(ChildData data) { BootstrapNodeInfo nodeInfo = extractBootstrapServerInfo(data); String bootstrapAddress = constructBootstrapAddress(nodeInfo); bootstrapNodesStartTimes.put(bootstrapAddress, nodeInfo.getTimeStarted()); for (BootstrapNodeListener listener : bootstrapListeners) { listener.onNodeAdded(nodeInfo); } } /** * Bootstrap updated. * * @param data the data */ protected void bootstrapUpdated(ChildData data) { BootstrapNodeInfo nodeInfo = extractBootstrapServerInfo(data); String bootstrapAddress = constructBootstrapAddress(nodeInfo); bootstrapNodesStartTimes.put(bootstrapAddress, nodeInfo.getTimeStarted()); for (BootstrapNodeListener listener : bootstrapListeners) { listener.onNodeUpdated(nodeInfo); } } /** * Bootstrap removed. * * @param data the data */ protected void bootstrapRemoved(ChildData data) { BootstrapNodeInfo nodeInfo = extractBootstrapServerInfo(data); String bootstrapAddress = constructBootstrapAddress(nodeInfo); Long removeTime = nodeInfo.getTimeStarted(); Long updateTime = bootstrapNodesStartTimes.get(bootstrapAddress); if (updateTime == null || removeTime >= updateTime) { for (BootstrapNodeListener listener : bootstrapListeners) { listener.onNodeRemoved(nodeInfo); } } else { LOG.debug("Ignoring [{}] bootstrap removal, as it was before add/update", bootstrapAddress); } } /** * Adds the listener. * * @param listener the listener */ public void addListener(OperationsNodeListener listener) { LOG.debug("Listener registered: " + listener); endpointListeners.add(listener); } /** * Adds the listener. * * @param listener the listener */ public void addListener(BootstrapNodeListener listener) { LOG.debug("Listener registered: " + listener); bootstrapListeners.add(listener); } /** * Removes the listener. * * @param listener the listener * @return true, if successful */ public boolean removeListener(OperationsNodeListener listener) { if (endpointListeners.remove(listener)) { LOG.debug("Listener removed: " + listener); return true; } else { LOG.debug("Listener not found: " + listener); return false; } } /** * Removes the listener. * * @param listener the listener * @return true, if successful */ public boolean removeListener(BootstrapNodeListener listener) { if (bootstrapListeners.remove(listener)) { LOG.debug("Listener removed: " + listener); return true; } else { LOG.debug("Listener not found: " + listener); return false; } } /** * Extract endpoint server info. * * @param currentData the current data * @return the endpoint node info */ private OperationsNodeInfo extractOperationServerInfo(ChildData currentData) { OperationsNodeInfo endpointServerInfo = null; try { endpointServerInfo = operationsNodeAvroConverter.get().fromByteArray(currentData.getData(), null); } catch (IOException ex) { LOG.error("error reading control service info", ex); } return endpointServerInfo; } /** * Extract bootstrap service info. * * @param currentData the current data * @return the bootstrap node info */ private BootstrapNodeInfo extractBootstrapServerInfo(ChildData currentData) { BootstrapNodeInfo bootstrapServerInfo = null; try { bootstrapServerInfo = bootstrapNodeAvroConverter.get().fromByteArray(currentData.getData(), null); } catch (IOException ex) { LOG.error("error reading control service info", ex); } return bootstrapServerInfo; } private String constructEndpointAddress(OperationsNodeInfo nodeInfo) { return nodeInfo.getConnectionInfo().getThriftHost() + ":" + String.valueOf(nodeInfo.getConnectionInfo().getThriftPort()); } private String constructBootstrapAddress(BootstrapNodeInfo nodeInfo) { return nodeInfo.getConnectionInfo().getThriftHost() + ":" + String.valueOf(nodeInfo.getConnectionInfo().getThriftPort()); } /* * (non-Javadoc) * * @see java.io.Closeable#close() */ @Override public void close() throws IOException { endpointCache.close(); bootstrapCache.close(); super.close(); } }