Skip to content

Add tools to evaluate infeasibility#11

Open
dlucredativ wants to merge 3 commits into
mainfrom
explain_infeasible
Open

Add tools to evaluate infeasibility#11
dlucredativ wants to merge 3 commits into
mainfrom
explain_infeasible

Conversation

@dlucredativ

Copy link
Copy Markdown
Contributor

No description provided.

@dlucredativ dlucredativ force-pushed the explain_infeasible branch 2 times, most recently from 2761715 to ed9dbca Compare May 15, 2026 14:46
@dlucredativ

dlucredativ commented May 15, 2026

Copy link
Copy Markdown
Contributor Author
Screenshot 2026-05-15 at 16-49-09 ProxLB Solver Report Screenshot 2026-05-15 at 16-54-11 ProxLB Solver Report

An infeasible blame log line will also show up in the logs.

@dlucredativ dlucredativ requested a review from formorer May 15, 2026 14:51
…enario converter

The cluster_state JSONL event now logs every field the solver receives —
full balancing config, all Node fields (incl. memory_reserve, cpu_reserve,
storage_free/reserve, pressure metrics, maintenance), and all VM fields
(disks, priority, vm_type, pressure metrics). Runtime memory_used stays
in the event for the reporter. The snapshot is built from the already-
constructed Cluster so there is no duplicated reservation logic.

A new scripts/jsonl_to_scenario.py reconstructs a YAML scenario from a
solver_run JSONL using the enriched cluster_state plus the constraint
events. The output loads cleanly via proxlb_solver.loader.load_scenario,
enabling failing prod runs to be replayed as scenarios.

Round-trip tests cover three representative scenarios (basic, pin,
anti-affinity) and assert node/VM/balancing/constraints all survive
scenario → cluster_state → YAML → Cluster.
Adds explain_infeasibility(cluster), a feasibility-only diagnostic solver
that mirrors solve()'s hard constraints but tags each blameable rule with
an AddAssumption() literal and wraps the constraint with .OnlyEnforceIf().
When CP-SAT proves the model unsat, SufficientAssumptionsForInfeasibility()
returns a minimal subset of those literals — they map back to structured
rule descriptions (maintenance, evacuate, ignore, pin, hard anti-affinity,
hard affinity, per-node memory/CPU capacity, per-pool storage capacity).

shadow.py calls the explainer when the main solve returns infeasible and
attaches the result as a new 'blame' field on the existing 'infeasible'
JSONL event, plus a single [solver] blame summary line to the ProxLB log.

Example output for scenarios/infeasible/pin_to_maintenance.yaml:
  {'type': 'maintenance', 'node': 'node-B'}
  {'type': 'pin', 'vm': 'stuck-vm', 'nodes': ['node-B']}

Tests cover three representative infeasible scenarios (anti-affinity
overflow, pin-vs-maintenance, affinity-vs-pin), feasible and empty
clusters (must return []), plus the shadow integration end-to-end.
cli.py runs explain_infeasibility() once per infeasible scenario and
plumbs the result through to all three report writers, mirroring the
existing migration_plans plumbing.

reporter.py gains _format_blame_line() (one-line human-readable rendering
of a blame dict) and prints the blame after the INFEASIBLE marker in:
  - print_report  (terminal: bulleted list under the INFEASIBLE line)
  - write_markdown_report  (Markdown: **Blame** block + list items)
  - write_html_report  (HTML: <p><b>Blame</b></p><ul>…)

make report now shows precisely which rules collide for every infeasible
scenario (affinity-vs-pin, anti-affinity overflow, pin-to-maintenance,
evacuate-vs-capacity, RULE_CONFLICT cases, …) instead of just the bare
"INFEASIBLE" status.
@dlucredativ dlucredativ force-pushed the explain_infeasible branch from ed9dbca to fd37a94 Compare May 21, 2026 12:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant