Skip to content

Commit 77ef790

Browse files
committed
JPERF-273: Hook into Jira provisioning
1 parent e6483fb commit 77ef790

30 files changed

Lines changed: 840 additions & 7 deletions
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package com.atlassian.performance.tools.infrastructure.api.jira.install.hook
2+
3+
import com.atlassian.performance.tools.infrastructure.api.jira.install.TcpServer
4+
import com.atlassian.performance.tools.infrastructure.api.jira.report.Report
5+
import com.atlassian.performance.tools.infrastructure.api.jira.start.StartedJira
6+
import com.atlassian.performance.tools.infrastructure.api.jira.start.hook.PostStartHook
7+
import com.atlassian.performance.tools.infrastructure.api.jira.start.hook.PostStartHooks
8+
import com.atlassian.performance.tools.ssh.api.SshConnection
9+
import java.net.URI
10+
11+
class AsyncProfilerHook : PreInstallHook {
12+
13+
override fun call(
14+
ssh: SshConnection,
15+
server: TcpServer,
16+
hooks: PreInstallHooks
17+
) {
18+
val directory = "async-profiler"
19+
val downloads = URI("https://github.com/jvm-profiling-tools/async-profiler/releases/download/")
20+
val distribution = downloads.resolve("v1.4/async-profiler-1.4-linux-x64.tar.gz")
21+
ssh.execute("wget -q $distribution")
22+
ssh.execute("mkdir $directory")
23+
ssh.execute("tar -xzf async-profiler-1.4-linux-x64.tar.gz -C $directory")
24+
ssh.execute("sudo sh -c 'echo 1 > /proc/sys/kernel/perf_event_paranoid'")
25+
ssh.execute("sudo sh -c 'echo 0 > /proc/sys/kernel/kptr_restrict'")
26+
val profilerPath = "./$directory/profiler.sh"
27+
val profiler = InstalledAsyncProfiler(profilerPath)
28+
hooks.postStart.insert(profiler)
29+
}
30+
}
31+
32+
private class InstalledAsyncProfiler(
33+
private val profilerPath: String
34+
) : PostStartHook {
35+
36+
override fun call(
37+
ssh: SshConnection,
38+
jira: StartedJira,
39+
hooks: PostStartHooks
40+
) {
41+
ssh.execute("$profilerPath -b 20000000 start ${jira.pid}")
42+
val profiler = StartedAsyncProfiler(jira.pid, profilerPath)
43+
hooks.reports.add(profiler)
44+
}
45+
}
46+
47+
private class StartedAsyncProfiler(
48+
private val pid: Int,
49+
private val profilerPath: String
50+
) : Report {
51+
52+
override fun locate(ssh: SshConnection): List<String> {
53+
val flameGraphFile = "flamegraph.svg"
54+
ssh.execute("$profilerPath stop $pid -o svg > $flameGraphFile")
55+
return listOf(flameGraphFile)
56+
}
57+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.atlassian.performance.tools.infrastructure.api.jira.install.hook
2+
3+
import com.atlassian.performance.tools.infrastructure.api.jira.SharedHome
4+
import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira
5+
import com.atlassian.performance.tools.ssh.api.SshConnection
6+
7+
class DataCenterHook(
8+
private val nodeId: String,
9+
private val sharedHome: SharedHome
10+
) : PostInstallHook {
11+
12+
override fun call(
13+
ssh: SshConnection,
14+
jira: InstalledJira,
15+
hooks: PostInstallHooks
16+
) {
17+
val localSharedHome = sharedHome.localSharedHome
18+
sharedHome.mount(ssh)
19+
val jiraHome = jira.home.path // TODO what's the difference between localSharedHome and jiraHome? should both be hookable?
20+
ssh.execute("echo ehcache.object.port = 40011 >> $jiraHome/cluster.properties")
21+
ssh.execute("echo jira.node.id = $nodeId >> $jiraHome/cluster.properties")
22+
ssh.execute("echo jira.shared.home = `realpath $localSharedHome` >> $jiraHome/cluster.properties")
23+
}
24+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.atlassian.performance.tools.infrastructure.api.jira.install.hook
2+
3+
import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira
4+
import com.atlassian.performance.tools.ssh.api.SshConnection
5+
6+
class DisabledAutoBackup : PostInstallHook {
7+
8+
override fun call(
9+
ssh: SshConnection,
10+
jira: InstalledJira,
11+
hooks: PostInstallHooks
12+
) {
13+
ssh.execute("echo jira.autoexport=false > ${jira.home.path}/jira-config.properties")
14+
}
15+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.atlassian.performance.tools.infrastructure.api.jira.install.hook
2+
3+
import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira
4+
import com.atlassian.performance.tools.infrastructure.api.jira.install.JiraInstallation
5+
import com.atlassian.performance.tools.infrastructure.api.jira.install.TcpServer
6+
7+
class HookedJiraInstallation(
8+
private val installation: JiraInstallation,
9+
private val hooks: PreInstallHooks
10+
) : JiraInstallation {
11+
12+
override fun install(
13+
server: TcpServer
14+
): InstalledJira {
15+
server.ssh.newConnection().use { ssh ->
16+
hooks.call(ssh, server)
17+
}
18+
val installed = installation.install(server)
19+
server.ssh.newConnection().use { ssh ->
20+
hooks.postInstall.call(ssh, installed)
21+
}
22+
return installed
23+
}
24+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package com.atlassian.performance.tools.infrastructure.api.jira.install.hook
2+
3+
import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira
4+
import com.atlassian.performance.tools.ssh.api.SshConnection
5+
6+
class JiraHomeProperty : PostInstallHook {
7+
8+
override fun call(
9+
ssh: SshConnection,
10+
jira: InstalledJira,
11+
hooks: PostInstallHooks
12+
) {
13+
val properties = "${jira.installation.path}/atlassian-jira/WEB-INF/classes/jira-application.properties"
14+
ssh.execute("echo jira.home=`realpath ${jira.home.path}` > $properties")
15+
}
16+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.atlassian.performance.tools.infrastructure.api.jira.install.hook
2+
3+
import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira
4+
import com.atlassian.performance.tools.infrastructure.api.jira.report.StaticReport
5+
import com.atlassian.performance.tools.ssh.api.SshConnection
6+
import java.nio.file.Path
7+
import java.nio.file.Paths
8+
9+
class JiraLogs : PostInstallHook {
10+
11+
override fun call(ssh: SshConnection, jira: InstalledJira, hooks: PostInstallHooks) {
12+
listOf(
13+
"${jira.home.path}/log/atlassian-jira.log",
14+
"${jira.installation.path}/logs/catalina.out"
15+
)
16+
.onEach { ensureFile(Paths.get(it), ssh) }
17+
.map { StaticReport(it) }
18+
.forEach { hooks.reports.add(it) }
19+
}
20+
21+
private fun ensureFile(
22+
path: Path,
23+
ssh: SshConnection
24+
) {
25+
ssh.execute("mkdir -p ${path.parent!!}")
26+
ssh.execute("touch $path")
27+
}
28+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.atlassian.performance.tools.infrastructure.api.jira.install.hook
2+
3+
import com.atlassian.performance.tools.infrastructure.api.jira.JiraGcLog
4+
import com.atlassian.performance.tools.infrastructure.api.jira.JiraNodeConfig
5+
import com.atlassian.performance.tools.infrastructure.api.jira.SetenvSh
6+
import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira
7+
import com.atlassian.performance.tools.infrastructure.api.jira.report.FileListing
8+
import com.atlassian.performance.tools.ssh.api.SshConnection
9+
10+
class JvmConfig(
11+
private val config: JiraNodeConfig
12+
) : PostInstallHook {
13+
14+
override fun call(
15+
ssh: SshConnection,
16+
jira: InstalledJira,
17+
hooks: PostInstallHooks
18+
) {
19+
val gcLog = JiraGcLog(jira.installation.path)
20+
SetenvSh(jira.installation.path).setup(
21+
connection = ssh,
22+
config = config,
23+
gcLog = gcLog,
24+
jiraIp = jira.server.ip
25+
)
26+
val report = FileListing(gcLog.path("*"))
27+
hooks.reports.add(report)
28+
}
29+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.atlassian.performance.tools.infrastructure.api.jira.install.hook
2+
3+
import com.atlassian.performance.tools.infrastructure.Iostat
4+
import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira
5+
import com.atlassian.performance.tools.infrastructure.api.jira.start.StartedJira
6+
import com.atlassian.performance.tools.infrastructure.api.jira.start.hook.PostStartHook
7+
import com.atlassian.performance.tools.infrastructure.api.jira.start.hook.PostStartHooks
8+
import com.atlassian.performance.tools.infrastructure.api.os.OsMetric
9+
import com.atlassian.performance.tools.infrastructure.api.os.Ubuntu
10+
import com.atlassian.performance.tools.infrastructure.api.os.Vmstat
11+
import com.atlassian.performance.tools.infrastructure.jira.report.RemoteMonitoringProcessReport
12+
import com.atlassian.performance.tools.ssh.api.SshConnection
13+
14+
class LateUbuntuSysstat : PostInstallHook {
15+
override fun call(
16+
ssh: SshConnection,
17+
jira: InstalledJira,
18+
hooks: PostInstallHooks
19+
) {
20+
val ubuntu = Ubuntu()
21+
ubuntu.install(ssh, listOf("sysstat"))
22+
listOf(Vmstat(), Iostat())
23+
.map { PostStartOsMetric(it) }
24+
.forEach { hooks.postStart.insert(it) }
25+
}
26+
}
27+
28+
private class PostStartOsMetric(
29+
private val metric: OsMetric
30+
) : PostStartHook {
31+
override fun call(ssh: SshConnection, jira: StartedJira, hooks: PostStartHooks) {
32+
val process = metric.start(ssh)
33+
hooks.reports.add(RemoteMonitoringProcessReport(process))
34+
}
35+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.atlassian.performance.tools.infrastructure.api.jira.install.hook
2+
3+
import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira
4+
import com.atlassian.performance.tools.ssh.api.SshConnection
5+
6+
/**
7+
* Intercepts a call after Jira is installed.
8+
*/
9+
interface PostInstallHook {
10+
11+
/**
12+
* @param [ssh] connects to the [jira]
13+
* @param [jira] points to the installed Jira
14+
* @param [hooks] inserts future hooks and reports
15+
*/
16+
fun call(
17+
ssh: SshConnection,
18+
jira: InstalledJira,
19+
hooks: PostInstallHooks
20+
)
21+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.atlassian.performance.tools.infrastructure.api.jira.install.hook
2+
3+
import com.atlassian.performance.tools.infrastructure.api.jira.JiraNodeConfig
4+
import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira
5+
import com.atlassian.performance.tools.infrastructure.api.jira.start.hook.PreStartHooks
6+
import com.atlassian.performance.tools.infrastructure.jira.install.hook.ProfilerHook
7+
import com.atlassian.performance.tools.infrastructure.jira.install.hook.SplunkForwarderHook
8+
import com.atlassian.performance.tools.ssh.api.SshConnection
9+
import java.util.*
10+
import java.util.concurrent.ConcurrentLinkedQueue
11+
12+
class PostInstallHooks private constructor(
13+
val preStart: PreStartHooks
14+
) {
15+
16+
private val hooks: Queue<PostInstallHook> = ConcurrentLinkedQueue()
17+
val postStart = preStart.postStart
18+
val reports = postStart.reports
19+
20+
fun insert(
21+
hook: PostInstallHook
22+
) {
23+
hooks.add(hook)
24+
}
25+
26+
internal fun call(
27+
ssh: SshConnection,
28+
jira: InstalledJira
29+
) {
30+
while (true) {
31+
hooks
32+
.poll()
33+
?.call(ssh, jira, this)
34+
?: break
35+
}
36+
}
37+
38+
companion object Factory {
39+
fun default(): PostInstallHooks = PostInstallHooks(PreStartHooks.default()).apply {
40+
val config = JiraNodeConfig.Builder().build()
41+
listOf(
42+
JiraHomeProperty(),
43+
DisabledAutoBackup(),
44+
JvmConfig(config),
45+
ProfilerHook(config.profiler),
46+
SplunkForwarderHook(config.splunkForwarder),
47+
JiraLogs(),
48+
LateUbuntuSysstat()
49+
).forEach { insert(it) }
50+
}
51+
52+
fun empty(): PostInstallHooks = PostInstallHooks(PreStartHooks.empty())
53+
}
54+
}

0 commit comments

Comments
 (0)