diff --git a/README.md b/README.md
index 6cf3b87..fa0a3f6 100644
--- a/README.md
+++ b/README.md
@@ -39,7 +39,7 @@ SubDeployments are simply a collection of deployments used by a more complex dep
## Utils
These are classes mainly intended to be used by Container/Deployment-classes when massaging of the containers are needed for example.
-Currently, [ImageBuilder.groovy](src%2Fmain%2Fgroovy%2Fcom%2Feficode%2Fdevstack%2Futil%2FImageBuilder.groovy) dynamically builds Atlassian images for non x86 architectures on the fly.
+Currently, [ImageBuilder.groovy](src%2Fmain%2Fgroovy%2Fcom%2Feficode%2Fdevstack%2Futil%2FImageBuilder.groovy) dynamically builds Atlassian images on the fly.
[TimeMachine.groovy](src%2Fmain%2Fgroovy%2Fcom%2Feficode%2Fdevstack%2Futil%2FTimeMachine.groovy) changes the apparent time for all
containers sharing a Docker Engine, intended for testing date changes.
@@ -155,4 +155,4 @@ mvn dependency:get -Dartifact=com.eficode:devstack-standalone:2.3.9-SNAPSHOT -Dr
# Breaking Changes
* 2.3.9
- * From now on two artifacts will be generated, devstack and devstack-standalone and the classifier standalone is deprecated
\ No newline at end of file
+ * From now on two artifacts will be generated, devstack and devstack-standalone and the classifier standalone is deprecated
diff --git a/pom.xml b/pom.xml
index e744f84..d3b8f22 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
com.eficode
devstack
- 2.3.12-SNAPSHOT
+ 2.3.13-SNAPSHOT
jar
DevStack
@@ -102,7 +102,7 @@
com.eficode.atlassian
jirainstancemanager
- 2.0.3-SNAPSHOT
+ 2.0.9-SNAPSHOT
@@ -228,4 +228,4 @@
-
\ No newline at end of file
+
diff --git a/src/main/groovy/com/eficode/devstack/container/Container.groovy b/src/main/groovy/com/eficode/devstack/container/Container.groovy
index b99eb52..a61f068 100644
--- a/src/main/groovy/com/eficode/devstack/container/Container.groovy
+++ b/src/main/groovy/com/eficode/devstack/container/Container.groovy
@@ -974,4 +974,4 @@ trait Container {
return callBack.output
}
-}
\ No newline at end of file
+}
diff --git a/src/main/groovy/com/eficode/devstack/container/impl/JsmContainer.groovy b/src/main/groovy/com/eficode/devstack/container/impl/JsmContainer.groovy
index 0eb7714..5007718 100644
--- a/src/main/groovy/com/eficode/devstack/container/impl/JsmContainer.groovy
+++ b/src/main/groovy/com/eficode/devstack/container/impl/JsmContainer.groovy
@@ -23,6 +23,8 @@ class JsmContainer implements Container {
long jvmMaxRam = 6000
private String debugPort //Contains the port used for JVM debug
+ private Boolean enableJvmTimeTravel //If true, jvm time travel will be enabled
+
JsmContainer(String dockerHost = "", String dockerCertPath = "") {
if (dockerHost && dockerCertPath) {
@@ -40,6 +42,10 @@ class JsmContainer implements Container {
debugPort = portNr
}
+ void enableJvmTimeTravel(boolean enable) {
+ this.enableJvmTimeTravel = true
+ }
+
/**
* Gets the latest version number from Atlassian Marketplace
* @return ex: 5.6.0
@@ -59,30 +65,33 @@ class JsmContainer implements Container {
}
@Override
+ //TODO check but looks like it always builds a custom image now, even if x86
ContainerCreateRequest setupContainerCreateRequest() {
+ log.debug("Setting up container create request for JSM container")
- String image = containerImage + ":" + containerImageTag
- log.debug("Setting up container create request for JSM container")
- if (dockerClient.engineArch != "x86_64") {
- log.debug("\tDocker engine is not x86, building custom JSM docker image")
-
- ImageBuilder imageBuilder = new ImageBuilder(dockerClient.host, dockerClient.certPath)
- String jsmVersion = containerImageTag
- if (jsmVersion == "latest") {
- log.debug("\tCurrent image tag is set to \"latest\", need to resolve latest version number from Atlassian Marketplace in order to build custom image")
- jsmVersion = getLatestJsmVersion()
- }
- log.debug("\tStarting building of Docker Image for JSM verion $jsmVersion")
- ImageSummary newImage = imageBuilder.buildJsm(jsmVersion)
- log.debug("\tFinished building custom image:" + newImage.repoTags.join(","))
+ String jsmVersion = containerImageTag
+ if (jsmVersion == "latest") {
+ log.debug("\tCurrent image tag is set to \"latest\", need to resolve latest version number from Atlassian Marketplace in order to build custom image")
+ jsmVersion = getLatestJsmVersion()
+ }
+ log.debug("\tStarting building of Docker Image for JSM verion $jsmVersion")
+ ImageSummary jsmImage = new ImageBuilder(dockerClient.host, dockerClient.certPath).buildJsm(jsmVersion)
+ log.debug("\tFinished building custom image:" + jsmImage.repoTags.join(","))
+ String imageNameAndTag = jsmImage.repoTags.first()
- image = newImage.repoTags.first()
+ if (enableJvmTimeTravel) {
+ log.debug("\tStarting building of Docker Image for faketime JSM")
+ ImageSummary faketimeJsmImage = new ImageBuilder(dockerClient.host, dockerClient.certPath).buildJvmFakeTime(jsmImage, false)
+ log.debug("\tFinished building custom image:" + faketimeJsmImage.repoTags.join(","))
+
+ imageNameAndTag = faketimeJsmImage.repoTags.first()
}
+
ContainerCreateRequest containerCreateRequest = new ContainerCreateRequest().tap { c ->
- c.image = image
+ c.image = imageNameAndTag
c.hostname = containerName
c.env = ["JVM_MAXIMUM_MEMORY=" + jvmMaxRam + "m", "JVM_MINIMUM_MEMORY=" + ((jvmMaxRam / 2) as String) + "m", "ATL_TOMCAT_PORT=" + containerMainPort] + customEnvVar
@@ -90,19 +99,24 @@ class JsmContainer implements Container {
c.exposedPorts = [(containerMainPort + "/tcp"): [:]]
c.hostConfig = new HostConfig().tap { h ->
h.portBindings = [(containerMainPort + "/tcp"): [new PortBinding("0.0.0.0", (containerMainPort))]]
-
+ ArrayList additionalJvmArgs = []
if (debugPort) {
h.portBindings.put((debugPort + "/tcp"), [new PortBinding("0.0.0.0", (debugPort))])
c.exposedPorts.put((debugPort + "/tcp"), [:])
- c.env.add("JVM_SUPPORT_RECOMMENDED_ARGS=-Xdebug -Xrunjdwp:transport=dt_socket,address=*:${debugPort},server=y,suspend=n".toString())
+ additionalJvmArgs += "-Xdebug -Xrunjdwp:transport=dt_socket,address=*:${debugPort},server=y,suspend=n".toString()
}
+ if (enableJvmTimeTravel) {
+ additionalJvmArgs += "-XX:+UnlockDiagnosticVMOptions -XX:DisableIntrinsic=_currentTimeMillis -XX:CompileCommand=dontinline,java.lang.System::currentTimeMillis -agentpath:/libfaketime.so".toString()
+ }
+
+
+ c.env.add("JVM_SUPPORT_RECOMMENDED_ARGS=${additionalJvmArgs.join(" ")}".toString())
h.mounts = this.preparedMounts
}
-
}
return containerCreateRequest
@@ -115,7 +129,7 @@ class JsmContainer implements Container {
* @return
*/
MountPoint getJiraHomeMountPoint() {
- return getMounts().find {it.destination == "/var/atlassian/application-data/jira"}
+ return getMounts().find { it.destination == "/var/atlassian/application-data/jira" }
}
@@ -131,7 +145,7 @@ class JsmContainer implements Container {
stopContainer()
snapshotName = snapshotName ?: shortId + "-clone"
- boolean success = dockerClient.overwriteVolume(snapshotName, jiraHomeMountPoint.name)
+ boolean success = dockerClient.overwriteVolume(snapshotName, jiraHomeMountPoint.name)
if (wasRunning) {
startContainer()
}
@@ -147,9 +161,9 @@ class JsmContainer implements Container {
if (volumes.size() == 1) {
return volumes.first()
- }else if (volumes.isEmpty()) {
+ } else if (volumes.isEmpty()) {
return null
- }else {
+ } else {
throw new InputMismatchException("Error finding snapshot volume:" + snapshotName)
}
@@ -171,7 +185,7 @@ class JsmContainer implements Container {
snapshotName = snapshotName ?: shortId + "-clone"
ArrayList existingVolumes = dockerClient.getVolumesWithName(snapshotName)
- existingVolumes.each {existingVolume ->
+ existingVolumes.each { existingVolume ->
log.debug("\tRemoving existing snapshot volume:" + existingVolume.name)
dockerClient.manageVolume.rmVolume(existingVolume.name)
}
@@ -189,7 +203,7 @@ class JsmContainer implements Container {
* Clone JIRA home volume
* Container must be stopped
* @param newVolumeName must be unique
- * @param labels, optional labels to add to the new volume
+ * @param labels , optional labels to add to the new volume
* @return
*/
Volume cloneJiraHome(String newVolumeName = "", Map labels = null) {
@@ -197,8 +211,8 @@ class JsmContainer implements Container {
newVolumeName = newVolumeName ?: shortId + "-clone"
labels = labels ?: [
- srcContainerId : getId(),
- created : System.currentTimeSeconds()
+ srcContainerId: getId(),
+ created : System.currentTimeSeconds()
] as Map
Volume newVolume = dockerClient.cloneVolume(jiraHomeMountPoint.name, newVolumeName, labels)
diff --git a/src/main/groovy/com/eficode/devstack/util/DockerClientDS.groovy b/src/main/groovy/com/eficode/devstack/util/DockerClientDS.groovy
index 7f1048d..5cf33d6 100644
--- a/src/main/groovy/com/eficode/devstack/util/DockerClientDS.groovy
+++ b/src/main/groovy/com/eficode/devstack/util/DockerClientDS.groovy
@@ -6,6 +6,7 @@ import de.gesellix.docker.client.EngineResponseContent
import de.gesellix.docker.engine.DockerClientConfig
import de.gesellix.docker.engine.DockerEnv
import de.gesellix.docker.engine.EngineResponse
+import de.gesellix.docker.remote.api.ContainerInspectResponse
import de.gesellix.docker.remote.api.ContainerSummary
import de.gesellix.docker.remote.api.ExecConfig
import de.gesellix.docker.remote.api.ExecStartConfig
@@ -92,6 +93,21 @@ class DockerClientDS extends DockerClientImpl {
}
+ ContainerSummary getContainerById(String completeId) {
+ EngineResponse response = ps(true, 1000, true, " {\"id\":[\"${completeId}\"]}")
+
+
+ ArrayList containers = response.content
+
+ return containers.find{true}
+
+ }
+
+
+ EngineResponseContent inspectContainer(ContainerSummary containerSummary){
+ return inspectContainer(containerSummary.id)
+ }
+
EngineResponseContent createVolume(String name = null, Map labels = null, Map driverOpts = null) {
VolumeCreateOptions volumeOptions = new VolumeCreateOptions()
diff --git a/src/main/groovy/com/eficode/devstack/util/ImageBuilder.groovy b/src/main/groovy/com/eficode/devstack/util/ImageBuilder.groovy
index a978ac7..226cbf9 100644
--- a/src/main/groovy/com/eficode/devstack/util/ImageBuilder.groovy
+++ b/src/main/groovy/com/eficode/devstack/util/ImageBuilder.groovy
@@ -3,7 +3,6 @@ import com.eficode.devstack.container.impl.DoodContainer
import de.gesellix.docker.remote.api.ImageSummary
import java.util.concurrent.TimeoutException
-
/**
* A utility class intended to build docker images so that they match the docker engines CPU architecture
*
@@ -16,7 +15,6 @@ class ImageBuilder extends DoodContainer {
Map>builderOut = [:]
long cmdTimeoutS = 800 //Will timeout individual container commands after this many seconds
-
ImageBuilder(String dockerHost, String dockerCertPath) {
assert setupSecureRemoteConnection(dockerHost, dockerCertPath): "Error setting up secure remote docker connection"
prepareBindMount("/var/run/docker.sock", "/var/run/docker.sock")
@@ -48,11 +46,11 @@ class ImageBuilder extends DoodContainer {
* @return
*/
ImageSummary buildJsm(String jsmVersion, boolean force = false){
-
String imageName = "atlassian/jira-servicemanagement"
String artifactName = "atlassian-servicedesk"
String archType = dockerClient.engineArch
- String imageTag = "$imageName:$jsmVersion-$archType"
+ String archTypeSuffix = archType == "x86_64" ? "" : "-$archType"
+ String imageTag = "$imageName:$jsmVersion$archTypeSuffix"
containerName = imageTag.replaceAll(/[^a-zA-Z0-9_.-]/, "-").take(128-"-imageBuilder".length())
containerName += "-imageBuilder"
@@ -76,6 +74,118 @@ class ImageBuilder extends DoodContainer {
ImageSummary newImage = images.find {it.repoTags == [imageTag]}
log.debug("\tFinished building image:" + imageTag + ", ID:" + newImage.id[7..17])
return newImage
+ }
+
+ /*
+ ImageSummary buildFakeTimeJsm(String jsmVersion, boolean force = false){
+ String imageName = "atlassian/jira-servicemanagement"
+ String artifactName = "atlassian-servicedesk"
+ String archType = dockerClient.engineArch
+ String archTypeSuffix = archType == "x86_64" ? "" : "-$archType"
+ String imageTag = "$imageName:$jsmVersion$archTypeSuffix"
+ String fakeTimeRoot = "/faketimebuild"
+ String fakeTimeDockerFilePath = "$fakeTimeRoot/Dockerfile"
+ String fakeTimeAgentFilePath = "$fakeTimeRoot/faketime.cpp"
+ String fakeTimeImageTag = "$imageName-faketime:$jsmVersion$archTypeSuffix"
+ String fakeTimCpp = getClass().getResourceAsStream("/faketime.cpp").text
+ containerName = fakeTimeImageTag.replaceAll(/[^a-zA-Z0-9_.-]/, "-").take(128-"-IB".length())
+ containerName += "-IB"
+
+ log.info("my name is now $containerName")
+
+ //Check first if an image with the expected tag already exists
+ if (!force) {
+ ArrayList existingImages = dockerClient.images().content
+ ImageSummary existingImage = existingImages.find {it.repoTags == [fakeTimeImageTag]}
+ if (existingImage) {
+ return existingImage
+ }
+ }
+
+ String fakeTimeDockerFile = """
+ FROM $imageTag
+ WORKDIR /
+ RUN apt-get update && apt-get install -y wget g++ make
+ # RUN wget https://github.com/odnoklassniki/jvmti-tools/raw/master/faketime/faketime.cpp
+ COPY faketime.cpp .
+ RUN g++ -O2 -fPIC -shared -I \$JAVA_HOME/include -I \$JAVA_HOME/include/linux -olibfaketime.so faketime.cpp
+
+ ENV JVM_SUPPORT_RECOMMENDED_ARGS="-agentpath:/libfaketime.so=+2592000000"
+ """
+
+
+ putBuilderCommand("mkdir -p $fakeTimeRoot", "")
+ putBuilderCommand("cat > $fakeTimeDockerFilePath <<- 'EOF'\n" + fakeTimeDockerFile + "\nEOF", "")
+ putBuilderCommand("cat > $fakeTimeAgentFilePath <<- 'EOF'\n" + fakeTimCpp + "\nEOF", "")
+ putBuilderCommand("cd $fakeTimeRoot && docker build --tag $fakeTimeImageTag --build-arg JIRA_VERSION=$jsmVersion --build-arg ARTEFACT_NAME=$artifactName . && echo status:\$?", "status:0")
+ putBuilderCommand("pkill tail", "")
+
+ assert build() : "Error building the image."
+
+ ArrayList images = dockerClient.images().content
+ ImageSummary newImage = images.find {it.repoTags == [fakeTimeImageTag]}
+ return newImage
+ }
+
+ */
+
+
+
+ ImageSummary buildJvmFakeTime(ImageSummary originalImage, boolean force = false) {
+
+ String originalRepoTag = originalImage.repoTags.first()
+ String origImageName = originalRepoTag.substring(0,originalRepoTag.indexOf(":"))
+ String origImageTag = originalRepoTag.substring(originalRepoTag.indexOf(":")+ 1)
+
+ return buildJvmFakeTime(origImageName, origImageTag, force)
+
+ }
+
+ //Presumes srcImage has "apt-get" commands
+ ImageSummary buildJvmFakeTime(String srcImage, String srcImageTag, boolean force) {
+
+ String fakeTimeImageTag = "$srcImage-faketime:$srcImageTag"
+ containerName = fakeTimeImageTag.replaceAll(/[^a-zA-Z0-9_.-]/, "-").take(120-"-BuildFake".length())
+
+ String fakeTimeRoot = "/faketimebuild"
+ String fakeTimeDockerFilePath = "$fakeTimeRoot/Dockerfile"
+ String fakeTimeAgentFilePath = "$fakeTimeRoot/faketime.cpp"
+ String fakeTimCpp = getClass().getResourceAsStream("/faketime.cpp").text
+
+ //Check first if an image with the expected tag already exists
+ if (!force) {
+ ArrayList existingImages = dockerClient.images().content
+ ImageSummary existingImage = existingImages.find {it.repoTags == [fakeTimeImageTag]}
+ if (existingImage) {
+ return existingImage
+ }
+ }
+
+ String fakeTimeDockerFile = """
+ FROM $srcImage:$srcImageTag
+ WORKDIR /
+ RUN apt-get update && apt-get install -y wget g++ make
+ COPY faketime.cpp /faketime.cpp
+ RUN g++ -O2 -fPIC -shared -I \$JAVA_HOME/include -I \$JAVA_HOME/include/linux -olibfaketime.so faketime.cpp
+
+ """
+
+ // #ENV JVM_SUPPORT_RECOMMENDED_ARGS="-agentpath:/libfaketime.so=+2592000000"
+ //#RUN apt-get update && apt-get install -y g++ make
+ // #RUN wget https://github.com/odnoklassniki/jvmti-tools/raw/master/faketime/faketime.cpp
+
+ putBuilderCommand("mkdir -p $fakeTimeRoot", "")
+ putBuilderCommand("cat > $fakeTimeDockerFilePath <<- 'EOF'\n" + fakeTimeDockerFile + "\nEOF", "")
+ putBuilderCommand("cat > $fakeTimeAgentFilePath <<- 'EOF'\n" + fakeTimCpp + "\nEOF", "")
+ putBuilderCommand("cd $fakeTimeRoot && docker build --tag $fakeTimeImageTag . && echo status:\$?", "status:0")
+ putBuilderCommand("pkill tail", "")
+
+
+ assert build() : "Error building the image."
+
+ ArrayList images = dockerClient.images().content
+ ImageSummary newImage = images.find {it.repoTags == [fakeTimeImageTag]}
+ return newImage
}
@@ -140,7 +250,6 @@ class ImageBuilder extends DoodContainer {
@Override
boolean runAfterDockerSetup(){
-
builderCommands.each {cmd, expectedLastOut ->
log.info("Running container command:" + cmd)
log.info("\tExpecting last output from command:" + expectedLastOut)
diff --git a/src/main/resources/faketime.cpp b/src/main/resources/faketime.cpp
new file mode 100644
index 0000000..585ba7c
--- /dev/null
+++ b/src/main/resources/faketime.cpp
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2020 Odnoklassniki Ltd, Mail.Ru Group
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ * Original credit goes to: https://github.com/odnoklassniki/jvmti-tools/raw/master/faketime/faketime.cpp
+ */
+
+#include
+#include
+#include
+#include
+
+static jlong (*real_time_millis)(JNIEnv *, jclass) = NULL;
+static jlong (*real_nano_time_adjustment)(JNIEnv *, jclass, jlong) = NULL;
+
+jlong JNICALL fake_time_millis(JNIEnv* env, jclass cls)
+{
+ jclass systemClass = env->FindClass("java/lang/System");
+ jmethodID getPropertyMethodId = env->GetStaticMethodID(systemClass, "getProperty", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
+ jstring offsetPropertyName = env->NewStringUTF("faketime.offset.seconds");
+ jstring offsetPropertyDefault = env->NewStringUTF("0");
+ jstring offsetValue = (jstring)env->CallStaticObjectMethod(systemClass, getPropertyMethodId, offsetPropertyName, offsetPropertyDefault);
+ const char *offset = env->GetStringUTFChars(offsetValue, NULL);
+ jlong result = real_time_millis(env, cls) + atoll(offset);
+ env->ReleaseStringUTFChars(offsetValue, offset);
+ return result;
+}
+
+jlong JNICALL fake_nano_time_adjustment(JNIEnv *env, jclass cls, jlong offset_seconds)
+{
+ jclass systemClass = env->FindClass("java/lang/System");
+ jmethodID getPropertyMethodId = env->GetStaticMethodID(systemClass, "getProperty", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
+ jstring offsetPropertyName = env->NewStringUTF("faketime.offset.seconds");
+ jstring offsetPropertyDefault = env->NewStringUTF("0");
+ jstring offsetValue = (jstring)env->CallStaticObjectMethod(systemClass, getPropertyMethodId, offsetPropertyName, offsetPropertyDefault);
+ const char *offset = env->GetStringUTFChars(offsetValue, NULL);
+ jlong result = real_nano_time_adjustment(env, cls, offset_seconds) + atoll(offset) * 1000000;
+ env->ReleaseStringUTFChars(offsetValue, offset);
+ return result;
+}
+
+void JNICALL NativeMethodBind(jvmtiEnv *jvmti, JNIEnv *env, jthread thread, jmethodID method,
+ void *address, void **new_address_ptr)
+{
+ char *name;
+ if (jvmti->GetMethodName(method, &name, NULL, NULL) == 0)
+ {
+ if (real_time_millis == NULL && strcmp(name, "currentTimeMillis") == 0)
+ {
+ real_time_millis = (jlong(*)(JNIEnv *, jclass))address;
+ *new_address_ptr = (void *)fake_time_millis;
+ }
+ else if (real_nano_time_adjustment == NULL && strcmp(name, "getNanoTimeAdjustment") == 0)
+ {
+ real_nano_time_adjustment = (jlong(*)(JNIEnv *, jclass, jlong))address;
+ *new_address_ptr = (void *)fake_nano_time_adjustment;
+ }
+ jvmti->Deallocate((unsigned char *)name);
+ }
+}
+
+JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved)
+{
+ jvmtiEnv *jvmti;
+ vm->GetEnv((void **)&jvmti, JVMTI_VERSION_1_0);
+
+ jvmtiCapabilities capabilities = {0};
+ capabilities.can_generate_native_method_bind_events = 1;
+#if JNI_VERSION_9
+ jvmtiCapabilities potential_capabilities;
+ jvmti->GetPotentialCapabilities(&potential_capabilities);
+ capabilities.can_generate_early_vmstart = potential_capabilities.can_generate_early_vmstart;
+#endif
+ jvmti->AddCapabilities(&capabilities);
+
+ jvmtiEventCallbacks callbacks = {0};
+ callbacks.NativeMethodBind = NativeMethodBind;
+ jvmti->SetEventCallbacks(&callbacks, sizeof(callbacks));
+
+ jvmti->SetEventNotificationMode(JVMTI_ENABLE, JVMTI_EVENT_NATIVE_METHOD_BIND, NULL);
+
+ return 0;
+}
diff --git a/src/test/groovy/com/eficode/devstack/container/impl/JsmContainerTest.groovy b/src/test/groovy/com/eficode/devstack/container/impl/JsmContainerTest.groovy
index ce5b2e9..eed52b5 100644
--- a/src/test/groovy/com/eficode/devstack/container/impl/JsmContainerTest.groovy
+++ b/src/test/groovy/com/eficode/devstack/container/impl/JsmContainerTest.groovy
@@ -3,6 +3,7 @@ package com.eficode.devstack.container.impl
import com.eficode.devstack.DevStackSpec
import de.gesellix.docker.remote.api.ContainerInspectResponse
import de.gesellix.docker.remote.api.ContainerState
+import de.gesellix.docker.remote.api.ContainerSummary
import de.gesellix.docker.remote.api.core.ClientException
import org.apache.commons.io.FileUtils
import org.slf4j.LoggerFactory
@@ -15,43 +16,42 @@ class JsmContainerTest extends DevStackSpec {
def setupSpec() {
- dockerRemoteHost = "https://docker.domain.se:2376"
- dockerCertPath = "~/.docker/"
+ dockerRemoteHost = ""// "https://docker.domain.se:2376"
+ dockerCertPath = ""// "~/.docker/"
- DevStackSpec.log = LoggerFactory.getLogger(JsmContainerTest.class)
+ log = LoggerFactory.getLogger(JsmContainerTest.class)
cleanupContainerNames = ["jira.domain.se", "JSM", "Spoc-JSM"]
cleanupContainerPorts = [8080]
}
-
def "test isCreated"(String dockerHost, String certPath) {
when:
- DevStackSpec.log.info("Testing isCreated")
+ log.info("Testing isCreated")
JsmContainer jsm = new JsmContainer(dockerHost, certPath)
then:
!jsm.isCreated()
- DevStackSpec.log.info("\tDid not return a false positive")
+ log.info("\tDid not return a false positive")
when:
String containerId = jsm.createContainer()
- DevStackSpec.log.info("\tCreated container:" + containerId)
+ log.info("\tCreated container:" + containerId)
then:
jsm.isCreated()
- DevStackSpec.log.info("\tisCreated now returns true")
+ log.info("\tisCreated now returns true")
when:
- jsm.stopAndRemoveContainer() ?: {throw new Exception("Error revoming container $containerId")}
- DevStackSpec.log.info("\tRemoved container")
+ jsm.stopAndRemoveContainer() ?: { throw new Exception("Error revoming container $containerId") }
+ log.info("\tRemoved container")
then:
!jsm.isCreated()
- DevStackSpec.log.info("\tisCreated now again returns false")
+ log.info("\tisCreated now again returns false")
where:
dockerHost | certPath
@@ -60,6 +60,7 @@ class JsmContainerTest extends DevStackSpec {
}
+
def "test setupSecureRemoteConnection"() {
/**
@@ -70,31 +71,35 @@ class JsmContainerTest extends DevStackSpec {
JsmContainer jsm = new JsmContainer(dockerRemoteHost, dockerCertPath)
then:
- assert jsm.ping(): "Error pinging docker engine"
- assert jsm.dockerClient.dockerClientConfig.scheme == "https"
+ assert jsm.ping() || dockerRemoteHost == "": "Error pinging docker engine"
+ assert jsm.dockerClient.dockerClientConfig.scheme == "https" || dockerRemoteHost == ""
}
-
def "test setupContainer"(String dockerHost, String certPath) {
setup:
- DevStackSpec.log.info("Testing setup of JSM container using trait method")
+ log.info("Testing setup of JSM container using trait method")
JsmContainer jsm = new JsmContainer(dockerHost, certPath)
+ String archTypeSuffix = dockerClient.engineArch == "x86_64" ? "" : "-$dockerClient.engineArch"
+ String latestJsmVersion = JsmContainer.getLatestJsmVersion()
+
+ //If arch != x86 a custom image will be built with a tag != latest
+ ArrayList expectedImageTags = ["atlassian/jira-servicemanagement:latest", "atlassian/jira-servicemanagement:$latestJsmVersion$archTypeSuffix".toString()]
when:
String containerId = jsm.createContainer()
- ContainerInspectResponse containerInspect = dockerClient.inspectContainer(containerId).content
+ ContainerInspectResponse containerInspect = dockerClient.inspectContainer(containerId).content
then:
- assert containerInspect.name == "/" + jsm.containerName : "JSM was not given the expected name"
- assert containerInspect.state.status == ContainerState.Status.Created : "JSM Container status is of unexpected value"
- assert containerInspect.state.running == false : "JSM Container was started even though it should only have been created"
- assert dockerClient.inspectImage(containerInspect.image).content.repoTags.find {it == "atlassian/jira-servicemanagement:latest"} : "JSM container was created with incorrect Docker image"
- assert containerInspect.hostConfig.portBindings.containsKey("8080/tcp") : "JSM Container port binding was not setup correctly"
- DevStackSpec.log.info("\tJSM Container was setup correctly")
+ assert containerInspect.name == "/" + jsm.containerName: "JSM was not given the expected name"
+ assert containerInspect.state.status == ContainerState.Status.Created: "JSM Container status is of unexpected value"
+ assert containerInspect.state.running == false: "JSM Container was started even though it should only have been created"
+ assert dockerClient.inspectImage(containerInspect.image).content.repoTags.any { it in expectedImageTags }: "JSM container was created with incorrect Docker image"
+ assert containerInspect.hostConfig.portBindings.containsKey("8080/tcp"): "JSM Container port binding was not setup correctly"
+ log.info("\tJSM Container was setup correctly")
where:
@@ -105,27 +110,24 @@ class JsmContainerTest extends DevStackSpec {
}
-
def "test non standard parameters"(String dockerHost, String certPath) {
setup:
- DevStackSpec.log.info("Testing setup of JSM container using dedicated JSM method")
+ log.info("Testing setup of JSM container using dedicated JSM method")
JsmContainer jsm = new JsmContainer(dockerHost, certPath)
jsm.containerName = "Spoc-JSM"
- jsm.containerImageTag = "4-ubuntu-jdk11"
jsm.containerMainPort = "666"
when:
String containerId = jsm.createContainer()
- ContainerInspectResponse containerInspect = dockerClient.inspectContainer(containerId).content
+ ContainerInspectResponse containerInspect = dockerClient.inspectContainer(containerId).content
then:
- assert containerInspect.name == "/Spoc-JSM" : "JSM was not given the expected name"
- assert containerInspect.state.status == ContainerState.Status.Created : "JSM Container status is of unexpected value"
- assert containerInspect.state.running == false : "JSM Container was started even though it should only have been created"
- assert containerInspect.hostConfig.portBindings.containsKey("666/tcp") : "JSM Container port binding was not setup correctly"
- assert dockerClient.inspectImage(containerInspect.image).content.repoTags.find {it == "atlassian/jira-servicemanagement:4-ubuntu-jdk11"} : "JSM container was created with incorrect Docker image"
- DevStackSpec.log.info("\tJSM Container was setup correctly")
+ assert containerInspect.name == "/Spoc-JSM": "JSM was not given the expected name"
+ assert containerInspect.state.status == ContainerState.Status.Created: "JSM Container status is of unexpected value"
+ assert containerInspect.state.running == false: "JSM Container was started even though it should only have been created"
+ assert containerInspect.hostConfig.portBindings.containsKey("666/tcp"): "JSM Container port binding was not setup correctly"
+ log.info("\tJSM Container was setup correctly")
where:
@@ -136,16 +138,15 @@ class JsmContainerTest extends DevStackSpec {
}
-
def "test stopAndRemoveContainer"(String dockerHost, String certPath) {
setup:
- DevStackSpec.log.info("Testing stop and removal of JSM container")
+ log.info("Testing stop and removal of JSM container")
JsmContainer jsm = new JsmContainer(dockerHost, certPath)
when: "Setting up the container with the trait method"
- DevStackSpec.log.info("\tSetting up JSM container using trait method")
+ log.info("\tSetting up JSM container using trait method")
String containerId = jsm.createContainer()
then: "Removing it should return true"
@@ -156,12 +157,11 @@ class JsmContainerTest extends DevStackSpec {
then: "Exception should be thrown"
ClientException ex = thrown(ClientException)
- assert ex.message.startsWith("Client error : 404 Not Found") : "Unexpected exception thrown when inspecting the deleted container"
-
+ assert ex.message.startsWith("Client error : 404 Not Found"): "Unexpected exception thrown when inspecting the deleted container"
when: "Setting up the container with the trait method"
- DevStackSpec.log.info("\tSetting up JSM container using dedicated JSM method")
+ log.info("\tSetting up JSM container using dedicated JSM method")
String containerId2 = jsm.createContainer()
then: "Removing it should return true"
@@ -172,7 +172,7 @@ class JsmContainerTest extends DevStackSpec {
then: "Exception should be thrown"
ClientException ex2 = thrown(ClientException)
- assert ex2.message.startsWith("Client error : 404 Not Found") : "Unexpected exception thrown when inspecting the deleted container"
+ assert ex2.message.startsWith("Client error : 404 Not Found"): "Unexpected exception thrown when inspecting the deleted container"
where:
dockerHost | certPath
@@ -188,53 +188,52 @@ class JsmContainerTest extends DevStackSpec {
String containerSrcPath = "/opt/atlassian/jira/atlassian-jira/WEB-INF/classes/com/atlassian/jira/"
String containerDstDir = "/var/atlassian/application-data/jira/"
- DevStackSpec.log.info("Testing copying files to and from JSM container")
+ log.info("Testing copying files to and from JSM container")
JsmContainer jsm = new JsmContainer(dockerHost, certPath)
String containerId = jsm.createContainer()
- DevStackSpec.log.info("\tCreated container:" + containerId)
+ log.info("\tCreated container:" + containerId)
Path tempDir = Files.createTempDirectory("testing-${this.class.simpleName}")
- DevStackSpec.log.info("\tCreated temp dir:" + containerId.toString())
+ log.info("\tCreated temp dir:" + containerId.toString())
when: "Copying files from container path:"
- DevStackSpec.log.info("\tCopying files from container path:" + containerSrcPath)
- ArrayListcopiedFiles = jsm.copyFilesFromContainer(containerSrcPath, tempDir.toString() + "/")
- DevStackSpec.log.info("\tCopied ${copiedFiles.size()} files from container")
+ log.info("\tCopying files from container path:" + containerSrcPath)
+ ArrayList copiedFiles = jsm.copyFilesFromContainer(containerSrcPath, tempDir.toString() + "/")
+ log.info("\tCopied ${copiedFiles.size()} files from container")
then: "Several files and directories should have been copied"
- assert copiedFiles.size() : "No files where copied from container"
- assert copiedFiles.any {it.directory} : "No directories where copied from container"
- DevStackSpec.log.info("\tCopying files from container appears successful")
+ assert copiedFiles.size(): "No files where copied from container"
+ assert copiedFiles.any { it.directory }: "No directories where copied from container"
+ log.info("\tCopying files from container appears successful")
when: "Copying a file to container"
- assert jsm.startContainer() : "Error starting container"
+ assert jsm.startContainer(): "Error starting container"
- File largestFile = copiedFiles.sort {it.size()}.last()
+ File largestFile = copiedFiles.sort { it.size() }.last()
String fileHash = largestFile.bytes.sha256()
- DevStackSpec.log.info("\tCopying file ($largestFile.name) to container path:" + containerDstDir + largestFile.name)
- DevStackSpec.log.debug("\t\tFile size:" + (largestFile.size() * 0.000001).round(1) + "MB")
- DevStackSpec.log.debug("\t\tFile hash:" + fileHash)
+ log.info("\tCopying file ($largestFile.name) to container path:" + containerDstDir + largestFile.name)
+ log.debug("\t\tFile size:" + (largestFile.size() * 0.000001).round(1) + "MB")
+ log.debug("\t\tFile hash:" + fileHash)
then: "File should copy without error"
jsm.copyFileToContainer(largestFile.path, containerDstDir)
- DevStackSpec.log.info("\tFinished copying file to container")
+ log.info("\tFinished copying file to container")
when: "Running a hash in the container"
- DevStackSpec.log.info("Executing hash calculation of file in container")
+ log.info("Executing hash calculation of file in container")
ArrayList hashOutput = jsm.runBashCommandInContainer("sha256sum " + containerDstDir + largestFile.name)
- DevStackSpec.log.debug("\tContainer hash output:" + hashOutput)
+ log.debug("\tContainer hash output:" + hashOutput)
then: "The container hash and local hash should be identical"
- assert hashOutput.size() == 1 : "Expected one output row from remote bash command"
- assert hashOutput.first().contains(fileHash) : "Output from container does not contain the expected hash"
- assert hashOutput.first().contains(containerDstDir + largestFile.name) : "Output from container does not contain the expected file path"
- assert hashOutput == [ fileHash + " " + containerDstDir + largestFile.name] : "Output from container is not formatted as expected"
-
+ assert hashOutput.size() == 1: "Expected one output row from remote bash command"
+ assert hashOutput.first().contains(fileHash): "Output from container does not contain the expected hash"
+ assert hashOutput.first().contains(containerDstDir + largestFile.name): "Output from container does not contain the expected file path"
+ assert hashOutput == [fileHash + " " + containerDstDir + largestFile.name]: "Output from container is not formatted as expected"
cleanup:
- DevStackSpec.log.info("\tDeleting temp dir:" + tempDir.toString())
+ log.info("\tDeleting temp dir:" + tempDir.toString())
FileUtils.deleteDirectory(tempDir.toFile())
@@ -246,6 +245,40 @@ class JsmContainerTest extends DevStackSpec {
}
+ //Does not test functionality, just that the needed envs and bins seems to get deployed correctly
+ def "Test building of JSM JVM TimeTravel"(boolean enableTimeTravel, boolean enableJvmDebug) {
+
+
+ setup:
+ log.info("Testing setup and use of JVM TimeTravel enabled JSM container")
+ JsmContainer jsm = new JsmContainer()
+
+
+ when: "Setting up the container JvmTimeTravel enabled"
+ log.info("\tSetting up JSM container using trait method")
+ jsm.enableJvmTimeTravel(enableTimeTravel)
+ enableJvmDebug ? jsm.enableJvmDebug() : null
+ String containerId = jsm.createContainer()
+ ContainerSummary containerSummary = dockerClient.getContainerById(containerId)
+ ContainerInspectResponse containerInspect = dockerClient.inspectContainer(containerSummary).content
+ String jvmArgs = containerInspect.config.env.find { it.startsWith("JVM_SUPPORT_RECOMMENDED_ARGS") }
+
+ jsm.startContainer()
+
+ then: "The container should have envs and files enabling time travel"
+ assert !enableTimeTravel || jvmArgs.contains("-XX:DisableIntrinsic=_currentTimeMillis"): "Container is missing expected env var"
+ assert !enableTimeTravel || jvmArgs.contains("-XX:+UnlockDiagnosticVMOptions"): "Container is missing expected env var"
+ assert !enableTimeTravel || jvmArgs.contains("-agentpath:"): "Container is missing expected env var"
+ assert !enableJvmDebug || jvmArgs.contains("-Xdebug"): "JVM Debug was enabled but not is missing from env vars"
+ assert jsm.runBashCommandInContainer("test -f /faketime.cpp && echo status: \$?").contains("status: 0"): "Could not find the expected file /faketime.cpp in the container "
+
+
+ where:
+ enableTimeTravel | enableJvmDebug
+ true | false
+ true | true
+ false | false
+ }
}
diff --git a/src/test/groovy/com/eficode/devstack/deployment/impl/JsmH2DeploymentTest.groovy b/src/test/groovy/com/eficode/devstack/deployment/impl/JsmH2DeploymentTest.groovy
index 833bf26..7dd5a51 100644
--- a/src/test/groovy/com/eficode/devstack/deployment/impl/JsmH2DeploymentTest.groovy
+++ b/src/test/groovy/com/eficode/devstack/deployment/impl/JsmH2DeploymentTest.groovy
@@ -1,5 +1,6 @@
package com.eficode.devstack.deployment.impl
+import com.eficode.atlassian.jiraInstanceManager.beans.MarketplaceApp
import com.eficode.devstack.DevStackSpec
import kong.unirest.Unirest
import org.slf4j.LoggerFactory
@@ -8,8 +9,6 @@ import spock.lang.Shared
class JsmH2DeploymentTest extends DevStackSpec {
-
-
@Shared
File projectRoot = new File(".")
@@ -25,7 +24,7 @@ class JsmH2DeploymentTest extends DevStackSpec {
cleanupContainerNames = ["jira.domain.se", "jira2.domain.se", "localhost"]
cleanupContainerPorts = [8080, 8082, 80]
- disableCleanup = false
+ disableCleanup = true
}
@@ -50,9 +49,8 @@ class JsmH2DeploymentTest extends DevStackSpec {
jsmDep.jsmContainer.inspectContainer().networkSettings.ports.find { it.key == "$port/tcp" }
//Make sure websudo was disabled
- jsmDep.jsmContainer.runBashCommandInContainer("cat jira-config.properties").find {it == "jira.websudo.is.disabled=true"}
- jsmDep.jsmContainer.containerLogs.find {it.matches(".*jira.websudo.is.disabled.*:.*true.*")}
-
+ jsmDep.jsmContainer.runBashCommandInContainer("cat jira-config.properties").find { it == "jira.websudo.is.disabled=true" }
+ jsmDep.jsmContainer.containerLogs.find { it.matches(".*jira.websudo.is.disabled.*:.*true.*") }
where:
@@ -63,5 +61,82 @@ class JsmH2DeploymentTest extends DevStackSpec {
}
+ def "test FakeTime"(String baseurl, String port, String dockerHost, String certPath) {
+ setup:
+
+ JsmH2Deployment jsmDep = new JsmH2Deployment(baseurl, dockerHost, certPath)
+
+ String srLicense = new File(System.getProperty("user.home") + "/.licenses/jira/sr.license").text
+ assert srLicense: "Error finding script runner license"
+
+
+ MarketplaceApp srMarketApp = MarketplaceApp.searchMarketplace("Adaptavist ScriptRunner for JIRA", MarketplaceApp.Hosting.Datacenter).find { it.key == "com.onresolve.jira.groovy.groovyrunner" }
+ MarketplaceApp.Version srVersion = srMarketApp?.getVersion("latest", MarketplaceApp.Hosting.Datacenter)
+
+ jsmDep.setJiraLicense(new File(System.getProperty("user.home") + "/.licenses/jira/jsm.license").text)
+ jsmDep.appsToInstall.put(srVersion, srLicense)
+ jsmDep.jsmContainer.enableJvmTimeTravel(true)
+ when:
+
+
+ boolean setupSuccess = jsmDep.setupDeployment(true, true)
+ jsmDep.jiraRest.waitForSrToBeResponsive()
+ String jvmArgs = jsmDep.jsmContainer.inspectContainer().config.env.find { it.startsWith("JVM_SUPPORT_RECOMMENDED_ARGS") }
+ then:
+ assert setupSuccess: "Error setting up JIRA"
+ assert jvmArgs.contains("-XX:DisableIntrinsic=_currentTimeMillis"): "Container is missing expected env var"
+ assert jvmArgs.contains("-XX:+UnlockDiagnosticVMOptions"): "Container is missing expected env var"
+ assert jvmArgs.contains("-agentpath:"): "Container is missing expected env var"
+ assert jsmDep.jsmContainer.runBashCommandInContainer("test -f /faketime.cpp && echo status: \$?").contains("status: 0"): "Could not find the expected file /faketime.cpp in the container "
+ assert (new Date().toInstant().epochSecond - getJsmGroovyTime(jsmDep)).abs() < 5: "Time diff between JVM and localhost before any time traveling"
+
+
+ when:
+ log.info("Time traveling +60s")
+ assert setOffset(jsmDep, 600): "Error setting offset"
+ log.debug("\tSuccessfully set the time offset property")
+ sleep(30 * 1000) //Just to be sure the property change has time to get picked up
+
+ then:
+ log.debug("\tVerifying the travel worked")
+ assert (getJsmGroovyTime(jsmDep) - new Date().toInstant().epochSecond) > 50: "Time diff between JVM and localhost before any time traveling"
+ log.debug("\tSuccessfully time traveled!")
+
+
+ where:
+ baseurl | port | dockerHost | certPath
+ //"http://localhost" | "80" | "" | ""
+ //"http://jira2.domain.se:8082" | "8082" | dockerRemoteHost | dockerCertPath
+ "http://jira.domain.se:8080" | "8080" | dockerRemoteHost | dockerCertPath
+
+ }
+
+
+ long getJsmGroovyTime(JsmH2Deployment jsmDeploy) {
+
+
+ Map rawOut = jsmDeploy.jiraRest.executeLocalScriptFile("log.warn(\"EPOCH:\" + ( System.currentTimeMillis() / 1000).round(0))")
+ //Map rawOut = jsmDeploy.jiraRest.executeLocalScriptFile("log.warn(\"EPOCH:\" + new Date().toInstant().epochSecond)")
+
+ assert rawOut.success == true: "There was an error querying for GroovyTime from JSM ScriptRunner"
+ assert (rawOut.log as ArrayList).size() == 1
+
+ String rawLogStatement = (rawOut.log as ArrayList).get(0)
+ long epochS = rawLogStatement.substring(rawLogStatement.lastIndexOf(":") + 1).toLong()
+
+ return epochS
+
+ }
+
+
+ boolean setOffset(JsmH2Deployment jsmDeploy, long offsetS) {
+
+ Map rawOut = jsmDeploy.jiraRest.executeLocalScriptFile("System.setProperty(\"faketime.offset.seconds\", \"$offsetS\")")
+
+ assert rawOut.success == true: "There was an error querying for GroovyTime from JSM ScriptRunner"
+
+ return true
+ }
+
}