diff --git a/CHANGELOG.md b/CHANGELOG.md index 7830c879..c3a18152 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -21,7 +21,14 @@ Adding a requirement of a major version of a dependency is breaking a contract. Dropping a requirement of a major version of a dependency is a new contract. ## [Unreleased] -[Unreleased]: https://github.com/atlassian/aws-infrastructure/compare/release-3.1.6...master +[Unreleased]: https://github.com/atlassian/aws-infrastructure/compare/release-3.2.0...master + +### Fixed +- Harden `JiraStatus` parsing. +- Reduce `JiraStatus` log spam. + +## [3.2.0] - 2024-06-24 +[3.2.0]: https://github.com/atlassian/aws-infrastructure/compare/release-3.1.4...release-3.2.0 ## 3.1.7 - 2024-06-24 ## 3.1.6 - 2024-06-12 diff --git a/src/main/kotlin/com/atlassian/performance/tools/awsinfrastructure/api/jira/DataCenterFormula.kt b/src/main/kotlin/com/atlassian/performance/tools/awsinfrastructure/api/jira/DataCenterFormula.kt index bc0963ff..9711a357 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/awsinfrastructure/api/jira/DataCenterFormula.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/awsinfrastructure/api/jira/DataCenterFormula.kt @@ -413,6 +413,9 @@ class DataCenterFormula private constructor( fun accessRequester(accessRequester: AccessRequester) = apply { this.accessRequester = accessRequester } + /** + * @since 3.2.0 + */ fun waitForRunning(waitForRunning: Boolean) = apply { this.waitForRunning = waitForRunning } /** diff --git a/src/main/kotlin/com/atlassian/performance/tools/awsinfrastructure/api/jira/StandaloneFormula.kt b/src/main/kotlin/com/atlassian/performance/tools/awsinfrastructure/api/jira/StandaloneFormula.kt index a41243db..99be96b1 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/awsinfrastructure/api/jira/StandaloneFormula.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/awsinfrastructure/api/jira/StandaloneFormula.kt @@ -319,6 +319,9 @@ class StandaloneFormula private constructor( fun accessRequester(accessRequester: AccessRequester) = apply { this.accessRequester = accessRequester } + /** + * @since 3.2.0 + */ fun waitForRunning(waitForRunning: Boolean) = apply { this.waitForRunning = waitForRunning } fun waitForUpgrades(waitForUpgrades: Boolean) = apply { this.waitForUpgrades = waitForUpgrades } diff --git a/src/main/kotlin/com/atlassian/performance/tools/awsinfrastructure/jira/JiraStatus.kt b/src/main/kotlin/com/atlassian/performance/tools/awsinfrastructure/jira/JiraStatus.kt index 1eb8c8a1..8fdc722d 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/awsinfrastructure/jira/JiraStatus.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/awsinfrastructure/jira/JiraStatus.kt @@ -1,8 +1,8 @@ package com.atlassian.performance.tools.awsinfrastructure.jira +import org.apache.logging.log4j.LogManager import java.io.StringReader import javax.json.Json -import kotlin.streams.asSequence /** * https://confluence.atlassian.com/jirakb/jira-status-endpoint-response-meanings-1116294680.html @@ -17,16 +17,19 @@ enum class JiraStatus { object Parser { + private val LOG = LogManager.getLogger(this::class.java) + fun parseResponse(response: String): JiraStatus? { - return Json.createParser(StringReader(response)).use { jsonParser -> - jsonParser - .valueStream - .asSequence() - .firstOrNull() - ?.asJsonObject() - ?.getString("state") - ?.let { state -> JiraStatus.values().find { it.name == state } } + val state = try { + Json.createReader(StringReader(response)) + .read() + .asJsonObject() + .getString("state") + } catch (e: Exception) { + LOG.warn("Invalid JSON state: '$response'", e) + return null } + return JiraStatus.values().find { it.name == state } } } } diff --git a/src/main/kotlin/com/atlassian/performance/tools/awsinfrastructure/jira/StandaloneStoppedNode.kt b/src/main/kotlin/com/atlassian/performance/tools/awsinfrastructure/jira/StandaloneStoppedNode.kt index 8fb2014d..c29948de 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/awsinfrastructure/jira/StandaloneStoppedNode.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/awsinfrastructure/jira/StandaloneStoppedNode.kt @@ -10,15 +10,14 @@ import com.atlassian.performance.tools.infrastructure.api.process.RemoteMonitori import com.atlassian.performance.tools.infrastructure.api.profiler.Profiler import com.atlassian.performance.tools.ssh.api.Ssh import com.atlassian.performance.tools.ssh.api.SshConnection +import org.apache.logging.log4j.Level import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.Logger -import java.io.StringReader import java.net.URI import java.time.Duration import java.time.Duration.ofMinutes import java.time.Duration.ofSeconds import java.time.Instant.now -import javax.json.Json internal class StandaloneStoppedNode( private val name: String, @@ -118,8 +117,13 @@ internal class StandaloneStoppedNode( threadDump, "RUNNING state" ) { - val response = ssh.safeExecute("curl $statusEndpoint", launchTimeouts.unresponsivenessTimeout).output - JiraStatus.Parser.parseResponse(response) == JiraStatus.RUNNING + val curl = ssh.safeExecute( + "curl --silent $statusEndpoint", + launchTimeouts.unresponsivenessTimeout, + stdout = Level.DEBUG, + stderr = Level.DEBUG + ) + curl.isSuccessful() && JiraStatus.Parser.parseResponse(curl.output) == JiraStatus.RUNNING } } diff --git a/src/test/kotlin/com/atlassian/performance/tools/awsinfrastructure/jira/JiraStatusTest.kt b/src/test/kotlin/com/atlassian/performance/tools/awsinfrastructure/jira/JiraStatusTest.kt index 77c5f9f9..eb5e8cf3 100644 --- a/src/test/kotlin/com/atlassian/performance/tools/awsinfrastructure/jira/JiraStatusTest.kt +++ b/src/test/kotlin/com/atlassian/performance/tools/awsinfrastructure/jira/JiraStatusTest.kt @@ -1,9 +1,19 @@ package com.atlassian.performance.tools.awsinfrastructure.jira +import com.atlassian.performance.tools.awsinfrastructure.LogConfigurationFactory +import com.atlassian.performance.tools.workspace.api.RootWorkspace +import org.apache.logging.log4j.core.config.ConfigurationFactory import org.assertj.core.api.Assertions.assertThat +import org.junit.Before import org.junit.Test class JiraStatusTest { + + @Before + fun setUpLogs() { + ConfigurationFactory.setConfigurationFactory(LogConfigurationFactory(RootWorkspace().currentTask)) + } + @Test fun shouldParseRunning() { val actual = JiraStatus.Parser.parseResponse("""{"state":"RUNNING"}""") @@ -44,4 +54,25 @@ class JiraStatusTest { assertThat(actual).isNull() } + + @Test + fun shouldParseGarbled() { + val actual = JiraStatus.Parser.parseResponse("""SURPRISE""") + + assertThat(actual).isNull() + } + + @Test + fun shouldParseRandomJson() { + val actual = JiraStatus.Parser.parseResponse("""[{"banana": "sure"}]""") + + assertThat(actual).isNull() + } + + @Test + fun shouldParseRandomJsonObject() { + val actual = JiraStatus.Parser.parseResponse("""{"hello": "it's me"}""") + + assertThat(actual).isNull() + } } \ No newline at end of file