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