Skip to content
Open
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 @@ -37,7 +37,7 @@ public class SupportedParameters {
public static final String APPLY_NAMESPACE_APPS = "app-names";
public static final String APPLY_NAMESPACE_SERVICES = "service-names";
public static final String APPLY_NAMESPACE_ROUTES = "app-routes";

public static final String BG_DEPENDENCY_AWARE_STOP_ORDER = "bg-dependency-aware-stop-order";
public static final String DEPLOY_MODE = "deploy_mode";

public static final String PATH = "path";
Expand Down Expand Up @@ -212,7 +212,7 @@ public class SupportedParameters {
FAIL_ON_SERVICE_UPDATE, SERVICE_PROVIDER, SERVICE_VERSION);
public static final Set<String> GLOBAL_PARAMETERS = Set.of(KEEP_EXISTING_ROUTES, APPS_UPLOAD_TIMEOUT, APPS_TASK_EXECUTION_TIMEOUT,
APPS_START_TIMEOUT, APPS_STAGE_TIMEOUT, APPLY_NAMESPACE,
ENABLE_PARALLEL_DEPLOYMENTS, DEPLOY_MODE);
ENABLE_PARALLEL_DEPLOYMENTS, DEPLOY_MODE, BG_DEPENDENCY_AWARE_STOP_ORDER);

public static final Set<String> DEPENDENCY_PARAMETERS = Set.of(BINDING_NAME, ENV_VAR_NAME, VISIBILITY, USE_LIVE_ROUTES,
SERVICE_BINDING_CONFIG, DELETE_SERVICE_KEY_AFTER_DEPLOYMENT);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,15 @@
import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudApplicationExtended;
import org.cloudfoundry.multiapps.controller.core.cf.v2.ConfigurationEntriesCloudModelBuilder;
import org.cloudfoundry.multiapps.controller.core.helpers.ModuleToDeployHelper;
import org.cloudfoundry.multiapps.controller.core.model.Phase;
import org.cloudfoundry.multiapps.controller.core.model.SupportedParameters;
import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization;
import org.cloudfoundry.multiapps.controller.core.util.NameUtil;
import org.cloudfoundry.multiapps.controller.persistence.model.ConfigurationEntry;
import org.cloudfoundry.multiapps.controller.process.Messages;
import org.cloudfoundry.multiapps.controller.process.util.AdditionalModuleParametersReporter;
import org.cloudfoundry.multiapps.controller.process.util.ApplicationEnvironmentCalculator;
import org.cloudfoundry.multiapps.controller.process.util.DependentModuleStopResolver;
import org.cloudfoundry.multiapps.controller.process.variables.Variables;
import org.cloudfoundry.multiapps.mta.handlers.HandlerFactory;
import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor;
Expand All @@ -38,6 +40,8 @@ public class BuildApplicationDeployModelStep extends SyncFlowableStep {
private ModuleToDeployHelper moduleToDeployHelper;
@Inject
private ApplicationEnvironmentCalculator applicationEnvironmentCalculator;
@Inject
private DependentModuleStopResolver moduleStopResolver;

@Override
protected StepPhase executeStep(ProcessContext context) {
Expand All @@ -61,6 +65,7 @@ protected StepPhase executeStep(ProcessContext context) {
buildConfigurationEntries(context, modifiedApp);
context.setVariable(Variables.TASKS_TO_EXECUTE, modifiedApp.getTasks());
getStepLogger().debug(Messages.CLOUD_APP_MODEL_BUILT);
determineDependentModulesToStop(context, module);
return StepPhase.DONE;
}

Expand Down Expand Up @@ -135,6 +140,15 @@ private ConfigurationEntriesCloudModelBuilder getConfigurationEntriesCloudModelB
return new ConfigurationEntriesCloudModelBuilder(organizationName, spaceName, spaceGuid, namespace);
}

private void determineDependentModulesToStop(ProcessContext context, Module module) {
if (context.getVariable(Variables.PHASE) != Phase.AFTER_RESUME) {
return;
}
DeploymentDescriptor descriptor = context.getVariable(Variables.COMPLETE_DEPLOYMENT_DESCRIPTOR);
List<Module> modulesToStop = moduleStopResolver.resolveDependentModulesToStop(module, descriptor);
context.setVariable(Variables.DEPENDENT_MODULES_TO_STOP, modulesToStop);
}

@Override
protected String getStepErrorMessage(ProcessContext context) {
return Messages.ERROR_BUILDING_CLOUD_APP_MODEL;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.cloudfoundry.multiapps.controller.process.steps;

import jakarta.inject.Named;

import org.cloudfoundry.multiapps.controller.process.Messages;
import org.cloudfoundry.multiapps.controller.process.variables.Variables;
import org.springframework.beans.factory.config.BeanDefinition;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package org.cloudfoundry.multiapps.controller.process.steps;

import java.text.MessageFormat;
import java.util.List;
import java.util.Map;
import java.util.Set;

import jakarta.inject.Inject;
import jakarta.inject.Named;
import org.apache.commons.lang3.StringUtils;
import org.cloudfoundry.client.v3.processes.HealthCheckType;
import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute;
import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableStaging;
import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging;
import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudApplicationExtended;
import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudApplicationExtended;
import org.cloudfoundry.multiapps.controller.core.cf.v2.ApplicationCloudModelBuilder;
import org.cloudfoundry.multiapps.controller.core.helpers.ModuleToDeployHelper;
import org.cloudfoundry.multiapps.controller.process.Messages;
import org.cloudfoundry.multiapps.controller.process.util.AdditionalModuleParametersReporter;
import org.cloudfoundry.multiapps.controller.process.util.ApplicationEnvironmentCalculator;
import org.cloudfoundry.multiapps.controller.process.variables.Variables;
import org.cloudfoundry.multiapps.mta.handlers.HandlerFactory;
import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor;
import org.cloudfoundry.multiapps.mta.model.Module;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;

@Named("prepareToStopDependentModuleStep")
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class PrepareToStopDependentModuleStep extends SyncFlowableStep {

@Inject
private ModuleToDeployHelper moduleToDeployHelper;

@Inject
private ApplicationEnvironmentCalculator applicationEnvironmentCalculator;

@Override
protected StepPhase executeStep(ProcessContext context) {
Module applicationModule = findModuleInDeploymentDescriptor(context, getCurrentModuleToStop(context).getName());
context.setVariable(Variables.MODULE_TO_DEPLOY, applicationModule);
CloudApplicationExtended modifiedApp = getApplicationCloudModelBuilder(context).build(applicationModule, moduleToDeployHelper);
buildAdditionalModuleParametersReporter(context).reportUsageOfAdditionalParameters(applicationModule);
Staging stagingWithUpdatedHealthCheck = modifyHealthCheckType(modifiedApp.getStaging());
Map<String, String> calculatedAppEnv = applicationEnvironmentCalculator.calculateNewApplicationEnv(context, modifiedApp);
modifiedApp = ImmutableCloudApplicationExtended.builder()
.from(modifiedApp)
.staging(stagingWithUpdatedHealthCheck)
.routes(getApplicationRoutes(context, modifiedApp))
.env(calculatedAppEnv)
.build();
context.setVariable(Variables.APP_TO_PROCESS, modifiedApp);
return StepPhase.DONE;
}

protected ApplicationCloudModelBuilder getApplicationCloudModelBuilder(ProcessContext context) {
return StepsUtil.getApplicationCloudModelBuilder(context);
}

@Override
protected String getStepErrorMessage(ProcessContext context) {
return MessageFormat.format("Failed to stop dependent module", context.getVariable(Variables.APP_TO_PROCESS)
.getName());
}

private Module getCurrentModuleToStop(ProcessContext context) {
List<Module> modules = context.getVariable(Variables.DEPENDENT_MODULES_TO_STOP);
int index = context.getVariable(Variables.APPS_TO_STOP_INDEX);
return modules.get(index);
}

private Module findModuleInDeploymentDescriptor(ProcessContext context, String module) {
HandlerFactory handlerFactory = StepsUtil.getHandlerFactory(context.getExecution());
DeploymentDescriptor deploymentDescriptor = context.getVariable(Variables.COMPLETE_DEPLOYMENT_DESCRIPTOR);
return handlerFactory.getDescriptorHandler()
.findModule(deploymentDescriptor, module);
}

private AdditionalModuleParametersReporter buildAdditionalModuleParametersReporter(ProcessContext context) {
return new AdditionalModuleParametersReporter(context);
}

private Staging modifyHealthCheckType(Staging staging) {
String healthCheckType = staging.getHealthCheckType();
if (StringUtils.isEmpty(healthCheckType)) {
getStepLogger().debug(Messages.NOT_SPECIFIED_HEALTH_CHECK_TYPE, HealthCheckType.PORT.getValue());
return ImmutableStaging.copyOf(staging)
.withHealthCheckType(HealthCheckType.PORT.getValue());
}
if (HealthCheckType.NONE.getValue()
.equalsIgnoreCase(healthCheckType)) {
getStepLogger().info(Messages.USING_DEPRECATED_HEALTH_CHECK_TYPE_0_SETTING_TO_1, healthCheckType,
HealthCheckType.PROCESS.getValue());
return ImmutableStaging.copyOf(staging)
.withHealthCheckType(HealthCheckType.PROCESS.getValue());
}
return staging;
}

private Set<CloudRoute> getApplicationRoutes(ProcessContext context, CloudApplicationExtended modifiedApp) {
if (context.getVariable(Variables.USE_IDLE_URIS)) {
return modifiedApp.getIdleRoutes();
}
return modifiedApp.getRoutes();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.cloudfoundry.multiapps.controller.process.steps;

import java.text.MessageFormat;
import java.util.List;

import jakarta.inject.Named;
import org.cloudfoundry.multiapps.controller.core.model.SubprocessPhase;
import org.cloudfoundry.multiapps.controller.process.variables.Variables;
import org.cloudfoundry.multiapps.mta.model.Module;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;

@Named("prepareToStopDependentModulesStep")
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class PrepareToStopDependentModulesStep extends SyncFlowableStep {

@Override
protected StepPhase executeStep(ProcessContext context) {
List<Module> dependentModulesToStop = context.getVariable(Variables.DEPENDENT_MODULES_TO_STOP);
context.setVariable(Variables.APPS_TO_STOP_COUNT, dependentModulesToStop.size());
context.setVariable(Variables.APPS_TO_STOP_INDEX, 0);
context.setVariable(Variables.INDEX_VARIABLE_NAME, Variables.APPS_TO_STOP_INDEX.getName());
context.setVariable(Variables.SUBPROCESS_PHASE, SubprocessPhase.BEFORE_APPLICATION_STOP);
return StepPhase.DONE;
}

@Override
protected String getStepErrorMessage(ProcessContext context) {
return MessageFormat.format("Failed to stop dependent modules", context.getVariable(Variables.APP_TO_PROCESS)
.getName());
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package org.cloudfoundry.multiapps.controller.process.steps;

import java.text.MessageFormat;
import java.util.List;

import jakarta.inject.Inject;
import jakarta.inject.Named;
import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient;
import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudApplication;
import org.cloudfoundry.multiapps.controller.core.helpers.ModuleToDeployHelper;
import org.cloudfoundry.multiapps.controller.core.model.BlueGreenApplicationNameSuffix;
import org.cloudfoundry.multiapps.controller.core.model.HookPhase;
import org.cloudfoundry.multiapps.controller.process.Messages;
import org.cloudfoundry.multiapps.controller.process.util.ApplicationWaitAfterStopHandler;
import org.cloudfoundry.multiapps.controller.process.variables.Variables;
import org.cloudfoundry.multiapps.mta.model.Module;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.context.annotation.Scope;

@Named("stopDependentModuleStep")
@Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class StopDependentModuleStep extends SyncFlowableStepWithHooks implements BeforeStepHookPhaseProvider, AfterStepHookPhaseProvider {

@Inject
private ModuleToDeployHelper moduleToDeployHelper;

@Inject
private ApplicationWaitAfterStopHandler waitAfterStopHandler;

@Override
protected StepPhase executeStepInternal(ProcessContext context) {
CloudControllerClient client = context.getControllerClient();
String idleName = getCurrentModuleToStop(context).getName() + BlueGreenApplicationNameSuffix.IDLE.asSuffix();
CloudApplication app = client.getApplication(idleName);
if (app != null && !app.getState()
.equals(CloudApplication.State.STOPPED)) {
client.stopApplication(idleName);
getStepLogger().info(Messages.APP_STOPPED, idleName);
}
waitAfterStopHandler.configureDelayAfterAppStop(context, idleName);
return StepPhase.DONE;
}

@Override
protected String getStepErrorMessage(ProcessContext context) {
return MessageFormat.format("Failed to stop dependent modules", getCurrentModuleToStop(context).getName());
}

static Module getCurrentModuleToStop(ProcessContext context) {
List<Module> modules = context.getVariable(Variables.DEPENDENT_MODULES_TO_STOP);
int index = context.getVariable(Variables.APPS_TO_STOP_INDEX);
return modules.get(index);
}

@Override
public List<HookPhase> getHookPhasesBeforeStep(ProcessContext context) {
List<HookPhase> hookPhases = List.of(HookPhase.BEFORE_STOP);
return hooksPhaseBuilder.buildHookPhases(hookPhases, context);
}

@Override
public List<HookPhase> getHookPhasesAfterStep(ProcessContext context) {
List<HookPhase> hookPhases = List.of(HookPhase.AFTER_STOP);
return hooksPhaseBuilder.buildHookPhases(hookPhases, context);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

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 @@ -14,8 +15,6 @@
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 Down Expand Up @@ -61,5 +60,5 @@ protected HooksExecutor getHooksExecutor(HooksCalculator hooksCalculator, Module
}

protected abstract StepPhase executeStepInternal(ProcessContext context);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package org.cloudfoundry.multiapps.controller.process.util;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import jakarta.inject.Named;
import org.cloudfoundry.multiapps.controller.core.model.SupportedParameters;
import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor;
import org.cloudfoundry.multiapps.mta.model.Module;
import org.cloudfoundry.multiapps.mta.util.PropertiesUtil;

@Named
public class DependentModuleStopResolver {

public List<Module> resolveDependentModulesToStop(Module root, DeploymentDescriptor descriptor) {
if (stopOrderShouldBeDependencyAware(descriptor)) {
Map<String, Module> modulesByName = descriptor.getModules()
.stream()
.collect(Collectors.toMap(Module::getName, Function.identity()));

List<Module> result = new ArrayList<>();
Set<String> visited = new HashSet<>();
collectDependentsPostOrder(root.getName(), modulesByName, visited, result);

result.remove(root);
return result;
}
return Collections.emptyList();
}

private void collectDependentsPostOrder(String targetName, Map<String, Module> modulesByName, Set<String> visited,
List<Module> result) {
for (Module module : modulesByName.values()) {
if (!supportsDeployedAfter(module)) {
continue;
}
if (!hasDeployedAfter(module, targetName)) {
continue;
}
if (visited.contains(module.getName())) {
continue;
}
visited.add(module.getName());
collectDependentsPostOrder(module.getName(), modulesByName, visited, result);
result.add(module);
}
}

private boolean stopOrderShouldBeDependencyAware(DeploymentDescriptor descriptor) {
return (boolean) PropertiesUtil.getPropertyValue(List.of(descriptor.getParameters()),
SupportedParameters.BG_DEPENDENCY_AWARE_STOP_ORDER, false);
}

private boolean supportsDeployedAfter(Module module) {
return module.getMajorSchemaVersion() >= 3;
}

private boolean hasDeployedAfter(Module module, String targetName) {
List<String> deployedAfterNames = module.getDeployedAfter();
return deployedAfterNames != null
&& !deployedAfterNames.isEmpty()
&& deployedAfterNames.stream()
.anyMatch(targetName::equals);
}
}
Loading