Bug: Cassandra Dtest AbstractCluster failed to create new instance config
Status: Triage Needed
Priority: Normal
Created: 01/Jul/24 23:30

h2. What happened

In the Cassandra dtest framework, when {{newInstanceConfig()}} is explicitly called in the test code for generating a new instance configuration, it will throw an unexpected index out-of-bound exception when getting tokens from tokenSupplier.
h2. How to reproduce

Put the following test under cassandra/test/distributed/org/apache/cassandra/distributed/upgrade/, and build dtest jars. I'm using version pair [5.0-alpha1, 5.0-alpha2], and this failure happens also in older versions.
{code:java}
package org.apache.cassandra.distributed.upgrade;
public class demoUpgradeTest extends UpgradeTestBase
    @Test
    public void demoTest() throws Throwable
    {
        new TestCase()
                .nodes(2)
                .nodesToUpgrade(1)
                .withConfig(config -> config.with(Feature.GOSSIP, Feature.NETWORK))
                .upgradesToCurrentFrom(v3X)
                .setup((cluster) -> {
                    // do nothing.
                })
                .runAfterNodeUpgrade((cluster, node) -> {
                    IInstanceConfig config = cluster.newInstanceConfig();
                }).run();
    }
} {code}
Run the test with
{code:java}
$ ant test-jvm-dtest-some-Duse.jdk11=true -Dtest.name=org.apache.cassandra.distributed.upgrade.demoUpgradeTest {code}
You will see the following failure:
{code:java}
[junit-timeout] Testcase: demoUpgradeTest(org.apache.cassandra.distributed.upgrade.demoUpgradeTest)-_jdk11:    FAILED
[junit-timeout] Error in test '5.0-alpha2 -> [5.0-alpha2]' while upgrading to '5.0-alpha2'; successful upgrades []
[junit-timeout] junit.framework.AssertionFailedError: Error in test '5.0-alpha2 -> [5.0-alpha2]' while upgrading to '5.0-alpha2'; successful upgrades []
[junit-timeout]     at org.apache.cassandra.distributed.upgrade.UpgradeTestBase$TestCase.run(UpgradeTestBase.java:442)
[junit-timeout]     at org.apache.cassandra.distributed.upgrade.demoUpgradeTest.demoTest(demoUpgradeTest.java:104)
[junit-timeout]     at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
[junit-timeout]     at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
[junit-timeout]     at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
[junit-timeout] Caused by: java.lang.ArrayIndexOutOfBoundsException: Index 2 out of bounds for length 2
[junit-timeout]     at org.apache.cassandra.distributed.api.TokenSupplier.lambda$evenlyDistributedTokens$0(TokenSupplier.java:59)
[junit-timeout]     at org.apache.cassandra.distributed.impl.AbstractCluster.createInstanceConfig(AbstractCluster.java:589)
[junit-timeout]     at org.apache.cassandra.distributed.impl.AbstractCluster.newInstanceConfig(AbstractCluster.java:582)
[junit-timeout]     at org.apache.cassandra.distributed.upgrade.demoUpgradeTest.lambda$demoTest$2(demoUpgradeTest.java:103)
[junit-timeout]     at org.apache.cassandra.distributed.upgrade.UpgradeTestBase$TestCase.run(UpgradeTestBase.java:433) {code}
The failure is caused by the code below in [AbstractCluster.java|https://github.com/apache/cassandra/blob/trunk/test/distributed/org/apache/cassandra/distributed/impl/AbstractCluster.java#L589]
{code:java}
    public InstanceConfig newInstanceConfig()
    {
        return createInstanceConfig(size() + 1);     // here in our example -> 2 + 1
    }    @VisibleForTesting
    InstanceConfig createInstanceConfig(int nodeNum)   // nodeNum = 3 in the example
    {
        INodeProvisionStrategy provisionStrategy = nodeProvisionStrategy.create(subnet, portMap);
        Collection<String> tokens = tokenSupplier.tokens(nodeNum);  // however, here, tokenSupplier only has 2 tokens; we are trying to get the index of 3, which triggers the out-of-bound exception. 
        ...
    } {code}
The code should carefully check the length of the tokenSupplier before actually fetching elements from it.