Skip to content

feat(kubevirt): add vm_troubleshoot tool for automated VM diagnostics#1239

Open
sradco wants to merge 2 commits into
containers:mainfrom
sradco:feat/kubevirt-vm-troubleshoot-tool
Open

feat(kubevirt): add vm_troubleshoot tool for automated VM diagnostics#1239
sradco wants to merge 2 commits into
containers:mainfrom
sradco:feat/kubevirt-vm-troubleshoot-tool

Conversation

@sradco

@sradco sradco commented Jun 29, 2026

Copy link
Copy Markdown

Summary

  • Adds a new vm_troubleshoot MCP Tool that collects comprehensive diagnostic data for KubeVirt VirtualMachines (VM/VMI status, DataVolume/PVC state, cloud-init config, virt-launcher pod state, pod logs, and events)
  • Includes automated heuristic issue detection that produces pre-digested "Detected Issues" and "Suggested Fixes" sections, enabling small LLMs (e.g. gpt-4o-mini) to relay actionable findings without synthesizing root causes from raw YAML
  • Complements the existing vm-troubleshoot Prompt with a proper Tool that LLMs can invoke automatically based on user intent
  • Includes targeted event collection (no namespace-wide scan), sensitive field redaction for cloud-init, and correct NotFound-only PVC fallback logic

Motivation

The existing vm-troubleshoot is implemented as a Prompt, which requires explicit user selection and cannot be auto-invoked by LLMs during troubleshooting conversations. This PR adds a parallel Tool implementation that:

  1. Enables proactive invocation — LLMs can call it automatically when users describe VM issues
  2. Enriches diagnostics — adds DataVolume/PVC status (with StorageClass) and cloud-init content extraction
  3. Improves small-model performance — heuristic checks pre-analyze collected data and surface root causes directly, so even small models produce accurate answers
  4. Addresses security — redacts sensitive cloud-init fields (passwords, SSH keys, tokens) while preserving diagnostic content (runcmd, packages, hostname)
  5. Improves performance — uses targeted field selectors for events; extracts only diagnostic-relevant pod fields (phase, conditions, restartCount, nodeSelector) instead of full pod YAML
  6. Handles edge cases — multi-line block scalar redaction, NotFound-only PVC fallback, float64-safe numeric conversion for real cluster data

Automated Issue Detection

The tool runs heuristic checks on collected diagnostic data and produces a structured report:

## Detected Issues

- **CRITICAL**: StorageClass "fast-ssd" referenced by DataVolume "rootdisk" does not exist on this cluster. Available StorageClasses: ocs-storagecluster-ceph-rbd (default).
- **WARNING**: VM is stuck in Provisioning state.

## Suggested Fixes

1. Change the DataVolume storageClassName from "fast-ssd" to an existing StorageClass (e.g., ocs-storagecluster-ceph-rbd (default)).

Heuristic checks implemented:

  • Missing StorageClass detection (lists available alternatives)
  • DataVolume/PVC errors with StorageClass deduplication
  • Dangerous cloud-init commands (shutdown, poweroff, halt, reboot, systemctl variants, init 0/6)
  • Hostname nodeSelector migration blockers
  • Pod crashloops and OOMKill detection
  • Failed VirtualMachineInstanceMigration detection
  • VM condition analysis (Ready=False reasons, printableStatus)

Prompt vs. Tool coexistence

The existing vm-troubleshoot Prompt and this new vm_troubleshoot Tool serve different purposes:

  • Prompt: Provides a guided step-by-step troubleshooting workflow template. Must be explicitly selected by the user or UI. Useful for structured walkthroughs.
  • Tool: Returns diagnostic data with automated analysis. Can be auto-invoked by LLMs when users ask questions like "why is my VM not starting?" without requiring explicit tool selection.

Both are kept because they serve different interaction patterns. The Tool enables the agentic/autonomous troubleshooting use case that the Prompt cannot support.

Testing

Tested manually against 3 broken VM scenarios on OpenShift 4.22:

Scenario Root cause Tool provides
VM stuck Provisioning Missing StorageClass + no accessModes CRITICAL issue with available SC list + fix suggestion
VM crashloop (30-60s restarts) cloud-init runcmd: shutdown -h now CRITICAL cloud-init detection + crashloop explanation
Live migration failure nodeSelector pinning VM to single node WARNING nodeSelector + failed migration detection

Tested with both gpt-4o-mini (troubleshooting mode) and gpt-5.2 (simple questions) — tool is auto-invoked and produces accurate root-cause identification. Small models correctly relay the pre-analyzed findings without needing to interpret raw YAML.

Key Design Decisions

  • float64-safe numeric conversion: Kubernetes dynamic client deserializes JSON numbers as float64; the toInt64() helper ensures restartCount detection works on real clusters (not just in tests using int64 directly)
  • False-positive prevention in cloud-init: Skips comments, echo/print lines, and YAML config keys to avoid flagging innocuous text containing "shutdown" or "halt"
  • Deduplication: When a missing StorageClass is detected, downstream PVC "not found" errors are suppressed to avoid reporting symptoms alongside root causes
  • API limiting: Migration list capped at 50 objects to avoid unbounded queries in busy namespaces

Related

Test plan

  • Unit tests for all helper functions (fetchVMStatus, fetchVMIStatus, fetchVolumes, fetchDataVolumeStatus, extractCloudInit, fetchVirtLauncherPod, fetchVirtLauncherPodLogs)
  • Unit tests for all heuristic check functions (checkVMConditions, checkStorageClass, checkDataVolumeErrors, checkCloudInitCommands, checkNodeSelector, checkPodHealth, checkMigrationStatus)
  • Cloud-init redaction tests (inline values, SSH keys, block scalars, false-positive resistance, token fields)
  • Cloud-init dangerous command detection tests (shutdown, halt, poweroff, systemctl variants, array format, false-positive resistance)
  • Pod diagnostics extraction test (verifies env vars, images, volumeMounts are excluded)
  • float64 restartCount test (simulates real cluster JSON deserialization)
  • Tool registration, schema, and annotation validation tests
  • Snapshot test updated
  • go build ./... and go test pass locally

Signed-off-by: Shirly Radco sradco@redhat.com
Co-authored-by: AI Assistant noreply@cursor.com

@sradco
sradco force-pushed the feat/kubevirt-vm-troubleshoot-tool branch 4 times, most recently from 2ec5a01 to 79443db Compare July 2, 2026 12:31
@sradco
sradco requested review from Cali0707 and manusa as code owners July 2, 2026 12:31
@github-actions

github-actions Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

👋 Heads up — this pull request changes files owned by @ksimon1 @lyarwood.

You are listed as an owner of one or more of the changed areas in .github/CODEOWNERS. GitHub cannot auto-request review from owners without write access, so this comment is the notification instead. A review when you have a moment would be appreciated 🙏

Comment thread pkg/toolsets/kubevirt/vm/troubleshoot/tool.go Outdated
Comment thread pkg/toolsets/kubevirt/vm/troubleshoot/analysis.go
Comment thread pkg/toolsets/kubevirt/vm/troubleshoot/tool.go
Comment thread pkg/toolsets/kubevirt/vm/troubleshoot/tool.go Outdated
Comment thread pkg/toolsets/kubevirt/vm/troubleshoot/analysis.go Outdated

@lyarwood lyarwood 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.

Thanks @sradco, I'll review in more detail tomorrow but initially it looks promising. We do however mandate that each new tool is accompanied by one or preferably more evals under https://github.com/containers/kubernetes-mcp-server/tree/main/evals/tasks/kubevirt in seperate commits that both show the need for this tooling and exercise it in a way that can be asserted over time to ensure correctness. Would you mind having a look into these next week, happy to help get you up and running if you need help.

@sradco
sradco force-pushed the feat/kubevirt-vm-troubleshoot-tool branch 3 times, most recently from cf06c60 to 5aa46d0 Compare July 5, 2026 09:26
@sradco

sradco commented Jul 5, 2026

Copy link
Copy Markdown
Author

Thanks @sradco, I'll review in more detail tomorrow but initially it looks promising. We do however mandate that each new tool is accompanied by one or preferably more evals under https://github.com/containers/kubernetes-mcp-server/tree/main/evals/tasks/kubevirt in seperate commits that both show the need for this tooling and exercise it in a way that can be asserted over time to ensure correctness. Would you mind having a look into these next week, happy to help get you up and running if you need help.

Added 2 evals

}

var issues []issue
for _, vmim := range vmimList.Items {

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.

Would it make sense to filter out old migrations (e.g. older than 2 days)?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Instead of a hard time cutoff (which is somewhat arbitrary), I now report only the 3 most recent failed migrations sorted by creation time, with age included in the message (e.g., "failed (3h ago)").
If more exist, an INFO note says how many older ones were skipped. This keeps output focused without losing potentially relevant failures.

@sradco
sradco force-pushed the feat/kubevirt-vm-troubleshoot-tool branch from 5aa46d0 to 7e0d49b Compare July 7, 2026 12:47

@lyarwood lyarwood 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.

Review of the vm_troubleshoot tool implementation. All prior review comments from @ksimon1 have been addressed in the current code. Two eval tasks added as requested. Overall the code is solid — main concern is maintainability around prompt/tool duplication.

Summary: The heuristic analysis architecture is well-designed, cloud-init redaction is thorough, and the float64-safe numeric handling shows good attention to real-cluster behavior. See inline comments for specific suggestions.

@@ -0,0 +1,559 @@
package troubleshoot

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.

Suggestion: Extract shared data-fetching logic with the existing prompt handler

This file reimplements fetchVMStatus, fetchVMIStatus, fetchVolumes, fetchVirtLauncherPod, fetchVirtLauncherPodLogs, and fetchEvents — all of which have near-identical versions in pkg/toolsets/kubevirt/vm_troubleshoot.go (the prompt handler).

The tool versions are enhanced (DataVolume/PVC status, cloud-init extraction with redaction, pod diagnostics summary, targeted events), but the base fetching logic is the same. As these evolve independently, bug fixes in one will need manual replication in the other.

Consider extracting the shared data-fetching logic into a common internal package (e.g., pkg/toolsets/kubevirt/internal/diagnostics/) that both the prompt and tool import, with the tool adding its analysis layer on top. This could be a follow-up PR if you'd prefer to keep this one focused.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

+1. Agreed — the duplication is a maintenance concern. The tool versions are enhanced (DataVolume/PVC status, cloud-init extraction with redaction, pod diagnostics summary, targeted events per object), but the base fetching logic is shared.

I'll open a follow-up PR to extract the common data-fetching logic into pkg/toolsets/kubevirt/internal/diagnostics/ that both the prompt and tool import, with the tool adding its analysis layer on top. This keeps the current PR focused on the new tool functionality.

Handler: troubleshoot,
},
}
}

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.

Suggestion: Add an MCP-level integration test

pkg/mcp/kubevirt_test.go tests the vm-troubleshoot prompt at the MCP layer but doesn't have a CallTool("vm_troubleshoot", ...) test for the new tool. An MCP-level test would exercise the full handler path (parameter extraction via the MCP server, tool registration, report assembly) and match the testing pattern used by other kubevirt tools in that file.

The unit tests here are thorough for the internal logic, but an integration test would catch wiring issues (e.g., if the tool handler signature or ToolHandlerParams fields change upstream).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

+1. Added a TestVMTroubleshoot MCP-level integration test in pkg/mcp/kubevirt_test.go that exercises the full handler path through CallTool("vm_troubleshoot", ...). It covers:

  1. Missing required params — verifies namespace and name are required (matches the pattern used by other kubevirt tools)
  2. Existing VM — creates a VM with a containerDisk volume, calls the tool, verifies the diagnostic report header, section structure, and VM name/namespace presence
  3. Non-existent VM — verifies the tool handles gracefully (returns report with error info, no IsError)

This catches wiring issues like ToolHandlerParams field changes or parameter extraction regressions.

return nil
}

vmimList, err := dynamicClient.Resource(kubevirt.VirtualMachineInstanceMigrationGVR).Namespace(namespace).List(ctx, metav1.ListOptions{

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.

Suggestion: Consider label-based filtering for migrations

This lists up to 50 migrations namespace-wide and filters by vmiName client-side. In namespaces with heavy migration activity, the relevant migration could fall outside the first 50 results.

If KubeVirt's VMIM resources carry a label like kubevirt.io/migration-target-vmi=<name> (or similar), a LabelSelector would be more efficient and avoid the cap concern. Worth checking the KubeVirt API — if no such label exists, the current approach with Limit: 50 is a reasonable pragmatic bound.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Correction to my earlier reply — I was wrong. The kubevirt.io/vmi-name label (MigrationSelectorLabel) is still present on VMIM resources. PR #17382 proposed removing it but was closed without merging.

I'll switch checkMigrationStatus to use a LabelSelector with kubevirt.io/vmi-name=<name> for server-side filtering. This removes the Limit: 50 cap concern entirely and is more efficient.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

fixed

@sradco
sradco force-pushed the feat/kubevirt-vm-troubleshoot-tool branch 3 times, most recently from 926062d to 41a66ee Compare July 8, 2026 18:55
fi
echo "✓ VirtualMachine crashloop-vm still exists"
exit 0
- llmJudge:

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.

nit: contains: "shutdown" is a fairly weak check — the agent could pass by saying something generic like "the VM appears to shut down" without actually diagnosing the cloud-init cause.

Consider tightening to contains: "shutdown -h now" or adding a second contains for "cloud-init" to confirm the agent identified the specific command and its source.

For comparison, the missing-storageclass task's contains: "non-existent-sc-xyz" is much stronger since it requires the exact StorageClass name.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

fixed

- k8s.delete:
apiVersion: v1
kind: Namespace
metadata:

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.

The prompt explicitly names the tool to use (Use the vm_troubleshoot tool). This is an anti-pattern for evals — it tests whether the agent can follow an instruction rather than whether it can select the right tool for the job.

Consider rephrasing to present the problem and let the agent figure out which tools to use, e.g.:

A VirtualMachine named "crashloop-vm" in the vm-test-troubleshoot-ci namespace keeps restarting.
Diagnose why this VM is crashlooping. Report:
- The root cause of the crashloop
- Which cloud-init command is causing the problem
- How to fix it

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

fixed

contains: "non-existent-sc-xyz"
cleanup:
- k8s.delete:
apiVersion: kubevirt.io/v1

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.

Same comment as the cloud-init task — the prompt explicitly names vm_troubleshoot which makes the eval test instruction-following rather than tool selection. Consider removing the tool name and letting the agent discover the right tool on its own, e.g.:

A VirtualMachine named "broken-sc-vm" in the vm-test-troubleshoot-sc namespace is not starting.
Diagnose why this VM is stuck. Report:
- The root cause of the issue
- What StorageClass is missing
- What alternative StorageClasses are available on the cluster

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

fixed

@lyarwood lyarwood 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.

Review: feat(kubevirt): add vm_troubleshoot tool for automated VM diagnostics

Summary

This PR adds a well-designed vm_troubleshoot MCP tool with heuristic analysis for automated VM diagnostics, plus two eval tasks. The core implementation (analysis engine, cloud-init redaction, diagnostic data collection) is solid. All prior review feedback from @ksimon1 has been addressed, the MCP-level integration test was added as requested, and the migration label selector fix is in place.

Positive Observations

  • Heuristic analysis architecture is clean — analyzeIssues orchestrates individual checkers that each return typed issue structs with severity + fix suggestions. Easy to extend with new checks.
  • Cloud-init redaction is thorough — handles inline values, block scalars, SSH key patterns, and token fields while preserving diagnostic content.
  • float64-safe numeric conversion via toInt64() shows good attention to real-cluster behavior where JSON numbers deserialize as float64.
  • Deduplication between StorageClass and DataVolume errors avoids reporting symptoms alongside root causes.
  • Pod diagnostics extraction strips env vars, images, and volumeMounts to avoid leaking sensitive data.
  • Test coverage is comprehensive — unit tests for all heuristic checks, redaction, false-positive resistance, and an MCP integration test.

Issues

See inline comments. Summary:

  1. isConfigKeyLine false-negative (analysis.go:349) — YAML keys without underscores or hyphens (e.g. hostname: shutdown) are not recognized as config keys, which could cause a false positive in dangerous command detection.
  2. matchDangerousCommand over-broad prefix match (analysis.go:371) — shutdown -v or shutdown --help would match as dangerous when they're not power-off commands.
  3. Eval prompts name the tool explicitly (already commented) — anti-pattern for evals.
  4. Eval llmJudge contains: "shutdown" is weak (already commented) — should be tightened.

Existing Discussion Summary

  • @ksimon1's feedback (error handling, array format parsing, multi-pod diagnostics, migration filtering) — all addressed in current code.
  • @lyarwood's suggestions (shared data-fetching extraction, MCP integration test, label-based migration filtering) — integration test added, migration label selector fixed, data-fetching extraction deferred to follow-up PR.

Verdict

Close to ready. The two eval issues (already commented) and the isConfigKeyLine false-negative are the main items to address. The shutdown - prefix match is minor.

if strings.Contains(lower, ":") && !strings.HasPrefix(lower, "-") && !strings.HasPrefix(lower, "[") {
parts := strings.SplitN(lower, ":", 2)
key := strings.TrimSpace(parts[0])
if strings.Contains(key, "_") || strings.Contains(key, "-") {

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.

Bug: isConfigKeyLine misses YAML keys without underscores or hyphens

This function only returns true if the key part of a YAML line contains _ or -. Keys like hostname, packages, or bootcmd would not be recognized, so a line like:

hostname: shutdown

would fall through to matchDangerousCommand and trigger a false positive (the stripped value shutdown matches exactly).

The intent is to skip lines that are YAML key-value pairs (not shell commands), but the current heuristic is too narrow. Consider using a broader check — e.g., if the line has the form word: value and doesn't start with - , it's likely a config key regardless of whether the key name contains _ or -.

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.

@sradco this is not fixed yet

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

+1. Fixed by broadening isConfigKeyLine to recognize any single-word YAML key (not just those containing _ or -). The check now treats any line matching the pattern word: value (that does not start with - or [) as a config key. This handles hostname: shutdown, packages: [halt], bootcmd: [], etc. without false positives.

Added hostname: shutdown and packages: [halt] to the existing false-positive test to cover this case.

switch {
case stripped == "shutdown" || stripped == "shutdown -h now" ||
stripped == "/sbin/shutdown" || strings.HasPrefix(stripped, "/sbin/shutdown ") ||
strings.HasPrefix(stripped, "shutdown -"):

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.

nit: strings.HasPrefix(stripped, "shutdown -") is quite broad — it matches shutdown -v, shutdown --help, shutdown -c (cancel a pending shutdown), etc. Only -h, -P, and -r (and bare shutdown) are actually dangerous power-off/reboot variants.

Not a high-priority fix since false positives here are relatively harmless (they'd surface a warning about a non-dangerous shutdown invocation), but worth noting for future refinement.

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.

@sradco this is not fixed yet

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

+1. Tightened the shutdown flag matching to only match actually dangerous flags: -h (halt), -P (poweroff), and -r (reboot). Non-dangerous flags like -v, --help, and -c (cancel) no longer trigger a warning.

Added a test confirming shutdown -c does not produce a false positive.

@sradco
sradco force-pushed the feat/kubevirt-vm-troubleshoot-tool branch 2 times, most recently from f7e1e76 to c2ae268 Compare July 13, 2026 09:55
@ksimon1

ksimon1 commented Jul 13, 2026

Copy link
Copy Markdown
Contributor

/lgtm

@Cali0707

Copy link
Copy Markdown
Collaborator

/run-mcpchecker kubevirt

@Cali0707

Copy link
Copy Markdown
Collaborator

@sradco can you run make update-readme-tools

@sradco
sradco force-pushed the feat/kubevirt-vm-troubleshoot-tool branch from c2ae268 to 329c386 Compare July 13, 2026 14:58
@sradco

sradco commented Jul 13, 2026

Copy link
Copy Markdown
Author

@Cali0707, @lyarwood, @ksimon1 I run make update-readme-tools, PR is ready.

@lyarwood lyarwood 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.

All review feedback has been addressed:

  • isConfigKeyLine broadened to recognize any single-word YAML key — fixes the hostname: shutdown false positive
  • matchDangerousCommand tightened to only match -h, -P, -r flags — shutdown -c no longer triggers
  • Eval llmJudge tightened from contains: "shutdown" to contains: "shutdown -h now"
  • Eval prompts no longer name vm_troubleshoot — both present the problem and let the agent choose the tool
  • New test cases added for both fixes

LGTM — nice work on the heuristic analysis engine and the thorough test coverage.

@ksimon1

ksimon1 commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

/lgtm

@sradco

sradco commented Jul 15, 2026

Copy link
Copy Markdown
Author

@Cali0707 Hi, can we merge this pr?

@Cali0707 Cali0707 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM

@Cali0707

Copy link
Copy Markdown
Collaborator

/run-mcpchecker kubevirt

@sradco
sradco force-pushed the feat/kubevirt-vm-troubleshoot-tool branch from 329c386 to 254606c Compare July 16, 2026 12:21
@sradco

sradco commented Jul 16, 2026

Copy link
Copy Markdown
Author

/run-mcpchecker kubevirt

@sradco

sradco commented Jul 16, 2026

Copy link
Copy Markdown
Author

The code needed a rebase.

sradco and others added 2 commits July 16, 2026 18:27
automated issue detection for small-model performance

Adds a new vm_troubleshoot MCP Tool that collects
comprehensive diagnostic data for KubeVirt VMs and
runs heuristic checks to produce pre-digested
"Detected Issues" and "Suggested Fixes" sections.
This enables small LLMs (e.g. gpt-4o-mini) to relay
actionable findings without synthesizing root causes
from raw YAML.

Diagnostic data collected:
- VM/VMI status and conditions
- DataVolume/PVC state with StorageClass info
- Cloud-init configuration (sensitive fields redacted)
- virt-launcher pod state and logs
- Related events (field-selector targeted)

Heuristic checks implemented:
- Missing StorageClass with available SC list
- DataVolume/PVC errors with SC deduplication
- Dangerous cloud-init commands (shutdown, poweroff,
  halt, reboot, systemctl variants, init 0/6)
- Hostname nodeSelector migration blockers
- Pod crashloops and OOMKill detection
- Failed VirtualMachineInstanceMigration detection
- VM condition analysis (Ready=False reasons)

Key design decisions:
- float64-safe numeric conversion for real cluster
  data (K8s JSON deserializes numbers as float64)
- False-positive prevention in cloud-init parsing
- Deduplication between StorageClass and DV errors
- API call limiting (Limit: 50) for migrations

Signed-off-by: Shirly Radco <sradco@redhat.com>
Co-authored-by: AI Assistant <noreply@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
missing StorageClass and cloud-init crashloop

Adds two eval tasks that exercise the vm_troubleshoot
tool's heuristic issue detection:

1. troubleshoot-vm-missing-storageclass: Creates a VM
   referencing a non-existent StorageClass. Verifies
   the agent calls vm_troubleshoot and reports the
   CRITICAL missing SC finding with alternatives.

2. troubleshoot-vm-cloudinit-shutdown: Creates a VM
   with "shutdown -h now" in cloud-init runcmd that
   causes CrashLoopBackOff. Verifies the agent
   identifies the dangerous command as root cause.

Both scenarios are deterministic and non-flaky on any
cluster with KubeVirt installed.

Signed-off-by: Shirly Radco <sradco@redhat.com>
Co-authored-by: AI Assistant <noreply@cursor.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@sradco
sradco force-pushed the feat/kubevirt-vm-troubleshoot-tool branch from 254606c to 36ea603 Compare July 16, 2026 15:27
@sradco

sradco commented Jul 16, 2026

Copy link
Copy Markdown
Author

/run-mcpchecker kubevirt

if isConfigKeyLine(trimmed) {
continue
}
if cmd := matchDangerousCommand(trimmed); cmd != "" {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Do we want to only surface the first match, or is it worth accumulating all dangerous matches?

stripped == "/sbin/shutdown" || strings.HasPrefix(stripped, "/sbin/shutdown ") ||
strings.HasPrefix(stripped, "shutdown -h") ||
strings.HasPrefix(stripped, "shutdown -P") || strings.HasPrefix(stripped, "shutdown -p") ||
strings.HasPrefix(stripped, "shutdown -r"):

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Isn't shutdown -r a reboot?

ctx := params.Context
if ctx == nil {
ctx = context.Background()
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

no need for this nil check - this should be reliably populated by the core of the mcp server

Comment on lines +27 to +65
func (s *TroubleshootToolSuite) TestToolRegistration() {
s.Run("tool is registered", func() {
tools := Tools(&fakeProvider{})
s.Require().Len(tools, 1, "Expected 1 troubleshoot tool")
s.Equal("vm_troubleshoot", tools[0].Tool.Name)
s.Equal("Virtual Machine: Troubleshoot", tools[0].Tool.Annotations.Title)
s.NotNil(tools[0].Tool.InputSchema)
s.NotNil(tools[0].Handler)
})

s.Run("tool has correct annotations", func() {
tools := Tools(&fakeProvider{})
tool := tools[0].Tool

s.True(*tool.Annotations.ReadOnlyHint, "troubleshoot should be read-only")
s.False(*tool.Annotations.DestructiveHint, "troubleshoot should not be destructive")
s.True(*tool.Annotations.IdempotentHint, "troubleshoot should be idempotent")
s.True(*tool.Annotations.OpenWorldHint, "troubleshoot should be open-world")
})

s.Run("tool has correct schema", func() {
tools := Tools(&fakeProvider{})
schema := tools[0].Tool.InputSchema

s.Require().NotNil(schema.Properties)
s.Contains(schema.Properties, "namespace")
s.Contains(schema.Properties, "name")
s.ElementsMatch([]string{"namespace", "name"}, schema.Required)
})

s.Run("description mentions priority and scope", func() {
tools := Tools(&fakeProvider{})
desc := tools[0].Tool.Description
s.Contains(desc, "FIRST")
s.Contains(desc, "root-cause")
s.Contains(desc, "StorageClasses")
s.Contains(desc, "cloud-init")
})
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Generally speaking we do this verification through the pkg/mcp/testdata/.json, and by checking once that that is what we expect. Any future changes would fail those tests.

})
}

func (s *TroubleshootToolSuite) TestFetchVMStatus() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Maybe for these tests we can just test the public API of the tools in pkg/mcp/kubevirt_test.go ?

We generally try to prefer testing the public api of the tools

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants