/** * Copyright (c) 2002-2012 "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.cluster.client; import java.net.URI; import java.util.Map; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.neo4j.cluster.BindingListener; import org.neo4j.cluster.ClusterSettings; import org.neo4j.cluster.ConnectedStateMachines; import org.neo4j.cluster.MultiPaxosServerFactory; import org.neo4j.cluster.ProtocolServer; import org.neo4j.cluster.com.NetworkInstance; import org.neo4j.cluster.protocol.atomicbroadcast.AtomicBroadcast; import org.neo4j.cluster.protocol.atomicbroadcast.AtomicBroadcastListener; import org.neo4j.cluster.protocol.atomicbroadcast.Payload; import org.neo4j.cluster.protocol.atomicbroadcast.multipaxos.InMemoryAcceptorInstanceStore; import org.neo4j.cluster.protocol.cluster.Cluster; import org.neo4j.cluster.protocol.cluster.ClusterConfiguration; import org.neo4j.cluster.protocol.cluster.ClusterListener; import org.neo4j.cluster.protocol.election.ElectionCredentialsProvider; import org.neo4j.cluster.protocol.heartbeat.Heartbeat; import org.neo4j.cluster.protocol.heartbeat.HeartbeatListener; import org.neo4j.cluster.protocol.heartbeat.HeartbeatMessage; import org.neo4j.cluster.statemachine.StateMachine; import org.neo4j.cluster.statemachine.StateTransitionLogger; import org.neo4j.cluster.timeout.FixedTimeoutStrategy; import org.neo4j.cluster.timeout.MessageTimeoutStrategy; import org.neo4j.cluster.timeout.Timeouts; import org.neo4j.helpers.DaemonThreadFactory; import org.neo4j.kernel.configuration.Config; import org.neo4j.kernel.impl.util.StringLogger; import org.neo4j.kernel.lifecycle.LifeSupport; import org.neo4j.kernel.lifecycle.Lifecycle; import org.neo4j.kernel.lifecycle.LifecycleAdapter; import org.neo4j.kernel.logging.Logging; public class ClusterClient extends LifecycleAdapter implements Cluster, AtomicBroadcast, Heartbeat { private LifeSupport life = new LifeSupport(); private Cluster cluster; private AtomicBroadcast broadcast; private Heartbeat heartbeat; private ProtocolServer server; public interface Configuration { long getHeartbeatTimeout(); long getHeartbeatInterval(); ElectionCredentialsProvider getElectionCredentialsProvider(); int[] getPorts(); String getAddress(); boolean isDiscoveryEnabled(); String[] getInitialHosts(); String getDiscoveryUrl(); String getClusterName(); } public static Configuration adapt( final Config config, final ElectionCredentialsProvider electionCredentialsProvider ) { return new Configuration() { @Override public boolean isDiscoveryEnabled() { return config.get( ClusterSettings.cluster_discovery_enabled ); } @Override public int[] getPorts() { return ClusterSettings.cluster_server.getPorts( config.getParams() ); } @Override public String[] getInitialHosts() { String hosts = config.get( ClusterSettings.initial_hosts ); return hosts != null ? hosts.split( "," ) : new String[0]; } @Override public long getHeartbeatTimeout() { return 5000; } @Override public long getHeartbeatInterval() { return 10000; } @Override public ElectionCredentialsProvider getElectionCredentialsProvider() { return electionCredentialsProvider; } @Override public String getDiscoveryUrl() { return config.get( ClusterSettings.cluster_discovery_url ); } @Override public String getClusterName() { return config.get( ClusterSettings.cluster_name ); } @Override public String getAddress() { return ClusterSettings.cluster_server.getAddress( config.getParams() ); } }; } public ClusterClient( final Configuration config, final Logging logging ) { MessageTimeoutStrategy timeoutStrategy = new MessageTimeoutStrategy( new FixedTimeoutStrategy( config.getHeartbeatTimeout() ) ) .timeout( HeartbeatMessage.sendHeartbeat, config.getHeartbeatInterval() ).relativeTimeout( HeartbeatMessage.timed_out, HeartbeatMessage.sendHeartbeat, config.getHeartbeatInterval() ); MultiPaxosServerFactory protocolServerFactory = new MultiPaxosServerFactory( new ClusterConfiguration( "neo4j.ha" ), logging ); InMemoryAcceptorInstanceStore acceptorInstanceStore = new InMemoryAcceptorInstanceStore(); ElectionCredentialsProvider electionCredentialsProvider = config.getElectionCredentialsProvider(); NetworkInstance networkNodeTCP = new NetworkInstance( new NetworkInstance.Configuration() { @Override public int[] getPorts() { return config.getPorts(); } @Override public String getAddress() { return config.getAddress(); } }, StringLogger.SYSTEM ); server = life.add( protocolServerFactory.newProtocolServer( timeoutStrategy, networkNodeTCP, networkNodeTCP, acceptorInstanceStore, electionCredentialsProvider ) ); networkNodeTCP.addNetworkChannelsListener( new NetworkInstance.NetworkChannelsListener() { @Override public void listeningAt( URI me ) { server.listeningAt( me ); server.addStateTransitionListener( new StateTransitionLogger( logging ) ); } @Override public void channelOpened( URI to ) { } @Override public void channelClosed( URI to ) { } } ); life.add( networkNodeTCP ); // Timeout timer - triggers every 10 ms life.add( new Lifecycle() { private ScheduledExecutorService scheduler; @Override public void init() throws Throwable { server.getTimeouts().tick( System.currentTimeMillis() ); } @Override public void start() throws Throwable { scheduler = Executors.newSingleThreadScheduledExecutor( new DaemonThreadFactory( "timeout" ) ); scheduler.scheduleWithFixedDelay( new Runnable() { @Override public void run() { long now = System.currentTimeMillis(); server.getTimeouts().tick( now ); } }, 0, 10, TimeUnit.MILLISECONDS ); } @Override public void stop() throws Throwable { scheduler.shutdownNow(); } @Override public void shutdown() throws Throwable { } } ); life.add( new ClusterJoin( new ClusterJoin.Configuration() { @Override public boolean isDiscoveryEnabled() { return config.isDiscoveryEnabled(); } @Override public String[] getInitialHosts() { return config.getInitialHosts(); } @Override public String getDiscoveryUrl() { return config.getDiscoveryUrl(); } @Override public String getClusterName() { return config.getClusterName(); } }, server, logging ) ); cluster = server.newClient( Cluster.class ); broadcast = server.newClient( AtomicBroadcast.class ); heartbeat = server.newClient( Heartbeat.class ); } @Override public void start() throws Throwable { life.start(); } @Override public void stop() throws Throwable { life.stop(); } @Override public void broadcast( Payload payload ) { broadcast.broadcast( payload ); } @Override public void addAtomicBroadcastListener( AtomicBroadcastListener listener ) { broadcast.addAtomicBroadcastListener( listener ); } @Override public void removeAtomicBroadcastListener( AtomicBroadcastListener listener ) { broadcast.removeAtomicBroadcastListener( listener ); } @Override public void create( String clusterName ) { cluster.create( clusterName ); } @Override public Future<ClusterConfiguration> join( URI otherServerUrl ) { return cluster.join( otherServerUrl ); } @Override public void leave() { cluster.leave(); } @Override public void addClusterListener( ClusterListener listener ) { cluster.addClusterListener( listener ); } @Override public void removeClusterListener( ClusterListener listener ) { cluster.removeClusterListener( listener ); } @Override public void addHeartbeatListener( HeartbeatListener listener ) { heartbeat.addHeartbeatListener( listener ); } @Override public void removeHeartbeatListener( HeartbeatListener listener ) { heartbeat.removeHeartbeatListener( listener ); } public void addBindingListener( BindingListener bindingListener ) { server.addBindingListener( bindingListener ); } public void dumpDiagnostics( StringBuilder appendTo ) { ConnectedStateMachines stateMachines = server.getConnectedStateMachines(); for ( StateMachine stateMachine : stateMachines.getStateMachines() ) { appendTo.append( " " ).append( stateMachine.getMessageType().getSimpleName() ).append( ":" ) .append( stateMachine.getState().toString() ).append( "\n" ); } appendTo.append( "Current timeouts:\n" ); for ( Map.Entry<Object, Timeouts.Timeout> objectTimeoutEntry : stateMachines.getTimeouts().getTimeouts() .entrySet() ) { appendTo.append( objectTimeoutEntry.getKey().toString() ).append( ":" ) .append( objectTimeoutEntry.getValue().getTimeoutMessage().toString() ); } } }