package io.fathom.auto.cluster; import io.fathom.auto.JsonCodec; import io.fathom.auto.TimeSpan; import io.fathom.auto.config.ConfigEntry; import io.fathom.auto.config.ConfigPath; import io.fathom.auto.config.MachineInfo; import java.io.IOException; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.collect.Lists; import com.google.common.collect.Maps; public abstract class Cluster<T> { private static final Logger log = LoggerFactory.getLogger(Cluster.class); protected final ConfigPath base; private final Lock lock; public Cluster(ConfigPath base, Lock lock) { this.base = base; this.lock = lock; } public void writeRegistration(int serverId, T registration) throws IOException { ConfigPath node = getServerPath(serverId); String json = JsonCodec.gson.toJson(registration); node.write(json); } public List<Integer> getServerIds() throws IOException { ConfigPath servers = base.child("servers"); List<Integer> serverIds = Lists.newArrayList(); Iterable<ConfigEntry> children = servers.listChildren(); if (children == null) { // Not found return null; } for (ConfigEntry o : children) { String key = o.getName(); serverIds.add(Integer.valueOf(key)); } return serverIds; } private ConfigPath getServerPath(int serverId) { ConfigPath servers = base.child("servers"); ConfigPath me = servers.child("" + serverId); return me; } private T readServerRegistration(int serverId) throws IOException { ConfigPath node = getServerPath(serverId); String json = node.read(); if (json == null) { return null; } T registration = deserialize(json); return registration; } protected abstract T deserialize(String json); public Map<Integer, T> getServers() throws IOException { Map<Integer, T> servers = Maps.newHashMap(); List<Integer> serverIds = getServerIds(); if (serverIds != null) { for (int serverId : serverIds) { T serverRegistration = readServerRegistration(serverId); servers.put(serverId, serverRegistration); } } return servers; } T me; protected T findMe(String signature) throws IOException { if (me == null) { Map<Integer, T> servers = getServers(); me = findMe(signature, servers); } return me; } private T findMe(String signature, Map<Integer, T> servers) { for (T server : servers.values()) { if (signature.equals(getSignature(server))) { return server; } } return null; } String signature; protected String getSignature() { if (signature == null) { MachineInfo machineInfo = MachineInfo.INSTANCE; signature = machineInfo.getMachineKey(); } return signature; } public T register() throws IOException { while (true) { log.info("Determining server id"); T me = findMe(getSignature()); if (me != null) { log.info("Found server: {}", me); return me; } // TODO: Strictly we don't need to take the global lock here... Lock pseudoLock = getLock(); try { if (pseudoLock.tryLock(1, TimeUnit.MINUTES)) { // We've got the lock... Map<Integer, T> servers = getServers(); T found = findMe(signature, servers); if (found == null) { int serverId = 1; while (servers.containsKey(serverId)) { serverId++; } T registration = buildRegistration(serverId); writeRegistration(serverId, registration); me = registration; log.info("Registered server: {}", me); } else { me = found; } return me; } else { log.info("Unable to obtain lock; retrying"); } } catch (Exception e) { log.warn("Error getting server id", e); pseudoLock.unlock(); TimeSpan.seconds(10).sleep(); continue; } finally { pseudoLock.unlock(); } } } protected abstract T buildRegistration(int serverId); protected abstract String getSignature(T server); public Lock getLock() { return lock; } }