Skip to content

feat: Add privileged_client support to VirtualMachineInstance#2648

Open
rnetser wants to merge 11 commits intomainfrom
vm_priv_property
Open

feat: Add privileged_client support to VirtualMachineInstance#2648
rnetser wants to merge 11 commits intomainfrom
vm_priv_property

Conversation

@rnetser
Copy link
Collaborator

@rnetser rnetser commented Feb 13, 2026

Summary

Add explicit privileged_client parameter support across VirtualMachineInstance methods to enable proper RBAC-aware client usage, replacing implicit reliance on self.client for privileged operations.

New public methods (replacing deprecated properties)

  • get_virt_launcher_pod(privileged_client=...) — replaces virt_launcher_pod
  • get_virt_handler_pod(privileged_client=...) — replaces virt_handler_pod
  • get_node(privileged_client=...) — replaces node
  • get_xml_dict(privileged_client=...) — replaces xml_dict

Modified methods with privileged_client parameter

  • pause, unpause, reset
  • get_xml, execute_virsh_command
  • get_dommemstat, get_domstate
  • wait_until_running
  • api_request

New private helpers

  • _get_pod_user_uid — extract runAsUser UID from pod security context
  • _is_pod_root — check if pod runs as root
  • _get_hypervisor_connection_uri — build hypervisor socket URI
  • _build_virsh_cmd — construct virsh command list
  • _resolve_privileged_client — resolve client with FutureWarning fallback
  • _get_subresource_api_url — build subresource API URL with optional client

Deprecation strategy

  • FutureWarning when privileged_client is omitted (falls back to self.client)
  • DeprecationWarning on old property-based access (virt_launcher_pod, virt_handler_pod, node, xml_dict, etc.)

Consumer PR

Summary by CodeRabbit

  • New Features

    • Added methods for VM introspection: retrieve XML configuration, DOM state, memory statistics, node information, and execute virsh commands.
    • Support for privileged client context propagation across VM operations.
  • Bug Fixes

    • Fixed pause/unpause methods to no longer return wait operation results.
  • Deprecations

    • Several VM properties marked as deprecated; migrate to new get_* methods.

Introduce explicit privileged_client parameter across VirtualMachineInstance
methods to support proper RBAC-aware client usage.

New public methods replacing deprecated properties:
- get_virt_launcher_pod, get_virt_handler_pod, get_node, get_xml_dict

Modified methods with privileged_client parameter:
- pause, unpause, reset, get_xml, execute_virsh_command
- get_dommemstat, get_domstate, wait_until_running, api_request

New private helpers:
- _get_pod_user_uid, _is_pod_root
- _get_hypervisor_connection_uri, _build_virsh_cmd
- _resolve_privileged_client, _get_subresource_api_url

Warning strategy:
- FutureWarning when privileged_client is omitted
- DeprecationWarning on old property-based access
@coderabbitai
Copy link

coderabbitai bot commented Feb 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Refactored VirtualMachineInstance to add new public methods for virsh operations, XML/DOM state access, and pod retrieval with optional privileged context propagation. Deprecated wrappers maintain backward compatibility. Updated wait_until_running() to support privilege escalation. Modified pause/unpause to not return wait results. Reformatted api_request signature in resource.py.

Changes

Cohort / File(s) Summary
VirtualMachineInstance virsh and pod operations
ocp_resources/virtual_machine_instance.py
Added get_virt_launcher_pod(), get_virt_handler_pod() for pod retrieval; added execute_virsh_command(), get_xml(), get_xml_dict(), get_domstate(), get_dommemstat(), get_node() for virsh/XML operations; added virsh_cmd() and _build_virsh_cmd() for command construction.
VirtualMachineInstance static helpers and deprecation wrappers
ocp_resources/virtual_machine_instance.py
Added static methods get_pod_user_uid(), is_pod_root(), get_hypervisor_connection_uri() for pod analysis; created deprecated properties virt_launcher_pod, virt_handler_pod, xml_dict delegating to new get_* methods with backward-compatibility warnings.
VirtualMachineInstance privileged context propagation
ocp_resources/virtual_machine_instance.py
Updated wait_until_running() and all virsh/network-access methods to accept optional privileged_client parameter; modified pause()/unpause() to not return wait call results; socket type determination logic added for hypervisor URIs.
Resource signature formatting
ocp_resources/resource.py
Reformatted api_request method signature across multiple lines; no behavioral or semantic changes.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 56.25% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change: adding privileged_client parameter support to VirtualMachineInstance.
Description check ✅ Passed The description provides comprehensive coverage of changes including new public methods, modified methods, private helpers, and deprecation strategy, addressing all key aspects of the PR.
Merge Conflict Detection ✅ Passed ✅ No merge conflicts detected when merging into main

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

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch vm_priv_property

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.

@rnetser rnetser marked this pull request as ready for review February 15, 2026 17:19
@redhat-qe-bot2
Copy link

Report bugs in Issues

Welcome! 🎉

This pull request will be automatically processed with the following features:

🔄 Automatic Actions

  • Reviewer Assignment: Reviewers are automatically assigned based on the OWNERS file in the repository root
  • Size Labeling: PR size labels (XS, S, M, L, XL, XXL) are automatically applied based on changes
  • Issue Creation: Disabled for this repository
  • Branch Labeling: Branch-specific labels are applied to track the target branch
  • Auto-verification: Auto-verified users have their PRs automatically marked as verified
  • Labels: All label categories are enabled (default configuration)

📋 Available Commands

PR Status Management

  • /wip - Mark PR as work in progress (adds WIP: prefix to title)
  • /wip cancel - Remove work in progress status
  • /hold - Block PR merging (approvers only)
  • /hold cancel - Unblock PR merging
  • /verified - Mark PR as verified
  • /verified cancel - Remove verification status
  • /reprocess - Trigger complete PR workflow reprocessing (useful if webhook failed or configuration changed)
  • /regenerate-welcome - Regenerate this welcome message

Review & Approval

  • /lgtm - Approve changes (looks good to me)
  • /approve - Approve PR (approvers only)
  • /automerge - Enable automatic merging when all requirements are met (maintainers and approvers only)
  • /assign-reviewers - Assign reviewers based on OWNERS file
  • /assign-reviewer @username - Assign specific reviewer
  • /check-can-merge - Check if PR meets merge requirements

Testing & Validation

  • /retest tox - Run Python test suite with tox
  • /retest python-module-install - Test Python package installation
  • /retest conventional-title - Validate commit message format
  • /retest all - Run all available tests

Cherry-pick Operations

  • /cherry-pick <branch> - Schedule cherry-pick to target branch when PR is merged
    • Multiple branches: /cherry-pick branch1 branch2 branch3

Label Management

  • /<label-name> - Add a label to the PR
  • /<label-name> cancel - Remove a label from the PR

✅ Merge Requirements

This PR will be automatically approved when the following conditions are met:

  1. Approval: /approve from at least one approver
  2. LGTM Count: Minimum 0 /lgtm from reviewers
  3. Status Checks: All required status checks must pass
  4. No Blockers: No WIP, hold, conflict labels
  5. Verified: PR must be marked as verified (if verification is enabled)

📊 Review Process

Approvers and Reviewers

Approvers:

  • myakove
  • rnetser

Reviewers:

  • myakove
  • rnetser
Available Labels
  • hold
  • verified
  • wip
  • lgtm
  • approve
  • automerge

💡 Tips

  • WIP Status: Use /wip when your PR is not ready for review
  • Verification: The verified label is automatically removed on each new commit
  • Cherry-picking: Cherry-pick labels are processed when the PR is merged
  • Permission Levels: Some commands require approver permissions
  • Auto-verified Users: Certain users have automatic verification and merge privileges

For more information, please refer to the project documentation or contact the maintainers.

@rnetser
Copy link
Collaborator Author

rnetser commented Feb 15, 2026

/verified

Copy link

@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

🤖 Fix all issues with AI agents
In `@ocp_resources/virtual_machine_instance.py`:
- Around line 574-596: _get_pod_user_uid is annotated as returning int but can
be None when spec.securityContext.runAsUser is unset, causing _is_pod_root to
misidentify pods as root; change _get_pod_user_uid's type hint to Optional[int]
(or at least allow None) and update _is_pod_root to explicitly check for UID ==
0 (return True only when _get_pod_user_uid(pod) == 0) and otherwise return
False, so absent runAsUser is treated as non-root; refer to
VirtualMachineInstance._get_pod_user_uid, VirtualMachineInstance._is_pod_root,
and Pod.instance.spec.securityContext.runAsUser when making the change.
- Around line 333-363: The current calls to api_request in pause/unpause/reset
always pass the resolved _client, causing api_request to always take the
privileged path; change those calls to pass the original privileged_client
parameter (not _client) so api_request can choose the non-privileged code path
when privileged_client is omitted (i.e., use privileged_client=privileged_client
in the api_request call inside pause, unpause, and reset). Optionally add a
one-line method comment on pause/unpause/reset documenting this deprecation
transition if you want to clarify intent.
🧹 Nitpick comments (1)
ocp_resources/virtual_machine_instance.py (1)

476-484: Consider self.logger.exception() for the error-path diagnostics (Ruff TRY400).

Lines 477-479 use self.logger.error() inside an except block. While the exception is re-raised, using self.logger.exception() (at least for the first call) would automatically attach the traceback to the log output, aiding debugging.

- Pass original privileged_client (not resolved _client) to api_request in pause/unpause/reset
- Handle unset runAsUser in _get_pod_user_uid (return int | None) and _is_pod_root (explicit uid == 0 check)
- Use self.logger.exception() for error-path diagnostics in wait_until_running
@rnetser
Copy link
Collaborator Author

rnetser commented Feb 16, 2026

/verifierd

@rnetser
Copy link
Collaborator Author

rnetser commented Feb 16, 2026

/verified

Copy link

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ocp_resources/virtual_machine_instance.py (1)

470-484: ⚠️ Potential issue | 🟡 Minor

wait_until_running bypasses _resolve_privileged_client, breaking the deprecation contract.

Every other method that accepts privileged_client routes through _resolve_privileged_client so callers get a FutureWarning when the argument is omitted. Here, the silent or self.client fallback on line 476 skips that warning entirely, creating an inconsistency consumers may not notice until the breaking change lands.

Suggested fix
     def wait_until_running(
         self,
         timeout: int = TIMEOUT_4MINUTES,
         logs: bool = True,
         stop_status: str | None = None,
         privileged_client: DynamicClient | None = None,
     ) -> None:
+        _client = self._resolve_privileged_client(
+            privileged_client=privileged_client,
+            fallback_client=self.client,
+            method_name="wait_until_running",
+        )
         ...
         try:
             self.wait_for_status(status=self.Status.RUNNING, timeout=timeout, stop_status=stop_status)
         except TimeoutExpiredError as sampler_ex:
             if not logs:
                 raise
             try:
-                virt_pod = self.get_virt_launcher_pod(privileged_client=privileged_client or self.client)
+                virt_pod = self.get_virt_launcher_pod(privileged_client=_client)
🧹 Nitpick comments (3)
ocp_resources/virtual_machine_instance.py (3)

436-440: ResourceNotFoundError raised without a descriptive message.

Line 394 provides a helpful message ("VIRT launcher POD not found for ..."), but line 440 raises a bare ResourceNotFoundError. Adding a similar message would help consumers debug issues.

Suggested fix
-        raise ResourceNotFoundError
+        raise ResourceNotFoundError(f"virt-handler pod not found on node {self.instance.status.nodeName}")

646-661: Redundant double-resolution of privileged_client across chained calls.

get_xml resolves the client via _resolve_privileged_client (line 656) then passes _client to execute_virsh_command (line 661), which internally calls _resolve_privileged_client again (line 823). The same pattern applies to get_dommemstat (line 747→752) and get_xml_dict (line 773→778→get_xmlexecute_virsh_command).

Because the resolved _client is always non-None, no double-warning is emitted, so this isn't a bug. However, the redundant resolution adds unnecessary overhead and cognitive noise. Consider having these "wrapper" methods pass _client directly and have execute_virsh_command skip re-resolution when the client is already provided, or document that execute_virsh_command is also a standalone public entry point that needs its own resolution.


477-479: Ruff TRY400: logging.error in an exception handler.

Ruff flags lines 477-479 because they use self.logger.error() inside an except block. These are diagnostic messages (pod status, pod name, pod logs) rather than exception messages, so logging.exception isn't semantically ideal here either. You could suppress with a # noqa: TRY400 if you want to keep the current style, or switch to self.logger.exception() for the first one (line 477) since it's in the context of a real error, and leave the rest as .error().

- Remove _resolve_privileged_client helper and FutureWarning mechanism
- Use simple 'privileged_client or self.client' fallback pattern
- Deprecated properties emit DeprecationWarning only
- Fix _is_pod_root to treat unset runAsUser as root (Kubernetes default)
Copy link

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

🧹 Nitpick comments (2)
ocp_resources/virtual_machine_instance.py (2)

295-296: Inconsistent parameter naming: client vs privileged_client.

reset uses client while pause (line 282) and unpause (line 289) use privileged_client. For a consistent public API, consider renaming to privileged_client here as well.

♻️ Proposed fix
-    def reset(self, client: DynamicClient | None = None) -> dict[str, Any]:
-        return self.api_request(method="PUT", action="reset", client=client or self.client)
+    def reset(self, privileged_client: DynamicClient | None = None) -> dict[str, Any]:
+        return self.api_request(method="PUT", action="reset", client=privileged_client or self.client)

509-520: Consider whether uid is None should truly be treated as root.

The docstring explicitly documents this, but in Kubernetes, an unset runAsUser doesn't always mean root — it depends on the container image's default user and pod security policies. If this is intentional for the KubeVirt virt-launcher context, a brief inline comment explaining why None ≈ root would help future readers.

Copy link

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

🧹 Nitpick comments (3)
ocp_resources/virtual_machine_instance.py (3)

295-296: Inconsistent parameter name: client vs privileged_client.

reset uses client while pause/unpause and all other new methods use privileged_client. This inconsistency in the public API could confuse callers about when to pass a privileged vs. regular client.

Suggested fix
-    def reset(self, client: DynamicClient | None = None) -> dict[str, Any]:
-        return self.api_request(method="PUT", action="reset", client=client or self.client)
+    def reset(self, privileged_client: DynamicClient | None = None) -> dict[str, Any]:
+        return self.api_request(method="PUT", action="reset", client=privileged_client or self.client)

686-701: Double pod lookup per virsh command execution.

execute_virsh_command calls get_virt_launcher_pod on line 697 to get the pod, then calls virsh_cmd on line 699, which internally calls get_virt_launcher_pod again (line 553). Each call results in a Kubernetes API list request, doubling the API overhead.

Consider fetching the pod once and passing it through:

Suggested refactor
-    def virsh_cmd(self, action: str, privileged_client: DynamicClient | None = None) -> list[str]:
+    def virsh_cmd(self, action: str, privileged_client: DynamicClient | None = None, pod: Pod | None = None) -> list[str]:
         ...
-        pod = self.get_virt_launcher_pod(privileged_client=privileged_client or self.client)
+        pod = pod or self.get_virt_launcher_pod(privileged_client=privileged_client or self.client)
         hypervisor_uri = self.get_hypervisor_connection_uri(pod=pod)
         return shlex.split(f"virsh {hypervisor_uri} {action} {self.namespace}_{self.name}")

     def execute_virsh_command(self, command: str, privileged_client: DynamicClient | None = None) -> str:
         ...
         _client = privileged_client or self.client
         pod = self.get_virt_launcher_pod(privileged_client=_client)
         return pod.execute(
-            command=self.virsh_cmd(action=command, privileged_client=_client),
+            command=self.virsh_cmd(action=command, privileged_client=_client, pod=pod),
             container="compute",
         )

365-365: Missing error message in ResourceNotFoundError.

get_virt_launcher_pod (line 322) raises with a descriptive message, but get_virt_handler_pod raises a bare ResourceNotFoundError. This makes debugging harder when the error surfaces.

Suggested fix
-        raise ResourceNotFoundError
+        raise ResourceNotFoundError(f"VIRT handler POD not found for {self.kind}:{self.name}")

Copy link

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

🧹 Nitpick comments (3)
ocp_resources/virtual_machine_instance.py (3)

533-545: virsh_cmd and execute_virsh_command duplicate the command-construction logic.

Both methods independently fetch the pod, resolve the hypervisor URI, and build the identical shlex.split(f"virsh {hypervisor_uri} ... {self.namespace}_{self.name}") string. execute_virsh_command could delegate to a shared helper to avoid the duplication.

A minimal refactor: extract a private _build_virsh_context that returns the (pod, command_list) tuple, then have both public methods call it.

Suggested refactor
+    def _build_virsh_context(
+        self, action: str, privileged_client: DynamicClient | None = None
+    ) -> tuple[Pod, list[str]]:
+        _client = privileged_client or self.client
+        pod = self.get_virt_launcher_pod(privileged_client=_client)
+        hypervisor_uri = self.get_hypervisor_connection_uri(pod=pod)
+        cmd = shlex.split(f"virsh {hypervisor_uri} {action} {self.namespace}_{self.name}")
+        return pod, cmd
+
     def virsh_cmd(self, action: str, privileged_client: DynamicClient | None = None) -> list[str]:
-        pod = self.get_virt_launcher_pod(privileged_client=privileged_client or self.client)
-        hypervisor_uri = self.get_hypervisor_connection_uri(pod=pod)
-        return shlex.split(f"virsh {hypervisor_uri} {action} {self.namespace}_{self.name}")
+        _, cmd = self._build_virsh_context(action=action, privileged_client=privileged_client)
+        return cmd

     def execute_virsh_command(self, command: str, privileged_client: DynamicClient | None = None) -> str:
-        _client = privileged_client or self.client
-        pod = self.get_virt_launcher_pod(privileged_client=_client)
-        hypervisor_uri = self.get_hypervisor_connection_uri(pod=pod)
-        return pod.execute(
-            command=shlex.split(f"virsh {hypervisor_uri} {command} {self.namespace}_{self.name}"),
-            container="compute",
-        )
+        pod, cmd = self._build_virsh_context(action=command, privileged_client=privileged_client)
+        return pod.execute(command=cmd, container="compute")

Also applies to: 676-691


547-557: Redundant or self.client fallback in wrapper methods.

get_xml, get_domstate, get_dommemstat, and get_xml_dict each resolve privileged_client or self.client before passing the result to execute_virsh_command / get_xml, which performs the same fallback internally. This means the inner fallback is never exercised when called through these wrappers.

Consider passing privileged_client through as-is and letting the leaf method (execute_virsh_command) handle the single fallback, which is cleaner and avoids masking a genuinely None intent:

     def get_xml(self, privileged_client: DynamicClient | None = None) -> str:
-        return self.execute_virsh_command(command="dumpxml", privileged_client=privileged_client or self.client)
+        return self.execute_virsh_command(command="dumpxml", privileged_client=privileged_client)

Same pattern applies to get_domstate, get_dommemstat, and get_xml_dict.

Also applies to: 605-605, 618-618, 639-641


512-531: get_hypervisor_connection_uri executes a remote ls — consider documenting the side effect.

This static method runs pod.execute(command=["ls", "/var/run/libvirt/"], ...) to probe for the socket type, which is a network call with latency and potential failure. Callers may not expect a "get URI" method to perform remote I/O. A brief docstring note about this would help.

rnetser added a commit to rnetser/openshift-virtualization-tests that referenced this pull request Feb 16, 2026
Replace all VirtualMachineForTests.privileged_vmi property usages
with the new openshift-python-wrapper privileged_client API:

- vm.privileged_vmi.xml_dict → vm.vmi.get_xml_dict(privileged_client=admin_client)
- vm.privileged_vmi.virt_launcher_pod → vm.vmi.get_virt_launcher_pod(privileged_client=admin_client)
- vm.privileged_vmi.node → vm.vmi.get_node(privileged_client=admin_client)
- vm.privileged_vmi.virt_handler_pod → vm.vmi.get_virt_handler_pod(privileged_client=admin_client)
- vm.privileged_vmi.execute_virsh_command() → vm.vmi.execute_virsh_command(privileged_client=admin_client)
- vm.privileged_vmi.pause/unpause → vm.vmi.pause/unpause (no privileged_client needed)
- vm.privileged_vmi.interfaces → vm.vmi.interfaces (no privileged_client needed)

Remove privileged_vmi property and get_client import.

Depends on: RedHatQE/openshift-python-wrapper#2648
Closes RedHatQE#3846
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.

5 participants