Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wip #33

Draft
wants to merge 8 commits into
base: master
Choose a base branch
from
Draft

wip #33

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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
* From now on two artifacts will be generated, devstack and devstack-standalone and the classifier standalone is deprecated
6 changes: 3 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.eficode</groupId>
<artifactId>devstack</artifactId>
<version>2.3.12-SNAPSHOT</version>
<version>2.3.13-SNAPSHOT</version>
<packaging>jar</packaging>

<name>DevStack</name>
Expand Down Expand Up @@ -102,7 +102,7 @@
<dependency>
<groupId>com.eficode.atlassian</groupId>
<artifactId>jirainstancemanager</artifactId>
<version>2.0.3-SNAPSHOT</version>
<version>2.0.9-SNAPSHOT</version>
</dependency>


Expand Down Expand Up @@ -228,4 +228,4 @@
</plugins>
</build>

</project>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -974,4 +974,4 @@ trait Container {
return callBack.output
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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
Expand All @@ -59,50 +65,58 @@ 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


c.exposedPorts = [(containerMainPort + "/tcp"): [:]]
c.hostConfig = new HostConfig().tap { h ->
h.portBindings = [(containerMainPort + "/tcp"): [new PortBinding("0.0.0.0", (containerMainPort))]]

ArrayList<String> 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
Expand All @@ -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" }
}


Expand All @@ -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()
}
Expand All @@ -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)
}

Expand All @@ -171,7 +185,7 @@ class JsmContainer implements Container {
snapshotName = snapshotName ?: shortId + "-clone"

ArrayList<Volume> existingVolumes = dockerClient.getVolumesWithName(snapshotName)
existingVolumes.each {existingVolume ->
existingVolumes.each { existingVolume ->
log.debug("\tRemoving existing snapshot volume:" + existingVolume.name)
dockerClient.manageVolume.rmVolume(existingVolume.name)
}
Expand All @@ -189,16 +203,16 @@ 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<String, Object> labels = null) {

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)
Expand Down
16 changes: 16 additions & 0 deletions src/main/groovy/com/eficode/devstack/util/DockerClientDS.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -92,6 +93,21 @@ class DockerClientDS extends DockerClientImpl {

}

ContainerSummary getContainerById(String completeId) {
EngineResponse response = ps(true, 1000, true, " {\"id\":[\"${completeId}\"]}")


ArrayList<ContainerSummary> containers = response.content

return containers.find{true}

}


EngineResponseContent<ContainerInspectResponse> inspectContainer(ContainerSummary containerSummary){
return inspectContainer(containerSummary.id)
}


EngineResponseContent<Volume> createVolume(String name = null, Map<String, String> labels = null, Map<String, String> driverOpts = null) {
VolumeCreateOptions volumeOptions = new VolumeCreateOptions()
Expand Down
119 changes: 114 additions & 5 deletions src/main/groovy/com/eficode/devstack/util/ImageBuilder.groovy
Original file line number Diff line number Diff line change
Expand Up @@ -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
*
Expand All @@ -16,7 +15,6 @@ class ImageBuilder extends DoodContainer {
Map<String, ArrayList<String>>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")
Expand Down Expand Up @@ -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"

Expand All @@ -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<ImageSummary> 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<ImageSummary> 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<ImageSummary> 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<ImageSummary> images = dockerClient.images().content
ImageSummary newImage = images.find {it.repoTags == [fakeTimeImageTag]}
return newImage

}

Expand Down Expand Up @@ -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)
Expand Down
Loading