Copilot commented on code in PR #11931:
URL: https://github.com/apache/cloudstack/pull/11931#discussion_r4182298972


##########
test/integration/smoke/test_backup_recovery_nas.py:
##########
@@ -39,19 +43,56 @@ def setUpClass(cls):
         cls.services["mode"] = cls.zone.networktype
         cls.hypervisor = cls.testClient.getHypervisorInfo()
         cls.domain = get_domain(cls.api_client)
+        cls._cleanup = []
+
+        if cls.hypervisor.lower() != 'kvm':
+            cls.skipTest(cls, reason="Test can be run only on KVM hypervisor")
+
         cls.template = get_template(cls.api_client, cls.zone.id, 
cls.services["ostype"])
         if cls.template == FAILED:
             assert False, "get_template() failed to return template with 
description %s" % cls.services["ostype"]
         cls.services["small"]["zoneid"] = cls.zone.id
         cls.services["small"]["template"] = cls.template.id
-        cls._cleanup = []
 
-        if cls.hypervisor.lower() != 'kvm':
-            cls.skipTest(cls, reason="Test can be run only on KVM hypervisor")
-
-        cls.storage_pool = StoragePool.list(cls.api_client)[0]
-        if cls.storage_pool.type.lower() != 'networkfilesystem':
-            cls.skipTest(cls, reason="Test can be run only if the primary 
storage is of type NFS")
+        # Pick a pool that's actually usable, not just list()[0] -- 
environments that
+        # added Ceph/RBD storage after the zone's original NFS primary storage 
keep the
+        # old NFS pool around in Disabled state, and it still sorts first. 
Falling back
+        # to index 0 there silently exercises the disabled NFS pool's path as 
if it were
+        # the primary storage in use, rather than the RBD pool VMs actually 
deploy on.
+        storage_pools = StoragePool.list(cls.api_client)
+        usable_pools = [p for p in storage_pools if getattr(p, 'state', 'Up') 
== 'Up']
+        cls.storage_pool = usable_pools[0] if usable_pools else 
storage_pools[0]
+        if cls.storage_pool.type.lower() not in 
SUPPORTED_PRIMARY_STORAGE_POOL_TYPES:
+            cls.skipTest(cls, reason="Test can be run only if the primary 
storage is of type NFS or RBD (Ceph)")

Review Comment:
   Filtering only by state still lets an unrelated pool determine whether this 
suite runs. For example, an Up Filesystem pool listed before an Up RBD pool 
skips all tests despite supported storage being available. The global listing 
can also select another zone's pool for the default NAS export. Restrict 
candidates to Up NFS/RBD pools in the test zone, and skip when none exist 
instead of falling back to the first global result.



##########
test/integration/smoke/test_backup_recovery_nas.py:
##########
@@ -594,3 +662,79 @@ def test_stopped_vm_falls_back_to_full(self):
         finally:
             self._set_full_every(original_full_every)
             self.backup_offering.removeOffering(self.apiclient, self.vm.id)
+
+    # ------------------------------------------------------------------
+    # Restore-volume-and-attach regression (PR apache/cloudstack#14007)
+    # ------------------------------------------------------------------
+    # This test exercises the fixed path end to end via 
restoreVolumeFromBackupAndAttachToVM,
+    # on whichever primary storage this environment is running with (NFS or 
Ceph/RBD).
+
+    @attr(tags=["advanced", "backup"], required_hardware="true")
+    def test_restore_volume_and_attach_to_vm(self):
+        """
+        Test restoring the ROOT and DATADISK volumes of a backup and attaching 
them
+        to a different Instance (restoreVolumeFromBackupAndAttachToVM).
+        """
+        target_vm = None
+        self.backup_offering.assignOffering(self.apiclient, self.vm.id)
+        try:
+            ssh_client_vm = self.vm.get_ssh_client(reconnect=True)
+            ssh_client_vm.execute("echo restore-attach-marker > 
/root/restore_attach_marker.txt; sync")
+
+            Backup.create(self.apiclient, self.vm.id, "restore_attach_backup")
+
+            backups = Backup.list(self.apiclient, self.vm.id)
+            self.assertEqual(len(backups), 1, "There should exist only one 
backup for the VM")
+            backup = backups[0]
+
+            volumes = Volume.list(self.apiclient, virtualmachineid=self.vm.id, 
listall=True)
+            self.assertTrue(isinstance(volumes, list), "List volumes should 
return a valid list")
+            root_disk_id = None
+            data_disk_id = None
+            for volume in volumes:
+                if volume.type == 'ROOT':
+                    root_disk_id = volume.id
+                elif volume.type == 'DATADISK':
+                    data_disk_id = volume.id
+            self.assertIsNotNone(root_disk_id, "The backed up VM should have a 
ROOT volume")
+
+            # Target Instance that will receive the restored volumes. The nas 
provider
+            # (unlike KBOSS) requires the target Instance to be stopped before 
a volume
+            # can be restored and attached to it.
+            target_vm = VirtualMachine.create(
+                self.apiclient, self.services["small"], 
accountid=self.account.name,
+                domainid=self.account.domainid, 
serviceofferingid=self.offering.id,
+                mode=self.services["mode"]
+            )
+            target_vm.stop(self.apiclient, forced=True)
+
+            # Restore and attach the ROOT volume backup as an extra disk.
+            Backup.restoreVolumeFromBackupAndAttachToVM(
+                self.apiclient, backupid=backup.id, volumeid=root_disk_id, 
virtualmachineid=target_vm.id
+            )
+            target_volumes = Volume.list(self.apiclient, 
virtualmachineid=target_vm.id, listall=True)
+            self.assertTrue(isinstance(target_volumes, list), "List volumes 
should return a valid list")
+            self.assertEqual(2, len(target_volumes),
+                "Target Instance should have its own ROOT volume plus the 
restored volume")
+
+            if data_disk_id:
+                # Restore and attach the DATADISK volume backup as well.
+                Backup.restoreVolumeFromBackupAndAttachToVM(
+                    self.apiclient, backupid=backup.id, volumeid=data_disk_id, 
virtualmachineid=target_vm.id
+                )
+                target_volumes = Volume.list(self.apiclient, 
virtualmachineid=target_vm.id, listall=True)
+                self.assertEqual(3, len(target_volumes),
+                    "Target Instance should have 3 volumes after restoring 
both the ROOT and DATADISK backups")
+
+            # Start the target Instance to verify the restored disk(s) are 
actually
+            # usable and libvirt accepted the attach-device/attach-disk calls.
+            target_vm.start(self.apiclient)
+
+            Backup.delete(self.apiclient, backup.id)
+        finally:

Review Comment:
   Backup deletion runs only on success. If a restore, assertion, or target VM 
startup fails, `finally` removes the offering but leaves the backup behind: NAS 
explicitly preserves backups on offering removal. This contaminates later tests 
sharing `self.vm` and can block repository cleanup. Initialize a backup 
variable before `try`, capture the object returned by `Backup.create()`, and 
delete that backup in `finally`, while still ensuring VM cleanup and offering 
removal are attempted.



##########
test/integration/smoke/test_direct_download.py:
##########
@@ -218,7 +218,7 @@ def setUpClass(cls):
                 zoneid=cls.zone.id
             )
             for pool in storage_pools:
-                if not cls.nfsStorageFound and pool.type == 
"NetworkFilesystem":
+                if not cls.nfsStorageFound and pool.type in 
("NetworkFilesystem", "RBD"):

Review Comment:
   A disabled legacy NFS pool can be selected before an active RBD pool. The 
test then tags that disabled pool and requires the same tag in its service 
offering, so deployment fails instead of exercising Ceph. Require an Up pool 
before recording this candidate.



##########
test/integration/smoke/test_backup_recovery_nas.py:
##########
@@ -303,12 +352,26 @@ def _backup_type(self, backup):
         # Backup objects expose `type`; for chained backups it's 
"INCREMENTAL", else "FULL".
         return getattr(backup, 'type', 'FULL') or 'FULL'
 
+    def _require_incremental_capable_storage(self):
+        """
+        Incremental NAS backups rely on QEMU dirty bitmaps / libvirt 
checkpoints, which
+        only exist on file-based qcow2 storage -- see
+        NASBackupProvider.allVolumesOnCheckpointCapableStorage(), which forces 
every VM
+        on RBD/Ceph (or Linstor) onto the legacy full-only path server-side. 
On such
+        storage every backup comes back FULL regardless of cadence, so these 
chain/type
+        assertions can't pass (and some would pass vacuously without 
exercising the
+        chain logic at all). Skip rather than fail when running against RBD.
+        """
+        if self.storage_pool.type.lower() == 'rbd':
+            self.skipTest("Incremental backups are not supported on RBD/Ceph 
primary Storage")

Review Comment:
   `self.storage_pool` is selected before deployment, and the VM's volumes are 
not pinned to it. With NFS listed first but either VM disk on RBD, this guard 
runs incremental tests even though the provider returns only FULL backups; the 
reverse incorrectly skips supported tests. Resolve every attached volume's 
actual pool and use the same capability predicate as 
`NASBackupProvider.allVolumesOnCheckpointCapableStorage()`.



##########
test/integration/smoke/test_vm_snapshots.py:
##########
@@ -47,6 +47,11 @@ def setUpClass(cls):
             cls.unsupportedHypervisor = True
             return
 
+        list_volume_pool_response = list_storage_pools(cls.apiclient)
+        volume_pool = list_volume_pool_response[0]
+        if volume_pool.type == "RBD":
+            cls.skipTest(cls, reason="VM snapshot is unsupported for VMs on 
RBD storage pool")

Review Comment:
   This checks the first pool across all zones, not the pool holding the test 
VM. A disabled NFS pool listed before active RBD storage lets unsupported 
snapshot tests run; an unrelated RBD pool can instead skip valid NFS coverage. 
Remove this early check and gate each test in `setUp()` using 
`self.volume_pool`, which is already resolved from the deployed VM's root 
volume. Keeping the skip in `setUp()` also allows class resource cleanup to run.



##########
test/integration/smoke/test_volumes.py:
##########
@@ -1111,6 +1117,11 @@ def setUpClass(cls):
             cls.unsupportedHypervisor = True
             return
 
+        list_volume_pool_response = list_storage_pools(cls.apiclient)
+        volume_pool = list_volume_pool_response[0]
+        if volume_pool.type == "RBD":
+            cls.skipTest(cls, reason="Volume encryption is unsupported for 
volumes on RBD storage pool")

Review Comment:
   The first global pool does not establish whether encryption is supported in 
the selected test zone. An unrelated RBD pool can skip the entire suite despite 
available encryption-capable storage, while a disabled NFS pool listed first 
leaves a Ceph-only zone unskipped and encrypted deployment fails. Resolve the 
test zone first and check eligible encryption-capable pools against the 
offerings' placement constraints before creating encrypted volumes.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to