/************************************************************************* * Copyright 2009-2012 Eucalyptus Systems, Inc. * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; version 3 of the License. * * 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see http://www.gnu.org/licenses/. * * Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta * CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need * additional information or have any questions. * * This file may incorporate work covered under the following copyright * and permission notice: * * Software License Agreement (BSD License) * * Copyright (c) 2008, Regents of the University of California * All rights reserved. * * Redistribution and use of this software in source and binary forms, * with or without modification, are permitted provided that the * following conditions are met: * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer * in the documentation and/or other materials provided with the * distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. USERS OF THIS SOFTWARE ACKNOWLEDGE * THE POSSIBLE PRESENCE OF OTHER OPEN SOURCE LICENSED MATERIAL, * COPYRIGHTED MATERIAL OR PATENTED MATERIAL IN THIS SOFTWARE, * AND IF ANY SUCH MATERIAL IS DISCOVERED THE PARTY DISCOVERING * IT MAY INFORM DR. RICH WOLSKI AT THE UNIVERSITY OF CALIFORNIA, * SANTA BARBARA WHO WILL THEN ASCERTAIN THE MOST APPROPRIATE REMEDY, * WHICH IN THE REGENTS' DISCRETION MAY INCLUDE, WITHOUT LIMITATION, * REPLACEMENT OF THE CODE SO IDENTIFIED, LICENSING OF THE CODE SO * IDENTIFIED, OR WITHDRAWAL OF THE CODE CAPABILITY TO THE EXTENT * NEEDED TO COMPLY WITH ANY SUCH LICENSES OR RIGHTS. ************************************************************************/ package com.eucalyptus.component; import java.net.UnknownHostException; import java.util.List; import java.util.concurrent.Callable; import java.util.concurrent.TimeUnit; import com.eucalyptus.component.events.ServiceEvents; import org.apache.log4j.Logger; import com.eucalyptus.bootstrap.Bootstrap; import com.eucalyptus.bootstrap.BootstrapArgs; import com.eucalyptus.bootstrap.Databases; import com.eucalyptus.bootstrap.Host; import com.eucalyptus.bootstrap.Hosts; import com.eucalyptus.component.Component.State; import com.eucalyptus.component.Component.Transition; import com.eucalyptus.component.Faults.CheckException; import com.eucalyptus.configurable.ConfigurableProperty; import com.eucalyptus.configurable.MultiDatabasePropertyEntry; import com.eucalyptus.configurable.PropertyDirectory; import com.eucalyptus.configurable.SingletonDatabasePropertyEntry; import com.eucalyptus.configurable.StaticPropertyEntry; import com.eucalyptus.empyrean.DescribeServicesResponseType; import com.eucalyptus.empyrean.DescribeServicesType; import com.eucalyptus.empyrean.DisableServiceResponseType; import com.eucalyptus.empyrean.DisableServiceType; import com.eucalyptus.empyrean.Empyrean; import com.eucalyptus.empyrean.EmpyreanMessage; import com.eucalyptus.empyrean.EnableServiceResponseType; import com.eucalyptus.empyrean.EnableServiceType; import com.eucalyptus.empyrean.ServiceId; import com.eucalyptus.empyrean.ServiceStatusType; import com.eucalyptus.empyrean.StartServiceResponseType; import com.eucalyptus.empyrean.StartServiceType; import com.eucalyptus.empyrean.StopServiceResponseType; import com.eucalyptus.empyrean.StopServiceType; import com.eucalyptus.records.EventRecord; import com.eucalyptus.records.EventType; import com.eucalyptus.records.Logs; import com.eucalyptus.util.Callback; import com.eucalyptus.util.Callback.Completion; import com.eucalyptus.util.Exceptions; import com.eucalyptus.util.TypeMappers; import com.eucalyptus.util.async.AsyncRequests; import com.eucalyptus.util.async.CheckedListenableFuture; import com.eucalyptus.util.async.Futures; import com.eucalyptus.util.fsm.Automata; import com.eucalyptus.util.fsm.TransitionAction; import com.eucalyptus.util.fsm.TransitionRecord; import com.google.common.base.Joiner; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.cache.CacheBuilder; import com.google.common.cache.CacheLoader; import com.google.common.cache.LoadingCache; import com.google.common.collect.Iterables; import com.google.common.collect.ObjectArrays; public class ServiceTransitions { static Logger LOG = Logger.getLogger( ServiceTransitions.class ); private static final Component.State[] EMPTY = {}; private static final Supplier<Void> STATIC_PROPERTIES_SUPPLIER = Suppliers.memoizeWithExpiration( StaticPropertiesAdd.INSTANCE, 10, TimeUnit.SECONDS ); LoadingCache<TransitionActions, ServiceTransitionCallback> hi = CacheBuilder.newBuilder().build( new CacheLoader<TransitionActions, ServiceTransitionCallback>( ) { @Override public ServiceTransitionCallback load( TransitionActions input ) { return null; } } ); private static Component.State[] sequence( Component.State... states ) { return states; } interface ServiceTransitionCallback { public void fire( ServiceConfiguration parent ) throws Exception; } /** * GRZE:FIXME: this is a shoddy static method for definining the prefered path from n_0 to n_1 for * n_0,n_1 \in G the state machine; think dijkstra. **/ @SuppressWarnings( "unchecked" ) public static CheckedListenableFuture<ServiceConfiguration> pathTo( final ServiceConfiguration configuration, final Component.State goalState ) { try { State[] path = null; State initialState = configuration.lookupState( ); switch ( goalState ) { case LOADED: path = pathToLoaded( initialState ); break; case DISABLED: path = pathToDisabled( initialState ); break; case ENABLED: path = pathToEnabled( initialState ); break; case STOPPED: path = pathToStopped( initialState ); break; case NOTREADY: path = pathToStarted( initialState ); break; case PRIMORDIAL: path = pathToPrimordial( initialState ); break; case BROKEN: path = pathToBroken( initialState ); break; case INITIALIZED: path = pathToInitialized( initialState ); break; } if ( !initialState.equals( goalState ) ) { Logs.extreme().debug(configuration.getName() + " transitioning " + initialState + "->" + goalState + " using path " + Joiner.on("->").join(path)); } CheckedListenableFuture<ServiceConfiguration> result = executeTransition( configuration, Automata.sequenceTransitions( configuration, path ) ); return result; } catch ( RuntimeException ex ) { Logs.extreme().error(configuration.getName() + " failed to transition to " + goalState + " because of: " + Exceptions.causeString(ex)); Logs.extreme( ).error( ex, ex ); throw ex; } } private static State[] pathToBroken( Component.State fromState ) { State[] transition = new State[] { fromState }; switch ( fromState ) { case BROKEN: break; default: transition = ObjectArrays.concat( ServiceTransitions.pathToPrimordial( fromState ), Component.State.BROKEN ); break; } return transition; } private static State[] pathToPrimordial( Component.State fromState ) { State[] transition = new State[] { fromState }; switch ( fromState ) { case PRIMORDIAL: break; default: transition = ObjectArrays.concat( ServiceTransitions.pathToStopped( fromState ), Component.State.PRIMORDIAL ); break; } return transition; } private static State[] pathToLoaded( Component.State fromState ) { State[] transition = new State[] { fromState }; switch ( fromState ) { case LOADED: break; default: transition = ObjectArrays.concat( ServiceTransitions.pathToInitialized( fromState ), Component.State.LOADED ); break; } return transition; } private static final State[] pathToInitialized( final Component.State fromState ) { State[] transition = new State[] { fromState }; switch ( fromState ) { case LOADED: transition = ObjectArrays.concat( fromState, pathToInitialized( Component.State.NOTREADY ) ); break; case ENABLED: transition = ObjectArrays.concat( transition, Component.State.DISABLED ); //$FALL-THROUGH$ case DISABLED: case NOTREADY: transition = ObjectArrays.concat( transition, Component.State.STOPPED ); //$FALL-THROUGH$ case BROKEN: case PRIMORDIAL: case STOPPED: transition = ObjectArrays.concat( transition, Component.State.INITIALIZED ); break; case INITIALIZED: break; } return transition; } private static State[] pathToStarted( Component.State fromState ) { State[] transition = new State[] { fromState }; switch ( fromState ) { case NOTREADY: break; case LOADED: transition = ObjectArrays.concat( transition, Component.State.NOTREADY ); break; default: transition = ObjectArrays.concat( pathToLoaded( fromState ), Component.State.NOTREADY ); } return transition; } private static final State[] pathToDisabled( final Component.State fromState ) { State[] transition = new State[] { fromState }; switch ( fromState ) { case ENABLED: case DISABLED: case NOTREADY: transition = ObjectArrays.concat( transition, Component.State.DISABLED ); break; default: transition = ObjectArrays.concat( pathToStarted( fromState ), Component.State.DISABLED ); } return transition; } private static final State[] pathToEnabled( final Component.State fromState ) { State[] transition = new State[] { fromState }; switch ( fromState ) { case ENABLED: transition = ObjectArrays.concat( transition, Component.State.ENABLED ); break; default: transition = ObjectArrays.concat( pathToDisabled( fromState ), Component.State.ENABLED ); } return transition; } private static final State[] pathToStopped( final Component.State fromState ) { State[] transition = new State[] { fromState }; switch ( fromState ) { case ENABLED: transition = ObjectArrays.concat( transition, Component.State.DISABLED ); //$FALL-THROUGH$ case DISABLED: case NOTREADY: case BROKEN: transition = ObjectArrays.concat( transition, Component.State.STOPPED ); break; case STOPPED: break; default: transition = ObjectArrays.concat( pathToStarted( fromState ), Component.State.STOPPED ); } return transition; } private static CheckedListenableFuture<ServiceConfiguration> executeTransition( final ServiceConfiguration config, Callable<CheckedListenableFuture<ServiceConfiguration>> transition ) { if ( transition != null ) { try { return transition.call( ); } catch ( Exception ex ) { return Futures.predestinedFailedFuture( ex ); } } else { return Futures.predestinedFuture( config ); } } private static <T extends EmpyreanMessage> T sendEmpyreanRequest( final ServiceConfiguration parent, final EmpyreanMessage msg ) throws Exception { ServiceConfiguration config = ServiceConfigurations.createEphemeral( Empyrean.INSTANCE, parent.getInetAddress( ) ); Logs.extreme( ).debug( "Sending request " + msg.getClass( ).getSimpleName( ) + " to " + parent.getFullName( ) ); try { if ( BootstrapArgs.debugTopology( ) == null ) { T reply = ( T ) AsyncRequests.sendSync( config, msg ); return reply; } else { return msg.getReply( ); } } catch ( Exception ex ) { Logs.extreme( ).error( parent.getFullName( ) + " failed request because of: " + ex.getMessage( ), ex ); throw ex; } } private static void processTransition( final ServiceConfiguration parent, final Completion transitionCallback, final TransitionActions transitionAction ) { TransitionRecord<ServiceConfiguration, State, Transition> transitionRecord = parent.lookupStateMachine( ).getTransitionRecord( ); ServiceTransitionCallback trans = null; try { if ( Hosts.isServiceLocal( parent ) ) { trans = ServiceLocalTransitionCallbacks.valueOf( transitionAction.name( ) ); } else if ( Hosts.isCoordinator( ) ) { trans = CloudRemoteTransitionCallbacks.valueOf( transitionAction.name( ) ); } else { trans = ServiceRemoteTransitionNotification.valueOf( transitionAction.name( ) ); } if ( trans != null ) { Logs.extreme( ).debug( "Executing transition: " + trans.getClass( ) + "." + transitionAction.name( ) + " for " + parent ); trans.fire( parent ); } transitionCallback.fire( ); Faults.flush( parent ); } catch ( Exception ex ) { Logs.extreme().info( parent.getName( ) + " failed " + transitionAction.name( ) + ": " + ex.getMessage( ) ); if ( Faults.filter( parent, ex ) ) { transitionCallback.fireException( ex ); Faults.submit( parent, transitionRecord, Faults.failure( parent, ex ) ); throw Exceptions.toUndeclared( ex ); } else { transitionCallback.fire( ); Faults.submit( parent, transitionRecord, Faults.advisory( parent, ex ) ); } // } finally { // transitionCallback.fire( ); } } public enum TransitionActions implements TransitionAction<ServiceConfiguration> { ENABLE, CHECK, DISABLE, START, LOAD, STOP, DESTROY; @Override public boolean before( final ServiceConfiguration parent ) { try { EventRecord.here( ServiceBuilder.class, EventType.SERVICE_TRANSITION_BEFORE, this.name( ), parent.lookupState( ).toString( ), parent.getFullName( ).toString( ), parent.toString( ) ).exhaust( ); } catch ( Exception ex ) { LOG.error( ex, ex ); } return true; } @Override public void leave( ServiceConfiguration parent, Completion transitionCallback ) { try { EventRecord.here( ServiceBuilder.class, EventType.SERVICE_TRANSITION, this.name( ), parent.lookupState( ).toString( ), parent.getFullName( ).toString( ), parent.toString( ) ).exhaust( ); ServiceTransitions.processTransition( parent, transitionCallback, this ); } catch ( Exception ex ) { transitionCallback.fireException( ex ); } } @Override public void enter( final ServiceConfiguration parent ) { try { EventRecord.here( ServiceBuilder.class, EventType.SERVICE_TRANSITION_ENTER_STATE, this.name( ), parent.lookupState( ).toString( ), parent.getFullName( ).toString( ), parent.toString( ) ).exhaust( ); } catch ( Exception ex ) { LOG.error( ex, ex ); } } @Override public void after( final ServiceConfiguration parent ) { try { EventRecord.here( ServiceBuilder.class, EventType.SERVICE_TRANSITION_AFTER_STATE, this.name( ), parent.lookupState( ).toString( ), parent.getFullName( ).toString( ), parent.toString( ) ).exhaust( ); } catch ( Exception ex ) { LOG.error( ex, ex ); } } } enum CloudRemoteTransitionCallbacks implements ServiceTransitionCallback { LOAD { @Override public void fire( final ServiceConfiguration parent ) throws Exception {} }, DESTROY { @Override public void fire( final ServiceConfiguration parent ) throws Exception {} }, CHECK { @Override public void fire( final ServiceConfiguration parent ) throws Exception { if ( !parent.getComponentId( ).isDistributedService( ) ) { return; } else { CheckException errors = null; Host h = Hosts.lookup( parent.getHostName( ) ); if ( h == null ) { UnknownHostException ex = new UnknownHostException( "Failed to lookup host " + parent.getHostName( ) + " for service " + parent.getFullName( ) + ". Current hosts are: " + Hosts.list( ) ); errors = Faults.failure( parent, ex ); } else if ( !h.hasBootstrapped( ) ) { UnknownHostException ex = new UnknownHostException( "Host " + parent.getHostName( ) + " not yet bootstrapped for service " + parent.getFullName( ) + "." ); errors = Faults.failure( parent, ex ); } else { DescribeServicesResponseType response = ServiceTransitions.sendEmpyreanRequest( parent, new DescribeServicesType( ) { { this.getServices( ).add( TypeMappers.transform( parent, ServiceId.class ) ); } } ); ServiceStatusType status = Iterables.find( response.getServiceStatuses( ), new Predicate<ServiceStatusType>( ) { @Override public boolean apply( final ServiceStatusType arg0 ) { return parent.getName( ).equals( arg0.getServiceId( ).getName( ) ); } } ); errors = Faults.transformToExceptions( ).apply( status ); } if ( Faults.Severity.FATAL.equals( errors.getSeverity( ) ) ) { // Faults.failstop( parent, errors ); makes no sense! throw errors; } else if ( Faults.Severity.TRACE.equals( errors.getSeverity( ) ) ) { Logs.extreme( ).error( errors, errors ); return; } else if ( errors.getSeverity( ).ordinal( ) < Faults.Severity.ERROR.ordinal( ) ) { Logs.extreme( ).error( errors, errors ); } else { throw errors; } } } }, START { @Override public void fire( final ServiceConfiguration parent ) throws Exception { StartServiceResponseType msg = ServiceTransitions.sendEmpyreanRequest( parent, new StartServiceType( ) { { this.getServices( ).add( TypeMappers.transform( parent, ServiceId.class ) ); } } ); try { ServiceBuilders.lookup( parent.getComponentId( ) ).fireStart( parent ); } catch ( Exception ex ) { LOG.error( ex, ex ); } } }, ENABLE { @Override public void fire( final ServiceConfiguration parent ) throws Exception { EnableServiceResponseType msg = ServiceTransitions.sendEmpyreanRequest( parent, new EnableServiceType( ) { { this.getServices( ).add( TypeMappers.transform( parent, ServiceId.class ) ); } } ); try { ServiceBuilders.lookup( parent.getComponentId( ) ).fireEnable( parent ); } catch ( Exception ex ) { LOG.error( ex, ex ); } } }, DISABLE { @Override public void fire( final ServiceConfiguration parent ) throws Exception { DisableServiceResponseType msg = ServiceTransitions.sendEmpyreanRequest( parent, new DisableServiceType( ) { { this.getServices( ).add( TypeMappers.transform( parent, ServiceId.class ) ); } } ); try { ServiceBuilders.lookup( parent.getComponentId( ) ).fireDisable( parent ); } catch ( Exception ex ) { LOG.error( ex, ex ); } } }, STOP { @Override public void fire( final ServiceConfiguration parent ) throws Exception { StopServiceResponseType msg = ServiceTransitions.sendEmpyreanRequest( parent, new StopServiceType( ) { { this.getServices( ).add( TypeMappers.transform( parent, ServiceId.class ) ); } } ); try { ServiceBuilders.lookup( parent.getComponentId( ) ).fireStop( parent ); } catch ( Exception ex ) { LOG.error( ex, ex ); } } }; private static LoadingCache<TransitionActions, ServiceTransitionCallback> map = CacheBuilder.newBuilder().build( new CacheLoader<TransitionActions, ServiceTransitionCallback>( ) { @Override public ServiceTransitionCallback load( TransitionActions input ) { return valueOf( input.name( ) ); } }); public static ServiceTransitionCallback map( TransitionActions transition ) { return map.getUnchecked( transition ); } } enum ServiceLocalTransitionCallbacks implements ServiceTransitionCallback { LOAD { @Override public void fire( final ServiceConfiguration parent ) throws Exception { parent.lookupBootstrapper( ).load( ); ServiceBuilders.lookup( parent.getComponentId( ) ).fireLoad( parent ); } }, DESTROY { @Override public void fire( final ServiceConfiguration parent ) throws Exception { parent.lookupBootstrapper( ).destroy( ); if ( Bootstrap.isFinished( ) ) { Components.lookup( parent.getComponentId( ) ).destroy( parent ); } } }, CHECK { @Override public void fire( final ServiceConfiguration parent ) throws Exception { if ( parent.isVmLocal( ) && Faults.isFailstop( ) ) { if ( Component.State.ENABLED.apply( parent ) ) { try { DISABLE.fire( parent ); } catch ( Exception ex1 ) { } } throw new IllegalStateException( "Failed to CHECK service " + parent.getFullName( ) + " because the host is currently fail-stopped." ); } else if ( Component.State.ENABLED.apply( parent ) ) { try { parent.lookupBootstrapper( ).check( ); ServiceBuilders.lookup( parent.getComponentId( ) ).fireCheck( parent ); } catch ( Exception ex ) { if ( Exceptions.isCausedBy( ex, CheckException.class ) ) { CheckException checkEx = Exceptions.findCause( ex, CheckException.class ); Faults.failstop( parent, checkEx ); } if ( Faults.filter( parent, ex ) ) { try { DISABLE.fire( parent ); } catch ( Exception ex1 ) { LOG.error( "Failed to call DISABLE on an ENABLED service after CHECK failure: " + parent.getFullName( ) + " due to: " + ex.getMessage( ) + ". With current service info: " + parent ); Logs.extreme( ).error( ex1, ex1 ); } } throw ex; } } else { try { parent.lookupBootstrapper( ).check( ); ServiceBuilders.lookup( parent.getComponentId( ) ).fireCheck( parent ); } catch ( Exception ex ) { if ( Exceptions.isCausedBy( ex, CheckException.class ) ) { CheckException checkEx = Exceptions.findCause( ex, CheckException.class ); Faults.failstop( parent, checkEx ); } throw ex; } } } }, START { @Override public void fire( final ServiceConfiguration parent ) throws Exception { if ( !Bootstrap.isShuttingDown( ) ) { parent.lookupBootstrapper( ).start( ); ServiceBuilders.lookup( parent.getComponentId( ) ).fireStart( parent ); } } }, ENABLE { @Override public void fire( final ServiceConfiguration parent ) throws Exception { if ( parent.isVmLocal( ) && Faults.isFailstop( ) ) { throw new IllegalStateException( "Failed to ENABLE service " + parent.getFullName( ) + " because the host is currently fail-stopped." ); } else { parent.lookupBootstrapper( ).enable( ); try { ServiceBuilders.lookup( parent.getComponentId( ) ).fireEnable( parent ); } catch ( Exception ex ) { try { parent.lookupBootstrapper( ).disable( ); } catch ( Exception ex1 ) { } throw ex; } } } }, DISABLE { @Override public void fire( final ServiceConfiguration parent ) throws Exception { try {//GRZE: disable transition must always succeed to avoid ambigious/duplicate invocation from in( State.NOTREADY ) parent.lookupBootstrapper( ).disable( ); ServiceBuilders.lookup( parent.getComponentId( ) ).fireDisable( parent ); } catch ( Exception ex ) { LOG.error( ex, ex ); } } }, STOP { @Override public void fire( final ServiceConfiguration parent ) throws Exception { parent.lookupBootstrapper( ).stop( ); ServiceBuilders.lookup( parent.getComponentId( ) ).fireStop( parent ); } }; private static LoadingCache<TransitionActions, ServiceTransitionCallback> map = CacheBuilder.newBuilder().build( new CacheLoader<TransitionActions, ServiceTransitionCallback>( ) { @Override public ServiceTransitionCallback load( TransitionActions input ) { return ServiceLocalTransitionCallbacks.valueOf( input.name( ) ); } }); public static ServiceTransitionCallback map( TransitionActions transition ) { return map.getUnchecked( transition ); } } enum ServiceRemoteTransitionNotification implements ServiceTransitionCallback { LOAD { @Override public void fire( final ServiceConfiguration parent ) throws Exception {} }, DESTROY { @Override public void fire( final ServiceConfiguration parent ) throws Exception {} }, CHECK { @Override public void fire( final ServiceConfiguration parent ) throws Exception { try { ServiceBuilders.lookup( parent.getComponentId( ) ).fireCheck( parent ); } catch ( Exception ex ) { Logs.extreme( ).error( ex, ex ); } } }, START { @Override public void fire( final ServiceConfiguration parent ) throws Exception { try { ServiceBuilders.lookup( parent.getComponentId( ) ).fireStart( parent ); } catch ( Exception ex ) { Logs.extreme( ).error( ex, ex ); } } }, ENABLE { @Override public void fire( final ServiceConfiguration parent ) throws Exception { try { ServiceBuilders.lookup( parent.getComponentId( ) ).fireEnable( parent ); } catch ( Exception ex ) { LOG.error( ex, ex ); } } }, DISABLE { @Override public void fire( final ServiceConfiguration parent ) throws Exception { try { ServiceBuilders.lookup( parent.getComponentId( ) ).fireDisable( parent ); } catch ( Exception ex ) { Logs.extreme( ).error( ex, ex ); } } }, STOP { @Override public void fire( final ServiceConfiguration parent ) throws Exception { try { ServiceBuilders.lookup( parent.getComponentId( ) ).fireStop( parent ); } catch ( Exception ex ) { Logs.extreme( ).error( ex, ex ); } } }; private static LoadingCache<TransitionActions, ServiceTransitionCallback> map = CacheBuilder.newBuilder().build( new CacheLoader<TransitionActions, ServiceTransitionCallback>( ) { @Override public ServiceTransitionCallback load( TransitionActions input ) { return valueOf( input.name( ) ); } }); public static ServiceTransitionCallback map( TransitionActions transition ) { return map.getUnchecked( transition ); } } public enum StateCallbacks implements Callback<ServiceConfiguration> {//TODO:GRZE: make these discoverable FIRE_STATE_EVENT { @Override public void fire( final ServiceConfiguration config ) { if ( Hosts.isCoordinator( ) && !config.isVmLocal( ) && config.getComponentId( ).isRegisterable( ) && !( config.getComponentId( ).isAlwaysLocal( ) || config.getComponentId( ).isCloudLocal( ) ) ) { ServiceEvents.fire( config, config.getStateMachine().getState() ); } } }, PROPERTIES_ADD { @Override public void fire( final ServiceConfiguration config ) { if ( Bootstrap.isFinished( ) ) { try { List<ConfigurableProperty> props = PropertyDirectory.getPendingPropertyEntrySet( config.getComponentId( ).name( ) ); for ( ConfigurableProperty prop : props ) { if ( prop instanceof SingletonDatabasePropertyEntry ) { PropertyDirectory.addProperty( prop ); } else if ( prop instanceof MultiDatabasePropertyEntry ) { MultiDatabasePropertyEntry addProp = ( ( MultiDatabasePropertyEntry ) prop ).getClone( config.getPartition( ) ); PropertyDirectory.addProperty( addProp ); } } } catch ( Exception ex ) { LOG.error( ex, ex ); } } } }, STATIC_PROPERTIES_ADD { @Override public void fire( final ServiceConfiguration config ) { if ( Bootstrap.isOperational( ) && !Databases.isVolatile( ) ) { STATIC_PROPERTIES_SUPPLIER.get( ); } } }, PROPERTIES_REMOVE { @Override public void fire( final ServiceConfiguration config ) { try { String prefix = config.getPartition( ) + "." + config.getComponentId( ).name( ); List<ConfigurableProperty> props = PropertyDirectory.getPropertyEntrySet( prefix ); for ( ConfigurableProperty prop : props ) { if ( prop instanceof SingletonDatabasePropertyEntry ) { //GRZE:REVIEW do nothing? } else if ( prop instanceof MultiDatabasePropertyEntry ) { ( ( MultiDatabasePropertyEntry ) prop ).setIdentifierValue( config.getPartition( ) ); PropertyDirectory.removeProperty( prop ); } } } catch ( Exception ex ) { LOG.error( ex, ex ); } } }, ENSURE_DISABLED { @Override public void fire( ServiceConfiguration input ) { if ( State.ENABLED.apply( input ) && Hosts.isServiceLocal( input ) ) { try { LOG.debug( "Ensuring .disable()/.fireDisable() have been called for service entering NOTREADY: " + input.getFullName( ) ); ServiceLocalTransitionCallbacks.DISABLE.fire( input ); } catch ( Exception ex ) { LOG.error( ex, ex ); } } } }; } private enum StaticPropertiesAdd implements Supplier<Void> { INSTANCE; @Override public Void get() { for ( ConfigurableProperty prop : Iterables.filter( PropertyDirectory.getPendingPropertyValues( ), Predicates.instanceOf( StaticPropertyEntry.class ) ) ) { try { PropertyDirectory.addProperty( prop ); try { if (!Databases.isVolatile()) { prop.getValue(); } } catch (Exception ex) { Logs.extreme().error(ex); } } catch ( Exception ex ) { Logs.extreme( ).error( ex, ex ); } } return null; } } }