diff --git a/server/src/main/java/org/elasticsearch/TransportVersion.java b/server/src/main/java/org/elasticsearch/TransportVersion.java
index f1e20ac4bb77e..2ac4c1bf72ab6 100644
--- a/server/src/main/java/org/elasticsearch/TransportVersion.java
+++ b/server/src/main/java/org/elasticsearch/TransportVersion.java
@@ -15,19 +15,35 @@
import org.elasticsearch.internal.VersionExtension;
import org.elasticsearch.plugins.ExtensionLoader;
+import java.io.BufferedReader;
import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UncheckedIOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.ServiceLoader;
import java.util.function.Function;
+import java.util.function.IntFunction;
import java.util.stream.Collectors;
-import java.util.stream.Stream;
/**
* Represents the version of the wire protocol used to communicate between a pair of ES nodes.
*
+ * Note: We are currently transitioning to a file-based system to load and maintain transport versions. These file-based transport
+ * versions are named and are referred to as named transport versions. Named transport versions also maintain a linked list of their
+ * own patch versions to simplify transport version compatibility checks. Transport versions that continue to be loaded through
+ * {@link TransportVersions} are referred to as unnamed transport versions. Unnamed transport versions will continue being used
+ * over the wire as we only need the id for compatibility checks even against named transport versions. There are changes
+ * throughout {@link TransportVersion} that are for this transition. For now, continue to use the existing system of adding unnamed
+ * transport versions to {@link TransportVersions}.
+ *
* Prior to 8.8.0, the release {@link Version} was used everywhere. This class separates the wire protocol version from the release version.
*
* Each transport version constant has an id number, which for versions prior to 8.9.0 is the same as the release version for backwards
@@ -57,7 +73,55 @@
* different version value. If you need to know whether the cluster as a whole speaks a new enough {@link TransportVersion} to understand a
* newly-added feature, use {@link org.elasticsearch.cluster.ClusterState#getMinTransportVersion}.
*/
-public record TransportVersion(int id) implements VersionId {
+public record TransportVersion(String name, int id, TransportVersion nextPatchVersion) implements VersionId {
+
+ /**
+ * Constructs an unnamed transport version.
+ */
+ public TransportVersion(int id) {
+ this(null, id, null);
+ }
+
+ /**
+ * Constructs a named transport version along with its set of compatible patch versions from x-content.
+ * This method takes in the parameter {@code latest} which is the highest valid transport version id
+ * supported by this node. Versions newer than the current transport version id for this node are discarded.
+ */
+ public static TransportVersion fromInputStream(String path, boolean nameInFile, InputStream stream, Integer latest) {
+ try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8))) {
+ String line = reader.readLine();
+ String[] parts = line.replaceAll("\\s+", "").split(",");
+ String check;
+ while ((check = reader.readLine()) != null) {
+ if (check.replaceAll("\\s+", "").isEmpty() == false) {
+ throw new IllegalArgumentException("invalid transport version file format [" + path + "]");
+ }
+ }
+ if (parts.length < (nameInFile ? 2 : 1)) {
+ throw new IllegalStateException("invalid transport version file format [" + path + "]");
+ }
+ String name = nameInFile ? parts[0] : path.substring(path.lastIndexOf('/') + 1, path.length() - 4);
+ List ids = new ArrayList<>();
+ for (int i = nameInFile ? 1 : 0; i < parts.length; ++i) {
+ try {
+ ids.add(Integer.parseInt(parts[i]));
+ } catch (NumberFormatException nfe) {
+ throw new IllegalStateException("invalid transport version file format [" + path + "]", nfe);
+ }
+ }
+ ids.sort(Integer::compareTo);
+ TransportVersion transportVersion = null;
+ for (int idIndex = 0; idIndex < ids.size(); ++idIndex) {
+ if (ids.get(idIndex) > latest) {
+ break;
+ }
+ transportVersion = new TransportVersion(name, ids.get(idIndex), transportVersion);
+ }
+ return transportVersion;
+ } catch (IOException ioe) {
+ throw new UncheckedIOException("cannot parse transport version [" + path + "]", ioe);
+ }
+ }
public static TransportVersion readVersion(StreamInput in) throws IOException {
return fromId(in.readVInt());
@@ -70,7 +134,7 @@ public static TransportVersion readVersion(StreamInput in) throws IOException {
* The new instance is not registered in {@code TransportVersion.getAllVersions}.
*/
public static TransportVersion fromId(int id) {
- TransportVersion known = VersionsHolder.ALL_VERSIONS_MAP.get(id);
+ TransportVersion known = VersionsHolder.ALL_VERSIONS_BY_ID.get(id);
if (known != null) {
return known;
}
@@ -78,6 +142,23 @@ public static TransportVersion fromId(int id) {
return new TransportVersion(id);
}
+ /**
+ * Finds a {@link TransportVersion} by its name. The parameter {@code name} must be a {@link String}
+ * direct value or validation checks will fail. {@code TransportVersion.fromName("direct_value")}.
+ *
+ * This will only return the latest known named transport version for a given name and not its
+ * patch versions. Patch versions are constructed as a linked list internally and may be found by
+ * cycling through them in a loop using {@link TransportVersion#nextPatchVersion()}.
+ *
+ */
+ public static TransportVersion fromName(String name) {
+ TransportVersion known = VersionsHolder.ALL_VERSIONS_BY_NAME.get(name);
+ if (known == null) {
+ throw new IllegalStateException("unknown transport version [" + name + "]");
+ }
+ return known;
+ }
+
public static void writeVersion(TransportVersion version, StreamOutput out) throws IOException {
out.writeVInt(version.id);
}
@@ -123,7 +204,7 @@ public static List getAllVersions() {
* in the wild (they're sent over the wire by numeric ID) but we don't know how to communicate using such versions.
*/
public boolean isKnown() {
- return VersionsHolder.ALL_VERSIONS_MAP.containsKey(id);
+ return VersionsHolder.ALL_VERSIONS_BY_ID.containsKey(id);
}
/**
@@ -135,7 +216,7 @@ public TransportVersion bestKnownVersion() {
return this;
}
TransportVersion bestSoFar = TransportVersions.ZERO;
- for (final var knownVersion : VersionsHolder.ALL_VERSIONS_MAP.values()) {
+ for (final var knownVersion : VersionsHolder.ALL_VERSIONS_BY_ID.values()) {
if (knownVersion.after(bestSoFar) && knownVersion.before(this)) {
bestSoFar = knownVersion;
}
@@ -171,12 +252,75 @@ public boolean isPatchFrom(TransportVersion version) {
return onOrAfter(version) && id < version.id + 100 - (version.id % 100);
}
+ /**
+ * Supports is used to determine if a named transport version is supported
+ * by a caller transport version. This will check both the latest id
+ * and all of its patch ids for compatibility. This replaces the pattern
+ * of {@code wireTV.onOrAfter(TV_FEATURE) || wireTV.isPatchFrom(TV_FEATURE_BACKPORT) || ...}
+ * for unnamed transport versions with {@code wireTV.supports(TV_FEATURE)} for named
+ * transport versions (since named versions know about their own patch versions).
+ *
+ * The recommended use of this method is to declare a static final {@link TransportVersion}
+ * as part of the file that it's used in. This constant is then used in conjunction with
+ * this method to check transport version compatability.
+ *
+ * An example:
+ * {@code
+ * public class ExampleClass {
+ * ...
+ * TransportVersion TV_FEATURE = TransportVersion.fromName("tv_feature");
+ * ...
+ * public static ExampleClass readFrom(InputStream in) {
+ * ...
+ * if (in.getTransportVersion().supports(TV_FEATURE) {
+ * // read newer values
+ * }
+ * ...
+ * }
+ * ...
+ * public void writeTo(OutputStream out) {
+ * ...
+ * if (out.getTransportVersion().supports(TV_FEATURE) {
+ * // write newer values
+ * }
+ * ...
+ * }
+ * ...
+ * }
+ * }
+ */
+ public boolean supports(TransportVersion version) {
+ if (onOrAfter(version)) {
+ return true;
+ }
+ TransportVersion nextPatchVersion = version.nextPatchVersion;
+ while (nextPatchVersion != null) {
+ if (isPatchFrom(nextPatchVersion)) {
+ return true;
+ }
+ nextPatchVersion = nextPatchVersion.nextPatchVersion;
+ }
+ return false;
+ }
+
/**
* Returns a string representing the Elasticsearch release version of this transport version,
* if applicable for this deployment, otherwise the raw version number.
*/
public String toReleaseVersion() {
- return TransportVersions.VERSION_LOOKUP.apply(id);
+ return VersionsHolder.VERSION_LOOKUP_BY_RELEASE.apply(id);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == null || getClass() != o.getClass()) return false;
+ TransportVersion that = (TransportVersion) o;
+ return id == that.id;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hashCode(id);
}
@Override
@@ -184,25 +328,120 @@ public String toString() {
return Integer.toString(id);
}
+ /**
+ * This class holds various data structures for looking up known transport versions both
+ * named and unnamed. While we transition to named transport versions, this class will
+ * load and merge unnamed transport versions from {@link TransportVersions} along with
+ * named transport versions specified in a manifest file in resources.
+ */
private static class VersionsHolder {
+
private static final List ALL_VERSIONS;
- private static final Map ALL_VERSIONS_MAP;
+ private static final Map ALL_VERSIONS_BY_ID;
+ private static final Map ALL_VERSIONS_BY_NAME;
+ private static final IntFunction VERSION_LOOKUP_BY_RELEASE;
private static final TransportVersion CURRENT;
static {
+ // collect all the transport versions from server and es modules/plugins (defined in server)
+ List allVersions = new ArrayList<>(TransportVersions.DEFINED_VERSIONS);
+ Map allVersionsByName = loadTransportVersionsByName();
+ addTransportVersions(allVersionsByName.values(), allVersions).sort(TransportVersion::compareTo);
+
+ // set version lookup by release before adding serverless versions
+ // serverless versions should not affect release version
+ VERSION_LOOKUP_BY_RELEASE = ReleaseVersions.generateVersionsLookup(
+ TransportVersions.class,
+ allVersions.get(allVersions.size() - 1).id()
+ );
+
+ // collect all the transport versions from serverless
Collection extendedVersions = ExtensionLoader.loadSingleton(ServiceLoader.load(VersionExtension.class))
.map(VersionExtension::getTransportVersions)
.orElse(Collections.emptyList());
+ addTransportVersions(extendedVersions, allVersions).sort(TransportVersion::compareTo);
+ for (TransportVersion version : extendedVersions) {
+ if (version.name() != null) {
+ allVersionsByName.put(version.name(), version);
+ }
+ }
- if (extendedVersions.isEmpty()) {
- ALL_VERSIONS = TransportVersions.DEFINED_VERSIONS;
- } else {
- ALL_VERSIONS = Stream.concat(TransportVersions.DEFINED_VERSIONS.stream(), extendedVersions.stream()).sorted().toList();
+ // set the transport version lookups
+ ALL_VERSIONS = Collections.unmodifiableList(allVersions);
+ ALL_VERSIONS_BY_ID = ALL_VERSIONS.stream().collect(Collectors.toUnmodifiableMap(TransportVersion::id, Function.identity()));
+ ALL_VERSIONS_BY_NAME = Collections.unmodifiableMap(allVersionsByName);
+ CURRENT = ALL_VERSIONS.getLast();
+ }
+
+ private static Map loadTransportVersionsByName() {
+ Map transportVersions = new HashMap<>();
+
+ String latestLocation = "/transport/latest/" + Version.CURRENT.major + "." + Version.CURRENT.minor + ".csv";
+ int latestId = -1;
+ try (InputStream inputStream = TransportVersion.class.getResourceAsStream(latestLocation)) {
+ // this check is required until bootstrapping for the new transport versions format is completed;
+ // when load is false, we will only use the transport versions in the legacy format;
+ // load becomes false if we don't find the latest or manifest files required for the new format
+ if (inputStream != null) {
+ TransportVersion latest = fromInputStream(latestLocation, true, inputStream, Integer.MAX_VALUE);
+ if (latest == null) {
+ throw new IllegalStateException(
+ "invalid latest transport version for minor version ["
+ + Version.CURRENT.major
+ + "."
+ + Version.CURRENT.minor
+ + "]"
+ );
+ }
+ latestId = latest.id();
+ }
+ } catch (IOException ioe) {
+ throw new UncheckedIOException("latest transport version file not found at [" + latestLocation + "]", ioe);
}
- ALL_VERSIONS_MAP = ALL_VERSIONS.stream().collect(Collectors.toUnmodifiableMap(TransportVersion::id, Function.identity()));
+ String manifestLocation = "/transport/constant/manifest.txt";
+ List versionFileNames = null;
+ if (latestId > -1) {
+ try (InputStream inputStream = TransportVersion.class.getResourceAsStream(manifestLocation)) {
+ if (inputStream != null) {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8));
+ versionFileNames = reader.lines().filter(line -> line.isBlank() == false).toList();
+ }
+ } catch (IOException ioe) {
+ throw new UncheckedIOException("transport version manifest file not found at [" + manifestLocation + "]", ioe);
+ }
+ }
- CURRENT = ALL_VERSIONS.getLast();
+ if (versionFileNames != null) {
+ for (String name : versionFileNames) {
+ String versionLocation = "/transport/constant/" + name;
+ try (InputStream inputStream = TransportVersion.class.getResourceAsStream(versionLocation)) {
+ if (inputStream == null) {
+ throw new IllegalStateException("transport version file not found at [" + versionLocation + "]");
+ }
+ TransportVersion transportVersion = TransportVersion.fromInputStream(versionLocation, false, inputStream, latestId);
+ if (transportVersion != null) {
+ transportVersions.put(transportVersion.name(), transportVersion);
+ }
+ } catch (IOException ioe) {
+ throw new UncheckedIOException("transport version file not found at [ " + versionLocation + "]", ioe);
+ }
+ }
+ }
+
+ return transportVersions;
+ }
+
+ private static List addTransportVersions(Collection addFrom, List addTo) {
+ for (TransportVersion transportVersion : addFrom) {
+ addTo.add(transportVersion);
+ TransportVersion patchVersion = transportVersion.nextPatchVersion();
+ while (patchVersion != null) {
+ addTo.add(patchVersion);
+ patchVersion = patchVersion.nextPatchVersion();
+ }
+ }
+ return addTo;
}
}
}
diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java
index 0fea2a42b7f3e..dad300ae72744 100644
--- a/server/src/main/java/org/elasticsearch/TransportVersions.java
+++ b/server/src/main/java/org/elasticsearch/TransportVersions.java
@@ -19,7 +19,6 @@
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
-import java.util.function.IntFunction;
/**
* Transport version is used to coordinate compatible wire protocol communication between nodes, at a fine-grained level. This replaces
@@ -426,16 +425,6 @@ static TransportVersion def(int id) {
*/
static final List DEFINED_VERSIONS = collectAllVersionIdsDefinedInClass(TransportVersions.class);
- // the highest transport version constant defined
- static final TransportVersion LATEST_DEFINED;
- static {
- LATEST_DEFINED = DEFINED_VERSIONS.getLast();
-
- // see comment on IDS field
- // now we're registered all the transport versions, we can clear the map
- IDS = null;
- }
-
public static List collectAllVersionIdsDefinedInClass(Class> cls) {
Map versionIdFields = new HashMap<>();
List definedTransportVersions = new ArrayList<>();
@@ -477,8 +466,6 @@ public static List collectAllVersionIdsDefinedInClass(Class>
return List.copyOf(definedTransportVersions);
}
- static final IntFunction VERSION_LOOKUP = ReleaseVersions.generateVersionsLookup(TransportVersions.class, LATEST_DEFINED.id());
-
// no instance
private TransportVersions() {}
}
diff --git a/server/src/main/resources/transport/latest/8.18.csv b/server/src/main/resources/transport/latest/8.18.csv
new file mode 100644
index 0000000000000..987d72e2aaeae
--- /dev/null
+++ b/server/src/main/resources/transport/latest/8.18.csv
@@ -0,0 +1 @@
+placeholder,8840007
diff --git a/server/src/main/resources/transport/latest/8.19.csv b/server/src/main/resources/transport/latest/8.19.csv
new file mode 100644
index 0000000000000..2480f207cc6e4
--- /dev/null
+++ b/server/src/main/resources/transport/latest/8.19.csv
@@ -0,0 +1 @@
+placeholder,8841064
diff --git a/server/src/main/resources/transport/latest/9.0.csv b/server/src/main/resources/transport/latest/9.0.csv
new file mode 100644
index 0000000000000..478f07788af87
--- /dev/null
+++ b/server/src/main/resources/transport/latest/9.0.csv
@@ -0,0 +1 @@
+placeholder,9000014
diff --git a/server/src/main/resources/transport/latest/9.1.csv b/server/src/main/resources/transport/latest/9.1.csv
new file mode 100644
index 0000000000000..21304ce07f713
--- /dev/null
+++ b/server/src/main/resources/transport/latest/9.1.csv
@@ -0,0 +1 @@
+placeholder,9112003
diff --git a/server/src/main/resources/transport/latest/9.2.csv b/server/src/main/resources/transport/latest/9.2.csv
new file mode 100644
index 0000000000000..5db8e8fb48f39
--- /dev/null
+++ b/server/src/main/resources/transport/latest/9.2.csv
@@ -0,0 +1 @@
+placeholder,9130000
diff --git a/server/src/test/java/org/elasticsearch/TransportVersionTests.java b/server/src/test/java/org/elasticsearch/TransportVersionTests.java
index 4e100e3067444..39b4fed0b24df 100644
--- a/server/src/test/java/org/elasticsearch/TransportVersionTests.java
+++ b/server/src/test/java/org/elasticsearch/TransportVersionTests.java
@@ -220,4 +220,112 @@ public void testDuplicateConstants() {
previous = next;
}
}
+
+ public void testFromName() {
+ assertThat(TransportVersion.fromName("test_0"), is(new TransportVersion("test_0", 3001000, null)));
+ assertThat(TransportVersion.fromName("test_1"), is(new TransportVersion("test_1", 3002000, null)));
+ assertThat(
+ TransportVersion.fromName("test_2"),
+ is(
+ new TransportVersion(
+ "test_2",
+ 3003000,
+ new TransportVersion("test_2", 2001001, new TransportVersion("test_2", 1001001, null))
+ )
+ )
+ );
+ assertThat(
+ TransportVersion.fromName("test_3"),
+ is(new TransportVersion("test_3", 3003001, new TransportVersion("test_3", 2001002, null)))
+ );
+ assertThat(
+ TransportVersion.fromName("test_4"),
+ is(
+ new TransportVersion(
+ "test_4",
+ 3003002,
+ new TransportVersion("test_4", 2001003, new TransportVersion("test_4", 1001002, null))
+ )
+ )
+ );
+ }
+
+ public void testSupports() {
+ TransportVersion test0 = TransportVersion.fromName("test_0");
+ assertThat(new TransportVersion(null, 2003000, null).supports(test0), is(false));
+ assertThat(new TransportVersion(null, 3001000, null).supports(test0), is(true));
+ assertThat(new TransportVersion(null, 100001001, null).supports(test0), is(true));
+
+ TransportVersion test1 = TransportVersion.fromName("test_1");
+ assertThat(new TransportVersion(null, 2003000, null).supports(test1), is(false));
+ assertThat(new TransportVersion(null, 3001000, null).supports(test1), is(false));
+ assertThat(new TransportVersion(null, 3001001, null).supports(test1), is(false));
+ assertThat(new TransportVersion(null, 3002000, null).supports(test1), is(true));
+ assertThat(new TransportVersion(null, 100001000, null).supports(test1), is(true));
+ assertThat(new TransportVersion(null, 100001001, null).supports(test1), is(true));
+
+ TransportVersion test2 = TransportVersion.fromName("test_2");
+ assertThat(new TransportVersion(null, 1001000, null).supports(test2), is(false));
+ assertThat(new TransportVersion(null, 1001001, null).supports(test2), is(true));
+ assertThat(new TransportVersion(null, 1001002, null).supports(test2), is(true));
+ assertThat(new TransportVersion(null, 1002000, null).supports(test2), is(false));
+ assertThat(new TransportVersion(null, 1002001, null).supports(test2), is(false));
+ assertThat(new TransportVersion(null, 2001000, null).supports(test2), is(false));
+ assertThat(new TransportVersion(null, 2001001, null).supports(test2), is(true));
+ assertThat(new TransportVersion(null, 2001002, null).supports(test2), is(true));
+ assertThat(new TransportVersion(null, 2003000, null).supports(test2), is(false));
+ assertThat(new TransportVersion(null, 2003001, null).supports(test2), is(false));
+ assertThat(new TransportVersion(null, 3001000, null).supports(test2), is(false));
+ assertThat(new TransportVersion(null, 3001001, null).supports(test2), is(false));
+ assertThat(new TransportVersion(null, 3003000, null).supports(test2), is(true));
+ assertThat(new TransportVersion(null, 3003001, null).supports(test2), is(true));
+ assertThat(new TransportVersion(null, 3003002, null).supports(test2), is(true));
+ assertThat(new TransportVersion(null, 3003003, null).supports(test2), is(true));
+ assertThat(new TransportVersion(null, 100001000, null).supports(test2), is(true));
+ assertThat(new TransportVersion(null, 100001001, null).supports(test2), is(true));
+
+ TransportVersion test3 = TransportVersion.fromName("test_3");
+ assertThat(new TransportVersion(null, 1001001, null).supports(test3), is(false));
+ assertThat(new TransportVersion(null, 1001002, null).supports(test3), is(false));
+ assertThat(new TransportVersion(null, 1001003, null).supports(test3), is(false));
+ assertThat(new TransportVersion(null, 1002001, null).supports(test3), is(false));
+ assertThat(new TransportVersion(null, 1002002, null).supports(test3), is(false));
+ assertThat(new TransportVersion(null, 2001001, null).supports(test3), is(false));
+ assertThat(new TransportVersion(null, 2001002, null).supports(test3), is(true));
+ assertThat(new TransportVersion(null, 2001003, null).supports(test3), is(true));
+ assertThat(new TransportVersion(null, 2003000, null).supports(test3), is(false));
+ assertThat(new TransportVersion(null, 2003001, null).supports(test3), is(false));
+ assertThat(new TransportVersion(null, 3001000, null).supports(test3), is(false));
+ assertThat(new TransportVersion(null, 3001001, null).supports(test3), is(false));
+ assertThat(new TransportVersion(null, 3003000, null).supports(test3), is(false));
+ assertThat(new TransportVersion(null, 3003001, null).supports(test3), is(true));
+ assertThat(new TransportVersion(null, 3003002, null).supports(test3), is(true));
+ assertThat(new TransportVersion(null, 3003003, null).supports(test3), is(true));
+ assertThat(new TransportVersion(null, 3004000, null).supports(test3), is(true));
+ assertThat(new TransportVersion(null, 100001000, null).supports(test3), is(true));
+ assertThat(new TransportVersion(null, 100001001, null).supports(test3), is(true));
+
+ TransportVersion test4 = TransportVersion.fromName("test_4");
+ assertThat(new TransportVersion(null, 1001001, null).supports(test4), is(false));
+ assertThat(new TransportVersion(null, 1001002, null).supports(test4), is(true));
+ assertThat(new TransportVersion(null, 1001003, null).supports(test4), is(true));
+ assertThat(new TransportVersion(null, 1002001, null).supports(test4), is(false));
+ assertThat(new TransportVersion(null, 1002002, null).supports(test4), is(false));
+ assertThat(new TransportVersion(null, 1002003, null).supports(test3), is(false));
+ assertThat(new TransportVersion(null, 2001002, null).supports(test4), is(false));
+ assertThat(new TransportVersion(null, 2001003, null).supports(test4), is(true));
+ assertThat(new TransportVersion(null, 2001004, null).supports(test4), is(true));
+ assertThat(new TransportVersion(null, 2003000, null).supports(test4), is(false));
+ assertThat(new TransportVersion(null, 2003001, null).supports(test4), is(false));
+ assertThat(new TransportVersion(null, 3001000, null).supports(test4), is(false));
+ assertThat(new TransportVersion(null, 3001001, null).supports(test4), is(false));
+ assertThat(new TransportVersion(null, 3003000, null).supports(test4), is(false));
+ assertThat(new TransportVersion(null, 3003001, null).supports(test4), is(false));
+ assertThat(new TransportVersion(null, 3003002, null).supports(test4), is(true));
+ assertThat(new TransportVersion(null, 3003003, null).supports(test4), is(true));
+ assertThat(new TransportVersion(null, 3003004, null).supports(test4), is(true));
+ assertThat(new TransportVersion(null, 3004000, null).supports(test4), is(true));
+ assertThat(new TransportVersion(null, 100001000, null).supports(test4), is(true));
+ assertThat(new TransportVersion(null, 100001001, null).supports(test4), is(true));
+ }
}
diff --git a/server/src/test/resources/transport/constant/manifest.txt b/server/src/test/resources/transport/constant/manifest.txt
new file mode 100644
index 0000000000000..b158f9910edf4
--- /dev/null
+++ b/server/src/test/resources/transport/constant/manifest.txt
@@ -0,0 +1,5 @@
+test_0.csv
+test_1.csv
+test_2.csv
+test_3.csv
+test_4.csv
diff --git a/server/src/test/resources/transport/constant/test_0.csv b/server/src/test/resources/transport/constant/test_0.csv
new file mode 100644
index 0000000000000..46b80e0a7f735
--- /dev/null
+++ b/server/src/test/resources/transport/constant/test_0.csv
@@ -0,0 +1 @@
+100001000,3001000
diff --git a/server/src/test/resources/transport/constant/test_1.csv b/server/src/test/resources/transport/constant/test_1.csv
new file mode 100644
index 0000000000000..68f67c2ab7884
--- /dev/null
+++ b/server/src/test/resources/transport/constant/test_1.csv
@@ -0,0 +1,2 @@
+3002000
+
diff --git a/server/src/test/resources/transport/constant/test_2.csv b/server/src/test/resources/transport/constant/test_2.csv
new file mode 100644
index 0000000000000..5db5b13038410
--- /dev/null
+++ b/server/src/test/resources/transport/constant/test_2.csv
@@ -0,0 +1 @@
+3003000,2001001,1001001
diff --git a/server/src/test/resources/transport/constant/test_3.csv b/server/src/test/resources/transport/constant/test_3.csv
new file mode 100644
index 0000000000000..b9dd0509e1364
--- /dev/null
+++ b/server/src/test/resources/transport/constant/test_3.csv
@@ -0,0 +1 @@
+100002000,3003001,2001002
diff --git a/server/src/test/resources/transport/constant/test_4.csv b/server/src/test/resources/transport/constant/test_4.csv
new file mode 100644
index 0000000000000..55c482a68ee7f
--- /dev/null
+++ b/server/src/test/resources/transport/constant/test_4.csv
@@ -0,0 +1 @@
+100002000,3003002,2001003,1001002