Skip to content

Commit 1d3f2cf

Browse files
Feature/CLD-429 (#76)
* Update test README with detail for new tests * Fix license validation case * Fixes to test cases and additional wait for init * Fix email functionality * Disable build abort function * handle email case when job is executed manually * Add parameters for test stages * Cleanup output * Add Jira link to email * Separating test case file from the script * Update test README with detail for new tests * Fix license validation case * Fix test cases and add more wait for compose
1 parent b4b6e68 commit 1d3f2cf

File tree

3 files changed

+270
-17
lines changed

3 files changed

+270
-17
lines changed

Diff for: Jenkinsfile

+149-17
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ void PreBuildCheck() {
2222
githubAPIUrl = GIT_URL.replace('.git', '').replace('github.com', 'api.github.com/repos')
2323
echo 'githubAPIUrl: ' + githubAPIUrl
2424
} else {
25-
echo 'ERROR: GIT_URL is not defined'
26-
sh 'exit 1'
25+
echo 'Warning: GIT_URL is not defined'
2726
}
2827

2928
if (env.CHANGE_ID) {
@@ -52,21 +51,21 @@ def ExtractJiraID() {
5251
match = env.GIT_BRANCH =~ JIRA_ID_PATTERN
5352
}
5453
else {
55-
echo 'ERROR: Jira ticket number not detected.'
54+
echo 'Warning: Jira ticket number not detected.'
5655
return ''
5756
}
5857
try {
5958
return match[0]
6059
} catch (any) {
61-
echo 'ERROR: Jira ticket number not detected.'
60+
echo 'Warning: Jira ticket number not detected.'
6261
return ''
6362
}
6463
}
6564

6665
def PRDraftCheck() {
6766
withCredentials([usernameColonPassword(credentialsId: gitCredID, variable: 'Credentials')]) {
6867
PrObj = sh(returnStdout: true, script:'''
69-
curl -u $Credentials -X GET ''' + githubAPIUrl + '''/pulls/$CHANGE_ID
68+
curl -s -u $Credentials -X GET ''' + githubAPIUrl + '''/pulls/$CHANGE_ID
7069
''')
7170
}
7271
def jsonObj = new JsonSlurperClassic().parseText(PrObj.toString().trim())
@@ -78,10 +77,10 @@ def getReviewState() {
7877
def commitHash
7978
withCredentials([usernameColonPassword(credentialsId: gitCredID, variable: 'Credentials')]) {
8079
reviewResponse = sh(returnStdout: true, script:'''
81-
curl -u $Credentials -X GET ''' + githubAPIUrl + '''/pulls/$CHANGE_ID/reviews
80+
curl -s -u $Credentials -X GET ''' + githubAPIUrl + '''/pulls/$CHANGE_ID/reviews
8281
''')
8382
commitHash = sh(returnStdout: true, script:'''
84-
curl -u $Credentials -X GET ''' + githubAPIUrl + '''/pulls/$CHANGE_ID
83+
curl -s -u $Credentials -X GET ''' + githubAPIUrl + '''/pulls/$CHANGE_ID
8584
''')
8685
}
8786
def jsonObj = new JsonSlurperClassic().parseText(commitHash.toString().trim())
@@ -118,10 +117,13 @@ def ResultNotification(message) {
118117
emailList = params.emailList
119118
}
120119

121-
mail charset: 'UTF-8', mimeType: 'text/html', to: "${emailList}", body: "<b>Jenkins pipeline for ${env.JOB_NAME} <br>Build Number: ${env.BUILD_NUMBER} <br>${env.BUILD_URL}</b>", subject: "${message}: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
122120
if (JIRA_ID) {
123121
def comment = [ body: "Jenkins pipeline build result: ${message}" ]
124122
jiraAddComment site: 'JIRA', idOrKey: JIRA_ID, input: comment
123+
mail charset: 'UTF-8', mimeType: 'text/html', to: "${emailList}", body: "<b>Jenkins pipeline for ${env.JOB_NAME} <br>Build Number: ${env.BUILD_NUMBER} <br>${env.BUILD_URL}<br>https://project.marklogic.com/jira/browse/${JIRA_ID}</b>", subject: "${message}: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
124+
} else {
125+
mail charset: 'UTF-8', mimeType: 'text/html', to: "${emailList}", body: "<b>Jenkins pipeline for ${env.JOB_NAME} <br>Build Number: ${env.BUILD_NUMBER} <br>${env.BUILD_URL}</b>", subject: "${message}: ${env.JOB_NAME} #${env.BUILD_NUMBER}"
126+
125127
}
126128
}
127129

@@ -189,26 +191,132 @@ def CopyRPMs() {
189191
}
190192
}
191193

192-
def RunStructureTests() {
194+
def StructureTests() {
193195
sh """
194196
cd test
195197
#insert current version
196198
sed -i -e 's/VERSION_PLACEHOLDER/${mlVersion}-${env.platformString}-${env.dockerVersion}/' ./structure-test.yml
197-
curl -LO https://storage.googleapis.com/container-structure-test/latest/container-structure-test-linux-amd64 && chmod +x container-structure-test-linux-amd64 && mv container-structure-test-linux-amd64 container-structure-test
199+
curl -s -LO https://storage.googleapis.com/container-structure-test/latest/container-structure-test-linux-amd64 && chmod +x container-structure-test-linux-amd64 && mv container-structure-test-linux-amd64 container-structure-test
198200
./container-structure-test test --config ./structure-test.yml --image marklogic-centos/marklogic-server-centos:${mlVersion}-${env.platformString}-${env.dockerVersion} --output junit | tee container-structure-test.xml
199201
#fix junit output
200202
sed -i -e 's/<\\/testsuites>//' -e 's/<testsuite>//' -e 's/<testsuites/<testsuite name="container-structure-test"/' ./container-structure-test.xml
201203
"""
202204
junit testResults: '**/container-structure-test.xml'
203205
}
204206

205-
def RunServerRegressionTests() {
207+
def ServerRegressionTests() {
206208
//TODO: run this conditionally for develop and master branches only
207209
echo 'Server regression tests would execute here'
208210
// The following can be uncommented to show an interactive prompt for manual regresstion tests
209211
// input "Server regression tests need to be executed manually. "
210212
}
211213

214+
def DockerRunTests() {
215+
echo "----------------- Docker Tests -----------------"
216+
// Define test parameters
217+
def testImage="marklogic-centos/marklogic-server-centos:${mlVersion}-${env.platformString}-${env.dockerVersion}"
218+
def defaultParams='-it -d -p 8000:8000 -p 8001:8001 -p 8002:8002 -p7997:7997'
219+
def curlCommand='curl -sL'
220+
def curlCommandAuth='curl -sL --anyauth -u test_admin:test_admin_pass'
221+
def composePath='./docker-compose/'
222+
def jUnitReport = "docker-test-results.xml"
223+
def testCases = readJSON file: './test/docker-test-cases.json'
224+
225+
//validate JSON data
226+
assert testCases instanceof Map
227+
228+
//create credential files for compose
229+
writeFile(file: "${composePath}mldb_admin_username.txt", text: "test_admin")
230+
writeFile(file: "${composePath}mldb_admin_password.txt", text: "test_admin_pass")
231+
232+
def testResults = ''
233+
def totalTests = 0
234+
def totalErrors = 0
235+
def cmdOutput
236+
def composeFile
237+
def testCont
238+
239+
// Run test cases
240+
testCases.each { key, value ->
241+
242+
echo "Running "+key+": "+value.description
243+
// if .yml config is provided in params, start compose. otherwise docker run is used
244+
if ( value.params.toString().contains(".yml")) {
245+
//update image label in yml file
246+
composeFile = readFile(composePath + value.params)
247+
composeFile = composeFile.replaceFirst(/image: .*/, "image: "+testImage)
248+
writeFile( file: composePath + value.params, text: composeFile)
249+
// start docker compose
250+
sh( returnStdout: true, script: "docker compose -f ${composePath}${value.params} up -d" )
251+
} else {
252+
//insert valid license data in parameters
253+
value.params = value.params.toString().replaceAll("LICENSE_PLACEHOLDER", "LICENSEE='MarkLogic - Version 9 QA Test License' -e LICENSE_KEY=\"${env.QA_LICENSE_KEY}\"")
254+
// start docker container
255+
testCont = sh( returnStdout: true, script: "docker run ${defaultParams} ${value.params} ${testImage}" )
256+
}
257+
258+
// TODO find a good way to skip the test on error from invalid params
259+
// TODO: Find a way to check for server status instead of a wait. (log: Database Modules is online)
260+
sleep(60)
261+
262+
echo "-Unauthenticated requests"
263+
value.expected.unauthenticated.each { test, verify ->
264+
//TODO if key is 'log' then check for log message
265+
try {
266+
cmdOutput = sh( returnStdout: true, script: "${curlCommand} http://localhost:${test}" )
267+
} catch (e) {
268+
cmdOutput = 'Curl retured error: '+e.message
269+
}
270+
testResults = testResults + '<testcase name="'+value.description+' on '+key+' without credentials"'
271+
totalTests += 1
272+
echo "--Port ${test}: "
273+
if ( cmdOutput.contains(verify) ) {
274+
echo "PASS"
275+
testResults = testResults + '/>'
276+
} else {
277+
echo "FAIL"
278+
testResults = testResults + '><failure type="Text mismatch">'+cmdOutput+'</failure></testcase>'
279+
totalErrors += 1
280+
}
281+
sleep(1)
282+
}
283+
echo "-Authenticated requests"
284+
value.expected.authenticated.each { test, verify ->
285+
try {
286+
cmdOutput = sh( returnStdout: true, script: "${curlCommandAuth} http://localhost:${test}" )
287+
} catch (e) {
288+
cmdOutput = 'Curl retured error: '+e.message
289+
}
290+
testResults = testResults + '<testcase name="'+value.description+' on '+key+' with credentials"'
291+
totalTests += 1
292+
echo "--Port ${test}: "
293+
if ( cmdOutput.contains(verify) ) {
294+
echo "PASS"
295+
testResults = testResults + '/>'
296+
} else {
297+
echo "FAIL"
298+
testResults = testResults + '><failure type="Text mismatch">'+cmdOutput+'</failure></testcase>'
299+
totalErrors += 1
300+
}
301+
sleep(1)
302+
}
303+
echo "-Deleting resources"
304+
if ( value.params.toString().contains(".yml")) {
305+
sh( returnStdout: true, script: "docker compose -f ${composePath}${value.params} down" )
306+
} else {
307+
sh( returnStdout: true, script: "docker rm -f ${testCont}" )
308+
}
309+
sh( returnStdout: true, script: "docker volume prune -f")
310+
}
311+
// Generate JUnit XML file for Jenkins report
312+
// TODO: find a better way to generate junit file
313+
def jUnitXML = '<testsuite name="Docker Run Tests" tests="'+totalTests+'" failures="'+totalErrors+'">'
314+
jUnitXML = jUnitXML + testResults + "</testsuite>"
315+
writeFile( file: jUnitReport, text: jUnitXML )
316+
junit testResults: jUnitReport
317+
echo "-------------- End of Docker Tests --------------"
318+
}
319+
212320
def PublishToInternalRegistry() {
213321
withCredentials([usernamePassword(credentialsId: '8c2e0b38-9e97-4953-aa60-f2851bb70cc8', passwordVariable: 'docker_password', usernameVariable: 'docker_user')]) {
214322
sh """
@@ -222,7 +330,7 @@ def PublishToInternalRegistry() {
222330
pipeline {
223331
agent {
224332
label {
225-
label 'docker-vitaly'
333+
label 'cld-docker'
226334
}
227335
}
228336
options {
@@ -236,6 +344,7 @@ pipeline {
236344
buildServerPlatform = 'linux64-rh7'
237345
buildServerPath = getServerPath(params.ML_SERVER_BRANCH)
238346
dockerRegistry = 'https://ml-docker-dev.marklogic.com'
347+
QA_LICENSE_KEY = credentials('QA_LICENSE_KEY')
239348
}
240349

241350
parameters {
@@ -246,6 +355,9 @@ pipeline {
246355
string(name: 'ML_RPM', defaultValue: '', description: 'RPM to be used for Image creation. \n If left blank nightly ML rpm will be used.\n Please provide Jenkins accessible path e.g. /project/engineering or /project/qa', trim: true)
247356
string(name: 'ML_CONVERTERS', defaultValue: '', description: 'The Converters RPM to be included in the image creation \n If left blank the nightly ML Converters Package will be used.', trim: true)
248357
booleanParam(name: 'PUBLISH_IMAGE', defaultValue: false, description: 'Publish image to internal registry')
358+
booleanParam(name: 'TEST_STRUCTURE', defaultValue: true, description: 'Run container structure tests')
359+
booleanParam(name: 'DOCKER_TESTS', defaultValue: true, description: 'Run server regression tests')
360+
booleanParam(name: 'SERVER_REGRESSION', defaultValue: true, description: 'Run server regression tests')
249361
}
250362

251363
stages {
@@ -267,21 +379,39 @@ pipeline {
267379
}
268380
}
269381

270-
stage('Image-Test') {
382+
stage('Structure-Tests') {
383+
when {
384+
expression { return params.TEST_STRUCTURE }
385+
}
271386
steps {
272-
RunStructureTests()
387+
StructureTests()
388+
}
389+
}
390+
391+
stage('Docker-Run-Tests') {
392+
when {
393+
expression { return params.DOCKER_TESTS }
394+
}
395+
steps {
396+
DockerRunTests()
273397
}
274398
}
275399

276400
stage('Run-Server-Regression-Tests') {
401+
when {
402+
expression { return params.SERVER_REGRESSION }
403+
}
277404
steps {
278-
RunServerRegressionTests()
405+
ServerRegressionTests()
279406
}
280407
}
281408

282409
stage('Publish-Image') {
283410
when {
284-
expression { return params.PUBLISH_IMAGE }
411+
anyOf {
412+
branch 'develop'
413+
expression { return params.PUBLISH_IMAGE }
414+
}
285415
}
286416
steps {
287417
PublishToInternalRegistry()
@@ -294,6 +424,8 @@ pipeline {
294424
sh '''
295425
cd src/centos
296426
rm -rf *.rpm
427+
docker system prune --force --filter "until=720h"
428+
docker volume prune --force
297429
'''
298430
}
299431
success {
@@ -306,4 +438,4 @@ pipeline {
306438
ResultNotification('BUILD UNSTABLE ❌')
307439
}
308440
}
309-
}
441+
}

Diff for: test/README.md

+11
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,16 @@
1+
# Docker Tests
2+
3+
There are two types of tests: Docker structure tests and Docker image tests. Both are started by the pipeline.
4+
5+
## Docker Structure Tests
16
We use [container-structure-test](https://github.com/GoogleContainerTools/container-structure-testhttps:/) to validate the structure of our Docker images. Configuration file structure-test.yml defines the test cases for validation of image metadata, exposed ports, and essential files.
27

38
Here is an example command to run the test:
49

510
`container-structure-test test --config ./structure-test.yml --image ml-docker-dev.marklogic.com/marklogic/marklogic-server-centos:10.0-8.1-centos-1.0.0-ea2`
11+
12+
13+
## Docker Image Tests
14+
Test cases for Docker image tests are defined in docker-test-cases.json. Test cases validate running containers with either authenticated or unauthenticated user.
15+
Each test defines a port and a string to match in the response. Pipeline iterates through the test cases and generates a junit report.
16+
The driver is defined in DockerRunTests().

0 commit comments

Comments
 (0)