Skip to content

Commit 279be1b

Browse files
authored
2.3.20 - DirectorySyncer improvements (#41)
* DirectorySyncer.groovy * Now returns existing matching DirectorySyncer-container if it exists. Intended to stop creation of duplicates * Bumped to 2.3.20
1 parent e3efe3d commit 279be1b

File tree

4 files changed

+130
-6
lines changed

4 files changed

+130
-6
lines changed

pom.xml

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
<groupId>com.eficode</groupId>
88
<artifactId>devstack</artifactId>
9-
<version>2.3.19</version>
9+
<version>2.3.20</version>
1010
<packaging>jar</packaging>
1111

1212
<name>DevStack</name>

src/main/groovy/com/eficode/devstack/container/Container.groovy

+1-1
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ trait Container {
184184
ArrayList<MountPoint> getMounts() {
185185

186186
ContainerInspectResponse response = inspectContainer()
187-
return response.mounts
187+
return response?.mounts
188188
}
189189

190190
ContainerCreateRequest setupContainerCreateRequest() {

src/main/groovy/com/eficode/devstack/util/DirectorySyncer.groovy

+82-4
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package com.eficode.devstack.util
22

33
import com.eficode.devstack.container.Container
4+
import com.fasterxml.jackson.databind.ObjectMapper
45
import de.gesellix.docker.client.EngineResponseContent
56
import de.gesellix.docker.remote.api.ContainerSummary
7+
import de.gesellix.docker.remote.api.Mount
8+
import de.gesellix.docker.remote.api.MountPoint
69
import de.gesellix.docker.remote.api.Volume
710
import org.slf4j.Logger
811

@@ -23,6 +26,19 @@ class DirectorySyncer implements Container {
2326
}
2427
}
2528

29+
/**
30+
* Create a DirectorySyncer based on existing container
31+
* @param dockerClient
32+
* @param summary
33+
*/
34+
DirectorySyncer (DockerClientDS dockerClient, ContainerSummary summary) {
35+
36+
DirectorySyncer syncer = new DirectorySyncer(dockerClient.host, dockerClient.certPath)
37+
syncer.containerName = summary.names.first().replaceFirst("/", "")
38+
39+
40+
}
41+
2642
static String getSyncScript(String rsyncOptions = "-avh") {
2743

2844
return """
@@ -80,10 +96,56 @@ class DirectorySyncer implements Container {
8096

8197
}
8298

99+
100+
101+
/**
102+
* Checks if there already exists a DirectorySyncer container with the same mount points,
103+
* if found will return that container
104+
* @return
105+
*/
106+
DirectorySyncer getDuplicateContainer() {
107+
108+
Map filterMap = [name: ["DirectorySyncer.*"], "volume": this.preparedMounts.collect { it.target }]
109+
String filterString = new ObjectMapper().writeValueAsString(filterMap)
110+
ArrayList<ContainerSummary> looselyMatchingContainers = dockerClient.ps(true, null, false, filterString).content
111+
ArrayList<ContainerSummary> matchingContainers = []
112+
ArrayList<String> myMounts = this.preparedMounts.target
113+
myMounts += this.preparedMounts.findAll {it.type == Mount.Type.Volume}.source
114+
if (looselyMatchingContainers) {
115+
matchingContainers = looselyMatchingContainers.findAll { matchingContainer ->
116+
117+
ArrayList<String> matchingMounts = matchingContainer.mounts.destination
118+
matchingMounts += matchingContainer.mounts.findAll {it.type == MountPoint.Type.Volume}.name
119+
//Handles the fact the mount points arent always given with a trailing /
120+
Boolean mountsMatch = myMounts.every { myMount ->
121+
matchingMounts.any { it.equalsIgnoreCase(myMount) } ||
122+
matchingMounts.collect { it + "/" }.any { it.equalsIgnoreCase(myMount) }
123+
}
124+
125+
return mountsMatch
126+
127+
}
128+
}
129+
130+
if (matchingContainers.size() > 1) {
131+
throw new InputMismatchException("Found multiple potential duplicate DirectorySyncer´s: " + matchingContainers.collect { it.id }.join(","))
132+
} else if (matchingContainers.size() == 1) {
133+
return new DirectorySyncer(dockerClient, matchingContainers.first())
134+
} else {
135+
return null
136+
}
137+
138+
}
139+
83140
/**
84141
* <pre>
85-
* Creates a Util container:
86-
* 1. Listens for file changes in one or more docker engine src paths (hostAbsSourcePaths)
142+
* This UtilContainer is intended to be used to sync one or several docker engine local dirs
143+
* to a Docker volume continuously
144+
*
145+
* If a DirectorySyncer with the same mount points exists, it will be started and returned instead
146+
*
147+
* The container will :
148+
* 1. Listen for file changes in one or more docker engine src paths recursively (hostAbsSourcePaths)
87149
* 2. If changes are detected rsync is triggered
88150
* 3. Rsync detects changes and sync them to destVolumeName
89151
*
@@ -134,9 +196,25 @@ class DirectorySyncer implements Container {
134196

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

137-
hostAbsSourcePaths.each { srcPath ->
199+
hostAbsSourcePaths.eachWithIndex { srcPath, index ->
200+
138201
String srcDirName = srcPath.substring(srcPath.lastIndexOf("/") + 1)
139-
container.prepareBindMount(srcPath, "/mnt/src/$srcDirName", true)
202+
String targetPath = "/mnt/src/$srcDirName"
203+
if (container.preparedMounts.any { it.target == targetPath }) {
204+
targetPath = targetPath + index //Make sure target path is unique
205+
}
206+
container.prepareBindMount(srcPath, targetPath, true)
207+
}
208+
209+
DirectorySyncer duplicate = container.getDuplicateContainer()
210+
if (duplicate) {
211+
log.info("\tFound an existing DirectorySyncer with same mount points:" + duplicate.shortId)
212+
if (!duplicate.running) {
213+
log.debug("\t" * 2 + "Duplicate is not running, starting it")
214+
duplicate.startContainer()
215+
}
216+
log.info("\t" * 2 + "Returning duplicate instead of creating a new one")
217+
return duplicate
140218
}
141219

142220
container.createContainer(["/bin/sh", "-c", "echo \"\$syncScript\" > /syncScript.sh && /bin/sh syncScript.sh"], [])

src/test/groovy/com/eficode/devstack/container/impl/DirectorySyncerTest.groovy

+46
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,52 @@ class DirectorySyncerTest extends DevStackSpec {
2828
Boolean result = dockerClient.volumes().content?.volumes?.any { it.name == volumeName }
2929
return result
3030

31+
}
32+
33+
def "Test create duplicate Syncer"() {
34+
35+
log.info("Testing that createSyncToVolume detects a duplicate container and returns it in stead of creating a new one")
36+
File srcDir1 = File.createTempDir("srcDir1")
37+
log.debug("\tCreated Engine local temp dir:" + srcDir1.canonicalPath)
38+
File srcDir2 = File.createTempDir("srcDir2")
39+
log.debug("\tCreated Engine local temp dir:" + srcDir2.canonicalPath)
40+
41+
String uniqueVolumeName = "syncVolume" + System.currentTimeMillis().toString().takeRight(3)
42+
!volumeExists(uniqueVolumeName) ?: dockerClient.rmVolume(uniqueVolumeName)
43+
log.debug("\tWill use sync to Docker volume:" + uniqueVolumeName)
44+
45+
DirectorySyncer firstSyncer = DirectorySyncer.createSyncToVolume([srcDir1.canonicalPath, srcDir2.canonicalPath], uniqueVolumeName, "-avh --delete", dockerRemoteHost, dockerCertPath )
46+
log.info("\tCreated first sync container: ${firstSyncer.containerName} (${firstSyncer.shortId})")
47+
Integer containersAfterFirst = firstSyncer.dockerClient.ps(true).content.size()
48+
log.info("\t\tDocker engine now has a total of ${containersAfterFirst} contianers")
49+
50+
when: "Creating second sync container"
51+
DirectorySyncer secondSyncer = DirectorySyncer.createSyncToVolume([srcDir1.canonicalPath, srcDir2.canonicalPath], uniqueVolumeName, "-avh --delete", dockerRemoteHost, dockerCertPath )
52+
log.info("\tCreated second sync container: ${secondSyncer.containerName} (${secondSyncer.shortId})")
53+
54+
then: "They should have the same ID"
55+
assert firstSyncer.id == secondSyncer.id : "The second container doesnt have the same id"
56+
assert containersAfterFirst == secondSyncer.dockerClient.ps(true).content.size() : "The number of containers changed after creating the second one"
57+
assert secondSyncer.running
58+
log.info("\tA duplicate container was not created, instead the first one was returned")
59+
60+
when: "Stopping the sync container, and creating another duplicate"
61+
firstSyncer.stopContainer()
62+
assert !firstSyncer.running
63+
secondSyncer = DirectorySyncer.createSyncToVolume([srcDir1.canonicalPath, srcDir2.canonicalPath], uniqueVolumeName, "-avh --delete", dockerRemoteHost, dockerCertPath )
64+
65+
then:"The duplicate should have been automatically started"
66+
secondSyncer.running
67+
68+
69+
cleanup:
70+
assert srcDir1.deleteDir()
71+
assert srcDir2.deleteDir()
72+
assert firstSyncer.stopAndRemoveContainer()
73+
dockerClient.rmVolume(uniqueVolumeName)
74+
75+
76+
3177
}
3278

3379
def "Test createSyncToVolume"() {

0 commit comments

Comments
 (0)