Skip to content

refactor: extract BondBase class and improve DPF validation robustness - #65

Closed
tkarbach wants to merge 1 commit into
RedHatInsights:mainfrom
tkarbach:pr-39
Closed

refactor: extract BondBase class and improve DPF validation robustness#65
tkarbach wants to merge 1 commit into
RedHatInsights:mainfrom
tkarbach:pr-39

Conversation

@tkarbach

@tkarbach tkarbach commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

This PR builds on top of #39 with the following improvements:

  • Extract BondBase shared class for common bond prerequisite checking
  • Improve parsing robustness in DpuBondLacpHealth by splitting sections first
  • Refactor to use file_utils methods instead of direct command execution
  • Update tests to match new implementation

Note: This PR has been updated. All changes from PR #65 have been incorporated into PR #39's commit using --amend, preserving the original author (szigmon) and commit message.

Changes

1. New BondBase class (bond_base.py)

  • Shared prerequisite check for bond interfaces (/proc/net/bonding directory)
  • Both VerifyBondedInterfacesUp and DpuBondLacpHealth now inherit from it
  • Uses file_utils.is_dir_exist() for directory checking

2. Improved DpuBondLacpHealth parsing (dpf_validations.py)

  • More robust parsing: Split by blank lines first, then parse each section independently
    • No state machine - each section is self-contained
    • Malformed sections can't affect others
    • Safer against unexpected kernel output format changes
  • Use file_utils.get_lines_in_file() instead of cat command
  • Use file_utils.list_files() instead of ls command

3. Improved OvnGeneveTunnelLocalIp helpers

  • _get_ovs_show(): Uses get_output_from_run_cmd() with proper exception handling
  • _get_node_ip(): Uses file_utils.read_file() instead of direct cat command

4. Updated tests

  • All test scenarios updated to match new file_utils usage
  • All 134 network tests pass ✅

Testing

pytest tests/rules/network/ -v
# 134 passed, 60 skipped

Assisted-by: Claude Code (Claude Sonnet 4.5) noreply@anthropic.com

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR adds two new network validation rules (DpuBondLacpHealth and OvnGeneveTunnelLocalIp) for DPU deployments, extracts shared bond prerequisite checking into a reusable BondBase class, integrates the validators into the network domain, and provides comprehensive test coverage with parametrized fixtures simulating various bonding LACP and OVN Geneve tunnel states.

Changes

DPF Network Validation Rules

Layer / File(s) Summary
Shared bond prerequisite base class
src/in_cluster_checks/rules/network/bond_base.py
New BondBase class encapsulates /proc/net/bonding directory existence check with met/not-met prerequisite results, enabling reuse across bond-dependent validators.
DPU LACP bond health validator
src/in_cluster_checks/rules/network/dpf_validations.py
Introduces DpuBondLacpHealth rule that parses bond files, validates LACP mode bonds for MII status, sufficient slaves, consistent aggregator IDs, and absence of churn, returning aggregated pass/fail/skip results.
OVN Geneve tunnel local IP validator
src/in_cluster_checks/rules/network/dpf_validations.py
Adds OvnGeneveTunnelLocalIp rule that parses ovs-vsctl show output, extracts Geneve tunnel local IPs, and validates consistency against node primary IP from /run/nodeip-configuration/primary-ip.
Refactor VerifyBondedInterfacesUp
src/in_cluster_checks/rules/network/node_connectivity_validations.py
Updates VerifyBondedInterfacesUp to inherit from BondBase instead of Rule, removing duplicated prerequisite logic and delegating to the shared base class.
Network domain integration
src/in_cluster_checks/domains/network_domain.py
Registers DpuBondLacpHealth and OvnGeneveTunnelLocalIp in domain's get_rule_classes() method; updates docstring to include DPF validation scope.
DPF profile configuration
src/profiles/profiles.yaml
Adds dpf profile for BlueField-3 DPU deployments, configured to include general rules.
Domain test updates
tests/domains/test_network_domain.py
Imports new validators and asserts their presence in domain rule list; updates expected rule count from 16 to 18.
Comprehensive validator tests
tests/rules/network/test_dpf_validations.py
Introduces parametrized test suites for both validators with multi-line command output fixtures covering healthy/degraded LACP scenarios, Geneve tunnel configurations, and validation failures with exact expected messages.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • sprizend-rh
  • sarad-rh
  • hoberger-rh

🐰 In DPU bonds we trust, LACP and Geneve align,
BondBase now shared, the refactor's so fine,
Two validators spring forth with network might,
Tests parametrized to catch every slight.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: extracting a shared BondBase class and enhancing DPF validation robustness with improved parsing and file utility usage.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 and usage tips.

@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 94.04762% with 10 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (main@e985a64). Learn more about missing BASE report.

Files with missing lines Patch % Lines
...in_cluster_checks/rules/network/dpf_validations.py 93.63% 10 Missing ⚠️
Additional details and impacted files
@@           Coverage Diff           @@
##             main      #65   +/-   ##
=======================================
  Coverage        ?   87.64%           
=======================================
  Files           ?       56           
  Lines           ?     6029           
  Branches        ?        0           
=======================================
  Hits            ?     5284           
  Misses          ?      745           
  Partials        ?        0           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
src/in_cluster_checks/rules/network/dpf_validations.py (1)

161-164: 💤 Low value

Remove redundant aggregator ID computation.

The agg_ids set is already computed at line 144 and verified to have consistent values (len == 1 after handling missing_agg and mismatched cases). Re-computing it here on line 161 is unnecessary.

♻️ Simplify by removing redundant computation
         if not down_slaves and not missing_agg and not churned:
-            agg_ids = set(s["aggregator_id"] for s in info["slaves"])
-            if len(agg_ids) == 1:
-                slave_info = ", ".join(f"{s['name']} ({s['speed']})" for s in info["slaves"])
-                all_passed.append(f"{bond_name}: LACP healthy, {len(info['slaves'])} slaves UP ({slave_info})")
+            slave_info = ", ".join(f"{s['name']} ({s['speed']})" for s in info["slaves"])
+            all_passed.append(f"{bond_name}: LACP healthy, {len(info['slaves'])} slaves UP ({slave_info})")

Rationale: By the time we reach this block, we've already filtered out bonds with missing or inconsistent aggregator IDs in lines 138-149, so we know all slaves have the same aggregator_id.

🤖 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 `@src/in_cluster_checks/rules/network/dpf_validations.py` around lines 161 -
164, Remove the redundant recomputation of agg_ids inside the block that builds
slave_info: instead of re-evaluating agg_ids = set(s["aggregator_id"] for s in
info["slaves"]), reuse the previously computed agg_ids variable (which has
already been validated for consistency) and keep the existing conditional check
and message construction using info, slaves, bond_name, and all_passed; delete
the redundant set(...) line so the block simply checks len(agg_ids) == 1 and
builds slave_info as before.
🤖 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 `@src/in_cluster_checks/rules/network/dpf_validations.py`:
- Line 7: Remove the unused "stdout" import from sys at the top of the module to
avoid shadowing the local variable named stdout used later; locate the import
statement "from sys import stdout" and delete it, then run a quick search in the
file for references to stdout to confirm only the local variable (not the
removed import) is used and that no other code relies on the module-level
symbol.
- Around line 194-199: The try/except in _get_ovs_show currently catches all
Exception; change it to catch only UnExpectedSystemOutput (the specific
exception raised by get_output_from_run_cmd) and return None in that except
block so we avoid broad exception swallowing; locate the _get_ovs_show method
and replace "except Exception:" with "except UnExpectedSystemOutput:" and ensure
the UnExpectedSystemOutput symbol is imported or referenced correctly.
- Line 27: The DPF-focused rule class DpuBondLacpHealth currently sets
supported_profiles = {"gpu"}; update the supported_profiles attribute on the
DpuBondLacpHealth class to {"dpf"} so it matches the module intent (same pattern
as OvnGeneveTunnelLocalIp) and ensures the rule applies to the correct profile.

In `@src/profiles/profiles.yaml`:
- Around line 15-16: The dpf profile currently only includes "general" so DPF
validators gated by supported_profiles = {"gpu"} (e.g., DpuBondLacpHealth) stay
disabled; update the profiles YAML so the dpf profile includes "gpu" (either add
gpu to the include list for the dpf profile or ensure a transitive include that
brings in gpu) so that validators using supported_profiles = {"gpu"} are enabled
when active_profile=dpf.

---

Nitpick comments:
In `@src/in_cluster_checks/rules/network/dpf_validations.py`:
- Around line 161-164: Remove the redundant recomputation of agg_ids inside the
block that builds slave_info: instead of re-evaluating agg_ids =
set(s["aggregator_id"] for s in info["slaves"]), reuse the previously computed
agg_ids variable (which has already been validated for consistency) and keep the
existing conditional check and message construction using info, slaves,
bond_name, and all_passed; delete the redundant set(...) line so the block
simply checks len(agg_ids) == 1 and builds slave_info as before.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Enterprise

Run ID: 3d51bcaf-3e06-41b0-8f7c-8c604321f743

📥 Commits

Reviewing files that changed from the base of the PR and between c68c260 and 417aabe.

📒 Files selected for processing (7)
  • src/in_cluster_checks/domains/network_domain.py
  • src/in_cluster_checks/rules/network/bond_base.py
  • src/in_cluster_checks/rules/network/dpf_validations.py
  • src/in_cluster_checks/rules/network/node_connectivity_validations.py
  • src/profiles/profiles.yaml
  • tests/domains/test_network_domain.py
  • tests/rules/network/test_dpf_validations.py

"""

import re
from sys import stdout

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Remove unused import.

The stdout import from sys is unused and also causes a shadowing issue at line 227 where a local variable with the same name is defined.

🔧 Proposed fix
-from sys import stdout
📝 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
from sys import stdout
🧰 Tools
🪛 GitHub Actions: CI / 2_Pre-commit checks.txt

[error] 7-7: flake8 (hook id: flake8) reported unused import: F401 'sys.stdout' imported but unused

🪛 GitHub Actions: CI / 3_Linting.txt

[error] 7-7: flake8: F401 'sys.stdout' imported but unused

🪛 GitHub Actions: CI / Linting

[error] 7-7: flake8: F401 'sys.stdout' imported but unused

🪛 GitHub Actions: CI / Pre-commit checks

[error] 7-7: flake8 (F401): 'sys.stdout' imported but unused

🤖 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 `@src/in_cluster_checks/rules/network/dpf_validations.py` at line 7, Remove the
unused "stdout" import from sys at the top of the module to avoid shadowing the
local variable named stdout used later; locate the import statement "from sys
import stdout" and delete it, then run a quick search in the file for references
to stdout to confirm only the local variable (not the removed import) is used
and that no other code relies on the module-level symbol.

Source: Pipeline failures

"""

objective_hosts = [Objectives.ALL_NODES]
supported_profiles = {"gpu"}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check profile definitions and DPU/DPF related configuration

echo "=== Checking profiles.yaml for dpf/gpu profiles ==="
cat src/profiles/profiles.yaml

echo -e "\n=== Checking other DPU/DPF references in codebase ==="
rg -n -C2 '\bdpf\b|\bdpu\b' --type=yaml

echo -e "\n=== Checking profile usage in both DPF rules ==="
rg -n 'supported_profiles.*=.*\{' src/in_cluster_checks/rules/network/dpf_validations.py

Repository: RedHatInsights/incluster-checks

Length of output: 1139


🏁 Script executed:

#!/bin/bash
# Inspect the rule around supported_profiles={"gpu"} near line 27 and list all supported_profiles in the file.

FILE="src/in_cluster_checks/rules/network/dpf_validations.py"

echo "=== Line 27 context ==="
nl -ba "$FILE" | sed -n '1,120p' | sed -n '20,60p'

echo -e "\n=== All supported_profiles in file ==="
rg -n 'supported_profiles\s*=\s*\{' "$FILE"

Repository: RedHatInsights/incluster-checks

Length of output: 271


🏁 Script executed:

#!/bin/bash
FILE="src/in_cluster_checks/rules/network/dpf_validations.py"

echo "=== Context around line 27 ==="
awk 'NR>=15 && NR<=45 {printf "%d:%s\n", NR, $0}' "$FILE"

echo -e "\n=== Context around line 189 ==="
awk 'NR>=175 && NR<=205 {printf "%d:%s\n", NR, $0}' "$FILE"

Repository: RedHatInsights/incluster-checks

Length of output: 2857


🏁 Script executed:

#!/bin/bash
rg -n 'supported_profiles\s*=\s*\{"gpu"\}' src/in_cluster_checks/rules

Repository: RedHatInsights/incluster-checks

Length of output: 166


🏁 Script executed:

#!/bin/bash
# Show nearby title/unique_name/links context for the GPU-profile rules
rg -n 'supported_profiles\s*=\s*\{"gpu"\}' -n src/in_cluster_checks/rules -C3

Repository: RedHatInsights/incluster-checks

Length of output: 768


🏁 Script executed:

#!/bin/bash
# Check for other DPU-related supported_profiles assignments
rg -n '\bDpu\b|DPU' src/in_cluster_checks/rules/network/dpf_validations.py

Repository: RedHatInsights/incluster-checks

Length of output: 390


🏁 Script executed:

#!/bin/bash
rg -n 'supported_profiles\s*=\s*\{"gpu"\}' src/in_cluster_checks/rules -C2

Repository: RedHatInsights/incluster-checks

Length of output: 536


🏁 Script executed:

#!/bin/bash
FILE="src/in_cluster_checks/rules/network/dpf_validations.py"
echo "=== File header (top ~60 lines) ==="
awk 'NR>=1 && NR<=80 {printf "%d:%s\n", NR, $0}' "$FILE"

Repository: RedHatInsights/incluster-checks

Length of output: 3301


Fix supported_profiles for DPF rule (DpuBondLacpHealth)
In src/in_cluster_checks/rules/network/dpf_validations.py, the module is explicitly DPF-focused, but class DpuBondLacpHealth sets supported_profiles = {"gpu"} while class OvnGeneveTunnelLocalIp correctly uses supported_profiles = {"dpf"}. Change DpuBondLacpHealth.supported_profiles to {"dpf"}.

🧰 Tools
🪛 Ruff (0.15.15)

[warning] 27-27: Mutable default value for class attribute

(RUF012)

🤖 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 `@src/in_cluster_checks/rules/network/dpf_validations.py` at line 27, The
DPF-focused rule class DpuBondLacpHealth currently sets supported_profiles =
{"gpu"}; update the supported_profiles attribute on the DpuBondLacpHealth class
to {"dpf"} so it matches the module intent (same pattern as
OvnGeneveTunnelLocalIp) and ensures the rule applies to the correct profile.

Comment on lines +194 to +199
def _get_ovs_show(self) -> Optional[str]:
"""Run ovs-vsctl show and return stdout, or None on failure."""
try:
return self.get_output_from_run_cmd(SafeCmdString("ovs-vsctl show"))
except Exception:
return None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Narrow exception handling to UnExpectedSystemOutput.

Catching broad Exception violates coding guidelines and the linter warning (BLE001). Since get_output_from_run_cmd raises UnExpectedSystemOutput on command failure, catch that specific exception instead.

♻️ Catch specific exception type
     def _get_ovs_show(self) -> Optional[str]:
         """Run ovs-vsctl show and return stdout, or None on failure."""
         try:
             return self.get_output_from_run_cmd(SafeCmdString("ovs-vsctl show"))
-        except Exception:
+        except UnExpectedSystemOutput:
             return None

As per coding guidelines: "Do NOT catch Exception and return empty/default values." The specific exception type provides better clarity about what's being handled.

🧰 Tools
🪛 Ruff (0.15.15)

[warning] 198-198: Do not catch blind exception: Exception

(BLE001)

🤖 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 `@src/in_cluster_checks/rules/network/dpf_validations.py` around lines 194 -
199, The try/except in _get_ovs_show currently catches all Exception; change it
to catch only UnExpectedSystemOutput (the specific exception raised by
get_output_from_run_cmd) and return None in that except block so we avoid broad
exception swallowing; locate the _get_ovs_show method and replace "except
Exception:" with "except UnExpectedSystemOutput:" and ensure the
UnExpectedSystemOutput symbol is imported or referenced correctly.

Sources: Coding guidelines, Linters/SAST tools

Comment on lines +15 to +16
dpf:
include: [general] # DPF (DPU Platform Framework) deployments with NVIDIA BlueField-3 DPUs

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

dpf profile does not currently enable DPF validators.

dpf only includes general, but DPF rules are gated with supported_profiles = {"gpu"} (for example, DpuBondLacpHealth), so these rules remain disabled when active_profile=dpf. Please include gpu (directly or transitively) in the dpf profile to satisfy the existing rule-gating contract.

Suggested fix
   dpf:
-    include: [general] # DPF (DPU Platform Framework) deployments with NVIDIA BlueField-3 DPUs
+    include: [gpu] # DPF (DPU Platform Framework) deployments with NVIDIA BlueField-3 DPUs
📝 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
dpf:
include: [general] # DPF (DPU Platform Framework) deployments with NVIDIA BlueField-3 DPUs
dpf:
include: [gpu] # DPF (DPU Platform Framework) deployments with NVIDIA BlueField-3 DPUs
🤖 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 `@src/profiles/profiles.yaml` around lines 15 - 16, The dpf profile currently
only includes "general" so DPF validators gated by supported_profiles = {"gpu"}
(e.g., DpuBondLacpHealth) stay disabled; update the profiles YAML so the dpf
profile includes "gpu" (either add gpu to the include list for the dpf profile
or ensure a transitive include that brings in gpu) so that validators using
supported_profiles = {"gpu"} are enabled when active_profile=dpf.

Add two new operational health checks for OpenShift clusters
with NVIDIA BlueField DPUs:

DpuBondLacpHealth:
- Validates 802.3ad (LACP) bond health on DPU ports
- Detects slave interfaces down, slaves in different aggregators,
  LACP churn, and bonds with fewer than 2 active slaves
- A degraded bond silently halves available bandwidth

OvnGeneveTunnelLocalIp:
- Verifies OVN Geneve tunnel local_ip matches the node InternalIP
- Detects stale tunnel IPs after node IP changes (e.g. interface
  migration) which cause silent inter-node connectivity loss

Both rules run on all nodes, skip when not applicable, and include
unit tests covering pass, fail, and prerequisite scenarios.
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.

4 participants