Skip to content

net: Nad live-update non-migratable VM test#5212

Open
azhivovk wants to merge 2 commits into
RedHatQE:mainfrom
azhivovk:nad-ref-non-migratable
Open

net: Nad live-update non-migratable VM test#5212
azhivovk wants to merge 2 commits into
RedHatQE:mainfrom
azhivovk:nad-ref-non-migratable

Conversation

@azhivovk

@azhivovk azhivovk commented Jun 14, 2026

Copy link
Copy Markdown
Contributor
What this PR does / why we need it:

Adds test_non_migratable_vm_nad_change_not_applied: verifies that updating the NAD reference on a non-migratable VM does not apply live and the VM reports a RestartRequired condition and retains connectivity to the original VLAN-A.

To support this, the PR also:

  • Extends the typed VM spec with DataVolume volume support, and adds a non-migratable VM factory that attaches a pre-existing RWO DataVolume as an extra disk to prevent live migration
  • Fixes the containerdisk insertion logic to detect an existing boot disk by name rather than by absence of a DataVolume, so attaching a DataVolume as an extra disk no longer suppresses the
    containerdisk
  • Adds a patch-only NAD reference update variant for non-migratable VMs, where the expected post-change condition is a restart requirement rather than live migration
Which issue(s) this PR fixes: -
Special notes for reviewer:

The test is quarantined via @pytest.mark.jira("CNV-87878", run=False): the RestartRequired condition is not yet wired up in KubeVirt.

jira-ticket: https://redhat.atlassian.net/browse/CNV-89745

Summary by CodeRabbit

Summary

  • New Features

    • Added DataVolume template support to VM spec utilities, enabling creation of non-migratable test VMs.
    • Introduced a helper to patch NAD/network references in VM specs to drive NAD reference-change scenarios.
  • Bug Fixes

    • Updated the NAD reference update flow to apply spec/network patching before waiting for VMI migration-required state.
    • Improved connectivity validation to assert expected TCP reachability per VLAN (success on VLAN-A, no connectivity on VLAN-B).
  • Tests

    • Added fixtures for a non-migratable under-test VM and baseline connectivity checks.
    • Updated the non-migratable NAD-change test to use the new fixtures and per-IP subtest assertions.

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c9d3459c-d9a9-4ead-be9a-8c1fa7e9e1e1

📥 Commits

Reviewing files that changed from the base of the PR and between 9ac711b and 4400855.

📒 Files selected for processing (4)
  • libs/net/vmspec.py
  • tests/network/l2_bridge/nad_ref_change/conftest.py
  • tests/network/l2_bridge/nad_ref_change/lib_helpers.py
  • tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py

📝 Walkthrough

Walkthrough

Adds DataVolume-backed VM spec support, reusable NAD patching, a non-migratable VM path, and a negative test that switches a secondary bridge NAD and verifies VLAN-specific connectivity outcomes.

Changes

DataVolume-backed non-migratable VM NAD change test

Layer / File(s) Summary
DataVolume reference support
libs/vm/spec.py, libs/vm/vm.py
Adds Volume.dataVolume and DataVolumeRef, and introduces a helper to create a virtio disk and matching volume backed by that reference.
NAD patch helper split
libs/net/vmspec.py
Adds patch_nad_references(...) and refactors update_nad_references(...) to call it before waiting for MigrationRequired to appear and clear.
Bridge VM factory and connectivity helper
tests/network/l2_bridge/nad_ref_change/lib_helpers.py
Adds verify_baseline_connectivity(...), factors bridge VM spec composition into _bridge_vm_spec(...), and adds non_migratable_bridge_vm(...) that appends a DataVolume-backed disk and volume to the bridge VM spec.
Non-migratable VM fixtures
tests/network/l2_bridge/nad_ref_change/conftest.py
Updates baseline connectivity to call the shared verifier, adds the non-migratable under-test VM fixture, and wires the new baseline connectivity fixture for that VM shape.
NAD change test implementation
tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py
Updates the negative test to use the new fixtures, patches the NAD reference, waits for RestartRequired, and checks VLAN-A/VLAN-B TCP reachability with subtests.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

new-tests

Suggested reviewers

  • nirdothan
  • rnetser
  • orelmisan
  • dshchedr
  • yossisegev
  • EdDev
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise, under 120 characters, and clearly matches the non-migratable NAD update test change.
Description check ✅ Passed The description follows the required template and includes the PR purpose, reviewer notes, and Jira ticket, with only a minor placeholder in the issues section.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Stp Link Required ✅ Passed PASS: the added test function is covered by the file’s module-level STP docstring; no new test file and no Jira line in the test docstring.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 OpenGrep (1.23.0)
libs/net/vmspec.py

┌──────────────┐
│ Opengrep CLI │
└──────────────┘

�[32m✔�[39m �[1mOpengrep OSS�[0m
�[32m✔�[39m Basic security coverage for first-party code vulnerabilities.

[00.12][ERROR]: unable to find a config; path .coderabbit-opengrep-fallback.yml does not exist

tests/network/l2_bridge/nad_ref_change/conftest.py

┌──────────────┐
│ Opengrep CLI │
└──────────────┘

�[32m✔�[39m �[1mOpengrep OSS�[0m
�[32m✔�[39m Basic security coverage for first-party code vulnerabilities.

[00.13][ERROR]: unable to find a config; path .coderabbit-opengrep-fallback.yml does not exist

tests/network/l2_bridge/nad_ref_change/lib_helpers.py

┌──────────────┐
│ Opengrep CLI │
└──────────────┘

�[32m✔�[39m �[1mOpengrep OSS�[0m
�[32m✔�[39m Basic security coverage for first-party code vulnerabilities.

[00.13][ERROR]: unable to find a config; path .coderabbit-opengrep-fallback.yml does not exist

  • 1 others

Warning

Review ran into problems

🔥 Problems

Linked repositories: Your configuration references 1 linked repositories, but your current plan allows 0. Analyzed ``, skipped RedHatQE/openshift-virtualization-tests-design-docs.


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.

@openshift-virtualization-qe-bot-6

Copy link
Copy Markdown

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: A tracking issue is created for this PR and will be closed when the PR is merged or closed
  • 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: Enabled categories: branch, can-be-merged, cherry-pick, has-conflicts, hold, needs-rebase, size, verified, wip

📋 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
  • /security-override - Set security check runs to pass (maintainers only)
  • /security-override cancel - Re-run security checks

Review & Approval

  • /lgtm - Approve changes (looks good to me)
  • /approve - Approve PR (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 build-container - Rebuild and test container image
  • /retest verify-bugs-are-open - verify-bugs-are-open
  • /retest all - Run all available tests

Container Operations

  • /build-and-push-container - Build and push container image (tagged with PR number)
    • Supports additional build arguments: /build-and-push-container --build-arg KEY=value

Cherry-pick Operations

  • /cherry-pick <branch> - Schedule cherry-pick to target branch when PR is merged
    • Multiple branches: /cherry-pick branch1 branch2 branch3
  • /cherry-pick-retry <branch> - Retry a failed cherry-pick (merged PRs only)

Branch Management

  • /rebase - Rebase this PR branch onto its base branch

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 2 /lgtm from reviewers
  3. Status Checks: All required status checks must pass
  4. No Blockers: No wip, hold, has-conflicts labels and PR must be mergeable (no conflicts)
  5. Verified: PR must be marked as verified

📊 Review Process

Approvers and Reviewers

Approvers:

  • EdDev
  • dshchedr
  • myakove
  • rnetser
  • vsibirsk

Reviewers:

  • Anatw
  • EdDev
  • RoniKishner
  • azhivovk
  • dshchedr
  • frenzyfriday
  • nirdothan
  • orelmisan
  • rnetser
  • servolkov
  • vsibirsk
  • yossisegev
Available Labels
  • hold
  • verified
  • wip
  • lgtm
  • approve
AI Features
  • Cherry-Pick Conflict Resolution: Enabled (claude/claude-opus-4-6[1m])
Security Checks
  • Suspicious Path Detection: Monitors paths: .claude/, .vscode/, .cursor/, .devcontainer/, .pi/, .github/workflows/, .github/actions/
  • Committer Identity Check: Verifies last committer matches PR author
  • Mandatory: Security checks block merge (use /security-override to bypass — maintainers only)

💡 Tips

  • WIP Status: Use /wip when your PR is not ready for review
  • Verification: The verified label is removed on new commits unless the push is detected as a clean rebase
  • Cherry-picking: Cherry-pick labels are processed when the PR is merged
  • Container Builds: Container images are automatically tagged with the PR number
  • 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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

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

Inline comments:
In `@libs/vm/factory.py`:
- Around line 44-47: The condition on line 44 incorrectly suppresses the default
containerdisk insertion whenever any DataVolume is present, breaking the ability
to use DataVolumes as additional disks alongside a boot disk. Replace the logic
that checks for any DataVolume with a check that determines whether a boot disk
or containerdisk already exists. The containerdisk should be inserted by default
unless a boot disk is already present, allowing DataVolumes to coexist as
additional disks. Modify the condition in the containerdisk_storage function
call to verify the absence of an actual boot disk rather than the absence of any
DataVolume.

In `@tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py`:
- Around line 244-248: The wait_for_vmi_condition_status call needs to be
anchored to the pre-patch resource state to ensure it validates the
RestartRequired condition is set as a result of the current patch operation, not
from a stale previous state. Capture the resourceVersion of
non_migratable_under_test_vm before calling patch_nad_references, then pass this
resourceVersion to the wait_for_vmi_condition_status call so it waits for a
condition transition that occurs after the patch, not a pre-existing condition
state.

In `@tests/network/libs/nad_ref.py`:
- Around line 27-40: The patch_nad_references function contains duplicate
network mutation logic that is already present in update_nad_references,
specifically the block that iterates through networks and updates the
multus.networkName based on the nad_name_by_net mapping. Extract this common
logic (the deepcopy, the for loop iterating through networks, and the
conditional update of network.multus.networkName) into a separate shared helper
function, then refactor both patch_nad_references and update_nad_references to
call this new helper function instead of duplicating the code.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 70873300-8c28-46c1-a952-0a736d2105fb

📥 Commits

Reviewing files that changed from the base of the PR and between c5e4a14 and 524eef8.

📒 Files selected for processing (6)
  • libs/vm/factory.py
  • libs/vm/spec.py
  • tests/network/l2_bridge/nad_ref_change/conftest.py
  • tests/network/l2_bridge/nad_ref_change/lib_helpers.py
  • tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py
  • tests/network/libs/nad_ref.py

Comment thread libs/vm/factory.py Outdated
Comment thread tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py
Comment thread tests/network/libs/nad_ref.py Outdated
@azhivovk

Copy link
Copy Markdown
Contributor Author

@jpeimer Hi, please review the DV related changes

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 22, 2026
@azhivovk

Copy link
Copy Markdown
Contributor Author

Change: rebase upon main and resolve conflicts

@azhivovk

Copy link
Copy Markdown
Contributor Author

Change: move NAD update helper to vmspec

nad_names: list[str],
ip_addresses: list[list[str]],
iface_names: list[str],
data_volume: DataVolume,

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.

It's not clear what we expect to get here.
There are at least two kinds of relationships between the VM and the DV:

  • VM can have a dataVolumeTemplate (a dictionary) in its spec, so the VM creation will trigger a DataVolume creation, and the VM will own this DataVolume
  • VM can use an existing DV that was created by someone else

In this case, data_volume arg is used for the dataVolumeTemplate, so it makes more sense to get the dictionary, and not the DataVolume object.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed, thanks

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, looks good!

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 function caller constructs a DataVolume describing what storage they want. The function decides to embed it as a template - this is an implementation detail. This all is about clear function API - accept the typed object.

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 function is using this argument as a dataVolumeTemplate, so it needs to explicitly say it expects the data_volume_template (dictionary), and not a DataVolume object.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I implemented the second approach

VM can use an existing DV that was created by someone else

Now we create DV separately and the VM references it
That way we can avoid usage of raw dicts and use the DataVolume OCP resource

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.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

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.

Creating a DataVolume separately will make your life much harder.
VM with dataVolumeTemplate is a much more common use.

this is an example how they pass such data to VM: direct DataVolume usage

This still adds a dataVolumeTemplate to the VM spec.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Hi @rnetser can you share your thoughts?

@servolkov requested to avoid using raw dicts (as we do now in net tests) and create dataclasses
This led to implement usage of DataVolume OCP resource: DV is first created and then referenced by the VM but @jpeimer asks to use DataVolumeTemplate
Using DataVolumeTemplate will require addition of dataclasses to avoid using raw dicts

WDYT?

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.

This still adds a dataVolumeTemplate to the VM spec.

@jpeimer very well, I don't mind. But I was talking about what they use as a function parameter, not about implementation (as well as the topic here).

Using DataVolumeTemplate will require addition of dataclasses to avoid using raw dicts

not necessary. See your very first implementation.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 23, 2026
@azhivovk

Copy link
Copy Markdown
Contributor Author

Change: rebase upon main to merge fix of pre-commit and tox errors

@azhivovk

Copy link
Copy Markdown
Contributor Author

Change: use dataVolumeTemplate to use the DV as integrated in the VM

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 23, 2026
runcmd=runcmd,
)
dv_name = data_volume_template["metadata"]["name"]
vm.body["spec"]["template"]["spec"]["volumes"].append({"name": dv_name, "dataVolume": {"name": dv_name}})

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.

a raw dict manipulation bypasses the typed spec system that the rest of the our codebase uses. I think you should:

  1. restore DataVolumeRef and Volume.dataVolume in libs/vm/spec.py - they were in the previous iteration and they're correct: they map to real KubeVirt API fields and follow the same pattern
  2. extract _bridge_vm_spec from two_secondary_bridge_vm so both builders share spec construction
  3. accept data_volume: DataVolume instead of data_volume_template: dict[str, Any] - DataVolume enforces at the type level that only a DataVolume can be passed, a raw dict throws away the type, the schema, and the API identity.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It seems like we need to use dataVolumeTemplate
As @jpeimer explained here - I made the DV integrated in the VM

#5212 (comment)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Now we create DV separately and the VM references it
That way we can avoid usage of raw dicts and use the DataVolume OCP resource
#5212 (comment)

@azhivovk

Copy link
Copy Markdown
Contributor Author

Change:

  • Extract VM spec
  • Separate DV creation and VM references it
  • Add data_volume_storage

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@tests/network/l2_bridge/nad_ref_change/lib_helpers.py`:
- Around line 161-186: The docstring in the helper that builds the Fedora bridge
VM spec is inaccurate and should be updated to match the implementation. Clarify
that the function uses data_volume_storage(data_volume.name) to reference an
already-created DataVolume by name and adds it via add_volume_disk to the VM
spec, rather than creating a dataVolumeTemplate or relying on the VM controller
to provision it. Keep the description aligned with _bridge_vm_spec,
data_volume_storage, and fedora_vm so readers understand the fixture depends on
an existing deployed DataVolume.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0bdace48-2a5b-4bdf-825c-5bee5b90337a

📥 Commits

Reviewing files that changed from the base of the PR and between 7521ad5 and 5f34da7.

📒 Files selected for processing (6)
  • libs/net/vmspec.py
  • libs/vm/spec.py
  • libs/vm/vm.py
  • tests/network/l2_bridge/nad_ref_change/conftest.py
  • tests/network/l2_bridge/nad_ref_change/lib_helpers.py
  • tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py

Comment thread tests/network/l2_bridge/nad_ref_change/lib_helpers.py Outdated
@azhivovk

Copy link
Copy Markdown
Contributor Author

Change: edit docstring

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

The PR description looks outdated. In general LGTM, a few cosmetic comments:

) -> None:
"""Verify baseline connectivity before the NAD reference change.

Asserts that the non-migratable under-test VM can reach the reference VM on VLAN-A

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
avoid mentioning implementation details in such fixtures, the configuration may change (VLAN A and so on). IMO all this docstring is redundant.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed, thanks

namespace=namespace,
name=name,
client=client,
spec=_bridge_vm_spec(

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
it's better to put it in a variable for readability.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

)


def assert_baseline_connectivity(

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.

Since it is used as a helper for the precondition, consider poll_baseline_connectivity or verify_beseline_connectivity, not assert_.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Done

Comment on lines +100 to +111
with DataVolume(
name="non-migratable-dv",
namespace=namespace.name,
source_dict={"blank": {}},
access_modes=DataVolume.AccessMode.RWO,
size="20Gi",
storage_class=py_config["default_storage_class"],
api_name="storage",
client=unprivileged_client,
) as dv:
dv.wait_for_dv_success()
yield dv

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.

How will this work with a WaitForFirstConsumer storage?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Can you please elaborate?
I need to remove dv.wait_for_dv_success() before yielding the DV, since the VM fixture already guarantees the storage was successfully mounted before the test runs?

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.

When we create a DV with WaitForFirstConsumer storage, it stays pending until the first consumer (pod/virt-launcher pod) comes and consumes it. But then other issues may come up (PVC and VM scheduled on different nodes / DV and VM architecture mismatch etc). So it's much better and safer to create a VM with dataVolumeTemplate instead of using a pre-created DV.

Comment thread libs/vm/vm.py Outdated
)


def data_volume_storage(name: str) -> tuple[SpecDisk, Volume]:

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.

It's not very clear what data_volume_storage means.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Renamed to disk_and_volume_for_data_volume
I created it to improve readability of disk and volume creation

nad_names: list[str],
ip_addresses: list[list[str]],
iface_names: list[str],
data_volume: DataVolume,

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.

Creating a DataVolume separately will make your life much harder.
VM with dataVolumeTemplate is a much more common use.

this is an example how they pass such data to VM: direct DataVolume usage

This still adds a dataVolumeTemplate to the VM spec.

@azhivovk

Copy link
Copy Markdown
Contributor Author

Change: apply changes after CR

  • Rename disk and volume helper
  • Drop redundant docstrings
  • Remove dv.wait_for_dv_success() in DV creation

and disk-volume helper

The VM spec lacked typed support for volumes backed by a
DataVolume, forcing callers to manipulate vm.body["spec"]
directly and bypass the typed spec layer.

DataVolumeRef maps the KubeVirt dataVolume volume source to
the same typed pattern used by ContainerDisk and
CloudInitNoCloud. disk_and_volume_for_data_volume follows the
same factory convention as containerdisk_storage and
cloudinitdisk_storage.

Signed-off-by: Asia Khromov <azhivovk@redhat.com>
Assisted-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@libs/net/vmspec.py`:
- Around line 318-322: In vmspec’s network update flow, avoid masking a broken
template by defaulting `deepcopy(vm.template_spec.networks)` to an empty list
and then calling `vm.set_networks`; instead, fail fast if the template networks
are missing or invalid. Update the helper that rewrites networks so it only
proceeds when a matching Multus network exists for an entry in
`nad_name_by_net`, and if none of the NAD mappings can be applied, return an
error rather than letting the caller time out with `MigrationRequired` or
`RestartRequired`. Use the `vm.set_networks` path and the
`nad_name_by_net`/`network.multus` checks to locate the fix.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2c0a9573-2923-4478-baa0-a4638224afe1

📥 Commits

Reviewing files that changed from the base of the PR and between 7272e4f and 9ac711b.

📒 Files selected for processing (6)
  • libs/net/vmspec.py
  • libs/vm/spec.py
  • libs/vm/vm.py
  • tests/network/l2_bridge/nad_ref_change/conftest.py
  • tests/network/l2_bridge/nad_ref_change/lib_helpers.py
  • tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py

Comment thread libs/net/vmspec.py
@azhivovk

Copy link
Copy Markdown
Contributor Author

Change: use DV template

@openshift-virtualization-qe-bot-3

Copy link
Copy Markdown
Contributor

/retest all

Auto-triggered: Files in this PR were modified by merged PR #5285.

Overlapping files

libs/vm/vm.py

A NAD reference change on a non-live-migratable VM cannot be
applied without a restart. The expected behavior is that KubeVirt
sets a RestartRequired condition and the VM retains connectivity
to the original VLAN until restarted.

A RWO DataVolume is embedded as a dataVolumeTemplate so the VM
owns and creates it. Each DV is bound to exactly one VM; creating
it inside the helper ensures the DV is scheduled on the same node
as the VM so the RWO PVC can be claimed.

Quarantined via CNV-87878: the RestartRequired condition is not
yet wired up in KubeVirt, so the wait step will remain skipped
until the fix lands and the quarantine is lifted.

Signed-off-by: Asia Khromov <azhivovk@redhat.com>
Assisted-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
@azhivovk

Copy link
Copy Markdown
Contributor Author

Change: DV creation in non-migratable VM helper

@azhivovk

azhivovk commented Jun 29, 2026

Copy link
Copy Markdown
Contributor Author

/verified

Dual-stack BM with cnv-4.23:

Test is not collected
uv run pytest --verbose -rs -s -o log_cli=true --leftovers-collector --tb=native --skip-deprecated-api-test --cluster-sanity-skip-storage-check tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::test_non_migratable_vm_nad_change_not_applied --jira
Result: SKIPPED [1] tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py:219

New test is passing when run without waiting for RestartRequired (to make sure the tests pass once unblocked)
# wait_for_vmi_condition_status(vm=non_migratable_under_test_vm, condition="RestartRequired")
Result: 1 passed, 1 deselected, 52 warnings, 4 subtests passed in 653.17s (0:10:53)

Regression due to helper update_nad_references refactoring:
uv run pytest --verbose -rs -s -o log_cli=true --leftovers-collector --tb=native --skip-deprecated-api-test --cluster-sanity-skip-storage-check tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py tests/network/localnet/nad_ref_change/test_nad_ref_change.py --jira
Result: 4 passed, 1 skipped, 1 deselected, 128 warnings, 12 subtests passed in 1247.19s (0:20:47)

@openshift-virtualization-qe-bot-3

Copy link
Copy Markdown
Contributor

@coderabbitai

Test execution plan request details

CRITICAL: You MUST post an inline review comment on the first changed line of the first file.
The inline comment should contain the full Test Execution Plan (smoke decision, gating decision, and specific affected tests).
Do NOT submit a blocking review event (REQUEST_CHANGES/APPROVE).
Post a single inline PR comment on Files Changed (non-blocking COMMENT flow).

As an expert software testing engineer, analyze all modified files in this PR and create a targeted test execution plan.
You will post an inline review comment with the test execution plan on the first changed file.
If you fail to run or post a comment, retry.

Analysis Requirements:

  1. Examine code changes in each modified file

  2. Identify affected code paths, functions, and classes

  3. Analyze pytest-specific elements: fixtures (scope, dependencies), parametrization, markers, conftest changes

  4. Trace test dependencies through imports, shared utilities, fixture inheritance, fixture teardown, and yield from cleanup in conftest

  5. Detect new tests introduced in the PR

  6. Utilities and libs impact (when utilities/ or libs/ changes):
    You MUST use shell scripts (rg, git diff) to trace the full impact.
    Follow these sub-steps in order:

    6a. Identify modified symbols: For each changed file under utilities/ or libs/,
    list every modified function or method.
    Example: git diff HEAD~1 --unified=0 -- utilities/hco.py | grep '^[+-]def '

    6b. Find direct callers: Search tests and conftest for each symbol from 6a.
    Example: rg -l 'get_hco_version' tests/

    6c. Trace fixture teardown and cleanup: Find fixtures that reach
    the modified symbol through yield from or context-manager wrappers.
    Example: rg -l 'yield from.*enable_common_boot|def.*enable_common_boot' tests/

    6d. Trace same-file callers: In each changed file, find other functions
    whose body calls a modified symbol (including code after yield
    in @contextmanager helpers).
    Example: rg 'get_hco_version|enable_common_boot' utilities/hco.py

    6e. Expand transitively: If function A calls modified B, then
    tests/fixtures that call A are affected — even when the test body
    never imports B directly.

    Do NOT limit impact to tests that import the modified symbol only.

  7. Smoke test impact: Intersect the affected set from step 6 with smoke-marked tests.
    Run: rg -l '@pytest.mark.smoke' tests/
    VERIFY the above command returned actual file paths before concluding False.
    Set True if either condition is met:

    • a smoke-marked file appears in the affected set from 6b-6e, OR
    • any conftest.py in the smoke test's parent-directory hierarchy (up to repo root)
      imports or calls a modified utilities/libs symbol — including autouse fixtures
      that depend on modified functions. ALL tests in that directory and below are affected.
      Example check: for each smoke_file, scan dirname(smoke_file)/conftest.py,
      dirname(dirname(smoke_file))/conftest.py, etc. for modified symbol imports
      and autouse fixtures that depend on modified symbols.
  8. Gating test impact: Intersect the affected set from step 6 with gating-marked tests.
    Run: rg -l '@pytest.mark.gating' tests/
    Set True if a gating-marked file also appears in the affected set from 6b-6e.
    Utilities/libs changes often affect gating tests without affecting smoke tests.
    Do NOT stop analysis after concluding Run smoke tests: False.

Output rules:
Do NOT include analysis step numbers (1-8) in your visible output.

Your deliverable:
Your inline informational comment will be based on the following requirements:

Test Execution Plan

  • Run smoke tests: True / False — If True, state the dependency path (test → fixture → changed symbol). True ONLY with a verified path.
  • Run gating tests: True / False — If True, state the dependency path. True if any gating-marked test is in the affected set.
  • Affected tests to run (required when utilities/, libs/, or shared conftest changes — list concrete paths even when smoke is False)

Use these formats:

  • path/to/test_file.py - When the entire test file needs verification
  • path/to/test_file.py::TestClass::test_method - When specific test(s) needed
  • path/to/test_file.py::test_function - When specific test(s) needed
  • -m marker - When a marker covers multiple affected tests (e.g. -m gating only if ALL gating tests in scope need run)
  • Tag each listed test or group with its marker when not obvious, e.g. (gating) or (smoke)

Real test commands (MANDATORY when changes affect session/runtime code):

When the affected code runs at session/collection time (conftest fixtures, pytest plugins,
config hooks, session-scoped setup) or modifies runtime behavior that unit tests mock away,
you MUST include concrete pytest commands the PR author must run on a real cluster
to verify the change works end-to-end. Include:

  • A command for the error/fix path (the scenario the PR fixes)
  • A command for the happy path (regression: the normal case still works)
  • Use lightweight tests (e.g., --collect-only for startup failures,
    a single small test for runtime behavior)
    If the PR only changes test logic (not utilities/libs/conftest), the affected test
    paths themselves serve as the real test commands — no separate section needed.

Example output for a session-startup fix:

**Real tests (cluster required)**
Error path (the fix):
`pytest tests/storage/.../test_foo.py --storage-class-matrix=nonexistent-sc --collect-only`
Expected: ValueError with clear message, not IndexError

Happy path (regression):
`pytest tests/storage/.../test_foo.py --storage-class-matrix=<valid-sc> -k test_bar`
Expected: session starts normally

Guidelines:

  • Include tests affected directly OR via fixture setup/teardown, yield from cleanup, or transitive utility call chains (caller calls modified helper)
  • Use a full file path only if ALL tests in that file require verification
  • Use file path + test name when only specific tests use an affected fixture or utility wrapper (preferred for partial file impact)
  • If a test marker can cover multiple files/tests, provide the marker
  • Balance coverage vs over-testing - Keep descriptions minimal
  • Example: if leaf helper foo() changes, include tests whose fixture teardown calls wrapper bar() where bar() calls foo(), even when the test body only imports an unrelated symbol from the same utilities module

Hardware-Related Checks (SR-IOV, GPU, DPDK):

When PR modifies fixtures for hardware-specific resources:

  • Collection Safety: Fixtures MUST have existence checks (return None when hardware unavailable)
  • Test Plan: MUST verify both WITH and WITHOUT hardware:
    • Run affected tests on cluster WITH hardware
    • Verify collection succeeds on cluster WITHOUT hardware

CRITICAL WORKFLOW COMPLETION RULES:

When responding to this test execution plan request, you MUST follow these rules EXACTLY:

  1. YOUR ONLY DELIVERABLE: Post one non-blocking inline comment containing the test execution plan on the first changed line
  2. THEN STOP IMMEDIATELY - Do NOT generate any additional response
  3. FALLBACK ONLY: If inline comment API calls fail after retrying, post as a regular PR comment
  4. SILENCE = SUCCESS: After successfully submitting the review, your task is complete. No confirmation needed.

ABSOLUTE PROHIBITIONS (violating these creates empty/meaningless reviews):

  • ❌ Do NOT post acknowledgment messages like "Test execution plan posted", "Review posted successfully", "I've successfully posted"
  • ❌ Do NOT mention review IDs, URLs, or confirmation of posting in the PR thread
  • ❌ Do NOT add any follow-up comments after submitting the review
  • ❌ Do NOT reply to confirm task completion
  • ❌ Do NOT explain what you did - just do it and stop

Remember: The pull request review is visible to users. Additional comments are redundant noise.

CRITICAL — Verification rules for smoke and gating decisions:
- For each True decision, you must have traced a concrete path: test file → fixture → utility function → modified symbol.
- Do NOT assume impact based on scope, file proximity, or infrastructure-sounding names.
Only set True when you trace a concrete path through steps 6a-6e, then 7 or 8.
WRONG: "utilities/hco.py changed → run smoke" (no traced dependency path)
WRONG: "Run smoke tests: False" with no affected-test list when gating tests use the changed utility
RIGHT: "Run smoke tests: False, Run gating tests: True" plus tests/.../test_foo.py::TestBar::test_baz (gating)
RIGHT: "Smoke test X uses fixture Y → wrapper Z → changed function W in utilities/hco.py"

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="RedHatQE/openshift-virtualization-tests"
pr="5212"

# Ensure we are on the PR branch clone; derive merge-base against base branch.
base_ref=$(gh pr view "$pr" --repo "$repo" --json baseRefName --jq '.baseRefName')
head_sha=$(gh pr view "$pr" --repo "$repo" --json headRefOid --jq '.headRefOid')

git fetch --quiet origin "$base_ref"
merge_base=$(git merge-base "origin/$base_ref" HEAD)

echo "BASE_REF=$base_ref"
echo "HEAD_SHA=$head_sha"
echo "MERGE_BASE=$merge_base"

echo
echo "=== Changed files ==="
git diff --name-only "$merge_base"...HEAD

echo
echo "=== First changed file and first changed line ==="
first_file=$(git diff --name-only "$merge_base"...HEAD | head -n1)
echo "FIRST_FILE=$first_file"

python - <<'PY' "$merge_base" "$first_file"
import re, subprocess, sys
base, path = sys.argv[1], sys.argv[2]
patch = subprocess.check_output(["git", "diff", "--unified=0", f"{base}...HEAD", "--", path], text=True)
line = None
for ln in patch.splitlines():
    m = re.match(r'^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@', ln)
    if m:
        start = int(m.group(1))
        # first changed content line on RIGHT side is the hunk start
        line = start
        break
if line is None:
    raise SystemExit("Could not determine first changed line")
print(f"FIRST_LINE={line}")
PY

first_line=$(python - <<'PY' "$merge_base" "$first_file"
import re, subprocess, sys
base, path = sys.argv[1], sys.argv[2]
patch = subprocess.check_output(["git", "diff", "--unified=0", f"{base}...HEAD", "--", path], text=True)
for ln in patch.splitlines():
    m = re.match(r'^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@', ln)
    if m:
        print(m.group(1))
        break
PY
)

echo
echo "=== Modified symbols in libs and changed test helpers ==="
for f in libs/net/vmspec.py libs/vm/spec.py libs/vm/vm.py tests/network/l2_bridge/nad_ref_change/lib_helpers.py tests/network/l2_bridge/nad_ref_change/conftest.py tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py; do
  if [ -f "$f" ]; then
    echo "--- $f"
    ast-grep outline "$f" --view expanded || true
    echo "DIFF SYMBOL LINES:"
    git diff --unified=0 "$merge_base"...HEAD -- "$f" | grep -nE '^[+-](def |class |`@pytest`\.fixture|[[:space:]]+[A-Za-z_][A-Za-z0-9_]*\s*:)' || true
    echo
  fi
done

echo
echo "=== Direct symbol searches in tests and conftest ==="
# Modified / introduced symbols based on summaries and expected diff
symbols=(
  patch_nad_references
  update_nad_references
  wait_for_vmi_condition_status
  verify_baseline_connectivity
  non_migratable_bridge_vm
  disk_and_volume_for_data_volume
  DataVolumeRef
)
for sym in "${symbols[@]}"; do
  echo "--- symbol: $sym"
  rg -n -C2 --glob 'tests/**' --glob 'tests/**/conftest.py' --glob 'tests/**/*.py' "\\b${sym}\\b" tests || true
  echo
done

echo
echo "=== Same-file callers / transitive expansion ==="
for f in libs/net/vmspec.py libs/vm/vm.py tests/network/l2_bridge/nad_ref_change/lib_helpers.py tests/network/l2_bridge/nad_ref_change/conftest.py; do
  echo "--- $f"
  rg -n -C2 'patch_nad_references|update_nad_references|verify_baseline_connectivity|non_migratable_bridge_vm|disk_and_volume_for_data_volume|DataVolumeRef' "$f" || true
  echo
done

echo
echo "=== Changed file content slices ==="
sed -n '1,260p' tests/network/l2_bridge/nad_ref_change/conftest.py
echo
sed -n '1,260p' tests/network/l2_bridge/nad_ref_change/lib_helpers.py
echo
sed -n '1,320p' tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py
echo
sed -n '1,240p' libs/net/vmspec.py
echo
sed -n '1,220p' libs/vm/spec.py
echo
sed -n '1,220p' libs/vm/vm.py

echo
echo "=== Smoke and gating files ==="
echo "--- smoke"
rg -l '`@pytest`\.mark\.smoke' tests || true
echo "--- gating"
rg -l '`@pytest`\.mark\.gating' tests || true

echo
echo "=== Intersections / hierarchy checks for smoke and gating ==="
python - <<'PY'
from pathlib import Path
import subprocess, re

# Collect changed symbols and observed affected files manually from rg-based searches
def run(cmd):
    return subprocess.check_output(cmd, text=True)

smoke_files = [p for p in run(["rg", "-l", r"`@pytest`\.mark\.smoke", "tests"]).splitlines() if p.strip()]
gating_files = [p for p in run(["rg", "-l", r"`@pytest`\.mark\.gating", "tests"]).splitlines() if p.strip()]

# Candidate affected files based on direct known usages from this PR
affected = set()
for pattern in [
    r"\bverify_baseline_connectivity\b",
    r"\bnon_migratable_bridge_vm\b",
    r"\bpatch_nad_references\b",
]:
    out = run(["rg", "-l", pattern, "tests"])
    affected.update([p for p in out.splitlines() if p.strip()])

# Conftest hierarchy check for smoke files using modified symbols
symbols = ["patch_nad_references", "verify_baseline_connectivity", "non_migratable_bridge_vm"]
hier_hits = {}
for smoke in smoke_files:
    p = Path(smoke).parent
    checked = []
    while True:
        cf = p / "conftest.py"
        if cf.exists():
            txt = cf.read_text()
            for s in symbols:
                if re.search(rf"\b{s}\b", txt):
                    hier_hits.setdefault(smoke, []).append((str(cf), s))
        checked.append(str(cf))
        if str(p) == ".":
            break
        p = p.parent

print("SMOKE_FILES_COUNT", len(smoke_files))
print("GATING_FILES_COUNT", len(gating_files))
print("AFFECTED_DIRECT", sorted(affected))
print("SMOKE_HIER_HITS", hier_hits)
print("AFFECTED_GATING", sorted(set(gating_files) & affected))
PY

echo
echo "=== Build test plan body ==="
python - <<'PY' > /tmp/test_plan_comment.txt
from pathlib import Path
import subprocess, re, sys

def run(*cmd):
    return subprocess.check_output(list(cmd), text=True)

# Verify actual dependency paths from repo content
conftest = Path("tests/network/l2_bridge/nad_ref_change/conftest.py").read_text()
helpers = Path("tests/network/l2_bridge/nad_ref_change/lib_helpers.py").read_text()
test_file = Path("tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py").read_text()

# Determine smoke/gating usage
smoke_files = [p for p in run("rg", "-l", r"`@pytest`\.mark\.smoke", "tests").splitlines() if p.strip()]
gating_files = [p for p in run("rg", "-l", r"`@pytest`\.mark\.gating", "tests").splitlines() if p.strip()]

affected_tests = []
paths = []

# Direct affected tests from changed test file
if "test_non_migratable_vm_nad_change_not_applied" in test_file:
    affected_tests.append("tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::test_non_migratable_vm_nad_change_not_applied")
    paths.append("test_non_migratable_vm_nad_change_not_applied → non_migratable_baseline_connectivity fixture → verify_baseline_connectivity() / non_migratable_under_test_vm fixture → non_migratable_bridge_vm() → libs.vm.vm.disk_and_volume_for_data_volume()")

# Same file changed tests using shared baseline helper
if "verify_baseline_connectivity" in conftest:
    affected_tests.append("tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::test_migratable_vm_nad_change_applied")
    paths.append("test_migratable_vm_nad_change_applied → baseline_connectivity fixture → verify_baseline_connectivity()")

# patch_nad_references is only used in this changed test file
if "patch_nad_references" in test_file:
    paths.append("test_non_migratable_vm_nad_change_not_applied → libs.net.vmspec.patch_nad_references()")

# Determine smoke impact: require verified path to smoke file or smoke conftest hierarchy usage
smoke_true = False
smoke_reason = ""
symbols = ["patch_nad_references", "verify_baseline_connectivity", "non_migratable_bridge_vm"]
for smoke in smoke_files:
    p = Path(smoke).parent
    while True:
        cf = p / "conftest.py"
        if cf.exists():
            txt = cf.read_text()
            for s in symbols:
                if re.search(rf"\b{s}\b", txt):
                    smoke_true = True
                    smoke_reason = f"{smoke} → {cf} → {s}"
                    break
        if smoke_true or str(p) == ".":
            break
        p = p.parent
    if smoke_true:
        break

# Determine gating impact: any gating-marked file in affected set
affected_set = set()
for pat in [r"\bverify_baseline_connectivity\b", r"\bnon_migratable_bridge_vm\b", r"\bpatch_nad_references\b"]:
    out = run("rg", "-l", pat, "tests")
    affected_set.update([p for p in out.splitlines() if p.strip()])
gating_hits = sorted(set(gating_files) & affected_set)
gating_true = bool(gating_hits)
gating_reason = ""
if gating_true:
    # build specific path for first gating hit if available
    g = gating_hits[0]
    if g == "tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py":
        gating_reason = "tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::test_non_migratable_vm_nad_change_not_applied (gating) → non_migratable_baseline_connectivity / non_migratable_under_test_vm → verify_baseline_connectivity() / non_migratable_bridge_vm() / patch_nad_references()"
    else:
        gating_reason = g

comment = []
comment.append("**Test Execution Plan**")
comment.append("")
comment.append(f"- **Run smoke tests:** {'True' if smoke_true else 'False'}" + (f" — {smoke_reason}" if smoke_true else " — no verified dependency path from any smoke-marked test or parent `conftest.py` to the modified symbols."))
comment.append(f"- **Run gating tests:** {'True' if gating_true else 'False'}" + (f" — {gating_reason}" if gating_true else ""))
comment.append("")
comment.append("**Affected tests to run**")
for t in affected_tests:
    marker = " (gating)" if "non_migratable" in t else ""
    comment.append(f"- `{t}`{marker}")
comment.append("")
comment.append("**Real tests (cluster required)**")
comment.append("Error path (the fix):")
comment.append("`uv run pytest --verbose -rs -s -o log_cli=true --leftovers-collector --tb=native --skip-deprecated-api-test --cluster-sanity-skip-storage-check tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::test_non_migratable_vm_nad_change_not_applied`")
comment.append("Expected: the VM NAD patch does not apply live; connectivity stays on VLAN-A and not VLAN-B. When KubeVirt wires `RestartRequired`, re-enable the wait and expect that condition before connectivity checks.")
comment.append("")
comment.append("Happy path (regression):")
comment.append("`uv run pytest --verbose -rs -s -o log_cli=true --leftovers-collector --tb=native --skip-deprecated-api-test --cluster-sanity-skip-storage-check tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::test_migratable_vm_nad_change_applied`")
comment.append("Expected: the normal migratable NAD update path still applies live and connectivity switches as expected.")
print("\n".join(comment))
PY

cat /tmp/test_plan_comment.txt

echo
echo "=== Post inline review comment ==="
# Try submit as a single COMMENT review with one inline comment.
gh api \
  --method POST \
  -H "Accept: application/vnd.github+json" \
  "/repos/$repo/pulls/$pr/reviews" \
  -f body='' \
  -f event='COMMENT' \
  -f commit_id="$head_sha" \
  -f "comments[0][path]=$first_file" \
  -F "comments[0][line]=$first_line" \
  -f "comments[0][side]=RIGHT" \
  -F "comments[0][body]=`@openshift-virtualization-qe-bot-3` $(cat /tmp/test_plan_comment.txt)" \
  >/tmp/review_response.json || {
    echo "Inline review creation failed, retrying with issue comment fallback" >&2
    gh pr comment "$pr" --repo "$repo" --body "`@openshift-virtualization-qe-bot-3` $(cat /tmp/test_plan_comment.txt)"
  }

echo "DONE"

Length of output: 85114


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="RedHatQE/openshift-virtualization-tests"
pr="5212"
head_sha=$(gh pr view "$pr" --repo "$repo" --json headRefOid --jq '.headRefOid')
first_file="libs/net/vmspec.py"
first_line="309"

echo "=== Inspect direct callers and markers for localnet NAD ref change ==="
sed -n '1,260p' tests/network/localnet/nad_ref_change/test_nad_ref_change.py
echo
echo "--- smoke marker in affected files"
rg -n '`@pytest`\.mark\.smoke|`@pytest`\.mark\.gating' \
  tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py \
  tests/network/localnet/nad_ref_change/test_nad_ref_change.py || true

cat > /tmp/test_plan_comment.txt <<'EOF'
**Test Execution Plan**

- **Run smoke tests:** False — no verified dependency path from any smoke-marked test or parent `conftest.py` to the modified symbols in `libs/net/vmspec.py`, `libs/vm/spec.py`, `libs/vm/vm.py`, or the changed NAD-ref-change helpers/fixtures.
- **Run gating tests:** False — no gating-marked test file appears in the affected set traced from `patch_nad_references()`, `update_nad_references()`, `verify_baseline_connectivity()`, `non_migratable_bridge_vm()`, and `disk_and_volume_for_data_volume()`.

**Affected tests to run**
- `tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::TestRunningVMLinuxBridgeVlanChange::test_vm_state_iface_info_preserved`
- `tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::TestRunningVMLinuxBridgeVlanChange::test_connectivity`
- `tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::TestRunningVMLinuxBridgeVlanChange::test_two_networks`
- `tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::test_non_migratable_vm_nad_change_not_applied`
- `tests/network/localnet/nad_ref_change/test_nad_ref_change.py`

Dependency paths:
- `tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::test_non_migratable_vm_nad_change_not_applied` → `non_migratable_baseline_connectivity` / `non_migratable_under_test_vm` fixtures in `tests/network/l2_bridge/nad_ref_change/conftest.py` → `verify_baseline_connectivity()` / `non_migratable_bridge_vm()` in `tests/network/l2_bridge/nad_ref_change/lib_helpers.py` → `disk_and_volume_for_data_volume()` in `libs/vm/vm.py`, plus direct call to `patch_nad_references()` in `libs/net/vmspec.py`
- `tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::TestRunningVMLinuxBridgeVlanChange::*` → `baseline_connectivity` fixture in `tests/network/l2_bridge/nad_ref_change/conftest.py` → `verify_baseline_connectivity()` in `tests/network/l2_bridge/nad_ref_change/lib_helpers.py`; the tests also call `update_nad_references()` in `libs/net/vmspec.py`
- `tests/network/localnet/nad_ref_change/test_nad_ref_change.py` → direct call(s) to `update_nad_references()` in `libs/net/vmspec.py`, which now delegate to `patch_nad_references()`

**Real tests (cluster required)**
Error path (the fix):
`uv run pytest --verbose -rs -s -o log_cli=true --leftovers-collector --tb=native --skip-deprecated-api-test --cluster-sanity-skip-storage-check tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::test_non_migratable_vm_nad_change_not_applied`
Expected: the VM NAD patch does not apply live; connectivity stays on VLAN-A and not VLAN-B. Once KubeVirt wires `RestartRequired`, the condition wait should also pass before the connectivity assertions.

Happy path (regression):
`uv run pytest --verbose -rs -s -o log_cli=true --leftovers-collector --tb=native --skip-deprecated-api-test --cluster-sanity-skip-storage-check tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::TestRunningVMLinuxBridgeVlanChange`
Expected: the normal migratable NAD update path still applies live and connectivity follows the updated NAD references.

Additional regression for the refactored shared helper:
`uv run pytest --verbose -rs -s -o log_cli=true --leftovers-collector --tb=native --skip-deprecated-api-test --cluster-sanity-skip-storage-check tests/network/localnet/nad_ref_change/test_nad_ref_change.py`
Expected: the localnet NAD reference update path still applies live after `update_nad_references()` was refactored to call `patch_nad_references()`.
EOF

python - <<'PY'
import json, pathlib
body = pathlib.Path("/tmp/test_plan_comment.txt").read_text()
payload = {
    "body": "",
    "event": "COMMENT",
    "commit_id": "440085587c13fb65d210f9684f3d781d8e5841fd",
    "comments": [
        {
            "path": "libs/net/vmspec.py",
            "line": 309,
            "side": "RIGHT",
            "body": body,
        }
    ],
}
pathlib.Path("/tmp/review_payload.json").write_text(json.dumps(payload))
print(body)
PY

echo
echo "=== Post inline review comment via raw JSON ==="
gh api \
  --method POST \
  -H "Accept: application/vnd.github+json" \
  "/repos/$repo/pulls/$pr/reviews" \
  --input /tmp/review_payload.json

Length of output: 12236


🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="RedHatQE/openshift-virtualization-tests"
pr="5212"

cat > /tmp/test_plan_comment.txt <<'EOF'
**Test Execution Plan**

- **Run smoke tests:** False — no verified dependency path from any smoke-marked test or parent `conftest.py` to the modified symbols in `libs/net/vmspec.py`, `libs/vm/spec.py`, `libs/vm/vm.py`, or the changed NAD-ref-change helpers/fixtures.
- **Run gating tests:** False — no gating-marked test file appears in the affected set traced from `patch_nad_references()`, `update_nad_references()`, `verify_baseline_connectivity()`, `non_migratable_bridge_vm()`, and `disk_and_volume_for_data_volume()`.

**Affected tests to run**
- `tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::TestRunningVMLinuxBridgeVlanChange::test_vm_state_iface_info_preserved`
- `tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::TestRunningVMLinuxBridgeVlanChange::test_connectivity`
- `tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::TestRunningVMLinuxBridgeVlanChange::test_two_networks`
- `tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::test_non_migratable_vm_nad_change_not_applied`
- `tests/network/localnet/nad_ref_change/test_nad_ref_change.py`

Dependency paths:
- `tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::test_non_migratable_vm_nad_change_not_applied` → `non_migratable_baseline_connectivity` / `non_migratable_under_test_vm` fixtures in `tests/network/l2_bridge/nad_ref_change/conftest.py` → `verify_baseline_connectivity()` / `non_migratable_bridge_vm()` in `tests/network/l2_bridge/nad_ref_change/lib_helpers.py` → `disk_and_volume_for_data_volume()` in `libs/vm/vm.py`, plus direct call to `patch_nad_references()` in `libs/net/vmspec.py`
- `tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::TestRunningVMLinuxBridgeVlanChange::*` → `baseline_connectivity` fixture in `tests/network/l2_bridge/nad_ref_change/conftest.py` → `verify_baseline_connectivity()` in `tests/network/l2_bridge/nad_ref_change/lib_helpers.py`; the tests also call `update_nad_references()` in `libs/net/vmspec.py`
- `tests/network/localnet/nad_ref_change/test_nad_ref_change.py` → direct call(s) to `update_nad_references()` in `libs/net/vmspec.py`, which now delegate to `patch_nad_references()`

**Real tests (cluster required)**
Error path (the fix):
`uv run pytest --verbose -rs -s -o log_cli=true --leftovers-collector --tb=native --skip-deprecated-api-test --cluster-sanity-skip-storage-check tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::test_non_migratable_vm_nad_change_not_applied`
Expected: the VM NAD patch does not apply live; connectivity stays on VLAN-A and not VLAN-B. Once KubeVirt wires `RestartRequired`, the condition wait should also pass before the connectivity assertions.

Happy path (regression):
`uv run pytest --verbose -rs -s -o log_cli=true --leftovers-collector --tb=native --skip-deprecated-api-test --cluster-sanity-skip-storage-check tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::TestRunningVMLinuxBridgeVlanChange`
Expected: the normal migratable NAD update path still applies live and connectivity follows the updated NAD references.

Additional regression for the refactored shared helper:
`uv run pytest --verbose -rs -s -o log_cli=true --leftovers-collector --tb=native --skip-deprecated-api-test --cluster-sanity-skip-storage-check tests/network/localnet/nad_ref_change/test_nad_ref_change.py`
Expected: the localnet NAD reference update path still applies live after `update_nad_references()` was refactored to call `patch_nad_references()`.
EOF

python - <<'PY'
import json, pathlib
payload = {"body": pathlib.Path("/tmp/test_plan_comment.txt").read_text()}
pathlib.Path("/tmp/issue_comment_payload.json").write_text(json.dumps(payload))
PY

gh api \
  --method POST \
  -H "Accept: application/vnd.github+json" \
  "/repos/$repo/issues/$pr/comments" \
  --input /tmp/issue_comment_payload.json

Length of output: 4498


@openshift-virtualization-qe-bot-3

CRITICAL: I could not post the required inline review comment because the GitHub integration for this run does not have write access to the PR. All three write paths failed with 403 Resource not accessible by integration:

  • inline review comment API
  • regular PR comment API
  • issue comment REST API

That rule exists to ensure the plan is attached to the actual diff, but here the permission boundary is the blocker, not the analysis.

Here is the exact test execution plan that should be posted on libs/net/vmspec.py, Line 309:

Test Execution Plan

  • Run smoke tests: False — no verified dependency path from any smoke-marked test or parent conftest.py to the modified symbols in libs/net/vmspec.py, libs/vm/spec.py, libs/vm/vm.py, or the changed NAD-ref-change helpers/fixtures.
  • Run gating tests: False — no gating-marked test file appears in the affected set traced from patch_nad_references(), update_nad_references(), verify_baseline_connectivity(), non_migratable_bridge_vm(), and disk_and_volume_for_data_volume().

Affected tests to run

  • tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::TestRunningVMLinuxBridgeVlanChange::test_vm_state_iface_info_preserved
  • tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::TestRunningVMLinuxBridgeVlanChange::test_connectivity
  • tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::TestRunningVMLinuxBridgeVlanChange::test_two_networks
  • tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::test_non_migratable_vm_nad_change_not_applied
  • tests/network/localnet/nad_ref_change/test_nad_ref_change.py

Dependency paths:

  • tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::test_non_migratable_vm_nad_change_not_appliednon_migratable_baseline_connectivity / non_migratable_under_test_vm fixtures in tests/network/l2_bridge/nad_ref_change/conftest.pyverify_baseline_connectivity() / non_migratable_bridge_vm() in tests/network/l2_bridge/nad_ref_change/lib_helpers.pydisk_and_volume_for_data_volume() in libs/vm/vm.py, plus direct call to patch_nad_references() in libs/net/vmspec.py
  • tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::TestRunningVMLinuxBridgeVlanChange::*baseline_connectivity fixture in tests/network/l2_bridge/nad_ref_change/conftest.pyverify_baseline_connectivity() in tests/network/l2_bridge/nad_ref_change/lib_helpers.py; the tests also call update_nad_references() in libs/net/vmspec.py
  • tests/network/localnet/nad_ref_change/test_nad_ref_change.py → direct call(s) to update_nad_references() in libs/net/vmspec.py, which now delegate to patch_nad_references()

Real tests (cluster required)
Error path (the fix):
uv run pytest --verbose -rs -s -o log_cli=true --leftovers-collector --tb=native --skip-deprecated-api-test --cluster-sanity-skip-storage-check tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::test_non_migratable_vm_nad_change_not_applied
Expected: the VM NAD patch does not apply live; connectivity stays on VLAN-A and not VLAN-B. Once KubeVirt wires RestartRequired, the condition wait should also pass before the connectivity assertions.

Happy path (regression):
uv run pytest --verbose -rs -s -o log_cli=true --leftovers-collector --tb=native --skip-deprecated-api-test --cluster-sanity-skip-storage-check tests/network/l2_bridge/nad_ref_change/test_nad_ref_change.py::TestRunningVMLinuxBridgeVlanChange
Expected: the normal migratable NAD update path still applies live and connectivity follows the updated NAD references.

Additional regression for the refactored shared helper:
uv run pytest --verbose -rs -s -o log_cli=true --leftovers-collector --tb=native --skip-deprecated-api-test --cluster-sanity-skip-storage-check tests/network/localnet/nad_ref_change/test_nad_ref_change.py
Expected: the localnet NAD reference update path still applies live after update_nad_references() was refactored to call patch_nad_references().

@azhivovk

Copy link
Copy Markdown
Contributor Author

@coderabbitai I ran the new test without the condition waiting to make sure it'll pass once we unblock it
All other requested verification is done #5212 (comment)

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

@azhivovk Thanks — that closes the verification gap for me.

  • CRITICAL: No blocker here from the requested verification.
  • HIGH: Running test_non_migratable_vm_nad_change_not_applied without the RestartRequired wait is the right interim check, because that wait is currently blocked by upstream KubeVirt behavior rather than by this test’s logic. That rule exists to catch product/runtime state transitions, not to mask whether the connectivity assertions are correct.
  • LOW: Once RestartRequired is wired up, the only follow-up is to restore that wait and rerun the same test path end-to-end.

The linked verification comment covers the regression path I was concerned about as well. 🐇

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.

10 participants