Java tutorial
/** * Copyright (c) 2017 Dell Inc., or its subsidiaries. All Rights Reserved. * * 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 */ package io.pravega.common.cluster.zkImpl; import io.pravega.common.Exceptions; import io.pravega.common.cluster.Cluster; import io.pravega.common.cluster.ClusterException; import io.pravega.common.cluster.ClusterListener; import io.pravega.common.cluster.Host; import com.google.common.base.Preconditions; import lombok.Synchronized; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.SerializationUtils; import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.imps.CuratorFrameworkState; import org.apache.curator.framework.recipes.cache.ChildData; import org.apache.curator.framework.recipes.cache.PathChildrenCache; import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent; import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener; import org.apache.curator.framework.recipes.nodes.PersistentNode; import org.apache.curator.utils.ZKPaths; import org.apache.zookeeper.CreateMode; import java.io.Closeable; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.concurrent.Executor; import java.util.stream.Collectors; import static io.pravega.common.cluster.ClusterListener.EventType.ERROR; import static io.pravega.common.cluster.ClusterListener.EventType.HOST_ADDED; import static io.pravega.common.cluster.ClusterListener.EventType.HOST_REMOVED; /** * Zookeeper based implementation of Cluster. * - It uses persistent ephemeral node which is an ephemeral node that attempts to stay present in ZooKeeper, even through * connection and session interruptions. * - Ephemeral Node is valid until a session timeout, default session timeout is 60 seconds. * System property "curator-default-session-timeout" can be used to change it. */ @Slf4j public class ClusterZKImpl implements Cluster { private final static String PATH_CLUSTER = "/cluster/"; private final static int INIT_SIZE = 3; private final String clusterName; private final CuratorFramework client; private final Map<Host, PersistentNode> entryMap = new HashMap<>(INIT_SIZE); private Optional<PathChildrenCache> cache = Optional.empty(); public ClusterZKImpl(CuratorFramework zkClient, String clusterName) { this.client = zkClient; this.clusterName = clusterName; if (client.getState().equals(CuratorFrameworkState.LATENT)) { client.start(); } } /** * Register Host to cluster. * * @param host Host to be part of cluster. */ @Override @Synchronized public void registerHost(Host host) { Preconditions.checkNotNull(host, "host"); Exceptions.checkArgument(!entryMap.containsKey(host), "host", "host is already registered to cluster."); String hostPath = ZKPaths.makePath(getPathPrefix(), host.toString()); PersistentNode node = new PersistentNode(client, CreateMode.EPHEMERAL, false, hostPath, SerializationUtils.serialize(host)); node.start(); //start creation of ephemeral node in background. entryMap.put(host, node); } /** * Remove Host from cluster. * * @param host Host to be removed from cluster. */ @Override @Synchronized public void deregisterHost(Host host) { Preconditions.checkNotNull(host, "host"); PersistentNode node = entryMap.get(host); Preconditions.checkNotNull(node, "Host is not present in cluster."); entryMap.remove(host); close(node); } /** * Add Listener to the cluster. * * @param listener Cluster event Listener. */ @Override @Synchronized public void addListener(ClusterListener listener) { Preconditions.checkNotNull(listener, "listener"); if (!cache.isPresent()) { initializeCache(); } cache.get().getListenable().addListener(pathChildrenCacheListener(listener)); } /** * Add Listener to the cluster. * * @param listener Cluster event Listener. * @param executor Executor to run the listener on. */ @Override @Synchronized public void addListener(final ClusterListener listener, final Executor executor) { Preconditions.checkNotNull(listener, "listener"); Preconditions.checkNotNull(executor, "executor"); if (!cache.isPresent()) { initializeCache(); } cache.get().getListenable().addListener(pathChildrenCacheListener(listener), executor); } /** * Get the current cluster members. * * @return List of cluster members. */ @Override @Synchronized public Set<Host> getClusterMembers() { if (!cache.isPresent()) { initializeCache(); } List<ChildData> data = cache.get().getCurrentData(); return data.stream().map(d -> (Host) SerializationUtils.deserialize(d.getData())) .collect(Collectors.toSet()); } @Override public void close() throws Exception { synchronized (entryMap) { entryMap.values().forEach(this::close); cache.ifPresent(this::close); } } private void close(Closeable c) { if (c == null) { return; } try { c.close(); } catch (IOException e) { log.error("Error while closing resource", e); } } private void initializeCache() throws ClusterException { cache = Optional.of(new PathChildrenCache(client, getPathPrefix(), true)); try { cache.get().start(PathChildrenCache.StartMode.BUILD_INITIAL_CACHE); } catch (Exception e) { throw ClusterException.create(ClusterException.Type.METASTORE, "Failed to initialize ZooKeeper cache: " + e.getMessage()); } } private PathChildrenCacheListener pathChildrenCacheListener(final ClusterListener listener) { return (client, event) -> { log.debug("Event {} generated on cluster", event); switch (event.getType()) { case CHILD_ADDED: log.info("Node {} added to cluster", getServerName(event)); listener.onEvent(HOST_ADDED, (Host) SerializationUtils.deserialize(event.getData().getData())); break; case CHILD_REMOVED: log.info("Node {} removed from cluster", getServerName(event)); listener.onEvent(HOST_REMOVED, (Host) SerializationUtils.deserialize(event.getData().getData())); break; case CHILD_UPDATED: log.warn("Invalid usage: Node {} updated externally for cluster", getServerName(event)); break; case CONNECTION_LOST: log.error("Connection lost with Zookeeper"); listener.onEvent(ERROR, null); break; //$CASES-OMITTED$ default: log.warn("Received unknown event {}", event.getType()); } }; } private String getServerName(final PathChildrenCacheEvent event) { String path = event.getData().getPath(); return path.substring(path.lastIndexOf("/") + 1); } private String getPathPrefix() { return ZKPaths.makePath(PATH_CLUSTER, clusterName); } }