Skip to content

Commit 17e8390

Browse files
authored
add step healthExecuteCheck (#339)
This step allows to perform a basic health check on an installed application. It verifies that your app has a simple health endpoint available and that there is no error when calling it.
1 parent a56b9c0 commit 17e8390

File tree

5 files changed

+199
-0
lines changed

5 files changed

+199
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# healthExecuteCheck
2+
3+
## Description
4+
Calls the health endpoint url of the application.
5+
6+
The intention of the check is to verify that a suitable health endpoint is available. Such a health endpoint is required for operation purposes.
7+
8+
This check is used as a real-life test for your productive health endpoints.
9+
10+
!!! note "Check Depth"
11+
Typically, tools performing simple health checks are not too smart. Therefore it is important to choose an endpoint for checking wisely.
12+
13+
This check therefore only checks if the application/service url returns `HTTP 200`.
14+
15+
This is in line with health check capabilities of platforms which are used for example in load balancing scenarios. Here you can find an [example for Amazon AWS](http://docs.aws.amazon.com/elasticloadbalancing/latest/classic/elb-healthchecks.html).
16+
17+
18+
19+
## Prerequisites
20+
21+
Endpoint for health check is configured.
22+
23+
!!! warning
24+
The health endpoint needs to be available without authentication!
25+
26+
!!! tip
27+
If using Spring Boot framework, ideally the provided `/health` endpoint is used and extended by development. Further information can be found in the [Spring Boot documenation for Endpoints](http://docs.spring.io/spring-boot/docs/current/reference/html/production-ready-endpoints.html)
28+
29+
30+
## Example
31+
32+
Pipeline step:
33+
34+
```groovy
35+
healthExecuteCheck testServerUrl: 'https://testserver.com'
36+
```
37+
38+
## Parameters
39+
40+
| parameter | mandatory | default | possible values |
41+
| ----------|-----------|---------|-----------------|
42+
|script|yes|||
43+
|healthEndpoint|no|``||
44+
|testServerUrl|no|||
45+
46+
47+
Details:
48+
* `script` defines the global script environment of the Jenkinsfile run. Typically `this` is passed to this parameter. This allows the function to access the [`commonPipelineEnvironment`](commonPipelineEnvironment.md) for storing the measured duration.
49+
* Health check function is called providing full qualified `testServerUrl` (and optionally with `healthEndpoint` if endpoint is not the standard url) to the health check.
50+
* In case response of the call is different than `HTTP 200 OK` the **health check fails and the pipeline stops**.
51+
52+
## Step configuration
53+
54+
We recommend to define values of step parameters via [config.yml file](../configuration.md).
55+
56+
In following sections the configuration is possible:
57+
58+
| parameter | general | step | stage |
59+
| ----------|-----------|---------|-----------------|
60+
|script||||
61+
|healthEndpoint|X|X|X|
62+
|testServerUrl|X|X|X|

documentation/mkdocs.yml

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ nav:
1212
- dockerExecuteOnKubernetes: steps/dockerExecuteOnKubernetes.md
1313
- durationMeasure: steps/durationMeasure.md
1414
- handlePipelineStepErrors: steps/handlePipelineStepErrors.md
15+
- healthExecuteCheck: steps/healthExecuteCheck.md
1516
- influxWriteData: steps/influxWriteData.md
1617
- mavenExecute: steps/mavenExecute.md
1718
- mtaBuild: steps/mtaBuild.md

resources/default_pipeline_environment.yml

+2
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,8 @@ steps:
139139
workspace: '**/*.*'
140140
stashExcludes:
141141
workspace: 'nohup.out'
142+
healthExecuteCheck:
143+
healthEndpoint: ''
142144
influxWriteData:
143145
influxServer: 'jenkins'
144146
mavenExecute:
+88
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
#!groovy
2+
import org.junit.Before
3+
import org.junit.Rule
4+
import org.junit.Test
5+
import org.junit.rules.ExpectedException
6+
import org.junit.rules.RuleChain
7+
import util.BasePiperTest
8+
import util.JenkinsLoggingRule
9+
import util.JenkinsReadYamlRule
10+
import util.JenkinsStepRule
11+
import util.Rules
12+
13+
import static org.hamcrest.Matchers.*
14+
import static org.junit.Assert.assertThat
15+
16+
class HealthExecuteCheckTest extends BasePiperTest {
17+
private JenkinsStepRule jsr = new JenkinsStepRule(this)
18+
private JenkinsLoggingRule jlr = new JenkinsLoggingRule(this)
19+
private ExpectedException thrown = ExpectedException.none()
20+
21+
@Rule
22+
public RuleChain rules = Rules
23+
.getCommonRules(this)
24+
.around(new JenkinsReadYamlRule(this))
25+
.around(jlr)
26+
.around(jsr)
27+
.around(thrown)
28+
29+
30+
@Before
31+
void init() throws Exception {
32+
// register Jenkins commands with mock values
33+
def command1 = "curl -so /dev/null -w '%{response_code}' http://testserver"
34+
def command2 = "curl -so /dev/null -w '%{response_code}' http://testserver/endpoint"
35+
helper.registerAllowedMethod('sh', [Map.class], {map ->
36+
return map.script == command1 || map.script == command2 ? "200" : "404"
37+
})
38+
}
39+
40+
@Test
41+
void testHealthCheckOk() throws Exception {
42+
def testUrl = 'http://testserver/endpoint'
43+
44+
jsr.step.healthExecuteCheck(
45+
script: nullScript,
46+
testServerUrl: testUrl
47+
)
48+
49+
assertThat(jlr.log, containsString("Health check for ${testUrl} successful"))
50+
}
51+
52+
@Test
53+
void testHealthCheck404() throws Exception {
54+
def testUrl = 'http://testserver/404'
55+
56+
thrown.expect(Exception)
57+
thrown.expectMessage('Health check failed: 404')
58+
59+
jsr.step.healthExecuteCheck(
60+
script: nullScript,
61+
testServerUrl: testUrl
62+
)
63+
}
64+
65+
66+
@Test
67+
void testHealthCheckWithEndPoint() throws Exception {
68+
jsr.step.healthExecuteCheck(
69+
script: nullScript,
70+
testServerUrl: 'http://testserver',
71+
healthEndpoint: 'endpoint'
72+
)
73+
74+
assertThat(jlr.log, containsString("Health check for http://testserver/endpoint successful"))
75+
}
76+
77+
@Test
78+
void testHealthCheckWithEndPointTrailingSlash() throws Exception {
79+
jsr.step.healthExecuteCheck(
80+
script: nullScript,
81+
testServerUrl: 'http://testserver/',
82+
healthEndpoint: 'endpoint'
83+
)
84+
85+
assertThat(jlr.log, containsString("Health check for http://testserver/endpoint successful"))
86+
}
87+
88+
}

vars/healthExecuteCheck.groovy

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import com.sap.piper.ConfigurationHelper
2+
3+
import groovy.transform.Field
4+
5+
@Field String STEP_NAME = 'healthExecuteCheck'
6+
@Field Set STEP_CONFIG_KEYS = [
7+
'healthEndpoint',
8+
'testServerUrl'
9+
]
10+
@Field Set PARAMETER_KEYS = STEP_CONFIG_KEYS
11+
12+
void call(Map parameters = [:]) {
13+
handlePipelineStepErrors (stepName: STEP_NAME, stepParameters: parameters) {
14+
def script = parameters?.script ?: [commonPipelineEnvironment: commonPipelineEnvironment]
15+
// load default & individual configuration
16+
Map config = ConfigurationHelper
17+
.loadStepDefaults(this)
18+
.mixinGeneralConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS)
19+
.mixinStepConfig(script.commonPipelineEnvironment, STEP_CONFIG_KEYS)
20+
.mixinStageConfig(script.commonPipelineEnvironment, parameters.stageName?:env.STAGE_NAME, STEP_CONFIG_KEYS)
21+
.mixin(parameters, PARAMETER_KEYS)
22+
.withMandatoryProperty('testServerUrl')
23+
.use()
24+
25+
def checkUrl = config.testServerUrl
26+
if(config.healthEndpoint){
27+
if(!checkUrl.endsWith('/'))
28+
checkUrl += '/'
29+
checkUrl += config.healthEndpoint
30+
}
31+
32+
def statusCode = curl(checkUrl)
33+
if (statusCode != '200') {
34+
error "Health check failed: ${statusCode}"
35+
} else {
36+
echo "Health check for ${checkUrl} successful"
37+
}
38+
}
39+
}
40+
41+
def curl(url){
42+
return sh(
43+
returnStdout: true,
44+
script: "curl -so /dev/null -w '%{response_code}' ${url}"
45+
).trim()
46+
}

0 commit comments

Comments
 (0)