Skip to content

Commit

Permalink
2.3.20 - DirectorySyncer improvements (#41)
Browse files Browse the repository at this point in the history
* DirectorySyncer.groovy
 * Now returns existing matching DirectorySyncer-container if it exists. Intended to stop creation of duplicates

* Bumped to 2.3.20
  • Loading branch information
farthinder authored Apr 30, 2024
1 parent e3efe3d commit 279be1b
Show file tree
Hide file tree
Showing 4 changed files with 130 additions and 6 deletions.
2 changes: 1 addition & 1 deletion 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.19</version>
<version>2.3.20</version>
<packaging>jar</packaging>

<name>DevStack</name>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ trait Container {
ArrayList<MountPoint> getMounts() {

ContainerInspectResponse response = inspectContainer()
return response.mounts
return response?.mounts
}

ContainerCreateRequest setupContainerCreateRequest() {
Expand Down
86 changes: 82 additions & 4 deletions src/main/groovy/com/eficode/devstack/util/DirectorySyncer.groovy
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.eficode.devstack.util

import com.eficode.devstack.container.Container
import com.fasterxml.jackson.databind.ObjectMapper
import de.gesellix.docker.client.EngineResponseContent
import de.gesellix.docker.remote.api.ContainerSummary
import de.gesellix.docker.remote.api.Mount
import de.gesellix.docker.remote.api.MountPoint
import de.gesellix.docker.remote.api.Volume
import org.slf4j.Logger

Expand All @@ -23,6 +26,19 @@ class DirectorySyncer implements Container {
}
}

/**
* Create a DirectorySyncer based on existing container
* @param dockerClient
* @param summary
*/
DirectorySyncer (DockerClientDS dockerClient, ContainerSummary summary) {

DirectorySyncer syncer = new DirectorySyncer(dockerClient.host, dockerClient.certPath)
syncer.containerName = summary.names.first().replaceFirst("/", "")


}

static String getSyncScript(String rsyncOptions = "-avh") {

return """
Expand Down Expand Up @@ -80,10 +96,56 @@ class DirectorySyncer implements Container {

}



/**
* Checks if there already exists a DirectorySyncer container with the same mount points,
* if found will return that container
* @return
*/
DirectorySyncer getDuplicateContainer() {

Map filterMap = [name: ["DirectorySyncer.*"], "volume": this.preparedMounts.collect { it.target }]
String filterString = new ObjectMapper().writeValueAsString(filterMap)
ArrayList<ContainerSummary> looselyMatchingContainers = dockerClient.ps(true, null, false, filterString).content
ArrayList<ContainerSummary> matchingContainers = []
ArrayList<String> myMounts = this.preparedMounts.target
myMounts += this.preparedMounts.findAll {it.type == Mount.Type.Volume}.source
if (looselyMatchingContainers) {
matchingContainers = looselyMatchingContainers.findAll { matchingContainer ->

ArrayList<String> matchingMounts = matchingContainer.mounts.destination
matchingMounts += matchingContainer.mounts.findAll {it.type == MountPoint.Type.Volume}.name
//Handles the fact the mount points arent always given with a trailing /
Boolean mountsMatch = myMounts.every { myMount ->
matchingMounts.any { it.equalsIgnoreCase(myMount) } ||
matchingMounts.collect { it + "/" }.any { it.equalsIgnoreCase(myMount) }
}

return mountsMatch

}
}

if (matchingContainers.size() > 1) {
throw new InputMismatchException("Found multiple potential duplicate DirectorySyncer´s: " + matchingContainers.collect { it.id }.join(","))
} else if (matchingContainers.size() == 1) {
return new DirectorySyncer(dockerClient, matchingContainers.first())
} else {
return null
}

}

/**
* <pre>
* Creates a Util container:
* 1. Listens for file changes in one or more docker engine src paths (hostAbsSourcePaths)
* This UtilContainer is intended to be used to sync one or several docker engine local dirs
* to a Docker volume continuously
*
* If a DirectorySyncer with the same mount points exists, it will be started and returned instead
*
* The container will :
* 1. Listen for file changes in one or more docker engine src paths recursively (hostAbsSourcePaths)
* 2. If changes are detected rsync is triggered
* 3. Rsync detects changes and sync them to destVolumeName
*
Expand Down Expand Up @@ -134,9 +196,25 @@ class DirectorySyncer implements Container {

container.prepareVolumeMount(volume.name, "/mnt/dest/", false)

hostAbsSourcePaths.each { srcPath ->
hostAbsSourcePaths.eachWithIndex { srcPath, index ->

String srcDirName = srcPath.substring(srcPath.lastIndexOf("/") + 1)
container.prepareBindMount(srcPath, "/mnt/src/$srcDirName", true)
String targetPath = "/mnt/src/$srcDirName"
if (container.preparedMounts.any { it.target == targetPath }) {
targetPath = targetPath + index //Make sure target path is unique
}
container.prepareBindMount(srcPath, targetPath, true)
}

DirectorySyncer duplicate = container.getDuplicateContainer()
if (duplicate) {
log.info("\tFound an existing DirectorySyncer with same mount points:" + duplicate.shortId)
if (!duplicate.running) {
log.debug("\t" * 2 + "Duplicate is not running, starting it")
duplicate.startContainer()
}
log.info("\t" * 2 + "Returning duplicate instead of creating a new one")
return duplicate
}

container.createContainer(["/bin/sh", "-c", "echo \"\$syncScript\" > /syncScript.sh && /bin/sh syncScript.sh"], [])
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,52 @@ class DirectorySyncerTest extends DevStackSpec {
Boolean result = dockerClient.volumes().content?.volumes?.any { it.name == volumeName }
return result

}

def "Test create duplicate Syncer"() {

log.info("Testing that createSyncToVolume detects a duplicate container and returns it in stead of creating a new one")
File srcDir1 = File.createTempDir("srcDir1")
log.debug("\tCreated Engine local temp dir:" + srcDir1.canonicalPath)
File srcDir2 = File.createTempDir("srcDir2")
log.debug("\tCreated Engine local temp dir:" + srcDir2.canonicalPath)

String uniqueVolumeName = "syncVolume" + System.currentTimeMillis().toString().takeRight(3)
!volumeExists(uniqueVolumeName) ?: dockerClient.rmVolume(uniqueVolumeName)
log.debug("\tWill use sync to Docker volume:" + uniqueVolumeName)

DirectorySyncer firstSyncer = DirectorySyncer.createSyncToVolume([srcDir1.canonicalPath, srcDir2.canonicalPath], uniqueVolumeName, "-avh --delete", dockerRemoteHost, dockerCertPath )
log.info("\tCreated first sync container: ${firstSyncer.containerName} (${firstSyncer.shortId})")
Integer containersAfterFirst = firstSyncer.dockerClient.ps(true).content.size()
log.info("\t\tDocker engine now has a total of ${containersAfterFirst} contianers")

when: "Creating second sync container"
DirectorySyncer secondSyncer = DirectorySyncer.createSyncToVolume([srcDir1.canonicalPath, srcDir2.canonicalPath], uniqueVolumeName, "-avh --delete", dockerRemoteHost, dockerCertPath )
log.info("\tCreated second sync container: ${secondSyncer.containerName} (${secondSyncer.shortId})")

then: "They should have the same ID"
assert firstSyncer.id == secondSyncer.id : "The second container doesnt have the same id"
assert containersAfterFirst == secondSyncer.dockerClient.ps(true).content.size() : "The number of containers changed after creating the second one"
assert secondSyncer.running
log.info("\tA duplicate container was not created, instead the first one was returned")

when: "Stopping the sync container, and creating another duplicate"
firstSyncer.stopContainer()
assert !firstSyncer.running
secondSyncer = DirectorySyncer.createSyncToVolume([srcDir1.canonicalPath, srcDir2.canonicalPath], uniqueVolumeName, "-avh --delete", dockerRemoteHost, dockerCertPath )

then:"The duplicate should have been automatically started"
secondSyncer.running


cleanup:
assert srcDir1.deleteDir()
assert srcDir2.deleteDir()
assert firstSyncer.stopAndRemoveContainer()
dockerClient.rmVolume(uniqueVolumeName)



}

def "Test createSyncToVolume"() {
Expand Down

0 comments on commit 279be1b

Please sign in to comment.