diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 33e97718..c34c1c2d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,6 +15,8 @@ jobs: build: runs-on: ubuntu-latest steps: + - name: Check Docker + run: docker info - name: Checkout uses: actions/checkout@v2 with: @@ -32,6 +34,12 @@ jobs: with: name: test-reports path: build/reports/tests + - name: Upload test artifacts + if: always() + uses: actions/upload-artifact@v2 + with: + name: test-artifacts + path: build/test-artifacts - name: Release if: github.event.inputs.release == 'yes' env: diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cb48b3b..42eb669f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -26,6 +26,19 @@ Dropping a requirement of a major version of a dependency is a new contract. ## [Unreleased] [Unreleased]: https://github.com/atlassian/infrastructure/compare/release-4.18.0...master +### Added +Fix [JPERF-273]: +- Allow multiple ways of installing Jira via `JiraInstallation` or starting it via `JiraStart`. +- Represent the information required to use an already installed Jira via `InstalledJira` or `JiraStart` if started. +- Represent a brand-new Jira instance via `EmptyJiraHome`. +- Hook into Jira installation via `PreInstallHooks` and `PostInstallHooks`. +- Hook into Jira start via `PreStartHooks` and `PostStartHooks`. +- Let hooks insert new hooks. +- Locate and download any logs, charts, profiles and other reports via `Report` (rather than hardcoding the paths). +- Expose preset `NfsSharedHome` or `SambaSharedHome` for Data Center. + +[JPERF-273]: https://ecosystem.atlassian.net/browse/JPERF-273 + ## [4.18.0] - 2021-04-14 [4.18.0]: https://github.com/atlassian/infrastructure/compare/release-4.17.5...release-4.18.0 @@ -130,6 +143,10 @@ This is an erroneous release. Don't use new APIs from this version, switch to `4 ## [4.14.3] - 2019-11-27 [4.14.3]: https://github.com/atlassian/infrastructure/compare/release-4.14.2...release-4.14.3 +### Added +- Offer `Docker` and `DockerImage` for customization and injection. Unblock Docker to work with other Ubuntu versions. +- Add `DockerMysqlServer`. + ### Fixed - Quote VU CLI args. Fix [JPERF-569]. - Clean up after `apt-get install` flakes. Help fix [JPERF-219]. diff --git a/build.gradle.kts b/build.gradle.kts index 433230df..1d3d01b3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -32,6 +32,9 @@ configurations.all { "org.jetbrains:annotations" -> useVersion("15.0") "com.google.code.findbugs:jsr305" -> useVersion("3.0.2") "org.apache.commons:commons-compress" -> useVersion("1.9") + "commons-io:commons-io" -> useVersion("2.6") + "org.bouncycastle:bcpkix-jdk15on" -> useVersion("1.64") + "org.bouncycastle:bcprov-jdk15on" -> useVersion("1.64") } when (requested.group) { "org.jetbrains.kotlin" -> useVersion(kotlinVersion) @@ -64,16 +67,17 @@ dependencies { testCompile("com.atlassian.performance.tools:jira-software-actions:[1.0.0,2.0.0)") testCompile("org.hamcrest:hamcrest-library:1.3") testCompile("org.assertj:assertj-core:3.11.1") - testCompile("com.atlassian.performance.tools:ssh-ubuntu:0.2.0") testCompile("org.rnorth.duct-tape:duct-tape:1.0.7") testCompile("org.threeten:threeten-extra:1.5.0") + testCompile("com.github.docker-java:docker-java-core:[3.2.5, 4.0.0)") + testCompile("com.github.docker-java:docker-java-transport-zerodep:[3.2.5, 4.0.0)") } fun webdriver(): List = listOf( "selenium-support", "selenium-chrome-driver" ).map { module -> - "org.seleniumhq.selenium:$module:3.11.0" + "org.seleniumhq.selenium:$module:[3.11.0, 3.999.999]" } fun log4j( diff --git a/gradle/dependency-locks/apiDependenciesMetadata.lockfile b/gradle/dependency-locks/apiDependenciesMetadata.lockfile index 83ef09cb..f01abbfd 100644 --- a/gradle/dependency-locks/apiDependenciesMetadata.lockfile +++ b/gradle/dependency-locks/apiDependenciesMetadata.lockfile @@ -1,13 +1,15 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. +com.atlassian.data:random-data:1.4.3 com.atlassian.performance.tools:concurrency:1.1.0 com.atlassian.performance.tools:io:1.2.0 -com.atlassian.performance.tools:jira-actions:3.9.0 -com.atlassian.performance.tools:jira-software-actions:1.3.2 -com.atlassian.performance.tools:jvm-tasks:1.2.0 -com.atlassian.performance.tools:ssh:2.3.0 -com.atlassian.performance.tools:virtual-users:3.10.0 +com.atlassian.performance.tools:jira-actions:3.13.4 +com.atlassian.performance.tools:jira-software-actions:1.3.3 +com.atlassian.performance.tools:jvm-tasks:1.2.1 +com.atlassian.performance.tools:ssh:2.4.0 +com.atlassian.performance.tools:virtual-users:3.13.0 +com.atlassian.performance:selenium-js:1.0.1 com.github.stephenc.jcip:jcip-annotations:1.0-1 com.google.code.findbugs:jsr305:3.0.2 com.google.code.gson:gson:2.8.2 @@ -16,15 +18,15 @@ com.google.guava:guava:23.6-jre com.google.j2objc:j2objc-annotations:1.1 com.hierynomus:sshj:0.23.0 com.jcraft:jzlib:1.1.3 -com.squareup.okhttp3:okhttp:3.9.1 -com.squareup.okio:okio:1.13.0 +com.squareup.okhttp3:okhttp:3.11.0 +com.squareup.okio:okio:1.14.0 com.typesafe:config:1.2.1 commons-cli:commons-cli:1.4 commons-codec:commons-codec:1.10 -commons-io:commons-io:2.5 +commons-io:commons-io:2.6 commons-logging:commons-logging:1.2 io.github.bonigarcia:webdrivermanager:1.7.1 -net.bytebuddy:byte-buddy:1.7.9 +net.bytebuddy:byte-buddy:1.8.15 net.i2p.crypto:eddsa:0.2.0 org.apache.commons:commons-compress:1.9 org.apache.commons:commons-csv:1.3 @@ -37,22 +39,20 @@ org.apache.logging.log4j:log4j-api:2.10.0 org.apache.logging.log4j:log4j-core:2.10.0 org.apache.logging.log4j:log4j-jul:2.10.0 org.apache.logging.log4j:log4j-slf4j-impl:2.10.0 -org.bouncycastle:bcpkix-jdk15on:1.56 -org.bouncycastle:bcprov-jdk15on:1.56 +org.bouncycastle:bcpkix-jdk15on:1.64 +org.bouncycastle:bcprov-jdk15on:1.64 org.checkerframework:checker-compat-qual:2.0.0 org.codehaus.mojo:animal-sniffer-annotations:1.14 org.glassfish:javax.json:1.1 org.jetbrains.kotlin:kotlin-stdlib-common:1.2.70 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.70 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.2.70 -org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.70 -org.jetbrains.kotlin:kotlin-stdlib-jre8:1.2.70 org.jetbrains.kotlin:kotlin-stdlib:1.2.70 org.jetbrains:annotations:15.0 org.jsoup:jsoup:1.10.2 org.rauschig:jarchivelib:0.7.1 -org.seleniumhq.selenium:selenium-api:3.11.0 -org.seleniumhq.selenium:selenium-chrome-driver:3.11.0 -org.seleniumhq.selenium:selenium-remote-driver:3.11.0 -org.seleniumhq.selenium:selenium-support:3.11.0 +org.seleniumhq.selenium:selenium-api:3.141.59 +org.seleniumhq.selenium:selenium-chrome-driver:3.141.59 +org.seleniumhq.selenium:selenium-remote-driver:3.141.59 +org.seleniumhq.selenium:selenium-support:3.141.59 org.slf4j:slf4j-api:1.8.0-alpha2 diff --git a/gradle/dependency-locks/compileClasspath.lockfile b/gradle/dependency-locks/compileClasspath.lockfile index 5fbd64b4..ac10f0b8 100644 --- a/gradle/dependency-locks/compileClasspath.lockfile +++ b/gradle/dependency-locks/compileClasspath.lockfile @@ -3,10 +3,11 @@ # This file is expected to be part of source control. com.atlassian.performance.tools:concurrency:1.1.0 com.atlassian.performance.tools:io:1.2.0 -com.atlassian.performance.tools:jira-actions:3.9.0 -com.atlassian.performance.tools:jvm-tasks:1.2.0 -com.atlassian.performance.tools:ssh:2.3.0 -com.atlassian.performance.tools:virtual-users:3.10.0 +com.atlassian.performance.tools:jira-actions:3.13.4 +com.atlassian.performance.tools:jvm-tasks:1.2.1 +com.atlassian.performance.tools:ssh:2.4.0 +com.atlassian.performance.tools:virtual-users:3.13.0 +com.atlassian.performance:selenium-js:1.0.1 com.github.stephenc.jcip:jcip-annotations:1.0-1 com.google.code.findbugs:jsr305:3.0.2 com.google.errorprone:error_prone_annotations:2.1.3 @@ -14,11 +15,15 @@ com.google.guava:guava:23.6-jre com.google.j2objc:j2objc-annotations:1.1 com.hierynomus:sshj:0.23.0 com.jcraft:jzlib:1.1.3 +com.squareup.okhttp3:okhttp:3.11.0 +com.squareup.okio:okio:1.14.0 commons-codec:commons-codec:1.10 -commons-io:commons-io:2.5 +commons-io:commons-io:2.6 commons-logging:commons-logging:1.2 javax.inject:javax.inject:1 +net.bytebuddy:byte-buddy:1.8.15 net.i2p.crypto:eddsa:0.2.0 +org.apache.commons:commons-exec:1.3 org.apache.commons:commons-lang3:3.5 org.apache.httpcomponents:httpclient:4.5.5 org.apache.httpcomponents:httpcore:4.4.9 @@ -43,8 +48,8 @@ org.apache.maven:maven-repository-metadata:3.5.2 org.apache.maven:maven-resolver-provider:3.5.2 org.apache.maven:maven-settings-builder:3.5.2 org.apache.maven:maven-settings:3.5.2 -org.bouncycastle:bcpkix-jdk15on:1.56 -org.bouncycastle:bcprov-jdk15on:1.56 +org.bouncycastle:bcpkix-jdk15on:1.64 +org.bouncycastle:bcprov-jdk15on:1.64 org.checkerframework:checker-compat-qual:2.0.0 org.codehaus.mojo:animal-sniffer-annotations:1.14 org.codehaus.plexus:plexus-component-annotations:1.7.1 @@ -62,7 +67,9 @@ org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.2.70 org.jetbrains.kotlin:kotlin-stdlib:1.2.70 org.jetbrains:annotations:15.0 org.jsoup:jsoup:1.10.2 -org.seleniumhq.selenium:selenium-api:3.11.0 +org.seleniumhq.selenium:selenium-api:3.141.59 +org.seleniumhq.selenium:selenium-remote-driver:3.141.59 +org.seleniumhq.selenium:selenium-support:3.141.59 org.slf4j:slf4j-api:1.8.0-alpha2 org.sonatype.plexus:plexus-cipher:1.4 org.sonatype.plexus:plexus-sec-dispatcher:1.4 diff --git a/gradle/dependency-locks/default.lockfile b/gradle/dependency-locks/default.lockfile index c0f7c7ab..4674c635 100644 --- a/gradle/dependency-locks/default.lockfile +++ b/gradle/dependency-locks/default.lockfile @@ -1,13 +1,15 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. +com.atlassian.data:random-data:1.4.3 com.atlassian.performance.tools:concurrency:1.1.0 com.atlassian.performance.tools:io:1.2.0 -com.atlassian.performance.tools:jira-actions:3.9.0 -com.atlassian.performance.tools:jira-software-actions:1.3.2 -com.atlassian.performance.tools:jvm-tasks:1.2.0 -com.atlassian.performance.tools:ssh:2.3.0 -com.atlassian.performance.tools:virtual-users:3.10.0 +com.atlassian.performance.tools:jira-actions:3.13.4 +com.atlassian.performance.tools:jira-software-actions:1.3.3 +com.atlassian.performance.tools:jvm-tasks:1.2.1 +com.atlassian.performance.tools:ssh:2.4.0 +com.atlassian.performance.tools:virtual-users:3.13.0 +com.atlassian.performance:selenium-js:1.0.1 com.github.stephenc.jcip:jcip-annotations:1.0-1 com.google.code.findbugs:jsr305:3.0.2 com.google.code.gson:gson:2.8.2 @@ -16,16 +18,16 @@ com.google.guava:guava:23.6-jre com.google.j2objc:j2objc-annotations:1.1 com.hierynomus:sshj:0.23.0 com.jcraft:jzlib:1.1.3 -com.squareup.okhttp3:okhttp:3.9.1 -com.squareup.okio:okio:1.13.0 +com.squareup.okhttp3:okhttp:3.11.0 +com.squareup.okio:okio:1.14.0 com.typesafe:config:1.2.1 commons-cli:commons-cli:1.4 commons-codec:commons-codec:1.10 -commons-io:commons-io:2.5 +commons-io:commons-io:2.6 commons-logging:commons-logging:1.2 io.github.bonigarcia:webdrivermanager:1.7.1 javax.inject:javax.inject:1 -net.bytebuddy:byte-buddy:1.7.9 +net.bytebuddy:byte-buddy:1.8.15 net.i2p.crypto:eddsa:0.2.0 org.apache.commons:commons-compress:1.9 org.apache.commons:commons-csv:1.3 @@ -56,8 +58,8 @@ org.apache.maven:maven-repository-metadata:3.5.2 org.apache.maven:maven-resolver-provider:3.5.2 org.apache.maven:maven-settings-builder:3.5.2 org.apache.maven:maven-settings:3.5.2 -org.bouncycastle:bcpkix-jdk15on:1.56 -org.bouncycastle:bcprov-jdk15on:1.56 +org.bouncycastle:bcpkix-jdk15on:1.64 +org.bouncycastle:bcprov-jdk15on:1.64 org.checkerframework:checker-compat-qual:2.0.0 org.codehaus.mojo:animal-sniffer-annotations:1.14 org.codehaus.plexus:plexus-component-annotations:1.7.1 @@ -72,16 +74,14 @@ org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-spi:3.1.3 org.jetbrains.kotlin:kotlin-stdlib-common:1.2.70 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.70 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.2.70 -org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.70 -org.jetbrains.kotlin:kotlin-stdlib-jre8:1.2.70 org.jetbrains.kotlin:kotlin-stdlib:1.2.70 org.jetbrains:annotations:15.0 org.jsoup:jsoup:1.10.2 org.rauschig:jarchivelib:0.7.1 -org.seleniumhq.selenium:selenium-api:3.11.0 -org.seleniumhq.selenium:selenium-chrome-driver:3.11.0 -org.seleniumhq.selenium:selenium-remote-driver:3.11.0 -org.seleniumhq.selenium:selenium-support:3.11.0 +org.seleniumhq.selenium:selenium-api:3.141.59 +org.seleniumhq.selenium:selenium-chrome-driver:3.141.59 +org.seleniumhq.selenium:selenium-remote-driver:3.141.59 +org.seleniumhq.selenium:selenium-support:3.141.59 org.slf4j:slf4j-api:1.8.0-alpha2 org.sonatype.plexus:plexus-cipher:1.4 org.sonatype.plexus:plexus-sec-dispatcher:1.4 diff --git a/gradle/dependency-locks/implementationDependenciesMetadata.lockfile b/gradle/dependency-locks/implementationDependenciesMetadata.lockfile index c0f7c7ab..4674c635 100644 --- a/gradle/dependency-locks/implementationDependenciesMetadata.lockfile +++ b/gradle/dependency-locks/implementationDependenciesMetadata.lockfile @@ -1,13 +1,15 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. +com.atlassian.data:random-data:1.4.3 com.atlassian.performance.tools:concurrency:1.1.0 com.atlassian.performance.tools:io:1.2.0 -com.atlassian.performance.tools:jira-actions:3.9.0 -com.atlassian.performance.tools:jira-software-actions:1.3.2 -com.atlassian.performance.tools:jvm-tasks:1.2.0 -com.atlassian.performance.tools:ssh:2.3.0 -com.atlassian.performance.tools:virtual-users:3.10.0 +com.atlassian.performance.tools:jira-actions:3.13.4 +com.atlassian.performance.tools:jira-software-actions:1.3.3 +com.atlassian.performance.tools:jvm-tasks:1.2.1 +com.atlassian.performance.tools:ssh:2.4.0 +com.atlassian.performance.tools:virtual-users:3.13.0 +com.atlassian.performance:selenium-js:1.0.1 com.github.stephenc.jcip:jcip-annotations:1.0-1 com.google.code.findbugs:jsr305:3.0.2 com.google.code.gson:gson:2.8.2 @@ -16,16 +18,16 @@ com.google.guava:guava:23.6-jre com.google.j2objc:j2objc-annotations:1.1 com.hierynomus:sshj:0.23.0 com.jcraft:jzlib:1.1.3 -com.squareup.okhttp3:okhttp:3.9.1 -com.squareup.okio:okio:1.13.0 +com.squareup.okhttp3:okhttp:3.11.0 +com.squareup.okio:okio:1.14.0 com.typesafe:config:1.2.1 commons-cli:commons-cli:1.4 commons-codec:commons-codec:1.10 -commons-io:commons-io:2.5 +commons-io:commons-io:2.6 commons-logging:commons-logging:1.2 io.github.bonigarcia:webdrivermanager:1.7.1 javax.inject:javax.inject:1 -net.bytebuddy:byte-buddy:1.7.9 +net.bytebuddy:byte-buddy:1.8.15 net.i2p.crypto:eddsa:0.2.0 org.apache.commons:commons-compress:1.9 org.apache.commons:commons-csv:1.3 @@ -56,8 +58,8 @@ org.apache.maven:maven-repository-metadata:3.5.2 org.apache.maven:maven-resolver-provider:3.5.2 org.apache.maven:maven-settings-builder:3.5.2 org.apache.maven:maven-settings:3.5.2 -org.bouncycastle:bcpkix-jdk15on:1.56 -org.bouncycastle:bcprov-jdk15on:1.56 +org.bouncycastle:bcpkix-jdk15on:1.64 +org.bouncycastle:bcprov-jdk15on:1.64 org.checkerframework:checker-compat-qual:2.0.0 org.codehaus.mojo:animal-sniffer-annotations:1.14 org.codehaus.plexus:plexus-component-annotations:1.7.1 @@ -72,16 +74,14 @@ org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-spi:3.1.3 org.jetbrains.kotlin:kotlin-stdlib-common:1.2.70 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.70 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.2.70 -org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.70 -org.jetbrains.kotlin:kotlin-stdlib-jre8:1.2.70 org.jetbrains.kotlin:kotlin-stdlib:1.2.70 org.jetbrains:annotations:15.0 org.jsoup:jsoup:1.10.2 org.rauschig:jarchivelib:0.7.1 -org.seleniumhq.selenium:selenium-api:3.11.0 -org.seleniumhq.selenium:selenium-chrome-driver:3.11.0 -org.seleniumhq.selenium:selenium-remote-driver:3.11.0 -org.seleniumhq.selenium:selenium-support:3.11.0 +org.seleniumhq.selenium:selenium-api:3.141.59 +org.seleniumhq.selenium:selenium-chrome-driver:3.141.59 +org.seleniumhq.selenium:selenium-remote-driver:3.141.59 +org.seleniumhq.selenium:selenium-support:3.141.59 org.slf4j:slf4j-api:1.8.0-alpha2 org.sonatype.plexus:plexus-cipher:1.4 org.sonatype.plexus:plexus-sec-dispatcher:1.4 diff --git a/gradle/dependency-locks/runtimeClasspath.lockfile b/gradle/dependency-locks/runtimeClasspath.lockfile index c0f7c7ab..4674c635 100644 --- a/gradle/dependency-locks/runtimeClasspath.lockfile +++ b/gradle/dependency-locks/runtimeClasspath.lockfile @@ -1,13 +1,15 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. +com.atlassian.data:random-data:1.4.3 com.atlassian.performance.tools:concurrency:1.1.0 com.atlassian.performance.tools:io:1.2.0 -com.atlassian.performance.tools:jira-actions:3.9.0 -com.atlassian.performance.tools:jira-software-actions:1.3.2 -com.atlassian.performance.tools:jvm-tasks:1.2.0 -com.atlassian.performance.tools:ssh:2.3.0 -com.atlassian.performance.tools:virtual-users:3.10.0 +com.atlassian.performance.tools:jira-actions:3.13.4 +com.atlassian.performance.tools:jira-software-actions:1.3.3 +com.atlassian.performance.tools:jvm-tasks:1.2.1 +com.atlassian.performance.tools:ssh:2.4.0 +com.atlassian.performance.tools:virtual-users:3.13.0 +com.atlassian.performance:selenium-js:1.0.1 com.github.stephenc.jcip:jcip-annotations:1.0-1 com.google.code.findbugs:jsr305:3.0.2 com.google.code.gson:gson:2.8.2 @@ -16,16 +18,16 @@ com.google.guava:guava:23.6-jre com.google.j2objc:j2objc-annotations:1.1 com.hierynomus:sshj:0.23.0 com.jcraft:jzlib:1.1.3 -com.squareup.okhttp3:okhttp:3.9.1 -com.squareup.okio:okio:1.13.0 +com.squareup.okhttp3:okhttp:3.11.0 +com.squareup.okio:okio:1.14.0 com.typesafe:config:1.2.1 commons-cli:commons-cli:1.4 commons-codec:commons-codec:1.10 -commons-io:commons-io:2.5 +commons-io:commons-io:2.6 commons-logging:commons-logging:1.2 io.github.bonigarcia:webdrivermanager:1.7.1 javax.inject:javax.inject:1 -net.bytebuddy:byte-buddy:1.7.9 +net.bytebuddy:byte-buddy:1.8.15 net.i2p.crypto:eddsa:0.2.0 org.apache.commons:commons-compress:1.9 org.apache.commons:commons-csv:1.3 @@ -56,8 +58,8 @@ org.apache.maven:maven-repository-metadata:3.5.2 org.apache.maven:maven-resolver-provider:3.5.2 org.apache.maven:maven-settings-builder:3.5.2 org.apache.maven:maven-settings:3.5.2 -org.bouncycastle:bcpkix-jdk15on:1.56 -org.bouncycastle:bcprov-jdk15on:1.56 +org.bouncycastle:bcpkix-jdk15on:1.64 +org.bouncycastle:bcprov-jdk15on:1.64 org.checkerframework:checker-compat-qual:2.0.0 org.codehaus.mojo:animal-sniffer-annotations:1.14 org.codehaus.plexus:plexus-component-annotations:1.7.1 @@ -72,16 +74,14 @@ org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-spi:3.1.3 org.jetbrains.kotlin:kotlin-stdlib-common:1.2.70 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.70 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.2.70 -org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.70 -org.jetbrains.kotlin:kotlin-stdlib-jre8:1.2.70 org.jetbrains.kotlin:kotlin-stdlib:1.2.70 org.jetbrains:annotations:15.0 org.jsoup:jsoup:1.10.2 org.rauschig:jarchivelib:0.7.1 -org.seleniumhq.selenium:selenium-api:3.11.0 -org.seleniumhq.selenium:selenium-chrome-driver:3.11.0 -org.seleniumhq.selenium:selenium-remote-driver:3.11.0 -org.seleniumhq.selenium:selenium-support:3.11.0 +org.seleniumhq.selenium:selenium-api:3.141.59 +org.seleniumhq.selenium:selenium-chrome-driver:3.141.59 +org.seleniumhq.selenium:selenium-remote-driver:3.141.59 +org.seleniumhq.selenium:selenium-support:3.141.59 org.slf4j:slf4j-api:1.8.0-alpha2 org.sonatype.plexus:plexus-cipher:1.4 org.sonatype.plexus:plexus-sec-dispatcher:1.4 diff --git a/gradle/dependency-locks/testApiDependenciesMetadata.lockfile b/gradle/dependency-locks/testApiDependenciesMetadata.lockfile index 7f43998c..e560bcfb 100644 --- a/gradle/dependency-locks/testApiDependenciesMetadata.lockfile +++ b/gradle/dependency-locks/testApiDependenciesMetadata.lockfile @@ -2,39 +2,39 @@ # Manual edits can break the build and are not advised. # This file is expected to be part of source control. com.atlassian.performance.tools:concurrency:1.1.0 -com.atlassian.performance.tools:jira-actions:3.9.0 -com.atlassian.performance.tools:jira-software-actions:1.3.2 -com.atlassian.performance.tools:jvm-tasks:1.2.0 -com.atlassian.performance.tools:ssh-ubuntu:0.2.0 +com.atlassian.performance.tools:jira-actions:3.13.4 +com.atlassian.performance.tools:jira-software-actions:1.3.3 +com.atlassian.performance.tools:jvm-tasks:1.2.1 +com.atlassian.performance:selenium-js:1.0.1 +com.fasterxml.jackson.core:jackson-annotations:2.10.3 +com.fasterxml.jackson.core:jackson-core:2.10.3 +com.fasterxml.jackson.core:jackson-databind:2.10.3 +com.github.docker-java:docker-java-api:3.2.7 +com.github.docker-java:docker-java-core:3.2.7 +com.github.docker-java:docker-java-transport-zerodep:3.2.7 +com.github.docker-java:docker-java-transport:3.2.7 com.github.stephenc.jcip:jcip-annotations:1.0-1 com.google.code.findbugs:jsr305:3.0.2 -com.google.code.gson:gson:2.8.2 com.google.errorprone:error_prone_annotations:2.1.3 com.google.guava:guava:23.6-jre com.google.j2objc:j2objc-annotations:1.1 -com.kohlschutter.junixsocket:junixsocket-common:2.0.4 -com.kohlschutter.junixsocket:junixsocket-native-common:2.0.4 -com.squareup.okhttp3:okhttp:3.9.1 -com.squareup.okio:okio:1.13.0 -commons-codec:commons-codec:1.10 -commons-logging:commons-logging:1.2 -javax.activation:javax.activation-api:1.2.0 -javax.annotation:javax.annotation-api:1.3.2 -javax.xml.bind:jaxb-api:2.3.1 +com.squareup.okhttp3:okhttp:3.11.0 +com.squareup.okio:okio:1.14.0 +commons-io:commons-io:2.6 +commons-lang:commons-lang:2.6 junit:junit:4.12 -net.bytebuddy:byte-buddy:1.7.9 -net.java.dev.jna:jna-platform:5.2.0 -net.java.dev.jna:jna:5.2.0 +net.bytebuddy:byte-buddy:1.8.15 +net.java.dev.jna:jna:5.5.0 org.apache.commons:commons-compress:1.9 org.apache.commons:commons-exec:1.3 org.apache.commons:commons-math3:3.6.1 -org.apache.httpcomponents:httpclient:4.5.5 -org.apache.httpcomponents:httpcore:4.4.9 org.apache.logging.log4j:log4j-api:2.10.0 org.apache.logging.log4j:log4j-core:2.10.0 org.apache.logging.log4j:log4j-jul:2.10.0 org.apache.logging.log4j:log4j-slf4j-impl:2.10.0 org.assertj:assertj-core:3.11.1 +org.bouncycastle:bcpkix-jdk15on:1.64 +org.bouncycastle:bcprov-jdk15on:1.64 org.checkerframework:checker-compat-qual:2.0.0 org.codehaus.mojo:animal-sniffer-annotations:1.14 org.glassfish:javax.json:1.1 @@ -43,18 +43,12 @@ org.hamcrest:hamcrest-library:1.3 org.jetbrains.kotlin:kotlin-stdlib-common:1.2.70 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.70 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.2.70 -org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.70 -org.jetbrains.kotlin:kotlin-stdlib-jre8:1.2.70 org.jetbrains.kotlin:kotlin-stdlib:1.2.70 org.jetbrains:annotations:15.0 org.rnorth.duct-tape:duct-tape:1.0.7 -org.rnorth.visible-assertions:visible-assertions:2.1.2 -org.rnorth:tcp-unix-socket-proxy:1.0.2 -org.scijava:native-lib-loader:2.0.2 -org.seleniumhq.selenium:selenium-api:3.11.0 -org.seleniumhq.selenium:selenium-chrome-driver:3.11.0 -org.seleniumhq.selenium:selenium-remote-driver:3.11.0 -org.seleniumhq.selenium:selenium-support:3.11.0 +org.seleniumhq.selenium:selenium-api:3.141.59 +org.seleniumhq.selenium:selenium-chrome-driver:3.141.59 +org.seleniumhq.selenium:selenium-remote-driver:3.141.59 +org.seleniumhq.selenium:selenium-support:3.141.59 org.slf4j:slf4j-api:1.8.0-alpha2 -org.testcontainers:testcontainers:1.10.5 org.threeten:threeten-extra:1.5.0 diff --git a/gradle/dependency-locks/testCompile.lockfile b/gradle/dependency-locks/testCompile.lockfile index 7f43998c..e560bcfb 100644 --- a/gradle/dependency-locks/testCompile.lockfile +++ b/gradle/dependency-locks/testCompile.lockfile @@ -2,39 +2,39 @@ # Manual edits can break the build and are not advised. # This file is expected to be part of source control. com.atlassian.performance.tools:concurrency:1.1.0 -com.atlassian.performance.tools:jira-actions:3.9.0 -com.atlassian.performance.tools:jira-software-actions:1.3.2 -com.atlassian.performance.tools:jvm-tasks:1.2.0 -com.atlassian.performance.tools:ssh-ubuntu:0.2.0 +com.atlassian.performance.tools:jira-actions:3.13.4 +com.atlassian.performance.tools:jira-software-actions:1.3.3 +com.atlassian.performance.tools:jvm-tasks:1.2.1 +com.atlassian.performance:selenium-js:1.0.1 +com.fasterxml.jackson.core:jackson-annotations:2.10.3 +com.fasterxml.jackson.core:jackson-core:2.10.3 +com.fasterxml.jackson.core:jackson-databind:2.10.3 +com.github.docker-java:docker-java-api:3.2.7 +com.github.docker-java:docker-java-core:3.2.7 +com.github.docker-java:docker-java-transport-zerodep:3.2.7 +com.github.docker-java:docker-java-transport:3.2.7 com.github.stephenc.jcip:jcip-annotations:1.0-1 com.google.code.findbugs:jsr305:3.0.2 -com.google.code.gson:gson:2.8.2 com.google.errorprone:error_prone_annotations:2.1.3 com.google.guava:guava:23.6-jre com.google.j2objc:j2objc-annotations:1.1 -com.kohlschutter.junixsocket:junixsocket-common:2.0.4 -com.kohlschutter.junixsocket:junixsocket-native-common:2.0.4 -com.squareup.okhttp3:okhttp:3.9.1 -com.squareup.okio:okio:1.13.0 -commons-codec:commons-codec:1.10 -commons-logging:commons-logging:1.2 -javax.activation:javax.activation-api:1.2.0 -javax.annotation:javax.annotation-api:1.3.2 -javax.xml.bind:jaxb-api:2.3.1 +com.squareup.okhttp3:okhttp:3.11.0 +com.squareup.okio:okio:1.14.0 +commons-io:commons-io:2.6 +commons-lang:commons-lang:2.6 junit:junit:4.12 -net.bytebuddy:byte-buddy:1.7.9 -net.java.dev.jna:jna-platform:5.2.0 -net.java.dev.jna:jna:5.2.0 +net.bytebuddy:byte-buddy:1.8.15 +net.java.dev.jna:jna:5.5.0 org.apache.commons:commons-compress:1.9 org.apache.commons:commons-exec:1.3 org.apache.commons:commons-math3:3.6.1 -org.apache.httpcomponents:httpclient:4.5.5 -org.apache.httpcomponents:httpcore:4.4.9 org.apache.logging.log4j:log4j-api:2.10.0 org.apache.logging.log4j:log4j-core:2.10.0 org.apache.logging.log4j:log4j-jul:2.10.0 org.apache.logging.log4j:log4j-slf4j-impl:2.10.0 org.assertj:assertj-core:3.11.1 +org.bouncycastle:bcpkix-jdk15on:1.64 +org.bouncycastle:bcprov-jdk15on:1.64 org.checkerframework:checker-compat-qual:2.0.0 org.codehaus.mojo:animal-sniffer-annotations:1.14 org.glassfish:javax.json:1.1 @@ -43,18 +43,12 @@ org.hamcrest:hamcrest-library:1.3 org.jetbrains.kotlin:kotlin-stdlib-common:1.2.70 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.70 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.2.70 -org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.70 -org.jetbrains.kotlin:kotlin-stdlib-jre8:1.2.70 org.jetbrains.kotlin:kotlin-stdlib:1.2.70 org.jetbrains:annotations:15.0 org.rnorth.duct-tape:duct-tape:1.0.7 -org.rnorth.visible-assertions:visible-assertions:2.1.2 -org.rnorth:tcp-unix-socket-proxy:1.0.2 -org.scijava:native-lib-loader:2.0.2 -org.seleniumhq.selenium:selenium-api:3.11.0 -org.seleniumhq.selenium:selenium-chrome-driver:3.11.0 -org.seleniumhq.selenium:selenium-remote-driver:3.11.0 -org.seleniumhq.selenium:selenium-support:3.11.0 +org.seleniumhq.selenium:selenium-api:3.141.59 +org.seleniumhq.selenium:selenium-chrome-driver:3.141.59 +org.seleniumhq.selenium:selenium-remote-driver:3.141.59 +org.seleniumhq.selenium:selenium-support:3.141.59 org.slf4j:slf4j-api:1.8.0-alpha2 -org.testcontainers:testcontainers:1.10.5 org.threeten:threeten-extra:1.5.0 diff --git a/gradle/dependency-locks/testCompileClasspath.lockfile b/gradle/dependency-locks/testCompileClasspath.lockfile index 68e1a517..6cf5d0a9 100644 --- a/gradle/dependency-locks/testCompileClasspath.lockfile +++ b/gradle/dependency-locks/testCompileClasspath.lockfile @@ -3,36 +3,37 @@ # This file is expected to be part of source control. com.atlassian.performance.tools:concurrency:1.1.0 com.atlassian.performance.tools:io:1.2.0 -com.atlassian.performance.tools:jira-actions:3.9.0 -com.atlassian.performance.tools:jira-software-actions:1.3.2 -com.atlassian.performance.tools:jvm-tasks:1.2.0 -com.atlassian.performance.tools:ssh-ubuntu:0.2.0 -com.atlassian.performance.tools:ssh:2.3.0 -com.atlassian.performance.tools:virtual-users:3.10.0 +com.atlassian.performance.tools:jira-actions:3.13.4 +com.atlassian.performance.tools:jira-software-actions:1.3.3 +com.atlassian.performance.tools:jvm-tasks:1.2.1 +com.atlassian.performance.tools:ssh:2.4.0 +com.atlassian.performance.tools:virtual-users:3.13.0 +com.atlassian.performance:selenium-js:1.0.1 +com.fasterxml.jackson.core:jackson-annotations:2.10.3 +com.fasterxml.jackson.core:jackson-core:2.10.3 +com.fasterxml.jackson.core:jackson-databind:2.10.3 +com.github.docker-java:docker-java-api:3.2.7 +com.github.docker-java:docker-java-core:3.2.7 +com.github.docker-java:docker-java-transport-zerodep:3.2.7 +com.github.docker-java:docker-java-transport:3.2.7 com.github.stephenc.jcip:jcip-annotations:1.0-1 com.google.code.findbugs:jsr305:3.0.2 -com.google.code.gson:gson:2.8.2 com.google.errorprone:error_prone_annotations:2.1.3 com.google.guava:guava:23.6-jre com.google.j2objc:j2objc-annotations:1.1 com.hierynomus:sshj:0.23.0 com.jcraft:jzlib:1.1.3 -com.kohlschutter.junixsocket:junixsocket-common:2.0.4 -com.kohlschutter.junixsocket:junixsocket-native-common:2.0.4 -com.squareup.okhttp3:okhttp:3.9.1 -com.squareup.okio:okio:1.13.0 +com.squareup.okhttp3:okhttp:3.11.0 +com.squareup.okio:okio:1.14.0 commons-codec:commons-codec:1.10 -commons-io:commons-io:2.5 +commons-io:commons-io:2.6 +commons-lang:commons-lang:2.6 commons-logging:commons-logging:1.2 -javax.activation:javax.activation-api:1.2.0 -javax.annotation:javax.annotation-api:1.3.2 javax.inject:javax.inject:1 -javax.xml.bind:jaxb-api:2.3.1 junit:junit:4.12 -net.bytebuddy:byte-buddy:1.7.9 +net.bytebuddy:byte-buddy:1.8.15 net.i2p.crypto:eddsa:0.2.0 -net.java.dev.jna:jna-platform:5.2.0 -net.java.dev.jna:jna:5.2.0 +net.java.dev.jna:jna:5.5.0 org.apache.commons:commons-compress:1.9 org.apache.commons:commons-exec:1.3 org.apache.commons:commons-lang3:3.5 @@ -60,8 +61,8 @@ org.apache.maven:maven-resolver-provider:3.5.2 org.apache.maven:maven-settings-builder:3.5.2 org.apache.maven:maven-settings:3.5.2 org.assertj:assertj-core:3.11.1 -org.bouncycastle:bcpkix-jdk15on:1.56 -org.bouncycastle:bcprov-jdk15on:1.56 +org.bouncycastle:bcpkix-jdk15on:1.64 +org.bouncycastle:bcprov-jdk15on:1.64 org.checkerframework:checker-compat-qual:2.0.0 org.codehaus.mojo:animal-sniffer-annotations:1.14 org.codehaus.plexus:plexus-component-annotations:1.7.1 @@ -82,15 +83,11 @@ org.jetbrains.kotlin:kotlin-stdlib:1.2.70 org.jetbrains:annotations:15.0 org.jsoup:jsoup:1.10.2 org.rnorth.duct-tape:duct-tape:1.0.7 -org.rnorth.visible-assertions:visible-assertions:2.1.2 -org.rnorth:tcp-unix-socket-proxy:1.0.2 -org.scijava:native-lib-loader:2.0.2 -org.seleniumhq.selenium:selenium-api:3.11.0 -org.seleniumhq.selenium:selenium-chrome-driver:3.11.0 -org.seleniumhq.selenium:selenium-remote-driver:3.11.0 -org.seleniumhq.selenium:selenium-support:3.11.0 +org.seleniumhq.selenium:selenium-api:3.141.59 +org.seleniumhq.selenium:selenium-chrome-driver:3.141.59 +org.seleniumhq.selenium:selenium-remote-driver:3.141.59 +org.seleniumhq.selenium:selenium-support:3.141.59 org.slf4j:slf4j-api:1.8.0-alpha2 org.sonatype.plexus:plexus-cipher:1.4 org.sonatype.plexus:plexus-sec-dispatcher:1.4 -org.testcontainers:testcontainers:1.10.5 org.threeten:threeten-extra:1.5.0 diff --git a/gradle/dependency-locks/testImplementationDependenciesMetadata.lockfile b/gradle/dependency-locks/testImplementationDependenciesMetadata.lockfile index 9a7bea8f..f6359438 100644 --- a/gradle/dependency-locks/testImplementationDependenciesMetadata.lockfile +++ b/gradle/dependency-locks/testImplementationDependenciesMetadata.lockfile @@ -1,14 +1,22 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. +com.atlassian.data:random-data:1.4.3 com.atlassian.performance.tools:concurrency:1.1.0 com.atlassian.performance.tools:io:1.2.0 -com.atlassian.performance.tools:jira-actions:3.9.0 -com.atlassian.performance.tools:jira-software-actions:1.3.2 -com.atlassian.performance.tools:jvm-tasks:1.2.0 -com.atlassian.performance.tools:ssh-ubuntu:0.2.0 -com.atlassian.performance.tools:ssh:2.3.0 -com.atlassian.performance.tools:virtual-users:3.10.0 +com.atlassian.performance.tools:jira-actions:3.13.4 +com.atlassian.performance.tools:jira-software-actions:1.3.3 +com.atlassian.performance.tools:jvm-tasks:1.2.1 +com.atlassian.performance.tools:ssh:2.4.0 +com.atlassian.performance.tools:virtual-users:3.13.0 +com.atlassian.performance:selenium-js:1.0.1 +com.fasterxml.jackson.core:jackson-annotations:2.10.3 +com.fasterxml.jackson.core:jackson-core:2.10.3 +com.fasterxml.jackson.core:jackson-databind:2.10.3 +com.github.docker-java:docker-java-api:3.2.7 +com.github.docker-java:docker-java-core:3.2.7 +com.github.docker-java:docker-java-transport-zerodep:3.2.7 +com.github.docker-java:docker-java-transport:3.2.7 com.github.stephenc.jcip:jcip-annotations:1.0-1 com.google.code.findbugs:jsr305:3.0.2 com.google.code.gson:gson:2.8.2 @@ -17,25 +25,20 @@ com.google.guava:guava:23.6-jre com.google.j2objc:j2objc-annotations:1.1 com.hierynomus:sshj:0.23.0 com.jcraft:jzlib:1.1.3 -com.kohlschutter.junixsocket:junixsocket-common:2.0.4 -com.kohlschutter.junixsocket:junixsocket-native-common:2.0.4 -com.squareup.okhttp3:okhttp:3.9.1 -com.squareup.okio:okio:1.13.0 +com.squareup.okhttp3:okhttp:3.11.0 +com.squareup.okio:okio:1.14.0 com.typesafe:config:1.2.1 commons-cli:commons-cli:1.4 commons-codec:commons-codec:1.10 -commons-io:commons-io:2.5 +commons-io:commons-io:2.6 +commons-lang:commons-lang:2.6 commons-logging:commons-logging:1.2 io.github.bonigarcia:webdrivermanager:1.7.1 -javax.activation:javax.activation-api:1.2.0 -javax.annotation:javax.annotation-api:1.3.2 javax.inject:javax.inject:1 -javax.xml.bind:jaxb-api:2.3.1 junit:junit:4.12 -net.bytebuddy:byte-buddy:1.7.9 +net.bytebuddy:byte-buddy:1.8.15 net.i2p.crypto:eddsa:0.2.0 -net.java.dev.jna:jna-platform:5.2.0 -net.java.dev.jna:jna:5.2.0 +net.java.dev.jna:jna:5.5.0 org.apache.commons:commons-compress:1.9 org.apache.commons:commons-csv:1.3 org.apache.commons:commons-exec:1.3 @@ -66,8 +69,8 @@ org.apache.maven:maven-resolver-provider:3.5.2 org.apache.maven:maven-settings-builder:3.5.2 org.apache.maven:maven-settings:3.5.2 org.assertj:assertj-core:3.11.1 -org.bouncycastle:bcpkix-jdk15on:1.56 -org.bouncycastle:bcprov-jdk15on:1.56 +org.bouncycastle:bcpkix-jdk15on:1.64 +org.bouncycastle:bcprov-jdk15on:1.64 org.checkerframework:checker-compat-qual:2.0.0 org.codehaus.mojo:animal-sniffer-annotations:1.14 org.codehaus.plexus:plexus-component-annotations:1.7.1 @@ -84,22 +87,16 @@ org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-spi:3.1.3 org.jetbrains.kotlin:kotlin-stdlib-common:1.2.70 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.70 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.2.70 -org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.70 -org.jetbrains.kotlin:kotlin-stdlib-jre8:1.2.70 org.jetbrains.kotlin:kotlin-stdlib:1.2.70 org.jetbrains:annotations:15.0 org.jsoup:jsoup:1.10.2 org.rauschig:jarchivelib:0.7.1 org.rnorth.duct-tape:duct-tape:1.0.7 -org.rnorth.visible-assertions:visible-assertions:2.1.2 -org.rnorth:tcp-unix-socket-proxy:1.0.2 -org.scijava:native-lib-loader:2.0.2 -org.seleniumhq.selenium:selenium-api:3.11.0 -org.seleniumhq.selenium:selenium-chrome-driver:3.11.0 -org.seleniumhq.selenium:selenium-remote-driver:3.11.0 -org.seleniumhq.selenium:selenium-support:3.11.0 +org.seleniumhq.selenium:selenium-api:3.141.59 +org.seleniumhq.selenium:selenium-chrome-driver:3.141.59 +org.seleniumhq.selenium:selenium-remote-driver:3.141.59 +org.seleniumhq.selenium:selenium-support:3.141.59 org.slf4j:slf4j-api:1.8.0-alpha2 org.sonatype.plexus:plexus-cipher:1.4 org.sonatype.plexus:plexus-sec-dispatcher:1.4 -org.testcontainers:testcontainers:1.10.5 org.threeten:threeten-extra:1.5.0 diff --git a/gradle/dependency-locks/testRuntime.lockfile b/gradle/dependency-locks/testRuntime.lockfile index 7f43998c..e560bcfb 100644 --- a/gradle/dependency-locks/testRuntime.lockfile +++ b/gradle/dependency-locks/testRuntime.lockfile @@ -2,39 +2,39 @@ # Manual edits can break the build and are not advised. # This file is expected to be part of source control. com.atlassian.performance.tools:concurrency:1.1.0 -com.atlassian.performance.tools:jira-actions:3.9.0 -com.atlassian.performance.tools:jira-software-actions:1.3.2 -com.atlassian.performance.tools:jvm-tasks:1.2.0 -com.atlassian.performance.tools:ssh-ubuntu:0.2.0 +com.atlassian.performance.tools:jira-actions:3.13.4 +com.atlassian.performance.tools:jira-software-actions:1.3.3 +com.atlassian.performance.tools:jvm-tasks:1.2.1 +com.atlassian.performance:selenium-js:1.0.1 +com.fasterxml.jackson.core:jackson-annotations:2.10.3 +com.fasterxml.jackson.core:jackson-core:2.10.3 +com.fasterxml.jackson.core:jackson-databind:2.10.3 +com.github.docker-java:docker-java-api:3.2.7 +com.github.docker-java:docker-java-core:3.2.7 +com.github.docker-java:docker-java-transport-zerodep:3.2.7 +com.github.docker-java:docker-java-transport:3.2.7 com.github.stephenc.jcip:jcip-annotations:1.0-1 com.google.code.findbugs:jsr305:3.0.2 -com.google.code.gson:gson:2.8.2 com.google.errorprone:error_prone_annotations:2.1.3 com.google.guava:guava:23.6-jre com.google.j2objc:j2objc-annotations:1.1 -com.kohlschutter.junixsocket:junixsocket-common:2.0.4 -com.kohlschutter.junixsocket:junixsocket-native-common:2.0.4 -com.squareup.okhttp3:okhttp:3.9.1 -com.squareup.okio:okio:1.13.0 -commons-codec:commons-codec:1.10 -commons-logging:commons-logging:1.2 -javax.activation:javax.activation-api:1.2.0 -javax.annotation:javax.annotation-api:1.3.2 -javax.xml.bind:jaxb-api:2.3.1 +com.squareup.okhttp3:okhttp:3.11.0 +com.squareup.okio:okio:1.14.0 +commons-io:commons-io:2.6 +commons-lang:commons-lang:2.6 junit:junit:4.12 -net.bytebuddy:byte-buddy:1.7.9 -net.java.dev.jna:jna-platform:5.2.0 -net.java.dev.jna:jna:5.2.0 +net.bytebuddy:byte-buddy:1.8.15 +net.java.dev.jna:jna:5.5.0 org.apache.commons:commons-compress:1.9 org.apache.commons:commons-exec:1.3 org.apache.commons:commons-math3:3.6.1 -org.apache.httpcomponents:httpclient:4.5.5 -org.apache.httpcomponents:httpcore:4.4.9 org.apache.logging.log4j:log4j-api:2.10.0 org.apache.logging.log4j:log4j-core:2.10.0 org.apache.logging.log4j:log4j-jul:2.10.0 org.apache.logging.log4j:log4j-slf4j-impl:2.10.0 org.assertj:assertj-core:3.11.1 +org.bouncycastle:bcpkix-jdk15on:1.64 +org.bouncycastle:bcprov-jdk15on:1.64 org.checkerframework:checker-compat-qual:2.0.0 org.codehaus.mojo:animal-sniffer-annotations:1.14 org.glassfish:javax.json:1.1 @@ -43,18 +43,12 @@ org.hamcrest:hamcrest-library:1.3 org.jetbrains.kotlin:kotlin-stdlib-common:1.2.70 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.70 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.2.70 -org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.70 -org.jetbrains.kotlin:kotlin-stdlib-jre8:1.2.70 org.jetbrains.kotlin:kotlin-stdlib:1.2.70 org.jetbrains:annotations:15.0 org.rnorth.duct-tape:duct-tape:1.0.7 -org.rnorth.visible-assertions:visible-assertions:2.1.2 -org.rnorth:tcp-unix-socket-proxy:1.0.2 -org.scijava:native-lib-loader:2.0.2 -org.seleniumhq.selenium:selenium-api:3.11.0 -org.seleniumhq.selenium:selenium-chrome-driver:3.11.0 -org.seleniumhq.selenium:selenium-remote-driver:3.11.0 -org.seleniumhq.selenium:selenium-support:3.11.0 +org.seleniumhq.selenium:selenium-api:3.141.59 +org.seleniumhq.selenium:selenium-chrome-driver:3.141.59 +org.seleniumhq.selenium:selenium-remote-driver:3.141.59 +org.seleniumhq.selenium:selenium-support:3.141.59 org.slf4j:slf4j-api:1.8.0-alpha2 -org.testcontainers:testcontainers:1.10.5 org.threeten:threeten-extra:1.5.0 diff --git a/gradle/dependency-locks/testRuntimeClasspath.lockfile b/gradle/dependency-locks/testRuntimeClasspath.lockfile index 9a7bea8f..f6359438 100644 --- a/gradle/dependency-locks/testRuntimeClasspath.lockfile +++ b/gradle/dependency-locks/testRuntimeClasspath.lockfile @@ -1,14 +1,22 @@ # This is a Gradle generated file for dependency locking. # Manual edits can break the build and are not advised. # This file is expected to be part of source control. +com.atlassian.data:random-data:1.4.3 com.atlassian.performance.tools:concurrency:1.1.0 com.atlassian.performance.tools:io:1.2.0 -com.atlassian.performance.tools:jira-actions:3.9.0 -com.atlassian.performance.tools:jira-software-actions:1.3.2 -com.atlassian.performance.tools:jvm-tasks:1.2.0 -com.atlassian.performance.tools:ssh-ubuntu:0.2.0 -com.atlassian.performance.tools:ssh:2.3.0 -com.atlassian.performance.tools:virtual-users:3.10.0 +com.atlassian.performance.tools:jira-actions:3.13.4 +com.atlassian.performance.tools:jira-software-actions:1.3.3 +com.atlassian.performance.tools:jvm-tasks:1.2.1 +com.atlassian.performance.tools:ssh:2.4.0 +com.atlassian.performance.tools:virtual-users:3.13.0 +com.atlassian.performance:selenium-js:1.0.1 +com.fasterxml.jackson.core:jackson-annotations:2.10.3 +com.fasterxml.jackson.core:jackson-core:2.10.3 +com.fasterxml.jackson.core:jackson-databind:2.10.3 +com.github.docker-java:docker-java-api:3.2.7 +com.github.docker-java:docker-java-core:3.2.7 +com.github.docker-java:docker-java-transport-zerodep:3.2.7 +com.github.docker-java:docker-java-transport:3.2.7 com.github.stephenc.jcip:jcip-annotations:1.0-1 com.google.code.findbugs:jsr305:3.0.2 com.google.code.gson:gson:2.8.2 @@ -17,25 +25,20 @@ com.google.guava:guava:23.6-jre com.google.j2objc:j2objc-annotations:1.1 com.hierynomus:sshj:0.23.0 com.jcraft:jzlib:1.1.3 -com.kohlschutter.junixsocket:junixsocket-common:2.0.4 -com.kohlschutter.junixsocket:junixsocket-native-common:2.0.4 -com.squareup.okhttp3:okhttp:3.9.1 -com.squareup.okio:okio:1.13.0 +com.squareup.okhttp3:okhttp:3.11.0 +com.squareup.okio:okio:1.14.0 com.typesafe:config:1.2.1 commons-cli:commons-cli:1.4 commons-codec:commons-codec:1.10 -commons-io:commons-io:2.5 +commons-io:commons-io:2.6 +commons-lang:commons-lang:2.6 commons-logging:commons-logging:1.2 io.github.bonigarcia:webdrivermanager:1.7.1 -javax.activation:javax.activation-api:1.2.0 -javax.annotation:javax.annotation-api:1.3.2 javax.inject:javax.inject:1 -javax.xml.bind:jaxb-api:2.3.1 junit:junit:4.12 -net.bytebuddy:byte-buddy:1.7.9 +net.bytebuddy:byte-buddy:1.8.15 net.i2p.crypto:eddsa:0.2.0 -net.java.dev.jna:jna-platform:5.2.0 -net.java.dev.jna:jna:5.2.0 +net.java.dev.jna:jna:5.5.0 org.apache.commons:commons-compress:1.9 org.apache.commons:commons-csv:1.3 org.apache.commons:commons-exec:1.3 @@ -66,8 +69,8 @@ org.apache.maven:maven-resolver-provider:3.5.2 org.apache.maven:maven-settings-builder:3.5.2 org.apache.maven:maven-settings:3.5.2 org.assertj:assertj-core:3.11.1 -org.bouncycastle:bcpkix-jdk15on:1.56 -org.bouncycastle:bcprov-jdk15on:1.56 +org.bouncycastle:bcpkix-jdk15on:1.64 +org.bouncycastle:bcprov-jdk15on:1.64 org.checkerframework:checker-compat-qual:2.0.0 org.codehaus.mojo:animal-sniffer-annotations:1.14 org.codehaus.plexus:plexus-component-annotations:1.7.1 @@ -84,22 +87,16 @@ org.jboss.shrinkwrap.resolver:shrinkwrap-resolver-spi:3.1.3 org.jetbrains.kotlin:kotlin-stdlib-common:1.2.70 org.jetbrains.kotlin:kotlin-stdlib-jdk7:1.2.70 org.jetbrains.kotlin:kotlin-stdlib-jdk8:1.2.70 -org.jetbrains.kotlin:kotlin-stdlib-jre7:1.2.70 -org.jetbrains.kotlin:kotlin-stdlib-jre8:1.2.70 org.jetbrains.kotlin:kotlin-stdlib:1.2.70 org.jetbrains:annotations:15.0 org.jsoup:jsoup:1.10.2 org.rauschig:jarchivelib:0.7.1 org.rnorth.duct-tape:duct-tape:1.0.7 -org.rnorth.visible-assertions:visible-assertions:2.1.2 -org.rnorth:tcp-unix-socket-proxy:1.0.2 -org.scijava:native-lib-loader:2.0.2 -org.seleniumhq.selenium:selenium-api:3.11.0 -org.seleniumhq.selenium:selenium-chrome-driver:3.11.0 -org.seleniumhq.selenium:selenium-remote-driver:3.11.0 -org.seleniumhq.selenium:selenium-support:3.11.0 +org.seleniumhq.selenium:selenium-api:3.141.59 +org.seleniumhq.selenium:selenium-chrome-driver:3.141.59 +org.seleniumhq.selenium:selenium-remote-driver:3.141.59 +org.seleniumhq.selenium:selenium-support:3.141.59 org.slf4j:slf4j-api:1.8.0-alpha2 org.sonatype.plexus:plexus-cipher:1.4 org.sonatype.plexus:plexus-sec-dispatcher:1.4 -org.testcontainers:testcontainers:1.10.5 org.threeten:threeten-extra:1.5.0 diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/Docker.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/Docker.kt deleted file mode 100644 index f4ebf8fc..00000000 --- a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/Docker.kt +++ /dev/null @@ -1,39 +0,0 @@ -package com.atlassian.performance.tools.infrastructure - -import com.atlassian.performance.tools.infrastructure.api.os.Ubuntu -import com.atlassian.performance.tools.ssh.api.SshConnection -import java.time.Duration - -internal class Docker { - - private val ubuntu = Ubuntu() - - /** - * See the [official guide](https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/#install-docker-ce). - */ - fun install( - ssh: SshConnection - ) { - ubuntu.install( - ssh = ssh, - packages = listOf( - "apt-transport-https", - "ca-certificates", - "curl" - ), - timeout = Duration.ofMinutes(2) - ) - ubuntu.addKey(ssh, "7EA0A9C3F273FCD8") - - val release = ubuntu.getDistributionCodename(ssh) - ubuntu.addRepository(ssh, "deb [arch=amd64] https://download.docker.com/linux/ubuntu $release stable", "docker"); - - val version = "5:19.03.13~3-0~ubuntu-$release" - ubuntu.install( - ssh = ssh, - packages = listOf("docker-ce=$version"), - timeout = Duration.ofMinutes(5) - ) - ssh.execute("sudo service docker status || sudo service docker start") - } -} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/DockerImage.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/DockerImage.kt deleted file mode 100644 index 3a77ecf3..00000000 --- a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/DockerImage.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.atlassian.performance.tools.infrastructure - -import com.atlassian.performance.tools.ssh.api.SshConnection -import org.apache.logging.log4j.Level -import java.time.Duration -import java.util.* - -internal class DockerImage( - private val name: String, - private val pullTimeout: Duration = Duration.ofMinutes(1) -) { - - private val docker = Docker() - - fun run( - ssh: SshConnection, - parameters: String = "", - arguments: String = "" - ): String { - docker.install(ssh) - val containerName = "jpt-" + UUID.randomUUID() - ssh.execute( - cmd = "sudo docker pull $name", - timeout = pullTimeout, - stdout = Level.TRACE, - stderr = Level.WARN - ) - ssh.execute("sudo docker run -d $parameters --name $containerName $name $arguments") - return containerName - } -} \ 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..da00606b --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/database/DatabaseIpConfig.kt @@ -0,0 +1,27 @@ +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.InstalledJira +import com.atlassian.performance.tools.infrastructure.api.jira.install.hook.PostInstallHook +import com.atlassian.performance.tools.infrastructure.api.jira.install.hook.PostInstallHooks +import com.atlassian.performance.tools.infrastructure.api.jira.report.Reports +import com.atlassian.performance.tools.ssh.api.SshConnection + +class DatabaseIpConfig( + private val databaseIp: String +) : PostInstallHook { + + override fun call( + ssh: SshConnection, + jira: InstalledJira, + hooks: PostInstallHooks, + reports: Reports + ) { + Sed().replace( + connection = ssh, + expression = "(.*(@(//)?|//))" + "([^:/]+)" + "(.*)", + output = """\1$databaseIp\5""", + file = "${jira.home.path}/dbconfig.xml" + ) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/database/DockerMysqlServer.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/database/DockerMysqlServer.kt new file mode 100644 index 00000000..78fa1f6e --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/database/DockerMysqlServer.kt @@ -0,0 +1,94 @@ +package com.atlassian.performance.tools.infrastructure.api.database + +import com.atlassian.performance.tools.infrastructure.api.dataset.DatasetPackage +import com.atlassian.performance.tools.infrastructure.api.docker.DockerImage +import com.atlassian.performance.tools.infrastructure.api.jira.install.TcpNode +import com.atlassian.performance.tools.infrastructure.api.jira.install.hook.PreInstallHooks +import com.atlassian.performance.tools.infrastructure.api.jira.instance.* +import com.atlassian.performance.tools.infrastructure.api.jira.report.Reports +import com.atlassian.performance.tools.infrastructure.api.network.TcpServerRoom +import com.atlassian.performance.tools.infrastructure.api.os.Ubuntu +import com.atlassian.performance.tools.infrastructure.database.SshMysqlClient +import com.atlassian.performance.tools.infrastructure.database.SshSqlClient +import com.atlassian.performance.tools.infrastructure.docker.DeadContainerCheck +import com.atlassian.performance.tools.jvmtasks.api.IdempotentAction +import com.atlassian.performance.tools.jvmtasks.api.StaticBackoff +import com.atlassian.performance.tools.ssh.api.Ssh +import com.atlassian.performance.tools.ssh.api.SshConnection +import java.time.Duration +import java.time.Duration.ofSeconds + +class DockerMysqlServer private constructor( + private val serverRoom: TcpServerRoom, + private val source: DatasetPackage, + private val dockerImage: DockerImage, + private val maxConnections: Int +) : PreInstanceHook { + + override fun call( + nodes: List, + hooks: PostInstanceHooks, + reports: Reports + ) { + val server = serverRoom.serveTcp("mysql") + val client = server.ssh.newConnection().use { setup(it, server) } + nodes.forEach { node -> + node.postInstall.insert(DatabaseIpConfig(server.privateIp)) + node.postInstall.insert(MysqlConnector()) + } + hooks.insert(FixJiraUriViaMysql(server.ssh, client)) + } + + private fun setup(ssh: SshConnection, host: TcpNode): SshSqlClient { + val mysqlData = source.download(ssh) + val port = host.port + val container = dockerImage.run( + ssh = ssh, + parameters = "-p $port:$port -v `realpath $mysqlData`:/var/lib/mysql", + arguments = "--skip-grant-tables --max_connections=$maxConnections" + ) + Ubuntu().install(ssh, listOf("mysql-client")) + val client = SshMysqlClient("127.0.0.1", port, "root") + IdempotentAction("wait for MySQL start") { client.runSql(ssh, "select 1;") } + .retry(90, DeadContainerCheck(container, ssh, StaticBackoff(ofSeconds(10)))) + return client + } + + class Builder( + private var serverRoom: TcpServerRoom, + private var source: DatasetPackage + ) { + + private var dockerImage = DockerImage.Builder("mysql:5.6.42") + .pullTimeout(Duration.ofMinutes(5)) + .build() + private var maxConnections: Int = 151 + + fun serverRoom(serverRoom: TcpServerRoom) = apply { this.serverRoom = serverRoom } + fun source(source: DatasetPackage) = apply { this.source = source } + fun dockerImage(dockerImage: DockerImage) = apply { this.dockerImage = dockerImage } + fun maxConnections(maxConnections: Int) = apply { this.maxConnections = maxConnections } + + fun build(): DockerMysqlServer = DockerMysqlServer( + serverRoom, + source, + dockerImage, + maxConnections + ) + } + + private class FixJiraUriViaMysql( + private val mysqlNode: Ssh, + private val client: SshSqlClient + ) : PostInstanceHook { + + override fun call(instance: JiraInstance, hooks: PostInstanceHooks, reports: Reports) { + mysqlNode.newConnection().use { ssh -> + val db = "jiradb" + val update = "UPDATE $db.propertystring SET propertyvalue = '${instance.address}'" + val where = "WHERE id IN (select id from $db.propertyentry where property_key like '%baseurl%')" + client.runSql(ssh, "$update $where;") + } + } + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/database/DockerPostgresServer.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/database/DockerPostgresServer.kt new file mode 100644 index 00000000..8b38eab8 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/database/DockerPostgresServer.kt @@ -0,0 +1,70 @@ +package com.atlassian.performance.tools.infrastructure.api.database + +import com.atlassian.performance.tools.infrastructure.api.docker.DockerImage +import com.atlassian.performance.tools.infrastructure.api.jira.install.TcpNode +import com.atlassian.performance.tools.infrastructure.api.jira.install.hook.PreInstallHooks +import com.atlassian.performance.tools.infrastructure.api.jira.instance.PostInstanceHooks +import com.atlassian.performance.tools.infrastructure.api.jira.instance.PreInstanceHook +import com.atlassian.performance.tools.infrastructure.api.jira.instance.PreInstanceHooks +import com.atlassian.performance.tools.infrastructure.api.jira.report.Reports +import com.atlassian.performance.tools.ssh.api.SshConnection +import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.Logger +import java.time.Duration +import java.util.function.Supplier + +class DockerPostgresServer private constructor( + private val hostSupplier: Supplier, + private val dockerImage: DockerImage, + private val maxConnections: Int +) : PreInstanceHook { + + private val logger: Logger = LogManager.getLogger(this::class.java) + + override fun call(nodes: List, hooks: PostInstanceHooks, reports: Reports) { + val server = hostSupplier.get() + server.ssh.newConnection().use { setup(it) } + nodes.forEach { node -> + node.postInstall.insert(DatabaseIpConfig(server.publicIp)) + } + } + + private fun setup(ssh: SshConnection) { + val container = dockerImage.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 $container psql --command \"CREATE USER jira WITH NOSUPERUSER INHERIT NOCREATEROLE NOCREATEDB LOGIN PASSWORD 'jira';\"") + ssh.execute("sudo docker exec -u postgres $container createdb -E UNICODE -l C -T template0 -O jira jira") + /** + * TODO Check logs for the following entry + * `LOG: database system is ready to accept connections` + */ + Thread.sleep(Duration.ofSeconds(15).toMillis()) + } + + class Builder( + private var hostSupplier: Supplier + ) { + + private var dockerImage = DockerImage.Builder("postgres:9.6.15") + .pullTimeout(Duration.ofMinutes(5)) + .build() + private var maxConnections: Int = 200 + + fun serverSupplier(hostSupplier: Supplier) = apply { this.hostSupplier = hostSupplier } + fun dockerImage(dockerImage: DockerImage) = apply { this.dockerImage = dockerImage } + fun maxConnections(maxConnections: Int) = apply { this.maxConnections = maxConnections } + + fun build(): DockerPostgresServer = DockerPostgresServer( + hostSupplier, + dockerImage, + maxConnections + ) + } +} 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..7b4d61fb 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 @@ -1,7 +1,7 @@ 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.infrastructure.api.docker.DockerImage import com.atlassian.performance.tools.infrastructure.api.os.Ubuntu import com.atlassian.performance.tools.ssh.api.SshConnection import org.apache.logging.log4j.LogManager @@ -19,10 +19,10 @@ class MySqlDatabase( ) : Database { private val logger: Logger = LogManager.getLogger(this::class.java) - private val image: DockerImage = DockerImage( - name = "mysql:5.7.32", - pullTimeout = Duration.ofMinutes(5) - ) + private val image: DockerImage = DockerImage.Builder("mysql:5.7.32") + .pullTimeout(Duration.ofMinutes(5)) + .build() + private val ubuntu = Ubuntu() /** 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..b4a13348 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/database/MysqlConnector.kt @@ -0,0 +1,31 @@ +package com.atlassian.performance.tools.infrastructure.api.database + +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.install.hook.PostInstallHooks +import com.atlassian.performance.tools.infrastructure.api.jira.report.Reports +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.time.Duration + +class MysqlConnector : PostInstallHook { + + override fun call( + ssh: SshConnection, + jira: InstalledJira, + hooks: PostInstallHooks, + reports: Reports + ) { + 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.path}/lib") + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/docker/Docker.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/docker/Docker.kt new file mode 100644 index 00000000..70c65ae9 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/docker/Docker.kt @@ -0,0 +1,64 @@ +package com.atlassian.performance.tools.infrastructure.api.docker + +import com.atlassian.performance.tools.infrastructure.api.os.Ubuntu +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.time.Duration + +class Docker private constructor( + private val dependencyPackagesTimeout: Duration, + private val mainPackageTimeout: Duration +) { + + private val ubuntu = Ubuntu() + + /** + * See the [official guide](https://docs.docker.com/engine/installation/linux/docker-ce/ubuntu/#install-docker-ce). + */ + fun install( + ssh: SshConnection + ) { + ubuntu.install( + ssh = ssh, + packages = listOf( + "apt-transport-https", + "ca-certificates", + "curl" + ), + timeout = dependencyPackagesTimeout + ) + ubuntu.addKey(ssh, "7EA0A9C3F273FCD8") + + val release = ubuntu.getDistributionCodename(ssh) + ubuntu.addRepository( + ssh, + "deb [arch=amd64] https://download.docker.com/linux/ubuntu $release stable", + "docker" + ); + + val version = "5:19.03.13~3-0~ubuntu-$release" + ubuntu.install( + ssh = ssh, + packages = listOf("docker-ce=$version"), + timeout = mainPackageTimeout + ) + ssh.execute("sudo mkdir -p /etc/docker") + ssh.execute("echo '{\"storage-driver\": \"vfs\"}' | sudo tee /etc/docker/daemon.json") + ssh.execute("sudo service docker status || sudo service docker start") + IdempotentAction("poll docker") { + ssh.execute("sudo docker ps") + }.retry(2, StaticBackoff(Duration.ofSeconds(1))) + } + + class Builder { + + private var dependencyPackagesTimeout: Duration = Duration.ofMinutes(2) + private var mainPackageTimeout: Duration = Duration.ofMinutes(5) + + fun build(): Docker = Docker( + dependencyPackagesTimeout, + mainPackageTimeout + ) + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/docker/DockerImage.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/docker/DockerImage.kt new file mode 100644 index 00000000..86c204af --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/docker/DockerImage.kt @@ -0,0 +1,55 @@ +package com.atlassian.performance.tools.infrastructure.api.docker + +import com.atlassian.performance.tools.ssh.api.SshConnection +import org.apache.logging.log4j.Level +import java.time.Duration +import java.util.* + +class DockerImage private constructor( + private val docker: Docker, + private val name: String, + private val pullTimeout: Duration +) { + + fun run( + ssh: SshConnection + ) = run( + ssh = ssh, + parameters = "", + arguments = "" + ) + + fun run( + ssh: SshConnection, + parameters: String, + arguments: String + ): String { + docker.install(ssh) + val containerName = "jpt-" + UUID.randomUUID() + ssh.execute( + cmd = "sudo docker pull $name", + timeout = pullTimeout, + stdout = Level.TRACE, + stderr = Level.WARN + ) + ssh.execute("sudo docker run -d $parameters --name $containerName $name $arguments") + return containerName + } + + class Builder( + private val name: String + ) { + + private var docker = Docker.Builder().build() + private var pullTimeout: Duration = Duration.ofMinutes(1) + + fun docker(docker: Docker) = apply { this.docker = docker } + fun pullTimeout(pullTimeout: Duration) = apply { this.pullTimeout = pullTimeout } + + fun build(): DockerImage = DockerImage( + docker, + name, + pullTimeout + ) + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/HttpNode.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/HttpNode.kt new file mode 100644 index 00000000..e7219f85 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/HttpNode.kt @@ -0,0 +1,19 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.install + +import java.net.URI + +class HttpNode( + val tcp: TcpNode, + private val basePath: String, + private val supportsTls: Boolean +) { + + fun addressPublicly(): URI = address(tcp.publicIp) + fun addressPrivately(): URI = address(tcp.privateIp) + fun addressPrivately(userName: String, password: String): URI = address(tcp.privateIp, "$userName:$password@") + + private fun address(ip: String, userInfo: String = ""): URI { + val scheme = if (supportsTls) "https" else "http" + return URI("$scheme://$userInfo$ip:${tcp.port}$basePath/") + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/install/InstalledJira.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/InstalledJira.kt similarity index 76% rename from src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/install/InstalledJira.kt rename to src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/InstalledJira.kt index f989223e..92dd76c3 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/install/InstalledJira.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/InstalledJira.kt @@ -1,10 +1,12 @@ -package com.atlassian.performance.tools.infrastructure.jira.install +package com.atlassian.performance.tools.infrastructure.api.jira.install import com.atlassian.performance.tools.infrastructure.api.jvm.JavaDevelopmentKit import com.atlassian.performance.tools.infrastructure.api.os.RemotePath /** * Points to an already installed Jira. + * + * @since 4.19.0 */ class InstalledJira( /** @@ -20,7 +22,7 @@ class InstalledJira( */ val jdk: JavaDevelopmentKit, /** - * Hosts Jira. Specifies sockets used by Jira to handle requests. + * Connects to Jira on HTTP level or below. */ - val server: TcpServer + val http: HttpNode ) 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..d34f1a76 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/JiraInstallation.kt @@ -0,0 +1,22 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.install + +import com.atlassian.performance.tools.infrastructure.api.jira.report.Reports +import net.jcip.annotations.ThreadSafe + +/** + * @since 4.19.0 + */ +@ThreadSafe +interface JiraInstallation { + + /** + * Installs Jira on [http] node. + * + * @param [http] will host the Jira + * @param [reports] accumulates reports + */ + fun install( + http: HttpNode, + reports: Reports + ): InstalledJira +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/install/ParallelInstallation.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/ParallelInstallation.kt similarity index 78% rename from src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/install/ParallelInstallation.kt rename to src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/ParallelInstallation.kt index 8dcd537e..a6d9b29c 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/install/ParallelInstallation.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/ParallelInstallation.kt @@ -1,11 +1,13 @@ -package com.atlassian.performance.tools.infrastructure.jira.install +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.jira.report.Reports import com.atlassian.performance.tools.infrastructure.api.jvm.JavaDevelopmentKit import com.atlassian.performance.tools.infrastructure.downloadRemotely import com.atlassian.performance.tools.infrastructure.installRemotely +import com.atlassian.performance.tools.infrastructure.jira.install.TomcatConfig import java.util.concurrent.Executors class ParallelInstallation( @@ -15,9 +17,10 @@ class ParallelInstallation( ) : JiraInstallation { override fun install( - server: TcpServer + http: HttpNode, + reports: Reports ): InstalledJira { - server.ssh.newConnection().use { ssh -> + http.tcp.ssh.newConnection().use { ssh -> val pool = Executors.newCachedThreadPool { runnable -> Thread(runnable, "jira-installation-${runnable.hashCode()}") } @@ -30,8 +33,9 @@ class ParallelInstallation( val java = pool.submitWithLogContext("java") { jdk.also { it.install(ssh) } } - val jira = InstalledJira(home.get(), product.get(), java.get(), server) + val jira = InstalledJira(home.get(), product.get(), java.get(), http) pool.shutdownNow() + TomcatConfig(jira, 8080).fixHttp(ssh) return jira } } diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/install/SequentialInstallation.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/SequentialInstallation.kt similarity index 63% rename from src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/install/SequentialInstallation.kt rename to src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/SequentialInstallation.kt index 0d9ca677..46cc6e51 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/install/SequentialInstallation.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/SequentialInstallation.kt @@ -1,10 +1,12 @@ -package com.atlassian.performance.tools.infrastructure.jira.install +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.jira.report.Reports import com.atlassian.performance.tools.infrastructure.api.jvm.JavaDevelopmentKit import com.atlassian.performance.tools.infrastructure.downloadRemotely import com.atlassian.performance.tools.infrastructure.installRemotely +import com.atlassian.performance.tools.infrastructure.jira.install.TomcatConfig class SequentialInstallation( private val jiraHomeSource: JiraHomeSource, @@ -13,13 +15,16 @@ class SequentialInstallation( ) : JiraInstallation { override fun install( - server: TcpServer + http: HttpNode, + reports: Reports ): InstalledJira { - server.ssh.newConnection().use { ssh -> + http.tcp.ssh.newConnection().use { ssh -> val installation = productDistribution.installRemotely(ssh, ".") val home = jiraHomeSource.downloadRemotely(ssh) jdk.install(ssh) - return InstalledJira(home, installation, jdk, server) + val jira = InstalledJira(home, installation, jdk, http) + TomcatConfig(jira, 8080).fixHttp(ssh) + return jira } } } diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/TcpNode.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/TcpNode.kt new file mode 100644 index 00000000..a69e756a --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/TcpNode.kt @@ -0,0 +1,16 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.install + +import com.atlassian.performance.tools.ssh.api.Ssh + +/** + * Has open TCP sockets. + * @param [ssh] connects to the host via [publicIp] + * @param [port] accepts connections at within the [privateIp] network + */ +class TcpNode( + val publicIp: String, + val privateIp: String, + val port: Int, + val name: String, + val ssh: Ssh +) 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..453ba963 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/AsyncProfilerHook.kt @@ -0,0 +1,60 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.install.hook + +import com.atlassian.performance.tools.infrastructure.api.jira.install.HttpNode +import com.atlassian.performance.tools.infrastructure.api.jira.report.Report +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.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, + http: HttpNode, + hooks: PreInstallHooks, + reports: Reports + ) { + 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, + reports: Reports + ) { + ssh.execute("$profilerPath -b 20000000 start ${jira.pid}") + val profiler = StartedAsyncProfiler(jira.pid, profilerPath) + reports.add(profiler, jira) + } +} + +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/DisabledAutoBackup.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/DisabledAutoBackup.kt new file mode 100644 index 00000000..164da451 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/DisabledAutoBackup.kt @@ -0,0 +1,17 @@ +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.Reports +import com.atlassian.performance.tools.ssh.api.SshConnection + +class DisabledAutoBackup : PostInstallHook { + + override fun call( + ssh: SshConnection, + jira: InstalledJira, + hooks: PostInstallHooks, + reports: Reports + ) { + ssh.execute("echo jira.autoexport=false > ${jira.home.path}/jira-config.properties") + } +} 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..b09991b5 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/JiraHomeProperty.kt @@ -0,0 +1,18 @@ +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.Reports +import com.atlassian.performance.tools.ssh.api.SshConnection + +class JiraHomeProperty : PostInstallHook { + + override fun call( + ssh: SshConnection, + jira: InstalledJira, + hooks: PostInstallHooks, + reports: Reports + ) { + val properties = "${jira.installation.path}/atlassian-jira/WEB-INF/classes/jira-application.properties" + ssh.execute("echo jira.home=`realpath ${jira.home.path}` > $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..7d544f19 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/JiraLogs.kt @@ -0,0 +1,33 @@ +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.Report +import com.atlassian.performance.tools.infrastructure.api.jira.report.Reports +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, reports: Reports) { + reports.add(report(jira), jira) + } + + fun report(jira: InstalledJira): Report { + return JiraLogsReport(jira) + } + + private class JiraLogsReport(private val jira: InstalledJira) : Report { + override fun locate(ssh: SshConnection): List { + return listOf( + "${jira.home.path}/log/atlassian-jira.log", + "${jira.installation.path}/logs/catalina.out" + ).onEach { ensureFile(Paths.get(it), ssh) } + } + + 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..3fe4ab97 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/JvmConfig.kt @@ -0,0 +1,31 @@ +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.infrastructure.api.jira.report.Reports +import com.atlassian.performance.tools.ssh.api.SshConnection + +class JvmConfig( + private val config: JiraNodeConfig +) : PostInstallHook { + + override fun call( + ssh: SshConnection, + jira: InstalledJira, + hooks: PostInstallHooks, + reports: Reports + ) { + val gcLog = JiraGcLog(jira.installation.path) + SetenvSh(jira.installation.path).setup( + connection = ssh, + config = config, + gcLog = gcLog, + jiraIp = jira.http.tcp.publicIp + ) + val report = FileListing(gcLog.path("*")) + reports.add(report, jira) + } +} 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..2404e567 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/LateUbuntuSysstat.kt @@ -0,0 +1,37 @@ +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.report.Reports +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.report.RemoteMonitoringProcessReport +import com.atlassian.performance.tools.ssh.api.SshConnection + +class LateUbuntuSysstat : PostInstallHook { + override fun call( + ssh: SshConnection, + jira: InstalledJira, + hooks: PostInstallHooks, + reports: Reports + ) { + 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, reports: Reports) { + val process = metric.start(ssh) + reports.add(RemoteMonitoringProcessReport(process), jira) + } +} 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..c95cd0cb --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/PostInstallHook.kt @@ -0,0 +1,24 @@ +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.Reports +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 + * @param [reports] accumulates reports + */ + fun call( + ssh: SshConnection, + jira: InstalledJira, + hooks: PostInstallHooks, + reports: Reports + ) +} 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..37088763 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/PostInstallHooks.kt @@ -0,0 +1,55 @@ +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.report.Reports +import com.atlassian.performance.tools.infrastructure.api.jira.start.hook.PreStartHooks +import com.atlassian.performance.tools.infrastructure.jira.install.hook.ProfilerHook +import com.atlassian.performance.tools.infrastructure.jira.install.hook.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 + + fun insert( + hook: PostInstallHook + ) { + hooks.add(hook) + } + + internal fun call( + ssh: SshConnection, + jira: InstalledJira, + reports: Reports + ) { + while (true) { + hooks + .poll() + ?.call(ssh, jira, this, reports) + ?: 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..6998f149 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/PreInstallHook.kt @@ -0,0 +1,25 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.install.hook + +import com.atlassian.performance.tools.infrastructure.api.jira.install.HttpNode +import com.atlassian.performance.tools.infrastructure.api.jira.report.Reports +import com.atlassian.performance.tools.ssh.api.SshConnection +import org.apache.http.HttpHost + +/** + * Intercepts a call before Jira is installed. + */ +interface PreInstallHook { + + /** + * @param [ssh] connects to the [http] host + * @param [http] will install Jira + * @param [hooks] inserts future hooks + * @param [reports] accumulates reports + */ + fun call( + ssh: SshConnection, + http: HttpNode, + hooks: PreInstallHooks, + reports: Reports + ) +} 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..d748580f --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/PreInstallHooks.kt @@ -0,0 +1,43 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.install.hook + +import com.atlassian.performance.tools.infrastructure.api.jira.install.HttpNode +import com.atlassian.performance.tools.infrastructure.api.jira.report.Reports +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 + + fun insert( + hook: PreInstallHook + ) { + hooks.add(hook) + } + + internal fun call( + ssh: SshConnection, + http: HttpNode, + reports: Reports + ) { + while (true) { + hooks + .poll() + ?.call(ssh, http, this, reports) + ?: break + } + } + + companion object Factory { + fun default(): PreInstallHooks = PreInstallHooks(PostInstallHooks.default()).apply { + insert(SystemLog()) + } + + 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..21904606 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/SystemLog.kt @@ -0,0 +1,13 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.install.hook + +import com.atlassian.performance.tools.infrastructure.api.jira.install.HttpNode +import com.atlassian.performance.tools.infrastructure.api.jira.report.FileListing +import com.atlassian.performance.tools.infrastructure.api.jira.report.Reports +import com.atlassian.performance.tools.ssh.api.SshConnection + +class SystemLog : PreInstallHook { + + override fun call(ssh: SshConnection, http: HttpNode, hooks: PreInstallHooks, reports: Reports) { + reports.add(FileListing("/var/log/syslog"), http) + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/DefaultClusterProperties.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/DefaultClusterProperties.kt new file mode 100644 index 00000000..ab342f07 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/DefaultClusterProperties.kt @@ -0,0 +1,18 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.instance + +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.install.hook.PostInstallHooks +import com.atlassian.performance.tools.infrastructure.api.jira.report.Reports +import com.atlassian.performance.tools.infrastructure.jira.instance.ClusterProperties +import com.atlassian.performance.tools.ssh.api.SshConnection + +class DefaultClusterProperties : PostInstallHook { + + override fun call(ssh: SshConnection, jira: InstalledJira, hooks: PostInstallHooks, reports: Reports) { + ClusterProperties(jira).apply { + set("jira.node.id", jira.http.tcp.name, ssh) + set("ehcache.object.port", "40011", ssh) + } + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/JiraDataCenterPlan.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/JiraDataCenterPlan.kt new file mode 100644 index 00000000..47710fa7 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/JiraDataCenterPlan.kt @@ -0,0 +1,101 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.instance + +import com.atlassian.performance.tools.infrastructure.api.jira.install.HttpNode +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira +import com.atlassian.performance.tools.infrastructure.api.jira.install.hook.PreInstallHooks +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.infrastructure.api.loadbalancer.ApacheProxyPlan +import com.atlassian.performance.tools.infrastructure.api.loadbalancer.LoadBalancer +import com.atlassian.performance.tools.infrastructure.api.loadbalancer.LoadBalancerPlan +import com.atlassian.performance.tools.infrastructure.api.network.HttpServerRoom +import java.net.URI +import java.time.Duration +import java.util.function.Consumer +import kotlin.streams.asStream +import kotlin.streams.toList + +class JiraDataCenterPlan private constructor( + private val nodePlans: List, + private val instanceHooks: PreInstanceHooks, + private val balancerPlan: LoadBalancerPlan +) : JiraInstancePlan { + + private val reports: Reports = Reports() + private val loadBalancingPatience = Duration.ofMinutes(5) + + override fun materialize(): JiraInstance { + instanceHooks.call(nodePlans.map { it.hooks }, reports) + val http = nodePlans.mapIndexed { nodeIndex, plan -> + val nodeNumber = nodeIndex + 1 + val http = plan.serverRoom.serveHttp("jira-node-$nodeNumber") + PlannedHttpNode(http, plan) + } + val balancer = balancerPlan.materialize(http.map { it.http }, nodePlans.map { it.hooks.preStart }) + val installed = installInParallel(http) + val started = installed.map { it.start(reports) } + val instance = JiraDataCenter(started, balancer) + instanceHooks.postInstance.call(instance, reports) + balancer.waitUntilHealthy(loadBalancingPatience) + return instance + } + + override fun report(): Reports = reports.copy() + + private fun installInParallel(nodes: Collection): List = nodes + .asSequence() + .asStream() + .parallel() + .map { it.install(reports) } + .toList() + + private class PlannedHttpNode( + val http: HttpNode, + val plan: JiraNodePlan + ) { + fun install(reports: Reports): PlannedInstalledJira { + return plan.installation.install(http, reports).let { PlannedInstalledJira(it, plan) } + } + } + + private class PlannedInstalledJira( + val installed: InstalledJira, + val plan: JiraNodePlan + ) { + fun start(reports: Reports): StartedJira { + return plan.start.start(installed, reports) + } + } + + private class JiraDataCenter( + override val nodes: List, + private val loadBalancer: LoadBalancer + ) : JiraInstance { + override val address: URI + get() = loadBalancer.uri + } + + class Builder( + private var nodePlans: List, + private var balancerPlan: LoadBalancerPlan + ) { + private var instanceHooks: PreInstanceHooks = PreInstanceHooks.default() + + constructor( + serverRoom: HttpServerRoom + ) : this( + nodePlans = listOf(1, 2).map { + JiraNodePlan.Builder(serverRoom) + .dataCenter() + .build() + }, + balancerPlan = ApacheProxyPlan(serverRoom) + ) + + fun nodePlans(nodePlans: List) = apply { this.nodePlans = nodePlans } + fun instanceHooks(instanceHooks: PreInstanceHooks) = apply { this.instanceHooks = instanceHooks } + fun balancerPlan(balancerPlan: LoadBalancerPlan) = apply { this.balancerPlan = balancerPlan } + + fun build(): JiraInstancePlan = JiraDataCenterPlan(nodePlans, instanceHooks, balancerPlan) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/JiraInstance.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/JiraInstance.kt new file mode 100644 index 00000000..173b48ac --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/JiraInstance.kt @@ -0,0 +1,9 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.instance + +import com.atlassian.performance.tools.infrastructure.api.jira.start.StartedJira +import java.net.URI + +interface JiraInstance { + val address: URI + val nodes: List +} \ No newline at end of file diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/JiraInstancePlan.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/JiraInstancePlan.kt new file mode 100644 index 00000000..2b80ac64 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/JiraInstancePlan.kt @@ -0,0 +1,8 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.instance + +import com.atlassian.performance.tools.infrastructure.api.jira.report.Reports + +interface JiraInstancePlan { + fun materialize(): JiraInstance + fun report(): Reports +} \ No newline at end of file diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/JiraNodePlan.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/JiraNodePlan.kt new file mode 100644 index 00000000..922ddd28 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/JiraNodePlan.kt @@ -0,0 +1,65 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.instance + +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.JiraInstallation +import com.atlassian.performance.tools.infrastructure.api.jira.install.ParallelInstallation +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.JiraStart +import com.atlassian.performance.tools.infrastructure.api.jvm.OracleJDK +import com.atlassian.performance.tools.infrastructure.api.network.HttpServerRoom +import com.atlassian.performance.tools.infrastructure.jira.install.hook.HookedJiraInstallation +import com.atlassian.performance.tools.infrastructure.jira.start.hook.HookedJiraStart +import net.jcip.annotations.NotThreadSafe + +/** + * Specifies how a Jira node should be built. + * Does not build the node, due to various possible ordering, concurrency and optimizations. + * Actual provisioning is reserved for a [JiraInstancePlan]. + * + * @constructor specifies the plan, but doesn't hold any resources + * @see JiraInstancePlan + * @since 4.19.0 + */ +class JiraNodePlan private constructor( + internal val serverRoom: HttpServerRoom, + internal val installation: JiraInstallation, + internal val start: JiraStart, + internal val hooks: PreInstallHooks +) { + + @NotThreadSafe + class Builder( + private var serverRoom: HttpServerRoom + ) { + private var installation: JiraInstallation = ParallelInstallation( + EmptyJiraHome(), + PublicJiraSoftwareDistribution("7.13.0"), + OracleJDK() + ) + private var start: JiraStart = JiraLaunchScript() + private var hooks: PreInstallHooks = PreInstallHooks.default() + + constructor(plan: JiraNodePlan) : this( + plan.serverRoom + ) { + this.installation = plan.installation + this.start = plan.start + this.hooks = plan.hooks + } + + fun serverRoom(serverRoom: HttpServerRoom) = apply { this.serverRoom = serverRoom } + fun installation(installation: JiraInstallation) = apply { this.installation = installation } + fun start(start: JiraStart) = apply { this.start = start } + fun hooks(hooks: PreInstallHooks) = apply { this.hooks = hooks } + fun dataCenter() = hooks(PreInstallHooks.default().apply { postInstall.insert(DefaultClusterProperties()) }) + + fun build() = JiraNodePlan( + serverRoom, + HookedJiraInstallation(installation, hooks), + HookedJiraStart(start, hooks.preStart), + hooks + ) + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/JiraServerPlan.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/JiraServerPlan.kt new file mode 100644 index 00000000..284f160c --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/JiraServerPlan.kt @@ -0,0 +1,50 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.instance + +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.infrastructure.api.network.HttpServerRoom +import java.net.URI + +class JiraServerPlan private constructor( + private val plan: JiraNodePlan, + private val hooks: PreInstanceHooks +) : JiraInstancePlan { + + private val reports: Reports = Reports() + + override fun materialize(): JiraInstance { + val nodeHooks = listOf(plan).map { it.hooks } + hooks.call(nodeHooks, reports) + val http = plan.serverRoom.serveHttp("jira-node") + val installed = plan.installation.install(http, reports) + val started = plan.start.start(installed, reports) + val instance = JiraServer(started) + hooks.postInstance.call(instance, reports) + return instance + } + + override fun report(): Reports = reports.copy() + + private class JiraServer( + node: StartedJira + ) : JiraInstance { + override val address: URI = node.installed.http.addressPublicly() + override val nodes: List = listOf(node) + } + + class Builder( + private var plan: JiraNodePlan + + ) { + private var hooks: PreInstanceHooks = PreInstanceHooks.default() + + constructor(serverRoom: HttpServerRoom) : this( + plan = JiraNodePlan.Builder(serverRoom).build() + ) + + fun plan(plan: JiraNodePlan) = apply { this.plan = plan } + fun hooks(hooks: PreInstanceHooks) = apply { this.hooks = hooks } + + fun build(): JiraInstancePlan = JiraServerPlan(plan, hooks) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/PostInstanceHook.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/PostInstanceHook.kt new file mode 100644 index 00000000..42e3442c --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/PostInstanceHook.kt @@ -0,0 +1,17 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.instance + +import com.atlassian.performance.tools.infrastructure.api.jira.report.Reports + +interface PostInstanceHook { + + /** + * @param [instance] a standalone Jira Server node or a Jira Data Center cluster + * @param [hooks] inserts future hooks + * @param [reports] accumulates reports + */ + fun call( + instance: JiraInstance, + hooks: PostInstanceHooks, + reports: Reports + ) +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/PostInstanceHooks.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/PostInstanceHooks.kt new file mode 100644 index 00000000..b56147ae --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/PostInstanceHooks.kt @@ -0,0 +1,29 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.instance + +import com.atlassian.performance.tools.infrastructure.api.jira.report.Reports +import java.util.* +import java.util.concurrent.ConcurrentLinkedQueue + +class PostInstanceHooks private constructor() { + + private val hooks: Queue = ConcurrentLinkedQueue() + + fun insert(hook: PostInstanceHook) { + hooks.add(hook) + } + + fun call(instance: JiraInstance, reports: Reports) { + while (true) { + hooks + .poll() + ?.call(instance, this, reports) + ?: break + } + } + + + companion object Factory { + fun default(): PostInstanceHooks = empty() + fun empty(): PostInstanceHooks = PostInstanceHooks() + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/PreInstanceHook.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/PreInstanceHook.kt new file mode 100644 index 00000000..10de8f1e --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/PreInstanceHook.kt @@ -0,0 +1,18 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.instance + +import com.atlassian.performance.tools.infrastructure.api.jira.install.hook.PreInstallHooks +import com.atlassian.performance.tools.infrastructure.api.jira.report.Reports + +interface PreInstanceHook { + + /** + * @param [nodes] inserts node hooks + * @param [hooks] inserts future hooks + * @param [reports] accumulates reports + */ + fun call( + nodes: List, + hooks: PostInstanceHooks, + reports: Reports + ) +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/PreInstanceHooks.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/PreInstanceHooks.kt new file mode 100644 index 00000000..f732f6df --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/PreInstanceHooks.kt @@ -0,0 +1,28 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.instance + +import com.atlassian.performance.tools.infrastructure.api.jira.install.hook.PreInstallHooks +import com.atlassian.performance.tools.infrastructure.api.jira.report.Reports +import java.util.* +import java.util.concurrent.ConcurrentLinkedQueue + +class PreInstanceHooks private constructor( + val postInstance: PostInstanceHooks +) { + + private val hooks: Queue = ConcurrentLinkedQueue() + + fun insert(hook: PreInstanceHook) { + hooks.add(hook) + } + + internal fun call(nodes: List, reports: Reports) { + hooks + .parallelStream() + .forEach { it.call(nodes, postInstance, reports) } + } + + companion object Factory { + fun default(): PreInstanceHooks = PreInstanceHooks(PostInstanceHooks.default()) + fun empty(): PreInstanceHooks = PreInstanceHooks(PostInstanceHooks.empty()) + } +} 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..b23e5965 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/report/FileListing.kt @@ -0,0 +1,18 @@ +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 + .safeExecute("ls $pattern") + .takeIf { it.isSuccessful() } + ?.output + ?.lines() + ?.filter { it.isNotBlank() } + ?: emptyList() +} 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..65059517 --- /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 interesting file paths, which could 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..b9737f42 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/report/Reports.kt @@ -0,0 +1,69 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.report + +import com.atlassian.performance.tools.infrastructure.api.jira.install.HttpNode +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira +import com.atlassian.performance.tools.infrastructure.api.jira.install.TcpNode +import com.atlassian.performance.tools.infrastructure.api.jira.start.StartedJira +import com.atlassian.performance.tools.infrastructure.api.os.RemotePath +import com.atlassian.performance.tools.io.api.ensureDirectory +import com.atlassian.performance.tools.io.api.resolveSafely +import java.io.File +import java.nio.file.Path +import java.nio.file.Paths +import java.util.* +import java.util.concurrent.ConcurrentLinkedQueue + +class Reports private constructor( // TODO turn into SPI to allow AWS CLI transport (S3) + private val hostReports: Queue +) { + constructor() : this(ConcurrentLinkedQueue()) + + fun add(report: Report, started: StartedJira) { + add(report, started.installed) + } + + fun add(report: Report, installed: InstalledJira) { + add(report, installed.http) + } + + fun add(report: Report, http: HttpNode) { + hostReports.add(HostReport(http.tcp, report)) + } + + fun add(report: Report, tcp: TcpNode) { + hostReports.add(HostReport(tcp, report)) + } + + fun downloadTo( + localDirectory: Path + ): File { + localDirectory.ensureDirectory() + hostReports.groupBy { it.host }.map { (host, reports) -> + host.ssh.newConnection().use { ssh -> + reports + .flatMap { report -> + report.report.locate(ssh).map { path -> RemotePath(host.ssh.host, path) } + } + .forEach { remotePath -> + localDirectory + .resolveSafely(host.name) + .resolve(remotePath.toLocalRelativePath()) + .normalize() + .let { remotePath.download(it) } + } + } + } + return localDirectory.toFile() + } + + private fun RemotePath.toLocalRelativePath(): Path = Paths.get(path.trimStart('/')) + + fun copy(): Reports { + return Reports(ConcurrentLinkedQueue(hostReports)) + } + + private class HostReport( + val host: TcpNode, + val report: Report + ) +} 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..16edfafa --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/report/StaticReport.kt @@ -0,0 +1,16 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.report + +import com.atlassian.performance.tools.ssh.api.SshConnection + +/** + * Points to a remote SSH **file**. For directories use a [FileListing] instead. + * + * @param [remotePath] Points to a file on a remote system. + * Relative to the SSH shell default directory (predominantly the user home directory). + */ +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/sharedhome/NfsSharedHome.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/sharedhome/NfsSharedHome.kt new file mode 100644 index 00000000..99f7072e --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/sharedhome/NfsSharedHome.kt @@ -0,0 +1,64 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.sharedhome + +import com.atlassian.performance.tools.infrastructure.api.jira.JiraHomeSource +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira +import com.atlassian.performance.tools.infrastructure.api.jira.install.TcpNode +import com.atlassian.performance.tools.infrastructure.api.jira.install.hook.PostInstallHook +import com.atlassian.performance.tools.infrastructure.api.jira.install.hook.PostInstallHooks +import com.atlassian.performance.tools.infrastructure.api.jira.install.hook.PreInstallHooks +import com.atlassian.performance.tools.infrastructure.api.jira.instance.PostInstanceHooks +import com.atlassian.performance.tools.infrastructure.api.jira.instance.PreInstanceHook +import com.atlassian.performance.tools.infrastructure.api.jira.report.Reports +import com.atlassian.performance.tools.infrastructure.api.network.Networked +import com.atlassian.performance.tools.infrastructure.api.network.TcpServerRoom +import com.atlassian.performance.tools.infrastructure.api.os.Ubuntu +import com.atlassian.performance.tools.infrastructure.jira.sharedhome.SharedHomeProperty +import com.atlassian.performance.tools.ssh.api.SshConnection + +class NfsSharedHome( + private val jiraHomeSource: JiraHomeSource, + private val infrastructure: TcpServerRoom, + private val networked: Networked +) : PreInstanceHook { + private val localHome = "/home/ubuntu/jira-shared-home" + + override fun call(nodes: List, hooks: PostInstanceHooks, reports: Reports) { + val tcp = infrastructure.serveTcp("shared-home") + tcp.ssh.newConnection().use { ssh -> + download(ssh) + export(ssh) + } + nodes.forEach { it.postInstall.insert(NfsMount(tcp, localHome)) } + } + + private fun download(ssh: SshConnection) { + ssh.execute("sudo mkdir -p $localHome") + val jiraHome = jiraHomeSource.download(ssh) + ssh.execute("sudo mv $jiraHome/{data,plugins,import,export} $localHome") + ssh.safeExecute("sudo mv $jiraHome/logos $localHome") + } + + private fun export(ssh: SshConnection): SshConnection.SshResult { + Ubuntu().install(ssh, listOf("nfs-kernel-server")) + val options = "rw,sync,no_subtree_check,no_root_squash" + ssh.execute("sudo echo '$localHome ${networked.subnet()}($options)' | sudo tee -a /etc/exports") + return ssh.execute("sudo service nfs-kernel-server restart") + } + + private class NfsMount( + private val tcp: TcpNode, + private val sharedHome: String + ) : PostInstallHook { + + override fun call(ssh: SshConnection, jira: InstalledJira, hooks: PostInstallHooks, reports: Reports) { + Ubuntu().install(ssh, listOf("nfs-common")) + val mountSource = "${tcp.privateIp}:$sharedHome" + val mountTarget = "mounted-shared-home" + ssh.execute("mkdir -p $mountTarget") + ssh.execute("sudo mount -o soft,intr,rsize=8192,wsize=8192 $mountSource $mountTarget") + ssh.execute("sudo chown ubuntu:ubuntu $mountTarget") + val mounted = "`realpath $mountTarget`" + SharedHomeProperty(jira).set(mounted, ssh) + } + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/sharedhome/SambaSharedHome.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/sharedhome/SambaSharedHome.kt new file mode 100644 index 00000000..da4d8c29 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/sharedhome/SambaSharedHome.kt @@ -0,0 +1,87 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.sharedhome + +import com.atlassian.performance.tools.infrastructure.api.jira.JiraHomeSource +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira +import com.atlassian.performance.tools.infrastructure.api.jira.install.TcpNode +import com.atlassian.performance.tools.infrastructure.api.jira.install.hook.PostInstallHook +import com.atlassian.performance.tools.infrastructure.api.jira.install.hook.PostInstallHooks +import com.atlassian.performance.tools.infrastructure.api.jira.install.hook.PreInstallHooks +import com.atlassian.performance.tools.infrastructure.api.jira.instance.PostInstanceHooks +import com.atlassian.performance.tools.infrastructure.api.jira.instance.PreInstanceHook +import com.atlassian.performance.tools.infrastructure.api.jira.instance.PreInstanceHooks +import com.atlassian.performance.tools.infrastructure.api.jira.report.Reports +import com.atlassian.performance.tools.infrastructure.api.network.TcpServerRoom +import com.atlassian.performance.tools.infrastructure.api.os.Ubuntu +import com.atlassian.performance.tools.infrastructure.jira.sharedhome.SharedHomeProperty +import com.atlassian.performance.tools.ssh.api.SshConnection +import java.util.* + +class SambaSharedHome( + private val jiraHomeSource: JiraHomeSource, + private val serverRoom: TcpServerRoom +) : PreInstanceHook { + + override fun call(nodes: List, hooks: PostInstanceHooks, reports: Reports) { + val server = serverRoom.serveTcp("samba-shared-home", listOf(139, 445), listOf(137, 138)) + val mount = server.ssh.newConnection().use { ssh -> + val sharedHome = download(ssh) + export(ssh, sharedHome, server) + } + nodes.forEach { it.postInstall.insert(mount) } + } + + private fun download(ssh: SshConnection): String { + val sharedHome = "/home/ubuntu/jira-shared-home" + ssh.execute("sudo mkdir -p $sharedHome") + val jiraHome = jiraHomeSource.download(ssh) + ssh.execute("sudo mv $jiraHome/{data,plugins,import,export} $sharedHome") + ssh.safeExecute("sudo mv $jiraHome/logos $sharedHome") + return sharedHome + } + + private fun export(ssh: SshConnection, sharedHome: String, server: TcpNode): SambaMount { + Ubuntu().install(ssh, listOf("samba")) + val shareName = "samba-jira-home" + val share = """ + [$shareName] + comment = shared Jira home + path = $sharedHome + read only = no + browsable = no + """.trimIndent() + ssh.execute("echo '$share' | sudo tee -a /etc/samba/smb.conf") + val user = ssh.execute("whoami").output.trim() + val password = generatePassword() + // could transfer password via file, but it's an ephemeral secret anyway + ssh.execute("echo -e '$password\\n$password\\n' | sudo smbpasswd -s -a $user") + ssh.execute("sudo service smbd restart") + return SambaMount(server.privateIp, shareName, user, password) + } + + private fun generatePassword(): String { + val rng = Random() + val chars = ('a'..'Z') + ('A'..'Z') + ('0'..'9') + return (1..32).map { chars[rng.nextInt(chars.size)] }.joinToString("") + } + + private class SambaMount( + private val ip: String, + private val shareName: String, + private val user: String, + private val password: String + ) : PostInstallHook { + + override fun call(ssh: SshConnection, jira: InstalledJira, hooks: PostInstallHooks, reports: Reports) { + Ubuntu().install(ssh, listOf("cifs-utils")) + val credentials = "username=$user,password=$password" + val mountSource = "//$ip/$shareName" + val mountTarget = "mounted-shared-home" + ssh.execute("mkdir -p $mountTarget") + val localUser = ssh.execute("whoami").output.trim() + ssh.execute("sudo chown $localUser:$localUser $mountTarget") + ssh.execute("sudo mount -t cifs -o $credentials $mountSource $mountTarget") + val mounted = "`realpath $mountTarget`" + SharedHomeProperty(jira).set(mounted, ssh) + } + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/start/JiraLaunchScript.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/JiraLaunchScript.kt similarity index 69% rename from src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/start/JiraLaunchScript.kt rename to src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/JiraLaunchScript.kt index 14086413..3ce78b6c 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/start/JiraLaunchScript.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/JiraLaunchScript.kt @@ -1,13 +1,15 @@ -package com.atlassian.performance.tools.infrastructure.jira.start +package com.atlassian.performance.tools.infrastructure.api.jira.start -import com.atlassian.performance.tools.infrastructure.jira.install.InstalledJira +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira +import com.atlassian.performance.tools.infrastructure.api.jira.report.Reports import com.atlassian.performance.tools.ssh.api.Ssh import java.time.Duration class JiraLaunchScript : JiraStart { override fun start( - installed: InstalledJira + installed: InstalledJira, + reports: Reports ): StartedJira { val installation = installed.installation Ssh(installation.host).newConnection().use { ssh -> 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..891e000c --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/JiraStart.kt @@ -0,0 +1,23 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.start + +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira +import com.atlassian.performance.tools.infrastructure.api.jira.report.Reports +import net.jcip.annotations.ThreadSafe + +/** + * @since 4.19.0 + */ +@ThreadSafe +interface JiraStart { + + /** + * Starts the [installed] Jira. + * + * @param [installed] will start the Jira + * @param [reports] accumulates reports + */ + fun start( + installed: InstalledJira, + reports: Reports + ): 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..c2c30a78 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/AccessLogs.kt @@ -0,0 +1,13 @@ +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.report.FileListing +import com.atlassian.performance.tools.infrastructure.api.jira.report.Reports +import com.atlassian.performance.tools.ssh.api.SshConnection + +class AccessLogs : PreStartHook { + + override fun call(ssh: SshConnection, jira: InstalledJira, hooks: PreStartHooks, reports: Reports) { + reports.add(FileListing("${jira.installation.path}/logs/*access*"), jira) + } +} 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..f1e15be5 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/JstatHook.kt @@ -0,0 +1,19 @@ +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.infrastructure.jira.report.RemoteMonitoringProcessReport +import com.atlassian.performance.tools.ssh.api.SshConnection + +class JstatHook : PostStartHook { + + override fun call( + ssh: SshConnection, + jira: StartedJira, + hooks: PostStartHooks, + reports: Reports + ) { + val process = jira.installed.jdk.jstatMonitoring.start(ssh, jira.pid) + reports.add(RemoteMonitoringProcessReport(process), jira) + } +} 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..ee867e14 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/PostStartHook.kt @@ -0,0 +1,24 @@ +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 + +/** + * 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 hooks + * @param [reports] accumulates reports + */ + fun call( + ssh: SshConnection, + jira: StartedJira, + hooks: PostStartHooks, + reports: Reports + ) +} 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..77671948 --- /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() + + fun insert( + hook: PostStartHook + ) { + hooks.add(hook) + } + + internal fun call( + ssh: SshConnection, + jira: StartedJira, + reports: Reports + ) { + while (true) { + hooks + .poll() + ?.call(ssh, jira, this, reports) + ?: 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..0123305e --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/PreStartHook.kt @@ -0,0 +1,24 @@ +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.report.Reports +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 + * @param [reports] accumulates reports + */ + fun call( + ssh: SshConnection, + jira: InstalledJira, + hooks: PreStartHooks, + reports: Reports + ) +} 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..bc3082f4 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/PreStartHooks.kt @@ -0,0 +1,41 @@ +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.report.Reports +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() + + fun insert( + hook: PreStartHook + ) { + hooks.add(hook) + } + + internal fun call( + ssh: SshConnection, + jira: InstalledJira, + reports: Reports + ) { + while (true) { + hooks + .poll() + ?.call(ssh, jira, this, reports) + ?: break + } + } + + companion object Factory { + fun default() = PreStartHooks(PostStartHooks.default()).apply { + insert(AccessLogs()) + } + + 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..3762f7d2 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/start/hook/RestUpgrade.kt @@ -0,0 +1,89 @@ +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.install.hook.JiraLogs +import com.atlassian.performance.tools.infrastructure.api.jira.report.FileListing +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.infrastructure.api.jvm.ThreadDump +import com.atlassian.performance.tools.infrastructure.jira.report.JiraLandingPage +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, reports: Reports) { + val threadDump = ThreadDump(jira.pid, jira.installed.jdk) + val polling = Upgrades(ssh, jira, adminUsername, adminPassword, timeouts, threadDump, reports) + polling.waitUntilOnline() + polling.waitUntilHealthy() + polling.triggerUpgrades() + polling.waitUntilUpgraded() + } + + private class Upgrades( + private val ssh: SshConnection, + private val jira: StartedJira, + adminUsername: String, + adminPassword: String, + private val timeouts: JiraLaunchTimeouts, + private val threadDump: ThreadDump, + private val reports: Reports + ) { + private val upgradesEndpoint: URI = jira + .installed + .http + .addressPrivately(adminUsername, adminPassword) + .resolve("rest/api/2/upgrade") + + fun waitUntilOnline() { + waitForStatusToChange("000", timeouts.offlineTimeout) + } + + fun waitUntilHealthy() { + waitForStatusToChange("503", timeouts.initTimeout) + } + + fun waitUntilUpgraded() { + waitForStatusToChange("303", timeouts.upgradeTimeout) + } + + private fun waitForStatusToChange( + statusQuo: String, + timeout: Duration + ) { + 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 $upgradesEndpoint", + timeout = timeouts.unresponsivenessTimeout + ).output + if (currentStatus != statusQuo) { + break + } + if (deadline < Instant.now()) { + reports.add(JiraLogs().report(jira.installed), jira) + reports.add(FileListing("thread-dumps/*"), jira) + reports.add(JiraLandingPage(jira), jira) + throw Exception("$upgradesEndpoint failed to get out of $statusQuo status within $timeout") + } + threadDump.gather(ssh, "thread-dumps") + Thread.sleep(backoff.toMillis()) + } + } + + fun triggerUpgrades() { + ssh.execute( + cmd = "curl --silent --retry 6 -X POST $upgradesEndpoint", + timeout = Duration.ofSeconds(15) + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/AdoptOpenJDK.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/AdoptOpenJDK.kt index 9675b04a..811fa258 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/AdoptOpenJDK.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/AdoptOpenJDK.kt @@ -1,5 +1,6 @@ package com.atlassian.performance.tools.infrastructure.api.jvm +import com.atlassian.performance.tools.infrastructure.api.os.Ubuntu import com.atlassian.performance.tools.jvmtasks.api.ExponentialBackoff import com.atlassian.performance.tools.jvmtasks.api.IdempotentAction import com.atlassian.performance.tools.ssh.api.SshConnection @@ -17,6 +18,7 @@ class AdoptOpenJDK : VersionedJavaDevelopmentKit { override fun getMajorVersion() = 8 override fun install(connection: SshConnection) { + Ubuntu().install(connection, listOf("curl")) download(connection) connection.execute("tar -xzf $jdkArchive") connection.execute("echo '${use()}' >> ~/.bashrc") diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/loadbalancer/ApacheProxyPlan.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/loadbalancer/ApacheProxyPlan.kt new file mode 100644 index 00000000..c3c498aa --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/loadbalancer/ApacheProxyPlan.kt @@ -0,0 +1,81 @@ +package com.atlassian.performance.tools.infrastructure.api.loadbalancer + +import com.atlassian.performance.tools.infrastructure.api.Sed +import com.atlassian.performance.tools.infrastructure.api.jira.install.HttpNode +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira +import com.atlassian.performance.tools.infrastructure.api.jira.report.Reports +import com.atlassian.performance.tools.infrastructure.api.jira.start.hook.PreStartHook +import com.atlassian.performance.tools.infrastructure.api.jira.start.hook.PreStartHooks +import com.atlassian.performance.tools.infrastructure.api.network.HttpServerRoom +import com.atlassian.performance.tools.infrastructure.api.os.Ubuntu +import com.atlassian.performance.tools.jvmtasks.api.ExponentialBackoff +import com.atlassian.performance.tools.jvmtasks.api.IdempotentAction +import com.atlassian.performance.tools.ssh.api.SshConnection +import java.net.URI +import java.time.Duration + +class ApacheProxyPlan( + private val serverRoom: HttpServerRoom +) : LoadBalancerPlan { + + private val configPath = "/etc/apache2/sites-enabled/000-default.conf" + + override fun materialize(nodes: List, hooks: List): LoadBalancer { + val proxyNode = serverRoom.serveHttp("apache-proxy") + IdempotentAction("Installing and configuring apache load balancer") { + proxyNode.tcp.ssh.newConnection().use { connection -> + tryToProvision(connection, nodes, proxyNode) + } + }.retry(2, ExponentialBackoff(Duration.ofSeconds(5))) + val balancerEndpoint = proxyNode.addressPrivately() + hooks.forEach { it.insert(InjectProxy(balancerEndpoint)) } + return ApacheProxy(balancerEndpoint) + } + + private fun tryToProvision(ssh: SshConnection, nodes: List, proxyNode: HttpNode) { + Ubuntu().install(ssh, listOf("apache2")) + Sed().replace(ssh, "Listen 80", "Listen ${proxyNode.tcp.port}", "/etc/apache2/ports.conf") + ssh.execute("sudo rm $configPath") + ssh.execute("sudo touch $configPath") + val mods = listOf( + "proxy", "proxy_ajp", "proxy_http", "rewrite", "deflate", "headers", "proxy_balancer", "proxy_connect", + "proxy_html", "xml2enc", "lbmethod_byrequests" + ) + ssh.execute("sudo a2enmod ${mods.joinToString(" ")}") + appendConfig( + ssh, + "Header add Set-Cookie \\\"ROUTEID=.%{BALANCER_WORKER_ROUTE}e; path=/\\\" env=BALANCER_ROUTE_CHANGED" + ) + appendConfig(ssh, "") + nodes.forEachIndexed { index, http -> + appendConfig(ssh, "\tBalancerMember ${http.addressPrivately()} route=$index") + } + appendConfig(ssh, "\n") + appendConfig(ssh, "ProxyPass / balancer://mycluster/ stickysession=ROUTEID") + appendConfig(ssh, "ProxyPassReverse / balancer://mycluster/ stickysession=ROUTEID") + ssh.execute("sudo service apache2 restart", Duration.ofMinutes(3)) + } + + private fun appendConfig(connection: SshConnection, line: String) { + connection.execute("echo \"$line\" | sudo tee -a $configPath") + } + + private class ApacheProxy( + override val uri: URI + ) : LoadBalancer { + override fun waitUntilHealthy(timeout: Duration) {} + } + + private class InjectProxy( + private val proxy: URI + ) : PreStartHook { + override fun call(ssh: SshConnection, jira: InstalledJira, hooks: PreStartHooks, reports: Reports) { + Sed().replace( + ssh, + "bindOnInit=\"false\"", + "bindOnInit=\"false\" scheme=\"http\" proxyName=\"${proxy.host}\" proxyPort=\"${proxy.port}\"", + "${jira.installation.path}/conf/server.xml" + ) + } + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/loadbalancer/LoadBalancerPlan.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/loadbalancer/LoadBalancerPlan.kt new file mode 100644 index 00000000..c0d9cca6 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/loadbalancer/LoadBalancerPlan.kt @@ -0,0 +1,8 @@ +package com.atlassian.performance.tools.infrastructure.api.loadbalancer + +import com.atlassian.performance.tools.infrastructure.api.jira.install.HttpNode +import com.atlassian.performance.tools.infrastructure.api.jira.start.hook.PreStartHooks + +interface LoadBalancerPlan { + fun materialize(nodes: List, hooks: List): LoadBalancer +} \ No newline at end of file diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/network/HttpServerRoom.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/network/HttpServerRoom.kt new file mode 100644 index 00000000..89316ba6 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/network/HttpServerRoom.kt @@ -0,0 +1,8 @@ +package com.atlassian.performance.tools.infrastructure.api.network + +import com.atlassian.performance.tools.infrastructure.api.jira.install.HttpNode + +interface HttpServerRoom { + + fun serveHttp(name: String): HttpNode +} \ No newline at end of file diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/network/Networked.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/network/Networked.kt new file mode 100644 index 00000000..27a9ee33 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/network/Networked.kt @@ -0,0 +1,9 @@ +package com.atlassian.performance.tools.infrastructure.api.network + +interface Networked { + + /** + * @return CIDR + */ + fun subnet(): String +} \ No newline at end of file diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/network/SshServerRoom.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/network/SshServerRoom.kt new file mode 100644 index 00000000..90ae5632 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/network/SshServerRoom.kt @@ -0,0 +1,7 @@ +package com.atlassian.performance.tools.infrastructure.api.network + +import com.atlassian.performance.tools.ssh.api.Ssh + +interface SshServerRoom { + fun serveSsh(name: String): Ssh +} \ No newline at end of file diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/network/TcpServerRoom.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/network/TcpServerRoom.kt new file mode 100644 index 00000000..a8c786e6 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/network/TcpServerRoom.kt @@ -0,0 +1,12 @@ +package com.atlassian.performance.tools.infrastructure.api.network + +import com.atlassian.performance.tools.infrastructure.api.jira.install.TcpNode + +interface TcpServerRoom { + + /** + * @return reachable by the caller via [TcpNode.publicIp] and by the rest of the network via [TcpNode.privateIp] + */ + fun serveTcp(name: String): TcpNode + fun serveTcp(name: String, tcpPorts: List, udpPorts: List): TcpNode +} \ No newline at end of file diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/splunk/AtlassianSplunkForwarder.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/splunk/AtlassianSplunkForwarder.kt index 60b3425a..ef17273a 100644 --- a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/splunk/AtlassianSplunkForwarder.kt +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/api/splunk/AtlassianSplunkForwarder.kt @@ -1,7 +1,7 @@ package com.atlassian.performance.tools.infrastructure.api.splunk -import com.atlassian.performance.tools.infrastructure.DockerImage import com.atlassian.performance.tools.infrastructure.api.Sed +import com.atlassian.performance.tools.infrastructure.api.docker.DockerImage import com.atlassian.performance.tools.ssh.api.SshConnection import java.time.Duration @@ -12,7 +12,9 @@ class AtlassianSplunkForwarder( ) : SplunkForwarder { override fun run(sshConnection: SshConnection, name: String, logsPath: String) { - val logstashImage = DockerImage("docker.elastic.co/logstash/logstash-oss:6.2.4", Duration.ofMinutes(5)) + val logstashImage = DockerImage.Builder("docker.elastic.co/logstash/logstash-oss:6.2.4") + .pullTimeout(Duration.ofMinutes(5)) + .build() val logstashConfFilePath = "~/logstash.conf" sshConnection.execute("""cat > $logstashConfFilePath <<'EOF' @@ -92,4 +94,4 @@ internal class LogStashConfigBuilder(private val additionalEventFields: Map + hooks.call(ssh, http, reports) + } + val installed = installation.install(http, reports) + http.tcp.ssh.newConnection().use { ssh -> + hooks.postInstall.call(ssh, installed, reports) + } + return installed + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/install/hook/ProfilerHook.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/install/hook/ProfilerHook.kt new file mode 100644 index 00000000..3ecd6098 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/install/hook/ProfilerHook.kt @@ -0,0 +1,43 @@ +package com.atlassian.performance.tools.infrastructure.jira.install.hook + +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.install.hook.PostInstallHooks +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.infrastructure.api.jira.start.hook.PostStartHook +import com.atlassian.performance.tools.infrastructure.api.jira.start.hook.PostStartHooks +import com.atlassian.performance.tools.infrastructure.api.jira.start.hook.PreStartHook +import com.atlassian.performance.tools.infrastructure.api.profiler.Profiler +import com.atlassian.performance.tools.infrastructure.jira.report.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 a [PreStartHook] or [PostStartHook] without this bridge. + */ +class ProfilerHook( + private val profiler: Profiler +) : PostInstallHook { + override fun call(ssh: SshConnection, jira: InstalledJira, hooks: PostInstallHooks, reports: Reports) { + 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, + reports: Reports + ) { + val process = profiler.start(ssh, jira.pid) + if (process != null) { + reports.add(RemoteMonitoringProcessReport(process), jira) + } + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/install/hook/SplunkForwarderHook.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/install/hook/SplunkForwarderHook.kt new file mode 100644 index 00000000..0baf1797 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/install/hook/SplunkForwarderHook.kt @@ -0,0 +1,23 @@ +package com.atlassian.performance.tools.infrastructure.jira.install.hook + +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.install.hook.PostInstallHooks +import com.atlassian.performance.tools.infrastructure.api.jira.report.Reports +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, + reports: Reports + ) { + splunk.jsonifyLog4j(ssh, "${jira.installation.path}/atlassian-jira/WEB-INF/classes/log4j.properties") + splunk.run(ssh, jira.http.tcp.name, "/home/ubuntu/jirahome/log") + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/instance/ClusterProperties.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/instance/ClusterProperties.kt new file mode 100644 index 00000000..f0a4f310 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/instance/ClusterProperties.kt @@ -0,0 +1,13 @@ +package com.atlassian.performance.tools.infrastructure.jira.instance + +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira +import com.atlassian.performance.tools.ssh.api.SshConnection + +internal class ClusterProperties( + private val jira: InstalledJira +) { + + fun set(key: String, value: String, ssh: SshConnection) { + ssh.execute("echo '$key = $value' >> ${jira.home.path}/cluster.properties") + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/report/JiraLandingPage.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/report/JiraLandingPage.kt new file mode 100644 index 00000000..8d0b1302 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/report/JiraLandingPage.kt @@ -0,0 +1,19 @@ +package com.atlassian.performance.tools.infrastructure.jira.report + +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.os.Ubuntu +import com.atlassian.performance.tools.ssh.api.SshConnection + +internal class JiraLandingPage( + private val started: StartedJira +) : Report { + override fun locate(ssh: SshConnection): List { + Ubuntu().install(ssh, listOf("curl")) + val landingPage = started.installed.http.addressPrivately() + val html = "jira-landing-page.html" + val headers = "jira-landing-page-headers.txt" + ssh.execute("curl $landingPage --location --output $html --dump-header $headers --silent") + return listOf(html, headers) + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/report/RemoteMonitoringProcessReport.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/report/RemoteMonitoringProcessReport.kt new file mode 100644 index 00000000..6d904047 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/report/RemoteMonitoringProcessReport.kt @@ -0,0 +1,14 @@ +package com.atlassian.performance.tools.infrastructure.jira.report + +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/sharedhome/SharedHomeProperty.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/sharedhome/SharedHomeProperty.kt new file mode 100644 index 00000000..d6bda38b --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/sharedhome/SharedHomeProperty.kt @@ -0,0 +1,14 @@ +package com.atlassian.performance.tools.infrastructure.jira.sharedhome + +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira +import com.atlassian.performance.tools.infrastructure.jira.instance.ClusterProperties +import com.atlassian.performance.tools.ssh.api.SshConnection + +internal class SharedHomeProperty( + private val jira: InstalledJira +) { + + fun set(mounted: String, ssh: SshConnection) { + ClusterProperties(jira).set("jira.shared.home", mounted, ssh) + } +} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/start/JiraStart.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/start/JiraStart.kt deleted file mode 100644 index 9cb166bb..00000000 --- a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/start/JiraStart.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.atlassian.performance.tools.infrastructure.jira.start - -import com.atlassian.performance.tools.infrastructure.jira.install.InstalledJira -import net.jcip.annotations.ThreadSafe - -@ThreadSafe -interface JiraStart { - - fun start( - installed: InstalledJira - ): StartedJira -} diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/start/StartedJira.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/start/StartedJira.kt deleted file mode 100644 index a7f29f61..00000000 --- a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/start/StartedJira.kt +++ /dev/null @@ -1,8 +0,0 @@ -package com.atlassian.performance.tools.infrastructure.jira.start - -import com.atlassian.performance.tools.infrastructure.jira.install.InstalledJira - -class StartedJira( - val installed: InstalledJira, - val pid: Int -) diff --git a/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/start/hook/HookedJiraStart.kt b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/start/hook/HookedJiraStart.kt new file mode 100644 index 00000000..73d02676 --- /dev/null +++ b/src/main/kotlin/com/atlassian/performance/tools/infrastructure/jira/start/hook/HookedJiraStart.kt @@ -0,0 +1,27 @@ +package com.atlassian.performance.tools.infrastructure.jira.start.hook + +import com.atlassian.performance.tools.infrastructure.api.jira.install.InstalledJira +import com.atlassian.performance.tools.infrastructure.api.jira.report.Reports +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.infrastructure.api.jira.start.hook.PreStartHooks + +class HookedJiraStart( + private val start: JiraStart, + private val hooks: PreStartHooks +) : JiraStart { + + override fun start( + installed: InstalledJira, + reports: Reports + ): StartedJira { + installed.http.tcp.ssh.newConnection().use { ssh -> + hooks.call(ssh, installed, reports) + } + val started = start.start(installed, reports) + installed.http.tcp.ssh.newConnection().use { ssh -> + hooks.postStart.call(ssh, started, reports) + } + return started + } +} diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/Datasets.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/Datasets.kt new file mode 100644 index 00000000..b4e78b39 --- /dev/null +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/Datasets.kt @@ -0,0 +1,43 @@ +package com.atlassian.performance.tools.infrastructure + +import com.atlassian.performance.tools.infrastructure.api.database.DockerMysqlServer +import com.atlassian.performance.tools.infrastructure.api.dataset.HttpDatasetPackage +import com.atlassian.performance.tools.infrastructure.api.jira.JiraLaunchTimeouts +import com.atlassian.performance.tools.infrastructure.api.jira.instance.PreInstanceHooks +import com.atlassian.performance.tools.infrastructure.api.jira.start.hook.PostStartHooks +import com.atlassian.performance.tools.infrastructure.api.jira.start.hook.RestUpgrade +import com.atlassian.performance.tools.infrastructure.api.network.TcpServerRoom +import java.net.URI +import java.time.Duration + +class Datasets { + + object JiraSevenDataset { + private val s3Bucket = URI("https://s3-eu-west-1.amazonaws.com/") + .resolve("jpt-custom-datasets-storage-a008820-datasetbucket-1sjxdtrv5hdhj/") + .resolve("dataset-f8dba866-9d1b-492e-b76c-f4a78ac3958c/") + + private val mysql = HttpDatasetPackage( + uri = s3Bucket.resolve("database.tar.bz2"), + downloadTimeout = Duration.ofMinutes(6) + ) + + val jiraHome = HttpDatasetPackage( + uri = s3Bucket.resolve("jirahome.tar.bz2"), + downloadTimeout = Duration.ofMinutes(6) + ) + + fun hookMysql(preInstanceHooks: PreInstanceHooks, serverRoom: TcpServerRoom) { + val mysqlServer = DockerMysqlServer.Builder(serverRoom, mysql).build() + preInstanceHooks.insert(mysqlServer) + } + + fun hookMysql(postStartHooks: PostStartHooks) { + val timeouts = JiraLaunchTimeouts.Builder() + .initTimeout(Duration.ofMinutes(4)) + .build() + val dataUpgrade = RestUpgrade(timeouts, "admin", "admin") + postStartHooks.insert(dataUpgrade) + } + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/DockerIT.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/DockerIT.kt deleted file mode 100644 index fb7642de..00000000 --- a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/DockerIT.kt +++ /dev/null @@ -1,31 +0,0 @@ -package com.atlassian.performance.tools.infrastructure - -import com.atlassian.performance.tools.sshubuntu.api.SshUbuntuContainer -import com.github.dockerjava.api.model.Bind -import com.github.dockerjava.api.model.Volume -import org.junit.Test -import org.testcontainers.containers.GenericContainer -import java.time.Duration -import java.util.function.Consumer - -class DockerIT { - @Test - fun installWorks() { - SshUbuntuContainer(Consumer { enableNestedDocker(it) }).start().use { ssh -> - ssh.toSsh().newConnection().use { connection -> - //workaround for a bug in Docker download site for bionic - val packageFile = "containerd.io_1.2.2-3_amd64.deb" - connection.execute("curl -O https://download.docker.com/linux/ubuntu/dists/bionic/pool/edge/amd64/$packageFile", Duration.ofMinutes(3)) - connection.execute("sudo apt install ./$packageFile", Duration.ofMinutes(3)) - - Docker().install(connection) - DockerImage("hello-world").run(connection) - } - } - } - - private fun enableNestedDocker(container: GenericContainer<*>) { - val dockerDaemonSocket = "/var/run/docker.sock" - container.setBinds(listOf(Bind(dockerDaemonSocket, Volume(dockerDaemonSocket)))) - } -} diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/SshExtensions.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/SshExtensions.kt new file mode 100644 index 00000000..201da516 --- /dev/null +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/SshExtensions.kt @@ -0,0 +1,9 @@ +package com.atlassian.performance.tools.infrastructure + +import com.atlassian.performance.tools.ssh.api.SshConnection + +internal fun SshConnection.SshResult.assertInterruptedJava() { + if (exitStatus !in listOf(0, 130)) { + throw Exception("$this doesn't look like an interrupted Java process") + } +} \ No newline at end of file diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/SshUbuntuExtensions.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/SshUbuntuExtensions.kt deleted file mode 100644 index 73d3a946..00000000 --- a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/SshUbuntuExtensions.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.atlassian.performance.tools.infrastructure - -import com.atlassian.performance.tools.ssh.api.Ssh -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( - ipAddress = ipAddress, - userName = userName, - authentication = com.atlassian.performance.tools.ssh.api.auth.PublicKeyAuthentication(privateKey), - port = port - ) - }) - ssh.newConnection().use { connection -> - connection.execute("apt-get update -qq", Duration.ofMinutes(3)) - 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/DockerInfrastructure.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/DockerInfrastructure.kt new file mode 100644 index 00000000..eb64a04b --- /dev/null +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/DockerInfrastructure.kt @@ -0,0 +1,182 @@ +package com.atlassian.performance.tools.infrastructure.api + +import com.atlassian.performance.tools.infrastructure.api.jira.install.HttpNode +import com.atlassian.performance.tools.infrastructure.api.jira.install.TcpNode +import com.atlassian.performance.tools.infrastructure.api.network.HttpServerRoom +import com.atlassian.performance.tools.infrastructure.api.network.Networked +import com.atlassian.performance.tools.infrastructure.api.network.SshServerRoom +import com.atlassian.performance.tools.infrastructure.api.network.TcpServerRoom +import com.atlassian.performance.tools.infrastructure.lib.docker.CreatedContainer +import com.atlassian.performance.tools.infrastructure.lib.docker.DockerNetwork +import com.atlassian.performance.tools.infrastructure.lib.docker.StartedContainer +import com.atlassian.performance.tools.infrastructure.lib.docker.execAsResource +import com.atlassian.performance.tools.ssh.api.Ssh +import com.atlassian.performance.tools.ssh.api.SshHost +import com.atlassian.performance.tools.ssh.api.auth.PasswordAuthentication +import com.github.dockerjava.api.DockerClient +import com.github.dockerjava.api.command.PullImageResultCallback +import com.github.dockerjava.api.model.* +import com.github.dockerjava.core.DefaultDockerClientConfig +import com.github.dockerjava.core.DockerClientImpl +import com.github.dockerjava.zerodep.ZerodepDockerHttpClient +import java.time.Duration +import java.util.* +import java.util.UUID.randomUUID +import java.util.concurrent.ConcurrentLinkedDeque + +internal class DockerInfrastructure : SshServerRoom, TcpServerRoom, HttpServerRoom, Networked, AutoCloseable { + + private val allocatedResources: Deque = ConcurrentLinkedDeque() + private val docker: DockerClient + private val network: DockerNetwork + private val subnetCidr: String + + init { + val dockerConfig = DefaultDockerClientConfig.createDefaultConfigBuilder().build() + val dockerHttp = ZerodepDockerHttpClient.Builder().dockerHost(dockerConfig.dockerHost).build() + docker = DockerClientImpl.getInstance(dockerConfig, dockerHttp) + allocatedResources.add(docker) + network = docker + .createNetworkCmd() + .withName(randomUUID().toString()) + .execAsResource(docker) + allocatedResources.add(network) + subnetCidr = docker + .inspectNetworkCmd() + .withNetworkId(network.response.id) + .exec() + .ipam + .config + .first() + .subnet + } + + fun serveTest(): Ssh { + return serveSsh("ssh") + } + + override fun serveSsh(name: String): Ssh { + return serveTcp(888, name).ssh + } + + + override fun serveTcp(name: String): TcpNode { + // TODO pre-provision all the hosts rather than on-demand - unlock batch provisioning (CFN Stack), picking EC2 types, SSD storage, TCP port ranges, subnets, etc. + return when { + name.startsWith("jira-node") -> serveTcp(8080, name) // TODO this is a contract on undocumented behavior + name.startsWith("mysql") -> serveTcp(3306, name) + name.startsWith("samba") -> serveTcp(3306, name) + else -> serveTcp(888, name) + } + } + + override fun serveHttp(name: String): HttpNode { + return HttpNode( + serveTcp(80, name), + "/", + false + ) + } + + private fun serveTcp(tcpPort: Int, name: String): TcpNode { + return serveTcp(name, listOf(tcpPort), emptyList()) + } + + + override fun serveTcp(name: String, tcpPorts: List, udpPorts: List): TcpNode { + val ports = tcpPorts.map { ExposedPort.tcp(it) } + + udpPorts.map { ExposedPort.udp(it) } + + ExposedPort.tcp(22) + docker + .pullImageCmd("rastasheep/ubuntu-sshd") + .withTag("18.04") + .exec(PullImageResultCallback()) + .awaitCompletion() + val createdContainer = docker + .createContainerCmd("rastasheep/ubuntu-sshd:18.04") + .withHostConfig( + HostConfig() + .withPublishAllPorts(true) + .withPrivileged(true) +// .withBinds(Bind("/var/run/docker.sock", Volume("/var/run/docker.sock"))) +// .withMounts( +// Mount() +// .withSource("/var/run/docker.sock") +// .withTarget("/var/run/docker.sock") +// .withType(MountType.VOLUME) +// .let { listOf(it) } +// ) + .withNetworkMode(network.response.id) + ) +// .withVolumes(Volume("/var/run/docker.sock")) + .withExposedPorts(ports) + .withName("$name-${randomUUID()}") + .execAsResource(docker) + allocatedResources.addLast(createdContainer) + return start(createdContainer, tcpPorts.first(), name) + } + + private fun start( + created: CreatedContainer, + tcpPort: Int, + name: String + ): TcpNode { + val startedContainer = docker + .startContainerCmd(created.response.id) + .execAsResource(docker) + allocatedResources.addLast(startedContainer); + return install(startedContainer, tcpPort, name) + } + + private fun install( + started: StartedContainer, + tcpPort: Int, + name: String + ): TcpNode { + val networkSettings = docker + .inspectContainerCmd(started.id) + .exec() + .networkSettings + val ip = networkSettings + .networks + .values + .single { it.networkID == network.response.id } + .ipAddress!! + val sshPort = getHostPort(networkSettings, ExposedPort.tcp(22)) + val sshHost = SshHost( + ipAddress = "localhost", + userName = "root", + authentication = PasswordAuthentication("root"), + port = sshPort + ) + val ssh = Ssh(sshHost) + ssh.newConnection().use { + it.execute("apt-get update", Duration.ofMinutes(2)) + it.execute("apt-get -y install sudo gnupg screen") + } + return TcpNode("localhost", ip, tcpPort, name, ssh) + } + + private fun getHostPort( + networkSettings: NetworkSettings, + port: ExposedPort + ): Int { + return networkSettings + .ports + .bindings[port]!! + .single { it.hostIp == "0.0.0.0" } // include just the IP4 bind + .hostPortSpec + .toInt() + } + + override fun close() { + while (true) { + allocatedResources + .pollLast() + ?.use {} + ?: break + } + } + + override fun subnet(): String = subnetCidr +} \ No newline at end of file diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/browser/ChromeIT.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/browser/ChromeIT.kt index e1891d71..383b8791 100644 --- a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/browser/ChromeIT.kt +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/browser/ChromeIT.kt @@ -1,8 +1,7 @@ package com.atlassian.performance.tools.infrastructure.api.browser -import com.atlassian.performance.tools.infrastructure.toSsh +import com.atlassian.performance.tools.infrastructure.api.DockerInfrastructure import com.atlassian.performance.tools.ssh.api.SshConnection -import com.atlassian.performance.tools.sshubuntu.api.SshUbuntuContainer import org.hamcrest.Matchers import org.junit.Assert import org.junit.Test @@ -12,8 +11,8 @@ class ChromeIT { @Test fun shouldInstallChromeBrowser() { - SshUbuntuContainer().start().use { ssh -> - ssh.toSsh().newConnection().use { connection -> + DockerInfrastructure().use { infra -> + infra.serveSsh("ChromeIT").newConnection().use { connection -> val wasInstalledBefore = isChromeInstalled(connection) Chrome().install(connection) @@ -24,7 +23,6 @@ class ChromeIT { Assert.assertThat(isInstalledAfter, Matchers.`is`(true)) Assert.assertThat(isChromedriverInstalled(connection), Matchers.`is`(true)) } - } } diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/browser/chromium/Chromium69IT.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/browser/chromium/Chromium69IT.kt index 11b31466..331a9c45 100644 --- a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/browser/chromium/Chromium69IT.kt +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/browser/chromium/Chromium69IT.kt @@ -1,10 +1,9 @@ package com.atlassian.performance.tools.infrastructure.api.browser.chromium -import com.atlassian.performance.tools.infrastructure.toSsh +import com.atlassian.performance.tools.infrastructure.api.DockerInfrastructure 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 com.atlassian.performance.tools.sshubuntu.api.SshUbuntuContainer import org.hamcrest.Matchers import org.junit.Assert import org.junit.Test @@ -14,8 +13,8 @@ class Chromium69IT { @Test fun shouldInstallBrowser() { - SshUbuntuContainer().start().use { sshUbuntu -> - sshUbuntu.toSsh().newConnection().use { connection -> + DockerInfrastructure().use { infra -> + infra.serveTest().newConnection().use { connection -> val installedBefore = isChromiumInstalled(connection) Chromium69().install(connection) diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/browser/chromium/PageLoadTimeoutRecoveryTest.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/browser/chromium/PageLoadTimeoutRecoveryTest.kt index 8a8af541..40b4db89 100644 --- a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/browser/chromium/PageLoadTimeoutRecoveryTest.kt +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/browser/chromium/PageLoadTimeoutRecoveryTest.kt @@ -1,10 +1,9 @@ package com.atlassian.performance.tools.infrastructure.api.browser.chromium +import com.atlassian.performance.tools.infrastructure.api.DockerInfrastructure import com.atlassian.performance.tools.infrastructure.api.browser.Browser import com.atlassian.performance.tools.infrastructure.browser.SshChromium import com.atlassian.performance.tools.infrastructure.mock.MockHttpServer -import com.atlassian.performance.tools.infrastructure.toSsh -import com.atlassian.performance.tools.sshubuntu.api.SshUbuntuContainer import com.sun.net.httpserver.HttpExchange import org.assertj.core.api.Assertions import org.openqa.selenium.TimeoutException @@ -20,8 +19,8 @@ internal class PageLoadTimeoutRecoveryTest { MockHttpServer().start().use { httpServer -> val fastResource = httpServer.register(FastResponse()) val slowResource = httpServer.register(SlowResponse()) - SshUbuntuContainer().start().use { sshUbuntu -> - val ssh = sshUbuntu.toSsh() + DockerInfrastructure().use { infra -> + val ssh = infra.serveTest() ssh.forwardRemotePort(httpServer.getPort(), httpServer.getPort()).use { val localChromedriverPort = findFreePort() ssh.forwardLocalPort(localChromedriverPort, remoteChromedriverPort).use { @@ -29,7 +28,7 @@ internal class PageLoadTimeoutRecoveryTest { chromium.install(connection) } val chromedriverUri = URI("http://localhost:$localChromedriverPort") - SshChromium(ssh.newConnection(), chromedriverUri).start().use { sshDriver -> + SshChromium(ssh, chromedriverUri).start().use { sshDriver -> val driver = sshDriver.getDriver() setPageLoadTimeout(driver) @@ -47,7 +46,7 @@ internal class PageLoadTimeoutRecoveryTest { } private fun findFreePort(): Int { - return ServerSocket(0).use { socket -> return socket.localPort } + return ServerSocket(0).use { socket -> socket.localPort } } private class FastResponse : MockHttpServer.RequestHandler { diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/database/LicenseOverridingMysqlTest.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/database/LicenseOverridingMysqlTest.kt index c41a4867..55d23970 100644 --- a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/database/LicenseOverridingMysqlTest.kt +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/database/LicenseOverridingMysqlTest.kt @@ -117,13 +117,13 @@ class LicenseOverridingMysqlTest { private fun generateExpectedCommands(ssh: RememberingSshConnection): List { return listOf( // essentially, delete all existing licences - """mysql -h 127.0.0.1 -u root -e "DELETE FROM jiradb.productlicense;"""", + """mysql -h 127.0.0.1 -P 3306 -u root -e "DELETE FROM jiradb.productlicense;"""", // then import the new ones *ssh.uploads .map { listOf( - """mysql -h 127.0.0.1 -u root < ${it.remoteDestination}""", + """mysql -h 127.0.0.1 -P 3306 -u root < ${it.remoteDestination}""", """rm ${it.remoteDestination}""" ) } diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/dataset/HttpDatasetPackageIT.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/dataset/HttpDatasetPackageIT.kt index 6f5f92f7..0281cb58 100644 --- a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/dataset/HttpDatasetPackageIT.kt +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/dataset/HttpDatasetPackageIT.kt @@ -1,9 +1,8 @@ package com.atlassian.performance.tools.infrastructure.api.dataset import com.atlassian.performance.tools.infrastructure.Ls -import com.atlassian.performance.tools.infrastructure.toSsh +import com.atlassian.performance.tools.infrastructure.api.DockerInfrastructure import com.atlassian.performance.tools.ssh.api.Ssh -import com.atlassian.performance.tools.sshubuntu.api.SshUbuntuContainer import org.assertj.core.api.Assertions import org.junit.Test import java.net.URI @@ -21,8 +20,8 @@ class HttpDatasetPackageIT { downloadTimeout = Duration.ofMinutes(1) ) - val filesInDataset = SshUbuntuContainer().start().use { sshUbuntu -> - val ssh = sshUbuntu.toSsh() + val filesInDataset = DockerInfrastructure().use { infra -> + val ssh = infra.serveSsh("HttpDatasetPackageIT") return@use RandomFilesGenerator(ssh).start().use { ssh.newConnection().use { connection -> val unpackedPath = dataset.download(connection) diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/distribution/PublicJiraServiceDeskDistributionIT.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/distribution/PublicJiraServiceDeskDistributionIT.kt index b6f351bb..d27a0666 100644 --- a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/distribution/PublicJiraServiceDeskDistributionIT.kt +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/distribution/PublicJiraServiceDeskDistributionIT.kt @@ -1,7 +1,6 @@ package com.atlassian.performance.tools.infrastructure.api.distribution -import com.atlassian.performance.tools.infrastructure.toSsh -import com.atlassian.performance.tools.sshubuntu.api.SshUbuntuContainer +import com.atlassian.performance.tools.infrastructure.api.DockerInfrastructure import org.assertj.core.api.Assertions.assertThat import org.junit.Test @@ -9,8 +8,8 @@ class PublicJiraServiceDeskDistributionIT { @Test fun shouldDownloadJiraServiceDesk() { - SshUbuntuContainer().start().use { ssh -> - ssh.toSsh().newConnection().use { connection -> + DockerInfrastructure().use { infra -> + infra.serveTest().newConnection().use { connection -> val serviceDeskDistribution: ProductDistribution = PublicJiraServiceDeskDistribution("4.0.1") val targetFolder = "test" connection.execute("mkdir $targetFolder") diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/distribution/PublicJiraSoftwareDistributionsIT.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/distribution/PublicJiraSoftwareDistributionsIT.kt index a77a70c4..6dd74c85 100644 --- a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/distribution/PublicJiraSoftwareDistributionsIT.kt +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/distribution/PublicJiraSoftwareDistributionsIT.kt @@ -1,7 +1,6 @@ package com.atlassian.performance.tools.infrastructure.api.distribution -import com.atlassian.performance.tools.infrastructure.toSsh -import com.atlassian.performance.tools.sshubuntu.api.SshUbuntuContainer +import com.atlassian.performance.tools.infrastructure.api.DockerInfrastructure import org.assertj.core.api.Assertions.assertThat import org.junit.Test @@ -9,8 +8,8 @@ class PublicJiraSoftwareDistributionsIT { @Test fun shouldDownloadJiraSoftware() { - SshUbuntuContainer().start().use { ssh -> - ssh.toSsh().newConnection().use { connection -> + DockerInfrastructure().use { infra -> + infra.serveTest().newConnection().use { connection -> val jiraDistribution: ProductDistribution = PublicJiraSoftwareDistribution("7.2.0") val targetFolder = "test" connection.execute("mkdir $targetFolder") diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/docker/DockerIT.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/docker/DockerIT.kt new file mode 100644 index 00000000..ecd1f8b5 --- /dev/null +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/docker/DockerIT.kt @@ -0,0 +1,24 @@ +package com.atlassian.performance.tools.infrastructure.api.docker + +import com.atlassian.performance.tools.infrastructure.api.DockerInfrastructure +import com.atlassian.performance.tools.infrastructure.api.os.Ubuntu +import org.junit.Test +import java.time.Duration + +class DockerIT { + @Test + fun installWorks() { + DockerInfrastructure().use { infra -> + infra.serveTest().newConnection().use { connection -> + //workaround for a bug in Docker download site for bionic + val packageFile = "containerd.io_1.2.2-3_amd64.deb" + Ubuntu().install(connection,listOf( "curl")) + connection.execute("curl -O https://download.docker.com/linux/ubuntu/dists/bionic/pool/edge/amd64/$packageFile", Duration.ofMinutes(3)) + connection.execute("sudo apt install ./$packageFile", Duration.ofMinutes(3)) + + Docker.Builder().build().install(connection) + DockerImage.Builder("hello-world").build().run(connection) + } + } + } +} 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..b94cfca2 --- /dev/null +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/install/hook/PreInstallHooksTest.kt @@ -0,0 +1,67 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.install.hook + +import com.atlassian.performance.tools.infrastructure.api.jira.install.HttpNode +import com.atlassian.performance.tools.infrastructure.api.jira.install.TcpNode +import com.atlassian.performance.tools.infrastructure.api.jira.report.Reports +import com.atlassian.performance.tools.infrastructure.mock.UnimplementedSshConnection +import com.atlassian.performance.tools.ssh.api.Ssh +import com.atlassian.performance.tools.ssh.api.SshConnection +import com.atlassian.performance.tools.ssh.api.SshHost +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import java.nio.file.Paths + +class PreInstallHooksTest { + + private val dummySsh = Ssh(SshHost("localhost", "dummyUser", Paths.get("dummyKey"))) + private val dummyHttp = HttpNode( + TcpNode("dummyPublicIp", "dummyPrivateIp", 123, "fake-server", dummySsh), + "/", + false + ) + + @Test + fun shouldInsertDuringListing() { + val counter = CountingHook() + val hooks = PreInstallHooks.empty().apply { + insert(counter) + insert(InsertingHook(counter)) + insert(counter) + } + + hooks.call(UnimplementedSshConnection(), dummyHttp, Reports()) + + assertThat(counter.count).isEqualTo(3) + } + + @Test + fun shouldHookToTheTailDuringListing() { + val counter = CountingHook() + val hooks = PreInstallHooks.empty().apply { + insert(counter) + insert(counter) + insert(InsertingHook(counter)) + } + + hooks.call(UnimplementedSshConnection(), dummyHttp, Reports()) + + assertThat(counter.count).isEqualTo(3) + } +} + +private class CountingHook : PreInstallHook { + + var count = 0 + + override fun call(ssh: SshConnection, http: HttpNode, hooks: PreInstallHooks, reports: Reports) { + count++ + } +} + +private class InsertingHook( + private val hook: PreInstallHook +) : PreInstallHook { + override fun call(ssh: SshConnection, http: HttpNode, hooks: PreInstallHooks, reports: Reports) { + hooks.insert(hook) + } +} diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/JiraDataCenterPlanIT.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/JiraDataCenterPlanIT.kt new file mode 100644 index 00000000..104e61fa --- /dev/null +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/JiraDataCenterPlanIT.kt @@ -0,0 +1,140 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.instance + +import com.atlassian.performance.tools.infrastructure.Datasets +import com.atlassian.performance.tools.infrastructure.api.DockerInfrastructure +import com.atlassian.performance.tools.infrastructure.api.distribution.PublicJiraSoftwareDistribution +import com.atlassian.performance.tools.infrastructure.api.jira.JiraHomePackage +import com.atlassian.performance.tools.infrastructure.api.jira.install.ParallelInstallation +import com.atlassian.performance.tools.infrastructure.api.jira.install.hook.PreInstallHooks +import com.atlassian.performance.tools.infrastructure.api.jira.report.Reports +import com.atlassian.performance.tools.infrastructure.api.jira.sharedhome.SambaSharedHome +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.jira.start.hook.PostStartHook +import com.atlassian.performance.tools.infrastructure.api.jira.start.hook.PostStartHooks +import com.atlassian.performance.tools.infrastructure.api.jvm.AdoptOpenJDK +import com.atlassian.performance.tools.infrastructure.api.loadbalancer.ApacheProxyPlan +import com.atlassian.performance.tools.ssh.api.SshConnection +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.catchThrowable +import org.junit.After +import org.junit.Before +import org.junit.Test +import java.nio.file.Files +import java.time.Duration.ofMinutes + +class JiraDataCenterPlanIT { + + private lateinit var infrastructure: DockerInfrastructure + + @Before + fun setUp() { + infrastructure = DockerInfrastructure() + } + + @After + fun tearDown() { + infrastructure.close() + } + + @Test + fun shouldStartDataCenter() { + // given + val jiraHomeSource = JiraHomePackage(Datasets.JiraSevenDataset.jiraHome) + val nodePlans = listOf(1, 2).map { + val nodeHooks = PreInstallHooks.default() + .also { Datasets.JiraSevenDataset.hookMysql(it.postStart) } + JiraNodePlan.Builder(infrastructure) + .installation( + ParallelInstallation( + jiraHomeSource = jiraHomeSource, + productDistribution = PublicJiraSoftwareDistribution("7.13.0"), + jdk = AdoptOpenJDK() + ) + ) + .start(JiraLaunchScript()) + .hooks(nodeHooks) + .build() + } + val instanceHooks = PreInstanceHooks.default() + .also { Datasets.JiraSevenDataset.hookMysql(it, infrastructure) } + .also { it.insert(SambaSharedHome(jiraHomeSource, infrastructure)) } + val dcPlan = JiraDataCenterPlan.Builder(infrastructure) + .nodePlans(nodePlans) + .instanceHooks(instanceHooks) + .balancerPlan(ApacheProxyPlan(infrastructure)) + .build() + + // when + val dataCenter = dcPlan.materialize() + + // then + dataCenter.nodes.forEach { node -> + val installed = node.installed + val serverXml = installed + .installation + .resolve("conf/server.xml") + .download(Files.createTempFile("downloaded-config", ".xml")) + assertThat(serverXml.readText()).contains(" + ssh.execute("wget ${dataCenter.address}") + } + } + } + + + @Test + fun shouldProvideLogsToDiagnoseFailure() { + // given + class FailingHook : PostStartHook { + override fun call(ssh: SshConnection, jira: StartedJira, hooks: PostStartHooks, reports: Reports) { + val installed = jira.installed + ssh.execute("${installed.jdk.use()}; ${installed.installation.path}/bin/stop-jira.sh", ofMinutes(1)) + throw Exception("Failing deliberately after Jira started") + } + } + + val nodePlans = listOf(1, 2).map { + JiraNodePlan.Builder(infrastructure) + .installation( + ParallelInstallation( + jiraHomeSource = JiraHomePackage(Datasets.JiraSevenDataset.jiraHome), + productDistribution = PublicJiraSoftwareDistribution("7.13.0"), + jdk = AdoptOpenJDK() + ) + ) + .start(JiraLaunchScript()) + .hooks(PreInstallHooks.default().also { it.postStart.insert(FailingHook()) }) + .build() + } + val dcPlan = JiraDataCenterPlan.Builder(infrastructure) + .nodePlans(nodePlans) + .build() + + // when + val thrown = catchThrowable { + dcPlan.materialize() + } + + val reports = dcPlan.report().downloadTo(Files.createTempDirectory("jira-dc-plan-")) + // then + assertThat(thrown).hasMessageStartingWith("Failing deliberately") + assertThat(reports).isDirectory() + val fileTree = reports + .walkTopDown() + .map { reports.toPath().relativize(it.toPath()) } + .toList() + assertThat(fileTree.map { it.toString() }).contains( + "jira-node-1/root/atlassian-jira-software-7.13.0-standalone/logs/catalina.out", + "jira-node-1/root/~/jpt-jstat.log", + "jira-node-2/root/atlassian-jira-software-7.13.0-standalone/logs/catalina.out" + ) + assertThat(fileTree.filter { it.fileName.toString() == "atlassian-jira.log" }) + .`as`("Jira log from $fileTree") + .isNotEmpty + assertThat(fileTree.filter { it.fileName.toString().startsWith("atlassian-jira-gc") }) + .`as`("GC logs from $fileTree") + .isNotEmpty + } +} diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/JiraServerPlanIT.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/JiraServerPlanIT.kt new file mode 100644 index 00000000..84fb6c33 --- /dev/null +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jira/instance/JiraServerPlanIT.kt @@ -0,0 +1,109 @@ +package com.atlassian.performance.tools.infrastructure.api.jira.instance + +import com.atlassian.performance.tools.infrastructure.Datasets +import com.atlassian.performance.tools.infrastructure.api.DockerInfrastructure +import com.atlassian.performance.tools.infrastructure.api.distribution.PublicJiraSoftwareDistribution +import com.atlassian.performance.tools.infrastructure.api.jira.JiraHomePackage +import com.atlassian.performance.tools.infrastructure.api.jira.install.ParallelInstallation +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.jvm.AdoptOpenJDK +import com.atlassian.performance.tools.io.api.resolveSafely +import org.assertj.core.api.Assertions.assertThat +import org.junit.After +import org.junit.Before +import org.junit.Test +import java.nio.file.Files +import java.nio.file.Paths +import java.time.Instant + +class JiraServerPlanIT { + + private lateinit var infrastructure: DockerInfrastructure + + @Before + fun setUp() { + infrastructure = DockerInfrastructure() + } + + @After + fun tearDown() { + infrastructure.close() + } + + @Test + fun shouldStartJiraWithHooks() { + // given + val hooks = PreInstallHooks.default() + .also { Datasets.JiraSevenDataset.hookMysql(it.postStart) } + val nodePlan = JiraNodePlan.Builder(infrastructure) + .hooks(hooks) + .installation( + ParallelInstallation( + jiraHomeSource = JiraHomePackage(Datasets.JiraSevenDataset.jiraHome), + productDistribution = PublicJiraSoftwareDistribution("7.13.0"), + jdk = AdoptOpenJDK() + ) + ) + .start(JiraLaunchScript()) + .hooks(hooks) + .build() + val instanceHooks = PreInstanceHooks.default() + .also { Datasets.JiraSevenDataset.hookMysql(it, infrastructure) } + val jiraServerPlan = JiraServerPlan.Builder(infrastructure) + .plan(nodePlan) + .hooks(instanceHooks) + .build() + + // when + val jiraServer = try { + jiraServerPlan.materialize() + } catch (e: Exception) { + debug(jiraServerPlan, e) + } + val reports = jiraServerPlan.report().downloadTo(Files.createTempDirectory("jira-server-plan-")) + + // then + val theNode = jiraServer.nodes.single() + val serverXml = theNode + .installed + .installation + .resolve("conf/server.xml") + .download(Files.createTempFile("downloaded-config", ".xml")) + assertThat(serverXml.readText()).contains(" - ssh.toSsh().newConnection().use { connection -> - JstatSupport(AdoptOpenJDK11()).shouldSupportJstat(connection) - } + DockerInfrastructure().use { infra -> + JstatSupport(AdoptOpenJDK11(), infra.serveTest()).shouldSupportJstat() } } -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/AdoptOpenJdkIT.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/AdoptOpenJdkIT.kt index 6f8aadb8..fd66fcd1 100644 --- a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/AdoptOpenJdkIT.kt +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/AdoptOpenJdkIT.kt @@ -1,17 +1,14 @@ package com.atlassian.performance.tools.infrastructure.api.jvm -import com.atlassian.performance.tools.infrastructure.toSsh -import com.atlassian.performance.tools.sshubuntu.api.SshUbuntuContainer +import com.atlassian.performance.tools.infrastructure.api.DockerInfrastructure import org.junit.Test class AdoptOpenJdkIT { @Test fun shouldSupportJstat() { - SshUbuntuContainer().start().use { ssh -> - ssh.toSsh().newConnection().use { connection -> - JstatSupport(AdoptOpenJDK()).shouldSupportJstat(connection) - } + DockerInfrastructure().use { infra -> + JstatSupport(AdoptOpenJDK(), infra.serveTest()).shouldSupportJstat() } } } \ No newline at end of file diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/JstatSupport.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/JstatSupport.kt index 2fdddfeb..77b8a80d 100644 --- a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/JstatSupport.kt +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/JstatSupport.kt @@ -1,38 +1,34 @@ package com.atlassian.performance.tools.infrastructure.api.jvm +import com.atlassian.performance.tools.infrastructure.api.os.Ubuntu +import com.atlassian.performance.tools.infrastructure.assertInterruptedJava import com.atlassian.performance.tools.jvmtasks.api.ExponentialBackoff import com.atlassian.performance.tools.jvmtasks.api.IdempotentAction +import com.atlassian.performance.tools.ssh.api.Ssh import com.atlassian.performance.tools.ssh.api.SshConnection -import org.assertj.core.api.Assertions +import org.assertj.core.api.Assertions.assertThat import java.io.File import java.time.Duration class JstatSupport( - private val jdk: VersionedJavaDevelopmentKit + private val jdk: VersionedJavaDevelopmentKit, + private val ssh: Ssh ) { - private val expectedStats: Set = setOf( - "Timestamp", - "S0", - "S1", - "E", - "O", - "M", - "CCS", - "YGC", - "YGCT", - "FGC", - "FGCT", - "GCT" - ) private val jarName = "hello-world-after-1m-wait.jar" - private val jar = "/com/atlassian/performance/tools/infrastructure/api/jvm/$jarName" + private val jarResource = "/com/atlassian/performance/tools/infrastructure/api/jvm/$jarName" private val timestampLength = "2018-12-17T14:10:44+00:00 ".length - fun shouldSupportJstat(connection: SshConnection) { - connection.execute("apt-get install curl screen -y -qq", Duration.ofMinutes(2)) - connection.upload(File(this.javaClass.getResource(jar).toURI()), jarName) + fun shouldSupportJstat() { + ssh.newConnection().use { connection -> + shouldSupportJstat(connection) + } + } + + private fun shouldSupportJstat(connection: SshConnection) { + Ubuntu().install(connection, listOf("curl", "screen"), Duration.ofMinutes(2)) + connection.upload(File(javaClass.getResource(jarResource).toURI()), jarName) jdk.install(connection) - connection.startProcess(jdk.command("-classpath $jarName samples.HelloWorld")) + val hello = ssh.runInBackground(jdk.command("-classpath $jarName samples.HelloWorld")) val pid = IdempotentAction( description = "Wait for the Hello, World! process to start.", action = { connection.execute("cat hello-world.pid").output } @@ -43,13 +39,16 @@ class JstatSupport( val jstatMonitoring = jdk.jstatMonitoring.start(connection, pid.toInt()) waitForJstatToCollectSomeData() jstatMonitoring.stop(connection) + hello.stop(Duration.ofSeconds(1)).assertInterruptedJava(); val jstatLog = connection.execute("cat ${jstatMonitoring.getResultPath()}").output val jstatHeader = jstatLog.substring(timestampLength, jstatLog.indexOf('\n')) - Assertions.assertThat(jstatHeader).contains(this.expectedStats) + assertThat(jstatHeader).contains( + "Timestamp", "S0", "S1", "E", "O", "M", "CCS", "YGC", "YGCT", "FGC", "FGCT", "GCT" + ) } private fun waitForJstatToCollectSomeData() { - Thread.sleep(4 * 1000) + Thread.sleep(Duration.ofSeconds(4).toMillis()) } } diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/OpenJdk11IT.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/OpenJdk11IT.kt index b2fb592e..a22bcffb 100644 --- a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/OpenJdk11IT.kt +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/OpenJdk11IT.kt @@ -1,17 +1,14 @@ package com.atlassian.performance.tools.infrastructure.api.jvm -import com.atlassian.performance.tools.infrastructure.toSsh -import com.atlassian.performance.tools.sshubuntu.api.SshUbuntuContainer +import com.atlassian.performance.tools.infrastructure.api.DockerInfrastructure import org.junit.Test class OpenJdk11IT { @Test fun shouldSupportJstat() { - SshUbuntuContainer().start().use { ssh -> - ssh.toSsh().newConnection().use { connection -> - JstatSupport(OpenJDK11()).shouldSupportJstat(connection) - } + DockerInfrastructure().use { infra -> + JstatSupport(OpenJDK11(), infra.serveTest()).shouldSupportJstat() } } } \ No newline at end of file diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/OpenJdkIT.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/OpenJdkIT.kt index e9e5c4c1..69da4f38 100644 --- a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/OpenJdkIT.kt +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/OpenJdkIT.kt @@ -1,17 +1,14 @@ package com.atlassian.performance.tools.infrastructure.api.jvm -import com.atlassian.performance.tools.infrastructure.toSsh -import com.atlassian.performance.tools.sshubuntu.api.SshUbuntuContainer +import com.atlassian.performance.tools.infrastructure.api.DockerInfrastructure import org.junit.Test class OpenJdkIT { @Test fun shouldSupportJstat() { - SshUbuntuContainer().start().use { ssh -> - ssh.toSsh().newConnection().use { connection -> - JstatSupport(OpenJDK()).shouldSupportJstat(connection) - } + DockerInfrastructure().use { infra -> + JstatSupport(OpenJDK(), infra.serveTest()).shouldSupportJstat() } } } \ No newline at end of file diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/OracleJdkIT.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/OracleJdkIT.kt index 4b93b70a..22b267fd 100644 --- a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/OracleJdkIT.kt +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/OracleJdkIT.kt @@ -1,19 +1,17 @@ package com.atlassian.performance.tools.infrastructure.api.jvm -import com.atlassian.performance.tools.infrastructure.toSsh -import com.atlassian.performance.tools.sshubuntu.api.SshUbuntuContainer +import com.atlassian.performance.tools.infrastructure.api.DockerInfrastructure import org.junit.Test class OracleJdkIT { @Test fun shouldSupportJstatAndThreadDumps() { - SshUbuntuContainer().start().use { ssh -> - ssh.toSsh().newConnection().use { connection -> - val jdk = OracleJDK() - JstatSupport(jdk).shouldSupportJstat(connection) - ThreadDumpTest().shouldGatherThreadDump(jdk, connection) - } + val jdk = OracleJDK() + DockerInfrastructure().use { infra -> + val ssh = infra.serveTest() + JstatSupport(jdk, ssh).shouldSupportJstat() + ThreadDumpTest(jdk, ssh).shouldGatherThreadDump() } } } \ No newline at end of file diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/ThreadDumpIT.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/ThreadDumpIT.kt index bd1d0608..fcd0f871 100644 --- a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/ThreadDumpIT.kt +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/jvm/ThreadDumpIT.kt @@ -1,17 +1,28 @@ package com.atlassian.performance.tools.infrastructure.api.jvm +import com.atlassian.performance.tools.infrastructure.assertInterruptedJava import com.atlassian.performance.tools.jvmtasks.api.IdempotentAction import com.atlassian.performance.tools.jvmtasks.api.StaticBackoff +import com.atlassian.performance.tools.ssh.api.Ssh import com.atlassian.performance.tools.ssh.api.SshConnection -import org.assertj.core.api.Assertions +import org.assertj.core.api.Assertions.assertThat import java.time.Duration -class ThreadDumpTest { - fun shouldGatherThreadDump(jdk: JavaDevelopmentKit, connection: SshConnection) { +class ThreadDumpTest( + private val jdk: JavaDevelopmentKit, + private val ssh: Ssh +) { + fun shouldGatherThreadDump() { + ssh.newConnection().use { connection -> + shouldGatherThreadDump(connection) + } + } + + private fun shouldGatherThreadDump(connection: SshConnection) { val destination = "thread-dumps" connection.execute("""echo "public class Test { public static void main(String[] args) { try { Thread.sleep(java.time.Duration.ofMinutes(1).toMillis()); } catch (InterruptedException e) { throw new RuntimeException(e); } }}" > Test.java """.trimIndent()) connection.execute("${jdk.use()}; javac Test.java") - val process = connection.startProcess("${jdk.use()}; java Test") + val process = ssh.runInBackground("${jdk.use()}; java Test") try { val pid = IdempotentAction("Get PID") { getPid(connection, jdk) @@ -21,11 +32,9 @@ class ThreadDumpTest { val threadDumpFile = connection.execute("ls $destination").output val threadDump = connection.execute("cat $destination/$threadDumpFile").output - Assertions.assertThat(threadDump).contains("Full thread dump Java HotSpot") - } catch (e: Exception) { - throw Exception(e) + assertThat(threadDump).contains("Full thread dump Java HotSpot") } finally { - connection.stopProcess(process) + process.stop(Duration.ofSeconds(1)).assertInterruptedJava() } } diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/os/UbuntuIT.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/os/UbuntuIT.kt index caea10ee..aa05264b 100644 --- a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/os/UbuntuIT.kt +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/api/os/UbuntuIT.kt @@ -1,10 +1,9 @@ package com.atlassian.performance.tools.infrastructure.api.os -import com.atlassian.performance.tools.infrastructure.toSsh +import com.atlassian.performance.tools.infrastructure.api.DockerInfrastructure +import com.atlassian.performance.tools.ssh.api.Ssh import com.atlassian.performance.tools.ssh.api.SshConnection import com.atlassian.performance.tools.ssh.api.SshHost -import com.atlassian.performance.tools.sshubuntu.api.SshUbuntu -import com.atlassian.performance.tools.sshubuntu.api.SshUbuntuContainer import org.apache.logging.log4j.Level import org.junit.After import org.junit.Before @@ -17,23 +16,25 @@ import java.util.concurrent.TimeUnit class UbuntuIT { private lateinit var executor: ExecutorService - private lateinit var sshUbuntu: SshUbuntu + private lateinit var infra: DockerInfrastructure + private lateinit var sshUbuntu: Ssh @Before fun before() { executor = Executors.newCachedThreadPool() - sshUbuntu = SshUbuntuContainer().start() + infra = DockerInfrastructure() + sshUbuntu = infra.serveSsh("UbuntuIT") } @After fun after() { - sshUbuntu.close() + infra.close() executor.shutdownNow() } @Test fun shouldRetry() { - sshUbuntu.toSsh().newConnection().use { connection -> + sshUbuntu.newConnection().use { connection -> Ubuntu().install( ColdAptSshConnection(connection), listOf("nano"), @@ -81,26 +82,21 @@ class UbuntuIT { @Test fun shouldBeThreadSafe() { - val lock = Object() val concurrency = 5 val latch = CountDownLatch(concurrency) (1..concurrency) .map { - executor.submit { installLftp(lock, latch) } + executor.submit { installLftp(latch) } }.map { it.get(5, TimeUnit.MINUTES) } } - private fun installLftp(lock: Any, latch: CountDownLatch) { - val ssh = synchronized(lock) { - sshUbuntu.toSsh() - } - ssh.newConnection().use { connection -> + private fun installLftp(latch: CountDownLatch) { + sshUbuntu.newConnection().use { connection -> latch.countDown() latch.await() Ubuntu().install(connection, listOf("lftp")) } } - -} \ No newline at end of file +} diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/browser/SshChromium.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/browser/SshChromium.kt index 5106dd31..17a0bad9 100644 --- a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/browser/SshChromium.kt +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/browser/SshChromium.kt @@ -2,22 +2,21 @@ package com.atlassian.performance.tools.infrastructure.browser import com.atlassian.performance.tools.jvmtasks.api.IdempotentAction import com.atlassian.performance.tools.jvmtasks.api.StaticBackoff -import com.atlassian.performance.tools.ssh.api.DetachedProcess +import com.atlassian.performance.tools.ssh.api.Ssh import com.atlassian.performance.tools.ssh.api.SshConnection import com.atlassian.performance.tools.virtualusers.api.browsers.Browser import com.atlassian.performance.tools.virtualusers.api.browsers.CloseableRemoteWebDriver import org.openqa.selenium.chrome.ChromeOptions import org.openqa.selenium.remote.RemoteWebDriver import java.net.URI -import java.time.Duration +import java.time.Duration.ofSeconds internal class SshChromium( - private val ssh: SshConnection, + private val ssh: Ssh, private val chromedriverUri: URI ) : Browser { override fun start(): CloseableRemoteWebDriver { - val chromedriverProcess: DetachedProcess = ssh.startProcess("./chromedriver --whitelisted-ips") - + ssh.runInBackground("./chromedriver --whitelisted-ips") val chromeOptions = ChromeOptions() .apply { addArguments("--headless") } .apply { addArguments("--no-sandbox") } @@ -28,16 +27,13 @@ internal class SshChromium( "credentials_enable_service" to false ) ) - IdempotentAction("Wait for chrome process") { - waitForChromeProcess(ssh) - }.retry(maxAttempts = 3, backoff = StaticBackoff(Duration.ofSeconds(5L))) - val driver = RemoteWebDriver(chromedriverUri.toURL(), chromeOptions) - return object : CloseableRemoteWebDriver(driver) { - override fun close() { - super.close() - ssh.stopProcess(chromedriverProcess) - } + ssh.newConnection().use { connection -> + IdempotentAction("Wait for chrome process") { + waitForChromeProcess(connection) + }.retry(3, StaticBackoff(ofSeconds(5))) } + val driver = RemoteWebDriver(chromedriverUri.toURL(), chromeOptions) + return CloseableRemoteWebDriver(driver) } private fun waitForChromeProcess(ssh: SshConnection) { diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/database/SshMysqlClientTest.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/database/SshMysqlClientTest.kt index 2773e309..92c9261d 100644 --- a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/database/SshMysqlClientTest.kt +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/database/SshMysqlClientTest.kt @@ -20,7 +20,7 @@ class SshMysqlClientTest { assertThat(ssh.commands) .`as`("SSH commands") .containsExactly( - "mysql -h 127.0.0.1 -u root -e \"$command\"" + "mysql -h 127.0.0.1 -P 3306 -u root -e \"$command\"" ) } @@ -36,7 +36,7 @@ class SshMysqlClientTest { assertThat(ssh.commands) .`as`("SSH commands") .containsExactly( - "mysql -h 127.0.0.1 -u root < ${file.name}", + "mysql -h 127.0.0.1 -P 3306 -u root < ${file.name}", "rm ${file.name}" ) } diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/jira/install/JiraLaunchScriptIT.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/jira/install/JiraLaunchScriptIT.kt deleted file mode 100644 index 2d8d0889..00000000 --- a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/jira/install/JiraLaunchScriptIT.kt +++ /dev/null @@ -1,57 +0,0 @@ -package com.atlassian.performance.tools.infrastructure.jira.install - -import com.atlassian.performance.tools.infrastructure.api.distribution.PublicJiraSoftwareDistribution -import com.atlassian.performance.tools.infrastructure.api.jira.EmptyJiraHome -import com.atlassian.performance.tools.infrastructure.jira.start.JiraLaunchScript -import com.atlassian.performance.tools.infrastructure.api.jvm.AdoptOpenJDK -import com.atlassian.performance.tools.infrastructure.toSsh -import com.atlassian.performance.tools.sshubuntu.api.SshUbuntuContainer -import org.assertj.core.api.Assertions.assertThat -import org.junit.Test -import java.nio.file.Files -import java.util.function.Consumer - -class JiraLaunchScriptIT { - - @Test - fun shouldInstallJira() { - // given - val installation = ParallelInstallation( - jiraHomeSource = EmptyJiraHome(), - productDistribution = PublicJiraSoftwareDistribution("7.13.0"), - jdk = AdoptOpenJDK() - ) - val start = JiraLaunchScript() - - testOnServer { server -> - // when - val installed = installation.install(server) - val started = start.start(installed) - - // then - val serverXml = installed - .installation - .resolve("conf/server.xml") - .download(Files.createTempFile("downloaded-config", ".xml")) - assertThat(serverXml.readText()).contains(" testOnServer(test: (TcpServer) -> T) { - val privatePort = 8080 - val container = SshUbuntuContainer(Consumer { - it.addExposedPort(privatePort) - }) - container.start().use { sshUbuntu -> - val server = TcpServer( - "localhost", - sshUbuntu.container.getMappedPort(privatePort), - privatePort, - "my-jira", - sshUbuntu.toSsh() - ) - test(server) - } - } -} diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/lib/docker/ConnectedContainer.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/lib/docker/ConnectedContainer.kt new file mode 100644 index 00000000..ee5ae626 --- /dev/null +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/lib/docker/ConnectedContainer.kt @@ -0,0 +1,30 @@ +package com.atlassian.performance.tools.infrastructure.lib.docker + +import com.github.dockerjava.api.DockerClient +import com.github.dockerjava.api.command.ConnectToNetworkCmd + +class ConnectedContainer( + private val docker: DockerClient, + val containerId: String, + val networkId: String +) : AutoCloseable { + + override fun close() { + docker + .disconnectFromNetworkCmd() + .withContainerId(containerId) + .withNetworkId(networkId) + .exec() + } +} + +fun ConnectToNetworkCmd.execAsResource( + docker: DockerClient +): ConnectedContainer { + exec() + return ConnectedContainer( + docker, + containerId!!, + networkId!! + ) +} diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/lib/docker/CreatedContainer.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/lib/docker/CreatedContainer.kt new file mode 100644 index 00000000..b06863ec --- /dev/null +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/lib/docker/CreatedContainer.kt @@ -0,0 +1,18 @@ +package com.atlassian.performance.tools.infrastructure.lib.docker + +import com.github.dockerjava.api.DockerClient +import com.github.dockerjava.api.command.CreateContainerCmd +import com.github.dockerjava.api.command.CreateContainerResponse + +class CreatedContainer( + private val docker: DockerClient, + val response: CreateContainerResponse +) : AutoCloseable { + override fun close() { + docker.removeContainerCmd(response.id).exec() + } +} + +fun CreateContainerCmd.execAsResource( + docker: DockerClient +): CreatedContainer = CreatedContainer(docker, exec()) diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/lib/docker/DockerNetwork.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/lib/docker/DockerNetwork.kt new file mode 100644 index 00000000..c137d153 --- /dev/null +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/lib/docker/DockerNetwork.kt @@ -0,0 +1,18 @@ +package com.atlassian.performance.tools.infrastructure.lib.docker + +import com.github.dockerjava.api.DockerClient +import com.github.dockerjava.api.command.CreateNetworkCmd +import com.github.dockerjava.api.command.CreateNetworkResponse + +class DockerNetwork( + private val docker: DockerClient, + val response: CreateNetworkResponse +) : AutoCloseable { + override fun close() { + docker.removeNetworkCmd(response.id).exec() + } +} + +fun CreateNetworkCmd.execAsResource( + docker: DockerClient +): DockerNetwork = DockerNetwork(docker, exec()) \ No newline at end of file diff --git a/src/test/kotlin/com/atlassian/performance/tools/infrastructure/lib/docker/StartedContainer.kt b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/lib/docker/StartedContainer.kt new file mode 100644 index 00000000..076a02a7 --- /dev/null +++ b/src/test/kotlin/com/atlassian/performance/tools/infrastructure/lib/docker/StartedContainer.kt @@ -0,0 +1,23 @@ +package com.atlassian.performance.tools.infrastructure.lib.docker + +import com.github.dockerjava.api.DockerClient +import com.github.dockerjava.api.command.StartContainerCmd + +class StartedContainer( + private val docker: DockerClient, + val id: String +) : AutoCloseable { + override fun close() { + docker.stopContainerCmd(id).exec() + } +} + +fun StartContainerCmd.execAsResource( + docker: DockerClient +): StartedContainer { + exec() + return StartedContainer( + docker, + containerId!! + ) +}