Skip to content

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

5 watching

Forks

Latest commit

 

History

487 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

MTV API Test Suite

Test suite for validating VM migrations to OpenShift from VMware vSphere, RHV, OpenStack, and OVA using Migration Toolkit for Virtualization (MTV).

Full Documentation | Copy-Offload Guide

The documentation site in docs/ is generated with pi-docsite: edit docs/*.md, keep the sidebar order in docs/nav.json, then rebuild the generated files with

uvx pi-docsite --docs-dir docs \
  --tagline "Pytest-based integration suite for validating Migration Toolkit for Virtualization migrations into OpenShift Virtualization."

Never edit the generated *.html, assets/, search-index.json, llms.txt or llms-full.txt by hand. See AGENTS.md.


Prerequisites

Local Machine Requirements

  • OpenShift cluster with MTV operator installed
  • Podman or Docker - To run the test container
    • Linux/macOS: Podman or Docker
    • Windows: Docker Desktop or Podman Desktop

Source Provider Requirements

You need a base VM/template in your source provider:

Provider Resource Type Requirements
VMware vSphere VM Powered off, QEMU guest agent installed
RHV/oVirt Template Min 1536 MiB memory
OpenStack Instance ACTIVE/SHUTOFF state, QEMU guest agent installed
OVA OVA file NFS-accessible OVA files

Note: Copy-offload tests have additional prerequisites. See the Copy-Offload Testing Guide for details.

Verify Setup

podman --version  # or: docker --version

Optional - If you have oc CLI installed, you can verify your cluster:

oc whoami                                # Check cluster access
oc get csv -n openshift-mtv | grep mtv  # Verify MTV operator

Quick Start

1. Build and Push the Test Image

Important: A pre-built public image is available at ghcr.io/redhatqe/mtv-api-tests:latest. You can use it directly or build and push your own custom image.

Option A: Use the public image (recommended):

Use ghcr.io/redhatqe/mtv-api-tests:latest directly in the commands below.

Option B: Build your own custom image:

# Clone the repository
git clone https://github.com/RedHatQE/mtv-api-tests.git
cd mtv-api-tests

# Build the image (use 'docker' if you prefer Docker)
podman build -t <YOUR-REGISTRY>/mtv-tests:latest .

# Push to your registry
podman push <YOUR-REGISTRY>/mtv-tests:latest

Replace <YOUR-REGISTRY> with your registry (e.g., quay.io/youruser, docker.io/youruser).

2. Grant Permissions

Ensure your OpenShift user has permissions to create MTV resources and VMs.

⚠️ Security Warning: The command below grants full cluster-admin privileges. This is only appropriate for isolated test/development clusters. For production or shared environments, use least-privilege RBAC instead (see below).

For Test/Development Clusters Only:

oc adm policy add-cluster-role-to-user cluster-admin $(oc whoami)

For Production/Shared Environments (Recommended - Least Privilege):

Instead of granting cluster-admin, create a dedicated role (e.g., mtv-operator-role) that grants only the required permissions for MTV testing. Your cluster admin should:

  1. Create a custom Role with specific verbs and resources:

    • Verbs: create, delete, get, list, watch, update, patch
    • MTV Resources (API group forklift.konveyor.io):
      • virtualmachines, plans, providers, storagemaps, networkmaps, migrations, hooks
    • Core Resources (in mtv-tests namespace):
      • secrets, configmaps, persistentvolumeclaims, pods, services
    • Read-only access (cluster-scoped):
      • storageclasses, namespaces
  2. Bind the role to your test user:

    # Example: Bind the custom role to the test user
    oc adm policy add-role-to-user mtv-operator-role $(oc whoami) -n mtv-tests
    oc adm policy add-role-to-user mtv-operator-role $(oc whoami) -n openshift-mtv

This least-privilege approach limits the blast radius and follows security best practices for production environments.

3. Configure Your Source Provider

What is .providers.json? A configuration file that tells the tests how to connect to your source virtualization platform.

Why do you need it? The tests need to:

  • Connect to your source provider (vSphere, RHV, OpenStack, OVA, or Hyper-V)
  • Find the base VM to clone for testing
  • Create test VMs and perform migrations

What should it include?

  • Connection details (hostname, credentials)
  • Location information (datacenter, cluster)
  • Base VM/template name to use for testing

Security Considerations

Protect your credentials file:

⚠️ IMPORTANT: The .providers.json file contains sensitive credentials. Follow these security practices:

  • Set restrictive permissions: chmod 600 .providers.json (owner read/write only)
  • Never commit to Git: Add .providers.json to your .gitignore file
  • Rotate secrets regularly: Update passwords and credentials on a regular schedule
  • Use secret management: For OpenShift deployments, use Kubernetes secrets
  • Delete when done: Remove the file from local systems when no longer needed

About # pragma: allowlist secret comments:

⚠️ The JSON examples below contain # pragma: allowlist secret comments - these are REQUIRED for this repository's pre-commit hooks but are NOT valid JSON. Do NOT copy these comments to your actual .providers.json file. They exist only for documentation tooling, not security.

By default, the file is loaded from .providers.json in the current directory. You can override this with:

  • --providers-json /path/to/file.json pytest CLI argument
  • PROVIDERS_JSON_PATH=/path/to/file.json environment variable

Priority: CLI arg > environment variable > default .providers.json

Create a providers JSON file with your provider's details:

  • default location: .providers.json in the current directory
  • custom location: any path passed via --providers-json or PROVIDERS_JSON_PATH

VMware vSphere Example:

{
  "vsphere-8.0.1": {
    "type": "vsphere",
    "version": "8.0.1",
    "fqdn": "vcenter.example.com",
    "api_url": "https://vcenter.example.com/sdk",
    "username": "administrator@vsphere.local",
    "password": "your-password",  # pragma: allowlist secret
    "guest_vm_linux_user": "root",
    "guest_vm_linux_password": "your-vm-password"  # pragma: allowlist secret
  }
}

Key requirements:

  • Configuration key must follow pattern: {type}-{version} (e.g., "vsphere-8.0.1" for vSphere 8.0.1)
  • This key is used directly with --tc=source_provider:vsphere-8.0.1
  • Replace 8.0.1 with your actual vSphere version - both the key and version field must match
  • All fields shown above are required
  • Replace placeholder values with your actual credentials and endpoints

For other providers (RHV, OpenStack, OVA, Hyper-V, or copy-offload configuration):

# Use the example file as a template
cp .providers.json.example .providers.json
# Edit with your actual values

See .providers.json.example for complete templates of all supported providers.

Hyper-V management_type: Optional. "cluster" for a Failover Cluster, "standalone" for a single host. The value is passed through to spec.settings.managementType on the MTV Provider CR. If the key is omitted, the suite does not set the field and MTV uses standalone.

Hyper-V two-phase transfer: TestColdMigrationComprehensive passes Plan pre_copy_source_disks (spec preCopySourceDisks) when the source provider is Hyper-V. The Hyper-V Provider CR is created with smb_max_channels from tests/tests_config/config.py (default 4, spec settings.smbMaxChannels) when any plan config sets pre_copy_source_disks: True. Other Hyper-V tests keep MTV defaults (preCopySourceDisks unset; smbMaxChannels omitted unless that plan flag is present).

Hyper-V preserve_static_ips: Before creating the Plan, the suite waits until Forklift inventory has guestNetworks entries matching every static IPv4 address reported in the saved source VM data (host KVP is not sufficient on cluster). Any required gateway must appear on the matching entry, not on an unrelated NIC. The existing IPv4/gateway readiness check also remains: a gateway is required when the source reports one or its network metadata is incomplete. The gateway requirement is waived only when every source NIC has IP entries that explicitly report no gateway; missing metadata is not treated as gatewayless. Timeout scales with HYPERV_REFRESH_INTERVAL (default ~700s).

Schema & IDE support: providers_schema.json documents which fields are required vs optional per provider type. Add "$schema" as the first key in your .providers.json for IDE autocomplete:

{
  "$schema": "https://raw.githubusercontent.com/RedHatQE/mtv-api-tests/main/providers_schema.json",
  "my-vsphere": { ... },
  "my-ovirt": { ... }
}

4. Find Your Storage Class

Check which storage classes are available in your OpenShift cluster:

oc get storageclass

Pick one that supports block storage (e.g., ocs-storagecluster-ceph-rbd, ontap-san-block). You'll use this name in the next step.

5. Run Your First Test

Execute tier0 tests (smoke tests) using the containerized test suite:

# Set cluster password in environment variable (avoids shell history exposure)
export CLUSTER_PASSWORD='your-cluster-password'  # pragma: allowlist secret

podman run --rm \
  -v $(pwd)/.providers.json:/app/.providers.json:ro \
  -e CLUSTER_PASSWORD \
  ghcr.io/redhatqe/mtv-api-tests:latest \
  uv run pytest -m tier0 -v \
    --tc=cluster_host:https://api.your-cluster.com:6443 \
    --tc=cluster_username:kubeadmin \
    --tc=cluster_password:${CLUSTER_PASSWORD} \
    --tc=source_provider:vsphere-8.0.1 \
    --tc=storage_class:YOUR-STORAGE-CLASS

Security Note: Use environment variables to avoid shell-history exposure. Note the expanded value can still appear in process listings inside the container; prefer OpenShift Secrets or other secret injection where possible.

Note: On RHEL/Fedora with SELinux, add ,z to volume mounts: -v $(pwd)/.providers.json:/app/.providers.json:ro,z. You can use docker instead of podman if preferred.

Windows Users: Replace $(pwd) with ${PWD} in PowerShell or use absolute paths like C:\path\to\.providers.json:/app/.providers.json:ro. Requires Docker Desktop or Podman Desktop.

Non-root Container: The container runs as UID 1001. Read-only mounts (:ro) are unaffected, but writable bind mounts must be accessible to that UID. See the Test Results and Reports section for details.

Replace:

  • https://api.your-cluster.com:6443 → Your OpenShift API URL
  • kubeadmin → Your cluster username
  • your-cluster-password → Your cluster password
  • YOUR-STORAGE-CLASS → Your storage class from step 4
  • vsphere-8.0.1 → Provider key from your .providers.json (e.g., vsphere-8.0.1, ovirt-4.4.9)

Cluster credentials: each of cluster_host, cluster_username, and cluster_password is resolved from the pytest config value first (--tc=cluster_host:...) and then from the matching CLUSTER_HOST, CLUSTER_USERNAME, or CLUSTER_PASSWORD environment variable. Runs that provide any of the three must provide all three as nonblank values; partial, empty, or whitespace-only sets fail fast with a named error such as Missing cluster credentials: set 'cluster_username' in pytest config or CLUSTER_USERNAME. Omitting all three is valid only for ambient authentication from a kubeconfig available to the test process. mtv-api-tests run reaches that path only when no OpenShift provider is selected from .providers.json — the CLI checks for an existing oc token only then and does not pass a standalone token to the client. See runtime configuration for details.

VM naming: the OpenShift version segment of generated source VM names is read from the target cluster automatically — do not pass --tc=target_ocp_version.


Remote Debugging

Set MTV_REMOTE_PDB=1 to enable a remote pdb debugger reachable over a socket, for interactive debugging of test runs by humans or AI agents. Without the variable, debugging is completely disabled and the helpers below are no-ops.

Run with -s so the listening message is not swallowed by pytest output capture, and use a single worker (-n 0) when debugging — parallel xdist workers listen on distinct ports (4444 + worker index), but a debugger session per worker is rarely what you want.

Trigger 1 — breakpoint in code

Temporarily insert a call where you want to stop (remove it when done):

from utilities.debug import remote_breakpoint

remote_breakpoint()

Run the test; the process blocks at the call and waits for a debugger connection.

Trigger 2 — post-mortem on failure

With MTV_REMOTE_PDB=1 set, any test failure automatically opens a remote post-mortem session on the failing traceback (no --pdb needed). If you pass --pdb, the local terminal debugger takes precedence.

Attaching

# host and port come from the run's "RemotePdb session open at <host>:<port>" line
# (defaults: 127.0.0.1 4444; see the REMOTE_PDB_HOST / REMOTE_PDB_PORT overrides below)
nc <host> <port>        # or: rlwrap nc <host> <port> / telnet <host> <port>

Send c to continue the run. q — or dropping the connection — quits the debugger: at a code breakpoint the current test fails with BdbQuit and the run continues; at a failure post-mortem the whole test session exits (matching pytest's native --pdb quit behavior).

The debugger binds to 127.0.0.1 only — it is never exposed on the cluster network. To debug a run inside an OpenShift Job pod, the pod must run with MTV_REMOTE_PDB=1 — the Job manifest generated by the CLI (see Running as OpenShift Job) does not set it, so add it to the container env (or prefix the pytest command in the manifest with MTV_REMOTE_PDB=1) before creating the Job. Then forward the port:

oc port-forward must stay alive while the client connects — run it in the background (or a separate terminal) as shown, and stop it when the session ends:

port=4444  # replace with the numeric listener port from the run's "RemotePdb session open at" line
oc port-forward -n "$job_namespace" "pod/$pod_name" "$port:$port" &
forward_pid=$!
nc localhost "$port"
kill "$forward_pid"

$job_namespace is the generated UUID-suffixed namespace shown by the CLI at manifest generation (and present in the manifest) — oc port-forward without -n searches only your current namespace and will not find the pod.

Host and port can be overridden with REMOTE_PDB_HOST / REMOTE_PDB_PORT. The host must resolve to a loopback address — non-loopback values are rejected with a ValueError, so the debugger can never be exposed on the cluster network.

AI agents

The repo ships an agent skill for driving debugger sessions — see skills/. It is not auto-discovered: symlink it into your agent harness's skills directory manually (see skills/README.md), and restart the session so the harness picks it up.

Note: while a debugger session is attached, the test process is frozen but the cluster keeps moving — migrations and reconciliation continue server-side. Use the debugger for inspection; avoid long pauses inside migration polling loops.


Running Different Test Categories

The Quick Start runs tier0 tests (smoke tests). You can run other test categories by changing the -m marker:

Marker What It Tests When to Use
tier0 Smoke tests - critical paths First run, quick validation
tier1 Extended functionality tests Broader coverage beyond smoke, including skip guest conversion
copyoffload Fast migrations via shared storage Testing storage arrays
copyoffload_sanity Copy-offload sanity subset (see below) Quick copy-offload validation
warm Warm migrations (VMs stay running) Specific scenario testing
shared_disk Shared disk migration tests Testing shared disk between VMs
ca_crt CA certificate field (ca.crt) in provider secrets Testing ca.crt secret field support
vsphere VMware vSphere provider-specific tests Tests specific to vSphere provider
hyperv Hyper-V provider-specific tests Tests specific to Hyper-V provider
upgrade Migration across MTV operator upgrades Validating upgrade compatibility

Warm Migration Sanity

The existing TestSanityWarmMtvMigration also verifies qcow2 overlay lifecycle logs for vSphere sources (MTV-5644) after migration completes, using the same VM, Plan, and conversion pods. It checks in-place conversion, matching created/committed overlay filenames, and no discarded overlays before the usual migrated-VM checks. Conversion logs are captured during migration, alongside Deep Inspection capture, and retained by migration, Plan VM ID, and conversion pod UID for verification after success. A complete capture requires logs read after the conversion container terminates; after Plan success, the test waits up to 60 seconds for Kubernetes to report termination and capture those logs. Missing or incomplete captures and missing lifecycle messages fail the sanity test. The capture must include one completed conversion pod for each configured Plan VM ID. Pods that disappear before complete capture can still prevent verification. RHV runs do not perform this check.

Resume Conversion Tests

tests/warm/test_resume_conversion_warm_migration.py covers vSphere warm migration recovery after conversion failure and rejection of a resume request without a prior migration failure. These tests require an MTV version supporting resumeConversion and openshift-python-wrapper version 11.0.148 or later.

Copy-Offload Sanity Tests

The copyoffload_sanity marker selects a curated subset of copy-offload tests that cover the core migration scenarios. Use this marker for quick validation of copy-offload functionality without running the full suite. The sanity set covers:

Test ID Test Class What It Covers
MTV-562 TestCopyoffloadRdmVirtualDiskMigration RDM (Raw Device Mapping) virtual disk migration
MTV-564 TestCopyoffloadMultiDatastoreMigration VMs with disks spread across multiple datastores
MTV-565 TestCopyoffloadMixedDatastoreMigration Mixed environment - XCOPY-capable and non-XCOPY datastores with fallback
MTV-572 TestCopyoffloadScaleMigration Scale migration (5 VMs concurrently)
MTV-573 TestCopyoffload10MixedDisksMigration VM with 10 mixed disks (thin and thick provisioned)
MTV-577 TestCopyoffloadWarmMigration Warm (live) migration with copy-offload

Together these tests validate: different disk types (thin, thick, RDM), multi-disk and multi-datastore layouts, mixed XCOPY/non-XCOPY fallback behavior, concurrent scale migration, and warm migration -- providing broad coverage of copy-offload functionality in a single run.

# Run only the sanity subset
podman run ... uv run pytest -m copyoffload_sanity -v --tc=source_provider:vsphere-8.0.1 ...

Examples - Change -m tier0 to run different tests:

Note: In the examples below, replace podman run ... with the full podman run command shown in the Quick Start section (step 5), including the image name and volume mounts.

# Warm migration tests
podman run ... uv run pytest -m warm -v --tc=source_provider:vsphere-8.0.1 ...

# Copy-offload tests (full suite)
podman run ... uv run pytest -m copyoffload -v --tc=source_provider:vsphere-8.0.1 ...

# Copy-offload sanity tests (quick validation)
podman run ... uv run pytest -m copyoffload_sanity -v --tc=source_provider:vsphere-8.0.1 ...

# Combine markers
podman run ... uv run pytest -m "tier0 or warm" -v --tc=source_provider:vsphere-8.0.1 ...

Running as OpenShift Job

For long-running test suites or automated CI/CD pipelines, you can run tests as OpenShift Jobs:

Step 1: Create Secret with Configuration

Store your .providers.json file and optionally cluster credentials as an OpenShift secret:

Option A: Providers configuration only (credentials passed as command args in Job):

oc create namespace mtv-tests
oc create secret generic mtv-test-config \
  --from-file=providers.json=.providers.json \
  -n mtv-tests

Option B: Include cluster credentials in Secret (recommended - avoids exposing secrets in Job YAML):

oc create namespace mtv-tests
oc create secret generic mtv-test-config \
  --from-file=providers.json=.providers.json \
  --from-literal=cluster_host=https://api.your-cluster.com:6443 \
  --from-literal=cluster_username=kubeadmin \
  --from-literal=cluster_password=your-cluster-password \
  -n mtv-tests

Replace the cluster values with your actual OpenShift API endpoint and credentials. This approach keeps sensitive data out of the Job definition and prevents credential exposure in oc get job -o yaml output.

Step 2: Create and Run Job

Use this template to run tests. Customize the placeholders:

  • [JOB_NAME] - Unique job name (e.g., mtv-tier0-tests, mtv-warm-tests, mtv-copyoffload-tests)
  • [TEST_MARKERS] - Pytest marker(s) (e.g., tier0, warm, copyoffload)
  • [TEST_FILTER] - Optional: specific test name for -k flag (omit lines for all tests)

Note: This Job template is also used in guides/copyoffload/how-to-run-copyoffload-tests.md. If updating, ensure both files remain in sync for the following fields: apiVersion, kind, metadata.name, spec.template, container image, command, and volumeMounts/volumes configuration.

Template:

cat <<EOF | oc apply -f -
apiVersion: batch/v1
kind: Job
metadata:
  name: [JOB_NAME]
  namespace: mtv-tests
spec:
  template:
    spec:
      restartPolicy: Never
      containers:
      - name: tests
        image: ghcr.io/redhatqe/mtv-api-tests:latest  # Or use your custom image
        env:
        - name: CLUSTER_HOST
          valueFrom:
            secretKeyRef:
              name: mtv-test-config
              key: cluster_host
              optional: true
        - name: CLUSTER_USERNAME
          valueFrom:
            secretKeyRef:
              name: mtv-test-config
              key: cluster_username
              optional: true
        - name: CLUSTER_PASSWORD
          valueFrom:
            secretKeyRef:
              name: mtv-test-config
              key: cluster_password
              optional: true
        command:
          - /bin/bash
          - -c
          - |
            uv run pytest -m [TEST_MARKERS] \
              -v \
              ${CLUSTER_HOST:+--tc=cluster_host:${CLUSTER_HOST}} \
              ${CLUSTER_USERNAME:+--tc=cluster_username:${CLUSTER_USERNAME}} \
              ${CLUSTER_PASSWORD:+--tc=cluster_password:${CLUSTER_PASSWORD}} \
              --tc=source_provider:[SOURCE_PROVIDER] \
              --tc=storage_class:[STORAGE_CLASS]
            # Replace [SOURCE_PROVIDER] with the key from your .providers.json (e.g., vsphere-8.0.3.00400)
            # Replace [STORAGE_CLASS] with your OpenShift storage class name
            # Optional: To run a specific test, add: -k [TEST_FILTER]
        volumeMounts:
        - name: config
          mountPath: /app/.providers.json
          subPath: providers.json
      volumes:
      - name: config
        secret:
          secretName: mtv-test-config
EOF

Example: Run tier0 tests

Replace placeholders:

  • [JOB_NAME] → mtv-tier0-tests
  • [TEST_MARKERS] → tier0
  • [SOURCE_PROVIDER] → vsphere-8.0.3.00400 (key from your .providers.json)
  • [STORAGE_CLASS] → rhosqe-ontap-san-block (your OpenShift storage class)
  • Remove the commented -k and [TEST_FILTER] lines

Replace cluster configuration:

  • ghcr.io/redhatqe/mtv-api-tests:latest - Use this public image, or substitute with your custom image from Quick Start Step 1 (e.g., <YOUR-REGISTRY>/mtv-tests:latest)
  • If you used Option A (credentials in Job): Replace the command with explicit --tc flags as shown below
  • If you used Option B (credentials in Secret): The Job template above automatically reads from the Secret
  • vsphere-8.0.3.00400 - Your source provider key (must match key in .providers.json)
  • your-storage-class - Your OpenShift storage class name

For Option A (credentials in Job YAML) - NOT RECOMMENDED:

🚨 Security Warning: This approach is strongly discouraged because:

  • Credentials are visible in plaintext via oc get job -o yaml
  • Passwords appear in CI logs, cluster audit logs, and etcd backups
  • Anyone with read access to the Job resource can see passwords
  • Use Option B with Kubernetes Secrets instead (recommended above)

If you must use Option A (only for isolated test environments), replace the command section with:

        command:
          - uv
          - run
          - pytest
          - -m
          - [TEST_MARKERS]
          - -v
          - --tc=cluster_host:https://api.your-cluster.com:6443
          - --tc=cluster_username:kubeadmin
          - --tc=cluster_password:your-cluster-password  # pragma: allowlist secret
          - --tc=source_provider:vsphere-8.0.3.00400
          - --tc=storage_class:your-storage-class

Step 3: Monitor Test Execution

Follow test logs in real-time:

# Replace [JOB_NAME] with your actual job name (e.g., mtv-tier0-tests)
oc logs -n mtv-tests job/[JOB_NAME] -f

Check Job status:

oc get jobs -n mtv-tests
# Look for "COMPLETIONS" showing 1/1 = success, 0/1 = still running

Retrieve test results:

# Copy JUnit XML report from completed pod (replace [JOB_NAME] with your actual job name)
# Note: pytest writes to /app/junit-report.xml (WORKDIR /app, pytest.ini: --junit-xml=junit-report.xml)
POD_NAME=$(oc get pods -n mtv-tests -l job-name=[JOB_NAME] -o jsonpath='{.items[0].metadata.name}')
oc cp mtv-tests/$POD_NAME:/app/junit-report.xml ./junit-report.xml

Clean up after tests:

# Replace [JOB_NAME] with your actual job name
oc delete job [JOB_NAME] -n mtv-tests

Interactive Setup Tool

The mtv-api-tests CLI tool automates configuration and test execution. It discovers vSphere and OpenShift resources via their APIs, generates .providers.json and a self-contained mtv-api-tests-manifests.yaml (Namespace + Secret + Job), and can run tests locally or as an OpenShift Job.

Generate Configuration

uv run mtv-api-tests generate

The wizard will:

  1. Prompt for vSphere credentials (or read from VSPHERE_HOST, VSPHERE_USERNAME, VSPHERE_PASSWORD)
  2. Connect to vSphere and let you select datastores, a test VM, and an ESXi host
  3. Prompt for storage vendor and credentials
  4. Connect to OpenShift and let you select a storage class
  5. Ask which test category to run (all, copyoffload, copyoffload_sanity, tier0, tier1, warm, remote)
  6. Write .providers.json and mtv-api-tests-manifests.yaml

Security Note: The generated mtv-api-tests-manifests.yaml contains embedded credentials (cluster username/password and provider secrets encoded in base64). Do not commit this file to version control or share it. The file is created with 0600 permissions and is listed in .dockerignore.

Run Tests

# Run locally
uv run mtv-api-tests run --mode local

# Run as OpenShift Job (uses the generated mtv-api-tests-manifests.yaml)
uv run mtv-api-tests run --mode job

When --category or --source-provider are omitted, the tool prompts interactively.

Non-Interactive / CI Usage

All interactive prompts can be bypassed with CLI flags, making the tool suitable for CI pipelines and repeatable lab scripts:

# Generate configuration with a specific category and custom image
uv run mtv-api-tests generate --category copyoffload --image quay.io/myorg/mtv-api-tests:v2.8.3

# Run locally with all options pre-set (no prompts)
uv run mtv-api-tests run --mode local \
  --category tier0 \
  --source-provider vsphere-8.0.3.00400 \
  --storage-class ontap-san-block \
  -k "thin or thick"

# Run as OpenShift Job with a custom manifest path
uv run mtv-api-tests run --mode job --job-yaml /path/to/mtv-api-tests-manifests.yaml

Use uv run mtv-api-tests generate --help or uv run mtv-api-tests run --help to see all available flags.

Coverage Search

uv run mtv-api-tests coverage <keywords> answers "is scenario X covered by a test class?" by searching test-class plan docstrings (see test_plan_schema.md). It parses the docstrings on demand with AST — no test modules are imported and no index is stored — so the docstrings are the single source of truth.

When a scenario or regression comes in, the workflow is: search what covers it → copy the pytest node ID from the output → check the CI status of that test instead of running it manually.

# Search keywords (case-insensitive, partial match; quoting and multiple
# arguments are equivalent - all words are ANDed)
uv run mtv-api-tests coverage "static IP"

# Multiple keywords are ANDed
uv run mtv-api-tests coverage warm resume

# List classes whose docstring is missing or does not follow the schema yet
uv run mtv-api-tests coverage --missing-plans

# JSON output for scripting
uv run mtv-api-tests coverage --json "shared disk"

Each match shows the class name, one-sentence scenario summary, pytest markers, file:line, the pytest node ID that runs exactly that class, and the matching docstring line. Keywords match across the whole docstring, the class name, and the markers (shared disk matches the shared_disk marker); matches in classes whose docstring does not follow the schema yet are still listed, marked with a note. No matches exits 0 with a "not covered" message, so scripts can chain on output rather than on exit codes.

Latency-Based Provider Selection

Instead of pinning --tc=source_provider:<key>, a run can identify a candidate pool by type — version optional — and let the suite pick the fastest member from the cluster's point of view:

--tc=source_provider_type:vsphere --tc=source_provider_version:8.0.1   # exact version
--tc=source_provider_type:vsphere                                     # every version of the type

A feature key (copyoffload) flips pool membership: copy-offload-capable providers — entries with a copyoffload section — are excluded from the pool by default and included only when the key is passed:

--tc=source_provider_type:vsphere --tc=source_provider_version:8.0.1 --tc=source_provider_feature:copyoffload

Selection happens once per run, in the session-scoped selected_source_provider fixture (requested first by the autouse fixture, before anything consumes the provider). A pool with a single member — or whose members all share one probe endpoint (e.g. two OVA entries on the same NFS server) — is selected instantly without probing; no probe pod is created. Otherwise, in a single-process run the pool is probed inline; under xdist the first worker to hold a run-scoped file lock probes and writes the selection while every other worker reads the same file, so all workers run against the same provider. Probing measures every unique endpoint once (providers sharing a host:port share the measurement); each endpoint's host comes from fqdn, else from api_url (URL or NFS export path), else from the OpenShift host field, and the port from api_url when it carries one, else the type's API port (OVA NFS 2049, Hyper-V 5986, otherwise 443). The chosen provider and its measured RTT are logged.

Rules:

  • Explicit --tc=source_provider:<key> always wins: nothing is probed and nothing changes.
  • The pool form requires the type key; version is optional and source_provider_version without source_provider_type aborts the session with pytest.exit(...).
  • Neither an explicit provider nor a pool aborts with the missing-config error, exactly as before.
  • Candidates are the .providers.json entries matching the requested type (and version, when passed), gated by the feature key (copyoffload providers only with the key, excluded without it); an empty pool aborts the session with a clear error — never a skip.
  • A single-candidate or single-endpoint pool is selected without probing; otherwise the winner is the lowest-RTT endpoint with the lowest provider key as the deterministic tiebreak.
  • OpenShift pools are never probed: create_source_provider rewrites OpenShift sources to the local cluster host, so their configured endpoints are unused and the lowest provider key wins deterministically.
  • Unreachable candidates are excluded; a fully unreachable pool aborts the session.
  • In pool mode the provider is unknown at collection time, so collection-time provider gating uses the pool's source_provider_type and test item names carry the type instead of the provider key.

Note: for copy-offload (XCOPY) plans the bulk disk copy executes array-side (storage array / ESXi hosts, including dedicated_migration_hosts) and does not traverse the cluster-to-provider path. The probe measures the vCenter API path, which copy-offload runs still use for inventory and controller operations — selection stays valid for copy-offload pools, but the runtime win applies to cold/warm (NBD) migrations only.

Warning: pool selection assumes every member of the pool is a true clone — same VM inventory (names, guest agent, disk layouts) and same capabilities. The feature key enforces copy-offload capability; VM inventory equivalence is the operator's responsibility because it is not machine-checkable. Test configs pin VM names, so a pool of non-interchangeable providers breaks tests.


Copy-Offload: Accelerated Migrations (Advanced)

Copy-offload is an MTV feature that uses the storage backend to directly copy VM disks from vSphere datastores to OpenShift PVCs using XCOPY operations, bypassing the traditional v2v transfer path. This requires shared storage infrastructure between vSphere and OpenShift, VAAI (vSphere APIs for Array Integration) enabled on ESXi hosts, and a configured StorageMap with offload plugin settings.

For detailed instructions on running copy-offload tests, including prerequisites, configuration, and troubleshooting, see:

📖 Copy-Offload Testing Guide

Note: TestCopyoffloadPopulatorThrottlingMigration (MTV-696) and TestCopyoffloadDedicatedMigrationHost set both the ForkliftController controller_max_populator_inflight and populator deployment MAX_POPULATOR_INFLIGHT to 2. TestCopyoffloadDedicatedHostSharedPopulatorLimit (MTV-4494) sets both to 1 and sets the VM in-flight limit to 2. The fixtures restore the prior values afterward. Do not run them alongside other MTV migrations or copy-offload tests on the same cluster. The original dedicated-host test pins both source VMs away from configured dedicated hosts and retains multi-host coverage. The shared-limit scenario requires exactly one configured dedicated ESXi host and at least one other ESXi host; it places one source VM on each and checks that their active populate pods stay within the shared runtime-host populator limit. The assertion checks the peak observed Pending/Running-pod count during migration; it does not require a specific count of PopulatorThrottled events. It requires one migration poll to observe a Pending PVC with a structured PopulatorThrottled event, alongside a Running worker from the other source host on the sole configured dedicated host. The Event's involved PVC is matched by name and UID; the PVC's sourceHost label identifies its source, and the worker's runtime metadata identifies the dedicated host. The Event's latest timestamp must be at or after that worker started. The poll reuses its populate-Pod snapshot for concurrency counting and only lists PVCs when throttling Events exist. Runtime-host grouping prefers throttleHost when present and otherwise uses the worker's --migration-host argument. Current Forklift builds may reuse the populate Pod's sourceHost label for the runtime host.

For array-specific Secret keys not covered by built-in vendor fields, use storage_secret_extra in the copyoffload section of .providers.json (or COPYOFFLOAD_STORAGE_SECRET_EXTRA); see the guide.

For technical implementation details, see the vsphere-xcopy-volume-populator documentation.


LUKS Disk Encryption Migration (tier1)

This test suite verifies MTV's ability to migrate LUKS-encrypted VMs. It validates both successful cold migration when the correct decryption passphrase is provided, and expected failure at the ImageConversion phase when an incorrect LUKS decryption passphrase is used.

Configuration

Add the luks_passphrase field to your .providers.json provider configuration:

{
  "vsphere-8.0.1": {
    "type": "vsphere",
    "luks_passphrase": "your-luks-passphrase"
  }
}

The passphrase can be configured at two levels:

  • Provider-level (in .providers.json): Used as the default for all LUKS VMs
  • Per-VM override (in test config): Set luks_passphrase on individual VM entries to override the provider default

Running LUKS Tests

# Run LUKS encryption migration tests
podman run ... uv run pytest -m tier1 -k luks -v --tc=source_provider:vsphere-8.0.1 ...

See .providers.json.example for the complete provider template including the luks_passphrase field.


Disk Exclusion Migration (tier1)

The disk-exclusion test verifies MTV's ability to exclude individual source disks from a migration: the excluded disks are neither copied nor attached to the migrated VM, and no PVC is created for them in the dedicated VM namespace.

Configuring exclude_disks

Set exclude_disks on a VM entry in the test configuration with the vSphere bus addresses of the disks to exclude:

"virtual_machines": [
    {
        "name": "your-source-vm",
        "exclude_disks": ["scsi0:1"],  # vSphere bus addresses of disks to exclude
    },
]

The suite renames this key to the Plan spec.vms[].excludeDisks field before plan creation.

Running Disk Exclusion Tests

# Run the disk-exclusion migration test
podman run ... uv run pytest -k exclude_disks -v --tc=source_provider:vsphere-8.0.1 ...

See test-plan configuration for the full exclude_disks reference and cold migrations for the exclude-disks test pattern.


Target Scheduling Features (MTV 2.10.0+)

Control where and how migrated VMs are scheduled in the target OpenShift cluster.

targetNodeSelector

Schedule VMs to specific labeled nodes.

"target_node_selector": {
    "migration-workload": None,  # Exactly one label; None = auto-generated key & value with session_uuid
}

targetLabels

Apply custom labels to migrated VMs.

"target_labels": {
    "app": "my-application",
    "migrated-from": "vsphere",
    "test-id": None,  # Auto-generated
}

targetAffinity

Configure pod affinity/anti-affinity and node affinity rules.

"target_affinity": {
    "podAffinity": {
        "preferredDuringSchedulingIgnoredDuringExecution": [{
            "podAffinityTerm": {
                "labelSelector": {"matchLabels": {"app": "database"}},
                "topologyKey": "kubernetes.io/hostname"
            },
            "weight": 100
        }]
    },
    "nodeAffinity": {
        "requiredDuringSchedulingIgnoredDuringExecution": {
            "nodeSelectorTerms": [{
                "matchExpressions": [{
                    "key": "node.kubernetes.io/instance-type",
                    "operator": "In",
                    "values": ["m5.2xlarge"]
                }]
            }]
        }
    }
}

Usage Notes

Version requirement: MTV 2.10.0+. Mark tests with @pytest.mark.min_mtv_version("2.10.0") and @pytest.mark.usefixtures("mtv_version_checker").

Auto-generation: Setting a value to None replaces it with the session UUID (e.g., "test-id": None becomes "test-id": "mtv-api-tests-abc123"). This ensures uniqueness for parallel test execution and prevents conflicts.


Upgrade Tests

Upgrade tests validate that migration resources created on a pre-upgrade MTV version survive the operator upgrade and that migrations execute successfully afterward. The test flow is:

  1. Create migration resources (StorageMap, NetworkMap, Plan) on the current MTV version
  2. Upgrade the MTV operator to the target version using an external upgrade script
  3. Verify the upgraded operator version and that existing Plan CRs remain in Ready state
  4. Execute the migration on the upgraded operator
  5. Validate the migrated VMs

Required --tc Parameters

Upgrade tests require additional --tc parameters beyond the standard cluster and provider options:

Parameter Required Description
upgrade_repo_url Yes URL of the Git repository containing the upgrade script
upgrade_repo_ref Yes Git ref (branch or tag) to checkout
upgrade_script_path Yes Relative path to the upgrade script within the repository
mtv_upgrade_to_version Yes Target MTV version to upgrade to (e.g., 2.7.0)
mtv_upgrade_to_source Yes MTV source identifier (e.g., brew, released)
mtv_upgrade_image_index No Image index override (pass empty string "" to skip)

Upgrade Timeout Budget

The suite waits for the external upgrade script with an outer budget that exists only to catch a wedged process — the script owns its own internal waits (the MCP rollout, its oc wait timeouts). Configure it with either source, highest priority first:

Source Example
pytest option --mtv-upgrade-timeout 7200
environment variable MTV_UPGRADE_TIMEOUT_SECONDS=7200
default 5400 seconds

Keep the outer budget above whatever the script waits for internally: when it expires first the script is SIGTERMed and never gets to report its own timeout. The resolved budget is logged at the start of the run, and the script's stdout/stderr are persisted under the data-collector directory (redacted, with the last 200 lines of each also carried in the failure message). With --skip-data-collector no file is written.

Example

uv run pytest -m upgrade -v \
  --tc=cluster_host:https://api.your-cluster.com:6443 \
  --tc=cluster_username:kubeadmin \
  --tc=cluster_password:${CLUSTER_PASSWORD} \
  --tc=source_provider:vsphere-8.0.1 \
  --tc=storage_class:YOUR-STORAGE-CLASS \
  --tc=upgrade_repo_url:https://github.com/org/mtv-autodeploy.git \
  --tc=upgrade_repo_ref:main \
  --tc=upgrade_script_path:scripts/upgrade.sh \
  --tc=mtv_upgrade_to_version:2.7.0 \
  --tc=mtv_upgrade_to_source:released \
  --tc=mtv_upgrade_image_index:""

Useful Test Options

Debug and Troubleshooting Flags

Add these flags to your test runs for debugging:

For containerized runs (Podman/Docker) - use uv run pytest:

# Enable verbose output
uv run pytest -v                      # Verbose test names

# Enable debug logging
uv run pytest -s -vv                  # Very verbose with output capture disabled

# Set MTV/OpenShift debug level (add as environment variable)
podman run -e OPENSHIFT_PYTHON_WRAPPER_LOG_LEVEL=DEBUG ...

# Keep resources after test for inspection
uv run pytest --skip-teardown         # Don't delete VMs, plans, etc. after tests

# Skip data collector (faster, but no resource tracking)
uv run pytest --skip-data-collector   # Don't track created resources

# Change data collector output location
uv run pytest --data-collector-path /tmp/my-logs

# Run a specific test from a marker/suite
uv run pytest -k test_name            # Run only tests matching pattern
uv run pytest -m copyoffload -k test_copyoffload_thin_migration  # Run only thin test

For local developer mode (after running uv sync) - use uv run pytest or just pytest:

# Same flags work in local mode
pytest -v                             # Verbose test names
pytest -s -vv                         # Very verbose with output capture disabled
pytest --skip-teardown                # Don't delete VMs, plans, etc. after tests
pytest -k test_name                   # Run only tests matching pattern

Example - Run tier0 with debug mode and keep resources:

podman run --rm \
  -v $(pwd)/.providers.json:/app/.providers.json:ro \
  -e OPENSHIFT_PYTHON_WRAPPER_LOG_LEVEL=DEBUG \
  -e CLUSTER_PASSWORD \
  ghcr.io/redhatqe/mtv-api-tests:latest \
  uv run pytest -s -vv -m tier0 --skip-teardown \
    --tc=cluster_host:https://api.your-cluster.com:6443 \
    --tc=cluster_username:kubeadmin \
    --tc=cluster_password:${CLUSTER_PASSWORD} \
    --tc=source_provider:vsphere-8.0.1 \
    --tc=storage_class:YOUR-STORAGE-CLASS

Security Note: Use environment variables to avoid shell-history exposure. Note the expanded value can still appear in process listings inside the container; prefer OpenShift Secrets or other secret injection where possible. Set export CLUSTER_PASSWORD='your-password' before running. # pragma: allowlist secret

When to use these flags:

  • --skip-teardown - Test failed and you want to inspect the created VMs/plans
  • --skip-data-collector - Running many quick tests and don't need resource tracking
  • -s -vv - Test is failing and you need detailed output to diagnose
  • OPENSHIFT_PYTHON_WRAPPER_LOG_LEVEL=DEBUG - Need to see all API calls to OpenShift
  • -k - Run only specific tests by name pattern (useful for debugging or running individual tests)

Note: All examples in this section show the full command. When using containers, always prefix with podman run ... ghcr.io/redhatqe/mtv-api-tests:latest before the uv run pytest command.

Running Specific Tests with -k

The -k flag allows you to run specific tests by matching their names:

# Run only the thin migration test from copyoffload
podman run ... uv run pytest -k test_copyoffload_thin_migration -v \
  --tc=source_provider:vsphere-8.0.1 --tc=storage_class:ontap-san-block

# Run multiple tests with pattern matching
podman run ... uv run pytest -k "test_copyoffload_multi_disk" -v ...  # Matches both multi-disk tests
podman run ... uv run pytest -k "thin or thick" -v ...                 # Matches thin and thick tests

List all available test names:

# In container
podman run --rm ghcr.io/redhatqe/mtv-api-tests:latest uv run pytest --collect-only -q

# In local developer mode
uv run pytest --collect-only -q

Test Results and Reports

Tests automatically generate a JUnit XML report (junit-report.xml) containing:

  • Test results (passed/failed/skipped)
  • Execution times
  • Error messages and stack traces
  • Test metadata

Accessing the report:

From local Podman/Docker run:

# Default path: /app/junit-report.xml (WORKDIR /app, pytest.ini: --junit-xml=junit-report.xml)
# Override with --junit-xml to write to a mounted volume for persistence:
# Note: source_provider must match a key from your .providers.json (e.g., vsphere-8.0.1)
podman run --rm \
  -v $(pwd)/.providers.json:/app/.providers.json:ro \
  -v $(pwd)/results:/app/results \
  -e CLUSTER_PASSWORD \
  ghcr.io/redhatqe/mtv-api-tests:latest \
  uv run pytest -m tier0 -v \
    --junit-xml=/app/results/junit-report.xml \
    --tc=cluster_host:https://api.your-cluster.com:6443 \
    --tc=cluster_username:kubeadmin \
    --tc=cluster_password:${CLUSTER_PASSWORD} \
    --tc=source_provider:vsphere-8.0.1 \
    --tc=storage_class:YOUR-STORAGE-CLASS

# Report will be saved to ./results/junit-report.xml

Non-root Container: The container runs as UID 1001, so the host results/ directory must be writable. With podman rootless this usually works automatically (user namespace mapping). For podman/docker rootful, run mkdir -p results && chmod 777 results or use podman run --userns=keep-id. On OpenShift, the Dockerfile's group-permission pattern (chgrp -R 0 && chmod -R g=u) handles this automatically.

Security Note: Use environment variables to avoid shell-history exposure. Note the expanded value can still appear in process listings inside the container; prefer OpenShift Secrets or other secret injection where possible. Set export CLUSTER_PASSWORD='your-password' before running. # pragma: allowlist secret

From OpenShift Job:

# Copy report from completed pod (default path: /app/junit-report.xml from pytest.ini)
# Replace [JOB_NAME] with your job name, e.g., mtv-tier0-tests
POD_NAME=$(oc get pods -n mtv-tests -l job-name=[JOB_NAME] -o jsonpath='{.items[0].metadata.name}')
oc cp mtv-tests/$POD_NAME:/app/junit-report.xml ./junit-report.xml

View report in CI/CD tools: Most CI/CD platforms (Jenkins, GitLab CI, GitHub Actions) can parse JUnit XML for test result dashboards.

AI-Powered Failure Analysis (--analyze-with-ai)

The --analyze-with-ai flag enriches JUnit XML reports with AI-powered failure analysis. After tests complete, failed test cases are sent to a rootcoz server, which classifies failures and suggests fixes. The analysis results are injected back into the JUnit XML as structured properties (classification, details, suggested code fixes, bug reports) and human-readable summaries.

Prerequisites:

  • A running rootcoz server
  • ROOTCOZ_SERVER_URL environment variable set to the server URL

Environment variables (pytest client):

Variable Required Default Description
ROOTCOZ_SERVER_URL Yes - rootcoz server URL
ROOTCOZ_AI_PROVIDER No from .rootcoz/settings.json via rootcoz Optional override for AI provider
ROOTCOZ_AI_MODEL No from .rootcoz/settings.json via rootcoz Optional override for AI model
ROOTCOZ_TIMEOUT No 600 Request timeout in seconds

Environment variables can be set via a .env file in the project root (loaded automatically when the flag is used). Pytest does not read .rootcoz/settings.json. When ROOTCOZ_AI_PROVIDER / ROOTCOZ_AI_MODEL are unset, the request omits them and rootcoz applies defaults from that file.

Repo settings for rootcoz (.rootcoz/settings.json):

This file is for the rootcoz server/CLI when it analyzes this repository (provider/model defaults, peer configs, timeouts, additional_repos, and related options). rootcoz reads and applies it; pytest --analyze-with-ai only passes optional ROOTCOZ_* overrides when set.

Usage:

uv run pytest -m tier0 -v --analyze-with-ai \
  --tc=source_provider:vsphere-8.0.1 \
  --tc=storage_class:YOUR-STORAGE-CLASS

Notes:

  • Requires --junit-xml (enabled by default in pytest.ini)
  • Skipped automatically during --collectonly and --setupplan
  • If ROOTCOZ_SERVER_URL is not set, the feature is disabled with a warning
  • If ROOTCOZ_AI_PROVIDER / ROOTCOZ_AI_MODEL are unset, rootcoz uses .rootcoz/settings.json
  • Original JUnit XML is preserved if enrichment fails

Known Limitations

Hyper-V KVP DHCP Detection (RHEL 9)

The preserve_static_ips feature relies on Hyper-V KVP DHCPEnabled to distinguish static from DHCP-assigned IPs. This field is unreliable on RHEL 9 guests using NetworkManager keyfiles (the default configuration). The Linux hv_kvp_daemon determines DHCP status by checking /etc/sysconfig/network-scripts/ifcfg-* files — on RHEL 9+ (which uses keyfiles in /etc/NetworkManager/system-connections/), no ifcfg file is found, causing all NICs to report DHCPEnabled = False regardless of actual DHCP state.

Impact: DHCP-assigned IPs are incorrectly treated as static, causing preserve_static_ips post-migration verification to fail.

Workarounds:

  • Use RHEL 10 guests (patched hv_kvp_daemon queries NetworkManager directly)
  • Use RHEL 9 guests with ifcfg-style network configuration
  • Ensure Hyper-V test VMs use only static IPs on NICs being verified

Troubleshooting

Error: "pytest: command not found"

Make sure you're using uv run pytest (not just pytest):

# ✅ Correct
podman run ... uv run pytest -m tier0 ...

# ❌ Wrong
podman run ... pytest -m tier0 ...

Authentication Failed

oc whoami
oc auth can-i create virtualmachines

Provider Connection Failed

# Test connectivity from cluster
oc run test-curl --rm -it --image=curlimages/curl -- curl -k https://vcenter.example.com

# List available provider keys
cat .providers.json | jq 'keys'

# Verify credentials for a specific provider (replace with your actual key)
cat .providers.json | jq '."vsphere-8.0.1"'

Storage Class Not Found

oc get storageclass  # Use actual storage class name

Migration Stuck

# Check MTV operator logs
oc logs -n openshift-mtv deployment/forklift-controller -f

# Check plan status
oc get plans -A
oc describe plan <plan-name> -n openshift-mtv

Collect Debug Information

Automatic failure collection attempts at most one successful must-gather per class-plan instance per worker, using a percent-encoded class node ID, class parameter indices and worker ID under .data-collector/. It binds the current Plan or explicit None at the class-plan boundary before default fixture finalization, including early exits, and retains that context for retries. Standalone tests gather immediately. Known operational class-plan collection failures remain pending for the session-finish retry; artifacts are not guaranteed if it fails. Standalone tests gather immediately on the first failing phase; an operational failure is retried only when another phase of the same test also fails. Collector exceptions during setup or call are stashed on the item, cleared at teardown and not retried there. Standalone failures are not queued for session-finish retry. Unexpected standalone setup/call collector errors are retained on the item until pytest fixture finalization. The session-finish hook runs the pending-gather flush, resource inventory writing and configured cleanup before the inner session hooks yield, so xdist worker shutdown cannot interrupt them; the summary and enabled AI enrichment run after those hooks, and errors are re-raised at the end with earlier errors chained. Invalid image metadata and other configuration/programming errors propagate instead of returning False. A pending class-plan gather remains queued and may raise again when retried at a later boundary or session finish. Inventory, configured teardown, temporary-directory removal, summary and enabled AI enrichment are attempted independently; cleanup errors propagate with earlier errors chained. See diagnostic collection for timing and teardown limitations.

oc adm must-gather --image=quay.io/kubev2v/forklift-must-gather:latest --dest-dir=/tmp/mtv-logs

Manual Resource Cleanup

If tests fail or you used --skip-teardown, clean up manually.

About the data collector: The test suite includes a data collector feature that tracks all created resources in .data-collector/resources.json. This file is created automatically during test runs unless you pass --skip-data-collector. When available, use tools/clean_cluster.py with the resources.json file for automated cleanup. If the file doesn't exist (e.g., you used --skip-data-collector), fall back to manual oc delete commands.

# Using resource tracker (if data collector was enabled)
uv run tools/clean_cluster.py .data-collector/resources.json

# Or manually delete resources
oc delete vm --all -n <test-namespace>
oc delete plan --all -n openshift-mtv
oc delete provider <provider-name> -n openshift-mtv

FAQ

Q: Do I need Python/pytest/uv on my machine? A: Not for container-based runs — you only need Podman or Docker. For local runs, you need uv (which manages Python automatically). See Running Locally Without Container.

Q: How long do tests take? A: Test duration varies. tier0 tests are fastest (smoke tests), warm migration tests include warm migration scenarios, and copy-offload tests are optimized for speed with shared storage.

Q: Can I run on SNO (Single Node OpenShift)? A: SNO has been validated with copy-offload tests. Other test types may work but have not been specifically validated on SNO.

Q: Do tests generate reports? A: Yes. Tests automatically generate a JUnit XML report (junit-report.xml) with test results, execution times, and error details. See the "Test Results and Reports" section for how to access it.

Q: How do I debug a failing test? A: Use --skip-teardown to keep resources after test, and -s -vv for verbose output. Set OPENSHIFT_PYTHON_WRAPPER_LOG_LEVEL=DEBUG for API call logs. See the "Useful Test Options" section for details.


Advanced Topics

Running Locally Without Container

For test developers who want to run tests directly on their machine (requires manual setup).

Prerequisites (Must Install Manually)

System packages:

Note: uv automatically downloads and manages Python versions—no system Python installation needed. However, the packages below are system-level compilation dependencies required by Python extensions used by the test suite

# RHEL/Fedora
sudo dnf install gcc clang libxml2-devel libcurl-devel openssl-devel

# Ubuntu/Debian
sudo apt install gcc clang libxml2-dev libcurl4-openssl-dev libssl-dev

# macOS
brew install gcc libxml2 curl openssl

Required tools:

  • uv package manager (manages Python automatically)
  • oc CLI
  • virtctl (optional — the suite downloads a cluster-matched binary; a virtctl installed on PATH is ignored so the version always matches the cluster)

Note: uv will automatically download and manage the appropriate Python version. If you encounter HTTPS failures with Python 3.13, the issue is urllib3 2.4.0+ (released April 2025) rejecting non-RFC-compliant certificates missing AKID. Fix: Regenerate certificates with AKID, pin urllib3<2.4.0, or use Python 3.12 for legacy certs.

Setup and Run

# 1. Install uv
curl -LsSf https://astral.sh/uv/install.sh | sh

# 2. Clone repository and install dependencies
git clone https://github.com/RedHatQE/mtv-api-tests.git
cd mtv-api-tests
uv sync  # uv will automatically handle Python version

# 3. Set cluster password (avoids shell history; still visible in process listings)
export CLUSTER_PASSWORD='your-cluster-password'  # pragma: allowlist secret

# 4. Run tests
uv run pytest -v \
  --tc=cluster_host:https://api.cluster.com:6443 \
  --tc=cluster_username:kubeadmin \
  --tc=cluster_password:${CLUSTER_PASSWORD} \
  --tc=source_provider:vsphere-8.0.1 \
  --tc=storage_class:standard-csi

# For debug options (--skip-teardown, -s -vv, etc.), see "Useful Test Options" section above

About

No description, website, or topics provided.

Resources

Stars

2 stars

Watchers

5 watching

Forks

Releases

Packages

Contributors

Languages