From 2ee7b17906bc72f38e037b71991ed8531f6d9789 Mon Sep 17 00:00:00 2001 From: IvanBorislavovDimitrov Date: Mon, 1 Jun 2026 15:18:37 +0300 Subject: [PATCH 1/2] Introduce health-check-interval module parameter Add support for the MTA module-level parameter health-check-interval, mirroring the existing readiness-health-check-interval plumbing: - Register the constant in SupportedParameters and add it to MODULE_PARAMETERS so the descriptor whitelist accepts it. - Parse it in StagingParametersParser and forward it through the Staging value object. - Forward it to the CF Cloud Controller in CloudControllerRestClientImpl#buildHealthCheck so create and update flows propagate the value via Data.builder().interval(...). - Round-trip the value when reading processes from CF in RawCloudProcess#derive, with a matching getHealthCheckInterval() accessor on CloudProcess. - Surface the parameter in AdditionalModuleParametersReporter via a new MTA_USES_HEALTH_CHECK_INTERVAL_PARAMETER message constant. JIRA:LMCROSSITXSADEPLOY-3316 --- .../client/facade/adapters/RawCloudProcess.java | 3 +++ .../client/facade/domain/CloudProcess.java | 3 +++ .../controller/client/facade/domain/Staging.java | 6 ++++++ .../facade/rest/CloudControllerRestClientImpl.java | 1 + .../controller/core/model/SupportedParameters.java | 2 ++ .../core/parser/StagingParametersParser.java | 3 +++ .../multiapps/controller/process/Messages.java | 2 ++ .../util/AdditionalModuleParametersReporter.java | 14 ++++++++++++++ 8 files changed, 34 insertions(+) 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) { String healthCheckHttpEndpoint = (String) PropertiesUtil.getPropertyValue(parametersList, SupportedParameters.HEALTH_CHECK_HTTP_ENDPOINT, getDefaultHealthCheckHttpEndpoint(healthCheckType)); + Integer healthCheckInterval = (Integer) PropertiesUtil.getPropertyValue(parametersList, SupportedParameters.HEALTH_CHECK_INTERVAL, + null); String readinessHealthCheckType = (String) PropertiesUtil.getPropertyValue(parametersList, SupportedParameters.READINESS_HEALTH_CHECK_TYPE, null); String readinessHealthCheckHttpEndpoint = (String) PropertiesUtil.getPropertyValue(parametersList, @@ -69,6 +71,7 @@ public Staging parse(List> parametersList) { .invocationTimeout(healthCheckInvocationTimeout) .healthCheckType(healthCheckType) .healthCheckHttpEndpoint(healthCheckHttpEndpoint) + .healthCheckInterval(healthCheckInterval) .readinessHealthCheckType(readinessHealthCheckType) .readinessHealthCheckHttpEndpoint(readinessHealthCheckHttpEndpoint) .readinessHealthCheckInvocationTimeout(readinessHealthCheckInvocationTimeout) 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..6d47a6ada6 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 @@ -819,6 +819,8 @@ public class Messages { public static final String IGNORING_NOT_FOUND_OPTIONAL_SERVICE = "Service {0} not found but is optional"; public static final String IGNORING_NOT_FOUND_INACTIVE_SERVICE = "Service {0} not found but is inactive"; + public static final String MTA_USES_HEALTH_CHECK_INTERVAL_PARAMETER = "MTA with ID \"{0}\" associated with operation ID \"{1}\" uses health check interval parameter: interval=\"{2}\", buildpacks=\"{3}\", moduleType=\"{4}\""; + // Not log messages public static final String SERVICE_TYPE = "{0}/{1}"; public static final String PARSE_NULL_STRING_ERROR = "Cannot parse null string"; 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..06da8e767c 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 @@ -4,6 +4,7 @@ import java.util.List; import org.cloudfoundry.multiapps.controller.core.model.SupportedParameters; +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.Module; @@ -33,6 +34,8 @@ public void reportUsageOfAdditionalParameters(Module module) { .get(SupportedParameters.READINESS_HEALTH_CHECK_INVOCATION_TIMEOUT); Integer readinessHealthCheckInterval = (Integer) module.getParameters() .get(SupportedParameters.READINESS_HEALTH_CHECK_INTERVAL); + Integer healthCheckInterval = (Integer) module.getParameters() + .get(SupportedParameters.HEALTH_CHECK_INTERVAL); List buildpacks = PropertiesUtil.getPluralOrSingular(List.of(module.getParameters()), SupportedParameters.BUILDPACKS, SupportedParameters.BUILDPACK); if (readinessHealthCheckType != null) { @@ -40,6 +43,10 @@ public void reportUsageOfAdditionalParameters(Module module) { readinessHealthCheckInvocationTimeout, readinessHealthCheckInterval, buildpacks.toString(), module.getType()); } + if (healthCheckInterval != null) { + reportUsageOfLivenessHealthCheckIntervalParameter(mtaId, correlationId, healthCheckInterval, buildpacks.toString(), + module.getType()); + } } // this method is being observed by Dynatrace, be careful if you change it @@ -51,4 +58,11 @@ private void reportUsageOfReadinessHealthCheckParameters(String mtaId, String co mtaId, correlationId, readinessHealthCheckType, readinessHealthCheckHttpEndpoint, readinessHealthCheckInvocationTimeout, readinessHealthCheckInterval, buildpacks, moduleType)); } + + // this method is being observed by Dynatrace, be careful if you change it + private void reportUsageOfLivenessHealthCheckIntervalParameter(String mtaId, String correlationId, Integer healthCheckInterval, + String buildpacks, String moduleType) { + LOGGER.info(MessageFormat.format(Messages.MTA_USES_HEALTH_CHECK_INTERVAL_PARAMETER, mtaId, correlationId, healthCheckInterval, + buildpacks, moduleType)); + } } From b3d36c16c0ff63fd78bd122fa396dec02ebe89bf Mon Sep 17 00:00:00 2001 From: IvanBorislavovDimitrov Date: Mon, 1 Jun 2026 15:33:40 +0300 Subject: [PATCH 2/2] Add unit tests for health-check-interval module parameter JIRA:LMCROSSITXSADEPLOY-3316 --- .../facade/adapters/RawCloudProcessTest.java | 121 ++++++++++++++++ .../parser/StagingParametersParserTest.java | 30 ++++ ...dditionalModuleParametersReporterTest.java | 132 ++++++++++++++++++ 3 files changed, 283 insertions(+) create mode 100644 multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudProcessTest.java 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/adapters/RawCloudProcessTest.java b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudProcessTest.java new file mode 100644 index 0000000000..1ea45bba48 --- /dev/null +++ b/multiapps-controller-client/src/test/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawCloudProcessTest.java @@ -0,0 +1,121 @@ +package org.cloudfoundry.multiapps.controller.client.facade.adapters; + +import org.cloudfoundry.client.v3.processes.Data; +import org.cloudfoundry.client.v3.processes.HealthCheck; +import org.cloudfoundry.client.v3.processes.HealthCheckType; +import org.cloudfoundry.client.v3.processes.Process; +import org.cloudfoundry.client.v3.processes.ReadinessHealthCheck; +import org.cloudfoundry.client.v3.processes.ReadinessHealthCheckType; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudProcess; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +class RawCloudProcessTest { + + private static final Integer HEALTH_CHECK_TIMEOUT = 60; + private static final Integer HEALTH_CHECK_INVOCATION_TIMEOUT = 5; + private static final Integer HEALTH_CHECK_INTERVAL = 15; + private static final String HEALTH_CHECK_HTTP_ENDPOINT = "/health"; + private static final Integer READINESS_HEALTH_CHECK_INVOCATION_TIMEOUT = 4; + private static final Integer READINESS_HEALTH_CHECK_INTERVAL = 30; + private static final String READINESS_HEALTH_CHECK_HTTP_ENDPOINT = "/ready"; + private static final Integer INSTANCES = 2; + private static final Integer MEMORY_IN_MB = 256; + private static final Integer DISK_IN_MB = 512; + private static final String COMMAND = "start.sh"; + + @Test + void testDeriveRoundTripsHealthCheckInterval() { + Process process = buildProcess(buildHealthCheckWithInterval(HEALTH_CHECK_INTERVAL), + buildReadinessHealthCheckWithInterval(READINESS_HEALTH_CHECK_INTERVAL)); + + CloudProcess cloudProcess = ImmutableRawCloudProcess.of(process) + .derive(); + + assertEquals(HEALTH_CHECK_INTERVAL, cloudProcess.getHealthCheckInterval()); + } + + @Test + void testDeriveHealthCheckIntervalIsNullWhenHealthCheckDataIsNull() { + Process process = buildProcess(buildHealthCheckWithoutData(), + buildReadinessHealthCheckWithInterval(READINESS_HEALTH_CHECK_INTERVAL)); + + CloudProcess cloudProcess = ImmutableRawCloudProcess.of(process) + .derive(); + + assertNull(cloudProcess.getHealthCheckInterval()); + } + + @Test + void testDeriveHealthCheckIntervalIsNullWhenIntervalNotSetOnData() { + Process process = buildProcess(buildHealthCheckWithInterval(null), + buildReadinessHealthCheckWithInterval(READINESS_HEALTH_CHECK_INTERVAL)); + + CloudProcess cloudProcess = ImmutableRawCloudProcess.of(process) + .derive(); + + assertNull(cloudProcess.getHealthCheckInterval()); + } + + @Test + void testDeriveHealthCheckIntervalDoesNotCollideWithReadinessInterval() { + Process process = buildProcess(buildHealthCheckWithInterval(HEALTH_CHECK_INTERVAL), + buildReadinessHealthCheckWithInterval(READINESS_HEALTH_CHECK_INTERVAL)); + + CloudProcess cloudProcess = ImmutableRawCloudProcess.of(process) + .derive(); + + assertEquals(HEALTH_CHECK_INTERVAL, cloudProcess.getHealthCheckInterval()); + assertEquals(READINESS_HEALTH_CHECK_INTERVAL, cloudProcess.getReadinessHealthCheckInterval()); + } + + private static Process buildProcess(HealthCheck healthCheck, ReadinessHealthCheck readinessHealthCheck) { + Process process = Mockito.mock(Process.class); + Mockito.when(process.getHealthCheck()) + .thenReturn(healthCheck); + Mockito.when(process.getReadinessHealthCheck()) + .thenReturn(readinessHealthCheck); + Mockito.when(process.getCommand()) + .thenReturn(COMMAND); + Mockito.when(process.getInstances()) + .thenReturn(INSTANCES); + Mockito.when(process.getMemoryInMb()) + .thenReturn(MEMORY_IN_MB); + Mockito.when(process.getDiskInMb()) + .thenReturn(DISK_IN_MB); + return process; + } + + private static HealthCheck buildHealthCheckWithInterval(Integer interval) { + return HealthCheck.builder() + .type(HealthCheckType.HTTP) + .data(Data.builder() + .endpoint(HEALTH_CHECK_HTTP_ENDPOINT) + .timeout(HEALTH_CHECK_TIMEOUT) + .invocationTimeout(HEALTH_CHECK_INVOCATION_TIMEOUT) + .interval(interval) + .build()) + .build(); + } + + private static HealthCheck buildHealthCheckWithoutData() { + return HealthCheck.builder() + .type(HealthCheckType.PROCESS) + .build(); + } + + private static ReadinessHealthCheck buildReadinessHealthCheckWithInterval(Integer interval) { + return ReadinessHealthCheck.builder() + .type(ReadinessHealthCheckType.HTTP) + .data(Data.builder() + .endpoint(READINESS_HEALTH_CHECK_HTTP_ENDPOINT) + .invocationTimeout(READINESS_HEALTH_CHECK_INVOCATION_TIMEOUT) + .interval(interval) + .build()) + .build(); + } + +} 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..959dcb394e 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,36 @@ void testValidateWithAllParametersMissing() { assertNull(staging.getDockerInfo()); } + @Test + void testParseHealthCheckIntervalWhenSet() { + parametersList.add(mapOf("health-check-interval", 15)); + + Staging staging = parser.parse(parametersList); + + assertNotNull(staging); + assertEquals(Integer.valueOf(15), staging.getHealthCheckInterval()); + } + + @Test + void testParseHealthCheckIntervalDefaultsToNullWhenAbsent() { + Staging staging = parser.parse(parametersList); + + assertNotNull(staging); + assertNull(staging.getHealthCheckInterval()); + } + + @Test + void testParseHealthCheckIntervalIsIndependentOfReadinessHealthCheckInterval() { + parametersList.add(mapOf("health-check-interval", 10)); + parametersList.add(mapOf("readiness-health-check-interval", 30)); + + Staging staging = parser.parse(parametersList); + + assertNotNull(staging); + assertEquals(Integer.valueOf(10), staging.getHealthCheckInterval()); + assertEquals(Integer.valueOf(30), staging.getReadinessHealthCheckInterval()); + } + private static Map mapOf(String key, Object value) { return Collections.singletonMap(key, value); } 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..6f2ee8c0ad --- /dev/null +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/AdditionalModuleParametersReporterTest.java @@ -0,0 +1,132 @@ +package org.cloudfoundry.multiapps.controller.process.util; + +import java.text.MessageFormat; +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.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; +import org.flowable.engine.delegate.DelegateExecution; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class AdditionalModuleParametersReporterTest { + + private static final String MTA_ID = "test-mta"; + private static final String CORRELATION_ID = "test-correlation-id"; + private static final String MODULE_TYPE = "java-tomcat"; + private static final String MODULE_NAME = "test-module"; + private static final Integer HEALTH_CHECK_INTERVAL_VALUE = 15; + + private ProcessContext context; + private AdditionalModuleParametersReporter reporter; + + @BeforeEach + void setUp() { + context = createContext(); + DeploymentDescriptor descriptor = DeploymentDescriptor.createV3() + .setId(MTA_ID); + context.setVariable(Variables.DEPLOYMENT_DESCRIPTOR, descriptor); + context.setVariable(Variables.CORRELATION_ID, CORRELATION_ID); + reporter = new AdditionalModuleParametersReporter(context); + } + + @Test + void testReportUsageWithHealthCheckIntervalDoesNotThrow() { + Module module = createModule(Map.of(SupportedParameters.HEALTH_CHECK_INTERVAL, HEALTH_CHECK_INTERVAL_VALUE)); + + assertDoesNotThrow(() -> reporter.reportUsageOfAdditionalParameters(module)); + } + + @Test + void testReportUsageWithoutHealthCheckIntervalDoesNotThrow() { + Module module = createModule(Map.of()); + + assertDoesNotThrow(() -> reporter.reportUsageOfAdditionalParameters(module)); + } + + @Test + void testReportUsageWithBothHealthCheckIntervalAndReadinessParametersDoesNotThrow() { + Map parameters = new HashMap<>(); + parameters.put(SupportedParameters.HEALTH_CHECK_INTERVAL, HEALTH_CHECK_INTERVAL_VALUE); + parameters.put(SupportedParameters.READINESS_HEALTH_CHECK_TYPE, "http"); + parameters.put(SupportedParameters.READINESS_HEALTH_CHECK_HTTP_ENDPOINT, "/ready"); + parameters.put(SupportedParameters.READINESS_HEALTH_CHECK_INVOCATION_TIMEOUT, 5); + parameters.put(SupportedParameters.READINESS_HEALTH_CHECK_INTERVAL, 30); + Module module = createModule(parameters); + + assertDoesNotThrow(() -> reporter.reportUsageOfAdditionalParameters(module)); + } + + @Test + void testHealthCheckIntervalMessageTemplateIsWellFormed() { + assertNotNull(Messages.MTA_USES_HEALTH_CHECK_INTERVAL_PARAMETER); + String formatted = MessageFormat.format(Messages.MTA_USES_HEALTH_CHECK_INTERVAL_PARAMETER, MTA_ID, CORRELATION_ID, + HEALTH_CHECK_INTERVAL_VALUE, "[]", MODULE_TYPE); + + assertTrue(formatted.contains(MTA_ID), () -> "expected MTA id in message, got: " + formatted); + assertTrue(formatted.contains(CORRELATION_ID), () -> "expected correlation id in message, got: " + formatted); + assertTrue(formatted.contains(HEALTH_CHECK_INTERVAL_VALUE.toString()), + () -> "expected health-check-interval value in message, got: " + formatted); + assertTrue(formatted.contains(MODULE_TYPE), () -> "expected module type in message, got: " + formatted); + } + + @Test + void testHealthCheckIntervalMessageMentionsIntervalKeyword() { + String formatted = MessageFormat.format(Messages.MTA_USES_HEALTH_CHECK_INTERVAL_PARAMETER, MTA_ID, CORRELATION_ID, + HEALTH_CHECK_INTERVAL_VALUE, "[]", MODULE_TYPE); + + assertTrue(formatted.toLowerCase() + .contains("interval"), + () -> "expected the message to identify it as a health-check-interval log line, got: " + formatted); + } + + @Test + void testReportUsageReadsHealthCheckIntervalFromModuleParametersAsInteger() { + Module module = createModule(Map.of(SupportedParameters.HEALTH_CHECK_INTERVAL, HEALTH_CHECK_INTERVAL_VALUE)); + + Object actual = module.getParameters() + .get(SupportedParameters.HEALTH_CHECK_INTERVAL); + + assertEquals(HEALTH_CHECK_INTERVAL_VALUE, actual); + assertDoesNotThrow(() -> reporter.reportUsageOfAdditionalParameters(module)); + } + + @Test + void testReportUsageWithHealthCheckIntervalAndBuildpacksDoesNotThrow() { + Map parameters = new HashMap<>(); + parameters.put(SupportedParameters.HEALTH_CHECK_INTERVAL, HEALTH_CHECK_INTERVAL_VALUE); + parameters.put(SupportedParameters.BUILDPACKS, List.of("java_buildpack")); + Module module = createModule(parameters); + + assertDoesNotThrow(() -> reporter.reportUsageOfAdditionalParameters(module)); + } + + private Module createModule(Map parameters) { + Map mutableParameters = new HashMap<>(parameters); + return Module.createV3() + .setName(MODULE_NAME) + .setType(MODULE_TYPE) + .setParameters(mutableParameters); + } + + private ProcessContext createContext() { + DelegateExecution execution = MockDelegateExecution.createSpyInstance(); + StepLogger stepLogger = Mockito.mock(StepLogger.class); + CloudControllerClientProvider clientProvider = Mockito.mock(CloudControllerClientProvider.class); + return new ProcessContext(execution, stepLogger, clientProvider); + } + +}