Repository: red-hat-storage/ocs-ci
Pull request #11199: CNV vm clone with PVC expansion ops
Author: login: avd-sagare
Labels: name: Verified
description: Mark when PR was verified and log provided
name: team/e2e
description: E2E team related issues/PRs
name: size/L
description: PR that changes 100-499 lines
name: lgtm
Created: 2025-01-22T13:11:36Z | Merged: 2025-05-30T10:28:39Z

This PR will validate PVC expansion operation before cloning the VM and After cloning the VM.

- [x] PVC expansion before cloning VM

- [x] PVC expansion after cloning VM 


Diff:
diff --git a/ocs_ci/helpers/cnv_helpers.py b/ocs_ci/helpers/cnv_helpers.py
index 6a069d92f0c..5cde41d0b02 100644
--- a/ocs_ci/helpers/cnv_helpers.py
+++ b/ocs_ci/helpers/cnv_helpers.py
@@ -19,6 +19,7 @@
 from ocs_ci.framework import config
 from ocs_ci.utility.retry import retry
 
+
 logger = logging.getLogger(__name__)
 
 
diff --git a/tests/conftest.py b/tests/conftest.py
index 5dc1aaffd37..90e73abdb32 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -212,6 +212,8 @@
 from ocs_ci.ocs.resources.storage_cluster import set_in_transit_encryption
 from ocs_ci.helpers.e2e_helpers import verify_osd_used_capacity_greater_than_expected
 from ocs_ci.helpers.cnv_helpers import run_fio
+from ocs_ci.helpers.performance_lib import run_oc_command
+
 
 log = logging.getLogger(__name__)
 
@@ -9780,3 +9782,148 @@ def finalizer():
                 )
 
         request.addfinalizer(finalizer)
+
+
+@pytest.fixture
+def vm_clone_fixture(request):
+    """
+    Fixture to clone a VM and ensure cleanup after test.
+    Returns a factory method to perform the clone operation.
+    """
+    cloned_vms = []
+
+    def factory(vm, admin_client):
+        """
+        Clone the given VM using VirtualMachineClone.
+
+        Args:
+            vm: The source VM object to be cloned.
+            admin_client: The admin client instance to perform the operation.
+
+        Returns:
+            vm_obj: The cloned VM object.
+
+        """
+        # The reason to importing here is to avoid issue with importing not fully initialized modules
+        from ocs_ci.ocs.cnv import virtual_machine
+        from ocp_resources.virtual_machine_clone import VirtualMachineClone
+
+        target_name = f"clone-{vm.name}"
+        with VirtualMachineClone(
+            name="clone-vm-test",
+            namespace=vm.namespace,
+            source_name=vm.name,
+            target_name=target_name,
+            client=admin_client,
+        ) as vmc:
+            vmc.wait_for_status(status=VirtualMachineClone.Status.SUCCEEDED)
+
+        cloned_vm = virtual_machine.VirtualMachine(
+            vm_name=target_name, namespace=vm.namespace
+        )
+        cloned_vm.start(wait=True)
+        cloned_vms.append(cloned_vm)
+        return cloned_vm
+
+    def teardown():
+        """
+        Cleans up cloned VM workloads.
+        """
+        for cloned_vm in cloned_vms:
+            volumes = (
+                cloned_vm.get()
+                .get("spec", {})
+                .get("template", {})
+                .get("spec", {})
+                .get("volumes", [])
+            )
+            if not volumes:
+                raise ValueError(f"No volumes found in cloned VM {cloned_vm.name}")
+            volume = volumes[0]
+            if "dataVolume" in volume:
+                cloned_vm.pvc_name = volume["dataVolume"]["name"]
+            elif "persistentVolumeClaim" in volume:
+                cloned_vm.pvc_name = volume["persistentVolumeClaim"]["claimName"]
+            else:
+                raise ValueError(
+                    f"Neither dataVolume nor persistentVolumeClaim found in cloned VM {cloned_vm.name}"
+                )
+            log.info(f"Deleting VM {cloned_vm.name}")
+            try:
+                cloned_vm.delete()
+                log.info(f"Cloned VM {cloned_vm.name} deleted")
+            except CommandFailed as e:
+                if "NotFound" in str(e):
+                    log.warning(f"Cloned VM {cloned_vm.name} was already deleted.")
+                else:
+                    raise
+
+            run_oc_command(
+                cmd=f"delete pvc {cloned_vm.pvc_name}", namespace=cloned_vm.namespace
+            )
+            log.info(f"Cloned VM PVC {cloned_vm.pvc_name} deleted")
+
+    request.addfinalizer(teardown)
+    return factory
+
+
+@pytest.fixture()
+def vm_snapshot_restore_fixture(request):
+    """
+    Fixture factory for snapshot & restore of a VM.
+    Returns a factory method to perform snapshot and restore operations.
+    """
+    snapshots = []
+
+    def factory(vm, admin_client):
+        """
+        Take snapshot and restore the VM.
+
+        Args:
+            vm: The VM object to snapshot and restore.
+            admin_client: The admin client instance.
+
+        Returns:
+            vm_obj: The restored VM object.
+
+        """
+        from ocp_resources.virtual_machine_snapshot import VirtualMachineSnapshot
+        from ocp_resources.virtual_machine_restore import VirtualMachineRestore
+        from ocs_ci.utility.utils import create_unique_resource_name
+
+        snapshot_name = f"snapshot-{vm.name}"
+        vm_snapshot = VirtualMachineSnapshot(
+            name=snapshot_name,
+            namespace=vm.namespace,
+            vm_name=vm.name,
+            client=admin_client,
+            teardown=False,
+        )
+        vm_snapshot.create()
+        vm_snapshot.wait_snapshot_done()
+        snapshots.append(vm_snapshot)
+        vm.stop()
+        restore_snapshot_name = create_unique_resource_name(snapshot_name, "restore")
+        with VirtualMachineRestore(
+            name=restore_snapshot_name,
+            namespace=vm.namespace,
+            vm_name=vm.name,
+            snapshot_name=snapshot_name,
+            client=admin_client,
+            teardown=False,
+        ) as vm_restore:
+            vm_restore.wait_restore_done()
+
+        vm.start()
+        vm.wait_for_ssh_connectivity(timeout=1200)
+        return vm
+
+    def teardown():
+        """
+        Cleans up VM snapshots.
+        """
+        for snap in snapshots:
+            snap.delete(wait=True)
+
+    request.addfinalizer(teardown)
+    return factory
diff --git a/tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py b/tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py
index 6c559ba1425..64c65dfadb6 100644
--- a/tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py
+++ b/tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py
@@ -3,10 +3,13 @@
 
 from ocs_ci.framework.pytest_customization.marks import magenta_squad, workloads
 from ocs_ci.framework.testlib import E2ETest
-from ocs_ci.helpers.cnv_helpers import cal_md5sum_vm, run_dd_io, expand_pvc_and_verify
-from ocs_ci.helpers.helpers import create_unique_resource_name
-from ocp_resources.virtual_machine_restore import VirtualMachineRestore
-from ocp_resources.virtual_machine_snapshot import VirtualMachineSnapshot
+
+from ocs_ci.helpers.cnv_helpers import (
+    cal_md5sum_vm,
+    run_dd_io,
+    expand_pvc_and_verify,
+)
+
 
 log = logging.getLogger(__name__)
 
@@ -18,9 +21,51 @@ class TestVmSnapshotClone(E2ETest):
     """
 
     @workloads
-    @pytest.mark.polarion_id("OCS-6288")
-    def test_vm_clone(
-        self, project_factory, multi_cnv_workload, clone_vm_workload, setup_cnv
+    @pytest.mark.parametrize(
+        argnames=["pvc_expand_before_clone", "pvc_expand_after_clone"],
+        argvalues=[
+            pytest.param(
+                False,
+                False,
+                marks=pytest.mark.polarion_id(
+                    "OCS-6288"
+                ),  # Polarion ID for no PVC expansion
+            ),
+            pytest.param(
+                True,
+                False,
+                marks=[
+                    pytest.mark.polarion_id(
+                        "OCS-6326"
+                    ),  # Polarion ID for expansion before clone
+                    pytest.mark.jira(
+                        "CNV-55558", run=False
+                    ),  # Skip if JIRA issue is open
+                ],
+            ),
+            pytest.param(
+                False,
+                True,
+                marks=[
+                    pytest.mark.polarion_id(
+                        "OCS-6326"
+                    ),  # Polarion ID for expansion after clone
+                    pytest.mark.jira(
+                        "CNV-55558", run=False
+                    ),  # Skip if JIRA issue is open
+                ],
+            ),
+        ],
+    )
+    def test_vm_clone_with_expansion(
+        self,
+        setup_cnv,
+        project_factory,
+        pvc_expand_before_clone,
+        pvc_expand_after_clone,
+        multi_cnv_workload,
+        admin_client,
+        vm_clone_fixture,
     ):
         """
         This test performs the VM cloning and IOs created using different
@@ -29,12 +74,14 @@ def test_vm_clone(
         Test steps:
         1. Create a clone of a VM PVC by following the documented procedure
         from ODF official docs.
-            1.1 Create clone of the pvc associated with VM.
-            1.2 Cloned pvc successfully created and listed
+            1.1 Expand PVC if `pvc_expand_before_clone` is True.
+            1.2 Verify the availability of expanded portion for IOs.
         2. Verify the cloned PVC is created.
         3. Create a VM using cloned PVC.
         4. Verify that the data on VM backed by cloned PVC is the
         same as that in the original VM.
+            4.1 Expand PVC if `pvc_expand_after_restore` is True
+            4.2 Verify the availability of expanded portion for IOs
         5. Add additional data to the cloned VM.
         6. Delete the clone by following the documented procedure from
         ODF official docs
@@ -51,30 +98,51 @@ def test_vm_clone(
         )
         vm_list = vm_objs_def + vm_objs_aggr
         log.info(f"Total VMs to process: {len(vm_list)}")
-        for index, vm_obj in enumerate(vm_list):
+        failed_vms = []
+        for vm_obj in vm_list:
+            # Expand PVC if `pvc_expand_before_clone` is True
+            if pvc_expand_before_clone:
+                new_size = 50
+                try:
+                    expand_pvc_and_verify(vm_obj, new_size)
+                except ValueError as e:
+                    log.error(
+                        f"Error for VM {vm_obj}: {e}. Continuing with the next VM."
+                    )
+                    failed_vms.append(vm_obj.name)
+                    continue
             log.info(
                 f"Starting I/O operation on VM {vm_obj.name} using "
                 f"{file_paths[0]}..."
             )
             source_csum = run_dd_io(vm_obj=vm_obj, file_path=file_paths[0], verify=True)
             log.info(f"Source checksum for {vm_obj.name}: {source_csum}")
-            log.info(f"Stopping VM {vm_obj.name}...")
-            vm_obj.stop()
+
             log.info(f"Cloning VM {vm_obj.name}...")
-            clone_obj = clone_vm_workload(
-                vm_obj=vm_obj,
-                volume_interface=vm_obj.volume_interface,
-                namespace=vm_obj.namespace,
-            )
-            log.info(
-                f"Clone created successfully for VM {vm_obj.name}: " f"{clone_obj.name}"
-            )
-            new_csum = cal_md5sum_vm(vm_obj=clone_obj, file_path=file_paths[0])
+            cloned_vm = vm_clone_fixture(vm_obj, admin_client)
+
+            new_csum = cal_md5sum_vm(vm_obj=cloned_vm, file_path=file_paths[0])
             assert source_csum == new_csum, (
                 f"Failed: MD5 comparison between source {vm_obj.name} "
-                f"and cloned {clone_obj.name} VMs"
+                f"and cloned {cloned_vm.name} VMs"
             )
-            run_dd_io(vm_obj=clone_obj, file_path=file_paths[1])
+
+            # Expand PVC if `pvc_expand_after_restore` is True
+            if pvc_expand_after_clone:
+                new_size = 50
+                try:
+                    # Update self.pvc_name from vm yaml same as 11071 PR
+                    expand_pvc_and_verify(vm_obj, new_size)
+                except ValueError as e:
+                    log.error(
+                        f"Error for VM {cloned_vm}: {e}. Continuing with the next VM."
+                    )
+                    failed_vms.append(cloned_vm.name)
+                    continue
+            run_dd_io(vm_obj=cloned_vm, file_path=file_paths[1])
+            log.info(f"Data written to {file_paths[1]} on cloned VM {cloned_vm.name}")
+        if failed_vms:
+            assert False, f"Test case failed for VMs: {', '.join(failed_vms)}"
 
     @workloads
     @pytest.mark.parametrize(
@@ -117,6 +185,7 @@ def test_vm_snapshot_ops(
         pvc_expand_after_restore,
         multi_cnv_workload,
         admin_client,
+        vm_snapshot_restore_fixture,
     ):
         """
         This test performs the VM PVC snapshot operations
@@ -159,41 +228,7 @@ def test_vm_snapshot_ops(
             # Writing IO on source VM
             source_csum = run_dd_io(vm_obj=vm_obj, file_path=file_paths[0], verify=True)
 
-            snapshot_name = f"snapshot-{vm_obj.name}"
-            # Explicitly create the VirtualMachineSnapshot instance
-            with VirtualMachineSnapshot(
-                name=snapshot_name,
-                namespace=vm_obj.namespace,
-                vm_name=vm_obj.name,
-                client=admin_client,
-                teardown=False,
-            ) as vm_snapshot:
-                vm_snapshot.wait_snapshot_done()
-
-            # Write file after snapshot
-            run_dd_io(vm_obj=vm_obj, file_path=file_paths[1])
-
-            # Stopping VM before restoring
-            vm_obj.stop()
-
-            # Explicitly create the VirtualMachineRestore instance
-            restore_snapshot_name = create_unique_resource_name(
-                vm_snapshot.name, "restore"
-            )
-            try:
-                with VirtualMachineRestore(
-                    name=restore_snapshot_name,
-                    namespace=vm_obj.namespace,
-                    vm_name=vm_obj.name,
-                    snapshot_name=vm_snapshot.name,
-                    client=admin_client,
-                    teardown=False,
-                ) as vm_restore:
-                    vm_restore.wait_restore_done()  # Wait for restore completion
-                    vm_obj.start()
-                    vm_obj.wait_for_ssh_connectivity(timeout=1200)
-            finally:
-                vm_snapshot.delete()
+            restored_vm = vm_snapshot_restore_fixture(vm_obj, admin_client)
 
             # Verify file written after snapshot is not present.
             command = f"test -f {file_paths[1]} && echo 'File exists' || echo 'File not found'"
@@ -248,7 +283,7 @@ def test_vm_snapshot_ops(
             res_csum = cal_md5sum_vm(vm_obj=vm_obj, file_path=file_paths[0])
             assert (
                 source_csum == res_csum
-            ), f"Failed: MD5 comparison between source {vm_obj.name} and restored {vm_restore.name} VMs"
+            ), f"Failed: MD5 comparison between source {vm_obj.name} and restored {restored_vm.name} VMs"
 
             # Write new file to VM
             run_dd_io(vm_obj=vm_obj, file_path=file_paths[1], verify=True)


Reviews:
user: login: hnallurv
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-29T07:33:57Z
user: login: PrasadDesala
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-29T07:47:12Z
user: login: PrasadDesala
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-01-29T07:48:25Z
user: login: ocs-ci
body: PR validation on existing cluster 


Cluster Name:           asagare-cnv28
Cluster Configuration:  
PR Test Suite:          
PR Test Path:           tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py::TestVmSnapshotClone::test_vm_clone_with_expansion
Additional Test Params: 
OCP VERSION:            4.18
OCS VERSION:            4.18
tested against branch:  master


 [Job PASSED](https://url.corp.redhat.com/3952329).
state: COMMENTED
author_association: NONE
submitted_at: 2025-01-31T13:54:05Z
user: login: avd-sagare
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-20T14:00:21Z
user: login: avd-sagare
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T06:10:24Z
user: login: avd-sagare
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T06:11:56Z
user: login: avd-sagare
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T06:12:11Z
user: login: avd-sagare
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T06:12:59Z
user: login: avd-sagare
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T06:14:20Z
user: login: avd-sagare
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T06:14:27Z
user: login: PrasadDesala
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T06:26:24Z
user: login: avd-sagare
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T06:57:17Z
user: login: PrasadDesala
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T07:16:55Z
user: login: avd-sagare
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-25T07:42:30Z
user: login: ocs-ci
body: PR validation on existing cluster 


Cluster Name:           asagare-virt12
Cluster Configuration:  
PR Test Suite:          
PR Test Path:           tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py::TestVmSnapshotClone::test_vm_clone_with_expansion
Additional Test Params: 
OCP VERSION:            4.19
OCS VERSION:            4.19
tested against branch:  master


 [Job UNSTABLE](https://url.corp.redhat.com/c9eb1c9) (some or all tests failed).
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-13T12:45:07Z
user: login: ocs-ci
body: PR validation on existing cluster 


Cluster Name:           asagare-virt12
Cluster Configuration:  
PR Test Suite:          
PR Test Path:           tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py::TestVmSnapshotClone::test_vm_clone_with_expansion
Additional Test Params: 
OCP VERSION:            4.19
OCS VERSION:            4.19
tested against branch:  master


 [Job UNSTABLE](https://url.corp.redhat.com/7ef8a78) (some or all tests failed).
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-13T15:01:22Z
user: login: ocs-ci
body: PR validation on existing cluster 


Cluster Name:           asagare-virt12
Cluster Configuration:  
PR Test Suite:          
PR Test Path:           tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py::TestVmSnapshotClone::test_vm_clone_with_expansion
Additional Test Params: 
OCP VERSION:            4.19
OCS VERSION:            4.19
tested against branch:  master


 [Job PASSED](https://url.corp.redhat.com/b51de82).
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-15T16:04:31Z
user: login: PrasadDesala
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T12:50:30Z
user: login: avd-sagare
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:14:04Z
user: login: avd-sagare
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:17:25Z
user: login: avd-sagare
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-21T14:18:15Z
user: login: avd-sagare
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T05:36:00Z
user: login: avd-sagare
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T05:36:11Z
user: login: avd-sagare
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-22T05:36:59Z
user: login: PrasadDesala
state: DISMISSED
author_association: CONTRIBUTOR
submitted_at: 2025-05-28T11:24:34Z
user: login: ocs-ci
body: PR validation on existing cluster 


Cluster Name:           asagare-virtodf
Cluster Configuration:  
PR Test Suite:          
PR Test Path:           tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py::TestVmSnapshotClone::test_vm_clone_with_expansion
Additional Test Params: 
OCP VERSION:            4.19
OCS VERSION:            4.19
tested against branch:  master


 [Job UNSTABLE](https://url.corp.redhat.com/56721e3) (some or all tests failed).
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-28T12:40:56Z
user: login: hnallurv
state: DISMISSED
author_association: NONE
submitted_at: 2025-05-28T14:17:04Z
user: login: ocs-ci
body: PR validation on existing cluster 


Cluster Name:           asagare-virtodf
Cluster Configuration:  
PR Test Suite:          
PR Test Path:           tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py::TestVmSnapshotClone::test_vm_clone_with_expansion
Additional Test Params: 
OCP VERSION:            4.19
OCS VERSION:            4.19
tested against branch:  master


 [Job UNSTABLE](https://url.corp.redhat.com/aac0b7f) (some or all tests failed).
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-29T07:49:58Z
user: login: ocs-ci
body: PR validation on existing cluster 


Cluster Name:           asagare-ibmvirt2
Cluster Configuration:  
PR Test Suite:          
PR Test Path:           tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py::TestVmSnapshotClone::test_vm_clone_with_expansion
Additional Test Params: 
OCP VERSION:            4.19
OCS VERSION:            4.19
tested against branch:  master


 [Job PASSED](https://url.corp.redhat.com/8129bb3).
state: COMMENTED
author_association: NONE
submitted_at: 2025-05-30T06:43:34Z
user: login: hnallurv
state: APPROVED
author_association: NONE
submitted_at: 2025-05-30T09:41:17Z
user: login: PrasadDesala
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-05-30T10:28:15Z

Review comments:
diff_hunk: @@ -16,9 +17,40 @@ class TestVmSnapshotClone(E2ETest):
     """
 
     @workloads
-    @pytest.mark.polarion_id("OCS-6288")
+    @pytest.mark.parametrize(
+        argnames=["pvc_expand_before_clone", "pvc_expand_after_clone"],
+        argvalues=[
+            pytest.param(
+                False,
+                False,
+                marks=pytest.mark.polarion_id(
+                    "OCS-6288"
+                ),  # Polarion ID for no PVC expansion
+            ),
+            # pytest.param(
path: tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py
user: login: hnallurv
body: Please check if you can skip expansion test for now with the CNV bug you filed.
created_at: 2025-01-29T07:33:57Z
author_association: NONE
diff_hunk: @@ -49,11 +83,32 @@ def test_vm_clone(
         )
         vm_list = vm_objs_def + vm_objs_aggr
         log.info(f"Total VMs to process: {len(vm_list)}")
-        for index, vm_obj in enumerate(vm_list):
+        for vm_obj in vm_list:
             log.info(
                 f"Starting I/O operation on VM {vm_obj.name} using "
                 f"{file_paths[0]}..."
             )
+            # Expand PVC if `pvc_expand_before_snapshot` is True
+            pvc_obj = vm_obj.get_vm_pvc_obj()
+            new_size = 50
+            if pvc_expand_before_clone:
+                try:
+                    pvc_obj.resize_pvc(new_size=new_size, verify=True)
+                    time.sleep(30)
+                    pvc_obj = vm_obj.get_vm_pvc_obj()
+                    result = vm_obj.run_ssh_cmd(command="lsblk -o SIZE")
+                    if str(new_size) in result:
+                        log.info("expanded PVC size is showing on vm")
+                    else:
+                        raise ValueError(
path: tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py
user: login: PrasadDesala
body: shouldn't we fail the test instead of allowing it to run for next iteration ?
created_at: 2025-01-29T07:38:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -16,9 +17,40 @@ class TestVmSnapshotClone(E2ETest):
     """
 
     @workloads
-    @pytest.mark.polarion_id("OCS-6288")
+    @pytest.mark.parametrize(
+        argnames=["pvc_expand_before_clone", "pvc_expand_after_clone"],
+        argvalues=[
+            pytest.param(
+                False,
+                False,
+                marks=pytest.mark.polarion_id(
+                    "OCS-6288"
+                ),  # Polarion ID for no PVC expansion
+            ),
+            # pytest.param(
+            #     True,
+            #     False,
+            #     marks=pytest.mark.polarion_id(
+            #         "OCS-6326"
+            #     ),  # Polarion ID for expansion before clone
+            # ),
+            # pytest.param(
+            #     False,
+            #     True,
+            #     marks=pytest.mark.polarion_id(
+            #         "OCS-6326"
+            #     ),  # Polarion ID for expansion after clone
+            # ),
+        ],
path: tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py
user: login: PrasadDesala
body: can we skip the test with the jira issue instead of commenting this param combination?
created_at: 2025-01-29T07:40:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -16,9 +17,40 @@ class TestVmSnapshotClone(E2ETest):
     """
 
     @workloads
-    @pytest.mark.polarion_id("OCS-6288")
+    @pytest.mark.parametrize(
+        argnames=["pvc_expand_before_clone", "pvc_expand_after_clone"],
+        argvalues=[
+            pytest.param(
+                False,
+                False,
+                marks=pytest.mark.polarion_id(
+                    "OCS-6288"
+                ),  # Polarion ID for no PVC expansion
+            ),
+            # pytest.param(
+            #     True,
+            #     False,
+            #     marks=pytest.mark.polarion_id(
+            #         "OCS-6326"
+            #     ),  # Polarion ID for expansion before clone
+            # ),
+            # pytest.param(
+            #     False,
+            #     True,
+            #     marks=pytest.mark.polarion_id(
+            #         "OCS-6326"
+            #     ),  # Polarion ID for expansion after clone
+            # ),
+        ],
+    )
     def test_vm_clone(
path: tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py
user: login: PrasadDesala
body: as discussed, please rename the test name according to the test steps, you are covering both clone and expansion
created_at: 2025-01-29T07:41:34Z
author_association: CONTRIBUTOR
diff_hunk: @@ -49,11 +83,32 @@ def test_vm_clone(
         )
         vm_list = vm_objs_def + vm_objs_aggr
         log.info(f"Total VMs to process: {len(vm_list)}")
-        for index, vm_obj in enumerate(vm_list):
+        for vm_obj in vm_list:
             log.info(
                 f"Starting I/O operation on VM {vm_obj.name} using "
                 f"{file_paths[0]}..."
             )
+            # Expand PVC if `pvc_expand_before_snapshot` is True
+            pvc_obj = vm_obj.get_vm_pvc_obj()
+            new_size = 50
+            if pvc_expand_before_clone:
+                try:
+                    pvc_obj.resize_pvc(new_size=new_size, verify=True)
+                    time.sleep(30)
+                    pvc_obj = vm_obj.get_vm_pvc_obj()
+                    result = vm_obj.run_ssh_cmd(command="lsblk -o SIZE")
path: tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py
user: login: PrasadDesala
body: As discussed, instead of getting the lsblk output, it would be better to read the device name and check whether the specific device has been expanded. I believe you can get the device information from the VM spec file.
created_at: 2025-01-29T07:46:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -72,6 +127,28 @@ def test_vm_clone(
                 f"Failed: MD5 comparison between source {vm_obj.name} "
                 f"and cloned {clone_obj.name} VMs"
             )
+            # Expand PVC if `pvc_expand_after_restore` is True
+            if pvc_expand_after_clone:
+                new_size = 50
+                try:
+                    clone_pvc_obj = clone_obj.get_vm_pvc_obj()
+                    clone_pvc_obj.resize_pvc(new_size=new_size, verify=True)
+                    assert (
+                        clone_pvc_obj.get_vm_pvc_obj().size == new_size
+                    ), f"Failed: VM PVC Expansion on cloned VM {clone_obj.name} "
+                    time.sleep(30)
+                    result = clone_obj.run_ssh_cmd(command="lsblk -o SIZE")
+                    if str(new_size) in result:
+                        log.info("expanded PVC size is showing on vm")
+                    else:
+                        raise ValueError(
+                            "Expanded PVC size is not showing on VM. "
+                            "Please verify the disk rescan and filesystem resize."
+                        )
+                except ValueError as e:
+                    log.error(
+                        f"Error for VM {vm_obj}: {e}. Continuing with the next VM."
+                    )
path: tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py
user: login: PrasadDesala
body: shouldn't we fail the test instead of allowing it to run for next iteration ?
created_at: 2025-01-29T07:46:50Z
author_association: CONTRIBUTOR
diff_hunk: @@ -49,11 +83,32 @@ def test_vm_clone(
         )
         vm_list = vm_objs_def + vm_objs_aggr
         log.info(f"Total VMs to process: {len(vm_list)}")
-        for index, vm_obj in enumerate(vm_list):
+        for vm_obj in vm_list:
             log.info(
                 f"Starting I/O operation on VM {vm_obj.name} using "
                 f"{file_paths[0]}..."
             )
+            # Expand PVC if `pvc_expand_before_snapshot` is True
+            pvc_obj = vm_obj.get_vm_pvc_obj()
+            new_size = 50
+            if pvc_expand_before_clone:
+                try:
+                    pvc_obj.resize_pvc(new_size=new_size, verify=True)
+                    time.sleep(30)
path: tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py
user: login: PrasadDesala
body: what is the need for the sleep here? can we avoid this hardcoded  sleep?
created_at: 2025-01-29T07:48:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -49,11 +83,32 @@ def test_vm_clone(
         )
         vm_list = vm_objs_def + vm_objs_aggr
         log.info(f"Total VMs to process: {len(vm_list)}")
-        for index, vm_obj in enumerate(vm_list):
+        for vm_obj in vm_list:
             log.info(
                 f"Starting I/O operation on VM {vm_obj.name} using "
                 f"{file_paths[0]}..."
             )
+            # Expand PVC if `pvc_expand_before_snapshot` is True
+            pvc_obj = vm_obj.get_vm_pvc_obj()
+            new_size = 50
+            if pvc_expand_before_clone:
+                try:
+                    pvc_obj.resize_pvc(new_size=new_size, verify=True)
+                    time.sleep(30)
+                    pvc_obj = vm_obj.get_vm_pvc_obj()
+                    result = vm_obj.run_ssh_cmd(command="lsblk -o SIZE")
+                    if str(new_size) in result:
+                        log.info("expanded PVC size is showing on vm")
+                    else:
+                        raise ValueError(
path: tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py
user: login: avd-sagare
body: Done
created_at: 2025-01-31T12:48:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -16,9 +17,40 @@ class TestVmSnapshotClone(E2ETest):
     """
 
     @workloads
-    @pytest.mark.polarion_id("OCS-6288")
+    @pytest.mark.parametrize(
+        argnames=["pvc_expand_before_clone", "pvc_expand_after_clone"],
+        argvalues=[
+            pytest.param(
+                False,
+                False,
+                marks=pytest.mark.polarion_id(
+                    "OCS-6288"
+                ),  # Polarion ID for no PVC expansion
+            ),
+            # pytest.param(
path: tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py
user: login: avd-sagare
body: I have refactored code of multi_cnv_workload fixture where I am creating non encryption pvc till this bug get resolved. Also skipped expansion code till JIRA is open
created_at: 2025-04-25T06:10:23Z
author_association: CONTRIBUTOR
diff_hunk: @@ -16,9 +17,40 @@ class TestVmSnapshotClone(E2ETest):
     """
 
     @workloads
-    @pytest.mark.polarion_id("OCS-6288")
+    @pytest.mark.parametrize(
+        argnames=["pvc_expand_before_clone", "pvc_expand_after_clone"],
+        argvalues=[
+            pytest.param(
+                False,
+                False,
+                marks=pytest.mark.polarion_id(
+                    "OCS-6288"
+                ),  # Polarion ID for no PVC expansion
+            ),
+            # pytest.param(
+            #     True,
+            #     False,
+            #     marks=pytest.mark.polarion_id(
+            #         "OCS-6326"
+            #     ),  # Polarion ID for expansion before clone
+            # ),
+            # pytest.param(
+            #     False,
+            #     True,
+            #     marks=pytest.mark.polarion_id(
+            #         "OCS-6326"
+            #     ),  # Polarion ID for expansion after clone
+            # ),
+        ],
path: tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py
user: login: avd-sagare
body: Yes skipped
created_at: 2025-04-25T06:11:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -16,9 +17,40 @@ class TestVmSnapshotClone(E2ETest):
     """
 
     @workloads
-    @pytest.mark.polarion_id("OCS-6288")
+    @pytest.mark.parametrize(
+        argnames=["pvc_expand_before_clone", "pvc_expand_after_clone"],
+        argvalues=[
+            pytest.param(
+                False,
+                False,
+                marks=pytest.mark.polarion_id(
+                    "OCS-6288"
+                ),  # Polarion ID for no PVC expansion
+            ),
+            # pytest.param(
+            #     True,
+            #     False,
+            #     marks=pytest.mark.polarion_id(
+            #         "OCS-6326"
+            #     ),  # Polarion ID for expansion before clone
+            # ),
+            # pytest.param(
+            #     False,
+            #     True,
+            #     marks=pytest.mark.polarion_id(
+            #         "OCS-6326"
+            #     ),  # Polarion ID for expansion after clone
+            # ),
+        ],
+    )
     def test_vm_clone(
path: tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py
user: login: avd-sagare
body: Done
created_at: 2025-04-25T06:12:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -49,11 +83,32 @@ def test_vm_clone(
         )
         vm_list = vm_objs_def + vm_objs_aggr
         log.info(f"Total VMs to process: {len(vm_list)}")
-        for index, vm_obj in enumerate(vm_list):
+        for vm_obj in vm_list:
             log.info(
                 f"Starting I/O operation on VM {vm_obj.name} using "
                 f"{file_paths[0]}..."
             )
+            # Expand PVC if `pvc_expand_before_snapshot` is True
+            pvc_obj = vm_obj.get_vm_pvc_obj()
+            new_size = 50
+            if pvc_expand_before_clone:
+                try:
+                    pvc_obj.resize_pvc(new_size=new_size, verify=True)
+                    time.sleep(30)
+                    pvc_obj = vm_obj.get_vm_pvc_obj()
+                    result = vm_obj.run_ssh_cmd(command="lsblk -o SIZE")
path: tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py
user: login: avd-sagare
body: Done
created_at: 2025-04-25T06:12:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -72,6 +127,28 @@ def test_vm_clone(
                 f"Failed: MD5 comparison between source {vm_obj.name} "
                 f"and cloned {clone_obj.name} VMs"
             )
+            # Expand PVC if `pvc_expand_after_restore` is True
+            if pvc_expand_after_clone:
+                new_size = 50
+                try:
+                    clone_pvc_obj = clone_obj.get_vm_pvc_obj()
+                    clone_pvc_obj.resize_pvc(new_size=new_size, verify=True)
+                    assert (
+                        clone_pvc_obj.get_vm_pvc_obj().size == new_size
+                    ), f"Failed: VM PVC Expansion on cloned VM {clone_obj.name} "
+                    time.sleep(30)
+                    result = clone_obj.run_ssh_cmd(command="lsblk -o SIZE")
+                    if str(new_size) in result:
+                        log.info("expanded PVC size is showing on vm")
+                    else:
+                        raise ValueError(
+                            "Expanded PVC size is not showing on VM. "
+                            "Please verify the disk rescan and filesystem resize."
+                        )
+                except ValueError as e:
+                    log.error(
+                        f"Error for VM {vm_obj}: {e}. Continuing with the next VM."
+                    )
path: tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py
user: login: avd-sagare
body: here I am collecting vms which are failed in list. So for running test for next VM i did not failed it here.
created_at: 2025-04-25T06:14:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -49,11 +83,32 @@ def test_vm_clone(
         )
         vm_list = vm_objs_def + vm_objs_aggr
         log.info(f"Total VMs to process: {len(vm_list)}")
-        for index, vm_obj in enumerate(vm_list):
+        for vm_obj in vm_list:
             log.info(
                 f"Starting I/O operation on VM {vm_obj.name} using "
                 f"{file_paths[0]}..."
             )
+            # Expand PVC if `pvc_expand_before_snapshot` is True
+            pvc_obj = vm_obj.get_vm_pvc_obj()
+            new_size = 50
+            if pvc_expand_before_clone:
+                try:
+                    pvc_obj.resize_pvc(new_size=new_size, verify=True)
+                    time.sleep(30)
path: tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py
user: login: avd-sagare
body: Done
created_at: 2025-04-25T06:14:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -16,9 +18,49 @@ class TestVmSnapshotClone(E2ETest):
     """
 
     @workloads
-    @pytest.mark.polarion_id("OCS-6288")
-    def test_vm_clone(
-        self, project_factory, multi_cnv_workload, clone_vm_workload, setup_cnv
+    @pytest.mark.parametrize(
+        argnames=["pvc_expand_before_clone", "pvc_expand_after_clone"],
+        argvalues=[
+            pytest.param(
+                False,
+                False,
+                marks=pytest.mark.polarion_id(
+                    "OCS-6288"
+                ),  # Polarion ID for no PVC expansion
+            ),
+            pytest.param(
+                True,
+                False,
+                marks=[
+                    pytest.mark.polarion_id(
+                        "OCS-6326"
+                    ),  # Polarion ID for expansion before clone
+                    pytest.mark.jira(
+                        "CNV-55558", run=False
path: tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py
user: login: PrasadDesala
body: what is the use of run=False here?
created_at: 2025-04-25T06:24:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -49,30 +93,123 @@ def test_vm_clone(
         )
         vm_list = vm_objs_def + vm_objs_aggr
         log.info(f"Total VMs to process: {len(vm_list)}")
-        for index, vm_obj in enumerate(vm_list):
+        failed_vms = []
+        for vm_obj in vm_list:
+            # Expand PVC if `pvc_expand_before_clone` is True
+            pvc_obj = vm_obj.get_vm_pvc_obj()
+            if pvc_expand_before_clone:
+                new_size = 50
+                try:
+                    pvc_obj.resize_pvc(new_size=new_size, verify=True)
+                    pvc_obj = vm_obj.get_vm_pvc_obj()
+
+                    # Get rootdisk name
+                    disk = (
+                        vm_obj.vmi_obj.get()
+                        .get("status")
+                        .get("volumeStatus")[1]["target"]
+                    )
+                    devicename = f"/dev/{disk}"
+
+                    result = vm_obj.run_ssh_cmd(
+                        command=f"lsblk -d -n -o SIZE {devicename}"
+                    ).strip()
+                    if result == f"{new_size}G":
+                        log.info("expanded PVC size is showing on vm")
+                    else:
+                        raise ValueError(
+                            "Expanded PVC size before clone is not showing on VM. "
+                            "Please verify the disk rescan and filesystem resize."
+                        )
+                except ValueError as e:
+                    log.error(
+                        f"Error for VM {vm_obj}: {e}. Continuing with the next VM."
+                    )
+                    failed_vms.append(vm_obj.name)
+                    continue
             log.info(
                 f"Starting I/O operation on VM {vm_obj.name} using "
                 f"{file_paths[0]}..."
             )
             source_csum = run_dd_io(vm_obj=vm_obj, file_path=file_paths[0], verify=True)
             log.info(f"Source checksum for {vm_obj.name}: {source_csum}")
-            log.info(f"Stopping VM {vm_obj.name}...")
-            vm_obj.stop()
             log.info(f"Cloning VM {vm_obj.name}...")
-            clone_obj = clone_vm_workload(
-                vm_obj=vm_obj,
-                volume_interface=vm_obj.volume_interface,
+
+            target_name = f"clone-{vm_obj.name}"
+            with VirtualMachineClone(
+                name="clone-vm-test",
                 namespace=vm_obj.namespace,
-            )
+                source_name=vm_obj.name,
+                target_name=target_name,
+            ) as vmc:
+                vmc.wait_for_status(status=VirtualMachineClone.Status.SUCCEEDED)
+            cloned_vm = VirtualMachine(vm_name=target_name, namespace=vm_obj.namespace)
+            cloned_vm.start(wait=True)
+            cloned_vm.wait_for_ssh_connectivity()
             log.info(
-                f"Clone created successfully for VM {vm_obj.name}: " f"{clone_obj.name}"
+                f"Clone created successfully for VM {vm_obj.name}: " f"{cloned_vm.name}"
             )
-            new_csum = cal_md5sum_vm(vm_obj=clone_obj, file_path=file_paths[0])
+            new_csum = cal_md5sum_vm(vm_obj=cloned_vm, file_path=file_paths[0])
             assert source_csum == new_csum, (
                 f"Failed: MD5 comparison between source {vm_obj.name} "
-                f"and cloned {clone_obj.name} VMs"
+                f"and cloned {cloned_vm.name} VMs"
             )
-            run_dd_io(vm_obj=clone_obj, file_path=file_paths[1])
+            # Expand PVC if `pvc_expand_after_restore` is True
+            if pvc_expand_after_clone:
+                new_size = 50
+                try:
+                    # Update  self.pvc_name from vm yaml same as 11071 PR
+                    if cloned_vm.volume_interface == "DVT":
+                        cloned_vm.pvc_name = (
+                            cloned_vm.get()
+                            .get("spec")
+                            .get("template")
+                            .get("spec")
+                            .get("volumes")[0]
+                            .get("dataVolume")
+                            .get("name")
+                        )
+                    else:
+                        cloned_vm.pvc_name = (
+                            cloned_vm.get()
+                            .get("spec")
+                            .get("template")
+                            .get("spec")
+                            .get("volumes")[0]
+                            .get("persistentVolumeClaim")
+                            .get("claimName")
+                        )
+                    clone_pvc_obj = cloned_vm.get_vm_pvc_obj()
+                    clone_pvc_obj.resize_pvc(new_size=new_size, verify=True)
+
+                    # Get rootdisk name
+                    disk = (
+                        cloned_vm.vmi_obj.get()
+                        .get("status")
+                        .get("volumeStatus")[1]["target"]
+                    )
+                    devicename = f"/dev/{disk}"
+
+                    result = cloned_vm.run_ssh_cmd(
+                        command=f"lsblk -d -n -o SIZE {devicename}"
+                    ).strip()
+                    if result == f"{new_size}G":
+                        log.info("expanded PVC size is showing on vm")
+                    else:
+                        raise ValueError(
+                            "Expanded PVC size after clone is not showing on VM. "
+                            "Please verify the disk rescan and filesystem resize."
+                        )
+                except ValueError as e:
+                    log.error(
+                        f"Error for VM {cloned_vm}: {e}. Continuing with the next VM."
+                    )
+                    failed_vms.append(cloned_vm.name)
+                    continue
+            run_dd_io(vm_obj=cloned_vm, file_path=file_paths[1])
+        if failed_vms:
+            assert False, f"Test case failed for VMs: {', '.join(failed_vms)}"
path: tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py
user: login: PrasadDesala
body: should this be at the end after deleing the cloned vm?
created_at: 2025-04-25T06:26:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -16,9 +18,49 @@ class TestVmSnapshotClone(E2ETest):
     """
 
     @workloads
-    @pytest.mark.polarion_id("OCS-6288")
-    def test_vm_clone(
-        self, project_factory, multi_cnv_workload, clone_vm_workload, setup_cnv
+    @pytest.mark.parametrize(
+        argnames=["pvc_expand_before_clone", "pvc_expand_after_clone"],
+        argvalues=[
+            pytest.param(
+                False,
+                False,
+                marks=pytest.mark.polarion_id(
+                    "OCS-6288"
+                ),  # Polarion ID for no PVC expansion
+            ),
+            pytest.param(
+                True,
+                False,
+                marks=[
+                    pytest.mark.polarion_id(
+                        "OCS-6326"
+                    ),  # Polarion ID for expansion before clone
+                    pytest.mark.jira(
+                        "CNV-55558", run=False
path: tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py
user: login: avd-sagare
body: It will skip this parametrise tc if CNV-55558 is open

created_at: 2025-04-25T06:34:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -49,30 +93,123 @@ def test_vm_clone(
         )
         vm_list = vm_objs_def + vm_objs_aggr
         log.info(f"Total VMs to process: {len(vm_list)}")
-        for index, vm_obj in enumerate(vm_list):
+        failed_vms = []
+        for vm_obj in vm_list:
+            # Expand PVC if `pvc_expand_before_clone` is True
+            pvc_obj = vm_obj.get_vm_pvc_obj()
+            if pvc_expand_before_clone:
+                new_size = 50
+                try:
+                    pvc_obj.resize_pvc(new_size=new_size, verify=True)
+                    pvc_obj = vm_obj.get_vm_pvc_obj()
+
+                    # Get rootdisk name
+                    disk = (
+                        vm_obj.vmi_obj.get()
+                        .get("status")
+                        .get("volumeStatus")[1]["target"]
+                    )
+                    devicename = f"/dev/{disk}"
+
+                    result = vm_obj.run_ssh_cmd(
+                        command=f"lsblk -d -n -o SIZE {devicename}"
+                    ).strip()
+                    if result == f"{new_size}G":
+                        log.info("expanded PVC size is showing on vm")
+                    else:
+                        raise ValueError(
+                            "Expanded PVC size before clone is not showing on VM. "
+                            "Please verify the disk rescan and filesystem resize."
+                        )
+                except ValueError as e:
+                    log.error(
+                        f"Error for VM {vm_obj}: {e}. Continuing with the next VM."
+                    )
+                    failed_vms.append(vm_obj.name)
+                    continue
             log.info(
                 f"Starting I/O operation on VM {vm_obj.name} using "
                 f"{file_paths[0]}..."
             )
             source_csum = run_dd_io(vm_obj=vm_obj, file_path=file_paths[0], verify=True)
             log.info(f"Source checksum for {vm_obj.name}: {source_csum}")
-            log.info(f"Stopping VM {vm_obj.name}...")
-            vm_obj.stop()
             log.info(f"Cloning VM {vm_obj.name}...")
-            clone_obj = clone_vm_workload(
-                vm_obj=vm_obj,
-                volume_interface=vm_obj.volume_interface,
+
+            target_name = f"clone-{vm_obj.name}"
+            with VirtualMachineClone(
+                name="clone-vm-test",
                 namespace=vm_obj.namespace,
-            )
+                source_name=vm_obj.name,
+                target_name=target_name,
+            ) as vmc:
+                vmc.wait_for_status(status=VirtualMachineClone.Status.SUCCEEDED)
+            cloned_vm = VirtualMachine(vm_name=target_name, namespace=vm_obj.namespace)
+            cloned_vm.start(wait=True)
+            cloned_vm.wait_for_ssh_connectivity()
             log.info(
-                f"Clone created successfully for VM {vm_obj.name}: " f"{clone_obj.name}"
+                f"Clone created successfully for VM {vm_obj.name}: " f"{cloned_vm.name}"
             )
-            new_csum = cal_md5sum_vm(vm_obj=clone_obj, file_path=file_paths[0])
+            new_csum = cal_md5sum_vm(vm_obj=cloned_vm, file_path=file_paths[0])
             assert source_csum == new_csum, (
                 f"Failed: MD5 comparison between source {vm_obj.name} "
-                f"and cloned {clone_obj.name} VMs"
+                f"and cloned {cloned_vm.name} VMs"
             )
-            run_dd_io(vm_obj=clone_obj, file_path=file_paths[1])
+            # Expand PVC if `pvc_expand_after_restore` is True
+            if pvc_expand_after_clone:
+                new_size = 50
+                try:
+                    # Update  self.pvc_name from vm yaml same as 11071 PR
+                    if cloned_vm.volume_interface == "DVT":
+                        cloned_vm.pvc_name = (
+                            cloned_vm.get()
+                            .get("spec")
+                            .get("template")
+                            .get("spec")
+                            .get("volumes")[0]
+                            .get("dataVolume")
+                            .get("name")
+                        )
+                    else:
+                        cloned_vm.pvc_name = (
+                            cloned_vm.get()
+                            .get("spec")
+                            .get("template")
+                            .get("spec")
+                            .get("volumes")[0]
+                            .get("persistentVolumeClaim")
+                            .get("claimName")
+                        )
+                    clone_pvc_obj = cloned_vm.get_vm_pvc_obj()
+                    clone_pvc_obj.resize_pvc(new_size=new_size, verify=True)
+
+                    # Get rootdisk name
+                    disk = (
+                        cloned_vm.vmi_obj.get()
+                        .get("status")
+                        .get("volumeStatus")[1]["target"]
+                    )
+                    devicename = f"/dev/{disk}"
+
+                    result = cloned_vm.run_ssh_cmd(
+                        command=f"lsblk -d -n -o SIZE {devicename}"
+                    ).strip()
+                    if result == f"{new_size}G":
+                        log.info("expanded PVC size is showing on vm")
+                    else:
+                        raise ValueError(
+                            "Expanded PVC size after clone is not showing on VM. "
+                            "Please verify the disk rescan and filesystem resize."
+                        )
+                except ValueError as e:
+                    log.error(
+                        f"Error for VM {cloned_vm}: {e}. Continuing with the next VM."
+                    )
+                    failed_vms.append(cloned_vm.name)
+                    continue
+            run_dd_io(vm_obj=cloned_vm, file_path=file_paths[1])
+        if failed_vms:
+            assert False, f"Test case failed for VMs: {', '.join(failed_vms)}"
path: tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py
user: login: avd-sagare
body: Added above assert statement
created_at: 2025-04-25T06:55:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -51,30 +97,123 @@ def test_vm_clone(
         )
         vm_list = vm_objs_def + vm_objs_aggr
         log.info(f"Total VMs to process: {len(vm_list)}")
-        for index, vm_obj in enumerate(vm_list):
+        failed_vms = []
+        for vm_obj in vm_list:
+            # Expand PVC if `pvc_expand_before_clone` is True
+            pvc_obj = vm_obj.get_vm_pvc_obj()
+            if pvc_expand_before_clone:
+                new_size = 50
+                try:
+                    pvc_obj.resize_pvc(new_size=new_size, verify=True)
+                    pvc_obj = vm_obj.get_vm_pvc_obj()
+
+                    # Get rootdisk name
+                    disk = (
+                        vm_obj.vmi_obj.get()
+                        .get("status")
+                        .get("volumeStatus")[1]["target"]
+                    )
+                    devicename = f"/dev/{disk}"
+
+                    result = vm_obj.run_ssh_cmd(
+                        command=f"lsblk -d -n -o SIZE {devicename}"
+                    ).strip()
path: tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py
user: login: PrasadDesala
body: The process of resizing the PVC and verifying it is repeated twice (once before cloning and once after). Please create a common function outside the test and reuse it the test
created_at: 2025-04-25T07:13:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -51,30 +97,123 @@ def test_vm_clone(
         )
         vm_list = vm_objs_def + vm_objs_aggr
         log.info(f"Total VMs to process: {len(vm_list)}")
-        for index, vm_obj in enumerate(vm_list):
+        failed_vms = []
+        for vm_obj in vm_list:
+            # Expand PVC if `pvc_expand_before_clone` is True
+            pvc_obj = vm_obj.get_vm_pvc_obj()
+            if pvc_expand_before_clone:
+                new_size = 50
+                try:
+                    pvc_obj.resize_pvc(new_size=new_size, verify=True)
+                    pvc_obj = vm_obj.get_vm_pvc_obj()
+
+                    # Get rootdisk name
+                    disk = (
+                        vm_obj.vmi_obj.get()
+                        .get("status")
+                        .get("volumeStatus")[1]["target"]
+                    )
+                    devicename = f"/dev/{disk}"
+
+                    result = vm_obj.run_ssh_cmd(
+                        command=f"lsblk -d -n -o SIZE {devicename}"
+                    ).strip()
path: tests/functional/workloads/cnv/test_vm_snapshot_cloning_ops.py
user: login: avd-sagare
body: Done
created_at: 2025-04-25T07:42:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -9715,3 +9717,140 @@ def admin_client():
     from ocp_resources.resource import get_client
 
     return get_client(config_file=kubeconfig_path)
+
+
+@pytest.fixture
+def vm_clone_fixture(request):
+    """
+    Fixture to clone a VM and ensure cleanup after test.
+    Returns a factory method to perform the clone operation.
+    """
+    cloned_vms = []
+
+    def factory(vm, admin_client):
+        """
+        Clone the given VM using VirtualMachineClone.
+
+        Args:
+            vm: The source VM object to be cloned.
+            admin_client: The admin client instance to perform the operation.
+
+        Returns:
+            The cloned VM object.
+        """
path: tests/conftest.py
user: login: PrasadDesala
body: add an empty line above this
created_at: 2025-05-21T12:35:52Z
author_association: CONTRIBUTOR
diff_hunk: @@ -9715,3 +9717,140 @@ def admin_client():
     from ocp_resources.resource import get_client
 
     return get_client(config_file=kubeconfig_path)
+
+
+@pytest.fixture
+def vm_clone_fixture(request):
+    """
+    Fixture to clone a VM and ensure cleanup after test.
+    Returns a factory method to perform the clone operation.
+    """
+    cloned_vms = []
+
+    def factory(vm, admin_client):
+        """
+        Clone the given VM using VirtualMachineClone.
+
+        Args:
+            vm: The source VM object to be cloned.
+            admin_client: The admin client instance to perform the operation.
+
+        Returns:
+            The cloned VM object.
+        """
+        from ocs_ci.ocs.cnv import virtual_machine
+        from ocp_resources.virtual_machine_clone import VirtualMachineClone
path: tests/conftest.py
user: login: PrasadDesala
body: why are the imports added here? due to circular imports? if so, please add a comment mentioning it
created_at: 2025-05-21T12:36:24Z
author_association: CONTRIBUTOR
diff_hunk: @@ -9715,3 +9717,140 @@ def admin_client():
     from ocp_resources.resource import get_client
 
     return get_client(config_file=kubeconfig_path)
+
+
+@pytest.fixture
+def vm_clone_fixture(request):
+    """
+    Fixture to clone a VM and ensure cleanup after test.
+    Returns a factory method to perform the clone operation.
+    """
+    cloned_vms = []
+
+    def factory(vm, admin_client):
+        """
+        Clone the given VM using VirtualMachineClone.
+
+        Args:
+            vm: The source VM object to be cloned.
+            admin_client: The admin client instance to perform the operation.
+
+        Returns:
+            The cloned VM object.
+        """
+        from ocs_ci.ocs.cnv import virtual_machine
+        from ocp_resources.virtual_machine_clone import VirtualMachineClone
+
+        target_name = f"clone-{vm.name}"
+        with VirtualMachineClone(
+            name="clone-vm-test",
+            namespace=vm.namespace,
+            source_name=vm.name,
+            target_name=target_name,
+            client=admin_client,
+        ) as vmc:
+            vmc.wait_for_status(status=VirtualMachineClone.Status.SUCCEEDED)
+
+        cloned_vm = virtual_machine.VirtualMachine(
+            vm_name=target_name, namespace=vm.namespace
+        )
+        cloned_vm.start(wait=True)
+        cloned_vms.append(cloned_vm)
+        return cloned_vm
+
+    def teardown():
+        """
+        Cleans up cloned VM workloads.
+        """
+        for cloned_vm in cloned_vms:
+            cloned_vm.delete()
path: tests/conftest.py
user: login: PrasadDesala
body: wouldn't deleting the cloned vm delete its underlying PVC?
created_at: 2025-05-21T12:43:05Z
author_association: CONTRIBUTOR
diff_hunk: @@ -9715,3 +9717,140 @@ def admin_client():
     from ocp_resources.resource import get_client
 
     return get_client(config_file=kubeconfig_path)
+
+
+@pytest.fixture
+def vm_clone_fixture(request):
+    """
+    Fixture to clone a VM and ensure cleanup after test.
+    Returns a factory method to perform the clone operation.
+    """
+    cloned_vms = []
+
+    def factory(vm, admin_client):
+        """
+        Clone the given VM using VirtualMachineClone.
+
+        Args:
+            vm: The source VM object to be cloned.
+            admin_client: The admin client instance to perform the operation.
+
+        Returns:
+            The cloned VM object.
path: tests/conftest.py
user: login: PrasadDesala
body: ```suggestion
         vm_obj:   The cloned VM object.
```
created_at: 2025-05-21T12:45:20Z
author_association: CONTRIBUTOR
diff_hunk: @@ -9715,3 +9717,140 @@ def admin_client():
     from ocp_resources.resource import get_client
 
     return get_client(config_file=kubeconfig_path)
+
+
+@pytest.fixture
+def vm_clone_fixture(request):
+    """
+    Fixture to clone a VM and ensure cleanup after test.
+    Returns a factory method to perform the clone operation.
+    """
+    cloned_vms = []
+
+    def factory(vm, admin_client):
+        """
+        Clone the given VM using VirtualMachineClone.
+
+        Args:
+            vm: The source VM object to be cloned.
+            admin_client: The admin client instance to perform the operation.
+
+        Returns:
+            The cloned VM object.
+        """
+        from ocs_ci.ocs.cnv import virtual_machine
+        from ocp_resources.virtual_machine_clone import VirtualMachineClone
+
+        target_name = f"clone-{vm.name}"
+        with VirtualMachineClone(
+            name="clone-vm-test",
+            namespace=vm.namespace,
+            source_name=vm.name,
+            target_name=target_name,
+            client=admin_client,
+        ) as vmc:
+            vmc.wait_for_status(status=VirtualMachineClone.Status.SUCCEEDED)
+
+        cloned_vm = virtual_machine.VirtualMachine(
+            vm_name=target_name, namespace=vm.namespace
+        )
+        cloned_vm.start(wait=True)
+        cloned_vms.append(cloned_vm)
+        return cloned_vm
+
+    def teardown():
+        """
+        Cleans up cloned VM workloads.
+        """
+        for cloned_vm in cloned_vms:
+            cloned_vm.delete()
+            volumes = (
+                cloned_vm.get()
+                .get("spec", {})
+                .get("template", {})
+                .get("spec", {})
+                .get("volumes", [])
+            )
+            if not volumes:
+                raise ValueError(f"No volumes found in cloned VM {cloned_vm.name}")
+
+            volume = volumes[0]
+
+            if "dataVolume" in volume:
+                cloned_vm.pvc_name = volume["dataVolume"]["name"]
+            elif "persistentVolumeClaim" in volume:
+                cloned_vm.pvc_name = volume["persistentVolumeClaim"]["claimName"]
+            else:
+                raise ValueError(
+                    f"Neither dataVolume nor persistentVolumeClaim found in cloned VM {cloned_vm.name}"
+                )
+            run_oc_command(
+                cmd=f"delete pvc {cloned_vm.pvc_name}", namespace=cloned_vm.namespace
+            )
+            log.info(f"Cloned VM PVC {cloned_vm.pvc_name} deleted")
+
+    request.addfinalizer(teardown)
+    return factory
+
+
+@pytest.fixture()
+def vm_snapshot_restore_fixture(request):
+    """
+    Fixture factory for snapshot & restore of a VM.
+    Returns a factory method to perform snapshot and restore operations.
+    """
+    snapshots = []
+
+    def factory(vm, admin_client):
+        """
+        Take snapshot and restore the VM.
+
+        Args:
+            vm: The VM object to snapshot and restore.
+            admin_client: The admin client instance.
+
+        Returns:
+            The restored VM object.
path: tests/conftest.py
user: login: PrasadDesala
body: ```suggestion
           vm_obj: The restored VM object.
```
created_at: 2025-05-21T12:45:41Z
author_association: CONTRIBUTOR
diff_hunk: @@ -9715,3 +9717,140 @@ def admin_client():
     from ocp_resources.resource import get_client
 
     return get_client(config_file=kubeconfig_path)
+
+
+@pytest.fixture
+def vm_clone_fixture(request):
+    """
+    Fixture to clone a VM and ensure cleanup after test.
+    Returns a factory method to perform the clone operation.
+    """
+    cloned_vms = []
+
+    def factory(vm, admin_client):
+        """
+        Clone the given VM using VirtualMachineClone.
+
+        Args:
+            vm: The source VM object to be cloned.
+            admin_client: The admin client instance to perform the operation.
+
+        Returns:
+            The cloned VM object.
+        """
+        from ocs_ci.ocs.cnv import virtual_machine
+        from ocp_resources.virtual_machine_clone import VirtualMachineClone
+
+        target_name = f"clone-{vm.name}"
+        with VirtualMachineClone(
+            name="clone-vm-test",
+            namespace=vm.namespace,
+            source_name=vm.name,
+            target_name=target_name,
+            client=admin_client,
+        ) as vmc:
+            vmc.wait_for_status(status=VirtualMachineClone.Status.SUCCEEDED)
+
+        cloned_vm = virtual_machine.VirtualMachine(
+            vm_name=target_name, namespace=vm.namespace
+        )
+        cloned_vm.start(wait=True)
+        cloned_vms.append(cloned_vm)
+        return cloned_vm
+
+    def teardown():
+        """
+        Cleans up cloned VM workloads.
+        """
+        for cloned_vm in cloned_vms:
+            cloned_vm.delete()
+            volumes = (
+                cloned_vm.get()
+                .get("spec", {})
+                .get("template", {})
+                .get("spec", {})
+                .get("volumes", [])
+            )
+            if not volumes:
+                raise ValueError(f"No volumes found in cloned VM {cloned_vm.name}")
+
+            volume = volumes[0]
+
+            if "dataVolume" in volume:
+                cloned_vm.pvc_name = volume["dataVolume"]["name"]
+            elif "persistentVolumeClaim" in volume:
+                cloned_vm.pvc_name = volume["persistentVolumeClaim"]["claimName"]
+            else:
+                raise ValueError(
+                    f"Neither dataVolume nor persistentVolumeClaim found in cloned VM {cloned_vm.name}"
+                )
+            run_oc_command(
+                cmd=f"delete pvc {cloned_vm.pvc_name}", namespace=cloned_vm.namespace
+            )
+            log.info(f"Cloned VM PVC {cloned_vm.pvc_name} deleted")
+
+    request.addfinalizer(teardown)
+    return factory
+
+
+@pytest.fixture()
+def vm_snapshot_restore_fixture(request):
+    """
+    Fixture factory for snapshot & restore of a VM.
+    Returns a factory method to perform snapshot and restore operations.
+    """
+    snapshots = []
+
+    def factory(vm, admin_client):
+        """
+        Take snapshot and restore the VM.
+
+        Args:
+            vm: The VM object to snapshot and restore.
+            admin_client: The admin client instance.
+
+        Returns:
+            The restored VM object.
+        """
+        from ocp_resources.virtual_machine_snapshot import VirtualMachineSnapshot
+        from ocp_resources.virtual_machine_restore import VirtualMachineRestore
+        from ocs_ci.utility.utils import create_unique_resource_name
+
+        snapshot_name = f"snapshot-{vm.name}"
+        vm_snapshot = VirtualMachineSnapshot(
+            name=snapshot_name,
+            namespace=vm.namespace,
+            vm_name=vm.name,
+            client=admin_client,
+            teardown=False,
+        )
+        vm_snapshot.create()
+        vm_snapshot.wait_snapshot_done()
+        snapshots.append(vm_snapshot)
+
+        vm.stop()
+
+        restore_snapshot_name = create_unique_resource_name(snapshot_name, "restore")
+        with VirtualMachineRestore(
+            name=restore_snapshot_name,
+            namespace=vm.namespace,
+            vm_name=vm.name,
+            snapshot_name=snapshot_name,
+            client=admin_client,
+            teardown=False,
+        ) as vm_restore:
+            vm_restore.wait_restore_done()
+
+        vm.start()
+        vm.wait_for_ssh_connectivity(timeout=1200)
+        return vm
+
+    def teardown():
+        """
+        Cleans up VM snapshots.
+        """
+        for snap in snapshots:
+            snap.delete(wait=True)
path: tests/conftest.py
user: login: PrasadDesala
body: would the snapshot delete, deletes the underlying PVC as well?
created_at: 2025-05-21T12:46:57Z
author_association: CONTRIBUTOR
diff_hunk: @@ -9715,3 +9717,140 @@ def admin_client():
     from ocp_resources.resource import get_client
 
     return get_client(config_file=kubeconfig_path)
+
+
+@pytest.fixture
+def vm_clone_fixture(request):
+    """
+    Fixture to clone a VM and ensure cleanup after test.
+    Returns a factory method to perform the clone operation.
+    """
+    cloned_vms = []
+
+    def factory(vm, admin_client):
+        """
+        Clone the given VM using VirtualMachineClone.
+
+        Args:
+            vm: The source VM object to be cloned.
+            admin_client: The admin client instance to perform the operation.
+
+        Returns:
+            The cloned VM object.
+        """
+        from ocs_ci.ocs.cnv import virtual_machine
+        from ocp_resources.virtual_machine_clone import VirtualMachineClone
+
+        target_name = f"clone-{vm.name}"
+        with VirtualMachineClone(
+            name="clone-vm-test",
+            namespace=vm.namespace,
+            source_name=vm.name,
+            target_name=target_name,
+            client=admin_client,
+        ) as vmc:
+            vmc.wait_for_status(status=VirtualMachineClone.Status.SUCCEEDED)
+
+        cloned_vm = virtual_machine.VirtualMachine(
+            vm_name=target_name, namespace=vm.namespace
+        )
+        cloned_vm.start(wait=True)
+        cloned_vms.append(cloned_vm)
+        return cloned_vm
+
+    def teardown():
+        """
+        Cleans up cloned VM workloads.
+        """
+        for cloned_vm in cloned_vms:
+            cloned_vm.delete()
+            volumes = (
+                cloned_vm.get()
+                .get("spec", {})
+                .get("template", {})
+                .get("spec", {})
+                .get("volumes", [])
+            )
+            if not volumes:
+                raise ValueError(f"No volumes found in cloned VM {cloned_vm.name}")
+
+            volume = volumes[0]
+
+            if "dataVolume" in volume:
+                cloned_vm.pvc_name = volume["dataVolume"]["name"]
+            elif "persistentVolumeClaim" in volume:
+                cloned_vm.pvc_name = volume["persistentVolumeClaim"]["claimName"]
+            else:
+                raise ValueError(
+                    f"Neither dataVolume nor persistentVolumeClaim found in cloned VM {cloned_vm.name}"
+                )
+            run_oc_command(
+                cmd=f"delete pvc {cloned_vm.pvc_name}", namespace=cloned_vm.namespace
+            )
+            log.info(f"Cloned VM PVC {cloned_vm.pvc_name} deleted")
+
+    request.addfinalizer(teardown)
+    return factory
+
+
+@pytest.fixture()
+def vm_snapshot_restore_fixture(request):
+    """
+    Fixture factory for snapshot & restore of a VM.
+    Returns a factory method to perform snapshot and restore operations.
+    """
+    snapshots = []
+
+    def factory(vm, admin_client):
+        """
+        Take snapshot and restore the VM.
+
+        Args:
+            vm: The VM object to snapshot and restore.
+            admin_client: The admin client instance.
+
+        Returns:
+            The restored VM object.
+        """
+        from ocp_resources.virtual_machine_snapshot import VirtualMachineSnapshot
+        from ocp_resources.virtual_machine_restore import VirtualMachineRestore
+        from ocs_ci.utility.utils import create_unique_resource_name
+
+        snapshot_name = f"snapshot-{vm.name}"
+        vm_snapshot = VirtualMachineSnapshot(
+            name=snapshot_name,
+            namespace=vm.namespace,
+            vm_name=vm.name,
+            client=admin_client,
+            teardown=False,
+        )
+        vm_snapshot.create()
+        vm_snapshot.wait_snapshot_done()
+        snapshots.append(vm_snapshot)
+
path: tests/conftest.py
user: login: PrasadDesala
body: remove the empty lines above  vm.stop and below
created_at: 2025-05-21T12:47:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -9715,3 +9717,140 @@ def admin_client():
     from ocp_resources.resource import get_client
 
     return get_client(config_file=kubeconfig_path)
+
+
+@pytest.fixture
+def vm_clone_fixture(request):
+    """
+    Fixture to clone a VM and ensure cleanup after test.
+    Returns a factory method to perform the clone operation.
+    """
+    cloned_vms = []
+
+    def factory(vm, admin_client):
+        """
+        Clone the given VM using VirtualMachineClone.
+
+        Args:
+            vm: The source VM object to be cloned.
+            admin_client: The admin client instance to perform the operation.
+
+        Returns:
+            The cloned VM object.
+        """
+        from ocs_ci.ocs.cnv import virtual_machine
+        from ocp_resources.virtual_machine_clone import VirtualMachineClone
+
+        target_name = f"clone-{vm.name}"
+        with VirtualMachineClone(
+            name="clone-vm-test",
+            namespace=vm.namespace,
+            source_name=vm.name,
+            target_name=target_name,
+            client=admin_client,
+        ) as vmc:
+            vmc.wait_for_status(status=VirtualMachineClone.Status.SUCCEEDED)
+
+        cloned_vm = virtual_machine.VirtualMachine(
+            vm_name=target_name, namespace=vm.namespace
+        )
+        cloned_vm.start(wait=True)
+        cloned_vms.append(cloned_vm)
+        return cloned_vm
+
+    def teardown():
+        """
+        Cleans up cloned VM workloads.
+        """
+        for cloned_vm in cloned_vms:
+            cloned_vm.delete()
+            volumes = (
+                cloned_vm.get()
+                .get("spec", {})
+                .get("template", {})
+                .get("spec", {})
+                .get("volumes", [])
+            )
+            if not volumes:
+                raise ValueError(f"No volumes found in cloned VM {cloned_vm.name}")
+
+            volume = volumes[0]
path: tests/conftest.py
user: login: PrasadDesala
body: delete empty line before and after volume = volumes[0]
created_at: 2025-05-21T12:48:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -9715,3 +9717,140 @@ def admin_client():
     from ocp_resources.resource import get_client
 
     return get_client(config_file=kubeconfig_path)
+
+
+@pytest.fixture
+def vm_clone_fixture(request):
+    """
+    Fixture to clone a VM and ensure cleanup after test.
+    Returns a factory method to perform the clone operation.
+    """
+    cloned_vms = []
+
+    def factory(vm, admin_client):
+        """
+        Clone the given VM using VirtualMachineClone.
+
+        Args:
+            vm: The source VM object to be cloned.
+            admin_client: The admin client instance to perform the operation.
+
+        Returns:
+            The cloned VM object.
+        """
+        from ocs_ci.ocs.cnv import virtual_machine
+        from ocp_resources.virtual_machine_clone import VirtualMachineClone
+
+        target_name = f"clone-{vm.name}"
+        with VirtualMachineClone(
+            name="clone-vm-test",
+            namespace=vm.namespace,
+            source_name=vm.name,
+            target_name=target_name,
+            client=admin_client,
+        ) as vmc:
+            vmc.wait_for_status(status=VirtualMachineClone.Status.SUCCEEDED)
+
+        cloned_vm = virtual_machine.VirtualMachine(
+            vm_name=target_name, namespace=vm.namespace
+        )
+        cloned_vm.start(wait=True)
+        cloned_vms.append(cloned_vm)
+        return cloned_vm
+
+    def teardown():
+        """
+        Cleans up cloned VM workloads.
+        """
+        for cloned_vm in cloned_vms:
+            cloned_vm.delete()
path: tests/conftest.py
user: login: avd-sagare
body: No. Its little tricky here. I have started this clone vm using ocs_ci VirtualMachine class object , but it is not created using VirtualMachine so that object  don't have pvc information. That's why I need to delete explicitly.

created_at: 2025-05-21T14:14:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -9715,3 +9717,140 @@ def admin_client():
     from ocp_resources.resource import get_client
 
     return get_client(config_file=kubeconfig_path)
+
+
+@pytest.fixture
+def vm_clone_fixture(request):
+    """
+    Fixture to clone a VM and ensure cleanup after test.
+    Returns a factory method to perform the clone operation.
+    """
+    cloned_vms = []
+
+    def factory(vm, admin_client):
+        """
+        Clone the given VM using VirtualMachineClone.
+
+        Args:
+            vm: The source VM object to be cloned.
+            admin_client: The admin client instance to perform the operation.
+
+        Returns:
+            The cloned VM object.
+        """
+        from ocs_ci.ocs.cnv import virtual_machine
+        from ocp_resources.virtual_machine_clone import VirtualMachineClone
+
+        target_name = f"clone-{vm.name}"
+        with VirtualMachineClone(
+            name="clone-vm-test",
+            namespace=vm.namespace,
+            source_name=vm.name,
+            target_name=target_name,
+            client=admin_client,
+        ) as vmc:
+            vmc.wait_for_status(status=VirtualMachineClone.Status.SUCCEEDED)
+
+        cloned_vm = virtual_machine.VirtualMachine(
+            vm_name=target_name, namespace=vm.namespace
+        )
+        cloned_vm.start(wait=True)
+        cloned_vms.append(cloned_vm)
+        return cloned_vm
+
+    def teardown():
+        """
+        Cleans up cloned VM workloads.
+        """
+        for cloned_vm in cloned_vms:
+            cloned_vm.delete()
+            volumes = (
+                cloned_vm.get()
+                .get("spec", {})
+                .get("template", {})
+                .get("spec", {})
+                .get("volumes", [])
+            )
+            if not volumes:
+                raise ValueError(f"No volumes found in cloned VM {cloned_vm.name}")
+
+            volume = volumes[0]
+
+            if "dataVolume" in volume:
+                cloned_vm.pvc_name = volume["dataVolume"]["name"]
+            elif "persistentVolumeClaim" in volume:
+                cloned_vm.pvc_name = volume["persistentVolumeClaim"]["claimName"]
+            else:
+                raise ValueError(
+                    f"Neither dataVolume nor persistentVolumeClaim found in cloned VM {cloned_vm.name}"
+                )
+            run_oc_command(
+                cmd=f"delete pvc {cloned_vm.pvc_name}", namespace=cloned_vm.namespace
+            )
+            log.info(f"Cloned VM PVC {cloned_vm.pvc_name} deleted")
+
+    request.addfinalizer(teardown)
+    return factory
+
+
+@pytest.fixture()
+def vm_snapshot_restore_fixture(request):
+    """
+    Fixture factory for snapshot & restore of a VM.
+    Returns a factory method to perform snapshot and restore operations.
+    """
+    snapshots = []
+
+    def factory(vm, admin_client):
+        """
+        Take snapshot and restore the VM.
+
+        Args:
+            vm: The VM object to snapshot and restore.
+            admin_client: The admin client instance.
+
+        Returns:
+            The restored VM object.
+        """
+        from ocp_resources.virtual_machine_snapshot import VirtualMachineSnapshot
+        from ocp_resources.virtual_machine_restore import VirtualMachineRestore
+        from ocs_ci.utility.utils import create_unique_resource_name
+
+        snapshot_name = f"snapshot-{vm.name}"
+        vm_snapshot = VirtualMachineSnapshot(
+            name=snapshot_name,
+            namespace=vm.namespace,
+            vm_name=vm.name,
+            client=admin_client,
+            teardown=False,
+        )
+        vm_snapshot.create()
+        vm_snapshot.wait_snapshot_done()
+        snapshots.append(vm_snapshot)
+
+        vm.stop()
+
+        restore_snapshot_name = create_unique_resource_name(snapshot_name, "restore")
+        with VirtualMachineRestore(
+            name=restore_snapshot_name,
+            namespace=vm.namespace,
+            vm_name=vm.name,
+            snapshot_name=snapshot_name,
+            client=admin_client,
+            teardown=False,
+        ) as vm_restore:
+            vm_restore.wait_restore_done()
+
+        vm.start()
+        vm.wait_for_ssh_connectivity(timeout=1200)
+        return vm
+
+    def teardown():
+        """
+        Cleans up VM snapshots.
+        """
+        for snap in snapshots:
+            snap.delete(wait=True)
path: tests/conftest.py
user: login: avd-sagare
body: snap is not VM it is snapshot only with help of which original VM is restored. So after restoring VM i deleted snapshot. The restored VM will get deleted by teardown part of fixture along with its PVC
created_at: 2025-05-21T14:17:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -9715,3 +9717,140 @@ def admin_client():
     from ocp_resources.resource import get_client
 
     return get_client(config_file=kubeconfig_path)
+
+
+@pytest.fixture
+def vm_clone_fixture(request):
+    """
+    Fixture to clone a VM and ensure cleanup after test.
+    Returns a factory method to perform the clone operation.
+    """
+    cloned_vms = []
+
+    def factory(vm, admin_client):
+        """
+        Clone the given VM using VirtualMachineClone.
+
+        Args:
+            vm: The source VM object to be cloned.
+            admin_client: The admin client instance to perform the operation.
+
+        Returns:
+            The cloned VM object.
+        """
+        from ocs_ci.ocs.cnv import virtual_machine
+        from ocp_resources.virtual_machine_clone import VirtualMachineClone
path: tests/conftest.py
user: login: avd-sagare
body: Yes . Will add comment there.
created_at: 2025-05-21T14:18:15Z
author_association: CONTRIBUTOR
diff_hunk: @@ -9715,3 +9717,140 @@ def admin_client():
     from ocp_resources.resource import get_client
 
     return get_client(config_file=kubeconfig_path)
+
+
+@pytest.fixture
+def vm_clone_fixture(request):
+    """
+    Fixture to clone a VM and ensure cleanup after test.
+    Returns a factory method to perform the clone operation.
+    """
+    cloned_vms = []
+
+    def factory(vm, admin_client):
+        """
+        Clone the given VM using VirtualMachineClone.
+
+        Args:
+            vm: The source VM object to be cloned.
+            admin_client: The admin client instance to perform the operation.
+
+        Returns:
+            The cloned VM object.
+        """
path: tests/conftest.py
user: login: avd-sagare
body: Added
created_at: 2025-05-22T05:36:00Z
author_association: CONTRIBUTOR
diff_hunk: @@ -9715,3 +9717,140 @@ def admin_client():
     from ocp_resources.resource import get_client
 
     return get_client(config_file=kubeconfig_path)
+
+
+@pytest.fixture
+def vm_clone_fixture(request):
+    """
+    Fixture to clone a VM and ensure cleanup after test.
+    Returns a factory method to perform the clone operation.
+    """
+    cloned_vms = []
+
+    def factory(vm, admin_client):
+        """
+        Clone the given VM using VirtualMachineClone.
+
+        Args:
+            vm: The source VM object to be cloned.
+            admin_client: The admin client instance to perform the operation.
+
+        Returns:
+            The cloned VM object.
+        """
+        from ocs_ci.ocs.cnv import virtual_machine
+        from ocp_resources.virtual_machine_clone import VirtualMachineClone
path: tests/conftest.py
user: login: avd-sagare
body: Done
created_at: 2025-05-22T05:36:11Z
author_association: CONTRIBUTOR
diff_hunk: @@ -9715,3 +9717,140 @@ def admin_client():
     from ocp_resources.resource import get_client
 
     return get_client(config_file=kubeconfig_path)
+
+
+@pytest.fixture
+def vm_clone_fixture(request):
+    """
+    Fixture to clone a VM and ensure cleanup after test.
+    Returns a factory method to perform the clone operation.
+    """
+    cloned_vms = []
+
+    def factory(vm, admin_client):
+        """
+        Clone the given VM using VirtualMachineClone.
+
+        Args:
+            vm: The source VM object to be cloned.
+            admin_client: The admin client instance to perform the operation.
+
+        Returns:
+            The cloned VM object.
+        """
+        from ocs_ci.ocs.cnv import virtual_machine
+        from ocp_resources.virtual_machine_clone import VirtualMachineClone
+
+        target_name = f"clone-{vm.name}"
+        with VirtualMachineClone(
+            name="clone-vm-test",
+            namespace=vm.namespace,
+            source_name=vm.name,
+            target_name=target_name,
+            client=admin_client,
+        ) as vmc:
+            vmc.wait_for_status(status=VirtualMachineClone.Status.SUCCEEDED)
+
+        cloned_vm = virtual_machine.VirtualMachine(
+            vm_name=target_name, namespace=vm.namespace
+        )
+        cloned_vm.start(wait=True)
+        cloned_vms.append(cloned_vm)
+        return cloned_vm
+
+    def teardown():
+        """
+        Cleans up cloned VM workloads.
+        """
+        for cloned_vm in cloned_vms:
+            cloned_vm.delete()
path: tests/conftest.py
user: login: avd-sagare
body: check
created_at: 2025-05-22T05:36:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -9715,3 +9717,140 @@ def admin_client():
     from ocp_resources.resource import get_client
 
     return get_client(config_file=kubeconfig_path)
+
+
+@pytest.fixture
+def vm_clone_fixture(request):
+    """
+    Fixture to clone a VM and ensure cleanup after test.
+    Returns a factory method to perform the clone operation.
+    """
+    cloned_vms = []
+
+    def factory(vm, admin_client):
+        """
+        Clone the given VM using VirtualMachineClone.
+
+        Args:
+            vm: The source VM object to be cloned.
+            admin_client: The admin client instance to perform the operation.
+
+        Returns:
+            The cloned VM object.
+        """
+        from ocs_ci.ocs.cnv import virtual_machine
+        from ocp_resources.virtual_machine_clone import VirtualMachineClone
+
+        target_name = f"clone-{vm.name}"
+        with VirtualMachineClone(
+            name="clone-vm-test",
+            namespace=vm.namespace,
+            source_name=vm.name,
+            target_name=target_name,
+            client=admin_client,
+        ) as vmc:
+            vmc.wait_for_status(status=VirtualMachineClone.Status.SUCCEEDED)
+
+        cloned_vm = virtual_machine.VirtualMachine(
+            vm_name=target_name, namespace=vm.namespace
+        )
+        cloned_vm.start(wait=True)
+        cloned_vms.append(cloned_vm)
+        return cloned_vm
+
+    def teardown():
+        """
+        Cleans up cloned VM workloads.
+        """
+        for cloned_vm in cloned_vms:
+            cloned_vm.delete()
+            volumes = (
+                cloned_vm.get()
+                .get("spec", {})
+                .get("template", {})
+                .get("spec", {})
+                .get("volumes", [])
+            )
+            if not volumes:
+                raise ValueError(f"No volumes found in cloned VM {cloned_vm.name}")
+
+            volume = volumes[0]
path: tests/conftest.py
user: login: avd-sagare
body: Removed
created_at: 2025-05-22T05:36:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -9715,3 +9717,140 @@ def admin_client():
     from ocp_resources.resource import get_client
 
     return get_client(config_file=kubeconfig_path)
+
+
+@pytest.fixture
+def vm_clone_fixture(request):
+    """
+    Fixture to clone a VM and ensure cleanup after test.
+    Returns a factory method to perform the clone operation.
+    """
+    cloned_vms = []
+
+    def factory(vm, admin_client):
+        """
+        Clone the given VM using VirtualMachineClone.
+
+        Args:
+            vm: The source VM object to be cloned.
+            admin_client: The admin client instance to perform the operation.
+
+        Returns:
+            The cloned VM object.
+        """
+        from ocs_ci.ocs.cnv import virtual_machine
+        from ocp_resources.virtual_machine_clone import VirtualMachineClone
+
+        target_name = f"clone-{vm.name}"
+        with VirtualMachineClone(
+            name="clone-vm-test",
+            namespace=vm.namespace,
+            source_name=vm.name,
+            target_name=target_name,
+            client=admin_client,
+        ) as vmc:
+            vmc.wait_for_status(status=VirtualMachineClone.Status.SUCCEEDED)
+
+        cloned_vm = virtual_machine.VirtualMachine(
+            vm_name=target_name, namespace=vm.namespace
+        )
+        cloned_vm.start(wait=True)
+        cloned_vms.append(cloned_vm)
+        return cloned_vm
+
+    def teardown():
+        """
+        Cleans up cloned VM workloads.
+        """
+        for cloned_vm in cloned_vms:
+            cloned_vm.delete()
+            volumes = (
+                cloned_vm.get()
+                .get("spec", {})
+                .get("template", {})
+                .get("spec", {})
+                .get("volumes", [])
+            )
+            if not volumes:
+                raise ValueError(f"No volumes found in cloned VM {cloned_vm.name}")
+
+            volume = volumes[0]
+
+            if "dataVolume" in volume:
+                cloned_vm.pvc_name = volume["dataVolume"]["name"]
+            elif "persistentVolumeClaim" in volume:
+                cloned_vm.pvc_name = volume["persistentVolumeClaim"]["claimName"]
+            else:
+                raise ValueError(
+                    f"Neither dataVolume nor persistentVolumeClaim found in cloned VM {cloned_vm.name}"
+                )
+            run_oc_command(
+                cmd=f"delete pvc {cloned_vm.pvc_name}", namespace=cloned_vm.namespace
+            )
+            log.info(f"Cloned VM PVC {cloned_vm.pvc_name} deleted")
+
+    request.addfinalizer(teardown)
+    return factory
+
+
+@pytest.fixture()
+def vm_snapshot_restore_fixture(request):
+    """
+    Fixture factory for snapshot & restore of a VM.
+    Returns a factory method to perform snapshot and restore operations.
+    """
+    snapshots = []
+
+    def factory(vm, admin_client):
+        """
+        Take snapshot and restore the VM.
+
+        Args:
+            vm: The VM object to snapshot and restore.
+            admin_client: The admin client instance.
+
+        Returns:
+            The restored VM object.
+        """
+        from ocp_resources.virtual_machine_snapshot import VirtualMachineSnapshot
+        from ocp_resources.virtual_machine_restore import VirtualMachineRestore
+        from ocs_ci.utility.utils import create_unique_resource_name
+
+        snapshot_name = f"snapshot-{vm.name}"
+        vm_snapshot = VirtualMachineSnapshot(
+            name=snapshot_name,
+            namespace=vm.namespace,
+            vm_name=vm.name,
+            client=admin_client,
+            teardown=False,
+        )
+        vm_snapshot.create()
+        vm_snapshot.wait_snapshot_done()
+        snapshots.append(vm_snapshot)
+
+        vm.stop()
+
+        restore_snapshot_name = create_unique_resource_name(snapshot_name, "restore")
+        with VirtualMachineRestore(
+            name=restore_snapshot_name,
+            namespace=vm.namespace,
+            vm_name=vm.name,
+            snapshot_name=snapshot_name,
+            client=admin_client,
+            teardown=False,
+        ) as vm_restore:
+            vm_restore.wait_restore_done()
+
+        vm.start()
+        vm.wait_for_ssh_connectivity(timeout=1200)
+        return vm
+
+    def teardown():
+        """
+        Cleans up VM snapshots.
+        """
+        for snap in snapshots:
+            snap.delete(wait=True)
path: tests/conftest.py
user: login: avd-sagare
body: check
created_at: 2025-05-22T05:36:46Z
author_association: CONTRIBUTOR

PR comments:
user: login: openshift-ci[bot]
created_at: 2025-05-30T10:28:19Z
body: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/red-hat-storage/ocs-ci/pull/11199#" title="Author self-approved">avd-sagare</a>*, *<a href="https://github.com/red-hat-storage/ocs-ci/pull/11199#pullrequestreview-2880760148" title="Approved">hnallurv</a>*, *<a href="https://github.com/red-hat-storage/ocs-ci/pull/11199#pullrequestreview-2880898449" title="Approved">PrasadDesala</a>*


The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=red-hat-storage%2Focs-ci).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/red-hat-storage/ocs-ci/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->
author_association: NONE