/*************************************************************************
* Copyright 2009-2016 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.ConnectException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import javax.annotation.Nonnull;
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.configurable.ConfigurableClass;
import com.eucalyptus.configurable.ConfigurableField;
import com.eucalyptus.empyrean.DestroyServiceType;
import com.eucalyptus.empyrean.Empyrean;
import com.eucalyptus.empyrean.ServiceId;
import com.eucalyptus.empyrean.ServiceTransitionType;
import com.eucalyptus.event.ClockTick;
import com.eucalyptus.event.EventListener;
import com.eucalyptus.event.Listeners;
import com.eucalyptus.records.EventRecord;
import com.eucalyptus.records.EventType;
import com.eucalyptus.records.Logs;
import com.eucalyptus.system.Threads;
import com.eucalyptus.util.Callback;
import com.eucalyptus.util.Classes;
import com.eucalyptus.util.Exceptions;
import com.eucalyptus.util.Internets;
import com.eucalyptus.util.LockResource;
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.ExistingTransitionException;
import com.eucalyptus.util.fsm.OrderlyTransitionException;
import com.google.common.base.Function;
import com.google.common.base.Functions;
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.Throwables;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.primitives.Ints;
import edu.ucsb.eucalyptus.msgs.BaseMessage;
import javaslang.collection.Stream;
@ConfigurableClass( root = "bootstrap.topology",
description = "Properties controlling the handling of service topology" )
public class Topology {
private static Logger LOG = Logger.getLogger( Topology.class );
private static volatile Topology singleton; //TODO:GRZE:handle differently for remote case?
private Integer currentEpoch = 0; //TODO:GRZE: get the right initial epoch value from membership bootstrap
@ConfigurableField( description = "Backoff between service state checks (in seconds).", initial = "10" )
public static Integer COORDINATOR_CHECK_BACKOFF_SECS = 10;
@ConfigurableField( description = "Backoff between service state checks (in seconds).", initial = "10" )
public static Integer LOCAL_CHECK_BACKOFF_SECS = 10;
private final ConcurrentMap<ServiceKey, ServiceConfiguration> services = new ConcurrentSkipListMap<Topology.ServiceKey, ServiceConfiguration>( );
private enum Queue implements Function<Callable, Future> {
INTERNAL( 1 ) {
ServiceConfiguration internal;
@Override
ServiceConfiguration queue( ) {
if ( this.internal == null ) {
this.internal = ServiceConfigurations.createEphemeral( Empyrean.INSTANCE, Topology.class.getSimpleName( ), "internal",
ServiceUris.internal( Empyrean.INSTANCE ) );
}
return this.internal;
}
},
EXTERNAL( 256 ) {
ServiceConfiguration external;
@Override
ServiceConfiguration queue( ) {
if ( this.external == null ) {
this.external = ServiceConfigurations.createEphemeral( Empyrean.INSTANCE, Topology.class.getSimpleName( ), "external",
ServiceUris.internal( Empyrean.INSTANCE ) );
}
return this.external;
}
};
private final int numWorkers;
private Queue( final int numWorkers ) {
this.numWorkers = numWorkers;
}
abstract ServiceConfiguration queue( );
@Override
public Future apply( final Callable call ) {
if ( Logs.extreme( ).isDebugEnabled( ) ) {
Logs.extreme( ).debug( Topology.class.getSimpleName( ) + ": queueing " + call.toString( ) );
Logs.extreme( ).debug( Threads.currentStackRange( 3, 9 ) );
}
return Threads.enqueue( this.queue( ), this.numWorkers, call );
}
@SuppressWarnings( "unchecked" )
public <C> Future<C> enqueue( final Callable<C> call ) {
return this.apply( call );
}
}
public static <T> T doTopologyWork( final Callable<T> callable ) throws Exception {
return TopologyTimer.INSTANCE.doWork( callable );
}
private enum TopologyTimer implements EventListener<ClockTick> {
INSTANCE;
private static final AtomicInteger counter = new AtomicInteger( 0 );
private static final AtomicBoolean busy = new AtomicBoolean( false );
private static final ReadWriteLock lock = new ReentrantReadWriteLock( );
private static final AtomicReference<Set<String>> enabledClassNames =
new AtomicReference<>( Collections.<String>emptySet( ) );
@Override
public void fireEvent( final ClockTick event ) {
final int backoff = Hosts.isCoordinator( ) ? COORDINATOR_CHECK_BACKOFF_SECS : LOCAL_CHECK_BACKOFF_SECS;
Callable<Object> call = new Callable<Object>( ) {
@Override
public Object call( ) {
try {
TimeUnit.SECONDS.sleep( backoff );
} catch ( InterruptedException ex ) {
busy.set( false );
return Collections.EMPTY_LIST;
}
try {
return RunChecks.INSTANCE.call( );
} finally {
busy.set( false );
}
}
};
if ( busy.compareAndSet( false, true ) ) {
final Set<String> enabledComponentClassNames = ImmutableSet.copyOf( Iterables.transform(
Topology.getInstance( ).services.values( ),
Functions.compose( Classes.nameFunction( ), ServiceConfigurations.componentId( ) ) ) );
if ( ( Hosts.isCoordinator( ) || counter.incrementAndGet( ) % 3 == 0 ||
!enabledClassNames.get( ).containsAll( enabledComponentClassNames ) ) && lock.writeLock( ).tryLock( ) ) {
// Write lock acquisition is to ensure no other tasks in progress
// we don't want to block others from running
lock.writeLock( ).unlock( );
enabledClassNames.set( enabledComponentClassNames );
try {
Queue.INTERNAL.enqueue( call );
} catch ( Exception ex ) {
busy.set( false );
}
} else {
busy.set( false );
}
}
}
public <T> T doWork( final Callable<T> callable ) throws Exception {
try ( final LockResource lockResource = LockResource.lock( lock.readLock( ) ) ) {
return callable.call( );
}
}
}
private Topology( final int i ) {
this.currentEpoch = i;
Listeners.register( ClockTick.class, TopologyTimer.INSTANCE );
}
private static Predicate<ServiceConfiguration> componentFilter( final Class<? extends ComponentId> c ) {
return new Predicate<ServiceConfiguration>( ) {
@Override
public boolean apply( final ServiceConfiguration input ) {
return input.getComponentId( ).getClass( ).equals( c ) || input.getComponentId( ).hasApi( c );
}
};
}
public static void populateServices( final ServiceConfiguration config, BaseMessage msg ) {
try {
Predicate<ServiceConfiguration> filter = new Predicate<ServiceConfiguration>( ) {
@Override
public boolean apply( final ServiceConfiguration filterConfig ) {
ComponentId filteredComponent = filterConfig.getComponentId( );
ComponentId destComponent = config.getComponentId( );
if ( filteredComponent.isDistributedService( ) ) {
if ( destComponent.isAlwaysLocal( ) ) {
return filterConfig.lookupState( ).ordinal( ) >= Component.State.STOPPED.ordinal( );
} else if ( destComponent.isPartitioned( ) && filteredComponent.isPartitioned( ) ) {
return config.getPartition( ).equals( filterConfig.getPartition( ) );
} else {
return true;
}
} else {
return false;
}
}
};
Function<ServiceConfiguration, ServiceId> typeMapper = TypeMappers.lookup( ServiceConfiguration.class, ServiceId.class );
if ( Hosts.isCoordinator( ) ) {
msg.set_epoch( Topology.epoch( ) );
for ( ServiceConfiguration s : Topology.getInstance( ).getServices( ).values( ) ) {
if ( filter.apply( s ) ) {
msg.get_services( ).add( typeMapper.apply( s ) );
}
}
for ( Component c : Components.list( ) ) {
for ( ServiceConfiguration s : c.services( ) ) {
if ( filter.apply( s ) && !msg.get_services( ).contains( s ) ) {
if ( State.DISABLED.apply( s ) ) {
msg.get_disabledServices().add( typeMapper.apply( s ) );
} else if ( State.STOPPED.apply( s ) ) {
msg.get_stoppedServices( ).add( typeMapper.apply( s ) );
} else if ( State.NOTREADY.ordinal( ) >= s.getStateMachine( ).getState( ).ordinal( ) ) {
msg.get_notreadyServices( ).add( typeMapper.apply( s ) );
} else if (State.ENABLED.apply(s) && c.getComponentId().isManyToOnePartition() && c.getComponentId().isDistributedService()) {
//Add many-to-one distributed services that are enabled.
msg.get_services().add(typeMapper.apply(s));
}
}
}
}
}
} catch ( Exception ex ) {
Logs.extreme( ).error( ex, ex );
}
}
public static void touch( final ServiceTransitionType msg ) {//TODO:GRZE: @Service interceptor
if ( !Hosts.isCoordinator( ) && msg.get_epoch( ) != null ) {
update( Iterables.concat(
msg.get_services(),
msg.get_disabledServices( ),
msg.get_notreadyServices( ),
msg.get_stoppedServices( ) ) );
performTransitionsById( msg.get_services( ), transition( State.ENABLED ) );
extractResults( performTransitionsById( msg.get_disabledServices( ), transition( State.DISABLED ) ) );
extractResults( performTransitions(
extractResults( performTransitionsById( msg.get_notreadyServices( ), transition( State.DISABLED ) ) ),
transition( State.NOTREADY ) ) );
extractResults( performTransitionsById( msg.get_stoppedServices( ), transition( State.STOPPED ) ) );
Topology.getInstance( ).currentEpoch = Ints.max( Topology.getInstance( ).currentEpoch, msg.get_epoch( ) );
}
}
private static Iterable<ServiceId> update( final Iterable<ServiceId> iterable ) {
return Iterables.transform( iterable, UpdateServiceConfiguration.INSTANCE );
}
private static List<Future<ServiceConfiguration>> performTransitionsById(
final Iterable<ServiceId> services,
final Function<ServiceConfiguration,Future<ServiceConfiguration>> transition
) {
return performTransitions(
Iterables.transform( services, ServiceConfigurations.ServiceIdToServiceConfiguration.INSTANCE ),
transition );
}
private static List<Future<ServiceConfiguration>> performTransitions(
final Iterable<ServiceConfiguration> serviceConfigurations,
final Function<ServiceConfiguration,Future<ServiceConfiguration>> transition
) {
final List<Future<ServiceConfiguration>> futures = Lists.newArrayList( );
for ( final ServiceConfiguration serviceConfiguration : serviceConfigurations ) {
if ( !serviceConfiguration.isVmLocal( ) ) {
futures.add( transition.apply( serviceConfiguration ) );
}
}
return futures;
}
private static List<ServiceConfiguration> extractResults(
final List<Future<ServiceConfiguration>> futures
) {
final List<ServiceConfiguration> results = Lists.newArrayList( );
for( final Future<ServiceConfiguration> future : futures ) {
try {
results.add( future.get( ) );
} catch ( InterruptedException ex ) {
Exceptions.maybeInterrupted( ex );
} catch ( ExecutionException ex ) {
Logs.extreme( ).error( ex, ex );
}
}
return results;
}
public static boolean check( final BaseMessage msg ) {
if ( !Hosts.isCoordinator( ) && ( msg.get_epoch( ) != null ) ) {
return Topology.epoch( ) <= msg.get_epoch( );
} else {
return true;
}
}
private static Topology getInstance( ) {
if (singleton == null) {
synchronized (Topology.class) {
if (singleton == null) {
singleton = new Topology( Hosts.maxEpoch( ) );
}
}
}
return singleton;
}
private Integer getEpoch( ) {
return this.currentEpoch;
}
public static int epoch( ) {
return Topology.getInstance( ).getEpoch( );
}
public static Function<ServiceConfiguration, Future<ServiceConfiguration>> transition( final Component.State toState ) {
final Function<ServiceConfiguration, Future<ServiceConfiguration>> transition = new Function<ServiceConfiguration, Future<ServiceConfiguration>>( ) {
private final List<Component.State> serializedStates = Lists.newArrayList( Component.State.ENABLED, Component.State.STOPPED );
@Override
public Future<ServiceConfiguration> apply( final ServiceConfiguration input ) {
final Callable<ServiceConfiguration> call = Topology.callable( input, Topology.get( toState ) );
if ( input.getComponentId().isManyToOnePartition() ) {//GRZE:ORLY: yes really. if this doesn't work the whole scheme is gebroken.
return Queue.EXTERNAL.enqueue( call );
} else if ( this.serializedStates.contains( toState ) || this.serializedStates.contains( input.lookupState( ) ) ) {
return Threads.enqueue( input, Topology.class, 1, call );
} else {
return Queue.EXTERNAL.enqueue( call );
}
}
};
return transition;
}
private static Future<ServiceConfiguration> check( final ServiceConfiguration config ) {
return Queue.EXTERNAL.enqueue( Topology.callable( config, Topology.check( ) ) );
}
public static Future<ServiceConfiguration> stop( final ServiceConfiguration config ) {
return transition( State.STOPPED ).apply( config );
}
public static Future<ServiceConfiguration> destroy( final ServiceConfiguration config ) {
try {
ServiceConfigurations.remove( config );
} catch ( Exception ex ) {
LOG.error( ex );
Logs.extreme( ).debug( ex, ex );
}
try {
Topology.disable( config ).get( );
} catch ( Exception ex ) {
Exceptions.maybeInterrupted( ex );
LOG.error( ex );
Logs.extreme( ).debug( ex, ex );
}
try {
Topology.stop( config ).get( );
} catch ( Exception ex ) {
Exceptions.maybeInterrupted( ex );
LOG.error( ex );
Logs.extreme( ).debug( ex, ex );
}
try {
Queue.INTERNAL.enqueue( new Callable<Boolean>(){
@Override
public Boolean call() throws Exception {
try {
Component comp = Components.lookup( config.getComponentId( ) );
comp.destroy( config );
} catch ( Exception ex ) {
Exceptions.maybeInterrupted( ex );
LOG.error( ex );
Logs.extreme( ).debug( ex, ex );
}
return true;
}
} ).get();
} catch ( Exception ex ) {
Exceptions.maybeInterrupted( ex );
LOG.error( ex );
Logs.extreme( ).debug( ex, ex );
}
if ( Hosts.isCoordinator( ) ) {
try {
final ServiceId service = TypeMappers.transform( config, ServiceId.class );
final List<CheckedListenableFuture<?>> futures = Lists.newArrayList( );
for ( Host h : Hosts.list( ) ) {
if ( !h.isLocalHost( ) && h.hasBootstrapped( ) ) {
try {
DestroyServiceType msg = new DestroyServiceType( );
msg.getServices( ).add( service );
futures.add( AsyncRequests.dispatch( ServiceConfigurations.createEphemeral( Empyrean.INSTANCE, h.getBindAddress() ), msg ) );
} catch ( Exception ex ) {
Exceptions.maybeInterrupted( ex );
LOG.error( ex );
Logs.extreme( ).debug( ex, ex );
}
}
}
for ( CheckedListenableFuture<?> future : futures ) {
try {
future.get( );
} catch ( Exception ex ) {
Exceptions.maybeInterrupted( ex );
LOG.error( ex );
Logs.extreme( ).debug( ex, ex );
}
}
} catch ( Exception ex ) {
LOG.error( ex );
Logs.extreme( ).debug( ex, ex );
}
}
try {
((Callback<ServiceConfiguration>)ServiceTransitions.StateCallbacks.PROPERTIES_REMOVE).fire( config );
} catch ( Exception ex ) {
Exceptions.maybeInterrupted( ex );
LOG.error( ex );
Logs.extreme( ).debug( ex, ex );
}
return Futures.predestinedFuture( config );
}
public static Future<ServiceConfiguration> load( final ServiceConfiguration config ) {
return transition( State.LOADED ).apply( config );
}
public static Future<ServiceConfiguration> start( final ServiceConfiguration config ) {
return transition( State.DISABLED ).apply( config );
}
public static Future<ServiceConfiguration> enable( final ServiceConfiguration config ) {
return transition( State.ENABLED ).apply( config );
}
public static Future<ServiceConfiguration> disable( final ServiceConfiguration config ) {
return transition( State.DISABLED ).apply( config );
}
private ServiceConfiguration lookup( final ServiceKey serviceKey ) {
return this.getServices().get( serviceKey );
}
private interface TransitionGuard {
boolean tryEnable( final ServiceConfiguration config );
boolean nextEpoch( );
boolean tryDisable( final ServiceConfiguration config );
}
private TransitionGuard cloudControllerGuard( ) {
return new TransitionGuard( ) {
@Override
public boolean nextEpoch( ) {
Topology.this.currentEpoch++;
return true;
}
@Override
public boolean tryEnable( final ServiceConfiguration config ) {
final ServiceKey serviceKey = ServiceKey.create( config );
final ServiceConfiguration curr = Topology.this.getServices( ).putIfAbsent( serviceKey, config );
LOG.trace( "tryEnable():before " + Topology.this.toString( ) + " => " + config );
if ( ( curr != null ) && !curr.equals( config ) ) {
LOG.trace( "tryEnable():false " + Topology.this.toString( ) + " => " + config );
return false;
} else if ( ( curr != null ) && curr.equals( config ) ) {
LOG.trace( "tryEnable():true " + Topology.this.toString( ) + " => " + config );
return true;
} else {
Topology.this.currentEpoch++;
LOG.trace( "tryEnable():true " + Topology.this.toString( ) + " => " + config );
return true;
}
}
@Override
public boolean tryDisable( final ServiceConfiguration config ) {
final ServiceKey serviceKey = ServiceKey.create( config );
boolean tryDisable = !config.equals( Topology.this.getServices( ).get( serviceKey ) )
|| ( Topology.this.getServices( ).remove( serviceKey, config ) && this.nextEpoch( ) );
LOG.trace( "tryDisable():" + tryDisable + " " + Topology.this.toString( ) + " => " + config );
return tryDisable;
}
};
}
private TransitionGuard remoteGuard( ) {
return new TransitionGuard( ) {
@Override
public boolean nextEpoch( ) {
return true;
}
@Override
public boolean tryEnable( final ServiceConfiguration config ) {
final ServiceKey serviceKey = ServiceKey.create( config );
LOG.trace( "tryEnable():before " + Topology.this.toString( ) + " => " + config );
final ServiceConfiguration curr = Topology.this.getServices( ).put( serviceKey, config );
Logs.extreme( ).info( "Current ENABLED: " + curr );
if ( ( curr != null ) && !curr.equals( config ) ) {
transition( State.DISABLED ).apply( curr );
LOG.trace( "tryEnable():false " + Topology.this.toString( ) + " => " + config );
return false;
} else if ( ( curr != null ) && curr.equals( config ) ) {
LOG.trace( "tryEnable():true " + Topology.this.toString( ) + " => " + config );
return true;
} else {
LOG.trace( "tryEnable():true " + Topology.this.toString( ) + " => " + config );
return true;
}
}
@Override
public boolean tryDisable( final ServiceConfiguration config ) {
final ServiceKey serviceKey = ServiceKey.create( config );
LOG.trace( "tryDisable():true " + Topology.this.toString( ) + " => " + config );
return ( Topology.this.getServices( ).remove( serviceKey, config ) || !config.equals( Topology.this.getServices( ).get( serviceKey ) ) )
&& this.nextEpoch( );
}
};
}
/**
* A wrapper around a tuple of ComponentId + Partition. ServiceKey identifies
* a specific component in a specific partition.
*
*/
public static class ServiceKey implements Comparable<ServiceKey> {
private final Partition partition;
private final ComponentId componentId;
static ServiceKey create( final ComponentId compId, final Partition partition ) {
return new ServiceKey( compId, partition );
}
static ServiceKey create( final ServiceConfiguration config ) {
if ( config.getComponentId( ).isPartitioned( ) && !Empyrean.class.equals( config.getComponentId( ).partitionParent( ).getClass() ) ) {
final Partition p = Partitions.lookup( config );
return new ServiceKey( config.getComponentId( ), p );
} else if ( config.getComponentId( ).isAlwaysLocal( ) || ( config.getComponentId( ).isCloudLocal( ) && !config.getComponentId( ).isDistributedService( ) ) ) {
final Partition p = Partitions.lookupInternal( config );
return new ServiceKey( config.getComponentId( ), p );
} else {
return new ServiceKey( config.getComponentId( ) );
}
}
ServiceKey( final ComponentId componentId ) {
this( componentId, null );
}
ServiceKey( final ComponentId componentId, final Partition partition ) {
super( );
this.partition = partition;
this.componentId = componentId;
}
public Partition getPartition( ) {
return this.partition;
}
@Override
public String toString( ) {
final StringBuilder builder = new StringBuilder( );
builder.append( "ServiceKey " ).append( this.componentId.name( ) ).append( ":" );
if ( this.partition == null ) {
builder.append( "cloud-global-service" );
} else {
builder.append( "partition=" ).append( this.partition );
}
return builder.toString( );
}
public ComponentId getComponentId( ) {
return this.componentId;
}
@Override
public int hashCode( ) {
final int prime = 31;
int result = 1;
result = prime * result
+ ( ( this.componentId == null )
? 0
: this.componentId.hashCode( ) );
result = prime * result
+ ( ( this.partition == null )
? 0
: this.partition.hashCode( ) );
return result;
}
@Override
public boolean equals( final Object obj ) {
if ( this == obj ) {
return true;
}
if ( obj == null ) {
return false;
}
if ( this.getClass( ) != obj.getClass( ) ) {
return false;
}
final ServiceKey other = ( ServiceKey ) obj;
if ( this.componentId == null ) {
if ( other.componentId != null ) {
return false;
}
} else if ( !this.componentId.equals( other.componentId ) ) {
return false;
}
if ( this.partition == null ) {
if ( other.partition != null ) {
return false;
}
} else if ( !this.partition.equals( other.partition ) ) {
return false;
}
return true;
}
@Override
public int compareTo( final ServiceKey that ) {
if ( this.componentId.equals( that.componentId ) ) {
if ( ( this.partition == null ) && ( that.partition == null ) ) {
return 0;
} else if ( this.partition != null ) {
return this.partition.compareTo( that.partition );
} else {
return -1;
}
} else {
return this.componentId.compareTo( that.componentId );
}
}
}
protected static TransitionGuard guard( ) {
return getInstance( ).getGuard( );
}
public TransitionGuard getGuard( ) {
return ( Hosts.isCoordinator( )
? this.cloudControllerGuard( )
: this.remoteGuard( ) );
}
private ConcurrentMap<ServiceKey, ServiceConfiguration> getServices( ) {
return this.services;
}
enum ProceedToDisabledServiceFilter implements Predicate<ServiceConfiguration> {
INSTANCE;
@Override
public boolean apply( final ServiceConfiguration arg0 ) {
return arg0.lookupState( ).ordinal( ) < Component.State.DISABLED.ordinal( ) && !Component.State.STOPPED.apply( arg0 );
}
}
enum AlwaysLocalServiceFilter implements Predicate<ServiceConfiguration> {
INSTANCE;
@Override
public boolean apply( final ServiceConfiguration arg0 ) {
return arg0.isVmLocal( )
&& arg0.getComponentId( ).isAlwaysLocal( )
&& arg0.lookupState( ).ordinal( ) < Component.State.ENABLED.ordinal( )
&& !Component.State.STOPPED.apply( arg0 );
}
}
enum CheckServiceFilter implements Predicate<ServiceConfiguration> {
INSTANCE;
@Override
public boolean apply( final ServiceConfiguration arg0 ) {
if ( Hosts.isCoordinator( ) && arg0.getComponentId( ).isDistributedService( ) ) {
return true;
} else if ( arg0.isHostLocal( ) && BootstrapArgs.isCloudController( ) ) {
return true;
} else {
return arg0.isVmLocal( );
}
}
}
enum SubmitEnable implements Function<ServiceConfiguration, Future<ServiceConfiguration>> {
INSTANCE;
@Override
public Future<ServiceConfiguration> apply( final ServiceConfiguration input ) {
return transition( State.ENABLED ).apply( input );
}
@Override
public String toString( ) {
return "ENABLED";
}
}
enum SubmitDisable implements Function<ServiceConfiguration, Future<ServiceConfiguration>> {
INSTANCE;
@Override
public Future<ServiceConfiguration> apply( final ServiceConfiguration input ) {
return transition( State.DISABLED ).apply( input );
}
@Override
public String toString( ) {
return "DISABLED";
}
}
enum SubmitCheck implements Function<ServiceConfiguration, Future<ServiceConfiguration>> {
INSTANCE;
@Override
public Future<ServiceConfiguration> apply( final ServiceConfiguration input ) {
final Callable<ServiceConfiguration> call = Topology.callable( input, Topology.check( ) );
final Future<ServiceConfiguration> future = Queue.EXTERNAL.enqueue( call );
return future;
}
@Override
public String toString( ) {
return "CHECKED";
}
}
enum WaitForResults implements Predicate<Future> {
INSTANCE;
@Override
public boolean apply( final Future input ) {
try {
final Object conf = input.get( 120, TimeUnit.SECONDS );
return true;
} catch ( final InterruptedException ex ) {
Thread.currentThread( ).interrupt( );
} catch ( final Exception ex ) {
Logs.extreme( ).trace( ex, ex );
}
return false;
}
}
enum ServiceString implements Function<ServiceConfiguration, String> {
INSTANCE;
@Override
public String apply( final ServiceConfiguration input ) {
return input.getFullName( ) + ":" + input.lookupState( );
}
}
enum ExtractFuture implements Function<Future<ServiceConfiguration>, ServiceConfiguration> {
INSTANCE;
@Override
public ServiceConfiguration apply( final Future<ServiceConfiguration> input ) {
try {
return input.get( );
} catch ( final InterruptedException ex ) {
Thread.currentThread( ).interrupt( );
} catch ( final Exception ex ) {
Logs.extreme( ).trace( ex, ex );
}
return null;
}
}
enum RunChecks implements Callable<List<ServiceConfiguration>> {
INSTANCE;
@Override
public List<ServiceConfiguration> call( ) {
if ( Databases.isVolatile( ) ) {
return Lists.newArrayList( );
}
/** submit describe operations **/
final List<ServiceConfiguration> allServices = Lists.newArrayList( );
for ( final Component c : Components.list( ) ) {
allServices.addAll( c.services( ) );
}
Faults.flush( );
List<ServiceConfiguration> checkedServices = submitTransitions( allServices, CheckServiceFilter.INSTANCE, SubmitCheck.INSTANCE );
if ( !checkedServices.isEmpty( ) ) {
Logs.extreme( ).debug( "CHECKED" + ": " + Joiner.on( "\n" + "CHECKED" + ": " ).join( Collections2.transform( checkedServices, ServiceString.INSTANCE ) ) );
}
if ( Faults.isFailstop( ) ) {
Hosts.failstop( );
submitTransitions( allServices, CheckServiceFilter.INSTANCE, SubmitCheck.INSTANCE );
return Lists.newArrayList( );
} else if ( !Hosts.isCoordinator( ) ) {
final Predicate<ServiceConfiguration> proceedToDisableFilter = Predicates.and( ServiceConfigurations.filterHostLocal( ),
ProceedToDisabledServiceFilter.INSTANCE );
submitTransitions( allServices, proceedToDisableFilter, SubmitDisable.INSTANCE );
submitTransitions( allServices, AlwaysLocalServiceFilter.INSTANCE, SubmitEnable.INSTANCE );
/** TODO:GRZE: check and disable timeout here **/
return checkedServices;
} else {
/** make promotion decisions **/
Predicate<ServiceConfiguration> alwaysTrue = Predicates.alwaysTrue( );
Collections.shuffle( allServices );
Collection<ServiceConfiguration> doPass1 = Lists.newArrayList( Iterables.filter( allServices, Predicates.and( CheckServiceFilter.INSTANCE, Component.State.NOTREADY ) ) );
Collection<ServiceConfiguration> disabledPass1 = submitTransitions( Lists.newArrayList( doPass1 ), alwaysTrue, SubmitDisable.INSTANCE );
List<ServiceConfiguration> doPass2 = Lists.newArrayList( doPass1 );
submitTransitions( doPass2, Predicates.not( Predicates.in( disabledPass1 ) ), SubmitDisable.INSTANCE );
final Predicate<ServiceConfiguration> canPromote = Predicates.and( Predicates.not( Predicates.in( doPass1 ) ), Component.State.DISABLED, FailoverPredicate.INSTANCE );
List<ServiceConfiguration> result = submitTransitions( allServices, canPromote, SubmitEnable.INSTANCE );
/** advance other components as needed **/
final Predicate<ServiceConfiguration> proceedToDisableFilter = Predicates.and( Predicates.not( Predicates.in( result ) ),
ProceedToDisabledServiceFilter.INSTANCE );
submitTransitions( allServices, proceedToDisableFilter, SubmitDisable.INSTANCE );
return result;
}
}
private static List<ServiceConfiguration> submitTransitions( final List<ServiceConfiguration> services,
final Predicate<ServiceConfiguration> serviceFilter,
final Function<ServiceConfiguration, Future<ServiceConfiguration>> submitFunction ) {
final Iterable<ServiceConfiguration> filteredServices = Iterables.filter( services, serviceFilter );
final Iterable<Future<ServiceConfiguration>> submittedCallables = Iterables.transform( filteredServices, submitFunction );
final Iterable<Future<ServiceConfiguration>> completedServices = Iterables.filter( submittedCallables, WaitForResults.INSTANCE );
final List<ServiceConfiguration> results = Lists.newArrayList( Iterables.transform( completedServices, ExtractFuture.INSTANCE ) );
printCheckInfo( submitFunction.toString( ), results );
return results;
}
private static void printCheckInfo( final String action, final Collection<ServiceConfiguration> result ) {
if ( !result.isEmpty( ) ) {
Logs.extreme( ).debug( action + ": " + Joiner.on( "\n" + action + ": " ).join( Collections2.transform( result, ServiceString.INSTANCE ) ) );
}
}
}
enum FailoverPredicate implements Predicate<ServiceConfiguration> {
INSTANCE;
@Override
public boolean apply( final ServiceConfiguration arg0 ) {
final ServiceKey key = ServiceKey.create( arg0 );
if ( !Hosts.isCoordinator() ) {
Logs.extreme( ).debug( "FAILOVER-REJECT: " + Internets.localHostInetAddress( )
+ ": not cloud controller, ignoring promotion for: "
+ arg0.getFullName() );
return false;
} else if ( !arg0.isHostLocal( ) && !Hosts.contains( arg0.getHostName( ) ) ) {
Logs.extreme( ).debug( "FAILOVER-REJECT: " + arg0.getFullName( )
+ ": host for the service is not available: " + arg0.getHostName( ) );
return false;
} else if ( !arg0.isHostLocal( ) && Hosts.contains( arg0.getHostName( ) ) && !Hosts.lookup( arg0.getHostName( ) ).hasBootstrapped( ) ) {
Logs.extreme( ).debug( "FAILOVER-REJECT: " + arg0.getFullName( )
+ ": host for the service has not yet bootstrapped: " + arg0.getHostName( ) );
return false;
} else if ( Topology.getInstance( ).getServices( ).containsKey( key ) && arg0.equals( Topology.getInstance( ).getServices( ).get( key ) ) ) {
Logs.extreme( ).debug( "FAILOVER-REJECT: " + arg0.getFullName( )
+ ": service is already ENABLED." );
return false;
} else if ( !Topology.getInstance( ).getServices( ).containsKey( key ) ) {
Logs.extreme( ).debug( "FAILOVER-ACCEPT: " + arg0.getFullName( )
+ ": service for partition: "
+ key );
return true;
} else {
Logs.extreme( ).debug( "FAILOVER-ACCEPT: " + arg0 );
return true;
}
}
}
enum UpdateServiceConfiguration implements Function<ServiceId,ServiceId> {
INSTANCE;
@Override
public ServiceId apply( final ServiceId serviceId ) {
try {
final ServiceConfiguration configuration = Components.lookup( serviceId.getType() ).lookup( serviceId.getName() );
final URI uri = new URI( serviceId.getUri( ) );
ServiceConfigurations.update( configuration, uri.getHost( ), uri.getPort( ) );
} catch ( NoSuchElementException | URISyntaxException e ) {
// update not possible
}
return serviceId;
}
}
/**
* Returns enabled ServiceConfiguration for given component and partition. If partition is manyToOne returns the first found.
* @param compClass
* @param maybePartition
* @return
*/
@Nonnull
public static ServiceConfiguration lookup( final Class<? extends ComponentId> compClass, final Partition... maybePartition ) {
final ComponentId requestedCompId = ComponentIds.lookup( compClass );
final Partition partition = requestedCompId.isPartitioned( ) && maybePartition != null && maybePartition.length > 0 ?
maybePartition[ 0 ] :
null;
ServiceConfiguration res = null;
final Set<ComponentId> serviceComponentIds = Sets.newHashSet( );
if ( requestedCompId.isApi( ) ) { // resolve to impl component
serviceComponentIds.addAll( Stream.ofAll( ComponentIds.list( ) ).filter( comp -> comp.hasApi( compClass ) ).toJavaList( ) );
} else {
serviceComponentIds.add( requestedCompId );
}
//ManyToOne partitions are handled differently
for ( final ComponentId compId : serviceComponentIds ) {
if ( compId.isManyToOnePartition( ) ) {
if ( partition != null ) {
res = Components.services( compId )
.filter( ServiceConfigurations.filterEnabledByPartition( partition ) )
.getOrElse( (ServiceConfiguration) null );
} else {
final Iterable<ServiceConfiguration> configurations =
Components.services( compId ).filter( ServiceConfigurations.filterEnabled( ) );
res = Iterables.tryFind( configurations, ServiceConfigurations.filterHostLocal( ) )
.or( Iterables.tryFind( configurations, Predicates.alwaysTrue( ) ) )
.orNull( );
}
} else {
res = Topology.getInstance( ).getServices( ).get( ServiceKey.create( compId, partition ) );
if ( res == null && !compId.equals( compId.partitionParent( ) ) && !compId.isAlwaysLocal( ) ) {
try {
ServiceConfiguration parent = Topology.getInstance( ).getServices( ).get( ServiceKey.create( compId.partitionParent( ), null ) );
Partition fakePartition = Partitions.lookupInternal( ServiceConfigurations.createEphemeral( compId, parent.getInetAddress( ) ) );
res = Topology.getInstance( ).getServices( ).get( ServiceKey.create( compId, fakePartition ) );
} catch ( RuntimeException e ) {//these may throw runtime exceptions and the only thing that should propage out of lookup ever is NoSuchElementException
res = null;
}
} else if ( res == null && ( compId.isAlwaysLocal( ) ||
( BootstrapArgs.isCloudController( ) && compId.isCloudLocal( ) && !compId.isRegisterable( ) ) ) ) {
res = Topology.getInstance( ).getServices( ).get( ServiceKey.create( ServiceConfigurations.createEphemeral( compId ) ) );
}
}
if ( res != null ) break;
}
String err = "Failed to lookup ENABLED service of type " + compClass.getSimpleName( ) + ( partition != null ? " in partition " + partition : "." );
if ( res == null ) {
throw new NoSuchElementException( err );
} else if ( !Component.State.ENABLED.apply( res ) ) {
throw new NoSuchElementException( err + " Service is currently ENABLING." );
} else {
return res;
}
}
@Nonnull
public static Iterable<ServiceConfiguration> lookupMany(
final Class<? extends ComponentId> compClass,
final Partition... maybePartition
) {
final ComponentId compId = ComponentIds.lookup( compClass );
final Partition partition = compId.isPartitioned( ) && maybePartition != null && maybePartition.length > 0 ?
maybePartition[ 0 ] :
null;
final Iterable<ServiceConfiguration> res;
if ( compId.isManyToOnePartition( ) ) {
res = Components.services( compId ).filter(
partition != null ?
ServiceConfigurations.filterEnabledByPartition( partition ) :
ServiceConfigurations.filterEnabled( ) );
} else {
res = Collections.singleton( lookup( compClass, partition ) );
}
String err = "Failed to lookup ENABLED service of type " + compClass.getSimpleName( ) +
( partition != null ? " in partition " + partition : "." );
if ( res == null ) {
throw new NoSuchElementException( err );
} else {
return res;
}
}
@Nonnull
public static Iterable<ServiceConfiguration> lookupAtLeastOne(
final Class<? extends ComponentId> compClass,
final Partition... maybePartition
) {
final ComponentId compId = ComponentIds.lookup( compClass );
final Partition partition = compId.isPartitioned( ) && maybePartition != null && maybePartition.length > 0 ?
maybePartition[ 0 ] :
null;
final Iterable<ServiceConfiguration> res = lookupMany( compClass, partition );
if ( Iterables.isEmpty( res ) ) {
throw new NoSuchElementException( "Failed to lookup ENABLED service of type " + compClass.getSimpleName( ) +
( partition != null ? " in partition " + partition : "." ) );
}
return res;
}
public static Collection<ServiceConfiguration> enabledServices( final Class<? extends ComponentId> compId ) {
return Collections2.filter( enabledServices( ), componentFilter( compId ) );
}
/**
* Returns a collection of all enabled services. May contain duplicates if a component is defined
* as manyToOne.
* @return
*/
public static Collection<ServiceConfiguration> enabledServices( ) {
//Union the two sets of services, the result may have duplicates!
Collection<ServiceConfiguration> enabledServices = Lists.newArrayList();
Collection<ServiceConfiguration> activePassiveServices = Topology.getInstance( ).services.values( );
enabledServices.addAll(activePassiveServices);
//Add the manyToOne services that have at least one enabled
for(Component comp : Components.whichAreManyToOneEnabled() ) {
for(ServiceConfiguration serv : Iterables.filter( comp.services( ), State.ENABLED )) {
if( !enabledServices.contains(serv)) {
enabledServices.add(serv);
}
}
}
return enabledServices;
}
public static boolean isEnabledLocally( final Class<? extends ComponentId> compClass ) {
return Iterables.any( Topology.enabledServices( compClass ), ServiceConfigurations.filterHostLocal( ) );
}
public static boolean isEnabled( final Class<? extends ComponentId> compClass ) {
return !Topology.enabledServices( compClass ).isEmpty( );
}
@Override
public String toString( ) {
final StringBuilder builder = new StringBuilder( );
builder.append( "Topology:currentEpoch=" ).append( this.currentEpoch ).append( ":guard=" ).append( Hosts.isCoordinator( )
? "cloud"
: "remote" );
return builder.toString( );
}
//@noformat
private static final
LoadingCache<Component.State, Function<ServiceConfiguration, ServiceConfiguration>>
cloudTransitionCallables = CacheBuilder.newBuilder().build(
new CacheLoader<Component.State, Function<ServiceConfiguration, ServiceConfiguration>>() {
@Override
public Function<ServiceConfiguration, ServiceConfiguration> load( final State input ) {
for ( final Transitions c : Transitions.values( ) ) {
if ( input.equals( c.state ) ) {
return c;
} else if ( input.name( ).startsWith( c.name( ) ) ) {
return c;
}
}
return Transitions.CHECK;
}
});
//@format
private static Function<ServiceConfiguration, ServiceConfiguration> get( final Component.State state ) {
return cloudTransitionCallables.getUnchecked( state );
}
private static Function<ServiceConfiguration, ServiceConfiguration> check( ) {
return Transitions.CHECK;
}
private static Callable<ServiceConfiguration>
callable( final ServiceConfiguration config, final Function<ServiceConfiguration, ServiceConfiguration> function ) {
final Long queueStart = System.currentTimeMillis( );
final Callable<ServiceConfiguration> call = new Callable<ServiceConfiguration>( ) {
@Override
public ServiceConfiguration call( ) throws Exception {
if ( Bootstrap.isShuttingDown( ) ) {
throw Exceptions.toUndeclared( "System is shutting down." );
} else {
final Long serviceStart = System.currentTimeMillis( );
Logs.extreme( ).debug( EventRecord.here( Topology.class, EventType.DEQUEUE, function.toString( ), config.getFullName( ).toString( ),
Long.toString( serviceStart - queueStart ), "ms" ) );
try {
final ServiceConfiguration result = function.apply( config );
Logs.extreme( ).debug( EventRecord.here( Topology.class, EventType.QUEUE, function.toString( ), config.getFullName( ).toString( ),
Long.toString( System.currentTimeMillis( ) - serviceStart ), "ms" ) );
return result;
} catch ( final Exception ex ) {
final Throwable t = Exceptions.unwrapCause( ex );
Logs.extreme( ).error( config.getFullName( ) + " failed to transition because of:\n" + t.getMessage( ) );
Logs.extreme( ).error( t, t );
throw ex;
}
}
}
@Override
public String toString( ) {
return Topology.class.getSimpleName( ) + ":" + config.getFullName( ) + " " + function.toString( );
}
};
return call;
}
private static Callable<ServiceConfiguration> perhapsDisable( final ServiceConfiguration input ) {
return new Callable<ServiceConfiguration>() {
@Override
public ServiceConfiguration call() throws Exception {
if ( !Component.State.ENABLED.equals( input.lookupState( ) ) && Topology.getInstance( ).services.containsValue( input ) ) {
Topology.guard( ).tryDisable( input );
}
return input;
}
};
}
public enum Transitions implements Function<ServiceConfiguration, ServiceConfiguration>, Supplier<Component.State> {
START( Component.State.DISABLED ),
STOP( Component.State.STOPPED ) {
@Override
public ServiceConfiguration apply( ServiceConfiguration input ) {
return super.tc.apply( input );
}
},
INITIALIZE( Component.State.INITIALIZED ),
LOAD( Component.State.LOADED ),
DESTROY( Component.State.PRIMORDIAL ),
ENABLE( Component.State.ENABLED ) {
@Override
public ServiceConfiguration apply( final ServiceConfiguration config ) {
boolean busy = config.lookupStateMachine( ).isBusy( );
boolean tryEnable = false;
boolean manyToOne = config.getComponentId( ).isManyToOnePartition( );
if ( manyToOne ) {
try {
return super.apply( config );
} catch ( final RuntimeException ex ) {
throw ex;
}
} else if ( Topology.guard( ).tryEnable( config ) ) {
try {
ServiceConfiguration res = super.apply( config );
return res;
} catch ( final RuntimeException ex ) {
Topology.guard( ).tryDisable( config );
throw ex;
}
} else if ( !busy ) {
// throw new IllegalStateException( "Failed to ENABLE " + config.getFullName( ) );
try {
return ServiceTransitions.pathTo( config, Component.State.DISABLED ).get( );
} catch ( final InterruptedException ex ) {
Thread.currentThread( ).interrupt( );
throw Exceptions.toUndeclared( ex );
} catch ( final ExecutionException ex ) {
throw Exceptions.toUndeclared( ex );
}
} else {
throw new IllegalStateException( "Failed to ENABLE "
+ config.getFullName( )
+ " since manyToOne="
+ manyToOne
+ " tryEnable="
+ tryEnable
+ " fsm.isBusy()="
+ busy );
}
}
},
DISABLE( Component.State.DISABLED ),
CHECK {
@Override
public ServiceConfiguration apply( final ServiceConfiguration config ) {
if ( !Bootstrap.isFinished( ) ) {
LOG.debug( this.toString( )
+ " aborted because bootstrap is not complete for service: "
+ config );
return config;
} else {
return super.apply( config );
}
}
},
RESTART;
private final Component.State state;
protected final TopologyChange tc;
private Transitions( ) {
this.state = null;
this.tc = new TopologyChange( this );
}
private Transitions( final State state ) {
this.state = state;
this.tc = new TopologyChange( this );
}
@Override
public ServiceConfiguration apply( final ServiceConfiguration input ) {
Components.lookup( input.getComponentId( ) ).setup( input );
return this.tc.apply( input );
}
@Override
public String toString( ) {
return this.name( ) + ":" + this.get( ) + " ";
}
@Override
public State get( ) {
return this.state;
}
}
private static class TopologyChange implements Function<ServiceConfiguration, ServiceConfiguration>, Supplier<Component.State> {
private final Topology.Transitions transitionName;
TopologyChange( final Transitions transitionName ) {
this.transitionName = transitionName;
}
@Override
public ServiceConfiguration apply( final ServiceConfiguration input ) {
State nextState = null;
if ( ( nextState = this.findNextCheckState( input.lookupState( ) ) ) == null ) {
return input;
} else {
return this.doTopologyChange( input, nextState );
}
}
private ServiceConfiguration doTopologyChange( final ServiceConfiguration input, final State nextState ) throws RuntimeException {
final State initialState = input.lookupState( );
boolean enabledEndState = false;
ServiceConfiguration endResult = input;
try {
endResult = ServiceTransitions.pathTo( input, nextState ).get( );
Logs.extreme( ).debug( this.toString( endResult, initialState, nextState ) );
return endResult;
} catch ( final Exception ex ) {
if ( Exceptions.isCausedBy( ex, ExistingTransitionException.class ) ) {
LOG.error( this.toString( input, initialState, nextState, ex ) );
enabledEndState = true;
} else if ( Exceptions.isCausedBy( ex, OrderlyTransitionException.class ) ) {
Logs.extreme( ).error( ex, ex );
} else if ( Exceptions.isCausedBy( ex, ConnectException.class )) {
if ( input.lookupState( ) != initialState ) {
LOG.warn( this.toString( input, initialState, nextState, ex ) );
}
Logs.extreme( ).error( ex, ex );
} else {
Exceptions.maybeInterrupted( ex );
LOG.error( this.toString( input, initialState, nextState, ex ) );
Logs.extreme( ).error( ex, Throwables.getRootCause( ex ) );
Logs.extreme( ).error( ex, ex );
}
throw Exceptions.toUndeclared( ex );
} finally {
enabledEndState |= Component.State.ENABLED.equals( endResult.lookupState( ) );
if ( Bootstrap.isFinished( ) && !enabledEndState && Topology.getInstance( ).services.containsValue( input ) ) {
Threads.enqueue( input, Topology.class, 1, perhapsDisable( input ) );
}
}
}
private String toString( final ServiceConfiguration endResult, final State initialState, final State nextState, final Throwable... throwables ) {
return String.format( "%s %s %s->%s=%s [%s]\n", this.toString( ), endResult.getFullName( ), initialState, nextState, endResult.lookupState( ),
( ( throwables != null ) && ( throwables.length > 0 )
? Throwables.getRootCause( throwables[0] ).getMessage( )
: "WINNING" ) );
}
@Override
public String toString( ) {
return this.transitionName.toString( );
}
@Override
public State get( ) {
return this.transitionName.get( );
}
private State findNextCheckState( final State initialState ) {
if ( this.get( ) == null ) {
if ( State.NOTREADY.equals( initialState ) || State.BROKEN.equals( initialState ) ) {
return State.DISABLED;
} else if ( initialState.ordinal( ) < State.NOTREADY.ordinal( ) ) {
return null;
} else {
return initialState;
}
} else {
return this.get( );
}
}
}
}