diff --git a/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/TVBackportRollingUpgradeIT.java b/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/TVBackportRollingUpgradeIT.java new file mode 100644 index 0000000000000..62ac166e99b96 --- /dev/null +++ b/qa/rolling-upgrade/src/javaRestTest/java/org/elasticsearch/upgrades/TVBackportRollingUpgradeIT.java @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.upgrades; + +import com.carrotsearch.randomizedtesting.annotations.Name; + +public class TVBackportRollingUpgradeIT extends AbstractRollingUpgradeTestCase { + + public TVBackportRollingUpgradeIT(@Name("upgradedNodes") int upgradedNodes) { + super(upgradedNodes); + } + + // We'd be most interested to see this failing on the patch issue where 9.0 thinks it's ahead of 8.x but it's not. + /* + v2 TV1,TV3, + v3 TV1,TV2,TV3,TV4 + + */ + public void testTVBackport() { + if (isOldCluster()) { + // TODO: Implement the test for old cluster + } else if (isMixedCluster()) { + // TODO: Test mixed cluster behavior + } else if (isUpgradedCluster()) { + // TODO: Test upgraded cluster behavior + } else { + fail("Unknown cluster state"); + } + } +} diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java index 9ac259cfa46e5..bf27ec19a7aeb 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersions.java +++ b/server/src/main/java/org/elasticsearch/TransportVersions.java @@ -29,6 +29,10 @@ */ public class TransportVersions { + // If there's a next minor, it's the next minor, otherwise the next major if next major, otherwise null for main. + // Probably won't ever have a patch version? + public static final Integer CUTTOFF_TRANSPORT_VERSION = 8_772_0_00; // can be null for major version boundaries and main. + /* * NOTE: IntelliJ lies! * This map is used during class construction, referenced by the registerTransportVersion method. @@ -323,6 +327,7 @@ static TransportVersion def(int id) { public static final TransportVersion ML_INFERENCE_ELASTIC_DENSE_TEXT_EMBEDDINGS_ADDED = def(9_109_00_0); public static final TransportVersion ML_INFERENCE_COHERE_API_VERSION = def(9_110_0_00); public static final TransportVersion ESQL_PROFILE_INCLUDE_PLAN = def(9_111_0_00); + // public static final TransportVersionsGroup FOO = new TransportVersionsGroup(9_112_0_00, 3, 4,5, 2 ); /* * STOP! READ THIS FIRST! No, really, @@ -398,6 +403,11 @@ static TransportVersion def(int id) { // the highest transport version constant defined static final TransportVersion LATEST_DEFINED; + + static { + + } + static { LATEST_DEFINED = DEFINED_VERSIONS.getLast(); diff --git a/server/src/main/java/org/elasticsearch/TransportVersionsGroup.java b/server/src/main/java/org/elasticsearch/TransportVersionsGroup.java new file mode 100644 index 0000000000000..14a8fa20f85ae --- /dev/null +++ b/server/src/main/java/org/elasticsearch/TransportVersionsGroup.java @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class TransportVersionsGroup { + public final TransportVersion localTV; + public final List backportTVs; + public final List futureTVs; + + public TransportVersionsGroup(List transportVersions) { + if (transportVersions.stream().sorted().toList().equals(transportVersions) == false) { + throw new IllegalArgumentException("transportVersions must be sorted by version"); + } + + TransportVersion currentVersion = TransportVersion.current(); // TODO this has to be set somehow + + TransportVersion localTV = null; + List backportTVs = new ArrayList<>(); + List futureTVs = new ArrayList<>(); + + for (TransportVersion transportVersion : transportVersions) { + if (transportVersion.onOrAfter(currentVersion) == false) { + futureTVs.add(transportVersion); + } else if (localTV == null) { + localTV = transportVersion; + } else { + backportTVs.add(transportVersion); + } + } + if (localTV == null) { + throw new IllegalArgumentException("TransportVersionsGroup must contain a local TransportVersion"); + } + this.backportTVs = Collections.unmodifiableList(backportTVs); + this.futureTVs = Collections.unmodifiableList(futureTVs); + this.localTV = localTV; + } + + public boolean isCompatible(TransportVersion version) { + return version.onOrAfter(localTV) || backportTVs.stream().anyMatch(version::isPatchFrom); + } + + public List getLocalAndBackportedTVIds() { + var localAndBackportedTVs = new ArrayList(); + localAndBackportedTVs.add(localTV); + localAndBackportedTVs.addAll(backportTVs); + return localAndBackportedTVs.stream().map(TransportVersion::id).toList(); + } +} diff --git a/server/src/main/java/org/elasticsearch/action/ActionModule.java b/server/src/main/java/org/elasticsearch/action/ActionModule.java index b17288e222d43..9919eb45e558e 100644 --- a/server/src/main/java/org/elasticsearch/action/ActionModule.java +++ b/server/src/main/java/org/elasticsearch/action/ActionModule.java @@ -210,6 +210,9 @@ import org.elasticsearch.action.termvectors.TransportMultiTermVectorsAction; import org.elasticsearch.action.termvectors.TransportShardMultiTermsVectorAction; import org.elasticsearch.action.termvectors.TransportTermVectorsAction; +import org.elasticsearch.action.tvbackport.RestTVBackportAction; +import org.elasticsearch.action.tvbackport.TVBackportAction; +import org.elasticsearch.action.tvbackport.TransportTVBackportAction; import org.elasticsearch.action.update.TransportUpdateAction; import org.elasticsearch.client.internal.node.NodeClient; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; @@ -809,6 +812,9 @@ public void reg actions.register(GetSynonymRuleAction.INSTANCE, TransportGetSynonymRuleAction.class); actions.register(DeleteSynonymRuleAction.INSTANCE, TransportDeleteSynonymRuleAction.class); + // Transport Version Backport Testing + actions.register(TVBackportAction.INSTANCE, TransportTVBackportAction.class); + return unmodifiableMap(actions.getRegistry()); } @@ -1036,6 +1042,9 @@ public void initRestHandlers(Supplier nodesInCluster, Predicate< registerHandler.accept(new RestPutSynonymRuleAction()); registerHandler.accept(new RestGetSynonymRuleAction()); registerHandler.accept(new RestDeleteSynonymRuleAction()); + + // Transport Version Backport Testing + registerHandler.accept(new RestTVBackportAction()); } @Override diff --git a/server/src/main/java/org/elasticsearch/action/tvbackport/RestTVBackportAction.java b/server/src/main/java/org/elasticsearch/action/tvbackport/RestTVBackportAction.java new file mode 100644 index 0000000000000..2b3f644792aef --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/tvbackport/RestTVBackportAction.java @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.action.tvbackport; + +import org.elasticsearch.client.internal.node.NodeClient; +import org.elasticsearch.rest.BaseRestHandler; +import org.elasticsearch.rest.RestRequest; +import org.elasticsearch.rest.action.RestToXContentListener; + +import java.io.IOException; +import java.util.List; + +public class RestTVBackportAction extends BaseRestHandler { + @Override + public String getName() { + return "_tv_backport_action"; + } + + @Override + public List routes() { + return List.of(new Route(RestRequest.Method.GET, "/_tv_backport")); + } + + @Override + protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { + TVBackportRequest tvBackportRequest = new TVBackportRequest(); + // Here you can set any parameters from the request to the tvBackportRequest if needed + return channel -> client.execute(TVBackportAction.INSTANCE, tvBackportRequest, new RestToXContentListener<>(channel)); + } +} diff --git a/server/src/main/java/org/elasticsearch/action/tvbackport/TVBackportAction.java b/server/src/main/java/org/elasticsearch/action/tvbackport/TVBackportAction.java new file mode 100644 index 0000000000000..2ce0c00669042 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/tvbackport/TVBackportAction.java @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.action.tvbackport; + +import org.elasticsearch.action.ActionType; + +public class TVBackportAction extends ActionType { + public static final TVBackportAction INSTANCE = new TVBackportAction(); + public static final String NAME = "cluster:monitor/tvbackport"; + + private TVBackportAction() { + super(NAME); + } +} diff --git a/server/src/main/java/org/elasticsearch/action/tvbackport/TVBackportRequest.java b/server/src/main/java/org/elasticsearch/action/tvbackport/TVBackportRequest.java new file mode 100644 index 0000000000000..39f2ff8ade764 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/tvbackport/TVBackportRequest.java @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.action.tvbackport; + +import org.elasticsearch.action.ActionRequest; +import org.elasticsearch.action.ActionRequestValidationException; + +public class TVBackportRequest extends ActionRequest { + + @Override + public ActionRequestValidationException validate() { + return null; + } +} diff --git a/server/src/main/java/org/elasticsearch/action/tvbackport/TVBackportResponse.java b/server/src/main/java/org/elasticsearch/action/tvbackport/TVBackportResponse.java new file mode 100644 index 0000000000000..8eb3a4ddc2dd7 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/tvbackport/TVBackportResponse.java @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.action.tvbackport; + +import org.elasticsearch.action.ActionResponse; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.versions.TestTV; +import org.elasticsearch.xcontent.ToXContentObject; +import org.elasticsearch.xcontent.XContentBuilder; + +import java.io.IOException; + +public class TVBackportResponse extends ActionResponse implements ToXContentObject { + String message; + String testTV; + + public TVBackportResponse(String message, String testTV) { + this.message = message; + this.testTV = testTV; + } + + public TVBackportResponse(StreamInput in) throws IOException { + if (TestTV.INSTANCE.isCompatible(in.getTransportVersion())) { + this.testTV = in.readOptionalString(); + } else { + this.testTV = null; // Not compatible, so we don't read it + } + this.message = in.readString(); + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + if (TestTV.INSTANCE.isCompatible(out.getTransportVersion())) { + out.writeOptionalString(testTV); + } + out.writeString(message); + } + + @Override + public XContentBuilder toXContent(XContentBuilder builder, Params params) throws IOException { + builder.startObject(); + if (testTV != null) { + builder.field("TestTV", testTV); + } + builder.field("message", message); + builder.endObject(); + return builder; + } +} diff --git a/server/src/main/java/org/elasticsearch/action/tvbackport/TransportTVBackportAction.java b/server/src/main/java/org/elasticsearch/action/tvbackport/TransportTVBackportAction.java new file mode 100644 index 0000000000000..85ecf7c8560a8 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/action/tvbackport/TransportTVBackportAction.java @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.action.tvbackport; + +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.elasticsearch.action.ActionListener; +import org.elasticsearch.action.support.ActionFilters; +import org.elasticsearch.action.support.TransportAction; +import org.elasticsearch.common.util.concurrent.EsExecutors; +import org.elasticsearch.injection.guice.Inject; +import org.elasticsearch.tasks.Task; +import org.elasticsearch.transport.TransportService; + +public class TransportTVBackportAction extends TransportAction { + private static final Logger logger = LogManager.getLogger(TransportTVBackportAction.class); + + @Inject + public TransportTVBackportAction(ActionFilters actionFilters, TransportService transportService) { + super(TVBackportAction.NAME, actionFilters, transportService.getTaskManager(), EsExecutors.DIRECT_EXECUTOR_SERVICE); + } + + @Override + protected void doExecute(Task task, TVBackportRequest request, ActionListener listener) { + logger.info("Executing TVBackportAction for request"); + listener.onResponse(new TVBackportResponse("This is a TV Backport response", "with the testTV TVGroup")); + } +} diff --git a/server/src/main/java/org/elasticsearch/versions/TestTV.java b/server/src/main/java/org/elasticsearch/versions/TestTV.java new file mode 100644 index 0000000000000..e13246a32e317 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/versions/TestTV.java @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.versions; + +import org.elasticsearch.TransportVersion; +import org.elasticsearch.TransportVersionsGroup; + +import java.util.List; + +public class TestTV extends TransportVersionsGroup { + private TestTV() { + super( + List.of( + new TransportVersion(9_111_0_00), // main + new TransportVersion(9_001_0_00), // 9.0 + new TransportVersion(8_111_0_00), // 8.19 + new TransportVersion(8_001_0_00) // 8.18 + ) + ); + } +} diff --git a/server/src/test/java/org/elasticsearch/TransportVersionTests.java b/server/src/test/java/org/elasticsearch/TransportVersionTests.java index 9b02b66583e78..2493f6176eaa1 100644 --- a/server/src/test/java/org/elasticsearch/TransportVersionTests.java +++ b/server/src/test/java/org/elasticsearch/TransportVersionTests.java @@ -166,6 +166,12 @@ public void testIsPatchFrom() { assertThat(TransportVersion.fromId(8_800_0_49).isPatchFrom(patchVersion), is(true)); assertThat(TransportVersion.fromId(8_800_1_00).isPatchFrom(patchVersion), is(false)); assertThat(TransportVersion.fromId(8_801_0_00).isPatchFrom(patchVersion), is(false)); + assertThat(TransportVersion.fromId(9_000_0_03).isPatchFrom(patchVersion), is(false)); + assertThat(TransportVersion.fromId(9_000_0_04).isPatchFrom(patchVersion), is(false)); + assertThat(TransportVersion.fromId(9_000_0_05).isPatchFrom(patchVersion), is(false)); + assertThat(TransportVersion.fromId(9_100_0_03).isPatchFrom(patchVersion), is(false)); + assertThat(TransportVersion.fromId(9_100_0_04).isPatchFrom(patchVersion), is(false)); + assertThat(TransportVersion.fromId(9_100_0_05).isPatchFrom(patchVersion), is(false)); } public void testVersionConstantPresent() {