Skip to content

WIP: MTV-2628: Specify firstboot scripts for migration#309

Open
Chenli-Hu wants to merge 1 commit into
RedHatQE:mainfrom
Chenli-Hu:feature
Open

WIP: MTV-2628: Specify firstboot scripts for migration#309
Chenli-Hu wants to merge 1 commit into
RedHatQE:mainfrom
Chenli-Hu:feature

Conversation

@Chenli-Hu
Copy link
Copy Markdown
Contributor

@Chenli-Hu Chenli-Hu commented Feb 28, 2026

PR Type

Tests, Enhancement


Description

  • Add comprehensive warm migration test with virt-customize firstboot scripts

  • Test ConfigMap creation with firstboot and run shell scripts

  • Validate StorageMap, NetworkMap, and Plan resource creation

  • Execute warm migration with cutover and verify VM migration success

  • Add test configuration for warm migration with virt-customize feature


Diagram Walkthrough

flowchart LR
  A["Test Configuration"] --> B["ConfigMap Creation"]
  B --> C["StorageMap Creation"]
  C --> D["NetworkMap Creation"]
  D --> E["Plan Creation"]
  E --> F["VM Migration Execution"]
  F --> G["VM Validation"]
Loading

File Walkthrough

Relevant files
Tests
test_mtv_warm_migration_feature.py
Warm migration test with virt-customize firstboot scripts

tests/test_mtv_warm_migration_feature.py

  • New test file implementing warm migration test class
    TestWarmVirtCustomizeFirstboot
  • Tests ConfigMap creation with firstboot and run scripts for
    virt-customize
  • Tests creation of StorageMap, NetworkMap, and Plan resources
  • Tests warm migration execution with cutover and VM validation
  • Includes skip markers for unsupported provider types and Jira marker
    for RHV
+278/-0 
Configuration changes
config.py
Add warm migration virt-customize test configuration         

tests/tests_config/config.py

  • Add new test configuration test_warm_virt_customize_firstboot to test
    parameters
  • Configure test VM mtv-feature-rhel9 with warm migration enabled
  • Define ConfigMap with two shell scripts: 01_linux_firstboot_test.sh
    and 01_linux_run_test.sh
  • Scripts execute echo commands to test firstboot and run functionality
+17/-0   

Summary by CodeRabbit

  • Tests
    • Added end-to-end warm migration tests validating virt-customize firstboot/run script usage during migration.
    • Covers full flow: script ConfigMap creation, storage and network mapping, migration plan creation, migration execution, and post-migration VM verification.
    • Added a test configuration enabling a warm migration scenario with one VM and the script ConfigMap.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 28, 2026

Walkthrough

Adds an end-to-end MTV warm-migration test using virt-customize firstboot scripts: creates a ConfigMap, StorageMap, NetworkMap, and Plan CR, runs the migration (including cutover retrieval) and validates migrated VMs; also adds a test case entry with the ConfigMap payload.

Changes

Cohort / File(s) Summary
MTV Warm Migration Test Suite
tests/test_mtv_warm_migration_feature.py
Adds TestWarmVirtCustomizeFirstboot with six tests that create/validate a virt-customize ConfigMap, create StorageMap and NetworkMap, create and populate an MTV Plan CR with VM IDs, execute migration (retrieve cutover) and validate migrated VMs. Uses fixtures for precopy intervals and cleanup; includes conditional skip/Jira marker for RHV.
Test Configuration Update
tests/tests_config/config.py
Adds test_warm_virt_customize_firstboot to tests_params: one VM with warm_migration: True and a forklift-virt-customize ConfigMap payload containing 01_linux_firstboot_test.sh and 01_linux_run_test.sh scripts.

Sequence Diagram(s)

sequenceDiagram
    participant Test as Test Runner
    participant OCP as OCP API
    participant MigCtrl as Migration Controller
    participant Src as Source Provider
    participant Dst as Destination Provider

    Test->>OCP: create ConfigMap (virt-customize scripts)
    OCP-->>Test: ConfigMap created
    Test->>OCP: create StorageMap & NetworkMap
    OCP-->>Test: Maps created
    Test->>OCP: create MTV Plan CR (populate VM IDs)
    OCP-->>Test: Plan created
    Test->>MigCtrl: trigger migration (execute_migration)
    MigCtrl->>Src: quiesce / pre-copy VMs
    MigCtrl->>Dst: provision target resources
    MigCtrl-->>Test: migration started (cutover value available)
    Test->>MigCtrl: request cutover value
    MigCtrl-->>Test: return cutover value
    Test->>MigCtrl: perform cutover
    MigCtrl->>Dst: finalize VM boot
    Test->>Dst: verify VMs (SSH/connectivity, run scripts)
    Dst-->>Test: verification results
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 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.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Title check ✅ Passed The title references MTV-2628 and mentions 'firstboot scripts for migration,' which directly relates to the core change: a new warm migration test with virt-customize firstboot scripts.

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

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

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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
🔒 Security concerns

Guest command execution:
This PR introduces shell scripts stored in a ConfigMap and intended to be executed in the guest via virt-customize. If any part of the ConfigMap data can be influenced by untrusted input (e.g., externalized test config, CI parameters, or shared environments), this becomes a code-execution vector inside the migrated VM. Ensure the scripts are strictly controlled (test-only), avoid interpolating user-provided values into script contents, and keep permissions/scope limited to the test namespace.

⚡ Recommended focus areas for review

Missing Imports

The test module uses type-related names that are not imported (e.g., TYPE_CHECKING and Any). This will raise NameError at import time and block the entire test suite from collecting/running. Ensure all typing symbols used in annotations and conditional TYPE_CHECKING blocks are imported from typing.

if TYPE_CHECKING:
    from kubernetes.dynamic import DynamicClient

    from libs.base_provider import BaseProvider
    from libs.forklift_inventory import ForkliftInventory
    from libs.providers.openshift import OCPProvider
    from utilities.ssh_utils import SSHConnectionManager

_SOURCE_PROVIDER_TYPE = load_source_providers().get(py_config.get("source_provider", ""), {}).get("type")
None Handling

ConfigMap .instance.data may be None depending on resource state/serialization, and membership checks against None will raise TypeError. Defensive handling (default to empty dict or assert non-null) makes the test failure clearer and avoids crashing with an unrelated exception.

# Verify the scripts are in the ConfigMap
cm_data = self.configmap.instance.data
assert "01_linux_firstboot_test.sh" in cm_data, "No firstboot script"
assert "01_linux_run_test.sh" in cm_data, "No run script 1"
Hardcoded Names

The test config hardcodes the ConfigMap name and the test also asserts that same literal name. This couples the test to a specific resource name and makes parallel runs or future renames harder. Prefer deriving the expected name from the config/fixture output (single source of truth) or parameterizing it.

"configmap": {
    "name": "forklift-virt-customize",
    "data": {
        "01_linux_firstboot_test.sh": ('#!/bin/sh\necho "First boot 1" >> /root/test\n'),
        "01_linux_run_test.sh": ('#!/bin/sh\necho "Run 1" >> /root/test\n'),
    },

💡 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

qodo-code-review Bot commented Feb 28, 2026

PR Code Suggestions ✨

Latest suggestions up to 8ebf6f5

CategorySuggestion                                                                                                                                    Impact
Possible bug
Make config access deterministic

Replace the chained .get() calls for py_config with direct dictionary access
([]) to ensure the test fails immediately and explicitly if the source_provider
configuration is missing.

tests/test_mtv_warm_migration_feature.py [28-37]

-_SOURCE_PROVIDER_TYPE = load_source_providers().get(py_config.get("source_provider", ""), {}).get("type")
-
+source_provider_name = py_config["source_provider"]
+source_provider = load_source_providers()[source_provider_name]
+_SOURCE_PROVIDER_TYPE = source_provider["type"]
 
 pytestmark = [
     pytest.mark.skipif(
         _SOURCE_PROVIDER_TYPE
         in (Provider.ProviderType.OPENSTACK, Provider.ProviderType.OPENSHIFT, Provider.ProviderType.OVA),
         reason=f"{_SOURCE_PROVIDER_TYPE} warm migration is not supported.",
     ),
 ]
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that using .get() with defaults can mask configuration errors, leading to silent failures or incorrect test behavior. Enforcing a fail-fast approach with direct key access improves the test suite's robustness.

Medium
Prevent None data runtime errors

Change the type hint for test_configmap to not allow None and add an assertion
to verify that self.configmap.instance.data is not None before accessing its
keys to prevent a potential TypeError.

tests/test_mtv_warm_migration_feature.py [70-90]

 def test_create_configmap(
     self,
-    test_configmap: ConfigMap | None,
+    test_configmap: ConfigMap,
 ) -> None:
     """Create virt-customize ConfigMap resource.
 ...
     self.__class__.configmap = test_configmap
-    assert self.configmap, "ConfigMap fixture returned None"
     assert self.configmap.exists, "ConfigMap does not exist"
     assert self.configmap.name == "forklift-virt-customize", "ConfigMap name mismatch"
 
-    # Verify the scripts are in the ConfigMap
     cm_data = self.configmap.instance.data
+    assert cm_data is not None, "ConfigMap data is missing"
     assert "01_linux_firstboot_test.sh" in cm_data, "No firstboot script"
     assert "01_linux_run_test.sh" in cm_data, "No run script 1"

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 6

__

Why: The suggestion correctly points out a potential TypeError if self.configmap.instance.data is None. Adding a None check makes the test more robust against unexpected API responses, preventing runtime errors.

Low
Security
Make scripts less brittle

In the test configuration, modify the shell scripts to write to a file in /tmp
instead of /root to avoid potential permission errors, and remove redundant
parentheses from the string literals.

tests/tests_config/config.py [517-533]

 "test_warm_virt_customize_firstboot": {
     "virtual_machines": [
         {
             "name": "mtv-feature-rhel9",
             "source_vm_power": "on",
             "guest_agent": True,
         },
     ],
     "warm_migration": True,
     "configmap": {
         "name": "forklift-virt-customize",
         "data": {
-            "01_linux_firstboot_test.sh": ('#!/bin/sh\necho "First boot 1" >> /root/test\n'),
-            "01_linux_run_test.sh": ('#!/bin/sh\necho "Run 1" >> /root/test\n'),
+            "01_linux_firstboot_test.sh": '#!/bin/sh\necho "First boot 1" >> /tmp/mtv-virt-customize-test\n',
+            "01_linux_run_test.sh": '#!/bin/sh\necho "Run 1" >> /tmp/mtv-virt-customize-test\n',
         },
     },
 },
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion improves the robustness of the test by writing to /tmp instead of /root, which is a more reliable location for write operations in various Linux environments and avoids potential permission issues.

Medium
  • More

Previous suggestions

Suggestions up to commit 8f492e4
CategorySuggestion                                                                                                                                    Impact
Security
Use non-privileged test output path

Modify the test configuration to write test artifacts to /tmp instead of /root
to avoid reliance on root privileges and improve test robustness. Also, remove
unnecessary parentheses from string literals.

tests/tests_config/config.py [517-533]

 "test_warm_virt_customize_firstboot": {
     "virtual_machines": [
         {
             "name": "mtv-feature-rhel9",
             "source_vm_power": "on",
             "guest_agent": True,
         },
     ],
     "warm_migration": True,
     "configmap": {
         "name": "forklift-virt-customize",
         "data": {
-            "01_linux_firstboot_test.sh": ('#!/bin/sh\necho "First boot 1" >> /root/test\n'),
-            "01_linux_run_test.sh": ('#!/bin/sh\necho "Run 1" >> /root/test\n'),
+            "01_linux_firstboot_test.sh": "#!/bin/sh\necho \"First boot 1\" >> /tmp/mtv_virt_customize_test\n",
+            "01_linux_run_test.sh": "#!/bin/sh\necho \"Run 1\" >> /tmp/mtv_virt_customize_test\n",
         },
     },
 },
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly points out that writing test files to /root is fragile and depends on root privileges; using /tmp is a more robust approach for test automation.

Medium
Possible bug
Normalize provider type comparisons

Normalize the provider type to a Provider.ProviderType enum before comparisons
to ensure test skipping and Jira marker logic works correctly.

tests/test_mtv_warm_migration_feature.py [28-41]

-_SOURCE_PROVIDER_TYPE = load_source_providers().get(py_config.get("source_provider", ""), {}).get("type")
+_RAW_SOURCE_PROVIDER_TYPE = load_source_providers().get(py_config.get("source_provider", ""), {}).get("type")
+try:
+    _SOURCE_PROVIDER_TYPE = Provider.ProviderType(_RAW_SOURCE_PROVIDER_TYPE)
+except Exception:
+    _SOURCE_PROVIDER_TYPE = _RAW_SOURCE_PROVIDER_TYPE
 
 
 pytestmark = [
     pytest.mark.skipif(
-        _SOURCE_PROVIDER_TYPE
-        in (Provider.ProviderType.OPENSTACK, Provider.ProviderType.OPENSHIFT, Provider.ProviderType.OVA),
-        reason=f"{_SOURCE_PROVIDER_TYPE} warm migration is not supported.",
+        _SOURCE_PROVIDER_TYPE in {Provider.ProviderType.OPENSTACK, Provider.ProviderType.OPENSHIFT, Provider.ProviderType.OVA},
+        reason=f"{_SOURCE_PROVIDER_TYPE or 'Unknown provider'} warm migration is not supported.",
     ),
 ]
 
 # Only apply Jira marker for RHV - skip if issue unresolved, run normally if resolved
 if _SOURCE_PROVIDER_TYPE == Provider.ProviderType.RHV:
     pytestmark.append(pytest.mark.jira("MTV-2846", run=False))
Suggestion importance[1-10]: 6

__

Why: The suggestion improves the robustness of the provider type comparison by attempting to normalize the value, preventing potential silent failures in test skipping logic.

Low
Suggestions up to commit 015b1fa
CategorySuggestion                                                                                                                                    Impact
High-level
Refactor test structure to avoid duplication

To reduce code duplication, create a base test class containing the common
migration test steps. The new TestWarmVirtCustomizeFirstboot class can then
inherit from this base class and only implement its specific logic, like
test_create_configmap.

Examples:

tests/test_mtv_warm_migration_feature.py [56-278]
class TestWarmVirtCustomizeFirstboot:
    """Warm migration test with virt-customize firstboot scripts.

    Tests the MTV virt-customize feature using ConfigMap with firstboot and run scripts.
    The ConfigMap contains shell scripts that are executed during VM customization.
    """

    configmap: ConfigMap
    storage_map: StorageMap
    network_map: NetworkMap

 ... (clipped 213 lines)

Solution Walkthrough:

Before:

class TestWarmVirtCustomizeFirstboot:
    # ... class attributes

    def test_create_configmap(self, ...):
        # Specific logic for this test
        ...

    def test_create_storagemap(self, ...):
        # Generic storage map creation logic
        ...

    def test_create_networkmap(self, ...):
        # Generic network map creation logic
        ...

    def test_create_plan(self, ...):
        # Generic plan creation logic
        ...

    def test_migrate_vms(self, ...):
        # Generic migration execution logic
        ...

    def test_check_vms(self, ...):
        # Generic VM check logic
        ...

After:

class BaseMigrationTest:
    # ... common class attributes

    def test_create_storagemap(self, ...):
        # Generic storage map creation logic
        ...

    def test_create_networkmap(self, ...):
        # Generic network map creation logic
        ...

    # ... other common test methods (plan, migrate, check_vms)

class TestWarmVirtCustomizeFirstboot(BaseMigrationTest):
    configmap: ConfigMap

    def test_create_configmap(self, ...):
        # Specific logic for this test
        ...
Suggestion importance[1-10]: 8

__

Why: This is a strong architectural suggestion that correctly identifies significant code duplication and proposes a valid refactoring using a base class, which would improve the test framework's design and maintainability.

Medium

best practice
Use triple-quoted strings for scripts

Replace single-quoted strings with \n with triple-quoted strings for the shell
scripts in the configmap definition. This improves readability and
maintainability of the multi-line script content.

tests/tests_config/config.py [529-530]

-"01_linux_firstboot_test.sh": ('#!/bin/sh\necho "First boot 1" >> /root/test\n'),
-"01_linux_run_test.sh": ('#!/bin/sh\necho "Run 1" >> /root/test\n'),
+"01_linux_firstboot_test.sh": """#!/bin/sh
+echo "First boot 1" >> /root/test
+""",
+"01_linux_run_test.sh": """#!/bin/sh
+echo "Run 1" >> /root/test
+""",
Suggestion importance[1-10]: 5

__

Why: The suggestion correctly points out a way to improve code readability and maintainability by using triple-quoted strings for multi-line shell scripts, which is a good practice.

Low

@Chenli-Hu
Copy link
Copy Markdown
Contributor Author

Chenli-Hu commented Feb 28, 2026

This one is still in progress, no need for review, thank you

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/test_mtv_warm_migration_feature.py`:
- Around line 43-56: Add the pytest incremental marker to the
TestWarmVirtCustomizeFirstboot test class so downstream tests stop if an earlier
dependent step fails: annotate the class (TestWarmVirtCustomizeFirstboot) with
`@pytest.mark.incremental` alongside the existing markers so that sequential
methods (configmap → storagemap → networkmap → plan → migrate → check) will
fail-fast and not access uninitialized class attributes like self.storage_map.
- Around line 1-8: The module uses the typing symbols TYPE_CHECKING and Any but
never imports them; add an import for these symbols from the typing module
(e.g., include "from typing import TYPE_CHECKING, Any") at the top of the file
so references to TYPE_CHECKING and Any used in annotations and conditional
imports resolve correctly (look for usages of TYPE_CHECKING and Any in the file
to place the import).
- Around line 83-88: The test currently calls pytest.fail() when the fixture
test_configmap is falsy; remove that conditional and let the fixture
test_configmap perform validation or raise an exception (or mark the test with
`@pytest.mark.skipif` if the ConfigMap is optional). Update the test method that
sets self.__class__.configmap = test_configmap and the subsequent assertions
(self.configmap.exists and self.configmap.name checks) to assume a valid fixture
and simply assert the properties; ensure any creation/validation logic is moved
into the test_configmap fixture (or adjust the fixture to raise ValueError with
a clear message when creation fails).
- Around line 242-278: Remove the unused target_namespace parameter from the
test_check_vms signature: update the function definition of test_check_vms to
drop the target_namespace argument (it is never referenced) while keeping
prepared_plan, source_provider, destination_provider, source_provider_data,
source_vms_namespace, source_provider_inventory, and vm_ssh_connections; note
that check_vms reads the target namespace from prepared_plan via
plan["_vm_target_namespace"], so no other changes to the call site or to
check_vms are required.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 00f909d and 015b1fa.

📒 Files selected for processing (2)
  • tests/test_mtv_warm_migration_feature.py
  • tests/tests_config/config.py

Comment thread tests/test_mtv_warm_migration_feature.py
Comment thread tests/test_mtv_warm_migration_feature.py
Comment thread tests/test_mtv_warm_migration_feature.py Outdated
Comment thread tests/test_mtv_warm_migration_feature.py
@qodo-code-review
Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

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

None Handling

self.configmap.instance.data can be None (or missing) depending on client/object state; asserting key membership on a None value will raise a TypeError and obscure the real failure. Add an explicit assertion that data is a dict (or default to {}) before checking for script keys so failures are clearer and more defensive.

# Verify the scripts are in the ConfigMap
cm_data = self.configmap.instance.data
assert "01_linux_firstboot_test.sh" in cm_data, "No firstboot script"
assert "01_linux_run_test.sh" in cm_data, "No run script 1"
Script Safety

The ConfigMap includes executable shell scripts that will run in the guest as root and write to /root/test. This is fine for a controlled test, but it increases blast radius if configs become user-provided. Consider keeping the scripts minimal, ensuring predictable file paths/permissions, and documenting that only trusted test content should be used.

"configmap": {
    "name": "forklift-virt-customize",
    "data": {
        "01_linux_firstboot_test.sh": ('#!/bin/sh\necho "First boot 1" >> /root/test\n'),
        "01_linux_run_test.sh": ('#!/bin/sh\necho "Run 1" >> /root/test\n'),
    },
},

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/test_mtv_warm_migration_feature.py`:
- Around line 252-266: The docstring for the "Validate migrated VMs" function
documents a non-existent parameter target_namespace; open the function that
lists prepared_plan, source_provider, destination_provider,
source_provider_data, source_vms_namespace, source_provider_inventory and
vm_ssh_connections in its signature (the validate/migration validation function
in tests/test_mtv_warm_migration_feature.py) and remove the "target_namespace:
Target namespace for migration" entry from the docstring (or, if the parameter
was intended, add it to the function signature consistently). Also ensure the
Args/Returns formatting remains consistent after removing the line.
- Around line 74-84: Remove the unnecessary "Returns:" section from the
docstring of the test that creates the virt-customize ConfigMap (the docstring
starting "Create virt-customize ConfigMap resource") and from the other test
methods listed (test_create_storagemap, test_create_networkmap,
test_create_plan, test_migrate_vms, test_check_vms); keep the rest of the
docstring (Args and Raises) intact so the docstrings describe purpose, args, and
raises but omit any Returns: block for these void test functions.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 015b1fa and 8f492e4.

📒 Files selected for processing (2)
  • tests/test_mtv_warm_migration_feature.py
  • tests/tests_config/config.py

Comment on lines +74 to +84
"""Create virt-customize ConfigMap resource.

Args:
test_configmap: ConfigMap fixture that creates the virt-customize ConfigMap

Returns:
None

Raises:
AssertionError: If ConfigMap creation fails or doesn't exist
"""
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.

🧹 Nitpick | 🔵 Trivial

LOW: Omit Returns: section for void functions.

WHY: Per repository convention, docstrings for functions returning None should not include a Returns: section. It adds noise without providing value.

🔧 Suggested fix
         """Create virt-customize ConfigMap resource.

         Args:
             test_configmap: ConfigMap fixture that creates the virt-customize ConfigMap

-        Returns:
-            None
-
         Raises:
             AssertionError: If ConfigMap creation fails or doesn't exist
         """

This same pattern applies to all other test methods in this class (test_create_storagemap, test_create_networkmap, test_create_plan, test_migrate_vms, test_check_vms).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_mtv_warm_migration_feature.py` around lines 74 - 84, Remove the
unnecessary "Returns:" section from the docstring of the test that creates the
virt-customize ConfigMap (the docstring starting "Create virt-customize
ConfigMap resource") and from the other test methods listed
(test_create_storagemap, test_create_networkmap, test_create_plan,
test_migrate_vms, test_check_vms); keep the rest of the docstring (Args and
Raises) intact so the docstrings describe purpose, args, and raises but omit any
Returns: block for these void test functions.

Comment thread tests/test_mtv_warm_migration_feature.py
Signed-off-by: Chenli Hu <chhu@redhat.com>
@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
🔒 Security concerns

Command execution surface:
The PR introduces a test configuration that injects and executes shell scripts inside the guest via virt-customize (01_linux_firstboot_test.sh, 01_linux_run_test.sh). While the current payload is benign, this pattern is inherently powerful (runs as root in the guest) and becomes a security concern if the ConfigMap content could ever be influenced by untrusted input or reused outside tests. Ensure the ConfigMap data remains static/test-scoped and is never populated from external/user-controlled values.

⚡ Recommended focus areas for review

Fixture None handling

The test assumes ConfigMap.instance.data is always populated; if the resource exists but .instance/.data is missing or not yet refreshed, the membership checks can raise TypeError/AttributeError instead of producing a clear assertion. Add defensive checks (e.g., assert .instance and .data are dict-like) or use .get() with explicit failure messages to keep failures actionable.

self.__class__.configmap = test_configmap
assert self.configmap, "ConfigMap fixture returned None"
assert self.configmap.exists, "ConfigMap does not exist"
assert self.configmap.name == "forklift-virt-customize", "ConfigMap name mismatch"

# Verify the scripts are in the ConfigMap
cm_data = self.configmap.instance.data
assert "01_linux_firstboot_test.sh" in cm_data, "No firstboot script"
assert "01_linux_run_test.sh" in cm_data, "No run script 1"
Shared class state

Tests store resources on self.__class__ (configmap, storage_map, network_map, plan_resource). This creates order dependence and can break under parallel execution or reruns (stale state). Prefer using pytest fixtures that return these resources and pass them explicitly between tests, or mark ordering/serialization if the framework requires it.

configmap: ConfigMap
storage_map: StorageMap
network_map: NetworkMap
plan_resource: Plan

def test_create_configmap(
    self,
    test_configmap: ConfigMap | None,
) -> None:
    """Create virt-customize ConfigMap resource.

    Args:
        test_configmap: ConfigMap fixture that creates the virt-customize ConfigMap

    Raises:
        AssertionError: If ConfigMap creation fails or doesn't exist
    """
    self.__class__.configmap = test_configmap
    assert self.configmap, "ConfigMap fixture returned None"
    assert self.configmap.exists, "ConfigMap does not exist"
    assert self.configmap.name == "forklift-virt-customize", "ConfigMap name mismatch"

    # Verify the scripts are in the ConfigMap
    cm_data = self.configmap.instance.data
    assert "01_linux_firstboot_test.sh" in cm_data, "No firstboot script"
    assert "01_linux_run_test.sh" in cm_data, "No run script 1"

def test_create_storagemap(
    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,
) -> None:
    """Create StorageMap resource for migration.

    Args:
        prepared_plan: The prepared migration plan
        fixture_store: Fixture store for resource tracking
        ocp_admin_client: OpenShift admin client
        source_provider: Source provider instance
        destination_provider: Destination provider instance
        source_provider_inventory: Source provider inventory
        target_namespace: Target namespace for migration

    Returns:
        None

    Raises:
        AssertionError: If StorageMap creation fails
    """
    vms = [vm["name"] for vm in prepared_plan["virtual_machines"]]
    self.__class__.storage_map = get_storage_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,
        vms=vms,
    )
    assert self.storage_map, "StorageMap creation failed"

def test_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],
) -> None:
    """Create NetworkMap resource for migration.

    Args:
        prepared_plan: The prepared migration plan
        fixture_store: Fixture store for resource tracking
        ocp_admin_client: OpenShift admin client
        source_provider: Source provider instance
        destination_provider: Destination provider instance
        source_provider_inventory: Source provider inventory
        target_namespace: Target namespace for migration
        multus_network_name: Name of the multus network

    Returns:
        None

    Raises:
        AssertionError: If NetworkMap creation fails
    """
    vms = [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,
        target_namespace=target_namespace,
        multus_network_name=multus_network_name,
        vms=vms,
    )
    assert self.network_map, "NetworkMap creation failed"

def test_create_plan(
    self,
    prepared_plan: dict[str, Any],
    fixture_store: dict[str, Any],
    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: The prepared migration plan
        fixture_store: Fixture store for resource tracking
        ocp_admin_client: OpenShift admin client
        source_provider: Source provider instance
        destination_provider: Destination provider instance
        target_namespace: Target namespace for migration
        source_provider_inventory: Source provider inventory

    Returns:
        None

    Raises:
        AssertionError: If Plan creation fails
    """
    populate_vm_ids(plan=prepared_plan, inventory=source_provider_inventory)

    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),
    )
    assert self.plan_resource, "Plan creation failed"
Root-write scripts

The virt-customize scripts append to /root/test. This requires root and can be brittle across images/security hardening (e.g., locked root, read-only FS, SELinux contexts). Consider writing to a more universally writable location (e.g., /var/tmp/...) or validating preconditions to reduce test flakiness.

"test_warm_virt_customize_firstboot": {
    "virtual_machines": [
        {
            "name": "mtv-feature-rhel9",
            "source_vm_power": "on",
            "guest_agent": True,
        },
    ],
    "warm_migration": True,
    "configmap": {
        "name": "forklift-virt-customize",
        "data": {
            "01_linux_firstboot_test.sh": ('#!/bin/sh\necho "First boot 1" >> /root/test\n'),
            "01_linux_run_test.sh": ('#!/bin/sh\necho "Run 1" >> /root/test\n'),
        },
    },
},

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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (1)
tests/test_mtv_warm_migration_feature.py (1)

113-115: 🧹 Nitpick | 🔵 Trivial

LOW: Remove Returns: None section from void function docstring.

WHY: Per repository convention documented in past reviews, docstrings for functions returning None should not include a Returns: section—it adds noise without value.

This applies to this method and also to test_create_networkmap (lines 154-155), test_create_plan (lines 194-195), and test_migrate_vms (lines 228-229).

🔧 Suggested fix for this method
         Args:
             ...
             target_namespace: Target namespace for migration

-        Returns:
-            None
-
         Raises:
             AssertionError: If StorageMap creation fails
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_mtv_warm_migration_feature.py` around lines 113 - 115, Remove the
unnecessary "Returns: None" docstring section from the void test functions: the
test method with the docstring around the first diff and the functions
test_create_networkmap, test_create_plan, and test_migrate_vms; edit each
function's docstring to omit the entire Returns block so the docstring only
contains the description/setup lines (no code changes otherwise), preserving
existing indentation and triple-quote style.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/test_mtv_warm_migration_feature.py`:
- Around line 28-29: The test silently masks a missing project config by calling
py_config.get("source_provider", "") when computing _SOURCE_PROVIDER_TYPE;
change this to directly read py_config["source_provider"] (or first check
py_config.get("source_provider") and set _SOURCE_PROVIDER_TYPE to None if
missing) and then call load_source_providers().get(value, {}).get("type") only
when a value is present so missing configuration fails loudly or results in an
explicit None; update the logic around _SOURCE_PROVIDER_TYPE and any skipif
checks that depend on it (referencing _SOURCE_PROVIDER_TYPE, py_config, and
load_source_providers) accordingly.

---

Duplicate comments:
In `@tests/test_mtv_warm_migration_feature.py`:
- Around line 113-115: Remove the unnecessary "Returns: None" docstring section
from the void test functions: the test method with the docstring around the
first diff and the functions test_create_networkmap, test_create_plan, and
test_migrate_vms; edit each function's docstring to omit the entire Returns
block so the docstring only contains the description/setup lines (no code
changes otherwise), preserving existing indentation and triple-quote style.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8f492e4 and 8ebf6f5.

📒 Files selected for processing (2)
  • tests/test_mtv_warm_migration_feature.py
  • tests/tests_config/config.py

Comment on lines +28 to +29
_SOURCE_PROVIDER_TYPE = load_source_providers().get(py_config.get("source_provider", ""), {}).get("type")

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.

🧹 Nitpick | 🔵 Trivial

MEDIUM: Avoid .get() with defaults for project-controlled configuration.

WHY this matters: Using py_config.get("source_provider", "") silently masks missing configuration. If source_provider isn't configured, the test will run with an empty string and the skipif logic won't work as intended—it may incorrectly allow tests to run when they shouldn't.

The guideline states project configs should use direct access so configuration errors surface early.

🔧 Proposed fix
-_SOURCE_PROVIDER_TYPE = load_source_providers().get(py_config.get("source_provider", ""), {}).get("type")
+_SOURCE_PROVIDER_TYPE = load_source_providers().get(py_config["source_provider"], {}).get("type")

If source_provider is truly optional for some test runs, add explicit validation:

_source_provider = py_config.get("source_provider")
if not _source_provider:
    _SOURCE_PROVIDER_TYPE = None
else:
    _SOURCE_PROVIDER_TYPE = load_source_providers().get(_source_provider, {}).get("type")

As per coding guidelines: "For configurations controlled by the project (py_config, plan, tests_params), never use defaults. Always use direct access."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_mtv_warm_migration_feature.py` around lines 28 - 29, The test
silently masks a missing project config by calling
py_config.get("source_provider", "") when computing _SOURCE_PROVIDER_TYPE;
change this to directly read py_config["source_provider"] (or first check
py_config.get("source_provider") and set _SOURCE_PROVIDER_TYPE to None if
missing) and then call load_source_providers().get(value, {}).get("type") only
when a value is present so missing configuration fails loudly or results in an
explicit None; update the logic around _SOURCE_PROVIDER_TYPE and any skipif
checks that depend on it (referencing _SOURCE_PROVIDER_TYPE, py_config, and
load_source_providers) accordingly.

@myakove
Copy link
Copy Markdown
Collaborator

myakove commented Mar 1, 2026

This one is still in progress, no need for review, thank you

Either convert to draft or set as WIP

@Chenli-Hu
Copy link
Copy Markdown
Contributor Author

/wip

@redhat-qe-bot2 redhat-qe-bot2 changed the title MTV-2628: Specify firstboot scripts for migration WIP: MTV-2628: Specify firstboot scripts for migration Mar 2, 2026
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.

6 participants