Skip to content

More log analyzer cleanup#155

Merged
openshift-merge-bot[bot] merged 7 commits intoopenshift-assisted:masterfrom
carbonin:cleanup
Nov 18, 2025
Merged

More log analyzer cleanup#155
openshift-merge-bot[bot] merged 7 commits intoopenshift-assisted:masterfrom
carbonin:cleanup

Conversation

@carbonin
Copy link
Copy Markdown
Collaborator

@carbonin carbonin commented Nov 14, 2025

Each commit is pretty self-contained and since this involves some moving stuff around I'd suggest looking at them one at a time.

The most significant changes were splitting the signatures into their own files, and changing the test files to match the source structure.

If this is too much though I can split this into separate PRs.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added expanded diagnostic capabilities to detect certificate expiration, invalid SAN values, pod startup failures, container creation errors, network configuration mismatches, duplicate VIP assignments, DNS/nameserver conflicts, MTU mismatches, image download failures, and host reboot issues in cluster diagnostics logs.
    • Improved error detection and reporting for installation failures with more detailed, structured error messages and warnings.

@carbonin carbonin requested a review from CrystalChun November 14, 2025 21:42
@openshift-ci openshift-ci Bot requested review from keitwb and omertuc November 14, 2025 21:43
@openshift-ci openshift-ci Bot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Nov 14, 2025
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Nov 14, 2025

Walkthrough

This pull request restructures the log analyzer metadata layout from nested cluster-scoped fields to root-level fields, reorganizes signature classes from consolidated modules into individual files (approximately 20 new signature modules), removes deprecated signatures and modules, and adds utility helpers for operator status parsing and hostname extraction. The changes consolidate log analysis patterns and improve modularity.

Changes

Cohort / File(s) Summary
Core metadata restructuring
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py
Refactored metadata handling: moved install_started_at and hosts from nested cluster key to root level; updated all host filtering and event extraction logic; removed get_hostname method; added cluster_is_sno() and all_host_journal_logs() iterator.
Signature module consolidation
assisted_service_mcp/src/utils/log_analyzer/signatures/__init__.py
Replaced wildcard imports with explicit class imports; maintained dynamic ALL_SIGNATURES population via module inspection.
New error/warning signatures
assisted_service_mcp/src/utils/log_analyzer/signatures/{api_expired_certificate,api_invalid_certificate,controller_failed_to_start,controller_warnings,dual_stack_bad_route,dualstackr_dns_bug,duplicate_vip,error_creating_read_write_layer,error_on_cleanup_install_device,events_installation_attempts,failed_request_triggers_host_timeout,machine_config_daemon_error_extracting,missing_mc,nameserver_in_cluster_network,networks_mtu_mismatch,release_pull_error,sno_hostname_has_etcd,sno_machine_cidr,user_has_logged_into_cluster,user_managed_networking_load_balancer}_signature.py
Added 21 new signature detector modules, each implementing error/warning detection from log bundles with pattern matching, metadata validation, and structured result generation.
Updated signature implementations
assisted_service_mcp/src/utils/log_analyzer/signatures/{components_version,libvirt_reboot_flag,slow_image_download}_signature.py
components_version: Updated to read metadata from root level; libvirt_reboot_flag: Extracted to individual file; slow_image_download: Converted _list_image_download_info from classmethod to instance method.
Signature refactoring & removals
assisted_service_mcp/src/utils/log_analyzer/signatures/{container_crash_analysis,ip_changed_after_reboot}.py
container_crash_analysis: Removed 6 advanced analysis signature classes, retained only ContainerCrashAnalysis; ip_changed_after_reboot: Removed LibvirtRebootFlagSignature (moved to separate file).
Deleted consolidated modules
assisted_service_mcp/src/utils/log_analyzer/signatures/{error_detection,networking}.py
Removed entire modules; signatures dispersed into individual files.
New utility helpers
assisted_service_mcp/src/utils/log_analyzer/signatures/helpers.py
Added utility functions: operator_statuses_from_controller_logs, condition_has_result, filter_operators, get_hostname (moved from log_analyzer.py).
Test updates
tests/utils/log_analyzer/signatures/{test_container_crash_analysis,test_slow_image_download_signature}.py, tests/utils/log_analyzer/test_log_analyzer.py, tests/utils/test_auth.py
Updated imports to reflect new signature module paths; adjusted test metadata structure for root-level fields; added reload fixture for auth module testing.

Sequence Diagram(s)

sequenceDiagram
    participant Client as LogAnalyzer User
    participant LogAnalyzer as LogAnalyzer
    participant MetadataRoot as Metadata (Root Level)
    participant HostIter as all_host_journal_logs()
    participant Sig as Signature

    Client->>LogAnalyzer: metadata()
    LogAnalyzer->>MetadataRoot: Read raw_metadata (root level)
    LogAnalyzer->>LogAnalyzer: _clean_metadata_json()
    MetadataRoot->>LogAnalyzer: md["hosts"], md["install_started_at"]
    LogAnalyzer-->>Client: Cleaned metadata

    Client->>LogAnalyzer: all_host_journal_logs()
    LogAnalyzer->>HostIter: Iterate hosts from md["hosts"]
    loop For each host
        HostIter->>LogAnalyzer: get_host_log_file(host_id, "journal.logs")
        alt File exists
            LogAnalyzer-->>HostIter: (host, journal_logs)
        else FileNotFoundError
            HostIter->>HostIter: Skip host
        end
    end
    HostIter-->>Client: Iterator[Tuple[host, logs]]

    Client->>Sig: analyze(log_analyzer)
    Sig->>LogAnalyzer: get_controller_logs() or hosts metadata
    LogAnalyzer-->>Sig: Log content or host data
    Sig->>Sig: Pattern match / validate
    Sig-->>Client: SignatureResult or None
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Metadata restructuring impact: Root-level field migration affects numerous code paths throughout log analysis logic; requires verification that all nested cluster-scoped references are correctly converted.
  • Large file migration/refactoring: ~20 new signature files created alongside removal of consolidated modules; pattern consistency should be validated, and each signature's logic independently reviewed.
  • Import path changes: Widespread import updates across test and source files; verify all references updated consistently.
  • Iterator interface addition: New all_host_journal_logs() method introduces a new abstraction layer; correctness of host iteration and FileNotFoundError handling should be verified.
  • Test coverage gaps: New signature implementations lack comprehensive test coverage in the provided diff.

Possibly related PRs

Suggested labels

lgtm, jira/valid-reference

Suggested reviewers

  • keitwb
  • omertuc
  • CrystalChun

Poem

🐰 Hoppy refactoring, files multiply,
Metadata flat now, no clusters on high!
Signatures splinter to modules so neat,
Twenty new detectors, our log dreams complete!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.28% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'More log analyzer cleanup' is vague and generic. It doesn't convey what specific cleanup was performed or the main change (splitting signatures into individual files, restructuring metadata, etc.). Consider a more specific title that highlights the primary change, such as 'Refactor log analyzer signatures into separate files' or 'Restructure log analyzer metadata and split signatures module'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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.

@openshift-ci openshift-ci Bot added the size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. label Nov 14, 2025
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (16)
assisted_service_mcp/src/utils/log_analyzer/signatures/notes (1)

1-12: Notes file is fine; consider tidying format for clarity

This looks like a pure notes/documentation file. If it’s meant to be human-only (not Python), you might consider:

  • Dropping the leading - so lines are just # ..., or
  • Renaming to notes.md/NOTES.txt to avoid any impression that it’s executable.

Purely cosmetic; no functional issues here.

tests/utils/test_auth.py (1)

3-40: Autouse reload fixture gives clean auth state per test

The reset_auth_module fixture combined with per-test imports is a straightforward way to avoid cross-test leakage from module-level state or patches. The extra reload after each test is defensive but harmless; if test runtime ever becomes a concern, you could drop the post-yield reload and rely on the pre-yield reload alone.

assisted_service_mcp/src/utils/log_analyzer/signatures/libvirt_reboot_flag_signature.py (1)

19-51: Consider hardening inventory parsing and clarifying the host threshold

The core matching logic looks correct for the described MGMT-2840 scenario, but there are a couple of robustness points you might want to tighten:

  • inventory = json.loads(host.get("inventory", "{}")) will raise if inventory is present but malformed JSON. That would abort the entire signature instead of just skipping a bad host. Wrapping this in a small try/except json.JSONDecodeError (and continuing on error) would keep the analysis resilient to partial data issues.
  • The final condition if hosts and len(hosts) + 1 == len(cluster.get("hosts", [])): encodes “all but one host match” implicitly. Given this is quite specific, a brief comment or helper (e.g., all_but_one_hosts_match = ...) would make the intention clearer for future maintainers.
  • Since generate_table is defined in base.py, please confirm it’s exposed on the signature base class (so self.generate_table is valid) rather than only as a free function.

None of these are blockers, but addressing them would make the signature more robust and self-explanatory.

assisted_service_mcp/src/utils/log_analyzer/signatures/error_creating_read_write_layer.py (1)

16-60: ErrorCreatingReadWriteLayer signature mirrors prior behavior and is defensively coded

The signature logic matches the described intent: it traverses must-gather namespaces, safely skips missing pods.yaml files, and only emits a result when it finds pods with CreateContainerError and the specific “error creating read-write layer with ID” message. The broad except Exception around YAML parsing is acceptable here to avoid a single bad namespace breaking the analysis; if you ever need deeper troubleshooting, adding a debug-level log inside that except would be a small quality-of-life improvement.

assisted_service_mcp/src/utils/log_analyzer/signatures/sno_hostname_has_etcd.py (1)

14-38: Logic looks correct; consider case‑insensitive match for robustness

The gating on cluster_is_sno(), single host, and use of get_hostname is appropriate and the error handling pattern is consistent with other signatures. As a small robustness improvement, you could normalize the hostname before checking:

hostname = get_hostname(cluster["hosts"][0])
if "etcd" in hostname.lower():
    ...

This would avoid missing any unexpected casing in hostnames while preserving current behavior.

assisted_service_mcp/src/utils/log_analyzer/signatures/duplicate_vip.py (1)

75-86: Clarify duplicate VIP message when bootstrap is involved

dup_msgs currently say “Found duplicate VIP {vip} in control-plane hosts ...”, but nodes can include "bootstrap" as well as control-plane nodes. To avoid confusing output like “in control-plane hosts bootstrap and 10.0.0.1”, consider slightly more generic wording, e.g.:

f"Found duplicate VIP {vip} in hosts {' and '.join(sorted(nodes))}"

so the message accurately reflects both control-plane and bootstrap participants.

assisted_service_mcp/src/utils/log_analyzer/signatures/missing_mc.py (1)

14-39: MissingMC behavior looks correct; optionally log decode failures

The SNO guard, path selection, and regex for rendered-master-<hash> ... not found all look correct for detecting this condition, and returning None on missing files is reasonable.

If you want easier debugging when archives are malformed, you could optionally log when the decode step fails instead of silently returning None in the except Exception: block so operators can distinguish “no issue” from “could not parse machineconfigpools/master.yaml”.

assisted_service_mcp/src/utils/log_analyzer/signatures/release_pull_error_signature.py (1)

15-34: ReleasePullErrorSignature aligns well with all_host_journal_logs; minor micro‑optimization possible

Iterating all_host_journal_logs() and emitting one line per affected host using get_hostname(host) is a good fit for this signature, and the resulting SignatureResult shape is consistent with other error signatures.

Since you only need to know whether a host’s journal contains at least one failure, you could slightly simplify the condition to use search instead of findall:

if self.ERROR_PATTERN.search(journal_logs):
    ...

Functionally it’s equivalent, just a bit cheaper on large logs.

assisted_service_mcp/src/utils/log_analyzer/signatures/api_expired_certificate_signature.py (1)

21-38: ApiExpiredCertificateSignature implementation looks solid; consider minor robustness tweaks

The overall flow (single log read, FileNotFoundError handling, summarizing duplicate lines) is clear and consistent with other signatures.

Two small nits you might consider (non‑blocking):

  • If logs_archive.get() can return bytes in some contexts, you may want to ensure logs is str (e.g., decode or cast) before running the regex, to avoid accidental type issues.
  • If this log can be very long, you could mirror the ApiInvalidCertificateSignature behavior and cap the number of lines shown (you already summarize duplicates, so this is mostly about being explicit).
assisted_service_mcp/src/utils/log_analyzer/signatures/controller_failed_to_start.py (1)

21-65: Clarify when this signature should fire and tighten a couple of lookups

The structure is good (bootstrap gate, pods.json fetch, ready/conditions tables), but a few details are worth revisiting:

  • The title is “Assisted Installer Controller failed to start”, yet the signature is returned even when ready is True (Line 57). That can be confusing: the content will say “The controller pod is ready” while the title claims a failure. Consider returning a result only when ready is False, or at least adjusting the title based on ready.
  • bootstrap[0]["progress"]["current_stage"] (Line 26) and the controller_pod selection (Lines 35‑39) assume specific keys and at least one matching pod. You’re catching any exception and returning None, which avoids crashes but also hides whether the metadata/JSON shape changed vs. “no controller”. If you want clearer behavior, you could:
    • Use .get("progress", {}).get("current_stage") for a softer check, and
    • Explicitly handle the “no assisted-installer pod found” case instead of relying on [0] raising.

None of these are blockers, but they’d make the signature behavior easier to reason about and less surprising for consumers.

assisted_service_mcp/src/utils/log_analyzer/signatures/sno_machine_cidr_signature.py (1)

18-57: Narrow the exception scope and consider using ErrorSignature

The core logic (SNO gate, first host inventory, default route vs machine CIDR) is sound, but the broad except Exception around the entire method makes it hard to distinguish between “input is slightly malformed” and “we have a real bug in the signature”:

  • A malformed route (e.g., bad IP in route["gateway"]) will cause the whole signature to silently return None instead of just skipping that route.
  • Issues like KeyError on "machine_networks" or "inventory" will also be swallowed, which can mask regressions in metadata shape.

Consider scoping the try/except more tightly (e.g., around the JSON load and the ipaddress conversions) and leaving truly unexpected exceptions to surface.

Also, since this represents an error condition and you manually construct SignatureResult with severity="error", it might be more consistent with the rest of the package to inherit from ErrorSignature and use self.create_result(...) instead of instantiating SignatureResult directly.

assisted_service_mcp/src/utils/log_analyzer/signatures/failed_request_triggers_host_timeout.py (1)

24-59: Logic is correct; a couple of small cleanups could improve readability

The matching between hosts with failed requests and timeout status is implemented cleanly. A few minor polish points you might consider:

  • cluster = log_analyzer.metadata (Line 25) assumes metadata is always a dict; if metadata can ever be None, a quick guard (if not cluster: return None) before .get("hosts", []) would avoid surprises.
  • For the log scan, if self.LOG_PATTERN.search(agent_logs): is slightly simpler and avoids building an intermediate list compared to len(self.LOG_PATTERN.findall(agent_logs)) > 0 (Lines 37‑38).
  • If host entries in metadata might ever lack "id", accessing h["id"] (Lines 28, 34) will raise; if that’s not guaranteed by the data contract, switching to h.get("id") and skipping None IDs would make this more defensive.

Functionally this is fine as-is; these are just small readability/robustness tweaks.

assisted_service_mcp/src/utils/log_analyzer/signatures/dual_stack_bad_route.py (1)

21-36: DualStackBadRoute implementation looks good; only tiny style nits

The signature does what it says: single log read with FileNotFoundError handling and a straightforward regex check, returning a clear SignatureResult on match. This is consistent with the other error signatures.

If you want to polish further (non‑blocking):

  • You could rename fatal_error_regex to FATAL_ERROR_REGEX to emphasize it’s a class-level constant.
  • As with other signatures, ensuring ovnkube_logs is always str (if logs_archive.get can yield bytes for some archives) would make type behavior more explicit.

Otherwise this looks ready.

assisted_service_mcp/src/utils/log_analyzer/signatures/dualstackr_dns_bug.py (1)

20-33: Straightforward detection; consider extracting the magic string

Implementation is clear and consistent with other signatures: one log fetch, FileNotFoundError handling, and a simple substring check to decide whether to emit a warning-level result.

The only minor nit is the hard-coded "must match public address family" string used both in code and content (Lines 26, 30). If this message ever changes upstream, having it as a single class-level constant (e.g., MATCH_TEXT = "must match public address family") would make maintenance a bit easier.

Otherwise, looks good.

assisted_service_mcp/src/utils/log_analyzer/signatures/user_managed_networking_load_balancer.py (1)

23-54: UMN load-balancer detection looks coherent; only minor defensive tweaks

This signature is nicely structured: metadata gating, controller log parsing via helpers, and a clear condition (unhealthy_keys subset of lb_operators) before emitting a warning-level result.

A couple of small, optional tweaks you might consider:

  • If log_analyzer.metadata can ever be None, a quick if not metadata: return None before .get(...) calls (Lines 26, 29) would make the function more defensive.
  • The message for missing operators (Lines 48‑49) is helpful; if you expect this to be used in automated triage, you might also consider including the actual unhealthy_keys set in the content for completeness (e.g., “Unhealthy operators: …”).

Functionally this looks good as-is.

assisted_service_mcp/src/utils/log_analyzer/signatures/nameserver_in_cluster_network.py (1)

23-83: Detection logic is good; tighten nameserver parsing and reuse parsed IPs

The high-level flow (collect nameservers, compare against cluster CIDRs, emit one consolidated error result) is solid. Two small improvements would make it more robust:

  • _get_nameservers uses NAMESERVER_PATTERN = re.compile(r"^nameserver (.*)$") and search (Lines 23, 41, 52). This will capture everything after nameserver , including inline comments or extra whitespace (e.g., nameserver 10.0.0.1 # comment). That causes ipaddress.ip_address(ns) (Line 70) to raise ValueError and skip the entry. Using a stricter pattern such as r"^nameserver\s+(\S+)" and match() would avoid pulling in comments and trailing spaces.
  • In the overlap loop (Lines 67‑76), you call ipaddress.ip_address(ns) for every (ns, cidr) pair. You could parse each nameserver once before the inner loop, skipping invalid entries early, and then reuse the ipaddress object across CIDRs. It’s minor, but simplifies the exception handling and avoids repeated parsing.

These are refinements; the current implementation will still work in the common case where resolv.conf lines are simple nameserver <ip>.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1611ccf and bf62af8.

📒 Files selected for processing (35)
  • assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (4 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/__init__.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/api_expired_certificate_signature.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/api_invalid_certificate_signature.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/components_version_signature.py (2 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/container_crash_analysis.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/controller_failed_to_start.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/controller_warnings.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/dual_stack_bad_route.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/dualstackr_dns_bug.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/duplicate_vip.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/error_creating_read_write_layer.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/error_detection.py (0 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/error_on_cleanup_install_device.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/events_installation_attempts.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/failed_request_triggers_host_timeout.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/helpers.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/ip_changed_after_reboot.py (2 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/libvirt_reboot_flag_signature.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/machine_config_daemon_error_extracting.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/missing_mc.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/nameserver_in_cluster_network.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/networking.py (0 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/networks_mtu_mismatch.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/notes (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/release_pull_error_signature.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/slow_image_download_signature.py (2 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/sno_hostname_has_etcd.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/sno_machine_cidr_signature.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/user_has_logged_into_cluster.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/user_managed_networking_load_balancer.py (1 hunks)
  • tests/utils/log_analyzer/signatures/test_container_crash_analysis.py (2 hunks)
  • tests/utils/log_analyzer/signatures/test_slow_image_download_signature.py (1 hunks)
  • tests/utils/log_analyzer/test_log_analyzer.py (6 hunks)
  • tests/utils/test_auth.py (4 hunks)
💤 Files with no reviewable changes (2)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/error_detection.py
  • assisted_service_mcp/src/utils/log_analyzer/signatures/networking.py
🧰 Additional context used
🧬 Code graph analysis (25)
assisted_service_mcp/src/utils/log_analyzer/signatures/user_has_logged_into_cluster.py (2)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (2)
  • Signature (50-89)
  • SignatureResult (14-47)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (1)
  • all_host_journal_logs (195-218)
assisted_service_mcp/src/utils/log_analyzer/signatures/ip_changed_after_reboot.py (1)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (1)
  • metadata (35-47)
assisted_service_mcp/src/utils/log_analyzer/signatures/sno_machine_cidr_signature.py (2)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (2)
  • Signature (50-89)
  • SignatureResult (14-47)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (2)
  • cluster_is_sno (180-193)
  • metadata (35-47)
assisted_service_mcp/src/utils/log_analyzer/signatures/dualstackr_dns_bug.py (1)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (3)
  • ErrorSignature (92-126)
  • SignatureResult (14-47)
  • create_result (110-126)
assisted_service_mcp/src/utils/log_analyzer/signatures/error_on_cleanup_install_device.py (3)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (4)
  • ErrorSignature (92-126)
  • SignatureResult (14-47)
  • generate_table (70-74)
  • create_result (110-126)
assisted_service_mcp/src/utils/log_analyzer/signatures/helpers.py (1)
  • get_hostname (67-77)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (2)
  • metadata (35-47)
  • get_host_log_file (118-149)
assisted_service_mcp/src/utils/log_analyzer/signatures/machine_config_daemon_error_extracting.py (1)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (2)
  • Signature (50-89)
  • SignatureResult (14-47)
assisted_service_mcp/src/utils/log_analyzer/signatures/libvirt_reboot_flag_signature.py (3)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (4)
  • ErrorSignature (92-126)
  • SignatureResult (14-47)
  • create_result (110-126)
  • generate_table (70-74)
assisted_service_mcp/src/utils/log_analyzer/signatures/helpers.py (1)
  • get_hostname (67-77)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (1)
  • metadata (35-47)
assisted_service_mcp/src/utils/log_analyzer/signatures/api_expired_certificate_signature.py (2)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (3)
  • ErrorSignature (92-126)
  • SignatureResult (14-47)
  • create_result (110-126)
assisted_service_mcp/src/utils/log_analyzer/signatures/api_invalid_certificate_signature.py (1)
  • analyze (21-39)
assisted_service_mcp/src/utils/log_analyzer/signatures/components_version_signature.py (1)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (1)
  • metadata (35-47)
tests/utils/test_auth.py (1)
assisted_service_mcp/utils/auth.py (2)
  • get_offline_token (10-45)
  • get_access_token (48-117)
assisted_service_mcp/src/utils/log_analyzer/signatures/duplicate_vip.py (2)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (4)
  • ErrorSignature (92-126)
  • SignatureResult (14-47)
  • archive_dir_contents (77-89)
  • create_result (110-126)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (1)
  • metadata (35-47)
assisted_service_mcp/src/utils/log_analyzer/signatures/dual_stack_bad_route.py (1)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (3)
  • ErrorSignature (92-126)
  • SignatureResult (14-47)
  • create_result (110-126)
assisted_service_mcp/src/utils/log_analyzer/signatures/controller_warnings.py (2)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (2)
  • Signature (50-89)
  • SignatureResult (14-47)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (1)
  • get_controller_logs (171-178)
assisted_service_mcp/src/utils/log_analyzer/signatures/release_pull_error_signature.py (3)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (3)
  • ErrorSignature (92-126)
  • SignatureResult (14-47)
  • create_result (110-126)
assisted_service_mcp/src/utils/log_analyzer/signatures/helpers.py (1)
  • get_hostname (67-77)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (1)
  • all_host_journal_logs (195-218)
tests/utils/log_analyzer/signatures/test_slow_image_download_signature.py (2)
assisted_service_mcp/src/utils/log_analyzer/signatures/slow_image_download_signature.py (2)
  • SlowImageDownloadSignature (14-54)
  • analyze (22-44)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (1)
  • get_last_install_cluster_events (69-80)
assisted_service_mcp/src/utils/log_analyzer/signatures/nameserver_in_cluster_network.py (2)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (4)
  • ErrorSignature (92-126)
  • SignatureResult (14-47)
  • archive_dir_contents (77-89)
  • create_result (110-126)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (1)
  • metadata (35-47)
assisted_service_mcp/src/utils/log_analyzer/signatures/sno_hostname_has_etcd.py (3)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (3)
  • ErrorSignature (92-126)
  • SignatureResult (14-47)
  • create_result (110-126)
assisted_service_mcp/src/utils/log_analyzer/signatures/helpers.py (1)
  • get_hostname (67-77)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (2)
  • cluster_is_sno (180-193)
  • metadata (35-47)
assisted_service_mcp/src/utils/log_analyzer/signatures/api_invalid_certificate_signature.py (3)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (3)
  • ErrorSignature (92-126)
  • SignatureResult (14-47)
  • create_result (110-126)
assisted_service_mcp/src/utils/log_analyzer/signatures/api_expired_certificate_signature.py (1)
  • analyze (23-39)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (1)
  • get_controller_logs (171-178)
assisted_service_mcp/src/utils/log_analyzer/signatures/events_installation_attempts.py (2)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (2)
  • Signature (50-89)
  • SignatureResult (14-47)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (3)
  • get_all_cluster_events (82-95)
  • partition_cluster_events (98-116)
  • get_last_install_cluster_events (69-80)
assisted_service_mcp/src/utils/log_analyzer/signatures/failed_request_triggers_host_timeout.py (2)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (2)
  • Signature (50-89)
  • SignatureResult (14-47)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (2)
  • metadata (35-47)
  • get_host_log_file (118-149)
assisted_service_mcp/src/utils/log_analyzer/signatures/user_managed_networking_load_balancer.py (3)
assisted_service_mcp/src/utils/log_analyzer/signatures/helpers.py (2)
  • operator_statuses_from_controller_logs (10-37)
  • filter_operators (50-64)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (3)
  • ErrorSignature (92-126)
  • SignatureResult (14-47)
  • create_result (110-126)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (2)
  • metadata (35-47)
  • get_controller_logs (171-178)
assisted_service_mcp/src/utils/log_analyzer/signatures/error_creating_read_write_layer.py (1)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (4)
  • ErrorSignature (92-126)
  • SignatureResult (14-47)
  • archive_dir_contents (77-89)
  • create_result (110-126)
assisted_service_mcp/src/utils/log_analyzer/signatures/controller_failed_to_start.py (2)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (3)
  • Signature (50-89)
  • SignatureResult (14-47)
  • generate_table (70-74)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (1)
  • metadata (35-47)
assisted_service_mcp/src/utils/log_analyzer/signatures/missing_mc.py (2)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (3)
  • ErrorSignature (92-126)
  • SignatureResult (14-47)
  • create_result (110-126)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (1)
  • cluster_is_sno (180-193)
assisted_service_mcp/src/utils/log_analyzer/signatures/networks_mtu_mismatch.py (1)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (3)
  • ErrorSignature (92-126)
  • SignatureResult (14-47)
  • create_result (110-126)
🪛 GitHub Actions: Black
assisted_service_mcp/src/utils/log_analyzer/signatures/helpers.py

[error] 1-1: Black formatting check failed: 1 file would be reformatted.

🪛 GitHub Actions: Python linter
assisted_service_mcp/src/utils/log_analyzer/signatures/helpers.py

[error] 77-77: Pylint: Final newline missing (C0304) in file.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Red Hat Konflux / assisted-service-mcp-saas-main-on-pull-request
🔇 Additional comments (25)
assisted_service_mcp/src/utils/log_analyzer/signatures/components_version_signature.py (1)

16-28: Metadata now assumes top-level release_tag/versions only

The switch to metadata.get("release_tag") / metadata.get("versions") at the root (no ["cluster"] fallback) simplifies things and matches the new LogAnalyzer.metadata comment, but it does change behavior for older bundles that might still nest these under cluster. If backward-compatibility with older archives matters, please confirm that _clean_metadata_json normalizes those cases or consider a small fallback to the nested keys.

tests/utils/test_auth.py (1)

42-132: Directly patching settings attributes improves test clarity—verify get_setting path

Switching the tests to patch assisted_service_mcp.src.settings.settings.OFFLINE_TOKEN / SSO_URL instead of get_setting makes the intent clearer and keeps the auth module logic unmocked. This assumes get_setting("OFFLINE_TOKEN"|"SSO_URL") ultimately reads from those attributes; if get_setting has other resolution or caching behavior, these tests might not fully exercise the intended code path. Worth double-checking that get_setting uses settings.OFFLINE_TOKEN/settings.SSO_URL directly.

assisted_service_mcp/src/utils/log_analyzer/signatures/api_invalid_certificate_signature.py (1)

14-38: ApiInvalidCertificateSignature implementation looks solid

The signature cleanly:

  • Guards missing controller logs with FileNotFoundError.
  • Scans logs via a precompiled pattern and summarizes up to 5 lines plus a “N similar lines” suffix.
  • Uses create_result from ErrorSignature with severity "error".

This fits well with the existing signature conventions.

assisted_service_mcp/src/utils/log_analyzer/signatures/ip_changed_after_reboot.py (2)

1-3: LGTM! Docstring correctly reflects the file's scope.

The updated module docstring now accurately describes that this file contains only the IpChangedAfterReboot signature, since LibvirtRebootFlagSignature was moved to its own module.


46-47: LGTM! Simplified metadata access.

The direct assignment eliminates an unnecessary intermediate variable while maintaining the same functionality.

tests/utils/log_analyzer/signatures/test_container_crash_analysis.py (2)

1-3: LGTM! Docstring accurately reflects the test target.

The updated docstring now specifically references ContainerCrashAnalysis, improving clarity.


13-15: LGTM! Explicit import improves maintainability.

The migration from wildcard imports to explicit module paths improves code clarity and makes dependencies transparent.

assisted_service_mcp/src/utils/log_analyzer/signatures/events_installation_attempts.py (1)

24-39: Verify the last attempt retrieval aligns with partition logic.

Ensure that get_last_install_cluster_events() returns events from the same partition that this signature identifies as the "last attempt" after the fix above.

assisted_service_mcp/src/utils/log_analyzer/signatures/machine_config_daemon_error_extracting.py (1)

1-41: LGTM! Well-structured signature for MCD extraction errors.

The implementation correctly:

  • Uses a glob pattern to locate logs across different hosts
  • Gracefully handles missing log files
  • Returns an appropriate warning-level result when the error pattern is detected
assisted_service_mcp/src/utils/log_analyzer/signatures/slow_image_download_signature.py (2)

1-3: LGTM! Docstring now specifically references the signature.

The updated docstring improves clarity by explicitly naming SlowImageDownloadSignature.


46-54: LGTM! Conversion to instance method with cleaner implementation.

The refactor from classmethod to instance method is appropriate, and the walrus operator makes the list comprehension more concise while maintaining readability.

assisted_service_mcp/src/utils/log_analyzer/signatures/user_has_logged_into_cluster.py (1)

1-35: LGTM! Clean implementation for detecting user logins.

The signature correctly:

  • Uses an appropriate regex pattern to detect PAM session openings
  • Leverages the all_host_journal_logs() iterator to process all hosts
  • Aggregates findings across multiple hosts
  • Returns a warning-level result when evidence is found
assisted_service_mcp/src/utils/log_analyzer/signatures/error_on_cleanup_install_device.py (1)

1-48: LGTM! Well-structured signature for cleanupInstallDevice errors.

The implementation correctly:

  • Detects non-fatal errors with an appropriate regex pattern
  • Iterates over all hosts in the cluster metadata
  • Gracefully handles missing installer logs
  • Uses OrderedDict to maintain consistent table column ordering
  • Provides helpful context about the non-fatal nature of the error
assisted_service_mcp/src/utils/log_analyzer/signatures/networks_mtu_mismatch.py (1)

1-34: LGTM! Effective signature for detecting MTU mismatches.

The implementation correctly:

  • Uses a precise regex pattern to extract both MTU values
  • Employs a glob pattern to locate SDN logs within the must-gather archive
  • Provides a clear error message explaining the mismatch
  • Returns error severity, which is appropriate for this networking configuration issue
tests/utils/log_analyzer/signatures/test_slow_image_download_signature.py (1)

13-231: Strong, thorough coverage for SlowImageDownloadSignature tests

The helper event generator matches the implementation’s regex format, and the suite exercises all key behaviors: normal vs slow downloads, threshold edge cases, absence/malformed events, exception handling, invalid download-rate formats, and multiple slow entries per host, plus simple checks on name and minimum_download_rate_mb. This gives very solid regression protection for the signature with no obvious gaps that need addressing now.

assisted_service_mcp/src/utils/log_analyzer/signatures/controller_warnings.py (1)

14-36: ControllerWarnings implementation is clear and appropriate

The signature cleanly handles missing controller logs, extracts warning lines with a simple regex, and caps output with a “+N additional warnings” summary, which is a good balance between signal and verbosity. Returning a SignatureResult with warning severity only when warnings are present is consistent with the rest of the signatures.

assisted_service_mcp/src/utils/log_analyzer/signatures/duplicate_vip.py (1)

41-67: Double‑check type returned from logs_archive.get(...ip-addr.txt)

This logic assumes that ip_addr and bootstrap_ip_addr are text strings so that vip in ip_addr works correctly. If logs_archive.get() can return bytes for these files, this will either raise a TypeError or silently fail the membership test depending on how it’s used elsewhere.

It’s worth confirming the expected type for these paths and normalizing if needed, e.g.:

ip_addr = log_analyzer.logs_archive.get(...)

if isinstance(ip_addr, bytes):
    ip_addr = ip_addr.decode("utf-8", errors="ignore")

so the substring check is always safe.

assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (3)

43-43: LGTM! Metadata structure flattened to root level.

The metadata handling has been successfully refactored to operate on root-level keys instead of nested cluster keys. This simplifies the data structure and aligns with the PR's cleanup objectives.

Also applies to: 52-52, 59-65


180-193: LGTM! Clear SNO detection with safe defaults.

The method appropriately handles exceptions by returning False as a safe default, which is reasonable for a boolean helper used in signature detection.


195-218: LGTM! Well-structured iterator with proper error handling.

The iterator cleanly handles missing journal logs and metadata errors, using early returns and continue statements appropriately.

tests/utils/log_analyzer/test_log_analyzer.py (2)

25-29: LGTM! Test updated to match new metadata structure.

The test correctly validates the flattened metadata structure with root-level keys, consistent with the changes in log_analyzer.py.

Also applies to: 49-49, 61-61


112-114: LGTM! Imports updated for reorganized signature modules.

The explicit import paths reflect the new module structure where each signature class has its own file, improving code organization and maintainability.

Also applies to: 130-132, 148-150

assisted_service_mcp/src/utils/log_analyzer/signatures/helpers.py (1)

10-77: Helper functions are well-designed with proper error handling.

The helper functions provide clean abstractions for operator status parsing and hostname extraction, with appropriate fallbacks for missing data.

assisted_service_mcp/src/utils/log_analyzer/signatures/container_crash_analysis.py (1)

1-11: LGTM! Module successfully refactored for single responsibility.

The file now focuses solely on ContainerCrashAnalysis, with helper functions moved to helpers.py and other signatures split into their own modules. This improves code organization and maintainability.

assisted_service_mcp/src/utils/log_analyzer/signatures/__init__.py (1)

10-46: LGTM! Excellent refactor from wildcard to explicit imports.

Replacing wildcard imports with explicit imports improves code maintainability, IDE support, and makes dependencies clear. The # noqa: F401 comments are appropriate since these classes are consumed dynamically via inspect.getmembers().

Comment on lines +16 to +23
def analyze(self, log_analyzer) -> Optional[SignatureResult]:
"""Analyze multiple installation attempts."""
try:
# Get all cluster events and partition them by reset events
all_events = log_analyzer.get_all_cluster_events()
partitions = log_analyzer.partition_cluster_events(all_events)
installation_attempts = len(partitions)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical logic bug: get_all_cluster_events() returns only the last partition.

The code calls get_all_cluster_events() expecting to receive all events, but according to the relevant code snippet from log_analyzer.py (lines 81-94), that method returns self._cluster_events = self.partition_cluster_events(all_events)[-1] — only the last partition. Calling partition_cluster_events() on that result will always return a single partition, so installation_attempts will always be 1, and this signature will never detect multiple installation attempts.

Apply this diff to load all events directly:

     def analyze(self, log_analyzer) -> Optional[SignatureResult]:
         """Analyze multiple installation attempts."""
         try:
-            # Get all cluster events and partition them by reset events
-            all_events = log_analyzer.get_all_cluster_events()
-            partitions = log_analyzer.partition_cluster_events(all_events)
+            # Load all events directly from the archive (not just last partition)
+            import json
+            from typing import cast
+            events_content = log_analyzer.logs_archive.get("cluster_events.json")
+            all_events = json.loads(cast(str | bytes, events_content))
+            
+            # Partition events by reset events to count installation attempts
+            partitions = log_analyzer.partition_cluster_events(all_events)
             installation_attempts = len(partitions)
📝 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
def analyze(self, log_analyzer) -> Optional[SignatureResult]:
"""Analyze multiple installation attempts."""
try:
# Get all cluster events and partition them by reset events
all_events = log_analyzer.get_all_cluster_events()
partitions = log_analyzer.partition_cluster_events(all_events)
installation_attempts = len(partitions)
def analyze(self, log_analyzer) -> Optional[SignatureResult]:
"""Analyze multiple installation attempts."""
try:
# Load all events directly from the archive (not just last partition)
import json
from typing import cast
events_content = log_analyzer.logs_archive.get("cluster_events.json")
all_events = json.loads(cast(str | bytes, events_content))
# Partition events by reset events to count installation attempts
partitions = log_analyzer.partition_cluster_events(all_events)
installation_attempts = len(partitions)
🤖 Prompt for AI Agents
In
assisted_service_mcp/src/utils/log_analyzer/signatures/events_installation_attempts.py
around lines 16 to 23, replace the call to log_analyzer.get_all_cluster_events()
(which returns only the last partition) with a call that loads the full event
list (e.g., log_analyzer.load_all_cluster_events() or the equivalent method that
returns all events), then pass that full event list into
log_analyzer.partition_cluster_events(...) so installation_attempts reflects the
true number of partitions; ensure the variable names remain consistent and
handle None/empty results as before.

Comment on lines +1 to +77
"""
Helper functions for signature analysis.
"""

import json
import re
from typing import Any, Generator, Callable, Dict


def operator_statuses_from_controller_logs(
controller_log: str, include_empty: bool = False
):
operator_regex = re.compile(r"Operator ([a-z\-]+), statuses: \[(.*)\].*")
conditions_regex = re.compile(r"\{(.+?)\}")
condition_regex = re.compile(
r"([A-Za-z]+) (False|True) ([0-9a-zA-Z\-]+ [0-9a-zA-Z\:]+ [0-9a-zA-Z\-\+]+ [A-Z]+) (.*)"
)
operator_statuses = {}

for operator_name, operator_status in operator_regex.findall(controller_log):
if include_empty:
operator_statuses[operator_name] = {}
operator_conditions = operator_statuses.setdefault(operator_name, {})
for operator_conditions_raw in conditions_regex.findall(operator_status):
for (
condition_name,
condition_result,
condition_timestamp,
condition_reason,
) in condition_regex.findall(operator_conditions_raw):
operator_conditions[condition_name] = {
"result": condition_result == "True",
"timestamp": condition_timestamp,
"reason": condition_reason,
}

return operator_statuses


def condition_has_result(
operator_conditions, expected_condition_name: str, expected_condition_result: bool
) -> bool:
return any(
condition_values["result"] == expected_condition_result
for condition_name, condition_values in operator_conditions.items()
if condition_name == expected_condition_name
)


def filter_operators(
operator_statuses,
required_conditions,
aggregation_function: Callable[[Generator[Any, None, None]], bool],
):
return {
operator_name: operator_conditions
for operator_name, operator_conditions in operator_statuses.items()
if aggregation_function(
condition_has_result(
operator_conditions, required_condition_name, expected_condition_result
)
for required_condition_name, expected_condition_result in required_conditions
)
}


def get_hostname(host: Dict[str, Any]) -> str:
"""Extract hostname from host metadata."""
hostname = host.get("requested_hostname")
if hostname:
return hostname

try:
inventory = json.loads(host["inventory"])
return inventory["hostname"]
except (KeyError, json.JSONDecodeError):
return host.get("id", "unknown") No newline at end of file
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Fix formatting and add final newline.

The pipeline is failing due to Black formatting issues and a missing final newline. These must be resolved before merging.

Run Black to reformat the file and ensure it ends with a newline:

#!/bin/bash
# Reformat the file with Black
black assisted_service_mcp/src/utils/log_analyzer/signatures/helpers.py
🧰 Tools
🪛 GitHub Actions: Black

[error] 1-1: Black formatting check failed: 1 file would be reformatted.

🪛 GitHub Actions: Python linter

[error] 77-77: Pylint: Final newline missing (C0304) in file.

🤖 Prompt for AI Agents
In assisted_service_mcp/src/utils/log_analyzer/signatures/helpers.py around
lines 1 to 77, the file fails CI due to Black formatting and a missing final
newline; run Black (e.g., black
assisted_service_mcp/src/utils/log_analyzer/signatures/helpers.py) to reformat
the file and then ensure the file ends with a single trailing newline (save
after formatting) before committing.

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.

Is this file meant to be added to this PR?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

lol nope. Maybe I should split this into smaller chunks 😅

This will make it easier to add new signatures and will make it more
clear where to find helper functions if/when those are needed.
This was added as an easy way to maintain compatibility with older
signatures, but it just adds complexity and can be removed
Adds helpers for checking if a cluster is SNO and for iterating through
all the hosts journal logs. These resolve some pylint duplicate code
errors.
Since it doesn't use and isn't used by the analyzer class it's a better
match for the utils file
Match the source directory structure in tests
This makes things like test_helpers.py clear when we have multiple
helpers.py files in the project
After the move a change in import order caused this test to start
failing. This is because the settings are a global concept and end up
shared across tests. This should be fixed more generally, but for now
just isolate the import of auth in the auth tests to address the
test-specific problem.
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
assisted_service_mcp/src/utils/log_analyzer/signatures/events_installation_attempts.py (1)

16-25: Logic bug: using get_all_cluster_events() means installation_attempts is always 1

log_analyzer.get_all_cluster_events() currently returns only the last partition of events, not the full event list. Calling partition_cluster_events(all_events) on that result will always give a single partition, so installation_attempts will always be 1 and this signature will never flag multiple attempts. This is the same issue noted in earlier review feedback.

To base the count on the real number of attempts, load the full event list from cluster_events.json and partition that, then derive the last attempt from those partitions instead of calling get_all_cluster_events():

-    def analyze(self, log_analyzer) -> Optional[SignatureResult]:
-        """Analyze multiple installation attempts."""
-        try:
-            # Get all cluster events and partition them by reset events
-            all_events = log_analyzer.get_all_cluster_events()
-            partitions = log_analyzer.partition_cluster_events(all_events)
-            installation_attempts = len(partitions)
+    def analyze(self, log_analyzer) -> Optional[SignatureResult]:
+        """Analyze multiple installation attempts."""
+        try:
+            # Load all cluster events and partition them by reset events
+            import json
+
+            events_content = log_analyzer.logs_archive.get("cluster_events.json")
+            all_events = json.loads(events_content)
+            partitions = log_analyzer.partition_cluster_events(all_events)
+            installation_attempts = len(partitions)
@@
-            if installation_attempts != 1:
-                current_events = log_analyzer.get_last_install_cluster_events()
+            if installation_attempts != 1:
+                current_events = partitions[-1] if partitions else []
assisted_service_mcp/src/utils/log_analyzer/signatures/helpers.py (1)

1-77: Address the formatting issues flagged in previous review.

The Black formatting and missing final newline issues identified in the previous review must be resolved before merging.

Based on past review comments.

🧹 Nitpick comments (10)
assisted_service_mcp/src/utils/log_analyzer/signatures/nameserver_in_cluster_network.py (1)

11-13: Use relative import for consistency.

The absolute import path from assisted_service_mcp.src.utils.log_analyzer.log_analyzer import should be a relative import for consistency with other signature files in this module.

Apply this diff:

-from assisted_service_mcp.src.utils.log_analyzer.log_analyzer import (
-    LOG_BUNDLE_PATH,
-)
+from ...log_analyzer import LOG_BUNDLE_PATH

Or simply:

-from assisted_service_mcp.src.utils.log_analyzer.log_analyzer import (
-    LOG_BUNDLE_PATH,
-)
+from ..log_analyzer import LOG_BUNDLE_PATH
assisted_service_mcp/src/utils/log_analyzer/signatures/dualstackr_dns_bug.py (1)

8-10: Use relative import for consistency.

The absolute import path should be a relative import to match the pattern used in other signature files.

Apply this diff:

-from assisted_service_mcp.src.utils.log_analyzer.log_analyzer import (
-    LOG_BUNDLE_PATH,
-)
+from ...log_analyzer import LOG_BUNDLE_PATH
assisted_service_mcp/src/utils/log_analyzer/signatures/error_on_cleanup_install_device.py (1)

21-27: Consider defensively handling missing metadata before iterating hosts

cluster = log_analyzer.metadata is assumed to be a mapping, and cluster.get("hosts", []) will fail if metadata is ever None or otherwise not a dict (e.g., if _clean_metadata_json returns None). You could early‑return when metadata is falsy to avoid noisy exceptions from this signature.

     def analyze(self, log_analyzer) -> Optional[SignatureResult]:
-        cluster = log_analyzer.metadata
+        cluster = log_analyzer.metadata
+        if not cluster:
+            return None
         hosts = []
         for host in cluster.get("hosts", []):
assisted_service_mcp/src/utils/log_analyzer/signatures/sno_hostname_has_etcd.py (1)

17-33: Title wording and host access could be clarified

This fires when "etcd" is present in the SNO hostname, but the title reads "No etcd in SNO hostname", which can be confusing when rendered in reports. Also, cluster["hosts"] will raise if metadata is unexpectedly None or missing hosts, even though you already treat such cases as non-fatal.

You could consider:

-            cluster = log_analyzer.metadata
-            if len(cluster["hosts"]) != 1:
+            cluster = log_analyzer.metadata
+            if not cluster or len(cluster.get("hosts", [])) != 1:
                 return None
@@
-                return self.create_result(
-                    title="No etcd in SNO hostname", content=content, severity="error"
-                )
+                return self.create_result(
+                    title="Etcd detected in SNO hostname",
+                    content=content,
+                    severity="error",
+                )
assisted_service_mcp/src/utils/log_analyzer/signatures/api_expired_certificate_signature.py (1)

18-38: LGTM: consistent with other certificate signatures and handles missing logs safely

The path, regex, and “first line plus count of additional similar lines” behavior mirror ApiInvalidCertificateSignature and correctly guard on missing kube-apiserver.log. If you ever find logs_archive.get returning bytes here, a small decode()/str() normalization step before findall() would make the function more defensive, but it’s not strictly required given current usage.

assisted_service_mcp/src/utils/log_analyzer/signatures/missing_mc.py (1)

17-38: Decode error handling could be more informative and narrowly scoped

The SNO guard, path, and regex are fine, but the broad except Exception: around raw.decode("utf-8") silently drops the signature on any decoding issue (including programmer errors like raw not being bytes). That can make troubleshooting harder if the bundle format changes.

You could narrow and log the failure:

-        try:
-            text = raw.decode("utf-8")
-        except Exception:
-            return None
+        try:
+            text = raw.decode("utf-8")
+        except (AttributeError, UnicodeDecodeError) as e:
+            logger.warning("Unable to decode MachineConfigPool yaml as UTF-8: %s", e)
+            return None
assisted_service_mcp/src/utils/log_analyzer/signatures/duplicate_vip.py (1)

22-87: Duplicate VIP detection logic looks correct; message text could be slightly clearer

The flow (skip SNO/UMN, gather VIPs from api_vips, scan control-plane and bootstrap ip-addr.txt, then aggregate by VIP) is sound and handles missing directories/files gracefully via FileNotFoundError and archive_dir_contents. Using a set per VIP also avoids duplicate node entries.

One small nit: when bootstrap participates in a collision, the message in control-plane hosts ... can be slightly misleading since "bootstrap" is included in the host list. If you care about wording, you could relax that phrasing to “in nodes …” or similar, but the behavior is otherwise solid.

assisted_service_mcp/src/utils/log_analyzer/signatures/error_creating_read_write_layer.py (1)

51-52: Overly broad exception handling masks potential issues.

The bare except Exception silently continues on any parsing or processing error. While resilience is valuable, this hides bugs and makes debugging difficult. Consider logging the exception before continuing, or narrowing to specific expected exceptions (e.g., yaml.YAMLError).

Apply this diff to log exceptions before continuing:

-            except Exception:
-                continue
+            except Exception as e:
+                logger.debug("Failed to parse pods.yaml in namespace %s: %s", namespace_dir, e)
+                continue
assisted_service_mcp/src/utils/log_analyzer/signatures/failed_request_triggers_host_timeout.py (1)

37-38: Simplify pattern matching check.

Using len(self.LOG_PATTERN.findall(agent_logs)) > 0 is less efficient and less idiomatic than self.LOG_PATTERN.search(agent_logs). The search method stops at the first match, while findall scans the entire log.

Apply this diff:

-            if len(self.LOG_PATTERN.findall(agent_logs)) > 0:
+            if self.LOG_PATTERN.search(agent_logs):
                 failed_requests_hosts.add(host["id"])
assisted_service_mcp/src/utils/log_analyzer/signatures/user_managed_networking_load_balancer.py (1)

8-13: Use consistent import style.

Lines 8-11 use absolute imports while line 13 uses relative imports. For consistency and maintainability, use relative imports throughout the module.

Apply this diff:

-from assisted_service_mcp.src.utils.log_analyzer.signatures.helpers import (
-    operator_statuses_from_controller_logs,
-    filter_operators,
-)
-
 from .base import ErrorSignature, SignatureResult
+from .helpers import operator_statuses_from_controller_logs, filter_operators
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bf62af8 and b0ccb8c.

📒 Files selected for processing (34)
  • assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (4 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/__init__.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/api_expired_certificate_signature.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/api_invalid_certificate_signature.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/components_version_signature.py (2 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/container_crash_analysis.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/controller_failed_to_start.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/controller_warnings.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/dual_stack_bad_route.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/dualstackr_dns_bug.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/duplicate_vip.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/error_creating_read_write_layer.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/error_detection.py (0 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/error_on_cleanup_install_device.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/events_installation_attempts.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/failed_request_triggers_host_timeout.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/helpers.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/ip_changed_after_reboot.py (2 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/libvirt_reboot_flag_signature.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/machine_config_daemon_error_extracting.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/missing_mc.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/nameserver_in_cluster_network.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/networking.py (0 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/networks_mtu_mismatch.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/release_pull_error_signature.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/slow_image_download_signature.py (2 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/sno_hostname_has_etcd.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/sno_machine_cidr_signature.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/user_has_logged_into_cluster.py (1 hunks)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/user_managed_networking_load_balancer.py (1 hunks)
  • tests/utils/log_analyzer/signatures/test_container_crash_analysis.py (2 hunks)
  • tests/utils/log_analyzer/signatures/test_slow_image_download_signature.py (1 hunks)
  • tests/utils/log_analyzer/test_log_analyzer.py (6 hunks)
  • tests/utils/test_auth.py (4 hunks)
💤 Files with no reviewable changes (2)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/error_detection.py
  • assisted_service_mcp/src/utils/log_analyzer/signatures/networking.py
🚧 Files skipped from review as they are similar to previous changes (8)
  • assisted_service_mcp/src/utils/log_analyzer/signatures/machine_config_daemon_error_extracting.py
  • tests/utils/log_analyzer/signatures/test_slow_image_download_signature.py
  • assisted_service_mcp/src/utils/log_analyzer/signatures/controller_failed_to_start.py
  • assisted_service_mcp/src/utils/log_analyzer/signatures/dual_stack_bad_route.py
  • assisted_service_mcp/src/utils/log_analyzer/signatures/networks_mtu_mismatch.py
  • assisted_service_mcp/src/utils/log_analyzer/signatures/sno_machine_cidr_signature.py
  • assisted_service_mcp/src/utils/log_analyzer/signatures/controller_warnings.py
  • tests/utils/test_auth.py
🧰 Additional context used
🧬 Code graph analysis (17)
assisted_service_mcp/src/utils/log_analyzer/signatures/failed_request_triggers_host_timeout.py (3)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (2)
  • Signature (50-89)
  • SignatureResult (14-47)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (2)
  • metadata (35-47)
  • get_host_log_file (118-149)
assisted_service_mcp/src/utils/log_analyzer/signatures/advanced_analysis.py (1)
  • FailedRequestTriggersHostTimeout (171-217)
assisted_service_mcp/src/utils/log_analyzer/signatures/error_on_cleanup_install_device.py (4)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (4)
  • ErrorSignature (92-126)
  • SignatureResult (14-47)
  • generate_table (70-74)
  • create_result (110-126)
assisted_service_mcp/src/utils/log_analyzer/signatures/helpers.py (1)
  • get_hostname (67-77)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (2)
  • metadata (35-47)
  • get_host_log_file (118-149)
assisted_service_mcp/src/utils/log_analyzer/signatures/error_detection.py (1)
  • ErrorOnCleanupInstallDevice (137-169)
assisted_service_mcp/src/utils/log_analyzer/signatures/libvirt_reboot_flag_signature.py (3)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (4)
  • ErrorSignature (92-126)
  • SignatureResult (14-47)
  • create_result (110-126)
  • generate_table (70-74)
assisted_service_mcp/src/utils/log_analyzer/signatures/helpers.py (1)
  • get_hostname (67-77)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (1)
  • metadata (35-47)
assisted_service_mcp/src/utils/log_analyzer/signatures/missing_mc.py (2)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (3)
  • ErrorSignature (92-126)
  • SignatureResult (14-47)
  • create_result (110-126)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (1)
  • cluster_is_sno (180-193)
assisted_service_mcp/src/utils/log_analyzer/signatures/ip_changed_after_reboot.py (1)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (1)
  • metadata (35-47)
assisted_service_mcp/src/utils/log_analyzer/signatures/release_pull_error_signature.py (3)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (3)
  • ErrorSignature (92-126)
  • SignatureResult (14-47)
  • create_result (110-126)
assisted_service_mcp/src/utils/log_analyzer/signatures/helpers.py (1)
  • get_hostname (67-77)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (1)
  • all_host_journal_logs (195-218)
assisted_service_mcp/src/utils/log_analyzer/signatures/dualstackr_dns_bug.py (1)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (3)
  • ErrorSignature (92-126)
  • SignatureResult (14-47)
  • create_result (110-126)
assisted_service_mcp/src/utils/log_analyzer/signatures/duplicate_vip.py (2)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (4)
  • ErrorSignature (92-126)
  • SignatureResult (14-47)
  • archive_dir_contents (77-89)
  • create_result (110-126)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (1)
  • metadata (35-47)
assisted_service_mcp/src/utils/log_analyzer/signatures/error_creating_read_write_layer.py (1)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (4)
  • ErrorSignature (92-126)
  • SignatureResult (14-47)
  • archive_dir_contents (77-89)
  • create_result (110-126)
assisted_service_mcp/src/utils/log_analyzer/signatures/api_invalid_certificate_signature.py (3)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (3)
  • ErrorSignature (92-126)
  • SignatureResult (14-47)
  • create_result (110-126)
assisted_service_mcp/src/utils/log_analyzer/signatures/api_expired_certificate_signature.py (1)
  • analyze (23-39)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (1)
  • get_controller_logs (171-178)
assisted_service_mcp/src/utils/log_analyzer/signatures/components_version_signature.py (1)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (1)
  • metadata (35-47)
assisted_service_mcp/src/utils/log_analyzer/signatures/api_expired_certificate_signature.py (2)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (3)
  • ErrorSignature (92-126)
  • SignatureResult (14-47)
  • create_result (110-126)
assisted_service_mcp/src/utils/log_analyzer/signatures/api_invalid_certificate_signature.py (1)
  • analyze (21-39)
assisted_service_mcp/src/utils/log_analyzer/signatures/user_managed_networking_load_balancer.py (3)
assisted_service_mcp/src/utils/log_analyzer/signatures/helpers.py (2)
  • operator_statuses_from_controller_logs (10-37)
  • filter_operators (50-64)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (3)
  • ErrorSignature (92-126)
  • SignatureResult (14-47)
  • create_result (110-126)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (2)
  • metadata (35-47)
  • get_controller_logs (171-178)
assisted_service_mcp/src/utils/log_analyzer/signatures/sno_hostname_has_etcd.py (3)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (3)
  • ErrorSignature (92-126)
  • SignatureResult (14-47)
  • create_result (110-126)
assisted_service_mcp/src/utils/log_analyzer/signatures/helpers.py (1)
  • get_hostname (67-77)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (2)
  • cluster_is_sno (180-193)
  • metadata (35-47)
assisted_service_mcp/src/utils/log_analyzer/signatures/events_installation_attempts.py (2)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (2)
  • Signature (50-89)
  • SignatureResult (14-47)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (3)
  • get_all_cluster_events (82-95)
  • partition_cluster_events (98-116)
  • get_last_install_cluster_events (69-80)
assisted_service_mcp/src/utils/log_analyzer/signatures/user_has_logged_into_cluster.py (2)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (2)
  • Signature (50-89)
  • SignatureResult (14-47)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (1)
  • all_host_journal_logs (195-218)
assisted_service_mcp/src/utils/log_analyzer/signatures/nameserver_in_cluster_network.py (2)
assisted_service_mcp/src/utils/log_analyzer/signatures/base.py (4)
  • ErrorSignature (92-126)
  • SignatureResult (14-47)
  • archive_dir_contents (77-89)
  • create_result (110-126)
assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (1)
  • metadata (35-47)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Red Hat Konflux / assisted-service-mcp-saas-main-on-pull-request
🔇 Additional comments (16)
assisted_service_mcp/src/utils/log_analyzer/signatures/ip_changed_after_reboot.py (1)

47-47: LGTM! Metadata access simplified.

The direct access to log_analyzer.metadata aligns with the refactored metadata structure that now exposes cluster information at the root level.

assisted_service_mcp/src/utils/log_analyzer/signatures/slow_image_download_signature.py (1)

46-54: LGTM! Clean refactor to instance method.

The conversion from classmethod to instance method and the use of the walrus operator in the list comprehension makes the code more concise while maintaining readability.

assisted_service_mcp/src/utils/log_analyzer/signatures/api_invalid_certificate_signature.py (1)

27-38: LGTM! Good error handling and output limiting.

The implementation correctly handles missing files and limits output to the first 5 matching lines to prevent overwhelming results, consistent with similar signatures in the codebase.

assisted_service_mcp/src/utils/log_analyzer/signatures/libvirt_reboot_flag_signature.py (1)

19-52: LGTM! Logic is sound for detecting the libvirt reboot flag issue.

The signature correctly:

  • Skips SNO clusters (not relevant)
  • Filters hosts with specific conditions (1 disk, KVM, Rebooting stage, error status)
  • Excludes bootstrap nodes
  • Returns a warning only when all-but-one host is affected
assisted_service_mcp/src/utils/log_analyzer/signatures/nameserver_in_cluster_network.py (2)

25-57: LGTM! Robust error handling for missing resolv.conf files.

The method correctly handles missing files at both control-plane and bootstrap levels, accumulating nameservers across all nodes.


59-84: LGTM! Correct use of strict=False for CIDR validation.

The strict=False parameter in ip_network() is appropriate here since we're checking if an IP belongs to a network, not validating CIDR notation strictness.

assisted_service_mcp/src/utils/log_analyzer/signatures/dualstackr_dns_bug.py (1)

20-34: LGTM! Simple and effective detection.

The substring search for "must match public address family" is straightforward and appropriate for detecting this specific issue.

assisted_service_mcp/src/utils/log_analyzer/signatures/components_version_signature.py (1)

23-27: LGTM! Correctly updated for root-level metadata access.

The changes align with the metadata refactor where cluster information is now at the root level rather than nested under a "cluster" key.

tests/utils/log_analyzer/signatures/test_container_crash_analysis.py (2)

2-2: LGTM! Docstring updated for clarity.

The docstring now specifically references the ContainerCrashAnalysis signature rather than the generic "advanced analysis" context.


13-15: LGTM! Import path correctly updated.

The import path reflects the reorganization where ContainerCrashAnalysis is now in its own dedicated module rather than the broader advanced_analysis module.

assisted_service_mcp/src/utils/log_analyzer/signatures/user_has_logged_into_cluster.py (1)

14-34: LGTM: signature cleanly reuses all_host_journal_logs and emits concise warnings

The logic is straightforward, resilient to missing logs via all_host_journal_logs(), and the per‑host messaging is clear. No changes needed.

tests/utils/log_analyzer/test_log_analyzer.py (1)

25-29: Tests correctly reflect new metadata layout and signature locations

The updated md fixture, deleted_hosts assertion, and the imports for ComponentsVersionSignature, SNOHostnameHasEtcd, and SNOMachineCidrSignature all match the new root-level metadata schema and per-module signature structure. The tests exercise the updated paths without over-constraining behavior.

Also applies to: 49-49, 61-62, 112-115, 130-143, 148-160

assisted_service_mcp/src/utils/log_analyzer/signatures/release_pull_error_signature.py (1)

15-34: LGTM!

The signature correctly identifies release image pull failures by scanning journal logs and produces clear error messages with host identification.

assisted_service_mcp/src/utils/log_analyzer/log_analyzer.py (1)

180-218: LGTM!

The new helper methods are well-designed:

  • cluster_is_sno has appropriate exception handling
  • all_host_journal_logs elegantly handles missing log files with the iterator pattern

The metadata refactoring to root-level fields simplifies access patterns.

assisted_service_mcp/src/utils/log_analyzer/signatures/container_crash_analysis.py (1)

2-2: LGTM!

The updated module docstring and import adjustments improve clarity without changing functionality.

Also applies to: 11-11

assisted_service_mcp/src/utils/log_analyzer/signatures/__init__.py (1)

10-46: LGTM!

Replacing wildcard imports with explicit imports improves code clarity and enables better static analysis while maintaining the dynamic signature discovery mechanism.

Comment on lines +17 to +19
LOG_PATTERN = re.compile(
'time=".*" level=error msg=".*x509: certificate is valid.* not .*'
)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

Verify the regex pattern is complete.

The LOG_PATTERN regex appears to be missing a closing quote or pattern marker. The pattern ends with 'time=".*" level=error msg=".*x509: certificate is valid.* not .*' but typically x509 certificate error messages have more structure.

Please verify this pattern matches the actual log format. Compare with similar patterns in the codebase (e.g., ApiExpiredCertificateSignature) to ensure consistency.


🏁 Script executed:

#!/bin/bash
# Search for similar x509 certificate error patterns in signature files
rg -n -A2 -B2 'x509.*certificate' --type=py -g 'signatures/*.py'

Length of output: 148



🏁 Script executed:

#!/bin/bash
# Find the target file and similar signature files
fd 'signature' -e py -p 'log_analyzer' 2>/dev/null | head -20

Length of output: 0


🏁 Script executed:

#!/bin/bash
# Also search more broadly for files containing 'x509' in the codebase
rg -l 'x509' --type=py

Length of output: 183



🏁 Script executed:

#!/bin/bash
# Read the target file
cat assisted_service_mcp/src/utils/log_analyzer/signatures/api_invalid_certificate_signature.py

Length of output: 1243


🏁 Script executed:

#!/bin/bash
# Read the comparison file for similar pattern
cat assisted_service_mcp/src/utils/log_analyzer/signatures/api_expired_certificate_signature.py

Length of output: 1307


The regex pattern is missing a closing quote for the message field.

The pattern 'time=".*" level=error msg=".*x509: certificate is valid.* not .*' opens the message field with msg=" but never closes it. While the Python string syntax is valid, the regex pattern itself is structurally incomplete. It should include a closing quote to properly match the log structure:

LOG_PATTERN = re.compile(
    'time=".*" level=error msg=".*x509: certificate is valid.* not .*"'
)

Compare this to api_expired_certificate_signature.py which uses a simpler pattern; the invalid certificate pattern requires closing the message field quote to properly validate against actual log output.

🤖 Prompt for AI Agents
In
assisted_service_mcp/src/utils/log_analyzer/signatures/api_invalid_certificate_signature.py
around lines 17 to 19, the regex pattern opens the msg=" field but never closes
it; update the LOG_PATTERN string to include the closing double-quote after the
final .* (i.e. end the regex with ... not .*") so the pattern matches the full
msg="..."; ensure the string quoting in Python remains correct (escape if
needed) and run tests to verify it matches the intended log lines.

Copy link
Copy Markdown
Contributor

@CrystalChun CrystalChun left a comment

Choose a reason for hiding this comment

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

/lgtm

@openshift-ci openshift-ci Bot added the lgtm Indicates that a PR is ready to be merged. label Nov 18, 2025
@openshift-ci
Copy link
Copy Markdown

openshift-ci Bot commented Nov 18, 2025

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: carbonin, CrystalChun

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

The pull request process is described 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

@openshift-merge-bot openshift-merge-bot Bot merged commit e6e0924 into openshift-assisted:master Nov 18, 2025
14 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved Indicates a PR has been approved by an approver from all required OWNERS files. lgtm Indicates that a PR is ready to be merged. size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants