import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.AfterAll;
import org.testcontainers.containers.GenericContainer;
import org.testcontainers.containers.Network;
import org.testcontainers.containers.wait.strategy.Wait;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.impl.CloudSolrClient;
import org.apache.solr.client.solrj.response.ClusteringResponse;
import java.util.ArrayList;
import java.util.List;
import static org.junit.jupiter.api.Assertions.*;
public class SolrRollingUpgradeTest {
private static final String INITIAL_VERSION = "8.11.1";
private static final String TARGET_VERSION = "9.4.1";
private static final int NUM_NODES = 3;
private static final int SOLR_PORT = 8983;
private static Network network;
private static GenericContainer<?> zookeeper;
private static List<GenericContainer<?>> solrNodes;
private static CloudSolrClient cloudClient;
@BeforeAll
static void setupCluster() throws Exception {
network = Network.newNetwork();
solrNodes = new ArrayList<>();
// Start ZooKeeper
zookeeper = new GenericContainer<>("zookeeper:3.9")
.withNetwork(network)
.withNetworkAliases("zookeeper")
.waitingFor(Wait.forListeningPort())
.withExposedPorts(2181);
zookeeper.start();
// Start 3 Solr nodes at initial version
for (int i = 0; i < NUM_NODES; i++) {
GenericContainer<?> solrNode = new GenericContainer<>("solr:" + INITIAL_VERSION)
.withNetwork(network)
.withNetworkAliases("solr-" + i)
.withEnv("ZK_HOST", "zookeeper:2181")
.withEnv("SOLR_HEAP", "512m")
.withEnv("SOLR_OPTS", "-Dsolr.clustering.enabled=false")
.waitingFor(Wait.forHttp("/solr/admin/info/system")
.forStatusCode(200))
.withExposedPorts(SOLR_PORT)
.withStartupTimeout(java.time.Duration.ofMinutes(2));
solrNode.start();
solrNodes.add(solrNode);
}
// Initialize CloudSolrClient pointing to ZooKeeper
String zkHost = zookeeper.getHost() + ":" + zookeeper.getMappedPort(2181);
cloudClient = new CloudSolrClient.Builder(List.of(zkHost), java.util.Optional.empty()).build();
cloudClient.setDefaultCollection("test_collection");
Thread.sleep(3000); // Wait for cluster stabilization
}
@Test
void testClusterUpgradeAndHealth() throws Exception {
// Validate initial cluster
assertNotNull(cloudClient.getClusterState(), "Initial cluster state should be available");
// Rolling upgrade: stop, replace, and restart each node
for (int i = 0; i < NUM_NODES; i++) {
GenericContainer<?> oldNode = solrNodes.get(i);
// Stop the old node
oldNode.stop();
Thread.sleep(2000); // Wait for graceful shutdown
// Create and start new node with target version
GenericContainer<?> newNode = new GenericContainer<>("solr:" + TARGET_VERSION)
.withNetwork(network)
.withNetworkAliases("solr-" + i)
.withEnv("ZK_HOST", "zookeeper:2181")
.withEnv("SOLR_HEAP", "512m")
.withEnv("SOLR_OPTS", "-Dsolr.clustering.enabled=false")
.waitingFor(Wait.forHttp("/solr/admin/info/system")
.forStatusCode(200))
.withExposedPorts(SOLR_PORT)
.withStartupTimeout(java.time.Duration.ofMinutes(2));
newNode.start();
solrNodes.set(i, newNode);
Thread.sleep(3000); // Wait for node to rejoin cluster
// Verify cluster health after each upgrade step
assertNotNull(cloudClient.getClusterState(),
"Cluster state should be available after upgrading node " + i);
assertTrue(cloudClient.getClusterState().getLiveNodes().size() >= NUM_NODES - 1,
"At least " + (NUM_NODES - 1) + " nodes should be live during upgrade");
}
// Verify final cluster health
Thread.sleep(2000); // Ensure all nodes are settled
assertNotNull(cloudClient.getClusterState(), "Final cluster state should be available");
assertEquals(NUM_NODES, cloudClient.getClusterState().getLiveNodes().size(),
"All nodes should be live after upgrade");
// Verify all nodes are running target version (via basic health check)
for (GenericContainer<?> node : solrNodes) {
assertTrue(node.isRunning(), "All Solr nodes should be running");
}
}
@AfterAll
static void teardownCluster() throws Exception {
if (cloudClient != null) {
cloudClient.close();
}
for (GenericContainer<?> node : solrNodes) {
node.stop();
}
if (zookeeper != null) {
zookeeper.stop();
}
if (network != null) {
network.close();
}
}
}