![JAR search and dependency download from the Maven repository](/logo.png)
io.jmnarloch.cd.go.plugin.healthcheck.HealthCheckTaskExecutor Maven / Gradle / Ivy
/**
* Copyright (c) 2015 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jmnarloch.cd.go.plugin.healthcheck;
import com.google.gson.JsonElement;
import com.google.gson.JsonParser;
import com.thoughtworks.go.plugin.api.logging.Logger;
import com.thoughtworks.go.plugin.api.task.JobConsoleLogger;
import io.jmnarloch.cd.go.plugin.api.executor.ExecutionConfiguration;
import io.jmnarloch.cd.go.plugin.api.executor.ExecutionContext;
import io.jmnarloch.cd.go.plugin.api.executor.ExecutionResult;
import io.jmnarloch.cd.go.plugin.api.executor.TaskExecutor;
import io.netty.buffer.ByteBuf;
import io.reactivex.netty.RxNetty;
import io.reactivex.netty.protocol.http.client.HttpClientResponse;
import org.apache.commons.lang3.StringUtils;
import rx.Observable;
import rx.functions.Func1;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
/**
* The health check executor. The plugin will perform polling of the configured health url until it will receive
* the first successful response matching the specific instance status. If the connection can not be establish or
* the health check statuses will not match the expected value the pooling will be timeouted after configured number
* of seconds and the step itself will result in build error.
*
* @author Jakub Narloch
*/
public class HealthCheckTaskExecutor implements TaskExecutor {
/**
* The dot notation separator.
*/
private static final String SEPARATOR = "\\.";
/**
* The logger used by this class.
*/
private final Logger logger = Logger.getLoggerFor(HealthCheckTaskExecutor.class);
/**
* The JSON parser.
*/
private final JsonParser parser;
/**
* Creates new instance of {@link HealthCheckTaskExecutor}.
*/
public HealthCheckTaskExecutor() {
this(new JsonParser());
}
/**
* Creates new instance of {@link HealthCheckTaskExecutor} with specific JSON parser.
*
* @param parser the JSON parser
*/
public HealthCheckTaskExecutor(JsonParser parser) {
this.parser = parser;
}
/**
* {@inheritDoc}
*/
@Override
public ExecutionResult execute(ExecutionContext context, ExecutionConfiguration config, JobConsoleLogger console) {
try {
final String healthCheckUrl = getProperty(config, HealthCheckTaskConfig.URL);
final String attribute = getProperty(config, HealthCheckTaskConfig.ATTRIBUTE);
final String status = getProperty(config, HealthCheckTaskConfig.STATUS);
final int timeout = getIntProperty(config, HealthCheckTaskConfig.TIMEOUT, 60);
final int retryDelay = getIntProperty(config, HealthCheckTaskConfig.DELAY, 15);
final boolean success = RxNetty.createHttpGet(healthCheckUrl)
.flatMap(parseJsonElement())
.map(mapStatusAttribute(attribute))
.map(mapAttributeValue())
.map(matchStatus(status))
.filter(filterStatuses())
.switchIfEmpty(Observable.error(null))
.retryWhen(retryPolicy(retryDelay, timeout))
.timeout(timeout, TimeUnit.SECONDS)
.toBlocking()
.firstOrDefault(false);
if (!success) {
return ExecutionResult.failure("Health check failed");
}
return ExecutionResult.success("Health check succeeded");
} catch (Exception e) {
logger.error("Unexpected error occurred when executing task", e);
return ExecutionResult.failure("Health check failed", e);
}
}
/**
* Maps the HTTP response and unmarshalls it's JSON payload.
*
* @return the mapping function
*/
private Func1, Observable> parseJsonElement() {
return new Func1, Observable>() {
@Override
public Observable call(HttpClientResponse response) {
return response.getContent().map(new Func1() {
@Override
public JsonElement call(ByteBuf byteBuf) {
return parser.parse(byteBuf.toString(StandardCharsets.UTF_8));
}
});
}
};
}
/**
* Maps the attribute that indicates the instance status.
*
* @param attribute the attribute name
* @return the mapping function
*/
private Func1 mapStatusAttribute(final String attribute) {
return new Func1() {
@Override
public JsonElement call(JsonElement jsonElement) {
JsonElement element = jsonElement;
final String[] parts = attribute.split(SEPARATOR);
for(String part : parts) {
element = element.getAsJsonObject().get(part);
}
return element;
}
};
}
/**
* Maps the attribute string value.
*
* @return the mapping function
*/
private Func1 mapAttributeValue() {
return new Func1() {
@Override
public String call(JsonElement jsonElement) {
return jsonElement.getAsString();
}
};
}
/**
* Matches the expected application status.
*
* @param status the status
* @return the mapping function
*/
private Func1 matchStatus(final String status) {
return new Func1() {
@Override
public Boolean call(String instanceStatus) {
return StringUtils.equalsIgnoreCase(status, instanceStatus);
}
};
}
/**
* Filters the application status.
*
* @return the predicate
*/
private Func1 filterStatuses() {
return new Func1() {
@Override
public Boolean call(Boolean value) {
return Boolean.TRUE.equals(value);
}
};
}
/**
* Specifies the retry policy for the {@link Observable}.
*
* @param retryDelay the delay
* @param timeout the maximum timeout
* @return the retry policy
*/
private Func1, Observable>> retryPolicy(final int retryDelay, final int timeout) {
return new Func1, Observable>>() {
@Override
public Observable> call(Observable extends Throwable> observable) {
final int retries = retryDelay > 0 && retryDelay <= timeout ? timeout / retryDelay : 0;
return Observable.interval(retryDelay, TimeUnit.SECONDS).take(retries);
}
};
}
/**
* Retrieves the integer property value.
*
* @param config the configuration
* @param property the property name
* @param defaultValue the default value
* @return the property value
*/
private int getIntProperty(ExecutionConfiguration config, HealthCheckTaskConfig property, int defaultValue) {
final String value = getProperty(config, property);
return value != null ? Integer.parseInt(value) : defaultValue;
}
/**
* Retrieves the property value.
*
* @param config the configuration
* @param property the property name
* @return the property value
*/
private String getProperty(ExecutionConfiguration config, HealthCheckTaskConfig property) {
return config.getProperty(property.getName());
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy