Skip to content

Fix hooks timeout #1565

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Dec 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
import java.util.stream.Collectors;

public enum HookPhase {


//@formatter:off
@Deprecated
APPLICATION_BEFORE_STOP_LIVE("application.before-stop.live"),
@Deprecated
Expand All @@ -19,8 +20,9 @@ public enum HookPhase {
APPLICATION_AFTER_STOP_IDLE("application.after-stop.idle"),
@Deprecated
APPLICATION_BEFORE_UNMAP_ROUTES("application.before-unmap-routes"),
//@formatter:on


//@formatter:off
BEFORE_STOP("before-stop"),
DEPLOY_APPLICATION_BEFORE_STOP("deploy.application.before-stop"),
BLUE_GREEN_APPLICATION_BEFORE_STOP_IDLE("blue-green.application.before-stop.idle"),
Expand All @@ -38,29 +40,29 @@ public enum HookPhase {
BLUE_GREEN_APPLICATION_BEFORE_START_IDLE("blue-green.application.before-start.idle"),
BLUE_GREEN_APPLICATION_BEFORE_START_LIVE("blue-green.application.before-start.live"),
NONE("");
//@formatter:on

private static Map<String, HookPhase> namesToValues = Arrays.stream(HookPhase.values())
.collect(Collectors.toMap(hookPhase -> hookPhase.value,
Function.identity()));
private static final Map<String, HookPhase> NAMES_TO_VALUES = Arrays.stream(HookPhase.values())
.collect(Collectors.toMap(hookPhase -> hookPhase.value,
Function.identity()));
private final String value;

HookPhase(String value) {
this.value = value;
}

public static HookPhase fromString(String hookPhaseName) {
HookPhase hookPhase = namesToValues.get(hookPhaseName);
HookPhase hookPhase = NAMES_TO_VALUES.get(hookPhaseName);
if (hookPhase == null) {
throw new IllegalStateException(MessageFormat.format("Unsupported hook phase \"{0}\"", hookPhaseName));
}
return hookPhase;
}

@Deprecated
public static List<HookPhase> getOldPhases() {
return Arrays.asList(APPLICATION_BEFORE_STOP_LIVE, APPLICATION_BEFORE_STOP_IDLE, APPLICATION_AFTER_STOP_LIVE,
APPLICATION_AFTER_STOP_IDLE, APPLICATION_BEFORE_UNMAP_ROUTES
);
APPLICATION_AFTER_STOP_IDLE, APPLICATION_BEFORE_UNMAP_ROUTES);
}

public String getValue() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package org.cloudfoundry.multiapps.controller.process.listeners;

import org.cloudfoundry.multiapps.controller.core.util.ApplicationConfiguration;
import org.cloudfoundry.multiapps.controller.persistence.services.HistoricOperationEventService;
import org.cloudfoundry.multiapps.controller.persistence.services.ProcessLoggerPersister;
import org.cloudfoundry.multiapps.controller.persistence.services.ProcessLoggerProvider;
import org.cloudfoundry.multiapps.controller.persistence.services.ProgressMessageService;
import org.cloudfoundry.multiapps.controller.process.flowable.FlowableFacade;
import org.cloudfoundry.multiapps.controller.process.util.StepLogger;
import org.cloudfoundry.multiapps.controller.process.variables.Variables;
import org.flowable.engine.delegate.DelegateExecution;

import jakarta.inject.Named;

@Named("hooksEndProcessListener")
public class HooksEndProcessListener extends AbstractProcessExecutionListener {

protected HooksEndProcessListener(ProgressMessageService progressMessageService, StepLogger.Factory stepLoggerFactory,
ProcessLoggerProvider processLoggerProvider, ProcessLoggerPersister processLoggerPersister,
HistoricOperationEventService historicOperationEventService, FlowableFacade flowableFacade,
ApplicationConfiguration configuration) {
super(progressMessageService,
stepLoggerFactory,
processLoggerProvider,
processLoggerPersister,
historicOperationEventService,
flowableFacade,
configuration);
}

@Override
protected void notifyInternal(DelegateExecution execution) throws Exception {
setVariableInParentProcess(execution, Variables.MUST_RESET_TIMEOUT.getName(), true);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,6 @@
import java.time.Duration;
import java.util.List;

import jakarta.inject.Inject;
import jakarta.inject.Named;

import org.cloudfoundry.multiapps.controller.core.cf.CloudControllerClientFactory;
import org.cloudfoundry.multiapps.controller.core.model.HookPhase;
import org.cloudfoundry.multiapps.controller.core.security.token.TokenService;
Expand All @@ -22,6 +19,9 @@
import com.sap.cloudfoundry.client.facade.domain.CloudApplication;
import com.sap.cloudfoundry.client.facade.domain.CloudApplication.State;

import jakarta.inject.Inject;
import jakarta.inject.Named;

@Named("restartAppStep")
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class RestartAppStep extends TimeoutAsyncFlowableStepWithHooks implements BeforeStepHookPhaseProvider {
Expand Down Expand Up @@ -96,6 +96,10 @@ protected List<AsyncExecution> getAsyncStepExecutions(ProcessContext context) {

@Override
public Duration getTimeout(ProcessContext context) {
Duration timeout = super.getTimeout(context);
if (timeout != null) {
return timeout;
}
return calculateTimeout(context, TimeoutType.START);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@

import java.util.List;

import jakarta.inject.Inject;

import org.cloudfoundry.multiapps.controller.core.cf.metadata.processor.MtaMetadataParser;
import org.cloudfoundry.multiapps.controller.process.util.HooksCalculator;
import org.cloudfoundry.multiapps.controller.process.util.HooksExecutor;
Expand All @@ -16,6 +14,8 @@
import org.cloudfoundry.multiapps.mta.model.Hook;
import org.cloudfoundry.multiapps.mta.model.Module;

import jakarta.inject.Inject;

public abstract class SyncFlowableStepWithHooks extends SyncFlowableStep {

@Inject
Expand All @@ -31,9 +31,9 @@ protected StepPhase executeStep(ProcessContext context) {
StepPhase currentStepPhase = context.getVariable(Variables.STEP_PHASE);
Module moduleToDeploy = moduleDeterminer.determineModuleToDeploy();
HooksCalculator hooksCalculator = getHooksCalculator(context);
HooksExecutor hooksExecutor = getHooksExecutor(hooksCalculator, moduleToDeploy);
List<Hook> executedHooks = hooksExecutor.executeBeforeStepHooks(currentStepPhase);
if (!executedHooks.isEmpty()) {
HooksExecutor hooksExecutor = getHooksExecutor(hooksCalculator, moduleToDeploy, context);
List<Hook> executedBeforeStepHooks = hooksExecutor.executeBeforeStepHooks(currentStepPhase);
if (!executedBeforeStepHooks.isEmpty()) {
return currentStepPhase;
}
currentStepPhase = executeStepInternal(context);
Expand All @@ -56,8 +56,8 @@ protected HooksCalculator getHooksCalculator(ProcessContext context) {
.build();
}

protected HooksExecutor getHooksExecutor(HooksCalculator hooksCalculator, Module moduleToDeploy) {
return new HooksExecutor(hooksCalculator, moduleToDeploy);
protected HooksExecutor getHooksExecutor(HooksCalculator hooksCalculator, Module moduleToDeploy, ProcessContext context) {
return new HooksExecutor(hooksCalculator, moduleToDeploy, context);
}

protected abstract StepPhase executeStepInternal(ProcessContext context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,12 @@ private long getStepStartTime(ProcessContext context) {
context.getExecution()
.setVariable(getStepStartTimeVariable(), stepStartTime);
}

if (context.getVariable(Variables.MUST_RESET_TIMEOUT)) {
stepStartTime = System.currentTimeMillis();
context.getExecution()
.setVariable(getStepStartTimeVariable(), stepStartTime);
context.setVariable(Variables.MUST_RESET_TIMEOUT, false);
}
return stepStartTime;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
package org.cloudfoundry.multiapps.controller.process.steps;

import java.time.Duration;
import java.util.List;

import jakarta.inject.Inject;

import org.cloudfoundry.multiapps.controller.core.cf.metadata.processor.MtaMetadataParser;
import org.cloudfoundry.multiapps.controller.process.util.HooksCalculator;
import org.cloudfoundry.multiapps.controller.process.util.HooksExecutor;
Expand All @@ -12,10 +11,13 @@
import org.cloudfoundry.multiapps.controller.process.util.ImmutableHooksCalculator;
import org.cloudfoundry.multiapps.controller.process.util.ImmutableModuleDeterminer;
import org.cloudfoundry.multiapps.controller.process.util.ModuleDeterminer;
import org.cloudfoundry.multiapps.controller.process.util.TimeoutType;
import org.cloudfoundry.multiapps.controller.process.variables.Variables;
import org.cloudfoundry.multiapps.mta.model.Hook;
import org.cloudfoundry.multiapps.mta.model.Module;

import jakarta.inject.Inject;

public abstract class TimeoutAsyncFlowableStepWithHooks extends TimeoutAsyncFlowableStep {

@Inject
Expand All @@ -27,18 +29,26 @@ public abstract class TimeoutAsyncFlowableStepWithHooks extends TimeoutAsyncFlow

@Override
public StepPhase executeAsyncStep(ProcessContext context) {
ModuleDeterminer moduleDeterminer = getModuleDeterminer(context);
StepPhase currentStepPhase = context.getVariable(Variables.STEP_PHASE);
Module moduleToDeploy = moduleDeterminer.determineModuleToDeploy();
HooksCalculator hooksCalculator = getHooksCalculator(context);
HooksExecutor hooksExecutor = getHooksExecutor(hooksCalculator, moduleToDeploy);
List<Hook> executedHooks = hooksExecutor.executeBeforeStepHooks(currentStepPhase);
List<Hook> executedHooks = executeHooksForExecution(context, currentStepPhase);
if (!executedHooks.isEmpty()) {
return currentStepPhase;
}
return executePollingStep(context);
}

private List<Hook> executeHooksForExecution(ProcessContext context, StepPhase currentStepPhase) {
HooksExecutor hooksExecutor = getHooksExecutor(context);
return hooksExecutor.executeBeforeStepHooks(currentStepPhase);
}

private HooksExecutor getHooksExecutor(ProcessContext context) {
ModuleDeterminer moduleDeterminer = getModuleDeterminer(context);
Module moduleToDeploy = moduleDeterminer.determineModuleToDeploy();
HooksCalculator hooksCalculator = getHooksCalculator(context);
return getHooksDeterminer(hooksCalculator, moduleToDeploy, context);
}

protected ModuleDeterminer getModuleDeterminer(ProcessContext context) {
return ImmutableModuleDeterminer.builder()
.context(context)
Expand All @@ -54,9 +64,19 @@ protected HooksCalculator getHooksCalculator(ProcessContext context) {
.build();
}

protected HooksExecutor getHooksExecutor(HooksCalculator hooksCalculator, Module moduleToDeploy) {
return new HooksExecutor(hooksCalculator, moduleToDeploy);
protected HooksExecutor getHooksDeterminer(HooksCalculator hooksCalculator, Module moduleToDeploy, ProcessContext context) {
return new HooksExecutor(hooksCalculator, moduleToDeploy, context);
}

protected abstract StepPhase executePollingStep(ProcessContext context);

@Override
public Duration getTimeout(ProcessContext context) {
StepPhase currentStepPhase = context.getVariable(Variables.STEP_PHASE);
if (!getHooksExecutor(context).determineBeforeStepHooks(currentStepPhase)
.isEmpty()) {
return calculateTimeout(context, TimeoutType.TASK);
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,19 @@
import org.cloudfoundry.multiapps.controller.core.model.HookPhase;
import org.cloudfoundry.multiapps.controller.process.steps.ProcessContext;
import org.cloudfoundry.multiapps.controller.process.steps.StepPhase;
import org.cloudfoundry.multiapps.controller.process.variables.Variables;
import org.cloudfoundry.multiapps.mta.model.Hook;
import org.cloudfoundry.multiapps.mta.model.Module;
import org.immutables.value.Value;

@Value.Immutable
public abstract class HooksCalculator {

public List<Hook> calculateHooksForExecution(Module moduleToDeploy, StepPhase currentStepPhase) {
public HooksWithPhases calculateHooksForExecution(Module moduleToDeploy, StepPhase currentStepPhase) {
List<HookPhase> currentHookPhasesForExecution = determineHookPhaseForCurrentStepPhase(currentStepPhase);
return getHooksForCurrentPhase(moduleToDeploy, currentHookPhasesForExecution);
return ImmutableHooksWithPhases.builder()
.hooks(getHooksForCurrentPhase(moduleToDeploy, currentHookPhasesForExecution))
.hookPhases(currentHookPhasesForExecution)
.build();
}

private List<HookPhase> determineHookPhaseForCurrentStepPhase(StepPhase currentStepPhase) {
Expand Down Expand Up @@ -45,10 +47,6 @@ private ModuleHooksAggregator getModuleHooksAggregator(Module moduleToDeploy) {
return new ModuleHooksAggregator(getContext().getExecution(), moduleToDeploy);
}

public void setHooksForExecution(List<Hook> hooksForExecution) {
getContext().setVariable(Variables.HOOKS_FOR_EXECUTION, hooksForExecution);
}

public abstract ProcessContext getContext();

public abstract List<HookPhase> getHookPhasesBeforeStep();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,37 @@
package org.cloudfoundry.multiapps.controller.process.util;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.collections4.ListUtils;
import org.cloudfoundry.multiapps.controller.core.model.HookPhase;
import org.cloudfoundry.multiapps.controller.process.steps.ProcessContext;
import org.cloudfoundry.multiapps.controller.process.steps.StepPhase;
import org.cloudfoundry.multiapps.controller.process.steps.StepsUtil;
import org.cloudfoundry.multiapps.controller.process.variables.Variables;
import org.cloudfoundry.multiapps.mta.model.Hook;
import org.cloudfoundry.multiapps.mta.model.Module;

public class HooksExecutor {

private final HooksCalculator hooksCalculator;
private final Module moduleToDeploy;
private final ProcessContext context;

public HooksExecutor(HooksCalculator hooksCalculator, Module moduleToDeploy) {
public HooksExecutor(HooksCalculator hooksCalculator, Module moduleToDeploy, ProcessContext context) {
this.hooksCalculator = hooksCalculator;
this.moduleToDeploy = moduleToDeploy;
this.context = context;
}

public List<Hook> determineBeforeStepHooks(StepPhase currentStepPhase) {
if (!hooksCalculator.isInPreExecuteStepPhase(currentStepPhase) || moduleToDeploy == null) {
return Collections.emptyList();
}
HooksWithPhases hooksWithPhases = hooksCalculator.calculateHooksForExecution(moduleToDeploy, currentStepPhase);
return hooksWithPhases.getHooks();
}

public List<Hook> executeBeforeStepHooks(StepPhase currentStepPhase) {
Expand All @@ -24,20 +41,49 @@ public List<Hook> executeBeforeStepHooks(StepPhase currentStepPhase) {
return executeHooks(currentStepPhase);
}

public List<Hook> executeAfterStepHooks(StepPhase currentStepPhase) {
if (!hooksCalculator.isInPostExecuteStepPhase(currentStepPhase)) {
private List<Hook> executeHooks(StepPhase currentStepPhase) {
if (moduleToDeploy == null) {
return Collections.emptyList();
}
return executeHooks(currentStepPhase);
HooksWithPhases hooksWithPhases = hooksCalculator.calculateHooksForExecution(moduleToDeploy, currentStepPhase);
Map<String, List<String>> alreadyExecutedHooksForModule = StepsUtil.getExecutedHooksForModule(context.getExecution(),
moduleToDeploy.getName());
updateExecutedHooksForModule(alreadyExecutedHooksForModule, hooksWithPhases.getHookPhases(), hooksWithPhases.getHooks());
context.setVariable(Variables.HOOKS_FOR_EXECUTION, hooksWithPhases.getHooks());
return hooksWithPhases.getHooks();
}

private List<Hook> executeHooks(StepPhase stepPhase) {
if (moduleToDeploy == null) {
private void updateExecutedHooksForModule(Map<String, List<String>> alreadyExecutedHooks, List<HookPhase> currentHookPhasesForExecution,
List<Hook> hooksForExecution) {
Map<String, List<String>> result = new HashMap<>(alreadyExecutedHooks);
updateExecutedHooks(result, currentHookPhasesForExecution, hooksForExecution);
StepsUtil.setExecutedHooksForModule(context.getExecution(), moduleToDeploy.getName(), result);
}

private void updateExecutedHooks(Map<String, List<String>> alreadyExecutedHooks, List<HookPhase> currentHookPhasesForExecution,
List<Hook> hooksForExecution) {
for (Hook hook : hooksForExecution) {
updateHook(alreadyExecutedHooks, currentHookPhasesForExecution, hook);
}
}

private void updateHook(Map<String, List<String>> alreadyExecutedHooks, List<HookPhase> currentHookPhasesForExecution, Hook hook) {
List<String> hookPhasesBasedOnCurrentHookPhase = getHookPhasesBasedOnCurrentHookPhase(currentHookPhasesForExecution,
hook.getPhases());
alreadyExecutedHooks.merge(hook.getName(), hookPhasesBasedOnCurrentHookPhase, ListUtils::union);
}

private List<String> getHookPhasesBasedOnCurrentHookPhase(List<HookPhase> currentHookPhasesForExecution, List<String> hookPhases) {
return hookPhases.stream()
.filter(phase -> currentHookPhasesForExecution.contains(HookPhase.fromString(phase)))
.toList();
}

public List<Hook> executeAfterStepHooks(StepPhase currentStepPhase) {
if (!hooksCalculator.isInPostExecuteStepPhase(currentStepPhase)) {
return Collections.emptyList();
}
List<Hook> hooksForExecution = hooksCalculator.calculateHooksForExecution(moduleToDeploy, stepPhase);
hooksCalculator.setHooksForExecution(hooksForExecution);
return hooksForExecution;
return executeHooks(currentStepPhase);
}

}
Loading
Loading