Skip to content

feat: Add simultaneous copyoffload migrations test - #285

Merged
myakove merged 14 commits into
RedHatQE:mainfrom
elsapassaro:simultaneous_migration_plans
Feb 5, 2026
Merged

myakove merged 14 commits into
RedHatQE:mainfrom
elsapassaro:simultaneous_migration_plans

Conversation

@elsapassaro

@elsapassaro elsapassaro commented Jan 30, 2026 •

Copy link
Copy Markdown
Collaborator

User description

Summary

This PR adds a new test to validate simultaneous execution of two copyoffload migration plans with MTV. The test demonstrates the ability to run multiple migrations concurrently, which is important for validating MTV's scalability and concurrent migration handling.

Changes

Configuration (tests/tests_config/config.py)

  • Added test_simultaneous_copyoffload_migrations configuration that defines two VMs to be cloned from the same template (xcopy-template-test)

Test Suite (tests/test_copyoffload_migration.py)

  • Added TestSimultaneousCopyoffloadMigrations class with 10 test methods:
    • Creates separate StorageMap and NetworkMap resources for each plan
    • Creates two MTV Plan CRs with copyoffload configuration
    • Executes both migrations simultaneously by creating Migration CRs back-to-back
    • Validates both migrated VMs independently

Reusable Fixtures (conftest.py)

  • Added prepared_plan_1 fixture: Extracts first VM from multi-VM prepared plan
  • Added prepared_plan_2 fixture: Extracts second VM from multi-VM prepared plan
  • These fixtures enable future tests to easily run simultaneous migrations

Key Features

  • ✅ Both plans use the same source template with cloning enabled
  • ✅ Each plan gets an independent VM with unique name via cloning mechanism
  • ✅ Independent StorageMap and NetworkMap resources per plan
  • ✅ Copyoffload acceleration enabled for both migrations
  • ✅ Simultaneous execution pattern (create both Migration CRs then wait)
  • ✅ Follows all CLAUDE.md guidelines (type annotations, docstrings, no linter errors)

Test Execution

# Run the new test
uv run pytest tests/test_copyoffload_migration.py::TestSimultaneousCopyoffloadMigrations -v

# Or run with copyoffload marker
uv run pytest -m copyoffload -v

Prerequisites

This test requires:

  • vSphere environment with copy-offload capable storage
  • Two VMs will be cloned from xcopy-template-test template
  • Standard copyoffload configuration in .providers.json

Testing Checklist

  • All pre-commit hooks pass
  • No linter errors
  • Type annotations complete
  • Docstrings follow required format
  • Fixtures properly scoped and documented

PR Type

Tests, Enhancement


Description

  • Add test class for simultaneous copyoffload migration execution

  • Create two independent migration plans with separate storage/network maps

  • Execute both migrations concurrently and validate results independently

  • Add reusable fixtures for multi-VM simultaneous migration testing


Diagram Walkthrough

flowchart LR
  config["Config: Two cloned VMs"]
  fixture1["prepared_plan_1 fixture"]
  fixture2["prepared_plan_2 fixture"]
  storage1["StorageMap Plan 1"]
  storage2["StorageMap Plan 2"]
  network1["NetworkMap Plan 1"]
  network2["NetworkMap Plan 2"]
  plan1["Plan Resource 1"]
  plan2["Plan Resource 2"]
  migration1["Migration 1"]
  migration2["Migration 2"]
  validate1["Validate Plan 1 VMs"]
  validate2["Validate Plan 2 VMs"]
  
  config --> fixture1
  config --> fixture2
  fixture1 --> storage1
  fixture1 --> network1
  fixture2 --> storage2
  fixture2 --> network2
  storage1 --> plan1
  network1 --> plan1
  storage2 --> plan2
  network2 --> plan2
  plan1 --> migration1
  plan2 --> migration2
  migration1 --> validate1
  migration2 --> validate2
Loading

File Walkthrough

Relevant files
Tests
conftest.py
Add fixtures for simultaneous migration plan preparation 

conftest.py

  • Added prepared_plan_1 fixture to extract first VM from multi-VM
    prepared plan
  • Added prepared_plan_2 fixture to extract second VM from multi-VM
    prepared plan
  • Both fixtures create independent plan configurations for simultaneous
    migrations
  • Fixtures use deep copy to avoid state sharing between plans
+46/-0   
test_copyoffload_migration.py
Add simultaneous copyoffload migration test class               

tests/test_copyoffload_migration.py

  • Added TestSimultaneousCopyoffloadMigrations class with 10 test methods
  • Implemented separate StorageMap and NetworkMap creation for each plan
  • Created independent Plan CR resources for both migrations
  • Implemented simultaneous Migration CR execution pattern
  • Added validation tests for both migrated VMs independently
  • Imported Migration resource and utility functions for concurrent
    execution
+321/-0 
Configuration changes
config.py
Add simultaneous copyoffload migrations test configuration

tests/tests_config/config.py

  • Added test_simultaneous_copyoffload_migrations configuration entry
  • Defined two virtual machines cloned from xcopy-template-test template
  • Configured both VMs with thin disk type and guest agent enabled
  • Set copyoffload flag to True and warm_migration to False
+20/-0   

Summary by CodeRabbit

  • Tests

    • Added fixtures and a new test suite to orchestrate and validate two simultaneous copy-offload migrations, including per-plan storage/network mapping, parallel migration polling, and per-plan VM/disk verification; test configs include a two-VM copy-offload scenario.
  • Bug Fixes

    • Treat missing additional-disk entries as zero added disks to prevent errors during migration checks.
  • Chores

    • Removed empty disk declarations from several test configurations.
  • Refactor

    • Added a utility to extract a single VM plan for isolated per-VM fixtures.

@coderabbitai

coderabbitai Bot commented Jan 30, 2026 •

Copy link
Copy Markdown
Contributor

Walkthrough

Adds a utility to extract a single VM from a multi‑VM prepared plan, two class‑scoped fixtures exposing single‑VM plans, a new test class to run two copy‑offload migrations concurrently (per‑plan maps/plans and two Migration CRs), updates test configs, and treats missing add_disks as empty.

Changes

Cohort / File(s) Summary
Test Fixtures
conftest.py
Import extract_vm_from_plan and add class‑scoped fixtures prepared_plan_1 and prepared_plan_2 that return single‑VM prepared plans (vm_index 0 and 1).
Simultaneous Migration Tests
tests/test_copyoffload_migration.py
Add TestSimultaneousCopyoffloadMigrations with per‑plan StorageMap/NetworkMap/Plan attributes and tests to create both plans, start two Migration CRs concurrently via create_and_store_resource, poll with TimeoutSampler/wait_for_migration_complate, assert simultaneous executing state, and validate migrated VM names and disk counts. Add EARLY_COMPLETION_MSG and typing/import adjustments (TimeoutSampler, Any, DynamicClient).
Test Configuration
tests/tests_config/config.py
Remove empty add_disks: [] from several test cases and add test_simultaneous_copyoffload_migrations config with two identical VM entries (copyoffload: True, warm_migration: False).
Migration Utility
utilities/mtv_migration.py
Use vm_config.get("add_disks", []) instead of direct indexing so missing add_disks is treated as no added disks.
Utilities
utilities/utils.py
Add extract_vm_from_plan(prepared_plan, vm_index, fixture_name) which deep‑copies and prunes a prepared plan to a single VM, validating index and presence in source_vms_data; raises ValueError on invalid inputs.

Sequence Diagram(s)

sequenceDiagram
    participant TestSuite as TestSuite
    participant Store as fixture_store
    participant OCP as ocp_admin_client
    participant PlanCR as Plan CRs
    participant MigCR as Migration CRs

    TestSuite->>Store: create StorageMap (plan1)
    TestSuite->>Store: create StorageMap (plan2)
    TestSuite->>Store: create NetworkMap (plan1)
    TestSuite->>Store: create NetworkMap (plan2)
    TestSuite->>PlanCR: create Plan CR (plan1)
    TestSuite->>PlanCR: create Plan CR (plan2)
    TestSuite->>OCP: create Migration CR (mig1)
    TestSuite->>OCP: create Migration CR (mig2)
    par Parallel migrations
        MigCR->>OCP: execute copy-offload for VM (plan1)
        MigCR->>OCP: execute copy-offload for VM (plan2)
    end
    TestSuite->>OCP: wait_for_migration_complate(mig1)
    TestSuite->>OCP: wait_for_migration_complate(mig2)
    TestSuite->>OCP: validate migrated VM names & disk counts (plan1)
    TestSuite->>OCP: validate migrated VM names & disk counts (plan2)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title directly describes the main feature addition: a new test for simultaneous copyoffload migrations, which is the core objective across conftest.py, test_copyoffload_migration.py, and config.py changes.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@redhat-qe-bot

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: 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 build-container - Rebuild and test container image
  • /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

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

Reviewers:

  • krcmarik
  • myakove
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
  • 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.

@qodo-code-review

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Index Safety

The new fixtures assume the prepared plan always contains at least two VMs and will raise IndexError if configuration or upstream fixtures provide fewer VMs. Add explicit validation (e.g., assert/skip with a clear message) to make failures actionable and avoid confusing fixture errors.

@pytest.fixture(scope="class")
def prepared_plan_1(prepared_plan: dict[str, Any]) -> Generator[dict[str, Any], None, None]:
    """Prepare first migration plan configuration for simultaneous migrations.

    This fixture extracts the first VM from a prepared plan containing multiple VMs,
    creating an independent plan for the first migration. Use this with prepared_plan_2
    to run two migrations simultaneously.

    Args:
        prepared_plan (dict[str, Any]): Base prepared plan with cloned VMs

    Yields:
        dict[str, Any]: Deep copy of prepared plan with first VM only
    """
    plan: dict[str, Any] = deepcopy(prepared_plan)
    # Use only the first VM for plan 1
    plan["virtual_machines"] = [plan["virtual_machines"][0]]
    # Copy source VM data for the first VM only
    vm_name = plan["virtual_machines"][0]["name"]
    plan["source_vms_data"] = {vm_name: prepared_plan["source_vms_data"][vm_name]}
    yield plan


@pytest.fixture(scope="class")
def prepared_plan_2(prepared_plan: dict[str, Any]) -> Generator[dict[str, Any], None, None]:
    """Prepare second migration plan configuration for simultaneous migrations.

    This fixture extracts the second VM from a prepared plan containing multiple VMs,
    creating an independent plan for the second migration. Use this with prepared_plan_1
    to run two migrations simultaneously.

    Args:
        prepared_plan (dict[str, Any]): Base prepared plan with cloned VMs

    Yields:
        dict[str, Any]: Deep copy of prepared plan with second VM only
    """
    plan: dict[str, Any] = deepcopy(prepared_plan)
    # Use only the second VM for plan 2
    plan["virtual_machines"] = [plan["virtual_machines"][1]]
    # Copy source VM data for the second VM only
    vm_name = plan["virtual_machines"][0]["name"]
    plan["source_vms_data"] = {vm_name: prepared_plan["source_vms_data"][vm_name]}
    yield plan
Not Concurrent

The “simultaneous” migration test currently creates both Migration CRs back-to-back but then waits for completion sequentially, which can mask concurrency issues (e.g., resource contention) and won’t detect cases where the second migration fails only when both are actively running. Consider waiting for both concurrently (or at least asserting both have started/running before awaiting completion).

def test_migrate_vms_simultaneously(
    self,
    fixture_store,
    ocp_admin_client,
    target_namespace,
):
    """Execute both copyoffload migrations simultaneously."""
    LOGGER.info("Starting simultaneous execution of both copyoffload migration plans")

    # Create Migration CR for first plan
    migration_1 = create_and_store_resource(
        client=ocp_admin_client,
        fixture_store=fixture_store,
        resource=Migration,
        namespace=target_namespace,
        plan_name=self.plan_resource_1.name,
        plan_namespace=self.plan_resource_1.namespace,
        test_name="simultaneous-copyoffload-migration1",
    )
    LOGGER.info(f"Created Migration CR for plan 1: {migration_1.name}")

    # Create Migration CR for second plan
    migration_2 = create_and_store_resource(
        client=ocp_admin_client,
        fixture_store=fixture_store,
        resource=Migration,
        namespace=target_namespace,
        plan_name=self.plan_resource_2.name,
        plan_namespace=self.plan_resource_2.namespace,
        test_name="simultaneous-copyoffload-migration2",
    )
    LOGGER.info(f"Created Migration CR for plan 2: {migration_2.name}")

    # Wait for both migrations to complete
    LOGGER.info("Waiting for both copyoffload migrations to complete")
    wait_for_migration_complate(plan=self.plan_resource_1)
    LOGGER.info("Copyoffload migration 1 completed")

    wait_for_migration_complate(plan=self.plan_resource_2)
    LOGGER.info("Copyoffload migration 2 completed")
Name Collision

Both entries use the same source template name; if cloning/name generation isn’t guaranteed unique or deterministic in the underlying preparation logic, the test may become flaky due to VM naming collisions. Ensure the cloning mechanism always produces unique destination names and that the test asserts uniqueness early.

"test_simultaneous_copyoffload_migrations": {
    "virtual_machines": [
        {
            "name": "xcopy-template-test",
            "source_vm_power": "off",
            "guest_agent": True,
            "clone": True,
            "disk_type": "thin",
        },
        {
            "name": "xcopy-template-test",
            "source_vm_power": "off",
            "guest_agent": True,
            "clone": True,
            "disk_type": "thin",
        },
    ],
    "warm_migration": False,
    "copyoffload": True,
},

💡 Tool usage guide:

Overview:
The review tool scans the PR code changes, and generates a PR review which includes several types of feedbacks, such as possible PR issues, security threats and relevant test in the PR. More feedbacks can be added by configuring the tool.

The tool can be triggered automatically every time a new PR is opened, or can be invoked manually by commenting on any PR.

  • When commenting, to edit configurations related to the review tool (pr_reviewer section), use the following template:
/review --pr_reviewer.some_config1=... --pr_reviewer.some_config2=...
[pr_reviewer]
some_config1=...
some_config2=...

See the review usage page for a comprehensive guide on using this tool.

@qodo-code-review

qodo-code-review Bot commented Jan 30, 2026 •

Copy link
Copy Markdown

PR Code Suggestions ✨

Latest suggestions up to 84bf948

CategorySuggestion                                                                                                                                    Impact
Possible bug
Validate concurrency via migration phases

To improve test reliability, validate migration concurrency by polling the
Migration CR's phase instead of the Plan CR's conditions, ensuring the resource
is refreshed on each poll and handling missing status fields.

tests/test_copyoffload_migration.py [3094-3128]

 # Validate both migrations are executing simultaneously before either completes
 LOGGER.info("Validating both migrations enter executing state simultaneously")
-plan_1_executing = False
-plan_2_executing = False
+mig_1_executing = False
+mig_2_executing = False
 both_executing_validated = False
 
-def _check_plan_status(plan: Plan) -> str:
-    """Check if plan is executing, succeeded, or failed.
+def _get_migration_phase(migration: Migration) -> str:
+    instance = migration.instance  # refresh
+    return (instance.get("status") or {}).get("phase") or "Pending"
 
-    Args:
-        plan: The Plan resource to check status for
+def _is_terminal(phase: str) -> bool:
+    # Align with MTV phases as exposed by Migration CR
+    return phase in ("Succeeded", "Failed")
 
-    Returns:
-        str: "Executing" if migration is in progress, Plan.Status.SUCCEEDED if completed successfully,
-            or Plan.Status.FAILED if migration failed
-
-    Raises:
-        AttributeError: If plan.instance.status.conditions is not accessible
-        KeyError: If condition dictionary is missing required keys (category, status, type)
-    """
-    for cond in plan.instance.status.conditions:
-        if cond["category"] == "Advisory" and cond["status"] == Plan.Condition.Status.TRUE:
-            cond_type = cond["type"]
-            if cond_type in (Plan.Status.SUCCEEDED, Plan.Status.FAILED):
-                return cond_type
-    return "Executing"
-
-# Poll both plans to ensure both reach executing state before either completes
-for sample in TimeoutSampler(
-    func=lambda: (_check_plan_status(self.plan_resource_1), _check_plan_status(self.plan_resource_2)),
+for phase_1, phase_2 in TimeoutSampler(
+    func=lambda: (_get_migration_phase(migration_1), _get_migration_phase(migration_2)),
     sleep=2,
     wait_timeout=120,
 ):
-    status_1, status_2 = sample
-    ...
+    if phase_1 == "Running" and not mig_1_executing:
+        LOGGER.info(f"Migration 1 '{migration_1.name}' is now running")
+        mig_1_executing = True
 
+    if phase_2 == "Running" and not mig_2_executing:
+        LOGGER.info(f"Migration 2 '{migration_2.name}' is now running")
+        mig_2_executing = True
+
+    if mig_1_executing and mig_2_executing and not both_executing_validated:
+        LOGGER.info("SUCCESS: Both migrations are executing simultaneously")
+        both_executing_validated = True
+        break
+
+    if _is_terminal(phase_1) and not both_executing_validated:
+        raise AssertionError(EARLY_COMPLETION_MSG.format(plan_num=1, completed_status=phase_1, other_status=phase_2))
+
+    if _is_terminal(phase_2) and not both_executing_validated:
+        raise AssertionError(EARLY_COMPLETION_MSG.format(plan_num=2, completed_status=phase_2, other_status=phase_1))
+
+assert both_executing_validated, "Failed to validate both migrations executing simultaneously"
+

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a potential flakiness in the test by polling the Plan status and proposes a more robust method of polling the Migration CR's phase, which directly reflects the execution state, thereby improving test reliability.

Medium
Possible issue
Ensure unique cloned VM names

In the test configuration for simultaneous migrations, assign a unique
clone_name to each virtual machine to prevent potential name collisions and
ensure deterministic behavior.

tests/tests_config/config.py [327-346]

 "test_simultaneous_copyoffload_migrations": {
     "virtual_machines": [
         {
             "name": "xcopy-template-test",
+            "clone_name": "xcopy-template-test-sim-1",
             "source_vm_power": "off",
             "guest_agent": True,
             "clone": True,
             "disk_type": "thin",
         },
         {
             "name": "xcopy-template-test",
+            "clone_name": "xcopy-template-test-sim-2",
             "source_vm_power": "off",
             "guest_agent": True,
             "clone": True,
             "disk_type": "thin",
         },
     ],
     "warm_migration": False,
     "copyoffload": True,
 },
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that relying on automatic unique name generation for clones can be fragile and proposes explicitly setting unique clone_name values, which makes the test configuration more deterministic and robust.

Medium
Maintainability
Reduce duplication with helpers

Refactor the nearly identical test_create_storagemap_plan1 and
test_create_storagemap_plan2 methods by extracting the common logic into a
private helper function to reduce code duplication and improve maintainability.

tests/test_copyoffload_migration.py [2747-2806]

-def test_create_storagemap_plan1(
+def _create_copyoffload_storagemap(
     self,
-    prepared_plan_1: dict[str, Any],
+    *,
+    prepared_plan: dict[str, Any],
     fixture_store: dict[str, Any],
     ocp_admin_client: "DynamicClient",
     source_provider: BaseProvider,
     destination_provider: OCPProvider,
     source_provider_inventory: ForkliftInventory,
     target_namespace: str,
     source_provider_data: dict[str, Any],
     copyoffload_storage_secret: Secret,
-) -> None:
-    ...
-    self.__class__.storage_map_1 = get_storage_migration_map(
+) -> StorageMap:
+    copyoffload_config_data = source_provider_data["copyoffload"]
+    storage_vendor_product = copyoffload_config_data["storage_vendor_product"]
+    datastore_id = copyoffload_config_data["datastore_id"]
+    storage_class = py_config["storage_class"]
+
+    vms_names = [vm["name"] for vm in prepared_plan["virtual_machines"]]
+    offload_plugin_config = {
+        "vsphereXcopyConfig": {
+            "secretRef": copyoffload_storage_secret.name,
+            "storageVendorProduct": storage_vendor_product,
+        }
+    }
+
+    return get_storage_migration_map(
         fixture_store=fixture_store,
         target_namespace=target_namespace,
         source_provider=source_provider,
         destination_provider=destination_provider,
         ocp_admin_client=ocp_admin_client,
         source_provider_inventory=source_provider_inventory,
         vms=vms_names,
         storage_class=storage_class,
         datastore_id=datastore_id,
         offload_plugin_config=offload_plugin_config,
         access_mode="ReadWriteOnce",
         volume_mode="Block",
     )
+
+def test_create_storagemap_plan1(... ) -> None:
+    self.__class__.storage_map_1 = self._create_copyoffload_storagemap(
+        prepared_plan=prepared_plan_1,
+        fixture_store=fixture_store,
+        ocp_admin_client=ocp_admin_client,
+        source_provider=source_provider,
+        destination_provider=destination_provider,
+        source_provider_inventory=source_provider_inventory,
+        target_namespace=target_namespace,
+        source_provider_data=source_provider_data,
+        copyoffload_storage_secret=copyoffload_storage_secret,
+    )
     assert self.storage_map_1, "StorageMap creation failed for plan 1"

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 5

__

Why: The suggestion correctly points out code duplication in the test methods for creating storage maps and proposes refactoring into a helper function, which improves code maintainability and reduces redundancy.

Low
  • More

Previous suggestions

Suggestions up to commit 84bf948
CategorySuggestion                                                                                                                                    Impact
Possible bug
Refresh and harden status polling

Refresh the Plan object state by calling plan.reload() and use defensive
dictionary access within the _check_plan_status function to prevent using stale
data and avoid potential errors during status polling.

tests/test_copyoffload_migration.py [3100-3119]

 def _check_plan_status(plan: Plan) -> str:
-    """Check if plan is executing, succeeded, or failed.
+    plan.reload()
 
-    Args:
-        plan: The Plan resource to check status for
-
-    Returns:
-        str: "Executing" if migration is in progress, Plan.Status.SUCCEEDED if completed successfully,
-            or Plan.Status.FAILED if migration failed
-
-    Raises:
-        AttributeError: If plan.instance.status.conditions is not accessible
-        KeyError: If condition dictionary is missing required keys (category, status, type)
-    """
-    for cond in plan.instance.status.conditions:
-        if cond["category"] == "Advisory" and cond["status"] == Plan.Condition.Status.TRUE:
-            cond_type = cond["type"]
+    conditions: list[dict[str, Any]] = (plan.instance.get("status", {}) or {}).get("conditions", []) or []
+    for cond in conditions:
+        if cond.get("category") == "Advisory" and cond.get("status") == Plan.Condition.Status.TRUE:
+            cond_type = cond.get("type")
             if cond_type in (Plan.Status.SUCCEEDED, Plan.Status.FAILED):
                 return cond_type
+
     return "Executing"
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies a potential race condition and test flakiness by polling a stale plan object, and the proposed fix using plan.reload() and defensive get() calls makes the test validation significantly more robust and reliable.

Medium
Possible issue
Use unique VM clone names

Add unique clone_name properties for each virtual machine in the
test_simultaneous_copyoffload_migrations configuration to prevent naming
collisions during the cloning process.

tests/tests_config/config.py [327-346]

 "test_simultaneous_copyoffload_migrations": {
     "virtual_machines": [
         {
             "name": "xcopy-template-test",
+            "clone_name": "xcopy-template-test-sim1",
             "source_vm_power": "off",
             "guest_agent": True,
             "clone": True,
             "disk_type": "thin",
         },
         {
             "name": "xcopy-template-test",
+            "clone_name": "xcopy-template-test-sim2",
             "source_vm_power": "off",
             "guest_agent": True,
             "clone": True,
             "disk_type": "thin",
         },
     ],
     "warm_migration": False,
     "copyoffload": True,
 },
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that cloning from the same template twice without specifying unique clone_names will likely cause a name collision, leading to test failures. Adding distinct clone_names is crucial for the correctness and reliability of the new test.

Medium
Maintainability
Extract common storagemap creation

Extract the duplicated StorageMap creation logic from
test_create_storagemap_plan1 and test_create_storagemap_plan2 into a single
private helper method to improve code maintainability.

tests/test_copyoffload_migration.py [2747-2806]

-def test_create_storagemap_plan1(
+def _create_copyoffload_storagemap(
     self,
-    prepared_plan_1: dict[str, Any],
+    *,
+    prepared_plan: dict[str, Any],
     fixture_store: dict[str, Any],
     ocp_admin_client: "DynamicClient",
     source_provider: BaseProvider,
     destination_provider: OCPProvider,
     source_provider_inventory: ForkliftInventory,
     target_namespace: str,
     source_provider_data: dict[str, Any],
     copyoffload_storage_secret: Secret,
-) -> None:
-    ...
-    self.__class__.storage_map_1 = get_storage_migration_map(
+) -> StorageMap:
+    copyoffload_config_data = source_provider_data["copyoffload"]
+    storage_vendor_product = copyoffload_config_data["storage_vendor_product"]
+    datastore_id = copyoffload_config_data["datastore_id"]
+    storage_class = py_config["storage_class"]
+
+    vms_names = [vm["name"] for vm in prepared_plan["virtual_machines"]]
+    offload_plugin_config = {
+        "vsphereXcopyConfig": {
+            "secretRef": copyoffload_storage_secret.name,
+            "storageVendorProduct": storage_vendor_product,
+        }
+    }
+
+    return get_storage_migration_map(
         fixture_store=fixture_store,
         target_namespace=target_namespace,
         source_provider=source_provider,
         destination_provider=destination_provider,
         ocp_admin_client=ocp_admin_client,
         source_provider_inventory=source_provider_inventory,
         vms=vms_names,
         storage_class=storage_class,
         datastore_id=datastore_id,
         offload_plugin_config=offload_plugin_config,
         access_mode="ReadWriteOnce",
         volume_mode="Block",
     )
+
+def test_create_storagemap_plan1(
+    self,
+    prepared_plan_1: dict[str, Any],
+    fixture_store: dict[str, Any],
+    ocp_admin_client: "DynamicClient",
+    source_provider: BaseProvider,
+    destination_provider: OCPProvider,
+    source_provider_inventory: ForkliftInventory,
+    target_namespace: str,
+    source_provider_data: dict[str, Any],
+    copyoffload_storage_secret: Secret,
+) -> None:
+    self.__class__.storage_map_1 = self._create_copyoffload_storagemap(
+        prepared_plan=prepared_plan_1,
+        fixture_store=fixture_store,
+        ocp_admin_client=ocp_admin_client,
+        source_provider=source_provider,
+        destination_provider=destination_provider,
+        source_provider_inventory=source_provider_inventory,
+        target_namespace=target_namespace,
+        source_provider_data=source_provider_data,
+        copyoffload_storage_secret=copyoffload_storage_secret,
+    )
     assert self.storage_map_1, "StorageMap creation failed for plan 1"
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly identifies duplicated code for StorageMap creation and proposes a refactoring that improves maintainability by adhering to the DRY principle, which is a good software engineering practice.

Low
Extract common networkmap creation

Extract the duplicated NetworkMap creation logic from
test_create_networkmap_plan1 and test_create_networkmap_plan2 into a single
private helper method to improve code maintainability.

tests/test_copyoffload_migration.py [2869-2910]

+def _create_networkmap(
+    self,
+    *,
+    prepared_plan: dict[str, Any],
+    fixture_store: dict[str, Any],
+    ocp_admin_client: "DynamicClient",
+    source_provider: BaseProvider,
+    destination_provider: OCPProvider,
+    source_provider_inventory: ForkliftInventory,
+    target_namespace: str,
+    multus_network_name: dict[str, str],
+) -> NetworkMap:
+    vms_names = [vm["name"] for vm in prepared_plan["virtual_machines"]]
+    return get_network_migration_map(
+        fixture_store=fixture_store,
+        source_provider=source_provider,
+        destination_provider=destination_provider,
+        source_provider_inventory=source_provider_inventory,
+        ocp_admin_client=ocp_admin_client,
+        target_namespace=target_namespace,
+        multus_network_name=multus_network_name,
+        vms=vms_names,
+    )
+
 def test_create_networkmap_plan1(
     self,
     prepared_plan_1: dict[str, Any],
     fixture_store: dict[str, Any],
     ocp_admin_client: "DynamicClient",
     source_provider: BaseProvider,
     destination_provider: OCPProvider,
     source_provider_inventory: ForkliftInventory,
     target_namespace: str,
     multus_network_name: dict[str, str],
 ) -> None:
-    ...
-    self.__class__.network_map_1 = get_network_migration_map(
+    self.__class__.network_map_1 = self._create_networkmap(
+        prepared_plan=prepared_plan_1,
         fixture_store=fixture_store,
+        ocp_admin_client=ocp_admin_client,
         source_provider=source_provider,
         destination_provider=destination_provider,
         source_provider_inventory=source_provider_inventory,
-        ocp_admin_client=ocp_admin_client,
         target_namespace=target_namespace,
         multus_network_name=multus_network_name,
-        vms=vms_names,
     )
     assert self.network_map_1, "NetworkMap creation failed for plan 1"
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly identifies duplicated code for NetworkMap creation and proposes a refactoring that improves maintainability by adhering to the DRY principle, which is a good software engineering practice.

Low
Suggestions up to commit 09241a7
CategorySuggestion                                                                                                                                    Impact
Possible bug
Refresh and harden status parsing

Refresh the Plan object before checking its status and use safe dictionary
access to parse conditions. This prevents potential errors from partially
populated API responses and improves test stability.

tests/test_copyoffload_migration.py [3100-3119]

 def _check_plan_status(plan: Plan) -> str:
     """Check if plan is executing, succeeded, or failed.
 
     Args:
         plan: The Plan resource to check status for
 
     Returns:
         str: "Executing" if migration is in progress, Plan.Status.SUCCEEDED if completed successfully,
             or Plan.Status.FAILED if migration failed
+    """
+    # Ensure we read a fresh status each poll
+    plan.reload()
 
-    Raises:
-        AttributeError: If plan.instance.status.conditions is not accessible
-        KeyError: If condition dictionary is missing required keys (category, status, type)
-    """
-    for cond in plan.instance.status.conditions:
-        if cond["category"] == "Advisory" and cond["status"] == Plan.Condition.Status.TRUE:
-            cond_type = cond["type"]
-            if cond_type in (Plan.Status.SUCCEEDED, Plan.Status.FAILED):
-                return cond_type
+    conditions = (plan.instance.get("status") or {}).get("conditions") or []
+    for cond in conditions:
+        if (
+            cond.get("category") == "Advisory"
+            and cond.get("status") == Plan.Condition.Status.TRUE
+            and cond.get("type") in (Plan.Status.SUCCEEDED, Plan.Status.FAILED)
+        ):
+            return cond["type"]
+
     return "Executing"
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies a potential source of test flakiness by addressing uncached resource state with plan.reload() and unsafe access to the status field, significantly improving test robustness.

Medium

best practice
Validate selected VM has name

Add validation to ensure the selected VM entry is a dictionary containing a name
field. This prevents potential downstream errors and provides clearer, more
specific error messages if the data is malformed.

utilities/utils.py [628-636]

 plan: dict[str, Any] = copy.deepcopy(prepared_plan)
 plan["virtual_machines"] = [plan["virtual_machines"][vm_index]]
 
-vm_name = plan["virtual_machines"][0]["name"]
-if vm_name not in prepared_plan.get("source_vms_data", {}):
+vm_entry = plan["virtual_machines"][0]
+if not isinstance(vm_entry, dict) or vm_entry.get("name") is None:
+    raise ValueError(f"{fixture_name} selected VM at index {vm_index} is missing a valid 'name' field")
+
+vm_name = vm_entry["name"]
+source_vms_data = prepared_plan.get("source_vms_data", {})
+if vm_name not in source_vms_data:
     raise ValueError(f"VM '{vm_name}' missing from prepared_plan source_vms_data")
 
-plan["source_vms_data"] = {vm_name: prepared_plan["source_vms_data"][vm_name]}
+plan["source_vms_data"] = {vm_name: source_vms_data[vm_name]}
 return plan
Suggestion importance[1-10]: 6

__

Why: The suggestion improves error handling by adding explicit validation for the VM entry structure and the presence of the name field, which provides clearer error messages and prevents potential TypeError or KeyError exceptions.

Low
Maintainability
Make timeout configurable and bounded

Replace the hardcoded 120-second timeout with a configurable value derived from
plan_wait_timeout. This makes the timeout for validating simultaneous execution
more flexible and bounded.

tests/test_copyoffload_migration.py [3122-3127]

+executing_validation_timeout = int(py_config.get("plan_wait_timeout", 3600))
+executing_validation_timeout = min(max(executing_validation_timeout // 30, 120), 600)
+
 for sample in TimeoutSampler(
     func=lambda: (_check_plan_status(self.plan_resource_1), _check_plan_status(self.plan_resource_2)),
     sleep=2,
-    wait_timeout=120,
+    wait_timeout=executing_validation_timeout,
 ):
     status_1, status_2 = sample
Suggestion importance[1-10]: 5

__

Why: The suggestion improves maintainability by replacing a hardcoded timeout with a configurable and bounded value, making the test more adaptable to different environment speeds.

Low
✅ Suggestions up to commit e5415f2
CategorySuggestion                                                                                                                                    Impact
Possible issue
Enforce unique VM identities

In the test_simultaneous_copyoffload_migrations configuration, assign a unique
clone_name to each of the two virtual machines to prevent name collisions during
the test.

tests/tests_config/config.py [327-346]

 "test_simultaneous_copyoffload_migrations": {
     "virtual_machines": [
         {
             "name": "xcopy-template-test",
+            "clone_name": "xcopy-template-test-sim-1",
             "source_vm_power": "off",
             "guest_agent": True,
             "clone": True,
             "disk_type": "thin",
         },
         {
             "name": "xcopy-template-test",
+            "clone_name": "xcopy-template-test-sim-2",
             "source_vm_power": "off",
             "guest_agent": True,
             "clone": True,
             "disk_type": "thin",
         },
     ],
     "warm_migration": False,
     "copyoffload": True,
 },
Suggestion importance[1-10]: 9

__

Why: This suggestion correctly identifies a critical flaw where cloning two VMs from the same template without unique names will cause name collisions, leading to incorrect test behavior and making the simultaneous migration test invalid.

High
Possible bug
✅ Validate extracted VM structure
Suggestion Impact:The PR removed the local _extract_vm_from_plan helper and switched fixtures to use a shared extract_vm_from_plan imported from utilities.utils, moving the VM-extraction logic (and any related validation) out of conftest.py rather than implementing the suggested inline validation changes there.

code diff:

@@ -66,6 +66,7 @@
 from utilities.utils import (
     create_source_cnv_vms,
     create_source_provider,
+    extract_vm_from_plan,
     generate_class_hash_prefix,
     get_cluster_client,
     get_cluster_version,
@@ -924,45 +925,6 @@
     # (e.g., if cleanup_migrated_vms is not used) will be caught by namespace deletion.
 
 
-def _extract_vm_from_plan(
-    prepared_plan: dict[str, Any],
-    vm_index: int,
-    fixture_name: str,
-) -> dict[str, Any]:
-    """Extract a single VM from a multi-VM prepared plan.
-
-    Helper function to create independent plan configurations for simultaneous migrations
-    by extracting a specific VM from a prepared plan containing multiple VMs.
-
-    Args:
-        prepared_plan: Base prepared plan with cloned VMs
-        vm_index: Zero-based index of the VM to extract
-        fixture_name: Name of the calling fixture (for error messages)
-
-    Returns:
-        Deep copy of prepared plan with only the specified VM
-
-    Raises:
-        ValueError: If prepared_plan contains insufficient VMs for the requested index
-        KeyError: If VM name is missing from source_vms_data
-    """
-    vms = prepared_plan.get("virtual_machines", [])
-    min_required_vms = vm_index + 1
-
-    if len(vms) < min_required_vms:
-        raise ValueError(f"{fixture_name} requires at least {min_required_vms} VM(s) in prepared_plan")
-
-    plan: dict[str, Any] = deepcopy(prepared_plan)
-    plan["virtual_machines"] = [plan["virtual_machines"][vm_index]]
-
-    vm_name = plan["virtual_machines"][0]["name"]
-    if vm_name not in prepared_plan.get("source_vms_data", {}):
-        raise KeyError(f"VM '{vm_name}' missing from prepared_plan source_vms_data")
-
-    plan["source_vms_data"] = {vm_name: prepared_plan["source_vms_data"][vm_name]}
-    return plan
-
-
 @pytest.fixture(scope="class")
 def prepared_plan_1(prepared_plan: dict[str, Any]) -> dict[str, Any]:
     """Prepare first migration plan configuration for simultaneous migrations.
@@ -981,7 +943,7 @@
         ValueError: If prepared_plan contains fewer than 1 VM
         KeyError: If VM name is missing from source_vms_data
     """
-    return _extract_vm_from_plan(prepared_plan, vm_index=0, fixture_name="prepared_plan_1")
+    return extract_vm_from_plan(prepared_plan, vm_index=0, fixture_name="prepared_plan_1")
 
 
 @pytest.fixture(scope="class")
@@ -1002,7 +964,7 @@
         ValueError: If prepared_plan contains fewer than 2 VMs
         KeyError: If VM name is missing from source_vms_data
     """
-    return _extract_vm_from_plan(prepared_plan, vm_index=1, fixture_name="prepared_plan_2")
+    return extract_vm_from_plan(prepared_plan, vm_index=1, fixture_name="prepared_plan_2")
 

Add validation to the _extract_vm_from_plan function to ensure the extracted VM
entry is a dictionary and contains a name key, failing with a clear error
message if not.

conftest.py [955-963]

 def _extract_vm_from_plan(
     prepared_plan: dict[str, Any],
     vm_index: int,
     fixture_name: str,
 ) -> dict[str, Any]:
     ...
     plan: dict[str, Any] = deepcopy(prepared_plan)
     plan["virtual_machines"] = [plan["virtual_machines"][vm_index]]
 
-    vm_name = plan["virtual_machines"][0]["name"]
-    if vm_name not in prepared_plan.get("source_vms_data", {}):
+    vm_entry = plan["virtual_machines"][0]
+    if not isinstance(vm_entry, dict):
+        raise TypeError(f"{fixture_name} expected VM entry to be a dict, got: {type(vm_entry).__name__}")
+
+    vm_name = vm_entry.get("name")
+    if vm_name is None:
+        raise ValueError(f"{fixture_name} VM entry missing required 'name' field: {vm_entry}")
+
+    source_vms_data: dict[str, Any] = prepared_plan.get("source_vms_data", {})
+    if vm_name not in source_vms_data:
         raise KeyError(f"VM '{vm_name}' missing from prepared_plan source_vms_data")
 
-    plan["source_vms_data"] = {vm_name: prepared_plan["source_vms_data"][vm_name]}
+    plan["source_vms_data"] = {vm_name: source_vms_data[vm_name]}
     return plan
Suggestion importance[1-10]: 6

__

Why: The suggestion improves robustness by adding explicit validation for the VM entry structure, which provides clearer error messages and prevents potential TypeError or KeyError exceptions on malformed test data.

Low

best practice
Validate required inventory fields

In test_create_plan1, validate that the VM id is present in the data returned
from source_provider_inventory.get_vm and raise a ValueError if it is missing.

tests/test_copyoffload_migration.py [2960-2963]

 for vm in prepared_plan_1["virtual_machines"]:
     vm_name = vm["name"]
     vm_data = source_provider_inventory.get_vm(vm_name)
-    vm["id"] = vm_data["id"]
+    vm_id = vm_data.get("id")
+    if vm_id is None:
+        raise ValueError(f"Missing VM id in inventory for VM '{vm_name}': {vm_data}")
+    vm["id"] = vm_id
Suggestion importance[1-10]: 6

__

Why: The suggestion improves code robustness by adding a check to ensure the VM id exists in the inventory data before use, which helps in providing clearer error messages for environmental or data issues.

Low
✅ Suggestions up to commit 49caece
CategorySuggestion                                                                                                                                    Impact
Maintainability
Reduce resource name collisions

Use dynamic plan names in the test_name argument when creating Migration
resources to prevent naming collisions during test execution.

tests/test_copyoffload_migration.py [2890-2910]

 migration_1 = create_and_store_resource(
     client=ocp_admin_client,
     fixture_store=fixture_store,
     resource=Migration,
     namespace=target_namespace,
     plan_name=self.plan_resource_1.name,
     plan_namespace=self.plan_resource_1.namespace,
-    test_name="simultaneous-copyoffload-migration1",
+    test_name=f"simultaneous-copyoffload-{self.plan_resource_1.name}-migration",
 )
 ...
 migration_2 = create_and_store_resource(
     client=ocp_admin_client,
     fixture_store=fixture_store,
     resource=Migration,
     namespace=target_namespace,
     plan_name=self.plan_resource_2.name,
     plan_namespace=self.plan_resource_2.namespace,
-    test_name="simultaneous-copyoffload-migration2",
+    test_name=f"simultaneous-copyoffload-{self.plan_resource_2.name}-migration",
 )
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that hardcoded test names can cause collisions in test runs, and proposes using dynamic plan names to create unique resource names, which improves test robustness.

Medium
Possible bug
✅ Add stronger input validation
Suggestion Impact:The prepared_plan_1 (and prepared_plan_2) fixture logic was refactored to delegate VM extraction to a new helper (extract_vm_from_plan), which is intended to centralize and enforce the kind of validation and fail-fast behavior suggested, rather than implementing the checks inline in the fixture.

code diff:

@@ -66,6 +66,7 @@
 from utilities.utils import (
     create_source_cnv_vms,
     create_source_provider,
+    extract_vm_from_plan,
     generate_class_hash_prefix,
     get_cluster_client,
     get_cluster_version,
@@ -925,7 +926,7 @@
 
 
 @pytest.fixture(scope="class")
-def prepared_plan_1(prepared_plan: dict[str, Any]) -> Generator[dict[str, Any], None, None]:
+def prepared_plan_1(prepared_plan: dict[str, Any]) -> dict[str, Any]:
     """Prepare first migration plan configuration for simultaneous migrations.
 
     This fixture extracts the first VM from a prepared plan containing multiple VMs,
@@ -933,22 +934,20 @@
     to run two migrations simultaneously.
 
     Args:
-        prepared_plan (dict[str, Any]): Base prepared plan with cloned VMs
-
-    Yields:
-        dict[str, Any]: Deep copy of prepared plan with first VM only
-    """
-    plan: dict[str, Any] = deepcopy(prepared_plan)
-    # Use only the first VM for plan 1
-    plan["virtual_machines"] = [plan["virtual_machines"][0]]
-    # Copy source VM data for the first VM only
-    vm_name = plan["virtual_machines"][0]["name"]
-    plan["source_vms_data"] = {vm_name: prepared_plan["source_vms_data"][vm_name]}
-    yield plan
+        prepared_plan: Base prepared plan with cloned VMs
+
+    Returns:
+        Deep copy of prepared plan with first VM only
+
+    Raises:
+        ValueError: If prepared_plan contains fewer than 1 VM
+        KeyError: If VM name is missing from source_vms_data
+    """
+    return extract_vm_from_plan(prepared_plan, vm_index=0, fixture_name="prepared_plan_1")
 
 
 @pytest.fixture(scope="class")
-def prepared_plan_2(prepared_plan: dict[str, Any]) -> Generator[dict[str, Any], None, None]:
+def prepared_plan_2(prepared_plan: dict[str, Any]) -> dict[str, Any]:
     """Prepare second migration plan configuration for simultaneous migrations.
 
     This fixture extracts the second VM from a prepared plan containing multiple VMs,
@@ -956,18 +955,16 @@
     to run two migrations simultaneously.
 
     Args:
-        prepared_plan (dict[str, Any]): Base prepared plan with cloned VMs
-
-    Yields:
-        dict[str, Any]: Deep copy of prepared plan with second VM only
-    """
-    plan: dict[str, Any] = deepcopy(prepared_plan)
-    # Use only the second VM for plan 2
-    plan["virtual_machines"] = [plan["virtual_machines"][1]]
-    # Copy source VM data for the second VM only
-    vm_name = plan["virtual_machines"][0]["name"]
-    plan["source_vms_data"] = {vm_name: prepared_plan["source_vms_data"][vm_name]}
-    yield plan
+        prepared_plan: Base prepared plan with cloned VMs
+
+    Returns:
+        Deep copy of prepared plan with second VM only
+
+    Raises:
+        ValueError: If prepared_plan contains fewer than 2 VMs
+        KeyError: If VM name is missing from source_vms_data
+    """
+    return extract_vm_from_plan(prepared_plan, vm_index=1, fixture_name="prepared_plan_2")

Add validation to fail fast if prepared_plan is missing source_vms_data or if a
VM is missing a name, preventing potential runtime errors.

conftest.py [945-958]

 vms = prepared_plan.get("virtual_machines", [])
 if len(vms) < 1:
     raise ValueError("prepared_plan_1 requires at least 1 VM in prepared_plan")
 
+source_vms_data = prepared_plan.get("source_vms_data")
+if source_vms_data is None:
+    raise ValueError("prepared_plan_1 requires 'source_vms_data' in prepared_plan")
+
 plan: dict[str, Any] = deepcopy(prepared_plan)
-# Use only the first VM for plan 1
 plan["virtual_machines"] = [plan["virtual_machines"][0]]
-# Copy source VM data for the first VM only
-vm_name = plan["virtual_machines"][0]["name"]
-if vm_name not in prepared_plan.get("source_vms_data", {}):
+
+vm_name = plan["virtual_machines"][0].get("name")
+if not vm_name:
+    raise ValueError("prepared_plan_1 requires VM 'name' in prepared_plan['virtual_machines'][0]")
+
+if vm_name not in source_vms_data:
     raise KeyError(f"VM '{vm_name}' missing from prepared_plan source_vms_data")
 
-plan["source_vms_data"] = {vm_name: prepared_plan["source_vms_data"][vm_name]}
+plan["source_vms_data"] = {vm_name: source_vms_data[vm_name]}
 return plan
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies potential KeyError or TypeError exceptions and proposes more robust validation, which improves the fixture's reliability and debuggability.

Low
✅ Suggestions up to commit c5c5a45
CategorySuggestion                                                                                                                                    Impact
Possible bug
Ensure unique VM names

Assign unique names to the virtual machines in the
test_simultaneous_copyoffload_migrations configuration. Using duplicate names
can cause ambiguous lookups and lead to both migration plans referencing the
same VM.

tests/tests_config/config.py [327-346]

 "test_simultaneous_copyoffload_migrations": {
     "virtual_machines": [
         {
-            "name": "xcopy-template-test",
+            "name": "xcopy-template-test-1",
             "source_vm_power": "off",
             "guest_agent": True,
             "clone": True,
             "disk_type": "thin",
         },
         {
-            "name": "xcopy-template-test",
+            "name": "xcopy-template-test-2",
             "source_vm_power": "off",
             "guest_agent": True,
             "clone": True,
             "disk_type": "thin",
         },
     ],
     "warm_migration": False,
     "copyoffload": True,
 },
Suggestion importance[1-10]: 8

__

Why: The suggestion correctly identifies that using the same VM name for two different clones can lead to ambiguous lookups. The source_provider_inventory.get_vm(vm_name) function returns the first match, which would cause both plans to reference the same cloned VM, defeating the purpose of a simultaneous migration test. This is a significant bug in the test logic.

Medium
Guard against wrong inventory match

In test_create_plan1, after fetching a VM from the inventory using
get_vm(vm_name), add a check to ensure the returned VM data's name matches the
requested vm_name. This guards against potential name collisions and ambiguous
lookups.

tests/test_copyoffload_migration.py [2550-2553]

 for vm in prepared_plan_1["virtual_machines"]:
     vm_name = vm["name"]
     vm_data = source_provider_inventory.get_vm(vm_name)
+    if vm_data.get("name") != vm_name:
+        raise ValueError(f"Inventory returned unexpected VM for '{vm_name}': {vm_data.get('name')}")
     vm["id"] = vm_data["id"]
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out a potential ambiguity in source_provider_inventory.get_vm(vm_name), which could return an incorrect VM if names are not unique. Adding a check to verify that the returned VM's name matches the requested name makes the test logic more robust and prevents subtle bugs.

Medium
Maintainability
✅ Validate plan structure early
Suggestion Impact:The commit added early validation around plan structure in prepared_plan_1 (and similarly in prepared_plan_2), including checking VM count and raising a KeyError when the VM name is missing from source_vms_data. However, it did not implement the suggested vm_entry.get("name") / explicit source_vms_data existence validation exactly.

code diff:

+    vms = prepared_plan.get("virtual_machines", [])
+    if len(vms) < 1:
+        raise ValueError("prepared_plan_1 requires at least 1 VM in prepared_plan")
+
     plan: dict[str, Any] = deepcopy(prepared_plan)
     # Use only the first VM for plan 1
     plan["virtual_machines"] = [plan["virtual_machines"][0]]
     # Copy source VM data for the first VM only
     vm_name = plan["virtual_machines"][0]["name"]
+    if vm_name not in prepared_plan.get("source_vms_data", {}):
+        raise KeyError(f"VM '{vm_name}' missing from prepared_plan source_vms_data")
+
     plan["source_vms_data"] = {vm_name: prepared_plan["source_vms_data"][vm_name]}
-    yield plan
+    return plan
 
 
 @pytest.fixture(scope="class")
-def prepared_plan_2(prepared_plan: dict[str, Any]) -> Generator[dict[str, Any], None, None]:
+def prepared_plan_2(prepared_plan: dict[str, Any]) -> dict[str, Any]:
     """Prepare second migration plan configuration for simultaneous migrations.
 
     This fixture extracts the second VM from a prepared plan containing multiple VMs,
@@ -958,16 +969,27 @@
     Args:
         prepared_plan (dict[str, Any]): Base prepared plan with cloned VMs
 
-    Yields:
+    Returns:
         dict[str, Any]: Deep copy of prepared plan with second VM only
-    """
+
+    Raises:
+        ValueError: If prepared_plan contains fewer than 2 VMs
+        KeyError: If VM name is missing from source_vms_data
+    """
+    vms = prepared_plan.get("virtual_machines", [])
+    if len(vms) < 2:
+        raise ValueError("prepared_plan_2 requires at least 2 VMs in prepared_plan")
+
     plan: dict[str, Any] = deepcopy(prepared_plan)
     # Use only the second VM for plan 2
     plan["virtual_machines"] = [plan["virtual_machines"][1]]
     # Copy source VM data for the second VM only
     vm_name = plan["virtual_machines"][0]["name"]
+    if vm_name not in prepared_plan.get("source_vms_data", {}):
+        raise KeyError(f"VM '{vm_name}' missing from prepared_plan source_vms_data")
+
     plan["source_vms_data"] = {vm_name: prepared_plan["source_vms_data"][vm_name]}

Add validation to the prepared_plan_1 fixture to ensure the VM entry contains a
name and that source_vms_data exists in the prepared plan. This will provide
clearer errors for malformed plan data.

conftest.py [945-958]

 vms = prepared_plan.get("virtual_machines", [])
 if len(vms) < 1:
     raise ValueError("prepared_plan_1 requires at least 1 VM in prepared_plan")
 
 plan: dict[str, Any] = deepcopy(prepared_plan)
-# Use only the first VM for plan 1
 plan["virtual_machines"] = [plan["virtual_machines"][0]]
-# Copy source VM data for the first VM only
-vm_name = plan["virtual_machines"][0]["name"]
-if vm_name not in prepared_plan.get("source_vms_data", {}):
+
+vm_entry = plan["virtual_machines"][0]
+vm_name = vm_entry.get("name")
+if vm_name is None:
+    raise ValueError("prepared_plan_1 requires VM entry to include 'name'")
+
+source_vms_data = prepared_plan.get("source_vms_data")
+if source_vms_data is None:
+    raise ValueError("prepared_plan_1 requires 'source_vms_data' in prepared_plan")
+
+if vm_name not in source_vms_data:
     raise KeyError(f"VM '{vm_name}' missing from prepared_plan source_vms_data")
 
-plan["source_vms_data"] = {vm_name: prepared_plan["source_vms_data"][vm_name]}
+plan["source_vms_data"] = {vm_name: source_vms_data[vm_name]}
 return plan
Suggestion importance[1-10]: 6

__

Why: The suggestion improves the robustness of the prepared_plan_1 fixture by adding more explicit validation for the plan's structure. This makes the fixture more resilient to malformed input and provides clearer error messages, which enhances maintainability and debuggability.

Low
✅ Suggestions up to commit c2d126f
CategorySuggestion                                                                                                                                    Impact
Possible bug
✅ Validate VM name before use
Suggestion Impact:The commit added robustness checks around prepared_plan VM counts and added an explicit KeyError when the derived vm_name is not present in source_vms_data. However, it did not implement the suggested .get("name")/missing-name validation (it still accesses ["name"] directly).

code diff:

+    vms = prepared_plan.get("virtual_machines", [])
+    if len(vms) < 1:
+        raise ValueError("prepared_plan_1 requires at least 1 VM in prepared_plan")
+
     plan: dict[str, Any] = deepcopy(prepared_plan)
     # Use only the first VM for plan 1
     plan["virtual_machines"] = [plan["virtual_machines"][0]]
     # Copy source VM data for the first VM only
     vm_name = plan["virtual_machines"][0]["name"]
+    if vm_name not in prepared_plan.get("source_vms_data", {}):
+        raise KeyError(f"VM '{vm_name}' missing from prepared_plan source_vms_data")
+
     plan["source_vms_data"] = {vm_name: prepared_plan["source_vms_data"][vm_name]}
-    yield plan
+    return plan
 
 
 @pytest.fixture(scope="class")
-def prepared_plan_2(prepared_plan: dict[str, Any]) -> Generator[dict[str, Any], None, None]:
+def prepared_plan_2(prepared_plan: dict[str, Any]) -> dict[str, Any]:
     """Prepare second migration plan configuration for simultaneous migrations.
 
     This fixture extracts the second VM from a prepared plan containing multiple VMs,
@@ -958,16 +969,27 @@
     Args:
         prepared_plan (dict[str, Any]): Base prepared plan with cloned VMs
 
-    Yields:
+    Returns:
         dict[str, Any]: Deep copy of prepared plan with second VM only
-    """
+
+    Raises:
+        ValueError: If prepared_plan contains fewer than 2 VMs
+        KeyError: If VM name is missing from source_vms_data
+    """
+    vms = prepared_plan.get("virtual_machines", [])
+    if len(vms) < 2:
+        raise ValueError("prepared_plan_2 requires at least 2 VMs in prepared_plan")
+
     plan: dict[str, Any] = deepcopy(prepared_plan)
     # Use only the second VM for plan 2
     plan["virtual_machines"] = [plan["virtual_machines"][1]]
     # Copy source VM data for the second VM only
     vm_name = plan["virtual_machines"][0]["name"]
+    if vm_name not in prepared_plan.get("source_vms_data", {}):
+        raise KeyError(f"VM '{vm_name}' missing from prepared_plan source_vms_data")
+
     plan["source_vms_data"] = {vm_name: prepared_plan["source_vms_data"][vm_name]}

Validate that the VM entry in prepared_plan contains a name key before accessing
it to prevent potential KeyError exceptions and improve robustness.

conftest.py [919-932]

 vms = prepared_plan.get("virtual_machines", [])
 if len(vms) < 1:
     raise ValueError("prepared_plan_1 requires at least 1 VM in prepared_plan")
 
 plan: dict[str, Any] = deepcopy(prepared_plan)
-# Use only the first VM for plan 1
 plan["virtual_machines"] = [plan["virtual_machines"][0]]
-# Copy source VM data for the first VM only
-vm_name = plan["virtual_machines"][0]["name"]
-if vm_name not in prepared_plan.get("source_vms_data", {}):
+
+vm_name = plan["virtual_machines"][0].get("name")
+if vm_name is None:
+    raise ValueError("First VM in prepared_plan is missing required 'name'")
+
+source_vms_data = prepared_plan.get("source_vms_data", {})
+if vm_name not in source_vms_data:
     raise KeyError(f"VM '{vm_name}' missing from prepared_plan source_vms_data")
 
-plan["source_vms_data"] = {vm_name: prepared_plan["source_vms_data"][vm_name]}
+plan["source_vms_data"] = {vm_name: source_vms_data[vm_name]}
 return plan
Suggestion importance[1-10]: 6

__

Why: The suggestion correctly identifies a potential KeyError and proposes a more robust check using .get(), which improves error handling and makes debugging easier.

Low
Maintainability
✅ Validate selected VM entry
Suggestion Impact:prepared_plan_2 was updated to add robustness checks (validate at least 2 VMs exist and raise an explicit error if the VM name is missing from source_vms_data), mirroring the stronger validation pattern; however, it still accesses the VM name via ["name"] rather than using .get() with a dedicated missing-name ValueError.

code diff:

-def prepared_plan_2(prepared_plan: dict[str, Any]) -> Generator[dict[str, Any], None, None]:
+def prepared_plan_2(prepared_plan: dict[str, Any]) -> dict[str, Any]:
     """Prepare second migration plan configuration for simultaneous migrations.
 
     This fixture extracts the second VM from a prepared plan containing multiple VMs,
@@ -958,16 +969,27 @@
     Args:
         prepared_plan (dict[str, Any]): Base prepared plan with cloned VMs
 
-    Yields:
+    Returns:
         dict[str, Any]: Deep copy of prepared plan with second VM only
-    """
+
+    Raises:
+        ValueError: If prepared_plan contains fewer than 2 VMs
+        KeyError: If VM name is missing from source_vms_data
+    """
+    vms = prepared_plan.get("virtual_machines", [])
+    if len(vms) < 2:
+        raise ValueError("prepared_plan_2 requires at least 2 VMs in prepared_plan")
+
     plan: dict[str, Any] = deepcopy(prepared_plan)
     # Use only the second VM for plan 2
     plan["virtual_machines"] = [plan["virtual_machines"][1]]
     # Copy source VM data for the second VM only
     vm_name = plan["virtual_machines"][0]["name"]
+    if vm_name not in prepared_plan.get("source_vms_data", {}):
+        raise KeyError(f"VM '{vm_name}' missing from prepared_plan source_vms_data")
+
     plan["source_vms_data"] = {vm_name: prepared_plan["source_vms_data"][vm_name]}
-    yield plan
+    return plan
 

Validate that the second VM entry in prepared_plan contains a name key before
accessing it, mirroring the validation from prepared_plan_1 for consistency and
robustness.

conftest.py [953-966]

 vms = prepared_plan.get("virtual_machines", [])
 if len(vms) < 2:
     raise ValueError("prepared_plan_2 requires at least 2 VMs in prepared_plan")
 
 plan: dict[str, Any] = deepcopy(prepared_plan)
-# Use only the second VM for plan 2
 plan["virtual_machines"] = [plan["virtual_machines"][1]]
-# Copy source VM data for the second VM only
-vm_name = plan["virtual_machines"][0]["name"]
-if vm_name not in prepared_plan.get("source_vms_data", {}):
+
+vm_name = plan["virtual_machines"][0].get("name")
+if vm_name is None:
+    raise ValueError("Second VM in prepared_plan is missing required 'name'")
+
+source_vms_data = prepared_plan.get("source_vms_data", {})
+if vm_name not in source_vms_data:
     raise KeyError(f"VM '{vm_name}' missing from prepared_plan source_vms_data")
 
-plan["source_vms_data"] = {vm_name: prepared_plan["source_vms_data"][vm_name]}
+plan["source_vms_data"] = {vm_name: source_vms_data[vm_name]}
 return plan
Suggestion importance[1-10]: 6

__

Why: Similar to the first suggestion, this improves robustness by safely accessing the VM name with .get() and provides a more explicit error message if it's missing.

Low

best practice
Avoid in-place fixture mutation

Replace the in-place mutation of the prepared_plan_1["virtual_machines"] list
with a list comprehension to create a new list, following the best practice of
immutability.

tests/test_copyoffload_migration.py [2562-2565]

-for vm in prepared_plan_1["virtual_machines"]:
-    vm_name = vm["name"]
-    vm_data = source_provider_inventory.get_vm(vm_name)
-    vm["id"] = vm_data["id"]
+prepared_plan_1["virtual_machines"] = [
+    {**vm, "id": source_provider_inventory.get_vm(vm["name"])["id"]} for vm in prepared_plan_1["virtual_machines"]
+]
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly promotes immutability by replacing an in-place mutation with a list comprehension, which is a good practice for avoiding side effects in tests.

Low
✅ Suggestions up to commit ed213c5
CategorySuggestion                                                                                                                                    Impact
Enhancement
✅ Wait for migrations to complete concurrently
Suggestion Impact:The commit changes the test to explicitly validate that both migrations enter an "Executing" state simultaneously (via polling with TimeoutSampler) before waiting for completion, strengthening verification of parallel execution even though it does not implement the suggested threading-based concurrent waits.

code diff:

+
+        # Validate both migrations are executing simultaneously before either completes
+        LOGGER.info("Validating both migrations enter executing state simultaneously")
+        plan_1_executing = False
+        plan_2_executing = False
+        both_executing_validated = False
+
+        def _check_plan_status(plan: Plan) -> str:
+            """Check if plan is executing, succeeded, or failed."""
+            for cond in plan.instance.status.conditions:
+                if cond["category"] == "Advisory" and cond["status"] == Plan.Condition.Status.TRUE:
+                    cond_type = cond["type"]
+                    if cond_type in (Plan.Status.SUCCEEDED, Plan.Status.FAILED):
+                        return cond_type
+            return "Executing"
+
+        # Poll both plans to ensure both reach executing state before either completes
+        for sample in TimeoutSampler(
+            func=lambda: (_check_plan_status(self.plan_resource_1), _check_plan_status(self.plan_resource_2)),
+            sleep=2,
+            wait_timeout=120,
+        ):
+            status_1, status_2 = sample
+
+            # Check if plan 1 is executing
+            if status_1 == "Executing":
+                if not plan_1_executing:
+                    LOGGER.info(f"Plan 1 '{self.plan_resource_1.name}' is now executing")
+                plan_1_executing = True
+
+            # Check if plan 2 is executing
+            if status_2 == "Executing":
+                if not plan_2_executing:
+                    LOGGER.info(f"Plan 2 '{self.plan_resource_2.name}' is now executing")
+                plan_2_executing = True
+
+            # Assert both are executing simultaneously
+            if plan_1_executing and plan_2_executing and not both_executing_validated:
+                LOGGER.info("SUCCESS: Both migrations are executing simultaneously")
+                both_executing_validated = True
+                break
+
+            # If either plan completed before both were executing, fail the test
+            if status_1 in (Plan.Status.SUCCEEDED, Plan.Status.FAILED) and not both_executing_validated:
+                raise AssertionError(
+                    f"Plan 1 reached {status_1} before both plans were executing simultaneously. "
+                    f"Plan 2 status: {status_2}"
+                )
+            if status_2 in (Plan.Status.SUCCEEDED, Plan.Status.FAILED) and not both_executing_v...

@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: 6

🤖 Fix all issues with AI agents
In `@conftest.py`:
- Around line 924-944: prepared_plan_2 currently assumes there are at least two
VMs and will raise an IndexError; add an explicit guard that checks
len(plan["virtual_machines"]) >= 2 and raise a ValueError with a clear message
(e.g., "prepared_plan_2 requires at least 2 virtual_machines in prepared_plan")
if the check fails, then proceed to slice to the second VM and build
source_vms_data as before; also update the prepared_plan_2 docstring to include
a Raises section documenting the ValueError so callers know the precondition and
the exception behavior (reference symbols: prepared_plan_2,
plan["virtual_machines"], vm_name, plan["source_vms_data"]).
- Around line 901-944: The fixtures prepared_plan_1 and prepared_plan_2 should
be converted from generator-style to return-style: change the return type from
Generator[...] to dict[str, Any], update the docstrings "Yields"→"Returns" and
add a "Raises" section documenting IndexError and KeyError, and replace "yield
plan" with "return plan"; add defensive checks before indexing
plan["virtual_machines"] (ensure len(...) > 0 for prepared_plan_1 and > 1 for
prepared_plan_2) and verify the vm_name exists in
prepared_plan["source_vms_data"], raising IndexError/KeyError with clear
messages if those conditions fail so the functions fail fast with informative
errors.

In `@tests/test_copyoffload_migration.py`:
- Around line 2312-2627: The test duplicates plan1/plan2 flows; collapse them
into the standard five-step tests by replacing
test_create_storagemap_plan1/plan2, test_create_networkmap_plan1/plan2,
test_create_plan1/plan2, test_migrate_vms_simultaneously, and
test_check_vms_plan1/plan2 with unified methods named test_create_storagemap,
test_create_networkmap, test_create_plan, test_migrate_vms, and test_check_vms
that iterate over both prepared_plan_1 and prepared_plan_2 (or call a shared
helper) to call get_storage_migration_map, get_network_migration_map,
create_plan_resource, create_and_store_resource (Migration), check_vms and
verify_vm_disk_count for each plan; keep using class attributes
storage_map_1/storage_map_2, network_map_1/network_map_2,
plan_resource_1/plan_resource_2 but set them inside the loop/helper to preserve
later references and assertions.
- Around line 2320-2456: The class-level `@pytest.mark.usefixtures` currently
includes multus_network_name while the same fixture is also declared as a
parameter to test_create_networkmap_plan1 and test_create_networkmap_plan2;
remove multus_network_name from the `@pytest.mark.usefixtures` decorator on the
TestSimultaneousCopyoffloadMigrations class so the fixture is only requested
explicitly via the test function parameters (leave multus_network_name in the
parameter lists of test_create_networkmap_plan1 and
test_create_networkmap_plan2), and keep usefixtures only for fixtures that are
truly side-effect-only.
- Around line 2332-2627: The new test functions (test_create_storagemap_plan1,
test_create_storagemap_plan2, test_create_networkmap_plan1,
test_create_networkmap_plan2, test_create_plan1, test_create_plan2,
test_migrate_vms_simultaneously, test_check_vms_plan1, test_check_vms_plan2)
must be annotated with explicit type hints for all parameters and return values
(e.g., fixture types, client types, namespace: str, etc.) and their docstrings
expanded into the project-standard format including Args (with types and brief
descriptions), Returns (type/meaning, usually None for tests) and Raises
(expected exceptions or test failure conditions); update each function signature
to add the annotations and replace the short triple-quoted summary with a full
docstring block listing Args/Returns/Raises consistent with other tests in the
repo so static analysis and linters pass.

In `@tests/tests_config/config.py`:
- Around line 373-388: The two VM entries in the
"test_simultaneous_copyoffload_migrations" virtual_machines list both use the
same template name "xcopy-template-test" with identical clone settings; add
explicit, distinct clone_name fields (e.g., "xcopy-template-test-clone-1" and
"xcopy-template-test-clone-2") to each VM dict so the clone behavior is clear
and the test is more readable while keeping all other settings (source_vm_power,
guest_agent, clone, disk_type) unchanged.

Comment thread conftest.py Resolved
Comment thread conftest.py Resolved
Comment thread tests/test_copyoffload_migration.py Resolved
Comment thread tests/test_copyoffload_migration.py Resolved
Comment thread tests/test_copyoffload_migration.py Resolved
Comment thread tests/tests_config/config.py Resolved
@qodo-code-review

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Concurrency

The “simultaneous” migration flow creates two Migration CRs back-to-back, but then waits for completion sequentially. Validate that the helper actually waits on the correct Migration objects (not “latest migration” per plan) and that the test truly exercises concurrent execution rather than effectively serializing completion checks.

def test_migrate_vms_simultaneously(
    self,
    fixture_store: dict[str, Any],
    ocp_admin_client: "DynamicClient",
    target_namespace: str,
) -> None:
    """Execute both copyoffload migrations simultaneously.

    Args:
        fixture_store: Fixture store for resource tracking
        ocp_admin_client: OpenShift admin client
        target_namespace: Target namespace for Migration CRs
    """
    LOGGER.info("Starting simultaneous execution of both copyoffload migration plans")

    # Create Migration CR for first plan
    migration_1 = create_and_store_resource(
        client=ocp_admin_client,
        fixture_store=fixture_store,
        resource=Migration,
        namespace=target_namespace,
        plan_name=self.plan_resource_1.name,
        plan_namespace=self.plan_resource_1.namespace,
        test_name="simultaneous-copyoffload-migration1",
    )
    LOGGER.info(f"Created Migration CR for plan 1: {migration_1.name}")

    # Create Migration CR for second plan
    migration_2 = create_and_store_resource(
        client=ocp_admin_client,
        fixture_store=fixture_store,
        resource=Migration,
        namespace=target_namespace,
        plan_name=self.plan_resource_2.name,
        plan_namespace=self.plan_resource_2.namespace,
        test_name="simultaneous-copyoffload-migration2",
    )
    LOGGER.info(f"Created Migration CR for plan 2: {migration_2.name}")

    # Wait for both migrations to complete
    LOGGER.info("Waiting for both copyoffload migrations to complete")
    wait_for_migration_complate(plan=self.plan_resource_1)
    LOGGER.info("Copyoffload migration 1 completed")

    wait_for_migration_complate(plan=self.plan_resource_2)
    LOGGER.info("Copyoffload migration 2 completed")
Typo Risk

The helper name wait_for_migration_complate appears misspelled. If both spellings exist in the codebase, this can cause confusion; if only the correct spelling exists elsewhere, this is a runtime import/use error and will fail the test.

from utilities.mtv_migration import (
    create_plan_resource,
    execute_migration,
    get_network_migration_map,
    get_storage_migration_map,
    verify_vm_disk_count,
    wait_for_migration_complate,
)
Defensive Checks

The fixtures assume each VM entry has a name and that source_vms_data is a dict. Consider validating presence/types of virtual_machines items before indexing and accessing nested keys to fail with clearer errors and avoid unexpected KeyError/TypeError when config changes.

@pytest.fixture(scope="class")
def prepared_plan_1(prepared_plan: dict[str, Any]) -> dict[str, Any]:
    """Prepare first migration plan configuration for simultaneous migrations.

    This fixture extracts the first VM from a prepared plan containing multiple VMs,
    creating an independent plan for the first migration. Use this with prepared_plan_2
    to run two migrations simultaneously.

    Args:
        prepared_plan (dict[str, Any]): Base prepared plan with cloned VMs

    Returns:
        dict[str, Any]: Deep copy of prepared plan with first VM only

    Raises:
        ValueError: If prepared_plan contains fewer than 1 VM
        KeyError: If VM name is missing from source_vms_data
    """
    vms = prepared_plan.get("virtual_machines", [])
    if len(vms) < 1:
        raise ValueError("prepared_plan_1 requires at least 1 VM in prepared_plan")

    plan: dict[str, Any] = deepcopy(prepared_plan)
    # Use only the first VM for plan 1
    plan["virtual_machines"] = [plan["virtual_machines"][0]]
    # Copy source VM data for the first VM only
    vm_name = plan["virtual_machines"][0]["name"]
    if vm_name not in prepared_plan.get("source_vms_data", {}):
        raise KeyError(f"VM '{vm_name}' missing from prepared_plan source_vms_data")

    plan["source_vms_data"] = {vm_name: prepared_plan["source_vms_data"][vm_name]}
    return plan


@pytest.fixture(scope="class")
def prepared_plan_2(prepared_plan: dict[str, Any]) -> dict[str, Any]:
    """Prepare second migration plan configuration for simultaneous migrations.

    This fixture extracts the second VM from a prepared plan containing multiple VMs,
    creating an independent plan for the second migration. Use this with prepared_plan_1
    to run two migrations simultaneously.

    Args:
        prepared_plan (dict[str, Any]): Base prepared plan with cloned VMs

    Returns:
        dict[str, Any]: Deep copy of prepared plan with second VM only

    Raises:
        ValueError: If prepared_plan contains fewer than 2 VMs
        KeyError: If VM name is missing from source_vms_data
    """
    vms = prepared_plan.get("virtual_machines", [])
    if len(vms) < 2:
        raise ValueError("prepared_plan_2 requires at least 2 VMs in prepared_plan")

    plan: dict[str, Any] = deepcopy(prepared_plan)
    # Use only the second VM for plan 2
    plan["virtual_machines"] = [plan["virtual_machines"][1]]
    # Copy source VM data for the second VM only
    vm_name = plan["virtual_machines"][0]["name"]
    if vm_name not in prepared_plan.get("source_vms_data", {}):
        raise KeyError(f"VM '{vm_name}' missing from prepared_plan source_vms_data")

    plan["source_vms_data"] = {vm_name: prepared_plan["source_vms_data"][vm_name]}
    return plan

💡 Tool usage guide:

Overview:
The review tool scans the PR code changes, and generates a PR review which includes several types of feedbacks, such as possible PR issues, security threats and relevant test in the PR. More feedbacks can be added by configuring the tool.

The tool can be triggered automatically every time a new PR is opened, or can be invoked manually by commenting on any PR.

  • When commenting, to edit configurations related to the review tool (pr_reviewer section), use the following template:
/review --pr_reviewer.some_config1=... --pr_reviewer.some_config2=...
[pr_reviewer]
some_config1=...
some_config2=...

See the review usage page for a comprehensive guide on using this tool.

coderabbitai[bot]
coderabbitai Bot previously approved these changes Jan 30, 2026
Comment thread tests/test_copyoffload_migration.py Resolved
@qodo-code-review

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Data Collision

The new config defines two entries with the same VM name, which can collide in any dict keyed by VM name (for example source_vms_data) and can lead to nondeterministic behavior depending on how prepared_plan expands clones. Ensure the underlying plan-preparation logic guarantees unique VM names/IDs per clone and that downstream mappings are not keyed solely by the original template name.

"test_simultaneous_copyoffload_migrations": {
    "virtual_machines": [
        {
            "name": "xcopy-template-test",
            "source_vm_power": "off",
            "guest_agent": True,
            "clone": True,
            "disk_type": "thin",
        },
        {
            "name": "xcopy-template-test",
            "source_vm_power": "off",
            "guest_agent": True,
            "clone": True,
            "disk_type": "thin",
        },
    ],
    "warm_migration": False,
    "copyoffload": True,
},
Concurrency Semantics

The test creates two Migration CRs back-to-back, but then waits for completion sequentially. If the goal is to validate simultaneous execution (resource contention, parallelism, race conditions), consider waiting on both migrations in a way that detects overlap (e.g., polling both statuses until both are Complete, or asserting both reach Running before waiting for completion).

def test_migrate_vms_simultaneously(
    self,
    fixture_store: dict[str, Any],
    ocp_admin_client: "DynamicClient",
    target_namespace: str,
) -> None:
    """Execute both copyoffload migrations simultaneously.

    Args:
        fixture_store: Fixture store for resource tracking
        ocp_admin_client: OpenShift admin client
        target_namespace: Target namespace for Migration CRs
    """
    LOGGER.info("Starting simultaneous execution of both copyoffload migration plans")

    # Create Migration CR for first plan
    migration_1 = create_and_store_resource(
        client=ocp_admin_client,
        fixture_store=fixture_store,
        resource=Migration,
        namespace=target_namespace,
        plan_name=self.plan_resource_1.name,
        plan_namespace=self.plan_resource_1.namespace,
        test_name="simultaneous-copyoffload-migration1",
    )
    LOGGER.info(f"Created Migration CR for plan 1: {migration_1.name}")

    # Create Migration CR for second plan
    migration_2 = create_and_store_resource(
        client=ocp_admin_client,
        fixture_store=fixture_store,
        resource=Migration,
        namespace=target_namespace,
        plan_name=self.plan_resource_2.name,
        plan_namespace=self.plan_resource_2.namespace,
        test_name="simultaneous-copyoffload-migration2",
    )
    LOGGER.info(f"Created Migration CR for plan 2: {migration_2.name}")

    # Wait for both migrations to complete
    LOGGER.info("Waiting for both copyoffload migrations to complete")
    wait_for_migration_complate(plan=self.plan_resource_1)
    LOGGER.info("Copyoffload migration 1 completed")

    wait_for_migration_complate(plan=self.plan_resource_2)
    LOGGER.info("Copyoffload migration 2 completed")
Typo / API Usage

The helper name wait_for_migration_complate looks like a typo and increases the chance of calling the wrong function or keeping a misspelled public API. Confirm this is the intended utility name and not an accidental misspelling, and align naming to project standards for readability and discoverability.

from utilities.mtv_migration import (
    create_plan_resource,
    execute_migration,
    get_network_migration_map,
    get_storage_migration_map,
    verify_vm_disk_count,
    wait_for_migration_complate,
)

💡 Tool usage guide:

Overview:
The review tool scans the PR code changes, and generates a PR review which includes several types of feedbacks, such as possible PR issues, security threats and relevant test in the PR. More feedbacks can be added by configuring the tool.

The tool can be triggered automatically every time a new PR is opened, or can be invoked manually by commenting on any PR.

  • When commenting, to edit configurations related to the review tool (pr_reviewer section), use the following template:
/review --pr_reviewer.some_config1=... --pr_reviewer.some_config2=...
[pr_reviewer]
some_config1=...
some_config2=...

See the review usage page for a comprehensive guide on using this tool.

@qodo-code-review

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

NameError Risk

The new helper uses copy.deepcopy but the diff does not show an import for copy in this module. If copy is not already imported elsewhere in the file, this will raise a runtime NameError when the fixtures call the helper.

def extract_vm_from_plan(
    prepared_plan: dict[str, Any],
    vm_index: int,
    fixture_name: str,
) -> dict[str, Any]:
    """Extract a single VM from a multi-VM prepared plan.

    Helper function to create independent plan configurations for simultaneous migrations
    by extracting a specific VM from a prepared plan containing multiple VMs.

    Args:
        prepared_plan: Base prepared plan with cloned VMs
        vm_index: Zero-based index of the VM to extract
        fixture_name: Name of the calling fixture (for error messages)

    Returns:
        Deep copy of prepared plan with only the specified VM

    Raises:
        ValueError: If prepared_plan contains insufficient VMs for the requested index
        ValueError: If VM name is missing from source_vms_data
    """
    vms = prepared_plan.get("virtual_machines", [])
    min_required_vms = vm_index + 1

    if len(vms) < min_required_vms:
        raise ValueError(f"{fixture_name} requires at least {min_required_vms} VM(s) in prepared_plan")

    plan: dict[str, Any] = copy.deepcopy(prepared_plan)
    plan["virtual_machines"] = [plan["virtual_machines"][vm_index]]

    vm_name = plan["virtual_machines"][0]["name"]
    if vm_name not in prepared_plan.get("source_vms_data", {}):
        raise ValueError(f"VM '{vm_name}' missing from prepared_plan source_vms_data")

    plan["source_vms_data"] = {vm_name: prepared_plan["source_vms_data"][vm_name]}
    return plan
Flaky Polling

The simultaneous-execution assertion relies on reading plan.instance.status.conditions without guarding for missing/None status or refreshing the resource, and it classifies anything not succeeded/failed as "Executing". This can create false positives/negatives (e.g., empty conditions early, stale cached object, other intermediate states) and make the test flaky under load.

def _check_plan_status(plan: Plan) -> str:
    """Check if plan is executing, succeeded, or failed."""
    for cond in plan.instance.status.conditions:
        if cond["category"] == "Advisory" and cond["status"] == Plan.Condition.Status.TRUE:
            cond_type = cond["type"]
            if cond_type in (Plan.Status.SUCCEEDED, Plan.Status.FAILED):
                return cond_type
    return "Executing"

# Poll both plans to ensure both reach executing state before either completes
for sample in TimeoutSampler(
    func=lambda: (_check_plan_status(self.plan_resource_1), _check_plan_status(self.plan_resource_2)),
    sleep=2,
    wait_timeout=120,
):
    status_1, status_2 = sample

    # Check if plan 1 is executing
    if status_1 == "Executing":
        if not plan_1_executing:
            LOGGER.info(f"Plan 1 '{self.plan_resource_1.name}' is now executing")
        plan_1_executing = True

    # Check if plan 2 is executing
    if status_2 == "Executing":
        if not plan_2_executing:
            LOGGER.info(f"Plan 2 '{self.plan_resource_2.name}' is now executing")
        plan_2_executing = True

    # Assert both are executing simultaneously
    if plan_1_executing and plan_2_executing and not both_executing_validated:
        LOGGER.info("SUCCESS: Both migrations are executing simultaneously")
        both_executing_validated = True
        break

    # If either plan completed before both were executing, fail the test
    if status_1 in (Plan.Status.SUCCEEDED, Plan.Status.FAILED) and not both_executing_validated:
        raise AssertionError(
            f"Plan 1 reached {status_1} before both plans were executing simultaneously. "
            f"Plan 2 status: {status_2}"
        )
    if status_2 in (Plan.Status.SUCCEEDED, Plan.Status.FAILED) and not both_executing_validated:
        raise AssertionError(
            f"Plan 2 reached {status_2} before both plans were executing simultaneously. "
            f"Plan 1 status: {status_1}"
        )

assert both_executing_validated, "Failed to validate both migrations executing simultaneously"
Typo / API Use

The function name wait_for_migration_complate looks misspelled; if this is not the actual exported symbol it will fail at import/runtime. Even if it exists, the naming inconsistency makes the API harder to discover and maintain.

from utilities.mtv_migration import (
    create_plan_resource,
    execute_migration,
    get_network_migration_map,
    get_storage_migration_map,
    verify_vm_disk_count,
    wait_for_migration_complate,
)
from utilities.naming import sanitize_kubernetes_name

💡 Tool usage guide:

Overview:
The review tool scans the PR code changes, and generates a PR review which includes several types of feedbacks, such as possible PR issues, security threats and relevant test in the PR. More feedbacks can be added by configuring the tool.

The tool can be triggered automatically every time a new PR is opened, or can be invoked manually by commenting on any PR.

  • When commenting, to edit configurations related to the review tool (pr_reviewer section), use the following template:
/review --pr_reviewer.some_config1=... --pr_reviewer.some_config2=...
[pr_reviewer]
some_config1=...
some_config2=...

See the review usage page for a comprehensive guide on using this tool.

@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: 2

🤖 Fix all issues with AI agents
In `@tests/test_copyoffload_migration.py`:
- Around line 3162-3176: Add a "Returns: None" section to the function docstring
that documents validation of migrated VMs (the docstring listing Args like
prepared_plan_1, source_provider, destination_provider, etc.); update the
docstring for that validation function to include a Returns: None entry
consistent with the class's docstring style so the docstring contains Args,
Returns, and Raises sections.
- Around line 3043-3149: Tests use long inline AssertionError messages when a
plan completes early; extract those messages into named constants or a helper
(e.g., EARLY_COMPLETION_MSG_PLAN1, EARLY_COMPLETION_MSG_PLAN2 or a function
format_early_completion_msg(plan_name, status, other_status)) and replace the
inline strings in the assertions inside test_migrate_vms_simultaneously with
references to those constants/helper; update the assertions that check
status_1/status_2 against Plan.Status.SUCCEEDED/Plan.Status.FAILED to call the
formatter so the logic (in _check_plan_status and the TimeoutSampler loop)
remains unchanged but readability and lint (Ruff TRY003) are satisfied.

Comment thread tests/test_copyoffload_migration.py Resolved
Comment thread tests/test_copyoffload_migration.py Resolved
@qodo-code-review

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Possible Issue

The “simultaneous execution” validation reads status from the in-memory Plan objects (plan.instance.status.conditions) but does not appear to refresh/reload the Plan from the cluster on each poll. If the object is not automatically updated by the underlying client, the sampler may loop on stale status, incorrectly fail early, or incorrectly pass. Also, iterating plan.instance.status.conditions assumes both status and conditions exist; missing fields during reconciliation can raise exceptions and make the test flaky.

def _check_plan_status(plan: Plan) -> str:
    """Check if plan is executing, succeeded, or failed."""
    for cond in plan.instance.status.conditions:
        if cond["category"] == "Advisory" and cond["status"] == Plan.Condition.Status.TRUE:
            cond_type = cond["type"]
            if cond_type in (Plan.Status.SUCCEEDED, Plan.Status.FAILED):
                return cond_type
    return "Executing"

# Poll both plans to ensure both reach executing state before either completes
for sample in TimeoutSampler(
    func=lambda: (_check_plan_status(self.plan_resource_1), _check_plan_status(self.plan_resource_2)),
    sleep=2,
    wait_timeout=120,
):
    status_1, status_2 = sample

    # Check if plan 1 is executing
    if status_1 == "Executing":
        if not plan_1_executing:
            LOGGER.info(f"Plan 1 '{self.plan_resource_1.name}' is now executing")
        plan_1_executing = True

    # Check if plan 2 is executing
    if status_2 == "Executing":
        if not plan_2_executing:
            LOGGER.info(f"Plan 2 '{self.plan_resource_2.name}' is now executing")
        plan_2_executing = True

    # Assert both are executing simultaneously
    if plan_1_executing and plan_2_executing and not both_executing_validated:
        LOGGER.info("SUCCESS: Both migrations are executing simultaneously")
        both_executing_validated = True
        break

    # If either plan completed before both were executing, fail the test
    if status_1 in (Plan.Status.SUCCEEDED, Plan.Status.FAILED) and not both_executing_validated:
        raise AssertionError(
            EARLY_COMPLETION_MSG.format(plan_num=1, completed_status=status_1, other_status=status_2)
        )
    if status_2 in (Plan.Status.SUCCEEDED, Plan.Status.FAILED) and not both_executing_validated:
        raise AssertionError(
            EARLY_COMPLETION_MSG.format(plan_num=2, completed_status=status_2, other_status=status_1)
        )

assert both_executing_validated, "Failed to validate both migrations executing simultaneously"
Robustness

_check_plan_status treats any non-(Succeeded/Failed) advisory condition state as “Executing”, which can misclassify “Not started / Pending / Validating / Running other phase” as executing. This weakens the core guarantee the test is trying to assert (both migrations truly running concurrently) and can lead to false positives.

def _check_plan_status(plan: Plan) -> str:
    """Check if plan is executing, succeeded, or failed."""
    for cond in plan.instance.status.conditions:
        if cond["category"] == "Advisory" and cond["status"] == Plan.Condition.Status.TRUE:
            cond_type = cond["type"]
            if cond_type in (Plan.Status.SUCCEEDED, Plan.Status.FAILED):
                return cond_type
    return "Executing"
Type/Import Risk

extract_vm_from_plan uses copy.deepcopy but the diff does not show an added import copy. If copy is not already imported in this module, this will raise a NameError at runtime. Even if currently present elsewhere in the file, it’s worth double-checking because this helper is now used by class-scoped fixtures and would break test collection early.

def extract_vm_from_plan(
    prepared_plan: dict[str, Any],
    vm_index: int,
    fixture_name: str,
) -> dict[str, Any]:
    """Extract a single VM from a multi-VM prepared plan.

    Helper function to create independent plan configurations for simultaneous migrations
    by extracting a specific VM from a prepared plan containing multiple VMs.

    Args:
        prepared_plan: Base prepared plan with cloned VMs
        vm_index: Zero-based index of the VM to extract
        fixture_name: Name of the calling fixture (for error messages)

    Returns:
        Deep copy of prepared plan with only the specified VM

    Raises:
        ValueError: If prepared_plan contains insufficient VMs for the requested index
        ValueError: If VM name is missing from source_vms_data
    """
    vms = prepared_plan.get("virtual_machines", [])
    min_required_vms = vm_index + 1

    if len(vms) < min_required_vms:
        raise ValueError(f"{fixture_name} requires at least {min_required_vms} VM(s) in prepared_plan")

    plan: dict[str, Any] = copy.deepcopy(prepared_plan)
    plan["virtual_machines"] = [plan["virtual_machines"][vm_index]]

    vm_name = plan["virtual_machines"][0]["name"]
    if vm_name not in prepared_plan.get("source_vms_data", {}):
        raise ValueError(f"VM '{vm_name}' missing from prepared_plan source_vms_data")

    plan["source_vms_data"] = {vm_name: prepared_plan["source_vms_data"][vm_name]}
    return plan

💡 Tool usage guide:

Overview:
The review tool scans the PR code changes, and generates a PR review which includes several types of feedbacks, such as possible PR issues, security threats and relevant test in the PR. More feedbacks can be added by configuring the tool.

The tool can be triggered automatically every time a new PR is opened, or can be invoked manually by commenting on any PR.

  • When commenting, to edit configurations related to the review tool (pr_reviewer section), use the following template:
/review --pr_reviewer.some_config1=... --pr_reviewer.some_config2=...
[pr_reviewer]
some_config1=...
some_config2=...

See the review usage page for a comprehensive guide on using this tool.

@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

🤖 Fix all issues with AI agents
In `@tests/test_copyoffload_migration.py`:
- Around line 3100-3107: The nested helper function _check_plan_status lacks a
structured docstring; add a docstring to _check_plan_status that includes an
Args section describing the plan parameter (type Plan), a Returns section
stating it returns a str ("Executing", Plan.Status.SUCCEEDED, or
Plan.Status.FAILED), and a Raises section (if applicable) noting any exceptions
that may be propagated (e.g., AttributeError or KeyError when
plan.instance.status.conditions is malformed); place this docstring immediately
above the _check_plan_status definition so repo tooling and linters recognize
the Args/Returns/Raises entries.

Comment thread tests/test_copyoffload_migration.py Resolved
@qodo-code-review

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Runtime Error

The new helper uses copy.deepcopy but the diff does not show an import for copy. If copy is not already imported in this module, this will raise a NameError at runtime when the fixtures call extract_vm_from_plan.

def extract_vm_from_plan(
    prepared_plan: dict[str, Any],
    vm_index: int,
    fixture_name: str,
) -> dict[str, Any]:
    """Extract a single VM from a multi-VM prepared plan.

    Helper function to create independent plan configurations for simultaneous migrations
    by extracting a specific VM from a prepared plan containing multiple VMs.

    Args:
        prepared_plan: Base prepared plan with cloned VMs
        vm_index: Zero-based index of the VM to extract
        fixture_name: Name of the calling fixture (for error messages)

    Returns:
        Deep copy of prepared plan with only the specified VM

    Raises:
        ValueError: If prepared_plan contains insufficient VMs for the requested index
        ValueError: If VM name is missing from source_vms_data
    """
    vms = prepared_plan.get("virtual_machines", [])
    min_required_vms = vm_index + 1

    if len(vms) < min_required_vms:
        raise ValueError(f"{fixture_name} requires at least {min_required_vms} VM(s) in prepared_plan")

    plan: dict[str, Any] = copy.deepcopy(prepared_plan)
    plan["virtual_machines"] = [plan["virtual_machines"][vm_index]]

    vm_name = plan["virtual_machines"][0]["name"]
    if vm_name not in prepared_plan.get("source_vms_data", {}):
        raise ValueError(f"VM '{vm_name}' missing from prepared_plan source_vms_data")

    plan["source_vms_data"] = {vm_name: prepared_plan["source_vms_data"][vm_name]}
    return plan
Status Polling

The simultaneous-execution validation may be unreliable because it inspects plan.instance.status.conditions without an explicit refresh/reload of the Plan object, and it treats absence of SUCCEEDED/FAILED advisory conditions as "Executing". This can incorrectly report "Executing" before the migration actually starts or miss transitions due to stale cached objects.

# Validate both migrations are executing simultaneously before either completes
LOGGER.info("Validating both migrations enter executing state simultaneously")
plan_1_executing = False
plan_2_executing = False
both_executing_validated = False

def _check_plan_status(plan: Plan) -> str:
    """Check if plan is executing, succeeded, or failed.

    Args:
        plan: The Plan resource to check status for

    Returns:
        str: "Executing" if migration is in progress, Plan.Status.SUCCEEDED if completed successfully,
            or Plan.Status.FAILED if migration failed

    Raises:
        AttributeError: If plan.instance.status.conditions is not accessible
        KeyError: If condition dictionary is missing required keys (category, status, type)
    """
    for cond in plan.instance.status.conditions:
        if cond["category"] == "Advisory" and cond["status"] == Plan.Condition.Status.TRUE:
            cond_type = cond["type"]
            if cond_type in (Plan.Status.SUCCEEDED, Plan.Status.FAILED):
                return cond_type
    return "Executing"

# Poll both plans to ensure both reach executing state before either completes
for sample in TimeoutSampler(
    func=lambda: (_check_plan_status(self.plan_resource_1), _check_plan_status(self.plan_resource_2)),
    sleep=2,
    wait_timeout=120,
):
    status_1, status_2 = sample

    # Check if plan 1 is executing
    if status_1 == "Executing":
        if not plan_1_executing:
            LOGGER.info(f"Plan 1 '{self.plan_resource_1.name}' is now executing")
        plan_1_executing = True

    # Check if plan 2 is executing
    if status_2 == "Executing":
        if not plan_2_executing:
            LOGGER.info(f"Plan 2 '{self.plan_resource_2.name}' is now executing")
        plan_2_executing = True

    # Assert both are executing simultaneously
    if plan_1_executing and plan_2_executing and not both_executing_validated:
        LOGGER.info("SUCCESS: Both migrations are executing simultaneously")
        both_executing_validated = True
        break

    # If either plan completed before both were executing, fail the test
    if status_1 in (Plan.Status.SUCCEEDED, Plan.Status.FAILED) and not both_executing_validated:
        raise AssertionError(
            EARLY_COMPLETION_MSG.format(plan_num=1, completed_status=status_1, other_status=status_2)
        )
    if status_2 in (Plan.Status.SUCCEEDED, Plan.Status.FAILED) and not both_executing_validated:
        raise AssertionError(
            EARLY_COMPLETION_MSG.format(plan_num=2, completed_status=status_2, other_status=status_1)
        )

assert both_executing_validated, "Failed to validate both migrations executing simultaneously"
Typo / API

The function name wait_for_migration_complate appears misspelled. If the canonical helper is wait_for_migration_complete (or similar), this will fail at import/runtime. Even if it exists, the typo reduces discoverability and increases the chance of future incorrect usage.

from utilities.mtv_migration import (
    create_plan_resource,
    execute_migration,
    get_network_migration_map,
    get_storage_migration_map,
    verify_vm_disk_count,
    wait_for_migration_complate,
)
from utilities.naming import sanitize_kubernetes_name
from utilities.post_migration import check_vms
from utilities.resources import create_and_store_resource
from utilities.ssh_utils import SSHConnectionManager


LOGGER = get_logger(__name__)

# Error message template for simultaneous migration validation
EARLY_COMPLETION_MSG = (
    "Plan {plan_num} reached {completed_status} before both plans were executing simultaneously. "
    "Other plan status: {other_status}"
)


@pytest.mark.copyoffload
@pytest.mark.incremental
@pytest.mark.parametrize(
    "class_plan_config",
    [pytest.param(py_config["tests_params"]["test_copyoffload_thin_migration"])],
    indirect=True,
    ids=["MTV-559:copyoffload-thin"],
)
@pytest.mark.usefixtures("multus_network_name", "copyoffload_config", "setup_copyoffload_ssh", "cleanup_migrated_vms")
class TestCopyoffloadThinMigration:
    """Copy-offload migration test - thin disk."""

    storage_map: StorageMap
    network_map: NetworkMap
    plan_resource: Plan

    def test_create_storagemap(
        self,
        prepared_plan,
        fixture_store,
        ocp_admin_client,
        source_provider,
        destination_provider,
        source_provider_inventory,
        target_namespace,
        source_provider_data,
        copyoffload_storage_secret,
    ):
        """Create StorageMap with copy-offload configuration."""
        copyoffload_config_data = source_provider_data["copyoffload"]
        storage_vendor_product = copyoffload_config_data["storage_vendor_product"]
        datastore_id = copyoffload_config_data["datastore_id"]
        storage_class = py_config["storage_class"]

        vms_names = [vm["name"] for vm in prepared_plan["virtual_machines"]]

        offload_plugin_config = {
            "vsphereXcopyConfig": {
                "secretRef": copyoffload_storage_secret.name,
                "storageVendorProduct": storage_vendor_product,
            }
        }

        self.__class__.storage_map = get_storage_migration_map(
            fixture_store=fixture_store,
            target_namespace=target_namespace,
            source_provider=source_provider,
            destination_provider=destination_provider,
            ocp_admin_client=ocp_admin_client,
            source_provider_inventory=source_provider_inventory,
            vms=vms_names,
            storage_class=storage_class,
            datastore_id=datastore_id,
            offload_plugin_config=offload_plugin_config,
            access_mode="ReadWriteOnce",
            volume_mode="Block",
        )
        assert self.storage_map, "StorageMap creation failed"

    def test_create_networkmap(
        self,
        prepared_plan,
        fixture_store,
        ocp_admin_client,
        source_provider,
        destination_provider,
        source_provider_inventory,
        target_namespace,
        multus_network_name,
    ):
        """Create NetworkMap resource."""
        vms_names = [vm["name"] for vm in prepared_plan["virtual_machines"]]
        self.__class__.network_map = get_network_migration_map(
            fixture_store=fixture_store,
            source_provider=source_provider,
            destination_provider=destination_provider,
            source_provider_inventory=source_provider_inventory,
            ocp_admin_client=ocp_admin_client,
            multus_network_name=multus_network_name,
            target_namespace=target_namespace,
            vms=vms_names,
        )
        assert self.network_map, "NetworkMap creation failed"

    def test_create_plan(
        self,
        prepared_plan,
        fixture_store,
        ocp_admin_client,
        source_provider,
        destination_provider,
        target_namespace,
        source_provider_inventory,
    ):
        """Create MTV Plan CR resource."""
        for vm in prepared_plan["virtual_machines"]:
            vm_name = vm["name"]
            vm_data = source_provider_inventory.get_vm(vm_name)
            vm["id"] = vm_data["id"]

        self.__class__.plan_resource = create_plan_resource(
            ocp_admin_client=ocp_admin_client,
            fixture_store=fixture_store,
            source_provider=source_provider,
            destination_provider=destination_provider,
            storage_map=self.storage_map,
            network_map=self.network_map,
            virtual_machines_list=prepared_plan["virtual_machines"],
            target_namespace=target_namespace,
            warm_migration=prepared_plan.get("warm_migration", False),
            copyoffload=prepared_plan.get("copyoffload", False),
        )
        assert self.plan_resource, "Plan creation failed"

    def test_migrate_vms(self, fixture_store, ocp_admin_client, target_namespace):
        """Execute migration."""
        execute_migration(
            ocp_admin_client=ocp_admin_client,
            fixture_store=fixture_store,
            plan=self.plan_resource,
            target_namespace=target_namespace,
        )

    def test_check_vms(
        self,
        prepared_plan,
        source_provider,
        destination_provider,
        source_provider_data,
        target_namespace,
        source_vms_namespace,
        source_provider_inventory,
        vm_ssh_connections,
    ):
        """Validate migrated VMs."""
        check_vms(
            plan=prepared_plan,
            source_provider=source_provider,
            destination_provider=destination_provider,
            network_map_resource=self.network_map,
            storage_map_resource=self.storage_map,
            source_provider_data=source_provider_data,
            source_vms_namespace=source_vms_namespace,
            source_provider_inventory=source_provider_inventory,
            vm_ssh_connections=vm_ssh_connections,
        )


class CopyoffloadSnapshotBase:
    """Base class for copy-offload migration tests with snapshots."""

    storage_map: StorageMap
    network_map: NetworkMap
    plan_resource: Plan

    def test_create_storagemap(
        self,
        prepared_plan,
        fixture_store,
        ocp_admin_client,
        source_provider,
        destination_provider,
        source_provider_inventory,
        target_namespace,
        source_provider_data,
        copyoffload_storage_secret,
    ):
        """Create StorageMap with copy-offload configuration after creating snapshots."""
        vm_cfg = prepared_plan["virtual_machines"][0]
        provider_vm_api = prepared_plan["source_vms_data"][vm_cfg["name"]]["provider_vm_api"]

        # Ensure VM is powered on for snapshot creation
        source_provider.start_vm(provider_vm_api)
        source_provider.wait_for_vmware_guest_info(provider_vm_api, timeout=60)

        snapshots_to_create = int(vm_cfg["snapshots"])
        snapshot_prefix = f"{vm_cfg['name']}-{fixture_store['session_uuid']}-snapshot"

        for idx in range(1, snapshots_to_create + 1):
            source_provider.create_snapshot(
                vm=provider_vm_api,
                name=f"{snapshot_prefix}-{idx}",
                description="mtv-api-tests copy-offload snapshots migration test",
                memory=False,
                quiesce=False,
                wait_timeout=60 * 10,
            )

        # Refresh and store snapshots list for post-migration snapshot checks
        vm_cfg["snapshots_before_migration"] = source_provider.vm_dict(provider_vm_api=provider_vm_api)[
            "snapshots_data"
        ]
        assert len(vm_cfg["snapshots_before_migration"]) >= snapshots_to_create, (
            f"Expected at least {snapshots_to_create} snapshots, got {len(vm_cfg['snapshots_before_migration'])}"
        )

        # Cold migration expects VM powered off
        source_provider.stop_vm(provider_vm_api)

        copyoffload_config_data = source_provider_data["copyoffload"]
        storage_vendor_product = copyoffload_config_data["storage_vendor_product"]
        datastore_id = copyoffload_config_data["datastore_id"]
        storage_class = py_config["storage_class"]

        vms_names = [vm["name"] for vm in prepared_plan["virtual_machines"]]

        offload_plugin_config = {
            "vsphereXcopyConfig": {
                "secretRef": copyoffload_storage_secret.name,
                "storageVendorProduct": storage_vendor_product,
            }
        }

        self.__class__.storage_map = get_storage_migration_map(
            fixture_store=fixture_store,
            target_namespace=target_namespace,
            source_provider=source_provider,
            destination_provider=destination_provider,
            ocp_admin_client=ocp_admin_client,
            source_provider_inventory=source_provider_inventory,
            vms=vms_names,
            storage_class=storage_class,
            datastore_id=datastore_id,
            offload_plugin_config=offload_plugin_config,
            access_mode="ReadWriteOnce",
            volume_mode="Block",
        )
        assert self.storage_map, "StorageMap creation failed"

    def test_create_networkmap(
        self,
        prepared_plan,
        fixture_store,
        ocp_admin_client,
        source_provider,
        destination_provider,
        source_provider_inventory,
        target_namespace,
        multus_network_name,
    ):
        """Create NetworkMap resource."""
        vms_names = [vm["name"] for vm in prepared_plan["virtual_machines"]]
        self.__class__.network_map = get_network_migration_map(
            fixture_store=fixture_store,
            source_provider=source_provider,
            destination_provider=destination_provider,
            source_provider_inventory=source_provider_inventory,
            ocp_admin_client=ocp_admin_client,
            multus_network_name=multus_network_name,
            target_namespace=target_namespace,
            vms=vms_names,
        )
        assert self.network_map, "NetworkMap creation failed"

    def test_create_plan(
        self,
        prepared_plan,
        fixture_store,
        ocp_admin_client,
        source_provider,
        destination_provider,
        target_namespace,
        source_provider_inventory,
    ):
        """Create MTV Plan CR resource."""
        for vm in prepared_plan["virtual_machines"]:
            vm_name = vm["name"]
            vm_data = source_provider_inventory.get_vm(vm_name)
            vm["id"] = vm_data["id"]

        self.__class__.plan_resource = create_plan_resource(
            ocp_admin_client=ocp_admin_client,
            fixture_store=fixture_store,
            source_provider=source_provider,
            destination_provider=destination_provider,
            storage_map=self.storage_map,
            network_map=self.network_map,
            virtual_machines_list=prepared_plan["virtual_machines"],
            target_namespace=target_namespace,
            warm_migration=prepared_plan.get("warm_migration", False),
            copyoffload=prepared_plan.get("copyoffload", False),
        )
        assert self.plan_resource, "Plan creation failed"

    def test_migrate_vms(self, fixture_store, ocp_admin_client, target_namespace):
        """Execute migration."""
        execute_migration(
            ocp_admin_client=ocp_admin_client,
            fixture_store=fixture_store,
            plan=self.plan_resource,
            target_namespace=target_namespace,
        )

    def test_check_vms(
        self,
        prepared_plan,
        source_provider,
        destination_provider,
        source_provider_data,
        target_namespace,
        source_vms_namespace,
        source_provider_inventory,
        vm_ssh_connections,
    ):
        """Validate migrated VMs and verify disk count."""
        check_vms(
            plan=prepared_plan,
            source_provider=source_provider,
            destination_provider=destination_provider,
            network_map_resource=self.network_map,
            storage_map_resource=self.storage_map,
            source_provider_data=source_provider_data,
            source_vms_namespace=source_vms_namespace,
            source_provider_inventory=source_provider_inventory,
            vm_ssh_connections=vm_ssh_connections,
        )
        verify_vm_disk_count(
            destination_provider=destination_provider, plan=prepared_plan, target_namespace=target_namespace
        )


@pytest.mark.copyoffload
@pytest.mark.incremental
@pytest.mark.parametrize(
    "class_plan_config",
    [pytest.param(py_config["tests_params"]["test_copyoffload_thin_snapshots_migration"])],
    indirect=True,
    ids=["copyoffload-thin-snapshots"],
)
@pytest.mark.skipif(
    py_config.get("source_provider_type") != Provider.ProviderType.VSPHERE,
    reason="Snapshots copy-offload test is only applicable to vSphere source providers",
)
@pytest.mark.usefixtures("multus_network_name", "copyoffload_config", "setup_copyoffload_ssh", "cleanup_migrated_vms")
class TestCopyoffloadThinSnapshotsMigration(CopyoffloadSnapshotBase):
    """Copy-offload migration test - thin disk with snapshots."""


@pytest.mark.copyoffload
@pytest.mark.incremental
@pytest.mark.parametrize(
    "class_plan_config",
    [pytest.param(py_config["tests_params"]["test_copyoffload_2tb_vm_snapshots_migration"])],
    indirect=True,
    ids=["MTV-575:copyoffload-2tb-vm-snapshots"],
)
@pytest.mark.skipif(
    py_config.get("source_provider_type") != Provider.ProviderType.VSPHERE,
    reason="Snapshots copy-offload test is only applicable to vSphere source providers",
)
@pytest.mark.usefixtures("multus_network_name", "copyoffload_config", "setup_copyoffload_ssh", "cleanup_migrated_vms")
class TestCopyoffload2TbVmSnapshotsMigration(CopyoffloadSnapshotBase):
    """Copy-offload migration test - 2TB VM with snapshots."""


@pytest.mark.copyoffload
@pytest.mark.incremental
@pytest.mark.parametrize(
    "class_plan_config",
    [pytest.param(py_config["tests_params"]["test_copyoffload_thick_lazy_migration"])],
    indirect=True,
    ids=["MTV-580:copyoffload-thick-lazy"],
)
@pytest.mark.usefixtures("multus_network_name", "copyoffload_config", "setup_copyoffload_ssh", "cleanup_migrated_vms")
class TestCopyoffloadThickLazyMigration:
    """Copy-offload migration test - thick lazy disk."""

    storage_map: StorageMap
    network_map: NetworkMap
    plan_resource: Plan

    def test_create_storagemap(
        self,
        prepared_plan,
        fixture_store,
        ocp_admin_client,
        source_provider,
        destination_provider,
        source_provider_inventory,
        target_namespace,
        source_provider_data,
        copyoffload_storage_secret,
    ):
        """Create StorageMap with copy-offload configuration."""
        copyoffload_config_data = source_provider_data["copyoffload"]
        storage_vendor_product = copyoffload_config_data["storage_vendor_product"]
        datastore_id = copyoffload_config_data["datastore_id"]
        storage_class = py_config["storage_class"]

        vms_names = [vm["name"] for vm in prepared_plan["virtual_machines"]]

        offload_plugin_config = {
            "vsphereXcopyConfig": {
                "secretRef": copyoffload_storage_secret.name,
                "storageVendorProduct": storage_vendor_product,
            }
        }

        self.__class__.storage_map = get_storage_migration_map(
            fixture_store=fixture_store,
            target_namespace=target_namespace,
            source_provider=source_provider,
            destination_provider=destination_provider,
            ocp_admin_client=ocp_admin_client,
            source_provider_inventory=source_provider_inventory,
            vms=vms_names,
            storage_class=storage_class,
            datastore_id=datastore_id,
            offload_plugin_config=offload_plugin_config,
            access_mode="ReadWriteOnce",
            volume_mode="Block",
        )
        assert self.storage_map, "StorageMap creation failed"

    def test_create_networkmap(
        self,
        prepared_plan,
        fixture_store,
        ocp_admin_client,
        source_provider,
        destination_provider,
        source_provider_inventory,
        target_namespace,
        multus_network_name,
    ):
        """Create NetworkMap resource."""
        vms_names = [vm["name"] for vm in prepared_plan["virtual_machines"]]
        self.__class__.network_map = get_network_migration_map(
            fixture_store=fixture_store,
            source_provider=source_provider,
            destination_provider=destination_provider,
            source_provider_inventory=source_provider_inventory,
            ocp_admin_client=ocp_admin_client,
            multus_network_name=multus_network_name,
            target_namespace=target_namespace,
            vms=vms_names,
        )
        assert self.network_map, "NetworkMap creation failed"

    def test_create_plan(
        self,
        prepared_plan,
        fixture_store,
        ocp_admin_client,
        source_provider,
        destination_provider,
        target_namespace,
        source_provider_inventory,
    ):
        """Create MTV Plan CR resource."""
        for vm in prepared_plan["virtual_machines"]:
            vm_name = vm["name"]
            vm_data = source_provider_inventory.get_vm(vm_name)
            vm["id"] = vm_data["id"]

        self.__class__.plan_resource = create_plan_resource(
            ocp_admin_client=ocp_admin_client,
            fixture_store=fixture_store,
            source_provider=source_provider,
            destination_provider=destination_provider,
            storage_map=self.storage_map,
            network_map=self.network_map,
            virtual_machines_list=prepared_plan["virtual_machines"],
            target_namespace=target_namespace,
            warm_migration=prepared_plan.get("warm_migration", False),
            copyoffload=prepared_plan.get("copyoffload", False),
        )
        assert self.plan_resource, "Plan creation failed"

    def test_migrate_vms(self, fixture_store, ocp_admin_client, target_namespace):
        """Execute migration."""
        execute_migration(
            ocp_admin_client=ocp_admin_client,
            fixture_store=fixture_store,
            plan=self.plan_resource,
            target_namespace=target_namespace,
        )

    def test_check_vms(
        self,
        prepared_plan,
        source_provider,
        destination_provider,
        source_provider_data,
        target_namespace,
        source_vms_namespace,
        source_provider_inventory,
        vm_ssh_connections,
    ):
        """Validate migrated VMs."""
        check_vms(
            plan=prepared_plan,
            source_provider=source_provider,
            destination_provider=destination_provider,
            network_map_resource=self.network_map,
            storage_map_resource=self.storage_map,
            source_provider_data=source_provider_data,
            source_vms_namespace=source_vms_namespace,
            source_provider_inventory=source_provider_inventory,
            vm_ssh_connections=vm_ssh_connections,
        )


@pytest.mark.copyoffload
@pytest.mark.incremental
@pytest.mark.parametrize(
    "class_plan_config",
    [pytest.param(py_config["tests_params"]["test_copyoffload_multi_disk_migration"])],
    indirect=True,
    ids=["MTV-561:copyoffload-multi-disk"],
)
@pytest.mark.usefixtures("multus_network_name", "copyoffload_config", "setup_copyoffload_ssh", "cleanup_migrated_vms")
class TestCopyoffloadMultiDiskMigration:
    """Copy-offload migration test - multiple disks."""

    storage_map: StorageMap
    network_map: NetworkMap
    plan_resource: Plan

    def test_create_storagemap(
        self,
        prepared_plan,
        fixture_store,
        ocp_admin_client,
        source_provider,
        destination_provider,
        source_provider_inventory,
        target_namespace,
        source_provider_data,
        copyoffload_storage_secret,
    ):
        """Create StorageMap with copy-offload configuration."""
        copyoffload_config_data = source_provider_data["copyoffload"]
        storage_vendor_product = copyoffload_config_data["storage_vendor_product"]
        datastore_id = copyoffload_config_data["datastore_id"]
        storage_class = py_config["storage_class"]

        vms_names = [vm["name"] for vm in prepared_plan["virtual_machines"]]

        offload_plugin_config = {
            "vsphereXcopyConfig": {
                "secretRef": copyoffload_storage_secret.name,
                "storageVendorProduct": storage_vendor_product,
            }
        }

        self.__class__.storage_map = get_storage_migration_map(
            fixture_store=fixture_store,
            target_namespace=target_namespace,
            source_provider=source_provider,
            destination_provider=destination_provider,
            ocp_admin_client=ocp_admin_client,
            source_provider_inventory=source_provider_inventory,
            vms=vms_names,
            storage_class=storage_class,
            datastore_id=datastore_id,
            offload_plugin_config=offload_plugin_config,
            access_mode="ReadWriteOnce",
            volume_mode="Block",
        )
        assert self.storage_map, "StorageMap creation failed"

    def test_create_networkmap(
        self,
        prepared_plan,
        fixture_store,
        ocp_admin_client,
        source_provider,
        destination_provider,
        source_provider_inventory,
        target_namespace,
        multus_network_name,
    ):
        """Create NetworkMap resource."""
        vms_names = [vm["name"] for vm in prepared_plan["virtual_machines"]]
        self.__class__.network_map = get_network_migration_map(
            fixture_store=fixture_store,
            source_provider=source_provider,
            destination_provider=destination_provider,
            source_provider_inventory=source_provider_inventory,
            ocp_admin_client=ocp_admin_client,
            multus_network_name=multus_network_name,
            target_namespace=target_namespace,
            vms=vms_names,
        )
        assert self.network_map, "NetworkMap creation failed"

    def test_create_plan(
        self,
        prepared_plan,
        fixture_store,
        ocp_admin_client,
        source_provider,
        destination_provider,
        target_namespace,
        source_provider_inventory,
    ):
        """Create MTV Plan CR resource."""
        for vm in prepared_plan["virtual_machines"]:
            vm_name = vm["name"]
            vm_data = source_provider_inventory.get_vm(vm_name)
            vm["id"] = vm_data["id"]

        self.__class__.plan_resource = create_plan_resource(
            ocp_admin_client=ocp_admin_client,
            fixture_store=fixture_store,
            source_provider=source_provider,
            destination_provider=destination_provider,
            storage_map=self.storage_map,
            network_map=self.network_map,
            virtual_machines_list=prepared_plan["virtual_machines"],
            target_namespace=target_namespace,
            warm_migration=prepared_plan.get("warm_migration", False),
            copyoffload=prepared_plan.get("copyoffload", False),
        )
        assert self.plan_resource, "Plan creation failed"

    def test_migrate_vms(self, fixture_store, ocp_admin_client, target_namespace):
        """Execute migration."""
        execute_migration(
            ocp_admin_client=ocp_admin_client,
            fixture_store=fixture_store,
            plan=self.plan_resource,
            target_namespace=target_namespace,
        )

    def test_check_vms(
        self,
        prepared_plan,
        source_provider,
        destination_provider,
        source_provider_data,
        target_namespace,
        source_vms_namespace,
        source_provider_inventory,
        vm_ssh_connections,
    ):
        """Validate migrated VMs and verify disk count."""
        check_vms(
            plan=prepared_plan,
            source_provider=source_provider,
            destination_provider=destination_provider,
            network_map_resource=self.network_map,
            storage_map_resource=self.storage_map,
            source_provider_data=source_provider_data,
            source_vms_namespace=source_vms_namespace,
            source_provider_inventory=source_provider_inventory,
            vm_ssh_connections=vm_ssh_connections,
        )
        verify_vm_disk_count(
            destination_provider=destination_provider, plan=prepared_plan, target_namespace=target_namespace
        )


@pytest.mark.copyoffload
@pytest.mark.incremental
@pytest.mark.parametrize(
    "class_plan_config",
    [pytest.param(py_config["tests_params"]["test_copyoffload_multi_disk_different_path_migration"])],
    indirect=True,
    ids=["MTV-563:copyoffload-multi-disk-different-path"],
)
@pytest.mark.usefixtures("multus_network_name", "copyoffload_config", "setup_copyoffload_ssh", "cleanup_migrated_vms")
class TestCopyoffloadMultiDiskDifferentPathMigration:
    """Copy-offload migration test - multiple disks in different paths."""

    storage_map: StorageMap
    network_map: NetworkMap
    plan_resource: Plan

    def test_create_storagemap(
        self,
        prepared_plan,
        fixture_store,
        ocp_admin_client,
        source_provider,
        destination_provider,
        source_provider_inventory,
        target_namespace,
        source_provider_data,
        copyoffload_storage_secret,
    ):
        """Create StorageMap with copy-offload configuration."""
        copyoffload_config_data = source_provider_data["copyoffload"]
        storage_vendor_product = copyoffload_config_data["storage_vendor_product"]
        datastore_id = copyoffload_config_data["datastore_id"]
        storage_class = py_config["storage_class"]

        vms_names = [vm["name"] for vm in prepared_plan["virtual_machines"]]

        offload_plugin_config = {
            "vsphereXcopyConfig": {
                "secretRef": copyoffload_storage_secret.name,
                "storageVendorProduct": storage_vendor_product,
            }
        }

        self.__class__.storage_map = get_storage_migration_map(
            fixture_store=fixture_store,
            target_namespace=target_namespace,
            source_provider=source_provider,
            destination_provider=destination_provider,
            ocp_admin_client=ocp_admin_client,
            source_provider_inventory=source_provider_inventory,
            vms=vms_names,
            storage_class=storage_class,
            datastore_id=datastore_id,
            offload_plugin_config=offload_plugin_config,
            access_mode="ReadWriteOnce",
            volume_mode="Block",
        )
        assert self.storage_map, "StorageMap creation failed"

    def test_create_networkmap(
        self,
        prepared_plan,
        fixture_store,
        ocp_admin_client,
        source_provider,
        destination_provider,
        source_provider_inventory,
        target_namespace,
        multus_network_name,
    ):
        """Create NetworkMap resource."""
        vms_names = [vm["name"] for vm in prepared_plan["virtual_machines"]]
        self.__class__.network_map = get_network_migration_map(
            fixture_store=fixture_store,
            source_provider=source_provider,
            destination_provider=destination_provider,
            source_provider_inventory=source_provider_inventory,
            ocp_admin_client=ocp_admin_client,
            multus_network_name=multus_network_name,
            target_namespace=target_namespace,
            vms=vms_names,
        )
        assert self.network_map, "NetworkMap creation failed"

    def test_create_plan(
        self,
        prepared_plan,
        fixture_store,
        ocp_admin_client,
        source_provider,
        destination_provider,
        target_namespace,
        source_provider_inventory,
    ):
        """Create MTV Plan CR resource."""
        for vm in prepared_plan["virtual_machines"]:
            vm_name = vm["name"]
            vm_data = source_provider_inventory.get_vm(vm_name)
            vm["id"] = vm_data["id"]

        self.__class__.plan_resource = create_plan_resource(
            ocp_admin_client=ocp_admin_client,
            fixture_store=fixture_store,
            source_provider=source_provider,
            destination_provider=destination_provider,
            storage_map=self.storage_map,
            network_map=self.network_map,
            virtual_machines_list=prepared_plan["virtual_machines"],
            target_namespace=target_namespace,
            warm_migration=prepared_plan.get("warm_migration", False),
            copyoffload=prepared_plan.get("copyoffload", False),
        )
        assert self.plan_resource, "Plan creation failed"

    def test_migrate_vms(self, fixture_store, ocp_admin_client, target_namespace):
        """Execute migration."""
        execute_migration(
            ocp_admin_client=ocp_admin_client,
            fixture_store=fixture_store,
            plan=self.plan_resource,
            target_namespace=target_namespace,
        )

    def test_check_vms(
        self,
        prepared_plan,
        source_provider,
        destination_provider,
        source_provider_data,
        target_namespace,
        source_vms_namespace,
        source_provider_inventory,
        vm_ssh_connections,
    ):
        """Validate migrated VMs and verify disk count."""
        check_vms(
            plan=prepared_plan,
            source_provider=source_provider,
            destination_provider=destination_provider,
            network_map_resource=self.network_map,
            storage_map_resource=self.storage_map,
            source_provider_data=source_provider_data,
            source_vms_namespace=source_vms_namespace,
            source_provider_inventory=source_provider_inventory,
            vm_ssh_connections=vm_ssh_connections,
        )
        verify_vm_disk_count(
            destination_provider=destination_provider, plan=prepared_plan, target_namespace=target_namespace
        )


@pytest.mark.copyoffload
@pytest.mark.incremental
@pytest.mark.parametrize(
    "class_plan_config",
    [pytest.param(py_config["tests_params"]["test_copyoffload_rdm_virtual_disk_migration"])],
    indirect=True,
    ids=["MTV-562:copyoffload-rdm-virtual"],
)
@pytest.mark.usefixtures("multus_network_name", "copyoffload_config", "cleanup_migrated_vms")
class TestCopyoffloadRdmVirtualDiskMigration:
    """Copy-offload migration test - RDM virtual disk."""

    storage_map: StorageMap
    network_map: NetworkMap
    plan_resource: Plan

    def test_create_storagemap(
        self,
        prepared_plan,
        fixture_store,
        ocp_admin_client,
        source_provider,
        destination_provider,
        source_provider_inventory,
        target_namespace,
        source_provider_data,
        copyoffload_storage_secret,
    ):
        """Create StorageMap with copy-offload configuration."""
        copyoffload_config_data = source_provider_data["copyoffload"]
        storage_vendor_product = copyoffload_config_data["storage_vendor_product"]
        datastore_id = copyoffload_config_data["datastore_id"]
        storage_class = py_config["storage_class"]

        # Validate RDM LUN is configured
        if "rdm_lun_uuid" not in copyoffload_config_data or not copyoffload_config_data["rdm_lun_uuid"]:
            pytest.fail("rdm_lun_uuid is required in copyoffload configuration for RDM disk tests")

        vms_names = [vm["name"] for vm in prepared_plan["virtual_machines"]]

        offload_plugin_config = {
            "vsphereXcopyConfig": {
                "secretRef": copyoffload_storage_secret.name,
                "storageVendorProduct": storage_vendor_product,
            }
        }

        self.__class__.storage_map = get_storage_migration_map(
            fixture_store=fixture_store,
            target_namespace=target_namespace,
            source_provider=source_provider,
            destination_provider=destination_provider,
            ocp_admin_client=ocp_admin_client,
            source_provider_inventory=source_provider_inventory,
            vms=vms_names,
            storage_class=storage_class,
            datastore_id=datastore_id,
            offload_plugin_config=offload_plugin_config,
            access_mode="ReadWriteOnce",
            volume_mode="Block",
        )
        assert self.storage_map, "StorageMap creation failed"

    def test_create_networkmap(
        self,
        prepared_plan,
        fixture_store,
        ocp_admin_client,
        source_provider,
        destination_provider,
        source_provider_inventory,
        target_namespace,
        multus_network_name,
    ):
        """Create NetworkMap resource."""
        vms_names = [vm["name"] for vm in prepared_plan["virtual_machines"]]
        self.__class__.network_map = get_network_migration_map(
            fixture_store=fixture_store,
            source_provider=source_provider,
            destination_provider=destination_provider,
            source_provider_inventory=source_provider_inventory,
            ocp_admin_client=ocp_admin_client,
            multus_network_name=multus_network_name,
            target_namespace=target_namespace,
            vms=vms_names,
        )
        assert self.network_map, "NetworkMap creation failed"

    def test_create_plan(
        self,
        prepared_plan,
        fixture_store,
        ocp_admin_client,
        source_provider,
        destination_provider,
        target_namespace,
        source_provider_inventory,
    ):
        """Create MTV Plan CR resource."""
        for vm in prepared_plan["virtual_machines"]:
            vm_name = vm["name"]
            vm_data = source_provider_inventory.get_vm(vm_name)
            vm["id"] = vm_data["id"]

        self.__class__.plan_resource = create_plan_resource(
            ocp_admin_client=ocp_admin_client,
            fixture_store=fixture_store,
            source_provider=source_provider,
            destination_provider=destination_provider,
            storage_map=self.storage_map,
            network_map=self.network_map,
            virtual_machines_list=prepared_plan["virtual_machines"],
            target_namespace=target_namespace,
            warm_migration=prepared_plan.get("warm_migration", False),
            copyoffload=prepared_plan.get("copyoffload", False),
        )
        assert self.plan_resource, "Plan creation failed"

    def test_migrate_vms(self, fixture_store, ocp_admin_client, target_namespace):
        """Execute migration."""
        execute_migration(
            ocp_admin_client=ocp_admin_client,
            fixture_store=fixture_store,
            plan=self.plan_resource,
            target_namespace=target_namespace,
        )

    def test_check_vms(
        self,
        prepared_plan,
        source_provider,
        destination_provider,
        source_provider_data,
        target_namespace,
        source_vms_namespace,
        source_provider_inventory,
        vm_ssh_connections,
    ):
        """Validate migrated VMs and verify disk count."""
        check_vms(
            plan=prepared_plan,
            source_provider=source_provider,
            destination_provider=destination_provider,
            network_map_resource=self.network_map,
            storage_map_resource=self.storage_map,
            source_provider_data=source_provider_data,
            source_vms_namespace=source_vms_namespace,
            source_provider_inventory=source_provider_inventory,
            vm_ssh_connections=vm_ssh_connections,
        )
        verify_vm_disk_count(
            destination_provider=destination_provider, plan=prepared_plan, target_namespace=target_namespace
        )


@pytest.mark.copyoffload
@pytest.mark.incremental
@pytest.mark.parametrize(
    "class_plan_config",
    [pytest.param(py_config["tests_params"]["test_copyoffload_multi_datastore_migration"])],
    indirect=True,
    ids=["MTV-564:copyoffload-multi-datastore"],
)
@pytest.mark.usefixtures("multus_network_name", "copyoffload_config", "cleanup_migrated_vms")
class TestCopyoffloadMultiDatastoreMigration:
    """Copy-offload migration test - multiple datastores."""

    storage_map: StorageMap
    network_map: NetworkMap
    plan_resource: Plan

    def test_create_storagemap(
        self,
        prepared_plan,
        fixture_store,
        ocp_admin_client,
        source_provider,
        destination_provider,
        source_provider_inventory,
        target_namespace,
        source_provider_data,
        copyoffload_storage_secret,
    ):
        """Create StorageMap with copy-offload configuration for multiple datastores."""
        copyoffload_config_data = source_provider_data["copyoffload"]
        storage_vendor_product = copyoffload_config_data["storage_vendor_product"]
        datastore_id = copyoffload_config_data["datastore_id"]
        secondary_datastore_id = copyoffload_config_data["secondary_datastore_id"]
        storage_class = py_config["storage_class"]

        # For multi-datastore test, ensure secondary datastore is configured
        if not secondary_datastore_id:
            pytest.fail(
                "Multi-datastore test requires 'secondary_datastore_id' to be configured in copyoffload section."
            )

        LOGGER.info("Multi-datastore migration using primary datastore: %s", datastore_id)
        LOGGER.info("Multi-datastore migration using secondary datastore: %s", secondary_datastore_id)

        vms_names = [vm["name"] for vm in prepared_plan["virtual_machines"]]

        offload_plugin_config = {
            "vsphereXcopyConfig": {
                "secretRef": copyoffload_storage_secret.name,
                "storageVendorProduct": storage_vendor_product,
            }
        }

        self.__class__.storage_map = get_storage_migration_map(
            fixture_store=fixture_store,
            target_namespace=target_namespace,
            source_provider=source_provider,
            destination_provider=destination_provider,
            ocp_admin_client=ocp_admin_client,
            source_provider_inventory=source_provider_inventory,
            vms=vms_names,
            storage_class=storage_class,
            datastore_id=datastore_id,
            secondary_datastore_id=secondary_datastore_id,
            offload_plugin_config=offload_plugin_config,
            access_mode="ReadWriteOnce",
            volume_mode="Block",
        )
        assert self.storage_map, "StorageMap creation failed"

    def test_create_networkmap(
        self,
        prepared_plan,
        fixture_store,
        ocp_admin_client,
        source_provider,
        destination_provider,
        source_provider_inventory,
        target_namespace,
        multus_network_name,
    ):
        """Create NetworkMap resource."""
        vms_names = [vm["name"] for vm in prepared_plan["virtual_machines"]]
        self.__class__.network_map = get_network_migration_map(
            fixture_store=fixture_store,
            source_provider=source_provider,
            destination_provider=destination_provider,
            source_provider_inventory=source_provider_inventory,
            ocp_admin_client=ocp_admin_client,
            multus_network_name=multus_network_name,
            target_namespace=target_namespace,
            vms=vms_names,
        )
        assert self.network_map, "NetworkMap creation failed"

    def test_create_plan(
        self,
        prepared_plan,
        fixture_store,
        ocp_admin_client,
        source_provider,
        destination_provider,
        target_namespace,
        source_provider_inventory,
    ):
        """Create MTV Plan CR resource."""
        for vm in prepared_plan["virtual_machines"]:
            vm_name = vm["name"]
            vm_data = source_provider_inventory.get_vm(vm_name)
            vm["id"] = vm_data["id"]

        self.__class__.plan_resource = create_plan_resource(
            ocp_admin_client=ocp_admin_client,
            fixture_store=fixture_store,
            source_provider=source_provider,
            destination_provider=destination_provider,
            storage_map=self.storage_map,
            network_map=self.network_map,
            virtual_machines_list=prepared_plan["virtual_machines"],
            target_namespace=target_namespace,
            warm_migration=prepared_plan.get("warm_migration", False),
            copyoffload=prepared_plan.get("copyoffload", False),
        )
        assert self.plan_resource, "Plan creation failed"

    def test_migrate_vms(self, fixture_store, ocp_admin_client, target_namespace):
        """Execute migration."""
        execute_migration(
            ocp_admin_client=ocp_admin_client,
            fixture_store=fixture_store,
            plan=self.plan_resource,
            target_namespace=target_namespace,
        )

    def test_check_vms(
        self,
        prepared_plan,
        source_provider,
        destination_provider,
        source_provider_data,
        target_namespace,
        source_vms_namespace,
        source_provider_inventory,
        vm_ssh_connections,
    ):
        """Validate migrated VMs and verify disk count."""
        check_vms(
            plan=prepared_plan,
            source_provider=source_provider,
            destination_provider=destination_provider,
            network_map_resource=self.network_map,
            storage_map_resource=self.storage_map,
            source_provider_data=source_provider_data,
            source_vms_namespace=source_vms_namespace,
            source_provider_inventory=source_provider_inventory,
            vm_ssh_connections=vm_ssh_connections,
        )
        verify_vm_disk_count(
            destination_provider=destination_provider, plan=prepared_plan, target_namespace=target_namespace
        )


@pytest.mark.copyoffload
@pytest.mark.incremental
@pytest.mark.parametrize(
    "class_plan_config",
    [pytest.param(py_config["tests_params"]["test_copyoffload_mixed_datastore_migration"])],
    indirect=True,
    ids=["MTV-565:copyoffload-mixed-datastore"],
)
@pytest.mark.usefixtures("multus_network_name", "copyoffload_config", "mixed_datastore_config", "cleanup_migrated_vms")
class TestCopyoffloadMixedDatastoreMigration:
    """Copy-offload migration test - mixed XCOPY and non-XCOPY datastores.

    This test validates copy-offload functionality when a VM has:
    - One disk on the primary XCOPY-capable datastore (from the template)
    - One additional disk on a non-XCOPY datastore (standard migration)

    This ensures that copy-offload correctly handles VMs with mixed disk types where
    only some disks can use XCOPY acceleration while others fall back to standard migration.
    """

    storage_map: StorageMap
    network_map: NetworkMap
    plan_resource: Plan

    def test_create_storagemap(
        self,
        prepared_plan,
        fixture_store,
        ocp_admin_client,
        source_provider,
        destination_provider,
        source_provider_inventory,
        target_namespace,
        source_provider_data,
        copyoffload_storage_secret,
    ):
        """Create StorageMap with copy-offload configuration for mixed datastores."""
        copyoffload_config_data = source_provider_data["copyoffload"]
        storage_vendor_product = copyoffload_config_data["storage_vendor_product"]
        datastore_id = copyoffload_config_data["datastore_id"]
        non_xcopy_datastore_id = copyoffload_config_data["non_xcopy_datastore_id"]
        storage_class = py_config["storage_class"]

        LOGGER.info("Mixed datastore migration using XCOPY datastore: %s", datastore_id)
        LOGGER.info("Mixed datastore migration using non-XCOPY datastore: %s", non_xcopy_datastore_id)

        vms_names = [vm["name"] for vm in prepared_plan["virtual_machines"]]

        offload_plugin_config = {
            "vsphereXcopyConfig": {
                "secretRef": copyoffload_storage_secret.name,
                "storageVendorProduct": storage_vendor_product,
            }
        }

        self.__class__.storage_map = get_storage_migration_map(
            fixture_store=fixture_store,
            target_namespace=target_namespace,
            source_provider=source_provider,
            destination_provider=destination_provider,
            ocp_admin_client=ocp_admin_client,
            source_provider_inventory=source_provider_inventory,
            vms=vms_names,
            storage_class=storage_class,
            datastore_id=datastore_id,
            non_xcopy_datastore_id=non_xcopy_datastore_id,
            offload_plugin_config=offload_plugin_config,
            access_mode="ReadWriteOnce",
            volume_mode="Block",
        )
        assert self.storage_map, "StorageMap creation failed"

    def test_create_networkmap(
        self,
        prepared_plan,
        fixture_store,
        ocp_admin_client,
        source_provider,
        destination_provider,
        source_provider_inventory,
        target_namespace,
        multus_network_name,
    ):
        """Create NetworkMap resource."""
        vms_names = [vm["name"] for vm in prepared_plan["virtual_machines"]]
        self.__class__.network_map = get_network_migration_map(
            fixture_store=fixture_store,
            source_provider=source_provider,
            destination_provider=destination_provider,
            source_provider_inventory=source_provider_inventory,
            ocp_admin_client=ocp_admin_client,
            multus_network_name=multus_network_name,
            target_namespace=target_namespace,
            vms=vms_names,
        )
        assert self.network_map, "NetworkMap creation failed"

    def test_create_plan(
        self,
        prepared_plan,
        fixture_store,
        ocp_admin_client,
        source_provider,
        destination_provider,
        target_namespace,
        source_provider_inventory,
    ):
        """Create MTV Plan CR resource."""
        for vm in prepared_plan["virtual_machines"]:
            vm_name = vm["name"]
            vm_data = source_provider_inventory.get_vm(vm_name)
            vm["id"] = vm_data["id"]

        self.__class__.plan_resource = create_plan_resource(
            ocp_admin_client=ocp_admin_client,
            fixture_store=fixture_store,
            source_provider=source_provider,
            destination_provider=destination_provider,
            storage_map=self.storage_map,
            network_map=self.network_map,
            virtual_machines_list=prepared_plan["virtual_machines"],
            target_namespace=target_namespace,
            warm_migration=prepared_plan.get("warm_migration", False),
            copyoffload=prepared_plan["copyoffload"],
        )
        assert self.plan_resource, "Plan creation failed"

    def test_migrate_vms(self, fixture_store, ocp_admin_client, target_namespace):
        """Execute migration."""
        execute_migration(
            ocp_admin_client=ocp_admin_client,
            fixture_store=fixture_store,
            plan=self.plan_resource,
            target_namespace=target_namespace,
        )

    def test_check_vms(
        self,
        prepared_plan,
        source_provider,
        destination_provider,
        source_provider_data,
        target_namespace,
        source_vms_namespace,
        source_provider_inventory,
        vm_ssh_connections,
    ):
        """Validate migrated VMs and verify disk count."""
        check_vms(
            plan=prepared_plan,
            source_provider=source_provider,
            destination_provider=destination_provider,
            network_map_resource=self.network_map,
            storage_map_resource=self.storage_map,
            source_provider_data=source_provider_data,
            source_vms_namespace=source_vms_namespace,
            source_provider_inventory=source_provider_inventory,
            vm_ssh_connections=vm_ssh_connections,
        )
        verify_vm_disk_count(
            destination_provider=destination_provider, plan=prepared_plan, target_namespace=target_namespace
        )


@pytest.mark.copyoffload
@pytest.mark.incremental
@pytest.mark.parametrize(
    "class_plan_config",
    [pytest.param(py_config["tests_params"]["test_copyoffload_fallback_large_migration"])],
    indirect=True,
    ids=["MTV-614:copyoffload-fallback-large"],
)
@pytest.mark.usefixtures(
    "multus_network_name",
    "copyoffload_config",
    "mixed_datastore_config",
    "setup_copyoffload_ssh",
    "cleanup_migrated_vms",
)
class TestCopyoffloadFallbackLargeMigration:
    """Copy-offload migration test - large VM with disks on non-XCOPY datastore.

    This test validates copy-offload fallback functionality when VM disks are
    located on a datastore that does NOT support VAAI XCOPY acceleration.
    The VM is configured with:
    - One disk from the template (relocated to non_xcopy_datastore)
    - One additional 100GB disk (also on non_xcopy_datastore)

    The migration should complete successfully using XCOPY's fallback method
    for datastores without VAAI support.
    """

    storage_map: StorageMap
    network_map: NetworkMap
    plan_resource: Plan

    def test_create_storagemap(
        self,
        prepared_plan: dict,
        fixture_store: dict,
        ocp_admin_client: "DynamicClient",
        source_provider: BaseProvider,
        destination_provider: BaseProvider,
        source_provider_inventory: ForkliftInventory,
        target_namespace: str,
        source_provider_data: dict,
        copyoffload_storage_secret: Secret,
    ) -> None:
        """Create StorageMap with copy-offload configuration for non-XCOPY datastore.

        Args:
            prepared_plan: Prepared plan configuration with VM details.
            fixture_store: Shared fixture storage for test resources.
            ocp_admin_client: Kubernetes dynamic client for API operations.
            source_provider: Source provider instance (VMware/RHV/etc).
            destination_provider: Destination provider instance (OpenShift).
            source_provider_inventory: Forklift inventory for source provider.
            target_namespace: Target namespace for migration.
            source_provider_data: Provider configuration data.
            copyoffload_storage_secret: Secret resource for copy-offload storage access.

        Returns:
            None

        Raises:
            AssertionError: If StorageMap creation fails.
        """
        copyoffload_config_data = source_provider_data["copyoffload"]
        storage_vendor_product = copyoffload_config_data["storage_vendor_product"]
        datastore_id = copyoffload_config_data["datastore_id"]
        non_xcopy_datastore_id = copyoffload_config_data["non_xcopy_datastore_id"]
        storage_class = py_config["storage_class"]

        LOGGER.info("Non-XCOPY large VM migration test - 2 disks on non-VAAI datastore")
        LOGGER.info(f"XCOPY datastore (for storage map): {datastore_id}")
        LOGGER.info(f"Non-XCOPY datastore (VM disks location): {non_xcopy_datastore_id}")
        LOGGER.info("VM configuration: template disk + 100GB added disk (both on non_xcopy_datastore)")
        LOGGER.info("Testing fallback behavior for large VM with disks on non-VAAI datastore")

        vms_names = [vm["name"] for vm in prepared_plan["virtual_machines"]]

        offload_plugin_config = {
            "vsphereXcopyConfig": {
                "secretRef": copyoffload_storage_secret.name,
                "storageVendorProduct": storage_vendor_product,
            }
        }

        self.__class__.storage_map = get_storage_migration_map(
            fixture_store=fixture_store,
            target_namespace=target_namespace,
            source_provider=source_provider,
            destination_provider=destination_provider,
            ocp_admin_client=ocp_admin_client,
            source_provider_inventory=source_provider_inventory,
            vms=vms_names,
            storage_class=storage_class,
            datastore_id=datastore_id,
            non_xcopy_datastore_id=non_xcopy_datastore_id,
            offload_plugin_config=offload_plugin_config,
            access_mode="ReadWriteOnce",
            volume_mode="Block",
        )
        assert self.storage_map, "StorageMap creation failed"

    def test_create_networkmap(
        self,
        prepared_plan: dict,
        fixture_store: dict,
        ocp_admin_client: "DynamicClient",
        source_provider: BaseProvider,
        destination_provider: BaseProvider,
        source_provider_inventory: ForkliftInventory,
        target_namespace: str,
        multus_network_name: dict[str, str],
    ) -> None:
        """Create NetworkMap resource.

        Args:
            prepared_plan: Prepared plan configuration with VM details.
            fixture_store: Shared fixture storage for test resources.
            ocp_admin_client: Kubernetes dynamic client for API operations.
            source_provider: Source provider instance (VMware/RHV/etc).
            destination_provider: Destination provider instance (OpenShift).
            source_provider_inventory: Forklift inventory for source provider.
            target_namespace: Target namespace for migration.
            multus_network_name: Dictionary with Multus network configuration (name and namespace).

        Returns:
            None

        Raises:
            AssertionError: If NetworkMap creation fails.
        """
        vms_names = [vm["name"] for vm in prepared_plan["virtual_machines"]]
        self.__class__.network_map = get_network_migration_map(
            fixture_store=fixture_store,
            source_provider=source_provider,
            destination_provider=destination_provider,
            source_provider_inventory=source_provider_inventory,
            ocp_admin_client=ocp_admin_client,
            multus_network_name=multus_network_name,
            target_namespace=target_namespace,
            vms=vms_names,
        )
        assert self.network_map, "NetworkMap creation failed"

    def test_create_plan(
        self,
        prepared_plan: dict,
        fixture_store: dict,
        ocp_admin_client: "DynamicClient",
        source_provider: BaseProvider,
        destination_provider: OCPProvider,
        target_namespace: str,
        source_provider_inventory: ForkliftInventory,
    ) -> None:
        """Create MTV Plan CR resource.

        Args:
            prepared_plan: Prepared plan configuration with VM details.
            fixture_store: Shared fixture storage for test resources.
            ocp_admin_client: Kubernetes dynamic client for API operations.
            source_provider: Source provider instance (VMware/RHV/etc).
            destination_provider: OpenShift provider instance.
            target_namespace: Target namespace for migration.
            source_provider_inventory: Forklift inventory for source provider.

        Returns:
            None

        Raises:
            AssertionError: If Plan creation fails.
        """
        for vm in prepared_plan["virtual_machines"]:
            vm_name = vm["name"]
            vm_data = source_provider_inventory.get_vm(vm_name)
            vm["id"] = vm_data["id"]

        self.__class__.plan_resource = create_plan_resource(
            ocp_admin_client=ocp_admin_client,
            fixture_store=fixture_store,
            source_provider=source_provider,
            destination_provider=destination_provider,
            storage_map=self.storage_map,
            network_map=self.network_map,
            virtual_machines_list=prepared_plan["virtual_machines"],
            target_namespace=target_namespace,
            warm_migration=prepared_plan.get("warm_migration", False),
            copyoffload=prepared_plan["copyoffload"],
        )
        assert self.plan_resource, "Plan creation failed"

    def test_migrate_vms(
        self,
        fixture_store: dict,
        ocp_admin_client: "DynamicClient",
        target_namespace: str,
    ) -> None:
        """Execute migration.

        Args:
            fixture_store: Shared fixture storage for test resources.
            ocp_admin_client: Kubernetes dynamic client for API operations.
            target_namespace: Target namespace for migration.

        Returns:
            None

        Raises:
            AssertionError: If migration execution fails.
        """
        execute_migration(
            ocp_admin_client=ocp_admin_client,
            fixture_store=fixture_store,
            plan=self.plan_resource,
            target_namespace=target_namespace,
        )

    def test_check_vms(
        self,
        prepared_plan: dict,
        source_provider: BaseProvider,
        destination_provider: BaseProvider,
        source_provider_data: dict,
        target_namespace: str,
        source_vms_namespace: str,
        source_provider_inventory: ForkliftInventory,
        vm_ssh_connections: SSHConnectionManager,
    ) -> None:
        """Validate migrated VMs and verify disk count.

        Args:
            prepared_plan: Prepared plan configuration with VM details.
            source_provider: Source provider instance (VMware/RHV/etc).
            destination_provider: Destination provider instance (OpenShift).
            source_provider_data: Provider configuration data.
            target_namespace: Target namespace for migration.
            source_vms_namespace: Source VMs namespace.
            source_provider_inventory: Forklift inventory for source provider.
            vm_ssh_connections: SSH connection manager for VM validation.

        Returns:
            None

        Raises:
            AssertionError: If VM validation or disk count verification fails.
        """
        check_vms(
            plan=prepared_plan,
            source_provider=source_provider,
            destination_provider=destination_provider,
            network_map_resource=self.network_map,
            storage_map_resource=self.storage_map,
            source_provider_data=source_provider_data,
            source_vms_namespace=source_vms_namespace,
            source_provider_inventory=source_provider_inventory,
            vm_ssh_connections=vm_ssh_connections,
        )
        verify_vm_disk_count(
            destination_provider=destination_provider, plan=prepared_plan, target_namespace=target_namespace
        )


@pytest.mark.copyoffload
@pytest.mark.incremental
@pytest.mark.parametrize(
    "class_plan_config",
    [pytest.param(py_config["tests_params"]["test_copyoffload_independent_persistent_disk_migration"])],
    in...

coderabbitai[bot]
coderabbitai Bot previously approved these changes Feb 4, 2026
@elsapassaro

Copy link
Copy Markdown
Collaborator Author

/verified

Comment thread conftest.py Resolved
Comment thread conftest.py Resolved
@qodo-code-review

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

HIGH Import Error

The new helper uses copy.deepcopy but the diff does not show an import for copy. If copy is not imported at module scope, this will raise a NameError at runtime when the fixture calls extract_vm_from_plan.

def extract_vm_from_plan(
    prepared_plan: dict[str, Any],
    vm_index: int,
    fixture_name: str,
) -> dict[str, Any]:
    """Extract a single VM from a multi-VM prepared plan.

    Helper function to create independent plan configurations for simultaneous migrations
    by extracting a specific VM from a prepared plan containing multiple VMs.

    Args:
        prepared_plan: Base prepared plan with cloned VMs
        vm_index: Zero-based index of the VM to extract
        fixture_name: Name of the calling fixture (for error messages)

    Returns:
        Deep copy of prepared plan with only the specified VM

    Raises:
        ValueError: If prepared_plan contains insufficient VMs for the requested index
        ValueError: If VM name is missing from source_vms_data
    """
    vms = prepared_plan.get("virtual_machines", [])
    min_required_vms = vm_index + 1

    if len(vms) < min_required_vms:
        raise ValueError(f"{fixture_name} requires at least {min_required_vms} VM(s) in prepared_plan")

    plan: dict[str, Any] = copy.deepcopy(prepared_plan)
    plan["virtual_machines"] = [plan["virtual_machines"][vm_index]]

    vm_name = plan["virtual_machines"][0]["name"]
    if vm_name not in prepared_plan.get("source_vms_data", {}):
        raise ValueError(f"VM '{vm_name}' missing from prepared_plan source_vms_data")

    plan["source_vms_data"] = {vm_name: prepared_plan["source_vms_data"][vm_name]}
    return plan
HIGH Flaky Logic

The “simultaneous execution” validation relies on reading plan.instance.status.conditions and interpreting missing or transient conditions as “Executing”. This can be flaky: the status subresource may be unset early, conditions may not exist yet, and the chosen condition filter (category/status/type) may not map reliably to migration progress. Consider guarding for missing status/conditions and/or using Migration CR status (or a more explicit Plan phase field) to validate concurrency.

# Validate both migrations are executing simultaneously before either completes
LOGGER.info("Validating both migrations enter executing state simultaneously")
plan_1_executing = False
plan_2_executing = False
both_executing_validated = False

def _check_plan_status(plan: Plan) -> str:
    """Check if plan is executing, succeeded, or failed.

    Args:
        plan: The Plan resource to check status for

    Returns:
        str: "Executing" if migration is in progress, Plan.Status.SUCCEEDED if completed successfully,
            or Plan.Status.FAILED if migration failed

    Raises:
        AttributeError: If plan.instance.status.conditions is not accessible
        KeyError: If condition dictionary is missing required keys (category, status, type)
    """
    for cond in plan.instance.status.conditions:
        if cond["category"] == "Advisory" and cond["status"] == Plan.Condition.Status.TRUE:
            cond_type = cond["type"]
            if cond_type in (Plan.Status.SUCCEEDED, Plan.Status.FAILED):
                return cond_type
    return "Executing"

# Poll both plans to ensure both reach executing state before either completes
for sample in TimeoutSampler(
    func=lambda: (_check_plan_status(self.plan_resource_1), _check_plan_status(self.plan_resource_2)),
    sleep=2,
    wait_timeout=120,
):
    status_1, status_2 = sample

    # Check if plan 1 is executing
    if status_1 == "Executing":
        if not plan_1_executing:
            LOGGER.info(f"Plan 1 '{self.plan_resource_1.name}' is now executing")
        plan_1_executing = True

    # Check if plan 2 is executing
    if status_2 == "Executing":
        if not plan_2_executing:
            LOGGER.info(f"Plan 2 '{self.plan_resource_2.name}' is now executing")
        plan_2_executing = True

    # Assert both are executing simultaneously
    if plan_1_executing and plan_2_executing and not both_executing_validated:
        LOGGER.info("SUCCESS: Both migrations are executing simultaneously")
        both_executing_validated = True
        break

    # If either plan completed before both were executing, fail the test
    if status_1 in (Plan.Status.SUCCEEDED, Plan.Status.FAILED) and not both_executing_validated:
        raise AssertionError(
            EARLY_COMPLETION_MSG.format(plan_num=1, completed_status=status_1, other_status=status_2)
        )
    if status_2 in (Plan.Status.SUCCEEDED, Plan.Status.FAILED) and not both_executing_validated:
        raise AssertionError(
            EARLY_COMPLETION_MSG.format(plan_num=2, completed_status=status_2, other_status=status_1)
        )

assert both_executing_validated, "Failed to validate both migrations executing simultaneously"
MEDIUM Typos/Unused

wait_for_migration_complate appears to be a misspelling (“complete”), which makes the API harder to discover and is easy to call incorrectly elsewhere. Also, imports like execute_migration, get_cutover_value, and sanitize_kubernetes_name are not used in the new class/hunk, which will trip linters and increases cognitive load.

from libs.providers.openshift import OCPProvider
from utilities.migration_utils import get_cutover_value
from utilities.mtv_migration import (
    create_plan_resource,
    execute_migration,
    get_network_migration_map,
    get_storage_migration_map,
    verify_vm_disk_count,
    wait_for_migration_complate,
)

💡 Tool usage guide:

Overview:
The review tool scans the PR code changes, and generates a PR review which includes several types of feedbacks, such as possible PR issues, security threats and relevant test in the PR. More feedbacks can be added by configuring the tool.

The tool can be triggered automatically every time a new PR is opened, or can be invoked manually by commenting on any PR.

  • When commenting, to edit configurations related to the review tool (pr_reviewer section), use the following template:
/review --pr_reviewer.some_config1=... --pr_reviewer.some_config2=...
[pr_reviewer]
some_config1=...
some_config2=...

See the review usage page for a comprehensive guide on using this tool.

@qodo-code-review

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 4 🔵🔵🔵🔵⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

Possible Issue

The new call/import wait_for_migration_complate looks like a misspelling and may not exist or may hide the intended helper, causing runtime failures in the new simultaneous migration test. Verify the function name matches the actual implementation and keep naming consistent to avoid fragile test failures.

from libs.providers.openshift import OCPProvider
from utilities.migration_utils import get_cutover_value
from utilities.mtv_migration import (
    create_plan_resource,
    execute_migration,
    get_network_migration_map,
    get_storage_migration_map,
    verify_vm_disk_count,
    wait_for_migration_complate,
)
Robustness

The simultaneous-execution validation iterates plan.instance.status.conditions and assumes the structure/keys exist and are up to date without an explicit refresh. In Kubernetes dynamic clients, cached objects can be stale and status/conditions can be absent early on, which can raise exceptions or produce false results. Consider re-fetching the Plan (or reading Migration CR status), and defensively handling missing status/conditions/keys to make the test reliable under load.

# Validate both migrations are executing simultaneously before either completes
LOGGER.info("Validating both migrations enter executing state simultaneously")
plan_1_executing = False
plan_2_executing = False
both_executing_validated = False

def _check_plan_status(plan: Plan) -> str:
    """Check if plan is executing, succeeded, or failed.

    Args:
        plan: The Plan resource to check status for

    Returns:
        str: "Executing" if migration is in progress, Plan.Status.SUCCEEDED if completed successfully,
            or Plan.Status.FAILED if migration failed

    Raises:
        AttributeError: If plan.instance.status.conditions is not accessible
        KeyError: If condition dictionary is missing required keys (category, status, type)
    """
    for cond in plan.instance.status.conditions:
        if cond["category"] == "Advisory" and cond["status"] == Plan.Condition.Status.TRUE:
            cond_type = cond["type"]
            if cond_type in (Plan.Status.SUCCEEDED, Plan.Status.FAILED):
                return cond_type
    return "Executing"

# Poll both plans to ensure both reach executing state before either completes
for sample in TimeoutSampler(
    func=lambda: (_check_plan_status(self.plan_resource_1), _check_plan_status(self.plan_resource_2)),
    sleep=2,
    wait_timeout=120,
):
    status_1, status_2 = sample

    # Check if plan 1 is executing
    if status_1 == "Executing":
        if not plan_1_executing:
            LOGGER.info(f"Plan 1 '{self.plan_resource_1.name}' is now executing")
        plan_1_executing = True

    # Check if plan 2 is executing
    if status_2 == "Executing":
        if not plan_2_executing:
            LOGGER.info(f"Plan 2 '{self.plan_resource_2.name}' is now executing")
        plan_2_executing = True

    # Assert both are executing simultaneously
    if plan_1_executing and plan_2_executing and not both_executing_validated:
        LOGGER.info("SUCCESS: Both migrations are executing simultaneously")
        both_executing_validated = True
        break

    # If either plan completed before both were executing, fail the test
    if status_1 in (Plan.Status.SUCCEEDED, Plan.Status.FAILED) and not both_executing_validated:
        raise AssertionError(
            EARLY_COMPLETION_MSG.format(plan_num=1, completed_status=status_1, other_status=status_2)
        )
    if status_2 in (Plan.Status.SUCCEEDED, Plan.Status.FAILED) and not both_executing_validated:
        raise AssertionError(
            EARLY_COMPLETION_MSG.format(plan_num=2, completed_status=status_2, other_status=status_1)
        )
Import Risk

extract_vm_from_plan uses copy.deepcopy but the diff doesn’t show an added import copy near this function. If copy isn’t already imported in the module, this will fail at runtime. Confirm copy is imported in utilities/utils.py and keep imports close to usage to prevent accidental removal later.

def extract_vm_from_plan(
    prepared_plan: dict[str, Any],
    vm_index: int,
    fixture_name: str,
) -> dict[str, Any]:
    """Extract a single VM from a multi-VM prepared plan.

    Helper function to create independent plan configurations for simultaneous migrations
    by extracting a specific VM from a prepared plan containing multiple VMs.

    Args:
        prepared_plan: Base prepared plan with cloned VMs
        vm_index: Zero-based index of the VM to extract
        fixture_name: Name of the calling fixture (for error messages)

    Returns:
        Deep copy of prepared plan with only the specified VM

    Raises:
        ValueError: If prepared_plan contains insufficient VMs for the requested index
        ValueError: If VM name is missing from source_vms_data
    """
    vms = prepared_plan.get("virtual_machines", [])
    min_required_vms = vm_index + 1

    if len(vms) < min_required_vms:
        raise ValueError(f"{fixture_name} requires at least {min_required_vms} VM(s) in prepared_plan")

    plan: dict[str, Any] = copy.deepcopy(prepared_plan)
    plan["virtual_machines"] = [plan["virtual_machines"][vm_index]]

    vm_name = plan["virtual_machines"][0]["name"]
    if vm_name not in prepared_plan.get("source_vms_data", {}):
        raise ValueError(f"VM '{vm_name}' missing from prepared_plan source_vms_data")

    plan["source_vms_data"] = {vm_name: prepared_plan["source_vms_data"][vm_name]}
    return plan

💡 Tool usage guide:

Overview:
The review tool scans the PR code changes, and generates a PR review which includes several types of feedbacks, such as possible PR issues, security threats and relevant test in the PR. More feedbacks can be added by configuring the tool.

The tool can be triggered automatically every time a new PR is opened, or can be invoked manually by commenting on any PR.

  • When commenting, to edit configurations related to the review tool (pr_reviewer section), use the following template:
/review --pr_reviewer.some_config1=... --pr_reviewer.some_config2=...
[pr_reviewer]
some_config1=...
some_config2=...

See the review usage page for a comprehensive guide on using this tool.

@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: 2

🤖 Fix all issues with AI agents
In `@conftest.py`:
- Around line 945-959: The prepared_plan_2 fixture docstring is missing a Raises
section describing ValueError propagation from extract_vm_from_plan; update the
prepared_plan_2 docstring (like prepared_plan_1) to include a "Raises" entry
stating that a ValueError is raised if the source plan contains fewer than 2 VMs
(i.e., vm_index=1 is invalid), referencing extract_vm_from_plan and the vm_index
condition so readers know when the exception can occur.
- Around line 928-942: The fixture prepared_plan_1 calls extract_vm_from_plan
which can raise ValueError for insufficient VMs or missing VM data, so update
the prepared_plan_1 docstring to include a Raises section that documents
ValueError and when it is raised (e.g., insufficient VMs in prepared_plan or
missing VM in source_vms_data), referencing extract_vm_from_plan so callers know
the exception is propagated. Ensure the Raises entry is concise and follows the
existing docstring style.

Comment thread conftest.py Resolved
Comment thread conftest.py Resolved
@elsapassaro

Copy link
Copy Markdown
Collaborator Author

/verified

@myakove

myakove commented Feb 5, 2026

Copy link
Copy Markdown
Collaborator

/approve

@redhat-qe-bot1

Copy link
Copy Markdown

Failed to build and push ghcr.io/redhatqe/mtv-api-tests:latest

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.

7 participants