/** * Copyright (c) 2002-2011 "Neo Technology," * Network Engine for Objects in Lund AB [http://neotechnology.com] * * This file is part of Neo4j. * * Neo4j 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 this program. If not, see <http://www.gnu.org/licenses/>. */ package org.neo4j.kernel.ha.zookeeper; import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.apache.zookeeper.KeeperException; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooKeeper; import org.neo4j.helpers.Pair; import org.neo4j.helpers.Triplet; import org.neo4j.kernel.ha.Master; import org.neo4j.kernel.ha.MasterClient; import org.neo4j.kernel.impl.util.StringLogger; /** * Contains basic functionality for a ZooKeeper manager, f.ex. how to get * the current master in the cluster. */ public abstract class AbstractZooKeeperManager implements Watcher { protected static final String HA_SERVERS_CHILD = "ha-servers"; protected static final int SESSION_TIME_OUT = 5000; private final String servers; private final Map<Integer, String> haServersCache = Collections.synchronizedMap( new HashMap<Integer, String>() ); private Pair<Master, Machine> cachedMaster = Pair.<Master, Machine>of( null, Machine.NO_MACHINE ); private final String storeDir; private final StringLogger msgLog; public AbstractZooKeeperManager( String servers, String storeDir ) { this.servers = servers; this.storeDir = storeDir; msgLog = StringLogger.getLogger( storeDir + "/messages.log" ); } protected ZooKeeper instantiateZooKeeper() { try { return new ZooKeeper( servers, SESSION_TIME_OUT, this ); } catch ( IOException e ) { throw new ZooKeeperException( "Unable to create zoo keeper client", e ); } } protected abstract ZooKeeper getZooKeeper(); public abstract String getRoot(); protected Pair<Integer, Integer> parseChild( String child ) { int index = child.indexOf( '_' ); if ( index == -1 ) { return null; } int id = Integer.parseInt( child.substring( 0, index ) ); int seq = Integer.parseInt( child.substring( index + 1 ) ); return Pair.of( id, seq ); } protected long readDataAsLong( String path ) throws InterruptedException, KeeperException { byte[] data = getZooKeeper().getData( path, false, null ); ByteBuffer buf = ByteBuffer.wrap( data ); return buf.getLong(); } private void invalidateMaster() { if ( cachedMaster != null ) { MasterClient client = (MasterClient) cachedMaster.first(); if ( client != null ) { client.shutdown(); } cachedMaster = Pair.<Master, Machine>of( null, Machine.NO_MACHINE ); } } protected Pair<Master, Machine> getMasterFromZooKeeper( boolean wait ) { Machine master = getMasterBasedOn( getAllMachines( wait ).values() ); MasterClient masterClient = null; if ( cachedMaster.other().getMachineId() != master.getMachineId() ) { invalidateMaster(); if ( master != Machine.NO_MACHINE && master.getMachineId() != getMyMachineId() ) { masterClient = new MasterClient( master, storeDir ); } cachedMaster = Pair.<Master, Machine>of( masterClient, master ); } return cachedMaster; } protected abstract int getMyMachineId(); public Pair<Master, Machine> getCachedMaster() { return cachedMaster; } protected Machine getMasterBasedOn( Collection<Machine> machines ) { Collection<Triplet<Integer, Long, Integer>> debugData = new ArrayList<Triplet<Integer,Long,Integer>>(); Machine master = null; int lowestSeq = Integer.MAX_VALUE; long highestTxId = -1; for ( Machine info : machines ) { debugData.add( Triplet.of( info.getMachineId(), info.getLastCommittedTxId(), info.getSequenceId() ) ); if ( info.getLastCommittedTxId() >= highestTxId ) { if ( info.getLastCommittedTxId() > highestTxId || info.getSequenceId() < lowestSeq ) { master = info; lowestSeq = info.getSequenceId(); highestTxId = info.getLastCommittedTxId(); } } } msgLog.logMessage( "getMaster " + (master != null ? master.getMachineId() : "none") + " based on " + debugData ); return master != null ? master : Machine.NO_MACHINE; } protected Map<Integer, Machine> getAllMachines( boolean wait ) { if ( wait ) { waitForSyncConnected(); } try { Map<Integer, Machine> result = new HashMap<Integer, Machine>(); String root = getRoot(); List<String> children = getZooKeeper().getChildren( root, false ); for ( String child : children ) { Pair<Integer, Integer> parsedChild = parseChild( child ); if ( parsedChild == null ) { continue; } try { int id = parsedChild.first(); int seq = parsedChild.other(); long tx = readDataAsLong( root + "/" + child ); if ( !result.containsKey( id ) || seq > result.get( id ).getSequenceId() ) { result.put( id, new Machine( id, seq, tx, getHaServer( id, wait ) ) ); } } catch ( KeeperException inner ) { if ( inner.code() != KeeperException.Code.NONODE ) { throw new ZooKeeperException( "Unabe to get master.", inner ); } } } return result; } catch ( KeeperException e ) { throw new ZooKeeperException( "Unable to get master", e ); } catch ( InterruptedException e ) { Thread.interrupted(); throw new ZooKeeperException( "Interrupted.", e ); } } protected String getHaServer( int machineId, boolean wait ) { String result = haServersCache.get( machineId ); if ( result == null ) { result = readHaServer( machineId, wait ); haServersCache.put( machineId, result ); } return result; } protected String readHaServer( int machineId, boolean wait ) { if ( wait ) { waitForSyncConnected(); } String rootPath = getRoot(); String haServerPath = rootPath + "/" + HA_SERVERS_CHILD + "/" + machineId; try { byte[] serverData = getZooKeeper().getData( haServerPath, false, null ); ByteBuffer buffer = ByteBuffer.wrap( serverData ); byte length = buffer.get(); char[] chars = new char[length]; buffer.asCharBuffer().get( chars ); String result = String.valueOf( chars ); msgLog.logMessage( "Read HA server:" + result + " (for machineID " + machineId + ") from zoo keeper" ); return result; } catch ( KeeperException e ) { throw new ZooKeeperException( "Couldn't find the HA server: " + rootPath, e ); } catch ( InterruptedException e ) { throw new ZooKeeperException( "Interrupted", e ); } } public void shutdown() { try { invalidateMaster(); cachedMaster = Pair.<Master, Machine>of( null, Machine.NO_MACHINE ); getZooKeeper().close(); } catch ( InterruptedException e ) { throw new ZooKeeperException( "Error closing zookeeper connection", e ); } } public abstract void waitForSyncConnected(); }