Skip to content

Add monitortest for HA policy management - #31449

Draft
nhoriguchi wants to merge 1 commit into
openshift:mainfrom
nhoriguchi:ha_policy_monitortest
Draft

Add monitortest for HA policy management#31449
nhoriguchi wants to merge 1 commit into
openshift:mainfrom
nhoriguchi:ha_policy_monitortest

Conversation

@nhoriguchi

@nhoriguchi nhoriguchi commented Jul 29, 2026

Copy link
Copy Markdown

Enhancement: openshift/enhancements#1932

Summary by CodeRabbit

  • New Features
    • Added a new cluster health monitor for high-availability policy compliance.
    • Checks workloads across applicable namespaces for required container probes, PodDisruptionBudgets, and pod anti-affinity settings.
    • Reports compliance results and groups detected issues by namespace for easier troubleshooting.

Signed-off-by: Naoya Horiguchi <nahorigu@redhat.com>
Signed-off-by: Naoya Horiguchi <naoya-horiguchi@nec.com>
@openshift-merge-bot

Copy link
Copy Markdown
Contributor

Pipeline controller notification
This repo is configured to use the pipeline controller. Second-stage tests will be triggered either automatically or after lgtm label is added, depending on the repository configuration. The pipeline controller will automatically detect which contexts are required and will utilize /test Prow commands to trigger the second stage.

For optional jobs, comment /test ? to see a list of all defined jobs. To trigger manually all jobs from second stage use /pipeline required command.

This repository is configured in: automatic mode

@openshift-ci openshift-ci Bot added the ready-for-human-review Indicates a PR has been reviewed by automated tools and is ready for human review label Jul 29, 2026
@openshift-ci

openshift-ci Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@openshift-ci openshift-ci Bot added the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jul 29, 2026
@openshift-ci

openshift-ci Bot commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: nhoriguchi
Once this PR has been reviewed and has the lgtm label, please assign dgoodwin for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Walkthrough

Adds a universal HA policy management monitor that scans selected namespaces, validates workload probes, PodDisruptionBudgets, and anti-affinity, and reports aggregated namespace results as JUnit test cases.

Changes

HA policy management monitoring

Layer / File(s) Summary
Analyzer wiring and default registration
pkg/monitortests/monitoring/hapolicymanagement/monitortest.go, pkg/defaultmonitortests/types.go
Defines the analyzer lifecycle methods, initializes its Kubernetes client, and registers ha-policy-management-checker in the default universal monitor set.
Workload policy validation
pkg/monitortests/monitoring/hapolicymanagement/monitortest.go
Adds validation for container probes, non-metrics ports, PodDisruptionBudgets, pod anti-affinity, and controller-owned Pods.
Namespace collection and JUnit output
pkg/monitortests/monitoring/hapolicymanagement/monitortest.go
Filters namespaces, collects workloads and policies, evaluates failures, emits namespace-scoped JUnit cases, and stubs remaining lifecycle operations.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MonitorFramework
  participant haPolicyManagementChecker
  participant KubernetesAPI
  participant JUnitOutput
  MonitorFramework->>haPolicyManagementChecker: StartCollection REST config
  haPolicyManagementChecker->>KubernetesAPI: List selected namespaces and workloads
  KubernetesAPI-->>haPolicyManagementChecker: Return resource objects
  haPolicyManagementChecker->>haPolicyManagementChecker: Evaluate probes, PDBs, and anti-affinity
  haPolicyManagementChecker->>JUnitOutput: Emit one test case per namespace
Loading
🚥 Pre-merge checks | ✅ 15
✅ Passed checks (15 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding a new HA policy management monitor test.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Stable And Deterministic Test Names ✅ Passed No Ginkgo It/Describe/Context/When titles were added; the new monitor is registered with a static name.
Test Structure And Quality ✅ Passed PASS: No Ginkgo specs were added; this is a read-only monitor analyzer that matches existing monitor-test patterns and introduces no timeout/cleanup issues.
Microshift Test Compatibility ✅ Passed No Ginkgo e2e tests were added; the new monitor test only uses core Kubernetes APIs and lacks MicroShift-blocked APIs/features.
Single Node Openshift (Sno) Test Compatibility ✅ Passed Not a Ginkgo e2e test; the new monitor test only inspects cluster manifests and has no node-count, topology, scheduling, or SNO-specific assumptions.
Topology-Aware Scheduling Compatibility ✅ Passed Only adds a monitor test and registry entry; no deployment manifests, controllers, or scheduling constraints were changed.
Ote Binary Stdout Contract ✅ Passed No main/init/suite setup or stdout writes were added; the touched files only register the analyzer and use errors/JUnit output.
Ipv6 And Disconnected Network Test Compatibility ✅ Passed PASS: The added monitor test only queries the Kubernetes API and checks workload specs; no Ginkgo e2e test, hardcoded IPv4, or external internet usage found.
No-Weak-Crypto ✅ Passed Changed files only add HA policy monitor logic; no MD5/SHA1/DES/RC4/3DES/Blowfish/ECB, custom crypto, or secret/token comparisons found.
Container-Privileges ✅ Passed The patch only adds a monitor-test Go analyzer and registry entry; no K8s manifests or pod specs introduce privileged/hostNetwork/hostPID/hostIPC/SYS_ADMIN/allowPrivilegeEscalation settings.
No-Sensitive-Data-In-Logs ✅ Passed No new logger calls or sensitive fields were added; output only reports resource names in JUnit failures, not secrets.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/monitortests/monitoring/hapolicymanagement/monitortest.go`:
- Around line 53-71: Correct the boolean argument at all call sites of
checkPodReadinessLivenessProbes and checkPodStartupProbes: pass false for
regular Containers and true for InitContainers so probe validation runs only on
workload containers. Apply this consistently in the Deployment, StatefulSet,
DaemonSet, and Pod loops, preserving the existing skip behavior for init or
sidecar containers.
- Around line 119-135: Update checkPodAntiAffinity so a nil Affinity or
PodAntiAffinity is reported as a missing anti-affinity violation instead of
returning nil. Preserve the existing error message and continue rejecting
configurations where both required and preferred anti-affinity lists are empty.
- Around line 176-259: Handle and report errors from every Kubernetes List call
in the namespace monitoring flow, including PodDisruptionBudgets, Deployments,
StatefulSets, DaemonSets, and Pods. Replace each discarded error with explicit
handling that appends the collection failure to failures and prevents the
corresponding resource checks from treating an unavailable collection as empty;
preserve normal iteration when listing succeeds.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Central YAML (inherited)

Review profile: CHILL

Plan: Pro Plus

Run ID: 728fa0b5-a7a9-4997-adff-cbe6295159a5

📥 Commits

Reviewing files that changed from the base of the PR and between 00b3453 and 5f9e5ce.

📒 Files selected for processing (2)
  • pkg/defaultmonitortests/types.go
  • pkg/monitortests/monitoring/hapolicymanagement/monitortest.go

Comment on lines +53 to +71
func checkPodReadinessLivenessProbes(namespace string, initOrSidecar bool, container corev1.Container) error {
if initOrSidecar {
return nil
}

if container.LivenessProbe == nil {
return fmt.Errorf("container %s in ns/%s should have livenessProbe", container.Name, namespace)
}

if !checkNonMetricsPorts(container) {
return nil
}

if container.ReadinessProbe == nil {
return fmt.Errorf("container %s in ns/%s should have readinessProbe", container.Name, namespace)
}

return nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

initOrSidecar boolean is passed inverted at every call site — probe checks never run on real containers.

checkPodReadinessLivenessProbes and checkPodStartupProbes both skip validation (return nil) when initOrSidecar is true. But every caller passes true for the main Containers loop and false for the InitContainers loop (Deployments: lines 180/183 vs 188/191; StatefulSets: 211/214 vs 219/222; DaemonSets: 242/245 vs 250/253; Pods: 267/270 vs 275/278). This means the checks are skipped for regular application containers — the actual target of this policy — and instead incorrectly enforced on init containers, which typically don't need liveness/readiness probes. The monitor as written will never catch a missing probe on a real workload container.

🐛 Proposed fix (apply to all 8 call sites)
-			for _, container := range dep.Spec.Template.Spec.Containers {
-				if err := checkPodReadinessLivenessProbes(ns.Name, true, container); err != nil {
+			for _, container := range dep.Spec.Template.Spec.Containers {
+				if err := checkPodReadinessLivenessProbes(ns.Name, false, container); err != nil {
 					failures = append(failures, err.Error())
 				}
-				if err := checkPodStartupProbes(ns.Name, true, container); err != nil {
+				if err := checkPodStartupProbes(ns.Name, false, container); err != nil {
 					failures = append(failures, err.Error())
 				}
 			}
 			for _, container := range dep.Spec.Template.Spec.InitContainers {
-				if err := checkPodReadinessLivenessProbes(ns.Name, false, container); err != nil {
+				if err := checkPodReadinessLivenessProbes(ns.Name, true, container); err != nil {
 					failures = append(failures, err.Error())
 				}
-				if err := checkPodStartupProbes(ns.Name, false, container); err != nil {
+				if err := checkPodStartupProbes(ns.Name, true, container); err != nil {
 					failures = append(failures, err.Error())
 				}
 			}

Apply the same swap to the StatefulSets, DaemonSets, and Pods loops.

Also applies to: 73-83, 178-282

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/monitortests/monitoring/hapolicymanagement/monitortest.go` around lines
53 - 71, Correct the boolean argument at all call sites of
checkPodReadinessLivenessProbes and checkPodStartupProbes: pass false for
regular Containers and true for InitContainers so probe validation runs only on
workload containers. Apply this consistently in the Deployment, StatefulSet,
DaemonSet, and Pod loops, preserving the existing skip behavior for init or
sidecar containers.

Comment on lines +119 to +135
func checkPodAntiAffinity(namespace string, name string, podTemplate corev1.PodTemplateSpec) error {
podSpec := podTemplate.Spec

if podSpec.Affinity == nil || podSpec.Affinity.PodAntiAffinity == nil {
return nil
}

antiAffinity := podSpec.Affinity.PodAntiAffinity
hasRequired := len(antiAffinity.RequiredDuringSchedulingIgnoredDuringExecution) > 0
hasPreferred := len(antiAffinity.PreferredDuringSchedulingIgnoredDuringExecution) > 0

if !hasRequired && !hasPreferred {
return fmt.Errorf("workload %s in ns/%s should have podAntiAffinity", name, namespace)
}

return nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

checkPodAntiAffinity logic is inverted — workloads without any anti-affinity pass silently.

If podSpec.Affinity or PodAntiAffinity is nil (the common case for a workload that never configured anti-affinity), the function returns nil (no violation). Only when the struct exists but both RequiredDuringSchedulingIgnoredDuringExecution and PreferredDuringSchedulingIgnoredDuringExecution are empty does it report an error — an unusual, rare configuration. As written, this check will almost never fire for the actual non-compliant case it's meant to detect.

🐛 Proposed fix
 func checkPodAntiAffinity(namespace string, name string, podTemplate corev1.PodTemplateSpec) error {
 	podSpec := podTemplate.Spec
 
-	if podSpec.Affinity == nil || podSpec.Affinity.PodAntiAffinity == nil {
-		return nil
-	}
+	if podSpec.Affinity == nil || podSpec.Affinity.PodAntiAffinity == nil {
+		return fmt.Errorf("workload %s in ns/%s should have podAntiAffinity", name, namespace)
+	}
 
 	antiAffinity := podSpec.Affinity.PodAntiAffinity
 	hasRequired := len(antiAffinity.RequiredDuringSchedulingIgnoredDuringExecution) > 0
 	hasPreferred := len(antiAffinity.PreferredDuringSchedulingIgnoredDuringExecution) > 0
 
 	if !hasRequired && !hasPreferred {
 		return fmt.Errorf("workload %s in ns/%s should have podAntiAffinity", name, namespace)
 	}
 
 	return nil
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func checkPodAntiAffinity(namespace string, name string, podTemplate corev1.PodTemplateSpec) error {
podSpec := podTemplate.Spec
if podSpec.Affinity == nil || podSpec.Affinity.PodAntiAffinity == nil {
return nil
}
antiAffinity := podSpec.Affinity.PodAntiAffinity
hasRequired := len(antiAffinity.RequiredDuringSchedulingIgnoredDuringExecution) > 0
hasPreferred := len(antiAffinity.PreferredDuringSchedulingIgnoredDuringExecution) > 0
if !hasRequired && !hasPreferred {
return fmt.Errorf("workload %s in ns/%s should have podAntiAffinity", name, namespace)
}
return nil
}
func checkPodAntiAffinity(namespace string, name string, podTemplate corev1.PodTemplateSpec) error {
podSpec := podTemplate.Spec
if podSpec.Affinity == nil || podSpec.Affinity.PodAntiAffinity == nil {
return fmt.Errorf("workload %s in ns/%s should have podAntiAffinity", name, namespace)
}
antiAffinity := podSpec.Affinity.PodAntiAffinity
hasRequired := len(antiAffinity.RequiredDuringSchedulingIgnoredDuringExecution) > 0
hasPreferred := len(antiAffinity.PreferredDuringSchedulingIgnoredDuringExecution) > 0
if !hasRequired && !hasPreferred {
return fmt.Errorf("workload %s in ns/%s should have podAntiAffinity", name, namespace)
}
return nil
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/monitortests/monitoring/hapolicymanagement/monitortest.go` around lines
119 - 135, Update checkPodAntiAffinity so a nil Affinity or PodAntiAffinity is
reported as a missing anti-affinity violation instead of returning nil. Preserve
the existing error message and continue rejecting configurations where both
required and preferred anti-affinity lists are empty.

Comment on lines +176 to +259
pdbList, _ := w.kubeClient.PolicyV1().PodDisruptionBudgets(ns.Name).List(ctx, metav1.ListOptions{})
deployments, _ := w.kubeClient.AppsV1().Deployments(ns.Name).List(ctx, metav1.ListOptions{})
for _, dep := range deployments.Items {
for _, container := range dep.Spec.Template.Spec.Containers {
if err := checkPodReadinessLivenessProbes(ns.Name, true, container); err != nil {
failures = append(failures, err.Error())
}
if err := checkPodStartupProbes(ns.Name, true, container); err != nil {
failures = append(failures, err.Error())
}
}
for _, container := range dep.Spec.Template.Spec.InitContainers {
if err := checkPodReadinessLivenessProbes(ns.Name, false, container); err != nil {
failures = append(failures, err.Error())
}
if err := checkPodStartupProbes(ns.Name, false, container); err != nil {
failures = append(failures, err.Error())
}
}

if dep.Spec.Replicas != nil && *dep.Spec.Replicas <= 1 {
continue
}

if err := checkPodDisruptionBudget(ns.Name, dep.Name, dep.Spec.Template, dep.Spec.Selector, pdbList); err != nil {
failures = append(failures, err.Error())
}
if err := checkPodAntiAffinity(ns.Name, dep.Name, dep.Spec.Template); err != nil {
failures = append(failures, err.Error())
}
}

statefulsets, _ := w.kubeClient.AppsV1().StatefulSets(ns.Name).List(ctx, metav1.ListOptions{})
for _, sts := range statefulsets.Items {
for _, container := range sts.Spec.Template.Spec.Containers {
if err := checkPodReadinessLivenessProbes(ns.Name, true, container); err != nil {
failures = append(failures, err.Error())
}
if err := checkPodStartupProbes(ns.Name, true, container); err != nil {
failures = append(failures, err.Error())
}
}
for _, container := range sts.Spec.Template.Spec.InitContainers {
if err := checkPodReadinessLivenessProbes(ns.Name, false, container); err != nil {
failures = append(failures, err.Error())
}
if err := checkPodStartupProbes(ns.Name, false, container); err != nil {
failures = append(failures, err.Error())
}
}

if sts.Spec.Replicas != nil && *sts.Spec.Replicas <= 1 {
continue
}

if err := checkPodDisruptionBudget(ns.Name, sts.Name, sts.Spec.Template, sts.Spec.Selector, pdbList); err != nil {
failures = append(failures, err.Error())
}
if err := checkPodAntiAffinity(ns.Name, sts.Name, sts.Spec.Template); err != nil {
failures = append(failures, err.Error())
}
}

daemonsets, _ := w.kubeClient.AppsV1().DaemonSets(ns.Name).List(ctx, metav1.ListOptions{})
for _, dms := range daemonsets.Items {
for _, container := range dms.Spec.Template.Spec.Containers {
if err := checkPodReadinessLivenessProbes(ns.Name, true, container); err != nil {
failures = append(failures, err.Error())
}
if err := checkPodStartupProbes(ns.Name, true, container); err != nil {
failures = append(failures, err.Error())
}
}
for _, container := range dms.Spec.Template.Spec.InitContainers {
if err := checkPodReadinessLivenessProbes(ns.Name, false, container); err != nil {
failures = append(failures, err.Error())
}
if err := checkPodStartupProbes(ns.Name, false, container); err != nil {
failures = append(failures, err.Error())
}
}
}

pods, _ := w.kubeClient.CoreV1().Pods(ns.Name).List(ctx, metav1.ListOptions{})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Kubernetes API list errors are silently discarded.

PodDisruptionBudgets/Deployments/StatefulSets/DaemonSets/Pods .List(...) calls all discard the error via _ (lines 176, 177, 208, 239, 259). A transient API failure or permission issue will be treated as "no resources found" rather than surfacing a failure, producing a false "compliant" JUnit result for that namespace instead of flagging the collection error.

As per path instructions for **/*.go, "Never ignore error returns."

🛡️ Example fix pattern (repeat for each List call)
-		pdbList, _ := w.kubeClient.PolicyV1().PodDisruptionBudgets(ns.Name).List(ctx, metav1.ListOptions{})
-		deployments, _ := w.kubeClient.AppsV1().Deployments(ns.Name).List(ctx, metav1.ListOptions{})
+		pdbList, err := w.kubeClient.PolicyV1().PodDisruptionBudgets(ns.Name).List(ctx, metav1.ListOptions{})
+		if err != nil {
+			failures = append(failures, fmt.Sprintf("failed to list PodDisruptionBudgets in ns/%s: %v", ns.Name, err))
+		}
+		deployments, err := w.kubeClient.AppsV1().Deployments(ns.Name).List(ctx, metav1.ListOptions{})
+		if err != nil {
+			failures = append(failures, fmt.Sprintf("failed to list Deployments in ns/%s: %v", ns.Name, err))
+		}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/monitortests/monitoring/hapolicymanagement/monitortest.go` around lines
176 - 259, Handle and report errors from every Kubernetes List call in the
namespace monitoring flow, including PodDisruptionBudgets, Deployments,
StatefulSets, DaemonSets, and Pods. Replace each discarded error with explicit
handling that appends the collection failure to failures and prevents the
corresponding resource checks from treating an unavailable collection as empty;
preserve normal iteration when listing succeeds.

Source: Path instructions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. ready-for-human-review Indicates a PR has been reviewed by automated tools and is ready for human review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant