fix(reboot): route slo blockers to action matrix
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 59s
CD Pipeline / build-and-deploy (push) Successful in 4m40s
CD Pipeline / post-deploy-checks (push) Has been cancelled
Some checks failed
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 59s
CD Pipeline / build-and-deploy (push) Successful in 4m40s
CD Pipeline / post-deploy-checks (push) Has been cancelled
This commit is contained in:
@@ -487,8 +487,28 @@ def _build_payload(scorecard: dict[str, Any], path: Path) -> dict[str, Any]:
|
||||
readiness_percent=readiness_percent,
|
||||
can_claim_slo=can_claim_slo,
|
||||
)
|
||||
active_blocker_action_matrix = _build_active_blocker_action_matrix(
|
||||
active_blockers=active_blockers,
|
||||
primary_blocker=sop_progress["primary_blocker"],
|
||||
)
|
||||
rollups = {
|
||||
"active_blocker_count": active_blocker_count,
|
||||
"active_blocker_action_count": active_blocker_action_matrix["item_count"],
|
||||
"active_blocker_action_category_counts": active_blocker_action_matrix[
|
||||
"category_counts"
|
||||
],
|
||||
"active_blocker_action_owner_lane_counts": active_blocker_action_matrix[
|
||||
"owner_lane_counts"
|
||||
],
|
||||
"primary_blocker_owner_lane": active_blocker_action_matrix[
|
||||
"primary_blocker_action"
|
||||
].get("owner_lane", ""),
|
||||
"primary_blocker_action_category": active_blocker_action_matrix[
|
||||
"primary_blocker_action"
|
||||
].get("category", ""),
|
||||
"telegram_active_blocker_alert_required_count": active_blocker_action_matrix[
|
||||
"telegram_alert_required_count"
|
||||
],
|
||||
"readiness_percent": readiness_percent,
|
||||
"completed_check_count": completed_check_count,
|
||||
"required_check_count": len(required_checks),
|
||||
@@ -694,7 +714,17 @@ def _build_payload(scorecard: dict[str, Any], path: Path) -> dict[str, Any]:
|
||||
"safe_next_step": safe_next_step,
|
||||
"next_safe_action": sop_progress["next_safe_action"],
|
||||
"primary_blocker": sop_progress["primary_blocker"],
|
||||
"primary_blocker_owner_lane": active_blocker_action_matrix[
|
||||
"primary_blocker_action"
|
||||
].get("owner_lane", ""),
|
||||
"primary_blocker_action_category": active_blocker_action_matrix[
|
||||
"primary_blocker_action"
|
||||
].get("category", ""),
|
||||
"active_blocker_count": active_blocker_count,
|
||||
"active_blocker_action_count": active_blocker_action_matrix["item_count"],
|
||||
"telegram_active_blocker_alert_required_count": (
|
||||
active_blocker_action_matrix["telegram_alert_required_count"]
|
||||
),
|
||||
"readiness_percent": readiness_percent,
|
||||
"blocked_by_fresh_reboot_window_only": blocked_by_fresh_reboot_window_only,
|
||||
"latest_verify_only_metric_present": latest_verify_only_metric_present,
|
||||
@@ -740,6 +770,7 @@ def _build_payload(scorecard: dict[str, Any], path: Path) -> dict[str, Any]:
|
||||
],
|
||||
},
|
||||
"reboot_sop_progress": sop_progress,
|
||||
"active_blocker_action_matrix": active_blocker_action_matrix,
|
||||
"controlled_service_data_backup_readback": (
|
||||
controlled_service_data_backup_readback
|
||||
),
|
||||
@@ -1080,8 +1111,8 @@ def _reboot_sop_current_phase(active_blockers: list[str], can_claim_slo: bool) -
|
||||
"reboot_event_required_host_unreachable",
|
||||
"fresh_all_host_reboot_event_missing",
|
||||
"all_required_hosts_not_in_10_minute_reboot_window",
|
||||
"windows99_vmware_autostart_readback_missing",
|
||||
"windows99_remote_execution_channel_unavailable",
|
||||
"windows99_vmware_autostart_readback_missing",
|
||||
"windows99_vmrun_missing",
|
||||
"windows99_vmware_vmx_missing",
|
||||
"windows99_vmware_autostart_config_not_ready",
|
||||
@@ -1129,8 +1160,8 @@ def _reboot_sop_primary_blocker(active_blockers: list[str]) -> str:
|
||||
"all_required_hosts_not_in_10_minute_reboot_window",
|
||||
"host_boot_observation_older_than_target_window",
|
||||
"host_uptime_unknown",
|
||||
"windows99_vmware_autostart_readback_missing",
|
||||
"windows99_remote_execution_channel_unavailable",
|
||||
"windows99_vmware_autostart_readback_missing",
|
||||
"windows99_vmrun_missing",
|
||||
"windows99_vmware_vmx_missing",
|
||||
"windows99_vmware_autostart_config_not_ready",
|
||||
@@ -1185,6 +1216,228 @@ def _reboot_sop_eta_or_wait_reason(
|
||||
return "eta_unavailable"
|
||||
|
||||
|
||||
def _build_active_blocker_action_matrix(
|
||||
*,
|
||||
active_blockers: list[str],
|
||||
primary_blocker: str,
|
||||
) -> dict[str, Any]:
|
||||
items = [
|
||||
_active_blocker_action_row(
|
||||
blocker=blocker,
|
||||
priority_order=index,
|
||||
is_primary=blocker == primary_blocker,
|
||||
)
|
||||
for index, blocker in enumerate(active_blockers, start=1)
|
||||
]
|
||||
primary_action = next(
|
||||
(item for item in items if item.get("is_primary") is True),
|
||||
{},
|
||||
)
|
||||
return {
|
||||
"schema_version": "reboot_active_blocker_action_matrix_v1",
|
||||
"item_count": len(items),
|
||||
"telegram_alert_required_count": sum(
|
||||
1 for item in items if item.get("telegram_alert_required") is True
|
||||
),
|
||||
"category_counts": _count_by(items, "category"),
|
||||
"owner_lane_counts": _count_by(items, "owner_lane"),
|
||||
"primary_blocker_action": primary_action,
|
||||
"items": items,
|
||||
}
|
||||
|
||||
|
||||
def _active_blocker_action_row(
|
||||
*,
|
||||
blocker: str,
|
||||
priority_order: int,
|
||||
is_primary: bool,
|
||||
) -> dict[str, Any]:
|
||||
category = "reboot_slo_unknown"
|
||||
owner_lane = "sre_reboot_slo_scorecard"
|
||||
severity = "critical"
|
||||
next_safe_action = (
|
||||
"read_active_blocker_evidence_then_rerun_reboot_slo_scorecard_no_reboot"
|
||||
)
|
||||
post_verifier = (
|
||||
"scripts/reboot-recovery/reboot-auto-recovery-slo-scorecard.py "
|
||||
"--output <scorecard.json>"
|
||||
)
|
||||
evidence_inputs = ["scorecard.active_blockers"]
|
||||
controlled_apply_mode = "verifier_only_from_reboot_slo_lane"
|
||||
forbidden_actions = [
|
||||
"host_reboot",
|
||||
"service_restart",
|
||||
"docker_daemon_restart",
|
||||
"node_drain",
|
||||
"database_write_or_restore",
|
||||
"secret_value_read",
|
||||
"workflow_dispatch",
|
||||
"github_api",
|
||||
"force_push_or_ref_delete",
|
||||
]
|
||||
|
||||
if blocker.startswith("windows99_"):
|
||||
category = "windows99_vmware_autostart"
|
||||
owner_lane = "windows99_console_or_no_secret_management_channel"
|
||||
next_safe_action = (
|
||||
"collect_windows99_vmware_autostart_verify_readback_then_rerun_"
|
||||
"reboot_scorecard_no_secret_no_reboot"
|
||||
)
|
||||
if blocker == "windows99_remote_execution_channel_unavailable":
|
||||
next_safe_action = (
|
||||
"restore_windows99_no_secret_management_channel_or_collect_local_"
|
||||
"console_verify_readback_then_rerun_reboot_scorecard_no_reboot"
|
||||
)
|
||||
post_verifier = (
|
||||
"bash scripts/reboot-recovery/collect-windows99-vmware-verify.sh "
|
||||
"--check && rerun_reboot_auto_recovery_slo_scorecard"
|
||||
)
|
||||
evidence_inputs = [
|
||||
"windows99_management_channel",
|
||||
"windows99_vmware_autostart",
|
||||
"windows99_verify_collection",
|
||||
]
|
||||
controlled_apply_mode = "console_verify_or_no_secret_collector_only"
|
||||
forbidden_actions.extend(
|
||||
[
|
||||
"windows_password_or_secret_collection",
|
||||
"vm_power_change",
|
||||
"windows_update_policy_apply_from_scorecard",
|
||||
"manual_registry_edit",
|
||||
]
|
||||
)
|
||||
elif blocker in {
|
||||
"all_host_reboot_detection_missing",
|
||||
"stateful_reboot_event_detection_missing",
|
||||
"host_boot_probe_missing_hosts",
|
||||
"host_unreachable_after_reboot",
|
||||
"host_boot_observation_older_than_target_window",
|
||||
"host_uptime_unknown",
|
||||
"reboot_event_missing_required_hosts",
|
||||
"reboot_event_required_host_unreachable",
|
||||
"fresh_all_host_reboot_event_missing",
|
||||
"all_required_hosts_not_in_10_minute_reboot_window",
|
||||
}:
|
||||
category = "host_boot_detection"
|
||||
owner_lane = "reboot_event_detector_and_host_probe"
|
||||
next_safe_action = (
|
||||
"rerun_reboot_event_detector_and_host_probe_verify_only_no_reboot"
|
||||
)
|
||||
post_verifier = (
|
||||
"scripts/reboot-recovery/reboot-auto-recovery-host-probe.sh && "
|
||||
"scripts/reboot-recovery/reboot-event-detector.py"
|
||||
)
|
||||
evidence_inputs = ["host_boot_detection", "reboot_event_detection"]
|
||||
elif blocker in {
|
||||
"post_reboot_summary_missing",
|
||||
"post_start_blocked_not_zero",
|
||||
"service_green_not_1",
|
||||
"host_188_service_green_not_1",
|
||||
}:
|
||||
category = "post_reboot_service_readiness"
|
||||
owner_lane = "service_readiness_and_k3s_workloads"
|
||||
next_safe_action = (
|
||||
"collect_post_reboot_readiness_summary_and_public_health_no_restart"
|
||||
)
|
||||
post_verifier = "scripts/reboot-recovery/post-reboot-readiness-summary.sh"
|
||||
evidence_inputs = ["post_reboot_readiness", "public_health_readback"]
|
||||
elif blocker == "backup_core_green_not_1" or "backup" in blocker:
|
||||
category = "backup_observability"
|
||||
owner_lane = "backup_health_and_restore_drill"
|
||||
next_safe_action = (
|
||||
"collect_backup_health_restore_drill_and_offsite_readback_no_restore"
|
||||
)
|
||||
post_verifier = "scripts/ops/backup-health-textfile-exporter.py --help"
|
||||
evidence_inputs = [
|
||||
"post_reboot_readiness.backup_core_green",
|
||||
"backup_health_textfile",
|
||||
"restore_drill_readback",
|
||||
]
|
||||
elif blocker == "wazuh_dashboard_degraded":
|
||||
category = "security_observability"
|
||||
owner_lane = "wazuh_dashboard_and_security_readback"
|
||||
severity = "warning"
|
||||
next_safe_action = "collect_wazuh_dashboard_readback_no_service_restart"
|
||||
post_verifier = "read_wazuh_dashboard_or_registry_health_readback"
|
||||
evidence_inputs = ["post_reboot_readiness.wazuh_dashboard_degraded"]
|
||||
elif blocker in _PUBLIC_MAINTENANCE_BLOCKERS or blocker.startswith("public_"):
|
||||
category = "public_maintenance_fallback"
|
||||
owner_lane = "public_route_maintenance_fallback"
|
||||
next_safe_action = (
|
||||
"run_public_maintenance_fallback_probe_and_rerun_scorecard_no_reload"
|
||||
)
|
||||
post_verifier = (
|
||||
"python3 scripts/reboot-recovery/public-maintenance-fallback-probe.py"
|
||||
)
|
||||
evidence_inputs = ["public_maintenance_fallback"]
|
||||
controlled_apply_mode = "check_mode_then_separate_edge_or_nginx_apply"
|
||||
elif "stockplatform" in blocker or "product_data" in blocker:
|
||||
category = "product_data_freshness"
|
||||
owner_lane = "stockplatform_freshness_and_ingestion"
|
||||
next_safe_action = (
|
||||
"inspect_stockplatform_freshness_ingestion_readback_then_check_mode_"
|
||||
"recovery_if_required_no_manual_db_write"
|
||||
)
|
||||
post_verifier = "stockplatform_public_api_freshness_and_ingestion_readback"
|
||||
evidence_inputs = ["stockplatform_data_freshness"]
|
||||
controlled_apply_mode = "check_mode_then_controlled_data_recovery_lane"
|
||||
forbidden_actions.extend(
|
||||
["manual_zero_fill", "copy_previous_trading_day_data"]
|
||||
)
|
||||
elif blocker in {
|
||||
"host_pressure_high_load",
|
||||
"host_container_cpu_attribution_stale",
|
||||
"host_110_gitea_cpu_pressure",
|
||||
"host_188_postgres_cpu_pressure",
|
||||
"awooop_conversation_event_hot_path_index_drift_detected",
|
||||
}:
|
||||
category = "host_cpu_pressure"
|
||||
owner_lane = "host_pressure_controller"
|
||||
next_safe_action = (
|
||||
"collect_sanitized_host_pressure_and_apply_allowed_playbook_if_"
|
||||
"verifier_green_no_restart"
|
||||
)
|
||||
post_verifier = "host_pressure_readback_and_reboot_slo_scorecard"
|
||||
evidence_inputs = ["host_pressure"]
|
||||
controlled_apply_mode = "playbook_check_mode_then_controlled_apply_if_low_risk"
|
||||
forbidden_actions.extend(
|
||||
["unmask_legacy_runner_or_restore_generic_labels", "kill_process_without_gate"]
|
||||
)
|
||||
elif blocker == "local_disk_free_below_minimum":
|
||||
category = "host_capacity"
|
||||
owner_lane = "capacity_and_disk_hygiene"
|
||||
next_safe_action = "collect_disk_usage_inventory_and_prepare_cleanup_dry_run"
|
||||
post_verifier = "df_and_capacity_scorecard_readback"
|
||||
evidence_inputs = ["capacity"]
|
||||
controlled_apply_mode = "cleanup_dry_run_then_controlled_apply"
|
||||
|
||||
return {
|
||||
"blocker": blocker,
|
||||
"priority_order": priority_order,
|
||||
"is_primary": is_primary,
|
||||
"category": category,
|
||||
"owner_lane": owner_lane,
|
||||
"telegram_alert": "RebootAutoRecoveryActiveBlocker",
|
||||
"telegram_alert_required": True,
|
||||
"telegram_severity": severity,
|
||||
"metric_name": "awoooi_reboot_auto_recovery_slo_active_blocker",
|
||||
"evidence_inputs": evidence_inputs,
|
||||
"next_safe_action": next_safe_action,
|
||||
"post_verifier": post_verifier,
|
||||
"controlled_apply_mode": controlled_apply_mode,
|
||||
"controlled_apply_authorized_by_scorecard": False,
|
||||
"forbidden_actions": _unique_strings(forbidden_actions),
|
||||
}
|
||||
|
||||
|
||||
def _count_by(items: list[dict[str, Any]], key: str) -> dict[str, int]:
|
||||
counts: dict[str, int] = {}
|
||||
for item in items:
|
||||
value = str(item.get(key) or "unknown")
|
||||
counts[value] = counts.get(value, 0) + 1
|
||||
return counts
|
||||
|
||||
|
||||
def _require_operation_boundaries(payload: dict[str, Any], label: str) -> None:
|
||||
boundaries = _dict(payload.get("operation_boundaries"))
|
||||
forbidden_true = [
|
||||
|
||||
Reference in New Issue
Block a user