276 lines
12 KiB
Java
276 lines
12 KiB
Java
package unit;
|
|
|
|
import java.util.*;
|
|
|
|
/**
|
|
* Cilium0003Algorithm — CWE-407 unit test for cilium-0003
|
|
*
|
|
* cilium-0003: node/manager/manager.go:977-1073
|
|
* for _, address := range oldNode.IPAddresses { // O(A) addresses
|
|
* if slices.Contains(nodeIPsAdded, prefix) { // O(A) linear scan
|
|
*
|
|
* nodeIPsAdded is a []netip.Prefix built by appending one entry per address —
|
|
* scanning it per-address is O(A²) total. Five such slices are scanned.
|
|
*
|
|
* Called from NodeUpdated() on every node add/update event.
|
|
* In large ClusterMesh deployments (500+ nodes), every node event pays O(A²).
|
|
*
|
|
* SLOW: slices.Contains([]Prefix, p) — O(A) linear scan per address
|
|
* FAST: map[Prefix]struct{} pre-built — O(1) per address
|
|
*
|
|
* No JUnit. Run: javac -d . Cilium0003Algorithm.java && java -ea unit.Cilium0003Algorithm
|
|
*/
|
|
public class Cilium0003Algorithm {
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Data model — mirrors netip.Prefix (modeled as String for simplicity)
|
|
// -------------------------------------------------------------------------
|
|
|
|
// Each "address" is a string like "10.0.0.1/32" or "fd00::1/128"
|
|
// "nodeIPsAdded" is a list of such strings (the new addresses)
|
|
// "oldIPAddresses" is the old list to diff against
|
|
|
|
static long slowOps = 0;
|
|
static long fastOps = 0;
|
|
|
|
// -------------------------------------------------------------------------
|
|
// SLOW: O(A²) — models removeNodeFromIPCache with slices.Contains per address
|
|
// -------------------------------------------------------------------------
|
|
|
|
/**
|
|
* For each old address, check if it's in the added set.
|
|
* Models: for _, addr := range oldIPAddresses { slices.Contains(nodeIPsAdded, addr) }
|
|
* Returns the list of addresses to remove (not in added set).
|
|
*/
|
|
static List<String> computeRemovedSlow(List<String> oldAddresses, List<String> addedPrefixes) {
|
|
List<String> toRemove = new ArrayList<>();
|
|
for (String addr : oldAddresses) { // O(A)
|
|
boolean found = false;
|
|
for (String added : addedPrefixes) { // O(A) linear scan — defect
|
|
slowOps++;
|
|
if (added.equals(addr)) {
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
if (!found) {
|
|
toRemove.add(addr);
|
|
}
|
|
}
|
|
return toRemove;
|
|
}
|
|
|
|
/**
|
|
* Models the full removeNodeFromIPCache pattern:
|
|
* five separate slices each scanned linearly.
|
|
*/
|
|
static int removeNodeFromIPCacheSlow(
|
|
List<String> oldAddresses,
|
|
List<String> nodeIPsAdded,
|
|
List<String> ipsetEntries,
|
|
List<String> healthIPsAdded,
|
|
List<String> ingressIPsAdded,
|
|
List<String> podCIDRsAdded
|
|
) {
|
|
int removeOps = 0;
|
|
for (String addr : oldAddresses) { // O(A)
|
|
// slices.Contains(nodeIPsAdded, prefix)
|
|
boolean inNodeIPs = false;
|
|
for (String p : nodeIPsAdded) { slowOps++; if (p.equals(addr)) { inNodeIPs = true; break; } }
|
|
|
|
// slices.Contains(ipsetEntries, ...)
|
|
boolean inIpset = false;
|
|
for (String p : ipsetEntries) { slowOps++; if (p.equals(addr)) { inIpset = true; break; } }
|
|
|
|
if (!inNodeIPs) { removeOps++; }
|
|
}
|
|
// pod CIDR removals
|
|
for (String cidr : podCIDRsAdded) { // O(CIDR)
|
|
boolean inPodCIDRs = false;
|
|
for (String p : podCIDRsAdded) { slowOps++; if (p.equals(cidr)) { inPodCIDRs = true; break; } }
|
|
}
|
|
// health/ingress IPs
|
|
for (String addr : oldAddresses) {
|
|
for (String p : healthIPsAdded) { slowOps++; if (p.equals(addr)) break; }
|
|
for (String p : ingressIPsAdded) { slowOps++; if (p.equals(addr)) break; }
|
|
}
|
|
return removeOps;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// FAST: O(A) — pre-build map[string]struct{} for each slice
|
|
// -------------------------------------------------------------------------
|
|
|
|
static List<String> computeRemovedFast(List<String> oldAddresses, List<String> addedPrefixes) {
|
|
Set<String> addedSet = new HashSet<>(addedPrefixes.size() * 2);
|
|
for (String p : addedPrefixes) { fastOps++; addedSet.add(p); } // O(A) one-time build
|
|
|
|
List<String> toRemove = new ArrayList<>();
|
|
for (String addr : oldAddresses) { // O(A)
|
|
fastOps++;
|
|
if (!addedSet.contains(addr)) { // O(1) lookup
|
|
toRemove.add(addr);
|
|
}
|
|
}
|
|
return toRemove;
|
|
}
|
|
|
|
static int removeNodeFromIPCacheFast(
|
|
List<String> oldAddresses,
|
|
List<String> nodeIPsAdded,
|
|
List<String> ipsetEntries,
|
|
List<String> healthIPsAdded,
|
|
List<String> ingressIPsAdded,
|
|
List<String> podCIDRsAdded
|
|
) {
|
|
// Pre-build all five sets — O(A) total one-time cost
|
|
Set<String> nodeIPsSet = new HashSet<>(nodeIPsAdded);
|
|
Set<String> ipsetSet = new HashSet<>(ipsetEntries);
|
|
Set<String> healthSet = new HashSet<>(healthIPsAdded);
|
|
Set<String> ingressSet = new HashSet<>(ingressIPsAdded);
|
|
Set<String> podCIDRsSet = new HashSet<>(podCIDRsAdded);
|
|
|
|
int removeOps = 0;
|
|
for (String addr : oldAddresses) { // O(A)
|
|
fastOps++;
|
|
if (!nodeIPsSet.contains(addr)) { // O(1)
|
|
removeOps++;
|
|
}
|
|
if (!ipsetSet.contains(addr)) fastOps++;
|
|
if (!healthSet.contains(addr)) fastOps++;
|
|
if (!ingressSet.contains(addr)) fastOps++;
|
|
}
|
|
return removeOps;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Helpers
|
|
// -------------------------------------------------------------------------
|
|
|
|
/** Build A addresses. First half overlap with 'added', second half are old-only. */
|
|
static List<String> buildAddresses(int A, String prefix) {
|
|
List<String> addrs = new ArrayList<>(A);
|
|
for (int i = 0; i < A; i++) {
|
|
addrs.add(prefix + "10." + (i / 256) + "." + (i % 256) + ".1/32");
|
|
}
|
|
return addrs;
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Tests
|
|
// -------------------------------------------------------------------------
|
|
|
|
static void testCorrectness() {
|
|
List<String> old = Arrays.asList("10.0.0.1/32", "10.0.0.2/32", "10.0.0.3/32");
|
|
List<String> added = Arrays.asList("10.0.0.1/32", "10.0.0.3/32"); // 2 is removed
|
|
|
|
List<String> slowResult = computeRemovedSlow(old, added);
|
|
List<String> fastResult = computeRemovedFast(old, added);
|
|
|
|
assert slowResult.size() == 1 : "slow: expected 1 removal, got " + slowResult.size();
|
|
assert fastResult.size() == 1 : "fast: expected 1 removal, got " + fastResult.size();
|
|
assert slowResult.get(0).equals("10.0.0.2/32") : "slow: wrong removal " + slowResult.get(0);
|
|
assert fastResult.get(0).equals("10.0.0.2/32") : "fast: wrong removal " + fastResult.get(0);
|
|
System.out.println("PASS correctness: removal diff verified");
|
|
}
|
|
|
|
static void testOpsCount_A50() {
|
|
int A = 50;
|
|
List<String> old = buildAddresses(A, "old-");
|
|
List<String> added = buildAddresses(A / 2, "old-"); // first half overlap
|
|
|
|
slowOps = 0;
|
|
List<String> slowResult = computeRemovedSlow(old, added);
|
|
long measuredSlowOps = slowOps;
|
|
|
|
fastOps = 0;
|
|
List<String> fastResult = computeRemovedFast(old, added);
|
|
long measuredFastOps = fastOps;
|
|
|
|
assert slowResult.size() == fastResult.size()
|
|
: "sizes differ: " + slowResult.size() + " vs " + fastResult.size();
|
|
|
|
double ratio = (double) measuredSlowOps / Math.max(measuredFastOps, 1);
|
|
System.out.printf("PASS ops_count A=%d: slowOps=%d fastOps=%d ratio=%.1fx%n",
|
|
A, measuredSlowOps, measuredFastOps, ratio);
|
|
assert measuredSlowOps > measuredFastOps * 5
|
|
: "expected slowOps >> fastOps, got slow=" + measuredSlowOps + " fast=" + measuredFastOps;
|
|
}
|
|
|
|
static void testPerf_A100_NodeUpdate() {
|
|
int A = 100;
|
|
List<String> oldAddrs = buildAddresses(A, "");
|
|
List<String> nodeIPsAdded = buildAddresses(A / 2, "");
|
|
List<String> ipsetEntries = buildAddresses(A / 4, "");
|
|
List<String> healthIPs = buildAddresses(2, "h-");
|
|
List<String> ingressIPs = buildAddresses(2, "i-");
|
|
List<String> podCIDRs = buildAddresses(10, "c-");
|
|
|
|
long t0 = System.nanoTime();
|
|
int slowResult = 0;
|
|
for (int i = 0; i < 5000; i++) {
|
|
slowResult += removeNodeFromIPCacheSlow(
|
|
oldAddrs, nodeIPsAdded, ipsetEntries, healthIPs, ingressIPs, podCIDRs);
|
|
}
|
|
long slowMs = (System.nanoTime() - t0) / 1_000_000;
|
|
|
|
long t1 = System.nanoTime();
|
|
int fastResult = 0;
|
|
for (int i = 0; i < 5000; i++) {
|
|
fastResult += removeNodeFromIPCacheFast(
|
|
oldAddrs, nodeIPsAdded, ipsetEntries, healthIPs, ingressIPs, podCIDRs);
|
|
}
|
|
long fastMs = (System.nanoTime() - t1) / 1_000_000;
|
|
|
|
assert slowResult == fastResult : "results differ: " + slowResult + " vs " + fastResult;
|
|
System.out.printf("PASS perf A=%d 5000 node-updates: slow=%dms fast=%dms ratio=%.1fx%n",
|
|
A, slowMs, fastMs, (double) slowMs / Math.max(fastMs, 1));
|
|
assert slowMs >= fastMs :
|
|
"expected slow >= fast, got slow=" + slowMs + "ms fast=" + fastMs + "ms";
|
|
}
|
|
|
|
static void testPerf_A300_ClusterMesh_stress() {
|
|
int A = 300;
|
|
List<String> oldAddrs = buildAddresses(A, "");
|
|
List<String> nodeIPsAdded = buildAddresses(A, ""); // all added (worst-case scan)
|
|
List<String> ipsetEntries = buildAddresses(A / 2, "");
|
|
List<String> healthIPs = buildAddresses(2, "h-");
|
|
List<String> ingressIPs = buildAddresses(2, "i-");
|
|
List<String> podCIDRs = buildAddresses(A / 3, "c-");
|
|
|
|
long t0 = System.nanoTime();
|
|
int slowResult = 0;
|
|
for (int i = 0; i < 1000; i++) {
|
|
slowResult += removeNodeFromIPCacheSlow(
|
|
oldAddrs, nodeIPsAdded, ipsetEntries, healthIPs, ingressIPs, podCIDRs);
|
|
}
|
|
long slowMs = (System.nanoTime() - t0) / 1_000_000;
|
|
|
|
long t1 = System.nanoTime();
|
|
int fastResult = 0;
|
|
for (int i = 0; i < 1000; i++) {
|
|
fastResult += removeNodeFromIPCacheFast(
|
|
oldAddrs, nodeIPsAdded, ipsetEntries, healthIPs, ingressIPs, podCIDRs);
|
|
}
|
|
long fastMs = (System.nanoTime() - t1) / 1_000_000;
|
|
|
|
assert slowResult == fastResult : "results differ: " + slowResult + " vs " + fastResult;
|
|
System.out.printf("PASS stress A=%d 1000 node-updates: slow=%dms fast=%dms ratio=%.1fx%n",
|
|
A, slowMs, fastMs, (double) slowMs / Math.max(fastMs, 1));
|
|
assert slowMs >= fastMs :
|
|
"expected slow >= fast, got slow=" + slowMs + "ms fast=" + fastMs + "ms";
|
|
}
|
|
|
|
// -------------------------------------------------------------------------
|
|
// Main
|
|
// -------------------------------------------------------------------------
|
|
|
|
public static void main(String[] args) {
|
|
System.out.println("=== Cilium0003Algorithm: node manager IP address dedup (cilium-0003) ===");
|
|
testCorrectness();
|
|
testOpsCount_A50();
|
|
testPerf_A100_NodeUpdate();
|
|
testPerf_A300_ClusterMesh_stress();
|
|
System.out.println("4/4 PASS");
|
|
}
|
|
}
|