/*************************************************************************
* 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.
************************************************************************/
package com.eucalyptus.loadbalancing.workflow;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CancellationException;
import com.amazonaws.services.simpleworkflow.flow.core.AndPromise;
import org.apache.log4j.Logger;
import com.amazonaws.services.simpleworkflow.flow.ActivitySchedulingOptions;
import com.amazonaws.services.simpleworkflow.flow.ActivityTaskTimedOutException;
import com.amazonaws.services.simpleworkflow.flow.DecisionContextProvider;
import com.amazonaws.services.simpleworkflow.flow.DecisionContextProviderImpl;
import com.amazonaws.services.simpleworkflow.flow.WorkflowClock;
import com.amazonaws.services.simpleworkflow.flow.annotations.Asynchronous;
import com.amazonaws.services.simpleworkflow.flow.core.Promise;
import com.amazonaws.services.simpleworkflow.flow.core.Promises;
import com.amazonaws.services.simpleworkflow.flow.core.Settable;
import com.amazonaws.services.simpleworkflow.flow.core.TryCatchFinally;
import com.eucalyptus.component.annotation.ComponentPart;
import com.eucalyptus.loadbalancing.common.LoadBalancing;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
/**
* @author Sang-Min Park (sangmin.park@hpe.com)
*
*/
@ComponentPart(LoadBalancing.class)
public class CloudWatchPutMetricWorkflowImpl implements CloudWatchPutMetricWorkflow {
private static Logger LOG = Logger.getLogger( CloudWatchPutMetricWorkflowImpl.class );
private String accountId = null;
private String loadbalancer = null;
final LoadBalancingVmActivitiesClient vmClient =
new LoadBalancingVmActivitiesClientImpl();
final LoadBalancingActivitiesClient client =
new LoadBalancingActivitiesClientImpl();
final CloudWatchPutMetricWorkflowSelfClient selfClient =
new CloudWatchPutMetricWorkflowSelfClientImpl();
TryCatchFinally task = null;
private DecisionContextProvider contextProvider
= new DecisionContextProviderImpl();
// continuous workflow generates enourmous amount of history
// ideally we should use continueAsNewWorkflow, but the current EUCA SWF lacks it
// TODO: implement SWF:continueAsNewWorkflow
private int MAX_PUT_PER_WORKFLOW = 10;
private final int PUT_PERIOD_SEC = 30;
final WorkflowClock clock =
contextProvider.getDecisionContext().getWorkflowClock();
@Override
public void putCloudWatchMetric(final String accountId, final String loadbalancer) {
this.accountId = accountId;
this.loadbalancer = loadbalancer;
final Settable<Boolean> exception = new Settable<Boolean>();
task = new TryCatchFinally() {
@Override
protected void doTry() throws Throwable {
putCloudWatchMetricPeriodic(0);
}
@Override
protected void doCatch(Throwable ex) throws Throwable {
if (ex instanceof ActivityTaskTimedOutException) {
LOG.warn("Put metric activity timed out");
}else if (ex instanceof CancellationException){
;
}else {
LOG.warn("Put metric workflow failed", ex);
}
exception.set(true);
throw ex;
}
@Override
protected void doFinally() throws Throwable {
if(exception.isReady() && exception.get())
return;
else if (task.isCancelRequested())
return;
else {
selfClient.getSchedulingOptions().setTagList(
Lists.newArrayList(String.format("account:%s", accountId),
String.format("loadbalancer:%s", loadbalancer)));
selfClient.putCloudWatchMetric(accountId, loadbalancer);
}
}
};
}
@Asynchronous
private void putCloudWatchMetricPeriodic(final int count, Promise<?>... waitFor) {
if (count >= MAX_PUT_PER_WORKFLOW) {
return;
}
final Promise<List<String>> servoInstances = client.lookupServoInstances(this.accountId, this.loadbalancer);
// collect metrics from servos
doPutMetric(count, servoInstances);
}
@Asynchronous
private void doPutMetric(final int count, final Promise<List<String>> servoInstances) {
final Map<String, Promise<String>> metrics = Maps.newHashMap();
final List<String> instances = servoInstances.get();
for(final String instanceId : instances) {
metrics.put(instanceId, getCloudWatchMetricsFromVM(instanceId));
}
final Promise<Map<String, String>> metricMap = Promises.mapOfPromisesToPromise(metrics);
final List<Promise<Void>> activities = Lists.newArrayList();
activities.add(
client.putCloudWatchMetrics(
Promise.asPromise(this.accountId), Promise.asPromise(this.loadbalancer), metricMap)
);
activities.add(
client.putCloudWatchInstanceHealth(this.accountId, this.loadbalancer)
);
final Promise<Void> timer = startDaemonTimer(PUT_PERIOD_SEC);
putCloudWatchMetricPeriodic(count+1,
new AndPromise(timer, Promises.listOfPromisesToPromise(activities)));
}
@Asynchronous
private Promise<String> getCloudWatchMetricsFromVM(final String instanceId) {
final Settable<String> failure = new Settable<String>();
final Settable<String> result = new Settable<String>();
new TryCatchFinally() {
protected void doTry() throws Throwable {
final ActivitySchedulingOptions scheduler =
new ActivitySchedulingOptions();
scheduler.setTaskList(instanceId);
scheduler.setScheduleToCloseTimeoutSeconds(120L); /// account for VM startup delay
scheduler.setStartToCloseTimeoutSeconds(10L);
result.chain(vmClient.getCloudWatchMetrics(scheduler));
}
protected void doCatch(Throwable e) {
failure.set(instanceId);
}
protected void doFinally() throws Throwable {
if ( result.isReady() ) {
failure.set(null);
} else if ( failure.isReady()) {
result.set(null);
} else {
result.set(null);
failure.set(null);
}
}
};
return done(result, failure);
}
@Asynchronous
private Promise<String> done(final Settable<String> result, final Settable<String> failure) {
if (result.get() != null ) {
return Promise.asPromise(result.get());
} else if (failure.get() != null) {
return checkInstanceFailure(failure);
} else {
return Promise.asPromise(null); // this shouldn't happen
}
}
@Asynchronous
private Promise<String> checkInstanceFailure(Promise<String> failure) {
final String instanceId = failure.get();
if (instanceId != null) {
client.recordInstanceTaskFailure(instanceId);
}
return Promise.asPromise(null);
}
@Asynchronous(daemon = true)
private Promise<Void> startDaemonTimer(int seconds) {
Promise<Void> timer = clock.createTimer(seconds);
return timer;
}
}