/* This file is part of VoltDB. * Copyright (C) 2008-2017 VoltDB Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with VoltDB. If not, see <http://www.gnu.org/licenses/>. */ package org.voltdb.iv2; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.zookeeper_voltpatches.KeeperException; import org.apache.zookeeper_voltpatches.WatchedEvent; import org.apache.zookeeper_voltpatches.Watcher; import org.apache.zookeeper_voltpatches.ZooKeeper; import org.voltcore.logging.VoltLogger; import org.voltcore.utils.CoreUtils; import org.voltcore.zk.LeaderElector; import org.voltcore.zk.ZKUtil; import org.voltcore.zk.ZKUtil.ChildrenCallback; import org.voltdb.VoltDB; import org.voltdb.VoltZK; /** * Monitors initiator leaders for each partition. When a new partition is * created, it will automatically start monitoring that partition. */ public class InitiatorLeaderMonitor { private static final VoltLogger hostLog = new VoltLogger("HOST"); private final ZooKeeper zk; private final ExecutorService es = CoreUtils.getCachedSingleThreadExecutor("Client Interface", 15000); private final Map<Integer, Long> initiatorLeaders = Collections.synchronizedMap(new HashMap<Integer, Long>()); private final AtomicBoolean shutdown = new AtomicBoolean(false); private final Watcher partitionWatcher = new Watcher() { @Override public void process(WatchedEvent event) { if (shutdown.get() == false) { es.submit(handlePartitionChange); } } }; private final Runnable handlePartitionChange = new Runnable() { @Override public void run() { try { watchPartitions(); } catch (Exception e) { VoltDB.crashLocalVoltDB(e.getMessage(), false, e); } } }; private class LeaderWatcher implements Watcher { private final int partition; private final String path; public LeaderWatcher(int partition, String path) { this.partition = partition; this.path = path; } @Override public void process(WatchedEvent event) { if (shutdown.get() == false) { es.submit(new LeaderChangeHandler(partition, path)); } } }; private class LeaderChangeHandler implements Runnable { private final int partition; private final String path; public LeaderChangeHandler(int partition, String path) { this.partition = partition; this.path = path; } @Override public void run() { try { watchInitiatorLeader(partition, path); } catch (Exception e) { VoltDB.crashLocalVoltDB("Failed to get initiator leaders", false, e); } } }; /** * Need to call {@link #start()} to start monitoring the leaders * * @param zk ZooKeeper instance */ public InitiatorLeaderMonitor(ZooKeeper zk) { this.zk = zk; } /** * Start monitoring the leaders. This is a blocking operation. * * @throws InterruptedException * @throws ExecutionException */ public void start() throws InterruptedException, ExecutionException { Future<?> task = es.submit(handlePartitionChange); task.get(); } public void shutdown() { shutdown.set(true); } /** * Get the initiator leader HSId for the given partition. * * @param partition * partition ID * @return The leader HSId if it's found, or null if the leader is unknown */ public Long getLeader(int partition) { return initiatorLeaders.get(partition); } /** * @throws KeeperException * @throws InterruptedException */ private void watchPartitions() throws KeeperException, InterruptedException { HashSet<Integer> existingPartitions = new HashSet<Integer>(initiatorLeaders.keySet()); List<String> partitions = zk.getChildren(VoltZK.leaders_initiators, partitionWatcher); Map<Integer, ChildrenCallback> callbacks = new HashMap<Integer, ChildrenCallback>(); for (String partitionString : partitions) { int partition = LeaderElector.getPartitionFromElectionDir(partitionString); ChildrenCallback cb = new ChildrenCallback(); if (!existingPartitions.contains(partition)) { String path = ZKUtil.joinZKPath(VoltZK.leaders_initiators, partitionString); zk.getChildren(path, new LeaderWatcher(partition, path), cb, null); callbacks.put(partition, cb); } else { existingPartitions.remove(partition); } } // now existingPartitions only contains partitions that just disappeared initiatorLeaders.keySet().removeAll(existingPartitions); for (Entry<Integer, ChildrenCallback> e : callbacks.entrySet()) { processInitiatorLeader(e.getKey(), e.getValue()); } } private void watchInitiatorLeader(int partition, String path) throws KeeperException, InterruptedException { ChildrenCallback cb = new ChildrenCallback(); zk.getChildren(path, new LeaderWatcher(partition, path), cb, null); processInitiatorLeader(partition, cb); } private void processInitiatorLeader(int partition, ChildrenCallback cb) throws KeeperException, InterruptedException { Object[] result = cb.get(); @SuppressWarnings("unchecked") List<String> children = (List<String>) result[3]; if (!children.isEmpty()) { Collections.sort(children); String leader = children.get(0); // The leader HSId is the first half of the string try { long HSId = Long.parseLong(leader.split("_")[0]); initiatorLeaders.put(partition, HSId); } catch (NumberFormatException e) { hostLog.error("Unable to get initiator leader HSId from node " + leader); } } else { // No leader for this partition initiatorLeaders.remove(partition); } } }