Repository: infinispan/infinispan
Pull request #14706: [#14562] Adding Rest client option
Author: login: jabolina
Created: 2025-05-02T18:06:21Z | Merged: 2025-05-05T16:31:56Z

The downside is that the rest client connects to a specific server. This means we need to specify if it is connecting to the from or to driver.  For example, connecting to the driver in the from with state `REMOVED_OLD` could connect to one of the stopped servers, which will fail the test.


Diff:
diff --git a/server/testdriver/core/src/main/java/org/infinispan/server/test/core/ContainerInfinispanServerDriver.java b/server/testdriver/core/src/main/java/org/infinispan/server/test/core/ContainerInfinispanServerDriver.java
index 5bdf021d5b27..ad1fba954db0 100644
--- a/server/testdriver/core/src/main/java/org/infinispan/server/test/core/ContainerInfinispanServerDriver.java
+++ b/server/testdriver/core/src/main/java/org/infinispan/server/test/core/ContainerInfinispanServerDriver.java
@@ -322,7 +322,7 @@ public void startAdditionalServer(int expectedClusterSize, String volumeName) {
          CountdownLatchLoggingConsumer clusterLatch = new CountdownLatchLoggingConsumer(1, String.format(CLUSTER_VIEW_REGEX, expectedClusterSize));
          CountdownLatchLoggingConsumer startupLatch = new CountdownLatchLoggingConsumer(1, STARTUP_MESSAGE_REGEX);
 
-         log.infof("Starting new single server for container %d with volume %s", name, volumeName);
+         log.infof("Starting new single server for container %s with volume %s", name, volumeName);
          createContainer(createdContainers(), volumeName, startupLatch, clusterLatch);
          Exceptions.unchecked(() -> startupLatch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS));
          Exceptions.unchecked(() -> clusterLatch.await(TIMEOUT_SECONDS, TimeUnit.SECONDS));
diff --git a/server/testdriver/core/src/main/java/org/infinispan/server/test/core/rollingupgrade/RollingUpgradeHandler.java b/server/testdriver/core/src/main/java/org/infinispan/server/test/core/rollingupgrade/RollingUpgradeHandler.java
index 0c5c0189a816..165ac9e62388 100644
--- a/server/testdriver/core/src/main/java/org/infinispan/server/test/core/rollingupgrade/RollingUpgradeHandler.java
+++ b/server/testdriver/core/src/main/java/org/infinispan/server/test/core/rollingupgrade/RollingUpgradeHandler.java
@@ -1,11 +1,15 @@
 package org.infinispan.server.test.core.rollingupgrade;
 
 import java.lang.invoke.MethodHandles;
+import java.net.InetAddress;
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.concurrent.TimeUnit;
 
 import org.infinispan.client.hotrod.RemoteCacheManager;
+import org.infinispan.client.rest.RestClient;
+import org.infinispan.client.rest.configuration.RestClientConfigurationBuilder;
+import org.infinispan.commons.util.Util;
 import org.infinispan.server.Server;
 import org.infinispan.server.test.api.TestUser;
 import org.infinispan.server.test.core.ContainerInfinispanServerDriver;
@@ -27,6 +31,7 @@ public class RollingUpgradeHandler {
    private ContainerInfinispanServerDriver toDriver;
 
    private RemoteCacheManager remoteCacheManager;
+   private RestClient[] restClients;
 
    private STATE currentState = STATE.NOT_STARTED;
 
@@ -85,6 +90,17 @@ public RemoteCacheManager getRemoteCacheManager() {
       return remoteCacheManager;
    }
 
+   public RestClient rest(int server, RestClientConfigurationBuilder builder) {
+      if (restClients[server] != null) return restClients[server];
+
+      InetAddress address = getCurrentState() == STATE.OLD_RUNNING
+            ? fromDriver.getServerAddress(server)
+            : toDriver.getServerAddress(server);
+
+      builder.addServer().host(address.getHostAddress()).port(11222);
+      return restClients[server] = RestClient.forConfiguration(builder.build());
+   }
+
    public STATE getCurrentState() {
       return currentState;
    }
@@ -100,10 +116,11 @@ public static void performUpgrade(RollingUpgradeConfiguration configuration) thr
          log.debugf("Starting %d node to version %s", nodeCount, versionFrom);
          handler.fromDriver = handler.startNode(false, configuration.nodeCount(), configuration.nodeCount(),
                site1Name, configuration.jgroupsProtocol(), null);
-         handler.currentState = STATE.NEW_RUNNING;
+         handler.currentState = STATE.OLD_RUNNING;
 
          try (RemoteCacheManager manager = handler.createRemoteCacheManager()) {
             handler.remoteCacheManager = manager;
+            handler.restClients = new RestClient[nodeCount];
             configuration.initialHandler().accept(handler);
 
             for (int i = 0; i < nodeCount; ++i) {
@@ -111,6 +128,7 @@ public static void performUpgrade(RollingUpgradeConfiguration configuration) thr
                int nodeId = nodeCount - i - 1;
                String volumeId = handler.fromDriver.volumeId(nodeId);
                handler.fromDriver.stop(nodeId);
+               handler.cleanup(i);
 
                handler.currentState = STATE.REMOVED_OLD;
 
@@ -170,6 +188,13 @@ private void cleanup() {
       if (toDriver != null) {
          toDriver.stop(configuration.toVersion());
       }
+
+      Arrays.stream(restClients).forEach(Util::close);
+   }
+
+   private void cleanup(int server) {
+      Util.close(restClients[server]);
+      restClients[server] = null;
    }
 
    public RemoteCacheManager createRemoteCacheManager() {
diff --git a/server/tests/src/test/java/org/infinispan/server/rollingupgrade/DefaultRollingUpgradeTest.java b/server/tests/src/test/java/org/infinispan/server/rollingupgrade/DefaultRollingUpgradeTest.java
index ce54202b14d6..665836635557 100644
--- a/server/tests/src/test/java/org/infinispan/server/rollingupgrade/DefaultRollingUpgradeTest.java
+++ b/server/tests/src/test/java/org/infinispan/server/rollingupgrade/DefaultRollingUpgradeTest.java
@@ -1,5 +1,15 @@
 package org.infinispan.server.rollingupgrade;
 
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.infinispan.functional.FunctionalTestUtils.await;
+import static org.infinispan.server.test.core.rollingupgrade.RollingUpgradeHandler.STATE.REMOVED_OLD;
+
+import org.infinispan.client.rest.RestClient;
+import org.infinispan.client.rest.RestResponse;
+import org.infinispan.client.rest.configuration.RestClientConfigurationBuilder;
+import org.infinispan.commons.configuration.StringConfiguration;
+import org.infinispan.commons.util.ByRef;
+import org.infinispan.server.test.api.TestUser;
 import org.infinispan.server.test.core.rollingupgrade.RollingUpgradeConfigurationBuilder;
 import org.infinispan.server.test.core.rollingupgrade.RollingUpgradeHandler;
 import org.junit.jupiter.api.Test;
@@ -10,4 +20,52 @@ public void testDefaultSetting() throws InterruptedException {
       RollingUpgradeConfigurationBuilder builder = new RollingUpgradeConfigurationBuilder("15.2.0.Final", "15.2.1.Final");
       RollingUpgradeHandler.performUpgrade(builder.build());
    }
+
+   @Test
+   public void testHttpClient() throws Exception {
+      String cacheName = "rolling-upgrade";
+      TestUser user = TestUser.ADMIN;
+      int nodeCount = 3;
+      RollingUpgradeConfigurationBuilder builder = new RollingUpgradeConfigurationBuilder("15.2.0.Final", "15.2.1.Final")
+            .nodeCount(nodeCount);
+      RestClientConfigurationBuilder restBuilder = new RestClientConfigurationBuilder();
+      restBuilder.security().authentication().enable().username(user.getUser()).password(user.getPassword());
+      ByRef.Integer interactions = new ByRef.Integer(0);
+      ByRef.Integer node = new ByRef.Integer(0);
+      builder.handlers(
+            uh -> {
+               // First creates the cache as replicated.
+               uh.getRemoteCacheManager()
+                     .administration()
+                     .createCache(cacheName, new StringConfiguration("<replicated-cache></replicated-cache>"));
+
+               // Since this is the first check, we can connect to any of the nodes in the previous version.
+               RestResponse res = await(uh.rest(0, restBuilder).cache(cacheName).put("foo", "bar"));
+               assertThat(res.status()).isEqualTo(204);
+               res.close();
+               interactions.inc();
+               assertThat(interactions.get()).isOne();
+            },
+            uh -> {
+               if (uh.getCurrentState() == REMOVED_OLD)
+                  return true;
+
+               RestClient client = uh.rest(node.get(), restBuilder);
+               node.inc();
+               RestResponse res = await(client.cache(cacheName).get("foo"));
+
+               assertThat(res.status()).isEqualTo(200);
+               assertThat(res.body()).isEqualTo("bar");
+               res.close();
+               interactions.inc();
+               try (RestResponse health = await(client.container().health())) {
+                  return health.status() == 200;
+               }
+            }
+      );
+      RollingUpgradeHandler.performUpgrade(builder.build());
+
+      // Initial interaction plus one for each node added.
+      assertThat(interactions.get()).isEqualTo(1 + nodeCount);
+   }
 }


Reviews:
user: login: wburns
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T19:25:00Z
user: login: jabolina
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T20:21:15Z
user: login: jabolina
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T20:21:54Z
user: login: wburns
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T20:57:49Z
user: login: wburns
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T21:03:43Z
user: login: wburns
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-02T21:05:46Z
user: login: jabolina
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-05T13:15:55Z

Review comments:
diff_hunk: @@ -27,6 +32,8 @@ public class RollingUpgradeHandler {
    private ContainerInfinispanServerDriver toDriver;
 
    private RemoteCacheManager remoteCacheManager;
+   private RestClient[] fromRestClients;
path: server/testdriver/core/src/main/java/org/infinispan/server/test/core/rollingupgrade/RollingUpgradeHandler.java
user: login: wburns
body: I think it might be better to move the node offset value into the handler. This way it knows what position it is in and which nodes should be OLD and which should be NEW. This way we only need a single collection of clients and the user doesn't have to know which is to or from.
created_at: 2025-05-02T19:22:34Z
author_association: MEMBER
diff_hunk: @@ -10,4 +21,48 @@ public void testDefaultSetting() throws InterruptedException {
       RollingUpgradeConfigurationBuilder builder = new RollingUpgradeConfigurationBuilder("15.2.0.Final", "15.2.1.Final");
       RollingUpgradeHandler.performUpgrade(builder.build());
    }
+
+   @Test
+   public void testHttpClient() throws Exception {
+      String cacheName = "rolling-upgrade";
+      TestUser user = TestUser.ADMIN;
+      RollingUpgradeConfigurationBuilder builder = new RollingUpgradeConfigurationBuilder("15.2.0.Final", "15.2.1.Final")
+            .nodeCount(3);
+      RestClientConfigurationBuilder restBuilder = new RestClientConfigurationBuilder();
+      restBuilder.security().authentication().enable().username(user.getUser()).password(user.getPassword());
+      AtomicBoolean clientUtilized = new AtomicBoolean(false);
+      ByRef.Integer node = new ByRef.Integer(0);
+
+      builder.handlers(
+            uh -> {
+               // First creates the cache as replicated.
+               uh.getRemoteCacheManager()
+                     .administration()
+                     .createCache(cacheName, new StringConfiguration("<replicated-cache></replicated-cache>"));
+
+               // Since this is the first check, we can connect to any of the nodes in the previous version.
+               RestResponse res = await(uh.restFrom(0, restBuilder).cache(cacheName).put("foo", "bar"));
path: server/tests/src/test/java/org/infinispan/server/rollingupgrade/DefaultRollingUpgradeTest.java
user: login: wburns
body: This is fine for now, but we will want to change this to hit the liveness endpoint instead. But we can do that after.. as we will want basic put/get tested by REST as well.
created_at: 2025-05-02T19:24:56Z
author_association: MEMBER
diff_hunk: @@ -27,6 +32,8 @@ public class RollingUpgradeHandler {
    private ContainerInfinispanServerDriver toDriver;
 
    private RemoteCacheManager remoteCacheManager;
+   private RestClient[] fromRestClients;
path: server/testdriver/core/src/main/java/org/infinispan/server/test/core/rollingupgrade/RollingUpgradeHandler.java
user: login: jabolina
body: Updated now. I still have the client to specify the server offset, I think that might be useful for tests later, but it now automatically identifies if this is using the old or new version.
created_at: 2025-05-02T20:21:15Z
author_association: MEMBER
diff_hunk: @@ -10,4 +21,48 @@ public void testDefaultSetting() throws InterruptedException {
       RollingUpgradeConfigurationBuilder builder = new RollingUpgradeConfigurationBuilder("15.2.0.Final", "15.2.1.Final");
       RollingUpgradeHandler.performUpgrade(builder.build());
    }
+
+   @Test
+   public void testHttpClient() throws Exception {
+      String cacheName = "rolling-upgrade";
+      TestUser user = TestUser.ADMIN;
+      RollingUpgradeConfigurationBuilder builder = new RollingUpgradeConfigurationBuilder("15.2.0.Final", "15.2.1.Final")
+            .nodeCount(3);
+      RestClientConfigurationBuilder restBuilder = new RestClientConfigurationBuilder();
+      restBuilder.security().authentication().enable().username(user.getUser()).password(user.getPassword());
+      AtomicBoolean clientUtilized = new AtomicBoolean(false);
+      ByRef.Integer node = new ByRef.Integer(0);
+
+      builder.handlers(
+            uh -> {
+               // First creates the cache as replicated.
+               uh.getRemoteCacheManager()
+                     .administration()
+                     .createCache(cacheName, new StringConfiguration("<replicated-cache></replicated-cache>"));
+
+               // Since this is the first check, we can connect to any of the nodes in the previous version.
+               RestResponse res = await(uh.restFrom(0, restBuilder).cache(cacheName).put("foo", "bar"));
path: server/tests/src/test/java/org/infinispan/server/rollingupgrade/DefaultRollingUpgradeTest.java
user: login: jabolina
body: Done a small update here to use the health API. I am validating only for HTTP 200, I am not checking the JSON contents.
created_at: 2025-05-02T20:21:54Z
author_association: MEMBER
diff_hunk: @@ -100,17 +116,19 @@ public static void performUpgrade(RollingUpgradeConfiguration configuration) thr
          log.debugf("Starting %d node to version %s", nodeCount, versionFrom);
          handler.fromDriver = handler.startNode(false, configuration.nodeCount(), configuration.nodeCount(),
                site1Name, configuration.jgroupsProtocol(), null);
-         handler.currentState = STATE.NEW_RUNNING;
+         handler.currentState = STATE.OLD_RUNNING;
path: server/testdriver/core/src/main/java/org/infinispan/server/test/core/rollingupgrade/RollingUpgradeHandler.java
user: login: wburns
body: 👍 
created_at: 2025-05-02T20:57:49Z
author_association: MEMBER
diff_hunk: @@ -10,4 +22,48 @@ public void testDefaultSetting() throws InterruptedException {
       RollingUpgradeConfigurationBuilder builder = new RollingUpgradeConfigurationBuilder("15.2.0.Final", "15.2.1.Final");
       RollingUpgradeHandler.performUpgrade(builder.build());
    }
+
+   @Test
+   public void testHttpClient() throws Exception {
+      String cacheName = "rolling-upgrade";
+      TestUser user = TestUser.ADMIN;
+      RollingUpgradeConfigurationBuilder builder = new RollingUpgradeConfigurationBuilder("15.2.0.Final", "15.2.1.Final")
+            .nodeCount(3);
+      RestClientConfigurationBuilder restBuilder = new RestClientConfigurationBuilder();
+      restBuilder.security().authentication().enable().username(user.getUser()).password(user.getPassword());
+      AtomicBoolean clientUtilized = new AtomicBoolean(false);
path: server/tests/src/test/java/org/infinispan/server/rollingupgrade/DefaultRollingUpgradeTest.java
user: login: wburns
body: Maybe instead of a single boolean we have a ByRef.Integer that counts interactions to verify against?
created_at: 2025-05-02T21:03:42Z
author_association: MEMBER
diff_hunk: @@ -27,6 +32,8 @@ public class RollingUpgradeHandler {
    private ContainerInfinispanServerDriver toDriver;
 
    private RemoteCacheManager remoteCacheManager;
+   private RestClient[] fromRestClients;
path: server/testdriver/core/src/main/java/org/infinispan/server/test/core/rollingupgrade/RollingUpgradeHandler.java
user: login: wburns
body: This is more along with what I was thinking. Looks nice.
created_at: 2025-05-02T21:05:46Z
author_association: MEMBER
diff_hunk: @@ -10,4 +22,48 @@ public void testDefaultSetting() throws InterruptedException {
       RollingUpgradeConfigurationBuilder builder = new RollingUpgradeConfigurationBuilder("15.2.0.Final", "15.2.1.Final");
       RollingUpgradeHandler.performUpgrade(builder.build());
    }
+
+   @Test
+   public void testHttpClient() throws Exception {
+      String cacheName = "rolling-upgrade";
+      TestUser user = TestUser.ADMIN;
+      RollingUpgradeConfigurationBuilder builder = new RollingUpgradeConfigurationBuilder("15.2.0.Final", "15.2.1.Final")
+            .nodeCount(3);
+      RestClientConfigurationBuilder restBuilder = new RestClientConfigurationBuilder();
+      restBuilder.security().authentication().enable().username(user.getUser()).password(user.getPassword());
+      AtomicBoolean clientUtilized = new AtomicBoolean(false);
path: server/tests/src/test/java/org/infinispan/server/rollingupgrade/DefaultRollingUpgradeTest.java
user: login: jabolina
body: Updated.
created_at: 2025-05-05T13:15:55Z
author_association: MEMBER

PR comments:
user: login: wburns
created_at: 2025-05-03T00:43:20Z
body: Can you confirm maven build failure is not related?
author_association: MEMBER
user: login: jabolina
created_at: 2025-05-05T13:16:19Z
body: > Can you confirm maven build failure is not related?

It shouldn't be related. Looks like some test in core hang.
author_association: MEMBER
user: login: github-actions[bot]
created_at: 2025-05-05T15:48:05Z
body: |Overall Project|72.45%|:green_apple:|
|:-|:-|:-:|

> There is no coverage information present for the Files changed
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-05-05T16:09:02Z
body: __FLAKY TESTS__
   - org.infinispan.client.hotrod.ExecTypedTest#testDistTypedExecNullReturn 
   - org.infinispan.client.hotrod.query.pressure.LargePutAllPressureTest#test 
   - org.infinispan.client.hotrod.JsonScriptTest#testJSONScript 
   - org.infinispan.client.hotrod.StreamingOpsTest#testGetStreamWithMetadata 
   - org.infinispan.client.hotrod.near.InvalidatedFailoverNearCacheBloomTest#testNearCacheClearedUponFailover 
   - org.infinispan.distribution.rehash.StateTransferOverwritingValueTest#testBackupOwnerJoiningDuringRemove\[DIST_SYNC, tx\=true, locking\=OPTIMISTIC\] 
   - org.infinispan.distribution.rehash.StateTransferOverwritingValueTest#testBackupOwnerJoiningDuringReplace\[DIST_SYNC, tx\=true, locking\=OPTIMISTIC\] 
   - org.infinispan.persistence.sifs.SoftIndexFileStoreRestartTest#testStatsUponRestart\[LOCAL, tx\=false, l1\=false, shared\=false, preload\=false, segmented\=true, fileSize\=320000, hasPassivation\=true\]([NO-DELETE]) 
   - org.infinispan.reactive.publisher.impl.RehashClusterPublisherManagerTest#testSegmentMovesToOriginatorJustBeforePublisherCompletes([AT_MOST_ONCE, true, true]) 
   - org.infinispan.jcache.remote.JCacheCreateCachePredefinedTest#testCreateCachePredefinedTouched 
   - org.infinispan.query.statetransfer.PersistentStateTransferQueryDistributedIndexTest#testFetchingPersistentStateUpdatesIndex 

author_association: NONE
user: login: wburns
created_at: 2025-05-05T16:32:07Z
body: Integrated into main, thanks @jabolina !
author_association: MEMBER