/** * 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.kernel.ha; import java.io.File; import java.lang.reflect.Proxy; import java.util.List; import java.util.Map; import org.neo4j.cluster.ClusterSettings; import org.neo4j.cluster.client.ClusterClient; import org.neo4j.cluster.com.NetworkInstance; import org.neo4j.cluster.protocol.election.DefaultElectionCredentialsProvider; import org.neo4j.com.ComSettings; import org.neo4j.graphdb.DependencyResolver; import org.neo4j.graphdb.index.IndexProvider; import org.neo4j.kernel.HighlyAvailableKernelData; import org.neo4j.kernel.IdGeneratorFactory; import org.neo4j.kernel.InternalAbstractGraphDatabase; import org.neo4j.kernel.KernelData; import org.neo4j.kernel.configuration.ConfigurationDefaults; import org.neo4j.kernel.extension.KernelExtensionFactory; import org.neo4j.kernel.ha.cluster.ClusterEvents; import org.neo4j.kernel.ha.cluster.ClusterMemberChangeEvent; import org.neo4j.kernel.ha.cluster.ClusterMemberContext; import org.neo4j.kernel.ha.cluster.ClusterMemberListener; import org.neo4j.kernel.ha.cluster.ClusterMemberModeSwitcher; import org.neo4j.kernel.ha.cluster.ClusterMemberState; import org.neo4j.kernel.ha.cluster.ClusterMemberStateMachine; import org.neo4j.kernel.ha.cluster.paxos.PaxosClusterEvents; import org.neo4j.kernel.impl.cache.CacheProvider; import org.neo4j.kernel.impl.core.Caches; import org.neo4j.kernel.impl.core.RelationshipTypeCreator; import org.neo4j.kernel.impl.transaction.LockManager; import org.neo4j.kernel.impl.transaction.TxHook; import org.neo4j.kernel.impl.transaction.TxManager; import org.neo4j.kernel.impl.transaction.XaDataSourceManager; import org.neo4j.kernel.impl.transaction.xaframework.ForceMode; import org.neo4j.kernel.impl.transaction.xaframework.TransactionInterceptorProvider; import org.neo4j.kernel.impl.transaction.xaframework.TxIdGenerator; import org.neo4j.kernel.lifecycle.LifecycleAdapter; import org.neo4j.kernel.logging.ClassicLoggingService; import org.neo4j.kernel.logging.LogbackService; import org.neo4j.kernel.logging.Loggers; import org.neo4j.kernel.logging.Logging; import ch.qos.logback.classic.LoggerContext; public class HighlyAvailableGraphDatabase extends InternalAbstractGraphDatabase { private RequestContextFactory requestContextFactory; private ClusterSlaves slaves; private DelegateInvocationHandler delegateInvocationHandler; private LoggerContext loggerContext; private DefaultTransactionSupport transactionSupport; private Master master; private ClusterEvents clusterEvents; private InstanceAccessGuard accessGuard; private ClusterMemberStateMachine memberStateMachine; private UpdatePuller updatePuller; private ClusterMemberContext memberContext; private ClusterClient clusterClient; public HighlyAvailableGraphDatabase( String storeDir, Map<String, String> params, List<IndexProvider> indexProviders, List<KernelExtensionFactory<?>> kernelExtensions, List<CacheProvider> cacheProviders, List<TransactionInterceptorProvider> txInterceptorProviders ) { super( storeDir, withDefaults( params ), indexProviders, kernelExtensions, cacheProviders, txInterceptorProviders ); run(); } protected void create() { life.add( new BranchedDataMigrator( new File( storeDir ) ) ); delegateInvocationHandler = new DelegateInvocationHandler(); master = (Master) Proxy.newProxyInstance( Master.class.getClassLoader(), new Class[]{Master.class}, delegateInvocationHandler ); accessGuard = new InstanceAccessGuard(); super.create(); kernelEventHandlers.registerKernelEventHandler( new TxManagerCheckKernelEventHandler( xaDataSourceManager, (TxManager) txManager ) ); transactionSupport.setLockReleaser( lockReleaser ); life.add( memberStateMachine ); life.add( updatePuller = new UpdatePuller( (HaXaDataSourceManager) xaDataSourceManager, master, requestContextFactory, txManager, accessGuard, config, msgLog ) ); // Add this just before cluster join to ensure that it is up and running as late as possible // and is shut down as early as possible life.add( clusterClient ); life.add( new StartupWaiter() ); diagnosticsManager.appendProvider( new HighAvailabilityDiagnostics( memberStateMachine, clusterClient ) ); } private static Map<String, String> withDefaults( Map<String, String> params ) { return new ConfigurationDefaults( HaSettings.class, NetworkInstance.Configuration.class, ClusterSettings.class, ComSettings.class ).apply( params ); } public void start() { life.start(); } public void stop() { life.stop(); } @Override protected org.neo4j.graphdb.Transaction beginTx( ForceMode forceMode ) { // TODO first startup ever we don't have a proper db, so don't even serve read requests // if this is a startup for where we have been a member of this cluster before we // can server (possibly quite outdated) read requests. accessGuard.await( 1000 * 60 ); return super.beginTx( forceMode ); } protected Logging createStringLogger() { try { getClass().getClassLoader().loadClass( "ch.qos.logback.classic.LoggerContext" ); loggerContext = new LoggerContext(); return life.add( new LogbackService( config, loggerContext ) ); } catch ( ClassNotFoundException e ) { return life.add( new ClassicLoggingService( config ) ); } } @Override protected XaDataSourceManager createXaDataSourceManager() { XaDataSourceManager toReturn = new HaXaDataSourceManager( logging.getLogger( Loggers.DATASOURCE ) ); requestContextFactory = new RequestContextFactory( config.get( HaSettings.server_id ), toReturn, dependencyResolver ); return toReturn; } @Override protected TxHook createTxHook() { DefaultElectionCredentialsProvider electionCredentialsProvider = new DefaultElectionCredentialsProvider( config.get( HaSettings.server_id ), new OnDiskLastTxIdGetter( new File( getStoreDir() ) ) ); // Add if to lifecycle later, as late as possible really clusterClient = new ClusterClient( ClusterClient.adapt( config, electionCredentialsProvider ), logging ); clusterEvents = life.add( new PaxosClusterEvents( PaxosClusterEvents.adapt( config ), clusterClient, logging.getLogger( Loggers.CLUSTER ) ) ); memberContext = new ClusterMemberContext( clusterClient ); life.add( memberContext ); memberStateMachine = new ClusterMemberStateMachine( memberContext, accessGuard, clusterEvents, logging.getLogger( Loggers.CLUSTER ) ); life.add( new ClusterMemberModeSwitcher( delegateInvocationHandler, clusterEvents, memberStateMachine, this, config, logging.getLogger( Loggers.CLUSTER ) ) ); DelegateInvocationHandler<TxHook> txHookDelegate = new DelegateInvocationHandler<TxHook>(); TxHook txHook = (TxHook) Proxy.newProxyInstance( TxHook.class.getClassLoader(), new Class[]{TxHook.class}, txHookDelegate ); new TxHookModeSwitcher( memberStateMachine, txHookDelegate, master, new TxHookModeSwitcher.RequestContextFactoryResolver() { @Override public RequestContextFactory get() { return requestContextFactory; } }, dependencyResolver ); return txHook; } @Override protected TxIdGenerator createTxIdGenerator() { DelegateInvocationHandler<TxIdGenerator> txIdGeneratorDelegate = new DelegateInvocationHandler<TxIdGenerator>(); TxIdGenerator txIdGenerator = (TxIdGenerator) Proxy.newProxyInstance( TxIdGenerator.class.getClassLoader(), new Class[]{TxIdGenerator.class}, txIdGeneratorDelegate ); slaves = life.add( new ClusterSlaves( memberStateMachine, msgLog, config, xaDataSourceManager ) ); new TxIdGeneratorModeSwitcher( memberStateMachine, txIdGeneratorDelegate, (HaXaDataSourceManager) xaDataSourceManager, master, requestContextFactory, msgLog, config, slaves ); return txIdGenerator; } @Override protected IdGeneratorFactory createIdGeneratorFactory() { return new HaIdGeneratorFactory( master, memberStateMachine ); } @Override protected LockManager createLockManager() { // TransactionSupport piggy-backing on creating the lock manager transactionSupport = new DefaultTransactionSupport( lockReleaser, txManager, txHook, accessGuard, config ); DelegateInvocationHandler<LockManager> lockManagerDelegate = new DelegateInvocationHandler<LockManager>(); LockManager lockManager = (LockManager) Proxy.newProxyInstance( LockManager.class.getClassLoader(), new Class[]{LockManager.class}, lockManagerDelegate ); new LockManagerModeSwitcher( memberStateMachine, lockManagerDelegate, txManager, txHook, (HaXaDataSourceManager) xaDataSourceManager, master, requestContextFactory, transactionSupport ); return lockManager; } @Override protected RelationshipTypeCreator createRelationshipTypeCreator() { DelegateInvocationHandler<RelationshipTypeCreator> relationshipTypeCreatorDelegate = new DelegateInvocationHandler<RelationshipTypeCreator>(); RelationshipTypeCreator relationshipTypeCreator = (RelationshipTypeCreator) Proxy.newProxyInstance( RelationshipTypeCreator.class.getClassLoader(), new Class[]{RelationshipTypeCreator.class}, relationshipTypeCreatorDelegate ); new RelationshipTypeCreatorModeSwitcher( memberStateMachine, relationshipTypeCreatorDelegate, (HaXaDataSourceManager) xaDataSourceManager, master, requestContextFactory ); return relationshipTypeCreator; } @Override protected Caches createCaches() { return new HaCaches( msgLog ); } @Override protected void createNeoDataSource() { // no op, we must wait to join the cluster to do stuff } @Override protected KernelData createKernelData() { return new HighlyAvailableKernelData( this, new ClusterMemberInfoProvider( clusterEvents ), new ClusterDatabaseInfoProvider( clusterEvents, clusterClient ) ); } @Override protected void registerRecovery() { memberStateMachine.addClusterMemberListener( new ClusterMemberListener() { @Override public void masterIsElected( ClusterMemberChangeEvent event ) { } @Override public void masterIsAvailable( ClusterMemberChangeEvent event ) { if ( event.getOldState().equals( ClusterMemberState.TO_MASTER ) && event.getNewState().equals( ClusterMemberState.MASTER ) ) { doRecovery(); } } @Override public void slaveIsAvailable( ClusterMemberChangeEvent event ) { if ( event.getOldState().equals( ClusterMemberState.TO_SLAVE ) && event.getNewState().equals( ClusterMemberState.SLAVE ) ) { doRecovery(); } } @Override public void instanceStops( ClusterMemberChangeEvent event ) { } private void doRecovery() { try { synchronized ( xaDataSourceManager ) { HighlyAvailableGraphDatabase.this.doRecovery(); } } catch ( Throwable throwable ) { try { memberStateMachine.stop(); } catch ( Throwable throwable1 ) { msgLog.warn( "Could not stop", throwable1 ); } try { memberStateMachine.start(); } catch ( Throwable throwable1 ) { msgLog.warn( "Could not start", throwable1 ); } } } } ); } @Override public String toString() { return getClass().getSimpleName() + "[" + 0 + ", " + storeDir + "]"; } public String getInstanceState() { return memberStateMachine.getCurrentState().name(); } public long lastUpdateTime() { //TODO implement this as a transaction interceptor return 0; } public boolean isMaster() { return memberStateMachine.getCurrentState() == ClusterMemberState.MASTER; } @Override public DependencyResolver getDependencyResolver() { return new DependencyResolver() { @Override public <T> T resolveDependency( Class<T> type ) throws IllegalArgumentException { T result; try { result = dependencyResolver.resolveDependency( type ); } catch ( IllegalArgumentException e ) { if ( ClusterEvents.class.isAssignableFrom( type ) ) { result = (T) clusterEvents; } else if ( UpdatePuller.class.isAssignableFrom( type ) ) { result = (T) updatePuller; } else if ( Slaves.class.isAssignableFrom( type ) ) { result = (T) slaves; } else { throw e; } } return result; } }; } /** * At end of startup, wait for instance to become either master or slave. * <p/> * This helps users who expect to be able to access the instance after * the constructor is run. */ private class StartupWaiter extends LifecycleAdapter { @Override public void start() throws Throwable { accessGuard.await( 10000 ); } } }