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 8fb386031a..4bd4b83775 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 @@ -3,11 +3,11 @@ import org.cloudfoundry.client.v3.processes.Data; import org.cloudfoundry.client.v3.processes.HealthCheck; import org.cloudfoundry.client.v3.processes.Process; -import org.immutables.value.Value; - +import org.cloudfoundry.client.v3.processes.ReadinessHealthCheck; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudProcess; import org.cloudfoundry.multiapps.controller.client.facade.domain.HealthCheckType; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudProcess; +import org.immutables.value.Value; @Value.Immutable public abstract class RawCloudProcess extends RawCloudEntity { @@ -19,6 +19,7 @@ public abstract class RawCloudProcess extends RawCloudEntity { public CloudProcess derive() { Process process = getProcess(); HealthCheck healthCheck = process.getHealthCheck(); + ReadinessHealthCheck readinessHealthCheckType = process.getReadinessHealthCheck(); Integer healthCheckTimeout = null; String healthCheckHttpEndpoint = null; Integer healthCheckInvocationTimeout = null; @@ -28,6 +29,15 @@ public CloudProcess derive() { healthCheckInvocationTimeout = healthCheckData.getInvocationTimeout(); healthCheckHttpEndpoint = healthCheckData.getEndpoint(); } + Integer readinessHealthCheckInvocationTimeout = null; + String readinessHealthCheckHttpEndpoint = null; + Integer readinessHealthCheckInterval = null; + if (readinessHealthCheckType.getData() != null) { + Data readinessHealthCheckData = readinessHealthCheckType.getData(); + readinessHealthCheckInvocationTimeout = readinessHealthCheckData.getInvocationTimeout(); + readinessHealthCheckHttpEndpoint = readinessHealthCheckData.getEndpoint(); + readinessHealthCheckInterval = readinessHealthCheckData.getInterval(); + } return ImmutableCloudProcess.builder() .command(process.getCommand()) .instances(process.getInstances()) @@ -39,6 +49,11 @@ public CloudProcess derive() { .healthCheckHttpEndpoint(healthCheckHttpEndpoint) .healthCheckTimeout(healthCheckTimeout) .healthCheckInvocationTimeout(healthCheckInvocationTimeout) + .readinessHealthCheckType(readinessHealthCheckType.getType() + .getValue()) + .readinessHealthCheckHttpEndpoint(readinessHealthCheckHttpEndpoint) + .readinessHealthCheckInvocationTimeout(readinessHealthCheckInvocationTimeout) + .readinessHealthCheckInterval(readinessHealthCheckInterval) .build(); } } diff --git a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawInstancesInfo.java b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawInstancesInfo.java index 0c55d9f1e5..2c3a83c130 100644 --- a/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawInstancesInfo.java +++ b/multiapps-controller-client/src/main/java/org/cloudfoundry/multiapps/controller/client/facade/adapters/RawInstancesInfo.java @@ -6,13 +6,12 @@ import org.cloudfoundry.client.v3.applications.GetApplicationProcessStatisticsResponse; import org.cloudfoundry.client.v3.processes.ProcessStatisticsResource; -import org.immutables.value.Value; - import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableInstanceInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableInstancesInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.InstanceInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.InstanceState; import org.cloudfoundry.multiapps.controller.client.facade.domain.InstancesInfo; +import org.immutables.value.Value; @Value.Immutable public abstract class RawInstancesInfo extends RawCloudEntity { @@ -41,7 +40,7 @@ private static InstanceInfo parseProcessStatistic(ProcessStatisticsResource stat return ImmutableInstanceInfo.builder() .index(statsResource.getIndex()) .state(InstanceState.valueOfWithDefault(statsResource.getState())) + .isRoutable(Boolean.parseBoolean(statsResource.getRoutable())) .build(); } - } 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 8176d4cf66..86df6003a7 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 @@ -1,10 +1,9 @@ package org.cloudfoundry.multiapps.controller.client.facade.domain; -import org.immutables.value.Value; - import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.cloudfoundry.multiapps.controller.client.facade.Nullable; +import org.immutables.value.Value; @Value.Immutable @JsonSerialize(as = ImmutableCloudProcess.class) @@ -30,6 +29,18 @@ public abstract class CloudProcess extends CloudEntity implements Derivable RESOURCE_PARAMETERS = Set.of(APPLY_NAMESPACE, SERVICE_CONFIG, SYSLOG_DRAIN_URL, DEFAULT_CONTAINER_NAME, DEFAULT_SERVICE_NAME, DEFAULT_XS_APP_NAME, SERVICE, SERVICE_KEYS, diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/parser/StagingParametersParser.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/parser/StagingParametersParser.java index a5c2ddf924..14d6021864 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/parser/StagingParametersParser.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/parser/StagingParametersParser.java @@ -7,11 +7,11 @@ import java.util.Map; import org.cloudfoundry.multiapps.common.ContentException; -import org.cloudfoundry.multiapps.controller.core.Constants; import org.cloudfoundry.multiapps.controller.client.facade.domain.DockerInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableStaging; import org.cloudfoundry.multiapps.controller.client.facade.domain.LifecycleType; import org.cloudfoundry.multiapps.controller.client.facade.domain.Staging; +import org.cloudfoundry.multiapps.controller.core.Constants; import org.cloudfoundry.multiapps.controller.core.model.SupportedParameters; import org.cloudfoundry.multiapps.mta.util.PropertiesUtil; import org.springframework.util.CollectionUtils; @@ -41,6 +41,18 @@ public Staging parse(List> parametersList) { String healthCheckHttpEndpoint = (String) PropertiesUtil.getPropertyValue(parametersList, SupportedParameters.HEALTH_CHECK_HTTP_ENDPOINT, getDefaultHealthCheckHttpEndpoint(healthCheckType)); + String readinessHealthCheckType = (String) PropertiesUtil.getPropertyValue(parametersList, + SupportedParameters.READINESS_HEALTH_CHECK_TYPE, null); + String readinessHealthCheckHttpEndpoint = (String) PropertiesUtil.getPropertyValue(parametersList, + SupportedParameters.READINESS_HEALTH_CHECK_HTTP_ENDPOINT, + getDefaultHealthCheckHttpEndpoint( + readinessHealthCheckType)); + Integer readinessHealthCheckInvocationTimeout = (Integer) PropertiesUtil.getPropertyValue(parametersList, + SupportedParameters.READINESS_HEALTH_CHECK_INVOCATION_TIMEOUT, + null); + Integer readinessHealthCheckInterval = (Integer) PropertiesUtil.getPropertyValue(parametersList, + SupportedParameters.READINESS_HEALTH_CHECK_INTERVAL, + null); Boolean isSshEnabled = (Boolean) PropertiesUtil.getPropertyValue(parametersList, SupportedParameters.ENABLE_SSH, null); Map appFeatures = getAppFeatures(parametersList); overrideSshFeatureIfMissing(appFeatures, isSshEnabled); @@ -57,6 +69,10 @@ public Staging parse(List> parametersList) { .invocationTimeout(healthCheckInvocationTimeout) .healthCheckType(healthCheckType) .healthCheckHttpEndpoint(healthCheckHttpEndpoint) + .readinessHealthCheckType(readinessHealthCheckType) + .readinessHealthCheckHttpEndpoint(readinessHealthCheckHttpEndpoint) + .readinessHealthCheckInvocationTimeout(readinessHealthCheckInvocationTimeout) + .readinessHealthCheckInterval(readinessHealthCheckInterval) .isSshEnabled(isSshEnabled) .appFeatures(appFeatures) .dockerInfo(dockerInfo) diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/PollStartAppStatusExecution.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/PollStartAppStatusExecution.java index 6e94188a71..6975d93699 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/PollStartAppStatusExecution.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/PollStartAppStatusExecution.java @@ -9,6 +9,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudRoute; import org.cloudfoundry.multiapps.controller.client.facade.domain.InstanceInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.InstanceState; +import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudApplicationExtended; import org.cloudfoundry.multiapps.controller.core.cf.CloudControllerClientFactory; import org.cloudfoundry.multiapps.controller.core.security.token.TokenService; import org.cloudfoundry.multiapps.controller.core.util.UriUtil; @@ -79,14 +80,13 @@ private StartupStatus getStartupStatus(ProcessContext context, String appName, L long downInstances = getInstanceCount(appInstances, InstanceState.DOWN); long crashedInstances = getInstanceCount(appInstances, InstanceState.CRASHED); long startingInstances = getInstanceCount(appInstances, InstanceState.STARTING); - String states = composeStatesMessage(appInstances); context.getStepLogger() .debug(Messages.APPLICATION_0_X_OF_Y_INSTANCES_RUNNING, appName, runningInstances, expectedInstances, states); if (runningInstances == expectedInstances) { - return StartupStatus.STARTED; + return checkIfAppHasStarted(context, appInstances); } if (startingInstances > 0) { return StartupStatus.STARTING; @@ -100,6 +100,28 @@ private StartupStatus getStartupStatus(ProcessContext context, String appName, L return StartupStatus.STARTING; } + private StartupStatus checkIfAppHasStarted(ProcessContext context, List appInstances) { + if (shouldWaitForAppToBecomeRoutable(context)) { + if (isThereAtLeastOneRoutedInstance(appInstances)) { + return StartupStatus.STARTED; + } + return StartupStatus.STARTING; + } + return StartupStatus.STARTED; + } + + private boolean shouldWaitForAppToBecomeRoutable(ProcessContext context) { + CloudApplicationExtended appToProcess = context.getVariable(Variables.APP_TO_PROCESS); + + return appToProcess.getStaging() + .getReadinessHealthCheckType() != null; + } + + private boolean isThereAtLeastOneRoutedInstance(List instanceInfos) { + return instanceInfos.stream() + .anyMatch(InstanceInfo::isRoutable); + } + private AsyncExecutionState checkStartupStatus(ProcessContext context, CloudApplication app, StartupStatus status) { if (status == StartupStatus.CRASHED) { onError(context, Messages.ERROR_STARTING_APP_0_DESCRIPTION_1, app.getName(), Messages.SOME_INSTANCES_HAVE_CRASHED); diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/StagingApplicationAttributeUpdater.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/StagingApplicationAttributeUpdater.java index ec148d5c23..5235a0f422 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/StagingApplicationAttributeUpdater.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/StagingApplicationAttributeUpdater.java @@ -44,7 +44,8 @@ private boolean hasStagingChanged(Staging staging, CloudApplication existingApp, processContext.setVariable(Variables.APP_NEEDS_RESTAGE, true); return true; } - return !healthCheck.equals(existingHealthCheck) || areAppFeaturesChanged(staging.getAppFeatures(), existingAppFeatures); + return !healthCheck.equals(existingHealthCheck) || areAppFeaturesChanged(staging.getAppFeatures(), existingAppFeatures) + || !hasReadinessHealthCheckNotBeenChanged(staging); } private boolean isCommandDifferent(String newCommand) { @@ -58,6 +59,14 @@ private boolean areAppFeaturesChanged(Map newAppFeatures, Map