Project Structure
mtv-api-tests is organized as an end-to-end pytest suite for Migration Toolkit for Virtualization (MTV). Instead of a
conventional Python application layout such as src/, the repository is split into scenario files, shared fixtures, provider
adapters, validation helpers, and repository automation/configuration files.
Note: The main entrypoint is
pytest, not a packaged application. Most of the reusable behavior lives inconftest.py,utilities/, andlibs/, while the files undertests/mostly define scenarios and expectations. There is also a thinmtv-api-testsCLI (cli/mtv_api_tests/) that wraps setup and local/Job execution.
At A Glance
mtv-api-tests/
├── tests/
│ ├── tests_config/config.py
│ ├── cold/
│ ├── copyoffload/
│ ├── deep_inspection/
│ ├── hooks/
│ ├── luks/
│ ├── plan_lifecycle/
│ ├── shared_disk/
│ ├── upgrade/
│ └── warm/
├── cli/mtv_api_tests/
├── utilities/
├── libs/
│ ├── providers/
│ ├── base_provider.py
│ └── forklift_inventory.py
├── exceptions/
├── scripts/hooks/
├── tools/
├── guides/copyoffload/
├── docs/
├── conftest.py
├── pyproject.toml
├── uv.lock
├── pytest.ini
├── tox.toml
├── Dockerfile
├── providers_schema.json
├── .providers.json.example
├── .pre-commit-config.yaml
├── renovate.json
├── .release-it.json
├── .coderabbit.yaml
├── .pr_agent.toml
├── .flake8
├── .markdownlint.yaml
├── .rootcoz/settings.json
├── AGENTS.md
├── test_plan_schema.md
├── README.md
├── jira.cfg.example
├── OWNERS
└── junit_report_example.xml
Test files do not live directly under tests/. Every scenario sits in a feature subdirectory (tests/<feature>/), and the
check-test-file-location pre-commit hook enforces that placement:
def _is_tests_root_test_file(path: Path) -> bool:
"""Return True if ``path`` is repo-root ``tests/test_*.py`` (no subdirectory).
Only ``tests/`` at the repository root is considered. Nested trees such as
``plugins/.../tests/test_*.py`` are not flagged.
Args:
path (Path): Candidate file path.
Returns:
bool: Whether the file is a disallowed root-level test module.
"""
rel = Path(repo_relative_posix(path))
return rel.parent == Path("tests") and rel.name.startswith("test_") and rel.suffix == ".py"
AGENTS.md states the same rule as a MUST: put all test files under tests/<feature>/, and never add unit_tests/ or
standalone test scripts, because these are live end-to-end tests against MTV.
A useful way to read the repository is:
pytest.initells you how the suite is launched.tests/tests_config/config.pytells you what each scenario wants to do.tests/<feature>/test_*.pytells you which migration flow is being exercised.conftest.pyshows how providers, namespaces, networks, hooks, and cleanup are prepared.libs/shows how the suite talks to source and destination platforms.utilities/shows how MTV resources are created and how migrated VMs are validated.
pytest.ini makes that structure explicit by wiring pytest to the scenario config file, JUnit output, strict markers, and
xdist distribution:
[pytest]
testpaths = tests
addopts =
-s
-o log_cli=true
-p no:logging
--tc-file=tests/tests_config/config.py
--tc-format=python
--junit-xml=junit-report.xml
--show-progress
--strict-markers
--dist=loadscope
markers =
tier0: Core functionality tests (smoke tests)
tier1: Extended functionality tests
remote: Remote cluster migration tests
warm: Warm migration tests
copyoffload: Copy-offload (XCOPY) tests
copyoffload_sanity: Copy-offload sanity tests - core scenarios for quick validation
copyoffload_snapshots: Copy-offload snapshot tests (vSphere only)
shared_disk: Shared disk migration tests (vSphere only)
incremental: marks tests as incremental (xfail on previous failure)
min_mtv_version: mark test to require minimum MTV version (e.g., @pytest.mark.min_mtv_version("2.6.0"))
ova: OVA provider-specific tests
vsphere: vSphere provider-specific tests
esxi: ESXi provider-specific tests
rhv: RHV provider-specific tests
hyperv: Hyper-V provider-specific tests
openstack: OpenStack provider-specific tests
openshift: OpenShift provider-specific tests
deep_inspection: Deep Inspection / Conversion CR tests (vSphere only)
ca_crt: CA certificate field (ca.crt) provider secret tests
aap: AAP (Ansible Automation Platform) hook integration tests (vSphere only)
upgrade: MTV operator upgrade tests
junit_logging = all
Note:
pytest.inidoes not pass--jira, even thoughpytest-jirais still a dependency inpyproject.tomlandjira.cfg.examplestill exists. Treat Jira as an opt-in extra you enable yourself.
tests/: Feature Suites
The tests/ directory contains the scenario definitions, grouped by feature. Each feature directory has its own conftest.py
when the feature needs extra fixtures. These are mostly thin wrappers around shared fixtures and helpers, which keeps each suite
readable while still allowing the repository to cover many migration variations.
tests/cold/ covers the core cold migration path, the comprehensive cold scenario, XFS compatibility, dual-NIC plan
validation, ca.crt secret handling, insecureSkipVerify handling, and OVA cold migration. Its conftest.py provides
alternative source_provider fixtures (ca_crt_source_provider, insecure_source_provider) plus matching inventory fixtures,
because those scenarios need a provider registered differently from the session default.
tests/warm/ covers sanity warm migration, the comprehensive warm scenario, warm XFS, and warm migration with a
ClusterRole-based destination provider and an SCC binding. Its conftest.py provides clusterrole_destination_ocp_provider
and forklift_scc_binding.
tests/copyoffload/ is by far the largest suite (~228 KB) and covers copy-offload/XCOPY behavior: thin and thick disks,
snapshots, multi-datastore and mixed-datastore scenarios, scale tests, RDM, populator and VM in-flight throttling, dedicated
migration hosts, naming edge cases, simultaneous plans, and mixed XCOPY/VDDK behavior. Its conftest.py validates the copy-offload
configuration and patches ForkliftController limits.
tests/shared_disk/ covers shared-disk migration for Linux and Windows guests, including relink verification and Windows
volume labeling.
tests/hooks/ covers post-hook expected-failure behavior and AAP/AWX-backed hook integration. Its conftest.py deploys AWX
via Helm, creates an API token and job templates, and wires MTV to use it.
tests/luks/ covers LUKS-encrypted disk migration, including a wrong-passphrase failure case. Its conftest.py provides
luks_vm_specs.
tests/deep_inspection/ covers plan-driven warm Deep Inspection and standalone Conversion CR Deep Inspection, including
cancel and rerun coverage.
tests/plan_lifecycle/ covers Plan archive and delete after a failed migration, followed by PVC/DataVolume cleanup
verification.
tests/upgrade/ covers migration across an MTV operator upgrade: maps and Plan are created pre-upgrade by fixtures, the
operator is upgraded, then the migration runs.
tests/tests_config/ contains only config.py, the shared Python-based scenario configuration loaded by pytest-testconfig.
A representative test class from tests/cold/test_mtv_cold_migration.py shows the standard pattern used across the suite:
@pytest.mark.vsphere
@pytest.mark.rhv
@pytest.mark.openstack
@pytest.mark.openshift
@pytest.mark.esxi
@pytest.mark.hyperv
@pytest.mark.tier0
@pytest.mark.incremental
@pytest.mark.parametrize(
"class_plan_config",
[
pytest.param(
py_config["tests_params"]["test_sanity_cold_mtv_migration"],
)
],
indirect=True,
ids=["rhel8"],
)
@pytest.mark.usefixtures("cleanup_migrated_vms")
class TestSanityColdMtvMigration:
"""Cold migration test - sanity check."""
storage_map: StorageMap
network_map: NetworkMap
plan_resource: Plan
def test_create_storagemap(
self,
prepared_plan,
fixture_store,
ocp_admin_client,
source_provider,
destination_provider,
source_provider_inventory,
target_namespace,
):
"""Create StorageMap resource for migration."""
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"
That five-step flow repeats throughout the repository:
- Create
StorageMap(test_create_storagemap) - Create
NetworkMap(test_create_networkmap) - Create
Plan(test_create_plan) - Execute migration (
test_migrate_vms) - Validate migrated VMs (
test_check_vms)
Feature suites insert or replace steps. The verified variants, all defined in AGENTS.md under "Key Patterns" and present in
tests/, are: the 4-step plan-readiness pattern, the 6-step plan-archive PVC cleanup pattern, the 6-step shared-disk (Linux)
pattern, the 7-step shared-disk (Windows) pattern, the 6/7/8-step copy-offload patterns, the 6-step LUKS pattern, and the 6-step
XFS pattern. AGENTS.md names the exact test method for each step, so the authoritative naming list lives there rather than
being duplicated here.
Scenario data lives in tests/tests_config/config.py. This file does much more than list VM names: it carries migration mode,
target power state, hook behavior, PVC naming templates, labels, affinity rules, copy-offload flags, timeouts, and other
per-scenario settings.
For example, the comprehensive cold scenario defines advanced feature coverage directly in configuration:
"test_cold_migration_comprehensive": {
"virtual_machines": [
{
"name": "mtv-win2019-3disks",
"source_vm_power": "on", # VM must be on for guest tools to report static IP info
"guest_agent": True,
},
],
"warm_migration": False,
"target_power_state": "on",
"preserve_static_ips": True,
"enable_nested_virtualization": False,
"pvc_name_template": '{{ .VmName | trunc 32 | trimSuffix "-" }}-{{ .VmName | trunc -4 }}-disk-{{.DiskIndex}}',
"pvc_name_template_use_generate_name": False,
"target_node_selector": {
"mtv-comprehensive-node": None, # None = auto-generate with session_uuid
},
"target_labels": {
"mtv-comprehensive-label": None, # None = auto-generate with session_uuid
"test-type": "comprehensive", # Static value
},
"target_affinity": {
"podAffinity": {
"preferredDuringSchedulingIgnoredDuringExecution": [
{
"podAffinityTerm": {
"labelSelector": {"matchLabels": {"app": "test"}},
"topologyKey": "kubernetes.io/hostname",
},
"weight": 50,
}
]
}
},
"vm_target_namespace": f"mtv-vms-cold-comprehensive-{uuid.uuid4().hex[:4]}",
"multus_namespace": "default", # Cross-namespace NAD access
"guest_agent_timeout": 600,
},
The complete plan-option vocabulary is documented in AGENTS.md under "Plan Configuration Options". It covers warm_migration,
preserve_static_ips, copyoffload, xfs_compatibility, migrate_shared_disks, inventory_timeout, clone_to_same_host,
pin_to_non_dedicated_host, disable_drs_for_vms, per_nic_network_map, skip_clone, and rdm_as_lun.
Tip: When you want to understand a scenario quickly, start with its entry in
tests/tests_config/config.py, then read the matching class intests/<feature>/, and only then follow the shared helpers it imports.
conftest.py: Shared Fixtures And Runtime Orchestration
conftest.py is the operational heart of the repository. It is where session-wide and class-wide fixtures build the real test
environment, and where the pytest hooks that gate the run live.
Key fixtures in conftest.py include:
-
autouse_fixtures(session, autouse) pulls insource_provider_data,nfs_storage_profile,base_resource_name,forklift_pods_state, andvirtctl_binaryfor every run. -
target_namespacecreates a unique OpenShift namespace for the run, labeled for restricted pod security. nfs_storage_profilepatches the NFSStorageProfileaccess modes and volume mode when the configured storage class is NFS.source_providersandsource_provider_dataload the providers JSON and resolve the entry named bysource_provider.-
source_providercreates the sourceSecretandProviderCR and yields the provider SDK wrapper;destination_providercreates the local OpenShift destinationProvider. -
source_provider_inventoryresolves the right Forklift inventory implementation. virtctl_binarydownloads and cachesvirtctlfor the current cluster.precopy_interval_forkliftcontrollerpatches theForkliftControllerprecopy interval fromsnapshots_interval.-
vcenter_clone_providerbuilds a second vCenter-backed provider for ESXi sources that declareclone_provider, because ESXi cannot runCloneVM_Taskitself. -
multus_network_namecreates the class-scoped MultusNetworkAttachmentDefinitionresources needed for secondary NICs. validated_source_vmsfails fast when configured source VMs or templates do not exist, before any cloning happens.-
prepared_planclones source VMs, adjusts power state, and creates hooks;prepared_plan_1andprepared_plan_2split it for simultaneous-migration scenarios. -
mtv_version_checkerskips tests whosemin_mtv_versionmarker is not satisfied. labeled_worker_nodeandtarget_vm_labelsimplement thetarget_node_selectorandtarget_labelsplan features.vm_ssh_connectionsmanages post-migration SSH sessions to migrated VMs.cleanup_migrated_vmsdeletes migrated VMs after each test class.
A few practical details are worth calling out:
-
prepared_plandeep-copies the plan config, so thetests/tests_config/config.pyentry is never mutated. It stores detailed source VM facts inplan["source_vms_data"], so the serializedvirtual_machineslist stays clean for MTV resource creation. -
Cloning uses a two-phase pattern: every VM is cloned first, then
wait_for_cloned_vms_in_forklift_inventory()waits for all of them. Cloning VM2+ while VM1 inventory sync is still pending is what used to break inventory sync. -
For vSphere the inventory wait also blocks until inventory NIC MACs converge with live vCenter MACs, so the destination never gets a stale NIC MAC.
-
add_nicreconfigures hardware after the clone was synced, sowait_for_added_nics_in_forklift_inventory()forces a provider refresh and blocks until each VM's inventory NIC count matches. Otherwise the added NIC is missing from theNetworkMapand Forklift drops it. -
Hook resources are created from plan configuration before the test methods start running.
virtctl_binaryis cached in a cluster-version-scoped shared directory under the system temp dir, guarded by a file lock, an ownership check, and a symlink check, which makes parallelpytest-xdistruns safer.
pytest_collection_modifyitems in conftest.py is where provider-type gating happens at collection time: warm tests are skipped
for OpenStack, OpenShift, OVA, and Hyper-V; copyoffload, shared_disk, deep_inspection, aap, and luks marked tests are
skipped for non-vSphere sources; and ca_crt tests are skipped for OpenShift and OVA sources.
Note: Reusable logic is intentionally centralized here, and the
check-autouse-fixturespre-commit hook enforces thatautouse_fixturesis the only fixture allowed to useautouse=True. If a test file looks surprisingly small, that is usually by design.
libs/: Provider Adapters And Inventory Clients
The libs/ directory is the platform abstraction layer.
libs/base_provider.py defines BaseProvider, the common interface the rest of the suite expects. Each provider implementation
can connect, test availability, return a normalized VM description via vm_dict(), clone VMs where needed, delete VMs, and
expose provider-specific network information through a shared contract.
The provider implementations are:
-
libs/providers/vmware.pyfor VMware vSphere; this is by far the largest adapter and includes cloning, guest info handling, datastore logic, snapshot handling, shared-disk relinking, copy-offload support, NIC addition, and ESXi-related behavior. -
libs/providers/rhv.pyfor RHV/oVirt; clones from templates rather than VMs. libs/providers/hyperv.pyfor Microsoft Hyper-V.libs/providers/openstack.pyfor OpenStack.-
libs/providers/openshift.pyfor OpenShift Virtualization/KubeVirt; this adapter is especially important on the destination side because it inspects migratedVirtualMachineresources. -
libs/providers/ova.pyfor OVA-based scenarios.
libs/forklift_inventory.py is the second half of the abstraction. Instead of talking to source providers directly, some mapping
logic needs the Forklift inventory service. This file wraps the forklift-inventory route and provides the abstract
ForkliftInventory base plus provider-specific subclasses: OvirtForkliftInventory, OpenstackForliftinventory,
VsphereForkliftInventory, OvaForkliftInventory, OpenshiftForkliftInventory, and HypervForkliftInventory.
The fixture and the factory behind it select the right inventory client at runtime:
@pytest.fixture(scope="session")
def source_provider_inventory(
ocp_admin_client: DynamicClient, mtv_namespace: str, source_provider: BaseProvider
) -> ForkliftInventory:
return create_forklift_inventory(client=ocp_admin_client, mtv_namespace=mtv_namespace, provider=source_provider)
if not PROVIDER_INVENTORY_MAP:
_register_inventory_classes()
if provider.ocp_resource is None:
raise ValueError(f"{provider.type} provider ocp_resource is not set")
provider_class = PROVIDER_INVENTORY_MAP.get(provider.type)
if provider_class is None:
raise ValueError(f"Provider {provider.type} not implemented")
return provider_class( # type: ignore[call-arg] # Subclasses use 'namespace' param while base class uses 'mtv_namespace'
client=client,
namespace=mtv_namespace,
provider_name=provider.ocp_resource.name,
)
This split is important when reading the codebase:
libs/providers/*talks to the source or destination platform itself.libs/forklift_inventory.pytalks to the MTV/Forklift inventory API that MTV uses for discovery and mapping.
utilities/: Shared Orchestration, Validation, And Diagnostics
The utilities/ directory is where most of the suite's real work happens. If tests/ describes what to migrate, utilities/
contains most of the code for how to set up, execute, verify, and clean up the migration.
The major modules are:
-
utilities/mtv_migration.pybuildsStorageMap,NetworkMap,Plan, andMigrationresources, waits for migration completion, and resolves dual/simultaneous migration completion. -
utilities/post_migration.pyperforms post-migration validation for CPU and CPU features, VBS status, memory, storage, networking, guest agent state, SSH, snapshots, static IP preservation, node placement, labels, affinity, PVC names, data integrity, and LUKS encryption. -
utilities/resources.pycentralizes resource creation (create_and_store_resource,unregister_teardown_resource,get_or_create_namespace) and teardown tracking. -
utilities/utils.pyloads provider configuration, creates provider secrets andProviderCRs, fetches CA certificates, generates network map lists, populates inventory VM IDs, and contains general cluster helpers. -
utilities/provider_inventory.pyowns Forklift inventory synchronization: forced provider refresh, added-NIC convergence, MAC convergence, cloned-VM waits, and pre-clone source VM validation. -
utilities/hooks.pycreates MTV hook resources and validates expected hook-related failure behavior. -
utilities/migration_utils.pyhandles cutover timing, plan archiving, migration cancelation, and cleanup checks for DVs, PVCs, and PVs. -
utilities/ssh_utils.pyprovides post-migration SSH access to VMs throughvirtctlport-forward plus thepython-rrmngmntlibrary. utilities/virtctl.pydownloads the correctvirtctlbinary from the cluster for the current OS and architecture.-
utilities/vmware_guest_operations.pyruns guest commands over the VMware Guest Operations API, used to detect guest NIC names and IP origins. -
utilities/worker_node_selection.pypicks worker nodes for placement-sensitive tests, using Prometheus metrics when available. -
utilities/shared_disk.pymounts, labels, writes, and reads shared disks on both migrated guests for Linux and Windows. -
utilities/deep_inspection.pymanages the Deep InspectionConversionCR lifecycle: connection secrets, creation, phase waiting, cancel, snapshot handling, result verification, and concern capture. -
utilities/aap.pysupports Ansible Automation Platform hook integration. utilities/copyoffload_migration.pyholds copy-offload orchestration and XCOPY verification.utilities/copyoffload_constants.pyholds the supported storage vendors and the Forklift in-flight limits.utilities/copyoffload_datastore.pyresolves per-disk datastore IDs and formats the related error messages.utilities/copyoffload_plan_secret.pypolls for the Forklift-created plan secret.utilities/forklift_controller_populator.pypatches ForkliftController populator in-flight limits.utilities/esxi.pycontains ESXi SSH credential handling for SSH-based ESXi cloning.utilities/upgrade.pyruns the MTV operator upgrade script for the upgrade suite.utilities/must_gather.pycollects diagnostics withoc adm must-gather.utilities/pytest_utils.pyhandles dry-run detection, resource collection, session teardown, and optional AI analysis wiring.utilities/naming.pygenerates short unique resource names and sanitizes VM names for Kubernetes.utilities/logger.pyconfigures queue-based logging so parallel workers can write to a single stream cleanly.utilities/constants.pyholdsMTV_OPERATOR_NAME.
The shared resource creation helper is one of the simplest and most important building blocks in the repository:
def create_and_store_resource(
client: "DynamicClient",
fixture_store: dict[str, Any],
resource: type[Resource],
test_name: str | None = None,
**kwargs: Any,
) -> Any:
kwargs["client"] = client
_resource_name = kwargs.get("name")
_resource_dict = kwargs.get("kind_dict", {})
_resource_yaml = kwargs.get("yaml_file")
if _resource_yaml and _resource_dict:
raise ValueError("Cannot specify both yaml_file and kind_dict")
if not _resource_name:
if _resource_yaml:
with open(_resource_yaml) as fd:
_resource_dict = yaml.safe_load(fd)
_resource_name = _resource_dict.get("metadata", {}).get("name")
if not _resource_name:
_resource_name = generate_name_with_uuid(name=fixture_store["base_resource_name"])
if resource.kind in (Migration.kind, Plan.kind):
_resource_name = f"{_resource_name}-{'warm' if kwargs.get('warm_migration') else 'cold'}"
if len(_resource_name) > 63:
LOGGER.warning(f"'{_resource_name=}' is too long ({len(_resource_name)} > 63). Truncating.")
_resource_name = _resource_name[-63:]
kwargs["name"] = _resource_name
_resource = resource(**kwargs)
try:
_resource.deploy(wait=True)
except ConflictError:
LOGGER.warning(f"{_resource.kind} {_resource_name} already exists, reusing it.")
_resource.wait()
LOGGER.info(f"Storing {_resource.kind} {_resource.name} in fixture store")
_resource_dict = {"name": _resource.name, "namespace": _resource.namespace, "module": _resource.__module__}
if test_name:
_resource_dict["test_name"] = test_name
fixture_store["teardown"].setdefault(_resource.kind, []).append(_resource_dict)
return _resource
That helper is used by higher-level orchestration code in utilities/mtv_migration.py. One good example is storage mapping,
where the code branches between standard inventory-based mapping and copy-offload-specific mapping driven by datastore IDs and
offloadPlugin entries. The same module also validates that the configured pvc_name_template is compatible with the source
provider before the Plan is built.
A few utility modules are especially helpful to know by name:
-
utilities/post_migration.pyis where the deep VM checks happen. If a migrated VM has the wrong CPU, memory, disks, networks, PVC names, serial number, labels, or affinity, the logic is usually here. -
utilities/ssh_utils.pyreaches migrated VMs throughvirtctlport-forward plus thepython-rrmngmntlibrary, so validation does not depend on cluster nodes exposing guest SSH directly. -
utilities/hooks.pysupports both predefined success/failure hook playbooks and custom base64-encoded playbooks. utilities/must_gather.pyis what the suite uses when it needs richer failure diagnostics.-
utilities/worker_node_selection.pyexists because some scenarios validate placement-sensitive features rather than only migration success. -
utilities/provider_inventory.pyis the place to look when a migration fails because Forklift had not caught up with the source VM yet.
Tip: If you are tracing a failure after the MTV
Planis created,utilities/mtv_migration.pyandutilities/post_migration.pyare usually the next files to read.
exceptions/: Centralized Domain Errors
Custom exceptions are centralized in exceptions/exceptions.py. This keeps migration-specific failures easy to recognize and
avoids scattering project-specific error types across multiple modules. The check-exceptions-location pre-commit hook enforces
the placement.
Representative exceptions include:
MigrationPlanExecErrorfor plan execution failure or timeoutMigrationNotFoundErrorandMigrationStatusErrorfor missing or incomplete migration CR stateVmPipelineErrorandVmMigrationStepMismatchErrorfor hook or pipeline analysis problemsMissingProvidersFileErrorandProviderEmptyContentErrorfor missing or empty providers JSON-
InvalidVMNameError,VmCloneError,VmBadDatastoreError,VmMissingVmxError, andVmNotFoundErrorfor provider-side and VM-side failures -
SessionTeardownErrorfor cleanup problems after the run -
ForkliftPodsNotRunningError,MtvOperatorNotInstalledError, andRemoteClusterAndLocalCluterNamesErrorfor cluster and operator readiness -
MtvUpgradeError,GuestCommandError,SSHConnectionSetupError,PowerShellCommandError, andConversionErrorfor the upgrade, guest-operations, SSH, and Deep Inspection paths
This file is small, but it matters because the rest of the repository uses these names to make failures easier to understand during investigation.
cli/, scripts/, And tools/
cli/mtv_api_tests/ is a Typer application exposed as the mtv-api-tests console script (cli.mtv_api_tests:app in
pyproject.toml). It has two subcommands:
-
mtv-api-tests generatewalks through an interactive wizard that writes.providers.jsonandmtv-api-tests-manifests.yaml, with a--categoryoption drawn fromTEST_CATEGORIES(all,copyoffload,tier0,tier1,warm,remote). -
mtv-api-tests runruns the suite either locally (uv run pytest) or as an OpenShift Job (oc apply), with options for category, source and destination provider keys, storage class, a-ktest filter, and a Job YAML path.
scripts/hooks/ holds local AST-based pre-commit hooks that enforce selected AGENTS.md rules. They run from the repo: local
block of .pre-commit-config.yaml:
check-no-kubernetes-runtime: nokubernetes.*imports at runtime outside the allowed exceptions import.check-no-dynamicclient-construct: noDynamicClient()construction; useget_cluster_client().check-no-except-exception: noexcept Exceptionoutsidepytest_*hooks inconftest.py.check-no-module-load-source-providers: no module-levelload_source_providersundertests/.check-exceptions-location:Exceptionsubclasses live inexceptions/exceptions.py.check-test-file-location: notest_*.pydirectly undertests/.check-autouse-fixtures: onlyautouse_fixturesmay useautouse=True.check-no-runtimeerror: noraise RuntimeErroroutsidepytest_*hooks inconftest.py.
Legacy violations are grandfathered through a baseline ratchet: scripts/hooks/baselines/<hook_id>.txt holds
path:sha256-prefix fingerprints, currently for check_exceptions_location, check_no_except_exception,
check_no_kubernetes_runtime, and check_no_runtimeerror. scripts/hooks/README.md documents the matching rules, including
that each row grants exactly one suppression.
tools/ holds operational helpers:
-
tools/clean_cluster.pyreads a recorded resource list (theresources.jsonwritten by the data collector) and calls.clean_up()on the matching objects. This is useful when a test run was interrupted and left resources behind. -
tools/bm-dns-setup.shconfigures DNS for bare-metal environments. tools/update-branches.shupdates local branches.
guides/copyoffload/how-to-run-copyoffload-tests.md is the long-form user guide for setting up and running copy-offload
scenarios. It lives in guides/, not in docs/.
A few other support files are worth knowing about:
-
AGENTS.mdis the authoritative rules document for this repository: commands, code standards, architecture patterns, test structure, markers, and fixture rules. -
test_plan_schema.mddefines the required class docstring (a manual product test plan) that every new test class must carry. JOB_INSIGHT_PROMPT.mdcontains instructions for automated job/failure analysis tooling.OWNERSlists repository approvers and reviewers.-
.rootcoz/settings.jsonconfigures the rootcoz analysis server, including the additional repositories it reads for this project: theforkliftproduct repo, themtv-autodeploydeployment repo, and the MTV Jenkins job repo. -
junit_report_example.xmlshows the JUnit-style output shape emitted bypytest.
Tip:
tools/clean_cluster.pypairs naturally with the resource tracking written byutilities/pytest_utils.py, which stores created resources in a JSON file when data collection is enabled.
Configuration, Tooling, And Automation Files
The repository root also contains the files that make the suite installable, configurable, lintable, and reviewable.
The most important ones are:
-
pyproject.tomldefines the Python project metadata and dependencies. It requires Python>=3.12, <3.14and includespytest,pytest-xdist,pytest-testconfig,pytest-harvest, provider SDKs (pyvmomi,ovirt-python-sdk,openstacksdk),openshift-python-wrapper,openshift-python-utilities,typer,python-rrmngmnt,pyhelper-utils, andpy-go-template. -
uv.locklocks the exact dependency set used by the repository. pytest.iniconfigures test discovery, runtime options, markers, JUnit output, andpytest-testconfig.tests/tests_config/config.pyis the suite's shared Python-based scenario configuration file.-
providers_schema.jsonis the JSON Schema that.providers.jsonis validated against;.providers.json.exampleis the commented field reference. -
.providers.jsonis the real providers file. It is gitignored, so every developer supplies their own. jira.cfg.exampleshows the minimal format expected by thepytest-jiraintegration.-
tox.tomldefines lightweight local automation:pytest --setup-plan,pytest --collect-only, and unused-code scanning viapyutils-unusedcode. -
.pre-commit-config.yamlconfigures local quality gates includingflake8,ruff,ruff-format,mypy,detect-secrets,gitleaks,markdownlint-cli2, and the eightscripts/hooks/AGENTS-rules hooks. -
Dockerfileis a two-stage UBI 10 build: a builder stage that copiescli,docs,utilities,tests,libs, andexceptionsand runsuv sync --locked, and a minimal runtime stage that defaults touv run pytest --collect-only. It can optionally install pinnedopenshift-python-wrapper/openshift-python-utilitiescommits via build args. -
.release-it.jsonhandles version bumping, tagging, pushing, and GitHub release creation. renovate.jsonhandles automated dependency update PRs..coderabbit.yamland.pr_agent.tomlconfigure automated review behavior.-
.flake8and.markdownlint.yamlhold narrower lint settings for Python and Markdown. The Markdown config sets a 180-character line limit and allows onlydetails,summary, andstrongas inline HTML. -
MTV-VERSIONrecords the pinned MTV version (2.12). No script in the repository currently reads it.
The provider configuration example is especially important because most real test runs depend on it:
"vsphere-copy-offload": {
"type": "vsphere",
"version": "<SERVER VERSION>",
"fqdn": "SERVER FQDN/IP",
"api_url": "<SERVER FQDN/IP>/sdk",
"username": "USERNAME",
"password": "PASSWORD", # pragma: allowlist secret
"guest_vm_linux_user": "LINUX VMS USERNAME",
"guest_vm_linux_password": "LINUX VMS PASSWORD", # pragma: allowlist secret
"guest_vm_win_user": "WINDOWS VMS USERNAME",
"guest_vm_win_password": "WINDOWS VMS PASSWORD", # pragma: allowlist secret
"copyoffload": {
# Supported storage_vendor_product values:
# - "ontap" (NetApp ONTAP)
# - "vantara" (Hitachi Vantara)
# - "primera3par" (HPE Primera/3PAR)
# - "pureFlashArray" (Pure Storage FlashArray)
# - "powerflex" (Dell PowerFlex)
# - "powermax" (Dell PowerMax)
# - "powerstore" (Dell PowerStore)
# - "infinibox" (Infinidat InfiniBox)
# - "flashsystem" (IBM FlashSystem)
"storage_vendor_product": "ontap",
The same example also shows the ESXi profile, which is the shape most often missed:
"vsphere-esxi": {
"type": "vsphere",
"version": "<SERVER VERSION>",
"fqdn": "ESXI HOST FQDN/IP",
"api_url": "<ESXI HOST FQDN/IP>/sdk",
"username": "USERNAME",
"password": "PASSWORD", # pragma: allowlist secret
"endpoint_type": "esxi",
"vddk_init_image": "<PATH TO VDDK INIT IMAGE>",
"clone_provider": "vsphere" # Name of vCenter provider to use for VM cloning operations
},
endpoint_typeselects"vcenter"or"esxi". It maps to the MTVProvidersdkEndpointfield.-
clone_providernames another entry in the same file (a vCenter) that performsCloneVM_Task, because a bare ESXi host cannot. Thevcenter_clone_providerfixture inconftest.pybuilds it. -
luks_passphraseat provider level is the fallback for the per-VMluks_passphraseoverride used bytests/luks/.
Warning:
.providers.json.examplecontains placeholder values and inline comments such as# pragma: allowlist secret. Those comments are useful inside this repository, but they are not valid JSON, so the file does not parse with a standard JSON reader. Remove them when creating your real.providers.json.Warning: This repository is code-complete, but many scenarios only make sense with live OpenShift, MTV, and source-provider environments. A local checkout without cluster access and provider credentials will let you read the structure, but not exercise the full migration stack.
Note: In this repository snapshot, there is no checked-in
.github/workflowsdirectory. CI-adjacent behavior is expressed mostly through local tooling and repository-bot configuration files such astox.toml,.pre-commit-config.yaml,Dockerfile,.release-it.json,renovate.json,.coderabbit.yaml,.pr_agent.toml, and.rootcoz/settings.json.Tip: For the fastest mental model of the repository, read
pytest.ini, then the matching scenario intests/tests_config/config.py, then the test module intests/<feature>/, thenconftest.py, and finally the helper modules imported fromutilities/andlibs/.