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.contains() — O(N) per replica. * Fast path: HashSet.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 replicas; Shard(List replicas) { this.replicas = replicas; } } /** * Simulates the defective crossCheckReplicaStateWithLiveNodes. * Returns total comparison count (liveNodes.contains() calls × avg scan length). */ static long slowCrossCheck(List liveNodes, List 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 liveNodes, List shards) { Set 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 buildCluster(int S, int R, int N) { List shards = new ArrayList<>(); for (int s = 0; s < S; s++) { List 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 buildLiveNodes(int N) { List 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 liveNodes = new ArrayList<>(); liveNodes.add("node0"); liveNodes.add("node1"); // node2 is dead List shards = new ArrayList<>(); List 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 liveNodes = new ArrayList<>(); liveNodes.add("node0"); liveNodes.add("node1"); List shards = new ArrayList<>(); List 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 liveNodes = buildLiveNodes(N); List slowShards = buildCluster(S, R, N); List 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 liveNodes = buildLiveNodes(N); List slowShards = buildCluster(S, R, N); List 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); } }