195 lines
7 KiB
Java
195 lines
7 KiB
Java
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);
|
||
}
|
||
}
|