/*************************************************************************
* Copyright 2009-2015 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.
************************************************************************/
package com.eucalyptus.cluster.callback.reporting;
import com.eucalyptus.component.id.Eucalyptus;
import com.eucalyptus.compute.common.internal.vm.VmInstance;
import com.eucalyptus.system.Threads;
import com.eucalyptus.util.Exceptions;
import com.eucalyptus.cluster.common.msgs.DescribeSensorsResponseType;
import org.apache.log4j.Logger;
import com.eucalyptus.bootstrap.Bootstrap;
import com.eucalyptus.bootstrap.BootstrapArgs;
import com.eucalyptus.bootstrap.Hosts;
import com.eucalyptus.cluster.callback.DescribeSensorCallback;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import com.eucalyptus.util.async.AsyncRequests;
import com.eucalyptus.vm.VmInstances;
import com.eucalyptus.compute.common.internal.vm.VmInstance.VmState;
import com.eucalyptus.component.ServiceConfiguration;
import com.eucalyptus.component.Topology;
import com.eucalyptus.cluster.common.ClusterController;
import com.eucalyptus.configurable.ConfigurableClass;
import com.eucalyptus.configurable.ConfigurableField;
import com.eucalyptus.event.Hertz;
import com.eucalyptus.event.EventListener;
import com.eucalyptus.event.Listeners;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@ConfigurableClass( root = "cloud.monitor", description = "Parameters controlling cloud watch")
public class DescribeSensorsListener implements EventListener<Hertz> {
@ConfigurableField(initial = "5", description = "How often to request information from the cluster controller")
public static Long DEFAULT_POLL_INTERVAL_MINS = 5L;
private Integer COLLECTION_INTERVAL_TIME_MS;
@ConfigurableField(initial = "5", description = "The initial history size of metrics to be send from the cc to the clc")
public static Integer HISTORY_SIZE = 5;
private Integer MAX_WRITE_INTERVAL_MS = 86400000;
private Integer SENSOR_QUERY_BATCH_SIZE = 10;
private static final ConcurrentMap<String, Boolean> busyHosts = Maps.newConcurrentMap( );
private static final Logger LOG = Logger.getLogger(DescribeSensorsListener.class);
public static void register() {
Listeners.register( Hertz.class, new DescribeSensorsListener() );
}
@Override
public void fireEvent( final Hertz event ) {
final long defaultPollIntervalSeconds = TimeUnit.MINUTES.toSeconds( DEFAULT_POLL_INTERVAL_MINS );
if (!Bootstrap.isOperational() || !BootstrapArgs.isCloudController() || !event.isAsserted(defaultPollIntervalSeconds)) {
return;
} else {
if (DEFAULT_POLL_INTERVAL_MINS >= 1) {
COLLECTION_INTERVAL_TIME_MS = ((int) TimeUnit.MINUTES
.toMillis(DEFAULT_POLL_INTERVAL_MINS) / 2);
} else {
COLLECTION_INTERVAL_TIME_MS = 0;
}
if (COLLECTION_INTERVAL_TIME_MS == 0 || HISTORY_SIZE > 15 || HISTORY_SIZE < 1) {
LOG.debug("The instance usage report is disabled");
} else if (COLLECTION_INTERVAL_TIME_MS <= MAX_WRITE_INTERVAL_MS) {
try {
if (event.isAsserted(defaultPollIntervalSeconds)) {
if (Bootstrap.isFinished() && Hosts.isCoordinator()) {
CloudWatchHelper.DefaultInstanceInfoProvider.refresh( );
for ( final ServiceConfiguration ccConfig : Topology.enabledServices( ClusterController.class ) ) {
final String ccHost = ccConfig.getHostName( );
if ( busyHosts.replace( ccHost, false, true ) || busyHosts.putIfAbsent( ccHost, true ) == null ) {
Threads.lookup( Eucalyptus.class, DescribeSensorsListener.class ).submit( new Callable<Object>() {
@Override
public Object call() throws Exception {
final ExecutorService executorService = Threads.lookup( Eucalyptus.class, DescribeSensorsListener.class, "response-processing" ).limitTo( 4 );
final long startTime = System.currentTimeMillis( );
try {
final List<String> allInstanceIds = VmInstances.listWithProjection(
VmInstances.instanceIdProjection( ),
VmInstance.criterion( VmState.RUNNING ),
VmInstance.zoneCriterion( ccConfig.getPartition( ) ),
VmInstance.nonNullNodeCriterion( )
);
final Iterable<List<String>> processInts = Iterables.partition( allInstanceIds, SENSOR_QUERY_BATCH_SIZE );
for ( final List<String> instIds : processInts ) {
final ArrayList<String> instanceIds = Lists.newArrayList( instIds );
/**
* Here this is hijacking the sensor callback in order to control the thread of execution used when firing
*/
final DescribeSensorCallback msgCallback = new DescribeSensorCallback( HISTORY_SIZE,
COLLECTION_INTERVAL_TIME_MS, instanceIds ) {
@Override
public void fireException( Throwable e ) {}
@Override
public void fire( DescribeSensorsResponseType msg ) {}
};
/**
* Here we actually get the future reference to the result and on a response processing thread, invoke .fire().
*/
final DescribeSensorsResponseType response = AsyncRequests.newRequest( msgCallback ).dispatch( ccConfig ).get( );
executorService.submit( new Runnable( ){
@Override
public void run() {
try {
new DescribeSensorCallback( HISTORY_SIZE,
COLLECTION_INTERVAL_TIME_MS, instanceIds ).fire( response );
} catch ( Exception e ) {
Exceptions.maybeInterrupted( e );
}
}
} );
}
} finally {
/**
* Only and finally set the busy bit back to false.
*/
busyHosts.put( ccHost, false );
LOG.debug( "Sensor polling for " + ccHost + " took " + ( System.currentTimeMillis( ) - startTime ) + "ms" );
}
return null;
}
} );
} else {
LOG.warn( "Skipping sensors polling for "+ccHost+", previous poll not complete." );
}
}
}
}
} catch (Exception ex) {
LOG.error("Unable to listen for describe sensors events", ex);
}
} else {
LOG.error("DEFAULT_POLL_INTERVAL_MINS : "
+ DEFAULT_POLL_INTERVAL_MINS
+ " must be less than 1440 minutes");
}
}
}
}