diff --git a/CHANGELOG.md b/CHANGELOG.md index c22c1a1dad..bd17826dfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ ### Changed * Enhance SSDS Document Generation Performance using New Atlassian APIs ([#1084](https://github.com/opendevstack/ods-jenkins-shared-library/issues/1084)) +* Check DocGen service availability before generating documentation ([#1141](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1141)) ### Fixed * Fix Tailor deployment drifts for D, Q envs ([#1055](https://github.com/opendevstack/ods-jenkins-shared-library/pull/1055)) diff --git a/build.gradle b/build.gradle index 033d410353..590b775174 100644 --- a/build.gradle +++ b/build.gradle @@ -139,7 +139,7 @@ codenarc { toolVersion = '1.6' configFile = file('codenarc.groovy') maxPriority1Violations = 0 - maxPriority2Violations = 1 + maxPriority2Violations = 0 maxPriority3Violations = 300 reportFormat = 'html' } diff --git a/src/org/ods/orchestration/service/DocGenService.groovy b/src/org/ods/orchestration/service/DocGenService.groovy index 92fbf210be..f25ad9c7af 100644 --- a/src/org/ods/orchestration/service/DocGenService.groovy +++ b/src/org/ods/orchestration/service/DocGenService.groovy @@ -7,8 +7,6 @@ import com.cloudbees.groovy.cps.NonCPS import groovy.json.JsonOutput import groovy.json.JsonSlurperClassic -import java.net.URI - import kong.unirest.Unirest import org.apache.http.client.utils.URIBuilder @@ -31,6 +29,12 @@ class DocGenService { } } + @NonCPS + int healthCheck() { + def response = Unirest.get("${this.baseURL}/health").asEmpty() + return response.getStatus() + } + @NonCPS byte[] createDocument(String type, String version, Map data) { def response = Unirest.post("${this.baseURL}/document") @@ -41,7 +45,7 @@ class DocGenService { type: type, version: version ], - data: data + data: data, ])) .asString() @@ -65,4 +69,5 @@ class DocGenService { private static byte[] decodeBase64(String base64String) { return Base64.decoder.decode(base64String) } + } diff --git a/src/org/ods/orchestration/usecase/BitbucketTraceabilityUseCase.groovy b/src/org/ods/orchestration/usecase/BitbucketTraceabilityUseCase.groovy index 49e7cdfd45..37c587d1d3 100644 --- a/src/org/ods/orchestration/usecase/BitbucketTraceabilityUseCase.groovy +++ b/src/org/ods/orchestration/usecase/BitbucketTraceabilityUseCase.groovy @@ -115,10 +115,9 @@ class BitbucketTraceabilityUseCase { nextPageStart = pullRequests.nextPageStart } - records += pullRequests.values.collect { pullRequest -> - processPullRequest(token, repo, pullRequest)} - .flatten() - + records += pullRequests.values.collectMany { pullRequest -> + processPullRequest(token, repo, pullRequest) + } } return records } @@ -201,6 +200,7 @@ class BitbucketTraceabilityUseCase { Date dateObj = new Date(timestamp) return new SimpleDateFormat('yyyy-MM-dd', Locale.getDefault()).format(dateObj) } + } private class Developer { diff --git a/src/org/ods/orchestration/usecase/DocGenUseCase.groovy b/src/org/ods/orchestration/usecase/DocGenUseCase.groovy index 413235ed91..84c69eeb5b 100644 --- a/src/org/ods/orchestration/usecase/DocGenUseCase.groovy +++ b/src/org/ods/orchestration/usecase/DocGenUseCase.groovy @@ -1,5 +1,6 @@ package org.ods.orchestration.usecase +import com.cloudbees.groovy.cps.NonCPS import groovy.json.JsonOutput import org.ods.orchestration.service.DocGenService @@ -18,6 +19,9 @@ import org.ods.orchestration.util.Project 'DuplicateMapLiteral']) abstract class DocGenUseCase { + private static final int MAX_RETRIES = 12 + private static final long RETRY_WAIT_MILLISECONDS = 5000L + static final String RESURRECTED = "resurrected" protected Project project protected IPipelineSteps steps @@ -38,6 +42,7 @@ abstract class DocGenUseCase { } String createDocument(String documentType, Map repo, Map data, Map files = [:], Closure modifier = null, String templateName = null, String watermarkText = null) { + checkServiceReadiness() // Create a PDF document via the DocGen service def document = this.docGen.createDocument(templateName ?: documentType, this.getDocumentTemplatesVersion(), data) @@ -246,4 +251,24 @@ abstract class DocGenUseCase { abstract boolean shouldCreateArtifact (String documentType, Map repo) + @NonCPS + protected void checkServiceReadiness() { + int status + for (int i = 0; i < MAX_RETRIES; i++) { + try { + status = this.docGen.healthCheck() + if (status == 200) { + return + } + } catch (ignored) { + // There may be a lower-level error, such as a connection reset, for which there is no HTTP status. + // In these cases, healthCheck throws an exception. + // Given the lack of documentation about the possible exceptions, we have to consider all of them + // as retryable. Anyway, we have a MAX_RETRIES. + } + sleep(RETRY_WAIT_MILLISECONDS) + } + throw new ServiceNotReadyException(status, "DocGen service is not ready.") + } + } diff --git a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy index ec02672c0e..5800afb11a 100644 --- a/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy +++ b/src/org/ods/orchestration/usecase/LeVADocumentUseCase.groovy @@ -1041,13 +1041,13 @@ class LeVADocumentUseCase extends DocGenUseCase { //Add break space in url in manufacturer def p = ~'https?://\\S*' def m = it.metadata.supplier =~ p - StringBuffer sb = new StringBuffer(); + StringBuffer sb = new StringBuffer() while (m.find()) { - String url = m.group(); + String url = m.group() url = url.replaceAll('/+', '$0\u200B') - m.appendReplacement(sb, url); + m.appendReplacement(sb, url) } - m.appendTail(sb); + m.appendTail(sb) clone.printsupplier = sb.toString() return clone diff --git a/src/org/ods/orchestration/usecase/ServiceNotReadyException.groovy b/src/org/ods/orchestration/usecase/ServiceNotReadyException.groovy new file mode 100644 index 0000000000..4b290f1d7d --- /dev/null +++ b/src/org/ods/orchestration/usecase/ServiceNotReadyException.groovy @@ -0,0 +1,31 @@ +package org.ods.orchestration.usecase + +class ServiceNotReadyException extends Exception { + + final int status + + ServiceNotReadyException(int status) { + this.status = status + } + + ServiceNotReadyException(int status, String message) { + super(message) + this.status = status + } + + ServiceNotReadyException(int status, Throwable cause) { + super(cause) + this.status = status + } + + ServiceNotReadyException(int status, String message, Throwable cause) { + super(message, cause) + this.status = status + } + + @Override + String toString() { + return super.toString() + "\nHTTP status: ${status}" + } + +} diff --git a/test/groovy/org/ods/orchestration/usecase/DocGenUseCaseSpec.groovy b/test/groovy/org/ods/orchestration/usecase/DocGenUseCaseSpec.groovy index 273d0aa9b3..7e777a9d50 100644 --- a/test/groovy/org/ods/orchestration/usecase/DocGenUseCaseSpec.groovy +++ b/test/groovy/org/ods/orchestration/usecase/DocGenUseCaseSpec.groovy @@ -58,6 +58,7 @@ class DocGenUseCaseSpec extends SpecHelper { pdf = Mock(PDFUtil) jenkins = Mock(JenkinsService) usecase = Spy(new DocGenUseCaseImpl(project, steps, util, docGen, nexus, pdf, jenkins)) + docGen.healthCheck() >> 200 } def "create document"() { @@ -88,6 +89,7 @@ class DocGenUseCaseSpec extends SpecHelper { then: 1 * docGen.createDocument(documentType, "0.1", data) >> document + 5 * docGen.healthCheck() >>> [201, 300, 400, 500, 200] then: 1 * usecase.getDocumentBasename(documentType, version, steps.env.BUILD_ID,repo) @@ -123,13 +125,22 @@ class DocGenUseCaseSpec extends SpecHelper { logFile2.delete() } - + def "create document when docgen not ready"() { + when: + usecase.createDocument(null, null, null) + + then: + 5 * docGen.healthCheck() >>> [201, 300, 400, 100, 500] + ServiceNotReadyException e = thrown() + e.getStatus() == 500 + } + def "create document and stash"() { given: // Test Parameters def logFile1 = Files.createTempFile("raw", ".log").toFile() << "Log File 1" def logFile2 = Files.createTempFile("raw", ".log").toFile() << "Log File 2" - + def documentType = "myDocumentType" def version = project.buildParams.version def repo = project.repositories.first() @@ -138,15 +149,15 @@ class DocGenUseCaseSpec extends SpecHelper { "raw/${logFile1.name}": logFile1.bytes, "raw/${logFile2.name}": logFile2.bytes ] - + // Argument Constraints def basename = "${documentType}-${project.key}-${repo.id}-${version}-${steps.env.BUILD_ID}" - + // Stubbed Method Responses def document = "PDF".bytes def archive = "Archive".bytes def nexusUri = new URI("http://nexus") - + when: def result = usecase.createDocument(documentType, repo, data, files) @@ -181,10 +192,10 @@ class DocGenUseCaseSpec extends SpecHelper { archive, "application/zip" ) >> nexusUri - + then: result == nexusUri.toString() - + cleanup: logFile1.delete() logFile2.delete() @@ -381,7 +392,7 @@ class DocGenUseCaseSpec extends SpecHelper { new File ("${path}/${docName}").write("test") def metadata = [:] - + when: usecase.createOverallDocument(templateName, documentType, metadata) @@ -432,11 +443,11 @@ class DocGenUseCaseSpec extends SpecHelper { def version = project.buildParams.version def build = "0815" def repo = project.repositories.first() - + repo.data.openshift = [:] when: def result = usecase.resurrectAndStashDocument(documentType, repo) - + then: result.found == false } diff --git a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy index 799c04509e..c30e3aaccd 100644 --- a/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy +++ b/test/groovy/org/ods/orchestration/usecase/LeVADocumentUseCaseSpec.groovy @@ -90,6 +90,7 @@ class LeVADocumentUseCaseSpec extends SpecHelper { jenkins.unstashFilesIntoPath(_, _, "SonarQube Report") >> true steps.getEnv() >> ['RELEASE_PARAM_VERSION': 'WIP'] stepsNoWip.getEnv() >> ['RELEASE_PARAM_VERSION': 'CHG00001'] + docGen.healthCheck() >> 200 } def "compute test discrepancies"() {