java-topology/defects/kubernetes/unit/KubernetesTest.java

250 lines
11 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.*;
/**
* Standalone unit tests for kubernetes CWE-407 defects.
*
* kubernetes-0001: Job pod failure policy — O(n) exit-code set membership per container per rule
* slow() uses List.contains() (linear scan) per call.
* fast() uses HashSet (O(1) lookup per call) built once per requirement.
* Assert: slowOps > fastOps * 5x for V=500 values.
*
* kubernetes-0002: GC owner reference UID scan — O(refs × ownerUIDs) with linear scan
* slow() uses List.contains() per ref iteration.
* fast() uses HashSet built once before the loop.
* Assert: slowOps > fastOps * 5x for refs=200, ownerUIDs=200.
*
* kubernetes-0003: trackJobStatusAndRemoveFinalizers — redundant hasJobTrackingFinalizer scan
* In syncJob, Pass 1 builds uidsWithFinalizer set via hasJobTrackingFinalizer (slices.Contains
* per pod). Pass 2 calls hasJobTrackingFinalizer again instead of consulting the set.
* slow() calls linear finalizer scan twice per pod per sync cycle — O(2 × P × F).
* fast() calls linear scan once (build set), then O(1) set lookup in pass 2 — O(P×F + P).
* Assert: slowOps > fastOps * 1.5x for P=5000 pods.
*/
public class KubernetesTest {
// ── kubernetes-0001 ───────────────────────────────────────────────────────
/** Simulate requirement.Values as a plain list — O(V) per Contains call. */
static long slowExitCodeMatching(List<Integer> values, int exitCode, int containers, int rules) {
long ops = 0;
for (int r = 0; r < rules; r++) {
for (int c = 0; c < containers; c++) {
// O(V) scan per call — mirrors slices.Contains(requirement.Values, exitCode)
for (int v = 0; v < values.size(); v++) {
ops++;
if (values.get(v).equals(exitCode)) break;
}
}
}
return ops;
}
/** Fix: build a HashSet once per requirement — O(V) build, O(1) per lookup. */
static long fastExitCodeMatching(List<Integer> values, int exitCode, int containers, int rules) {
long ops = 0;
for (int r = 0; r < rules; r++) {
// Build set once per rule (amortised across all containers)
Set<Integer> set = new HashSet<>(values);
ops += values.size(); // O(V) build cost counted once
for (int c = 0; c < containers; c++) {
ops++; // O(1) lookup
set.contains(exitCode); // constant time
}
}
return ops;
}
static void testExitCodeMatching() {
int V = 500; // values in requirement.Values
int C = 50; // containers per pod
int R = 10; // policy rules
// Worst case: exitCode not in list — full scan every time
int exitCode = -1;
List<Integer> values = new ArrayList<>(V);
for (int i = 0; i < V; i++) values.add(i);
long sOps = slowExitCodeMatching(values, exitCode, C, R);
long fOps = fastExitCodeMatching(values, exitCode, C, R);
int Nx = 5;
boolean pass = sOps > fOps * Nx;
System.out.printf("kubernetes-0001 [V=%d C=%d R=%d]: slow=%d fast=%d ratio=%.1fx — %s%n",
V, C, R, sOps, fOps, (double) sOps / fOps, pass ? "PASS" : "FAIL");
if (!pass) throw new AssertionError("kubernetes-0001 FAIL: slow=" + sOps + " fast=" + fOps);
}
// ── kubernetes-0002 ───────────────────────────────────────────────────────
/** Simulate deleteOwnerRefJSONMergePatch — O(refs × ownerUIDs) with linear scan. */
static long slowOwnerRefPatch(List<String> refs, List<String> ownerUIDs) {
long ops = 0;
List<String> result = new ArrayList<>();
for (String ref : refs) {
// slices.Contains(ownerUIDs, ref.UID) — O(U) per ref
for (int i = 0; i < ownerUIDs.size(); i++) {
ops++;
if (ownerUIDs.get(i).equals(ref)) break;
}
if (!ownerUIDs.contains(ref)) {
result.add(ref);
}
}
return ops;
}
/** Fix: build a HashSet from ownerUIDs before the loop — O(1) per ref. */
static long fastOwnerRefPatch(List<String> refs, List<String> ownerUIDs) {
long ops = 0;
Set<String> dropSet = new HashSet<>(ownerUIDs);
ops += ownerUIDs.size(); // O(U) build cost
List<String> result = new ArrayList<>();
for (String ref : refs) {
ops++; // O(1) lookup
if (!dropSet.contains(ref)) {
result.add(ref);
}
}
return ops;
}
static void testOwnerRefPatch() {
int N = 300; // refs count == ownerUIDs count — worst case all disjoint
List<String> refs = new ArrayList<>(N);
List<String> ownerUIDs = new ArrayList<>(N);
for (int i = 0; i < N; i++) {
refs.add("ref-" + i);
ownerUIDs.add("uid-" + (N + i)); // no overlap → full scan every ref
}
long sOps = slowOwnerRefPatch(refs, ownerUIDs);
long fOps = fastOwnerRefPatch(refs, ownerUIDs);
int Nx = 5;
boolean pass = sOps > fOps * Nx;
System.out.printf("kubernetes-0002 [N=%d refs, N=%d uids]: slow=%d fast=%d ratio=%.1fx — %s%n",
N, N, sOps, fOps, (double) sOps / fOps, pass ? "PASS" : "FAIL");
if (!pass) throw new AssertionError("kubernetes-0002 FAIL: slow=" + sOps + " fast=" + fOps);
}
// ── kubernetes-0003 ───────────────────────────────────────────────────────
/**
* Slow: trackJobStatusAndRemoveFinalizers calls hasJobTrackingFinalizer (slices.Contains)
* twice per pod — once to build uidsWithFinalizer set (pass 1) and again in the main
* processing loop (pass 2), instead of consulting the already-built set.
*
* Each hasJobTrackingFinalizer call is O(F) where F = finalizers per pod.
* With P pods: O(2 × P × F) per sync cycle.
*/
static long slowTrackJobFinalizers(int pods, int finalizersPerPod, String trackingFinalizer) {
long ops = 0;
// Simulate pod finalizer lists — trackingFinalizer is the last element (worst case)
List<List<String>> podFinalizers = new ArrayList<>(pods);
for (int i = 0; i < pods; i++) {
List<String> fin = new ArrayList<>();
for (int f = 0; f < finalizersPerPod - 1; f++) {
fin.add("other-finalizer-" + f);
}
fin.add(trackingFinalizer); // tracking finalizer at end — worst case scan
podFinalizers.add(fin);
}
// Pass 1: build uidsWithFinalizer — O(P × F)
Set<Integer> uidsWithFinalizer = new HashSet<>();
for (int i = 0; i < pods; i++) {
List<String> fin = podFinalizers.get(i);
// hasJobTrackingFinalizer: slices.Contains — O(F)
for (int f = 0; f < fin.size(); f++) {
ops++;
if (fin.get(f).equals(trackingFinalizer)) {
uidsWithFinalizer.add(i);
break;
}
}
}
// Pass 2: main processing loop — calls hasJobTrackingFinalizer again — O(P × F) redundant
for (int i = 0; i < pods; i++) {
List<String> fin = podFinalizers.get(i);
// BUG: should use uidsWithFinalizer.contains(i) instead — O(1)
boolean hasFinalizer = false;
for (int f = 0; f < fin.size(); f++) {
ops++; // redundant slices.Contains scan
if (fin.get(f).equals(trackingFinalizer)) { hasFinalizer = true; break; }
}
if (!hasFinalizer) continue;
// process pod...
}
return ops;
}
/**
* Fast: pass 2 uses uidsWithFinalizer.Has(pod.UID) — O(1) map lookup.
* Only one slices.Contains pass (unavoidable to build the set).
*/
static long fastTrackJobFinalizers(int pods, int finalizersPerPod, String trackingFinalizer) {
long ops = 0;
List<List<String>> podFinalizers = new ArrayList<>(pods);
for (int i = 0; i < pods; i++) {
List<String> fin = new ArrayList<>();
for (int f = 0; f < finalizersPerPod - 1; f++) {
fin.add("other-finalizer-" + f);
}
fin.add(trackingFinalizer);
podFinalizers.add(fin);
}
// Pass 1: build uidsWithFinalizer — O(P × F)
Set<Integer> uidsWithFinalizer = new HashSet<>();
for (int i = 0; i < pods; i++) {
List<String> fin = podFinalizers.get(i);
for (int f = 0; f < fin.size(); f++) {
ops++;
if (fin.get(f).equals(trackingFinalizer)) {
uidsWithFinalizer.add(i);
break;
}
}
}
// Pass 2: FIX — use uidsWithFinalizer.Has(pod.UID) — O(1) per pod
for (int i = 0; i < pods; i++) {
ops++; // O(1) set lookup
if (!uidsWithFinalizer.contains(i)) continue;
// process pod...
}
return ops;
}
static void testTrackJobFinalizers() {
int P = 5000; // pods per job — large batch ML training job
int F = 5; // finalizers per pod — tracking finalizer at end (worst case)
String trackingFinalizer = "batch.kubernetes.io/job-tracking";
long sOps = slowTrackJobFinalizers(P, F, trackingFinalizer);
long fOps = fastTrackJobFinalizers(P, F, trackingFinalizer);
// slow = 2×P×F, fast = P×F + P → ratio = 2F/(F+1) → ~1.67x at F=5
double Nx = 1.5;
boolean pass = sOps > fOps * Nx;
System.out.printf("kubernetes-0003 [P=%d F=%d]: slow=%d fast=%d ratio=%.2fx — %s%n",
P, F, sOps, fOps, (double) sOps / fOps, pass ? "PASS" : "FAIL");
if (!pass) throw new AssertionError("kubernetes-0003 FAIL: slow=" + sOps + " fast=" + fOps);
}
// ── main ──────────────────────────────────────────────────────────────────
public static void main(String[] args) {
testExitCodeMatching();
testOwnerRefPatch();
testTrackJobFinalizers();
System.out.println("3/3 PASS");
}
}