diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/lib/domain/CloudServiceInstanceExtended.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/lib/domain/CloudServiceInstanceExtended.java index 3e0c375695..d765de5bf8 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/lib/domain/CloudServiceInstanceExtended.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/lib/domain/CloudServiceInstanceExtended.java @@ -1,5 +1,7 @@ package org.cloudfoundry.multiapps.controller.client.lib.domain; +import java.time.Duration; + import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.cloudfoundry.multiapps.common.Nullable; @@ -53,4 +55,14 @@ public boolean shouldSkipSyslogUrlUpdate() { @Nullable public abstract Boolean shouldFailOnTagsUpdateFailure(); + @Nullable + public abstract Duration getCreateServiceTimeout(); + + @Nullable + public abstract Duration getBindServiceTimeout(); + + @Nullable + public abstract Duration getCreateServiceKeyTimeout(); + + } diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/Messages.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/Messages.java index 6efb7e7b3d..70e667845d 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/Messages.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/Messages.java @@ -290,6 +290,7 @@ public final class Messages { public static final String API_INFO_AUDIT_LOG_CONFIG = "Api info"; public static final String IGNORING_NAMESPACE_PARAMETERS = "Ignoring parameter \"{0}\" , as the MTA is not deployed with namespace!"; public static final String NAMESPACE_PARSING_ERROR_MESSAGE = "Cannot parse \"{0}\" flag - expected a boolean format."; + public static final String PARAMETER_0_MUST_BE_POSITIVE_WITH_MAX_VALUE_1 = "Parameter \"{0}\" must be positive integer value up to {1}!"; private Messages() { } diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServicesCloudModelBuilder.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServicesCloudModelBuilder.java index 90abef06be..dbe542c6f6 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServicesCloudModelBuilder.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/cf/v2/ServicesCloudModelBuilder.java @@ -1,12 +1,14 @@ package org.cloudfoundry.multiapps.controller.core.cf.v2; import java.text.MessageFormat; +import java.time.Duration; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.TreeMap; import java.util.stream.Collectors; + import org.cloudfoundry.client.v3.serviceinstances.ServiceInstanceType; import org.cloudfoundry.multiapps.common.ContentException; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; @@ -14,6 +16,7 @@ import org.cloudfoundry.multiapps.controller.core.Messages; import org.cloudfoundry.multiapps.controller.core.model.SupportedParameters; import org.cloudfoundry.multiapps.controller.core.util.CloudModelBuilderUtil; +import org.cloudfoundry.multiapps.controller.core.util.DurationUtil; import org.cloudfoundry.multiapps.controller.core.util.NameUtil; import org.cloudfoundry.multiapps.controller.core.util.SpecialResourceTypesRequiredParametersUtil; import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor; @@ -88,6 +91,9 @@ protected CloudServiceInstanceExtended createManagedService(Resource resource, C commonServiceParameters.failOnServiceParametersUpdateFailure()) .shouldFailOnPlanUpdateFailure(commonServiceParameters.failOnServicePlanUpdateFailure()) .shouldFailOnTagsUpdateFailure(commonServiceParameters.failOnServiceTagsUpdateFailure()) + .createServiceTimeout(extractTimeoutValue(parameters, "create-service-timeout")) + .bindServiceTimeout(extractTimeoutValue(parameters, "bind-service-timeout")) + .createServiceKeyTimeout(extractTimeoutValue(parameters, "create-service-key-timeout")) .v3Metadata(ServiceMetadataBuilder.build(deploymentDescriptor, namespace, resource)) .build(); } @@ -120,11 +126,15 @@ protected CloudServiceInstanceExtended createUserProvidedService(Resource resour commonServiceParameters.failOnServiceParametersUpdateFailure()) .shouldFailOnPlanUpdateFailure(commonServiceParameters.failOnServicePlanUpdateFailure()) .shouldFailOnTagsUpdateFailure(commonServiceParameters.failOnServiceTagsUpdateFailure()) + .createServiceTimeout(extractTimeoutValue(parameters, "create-service-timeout")) + .bindServiceTimeout(extractTimeoutValue(parameters, "bind-service-timeout")) + .createServiceKeyTimeout(extractTimeoutValue(parameters, "create-service-key-timeout")) .v3Metadata(ServiceMetadataBuilder.build(deploymentDescriptor, namespace, resource)) .build(); } protected CloudServiceInstanceExtended createExistingService(Resource resource, CommonServiceParameters commonServiceParameters) { + Map parameters = resource.getParameters(); return ImmutableCloudServiceInstanceExtended.builder() .name(commonServiceParameters.getServiceName()) .resourceName(resource.getName()) @@ -137,6 +147,9 @@ protected CloudServiceInstanceExtended createExistingService(Resource resource, commonServiceParameters.failOnServiceParametersUpdateFailure()) .shouldFailOnPlanUpdateFailure(commonServiceParameters.failOnServicePlanUpdateFailure()) .shouldFailOnTagsUpdateFailure(commonServiceParameters.failOnServiceTagsUpdateFailure()) + .createServiceTimeout(extractTimeoutValue(parameters, "create-service-timeout")) + .bindServiceTimeout(extractTimeoutValue(parameters, "bind-service-timeout")) + .createServiceKeyTimeout(extractTimeoutValue(parameters, "create-service-key-timeout")) .v3Metadata(ServiceMetadataBuilder.build(deploymentDescriptor, namespace, resource)) .build(); } @@ -215,4 +228,12 @@ private Boolean failOnServicePlanUpdateFailure() { } + private Duration extractTimeoutValue(Map parameters, String timeoutParameterName) { + if (parameters == null) { + return null; + } + return DurationUtil.parseDurationSafely(parameters.get(timeoutParameterName)) + .orElse(null); + } + } diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/model/SupportedParameters.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/model/SupportedParameters.java index 1e84c0acd4..51056e291d 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/model/SupportedParameters.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/model/SupportedParameters.java @@ -183,6 +183,13 @@ public class SupportedParameters { @Deprecated public static final String DEPRECATED_CONFIG_MTA_PROVIDES_DEPENDENCY = "mta-provides-dependency"; + public static final String CREATE_SERVICE_TIMEOUT = "create-service-timeout"; + public static final String SERVICES_CREATE_SERVICE_TIMEOUT = "services-create-service-timeout"; + public static final String BIND_SERVICE_TIMEOUT = "bind-service-timeout"; + public static final String SERVICES_BIND_SERVICE_TIMEOUT = "services-bind-service-timeout"; + public static final String CREATE_SERVICE_KEY_TIMEOUT = "create-service-key-timeout"; + public static final String SERVICES_CREATE_SERVICE_KEY_TIMEOUT = "services-create-service-key-timeout"; + public static final Set MODULE_PARAMETERS = Set.of(APP_NAME, APPLY_NAMESPACE, BUILDPACK, BUILDPACKS, LIFECYCLE, COMMAND, CREATE_SERVICE_BROKER, DEFAULT_APP_NAME, DEFAULT_HOST, DEFAULT_INSTANCES, DEFAULT_LIVE_APP_NAME, DEFAULT_LIVE_DOMAIN, DEFAULT_LIVE_HOST, @@ -210,10 +217,13 @@ public class SupportedParameters { SERVICE_KEY_NAME, SERVICE_NAME, SERVICE_PLAN, SERVICE_TAGS, SERVICE_BROKER, SKIP_SERVICE_UPDATES, TYPE, PROVIDER_ID, PROVIDER_NID, TARGET, SERVICE_CONFIG_PATH, FILTER, MANAGED, VERSION, PATH, MEMORY, - FAIL_ON_SERVICE_UPDATE, SERVICE_PROVIDER, SERVICE_VERSION); + FAIL_ON_SERVICE_UPDATE, SERVICE_PROVIDER, SERVICE_VERSION, + CREATE_SERVICE_TIMEOUT, BIND_SERVICE_TIMEOUT, CREATE_SERVICE_KEY_TIMEOUT); public static final Set 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, BG_DEPENDENCY_AWARE_STOP_ORDER); + ENABLE_PARALLEL_DEPLOYMENTS, DEPLOY_MODE, BG_DEPENDENCY_AWARE_STOP_ORDER, + SERVICES_CREATE_SERVICE_TIMEOUT, + SERVICES_BIND_SERVICE_TIMEOUT, SERVICES_CREATE_SERVICE_KEY_TIMEOUT); public static final Set DEPENDENCY_PARAMETERS = Set.of(BINDING_NAME, ENV_VAR_NAME, VISIBILITY, USE_LIVE_ROUTES, SERVICE_BINDING_CONFIG, DELETE_SERVICE_KEY_AFTER_DEPLOYMENT); diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/util/DurationUtil.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/util/DurationUtil.java new file mode 100644 index 0000000000..eab7b1b0c8 --- /dev/null +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/util/DurationUtil.java @@ -0,0 +1,37 @@ +package org.cloudfoundry.multiapps.controller.core.util; + +import java.time.Duration; +import java.util.Optional; + +import org.cloudfoundry.multiapps.common.ContentException; +import org.cloudfoundry.multiapps.controller.core.Messages; + +public final class DurationUtil { + + private DurationUtil() { + // utility class + } + + public static Duration parseDuration(Object timeout, String parameterName, int maxAllowedValue) { + if (timeout == null) { + return null; + } + if (!(timeout instanceof Number number)) { + throw new ContentException(Messages.PARAMETER_0_MUST_BE_POSITIVE_WITH_MAX_VALUE_1, parameterName, maxAllowedValue); + } + int value = number.intValue(); + if (value < 0 || value > maxAllowedValue) { + throw new ContentException(Messages.PARAMETER_0_MUST_BE_POSITIVE_WITH_MAX_VALUE_1, parameterName, maxAllowedValue); + } + return Duration.ofSeconds(value); + } + + public static Optional parseDurationSafely(Object timeout) { + if (timeout == null || !(timeout instanceof Number number)) { + return Optional.empty(); + } + long seconds = number.longValue(); + return seconds > 0 ? Optional.of(Duration.ofSeconds(seconds)) : Optional.empty(); + } +} + diff --git a/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/util/DurationUtilTest.java b/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/util/DurationUtilTest.java new file mode 100644 index 0000000000..f611451941 --- /dev/null +++ b/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/util/DurationUtilTest.java @@ -0,0 +1,89 @@ +package org.cloudfoundry.multiapps.controller.core.util; + +import java.time.Duration; +import java.util.Optional; + +import org.cloudfoundry.multiapps.common.ContentException; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class DurationUtilTest { + + private static final String PARAM_NAME = "test-timeout"; + private static final int MAX_VALUE = 3600; + + @Test + void testParseDurationWithValidValue() { + Duration result = DurationUtil.parseDuration(300, PARAM_NAME, MAX_VALUE); + assertEquals(Duration.ofSeconds(300), result); + } + + @Test + void testParseDurationWithZero() { + Duration result = DurationUtil.parseDuration(0, PARAM_NAME, MAX_VALUE); + assertEquals(Duration.ZERO, result); + } + + @Test + void testParseDurationWithMaxValue() { + Duration result = DurationUtil.parseDuration(MAX_VALUE, PARAM_NAME, MAX_VALUE); + assertEquals(Duration.ofSeconds(MAX_VALUE), result); + } + + @Test + void testParseDurationWithNull() { + Duration result = DurationUtil.parseDuration(null, PARAM_NAME, MAX_VALUE); + assertNull(result); + } + + @Test + void testParseDurationWithNegativeValue() { + assertThrows(ContentException.class, () -> DurationUtil.parseDuration(-1, PARAM_NAME, MAX_VALUE)); + } + + @Test + void testParseDurationWithValueExceedingMax() { + assertThrows(ContentException.class, () -> DurationUtil.parseDuration(MAX_VALUE + 1, PARAM_NAME, MAX_VALUE)); + } + + @Test + void testParseDurationWithNonNumber() { + assertThrows(ContentException.class, () -> DurationUtil.parseDuration("invalid", PARAM_NAME, MAX_VALUE)); + } + + @Test + void testParseDurationSafelyWithValidValue() { + Optional result = DurationUtil.parseDurationSafely(300); + assertTrue(result.isPresent()); + assertEquals(Duration.ofSeconds(300), result.get()); + } + + @Test + void testParseDurationSafelyWithNull() { + Optional result = DurationUtil.parseDurationSafely(null); + assertTrue(result.isEmpty()); + } + + @Test + void testParseDurationSafelyWithZero() { + Optional result = DurationUtil.parseDurationSafely(0); + assertTrue(result.isEmpty()); + } + + @Test + void testParseDurationSafelyWithNegative() { + Optional result = DurationUtil.parseDurationSafely(-5); + assertTrue(result.isEmpty()); + } + + @Test + void testParseDurationSafelyWithNonNumber() { + Optional result = DurationUtil.parseDurationSafely("invalid"); + assertTrue(result.isEmpty()); + } +} + diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/Constants.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/Constants.java index 2595cdd1c5..4b61e507c5 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/Constants.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/Constants.java @@ -1,5 +1,6 @@ package org.cloudfoundry.multiapps.controller.process; +import java.time.Duration; import java.util.regex.Pattern; public class Constants { @@ -45,6 +46,10 @@ public class Constants { public static final Pattern STANDARD_INT_PATTERN = Pattern.compile("[+-]?[0-9]+"); + public static final Duration DEFAULT_DELETE_SERVICE_TIMEOUT = Duration.ofMinutes(60); + public static final Duration DEFAULT_DELETE_SERVICE_KEY_TIMEOUT = Duration.ofMinutes(60); + public static final Duration DEFAULT_UNBIND_SERVICE_TIMEOUT = Duration.ofMinutes(60); + protected Constants() { } } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/Messages.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/Messages.java index 1a3fb90642..5a69a3482c 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/Messages.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/Messages.java @@ -101,6 +101,7 @@ public class Messages { public static final String ERROR_WHILE_DELETING_SERVICE_INSTANCE_METADATA_0 = "Error while deleting service instance metadata \"{0}\""; public static final String ERROR_WHILE_DELETING_SERVICE_KEY_0 = "Error while deleting service key \"{0}\""; public static final String ERROR_WHILE_CALCULATING_SERVICE_KEYS_FOR_WAITING = "Error while calculating service keys for waiting"; + public static final String SERVICE_KEY_NOT_FOUND = "Service key \"{0}\" for service \"{1}\" was not found and will be skipped"; public static final String COULD_NOT_GET_APP_LOGS = "Could not get application recent logs: {0}"; public static final String ERROR_DURING_INCREMENTAL_INSTANCE_UPDATE_OF_MODULE_0 = "Error during incremental instance update of module \"{0}\""; public static final String ERROR_DURING_POLL_OF_INCREMENTAL_INSTANCE_UPDATE_OF_MODULE_0 = "Error during poll of incremental instance update of module \"{0}\""; @@ -552,6 +553,8 @@ public class Messages { public static final String DELETING_ERROR_TYPE_O_FOR_PROCESS_1 = "Deleting error type \"{0}\" for process \"{1}\""; public static final String TIMEOUT_MESSAGE = "Application {0} timeout is set by \"{1}\" parameter to {2} seconds"; public static final String TIMEOUT_DEFAULT_VALUE_MESSAGE = "Application {0} timeout is default one: {1} seconds"; + public static final String OPERATION_TIMEOUT_MESSAGE = "{0} timeout is set by \"{1}\" parameter to {2} seconds"; + public static final String OPERATION_TIMEOUT_DEFAULT_VALUE_MESSAGE = "{0} timeout is default one: {1} seconds"; public static final String PROVIDED_EXTENSION_DESCRIPTORS = "Provided extension descriptors: {0}"; public static final String CREATED_SUBSCRIPTION = "Created subscription with ID: {0}"; public static final String UPDATING_SUBSCRIBER_0 = "Updating subscriber: {0} "; @@ -735,6 +738,7 @@ public class Messages { public static final String WILL_ONLY_REMOVE_SERVICE_INSTANCE_METADATA_BECAUSE_THE_SERVICE_TYPE_IS_EXISTING = "Will only remove service instance metadata, because the service type is \"existing\""; public static final String DETERMINING_DELETE_ACTIONS_FOR_SERVICE_INSTANCE_0 = "Determining delete actions for service instance \"{0}\""; public static final String CANNOT_RETRIEVE_OPTIONAL_SERVICE_BINDING_FOR_SERVICE_INSTANCE_0 = "Cannot retrieve optional service binding for service instance \"{0}\""; + public static final String CANNOT_RETRIEVE_SERVICE_BINDING_FOR_SERVICE_INSTANCE_0_NOT_FOUND = "Service instance \"{0}\" was not found; skipping service binding check."; public static final String SERVICE_KEYS_SCHEDULED_FOR_RECREATION_MODIFICATION_0 = "Service keys scheduled for recreation due to modification: \"{0}\""; public static final String SERVICE_KEYS_SCHEDULED_FOR_RECREATION_STATE_0 = "Service keys scheduled for recreation due to state: \"{0}\""; public static final String SERVICE_KEYS_SCHEDULED_FOR_CREATION_0 = "Service keys scheduled for creation: \"{0}\""; @@ -825,6 +829,18 @@ public class Messages { public static final String INVALID_BOOLEAN_VALUE = "Invalid boolean value: must be 'true' or 'false'"; public static final String DISABLE_AUTOSCALER_LABEL_CONTENT = "Disabled_by_MTA_operation_{0}"; + public static final String COULD_NOT_RESOLVE_SERVICE_RESOURCE_NAME_FOR_TIMEOUT_TYPE_0 = "Could not resolve service resource name for timeout type {0}"; + public static final String COULD_NOT_FIND_RESOURCE_0_IN_DESCRIPTOR_FOR_TIMEOUT_TYPE_1 = "Could not find resource {0} in deployment descriptor for timeout type {1}"; + public static final String EXTRACTING_ALL_TIMEOUT_PARAMETERS_FROM_DESCRIPTOR = "Extracting all timeout parameters from descriptor"; + public static final String NO_DESCRIPTOR_FOUND_USING_DEFAULT_TIMEOUTS = "No descriptor found; sub-processes will use default timeouts"; + public static final String SUCCESSFULLY_EXTRACTED_0_TIMEOUT_PARAMETERS = "Successfully extracted {0} timeout parameters from descriptor"; + public static final String TIMEOUT_0_EQUALS_1_SECONDS_FROM_2 = "Timeout {0} = {1}s (from {2})"; + public static final String FAILED_TO_RESOLVE_TIMEOUT_FOR_0_1 = "Failed to resolve timeout for {0}: {1}"; + public static final String ERROR_EXTRACTING_GLOBAL_TIMEOUTS_FROM_DESCRIPTOR = "Error while extracting global timeouts from deployment descriptor"; + public static final String COULD_NOT_RESOLVE_DESCRIPTOR_RESOURCE_FOR_TIMEOUT_TYPE_0_PARAMETER_1 = "Could not resolve descriptor resource for timeout type {0}; parameter {1} cannot be applied"; + public static final String DEPLOYMENT_DESCRIPTOR_MISSING_GLOBAL_PARAMETER_0_NOT_APPLIED_FOR_1 = "Deployment descriptor is missing; global parameter {0} cannot be applied for {1}"; + public static final String NO_DEPLOYMENT_DESCRIPTOR_FOUND_IN_CONTEXT = "No deployment descriptor found in context"; + protected Messages() { } } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/BlueGreenDeployMetadata.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/BlueGreenDeployMetadata.java index b996d7c06a..08daccb6dc 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/BlueGreenDeployMetadata.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/BlueGreenDeployMetadata.java @@ -130,6 +130,24 @@ public static OperationMetadata getMetadata() { .customConverter(new TimeoutParameterConverter( Variables.APPS_TASK_EXECUTION_TIMEOUT_PROCESS_VARIABLE)) .build()) + .addParameter(ImmutableParameterMetadata.builder() + .id(Variables.CREATE_SERVICE_TIMEOUT_PROCESS_VARIABLE.getName()) + .type(ParameterType.INTEGER) + .customConverter(new TimeoutParameterConverter( + Variables.CREATE_SERVICE_TIMEOUT_PROCESS_VARIABLE)) + .build()) + .addParameter(ImmutableParameterMetadata.builder() + .id(Variables.BIND_SERVICE_TIMEOUT_PROCESS_VARIABLE.getName()) + .type(ParameterType.INTEGER) + .customConverter(new TimeoutParameterConverter( + Variables.BIND_SERVICE_TIMEOUT_PROCESS_VARIABLE)) + .build()) + .addParameter(ImmutableParameterMetadata.builder() + .id(Variables.CREATE_SERVICE_KEY_TIMEOUT_PROCESS_VARIABLE.getName()) + .type(ParameterType.INTEGER) + .customConverter(new TimeoutParameterConverter( + Variables.CREATE_SERVICE_KEY_TIMEOUT_PROCESS_VARIABLE)) + .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.IS_SECURITY_ENABLED.getName()) .type(ParameterType.BOOLEAN) diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/CtsDeployMetadata.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/CtsDeployMetadata.java index ff2131283c..49945708a6 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/CtsDeployMetadata.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/CtsDeployMetadata.java @@ -135,6 +135,24 @@ public static OperationMetadata getMetadata() { .customConverter(new TimeoutParameterConverter( Variables.APPS_UPLOAD_TIMEOUT_PROCESS_VARIABLE)) .build()) + .addParameter(ImmutableParameterMetadata.builder() + .id(Variables.CREATE_SERVICE_TIMEOUT_PROCESS_VARIABLE.getName()) + .type(ParameterType.INTEGER) + .customConverter(new TimeoutParameterConverter( + Variables.CREATE_SERVICE_TIMEOUT_PROCESS_VARIABLE)) + .build()) + .addParameter(ImmutableParameterMetadata.builder() + .id(Variables.BIND_SERVICE_TIMEOUT_PROCESS_VARIABLE.getName()) + .type(ParameterType.INTEGER) + .customConverter(new TimeoutParameterConverter( + Variables.BIND_SERVICE_TIMEOUT_PROCESS_VARIABLE)) + .build()) + .addParameter(ImmutableParameterMetadata.builder() + .id(Variables.CREATE_SERVICE_KEY_TIMEOUT_PROCESS_VARIABLE.getName()) + .type(ParameterType.INTEGER) + .customConverter(new TimeoutParameterConverter( + Variables.CREATE_SERVICE_KEY_TIMEOUT_PROCESS_VARIABLE)) + .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPS_TASK_EXECUTION_TIMEOUT_PROCESS_VARIABLE.getName()) .type(ParameterType.INTEGER) diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/DeployMetadata.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/DeployMetadata.java index 9e51a153ec..65f554ade1 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/DeployMetadata.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/DeployMetadata.java @@ -124,6 +124,24 @@ public static OperationMetadata getMetadata() { .customConverter(new TimeoutParameterConverter( Variables.APPS_UPLOAD_TIMEOUT_PROCESS_VARIABLE)) .build()) + .addParameter(ImmutableParameterMetadata.builder() + .id(Variables.CREATE_SERVICE_TIMEOUT_PROCESS_VARIABLE.getName()) + .type(ParameterType.INTEGER) + .customConverter(new TimeoutParameterConverter( + Variables.CREATE_SERVICE_TIMEOUT_PROCESS_VARIABLE)) + .build()) + .addParameter(ImmutableParameterMetadata.builder() + .id(Variables.BIND_SERVICE_TIMEOUT_PROCESS_VARIABLE.getName()) + .type(ParameterType.INTEGER) + .customConverter(new TimeoutParameterConverter( + Variables.BIND_SERVICE_TIMEOUT_PROCESS_VARIABLE)) + .build()) + .addParameter(ImmutableParameterMetadata.builder() + .id(Variables.CREATE_SERVICE_KEY_TIMEOUT_PROCESS_VARIABLE.getName()) + .type(ParameterType.INTEGER) + .customConverter(new TimeoutParameterConverter( + Variables.CREATE_SERVICE_KEY_TIMEOUT_PROCESS_VARIABLE)) + .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPS_TASK_EXECUTION_TIMEOUT_PROCESS_VARIABLE.getName()) .type(ParameterType.INTEGER) diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/RollbackMtaMetadata.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/RollbackMtaMetadata.java index c9abbd7e8e..42886d95ca 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/RollbackMtaMetadata.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/RollbackMtaMetadata.java @@ -64,6 +64,24 @@ public static OperationMetadata getMetadata() { .customConverter(new TimeoutParameterConverter( Variables.APPS_UPLOAD_TIMEOUT_PROCESS_VARIABLE)) .build()) + .addParameter(ImmutableParameterMetadata.builder() + .id(Variables.CREATE_SERVICE_TIMEOUT_PROCESS_VARIABLE.getName()) + .type(ParameterType.INTEGER) + .customConverter(new TimeoutParameterConverter( + Variables.CREATE_SERVICE_TIMEOUT_PROCESS_VARIABLE)) + .build()) + .addParameter(ImmutableParameterMetadata.builder() + .id(Variables.BIND_SERVICE_TIMEOUT_PROCESS_VARIABLE.getName()) + .type(ParameterType.INTEGER) + .customConverter(new TimeoutParameterConverter( + Variables.BIND_SERVICE_TIMEOUT_PROCESS_VARIABLE)) + .build()) + .addParameter(ImmutableParameterMetadata.builder() + .id(Variables.CREATE_SERVICE_KEY_TIMEOUT_PROCESS_VARIABLE.getName()) + .type(ParameterType.INTEGER) + .customConverter(new TimeoutParameterConverter( + Variables.CREATE_SERVICE_KEY_TIMEOUT_PROCESS_VARIABLE)) + .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPS_TASK_EXECUTION_TIMEOUT_PROCESS_VARIABLE.getName()) .type(ParameterType.INTEGER) diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BindServiceToApplicationStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BindServiceToApplicationStep.java index b5ffe14295..509ae68b45 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BindServiceToApplicationStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BindServiceToApplicationStep.java @@ -1,6 +1,7 @@ package org.cloudfoundry.multiapps.controller.process.steps; import java.text.MessageFormat; +import java.time.Duration; import java.util.List; import java.util.Map; import java.util.Optional; @@ -13,13 +14,14 @@ import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.util.DefaultApplicationServicesUpdateCallback; import org.cloudfoundry.multiapps.controller.process.util.ServiceBindingPollingFactory; +import org.cloudfoundry.multiapps.controller.process.util.TimeoutType; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; @Named("bindServiceToApplicationStep") @Scope(BeanDefinition.SCOPE_PROTOTYPE) -public class BindServiceToApplicationStep extends AsyncFlowableStep { +public class BindServiceToApplicationStep extends TimeoutAsyncFlowableStep { @Override protected StepPhase executeAsyncStep(ProcessContext context) throws Exception { @@ -71,4 +73,8 @@ protected String getStepErrorMessage(ProcessContext context) { .getName()); } + @Override + public Duration getTimeout(ProcessContext context) { + return calculateTimeout(context, TimeoutType.BIND_SERVICE); + } } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CheckServiceBindingOperationStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CheckServiceBindingOperationStep.java index 2972d915ad..10a9e69a52 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CheckServiceBindingOperationStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CheckServiceBindingOperationStep.java @@ -1,6 +1,7 @@ package org.cloudfoundry.multiapps.controller.process.steps; import java.text.MessageFormat; +import java.time.Duration; import java.util.Collections; import java.util.List; import java.util.UUID; @@ -13,13 +14,14 @@ import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudApplicationExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.process.Messages; +import org.cloudfoundry.multiapps.controller.process.util.TimeoutType; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; @Named("checkServiceBindingOperationStep") @Scope(BeanDefinition.SCOPE_PROTOTYPE) -public class CheckServiceBindingOperationStep extends AsyncFlowableStep { +public class CheckServiceBindingOperationStep extends TimeoutAsyncFlowableStep { @Override protected StepPhase executeAsyncStep(ProcessContext context) throws Exception { @@ -108,4 +110,9 @@ protected String getStepErrorMessage(ProcessContext context) { return MessageFormat.format(Messages.ERROR_WHILE_CHECKING_SERVICE_BINDING_OPERATIONS_BETWEEN_APP_0_AND_SERVICE_INSTANCE_1, app.getName(), serviceInstanceName); } + + @Override + public Duration getTimeout(ProcessContext context) { + return calculateTimeout(context, TimeoutType.BIND_SERVICE); + } } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CheckServiceKeyOperationStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CheckServiceKeyOperationStep.java index de465222b5..f10d2e7ced 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CheckServiceKeyOperationStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CheckServiceKeyOperationStep.java @@ -1,6 +1,7 @@ package org.cloudfoundry.multiapps.controller.process.steps; import java.text.MessageFormat; +import java.time.Duration; import java.util.List; import jakarta.inject.Named; @@ -8,13 +9,14 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceCredentialBindingOperation; import org.cloudfoundry.multiapps.controller.process.Messages; +import org.cloudfoundry.multiapps.controller.process.util.TimeoutType; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; @Named("checkServiceKeyOperationStep") @Scope(BeanDefinition.SCOPE_PROTOTYPE) -public class CheckServiceKeyOperationStep extends AsyncFlowableStep { +public class CheckServiceKeyOperationStep extends TimeoutAsyncFlowableStep { @Override protected StepPhase executeAsyncStep(ProcessContext context) throws Exception { @@ -51,4 +53,9 @@ protected String getStepErrorMessage(ProcessContext context) { CloudServiceKey serviceKeyToProcess = context.getVariable(Variables.SERVICE_KEY_TO_PROCESS); return MessageFormat.format(Messages.ERROR_WHILE_CHECKING_SERVICE_KEY_OPERATION_0, serviceKeyToProcess.getName()); } + + @Override + public Duration getTimeout(ProcessContext context) { + return calculateTimeout(context, TimeoutType.BIND_SERVICE); + } } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CreateServiceKeyStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CreateServiceKeyStep.java index 69d45976e7..4f6c949ff8 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CreateServiceKeyStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CreateServiceKeyStep.java @@ -1,6 +1,7 @@ package org.cloudfoundry.multiapps.controller.process.steps; import java.text.MessageFormat; +import java.time.Duration; import java.util.List; import java.util.Optional; @@ -13,6 +14,7 @@ import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.util.ServiceKeyPollingFactory; +import org.cloudfoundry.multiapps.controller.process.util.TimeoutType; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; @@ -20,7 +22,7 @@ @Named("createServiceKeyStep") @Scope(BeanDefinition.SCOPE_PROTOTYPE) -public class CreateServiceKeyStep extends AsyncFlowableStep { +public class CreateServiceKeyStep extends TimeoutAsyncFlowableStep { @Override protected StepPhase executeAsyncStep(ProcessContext context) throws Exception { @@ -81,4 +83,8 @@ protected String getStepErrorMessage(ProcessContext context) { return MessageFormat.format(Messages.ERROR_WHILE_CREATING_SERVICE_KEY_0, serviceKeyToCreate.getName()); } + @Override + public Duration getTimeout(ProcessContext context) { + return calculateTimeout(context, TimeoutType.CREATE_SERVICE_KEY); + } } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CreateServiceStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CreateServiceStep.java index 86606d1120..ee3d51812e 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CreateServiceStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CreateServiceStep.java @@ -1,6 +1,7 @@ package org.cloudfoundry.multiapps.controller.process.steps; import java.text.MessageFormat; +import java.time.Duration; import java.util.List; import java.util.Optional; @@ -15,6 +16,7 @@ import org.cloudfoundry.multiapps.controller.process.util.DynamicResolvableParametersContextUpdater; import org.cloudfoundry.multiapps.controller.process.util.ExceptionMessageTailMapper; import org.cloudfoundry.multiapps.controller.process.util.ExceptionMessageTailMapper.CloudComponents; +import org.cloudfoundry.multiapps.controller.process.util.TimeoutType; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; @@ -119,4 +121,9 @@ protected String getStepErrorMessageAdditionalDescription(ProcessContext context return ExceptionMessageTailMapper.map(configuration, CloudComponents.SERVICE_BROKERS, offering); } + @Override + public Duration getTimeout(ProcessContext context) { + return calculateTimeout(context, TimeoutType.CREATE_SERVICE); + } + } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DeleteServiceKeyStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DeleteServiceKeyStep.java index 9740690372..d9ec486f91 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DeleteServiceKeyStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DeleteServiceKeyStep.java @@ -1,6 +1,7 @@ package org.cloudfoundry.multiapps.controller.process.steps; import java.text.MessageFormat; +import java.time.Duration; import java.util.List; import java.util.Optional; @@ -11,6 +12,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceCredentialBindingOperation; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; +import org.cloudfoundry.multiapps.controller.process.Constants; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.util.ServiceKeyPollingFactory; import org.cloudfoundry.multiapps.controller.process.variables.Variables; @@ -20,7 +22,7 @@ @Named("deleteServiceKeyStep") @Scope(BeanDefinition.SCOPE_PROTOTYPE) -public class DeleteServiceKeyStep extends AsyncFlowableStep { +public class DeleteServiceKeyStep extends TimeoutAsyncFlowableStep { @Override protected StepPhase executeAsyncStep(ProcessContext context) throws Exception { @@ -94,4 +96,8 @@ protected String getStepErrorMessage(ProcessContext context) { return MessageFormat.format(Messages.ERROR_WHILE_DELETING_SERVICE_KEY_0, serviceKeyToDelete.getName()); } + @Override + public Duration getTimeout(ProcessContext context) { + return Constants.DEFAULT_DELETE_SERVICE_KEY_TIMEOUT; + } } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DeleteServiceStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DeleteServiceStep.java index 9835a9ced2..ce827cc8be 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DeleteServiceStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DeleteServiceStep.java @@ -1,5 +1,6 @@ package org.cloudfoundry.multiapps.controller.process.steps; +import java.time.Duration; import java.util.Collections; import java.util.List; import java.util.Map; @@ -11,6 +12,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceOperation; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudServiceInstanceExtended; +import org.cloudfoundry.multiapps.controller.process.Constants; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.util.ExceptionMessageTailMapper; import org.cloudfoundry.multiapps.controller.process.util.ExceptionMessageTailMapper.CloudComponents; @@ -23,7 +25,7 @@ @Named("deleteServiceStep") @Scope(BeanDefinition.SCOPE_PROTOTYPE) -public class DeleteServiceStep extends AsyncFlowableStep { +public class DeleteServiceStep extends TimeoutAsyncFlowableStep { private final ServiceOperationGetter serviceOperationGetter; private final ServiceProgressReporter serviceProgressReporter; @@ -79,4 +81,8 @@ protected List getAsyncStepExecutions(ProcessContext context) { return Collections.singletonList(new PollServiceDeleteOperationsExecution(serviceOperationGetter, serviceProgressReporter)); } + @Override + public Duration getTimeout(ProcessContext context) { + return Constants.DEFAULT_DELETE_SERVICE_TIMEOUT; + } } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/GlobalTimeoutSettingStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/GlobalTimeoutSettingStep.java new file mode 100644 index 0000000000..32e44fa6ed --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/GlobalTimeoutSettingStep.java @@ -0,0 +1,92 @@ +package org.cloudfoundry.multiapps.controller.process.steps; + +import java.time.Duration; + +import jakarta.inject.Inject; +import jakarta.inject.Named; +import org.cloudfoundry.multiapps.common.ContentException; +import org.cloudfoundry.multiapps.controller.process.Messages; +import org.cloudfoundry.multiapps.controller.process.util.TimeoutType; +import org.cloudfoundry.multiapps.controller.process.util.TimeoutValueResolver; +import org.cloudfoundry.multiapps.controller.process.variables.Variable; +import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.context.annotation.Scope; + +@Named("globalTimeoutSettingStep") +@Scope(BeanDefinition.SCOPE_PROTOTYPE) +public class GlobalTimeoutSettingStep extends SyncFlowableStep { + + @Inject + private TimeoutValueResolver timeoutValueResolver; + + @Override + protected StepPhase executeStep(ProcessContext context) { + getStepLogger().debug(Messages.EXTRACTING_ALL_TIMEOUT_PARAMETERS_FROM_DESCRIPTOR); + + DeploymentDescriptor descriptor = timeoutValueResolver.getDeploymentDescriptor(context, getStepLogger()); + if (descriptor == null) { + getStepLogger().debug(Messages.NO_DESCRIPTOR_FOUND_USING_DEFAULT_TIMEOUTS); + return StepPhase.DONE; + } + + int successCount = 0; + for (TimeoutType timeoutType : TimeoutType.values()) { + if (setTimeoutIfResolved(context, timeoutType)) { + successCount++; + } + } + + getStepLogger().debug(Messages.SUCCESSFULLY_EXTRACTED_0_TIMEOUT_PARAMETERS, successCount); + return StepPhase.DONE; + } + + private boolean setTimeoutIfResolved(ProcessContext context, TimeoutType timeoutType) { + try { + if (isAlreadySetFromOperationParams(context, timeoutType)) { + logExistingOperationParamsTimeout(context, timeoutType); + return true; + } + return resolveAndSetTimeout(context, timeoutType); + } catch (ContentException e) { + getStepLogger().warn(Messages.FAILED_TO_RESOLVE_TIMEOUT_FOR_0_1, timeoutType, e.getMessage()); + return false; + } + } + + private boolean isAlreadySetFromOperationParams(ProcessContext context, TimeoutType timeoutType) { + if (!timeoutType.isServiceScoped()) { + return false; + } + Variable operationParamsFlag = timeoutType.getOperationParamsFlag(); + return operationParamsFlag != null && Boolean.TRUE.equals(context.getVariableIfSet(operationParamsFlag)); + } + + private void logExistingOperationParamsTimeout(ProcessContext context, TimeoutType timeoutType) { + Duration timeout = context.getVariableIfSet(timeoutType.getProcessVariable()); + String timeoutSeconds = timeout != null ? String.valueOf(timeout.toSeconds()) : "null"; + getStepLogger().debug(Messages.TIMEOUT_0_EQUALS_1_SECONDS_FROM_2, + timeoutType.getProcessVariable() + .getName(), + timeoutSeconds, + timeoutType.getGlobalLevelParamName()); + } + + private boolean resolveAndSetTimeout(ProcessContext context, TimeoutType timeoutType) { + TimeoutValueResolver.TimeoutResolution resolution = timeoutValueResolver.resolveTimeout(context, timeoutType, getStepLogger()); + context.setVariable(timeoutType.getProcessVariable(), resolution.timeout()); + getStepLogger().debug(Messages.TIMEOUT_0_EQUALS_1_SECONDS_FROM_2, + timeoutType.getProcessVariable() + .getName(), + resolution.timeout() + .toSeconds(), + resolution.parameterName()); + return true; + } + + @Override + protected String getStepErrorMessage(ProcessContext context) { + return Messages.ERROR_EXTRACTING_GLOBAL_TIMEOUTS_FROM_DESCRIPTOR; + } +} + diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ServiceStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ServiceStep.java index cfb562278e..cb9aa6a1a7 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ServiceStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ServiceStep.java @@ -1,6 +1,7 @@ package org.cloudfoundry.multiapps.controller.process.steps; import java.text.MessageFormat; +import java.time.Duration; import java.util.HashMap; import java.util.Map; @@ -16,10 +17,11 @@ import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.util.ServiceOperationGetter; import org.cloudfoundry.multiapps.controller.process.util.ServiceProgressReporter; +import org.cloudfoundry.multiapps.controller.process.util.TimeoutType; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.springframework.http.HttpStatus; -public abstract class ServiceStep extends AsyncFlowableStep { +public abstract class ServiceStep extends TimeoutAsyncFlowableStep { @Inject private ServiceOperationGetter serviceOperationGetter; @@ -73,6 +75,11 @@ protected void processServiceActionFailure(ProcessContext context, CloudServiceI throw new CloudControllerException(e.getStatusCode(), e.getStatusText(), detailedMessage); } + @Override + public Duration getTimeout(ProcessContext context) { + return calculateTimeout(context, TimeoutType.CREATE_SERVICE); + } + protected abstract ServiceOperation.Type getOperationType(); protected ServiceOperationGetter getServiceOperationGetter() { diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/TimeoutAsyncFlowableStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/TimeoutAsyncFlowableStep.java index a052583ba8..1bf516f5b1 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/TimeoutAsyncFlowableStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/TimeoutAsyncFlowableStep.java @@ -3,133 +3,71 @@ import java.text.MessageFormat; import java.time.Duration; -import org.cloudfoundry.multiapps.common.ContentException; +import jakarta.inject.Inject; import org.cloudfoundry.multiapps.common.SLException; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudApplicationExtended; -import org.cloudfoundry.multiapps.controller.core.helpers.ApplicationAttributes; -import org.cloudfoundry.multiapps.controller.process.Constants; +import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.process.Messages; +import org.cloudfoundry.multiapps.controller.process.util.TimeoutStepStateManager; import org.cloudfoundry.multiapps.controller.process.util.TimeoutType; +import org.cloudfoundry.multiapps.controller.process.util.TimeoutValueResolver; +import org.cloudfoundry.multiapps.controller.process.util.TimeoutValueResolver.TimeoutResolution; import org.cloudfoundry.multiapps.controller.process.variables.Variables; -import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor; public abstract class TimeoutAsyncFlowableStep extends AsyncFlowableStep { + private static final String DEFAULT_TIMEOUT = "default"; + private final TimeoutStepStateManager timeoutStepStateManager = new TimeoutStepStateManager(); + + @Inject + protected TimeoutValueResolver timeoutValueResolver; + @Override public StepPhase executeStep(ProcessContext context) throws Exception { - boolean hasTimedOut = hasTimedOut(context); - if (hasTimedOut) { - throw new SLException(MessageFormat.format(Messages.EXECUTION_OF_STEP_HAS_TIMED_OUT, getStepName())); + if (timeoutStepStateManager.hasTimedOut(context, getClass().getSimpleName(), getTimeout(context))) { + throw new SLException(MessageFormat.format(Messages.EXECUTION_OF_STEP_HAS_TIMED_OUT, getClass().getSimpleName())); } return super.executeStep(context); } - private boolean hasTimedOut(ProcessContext context) { - long stepStartTime = getStepStartTime(context); - long currentTime = System.currentTimeMillis(); - return (currentTime - stepStartTime) >= getTimeout(context).toMillis(); - } - - private long getStepStartTime(ProcessContext context) { - Long stepStartTime = (Long) context.getExecution() - .getVariable(getStepStartTimeVariable()); - if (stepStartTime == null || isInRetry(context)) { - stepStartTime = System.currentTimeMillis(); - 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; - } - - private boolean isInRetry(ProcessContext context) { - return context.getVariable(Variables.STEP_PHASE) == StepPhase.RETRY; - } - - private String getStepStartTimeVariable() { - return Constants.VAR_STEP_START_TIME + getStepName(); - } - - private String getStepName() { - return getClass().getSimpleName(); - } - - public abstract Duration getTimeout(ProcessContext context); - protected Duration calculateTimeout(ProcessContext context, TimeoutType timeoutType) { - Duration timeoutFinal = context.getVariableIfSet(timeoutType.getProcessVariable()); - String timeoutParameterName = timeoutType.getProcessVariableAndGlobalLevelParamName(); - - if (timeoutFinal == null) { - timeoutFinal = extractTimeoutFromAppAttributes(context, timeoutType); - if (timeoutFinal != null) { - timeoutParameterName = timeoutType.getModuleLevelParamName(); - } - } - - if (timeoutFinal == null) { - timeoutFinal = extractTimeoutFromDescriptorParameters(context, timeoutType); - } - - if (timeoutFinal == null) { - timeoutFinal = Duration.ofSeconds(timeoutType.getProcessVariable() - .getDefaultValue() - .getSeconds()); - timeoutParameterName = DEFAULT_TIMEOUT; - } - - logTimeout(timeoutType.getModuleLevelParamName(), timeoutParameterName, timeoutFinal.toSeconds()); - return timeoutFinal; - } - - private Duration extractTimeoutFromAppAttributes(ProcessContext context, TimeoutType timeoutType) { - CloudApplicationExtended app = context.getVariable(Variables.APP_TO_PROCESS); - ApplicationAttributes appAttributes = ApplicationAttributes.fromApplication(app, app.getEnv()); - Number timeout = appAttributes.get(timeoutType.getModuleLevelParamName(), Number.class); - if (timeout != null && (timeout.intValue() < 0 || timeout.intValue() > timeoutType.getMaxAllowedValue())) { - throw new ContentException(Messages.PARAMETER_0_MUST_BE_POSITIVE_WITH_MAX_VALUE_1, - timeoutType.getModuleLevelParamName(), - timeoutType.getMaxAllowedValue()); - } - return timeout != null ? Duration.ofSeconds(timeout.intValue()) : null; - } - - private Duration extractTimeoutFromDescriptorParameters(ProcessContext context, TimeoutType timeoutType) { - DeploymentDescriptor descriptor = context.getVariable(Variables.DEPLOYMENT_DESCRIPTOR); - Object timeoutGlobalLevelValue = descriptor.getParameters() - .get(timeoutType.getProcessVariableAndGlobalLevelParamName()); - validateTimeoutGlobalValue(timeoutGlobalLevelValue, timeoutType); - return timeoutGlobalLevelValue != null ? Duration.ofSeconds((Integer) timeoutGlobalLevelValue) : null; + TimeoutResolution resolution = timeoutValueResolver.resolveTimeout(context, timeoutType, getStepLogger()); + logTimeout(context, timeoutType, resolution); + return resolution.timeout(); } - private void validateTimeoutGlobalValue(Object timeout, TimeoutType timeoutType) { - if (timeout != null && (timeout instanceof String || ((Number) timeout).intValue() < 0 - || ((Number) timeout).intValue() > timeoutType.getMaxAllowedValue())) { - throw new ContentException(Messages.PARAMETER_0_MUST_BE_POSITIVE_WITH_MAX_VALUE_1, - timeoutType.getProcessVariableAndGlobalLevelParamName(), - timeoutType.getMaxAllowedValue()); + private void logTimeout(ProcessContext context, TimeoutType timeoutType, TimeoutResolution resolution) { + String operation = resolveOperationName(context, timeoutType); + long seconds = resolution.timeout().toSeconds(); + if (DEFAULT_TIMEOUT.equals(resolution.parameterName())) { + getStepLogger().debug(Messages.OPERATION_TIMEOUT_DEFAULT_VALUE_MESSAGE, operation, seconds); + } else { + getStepLogger().debug(Messages.OPERATION_TIMEOUT_MESSAGE, operation, resolution.parameterName(), seconds); } } - private void logTimeout(String moduleLevelParameterName, String timeoutParameterName, Number timeout) { - String timeoutTypeName = processString(moduleLevelParameterName); - if (timeoutParameterName.equals(DEFAULT_TIMEOUT)) { - getStepLogger().debug(Messages.TIMEOUT_DEFAULT_VALUE_MESSAGE, timeoutTypeName, timeout); - } else { - getStepLogger().debug(Messages.TIMEOUT_MESSAGE, timeoutTypeName, timeoutParameterName, timeout); - } + private String resolveOperationName(ProcessContext context, TimeoutType timeoutType) { + return switch (timeoutType) { + case UPLOAD, STAGE, START, TASK -> { + CloudApplicationExtended app = context.getVariable(Variables.APP_TO_PROCESS); + yield app != null ? "Application " + app.getName() : "Application"; + } + case BIND_SERVICE -> { + String service = context.getVariable(Variables.SERVICE_TO_UNBIND_BIND); + yield service != null ? "Service binding for " + service : "Service binding"; + } + case CREATE_SERVICE -> formatServiceOperation("Service creation", context); + case CREATE_SERVICE_KEY -> formatServiceOperation("Service key creation", context); + }; } - private String processString(String input) { - return input.replace("-", " ") - .replace("timeout", "") - .trim(); + private String formatServiceOperation(String operation, ProcessContext context) { + CloudServiceInstanceExtended service = context.getVariable(Variables.SERVICE_TO_PROCESS); + return service != null && service.getResourceName() != null + ? operation + " for " + service.getResourceName() + : operation; } + public abstract Duration getTimeout(ProcessContext context); } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/UnbindServiceFromApplicationStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/UnbindServiceFromApplicationStep.java index ababdea3c3..7fd9bce05b 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/UnbindServiceFromApplicationStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/UnbindServiceFromApplicationStep.java @@ -1,6 +1,7 @@ package org.cloudfoundry.multiapps.controller.process.steps; import java.text.MessageFormat; +import java.time.Duration; import java.util.List; import java.util.Optional; import java.util.function.Supplier; @@ -12,6 +13,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceCredentialBindingOperation; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudApplicationExtended; +import org.cloudfoundry.multiapps.controller.process.Constants; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.util.DeletingServiceBindingOperationCallback; import org.cloudfoundry.multiapps.controller.process.util.ServiceBindingPollingFactory; @@ -22,7 +24,7 @@ @Named("unbindServiceFromApplicationStep") @Scope(BeanDefinition.SCOPE_PROTOTYPE) -public class UnbindServiceFromApplicationStep extends AsyncFlowableStep { +public class UnbindServiceFromApplicationStep extends TimeoutAsyncFlowableStep { @Override protected StepPhase executeAsyncStep(ProcessContext context) throws Exception { @@ -104,4 +106,8 @@ private ServiceBindingOperationCallback getServiceBindingOperationCallback(Proce return new DeletingServiceBindingOperationCallback(context, controllerClient); } + @Override + public Duration getTimeout(ProcessContext context) { + return Constants.DEFAULT_UNBIND_SERVICE_TIMEOUT; + } } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/TimeoutServiceResourceNameResolver.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/TimeoutServiceResourceNameResolver.java new file mode 100644 index 0000000000..b84ad9ca2f --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/TimeoutServiceResourceNameResolver.java @@ -0,0 +1,105 @@ +package org.cloudfoundry.multiapps.controller.process.util; + +import java.util.List; +import java.util.Objects; + +import jakarta.inject.Named; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; +import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; +import org.cloudfoundry.multiapps.controller.core.model.DeployedMta; +import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaService; +import org.cloudfoundry.multiapps.controller.process.Messages; +import org.cloudfoundry.multiapps.controller.process.steps.ProcessContext; +import org.cloudfoundry.multiapps.controller.process.variables.Variable; +import org.cloudfoundry.multiapps.controller.process.variables.Variables; +import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor; +import org.cloudfoundry.multiapps.mta.model.Resource; + +@Named +public class TimeoutServiceResourceNameResolver { + + private static final List>> SERVICE_LIST_VARIABLES = List.of( + Variables.SERVICES_TO_BIND, + Variables.SERVICES_TO_CREATE, + Variables.SERVICES_TO_POLL); + + public Resource resolveResource(ProcessContext context, TimeoutType timeoutType, DeploymentDescriptor descriptor, StepLogger logger) { + String resourceName = resolveResourceName(context, timeoutType); + if (resourceName == null) { + logger.debug(Messages.COULD_NOT_RESOLVE_SERVICE_RESOURCE_NAME_FOR_TIMEOUT_TYPE_0, timeoutType); + return null; + } + Resource resource = findResourceByName(descriptor, resourceName); + if (resource == null) { + logger.debug(Messages.COULD_NOT_FIND_RESOURCE_0_IN_DESCRIPTOR_FOR_TIMEOUT_TYPE_1, resourceName, timeoutType); + } + return resource; + } + + private String resolveResourceName(ProcessContext context, TimeoutType timeoutType) { + Variable serviceContextVariable = timeoutType.getServiceContextVariable(); + if (serviceContextVariable == null) { + return null; + } + Object value = context.getVariableIfSet(serviceContextVariable); + if (value instanceof CloudServiceInstanceExtended service && service.getResourceName() != null) { + return service.getResourceName(); + } + String serviceName = extractServiceName(value, context); + return serviceName != null ? findServiceResourceNameByServiceName(context, serviceName) : null; + } + + private String extractServiceName(Object contextValue, ProcessContext context) { + if (contextValue instanceof CloudServiceInstanceExtended service) { + return service.getName(); + } + if (contextValue instanceof String serviceName) { + return serviceName; + } + CloudServiceKey serviceKey = context.getVariableIfSet(Variables.SERVICE_KEY_TO_PROCESS); + if (serviceKey != null && serviceKey.getServiceInstance() != null) { + return serviceKey.getServiceInstance().getName(); + } + return null; + } + + private Resource findResourceByName(DeploymentDescriptor descriptor, String resourceName) { + if (descriptor == null || descriptor.getResources() == null) { + return null; + } + return descriptor.getResources() + .stream() + .filter(resource -> resourceName.equals(resource.getName())) + .findFirst() + .orElse(null); + } + + private String findServiceResourceNameByServiceName(ProcessContext context, String serviceName) { + String resourceName = SERVICE_LIST_VARIABLES.stream() + .map(context::getVariableIfSet) + .filter(Objects::nonNull) + .flatMap(List::stream) + .filter(service -> serviceName.equals(service.getName()) + && service.getResourceName() != null) + .map(CloudServiceInstanceExtended::getResourceName) + .findFirst() + .orElse(null); + if (resourceName != null) { + return resourceName; + } + return findServiceResourceNameInDeployedMta(context, serviceName); + } + + private String findServiceResourceNameInDeployedMta(ProcessContext context, String serviceName) { + DeployedMta deployedMta = context.getVariable(Variables.DEPLOYED_MTA); + if (deployedMta == null || deployedMta.getServices() == null) { + return null; + } + return deployedMta.getServices() + .stream() + .filter(service -> serviceName.equals(service.getName()) && service.getResourceName() != null) + .map(DeployedMtaService::getResourceName) + .findFirst() + .orElse(null); + } +} diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/TimeoutStepStateManager.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/TimeoutStepStateManager.java new file mode 100644 index 0000000000..e40d8fb368 --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/TimeoutStepStateManager.java @@ -0,0 +1,57 @@ +package org.cloudfoundry.multiapps.controller.process.util; + +import java.time.Duration; + +import org.cloudfoundry.multiapps.controller.process.Constants; +import org.cloudfoundry.multiapps.controller.process.steps.ProcessContext; +import org.cloudfoundry.multiapps.controller.process.steps.StepPhase; +import org.cloudfoundry.multiapps.controller.process.variables.Variables; + +public class TimeoutStepStateManager { + + public boolean hasTimedOut(ProcessContext context, String stepName, Duration timeout) { + long stepStartTime = getStepStartTime(context, stepName); + long currentTime = System.currentTimeMillis(); + return (currentTime - stepStartTime) >= timeout.toMillis(); + } + + public long getStepStartTime(ProcessContext context, String stepName) { + Long stepStartTime = (Long) context.getExecution() + .getVariable(getStepStartTimeVariable(stepName)); + if (shouldResetTimeout(stepStartTime, context)) { + stepStartTime = System.currentTimeMillis(); + context.getExecution() + .setVariable(getStepStartTimeVariable(stepName), stepStartTime); + clearResetFlag(context); + } + return stepStartTime; + } + + private boolean shouldResetTimeout(Long stepStartTime, ProcessContext context) { + return isTimeoutNotInitialized(stepStartTime) || isInRetry(context) || mustResetTimeout(context); + } + + private boolean isTimeoutNotInitialized(Long stepStartTime) { + return stepStartTime == null; + } + + private boolean isInRetry(ProcessContext context) { + return context.getVariable(Variables.STEP_PHASE) == StepPhase.RETRY; + } + + private boolean mustResetTimeout(ProcessContext context) { + return context.getVariable(Variables.MUST_RESET_TIMEOUT); + } + + private void clearResetFlag(ProcessContext context) { + if (mustResetTimeout(context)) { + context.setVariable(Variables.MUST_RESET_TIMEOUT, false); + } + } + + private String getStepStartTimeVariable(String stepName) { + return Constants.VAR_STEP_START_TIME + stepName; + } + +} + diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/TimeoutType.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/TimeoutType.java index 6c43828fdf..2a241f0159 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/TimeoutType.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/TimeoutType.java @@ -1,38 +1,103 @@ package org.cloudfoundry.multiapps.controller.process.util; +import java.time.Duration; + +import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.core.model.SupportedParameters; import org.cloudfoundry.multiapps.controller.process.variables.Variable; import org.cloudfoundry.multiapps.controller.process.variables.Variables; -import java.time.Duration; - public enum TimeoutType { + UPLOAD(new TimeoutParameterNames(SupportedParameters.UPLOAD_TIMEOUT, null, SupportedParameters.APPS_UPLOAD_TIMEOUT), + Variables.APPS_UPLOAD_TIMEOUT_PROCESS_VARIABLE, + 10800, + null, + null, + null), + + STAGE(new TimeoutParameterNames(SupportedParameters.STAGE_TIMEOUT, null, SupportedParameters.APPS_STAGE_TIMEOUT), + Variables.APPS_STAGE_TIMEOUT_PROCESS_VARIABLE, + 10800, + null, + null, + null), + + START(new TimeoutParameterNames(SupportedParameters.START_TIMEOUT, null, SupportedParameters.APPS_START_TIMEOUT), + Variables.APPS_START_TIMEOUT_PROCESS_VARIABLE, + 10800, + null, + null, + null), + + TASK(new TimeoutParameterNames(SupportedParameters.TASK_EXECUTION_TIMEOUT, null, SupportedParameters.APPS_TASK_EXECUTION_TIMEOUT), + Variables.APPS_TASK_EXECUTION_TIMEOUT_PROCESS_VARIABLE, + 86400, + null, + null, + null), + + CREATE_SERVICE( + new TimeoutParameterNames(null, SupportedParameters.CREATE_SERVICE_TIMEOUT, SupportedParameters.SERVICES_CREATE_SERVICE_TIMEOUT), + Variables.CREATE_SERVICE_TIMEOUT_PROCESS_VARIABLE, + 7200, + Variables.SERVICE_TO_PROCESS, + CloudServiceInstanceExtended::getCreateServiceTimeout, + Variables.CREATE_SERVICE_TIMEOUT_FROM_OPERATION_PARAMS), + - UPLOAD(SupportedParameters.UPLOAD_TIMEOUT, SupportedParameters.APPS_UPLOAD_TIMEOUT, Variables.APPS_UPLOAD_TIMEOUT_PROCESS_VARIABLE, - 10800), STAGE(SupportedParameters.STAGE_TIMEOUT, SupportedParameters.APPS_STAGE_TIMEOUT, - Variables.APPS_STAGE_TIMEOUT_PROCESS_VARIABLE, 10800), START(SupportedParameters.START_TIMEOUT, - SupportedParameters.APPS_START_TIMEOUT, Variables.APPS_START_TIMEOUT_PROCESS_VARIABLE, - 10800), TASK(SupportedParameters.TASK_EXECUTION_TIMEOUT, SupportedParameters.APPS_TASK_EXECUTION_TIMEOUT, - Variables.APPS_TASK_EXECUTION_TIMEOUT_PROCESS_VARIABLE, 86400); + BIND_SERVICE( + new TimeoutParameterNames(null, SupportedParameters.BIND_SERVICE_TIMEOUT, SupportedParameters.SERVICES_BIND_SERVICE_TIMEOUT), + Variables.BIND_SERVICE_TIMEOUT_PROCESS_VARIABLE, + 7200, + Variables.SERVICE_TO_UNBIND_BIND, + CloudServiceInstanceExtended::getBindServiceTimeout, + Variables.BIND_SERVICE_TIMEOUT_FROM_OPERATION_PARAMS), - private final String moduleLevelParamName; - private final String globalLevelParamName; + + CREATE_SERVICE_KEY(new TimeoutParameterNames(null, SupportedParameters.CREATE_SERVICE_KEY_TIMEOUT, + SupportedParameters.SERVICES_CREATE_SERVICE_KEY_TIMEOUT), + Variables.CREATE_SERVICE_KEY_TIMEOUT_PROCESS_VARIABLE, + 7200, + Variables.SERVICE_TO_PROCESS, + CloudServiceInstanceExtended::getCreateServiceKeyTimeout, + Variables.CREATE_SERVICE_KEY_TIMEOUT_FROM_OPERATION_PARAMS); + + @FunctionalInterface + public interface ServiceTimeoutGetter { + Duration getServiceTimeout(CloudServiceInstanceExtended service); + } + + private final TimeoutParameterNames parameterNames; private final Variable processVariable; private final Integer maxAllowedValue; + private final Variable serviceContextVariable; + private final ServiceTimeoutGetter serviceTimeoutGetter; + private final Variable operationParamsFlag; - TimeoutType(String moduleLevelParamName, String globalLevelParamName, Variable processVariable, Integer maxAllowedValue) { - this.moduleLevelParamName = moduleLevelParamName; - this.globalLevelParamName = globalLevelParamName; + TimeoutType(TimeoutParameterNames parameterNames, + Variable processVariable, + Integer maxAllowedValue, + Variable serviceContextVariable, + ServiceTimeoutGetter serviceTimeoutGetter, + Variable operationParamsFlag) { + this.parameterNames = parameterNames; this.processVariable = processVariable; this.maxAllowedValue = maxAllowedValue; + this.serviceContextVariable = serviceContextVariable; + this.serviceTimeoutGetter = serviceTimeoutGetter; + this.operationParamsFlag = operationParamsFlag; } public String getModuleLevelParamName() { - return moduleLevelParamName; + return parameterNames.moduleLevelParamName(); } - public String getProcessVariableAndGlobalLevelParamName() { - return globalLevelParamName; + public String getResourceLevelParamName() { + return parameterNames.resourceLevelParamName(); + } + + public String getGlobalLevelParamName() { + return parameterNames.globalLevelParamName(); } public Variable getProcessVariable() { @@ -42,4 +107,48 @@ public Variable getProcessVariable() { public Integer getMaxAllowedValue() { return maxAllowedValue; } + + public Variable getServiceContextVariable() { + return serviceContextVariable; + } + + public ServiceTimeoutGetter getServiceTimeoutGetter() { + return serviceTimeoutGetter; + } + + public Variable getOperationParamsFlag() { + return operationParamsFlag; + } + + public TimeoutScope getTimeoutScope() { + return inferTimeoutScope(); + } + + private TimeoutScope inferTimeoutScope() { + if (parameterNames.moduleLevelParamName() != null) { + return TimeoutScope.MODULE; + } + return serviceContextVariable != null ? TimeoutScope.SERVICE : TimeoutScope.SERVICE_KEY; + } + + public boolean isModuleScoped() { + return getTimeoutScope() == TimeoutScope.MODULE; + } + + public boolean isServiceScoped() { + return getTimeoutScope() != TimeoutScope.MODULE; + } + + public String getEntityLevelParamName() { + return isModuleScoped() ? getModuleLevelParamName() : getResourceLevelParamName(); + } + + public enum TimeoutScope { + MODULE, + SERVICE, + SERVICE_KEY + } + + public record TimeoutParameterNames(String moduleLevelParamName, String resourceLevelParamName, String globalLevelParamName) { + } } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/TimeoutValueResolver.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/TimeoutValueResolver.java new file mode 100644 index 0000000000..61b23e8dc4 --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/TimeoutValueResolver.java @@ -0,0 +1,202 @@ +package org.cloudfoundry.multiapps.controller.process.util; + +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Stream; + +import jakarta.inject.Named; +import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudApplicationExtended; +import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; +import org.cloudfoundry.multiapps.controller.core.helpers.ApplicationAttributes; +import org.cloudfoundry.multiapps.controller.core.util.DurationUtil; +import org.cloudfoundry.multiapps.controller.process.Messages; +import org.cloudfoundry.multiapps.controller.process.steps.ProcessContext; +import org.cloudfoundry.multiapps.controller.process.variables.Variables; +import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor; +import org.cloudfoundry.multiapps.mta.model.Module; + +@Named +public class TimeoutValueResolver { + + private static final String DEFAULT_TIMEOUT = "default"; + private static final String SERVICE_LEVEL = "service-level"; + + @FunctionalInterface + private interface TimeoutSource { + Optional resolve(ProcessContext context, TimeoutType timeoutType, StepLogger logger); + } + + public record TimeoutResolution(Duration timeout, String parameterName) { + } + + private final List moduleTimeoutSources = List.of( + this::resolveProcessVariableTimeout, + this::extractTimeoutFromModuleDescriptorParameters, + this::extractTimeoutFromAppAttributes, + this::extractTimeoutFromDescriptorParameters); + + private final List serviceTimeoutSources = List.of( + this::resolveOperationParamsServiceTimeout, + this::extractTimeoutFromServiceObject, + this::extractTimeoutFromDescriptorParameters, + this::resolveProcessVariableTimeout); + + public TimeoutResolution resolveTimeout(ProcessContext context, TimeoutType timeoutType, StepLogger logger) { + return getTimeoutSources(timeoutType).stream() + .map(source -> source.resolve(context, timeoutType, logger)) + .flatMap(Optional::stream) + .findFirst() + .orElseGet(() -> new TimeoutResolution(timeoutType.getProcessVariable() + .getDefaultValue(), DEFAULT_TIMEOUT)); + } + + private Optional resolveProcessVariableTimeout(ProcessContext context, TimeoutType timeoutType, StepLogger logger) { + Duration processVariable = context.getVariableIfSet(timeoutType.getProcessVariable()); + if (processVariable == null) { + return Optional.empty(); + } + if (timeoutType.isModuleScoped() && isProcessVariableDerivedFromModuleParameter(context, timeoutType, processVariable)) { + return Optional.empty(); + } + return Optional.of(new TimeoutResolution(processVariable, timeoutType.getGlobalLevelParamName())); + } + + private boolean isProcessVariableDerivedFromModuleParameter(ProcessContext context, TimeoutType timeoutType, Duration processVariable) { + String paramName = timeoutType.getModuleLevelParamName(); + if (paramName == null) { + return false; + } + return Optional.ofNullable(context.getVariableIfSet(Variables.APP_TO_PROCESS)) + .map(CloudApplicationExtended::getName) + .flatMap(appName -> findModuleByAppName(getDeploymentDescriptor(context), appName)) + .map(module -> getParameter(module.getParameters(), paramName)) + .map(timeout -> toDuration(timeout, paramName, timeoutType.getMaxAllowedValue())) + .map(processVariable::equals) + .orElse(false); + } + + private Optional resolveOperationParamsServiceTimeout(ProcessContext context, TimeoutType timeoutType, + StepLogger logger) { + var operationParamsFlag = timeoutType.getOperationParamsFlag(); + if (operationParamsFlag == null || !Boolean.TRUE.equals(context.getVariableIfSet(operationParamsFlag))) { + return Optional.empty(); + } + return Optional.ofNullable(context.getVariableIfSet(timeoutType.getProcessVariable())) + .map(pv -> new TimeoutResolution(pv, timeoutType.getGlobalLevelParamName())); + } + + private Optional extractTimeoutFromModuleDescriptorParameters(ProcessContext context, TimeoutType timeoutType, + StepLogger logger) { + String paramName = timeoutType.getModuleLevelParamName(); + DeploymentDescriptor descriptor = getDeploymentDescriptor(context); + if (paramName == null || descriptor == null) { + return Optional.empty(); + } + String appName = Optional.ofNullable(context.getVariableIfSet(Variables.APP_TO_PROCESS)) + .map(CloudApplicationExtended::getName) + .orElse(null); + + return findModuleByAppName(descriptor, appName) + .or(() -> descriptor.getModules() + .stream() + .findFirst()) + .flatMap(module -> toResolution(getParameter(module.getParameters(), paramName), paramName, timeoutType)); + } + + private Optional extractTimeoutFromAppAttributes(ProcessContext context, TimeoutType timeoutType, StepLogger logger) { + String paramName = timeoutType.getModuleLevelParamName(); + if (paramName == null) { + return Optional.empty(); + } + return Optional.ofNullable(context.getVariable(Variables.APP_TO_PROCESS)) + .map(app -> ApplicationAttributes.fromApplication(app, app.getEnv())) + .map(attrs -> attrs.get(paramName, Number.class)) + .flatMap(value -> toResolution(value, paramName, timeoutType)); + } + + private Optional extractTimeoutFromServiceObject(ProcessContext context, TimeoutType timeoutType, + StepLogger logger) { + String paramName = Optional.ofNullable(timeoutType.getEntityLevelParamName()) + .orElse(SERVICE_LEVEL); + return resolveServiceInstance(context, timeoutType) + .flatMap(service -> Optional.ofNullable(timeoutType.getServiceTimeoutGetter()) + .map(getter -> getter.getServiceTimeout(service))) + .map(timeout -> new TimeoutResolution(timeout, paramName)); + } + + private Optional resolveServiceInstance(ProcessContext context, TimeoutType timeoutType) { + if (timeoutType != TimeoutType.BIND_SERVICE) { + return Optional.ofNullable(context.getVariableIfSet(Variables.SERVICE_TO_PROCESS)); + } + String serviceName = context.getVariableIfSet(Variables.SERVICE_TO_UNBIND_BIND); + List services = context.getVariableIfSet(Variables.SERVICES_TO_BIND); + if (serviceName == null || services == null) { + return Optional.empty(); + } + return services.stream() + .filter(s -> serviceName.equals(s.getName())) + .findFirst(); + } + + private Optional extractTimeoutFromDescriptorParameters(ProcessContext context, TimeoutType timeoutType, + StepLogger logger) { + String paramName = timeoutType.getGlobalLevelParamName(); + DeploymentDescriptor descriptor = getDeploymentDescriptor(context); + if (descriptor == null) { + if (timeoutType.isServiceScoped()) { + logger.debug(Messages.DEPLOYMENT_DESCRIPTOR_MISSING_GLOBAL_PARAMETER_0_NOT_APPLIED_FOR_1, paramName, timeoutType); + } + return Optional.empty(); + } + return toResolution(getParameter(descriptor.getParameters(), paramName), paramName, timeoutType); + } + + private Optional toResolution(Object timeoutValue, String parameterName, TimeoutType timeoutType) { + return Optional.ofNullable(toDuration(timeoutValue, parameterName, timeoutType.getMaxAllowedValue())) + .map(duration -> new TimeoutResolution(duration, parameterName)); + } + + private Object getParameter(Map parameters, String parameterName) { + return parameters != null ? parameters.get(parameterName) : null; + } + + protected Duration toDuration(Object timeout, String timeoutParameterName, Integer maxAllowedValue) { + return DurationUtil.parseDuration(timeout, timeoutParameterName, maxAllowedValue); + } + + private Optional findModuleByAppName(DeploymentDescriptor descriptor, String appName) { + if (descriptor == null || appName == null) { + return Optional.empty(); + } + return descriptor.getModules() + .stream() + .filter(m -> appName.equals(m.getName())) + .findFirst(); + } + + private List getTimeoutSources(TimeoutType timeoutType) { + return timeoutType.isModuleScoped() ? moduleTimeoutSources : serviceTimeoutSources; + } + + public DeploymentDescriptor getDeploymentDescriptor(ProcessContext context, StepLogger stepLogger) { + return Stream.of(Variables.DEPLOYMENT_DESCRIPTOR, + Variables.DEPLOYMENT_DESCRIPTOR_WITH_SYSTEM_PARAMETERS, + Variables.COMPLETE_DEPLOYMENT_DESCRIPTOR) + .map(context::getVariable) + .filter(Objects::nonNull) + .findFirst() + .orElseGet(() -> { + if (stepLogger != null) { + stepLogger.debug(Messages.NO_DEPLOYMENT_DESCRIPTOR_FOUND_IN_CONTEXT); + } + return null; + }); + } + + private DeploymentDescriptor getDeploymentDescriptor(ProcessContext context) { + return getDeploymentDescriptor(context, null); + } +} diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/variables/Variables.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/variables/Variables.java index b041ef2a74..740496e8a9 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/variables/Variables.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/variables/Variables.java @@ -969,4 +969,35 @@ public Serializer> getSerializer() { .defaultValue(false) .build(); + Variable CREATE_SERVICE_TIMEOUT_PROCESS_VARIABLE = ImmutableSimpleVariable. builder() + .name("servicesCreateServiceTimeout") + .defaultValue(Duration.ofHours(1)) + .build(); + + Variable CREATE_SERVICE_TIMEOUT_FROM_OPERATION_PARAMS = ImmutableSimpleVariable. builder() + .name("createServiceTimeoutFromOperationParams") + .defaultValue(false) + .build(); + + Variable BIND_SERVICE_TIMEOUT_PROCESS_VARIABLE = ImmutableSimpleVariable. builder() + .name("servicesBindServiceTimeout") + .defaultValue(Duration.ofHours(1)) + .build(); + + Variable BIND_SERVICE_TIMEOUT_FROM_OPERATION_PARAMS = ImmutableSimpleVariable. builder() + .name("bindServiceTimeoutFromOperationParams") + .defaultValue(false) + .build(); + + Variable CREATE_SERVICE_KEY_TIMEOUT_PROCESS_VARIABLE = ImmutableSimpleVariable. builder() + .name("servicesCreateServiceKeyTimeout") + .defaultValue(Duration.ofHours(1)) + .build(); + + Variable CREATE_SERVICE_KEY_TIMEOUT_FROM_OPERATION_PARAMS = ImmutableSimpleVariable. builder() + .name( + "createServiceKeyTimeoutFromOperationParams") + .defaultValue(false) + .build(); + } diff --git a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/deploy-app.bpmn b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/deploy-app.bpmn index db163d450d..23578f6153 100644 --- a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/deploy-app.bpmn +++ b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/deploy-app.bpmn @@ -86,8 +86,8 @@ - + @@ -186,6 +186,8 @@ + + @@ -221,6 +223,8 @@ + + diff --git a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/manage-app-service-bindings.bpmn b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/manage-app-service-bindings.bpmn index 1d37925af6..3e10b0786a 100644 --- a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/manage-app-service-bindings.bpmn +++ b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/manage-app-service-bindings.bpmn @@ -27,7 +27,7 @@ - + PT${applicationConfiguration.getStepPollingIntervalInSeconds()}S @@ -146,11 +146,6 @@ - - - - - diff --git a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/process-batches-sequentially.bpmn b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/process-batches-sequentially.bpmn index 88c5f94466..9f034fbe80 100644 --- a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/process-batches-sequentially.bpmn +++ b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/process-batches-sequentially.bpmn @@ -1,5 +1,9 @@ - + @@ -40,6 +44,13 @@ + + + + + + + diff --git a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/rollback-mta.bpmn b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/rollback-mta.bpmn index 40d93d6806..a464420877 100644 --- a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/rollback-mta.bpmn +++ b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/rollback-mta.bpmn @@ -19,6 +19,7 @@ + @@ -62,7 +63,8 @@ - + + @@ -122,6 +124,9 @@ + + + @@ -259,8 +264,12 @@ - + + + + + diff --git a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/xs2-bg-deploy.bpmn b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/xs2-bg-deploy.bpmn index 984c919265..e4b324e685 100644 --- a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/xs2-bg-deploy.bpmn +++ b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/xs2-bg-deploy.bpmn @@ -12,7 +12,13 @@ - + + + + + + + @@ -30,6 +36,7 @@ + @@ -129,7 +136,8 @@ - + + @@ -773,4 +781,5 @@ - \ No newline at end of file + + diff --git a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/xs2-deploy.bpmn b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/xs2-deploy.bpmn index 643263993b..34831aa239 100644 --- a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/xs2-deploy.bpmn +++ b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/xs2-deploy.bpmn @@ -12,6 +12,7 @@ + @@ -34,7 +35,8 @@ - + + @@ -556,4 +558,5 @@ - \ No newline at end of file + + diff --git a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/xs2-undeploy.bpmn b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/xs2-undeploy.bpmn index 691e6e4f6c..03811594b0 100644 --- a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/xs2-undeploy.bpmn +++ b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/xs2-undeploy.bpmn @@ -12,6 +12,7 @@ + @@ -19,7 +20,8 @@ - + + @@ -452,4 +454,5 @@ - \ No newline at end of file + + diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/BlueGreenDeployMetadataTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/BlueGreenDeployMetadataTest.java index c2d88499d2..353a67953e 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/BlueGreenDeployMetadataTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/BlueGreenDeployMetadataTest.java @@ -44,21 +44,24 @@ protected String[] getParametersIds() { Variables.ABORT_ON_ERROR.getName(), Variables.MODULES_FOR_DEPLOYMENT.getName(), Variables.RESOURCES_FOR_DEPLOYMENT.getName(), - Variables.NO_CONFIRM.getName(), - Variables.KEEP_ORIGINAL_APP_NAMES_AFTER_DEPLOY.getName(), - Variables.SKIP_IDLE_START.getName(), - Variables.SHOULD_APPLY_INCREMENTAL_INSTANCES_UPDATE.getName(), Variables.APPS_START_TIMEOUT_PROCESS_VARIABLE.getName(), Variables.APPS_STAGE_TIMEOUT_PROCESS_VARIABLE.getName(), Variables.APPS_UPLOAD_TIMEOUT_PROCESS_VARIABLE.getName(), Variables.APPS_TASK_EXECUTION_TIMEOUT_PROCESS_VARIABLE.getName(), - Variables.SKIP_APP_DIGEST_CALCULATION.getName(), - Variables.SHOULD_BACKUP_PREVIOUS_VERSION.getName(), - Variables.STOP_ORDER_IS_DEPENDENCY_AWARE.getName(), + Variables.CREATE_SERVICE_TIMEOUT_PROCESS_VARIABLE.getName(), + Variables.CREATE_SERVICE_KEY_TIMEOUT_PROCESS_VARIABLE.getName(), + Variables.BIND_SERVICE_TIMEOUT_PROCESS_VARIABLE.getName(), Variables.IS_SECURITY_ENABLED.getName(), Variables.DISPOSABLE_USER_PROVIDED_SERVICE_NAME.getName(), Variables.IS_DISPOSABLE_USER_PROVIDED_SERVICE_ENABLED.getName(), - Variables.API_REQUEST_PATH.getName() + Variables.API_REQUEST_PATH.getName(), + Variables.NO_CONFIRM.getName(), + Variables.KEEP_ORIGINAL_APP_NAMES_AFTER_DEPLOY.getName(), + Variables.SKIP_IDLE_START.getName(), + Variables.SHOULD_APPLY_INCREMENTAL_INSTANCES_UPDATE.getName(), + Variables.SKIP_APP_DIGEST_CALCULATION.getName(), + Variables.SHOULD_BACKUP_PREVIOUS_VERSION.getName(), + Variables.STOP_ORDER_IS_DEPENDENCY_AWARE.getName() // @formatter:on }; } diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/CtsDeployMetadataTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/CtsDeployMetadataTest.java index 7109b0b423..646f26d5fc 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/CtsDeployMetadataTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/CtsDeployMetadataTest.java @@ -61,7 +61,10 @@ protected String[] getParametersIds() { Variables.IS_SECURITY_ENABLED.getName(), Variables.DISPOSABLE_USER_PROVIDED_SERVICE_NAME.getName(), Variables.IS_DISPOSABLE_USER_PROVIDED_SERVICE_ENABLED.getName(), - Variables.API_REQUEST_PATH.getName() + Variables.API_REQUEST_PATH.getName(), + Variables.CREATE_SERVICE_TIMEOUT_PROCESS_VARIABLE.getName(), + Variables.BIND_SERVICE_TIMEOUT_PROCESS_VARIABLE.getName(), + Variables.CREATE_SERVICE_KEY_TIMEOUT_PROCESS_VARIABLE.getName() // @formatter:on }; } diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/DeployMetadataTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/DeployMetadataTest.java index 1e35d8dfff..4a38c8ed09 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/DeployMetadataTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/DeployMetadataTest.java @@ -52,7 +52,10 @@ protected String[] getParametersIds() { Variables.IS_SECURITY_ENABLED.getName(), Variables.DISPOSABLE_USER_PROVIDED_SERVICE_NAME.getName(), Variables.IS_DISPOSABLE_USER_PROVIDED_SERVICE_ENABLED.getName(), - Variables.API_REQUEST_PATH.getName() + Variables.API_REQUEST_PATH.getName(), + Variables.CREATE_SERVICE_TIMEOUT_PROCESS_VARIABLE.getName(), + Variables.BIND_SERVICE_TIMEOUT_PROCESS_VARIABLE.getName(), + Variables.CREATE_SERVICE_KEY_TIMEOUT_PROCESS_VARIABLE.getName() // @formatter:on }; } diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/RollbackMtaMetadataTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/RollbackMtaMetadataTest.java index 7363be5780..9e2eef2739 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/RollbackMtaMetadataTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/RollbackMtaMetadataTest.java @@ -28,7 +28,10 @@ protected String[] getParametersIds() { Variables.APPS_START_TIMEOUT_PROCESS_VARIABLE.getName(), Variables.APPS_STAGE_TIMEOUT_PROCESS_VARIABLE.getName(), Variables.APPS_UPLOAD_TIMEOUT_PROCESS_VARIABLE.getName(), Variables.APPS_TASK_EXECUTION_TIMEOUT_PROCESS_VARIABLE.getName(), Variables.PROCESS_USER_PROVIDED_SERVICES.getName(), Variables.IS_SECURITY_ENABLED.getName(), - Variables.DISPOSABLE_USER_PROVIDED_SERVICE_NAME.getName(), Variables.IS_DISPOSABLE_USER_PROVIDED_SERVICE_ENABLED.getName() }; + Variables.DISPOSABLE_USER_PROVIDED_SERVICE_NAME.getName(), Variables.IS_DISPOSABLE_USER_PROVIDED_SERVICE_ENABLED.getName(), + Variables.CREATE_SERVICE_TIMEOUT_PROCESS_VARIABLE.getName(), + Variables.BIND_SERVICE_TIMEOUT_PROCESS_VARIABLE.getName(), + Variables.CREATE_SERVICE_KEY_TIMEOUT_PROCESS_VARIABLE.getName() }; } } diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/UndeployMetadataTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/UndeployMetadataTest.java index 0927c1cd45..fad4fdad37 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/UndeployMetadataTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/UndeployMetadataTest.java @@ -32,7 +32,7 @@ protected String[] getParametersIds() { Variables.MTA_NAMESPACE.getName(), Variables.NO_RESTART_SUBSCRIBED_APPS.getName(), Variables.NO_FAIL_ON_MISSING_PERMISSIONS.getName(), - Variables.ABORT_ON_ERROR.getName(), + Variables.ABORT_ON_ERROR.getName() // @formatter:on }; } diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/SyncFlowableStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/SyncFlowableStepTest.java index a083c69ff0..1c6472d437 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/SyncFlowableStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/SyncFlowableStepTest.java @@ -32,6 +32,7 @@ import org.cloudfoundry.multiapps.controller.process.util.ProcessHelper; import org.cloudfoundry.multiapps.controller.process.util.StepLogger; import org.cloudfoundry.multiapps.controller.process.util.TimeoutType; +import org.cloudfoundry.multiapps.controller.process.util.TimeoutValueResolver; import org.cloudfoundry.multiapps.controller.process.variables.Variable; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor; @@ -109,6 +110,7 @@ public abstract class SyncFlowableStepTest { protected final ProcessLoggerProvider processLoggerProvider = Mockito.spy(ProcessLoggerProvider.class); @Mock protected ProcessHelper processHelper; + protected TimeoutValueResolver timeoutValueResolver = new TimeoutValueResolver(); protected ProcessContext context; @InjectMocks @@ -120,6 +122,9 @@ public abstract class SyncFlowableStepTest { public void initMocks() throws Exception { MockitoAnnotations.openMocks(this) .close(); + if (step instanceof TimeoutAsyncFlowableStep timeoutStep) { + timeoutStep.timeoutValueResolver = timeoutValueResolver; + } this.stepLogger = Mockito.spy(new StepLogger(execution, progressMessageService, processLoggerProvider, LOGGER)); this.context = step.createProcessContext(execution); when(stepLoggerFactory.create(any(), any(), any(), any())).thenReturn(stepLogger); @@ -235,7 +240,7 @@ protected void setUpContext(Integer timeoutProcessVariable, Integer timeoutModul } context.setVariable(Variables.APP_TO_PROCESS, app); - when(timeoutType.getProcessVariableAndGlobalLevelParamName()).thenReturn(SupportedParameters.APPS_UPLOAD_TIMEOUT); + when(timeoutType.getGlobalLevelParamName()).thenReturn(SupportedParameters.APPS_UPLOAD_TIMEOUT); Map timeoutGlobalLevelParameters = new HashMap<>(); timeoutGlobalLevelParameters.put(timeoutGlobalParameter, timeoutGlobalLevel); diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/UploadAppStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/UploadAppStepTest.java index 31a95ac90c..89ed87ef16 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/UploadAppStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/UploadAppStepTest.java @@ -1,7 +1,5 @@ package org.cloudfoundry.multiapps.controller.process.steps; -import static org.junit.jupiter.api.Assertions.assertEquals; - import java.time.Duration; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudApplicationExtended; @@ -15,6 +13,8 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.MethodSource; +import static org.junit.jupiter.api.Assertions.assertEquals; + public class UploadAppStepTest { @Nested diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/TimeoutServiceResourceNameResolverTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/TimeoutServiceResourceNameResolverTest.java new file mode 100644 index 0000000000..7f9d8f3e0a --- /dev/null +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/TimeoutServiceResourceNameResolverTest.java @@ -0,0 +1,335 @@ +package org.cloudfoundry.multiapps.controller.process.util; + +import java.util.ArrayList; +import java.util.List; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceKey; +import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; +import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudServiceInstanceExtended; +import org.cloudfoundry.multiapps.controller.core.cf.metadata.ImmutableMtaMetadata; +import org.cloudfoundry.multiapps.controller.core.model.DeployedMta; +import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaService; +import org.cloudfoundry.multiapps.controller.core.model.ImmutableDeployedMta; +import org.cloudfoundry.multiapps.controller.core.model.ImmutableDeployedMtaService; +import org.cloudfoundry.multiapps.controller.process.steps.ProcessContext; +import org.cloudfoundry.multiapps.controller.process.variables.Variables; +import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor; +import org.cloudfoundry.multiapps.mta.model.Resource; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +class TimeoutServiceResourceNameResolverTest { + + private TimeoutServiceResourceNameResolver resolver; + + @Mock + private ProcessContext context; + + @Mock + private StepLogger logger; + + @BeforeEach + void setUp() { + try (var closeable = MockitoAnnotations.openMocks(this)) { + } catch (Exception e) { + throw new RuntimeException(e); + } + resolver = new TimeoutServiceResourceNameResolver(); + } + + @Test + void testResolveResourceFromCloudServiceInstanceExtended() { + String serviceName = "test-service"; + String resourceName = "test-resource"; + CloudServiceInstanceExtended service = ImmutableCloudServiceInstanceExtended.builder() + .name(serviceName) + .resourceName(resourceName) + .build(); + + DeploymentDescriptor descriptor = createDescriptorWithResource(resourceName); + + when(context.getVariableIfSet(Variables.SERVICE_TO_PROCESS)).thenReturn(service); + + Resource result = resolver.resolveResource(context, TimeoutType.CREATE_SERVICE, descriptor, logger); + + assertEquals(resourceName, result.getName()); + } + + @Test + void testResolveResourceFromServiceName() { + String serviceName = "test-service"; + String resourceName = "test-resource"; + CloudServiceInstanceExtended service = ImmutableCloudServiceInstanceExtended.builder() + .name(serviceName) + .build(); + + DeploymentDescriptor descriptor = createDescriptorWithResource(resourceName); + List servicesToCreate = new ArrayList<>(); + servicesToCreate.add(ImmutableCloudServiceInstanceExtended.builder() + .name(serviceName) + .resourceName(resourceName) + .build()); + + when(context.getVariableIfSet(Variables.SERVICE_TO_PROCESS)).thenReturn(service); + when(context.getVariableIfSet(Variables.SERVICES_TO_CREATE)).thenReturn(servicesToCreate); + + Resource result = resolver.resolveResource(context, TimeoutType.CREATE_SERVICE, descriptor, logger); + + assertEquals(resourceName, result.getName()); + } + + @Test + void testResolveResourceFromServicesToBind() { + String serviceName = "test-service"; + String resourceName = "test-resource"; + DeploymentDescriptor descriptor = createDescriptorWithResource(resourceName); + List servicesToBind = new ArrayList<>(); + servicesToBind.add(ImmutableCloudServiceInstanceExtended.builder() + .name(serviceName) + .resourceName(resourceName) + .build()); + + when(context.getVariableIfSet(Variables.SERVICE_TO_UNBIND_BIND)).thenReturn(serviceName); + when(context.getVariableIfSet(Variables.SERVICES_TO_BIND)).thenReturn(servicesToBind); + when(context.getVariableIfSet(Variables.SERVICES_TO_CREATE)).thenReturn(null); + when(context.getVariableIfSet(Variables.SERVICES_TO_POLL)).thenReturn(null); + when(context.getVariable(Variables.DEPLOYED_MTA)).thenReturn(null); + + Resource result = resolver.resolveResource(context, TimeoutType.BIND_SERVICE, descriptor, logger); + + assertNotNull(result); + assertEquals(resourceName, result.getName()); + } + + @Test + void testResolveResourceFromServicesToPoll() { + String serviceName = "test-service"; + String resourceName = "test-resource"; + + DeploymentDescriptor descriptor = createDescriptorWithResource(resourceName); + List servicesToPoll = new ArrayList<>(); + servicesToPoll.add(ImmutableCloudServiceInstanceExtended.builder() + .name(serviceName) + .resourceName(resourceName) + .build()); + + CloudServiceInstanceExtended serviceToProcess = ImmutableCloudServiceInstanceExtended.builder() + .name(serviceName) + .build(); + + when(context.getVariableIfSet(Variables.SERVICE_TO_PROCESS)).thenReturn(serviceToProcess); + when(context.getVariableIfSet(Variables.SERVICES_TO_BIND)).thenReturn(null); + when(context.getVariableIfSet(Variables.SERVICES_TO_CREATE)).thenReturn(null); + when(context.getVariableIfSet(Variables.SERVICES_TO_POLL)).thenReturn(servicesToPoll); + when(context.getVariable(Variables.DEPLOYED_MTA)).thenReturn(null); + + Resource result = resolver.resolveResource(context, TimeoutType.CREATE_SERVICE, descriptor, logger); + + assertNotNull(result); + assertEquals(resourceName, result.getName()); + } + + @Test + void testResolveResourceFromDeployedMta() { + String serviceName = "test-service"; + String resourceName = "test-resource"; + + DeploymentDescriptor descriptor = createDescriptorWithResource(resourceName); + + DeployedMtaService deployedService = ImmutableDeployedMtaService.builder() + .name(serviceName) + .resourceName(resourceName) + .build(); + DeployedMta deployedMta = ImmutableDeployedMta.builder() + .addServices(deployedService) + .metadata(ImmutableMtaMetadata.builder() + .id("test-mta") + .build()) + .build(); + + CloudServiceInstanceExtended serviceToProcess = ImmutableCloudServiceInstanceExtended.builder() + .name(serviceName) + .build(); + + when(context.getVariableIfSet(Variables.SERVICE_TO_PROCESS)).thenReturn(serviceToProcess); + when(context.getVariableIfSet(Variables.SERVICES_TO_BIND)).thenReturn(null); + when(context.getVariableIfSet(Variables.SERVICES_TO_CREATE)).thenReturn(null); + when(context.getVariableIfSet(Variables.SERVICES_TO_POLL)).thenReturn(null); + when(context.getVariable(Variables.DEPLOYED_MTA)).thenReturn(deployedMta); + + Resource result = resolver.resolveResource(context, TimeoutType.CREATE_SERVICE, descriptor, logger); + + assertNotNull(result); + assertEquals(resourceName, result.getName()); + } + + @Test + void testResolveResourceFromServiceKeyToProcess() { + String serviceName = "test-service"; + String resourceName = "test-resource"; + + DeploymentDescriptor descriptor = createDescriptorWithResource(resourceName); + + List servicesToCreate = new ArrayList<>(); + servicesToCreate.add(ImmutableCloudServiceInstanceExtended.builder() + .name(serviceName) + .resourceName(resourceName) + .build()); + + CloudServiceInstanceExtended serviceInstance = ImmutableCloudServiceInstanceExtended.builder() + .name(serviceName) + .build(); + CloudServiceKey serviceKey = ImmutableCloudServiceKey.builder() + .name("test-key") + .serviceInstance(serviceInstance) + .build(); + + when(context.getVariableIfSet(Variables.SERVICE_TO_PROCESS)).thenReturn(null); + when(context.getVariableIfSet(Variables.SERVICES_TO_BIND)).thenReturn(null); + when(context.getVariableIfSet(Variables.SERVICES_TO_CREATE)).thenReturn(servicesToCreate); + when(context.getVariableIfSet(Variables.SERVICES_TO_POLL)).thenReturn(null); + when(context.getVariableIfSet(Variables.SERVICE_KEY_TO_PROCESS)).thenReturn(serviceKey); + when(context.getVariable(Variables.DEPLOYED_MTA)).thenReturn(null); + + Resource result = resolver.resolveResource(context, TimeoutType.CREATE_SERVICE, descriptor, logger); + + assertNotNull(result); + assertEquals(resourceName, result.getName()); + } + + @Test + void testResolveResourceReturnsNullWhenResourceNotFound() { + String serviceName = "test-service"; + String resourceName = "non-existent-resource"; + + DeploymentDescriptor descriptor = createDescriptorWithResource("different-resource"); + CloudServiceInstanceExtended service = ImmutableCloudServiceInstanceExtended.builder() + .name(serviceName) + .resourceName(resourceName) + .build(); + + when(context.getVariableIfSet(Variables.SERVICE_TO_PROCESS)).thenReturn(service); + + Resource result = resolver.resolveResource(context, TimeoutType.CREATE_SERVICE, descriptor, logger); + + assertNull(result); + } + + @Test + void testResolveResourceReturnsNullWhenResourceNameNotResolved() { + DeploymentDescriptor descriptor = createDescriptorWithResource("test-resource"); + + when(context.getVariableIfSet(Variables.SERVICE_TO_PROCESS)).thenReturn(null); + when(context.getVariableIfSet(Variables.SERVICES_TO_BIND)).thenReturn(null); + when(context.getVariableIfSet(Variables.SERVICES_TO_CREATE)).thenReturn(null); + when(context.getVariableIfSet(Variables.SERVICES_TO_POLL)).thenReturn(null); + when(context.getVariable(Variables.DEPLOYED_MTA)).thenReturn(null); + + Resource result = resolver.resolveResource(context, TimeoutType.CREATE_SERVICE, descriptor, logger); + + assertNull(result); + } + + @Test + void testResolveResourceWithNullDescriptor() { + CloudServiceInstanceExtended service = ImmutableCloudServiceInstanceExtended.builder() + .name("test-service") + .resourceName("test-resource") + .build(); + + when(context.getVariableIfSet(Variables.SERVICE_TO_PROCESS)).thenReturn(service); + + Resource result = resolver.resolveResource(context, TimeoutType.CREATE_SERVICE, null, logger); + + assertNull(result); + } + + @Test + void testResolveResourceWithEmptyServicesList() { + DeploymentDescriptor descriptor = createDescriptorWithResource("test-resource"); + List emptyList = new ArrayList<>(); + + when(context.getVariableIfSet(Variables.SERVICE_TO_PROCESS)).thenReturn(null); + when(context.getVariableIfSet(Variables.SERVICES_TO_BIND)).thenReturn(emptyList); + + Resource result = resolver.resolveResource(context, TimeoutType.CREATE_SERVICE, descriptor, logger); + + assertNull(result); + } + + @Test + void testResolveResourceWithMultipleServices() { + String targetServiceName = "target-service"; + String resourceName = "target-resource"; + DeploymentDescriptor descriptor = createDescriptorWithResources( + "other-resource", + resourceName, + "another-resource" + ); + + List servicesToCreate = new ArrayList<>(); + servicesToCreate.add(ImmutableCloudServiceInstanceExtended.builder() + .name("other-service") + .resourceName("other-resource") + .build()); + servicesToCreate.add(ImmutableCloudServiceInstanceExtended.builder() + .name(targetServiceName) + .resourceName(resourceName) + .build()); + + CloudServiceInstanceExtended serviceToProcess = ImmutableCloudServiceInstanceExtended.builder() + .name(targetServiceName) + .build(); + + when(context.getVariableIfSet(Variables.SERVICE_TO_PROCESS)).thenReturn(serviceToProcess); + when(context.getVariableIfSet(Variables.SERVICES_TO_BIND)).thenReturn(null); + when(context.getVariableIfSet(Variables.SERVICES_TO_CREATE)).thenReturn(servicesToCreate); + when(context.getVariableIfSet(Variables.SERVICES_TO_POLL)).thenReturn(null); + when(context.getVariable(Variables.DEPLOYED_MTA)).thenReturn(null); + + Resource result = resolver.resolveResource(context, TimeoutType.CREATE_SERVICE, descriptor, logger); + + assertNotNull(result); + assertEquals(resourceName, result.getName()); + } + + private DeploymentDescriptor createDescriptorWithResource(String resourceName) { + DeploymentDescriptor descriptor = DeploymentDescriptor.createV3(); + List resources = new ArrayList<>(); + Resource resource = mock(Resource.class); + when(resource.getName()).thenReturn(resourceName); + resources.add(resource); + descriptor.setResources(resources); + return descriptor; + } + + private DeploymentDescriptor createDescriptorWithResources(String... resourceNames) { + DeploymentDescriptor descriptor = DeploymentDescriptor.createV3(); + List resources = new ArrayList<>(); + for (String name : resourceNames) { + Resource resource = mock(Resource.class); + when(resource.getName()).thenReturn(name); + resources.add(resource); + } + descriptor.setResources(resources); + return descriptor; + } + +} + + + + + + + + diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/TimeoutStepStateManagerTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/TimeoutStepStateManagerTest.java new file mode 100644 index 0000000000..77a3d05dae --- /dev/null +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/TimeoutStepStateManagerTest.java @@ -0,0 +1,225 @@ +package org.cloudfoundry.multiapps.controller.process.util; + +import java.time.Duration; + +import org.cloudfoundry.multiapps.controller.process.Constants; +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.flowable.engine.delegate.DelegateExecution; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class TimeoutStepStateManagerTest { + + private TimeoutStepStateManager timeoutStepStateManager; + + @Mock + private ProcessContext context; + + @Mock + private DelegateExecution execution; + + @BeforeEach + void setUp() { + try (var closeable = MockitoAnnotations.openMocks(this)) { + } catch (Exception e) { + throw new RuntimeException(e); + } + timeoutStepStateManager = new TimeoutStepStateManager(); + when(context.getExecution()).thenReturn(execution); + } + + @Test + void testHasTimedOutReturnsFalseWhenWithinTimeout() { + String stepName = "testStep"; + Duration timeout = Duration.ofSeconds(10); + long now = System.currentTimeMillis(); + long stepStartTime = now - 5000; + + when(execution.getVariable(Constants.VAR_STEP_START_TIME + stepName)).thenReturn(stepStartTime); + when(context.getVariable(Variables.STEP_PHASE)).thenReturn(StepPhase.DONE); + when(context.getVariable(Variables.MUST_RESET_TIMEOUT)).thenReturn(false); + + boolean hasTimedOut = timeoutStepStateManager.hasTimedOut(context, stepName, timeout); + + assertFalse(hasTimedOut); + } + + @Test + void testHasTimedOutReturnsTrueWhenTimeoutExceeded() { + String stepName = "testStep"; + Duration timeout = Duration.ofSeconds(5); + long now = System.currentTimeMillis(); + long stepStartTime = now - 10000; + + when(execution.getVariable(Constants.VAR_STEP_START_TIME + stepName)).thenReturn(stepStartTime); + when(context.getVariable(Variables.STEP_PHASE)).thenReturn(StepPhase.DONE); + when(context.getVariable(Variables.MUST_RESET_TIMEOUT)).thenReturn(false); + + boolean hasTimedOut = timeoutStepStateManager.hasTimedOut(context, stepName, timeout); + + assertTrue(hasTimedOut); + } + + @Test + void testHasTimedOutReturnsFalseAtExactTimeout() { + String stepName = "testStep"; + Duration timeout = Duration.ofSeconds(10); + long now = System.currentTimeMillis(); + long stepStartTime = now - 10000; + + when(execution.getVariable(Constants.VAR_STEP_START_TIME + stepName)).thenReturn(stepStartTime); + when(context.getVariable(Variables.STEP_PHASE)).thenReturn(StepPhase.DONE); + when(context.getVariable(Variables.MUST_RESET_TIMEOUT)).thenReturn(false); + + boolean hasTimedOut = timeoutStepStateManager.hasTimedOut(context, stepName, timeout); + + assertTrue(hasTimedOut); + } + + @Test + void testGetStepStartTimeInitializesTimeWhenNotSet() { + String stepName = "testStep"; + long now = System.currentTimeMillis(); + + when(execution.getVariable(Constants.VAR_STEP_START_TIME + stepName)).thenReturn(null); + when(context.getVariable(Variables.STEP_PHASE)).thenReturn(StepPhase.DONE); + when(context.getVariable(Variables.MUST_RESET_TIMEOUT)).thenReturn(false); + + long stepStartTime = timeoutStepStateManager.getStepStartTime(context, stepName); + + assertTrue(stepStartTime >= now); + assertTrue(stepStartTime <= System.currentTimeMillis()); + verify(execution).setVariable(Constants.VAR_STEP_START_TIME + stepName, stepStartTime); + } + + @Test + void testGetStepStartTimeReturnsExistingTimeWhenInitialized() { + String stepName = "testStep"; + long existingTime = System.currentTimeMillis() - 5000; + + when(execution.getVariable(Constants.VAR_STEP_START_TIME + stepName)).thenReturn(existingTime); + when(context.getVariable(Variables.STEP_PHASE)).thenReturn(StepPhase.DONE); + when(context.getVariable(Variables.MUST_RESET_TIMEOUT)).thenReturn(false); + + long stepStartTime = timeoutStepStateManager.getStepStartTime(context, stepName); + + assertEquals(existingTime, stepStartTime); + } + + @Test + void testGetStepStartTimeResetsTimeoutOnRetry() { + String stepName = "testStep"; + long oldTime = System.currentTimeMillis() - 100000; + long now = System.currentTimeMillis(); + + when(execution.getVariable(Constants.VAR_STEP_START_TIME + stepName)).thenReturn(oldTime); + when(context.getVariable(Variables.STEP_PHASE)).thenReturn(StepPhase.RETRY); + when(context.getVariable(Variables.MUST_RESET_TIMEOUT)).thenReturn(false); + + long stepStartTime = timeoutStepStateManager.getStepStartTime(context, stepName); + + assertTrue(stepStartTime >= now); + assertTrue(stepStartTime <= System.currentTimeMillis()); + verify(execution).setVariable(Constants.VAR_STEP_START_TIME + stepName, stepStartTime); + } + + @Test + void testGetStepStartTimeResetsTimeoutWhenMustResetFlagIsSet() { + String stepName = "testStep"; + long oldTime = System.currentTimeMillis() - 100000; + long now = System.currentTimeMillis(); + + when(execution.getVariable(Constants.VAR_STEP_START_TIME + stepName)).thenReturn(oldTime); + when(context.getVariable(Variables.STEP_PHASE)).thenReturn(StepPhase.DONE); + when(context.getVariable(Variables.MUST_RESET_TIMEOUT)).thenReturn(true); + + long stepStartTime = timeoutStepStateManager.getStepStartTime(context, stepName); + + assertTrue(stepStartTime >= now); + assertTrue(stepStartTime <= System.currentTimeMillis()); + verify(execution).setVariable(Constants.VAR_STEP_START_TIME + stepName, stepStartTime); + verify(context).setVariable(Variables.MUST_RESET_TIMEOUT, false); + } + + @Test + void testGetStepStartTimeDoesNotResetWhenNotNeeded() { + String stepName = "testStep"; + long existingTime = System.currentTimeMillis() - 5000; + + when(execution.getVariable(Constants.VAR_STEP_START_TIME + stepName)).thenReturn(existingTime); + when(context.getVariable(Variables.STEP_PHASE)).thenReturn(StepPhase.DONE); + when(context.getVariable(Variables.MUST_RESET_TIMEOUT)).thenReturn(false); + + long stepStartTime = timeoutStepStateManager.getStepStartTime(context, stepName); + + assertEquals(existingTime, stepStartTime); + } + + @Test + void testMultipleStepsHaveSeparateTimeoutStates() { + String step1 = "step1"; + String step2 = "step2"; + long time1 = System.currentTimeMillis() - 5000; + long time2 = System.currentTimeMillis() - 10000; + + when(execution.getVariable(Constants.VAR_STEP_START_TIME + step1)).thenReturn(time1); + when(execution.getVariable(Constants.VAR_STEP_START_TIME + step2)).thenReturn(time2); + when(context.getVariable(Variables.STEP_PHASE)).thenReturn(StepPhase.DONE); + when(context.getVariable(Variables.MUST_RESET_TIMEOUT)).thenReturn(false); + + long step1StartTime = timeoutStepStateManager.getStepStartTime(context, step1); + long step2StartTime = timeoutStepStateManager.getStepStartTime(context, step2); + + assertEquals(time1, step1StartTime); + assertEquals(time2, step2StartTime); + } + + @Test + void testHasTimedOutWithLargeTimeout() { + String stepName = "testStep"; + Duration timeout = Duration.ofHours(1); + long now = System.currentTimeMillis(); + long stepStartTime = now - 60000; + + when(execution.getVariable(Constants.VAR_STEP_START_TIME + stepName)).thenReturn(stepStartTime); + when(context.getVariable(Variables.STEP_PHASE)).thenReturn(StepPhase.DONE); + when(context.getVariable(Variables.MUST_RESET_TIMEOUT)).thenReturn(false); + + boolean hasTimedOut = timeoutStepStateManager.hasTimedOut(context, stepName, timeout); + + assertFalse(hasTimedOut); + } + + @Test + void testHasTimedOutWithSmallTimeout() { + String stepName = "testStep"; + Duration timeout = Duration.ofMillis(100); + long now = System.currentTimeMillis(); + long stepStartTime = now - 200; + + when(execution.getVariable(Constants.VAR_STEP_START_TIME + stepName)).thenReturn(stepStartTime); + when(context.getVariable(Variables.STEP_PHASE)).thenReturn(StepPhase.DONE); + when(context.getVariable(Variables.MUST_RESET_TIMEOUT)).thenReturn(false); + + boolean hasTimedOut = timeoutStepStateManager.hasTimedOut(context, stepName, timeout); + + assertTrue(hasTimedOut); + } + +} + + + + + + diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/TimeoutValueResolverTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/TimeoutValueResolverTest.java new file mode 100644 index 0000000000..a888926339 --- /dev/null +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/TimeoutValueResolverTest.java @@ -0,0 +1,202 @@ +package org.cloudfoundry.multiapps.controller.process.util; + +import java.time.Duration; + +import org.cloudfoundry.multiapps.common.ContentException; +import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudApplicationExtended; +import org.cloudfoundry.multiapps.controller.process.steps.ProcessContext; +import org.cloudfoundry.multiapps.controller.process.variables.Variables; +import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.Mockito.when; + +class TimeoutValueResolverTest { + + private TimeoutValueResolver timeoutValueResolver; + + @Mock + private ProcessContext context; + + @Mock + private StepLogger stepLogger; + + @BeforeEach + void setUp() throws Exception { + MockitoAnnotations.openMocks(this).close(); + timeoutValueResolver = new TimeoutValueResolver(); + } + + @Test + void testResolveTimeoutFromProcessVariable() { + Duration expectedTimeout = Duration.ofSeconds(600); + when(context.getVariableIfSet(Variables.APPS_UPLOAD_TIMEOUT_PROCESS_VARIABLE)) + .thenReturn(expectedTimeout); + when(context.getVariable(Variables.APP_TO_PROCESS)).thenReturn(null); + when(context.getVariable(Variables.DEPLOYMENT_DESCRIPTOR)).thenReturn(null); + + TimeoutValueResolver.TimeoutResolution resolution = + timeoutValueResolver.resolveTimeout(context, TimeoutType.UPLOAD, stepLogger); + + assertNotNull(resolution); + assertEquals(expectedTimeout, resolution.timeout()); + } + + @Test + void testResolveTimeoutFromAppAttributes() { + var app = ImmutableCloudApplicationExtended.builder() + .name("test-app") + .build(); + + when(context.getVariableIfSet(Variables.APPS_UPLOAD_TIMEOUT_PROCESS_VARIABLE)).thenReturn(null); + when(context.getVariable(Variables.APP_TO_PROCESS)).thenReturn(app); + when(context.getVariable(Variables.DEPLOYMENT_DESCRIPTOR)).thenReturn(null); + + TimeoutValueResolver.TimeoutResolution resolution = + timeoutValueResolver.resolveTimeout(context, TimeoutType.START, stepLogger); + + assertNotNull(resolution); + assertNotNull(resolution.timeout()); + } + + @Test + void testResolveTimeoutFromDescriptorParameters() { + } + + @Test + void testResolveTimeoutReturnsDefaultWhenNoTimeoutFound() { + when(context.getVariableIfSet(Variables.APPS_UPLOAD_TIMEOUT_PROCESS_VARIABLE)).thenReturn(null); + when(context.getVariable(Variables.APP_TO_PROCESS)).thenReturn(null); + when(context.getVariable(Variables.DEPLOYMENT_DESCRIPTOR)).thenReturn(null); + when(context.getVariable(Variables.DEPLOYMENT_DESCRIPTOR_WITH_SYSTEM_PARAMETERS)).thenReturn(null); + when(context.getVariable(Variables.COMPLETE_DEPLOYMENT_DESCRIPTOR)).thenReturn(null); + + TimeoutValueResolver.TimeoutResolution resolution = + timeoutValueResolver.resolveTimeout(context, TimeoutType.UPLOAD, stepLogger); + + assertNotNull(resolution); + assertEquals(Duration.ofSeconds(3600), resolution.timeout()); + } + + @Test + void testToDurationWithValidPositiveValue() { + Duration duration = timeoutValueResolver.toDuration(300, "testParam", 3600); + + assertEquals(Duration.ofSeconds(300), duration); + } + + @Test + void testToDurationWithZero() { + Duration duration = timeoutValueResolver.toDuration(0, "testParam", 3600); + + assertEquals(Duration.ZERO, duration); + } + + @Test + void testToDurationWithMaxValue() { + Duration duration = timeoutValueResolver.toDuration(3600, "testParam", 3600); + + assertEquals(Duration.ofSeconds(3600), duration); + } + + @Test + void testToDurationThrowsExceptionForNegativeValue() { + assertThrows(ContentException.class, () -> + timeoutValueResolver.toDuration(-1, "testParam", 3600) + ); + } + + @Test + void testToDurationThrowsExceptionForValueExceedingMax() { + assertThrows(ContentException.class, () -> + timeoutValueResolver.toDuration(3601, "testParam", 3600) + ); + } + + @Test + void testToDurationThrowsExceptionForNonNumberValue() { + assertThrows(ContentException.class, () -> + timeoutValueResolver.toDuration("invalid", "testParam", 3600) + ); + } + + @Test + void testToDurationReturnsNullForNullValue() { + Duration duration = timeoutValueResolver.toDuration(null, "testParam", 3600); + + assertNull(duration); + } + + @Test + void testGetDeploymentDescriptorReturnsFirstAvailable() { + DeploymentDescriptor descriptor = DeploymentDescriptor.createV3(); + when(context.getVariable(Variables.DEPLOYMENT_DESCRIPTOR)).thenReturn(descriptor); + + DeploymentDescriptor result = timeoutValueResolver.getDeploymentDescriptor(context, stepLogger); + + assertEquals(descriptor, result); + } + + @Test + void testGetDeploymentDescriptorReturnsFallbackDescriptor() { + DeploymentDescriptor fallbackDescriptor = DeploymentDescriptor.createV3(); + when(context.getVariable(Variables.DEPLOYMENT_DESCRIPTOR)).thenReturn(null); + when(context.getVariable(Variables.DEPLOYMENT_DESCRIPTOR_WITH_SYSTEM_PARAMETERS)) + .thenReturn(fallbackDescriptor); + + DeploymentDescriptor result = timeoutValueResolver.getDeploymentDescriptor(context, stepLogger); + + assertEquals(fallbackDescriptor, result); + } + + @Test + void testGetDeploymentDescriptorReturnsCompleteDescriptor() { + DeploymentDescriptor completeDescriptor = DeploymentDescriptor.createV3(); + when(context.getVariable(Variables.DEPLOYMENT_DESCRIPTOR)).thenReturn(null); + when(context.getVariable(Variables.DEPLOYMENT_DESCRIPTOR_WITH_SYSTEM_PARAMETERS)).thenReturn(null); + when(context.getVariable(Variables.COMPLETE_DEPLOYMENT_DESCRIPTOR)).thenReturn(completeDescriptor); + + DeploymentDescriptor result = timeoutValueResolver.getDeploymentDescriptor(context, stepLogger); + + assertEquals(completeDescriptor, result); + } + + @Test + void testGetDeploymentDescriptorReturnsNullWhenAllUnavailable() { + when(context.getVariable(Variables.DEPLOYMENT_DESCRIPTOR)).thenReturn(null); + when(context.getVariable(Variables.DEPLOYMENT_DESCRIPTOR_WITH_SYSTEM_PARAMETERS)).thenReturn(null); + when(context.getVariable(Variables.COMPLETE_DEPLOYMENT_DESCRIPTOR)).thenReturn(null); + + DeploymentDescriptor result = timeoutValueResolver.getDeploymentDescriptor(context, stepLogger); + + assertNull(result); + } + + @Test + void testToDurationWithDoubleValue() { + Duration duration = timeoutValueResolver.toDuration(300.5, "testParam", 3600); + + assertEquals(Duration.ofSeconds(300), duration); + } + + @Test + void testToDurationWithLongValue() { + Duration duration = timeoutValueResolver.toDuration(300L, "testParam", 3600); + + assertEquals(Duration.ofSeconds(300), duration); + } + +} + + + + + + diff --git a/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/api/impl/OperationsApiServiceImpl.java b/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/api/impl/OperationsApiServiceImpl.java index d25fdb32e2..d439e5d5ee 100644 --- a/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/api/impl/OperationsApiServiceImpl.java +++ b/multiapps-controller-web/src/main/java/org/cloudfoundry/multiapps/controller/web/api/impl/OperationsApiServiceImpl.java @@ -38,6 +38,7 @@ import org.cloudfoundry.multiapps.controller.process.flowable.ProcessActionRegistry; import org.cloudfoundry.multiapps.controller.process.metadata.ProcessTypeToOperationMetadataMapper; import org.cloudfoundry.multiapps.controller.process.util.OperationsHelper; +import org.cloudfoundry.multiapps.controller.process.util.TimeoutType; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.cloudfoundry.multiapps.controller.web.Constants; import org.cloudfoundry.multiapps.controller.web.Messages; @@ -301,12 +302,24 @@ private Operation addServiceParameters(Operation operation, String spaceGuid, St } private Operation addParameterValues(Operation operation, Set predefinedParameters) { - Map filteredParameters = filterUnnecessaryParameters(predefinedParameters, operation.getParameters()); + Map originalParameters = operation.getParameters(); + Map filteredParameters = filterUnnecessaryParameters(predefinedParameters, originalParameters); filteredParameters.putAll(ParameterConversion.toFlowableVariables(predefinedParameters, filteredParameters)); + addServiceTimeoutOperationParamsFlags(originalParameters, filteredParameters); return ImmutableOperation.copyOf(operation) .withParameters(filteredParameters); } + private void addServiceTimeoutOperationParamsFlags(Map originalParameters, Map targetParameters) { + Arrays.stream(TimeoutType.values()) + .filter(TimeoutType::isServiceScoped) + .filter(type -> type.getOperationParamsFlag() != null) + .filter(type -> originalParameters.containsKey(type.getProcessVariable() + .getName())) + .forEach(type -> targetParameters.put(type.getOperationParamsFlag() + .getName(), true)); + } + private Map filterUnnecessaryParameters(Set predefinedParameters, Map parameters) { Set allowedParameters = getAllowedParameters(predefinedParameters);