/************************************************************************* * 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.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.CancellationException; import com.eucalyptus.loadbalancing.common.msgs.PolicyDescription; 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.OrPromise; 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.eucalyptus.loadbalancing.common.msgs.LoadBalancerServoDescription; import com.eucalyptus.loadbalancing.common.msgs.LoadBalancerServoDescriptions; import com.google.common.collect.Lists; /** * @author Sang-Min Park (sangmin.park@hpe.com) * */ @ComponentPart(LoadBalancing.class) public class UpdateLoadBalancerWorkflowImpl implements UpdateLoadBalancerWorkflow { private static Logger LOG = Logger.getLogger( UpdateLoadBalancerWorkflowImpl.class ); private final LoadBalancingActivitiesClient client = new LoadBalancingActivitiesClientImpl(); private final UpdateLoadBalancerWorkflowSelfClient selfClient = new UpdateLoadBalancerWorkflowSelfClientImpl(); private final LoadBalancingVmActivitiesClient vmClient = new LoadBalancingVmActivitiesClientImpl(); private DecisionContextProvider contextProvider = new DecisionContextProviderImpl(); final WorkflowClock clock = contextProvider.getDecisionContext().getWorkflowClock(); private Settable<Boolean> signalReceived = new Settable<Boolean>(); private TryCatchFinally task = null; private final int MAX_UPDATE_PER_WORKFLOW = 10; private final int UPDATE_PERIOD_SEC = 60; private String accountId = null; private String loadbalancer = null; @Override public void updateLoadBalancer(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 { updateInstancesPeriodic(0); } @Override protected void doCatch(final Throwable ex) throws Throwable { if (ex instanceof ActivityTaskTimedOutException) { ; }else if (ex instanceof CancellationException) { ; }else { ; } 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.updateLoadBalancer(accountId, loadbalancer); } } }; } @Asynchronous private void updateInstancesPeriodic(final int count, Promise<?>... waitFor) { if (signalReceived.isReady() || count >= MAX_UPDATE_PER_WORKFLOW) { return; } // get map of instance->ELB description final Promise<Map<String, LoadBalancerServoDescription>> loadbalancer = client.lookupLoadBalancerDescription(this.accountId, this.loadbalancer); // each policy is a large text and SWF has a limit on input/output text; // so we push the policy in iteration final Promise<List<String>> policies = client.listLoadBalancerPolicies(this.accountId, this.loadbalancer); final Promise<Void> policyUpdate = updatePolicies(loadbalancer, policies); doUpdateInstances(count, loadbalancer, policyUpdate); // push LB definition after policies are pushed } @Asynchronous Promise<Void> updatePolicies(final Promise<Map<String, LoadBalancerServoDescription>> loadbalancer, final Promise<List<String>> policyNames) { final List<Promise<PolicyDescription>> policies = Lists.newArrayList(); for (final String policyName : policyNames.get()) { policies.add( client.getLoadBalancerPolicy(Promise.asPromise(this.accountId), Promise.asPromise(this.loadbalancer), Promise.asPromise(policyName)) ); } final Promise<Void> policyUpdated = pushPolicies(loadbalancer, Promises.listOfPromisesToPromise(policies)); return policyUpdated; } @Asynchronous private void doUpdateInstances(final int count, final Promise<Map<String, LoadBalancerServoDescription>> loadbalancer, final Promise<Void> policyUpdated) { // update each instance final Map<String, LoadBalancerServoDescription> description = loadbalancer.get(); final List<Promise<Void>> result = Lists.newArrayList(); for(final String instanceId : description.keySet()) { final LoadBalancerServoDescription desc = description.get(instanceId); result.add(doUpdateInstance(instanceId, desc)); } final Promise<Void> timer = startDaemonTimer(UPDATE_PERIOD_SEC); final OrPromise waitOrSignal = new OrPromise(timer, signalReceived); updateInstancesPeriodic(count+1, new AndPromise(waitOrSignal, Promises.listOfPromisesToPromise(result))); } @Asynchronous private Promise<Void> doUpdateInstance(final String instanceId, final LoadBalancerServoDescription desc) { // update each servo VM final String message = encodeLoadBalancer(desc); final Settable<Void> result = new Settable<Void>(); final Settable<String> failure = 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.setLoadBalancer(message, scheduler)); } protected void doCatch(Throwable e) { failure.set(instanceId); } protected void doFinally() throws Throwable { if (!failure.isReady()) { failure.set(null); } } }; return checkInstanceFailure(failure); } @Asynchronous private Promise<Void> pushPolicies(final Promise<Map<String, LoadBalancerServoDescription>> loadbalancer, final Promise<List<PolicyDescription>> policies) { final Map<String, LoadBalancerServoDescription> description = loadbalancer.get(); final List<Promise<Void>> result = Lists.newArrayList(); for(final String instanceId : description.keySet()) { result.add(pushPoliciesToVM(instanceId, policies)); } return done(Promises.listOfPromisesToPromise(result)); } @Asynchronous private Promise<Void> pushPoliciesToVM(final String instanceId, final Promise<List<PolicyDescription>> policies) { final List<Promise<Void>> result = Lists.newArrayList(); final Settable<String> failure = 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); for (final PolicyDescription p : policies.get()) { result.add(vmClient.setPolicy(encodePolicy(p), scheduler)); } } protected void doCatch(Throwable e) { failure.set(instanceId); } protected void doFinally() throws Throwable { if (!failure.isReady()) { failure.set(null); } } }; return checkInstanceFailure(failure); } @Asynchronous private Promise<Void> checkInstanceFailure(Promise<String> failure) { final String instanceId = failure.get(); if (instanceId != null) { return client.recordInstanceTaskFailure(instanceId); } return Promise.Void(); } @Asynchronous private Promise<Void> done(Promise<List<Void>> result) { return Promise.Void(); } private String encodePolicy(final PolicyDescription policy) { return VmWorkflowMarshaller.marshalPolicy(policy); } private String encodeLoadBalancer(final LoadBalancerServoDescription lbDescription) { final LoadBalancerServoDescriptions lbDescriptions = new LoadBalancerServoDescriptions(); lbDescriptions.setMember(new ArrayList<LoadBalancerServoDescription>()); lbDescriptions.getMember().add(lbDescription); final String encoded = VmWorkflowMarshaller.marshalLoadBalancer(lbDescriptions); return encoded; } @Asynchronous(daemon = true) private Promise<Void> startDaemonTimer(int seconds) { Promise<Void> timer = clock.createTimer(seconds); return timer; } @Override public void updateImmediately() { if(!signalReceived.isReady()) signalReceived.set(true); } }