Skip to content

Commit e32697f

Browse files
committed
Initial implementation of TV checks
1 parent 1ad6dbc commit e32697f

File tree

3 files changed

+107
-2
lines changed

3 files changed

+107
-2
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
config:
2+
skip-target-branches: "main"
3+
steps:
4+
- label: ensure-transport-version-in-main
5+
command: .ci/scripts/run-gradle.sh -Dignore.tests.seed ensureTransportVersionsInMain
6+
timeout_in_minutes: 5
7+
agents:
8+
provider: gcp
9+
image: family/elasticsearch-ubuntu-2404
10+
machineType: custom-32-98304
11+
buildDirectory: /dev/shm/bk
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
steps:
2+
- label: validate-transport-versions
3+
command: .ci/scripts/run-gradle.sh -Dignore.tests.seed validateTransportVersions
4+
timeout_in_minutes: 5
5+
agents:
6+
provider: gcp
7+
image: family/elasticsearch-ubuntu-2404
8+
machineType: custom-32-98304
9+
buildDirectory: /dev/shm/bk

build.gradle

Lines changed: 87 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@
88
*/
99

1010

11-
import de.thetaphi.forbiddenapis.gradle.ForbiddenApisPlugin
1211
import com.avast.gradle.dockercompose.tasks.ComposePull
1312
import com.fasterxml.jackson.databind.JsonNode
1413
import com.fasterxml.jackson.databind.ObjectMapper
15-
14+
import de.thetaphi.forbiddenapis.gradle.ForbiddenApisPlugin
1615
import org.elasticsearch.gradle.DistributionDownloadPlugin
1716
import org.elasticsearch.gradle.Version
1817
import org.elasticsearch.gradle.VersionProperties
@@ -22,6 +21,9 @@ import org.elasticsearch.gradle.util.GradleUtils
2221
import org.gradle.plugins.ide.eclipse.model.AccessRule
2322

2423
import java.nio.file.Files
24+
import java.util.Map.Entry
25+
import java.util.regex.Matcher
26+
import java.util.stream.Collectors
2527

2628
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING
2729
import static org.elasticsearch.gradle.util.GradleUtils.maybeConfigure
@@ -203,6 +205,89 @@ tasks.register("updateCIBwcVersions") {
203205
}
204206
}
205207

208+
/**
209+
* This task forces all TransportVersion (TV) changes to first go through the main branch, to force synchronization of
210+
* TV changes. We never want to be in a situation where a transport version integer ID is duplicated in two places
211+
* (e.g. 8.x and main) for two independent constants. This situation has happened historically, and was a result of
212+
* forward-porting TV changes to main. This task is not applicable to PRs to main, but only for PRs to older versions.
213+
*/
214+
tasks.register("ensureTransportVersionsInMain") {
215+
doLast {
216+
if (gradle.startParameter.isOffline()) {
217+
throw new GradleException("Must run in online mode to verify that transport versions already exist in main first.")
218+
}
219+
def mainTransportVersions = new URI('https://raw.githubusercontent.com/elastic/elasticsearch/refs/heads/main/server/src/main/java/org/elasticsearch/TransportVersions.java').toURL().text
220+
def currentTransportVersions = file('server/src/main/java/org/elasticsearch/TransportVersions.java').text
221+
222+
def mainTVNameAndIds = extractTransportVersionNameAndID(mainTransportVersions)
223+
def currentNameAndIds = extractTransportVersionNameAndID(currentTransportVersions)
224+
225+
ensureAllCurrentTVsExistInMain(currentNameAndIds, mainTVNameAndIds)
226+
}
227+
}
228+
229+
static def ensureAllCurrentTVsExistInMain(Map<String, Integer> currentNameAndIds, Map<String, Integer> mainTVNameAndIds) {
230+
currentNameAndIds.each { versionName, versionValue ->
231+
if (mainTVNameAndIds[versionName] != versionValue) {
232+
throw new GradleException("Transport version ${versionName} with value ${versionValue} is not present in " +
233+
"the main branch. All TVs must be added to main first, prior to backporting.")
234+
}
235+
}
236+
}
237+
238+
static Map<String, Integer> extractTransportVersionNameAndID(String transportVersionFileAsText) {
239+
def transportVersionRegex = /public static final TransportVersion (\w+) = def\((\w+)\);/
240+
Matcher tVMatcher = transportVersionFileAsText =~ transportVersionRegex
241+
return tVMatcher.iterator().collectEntries { [(it[1]): Integer.valueOf(it[2].replace("_", ""))] }
242+
}
243+
244+
/**
245+
* This task validates that the transport versions in the current branch are consistent with the main branch, and tests
246+
* for a number of race conditions that would lead to an invalid state if this branch was merged.
247+
*/
248+
tasks.register("validateTransportVersions") {
249+
doLast {
250+
if (gradle.startParameter.isOffline()) {
251+
throw new GradleException("Must run in online mode to validate transport versions")
252+
}
253+
def mainTransportVersions = new URI('https://raw.githubusercontent.com/elastic/elasticsearch/refs/heads/main/server/src/main/java/org/elasticsearch/TransportVersions.java').toURL().text
254+
def currentTransportVersions = file('server/src/main/java/org/elasticsearch/TransportVersions.java').text
255+
256+
def mainTVNameAndIds = extractTransportVersionNameAndID(mainTransportVersions)
257+
def currentNameAndIds = extractTransportVersionNameAndID(currentTransportVersions)
258+
259+
// Ensure that this is the latest TV (e.g. there are no newer Ids merged to main). Applicable for all branches.
260+
def latestCurrentId = currentNameAndIds.values().stream().max(Integer::compare).get();
261+
def latestMainId = mainTVNameAndIds.values().stream().max(Integer::compare).get();
262+
if (latestCurrentId < latestMainId) {
263+
throw new GradleException("The latest transport version with id ${latestCurrentId} on the current branch is " +
264+
"out of date with main, who latest transport version has the id ${latestMainId}")
265+
}
266+
267+
// Ensure that there are no duplicate IDs. Applicable for main builds, preventing the race conditions author1
268+
// checks-out main, then author2 checks out main and commits a new TV with the next TV id, then author1 commits a
269+
// TV with the same TV id as author 2. This will break the main build in this situation, but catch the bug.
270+
def idSet = new HashSet<Integer>()
271+
currentNameAndIds.each { versionName, versionValue ->
272+
if (idSet.contains(versionValue)) {
273+
throw new GradleException("Duplicate transport version id ${versionValue} found for version ${versionName}. " +
274+
"Transport versions must have unique IDs.")
275+
}
276+
idSet.add(versionValue)
277+
}
278+
279+
// Ensure there isn't a TV with another name but same ID in main. An additional check to try to catch the race
280+
// condition described above, but hopefully earlier on the PR prior to breaking the main build.
281+
def mainTVIdToName = mainTVNameAndIds.entrySet().stream().collect(Collectors.toMap(Entry::getValue, Entry::getKey))
282+
currentNameAndIds.each { versionName, versionValue ->
283+
if (mainTVIdToName.containsKey(versionValue) && mainTVIdToName.get(versionValue) != versionName) {
284+
throw new GradleException("Transport version id ${versionValue} is already used by ${mainTVIdToName.get(versionValue)} in main, " +
285+
"but is being used by ${versionName} in the current branch. Transport versions must have unique IDs.")
286+
}
287+
}
288+
}
289+
}
290+
206291
tasks.register("verifyVersions") {
207292
def verifyCiYaml = { File file, List<Version> versions ->
208293
String ciYml = file.text

0 commit comments

Comments
 (0)