This is an automated email from the ASF dual-hosted git repository.

ycai pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/cassandra-sidecar.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 385ab54  CASSANDRASC-130: Improve integration test speed (#121)
385ab54 is described below

commit 385ab5444884db09e7e018e24416a13198ea5139
Author: Yifan Cai <[email protected]>
AuthorDate: Fri May 10 13:23:44 2024 -0700

    CASSANDRASC-130: Improve integration test speed (#121)
    
    Patch by Yifan Cai; Reviewed by Francisco Guerrero for CASSANDRASC-130
---
 .circleci/config.yml                               | 141 ++++++++++++---------
 build.gradle                                       |  11 ++
 scripts/build-dtest-jars.sh                        |   5 +
 .../sidecar/db/schema/AbstractSchema.java          |   8 --
 .../sidecar/db/schema/RestoreJobsSchema.java       |   2 +-
 .../sidecar/db/schema/RestoreSlicesSchema.java     |   2 +-
 .../cassandra/sidecar/db/schema/TableSchema.java   |  27 ++++
 .../sidecar/common/CQLSessionProviderTest.java     |   6 +
 .../sidecar/common/DelegateIntegrationTest.java    |   3 +
 .../db/RestoreJobsDatabaseAccessorIntTest.java     |   3 +-
 .../tokenrange/BaseTokenRangeIntegrationTest.java  |   3 +-
 .../sidecar/routes/tokenrange/JoiningBaseTest.java |   3 +-
 .../tokenrange/JoiningTestDoubleCluster.java       |  43 ++-----
 .../routes/tokenrange/JoiningTestMultiDC.java      |  43 ++-----
 .../JoiningTestMultiDCSingleReplicated.java        |  43 ++-----
 .../tokenrange/JoiningTestMultipleNodes.java       |  43 ++-----
 .../routes/tokenrange/JoiningTestSingleNode.java   |  43 ++-----
 .../sidecar/routes/tokenrange/LeavingBaseTest.java |   4 +-
 .../sidecar/routes/tokenrange/LeavingTest.java     |   9 +-
 .../routes/tokenrange/LeavingTestMultiDC.java      |   6 +-
 .../tokenrange/LeavingTestMultiDCHalveCluster.java |   6 +-
 .../sidecar/routes/tokenrange/MovingBaseTest.java  |   3 +-
 .../routes/tokenrange/MovingMultiDCTest.java       |   6 +-
 .../sidecar/routes/tokenrange/MovingTest.java      |   6 +-
 .../routes/tokenrange/ReplacementBaseTest.java     |  47 +++++--
 .../routes/tokenrange/ReplacementMultiDCTest.java  |  45 ++-----
 .../sidecar/routes/tokenrange/ReplacementTest.java |  45 ++-----
 .../sidecar/testing/BootstrapBBUtils.java          |  53 ++++++++
 .../testing/CassandraSidecarTestContext.java       |  77 +++++++----
 .../sidecar/testing/IntegrationTestBase.java       |  13 +-
 .../sidecar/testing/IntegrationTestModule.java     |  32 +++--
 .../cassandra/testing/CassandraTestTemplate.java   |   6 +
 .../testing/ConfigurableCassandraTestContext.java  |   1 +
 33 files changed, 415 insertions(+), 373 deletions(-)

diff --git a/.circleci/config.yml b/.circleci/config.yml
index 2f0acb1..8f263a9 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -38,94 +38,104 @@ aliases:
       TERM: dumb
       TZ: "America/Los_Angeles"
 
-# we might modify this in the future to accept a parameter for the java 
package to install
 commands:
-  install_java:
-    description: "Installs Java using AdoptOpenJDK"
-    parameters:
-      version:
-        type: string
-    steps:
-      - run: wget -qO - 
https://adoptopenjdk.jfrog.io/adoptopenjdk/api/gpg/key/public | sudo apt-key 
add -
-      - run: sudo add-apt-repository --yes 
https://adoptopenjdk.jfrog.io/adoptopenjdk/deb/
-      - run: sudo apt-get update
-      - run: sudo apt-get install -y << parameters.version>>
-
   install_common:
-    description: "Installs common software and certificates"
     steps:
       - run: sudo apt-get update
-      - run: sudo apt-get install apt-transport-https ca-certificates curl 
gnupg-agent software-properties-common
+        # Somehow, the certs already installed in the java 11 image are 
installed strangely
+        # in that there's a directory called `cacerts` with a symlink in it 
named `cacerts`
+        # (/etc/ssl/certs/java/cacerts/cacerts) rather than just a symlink to
+        # `cacerts` in the /etc/ssl/certs/java directory - if this is the 
case, fix it by
+        # moving it down a directory and then ant should install cleanly
+      - run: |
+          if [ -f /etc/ssl/certs/java/cacerts/cacerts ]; then
+              sudo mv /etc/ssl/certs/java/cacerts/ 
/etc/ssl/certs/java/cacerts-old
+              sudo mv /etc/ssl/certs/java/cacerts-old/cacerts 
/etc/ssl/certs/java/
+              sudo rmdir /etc/ssl/certs/java/cacerts-old
+          fi
+        # The image already has java installed, but `apt-get install ant` 
reinstalls it.
+        # Therefore, we download just the deb package for ant and install it 
with dpkg.
+      - run: |
+          apt-get download ant ant-optional
+          sudo dpkg --force-all -i ant*.deb
+          rm ant*.deb
+      # We need aliases for localhost2,localhost3, ..., localhost20 in hosts
+      - run: |
+          sudo bash -c 'for i in {2..20}; do echo 127.0.0.${i} localhost${i} 
>> /etc/hosts; done'
+          cat /etc/hosts
 
 jobs:
   # Runs java 8 tests on a docker image
   unit_java8:
     docker:
-     - image: cimg/openjdk:8.0
+      - image: cimg/openjdk:8.0
     environment:
       skipIntegrationTest: true
     steps:
-     - setup_remote_docker
-     - checkout
-     - run: ./gradlew --info check -x integrationTest --stacktrace
+      - install_common
+      - setup_remote_docker
+      - checkout
+      - run: ./gradlew check -x integrationTest --stacktrace
 
-     - store_artifacts:
-         path: build/reports
-         destination: test-reports
+      - store_artifacts:
+          path: build/reports
+          destination: test-reports
 
-     - store_artifacts:
-         path: build/test-results/
-         destination: test-results
+      - store_artifacts:
+          path: build/test-results/
+          destination: test-results
 
-     - store_test_results:
-         path: build/test-results/
+      - store_test_results:
+          path: build/test-results/
 
   integration_cassandra_40_java8:
     docker:
-     - image: circleci/openjdk:8-jdk-stretch
+      - image: cimg/openjdk:8.0
     environment:
       INTEGRATION_MAX_PARALLEL_FORKS: 2
       INTEGRATION_MAX_HEAP_SIZE: "3500M"
     resource_class: large
     steps:
-     - checkout
-     # Cassandra 4.0 jar seems to be missing some dependencies, so we use 4.1 
here (this is what we currently do)
-     - run: BRANCHES="cassandra-4.0 cassandra-4.1" scripts/build-dtest-jars.sh
-     - run: ./gradlew --no-daemon -PdtestVersion=4.1.4 
-Dcassandra.sidecar.versions_to_test="4.0" --info checkstyleIntegrationTest 
spotbugsIntegrationTest integrationTest --stacktrace
+      - install_common
+      - checkout
+      # Cassandra 4.0 jar seems to be missing some dependencies, so we use 4.1 
here (this is what we currently do)
+      - run: BRANCHES="cassandra-4.0 cassandra-4.1" scripts/build-dtest-jars.sh
+      - run: ./gradlew --no-daemon -PdtestVersion=4.1.4 
-Dcassandra.sidecar.versions_to_test="4.0" checkstyleIntegrationTest 
spotbugsIntegrationTest integrationTest --stacktrace
 
-     - store_artifacts:
-         path: build/reports
-         destination: test-reports
+      - store_artifacts:
+          path: build/reports
+          destination: test-reports
 
-     - store_artifacts:
-         path: build/test-results/
-         destination: test-results
+      - store_artifacts:
+          path: build/test-results/
+          destination: test-results
 
-     - store_test_results:
-         path: build/test-results/
+      - store_test_results:
+          path: build/test-results/
 
   integration_cassandra_41_java8:
     docker:
-     - image: circleci/openjdk:8-jdk-stretch
+      - image: cimg/openjdk:8.0
     environment:
       INTEGRATION_MAX_PARALLEL_FORKS: 2
       INTEGRATION_MAX_HEAP_SIZE: "3500M"
     resource_class: large
     steps:
-     - checkout
-     - run: BRANCHES="cassandra-4.1" scripts/build-dtest-jars.sh
-     - run: ./gradlew --no-daemon -PdtestVersion=4.1.4 
-Dcassandra.sidecar.versions_to_test="4.1" --info checkstyleIntegrationTest 
spotbugsIntegrationTest integrationTest --stacktrace
+      - install_common
+      - checkout
+      - run: BRANCHES="cassandra-4.1" scripts/build-dtest-jars.sh
+      - run: ./gradlew --no-daemon -PdtestVersion=4.1.4 
-Dcassandra.sidecar.versions_to_test="4.1" checkstyleIntegrationTest 
spotbugsIntegrationTest integrationTest --stacktrace
 
-     - store_artifacts:
-         path: build/reports
-         destination: test-reports
+      - store_artifacts:
+          path: build/reports
+          destination: test-reports
 
-     - store_artifacts:
-         path: build/test-results/
-         destination: test-results
+      - store_artifacts:
+          path: build/test-results/
+          destination: test-results
 
-     - store_test_results:
-         path: build/test-results/
+      - store_test_results:
+          path: build/test-results/
 
   # Runs java 11 tests on a docker image
   unit_java11:
@@ -135,8 +145,9 @@ jobs:
       skipIntegrationTest: true
     steps:
       - setup_remote_docker
+      - install_common
       - checkout
-      - run: ./gradlew --info check -x integrationTest --stacktrace
+      - run: ./gradlew check -x integrationTest --stacktrace
 
       - store_artifacts:
           path: build/reports
@@ -147,16 +158,17 @@ jobs:
 
   integration_cassandra_40_java11:
     docker:
-      - image: circleci/openjdk:11-jdk-stretch
+      - image: cimg/openjdk:11.0
     environment:
       INTEGRATION_MAX_PARALLEL_FORKS: 2
       INTEGRATION_MAX_HEAP_SIZE: "3500M"
     resource_class: large
     steps:
+      - install_common
       - checkout
       # Cassandra 4.0 jar seems to be missing some dependencies, so we use 4.1 
here (this is what we currently do)
       - run: BRANCHES="cassandra-4.0 cassandra-4.1" CASSANDRA_USE_JDK11=true 
scripts/build-dtest-jars.sh
-      - run: ./gradlew --no-daemon -PdtestVersion=4.1.4 
-Dcassandra.sidecar.versions_to_test="4.0" --info checkstyleIntegrationTest 
spotbugsIntegrationTest integrationTest --stacktrace
+      - run: ./gradlew --no-daemon -PdtestVersion=4.1.4 
-Dcassandra.sidecar.versions_to_test="4.0" checkstyleIntegrationTest 
spotbugsIntegrationTest integrationTest --stacktrace
 
       - store_artifacts:
           path: build/reports
@@ -171,15 +183,16 @@ jobs:
 
   integration_cassandra_50_java11:
     docker:
-      - image: circleci/openjdk:11-jdk-stretch
+      - image: cimg/openjdk:11.0
     environment:
       INTEGRATION_MAX_PARALLEL_FORKS: 2
       INTEGRATION_MAX_HEAP_SIZE: "3500M"
     resource_class: large
     steps:
+      - install_common
       - checkout
       - run: BRANCHES="cassandra-5.0" scripts/build-dtest-jars.sh
-      - run: ./gradlew --no-daemon -PdtestVersion=5.0-alpha2 
-Dcassandra.sidecar.versions_to_test="5.0" --info checkstyleIntegrationTest 
spotbugsIntegrationTest integrationTest --stacktrace
+      - run: ./gradlew --no-daemon -PdtestVersion=5.0-alpha2 
-Dcassandra.sidecar.versions_to_test="5.0" checkstyleIntegrationTest 
spotbugsIntegrationTest integrationTest --stacktrace
 
       - store_artifacts:
           path: build/reports
@@ -194,15 +207,16 @@ jobs:
 
   integration_cassandra_trunk_java11:
     docker:
-      - image: circleci/openjdk:11-jdk-stretch
+      - image: cimg/openjdk:11.0
     environment:
       INTEGRATION_MAX_PARALLEL_FORKS: 2
       INTEGRATION_MAX_HEAP_SIZE: "3500M"
     resource_class: large
     steps:
+      - install_common
       - checkout
       - run: BRANCHES="trunk" scripts/build-dtest-jars.sh
-      - run: ./gradlew --no-daemon -PdtestVersion=5.1 
-Dcassandra.sidecar.versions_to_test="5.1" --info checkstyleIntegrationTest 
spotbugsIntegrationTest integrationTest --stacktrace
+      - run: ./gradlew --no-daemon -PdtestVersion=5.1 
-Dcassandra.sidecar.versions_to_test="5.1" checkstyleIntegrationTest 
spotbugsIntegrationTest integrationTest --stacktrace
 
       - store_artifacts:
           path: build/reports
@@ -218,10 +232,12 @@ jobs:
   # ensures we can build and install deb packages
   deb_build_install:
     docker:
-      - image: circleci/openjdk:11-jdk-stretch
+      - image: cimg/openjdk:11.0
     steps:
+      - install_common
       - checkout
-      - run: ./gradlew -i clean buildDeb
+      - run: sudo apt --fix-broken install
+      - run: ./gradlew --info clean buildDeb
       - run: DEBIAN_FRONTEND=noninteractive sudo apt install -y 
./build/distributions/cassandra-sidecar*.deb
       - run: test -f /opt/cassandra-sidecar/bin/cassandra-sidecar
 
@@ -242,12 +258,13 @@ jobs:
     <<: *base_job
     steps:
       - checkout
-      - run: ./gradlew -i clean jibDockerBuild
+      - run: ./gradlew --info clean jibDockerBuild
 
   docs_build:
     docker:
-      - image: circleci/openjdk:11-jdk-stretch
+      - image: cimg/openjdk:11.0
     steps:
+      - install_common
       - checkout
       - run: ./gradlew docs:asciidoctor
       - run: test -f docs/build/user.html
diff --git a/build.gradle b/build.gradle
index d791cbb..6f048d5 100644
--- a/build.gradle
+++ b/build.gradle
@@ -378,6 +378,17 @@ tasks.register("integrationTest", Test) {
     forkEvery = 1 // DTest framework tends to have issues without forkEvery 
test class
     maxHeapSize = integrationMaxHeapSize
     maxParallelForks = integrationMaxParallelForks
+
+    afterTest { descriptor, result ->
+        def totalTime = (result.endTime - result.startTime) / 1000.0
+        logger.lifecycle("--")
+        if (totalTime >= 60) { // log the tests that take 1 minute and more
+            logger.warn("$descriptor.displayName took $totalTime s")
+        }
+        else {
+            logger.info("$descriptor.displayName took $totalTime s")
+        }
+    }
 }
 
 tasks.register("containerTest", Test) {
diff --git a/scripts/build-dtest-jars.sh b/scripts/build-dtest-jars.sh
index e8492b2..112c8f4 100755
--- a/scripts/build-dtest-jars.sh
+++ b/scripts/build-dtest-jars.sh
@@ -22,6 +22,7 @@ CANDIDATE_BRANCHES=(
   "cassandra-4.0:1f79c8492528f01bcc5f88951a1cc9e0d7265c54"
   "cassandra-4.1:725655dda2776fef35567496a6e331102eb7610d"
   "cassandra-5.0:f19dd0bb1309c35535876e8f0f996ad2b76adda5"
+  # note the trunk hash cannot be advanced beyond 
ae0842372ff6dd1437d026f82968a3749f555ff4 (TCM), which breaks integration test
   "trunk:2a5e1b77c9f8a205dbec1afdea3f4ed1eaf6a4eb"
 )
 BRANCHES=( ${BRANCHES:-cassandra-4.0 cassandra-4.1 cassandra-5.0 trunk} )
@@ -99,3 +100,7 @@ for index in "${!CANDIDATE_BRANCHES[@]}"; do
       exit ${RETURN}
   fi
 done
+
+# Remove the build directory enclosing all Cassandra branches just cloned,
+# in order to not confuse IDE's indexing
+rm -rf ${BUILD_DIR}
diff --git 
a/src/main/java/org/apache/cassandra/sidecar/db/schema/AbstractSchema.java 
b/src/main/java/org/apache/cassandra/sidecar/db/schema/AbstractSchema.java
index ae72c28..05d27ec 100644
--- a/src/main/java/org/apache/cassandra/sidecar/db/schema/AbstractSchema.java
+++ b/src/main/java/org/apache/cassandra/sidecar/db/schema/AbstractSchema.java
@@ -73,12 +73,4 @@ abstract class AbstractSchema
     protected abstract boolean exists(@NotNull Metadata metadata);
 
     protected abstract String createSchemaStatement();
-
-    /**
-     * Abstract base schema class for table schema
-     */
-    public abstract static class TableSchema extends AbstractSchema
-    {
-
-    }
 }
diff --git 
a/src/main/java/org/apache/cassandra/sidecar/db/schema/RestoreJobsSchema.java 
b/src/main/java/org/apache/cassandra/sidecar/db/schema/RestoreJobsSchema.java
index 8df27bf..348f95d 100644
--- 
a/src/main/java/org/apache/cassandra/sidecar/db/schema/RestoreJobsSchema.java
+++ 
b/src/main/java/org/apache/cassandra/sidecar/db/schema/RestoreJobsSchema.java
@@ -30,7 +30,7 @@ import org.jetbrains.annotations.NotNull;
  * to {@link org.apache.cassandra.sidecar.db.RestoreJob} like inserting a 
restore job, updating a restore job,
  * finding restore jobs and more
  */
-public class RestoreJobsSchema extends AbstractSchema.TableSchema
+public class RestoreJobsSchema extends TableSchema
 {
     private static final String RESTORE_JOB_TABLE_NAME = "restore_job_v2";
 
diff --git 
a/src/main/java/org/apache/cassandra/sidecar/db/schema/RestoreSlicesSchema.java 
b/src/main/java/org/apache/cassandra/sidecar/db/schema/RestoreSlicesSchema.java
index 3c9e2f1..0221f01 100644
--- 
a/src/main/java/org/apache/cassandra/sidecar/db/schema/RestoreSlicesSchema.java
+++ 
b/src/main/java/org/apache/cassandra/sidecar/db/schema/RestoreSlicesSchema.java
@@ -30,7 +30,7 @@ import org.jetbrains.annotations.NotNull;
  * related to {@link org.apache.cassandra.sidecar.db.RestoreSlice} like 
inserting a new restore slice,
  * updating status of a slice, finding restore slices and more
  */
-public class RestoreSlicesSchema extends AbstractSchema.TableSchema
+public class RestoreSlicesSchema extends TableSchema
 {
     private static final String RESTORE_SLICE_TABLE_NAME = "restore_slice_v2";
 
diff --git 
a/src/main/java/org/apache/cassandra/sidecar/db/schema/TableSchema.java 
b/src/main/java/org/apache/cassandra/sidecar/db/schema/TableSchema.java
new file mode 100644
index 0000000..6792f71
--- /dev/null
+++ b/src/main/java/org/apache/cassandra/sidecar/db/schema/TableSchema.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.db.schema;
+
+/**
+ * Abstract base schema class for table schema
+ */
+public abstract class TableSchema extends AbstractSchema
+{
+
+}
diff --git 
a/src/test/integration/org/apache/cassandra/sidecar/common/CQLSessionProviderTest.java
 
b/src/test/integration/org/apache/cassandra/sidecar/common/CQLSessionProviderTest.java
index d77f5f8..cf361d6 100644
--- 
a/src/test/integration/org/apache/cassandra/sidecar/common/CQLSessionProviderTest.java
+++ 
b/src/test/integration/org/apache/cassandra/sidecar/common/CQLSessionProviderTest.java
@@ -49,6 +49,12 @@ public class CQLSessionProviderTest extends 
IntegrationTestBase
     public static final String OK_KEYSPACE_RESPONSE_START = 
"{\"schema\":\"CREATE KEYSPACE ";
     public static final String KEYSPACE_FAILED_RESPONSE_START = 
"{\"status\":\"Service Unavailable\",";
 
+    @Override
+    protected int getNumInstancesToManage(int clusterSize)
+    {
+        return 2;
+    }
+
     @CassandraIntegrationTest(nodesPerDc = 2, startCluster = false)
     void testCqlSessionProviderWorksAsExpected(VertxTestContext context, 
CassandraTestContext cassandraTestContext)
     throws Exception
diff --git 
a/src/test/integration/org/apache/cassandra/sidecar/common/DelegateIntegrationTest.java
 
b/src/test/integration/org/apache/cassandra/sidecar/common/DelegateIntegrationTest.java
index 9bc1c82..d367901 100644
--- 
a/src/test/integration/org/apache/cassandra/sidecar/common/DelegateIntegrationTest.java
+++ 
b/src/test/integration/org/apache/cassandra/sidecar/common/DelegateIntegrationTest.java
@@ -186,6 +186,9 @@ class DelegateIntegrationTest extends IntegrationTestBase
     @CassandraIntegrationTest(nodesPerDc = 2, newNodesPerDc = 1, startCluster 
= false)
     public void testChangingClusterSize(VertxTestContext context) throws 
InterruptedException
     {
+        // assume the sidecar has 3 managed instances, even though the cluster 
only starts with 2 instances initially
+        sidecarTestContext.setNumInstancesToManage(3);
+
         EventBus eventBus = vertx.eventBus();
 
         Checkpoint jmxConnected = context.checkpoint(3);
diff --git 
a/src/test/integration/org/apache/cassandra/sidecar/db/RestoreJobsDatabaseAccessorIntTest.java
 
b/src/test/integration/org/apache/cassandra/sidecar/db/RestoreJobsDatabaseAccessorIntTest.java
index def98cf..9fdb149 100644
--- 
a/src/test/integration/org/apache/cassandra/sidecar/db/RestoreJobsDatabaseAccessorIntTest.java
+++ 
b/src/test/integration/org/apache/cassandra/sidecar/db/RestoreJobsDatabaseAccessorIntTest.java
@@ -23,7 +23,6 @@ import java.util.UUID;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
-import com.google.common.util.concurrent.Uninterruptibles;
 import org.junit.jupiter.api.extension.ExtendWith;
 
 import com.datastax.driver.core.utils.UUIDs;
@@ -51,7 +50,7 @@ class RestoreJobsDatabaseAccessorIntTest extends 
IntegrationTestBase
         vertx.eventBus()
              
.localConsumer(SidecarServerEvents.ON_SIDECAR_SCHEMA_INITIALIZED.address(), msg 
-> latch.countDown());
 
-        Uninterruptibles.awaitUninterruptibly(latch, 10, TimeUnit.SECONDS);
+        awaitLatchOrTimeout(latch, 10, TimeUnit.SECONDS);
         assertThat(accessor.findAllRecent(3)).isEmpty();
 
         QualifiedTableName qualifiedTableName = new QualifiedTableName("ks", 
"tbl");
diff --git 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/BaseTokenRangeIntegrationTest.java
 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/BaseTokenRangeIntegrationTest.java
index 93269e9..08422d3 100644
--- 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/BaseTokenRangeIntegrationTest.java
+++ 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/BaseTokenRangeIntegrationTest.java
@@ -138,8 +138,7 @@ public class BaseTokenRangeIntegrationTest extends 
IntegrationTestBase
                                                                                
    1);
 
         int totalNodeCount = (annotation.nodesPerDc() + 
annotation.newNodesPerDc()) * annotation.numDcs();
-        return cassandraTestContext.configureAndStartCluster(
-        builder -> {
+        return cassandraTestContext.configureAndStartCluster(builder -> {
             builder.withInstanceInitializer(initializer);
             builder.withTokenSupplier(mdcTokenSupplier);
             builder.withNodeIdTopology(networkTopology(totalNodeCount,
diff --git 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/JoiningBaseTest.java
 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/JoiningBaseTest.java
index ac1862c..0dea56c 100644
--- 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/JoiningBaseTest.java
+++ 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/JoiningBaseTest.java
@@ -35,7 +35,6 @@ import java.util.stream.Collectors;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Range;
 import com.google.common.collect.Sets;
-import com.google.common.util.concurrent.Uninterruptibles;
 
 import io.netty.handler.codec.http.HttpResponseStatus;
 import io.vertx.junit5.VertxTestContext;
@@ -103,7 +102,7 @@ class JoiningBaseTest extends BaseTokenRangeIntegrationTest
                 }
             }
 
-            Uninterruptibles.awaitUninterruptibly(transientStateStart, 2, 
TimeUnit.MINUTES);
+            awaitLatchOrTimeout(transientStateStart, 2, TimeUnit.MINUTES);
 
             for (IUpgradeableInstance newInstance : newInstances)
             {
diff --git 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/JoiningTestDoubleCluster.java
 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/JoiningTestDoubleCluster.java
index 322c5ad..3b1065a 100644
--- 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/JoiningTestDoubleCluster.java
+++ 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/JoiningTestDoubleCluster.java
@@ -20,33 +20,23 @@ package org.apache.cassandra.sidecar.routes.tokenrange;
 
 import java.math.BigInteger;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 import com.google.common.collect.Range;
-import com.google.common.util.concurrent.Uninterruptibles;
 import org.junit.jupiter.api.extension.ExtendWith;
 
 import io.vertx.junit5.VertxExtension;
 import io.vertx.junit5.VertxTestContext;
-import net.bytebuddy.ByteBuddy;
-import net.bytebuddy.description.type.TypeDescription;
-import net.bytebuddy.dynamic.ClassFileLocator;
-import net.bytebuddy.dynamic.TypeResolutionStrategy;
-import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
-import net.bytebuddy.implementation.MethodDelegation;
 import net.bytebuddy.implementation.bind.annotation.SuperCall;
-import net.bytebuddy.pool.TypePool;
+import org.apache.cassandra.db.SystemKeyspace;
+import org.apache.cassandra.sidecar.testing.BootstrapBBUtils;
 import org.apache.cassandra.testing.CassandraIntegrationTest;
 import org.apache.cassandra.testing.ConfigurableCassandraTestContext;
-import org.apache.cassandra.utils.Shared;
-
-import static net.bytebuddy.matcher.ElementMatchers.named;
-import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
 
 /**
  * Cluster expansion scenarios integration tests for token range replica 
mapping endpoint with the in-jvm
@@ -173,7 +163,6 @@ public class JoiningTestDoubleCluster extends 
JoiningBaseTest
     /**
      * ByteBuddy helper for doubling cluster size
      */
-    @Shared
     public static class BBHelperDoubleClusterSize
     {
         static CountDownLatch transientStateStart = new CountDownLatch(5);
@@ -185,27 +174,19 @@ public class JoiningTestDoubleCluster extends 
JoiningBaseTest
             // We intercept the bootstrap of the new nodes (6-10) to validate 
token ranges
             if (nodeNumber > 5)
             {
-                TypePool typePool = TypePool.Default.of(cl);
-                TypeDescription description = 
typePool.describe("org.apache.cassandra.service.StorageService")
-                                                      .resolve();
-                new ByteBuddy().rebase(description, 
ClassFileLocator.ForClassLoader.of(cl))
-                               
.method(named("bootstrap").and(takesArguments(2)))
-                               
.intercept(MethodDelegation.to(BBHelperDoubleClusterSize.class))
-                               // Defer class loading until all dependencies 
are loaded
-                               .make(TypeResolutionStrategy.Lazy.INSTANCE, 
typePool)
-                               .load(cl, 
ClassLoadingStrategy.Default.INJECTION);
+                BootstrapBBUtils.installSetBoostrapStateIntercepter(cl, 
BBHelperDoubleClusterSize.class);
             }
         }
 
-        public static boolean bootstrap(Collection<?> tokens,
-                                        long bootstrapTimeoutMillis,
-                                        @SuperCall Callable<Boolean> orig) 
throws Exception
+        public static void setBootstrapState(SystemKeyspace.BootstrapState 
state, @SuperCall Callable<Void> orig) throws Exception
         {
-            boolean result = orig.call();
-            // trigger bootstrap start and wait until bootstrap is ready from 
test
-            transientStateStart.countDown();
-            Uninterruptibles.awaitUninterruptibly(transientStateEnd);
-            return result;
+            if (state == SystemKeyspace.BootstrapState.COMPLETED)
+            {
+                // trigger bootstrap start and wait until bootstrap is ready 
from test
+                transientStateStart.countDown();
+                awaitLatchOrTimeout(transientStateEnd, 2, TimeUnit.MINUTES);
+            }
+            orig.call();
         }
 
         public static void reset()
diff --git 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/JoiningTestMultiDC.java
 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/JoiningTestMultiDC.java
index 0f56390..16eb43f 100644
--- 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/JoiningTestMultiDC.java
+++ 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/JoiningTestMultiDC.java
@@ -20,34 +20,24 @@ package org.apache.cassandra.sidecar.routes.tokenrange;
 
 import java.math.BigInteger;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 import com.google.common.collect.Range;
-import com.google.common.util.concurrent.Uninterruptibles;
 import org.junit.jupiter.api.extension.ExtendWith;
 
 import io.vertx.junit5.VertxExtension;
 import io.vertx.junit5.VertxTestContext;
-import net.bytebuddy.ByteBuddy;
-import net.bytebuddy.description.type.TypeDescription;
-import net.bytebuddy.dynamic.ClassFileLocator;
-import net.bytebuddy.dynamic.TypeResolutionStrategy;
-import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
-import net.bytebuddy.implementation.MethodDelegation;
 import net.bytebuddy.implementation.bind.annotation.SuperCall;
-import net.bytebuddy.pool.TypePool;
+import org.apache.cassandra.db.SystemKeyspace;
 import org.apache.cassandra.distributed.UpgradeableCluster;
+import org.apache.cassandra.sidecar.testing.BootstrapBBUtils;
 import org.apache.cassandra.testing.CassandraIntegrationTest;
 import org.apache.cassandra.testing.ConfigurableCassandraTestContext;
-import org.apache.cassandra.utils.Shared;
-
-import static net.bytebuddy.matcher.ElementMatchers.named;
-import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
 
 /**
  * Multi-DC Cluster expansion scenarios integration tests for token range 
replica mapping endpoint with the in-jvm
@@ -229,7 +219,6 @@ public class JoiningTestMultiDC extends JoiningBaseTest
     /**
      * ByteBuddy helper for multiple joining nodes
      */
-    @Shared
     public static class BBHelperDoubleClusterMultiDC
     {
         static CountDownLatch transientStateStart = new CountDownLatch(6);
@@ -241,27 +230,19 @@ public class JoiningTestMultiDC extends JoiningBaseTest
             // We intercept the bootstrap of nodes (7-12) to validate token 
ranges
             if (nodeNumber > 6)
             {
-                TypePool typePool = TypePool.Default.of(cl);
-                TypeDescription description = 
typePool.describe("org.apache.cassandra.service.StorageService")
-                                                      .resolve();
-                new ByteBuddy().rebase(description, 
ClassFileLocator.ForClassLoader.of(cl))
-                               
.method(named("bootstrap").and(takesArguments(2)))
-                               
.intercept(MethodDelegation.to(BBHelperDoubleClusterMultiDC.class))
-                               // Defer class loading until all dependencies 
are loaded
-                               .make(TypeResolutionStrategy.Lazy.INSTANCE, 
typePool)
-                               .load(cl, 
ClassLoadingStrategy.Default.INJECTION);
+                BootstrapBBUtils.installSetBoostrapStateIntercepter(cl, 
BBHelperDoubleClusterMultiDC.class);
             }
         }
 
-        public static boolean bootstrap(Collection<?> tokens,
-                                        long bootstrapTimeoutMillis,
-                                        @SuperCall Callable<Boolean> orig) 
throws Exception
+        public static void setBootstrapState(SystemKeyspace.BootstrapState 
state, @SuperCall Callable<Void> orig) throws Exception
         {
-            boolean result = orig.call();
-            // trigger bootstrap start and wait until bootstrap is ready from 
test
-            transientStateStart.countDown();
-            Uninterruptibles.awaitUninterruptibly(transientStateEnd);
-            return result;
+            if (state == SystemKeyspace.BootstrapState.COMPLETED)
+            {
+                // trigger bootstrap start and wait until bootstrap is ready 
from test
+                transientStateStart.countDown();
+                awaitLatchOrTimeout(transientStateEnd, 2, TimeUnit.MINUTES);
+            }
+            orig.call();
         }
 
         public static void reset()
diff --git 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/JoiningTestMultiDCSingleReplicated.java
 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/JoiningTestMultiDCSingleReplicated.java
index 3db934d..170f8c6 100644
--- 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/JoiningTestMultiDCSingleReplicated.java
+++ 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/JoiningTestMultiDCSingleReplicated.java
@@ -20,34 +20,24 @@ package org.apache.cassandra.sidecar.routes.tokenrange;
 
 import java.math.BigInteger;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 import com.google.common.collect.Range;
-import com.google.common.util.concurrent.Uninterruptibles;
 import org.junit.jupiter.api.extension.ExtendWith;
 
 import io.vertx.junit5.VertxExtension;
 import io.vertx.junit5.VertxTestContext;
-import net.bytebuddy.ByteBuddy;
-import net.bytebuddy.description.type.TypeDescription;
-import net.bytebuddy.dynamic.ClassFileLocator;
-import net.bytebuddy.dynamic.TypeResolutionStrategy;
-import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
-import net.bytebuddy.implementation.MethodDelegation;
 import net.bytebuddy.implementation.bind.annotation.SuperCall;
-import net.bytebuddy.pool.TypePool;
+import org.apache.cassandra.db.SystemKeyspace;
 import org.apache.cassandra.distributed.UpgradeableCluster;
+import org.apache.cassandra.sidecar.testing.BootstrapBBUtils;
 import org.apache.cassandra.testing.CassandraIntegrationTest;
 import org.apache.cassandra.testing.ConfigurableCassandraTestContext;
-import org.apache.cassandra.utils.Shared;
-
-import static net.bytebuddy.matcher.ElementMatchers.named;
-import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
 
 /**
  * Multi-DC Cluster expansion scenarios integration tests for token range 
replica mapping endpoint with the in-jvm
@@ -158,7 +148,6 @@ public class JoiningTestMultiDCSingleReplicated extends 
JoiningBaseTest
     /**
      * ByteBuddy helper for multiple joining nodes
      */
-    @Shared
     public static class BBHelperMultiDC
     {
         static CountDownLatch transientStateStart = new CountDownLatch(2);
@@ -170,27 +159,19 @@ public class JoiningTestMultiDCSingleReplicated extends 
JoiningBaseTest
             // We intercept the bootstrap of nodes (11,12) to validate token 
ranges
             if (nodeNumber > 10)
             {
-                TypePool typePool = TypePool.Default.of(cl);
-                TypeDescription description = 
typePool.describe("org.apache.cassandra.service.StorageService")
-                                                      .resolve();
-                new ByteBuddy().rebase(description, 
ClassFileLocator.ForClassLoader.of(cl))
-                               
.method(named("bootstrap").and(takesArguments(2)))
-                               
.intercept(MethodDelegation.to(BBHelperMultiDC.class))
-                               // Defer class loading until all dependencies 
are loaded
-                               .make(TypeResolutionStrategy.Lazy.INSTANCE, 
typePool)
-                               .load(cl, 
ClassLoadingStrategy.Default.INJECTION);
+                BootstrapBBUtils.installSetBoostrapStateIntercepter(cl, 
BBHelperMultiDC.class);
             }
         }
 
-        public static boolean bootstrap(Collection<?> tokens,
-                                        long bootstrapTimeoutMillis,
-                                        @SuperCall Callable<Boolean> orig) 
throws Exception
+        public static void setBootstrapState(SystemKeyspace.BootstrapState 
state, @SuperCall Callable<Void> orig) throws Exception
         {
-            boolean result = orig.call();
-            // trigger bootstrap start and wait until bootstrap is ready from 
test
-            transientStateStart.countDown();
-            Uninterruptibles.awaitUninterruptibly(transientStateEnd);
-            return result;
+            if (state == SystemKeyspace.BootstrapState.COMPLETED)
+            {
+                // trigger bootstrap start and wait until bootstrap is ready 
from test
+                transientStateStart.countDown();
+                awaitLatchOrTimeout(transientStateEnd, 2, TimeUnit.MINUTES);
+            }
+            orig.call();
         }
 
         public static void reset()
diff --git 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/JoiningTestMultipleNodes.java
 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/JoiningTestMultipleNodes.java
index 1eec7e2..6f2fda6 100644
--- 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/JoiningTestMultipleNodes.java
+++ 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/JoiningTestMultipleNodes.java
@@ -20,33 +20,23 @@ package org.apache.cassandra.sidecar.routes.tokenrange;
 
 import java.math.BigInteger;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 import com.google.common.collect.Range;
-import com.google.common.util.concurrent.Uninterruptibles;
 import org.junit.jupiter.api.extension.ExtendWith;
 
 import io.vertx.junit5.VertxExtension;
 import io.vertx.junit5.VertxTestContext;
-import net.bytebuddy.ByteBuddy;
-import net.bytebuddy.description.type.TypeDescription;
-import net.bytebuddy.dynamic.ClassFileLocator;
-import net.bytebuddy.dynamic.TypeResolutionStrategy;
-import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
-import net.bytebuddy.implementation.MethodDelegation;
 import net.bytebuddy.implementation.bind.annotation.SuperCall;
-import net.bytebuddy.pool.TypePool;
+import org.apache.cassandra.db.SystemKeyspace;
+import org.apache.cassandra.sidecar.testing.BootstrapBBUtils;
 import org.apache.cassandra.testing.CassandraIntegrationTest;
 import org.apache.cassandra.testing.ConfigurableCassandraTestContext;
-import org.apache.cassandra.utils.Shared;
-
-import static net.bytebuddy.matcher.ElementMatchers.named;
-import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
 
 /**
  * Cluster expansion scenarios integration tests for token range replica 
mapping endpoint with the in-jvm
@@ -145,7 +135,6 @@ public class JoiningTestMultipleNodes extends 
JoiningBaseTest
     /**
      * ByteBuddy helper for multiple joining nodes
      */
-    @Shared
     public static class BBHelperMultipleJoiningNodes
     {
         static CountDownLatch transientStateStart = new CountDownLatch(2);
@@ -157,27 +146,19 @@ public class JoiningTestMultipleNodes extends 
JoiningBaseTest
             // We intercept the joining of nodes (4, 5) to validate token 
ranges
             if (nodeNumber > 3)
             {
-                TypePool typePool = TypePool.Default.of(cl);
-                TypeDescription description = 
typePool.describe("org.apache.cassandra.service.StorageService")
-                                                      .resolve();
-                new ByteBuddy().rebase(description, 
ClassFileLocator.ForClassLoader.of(cl))
-                               
.method(named("bootstrap").and(takesArguments(2)))
-                               
.intercept(MethodDelegation.to(BBHelperMultipleJoiningNodes.class))
-                               // Defer class loading until all dependencies 
are loaded
-                               .make(TypeResolutionStrategy.Lazy.INSTANCE, 
typePool)
-                               .load(cl, 
ClassLoadingStrategy.Default.INJECTION);
+                BootstrapBBUtils.installSetBoostrapStateIntercepter(cl, 
BBHelperMultipleJoiningNodes.class);
             }
         }
 
-        public static boolean bootstrap(Collection<?> tokens,
-                                        long bootstrapTimeoutMillis,
-                                        @SuperCall Callable<Boolean> orig) 
throws Exception
+        public static void setBootstrapState(SystemKeyspace.BootstrapState 
state, @SuperCall Callable<Void> orig) throws Exception
         {
-            boolean result = orig.call();
-            // trigger bootstrap start and wait until bootstrap is ready from 
test
-            transientStateStart.countDown();
-            Uninterruptibles.awaitUninterruptibly(transientStateEnd);
-            return result;
+            if (state == SystemKeyspace.BootstrapState.COMPLETED)
+            {
+                // trigger bootstrap start and wait until bootstrap is ready 
from test
+                transientStateStart.countDown();
+                awaitLatchOrTimeout(transientStateEnd, 2, TimeUnit.MINUTES);
+            }
+            orig.call();
         }
 
         public static void reset()
diff --git 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/JoiningTestSingleNode.java
 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/JoiningTestSingleNode.java
index ac36244..0f3916e 100644
--- 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/JoiningTestSingleNode.java
+++ 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/JoiningTestSingleNode.java
@@ -20,33 +20,23 @@ package org.apache.cassandra.sidecar.routes.tokenrange;
 
 import java.math.BigInteger;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 import com.google.common.collect.Range;
-import com.google.common.util.concurrent.Uninterruptibles;
 import org.junit.jupiter.api.extension.ExtendWith;
 
 import io.vertx.junit5.VertxExtension;
 import io.vertx.junit5.VertxTestContext;
-import net.bytebuddy.ByteBuddy;
-import net.bytebuddy.description.type.TypeDescription;
-import net.bytebuddy.dynamic.ClassFileLocator;
-import net.bytebuddy.dynamic.TypeResolutionStrategy;
-import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
-import net.bytebuddy.implementation.MethodDelegation;
 import net.bytebuddy.implementation.bind.annotation.SuperCall;
-import net.bytebuddy.pool.TypePool;
+import org.apache.cassandra.db.SystemKeyspace;
+import org.apache.cassandra.sidecar.testing.BootstrapBBUtils;
 import org.apache.cassandra.testing.CassandraIntegrationTest;
 import org.apache.cassandra.testing.ConfigurableCassandraTestContext;
-import org.apache.cassandra.utils.Shared;
-
-import static net.bytebuddy.matcher.ElementMatchers.named;
-import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
 
 /**
  * Cluster expansion scenarios integration tests for token range replica 
mapping endpoint with the in-jvm
@@ -112,7 +102,6 @@ public class JoiningTestSingleNode extends JoiningBaseTest
     /**
      * ByteBuddy helper for a single joining node
      */
-    @Shared
     public static class BBHelperSingleJoiningNode
     {
         static CountDownLatch transientStateStart = new CountDownLatch(1);
@@ -124,27 +113,19 @@ public class JoiningTestSingleNode extends JoiningBaseTest
             // We intercept the bootstrap of the leaving node (4) to validate 
token ranges
             if (nodeNumber == 6)
             {
-                TypePool typePool = TypePool.Default.of(cl);
-                TypeDescription description = 
typePool.describe("org.apache.cassandra.service.StorageService")
-                                                      .resolve();
-                new ByteBuddy().rebase(description, 
ClassFileLocator.ForClassLoader.of(cl))
-                               
.method(named("bootstrap").and(takesArguments(2)))
-                               
.intercept(MethodDelegation.to(BBHelperSingleJoiningNode.class))
-                               // Defer class loading until all dependencies 
are loaded
-                               .make(TypeResolutionStrategy.Lazy.INSTANCE, 
typePool)
-                               .load(cl, 
ClassLoadingStrategy.Default.INJECTION);
+                BootstrapBBUtils.installSetBoostrapStateIntercepter(cl, 
BBHelperSingleJoiningNode.class);
             }
         }
 
-        public static boolean bootstrap(Collection<?> tokens,
-                                        long bootstrapTimeoutMillis,
-                                        @SuperCall Callable<Boolean> orig) 
throws Exception
+        public static void setBootstrapState(SystemKeyspace.BootstrapState 
state, @SuperCall Callable<Void> orig) throws Exception
         {
-            boolean result = orig.call();
-            // trigger bootstrap start and wait until bootstrap is ready from 
test
-            transientStateStart.countDown();
-            Uninterruptibles.awaitUninterruptibly(transientStateEnd);
-            return result;
+            if (state == SystemKeyspace.BootstrapState.COMPLETED)
+            {
+                // trigger bootstrap start and wait until bootstrap is ready 
from test
+                transientStateStart.countDown();
+                awaitLatchOrTimeout(transientStateEnd, 2, TimeUnit.MINUTES);
+            }
+            orig.call();
         }
 
         public static void reset()
diff --git 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/LeavingBaseTest.java
 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/LeavingBaseTest.java
index ac661a4..287d9e9 100644
--- 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/LeavingBaseTest.java
+++ 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/LeavingBaseTest.java
@@ -27,12 +27,12 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Range;
 import com.google.common.collect.Sets;
-import com.google.common.util.concurrent.Uninterruptibles;
 
 import io.netty.handler.codec.http.HttpResponseStatus;
 import io.vertx.junit5.VertxTestContext;
@@ -84,7 +84,7 @@ class LeavingBaseTest extends BaseTokenRangeIntegrationTest
             }
 
             // Wait until nodes have reached expected state
-            Uninterruptibles.awaitUninterruptibly(transientStateStart);
+            awaitLatchOrTimeout(transientStateStart, 2, TimeUnit.MINUTES);
 
             for (IUpgradeableInstance node : leavingNodes)
             {
diff --git 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/LeavingTest.java
 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/LeavingTest.java
index d4e6a8a..dde164b 100644
--- 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/LeavingTest.java
+++ 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/LeavingTest.java
@@ -25,10 +25,10 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.function.BiConsumer;
 
 import com.google.common.collect.Range;
-import com.google.common.util.concurrent.Uninterruptibles;
 import org.junit.jupiter.api.extension.ExtendWith;
 
 import io.vertx.junit5.VertxExtension;
@@ -260,7 +260,6 @@ class LeavingTest extends LeavingBaseTest
     /**
      * ByteBuddy Helper for a single leaving node
      */
-    @Shared
     public static class BBHelperSingleLeavingNode
     {
         static CountDownLatch transientStateStart = new CountDownLatch(1);
@@ -288,7 +287,7 @@ class LeavingTest extends LeavingBaseTest
         public static void unbootstrap(@SuperCall Callable<?> orig) throws 
Exception
         {
             transientStateStart.countDown();
-            Uninterruptibles.awaitUninterruptibly(transientStateEnd);
+            awaitLatchOrTimeout(transientStateEnd, 2, TimeUnit.MINUTES);
             orig.call();
         }
 
@@ -330,7 +329,7 @@ class LeavingTest extends LeavingBaseTest
         public static void unbootstrap(@SuperCall Callable<?> orig) throws 
Exception
         {
             transientStateStart.countDown();
-            Uninterruptibles.awaitUninterruptibly(transientStateEnd);
+            awaitLatchOrTimeout(transientStateEnd, 2, TimeUnit.MINUTES);
             orig.call();
         }
 
@@ -372,7 +371,7 @@ class LeavingTest extends LeavingBaseTest
         public static void unbootstrap(@SuperCall Callable<?> orig) throws 
Exception
         {
             transientStateStart.countDown();
-            Uninterruptibles.awaitUninterruptibly(transientStateEnd);
+            awaitLatchOrTimeout(transientStateEnd, 2, TimeUnit.MINUTES);
             orig.call();
         }
 
diff --git 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/LeavingTestMultiDC.java
 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/LeavingTestMultiDC.java
index 0e28836..eb82353 100644
--- 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/LeavingTestMultiDC.java
+++ 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/LeavingTestMultiDC.java
@@ -25,9 +25,9 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 import com.google.common.collect.Range;
-import com.google.common.util.concurrent.Uninterruptibles;
 import org.junit.jupiter.api.extension.ExtendWith;
 
 import io.vertx.junit5.VertxExtension;
@@ -43,7 +43,6 @@ import net.bytebuddy.pool.TypePool;
 import org.apache.cassandra.distributed.UpgradeableCluster;
 import org.apache.cassandra.testing.CassandraIntegrationTest;
 import org.apache.cassandra.testing.ConfigurableCassandraTestContext;
-import org.apache.cassandra.utils.Shared;
 
 import static net.bytebuddy.matcher.ElementMatchers.named;
 
@@ -156,7 +155,6 @@ class LeavingTestMultiDC extends LeavingBaseTest
     /**
      * ByteBuddy helper for multiple leaving nodes multi-DC
      */
-    @Shared
     public static class BBHelperLeavingNodesMultiDC
     {
         static CountDownLatch transientStateStart = new CountDownLatch(2);
@@ -184,7 +182,7 @@ class LeavingTestMultiDC extends LeavingBaseTest
         public static void unbootstrap(@SuperCall Callable<?> orig) throws 
Exception
         {
             transientStateStart.countDown();
-            Uninterruptibles.awaitUninterruptibly(transientStateEnd);
+            awaitLatchOrTimeout(transientStateEnd, 2, TimeUnit.MINUTES);
             orig.call();
         }
 
diff --git 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/LeavingTestMultiDCHalveCluster.java
 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/LeavingTestMultiDCHalveCluster.java
index 6684f9e..d193613 100644
--- 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/LeavingTestMultiDCHalveCluster.java
+++ 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/LeavingTestMultiDCHalveCluster.java
@@ -25,9 +25,9 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 import com.google.common.collect.Range;
-import com.google.common.util.concurrent.Uninterruptibles;
 import org.junit.jupiter.api.extension.ExtendWith;
 
 import io.vertx.junit5.VertxExtension;
@@ -43,7 +43,6 @@ import net.bytebuddy.pool.TypePool;
 import org.apache.cassandra.distributed.UpgradeableCluster;
 import org.apache.cassandra.testing.CassandraIntegrationTest;
 import org.apache.cassandra.testing.ConfigurableCassandraTestContext;
-import org.apache.cassandra.utils.Shared;
 
 import static net.bytebuddy.matcher.ElementMatchers.named;
 
@@ -189,7 +188,6 @@ class LeavingTestMultiDCHalveCluster extends LeavingBaseTest
     /**
      * ByteBuddy helper for halve cluster size with multi-DC
      */
-    @Shared
     public static class BBHelperHalveClusterMultiDC
     {
         static CountDownLatch transientStateStart = new CountDownLatch(6);
@@ -217,7 +215,7 @@ class LeavingTestMultiDCHalveCluster extends LeavingBaseTest
         public static void unbootstrap(@SuperCall Callable<?> orig) throws 
Exception
         {
             transientStateStart.countDown();
-            Uninterruptibles.awaitUninterruptibly(transientStateEnd);
+            awaitLatchOrTimeout(transientStateEnd, 2, TimeUnit.MINUTES);
             orig.call();
         }
 
diff --git 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/MovingBaseTest.java
 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/MovingBaseTest.java
index bf07215..52a7d6c 100644
--- 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/MovingBaseTest.java
+++ 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/MovingBaseTest.java
@@ -35,7 +35,6 @@ import java.util.stream.Collectors;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Range;
 import com.google.common.collect.Sets;
-import com.google.common.util.concurrent.Uninterruptibles;
 
 import io.netty.handler.codec.http.HttpResponseStatus;
 import io.vertx.junit5.VertxTestContext;
@@ -88,7 +87,7 @@ class MovingBaseTest extends BaseTokenRangeIntegrationTest
                                        .success()).start();
 
             // Wait until nodes have reached expected state
-            Uninterruptibles.awaitUninterruptibly(transientStateStart, 2, 
TimeUnit.MINUTES);
+            awaitLatchOrTimeout(transientStateStart, 2, TimeUnit.MINUTES);
             ClusterUtils.awaitRingState(seed, movingNode, "Moving");
 
             retrieveMappingWithKeyspace(context, TEST_KEYSPACE, response -> {
diff --git 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/MovingMultiDCTest.java
 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/MovingMultiDCTest.java
index 64bf535..98c596a 100644
--- 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/MovingMultiDCTest.java
+++ 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/MovingMultiDCTest.java
@@ -26,9 +26,9 @@ import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
 
 import com.google.common.collect.Range;
-import com.google.common.util.concurrent.Uninterruptibles;
 import org.junit.jupiter.api.extension.ExtendWith;
 
 import io.vertx.junit5.VertxExtension;
@@ -44,7 +44,6 @@ import net.bytebuddy.pool.TypePool;
 import org.apache.cassandra.distributed.UpgradeableCluster;
 import org.apache.cassandra.testing.CassandraIntegrationTest;
 import org.apache.cassandra.testing.ConfigurableCassandraTestContext;
-import org.apache.cassandra.utils.Shared;
 
 import static net.bytebuddy.matcher.ElementMatchers.named;
 
@@ -173,7 +172,6 @@ class MovingMultiDCTest extends MovingBaseTest
     /**
      * ByteBuddy Helper for a multiDC moving node
      */
-    @Shared
     public static class BBHelperMovingNodeMultiDC
     {
         static CountDownLatch transientStateStart = new CountDownLatch(1);
@@ -201,7 +199,7 @@ class MovingMultiDCTest extends MovingBaseTest
         {
             Future<?> res = orig.call();
             transientStateStart.countDown();
-            Uninterruptibles.awaitUninterruptibly(transientStateEnd);
+            awaitLatchOrTimeout(transientStateEnd, 2, TimeUnit.MINUTES);
             return res;
         }
 
diff --git 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/MovingTest.java
 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/MovingTest.java
index 3e89b8e..f40322e 100644
--- 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/MovingTest.java
+++ 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/MovingTest.java
@@ -26,9 +26,9 @@ import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
 
 import com.google.common.collect.Range;
-import com.google.common.util.concurrent.Uninterruptibles;
 import org.junit.jupiter.api.extension.ExtendWith;
 
 import io.vertx.junit5.VertxExtension;
@@ -45,7 +45,6 @@ import org.apache.cassandra.distributed.UpgradeableCluster;
 import org.apache.cassandra.distributed.api.TokenSupplier;
 import org.apache.cassandra.testing.CassandraIntegrationTest;
 import org.apache.cassandra.testing.ConfigurableCassandraTestContext;
-import org.apache.cassandra.utils.Shared;
 
 import static net.bytebuddy.matcher.ElementMatchers.named;
 
@@ -133,7 +132,6 @@ class MovingTest extends MovingBaseTest
     /**
      * ByteBuddy Helper for a single moving node
      */
-    @Shared
     public static class BBHelperMovingNode
     {
         static CountDownLatch transientStateStart = new CountDownLatch(1);
@@ -161,7 +159,7 @@ class MovingTest extends MovingBaseTest
         {
             Future<?> res = orig.call();
             transientStateStart.countDown();
-            Uninterruptibles.awaitUninterruptibly(transientStateEnd);
+            awaitLatchOrTimeout(transientStateEnd, 2, TimeUnit.MINUTES);
             return res;
         }
 
diff --git 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/ReplacementBaseTest.java
 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/ReplacementBaseTest.java
index 17232d2..ca588eb 100644
--- 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/ReplacementBaseTest.java
+++ 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/ReplacementBaseTest.java
@@ -25,25 +25,29 @@ import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
 import java.util.stream.Collectors;
 
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Range;
 import com.google.common.collect.Sets;
-import com.google.common.util.concurrent.Uninterruptibles;
 
 import io.netty.handler.codec.http.HttpResponseStatus;
 import io.vertx.junit5.VertxTestContext;
 import org.apache.cassandra.config.CassandraRelevantProperties;
 import org.apache.cassandra.distributed.UpgradeableCluster;
 import org.apache.cassandra.distributed.api.Feature;
+import org.apache.cassandra.distributed.api.IInstance;
 import org.apache.cassandra.distributed.api.IInstanceConfig;
 import org.apache.cassandra.distributed.api.IUpgradeableInstance;
+import org.apache.cassandra.distributed.impl.AbstractCluster;
 import org.apache.cassandra.distributed.shared.ClusterUtils;
+import org.apache.cassandra.distributed.shared.NetworkTopology;
 import org.apache.cassandra.sidecar.common.response.TokenRangeReplicasResponse;
 import org.apache.cassandra.testing.CassandraIntegrationTest;
 
@@ -95,9 +99,9 @@ class ReplacementBaseTest extends 
BaseTokenRangeIntegrationTest
 
             stopNodes(seed, nodesToRemove);
             List<IUpgradeableInstance> newNodes = 
startReplacementNodes(nodeStart, cluster, nodesToRemove);
-
+            sidecarTestContext.refreshInstancesConfig();
             // Wait until replacement nodes are in JOINING state
-            Uninterruptibles.awaitUninterruptibly(transientStateStart, 2, 
TimeUnit.MINUTES);
+            awaitLatchOrTimeout(transientStateStart, 2, TimeUnit.MINUTES);
 
             // Verify state of replacement nodes
             for (IUpgradeableInstance newInstance : newNodes)
@@ -158,16 +162,17 @@ class ReplacementBaseTest extends 
BaseTokenRangeIntegrationTest
             String remAddress = 
removedConfig.broadcastAddress().getAddress().getHostAddress();
             int remPort = removedConfig.getInt("storage_port");
             IUpgradeableInstance replacement =
-            ClusterUtils.addInstance(cluster, removedConfig,
-                                     c -> {
-                                         c.set("auto_bootstrap", true);
-                                         // explicitly DOES NOT set instances 
that failed startup as "shutdown"
-                                         // so subsequent attempts to shut 
down the instance are honored
-                                         
c.set("dtest.api.startup.failure_as_shutdown", false);
-                                         c.with(Feature.GOSSIP,
-                                                Feature.JMX,
-                                                Feature.NATIVE_PROTOCOL);
-                                     });
+            addInstanceLocal(cluster, removedConfig.localDatacenter(), 
removedConfig.localRack(),
+                             c -> {
+                                 c.set("auto_bootstrap", true);
+                                 // explicitly DOES NOT set instances that 
failed startup as "shutdown"
+                                 // so subsequent attempts to shut down the 
instance are honored
+                                 
c.set("dtest.api.startup.failure_as_shutdown", false);
+                                 c.with(Feature.GOSSIP,
+                                        Feature.JMX,
+                                        Feature.NATIVE_PROTOCOL);
+                                 c.set("storage_port", remPort);
+                             });
 
             new Thread(() -> ClusterUtils.start(replacement, (properties) -> {
                 
properties.set(CassandraRelevantProperties.BOOTSTRAP_SKIP_SCHEMA_CHECK, true);
@@ -182,12 +187,25 @@ class ReplacementBaseTest extends 
BaseTokenRangeIntegrationTest
                 properties.with("cassandra.replace_address_first_boot", 
remAddress + ":" + remPort);
             })).start();
 
-            Uninterruptibles.awaitUninterruptibly(nodeStart, 2, 
TimeUnit.MINUTES);
+            awaitLatchOrTimeout(nodeStart, 2, TimeUnit.MINUTES);
             newNodes.add(replacement);
         }
         return newNodes;
     }
 
+    public static <I extends IInstance> I addInstanceLocal(AbstractCluster<I> 
cluster,
+                                                           String dc,
+                                                           String rack,
+                                                           
Consumer<IInstanceConfig> fn)
+    {
+        Objects.requireNonNull(dc, "dc");
+        Objects.requireNonNull(rack, "rack");
+        IInstanceConfig config = cluster.newInstanceConfig();
+        fn.accept(config);
+        config.networkTopology().put(config.broadcastAddress(), 
NetworkTopology.dcAndRack(dc, rack));
+        return cluster.bootstrap(config);
+    }
+
     private void stopNodes(IUpgradeableInstance seed, 
List<IUpgradeableInstance> removedNodes)
     {
         for (IUpgradeableInstance nodeToRemove : removedNodes)
@@ -195,6 +213,7 @@ class ReplacementBaseTest extends 
BaseTokenRangeIntegrationTest
             ClusterUtils.stopUnchecked(nodeToRemove);
             ClusterUtils.awaitRingStatus(seed, nodeToRemove, "Down");
         }
+        sidecarTestContext.refreshInstancesConfig();
     }
 
     private void validateReplicaMapping(TokenRangeReplicasResponse 
mappingResponse,
diff --git 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/ReplacementMultiDCTest.java
 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/ReplacementMultiDCTest.java
index d94dcb9..104e8e6 100644
--- 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/ReplacementMultiDCTest.java
+++ 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/ReplacementMultiDCTest.java
@@ -20,35 +20,25 @@ package org.apache.cassandra.sidecar.routes.tokenrange;
 
 import java.math.BigInteger;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
 import com.google.common.collect.Range;
-import com.google.common.util.concurrent.Uninterruptibles;
 import org.junit.jupiter.api.extension.ExtendWith;
 
 import io.vertx.junit5.VertxExtension;
 import io.vertx.junit5.VertxTestContext;
-import net.bytebuddy.ByteBuddy;
-import net.bytebuddy.description.type.TypeDescription;
-import net.bytebuddy.dynamic.ClassFileLocator;
-import net.bytebuddy.dynamic.TypeResolutionStrategy;
-import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
-import net.bytebuddy.implementation.MethodDelegation;
 import net.bytebuddy.implementation.bind.annotation.SuperCall;
-import net.bytebuddy.pool.TypePool;
+import org.apache.cassandra.db.SystemKeyspace;
 import org.apache.cassandra.distributed.UpgradeableCluster;
 import org.apache.cassandra.distributed.api.IUpgradeableInstance;
+import org.apache.cassandra.sidecar.testing.BootstrapBBUtils;
 import org.apache.cassandra.testing.CassandraIntegrationTest;
 import org.apache.cassandra.testing.ConfigurableCassandraTestContext;
-import org.apache.cassandra.utils.Shared;
-
-import static net.bytebuddy.matcher.ElementMatchers.named;
-import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
 
 /**
  * Multi-DC Host replacement scenario integration tests for token range 
replica mapping endpoint with the in-jvm
@@ -164,7 +154,6 @@ class ReplacementMultiDCTest extends ReplacementBaseTest
     /**
      * ByteBuddy helper for multi-DC node replacement
      */
-    @Shared
     public static class BBHelperReplacementsMultiDC
     {
         // Additional latch used here to sequentially start the 2 new nodes to 
isolate the loading
@@ -179,28 +168,20 @@ class ReplacementMultiDCTest extends ReplacementBaseTest
             // We intercept the bootstrap of the replacement nodes to validate 
token ranges
             if (nodeNumber > 10)
             {
-                TypePool typePool = TypePool.Default.of(cl);
-                TypeDescription description = 
typePool.describe("org.apache.cassandra.service.StorageService")
-                                                      .resolve();
-                new ByteBuddy().rebase(description, 
ClassFileLocator.ForClassLoader.of(cl))
-                               
.method(named("bootstrap").and(takesArguments(2)))
-                               
.intercept(MethodDelegation.to(BBHelperReplacementsMultiDC.class))
-                               // Defer class loading until all dependencies 
are loaded
-                               .make(TypeResolutionStrategy.Lazy.INSTANCE, 
typePool)
-                               .load(cl, 
ClassLoadingStrategy.Default.INJECTION);
+                BootstrapBBUtils.installSetBoostrapStateIntercepter(cl, 
BBHelperReplacementsMultiDC.class);
             }
         }
 
-        public static boolean bootstrap(Collection<?> tokens,
-                                        long bootstrapTimeoutMillis,
-                                        @SuperCall Callable<Boolean> orig) 
throws Exception
+        public static void setBootstrapState(SystemKeyspace.BootstrapState 
state, @SuperCall Callable<Void> orig) throws Exception
         {
-            boolean result = orig.call();
-            nodeStart.countDown();
-            // trigger bootstrap start and wait until bootstrap is ready from 
test
-            transientStateStart.countDown();
-            Uninterruptibles.awaitUninterruptibly(transientStateEnd);
-            return result;
+            if (state == SystemKeyspace.BootstrapState.COMPLETED)
+            {
+                nodeStart.countDown();
+                // trigger bootstrap start and wait until bootstrap is ready 
from test
+                transientStateStart.countDown();
+                awaitLatchOrTimeout(transientStateEnd, 2, TimeUnit.MINUTES);
+            }
+            orig.call();
         }
 
         public static void reset()
diff --git 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/ReplacementTest.java
 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/ReplacementTest.java
index c5d0e2b..5206353 100644
--- 
a/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/ReplacementTest.java
+++ 
b/src/test/integration/org/apache/cassandra/sidecar/routes/tokenrange/ReplacementTest.java
@@ -20,38 +20,28 @@ package org.apache.cassandra.sidecar.routes.tokenrange;
 
 import java.math.BigInteger;
 import java.util.Arrays;
-import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.concurrent.Callable;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.function.BiConsumer;
 
 import com.google.common.collect.Range;
-import com.google.common.util.concurrent.Uninterruptibles;
 import org.junit.jupiter.api.extension.ExtendWith;
 
 import io.vertx.junit5.VertxExtension;
 import io.vertx.junit5.VertxTestContext;
-import net.bytebuddy.ByteBuddy;
-import net.bytebuddy.description.type.TypeDescription;
-import net.bytebuddy.dynamic.ClassFileLocator;
-import net.bytebuddy.dynamic.TypeResolutionStrategy;
-import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
-import net.bytebuddy.implementation.MethodDelegation;
 import net.bytebuddy.implementation.bind.annotation.SuperCall;
-import net.bytebuddy.pool.TypePool;
+import org.apache.cassandra.db.SystemKeyspace;
 import org.apache.cassandra.distributed.UpgradeableCluster;
 import org.apache.cassandra.distributed.api.IUpgradeableInstance;
 import org.apache.cassandra.distributed.api.TokenSupplier;
+import org.apache.cassandra.sidecar.testing.BootstrapBBUtils;
 import org.apache.cassandra.testing.CassandraIntegrationTest;
 import org.apache.cassandra.testing.ConfigurableCassandraTestContext;
-import org.apache.cassandra.utils.Shared;
-
-import static net.bytebuddy.matcher.ElementMatchers.named;
-import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
 
 /**
  * Host replacement scenario integration tests for token range replica mapping 
endpoint with the in-jvm dtest framework.
@@ -146,7 +136,6 @@ class ReplacementTest extends ReplacementBaseTest
     /**
      * ByteBuddy helper for a single node replacement
      */
-    @Shared
     public static class BBHelperReplacementsNode
     {
         // Additional latch used here to sequentially start the 2 new nodes to 
isolate the loading
@@ -161,28 +150,20 @@ class ReplacementTest extends ReplacementBaseTest
             // We intercept the bootstrap of the replacement (6th) node to 
validate token ranges
             if (nodeNumber == 6)
             {
-                TypePool typePool = TypePool.Default.of(cl);
-                TypeDescription description = 
typePool.describe("org.apache.cassandra.service.StorageService")
-                                                      .resolve();
-                new ByteBuddy().rebase(description, 
ClassFileLocator.ForClassLoader.of(cl))
-                               
.method(named("bootstrap").and(takesArguments(2)))
-                               
.intercept(MethodDelegation.to(BBHelperReplacementsNode.class))
-                               // Defer class loading until all dependencies 
are loaded
-                               .make(TypeResolutionStrategy.Lazy.INSTANCE, 
typePool)
-                               .load(cl, 
ClassLoadingStrategy.Default.INJECTION);
+                BootstrapBBUtils.installSetBoostrapStateIntercepter(cl, 
BBHelperReplacementsNode.class);
             }
         }
 
-        public static boolean bootstrap(Collection<?> tokens,
-                                        long bootstrapTimeoutMillis,
-                                        @SuperCall Callable<Boolean> orig) 
throws Exception
+        public static void setBootstrapState(SystemKeyspace.BootstrapState 
state, @SuperCall Callable<Void> orig) throws Exception
         {
-            boolean result = orig.call();
-            nodeStart.countDown();
-            // trigger bootstrap start and wait until bootstrap is ready from 
test
-            transientStateStart.countDown();
-            Uninterruptibles.awaitUninterruptibly(transientStateEnd);
-            return result;
+            if (state == SystemKeyspace.BootstrapState.COMPLETED)
+            {
+                nodeStart.countDown();
+                // trigger bootstrap start and wait until bootstrap is ready 
from test
+                transientStateStart.countDown();
+                awaitLatchOrTimeout(transientStateEnd, 2, TimeUnit.MINUTES);
+            }
+            orig.call();
         }
 
         public static void reset()
diff --git 
a/src/test/integration/org/apache/cassandra/sidecar/testing/BootstrapBBUtils.java
 
b/src/test/integration/org/apache/cassandra/sidecar/testing/BootstrapBBUtils.java
new file mode 100644
index 0000000..ab5ec78
--- /dev/null
+++ 
b/src/test/integration/org/apache/cassandra/sidecar/testing/BootstrapBBUtils.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.cassandra.sidecar.testing;
+
+import net.bytebuddy.ByteBuddy;
+import net.bytebuddy.description.type.TypeDescription;
+import net.bytebuddy.dynamic.ClassFileLocator;
+import net.bytebuddy.dynamic.TypeResolutionStrategy;
+import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
+import net.bytebuddy.implementation.MethodDelegation;
+import net.bytebuddy.pool.TypePool;
+
+import static net.bytebuddy.matcher.ElementMatchers.named;
+import static net.bytebuddy.matcher.ElementMatchers.takesArguments;
+
+/**
+ * Utils to install the ByteBuddy method interceptor for bootstrap
+ */
+public class BootstrapBBUtils
+{
+    /**
+     * Note that the test class _must_ define the `setBootstrapState` method 
in order for the installed intercepter to be effective.
+     * See {@code ReplacementTest.BBHelperReplacementsNode} for example
+     */
+    public static void installSetBoostrapStateIntercepter(ClassLoader cl, 
Class<?> interceptor)
+    {
+        TypePool typePool = TypePool.Default.of(cl);
+        TypeDescription description = 
typePool.describe("org.apache.cassandra.db.SystemKeyspace")
+                                              .resolve();
+        new ByteBuddy().rebase(description, 
ClassFileLocator.ForClassLoader.of(cl))
+                       
.method(named("setBootstrapState").and(takesArguments(1)))
+                       .intercept(MethodDelegation.to(interceptor))
+                       // Defer class loading until all dependencies are loaded
+                       .make(TypeResolutionStrategy.Lazy.INSTANCE, typePool)
+                       .load(cl, ClassLoadingStrategy.Default.INJECTION);
+    }
+}
diff --git 
a/src/test/integration/org/apache/cassandra/sidecar/testing/CassandraSidecarTestContext.java
 
b/src/test/integration/org/apache/cassandra/sidecar/testing/CassandraSidecarTestContext.java
index 73e8ed5..bb3db58 100644
--- 
a/src/test/integration/org/apache/cassandra/sidecar/testing/CassandraSidecarTestContext.java
+++ 
b/src/test/integration/org/apache/cassandra/sidecar/testing/CassandraSidecarTestContext.java
@@ -25,6 +25,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
 import java.util.stream.Collectors;
 import java.util.stream.IntStream;
 
@@ -70,8 +71,8 @@ public class CassandraSidecarTestContext implements 
AutoCloseable
     private final DnsResolver dnsResolver;
     private final AbstractCassandraTestContext abstractCassandraTestContext;
     private final Vertx vertx;
-    private final int numInstancesToManage;
     private final List<InstanceConfigListener> instanceConfigListeners;
+    private int numInstancesToManage;
     public InstancesConfig instancesConfig;
     private List<JmxClient> jmxClients;
     private CQLSessionProvider sessionProvider;
@@ -156,22 +157,42 @@ public class CassandraSidecarTestContext implements 
AutoCloseable
         return cluster;
     }
 
+    public void setNumInstancesToManage(int numInstancesToManage)
+    {
+        this.numInstancesToManage = numInstancesToManage;
+        refreshInstancesConfig();
+    }
+
     public InstancesConfig instancesConfig()
     {
-        // rebuild instances config if cluster changed
-        if (instancesConfig == null
-            || instancesConfig.instances().size() != numInstancesToManage)
+        if (instancesConfig == null)
         {
-            // clean-up any open sessions or client resources
-            close();
-            setInstancesConfig();
+            refreshInstancesConfig();
         }
         return this.instancesConfig;
     }
 
+    public InstancesConfig refreshInstancesConfig()
+    {
+        // clean-up any open sessions or client resources
+        close();
+        setInstancesConfig();
+        return this.instancesConfig;
+    }
+
     public Session session()
     {
-        return sessionProvider.get();
+        return sessionProvider == null ? null : sessionProvider.get();
+    }
+
+    public void closeSessionProvider()
+    {
+        if (sessionProvider == null)
+        {
+            return;
+        }
+
+        sessionProvider.close();
     }
 
     @Override
@@ -192,20 +213,6 @@ public class CassandraSidecarTestContext implements 
AutoCloseable
         }
     }
 
-    public JmxClient jmxClient()
-    {
-        return jmxClient(0);
-    }
-
-    private JmxClient jmxClient(int instance)
-    {
-        if (jmxClients == null)
-        {
-            setInstancesConfig();
-        }
-        return jmxClients.get(instance);
-    }
-
     private void setInstancesConfig()
     {
         this.instancesConfig = buildInstancesConfig(versionProvider, 
dnsResolver);
@@ -227,6 +234,10 @@ public class CassandraSidecarTestContext implements 
AutoCloseable
                                                      0, 
SharedExecutorNettyOptions.INSTANCE);
         for (int i = 0; i < configs.size(); i++)
         {
+            if (configs.get(i) == null)
+            {
+                continue;
+            }
             IInstanceConfig config = configs.get(i);
             String hostName = JMXUtil.getJmxHost(config);
             int nativeTransportPort = tryGetIntConfig(config, 
"native_transport_port", 9042);
@@ -277,6 +288,7 @@ public class CassandraSidecarTestContext implements 
AutoCloseable
         // Always return the complete list of addresses even if the cluster 
isn't yet that large
         // this way, we populate the entire local instance list
         return configs.stream()
+                      .filter(Objects::nonNull)
                       .map(config -> new 
InetSocketAddress(config.broadcastAddress().getAddress(),
                                                            
tryGetIntConfig(config, "native_transport_port", 9042)))
                       .collect(Collectors.toList());
@@ -285,12 +297,27 @@ public class CassandraSidecarTestContext implements 
AutoCloseable
     @NotNull
     private List<InstanceConfig> buildInstanceConfigs(UpgradeableCluster 
cluster)
     {
-        return IntStream.range(1, numInstancesToManage + 1)
-                        .mapToObj(nodeNum ->
-                                  
AbstractClusterUtils.createInstanceConfig(cluster, nodeNum))
+        int nodes = numInstancesToManage == -1 ? cluster.size() : 
numInstancesToManage;
+        return IntStream.range(1, nodes + 1)
+                        .mapToObj(nodeNum -> {
+                            // check whether the instances are managed by the 
test framework first. Because the nodeNum might be greater than the cluster size
+                            if (manageInstanceByTestFramework() && 
cluster.get(nodeNum).isShutdown())
+                            {
+                                return null;
+                            }
+                            else
+                            {
+                                return 
AbstractClusterUtils.createInstanceConfig(cluster, nodeNum);
+                            }
+                        })
                         .collect(Collectors.toList());
     }
 
+    private boolean manageInstanceByTestFramework()
+    {
+        return numInstancesToManage == -1;
+    }
+
     /**
      * A listener for {@link InstancesConfig} state changes
      */
diff --git 
a/src/test/integration/org/apache/cassandra/sidecar/testing/IntegrationTestBase.java
 
b/src/test/integration/org/apache/cassandra/sidecar/testing/IntegrationTestBase.java
index 593414c..2755aec 100644
--- 
a/src/test/integration/org/apache/cassandra/sidecar/testing/IntegrationTestBase.java
+++ 
b/src/test/integration/org/apache/cassandra/sidecar/testing/IntegrationTestBase.java
@@ -133,11 +133,11 @@ public abstract class IntegrationTestBase
      * Defaults to the entire cluster.
      *
      * @param clusterSize the size of the cluster as defined by the 
integration test
-     * @return the number of instances to manage
+     * @return the number of instances to manage; or -1 to let test framework 
to determine the cluster size at the runtime
      */
     protected int getNumInstancesToManage(int clusterSize)
     {
-        return clusterSize;
+        return -1;
     }
 
     @AfterEach
@@ -199,6 +199,8 @@ public abstract class IntegrationTestBase
         {
             try
             {
+                sidecarTestContext.refreshInstancesConfig();
+
                 Session session = maybeGetSession();
 
                 session.execute("CREATE KEYSPACE IF NOT EXISTS " + 
TEST_KEYSPACE +
@@ -238,6 +240,13 @@ public abstract class IntegrationTestBase
         return tableName;
     }
 
+    protected static void awaitLatchOrTimeout(CountDownLatch latch, long 
duration, TimeUnit timeUnit)
+    {
+        assertThat(Uninterruptibles.awaitUninterruptibly(latch, duration, 
timeUnit))
+        .describedAs("Latch times out after " + duration + ' ' + 
timeUnit.name())
+        .isTrue();
+    }
+
     protected Session maybeGetSession()
     {
         Session session = sidecarTestContext.session();
diff --git 
a/src/test/integration/org/apache/cassandra/sidecar/testing/IntegrationTestModule.java
 
b/src/test/integration/org/apache/cassandra/sidecar/testing/IntegrationTestModule.java
index 3009155..5030261 100644
--- 
a/src/test/integration/org/apache/cassandra/sidecar/testing/IntegrationTestModule.java
+++ 
b/src/test/integration/org/apache/cassandra/sidecar/testing/IntegrationTestModule.java
@@ -18,16 +18,14 @@
 
 package org.apache.cassandra.sidecar.testing;
 
-import java.net.InetSocketAddress;
 import java.util.Collections;
 import java.util.List;
-import java.util.stream.Collectors;
 
+import com.datastax.driver.core.Session;
 import com.google.inject.AbstractModule;
 import com.google.inject.Provides;
 import com.google.inject.Singleton;
 import io.vertx.core.Vertx;
-import org.apache.cassandra.sidecar.cluster.CQLSessionProviderImpl;
 import org.apache.cassandra.sidecar.cluster.InstancesConfig;
 import org.apache.cassandra.sidecar.cluster.instance.InstanceMetadata;
 import org.apache.cassandra.sidecar.common.server.CQLSessionProvider;
@@ -40,6 +38,7 @@ import 
org.apache.cassandra.sidecar.config.yaml.SidecarConfigurationImpl;
 import org.apache.cassandra.sidecar.config.yaml.TestServiceConfiguration;
 import org.apache.cassandra.sidecar.exceptions.NoSuchSidecarInstanceException;
 import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
 
 import static 
org.apache.cassandra.sidecar.server.SidecarServerEvents.ON_SERVER_STOP;
 
@@ -84,13 +83,26 @@ public class IntegrationTestModule extends AbstractModule
     @Singleton
     public CQLSessionProvider cqlSessionProvider(Vertx vertx, InstancesConfig 
instancesConfig)
     {
-        // cql session provider uses local instances as contacts
-        List<InetSocketAddress> addresses = instancesConfig.instances()
-                                                           .stream()
-                                                           .map(i -> new 
InetSocketAddress(i.host(), i.port()))
-                                                           
.collect(Collectors.toList());
-        CQLSessionProviderImpl cqlSessionProvider = new 
CQLSessionProviderImpl(addresses, addresses, 500, null,
-                                                                               
0, SharedExecutorNettyOptions.INSTANCE);
+        CQLSessionProvider cqlSessionProvider = new CQLSessionProvider()
+        {
+            @Override
+            public @Nullable Session get()
+            {
+                return cassandraTestContext.session();
+            }
+
+            @Override
+            public void close()
+            {
+                cassandraTestContext.closeSessionProvider();
+            }
+
+            @Override
+            public @Nullable Session getIfConnected()
+            {
+                return get();
+            }
+        };
         vertx.eventBus().localConsumer(ON_SERVER_STOP.address(), message -> 
cqlSessionProvider.close());
         return cqlSessionProvider;
     }
diff --git 
a/src/test/integration/org/apache/cassandra/testing/CassandraTestTemplate.java 
b/src/test/integration/org/apache/cassandra/testing/CassandraTestTemplate.java
index 899f729..fba4f6a 100644
--- 
a/src/test/integration/org/apache/cassandra/testing/CassandraTestTemplate.java
+++ 
b/src/test/integration/org/apache/cassandra/testing/CassandraTestTemplate.java
@@ -23,6 +23,7 @@ import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Optional;
+import java.util.function.Predicate;
 import java.util.stream.Stream;
 
 import org.junit.jupiter.api.extension.AfterEachCallback;
@@ -40,6 +41,7 @@ import com.vdurmont.semver4j.Semver;
 import org.apache.cassandra.distributed.UpgradeableCluster;
 import org.apache.cassandra.distributed.api.Feature;
 import org.apache.cassandra.distributed.api.TokenSupplier;
+import org.apache.cassandra.distributed.impl.AbstractCluster;
 import org.apache.cassandra.distributed.shared.Versions;
 import org.apache.cassandra.sidecar.common.utils.Preconditions;
 
@@ -142,6 +144,9 @@ public class CassandraTestTemplate implements 
TestTemplateInvocationContextProvi
 
         private BeforeEachCallback beforeEach()
         {
+            Predicate<String> extra = c -> {
+                return c.contains("BBHelper") || c.contains("BootstrapState");
+            };
             return beforeEachCtx -> {
                 CassandraIntegrationTest annotation = 
getCassandraIntegrationTestAnnotation(context, true);
                 // spin up a C* cluster using the in-jvm dtest
@@ -162,6 +167,7 @@ public class CassandraTestTemplate implements 
TestTemplateInvocationContextProvi
                                   .withDynamicPortAllocation(true) // to allow 
parallel test runs
                                   .withVersion(requestedVersion)
                                   .withDCs(dcCount)
+                                  
.withSharedClasses(extra.or(AbstractCluster.SHARED_PREDICATE))
                                   
.withDataDirCount(annotation.numDataDirsPerInstance())
                                   .withConfig(config -> 
annotationToFeatureList(annotation).forEach(config::with));
                 TokenSupplier tokenSupplier = 
TokenSupplier.evenlyDistributedTokens(finalNodeCount,
diff --git 
a/src/test/integration/org/apache/cassandra/testing/ConfigurableCassandraTestContext.java
 
b/src/test/integration/org/apache/cassandra/testing/ConfigurableCassandraTestContext.java
index c1c0187..b43f3ac 100644
--- 
a/src/test/integration/org/apache/cassandra/testing/ConfigurableCassandraTestContext.java
+++ 
b/src/test/integration/org/apache/cassandra/testing/ConfigurableCassandraTestContext.java
@@ -71,6 +71,7 @@ public class ConfigurableCassandraTestContext extends 
AbstractCassandraTestConte
         {
             try
             {
+                cluster = null; // make sure cluster is null
                 cluster = configureCluster(configurator);
                 cluster.startup();
                 return cluster;


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]


Reply via email to