diff --git a/.editorconfig b/.editorconfig index 2b991f50..6fd134e2 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,2 +1,3 @@ -[*.{kt,kts}] -indent_size = 4 \ No newline at end of file +[*.{kt, kts}] +indent_size = 4 +insert_final_newline = true diff --git a/CHANGELOG.md b/CHANGELOG.md index f6f0b8a7..1f5f92da 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,9 @@ Dropping a requirement of a major version of a dependency is a new contract. ## [Unreleased] [Unreleased]: https://github.com/atlassian/infrastructure/compare/release-4.17.5...master +### Fixed +- Increase network-level retries for Jira/browser downloads. Decrease flakiness of such downloads on Ubuntu on WSL2. + ## [4.17.5] - 2020-12-15 [4.17.5]: https://github.com/atlassian/infrastructure/compare/release-4.17.4...release-4.17.5 diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/HttpResource.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/HttpResource.kt index f4326c94..01edbfae 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/HttpResource.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/HttpResource.kt @@ -27,7 +27,7 @@ class HttpResource( ) { Ubuntu().install(ssh, listOf("lftp"), Duration.ofMinutes(2)) ssh.execute( - """lftp -c 'set net:timeout 15; set net:max-retries 10; pget -n 32 -c "$uri" -o $destination'""", + """lftp -c 'set net:timeout 15; set net:max-retries 50; pget -n 32 -c "$uri" -o $destination'""", timeout ) } diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/Iostat.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/Iostat.kt index 7a7ed2c9..c82e0790 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/Iostat.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/Iostat.kt @@ -10,7 +10,7 @@ import java.time.temporal.ChronoUnit internal class Iostat : OsMetric { companion object { private val DELAY: Duration = Duration.ofSeconds(2) - private val LOG_PATH: String = "~/jpt-iostat.log" + private val LOG_PATH: String = "./jpt-iostat.log" private val TIME = "date -u \"+%d-%m-%Y %H:%M:%S UTC\"" private val ADD_TIME = @@ -32,4 +32,4 @@ internal class Iostat : OsMetric { val process = connection.startProcess("iostat -d $seconds -x | $ADD_TIME > $LOG_PATH") return MonitoringProcess(process, LOG_PATH) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/database/DatabaseIpConfig.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/database/DatabaseIpConfig.kt new file mode 100644 index 00000000..40c12af2 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/database/DatabaseIpConfig.kt @@ -0,0 +1,25 @@ +package com.atlassian.performance.tools.infrastructure.api.database + +import com.atlassian.performance.tools.infrastructure.api.Sed +import com.atlassian.performance.tools.infrastructure.api.jira.install.hook.PostInstallHooks +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira +import com.atlassian.performance.tools.infrastructure.api.jira.install.hook.PostInstallHook +import com.atlassian.performance.tools.ssh.api.SshConnection + +class DatabaseIpConfig( + private val databaseIp: String +) : PostInstallHook { + + override fun call( + ssh: SshConnection, + jira: InstalledJira, + hooks: PostInstallHooks + ) { + Sed().replace( + connection = ssh, + expression = "(.*(@(//)?|//))" + "([^:/]+)" + "(.*)", + output = """\1$databaseIp\5""", + file = "${jira.home}/dbconfig.xml" + ) + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/database/MySqlDatabase.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/database/MySqlDatabase.kt index 7746d94e..965ef55c 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/database/MySqlDatabase.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/database/MySqlDatabase.kt @@ -61,4 +61,4 @@ class MySqlDatabase( Thread.sleep(Duration.ofSeconds(10).toMillis()) } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/database/MysqlConnector.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/database/MysqlConnector.kt new file mode 100644 index 00000000..f8ecf19c --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/database/MysqlConnector.kt @@ -0,0 +1,35 @@ +package com.atlassian.performance.tools.infrastructure.api.database + +import com.atlassian.performance.tools.infrastructure.api.jira.install.hook.PostInstallHooks +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira +import com.atlassian.performance.tools.infrastructure.api.jira.install.hook.PostInstallHook +import com.atlassian.performance.tools.jvmtasks.api.Backoff +import com.atlassian.performance.tools.jvmtasks.api.IdempotentAction +import com.atlassian.performance.tools.ssh.api.SshConnection +import java.time.Duration + +class MysqlConnector : PostInstallHook { + + override fun call( + ssh: SshConnection, + jira: InstalledJira, + hooks: PostInstallHooks + ) { + val connector = "https://dev.mysql.com/get/Downloads/Connector-J/mysql-connector-java-5.1.40.tar.gz" + IdempotentAction( + description = "Download MySQL connector", + action = { ssh.execute("wget -q $connector") } + ).retry( + maxAttempts = 3, + backoff = StaticBackoff(Duration.ofSeconds(5)) + ) + ssh.execute("tar -xzf mysql-connector-java-5.1.40.tar.gz") + ssh.execute("cp mysql-connector-java-5.1.40/mysql-connector-java-5.1.40-bin.jar ${jira.installation}/lib") + } +} + +private class StaticBackoff( + private val backOff: Duration +) : Backoff { + override fun backOff(attempt: Int): Duration = backOff +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/database/PostgresDatabase.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/database/PostgresDatabase.kt new file mode 100644 index 00000000..602889df --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/database/PostgresDatabase.kt @@ -0,0 +1,51 @@ +package com.atlassian.performance.tools.infrastructure.api.database + +import com.atlassian.performance.tools.infrastructure.DockerImage +import com.atlassian.performance.tools.infrastructure.api.dataset.DatasetPackage +import com.atlassian.performance.tools.ssh.api.SshConnection +import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.Logger +import java.net.URI +import java.time.Duration + +class PostgresDatabase( + private val source: DatasetPackage, + private val maxConnections: Int +) : Database { + private val logger: Logger = LogManager.getLogger(this::class.java) + + private val image: DockerImage = DockerImage( + name = "postgres:9.6.15", + pullTimeout = Duration.ofMinutes(5) + ) + + constructor( + source: DatasetPackage + ) : this( + source = source, + maxConnections = 200 + ) + + override fun setup(ssh: SshConnection): String { + val data = source.download(ssh) + val containerName = image.run( + ssh = ssh, +// TODO Dataset for Postgres +// parameters = "-p 5432:5432 -v `realpath $data`:/", + parameters = "-p 5432:5432", + arguments = "-c 'listen_addresses='*'' -c 'max_connections=$maxConnections'" + ) + Thread.sleep(Duration.ofSeconds(15).toMillis()) + logger.debug("Postgres - creating jira user and database") + ssh.execute("sudo docker exec -u postgres $containerName psql --command \"CREATE USER jira WITH NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB LOGIN PASSWORD 'jira';\"") + ssh.execute("sudo docker exec -u postgres $containerName createdb -E UNICODE -l C -T template0 -O jira jira") + return data + } + + override fun start(jira: URI, ssh: SshConnection) { + // TODO Check logs for the following entry + // LOG: database system is ready to accept connections + Thread.sleep(Duration.ofSeconds(15).toMillis()) + } + +} \ No newline at end of file diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/EmptyJiraHome.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/EmptyJiraHome.kt new file mode 100644 index 00000000..0ea7f794 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/EmptyJiraHome.kt @@ -0,0 +1,11 @@ +package com.atlassian.performance.tools.infrastructure.api.jira + +import com.atlassian.performance.tools.ssh.api.SshConnection + +class EmptyJiraHome : JiraHomeSource { + override fun download(ssh: SshConnection): String { + val jiraHome = "jira-home" + ssh.execute("mkdir $jiraHome") + return jiraHome + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/JiraNodeConfig.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/JiraNodeConfig.kt index a759d080..1a0877cf 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/JiraNodeConfig.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/JiraNodeConfig.kt @@ -1,10 +1,6 @@ package com.atlassian.performance.tools.infrastructure.api.jira -import com.atlassian.performance.tools.infrastructure.api.jvm.DisabledJvmDebug -import com.atlassian.performance.tools.infrastructure.api.jvm.JavaDevelopmentKit -import com.atlassian.performance.tools.infrastructure.api.jvm.VersionedJavaDevelopmentKit -import com.atlassian.performance.tools.infrastructure.api.jvm.JvmDebug -import com.atlassian.performance.tools.infrastructure.api.jvm.OracleJDK +import com.atlassian.performance.tools.infrastructure.api.jvm.* import com.atlassian.performance.tools.infrastructure.api.jvm.jmx.DisabledRemoteJmx import com.atlassian.performance.tools.infrastructure.api.jvm.jmx.RemoteJmx import com.atlassian.performance.tools.infrastructure.api.profiler.Profiler @@ -157,4 +153,4 @@ class JiraNodeConfig private constructor( JiraNodeConfig::class.java.getResource("/collectd/conf/jira-default.conf").toURI() ) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/InstalledJira.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/InstalledJira.kt new file mode 100644 index 00000000..a796b32a --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/InstalledJira.kt @@ -0,0 +1,16 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.install + +import com.atlassian.performance.tools.infrastructure.api.jvm.JavaDevelopmentKit + +class InstalledJira( + /** + * E.g. it contains `./dbconfig.xml` + */ + val home: String, + /** + * E.g. it contains `./conf/server.xml` + */ + val installation: String, + val jdk: JavaDevelopmentKit, + val server: TcpServer +) diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/JiraInstallation.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/JiraInstallation.kt new file mode 100644 index 00000000..d9d96b83 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/JiraInstallation.kt @@ -0,0 +1,13 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.install + +import com.atlassian.performance.tools.ssh.api.SshConnection +import net.jcip.annotations.ThreadSafe + +@ThreadSafe +interface JiraInstallation { + + fun install( + ssh: SshConnection, + server: TcpServer + ): InstalledJira +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/ParallelInstallation.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/ParallelInstallation.kt new file mode 100644 index 00000000..72fefb17 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/ParallelInstallation.kt @@ -0,0 +1,36 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.install + +import com.atlassian.performance.tools.concurrency.api.submitWithLogContext +import com.atlassian.performance.tools.infrastructure.api.distribution.ProductDistribution +import com.atlassian.performance.tools.infrastructure.api.jira.JiraHomeSource +import com.atlassian.performance.tools.infrastructure.api.jvm.JavaDevelopmentKit +import com.atlassian.performance.tools.ssh.api.SshConnection +import java.util.concurrent.Executors + +class ParallelInstallation( + private val jiraHomeSource: JiraHomeSource, + private val productDistribution: ProductDistribution, + private val jdk: JavaDevelopmentKit +) : JiraInstallation { + + override fun install( + ssh: SshConnection, + server: TcpServer + ): InstalledJira { + val pool = Executors.newCachedThreadPool { runnable -> + Thread(runnable, "jira-installation-${runnable.hashCode()}") + } + val product = pool.submitWithLogContext("product") { + productDistribution.install(ssh, ".") + } + val home = pool.submitWithLogContext("home") { + jiraHomeSource.download(ssh) + } + val java = pool.submitWithLogContext("java") { + jdk.also { it.install(ssh) } + } + val jira = InstalledJira(home.get(), product.get(), java.get(), server) + pool.shutdownNow() + return jira + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/SequentialInstallation.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/SequentialInstallation.kt new file mode 100644 index 00000000..dbd7367a --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/SequentialInstallation.kt @@ -0,0 +1,23 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.install + +import com.atlassian.performance.tools.infrastructure.api.distribution.ProductDistribution +import com.atlassian.performance.tools.infrastructure.api.jira.JiraHomeSource +import com.atlassian.performance.tools.infrastructure.api.jvm.JavaDevelopmentKit +import com.atlassian.performance.tools.ssh.api.SshConnection + +class SequentialInstallation( + private val jiraHomeSource: JiraHomeSource, + private val productDistribution: ProductDistribution, + private val jdk: JavaDevelopmentKit +) : JiraInstallation { + + override fun install( + ssh: SshConnection, + server: TcpServer + ): InstalledJira { + val installation = productDistribution.install(ssh, ".") + val home = jiraHomeSource.download(ssh) + jdk.install(ssh) + return InstalledJira(home, installation, jdk, server) + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/TcpServer.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/TcpServer.kt new file mode 100644 index 00000000..9997f8a4 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/TcpServer.kt @@ -0,0 +1,19 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.install + +class TcpServer( + val ip: String, + val publicPort: Int, + val privatePort: Int, + val name: String +) { + constructor( + ip: String, + port: Int, + name: String + ) : this( + ip, + port, + port, + name + ) +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/AsyncProfilerHook.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/AsyncProfilerHook.kt new file mode 100644 index 00000000..32457b84 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/AsyncProfilerHook.kt @@ -0,0 +1,57 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.install.hook + +import com.atlassian.performance.tools.infrastructure.api.jira.install.TcpServer +import com.atlassian.performance.tools.infrastructure.api.jira.report.Report +import com.atlassian.performance.tools.infrastructure.api.jira.start.StartedJira +import com.atlassian.performance.tools.infrastructure.api.jira.start.hook.PostStartHook +import com.atlassian.performance.tools.infrastructure.api.jira.start.hook.PostStartHooks +import com.atlassian.performance.tools.ssh.api.SshConnection +import java.net.URI + +class AsyncProfilerHook : PreInstallHook { + + override fun call( + ssh: SshConnection, + server: TcpServer, + hooks: PreInstallHooks + ) { + val directory = "async-profiler" + val downloads = URI("https://github.com/jvm-profiling-tools/async-profiler/releases/download/") + val distribution = downloads.resolve("v1.4/async-profiler-1.4-linux-x64.tar.gz") + ssh.execute("wget -q $distribution") + ssh.execute("mkdir $directory") + ssh.execute("tar -xzf async-profiler-1.4-linux-x64.tar.gz -C $directory") + ssh.execute("sudo sh -c 'echo 1 > /proc/sys/kernel/perf_event_paranoid'") + ssh.execute("sudo sh -c 'echo 0 > /proc/sys/kernel/kptr_restrict'") + val profilerPath = "./$directory/profiler.sh" + val profiler = InstalledAsyncProfiler(profilerPath) + hooks.postStart.insert(profiler) + } +} + +private class InstalledAsyncProfiler( + private val profilerPath: String +) : PostStartHook { + + override fun call( + ssh: SshConnection, + jira: StartedJira, + hooks: PostStartHooks + ) { + ssh.execute("$profilerPath -b 20000000 start ${jira.pid}") + val profiler = StartedAsyncProfiler(jira.pid, profilerPath) + hooks.reports.add(profiler) + } +} + +private class StartedAsyncProfiler( + private val pid: Int, + private val profilerPath: String +) : Report { + + override fun locate(ssh: SshConnection): List { + val flameGraphFile = "flamegraph.svg" + ssh.execute("$profilerPath stop $pid -o svg > $flameGraphFile") + return listOf(flameGraphFile) + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/DataCenterHook.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/DataCenterHook.kt new file mode 100644 index 00000000..802a6a18 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/DataCenterHook.kt @@ -0,0 +1,24 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.install.hook + +import com.atlassian.performance.tools.infrastructure.api.jira.SharedHome +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira +import com.atlassian.performance.tools.ssh.api.SshConnection + +class DataCenterHook( + private val nodeId: String, + private val sharedHome: SharedHome +) : PostInstallHook { + + override fun call( + ssh: SshConnection, + jira: InstalledJira, + hooks: PostInstallHooks + ) { + val localSharedHome = sharedHome.localSharedHome + sharedHome.mount(ssh) + val jiraHome = jira.home // TODO what's the difference between localSharedHome and jiraHome? should both be hookable? + ssh.execute("echo ehcache.object.port = 40011 >> $jiraHome/cluster.properties") + ssh.execute("echo jira.node.id = $nodeId >> $jiraHome/cluster.properties") + ssh.execute("echo jira.shared.home = `realpath $localSharedHome` >> $jiraHome/cluster.properties") + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/DisabledAutoBackup.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/DisabledAutoBackup.kt new file mode 100644 index 00000000..dd482d1e --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/DisabledAutoBackup.kt @@ -0,0 +1,15 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.install.hook + +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira +import com.atlassian.performance.tools.ssh.api.SshConnection + +class DisabledAutoBackup : PostInstallHook { + + override fun call( + ssh: SshConnection, + jira: InstalledJira, + hooks: PostInstallHooks + ) { + ssh.execute("echo jira.autoexport=false > ${jira.home}/jira-config.properties") + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/HookedJiraInstallation.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/HookedJiraInstallation.kt new file mode 100644 index 00000000..06d40ba2 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/HookedJiraInstallation.kt @@ -0,0 +1,22 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.install.hook + +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira +import com.atlassian.performance.tools.infrastructure.api.jira.install.JiraInstallation +import com.atlassian.performance.tools.infrastructure.api.jira.install.TcpServer +import com.atlassian.performance.tools.ssh.api.SshConnection + +class HookedJiraInstallation( + private val installation: JiraInstallation, + private val hooks: PreInstallHooks +) : JiraInstallation { + + override fun install( + ssh: SshConnection, + server: TcpServer + ): InstalledJira { + hooks.call(ssh, server) + val installed = installation.install(ssh, server) + hooks.postInstall.call(ssh, installed) + return installed + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/JiraHomeProperty.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/JiraHomeProperty.kt new file mode 100644 index 00000000..a95498cf --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/JiraHomeProperty.kt @@ -0,0 +1,16 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.install.hook + +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira +import com.atlassian.performance.tools.ssh.api.SshConnection + +class JiraHomeProperty : PostInstallHook { + + override fun call( + ssh: SshConnection, + jira: InstalledJira, + hooks: PostInstallHooks + ) { + val properties = "${jira.installation}/atlassian-jira/WEB-INF/classes/jira-application.properties" + ssh.execute("echo jira.home=`realpath ${jira.home}` > $properties") + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/JiraLogs.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/JiraLogs.kt new file mode 100644 index 00000000..20e94ed1 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/JiraLogs.kt @@ -0,0 +1,28 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.install.hook + +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira +import com.atlassian.performance.tools.infrastructure.api.jira.report.StaticReport +import com.atlassian.performance.tools.ssh.api.SshConnection +import java.nio.file.Path +import java.nio.file.Paths + +class JiraLogs : PostInstallHook { + + override fun call(ssh: SshConnection, jira: InstalledJira, hooks: PostInstallHooks) { + listOf( + "${jira.home}/log/atlassian-jira.log", + "${jira.installation}/logs/catalina.out" + ) + .onEach { ensureFile(Paths.get(it), ssh) } + .map { StaticReport(it) } + .forEach { hooks.reports.add(it) } + } + + private fun ensureFile( + path: Path, + ssh: SshConnection + ) { + ssh.execute("mkdir -p ${path.parent!!}") + ssh.execute("touch $path") + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/JvmConfig.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/JvmConfig.kt new file mode 100644 index 00000000..e772aa13 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/JvmConfig.kt @@ -0,0 +1,29 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.install.hook + +import com.atlassian.performance.tools.infrastructure.api.jira.JiraGcLog +import com.atlassian.performance.tools.infrastructure.api.jira.JiraNodeConfig +import com.atlassian.performance.tools.infrastructure.api.jira.SetenvSh +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira +import com.atlassian.performance.tools.infrastructure.api.jira.report.FileListing +import com.atlassian.performance.tools.ssh.api.SshConnection + +class JvmConfig( + private val config: JiraNodeConfig +) : PostInstallHook { + + override fun call( + ssh: SshConnection, + jira: InstalledJira, + hooks: PostInstallHooks + ) { + val gcLog = JiraGcLog(jira.installation) + SetenvSh(jira.installation).setup( + connection = ssh, + config = config, + gcLog = gcLog, + jiraIp = jira.server.ip + ) + val report = FileListing(gcLog.path("*")) + hooks.reports.add(report) + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/LateUbuntuSysstat.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/LateUbuntuSysstat.kt new file mode 100644 index 00000000..3a6d2222 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/LateUbuntuSysstat.kt @@ -0,0 +1,35 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.install.hook + +import com.atlassian.performance.tools.infrastructure.Iostat +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira +import com.atlassian.performance.tools.infrastructure.api.jira.start.StartedJira +import com.atlassian.performance.tools.infrastructure.api.jira.start.hook.PostStartHook +import com.atlassian.performance.tools.infrastructure.api.jira.start.hook.PostStartHooks +import com.atlassian.performance.tools.infrastructure.api.os.OsMetric +import com.atlassian.performance.tools.infrastructure.api.os.Ubuntu +import com.atlassian.performance.tools.infrastructure.api.os.Vmstat +import com.atlassian.performance.tools.infrastructure.jira.hook.RemoteMonitoringProcessReport +import com.atlassian.performance.tools.ssh.api.SshConnection + +class LateUbuntuSysstat : PostInstallHook { + override fun call( + ssh: SshConnection, + jira: InstalledJira, + hooks: PostInstallHooks + ) { + val ubuntu = Ubuntu() + ubuntu.install(ssh, listOf("sysstat")) + listOf(Vmstat(), Iostat()) + .map { PostStartOsMetric(it) } + .forEach { hooks.postStart.insert(it) } + } +} + +private class PostStartOsMetric( + private val metric: OsMetric +) : PostStartHook { + override fun call(ssh: SshConnection, jira: StartedJira, hooks: PostStartHooks) { + val process = metric.start(ssh) + hooks.reports.add(RemoteMonitoringProcessReport(process)) + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/PostInstallHook.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/PostInstallHook.kt new file mode 100644 index 00000000..0fe19b0a --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/PostInstallHook.kt @@ -0,0 +1,21 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.install.hook + +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira +import com.atlassian.performance.tools.ssh.api.SshConnection + +/** + * Intercepts a call after Jira is installed. + */ +interface PostInstallHook { + + /** + * @param [ssh] connects to the [jira] + * @param [jira] points to the installed Jira + * @param [hooks] inserts future hooks and reports + */ + fun call( + ssh: SshConnection, + jira: InstalledJira, + hooks: PostInstallHooks + ) +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/PostInstallHooks.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/PostInstallHooks.kt new file mode 100644 index 00000000..155dcc63 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/PostInstallHooks.kt @@ -0,0 +1,54 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.install.hook + +import com.atlassian.performance.tools.infrastructure.api.jira.JiraNodeConfig +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira +import com.atlassian.performance.tools.infrastructure.api.jira.start.hook.PreStartHooks +import com.atlassian.performance.tools.infrastructure.jira.hook.install.ProfilerHook +import com.atlassian.performance.tools.infrastructure.jira.hook.install.SplunkForwarderHook +import com.atlassian.performance.tools.ssh.api.SshConnection +import java.util.* +import java.util.concurrent.ConcurrentLinkedQueue + +class PostInstallHooks private constructor( + val preStart: PreStartHooks +) { + + private val hooks: Queue = ConcurrentLinkedQueue() + val postStart = preStart.postStart + val reports = postStart.reports + + fun insert( + hook: PostInstallHook + ) { + hooks.add(hook) + } + + internal fun call( + ssh: SshConnection, + jira: InstalledJira + ) { + while (true) { + hooks + .poll() + ?.call(ssh, jira, this) + ?: break + } + } + + companion object Factory { + fun default(): PostInstallHooks = PostInstallHooks(PreStartHooks.default()).apply { + val config = JiraNodeConfig.Builder().build() + listOf( + JiraHomeProperty(), + DisabledAutoBackup(), + JvmConfig(config), + ProfilerHook(config.profiler), + SplunkForwarderHook(config.splunkForwarder), + JiraLogs(), + LateUbuntuSysstat() + ).forEach { insert(it) } + } + + fun empty(): PostInstallHooks = PostInstallHooks(PreStartHooks.empty()) + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/PreInstallHook.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/PreInstallHook.kt new file mode 100644 index 00000000..1e58b1af --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/PreInstallHook.kt @@ -0,0 +1,21 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.install.hook + +import com.atlassian.performance.tools.infrastructure.api.jira.install.TcpServer +import com.atlassian.performance.tools.ssh.api.SshConnection + +/** + * Intercepts a call before Jira is installed. + */ +interface PreInstallHook { + + /** + * @param [ssh] connects to the [server] + * @param [server] will install Jira + * @param [hooks] inserts future hooks and reports + */ + fun call( + ssh: SshConnection, + server: TcpServer, + hooks: PreInstallHooks + ) +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/PreInstallHooks.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/PreInstallHooks.kt new file mode 100644 index 00000000..5506de0d --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/PreInstallHooks.kt @@ -0,0 +1,40 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.install.hook + +import com.atlassian.performance.tools.infrastructure.api.jira.install.TcpServer +import com.atlassian.performance.tools.ssh.api.SshConnection +import java.util.* +import java.util.concurrent.ConcurrentLinkedQueue + +class PreInstallHooks private constructor( + val postInstall: PostInstallHooks +) { + + private val hooks: Queue = ConcurrentLinkedQueue() + val preStart = postInstall.preStart + val postStart = preStart.postStart + val reports = postStart.reports + + fun insert( + hook: PreInstallHook + ) { + hooks.add(hook) + } + + internal fun call( + ssh: SshConnection, + server: TcpServer + ) { + while (true) { + hooks + .poll() + ?.call(ssh, server, this) + ?: break + } + } + + companion object Factory { + fun default(): PreInstallHooks = PreInstallHooks(PostInstallHooks.default()) + + fun empty(): PreInstallHooks = PreInstallHooks(PostInstallHooks.empty()) + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/SystemLog.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/SystemLog.kt new file mode 100644 index 00000000..cb51739d --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/SystemLog.kt @@ -0,0 +1,12 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.install.hook + +import com.atlassian.performance.tools.infrastructure.api.jira.report.Report +import com.atlassian.performance.tools.infrastructure.api.jira.report.StaticReport +import com.atlassian.performance.tools.ssh.api.SshConnection + +class SystemLog : Report { + + override fun locate(ssh: SshConnection): List { + return StaticReport("/var/log/syslog").locate(ssh) + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/report/FileListing.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/report/FileListing.kt new file mode 100644 index 00000000..e1736a7d --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/report/FileListing.kt @@ -0,0 +1,15 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.report + +import com.atlassian.performance.tools.ssh.api.SshConnection + +class FileListing( + private val pattern: String +) : Report { + override fun locate( + ssh: SshConnection + ): List = ssh + .execute("ls $pattern") + .output + .lines() + .filter { it.isNotBlank() } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/report/Report.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/report/Report.kt new file mode 100644 index 00000000..1da33c75 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/report/Report.kt @@ -0,0 +1,15 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.report + +import com.atlassian.performance.tools.ssh.api.SshConnection + +/** + * Reports back about remote events. E.g. points to interesting logs, dumps, charts. + */ +interface Report { + + /** + * @param [ssh] connects to the server, which holds interesting data + * @return list of interesting file paths to be downloaded + */ + fun locate(ssh: SshConnection): List +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/report/Reports.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/report/Reports.kt new file mode 100644 index 00000000..bfc8b1cd --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/report/Reports.kt @@ -0,0 +1,18 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.report + +import java.util.* +import java.util.concurrent.ConcurrentLinkedQueue + +class Reports { + private val reports: Queue = ConcurrentLinkedQueue() + + fun add( + report: Report + ) { + reports.add(report) + } + + fun list(): Iterable { + return reports.toList() + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/report/StaticReport.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/report/StaticReport.kt new file mode 100644 index 00000000..da90e4d1 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/report/StaticReport.kt @@ -0,0 +1,10 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.report + +import com.atlassian.performance.tools.ssh.api.SshConnection + +class StaticReport( + private val remotePath: String +) : Report { + + override fun locate(ssh: SshConnection): List = listOf(remotePath) +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/JiraLaunchScript.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/JiraLaunchScript.kt new file mode 100644 index 00000000..32673528 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/JiraLaunchScript.kt @@ -0,0 +1,24 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.start + +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira +import com.atlassian.performance.tools.ssh.api.SshConnection +import java.time.Duration + +class JiraLaunchScript : JiraStart { + + override fun start( + ssh: SshConnection, + installed: InstalledJira + ): StartedJira { + ssh.execute( + "${installed.jdk.use()}; ${installed.installation}/bin/start-jira.sh", + Duration.ofMinutes(1) + ) + val pid = ssh + .execute("cat ${installed.installation}/work/catalina.pid") + .output + .trim() + .toInt() + return StartedJira(installed, pid) + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/JiraStart.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/JiraStart.kt new file mode 100644 index 00000000..dabddc38 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/JiraStart.kt @@ -0,0 +1,14 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.start + +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira +import com.atlassian.performance.tools.ssh.api.SshConnection +import net.jcip.annotations.ThreadSafe + +@ThreadSafe +interface JiraStart { + + fun start( + ssh: SshConnection, + installed: InstalledJira + ): StartedJira +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/StartedJira.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/StartedJira.kt new file mode 100644 index 00000000..da6f4955 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/StartedJira.kt @@ -0,0 +1,8 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.start + +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira + +class StartedJira( + val installed: InstalledJira, + val pid: Int +) diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/AccessLogs.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/AccessLogs.kt new file mode 100644 index 00000000..cccd0da5 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/AccessLogs.kt @@ -0,0 +1,12 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.start.hook + +import com.atlassian.performance.tools.infrastructure.api.jira.report.FileListing +import com.atlassian.performance.tools.infrastructure.api.jira.start.StartedJira +import com.atlassian.performance.tools.ssh.api.SshConnection + +class AccessLogs : PostStartHook { + + override fun call(ssh: SshConnection, jira: StartedJira, hooks: PostStartHooks) { + hooks.reports.add(FileListing("${jira.installed.installation}/logs/*access*")) + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/HookedJiraStart.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/HookedJiraStart.kt new file mode 100644 index 00000000..20634150 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/HookedJiraStart.kt @@ -0,0 +1,22 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.start.hook + +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira +import com.atlassian.performance.tools.infrastructure.api.jira.start.JiraStart +import com.atlassian.performance.tools.infrastructure.api.jira.start.StartedJira +import com.atlassian.performance.tools.ssh.api.SshConnection + +class HookedJiraStart( + private val start: JiraStart, + private val hooks: PreStartHooks +) : JiraStart { + + override fun start( + ssh: SshConnection, + installed: InstalledJira + ): StartedJira { + hooks.call(ssh, installed) + val started = start.start(ssh, installed) + hooks.postStart.call(ssh, started) + return started + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/JstatHook.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/JstatHook.kt new file mode 100644 index 00000000..27b23757 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/JstatHook.kt @@ -0,0 +1,17 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.start.hook + +import com.atlassian.performance.tools.infrastructure.api.jira.start.StartedJira +import com.atlassian.performance.tools.infrastructure.jira.hook.RemoteMonitoringProcessReport +import com.atlassian.performance.tools.ssh.api.SshConnection + +class JstatHook : PostStartHook { + + override fun call( + ssh: SshConnection, + jira: StartedJira, + hooks: PostStartHooks + ) { + val process = jira.installed.jdk.jstatMonitoring.start(ssh, jira.pid) + hooks.reports.add(RemoteMonitoringProcessReport(process)) + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/PostStartHook.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/PostStartHook.kt new file mode 100644 index 00000000..c3c47123 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/PostStartHook.kt @@ -0,0 +1,21 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.start.hook + +import com.atlassian.performance.tools.infrastructure.api.jira.start.StartedJira +import com.atlassian.performance.tools.ssh.api.SshConnection + +/** + * Intercepts a call after Jira is started. + */ +interface PostStartHook { + + /** + * @param [ssh] connects to the [jira] + * @param [jira] points to the started Jira + * @param [hooks] inserts future reports + */ + fun call( + ssh: SshConnection, + jira: StartedJira, + hooks: PostStartHooks + ) +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/PostStartHooks.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/PostStartHooks.kt new file mode 100644 index 00000000..21b0b46a --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/PostStartHooks.kt @@ -0,0 +1,39 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.start.hook + +import com.atlassian.performance.tools.infrastructure.api.jira.report.Reports +import com.atlassian.performance.tools.infrastructure.api.jira.start.StartedJira +import com.atlassian.performance.tools.ssh.api.SshConnection +import java.util.* +import java.util.concurrent.ConcurrentLinkedQueue + +class PostStartHooks private constructor() { + + private val hooks: Queue = ConcurrentLinkedQueue() + val reports = Reports() + + fun insert( + hook: PostStartHook + ) { + hooks.add(hook) + } + + internal fun call( + ssh: SshConnection, + jira: StartedJira + ) { + while (true) { + hooks + .poll() + ?.call(ssh, jira, this) + ?: break + } + } + + companion object Factory { + fun default(): PostStartHooks = empty().apply { + insert(JstatHook()) + } + + fun empty(): PostStartHooks = PostStartHooks() + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/PreStartHook.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/PreStartHook.kt new file mode 100644 index 00000000..d0fa8326 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/PreStartHook.kt @@ -0,0 +1,21 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.start.hook + +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira +import com.atlassian.performance.tools.ssh.api.SshConnection + +/** + * Intercepts a call before Jira is started. + */ +interface PreStartHook { + + /** + * @param [ssh] connects to the [jira] + * @param [jira] points to the installed Jira + * @param [hooks] inserts future hooks and reports + */ + fun call( + ssh: SshConnection, + jira: InstalledJira, + hooks: PreStartHooks + ) +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/PreStartHooks.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/PreStartHooks.kt new file mode 100644 index 00000000..6ce16b42 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/PreStartHooks.kt @@ -0,0 +1,38 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.start.hook + +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira +import com.atlassian.performance.tools.ssh.api.SshConnection +import java.util.* +import java.util.concurrent.ConcurrentLinkedQueue + +class PreStartHooks private constructor( + val postStart: PostStartHooks +) { + + private val hooks: Queue = ConcurrentLinkedQueue() + val reports = postStart.reports + + fun insert( + hook: PreStartHook + ) { + hooks.add(hook) + } + + internal fun call( + ssh: SshConnection, + jira: InstalledJira + ) { + while (true) { + hooks + .poll() + ?.call(ssh, jira, this) + ?: break + } + } + + companion object Factory { + fun default() = PreStartHooks(PostStartHooks.default()) + + fun empty() = PreStartHooks(PostStartHooks.empty()) + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/RestUpgrade.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/RestUpgrade.kt new file mode 100644 index 00000000..84aa39f4 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/RestUpgrade.kt @@ -0,0 +1,74 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.start.hook + +import com.atlassian.performance.tools.infrastructure.api.jira.JiraLaunchTimeouts +import com.atlassian.performance.tools.infrastructure.api.jira.report.StaticReport +import com.atlassian.performance.tools.infrastructure.api.jira.start.StartedJira +import com.atlassian.performance.tools.infrastructure.api.jvm.ThreadDump +import com.atlassian.performance.tools.ssh.api.SshConnection +import java.net.URI +import java.time.Duration +import java.time.Instant + +class RestUpgrade( + private val timeouts: JiraLaunchTimeouts, + private val adminUsername: String, + private val adminPassword: String +) : PostStartHook { + + override fun call(ssh: SshConnection, jira: StartedJira, hooks: PostStartHooks) { + val threadDump = ThreadDump(jira.pid, jira.installed.jdk) + val privatePort = jira.installed.server.privatePort + val upgradesEndpoint = URI("http://$adminUsername:$adminPassword@localhost:$privatePort/rest/api/2/upgrade") + hooks.reports.add(StaticReport("thread-dumps")) + waitForStatusToChange( + statusQuo = "000", + timeout = timeouts.offlineTimeout, + ssh = ssh, + uri = upgradesEndpoint, + threadDump = threadDump + ) + waitForStatusToChange( + statusQuo = "503", + timeout = timeouts.initTimeout, + ssh = ssh, + uri = upgradesEndpoint, + threadDump = threadDump + ) + ssh.execute( + cmd = "curl --silent --retry 6 -X POST $upgradesEndpoint", + timeout = Duration.ofSeconds(15) + ) + waitForStatusToChange( + statusQuo = "303", + timeout = timeouts.upgradeTimeout, + ssh = ssh, + uri = upgradesEndpoint, + threadDump = threadDump + ) + } + + private fun waitForStatusToChange( + statusQuo: String, + uri: URI, + timeout: Duration, + ssh: SshConnection, + threadDump: ThreadDump + ) { + val backoff = Duration.ofSeconds(10) + val deadline = Instant.now() + timeout + while (true) { + val currentStatus = ssh.safeExecute( + cmd = "curl --silent --write-out '%{http_code}' --output /dev/null -X GET $uri", + timeout = timeouts.unresponsivenessTimeout + ).output + if (currentStatus != statusQuo) { + break + } + if (deadline < Instant.now()) { + throw Exception("$uri failed to get out of $statusQuo status within $timeout") + } + threadDump.gather(ssh, "thread-dumps") + Thread.sleep(backoff.toMillis()) + } + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/Jstat.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/Jstat.kt index f0d01e0c..34a3e467 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/Jstat.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/Jstat.kt @@ -11,7 +11,7 @@ class Jstat( companion object { val LOG_FILE_NAME = "jpt-jstat.log" private val INTERVAL: Duration = Duration.ofSeconds(2) - private val LOG_PATH: String = "~/$LOG_FILE_NAME" + private val LOG_PATH: String = "./$LOG_FILE_NAME" private val TIME = "date -Iseconds" private val ADD_TIME = @@ -48,4 +48,4 @@ class Jstat( val process = connection.startProcess("${jvmBin}jstat $option -t $pid $interval | $ADD_TIME > $LOG_PATH") return MonitoringProcess(process, LOG_PATH) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/ThreadDump.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/ThreadDump.kt index 18a6d361..8932d469 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/ThreadDump.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/ThreadDump.kt @@ -26,7 +26,7 @@ class ThreadDump( */ fun gather(connection: SshConnection, destination: String) { val threadDumpName = Instant.now().toEpochMilli() - val command = "${jdk.use()}; jcmd $pid Thread.print > $destination/${threadDumpName}" + val command = "${jdk.use()}; jcmd $pid Thread.print > $destination/$threadDumpName" connection.execute("mkdir -p $destination") connection.execute(command) } diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/os/Vmstat.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/os/Vmstat.kt index 2d8f8005..84c6aaab 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/os/Vmstat.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/os/Vmstat.kt @@ -9,7 +9,7 @@ class Vmstat : OsMetric { companion object { val LOG_FILE_NAME = "jpt-vmstat.log" - private val LOG_PATH: String = "~/$LOG_FILE_NAME" + private val LOG_PATH: String = "./$LOG_FILE_NAME" private val DELAY: Duration = Duration.ofSeconds(2) } @@ -26,4 +26,4 @@ class Vmstat : OsMetric { val process = connection.startProcess("vmstat -t $delayInSeconds > $LOG_PATH") return MonitoringProcess(process, LOG_PATH) } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/profiler/AsyncProfiler.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/profiler/AsyncProfiler.kt index e2f3f23e..c2850884 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/profiler/AsyncProfiler.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/profiler/AsyncProfiler.kt @@ -6,6 +6,7 @@ import com.atlassian.performance.tools.ssh.api.SshConnection /** * Asynchronous profiler. See https://github.com/jvm-profiling-tools/async-profiler#basic-usage */ +@Deprecated("Use AsyncProfilerHook") class AsyncProfiler : Profiler { override fun install(ssh: SshConnection) { ssh.execute("wget -q https://github.com/jvm-profiling-tools/async-profiler/releases/download/v1.4/async-profiler-1.4-linux-x64.tar.gz") @@ -34,4 +35,4 @@ class AsyncProfiler : Profiler { return flameGraphFile } } -} \ No newline at end of file +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/virtualusers/SshVirtualUsers.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/virtualusers/SshVirtualUsers.kt index e5589fe1..5e0da2aa 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/virtualusers/SshVirtualUsers.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/virtualusers/SshVirtualUsers.kt @@ -89,8 +89,4 @@ class SshVirtualUsers( } } } - - override fun toString(): String { - return "SshVirtualUsers(name='$name', nodeOrder=$nodeOrder, resultsTransport=$resultsTransport, jarName='$jarName', ssh=$ssh, logger=$logger, jdk=$jdk)" - } } diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/hook/RemoteMonitoringProcessReport.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/hook/RemoteMonitoringProcessReport.kt new file mode 100644 index 00000000..674a03e6 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/hook/RemoteMonitoringProcessReport.kt @@ -0,0 +1,14 @@ +package com.atlassian.performance.tools.infrastructure.jira.hook + +import com.atlassian.performance.tools.infrastructure.api.jira.report.Report +import com.atlassian.performance.tools.infrastructure.api.process.RemoteMonitoringProcess +import com.atlassian.performance.tools.ssh.api.SshConnection + +internal class RemoteMonitoringProcessReport( + private val process: RemoteMonitoringProcess +) : Report { + override fun locate(ssh: SshConnection): List { + process.stop(ssh) + return listOf(process.getResultPath()) + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/hook/install/ProfilerHook.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/hook/install/ProfilerHook.kt new file mode 100644 index 00000000..220fdbd4 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/hook/install/ProfilerHook.kt @@ -0,0 +1,40 @@ +package com.atlassian.performance.tools.infrastructure.jira.hook.install + +import com.atlassian.performance.tools.infrastructure.api.jira.install.hook.PostInstallHooks +import com.atlassian.performance.tools.infrastructure.api.jira.start.hook.PostStartHooks +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira +import com.atlassian.performance.tools.infrastructure.api.jira.install.hook.PostInstallHook +import com.atlassian.performance.tools.infrastructure.api.jira.start.StartedJira +import com.atlassian.performance.tools.infrastructure.api.jira.start.hook.PostStartHook +import com.atlassian.performance.tools.infrastructure.api.profiler.Profiler +import com.atlassian.performance.tools.infrastructure.jira.hook.RemoteMonitoringProcessReport +import com.atlassian.performance.tools.ssh.api.SshConnection + +/** + * Bridges the [Profiler] SPI with the [PostInstallHook] SPI. + * In general any [Profiler] can be rewritten as an [PostInstallHook] or any other hookPreStart without this bridge. + */ +class ProfilerHook( + private val profiler: Profiler +) : PostInstallHook { + override fun call(ssh: SshConnection, jira: InstalledJira, hooks: PostInstallHooks) { + profiler.install(ssh) + hooks.preStart.postStart.insert(InstalledProfiler(profiler)) + } +} + +private class InstalledProfiler( + private val profiler: Profiler +) : PostStartHook { + + override fun call( + ssh: SshConnection, + jira: StartedJira, + hooks: PostStartHooks + ) { + val process = profiler.start(ssh, jira.pid) + if (process != null) { + hooks.reports.add(RemoteMonitoringProcessReport(process)) + } + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/hook/install/SplunkForwarderHook.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/hook/install/SplunkForwarderHook.kt new file mode 100644 index 00000000..2112e483 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/hook/install/SplunkForwarderHook.kt @@ -0,0 +1,21 @@ +package com.atlassian.performance.tools.infrastructure.jira.hook.install + +import com.atlassian.performance.tools.infrastructure.api.jira.install.hook.PostInstallHooks +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira +import com.atlassian.performance.tools.infrastructure.api.jira.install.hook.PostInstallHook +import com.atlassian.performance.tools.infrastructure.api.splunk.SplunkForwarder +import com.atlassian.performance.tools.ssh.api.SshConnection + +internal class SplunkForwarderHook( + private val splunk: SplunkForwarder +) : PostInstallHook { + + override fun call( + ssh: SshConnection, + jira: InstalledJira, + hooks: PostInstallHooks + ) { + splunk.jsonifyLog4j(ssh, "${jira.installation}/atlassian-jira/WEB-INF/classes/log4j.properties") + splunk.run(ssh, jira.server.name, "/home/ubuntu/jirahome/log") + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/ubuntu/EarlyUbuntuSysstat.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/ubuntu/EarlyUbuntuSysstat.kt new file mode 100644 index 00000000..9bc25df4 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/ubuntu/EarlyUbuntuSysstat.kt @@ -0,0 +1,19 @@ +package com.atlassian.performance.tools.infrastructure.ubuntu + +import com.atlassian.performance.tools.infrastructure.api.jira.install.TcpServer +import com.atlassian.performance.tools.infrastructure.api.jira.install.hook.PreInstallHooks +import com.atlassian.performance.tools.infrastructure.api.jira.install.hook.PreInstallHook +import com.atlassian.performance.tools.ssh.api.SshConnection + +class EarlyUbuntuSysstat : PreInstallHook { + + override fun call( + ssh: SshConnection, + server: TcpServer, + hooks: PreInstallHooks + ) { + UbuntuSysstat() + .install(ssh) + .forEach { hooks.insert(it) } + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/ubuntu/InstalledOsMetric.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/ubuntu/InstalledOsMetric.kt new file mode 100644 index 00000000..515b806f --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/ubuntu/InstalledOsMetric.kt @@ -0,0 +1,18 @@ +package com.atlassian.performance.tools.infrastructure.ubuntu + +import com.atlassian.performance.tools.infrastructure.api.jira.install.TcpServer +import com.atlassian.performance.tools.infrastructure.api.jira.install.hook.PreInstallHooks +import com.atlassian.performance.tools.infrastructure.api.jira.install.hook.PreInstallHook +import com.atlassian.performance.tools.infrastructure.api.os.OsMetric +import com.atlassian.performance.tools.infrastructure.jira.hook.RemoteMonitoringProcessReport +import com.atlassian.performance.tools.ssh.api.SshConnection + +internal class InstalledOsMetric( + private val metric: OsMetric +) : PreInstallHook { + + override fun call(ssh: SshConnection, server: TcpServer, hooks: PreInstallHooks) { + val process = metric.start(ssh) + hooks.reports.add(RemoteMonitoringProcessReport(process)) + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/ubuntu/UbuntuSysstat.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/ubuntu/UbuntuSysstat.kt new file mode 100644 index 00000000..8a76c645 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/ubuntu/UbuntuSysstat.kt @@ -0,0 +1,17 @@ +package com.atlassian.performance.tools.infrastructure.ubuntu + +import com.atlassian.performance.tools.infrastructure.Iostat +import com.atlassian.performance.tools.infrastructure.api.os.Ubuntu +import com.atlassian.performance.tools.infrastructure.api.os.Vmstat +import com.atlassian.performance.tools.ssh.api.SshConnection + +internal class UbuntuSysstat { + + fun install( + ssh: SshConnection + ): List { + val ubuntu = Ubuntu() + ubuntu.install(ssh, listOf("sysstat")) + return listOf(Vmstat(), Iostat()).map { InstalledOsMetric(it) } + } +} diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/SshUbuntuExtensions.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/SshUbuntuExtensions.kt index 73d3a946..03890f04 100644 --- a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/SshUbuntuExtensions.kt +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/SshUbuntuExtensions.kt @@ -1,20 +1,22 @@ package com.atlassian.performance.tools.infrastructure import com.atlassian.performance.tools.ssh.api.Ssh +import com.atlassian.performance.tools.ssh.api.SshHost +import com.atlassian.performance.tools.ssh.api.auth.PublicKeyAuthentication import com.atlassian.performance.tools.sshubuntu.api.SshUbuntu import java.time.Duration internal fun SshUbuntu.toSsh(): Ssh { val ssh = Ssh(with(this.ssh) { - com.atlassian.performance.tools.ssh.api.SshHost( + SshHost( ipAddress = ipAddress, userName = userName, - authentication = com.atlassian.performance.tools.ssh.api.auth.PublicKeyAuthentication(privateKey), + authentication = PublicKeyAuthentication(privateKey), port = port ) }) ssh.newConnection().use { connection -> - connection.execute("apt-get update -qq", Duration.ofMinutes(3)) + connection.execute("apt-get update -qq", Duration.ofMinutes(7)) connection.execute("export DEBIAN_FRONTEND=noninteractive; apt-get install sudo curl screen gnupg2 -y -qq", Duration.ofMinutes(10)) } return ssh diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/PreInstallHooksTest.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/PreInstallHooksTest.kt new file mode 100644 index 00000000..c5fb692f --- /dev/null +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/PreInstallHooksTest.kt @@ -0,0 +1,57 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.install.hook + +import com.atlassian.performance.tools.infrastructure.api.jira.install.TcpServer +import com.atlassian.performance.tools.infrastructure.mock.UnimplementedSshConnection +import com.atlassian.performance.tools.ssh.api.SshConnection +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test + +class JiraNodeHooksTest { + + @Test + fun shouldHookDuringListing() { + val counter = CountingHook() + val hooks = PreInstallHooks.empty().apply { + insert(counter) + insert(HookingHook(counter)) + insert(counter) + } + val server = TcpServer("doesn't matter", 123, "fake-server") + + hooks.call(UnimplementedSshConnection(), server) + + assertThat(counter.count).isEqualTo(3) + } + + @Test + fun shouldHookToTheTailDuringListing() { + val counter = CountingHook() + val hooks = PreInstallHooks.empty().apply { + insert(counter) + insert(counter) + insert(HookingHook(counter)) + } + val server = TcpServer("doesn't matter", 123, "fake-server") + + hooks.call(UnimplementedSshConnection(), server) + + assertThat(counter.count).isEqualTo(3) + } +} + +private class CountingHook : PreInstallHook { + + var count = 0 + + override fun call(ssh: SshConnection, server: TcpServer, hooks: PreInstallHooks) { + count++ + } +} + +private class HookingHook( + private val hook: PreInstallHook +) : PreInstallHook { + override fun call(ssh: SshConnection, server: TcpServer, hooks: PreInstallHooks) { + hooks.insert(hook) + } +} diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/HookedJiraStartIT.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/HookedJiraStartIT.kt new file mode 100644 index 00000000..d9088d51 --- /dev/null +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/HookedJiraStartIT.kt @@ -0,0 +1,141 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.start.hook + +import com.atlassian.performance.tools.infrastructure.api.distribution.PublicJiraSoftwareDistribution +import com.atlassian.performance.tools.infrastructure.api.jira.EmptyJiraHome +import com.atlassian.performance.tools.infrastructure.api.jira.install.ParallelInstallation +import com.atlassian.performance.tools.infrastructure.api.jira.install.TcpServer +import com.atlassian.performance.tools.infrastructure.api.jira.install.hook.HookedJiraInstallation +import com.atlassian.performance.tools.infrastructure.api.jira.install.hook.PreInstallHooks +import com.atlassian.performance.tools.infrastructure.api.jira.start.JiraLaunchScript +import com.atlassian.performance.tools.infrastructure.api.jira.start.StartedJira +import com.atlassian.performance.tools.infrastructure.api.jvm.S3HostedJdk +import com.atlassian.performance.tools.infrastructure.toSsh +import com.atlassian.performance.tools.infrastructure.ubuntu.EarlyUbuntuSysstat +import com.atlassian.performance.tools.ssh.api.SshConnection +import com.atlassian.performance.tools.sshubuntu.api.SshUbuntuContainer +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import java.io.File +import java.nio.file.Files +import java.util.function.Consumer + +class HookedJiraStartIT { + + @Test + fun shouldStartJiraWithDefaultHooks() { + // given + val hooks = PreInstallHooks.default() + val jiraInstallation = HookedJiraInstallation( + ParallelInstallation( + jiraHomeSource = EmptyJiraHome(), + productDistribution = PublicJiraSoftwareDistribution("7.13.0"), + jdk = S3HostedJdk() + ), + hooks + ) + val jiraStart = HookedJiraStart(JiraLaunchScript(), hooks.preStart) + val privatePort = 8080 + val container = SshUbuntuContainer(Consumer { + it.addExposedPort(privatePort) + }) + val remoteReports = container.start().use { sshUbuntu -> + val server = TcpServer( + "localhost", + sshUbuntu.container.getMappedPort(privatePort), + privatePort, + "my-jira" + ) + val remoteReports = sshUbuntu.toSsh().newConnection().use { ssh -> + // when + val installed = jiraInstallation.install(ssh, server) + val started = jiraStart.start(ssh, installed) + stop(started, ssh) + hooks.reports.list().flatMap { it.locate(ssh) } + } + + // then + sshUbuntu.toSsh().newConnection().use { ssh -> + download(remoteReports, ssh) + } + return@use remoteReports + } + + assertThat(remoteReports).contains( + "./jpt-vmstat.log", + "./jpt-iostat.log", + "jira-home/log/atlassian-jira.log", + "./atlassian-jira-software-7.13.0-standalone/logs/catalina.out", + "./jpt-jstat.log" + ) + } + + @Test + fun shouldDownloadPartialReportsInCaseOfFailure() { + // given + val hooks = PreInstallHooks.empty().apply { + insert(EarlyUbuntuSysstat()) + postStart.insert(FailingHook()) + } + val jiraInstallation = HookedJiraInstallation( + ParallelInstallation( + jiraHomeSource = EmptyJiraHome(), + productDistribution = PublicJiraSoftwareDistribution("7.13.0"), + jdk = S3HostedJdk() + ), + hooks = hooks + ) + val privatePort = 8080 + val container = SshUbuntuContainer(Consumer { + it.addExposedPort(privatePort) + }) + val remoteReports = container.start().use { sshUbuntu -> + val server = TcpServer( + "localhost", + sshUbuntu.container.getMappedPort(privatePort), + privatePort, + "my-jira" + ) + return@use sshUbuntu.toSsh().newConnection().use useSsh@{ ssh -> + // when + try { + jiraInstallation.install(ssh, server) + } catch (e: Exception) { + println("Failed: ${e.message}") + } + return@useSsh hooks.reports.list().flatMap { it.locate(ssh) } + } + } + + // then + assertThat(remoteReports).contains( + "./jpt-vmstat.log", + "./jpt-iostat.log" + ) + } + + private fun stop( + started: StartedJira, + ssh: SshConnection + ) { + val installed = started.installed + ssh.execute("${installed.jdk.use()}; ${installed.installation}/bin/stop-jira.sh") + } + + private fun download( + remotes: List, + ssh: SshConnection + ): List { + val downloads = Files.createTempDirectory("apt-infra-test") + return remotes.map { remote -> + val local = downloads.resolve("./$remote") + ssh.download(remote, local) + return@map local.toFile() + } + } +} + +private class FailingHook : PostStartHook { + override fun call(ssh: SshConnection, jira: StartedJira, hooks: PostStartHooks) { + throw Exception("Expected failure") + } +} diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/S3HostedJdk.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/S3HostedJdk.kt new file mode 100644 index 00000000..81188113 --- /dev/null +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/S3HostedJdk.kt @@ -0,0 +1,44 @@ +package com.atlassian.performance.tools.infrastructure.api.jvm + +import com.atlassian.performance.tools.jvmtasks.api.IdempotentAction +import com.atlassian.performance.tools.jvmtasks.api.StaticBackoff +import com.atlassian.performance.tools.ssh.api.SshConnection +import java.net.URI +import java.time.Duration + +/** + * Harvested from https://stash.atlassian.com/projects/JIRASERVER/repos/jira-performance-tests/pull-requests/630 + */ +class S3HostedJdk : VersionedJavaDevelopmentKit { + private val jdkVersion = "1.8.0" + private val jdkUpdate = 131 + private val jdkArchive = "jdk${jdkVersion}_$jdkUpdate-linux-x64.tar.gz" + private val jdkUrl = URI.create("https://s3.amazonaws.com/packages_java/$jdkArchive") + private val jdkBin = "~/jdk${jdkVersion}_$jdkUpdate/jre/bin/" + private val bin = "~/jdk${jdkVersion}_$jdkUpdate/bin/" + override val jstatMonitoring = Jstat(bin) + + override fun getMajorVersion() = 8 + + override fun install(connection: SshConnection) { + download(connection) + connection.execute("tar -xzf $jdkArchive") + connection.execute("echo '${use()}' >> ~/.bashrc") + } + + private fun download(connection: SshConnection) { + IdempotentAction("download JDK") { + connection.execute( + cmd = "curl -s -L -O -k $jdkUrl", + timeout = Duration.ofMinutes(4) + ) + }.retry( + maxAttempts = 3, + backoff = StaticBackoff(Duration.ofSeconds(4)) + ) + } + + override fun use(): String = "export PATH=$jdkBin:$bin:${'$'}PATH" + + override fun command(options: String) = "${jdkBin}java $options" +}