From 14794c77a2749d6c5e0b8a169d3e8ad0ee8222a2 Mon Sep 17 00:00:00 2001 From: IvanBorislavovDimitrov Date: Mon, 1 Jun 2026 18:01:49 +0300 Subject: [PATCH 1/4] Add health-check-interval module parameter plumbing Wires a new optional Integer module parameter `health-check-interval` through the client/core layer in parallel with the existing readiness-health-check-interval. Touched layers: - SupportedParameters: declare HEALTH_CHECK_INTERVAL constant and register it in MODULE_PARAMETERS. - StagingParametersParser: read the descriptor entry and populate Staging.healthCheckInterval. - Staging / CloudProcess: expose @Nullable Integer accessors so both the desired and existing-app sides of staging-change comparison carry the field. - RawCloudProcess: harvest the value from HealthCheck.getData().getInterval(). - CloudControllerRestClientImpl#buildHealthCheck: forward the value via Data.builder().interval(...). The cf-java-client setter ignores null, so omitting the parameter produces a byte-identical request. - HealthCheckInfo: add the field to the value object compared by StagingApplicationAttributeUpdater so an interval-only change triggers a staging update; also adds the missing hashCode override. JIRA:LMCROSSITXSADEPLOY-3316 --- .../facade/adapters/RawCloudProcess.java | 3 +++ .../client/facade/domain/CloudProcess.java | 3 +++ .../client/facade/domain/Staging.java | 6 +++++ .../rest/CloudControllerRestClientImpl.java | 1 + .../client/lib/domain/HealthCheckInfo.java | 22 +++++++++++++++---- .../core/model/SupportedParameters.java | 2 ++ .../core/parser/StagingParametersParser.java | 4 ++++ 7 files changed, 37 insertions(+), 4 deletions(-) diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudProcess.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudProcess.java index 4bd4b83775..931f528b75 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudProcess.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudProcess.java @@ -23,11 +23,13 @@ public CloudProcess derive() { Integer healthCheckTimeout = null; String healthCheckHttpEndpoint = null; Integer healthCheckInvocationTimeout = null; + Integer healthCheckInterval = null; if (healthCheck.getData() != null) { Data healthCheckData = healthCheck.getData(); healthCheckTimeout = healthCheckData.getTimeout(); healthCheckInvocationTimeout = healthCheckData.getInvocationTimeout(); healthCheckHttpEndpoint = healthCheckData.getEndpoint(); + healthCheckInterval = healthCheckData.getInterval(); } Integer readinessHealthCheckInvocationTimeout = null; String readinessHealthCheckHttpEndpoint = null; @@ -49,6 +51,7 @@ public CloudProcess derive() { .healthCheckHttpEndpoint(healthCheckHttpEndpoint) .healthCheckTimeout(healthCheckTimeout) .healthCheckInvocationTimeout(healthCheckInvocationTimeout) + .healthCheckInterval(healthCheckInterval) .readinessHealthCheckType(readinessHealthCheckType.getType() .getValue()) .readinessHealthCheckHttpEndpoint(readinessHealthCheckHttpEndpoint) diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudProcess.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudProcess.java index 86df6003a7..ef141ae3fe 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudProcess.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/domain/CloudProcess.java @@ -29,6 +29,9 @@ public abstract class CloudProcess extends CloudEntity implements Derivable> parametersList) { Integer healthCheckInvocationTimeout = (Integer) PropertiesUtil.getPropertyValue(parametersList, SupportedParameters.HEALTH_CHECK_INVOCATION_TIMEOUT, null); + Integer healthCheckInterval = (Integer) PropertiesUtil.getPropertyValue(parametersList, + SupportedParameters.HEALTH_CHECK_INTERVAL, + null); String healthCheckType = (String) PropertiesUtil.getPropertyValue(parametersList, SupportedParameters.HEALTH_CHECK_TYPE, null); String healthCheckHttpEndpoint = (String) PropertiesUtil.getPropertyValue(parametersList, SupportedParameters.HEALTH_CHECK_HTTP_ENDPOINT, @@ -67,6 +70,7 @@ public Staging parse(List> parametersList) { .stackName(stackName) .healthCheckTimeout(healthCheckTimeout) .invocationTimeout(healthCheckInvocationTimeout) + .healthCheckInterval(healthCheckInterval) .healthCheckType(healthCheckType) .healthCheckHttpEndpoint(healthCheckHttpEndpoint) .readinessHealthCheckType(readinessHealthCheckType) From 5bab70cca6e6e7d9481e197e01190e58cc584be7 Mon Sep 17 00:00:00 2001 From: IvanBorislavovDimitrov Date: Mon, 1 Jun 2026 18:10:51 +0300 Subject: [PATCH 2/4] Report liveness health-check parameters in additional reporter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Extends AdditionalModuleParametersReporter to surface the liveness health-check family (type, http endpoint, timeout, invocation timeout, interval) when any of those parameters is present on a module — in parallel with the existing readiness reporting. The new reportUsageOfHealthCheckParameters method mirrors the readiness method verbatim and is intentionally kept as a sibling rather than refactored into a generic helper (rule of three has not been met). Wording is the same MessageFormat shape as the readiness log line, substituting "readiness health check parameters" with "health check parameters" so existing Dynatrace observability on the readiness line is unaffected. JIRA:LMCROSSITXSADEPLOY-3316 --- .../AdditionalModuleParametersReporter.java | 36 +++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/AdditionalModuleParametersReporter.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/AdditionalModuleParametersReporter.java index 1d2cff63be..e09dfd0c68 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/AdditionalModuleParametersReporter.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/AdditionalModuleParametersReporter.java @@ -25,6 +25,13 @@ public void reportUsageOfAdditionalParameters(Module module) { String mtaId = context.getRequiredVariable(Variables.DEPLOYMENT_DESCRIPTOR) .getId(); String correlationId = context.getRequiredVariable(Variables.CORRELATION_ID); + List buildpacks = PropertiesUtil.getPluralOrSingular(List.of(module.getParameters()), SupportedParameters.BUILDPACKS, + SupportedParameters.BUILDPACK); + reportReadinessHealthCheckIfPresent(module, mtaId, correlationId, buildpacks); + reportHealthCheckIfPresent(module, mtaId, correlationId, buildpacks); + } + + private void reportReadinessHealthCheckIfPresent(Module module, String mtaId, String correlationId, List buildpacks) { String readinessHealthCheckType = (String) module.getParameters() .get(SupportedParameters.READINESS_HEALTH_CHECK_TYPE); String readinessHealthCheckHttpEndpoint = (String) module.getParameters() @@ -33,8 +40,6 @@ public void reportUsageOfAdditionalParameters(Module module) { .get(SupportedParameters.READINESS_HEALTH_CHECK_INVOCATION_TIMEOUT); Integer readinessHealthCheckInterval = (Integer) module.getParameters() .get(SupportedParameters.READINESS_HEALTH_CHECK_INTERVAL); - List buildpacks = PropertiesUtil.getPluralOrSingular(List.of(module.getParameters()), SupportedParameters.BUILDPACKS, - SupportedParameters.BUILDPACK); if (readinessHealthCheckType != null) { reportUsageOfReadinessHealthCheckParameters(mtaId, correlationId, readinessHealthCheckType, readinessHealthCheckHttpEndpoint, readinessHealthCheckInvocationTimeout, readinessHealthCheckInterval, @@ -51,4 +56,31 @@ private void reportUsageOfReadinessHealthCheckParameters(String mtaId, String co mtaId, correlationId, readinessHealthCheckType, readinessHealthCheckHttpEndpoint, readinessHealthCheckInvocationTimeout, readinessHealthCheckInterval, buildpacks, moduleType)); } + + private void reportHealthCheckIfPresent(Module module, String mtaId, String correlationId, List buildpacks) { + String healthCheckType = (String) module.getParameters() + .get(SupportedParameters.HEALTH_CHECK_TYPE); + String healthCheckHttpEndpoint = (String) module.getParameters() + .get(SupportedParameters.HEALTH_CHECK_HTTP_ENDPOINT); + Integer healthCheckTimeout = (Integer) module.getParameters() + .get(SupportedParameters.HEALTH_CHECK_TIMEOUT); + Integer healthCheckInvocationTimeout = (Integer) module.getParameters() + .get(SupportedParameters.HEALTH_CHECK_INVOCATION_TIMEOUT); + Integer healthCheckInterval = (Integer) module.getParameters() + .get(SupportedParameters.HEALTH_CHECK_INTERVAL); + if (healthCheckType != null || healthCheckHttpEndpoint != null || healthCheckTimeout != null || healthCheckInvocationTimeout != null + || healthCheckInterval != null) { + reportUsageOfHealthCheckParameters(mtaId, correlationId, healthCheckType, healthCheckHttpEndpoint, healthCheckTimeout, + healthCheckInvocationTimeout, healthCheckInterval, buildpacks.toString(), module.getType()); + } + } + + private void reportUsageOfHealthCheckParameters(String mtaId, String correlationId, String healthCheckType, + String healthCheckHttpEndpoint, Integer healthCheckTimeout, + Integer healthCheckInvocationTimeout, Integer healthCheckInterval, String buildpacks, + String moduleType) { + LOGGER.info(MessageFormat.format("MTA with ID \"{0}\" associated with operation ID \"{1}\" uses health check parameters: type=\"{2}\", httpEndpoint=\"{3}\", timeout=\"{4}\", invocationTimeout=\"{5}\", interval=\"{6}\", buildpacks=\"{7}\", moduleType=\"{8}\"", + mtaId, correlationId, healthCheckType, healthCheckHttpEndpoint, healthCheckTimeout, + healthCheckInvocationTimeout, healthCheckInterval, buildpacks, moduleType)); + } } From 241e83f1246b191c136c3650f326a87cc43d022a Mon Sep 17 00:00:00 2001 From: IvanBorislavovDimitrov Date: Mon, 1 Jun 2026 18:11:06 +0300 Subject: [PATCH 3/4] Add tests for health-check-interval - CloudModelBuilderTest fixtures: add health-check-interval to two mtad-health-check-*.yaml inputs and the matching expected JSON outputs (port=30, http-with-endpoint=45). The third http-without-endpoint case is left without the parameter to keep the "omitted -> null" assertion case intact. - ApplicationsCloudControllerClientIntegrationTest: set healthCheckInterval on the staging used in the createApplication test, then assert the value round-trips through the controller via CloudProcess.getHealthCheckInterval(). New constant HEALTH_CHECK_INTERVAL=60 added to IntegrationTestConstants. - CreateOrUpdateStepWithExistingAppTest: add a parameterized row whose only delta between old and new staging is healthCheckInterval, and propagate the field through the mocked CloudProcess so equality comparison sees both sides; verifies an interval-only change drives StagingApplicationAttributeUpdater to update. - ReadinessHealthCheckUtilTest: new test asserting that setting healthCheckInterval alone (without any readiness parameter) does NOT flip the readiness gate (FR-7-style guarantee). - RestartAppStepTest, PollStartAppStatusExecutionTest, PrepareToStopDependentModuleStepTest: propagate the new field through their existing Staging literals. - AdditionalModuleParametersReporterTest (new): three focused tests asserting that the reporter (a) handles a module declaring only health-check-interval, (b) handles the full liveness family, and (c) handles a module with no health-check parameters at all, without throwing. JIRA:LMCROSSITXSADEPLOY-3316 --- ...sCloudControllerClientIntegrationTest.java | 5 ++ .../facade/IntegrationTestConstants.java | 1 + ...-health-check-type-http-with-endpoint.json | 1 + .../v2/apps-with-health-check-type-port.json | 1 + ...-health-check-type-http-with-endpoint.yaml | 1 + .../cf/v2/mtad-health-check-type-port.yaml | 1 + ...CreateOrUpdateStepWithExistingAppTest.java | 10 ++- .../PollStartAppStatusExecutionTest.java | 1 + .../PrepareToStopDependentModuleStepTest.java | 1 + .../process/steps/RestartAppStepTest.java | 1 + ...dditionalModuleParametersReporterTest.java | 77 +++++++++++++++++++ .../util/ReadinessHealthCheckUtilTest.java | 15 ++++ 12 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/AdditionalModuleParametersReporterTest.java diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ApplicationsCloudControllerClientIntegrationTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ApplicationsCloudControllerClientIntegrationTest.java index 4cc72cbed1..1902d8768d 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ApplicationsCloudControllerClientIntegrationTest.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/ApplicationsCloudControllerClientIntegrationTest.java @@ -39,6 +39,7 @@ import static org.cloudfoundry.multiapps.controller.client.facade.IntegrationTestConstants.DEFAULT_DOMAIN; import static org.cloudfoundry.multiapps.controller.client.facade.IntegrationTestConstants.DISK_IN_MB; import static org.cloudfoundry.multiapps.controller.client.facade.IntegrationTestConstants.HEALTH_CHECK_ENDPOINT; +import static org.cloudfoundry.multiapps.controller.client.facade.IntegrationTestConstants.HEALTH_CHECK_INTERVAL; import static org.cloudfoundry.multiapps.controller.client.facade.IntegrationTestConstants.HEALTH_CHECK_TIMEMOUT; import static org.cloudfoundry.multiapps.controller.client.facade.IntegrationTestConstants.JAVA_BUILDPACK; import static org.cloudfoundry.multiapps.controller.client.facade.IntegrationTestConstants.JAVA_BUILDPACK_URL; @@ -88,10 +89,14 @@ void createApplication() { .healthCheckType(HealthCheckType.PROCESS.getValue()) .healthCheckHttpEndpoint(HEALTH_CHECK_ENDPOINT) .healthCheckTimeout(HEALTH_CHECK_TIMEMOUT) + .healthCheckInterval(HEALTH_CHECK_INTERVAL) .build(); CloudRoute route = getImmutableCloudRoute(); try { verifyApplicationWillBeCreated(applicationName, staging, Set.of(route)); + UUID applicationGuid = client.getApplicationGuid(applicationName); + CloudProcess cloudProcess = client.getApplicationProcess(applicationGuid); + assertEquals(HEALTH_CHECK_INTERVAL, cloudProcess.getHealthCheckInterval()); } catch (Exception e) { fail(e); } finally { diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/IntegrationTestConstants.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/IntegrationTestConstants.java index 70a7b1742e..b7edbbae9f 100644 --- a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/IntegrationTestConstants.java +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/IntegrationTestConstants.java @@ -10,6 +10,7 @@ private IntegrationTestConstants() { public static final String NODEJS_BUILDPACK = "nodejs_buildpack"; public static final String STATICFILE_BUILDPACK = "staticfile_buildpack"; public static final int HEALTH_CHECK_TIMEMOUT = 100; + public static final int HEALTH_CHECK_INTERVAL = 60; public static final int DISK_IN_MB = 128; public static final int MEMORY_IN_MB = 128; public static final String DEFAULT_DOMAIN = "deploy-service.custom.domain.for.integration.tests"; diff --git a/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/v2/apps-with-health-check-type-http-with-endpoint.json b/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/v2/apps-with-health-check-type-http-with-endpoint.json index b1d69773e8..76bbc81bce 100644 --- a/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/v2/apps-with-health-check-type-http-with-endpoint.json +++ b/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/v2/apps-with-health-check-type-http-with-endpoint.json @@ -21,6 +21,7 @@ "buildpacks": [], "healthCheckType": "http", "healthCheckHttpEndpoint": "/health", + "healthCheckInterval": 45, "appFeatures": {} }, "routes": [ diff --git a/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/v2/apps-with-health-check-type-port.json b/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/v2/apps-with-health-check-type-port.json index 50c38dc520..e571fb94da 100644 --- a/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/v2/apps-with-health-check-type-port.json +++ b/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/v2/apps-with-health-check-type-port.json @@ -20,6 +20,7 @@ "staging": { "buildpacks": [], "healthCheckType": "port", + "healthCheckInterval": 30, "appFeatures": {} }, "routes": [ diff --git a/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/v2/mtad-health-check-type-http-with-endpoint.yaml b/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/v2/mtad-health-check-type-http-with-endpoint.yaml index e7b3b4f0ff..dfa5c61f08 100644 --- a/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/v2/mtad-health-check-type-http-with-endpoint.yaml +++ b/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/v2/mtad-health-check-type-http-with-endpoint.yaml @@ -8,3 +8,4 @@ modules: parameters: health-check-type: http health-check-http-endpoint: /health + health-check-interval: 45 diff --git a/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/v2/mtad-health-check-type-port.yaml b/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/v2/mtad-health-check-type-port.yaml index 0b34885c33..44ec4cb14d 100644 --- a/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/v2/mtad-health-check-type-port.yaml +++ b/multiapps-controller-core/src/test/resources/org/cloudfoundry/multiapps/controller/core/cf/v2/mtad-health-check-type-port.yaml @@ -7,3 +7,4 @@ modules: type: foo parameters: health-check-type: port + health-check-interval: 30 diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/CreateOrUpdateStepWithExistingAppTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/CreateOrUpdateStepWithExistingAppTest.java index 78f20b5f65..9ec37642c8 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/CreateOrUpdateStepWithExistingAppTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/CreateOrUpdateStepWithExistingAppTest.java @@ -111,7 +111,13 @@ static Stream testHandleStagingApplicationAttributes() { true, LifecycleType.CNB), Arguments.of(ImmutableStaging.builder().addBuildpack("buildpack-333").build(), ImmutableStaging.builder().addBuildpacks("buildpack-4", "buildpack-8").lifecycleType(LifecycleType.CNB).build(), - true, LifecycleType.CNB)); + true, LifecycleType.CNB), + Arguments.of( + ImmutableStaging.builder().addBuildpack("buildpack-1").command("command1").stackName("stack1") + .healthCheckType("port").build(), + ImmutableStaging.builder().addBuildpack("buildpack-1").command("command1").stackName("stack1") + .healthCheckType("port").healthCheckInterval(15).build(), + true, LifecycleType.BUILDPACK)); //@formatter:on } @@ -233,6 +239,7 @@ private void prepareClient(CloudApplication application, Set routes, var hcType = staging.getHealthCheckType(); var hcTimeout = staging.getHealthCheckTimeout(); var hcEndpoint = staging.getHealthCheckHttpEndpoint(); + var hcInterval = staging.getHealthCheckInterval(); when(client.getApplicationProcess(application.getGuid())).thenReturn(ImmutableCloudProcess.builder() .command(command) .diskInMb(disk == null ? 1024 : disk) @@ -244,6 +251,7 @@ private void prepareClient(CloudApplication application, Set routes, hcType.toUpperCase())) .healthCheckTimeout(hcTimeout) .healthCheckHttpEndpoint(hcEndpoint) + .healthCheckInterval(hcInterval) .instances(1) .build()); } diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollStartAppStatusExecutionTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollStartAppStatusExecutionTest.java index 0a5d2cebb3..f231cb8c42 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollStartAppStatusExecutionTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollStartAppStatusExecutionTest.java @@ -119,6 +119,7 @@ private CloudApplicationExtended buildApplication(int instancesCount, boolean is .instances(instancesCount) .staging(ImmutableStaging.builder() .readinessHealthCheckType(isReadinessEnabled ? "test" : null) + .healthCheckInterval(30) .build()) .build(); } diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareToStopDependentModuleStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareToStopDependentModuleStepTest.java index 7b89dbd125..5845276736 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareToStopDependentModuleStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PrepareToStopDependentModuleStepTest.java @@ -82,6 +82,7 @@ private void setUpMocks(Module module) { private Staging createStaging() { return ImmutableStaging.builder() .readinessHealthCheckType("http") + .healthCheckInterval(30) .build(); } diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/RestartAppStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/RestartAppStepTest.java index 83fe91c9be..7bdc10e287 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/RestartAppStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/RestartAppStepTest.java @@ -93,6 +93,7 @@ private CloudApplicationExtended createApplication(String name, CloudApplication .state(state) .staging(ImmutableStaging.builder() .readinessHealthCheckType(isReadinessEnabled ? "test" : null) + .healthCheckInterval(30) .build()) .build(); } diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/AdditionalModuleParametersReporterTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/AdditionalModuleParametersReporterTest.java new file mode 100644 index 0000000000..bcf31afb7c --- /dev/null +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/AdditionalModuleParametersReporterTest.java @@ -0,0 +1,77 @@ +package org.cloudfoundry.multiapps.controller.process.util; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.cloudfoundry.multiapps.controller.core.cf.CloudControllerClientProvider; +import org.cloudfoundry.multiapps.controller.core.model.SupportedParameters; +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; +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.assertDoesNotThrow; + +class AdditionalModuleParametersReporterTest { + + private static final String MODULE_TYPE = "java"; + + @Mock + private StepLogger stepLogger; + @Mock + private CloudControllerClientProvider clientProvider; + + private AdditionalModuleParametersReporter reporter; + + @BeforeEach + void setUp() { + MockitoAnnotations.openMocks(this); + DelegateExecution execution = MockDelegateExecution.createSpyInstance(); + ProcessContext context = new ProcessContext(execution, stepLogger, clientProvider); + context.setVariable(Variables.DEPLOYMENT_DESCRIPTOR, DeploymentDescriptor.createV3() + .setId("test-mta") + .setVersion("1.0.0") + .setModules(List.of()) + .setResources(List.of())); + context.setVariable(Variables.CORRELATION_ID, "correlation-id-1"); + reporter = new AdditionalModuleParametersReporter(context); + } + + @Test + void testReportingHealthCheckIntervalOnlyDoesNotThrow() { + Module module = buildModuleWithParameters(Map.of(SupportedParameters.HEALTH_CHECK_INTERVAL, 30)); + assertDoesNotThrow(() -> reporter.reportUsageOfAdditionalParameters(module)); + } + + @Test + void testReportingFullHealthCheckFamilyDoesNotThrow() { + Map parameters = new HashMap<>(); + parameters.put(SupportedParameters.HEALTH_CHECK_TYPE, "http"); + parameters.put(SupportedParameters.HEALTH_CHECK_HTTP_ENDPOINT, "/health"); + parameters.put(SupportedParameters.HEALTH_CHECK_TIMEOUT, 10); + parameters.put(SupportedParameters.HEALTH_CHECK_INVOCATION_TIMEOUT, 5); + parameters.put(SupportedParameters.HEALTH_CHECK_INTERVAL, 45); + Module module = buildModuleWithParameters(parameters); + assertDoesNotThrow(() -> reporter.reportUsageOfAdditionalParameters(module)); + } + + @Test + void testReportingWithNoHealthCheckParametersDoesNotThrow() { + Module module = buildModuleWithParameters(Map.of()); + assertDoesNotThrow(() -> reporter.reportUsageOfAdditionalParameters(module)); + } + + private Module buildModuleWithParameters(Map parameters) { + Module module = Module.createV3() + .setName("test-module") + .setType(MODULE_TYPE); + module.setParameters(parameters); + return module; + } +} diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ReadinessHealthCheckUtilTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ReadinessHealthCheckUtilTest.java index 781efad760..c5cefb02e3 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ReadinessHealthCheckUtilTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ReadinessHealthCheckUtilTest.java @@ -10,12 +10,14 @@ import org.cloudfoundry.multiapps.controller.process.steps.ProcessContext; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.flowable.engine.delegate.DelegateExecution; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Mock; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; class ReadinessHealthCheckUtilTest { @@ -43,6 +45,19 @@ void testShouldWaitForAppToBecomeRoutable(String readinessHealthCheckType, boole createContext(readinessHealthCheckType))); } + @Test + void testHealthCheckIntervalAloneDoesNotTriggerReadinessGate() { + DelegateExecution execution = MockDelegateExecution.createSpyInstance(); + ProcessContext context = new ProcessContext(execution, stepLogger, clientProvider); + Staging staging = ImmutableStaging.builder() + .healthCheckInterval(30) + .build(); + context.setVariable(Variables.APP_TO_PROCESS, ImmutableCloudApplicationExtended.builder() + .staging(staging) + .build()); + assertFalse(ReadinessHealthCheckUtil.shouldWaitForAppToBecomeRoutable(context)); + } + private ProcessContext createContext(String readinessHealthCheckType) { DelegateExecution execution = MockDelegateExecution.createSpyInstance(); ProcessContext context = new ProcessContext(execution, stepLogger, clientProvider); From ba3e9bfe638642a5541abbe1700b64e4eedc9acd Mon Sep 17 00:00:00 2001 From: IvanBorislavovDimitrov Date: Mon, 1 Jun 2026 18:17:03 +0300 Subject: [PATCH 4/4] Add unit tests for health-check-interval JIRA:LMCROSSITXSADEPLOY-3316 --- .../lib/domain/HealthCheckInfoTest.java | 164 ++++++++++++++++++ .../parser/StagingParametersParserTest.java | 18 ++ 2 files changed, 182 insertions(+) create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/lib/domain/HealthCheckInfoTest.java diff --git a/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/lib/domain/HealthCheckInfoTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/lib/domain/HealthCheckInfoTest.java new file mode 100644 index 0000000000..186e9352fa --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/lib/domain/HealthCheckInfoTest.java @@ -0,0 +1,164 @@ +package org.cloudfoundry.multiapps.controller.client.lib.domain; + +import org.cloudfoundry.multiapps.controller.client.facade.domain.HealthCheckType; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudProcess; +import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableStaging; +import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class HealthCheckInfoTest { + + private static final String HTTP_TYPE = "http"; + private static final String PORT_TYPE = "port"; + private static final String ENDPOINT = "/health"; + private static final Integer TIMEOUT = 10; + private static final Integer INVOCATION_TIMEOUT = 5; + private static final Integer INTERVAL = 30; + + @Test + void testFromStagingExposesAllFieldsIncludingInterval() { + Staging staging = ImmutableStaging.builder() + .healthCheckType(HTTP_TYPE) + .healthCheckTimeout(TIMEOUT) + .invocationTimeout(INVOCATION_TIMEOUT) + .healthCheckHttpEndpoint(ENDPOINT) + .healthCheckInterval(INTERVAL) + .build(); + + HealthCheckInfo info = HealthCheckInfo.fromStaging(staging); + + assertEquals(HTTP_TYPE, info.getType()); + assertEquals(TIMEOUT, info.getTimeout()); + assertEquals(INVOCATION_TIMEOUT, info.getInvocationTimeout()); + assertEquals(ENDPOINT, info.getHttpEndpoint()); + assertEquals(INTERVAL, info.getInterval()); + } + + @Test + void testFromStagingDefaultsTypeToPortWhenAbsent() { + Staging staging = ImmutableStaging.builder() + .build(); + + HealthCheckInfo info = HealthCheckInfo.fromStaging(staging); + + assertEquals(PORT_TYPE, info.getType()); + assertNull(info.getTimeout()); + assertNull(info.getInvocationTimeout()); + assertNull(info.getHttpEndpoint()); + assertNull(info.getInterval()); + } + + @Test + void testFromStagingPropagatesNullInterval() { + Staging staging = ImmutableStaging.builder() + .healthCheckType(HTTP_TYPE) + .build(); + + HealthCheckInfo info = HealthCheckInfo.fromStaging(staging); + + assertEquals(HTTP_TYPE, info.getType()); + assertNull(info.getInterval()); + } + + @Test + void testFromProcessExposesAllFieldsIncludingInterval() { + ImmutableCloudProcess process = ImmutableCloudProcess.builder() + .command("./run.sh") + .diskInMb(1024) + .instances(1) + .memoryInMb(512) + .healthCheckType(HealthCheckType.HTTP) + .healthCheckHttpEndpoint(ENDPOINT) + .healthCheckTimeout(TIMEOUT) + .healthCheckInvocationTimeout(INVOCATION_TIMEOUT) + .healthCheckInterval(INTERVAL) + .build(); + + HealthCheckInfo info = HealthCheckInfo.fromProcess(process); + + assertEquals(HealthCheckType.HTTP.toString(), info.getType()); + assertEquals(TIMEOUT, info.getTimeout()); + assertEquals(INVOCATION_TIMEOUT, info.getInvocationTimeout()); + assertEquals(ENDPOINT, info.getHttpEndpoint()); + assertEquals(INTERVAL, info.getInterval()); + } + + @Test + void testFromProcessPropagatesNullInterval() { + ImmutableCloudProcess process = ImmutableCloudProcess.builder() + .command("./run.sh") + .diskInMb(1024) + .instances(1) + .memoryInMb(512) + .healthCheckType(HealthCheckType.PORT) + .build(); + + HealthCheckInfo info = HealthCheckInfo.fromProcess(process); + + assertEquals(HealthCheckType.PORT.toString(), info.getType()); + assertNull(info.getInterval()); + } + + @Test + void testEqualsReturnsTrueForSameValues() { + HealthCheckInfo first = HealthCheckInfo.fromStaging(buildStagingWithInterval(INTERVAL)); + HealthCheckInfo second = HealthCheckInfo.fromStaging(buildStagingWithInterval(INTERVAL)); + + assertEquals(first, second); + assertEquals(first.hashCode(), second.hashCode()); + } + + private static Staging buildStagingWithInterval(Integer interval) { + return ImmutableStaging.builder() + .healthCheckType(HTTP_TYPE) + .healthCheckTimeout(TIMEOUT) + .invocationTimeout(INVOCATION_TIMEOUT) + .healthCheckHttpEndpoint(ENDPOINT) + .healthCheckInterval(interval) + .build(); + } + + @Test + void testEqualsReturnsFalseWhenIntervalDiffers() { + HealthCheckInfo first = HealthCheckInfo.fromStaging(buildStagingWithInterval(30)); + HealthCheckInfo second = HealthCheckInfo.fromStaging(buildStagingWithInterval(60)); + + assertNotEquals(first, second); + } + + @Test + void testEqualsReturnsFalseWhenIntervalIsNullOnOneSide() { + HealthCheckInfo first = HealthCheckInfo.fromStaging(buildStagingWithInterval(INTERVAL)); + HealthCheckInfo second = HealthCheckInfo.fromStaging(buildStagingWithInterval(null)); + + assertNotEquals(first, second); + } + + @Test + void testEqualsReturnsTrueForReferenceIdentity() { + HealthCheckInfo info = HealthCheckInfo.fromStaging(buildStagingWithInterval(INTERVAL)); + + assertEquals(info, info); + } + + @Test + void testEqualsReturnsFalseForUnrelatedType() { + HealthCheckInfo info = HealthCheckInfo.fromStaging(buildStagingWithInterval(INTERVAL)); + + assertNotEquals(info, "not-a-health-check-info"); + } + + @Test + void testHashCodeRemainsStableAcrossInvocations() { + HealthCheckInfo info = HealthCheckInfo.fromStaging(buildStagingWithInterval(INTERVAL)); + + int firstHash = info.hashCode(); + int secondHash = info.hashCode(); + + assertEquals(firstHash, secondHash); + } +} diff --git a/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/parser/StagingParametersParserTest.java b/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/parser/StagingParametersParserTest.java index 98677fc307..06766dd350 100644 --- a/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/parser/StagingParametersParserTest.java +++ b/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/parser/StagingParametersParserTest.java @@ -138,6 +138,24 @@ void testValidateWithAllParametersMissing() { assertNull(staging.getDockerInfo()); } + @Test + void testHealthCheckIntervalIsParsedWhenProvided() { + parametersList.add(mapOf("health-check-interval", 45)); + + Staging staging = parser.parse(parametersList); + + assertNotNull(staging); + assertEquals(Integer.valueOf(45), staging.getHealthCheckInterval()); + } + + @Test + void testHealthCheckIntervalIsNullWhenAbsent() { + Staging staging = parser.parse(parametersList); + + assertNotNull(staging); + assertNull(staging.getHealthCheckInterval()); + } + private static Map mapOf(String key, Object value) { return Collections.singletonMap(key, value); }