java-topology/defects/solr/unit/ClusterStatusLiveNodesContains.java

195 lines
7 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package unit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* CWE-407 unit test: solr-001
* ClusterStatus.java:303 — liveNodes.contains(node_name) (List) inside double
* loop over shards × replicas — O(N × S × R) per CLUSTERSTATUS request.
*
* Slow path: List<String>.contains() — O(N) per replica.
* Fast path: HashSet<String>.contains() — O(1) per replica.
*
* Compile: javac -d . ClusterStatusLiveNodesContains.java
* Run: java -ea unit.ClusterStatusLiveNodesContains
*/
public class ClusterStatusLiveNodesContains {
/** Simulates a Replica record. */
static class Replica {
String nodeName;
String state; // "active" or "down"
Replica(String nodeName, String state) {
this.nodeName = nodeName;
this.state = state;
}
}
/** Simulates a Shard record. */
static class Shard {
List<Replica> replicas;
Shard(List<Replica> replicas) { this.replicas = replicas; }
}
/**
* Simulates the defective crossCheckReplicaStateWithLiveNodes.
* Returns total comparison count (liveNodes.contains() calls × avg scan length).
*/
static long slowCrossCheck(List<String> liveNodes, List<Shard> shards) {
long comparisons = 0;
for (Shard shard : shards) {
for (Replica replica : shard.replicas) {
if (!"down".equals(replica.state)) {
// O(liveNodes.size()) scan
comparisons += liveNodes.size();
if (!liveNodes.contains(replica.nodeName)) {
replica.state = "down";
}
}
}
}
return comparisons;
}
/**
* Simulates the fixed crossCheckReplicaStateWithLiveNodes using HashSet.
* Returns total hash operations (O(1) each).
*/
static long fastCrossCheck(List<String> liveNodes, List<Shard> shards) {
Set<String> liveNodeSet = new HashSet<>(liveNodes); // O(N) once
long operations = 0;
for (Shard shard : shards) {
for (Replica replica : shard.replicas) {
if (!"down".equals(replica.state)) {
operations++; // O(1) hash lookup
if (!liveNodeSet.contains(replica.nodeName)) {
replica.state = "down";
}
}
}
}
return operations;
}
/** Build a test cluster: N live nodes, S shards, R replicas each. */
static List<Shard> buildCluster(int S, int R, int N) {
List<Shard> shards = new ArrayList<>();
for (int s = 0; s < S; s++) {
List<Replica> replicas = new ArrayList<>();
for (int r = 0; r < R; r++) {
// Distribute replicas across nodes; node (s*R+r) % N
String nodeName = "node" + ((s * R + r) % N);
replicas.add(new Replica(nodeName, "active"));
}
shards.add(new Shard(replicas));
}
return shards;
}
static List<String> buildLiveNodes(int N) {
List<String> nodes = new ArrayList<>();
for (int i = 0; i < N; i++) nodes.add("node" + i);
return nodes;
}
public static void main(String[] args) {
int passed = 0;
int total = 0;
// Test 1: correctness — replicas on dead nodes get marked DOWN
{
total++;
List<String> liveNodes = new ArrayList<>();
liveNodes.add("node0");
liveNodes.add("node1");
// node2 is dead
List<Shard> shards = new ArrayList<>();
List<Replica> r1 = new ArrayList<>();
r1.add(new Replica("node0", "active"));
r1.add(new Replica("node2", "active")); // should go DOWN
r1.add(new Replica("node1", "active"));
shards.add(new Shard(r1));
slowCrossCheck(liveNodes, shards);
assert "active".equals(shards.get(0).replicas.get(0).state) : "node0 should stay active";
assert "down".equals(shards.get(0).replicas.get(1).state) : "node2 replica should be DOWN";
assert "active".equals(shards.get(0).replicas.get(2).state) : "node1 should stay active";
System.out.println("Test 1 (correctness): node2 replica correctly marked DOWN");
passed++;
}
// Test 2: fast path correctness
{
total++;
List<String> liveNodes = new ArrayList<>();
liveNodes.add("node0");
liveNodes.add("node1");
List<Shard> shards = new ArrayList<>();
List<Replica> r1 = new ArrayList<>();
r1.add(new Replica("node0", "active"));
r1.add(new Replica("node99", "active")); // dead
shards.add(new Shard(r1));
fastCrossCheck(liveNodes, shards);
assert "active".equals(shards.get(0).replicas.get(0).state);
assert "down".equals(shards.get(0).replicas.get(1).state) : "node99 should be DOWN";
System.out.println("Test 2 (fast correctness): node99 replica correctly marked DOWN");
passed++;
}
// Test 3: cost comparison — medium cluster
{
total++;
int N = 50; // live nodes
int S = 100; // shards
int R = 3; // replicas/shard
List<String> liveNodes = buildLiveNodes(N);
List<Shard> slowShards = buildCluster(S, R, N);
List<Shard> fastShards = buildCluster(S, R, N);
long slowCost = slowCrossCheck(liveNodes, slowShards);
long fastCost = fastCrossCheck(liveNodes, fastShards);
assert slowCost > fastCost * 5 :
String.format("Expected slow >> fast: slow=%d fast=%d", slowCost, fastCost);
System.out.printf("Test 3 (N=%d S=%d R=%d): slow=%d, fast=%d, ratio=%.1fx%n",
N, S, R, slowCost, fastCost, (double) slowCost / fastCost);
passed++;
}
// Test 4: production-scale cluster
{
total++;
int N = 100; // live nodes
int S = 500; // shards
int R = 3; // replicas/shard
List<String> liveNodes = buildLiveNodes(N);
List<Shard> slowShards = buildCluster(S, R, N);
List<Shard> fastShards = buildCluster(S, R, N);
long slowCost = slowCrossCheck(liveNodes, slowShards);
long fastCost = fastCrossCheck(liveNodes, fastShards);
double ratio = (double) slowCost / fastCost;
assert ratio > 20.0 :
String.format("Expected >20x speedup at N=%d S=%d R=%d, got %.1fx",
N, S, R, ratio);
System.out.printf("Test 4 (N=%d S=%d R=%d): slow=%d, fast=%d, speedup=%.1fx%n",
N, S, R, slowCost, fastCost, ratio);
passed++;
}
System.out.printf("%n%d/%d PASS%n", passed, total);
}
}