mkellermann97 abb4d40160 feat(sdn): cross-cluster EVPN vNets — Phase 2 (edit + drift) (#612)
Builds on Phase 1's create+read layer with the lifecycle + drift half PDM lacks.

Edit fan-out — PUT /api/multi-sdn/vnets/<id>: change alias + add/remove subnets,
fanned out to every member (PVE vnet-alias PUT, subnet POST/DELETE with the
<zone>-<cidr> id URL-encoded) + one apply per cluster. Structural fields
(name/zone/vni/asn/controller) are immutable — a change there rebuilds the span,
so it's rejected with a delete-and-recreate hint. Gated sdn.manage+admin.settings,
per-member check_cluster_access, partial-failure status like create.

Drift detect + reconcile:
- Background scanner (6h, daemon thread started in api/__init__, mirrors
  drift.py) reads live per-member SDN state for each aggregate vnet and persists
  the drift status. DETECT-ONLY by default — it never writes to a cluster unless
  the new global setting multi_sdn_drift_reconcile is opted in.
- Manual POST .../<id>/reconcile (re-assert desired + fix alias drift) and
  POST .../<id>/scan (read-only drift refresh, node.view) for on-demand use.
- When auto-reconcile is opted in, it fixes ONLY the drifted members (never
  reloads an in-sync member), debounces 'missing' (needs two consecutive
  non-healthy passes so a transient partial read can't resurrect intentionally-
  removed SDN), and writes a log_audit trail for the unattended mutation.

Frontend (MultiClusterEvpnView): edit modal (alias/subnets), per-cluster drift
detail, Scan / Reconcile / Edit actions, and an admin-only auto-reconcile toggle.
i18n all 7 langs.

Adversarial review (4 dimensions x verify, 2 workflows) → 7 findings fixed:
_apply_on_cluster now skips the cluster-wide apply when nothing changed (kills
the over-broad blast radius: one drifted member no longer reloads the whole
span, and reapply/reconcile of in-sync members is a no-op); reapply treats
in_sync as done; auto-reconcile is per-member + missing-debounced + audited; the
toggle is hidden from non-admins; edit subnet fold-back de-dupes; and the
pre-existing DUPLICATE it: translation block (silently dropping 317 Italian
keys) is merged into one — Italian is whole again.

Verified in-process (no real same-ASN EVPN clusters — honest limit): 44 tests
green across edit/reconcile/scan/scanner/gate/debounce/no-op-apply/subnet-dedup;
app boots + 9 routes + scanner thread; build + translations parse; auth/injection
review dimensions came back clean. Phase-2 auto-reconcile ships default-off.
2026-07-25 14:33:53 +02:00

118 lines
4.7 KiB
Python

# -*- coding: utf-8 -*-
"""
PegaProx API Blueprint Registration
"""
import logging
def register_blueprints(app):
"""Register all API blueprints with the Flask app."""
from pegaprox.api.auth import bp as auth_bp
from pegaprox.api.users import bp as users_bp
from pegaprox.api.clusters import bp as clusters_bp
from pegaprox.api.vms import bp as vms_bp
from pegaprox.api.nodes import bp as nodes_bp
from pegaprox.api.pbs import bp as pbs_bp
from pegaprox.api.storage import bp as storage_bp
from pegaprox.api.datacenter import bp as datacenter_bp
from pegaprox.api.vmware import bp as vmware_bp
from pegaprox.api.schedules import bp as schedules_bp
from pegaprox.api.reports import bp as reports_bp
from pegaprox.api.settings import bp as settings_bp
from pegaprox.api.alerts import bp as alerts_bp
from pegaprox.api.realtime import bp as realtime_bp
from pegaprox.api.search import bp as search_bp
from pegaprox.api.static_files import bp as static_files_bp
from pegaprox.api.history import bp as history_bp
from pegaprox.api.groups import bp as groups_bp
from pegaprox.api.ceph import bp as ceph_bp
from pegaprox.api.xhm import bp as xhm_bp
from pegaprox.api.site_recovery import bp as site_recovery_bp
from pegaprox.api.plugins import bp as plugins_bp
from pegaprox.api.webauthn import bp as webauthn_bp
from pegaprox.api.metrics_exporter import bp as metrics_exporter_bp
from pegaprox.api.insights import bp as insights_bp
from pegaprox.api.templates_lib import bp as templates_lib_bp
from pegaprox.api.push import bp as push_bp, register_alert_handler
from pegaprox.api.costs import bp as costs_bp
from pegaprox.api.drift import bp as drift_bp, start_scanner as start_drift_scanner
from pegaprox.api.audit_search import bp as audit_search_bp
from pegaprox.api.siem import bp as siem_bp, start_worker as start_siem_worker
from pegaprox.api.snapshots import bp as snapshots_bp, start_scheduler as start_snap_scheduler
from pegaprox.api.topology import bp as topology_bp
from pegaprox.api.power import bp as power_bp
from pegaprox.api.dr_drill import bp as dr_drill_bp
from pegaprox.api.multi_sdn import bp as multi_sdn_bp, start_scanner as start_multi_sdn_scanner
app.register_blueprint(auth_bp)
app.register_blueprint(users_bp)
app.register_blueprint(clusters_bp)
app.register_blueprint(vms_bp)
app.register_blueprint(nodes_bp)
app.register_blueprint(pbs_bp)
app.register_blueprint(storage_bp)
app.register_blueprint(datacenter_bp)
app.register_blueprint(vmware_bp)
app.register_blueprint(schedules_bp)
app.register_blueprint(reports_bp)
app.register_blueprint(settings_bp)
app.register_blueprint(alerts_bp)
app.register_blueprint(realtime_bp)
app.register_blueprint(search_bp)
app.register_blueprint(static_files_bp)
app.register_blueprint(history_bp)
app.register_blueprint(groups_bp)
app.register_blueprint(ceph_bp)
app.register_blueprint(xhm_bp)
app.register_blueprint(site_recovery_bp)
app.register_blueprint(plugins_bp)
app.register_blueprint(webauthn_bp)
app.register_blueprint(metrics_exporter_bp)
app.register_blueprint(insights_bp)
app.register_blueprint(templates_lib_bp)
app.register_blueprint(push_bp)
app.register_blueprint(costs_bp)
app.register_blueprint(drift_bp)
app.register_blueprint(audit_search_bp)
app.register_blueprint(siem_bp)
app.register_blueprint(snapshots_bp)
app.register_blueprint(topology_bp)
app.register_blueprint(power_bp)
app.register_blueprint(dr_drill_bp)
app.register_blueprint(multi_sdn_bp)
# Initialize WebSocket support for realtime blueprint
from pegaprox.api.realtime import sock
sock.init_app(app)
# MK May 2026 — wire push handler into the alerts pipeline
try:
register_alert_handler()
except Exception:
pass
# NS May 2026 — drift scanner thread (6h cadence; harmless if mgrs not yet up)
try:
start_drift_scanner()
except Exception as e:
logging.warning(f"drift scanner start failed: {e}")
# MK Jul 2026 — #612 Phase 2: cross-cluster EVPN drift scanner (6h cadence,
# detect-only unless multi_sdn_drift_reconcile is opted in)
try:
start_multi_sdn_scanner()
except Exception as e:
logging.warning(f"multi_sdn scanner start failed: {e}")
# MK May 2026 — SIEM forwarder worker
try:
start_siem_worker()
except Exception as e:
logging.warning(f"siem worker start failed: {e}")
# NS May 2026 — snapshot scheduler (60s tick, idempotent)
try:
start_snap_scheduler()
except Exception as e:
logging.warning(f"snapshot scheduler start failed: {e}")