/************************************************************************* * 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.concurrent.CancellationException; import com.amazonaws.services.simpleworkflow.flow.core.OrPromise; import com.eucalyptus.loadbalancing.common.msgs.HealthCheck; 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.AndPromise; 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; /** * @author Sang-Min Park (sangmin.park@hpe.com) * */ @ComponentPart(LoadBalancing.class) public class InstanceStatusWorkflowImpl implements InstanceStatusWorkflow { private static Logger LOG = Logger.getLogger( InstanceStatusWorkflowImpl.class ); private String accountId = null; private String loadbalancer = null; final LoadBalancingVmActivitiesClient vmClient = new LoadBalancingVmActivitiesClientImpl(); final LoadBalancingActivitiesClient client = new LoadBalancingActivitiesClientImpl(); final InstanceStatusWorkflowSelfClient selfClient = new InstanceStatusWorkflowSelfClientImpl(); TryCatchFinally task = null; private DecisionContextProvider contextProvider = new DecisionContextProviderImpl(); private Settable<Boolean> signalReceived = new Settable<Boolean>(); final WorkflowClock clock = contextProvider.getDecisionContext().getWorkflowClock(); // continuous workflow generates enormous amount of history // ideally we should use continueAsNewWorkflow, but the current EUCA SWF lacks it // TODO: implement SWF:continueAsNewWorkflow private int MAX_POLL_PER_WORKFLOW = 10; static public final int MIN_POLLING_PERIOD_SEC = 10; private int pollingPeriodSec = MIN_POLLING_PERIOD_SEC; @Override public void pollInstanceStatus(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 { final Promise<HealthCheck> healthCheck = client.lookupLoadBalancerHealthCheck(Promise.asPromise(accountId), Promise.asPromise(loadbalancer)); pollInstanceStatus(healthCheck); } @Override protected void doCatch(Throwable ex) throws Throwable { if (ex instanceof ActivityTaskTimedOutException) { LOG.warn("Instance polling task timed out"); }else if (ex instanceof CancellationException){ ; }else { LOG.warn("Instance polling 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.pollInstanceStatus(accountId, loadbalancer); } } }; } @Asynchronous private void pollInstanceStatus(final Promise<HealthCheck> healthCheck) { final HealthCheck hc = healthCheck.get(); if ( hc != null && hc.getInterval() != null ) { this.pollingPeriodSec = Math.max(MIN_POLLING_PERIOD_SEC, hc.getInterval().intValue()); } pollInstanceStatusPeriodic(0); } @Asynchronous private void pollInstanceStatusPeriodic(final int count, Promise<?>... waitFor) { if (signalReceived.isReady() || count >= MAX_POLL_PER_WORKFLOW) { return; } final Promise<List<String>> servoInstances = client.lookupServoInstances(this.accountId, this.loadbalancer); doPollStatus(count, servoInstances); } @Asynchronous private void doPollStatus(final int count, final Promise<List<String>> servoInstances) { final List<String> instances = servoInstances.get(); final List<Promise<Void>> activities = Lists.newArrayList(); for(final String instanceId : instances) { activities.add( client.updateInstanceStatus( Promise.asPromise(accountId), Promise.asPromise(loadbalancer), client.filterInstanceStatus( Promise.asPromise(accountId), Promise.asPromise(loadbalancer), Promise.asPromise(instanceId), pollStatusFromVM(instanceId) ) ) ); } final Promise<Void> timer = startDaemonTimer(this.pollingPeriodSec); final OrPromise waitOrSignal = new OrPromise(timer, signalReceived); pollInstanceStatusPeriodic(count+1, new AndPromise( Promises.listOfPromisesToPromise(activities), waitOrSignal ) ); } @Asynchronous private Promise<String> pollStatusFromVM(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.getInstanceStatus(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; } @Override public void pollImmediately() { if(!signalReceived.isReady()) signalReceived.set(true); } }