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


##########
test/integration/smoke/test_direct_download.py:
##########
@@ -295,82 +292,36 @@ def deployVM(self, offering) :
         self.cleanup.append(vm)
         return vm
 
-    @skipTestIf("nfsKvmNotAvailable")
+    @skipTestIf("storageKvmNotAvailable")
     @attr(tags=["advanced", "basic", "eip", "advancedns", "sg"], 
required_hardware="false")
-    def test_01_deploy_vm_from_direct_download_template_nfs_storage(self):
-        """Test Deploy VM from direct download template on NFS storage
+    def test_01_deploy_vm_from_direct_download_template(self):
+        """Test Deploy VM from direct download template on NFS, RBD, local 
storage and shared mount point
         """
 
-        # Create service offering for local storage using storage tags
-        tags = self.getCurrentStoragePoolTags(self.nfsPoolId)
-        test_tag = "marvin_test_nfs_storage_direct_download"
-        self.updateStoragePoolTags(self.nfsPoolId, test_tag)
-        nfs_storage_offering = 
self.createServiceOffering("TestNFSStorageDirectDownload", "shared", test_tag)
-
-        vm = self.deployVM(nfs_storage_offering)
-        self.assertEqual(
-            vm.state,
-            "Running",
-            "Check VM deployed from direct download template is running on NFS 
storage"
-        )
-
-        # Revert storage tags for the storage pool used in this test
-        self.updateStoragePoolTags(self.nfsPoolId, tags)
-        self.cleanup.append(nfs_storage_offering)
-        return
-
-    @skipTestIf("localStorageKvmNotAvailable")
-    @attr(tags=["advanced", "basic", "eip", "advancedns", "sg"], 
required_hardware="false")
-    def test_02_deploy_vm_from_direct_download_template_local_storage(self):
-        """Test Deploy VM from direct download template on local storage
-        """
-
-        # Create service offering for local storage using storage tags
-        tags = self.getCurrentStoragePoolTags(self.localPoolId)
-        test_tag = "marvin_test_local_storage_direct_download"
-        self.updateStoragePoolTags(self.localPoolId, test_tag)
-        local_storage_offering = 
self.createServiceOffering("TestLocalStorageDirectDownload", "local", test_tag)
-
-        # Deploy VM
-        vm = self.deployVM(local_storage_offering)
-        self.assertEqual(
-            vm.state,
-            "Running",
-            "Check VM deployed from direct download template is running on 
local storage"
-        )
-
-        # Revert storage tags for the storage pool used in this test
-        self.updateStoragePoolTags(self.localPoolId, tags)
-        self.cleanup.append(local_storage_offering)
-        return
-
-    @skipTestIf("sharedMountPointKvmNotAvailable")
-    @attr(tags=["advanced", "basic", "eip", "advancedns", "sg"], 
required_hardware="false")
-    def 
test_03_deploy_vm_from_direct_download_template_shared_mount_point_storage(self):
-        """Test Deploy VM from direct download template on shared mount point
-        """
-
-        # Create service offering for local storage using storage tags
-        tags = self.getCurrentStoragePoolTags(self.sharedPoolId)
-        test_tag = "marvin_test_shared_mount_point_storage_direct_download"
-        self.updateStoragePoolTags(self.sharedPoolId, test_tag)
-        shared_offering = 
self.createServiceOffering("TestSharedMountPointStorageDirectDownload", 
"shared", test_tag)
-
-        # Deploy VM
-        vm = VirtualMachine.create(
-            self.apiclient,
-            self.services["virtual_machine"],
-            serviceofferingid=shared_offering.id,
-            networkids=self.l2_network.id,
-        )
-        self.assertEqual(
-            vm.state,
-            "Running",
-            "Check VM deployed from direct download template is running on 
shared mount point"
-        )
-
-        # Revert storage tags for the storage pool used in this test
-        self.updateStoragePoolTags(self.sharedPoolId, tags)
-        self.cleanup.append(vm)
-        self.cleanup.append(shared_offering)
-        return
+        storages = []
+        for pool_type, pool_id in self.sharedPoolIds.items():
+            storages.append((pool_type, pool_id, "shared"))
+        if self.localStorageFound:
+            storages.append(("Filesystem", self.localPoolId, "local"))
+
+        for pool_type, pool_id, offering_type in storages:
+            self.debug("Deploying VM from direct download template on %s 
storage" % pool_type)
+
+            # Create service offering for the storage using the storage pool 
type as storage tag
+            tags = self.getCurrentStoragePoolTags(pool_id)
+            test_tag = pool_type.lower()

Review Comment:
   Using `pool_type.lower()` as the temporary storage tag is very generic 
(e.g., `rbd`, `networkfilesystem`, `filesystem`) and increases the chance of 
colliding with existing environment tags or parallel test runs that also rely 
on tags. Prefer a unique, test-scoped tag (e.g., prefix with 
`marvin_test_direct_download_...` and optionally add a random suffix) while 
still restoring the original tags in `finally`.



##########
test/integration/smoke/test_backup_recovery_nas.py:
##########
@@ -72,14 +113,22 @@ def setUpClass(cls):
 
         cls._cleanup = [cls.account]
 
-        # Create NAS backup repository and offering. Use the same directory as 
the storage pool
+        # Create NAS backup repository and offering.
         cls.backup_repository = BackupRepository.add(cls.api_client, 
zoneid=cls.zone.id, name="Nas",
-                                                     
address=cls.storage_pool.ipaddress + ":" + cls.storage_pool.path,
+                                                     
address=cls.nas_repository_address,
                                                      provider="nas", 
type="nfs",)
         cls._cleanup.append(cls.backup_repository)
+        # Match the external offering to the repository just created above by 
externalid
+        # (== the repository's own id for the nas provider) rather than 
blindly taking
+        # index 0 -- a stray repository left over from an earlier interrupted 
run (e.g.
+        # one whose backups didn't get cleaned up, so its own teardown 
couldn't remove
+        # it either) sorts alongside the new one, and index 0 has no guarantee 
of being
+        # the one this run owns.
         cls.provider_offerings = BackupOffering.listExternal(cls.api_client, 
cls.zone.id)
-        cls.backup_offering = BackupOffering.importExisting(cls.api_client, 
cls.zone.id, cls.provider_offerings[0].externalid,
-                                                            
cls.provider_offerings[0].name, cls.provider_offerings[0].description)
+        matching_offerings = [o for o in cls.provider_offerings if 
o.externalid == cls.backup_repository.id]
+        provider_offering = matching_offerings[0] if matching_offerings else 
cls.provider_offerings[0]
+        cls.backup_offering = BackupOffering.importExisting(cls.api_client, 
cls.zone.id, provider_offering.externalid,
+                                                            
provider_offering.name, provider_offering.description)

Review Comment:
   `BackupOffering.listExternal(...)` can return `None`/empty in some 
environments or transiently; indexing `cls.provider_offerings[0]` will raise at 
runtime. Add an explicit assertion/skip with a clear message when no external 
offerings are returned, or poll/retry `listExternal` for a short period after 
creating the repository before selecting the offering.



##########
test/integration/smoke/test_list_storage_pools.py:
##########
@@ -62,26 +62,31 @@ def test_01_list_storage_pools_clusterid_filter(self):
             isinstance(storage_pools, list),
             "Storage pool response type should be a list"
         )
-        self.assertGreater(
-            len(storage_pools),
-            0,
-            "Length of storage pools should greater than 0"
-        )
+        # Filter may return zone-wide pools (with clusterid=None) alongside 
cluster-scoped pools
+        cluster_scoped_found = False
         for storage_pool in storage_pools:
-            self.assertEqual(
-                storage_pool.clusterid,
-                self.cluster.id,
-                "Cluster id should be equal to the cluster id passed in the 
filter"
-            )
+            if storage_pool.clusterid is not None:
+                # Only assert clusterid for cluster-scoped pools
+                cluster_scoped_found = True
+                self.assertEqual(
+                    storage_pool.clusterid,
+                    self.cluster.id,
+                    "Cluster id should be equal to the cluster id passed in 
the filter"
+                )

Review Comment:
   `cluster_scoped_found` is set but never asserted/used, so the test can pass 
even when the cluster filter returns only zone-wide pools (i.e., it becomes 
non-validating). To preserve test value, either (a) `skipTest` when no 
cluster-scoped pools exist in the environment, or (b) assert 
`cluster_scoped_found` is true when `storage_pools` is non-empty and the 
environment is expected to have cluster-scoped pools.



##########
test/integration/smoke/test_vm_life_cycle.py:
##########
@@ -1707,12 +1708,17 @@ def get_target_host(self, virtualmachineid):
 
         return target_hosts[0]
 
-    def get_target_pool(self, volid):
-        target_pools = StoragePool.listForMigration(self.apiclient, id=volid)
+    def get_target_pool(self, vol):
+        target_pools = StoragePool.listForMigration(self.apiclient, id=vol.id)
 
         if target_pools is None or len(target_pools) == 0:
             self.skipTest("Not enough storage pools found for migration")
 
+        volume = Volume.list(self.apiclient, id=vol.id)[0]
+        source_pool = list_storage_pools(self.apiclient, 
id=volume.storageid)[0]
+        if source_pool.type == 'RBD' and target_pools[0].type == 'RBD':
+            self.skipTest("Live VM migration between RBD pools is unsupported")

Review Comment:
   The skip decision is based only on `target_pools[0]`. If the first candidate 
happens to be an RBD pool but non-RBD candidates exist later in the list, the 
test will be skipped even though a supported target is available. Consider 
filtering `target_pools` to prefer a non-RBD target when the source is RBD, and 
only skip if *all* candidates are RBD.



##########
test/integration/smoke/test_list_storage_pools.py:
##########
@@ -264,26 +269,31 @@ def test_07_list_storage_pools_podid_filter(self):
             isinstance(storage_pools, list),
             "Storage pool response type should be a list"
         )
-        self.assertGreater(
-            len(storage_pools),
-            0,
-            "Length of storage pools should greater than 0"
-        )
+        # Filter may return zone-wide pools (with podid=None) alongside 
pod-scoped pools
+        pod_scoped_found = False
         for storage_pool in storage_pools:
-            self.assertEqual(
-                storage_pool.podid,
-                self.cluster.podid,
-                "Pod id should be equal to the pod id passed in the filter"
-            )
+            if storage_pool.podid is not None:
+                # Only assert podid for pod-scoped pools
+                pod_scoped_found = True
+                self.assertEqual(
+                    storage_pool.podid,
+                    self.cluster.podid,
+                    "Pod id should be equal to the pod id passed in the filter"
+                )
 

Review Comment:
   Same issue as the clusterid filter test: `pod_scoped_found` is never 
asserted/used, so the test can pass without validating the pod filter behavior. 
Consider skipping when no pod-scoped pools are present, or assert that at least 
one pod-scoped pool was returned for a valid `podid` filter.



##########
test/integration/smoke/test_volumes.py:
##########
@@ -1640,14 +1743,25 @@ def check_volume_encryption(self, virtual_machine, 
volumes_count):
         parser = etree.XMLParser(remove_blank_text=True)
         virshxml_root = ET.fromstring(xml_as_str, parser=parser)
 
-        encryption_format = 
virshxml_root.findall(".devices/disk/encryption[@format='luks']")
-        self.assertIsNotNone(encryption_format, "The volume encryption format 
is not luks")
+        # RBD volumes are encrypted natively by librbd: <encryption 
format='luks2' engine='librbd'>.
+        # Any other pool type keeps using qemu-native LUKS1: <encryption 
format='luks'> (no engine).
+        encryption_format_value = 'luks2' if self.storage_pool_type == "RBD" 
else 'luks'
+        encryption_format = 
virshxml_root.findall(".devices/disk/encryption[@format='%s']" % 
encryption_format_value)
+        self.assertIsNotNone(encryption_format, "The volume encryption format 
is not %s" % encryption_format_value)

Review Comment:
   `ElementTree.findall(...)` always returns a list (possibly empty), so 
`assertIsNotNone(encryption_format, ...)` is ineffective and will never fail. 
To make the failure mode clearer (and avoid relying on the later `len(...)` 
assertion), assert that the list is non-empty (e.g., `assertGreater(len(...), 
0)` / `assertTrue(encryption_format)`) and keep the more specific error message.



##########
test/integration/smoke/test_volumes.py:
##########
@@ -1599,10 +1609,90 @@ def test_03_root_and_data_volume_encryption(self):
 
         self.check_volume_encryption(virtual_machine, 3)
 
+        # Resize the attached encrypted data volume, online then offline, 
reusing this VM
+        # instead of provisioning a separate one just for resize coverage.
+        #
+        # Online (VM stays running): for RBD this is routed through virsh 
blockresize; for
+        # any other pool type it goes through the same virsh blockresize 
NOTIFYONLY path used
+        # for qemu-native LUKS.
+        disk_offering_encrypt_2gb = DiskOffering.create(
+            self.apiclient,
+            self.services["disk_offering"],
+            name="Encrypted-2GB",
+            disksize=2,
+            encrypt=True
+        )
+        self.cleanup.append(disk_offering_encrypt_2gb)
+
+        volume.resize(self.apiclient, 
diskofferingid=disk_offering_encrypt_2gb.id)
+
+        # Online resize only grows the block device live; picking up the new 
capacity in the
+        # guest still needs a rescan or reboot depending on the guest 
OS/kernel (this template's
+        # CentOS 5.5 predates reliable virtio-blk live-capacity notification), 
so verify success
+        # through the volume's own reported size here. Guest-visible size is 
verified below after
+        # the offline resize, once the volume is freshly re-attached.
+        list_volume_response = Volume.list(self.apiclient, id=volume.id)
+        expected_raw_size = disk_offering_encrypt_2gb.disksize << 30
+        if self.storage_pool_type == "RBD" and 
getattr(list_volume_response[0], "encryptformat", None):
+            expected_raw_size += (16 << 20)
+        self.assertEqual(
+            int(list_volume_response[0].size),
+            expected_raw_size,
+            "Check that the online resize grew the volume to the expected size"
+        )
+        self.assertEqual(list_volume_response[0].state, 'Ready', "Check that 
the online-resized volume is Ready")
+
+        # Offline (volume detached first): for RBD this uses `rbd resize
+        # --encryption-passphrase-file`; for any other pool type it uses 
qemu-img
+        # (LibvirtResizeVolumeCommandWrapper.resizeEncryptedQcowFile).
+        virtual_machine.detach_volume(self.apiclient, volume)
+
+        disk_offering_encrypt_3gb = DiskOffering.create(
+            self.apiclient,
+            self.services["disk_offering"],
+            name="Encrypted-3GB",
+            disksize=3,
+            encrypt=True
+        )
+        self.cleanup.append(disk_offering_encrypt_3gb)
+
+        volume.resize(self.apiclient, 
diskofferingid=disk_offering_encrypt_3gb.id)
+
+        virtual_machine.attach_volume(self.apiclient, volume)
+        self.check_volume_encryption(virtual_machine, 3)
+
+        ssh = virtual_machine.get_ssh_client(reconnect=True)
+        list_volume_response = Volume.list(self.apiclient, id=volume.id)
+        vol_sz = self.get_expected_guest_visible_size(list_volume_response[0], 
list_volume_response[0].size)
+        # After a detach + reattach, libvirt is not guaranteed to reuse the 
same target device
+        # letter the volume had before (it may pick a fresh one), so the 
offering's deviceid no
+        # longer reliably maps to /dev/vdX here. Match by the reported byte 
count instead.
+        fdisk_output = ssh.execute("/sbin/fdisk -l | grep Disk")
+        self.debug("Offline resize - Volume Size Expected %s - guest disks: 
%s" % (vol_sz, fdisk_output))
+        self.assertTrue(
+            any(" %s bytes" % vol_sz in line for line in fdisk_output),
+            "Check if the offline-resized encrypted volume size is available 
in the guest"
+        )
+
         virtual_machine.detach_volume(self.apiclient, volume)
-        self.assertEqual(ret[0], SUCCESS, "Check if promised disk size 
actually available")
         time.sleep(self.services["sleep"])
 
+    def get_expected_guest_visible_size(self, volume, vol_sz):
+        """Some pool types/encryption mechanisms reserve overhead out of the 
requested size, so the
+        guest sees fewer usable bytes than the volume's nominal size. Returns 
the byte count the
+        guest should actually report for the given volume.
+        """
+        list_volume_pool_response = list_storage_pools(self.apiclient, 
id=volume.storageid)
+        volume_pool = list_volume_pool_response[0]
+        vol_sz = int(vol_sz)
+        if volume_pool.type.lower() == "powerflex":
+            vol_sz = vol_sz - (128 << 20) - ((vol_sz >> 30) * 200704)
+        elif volume_pool.type == "RBD" and getattr(volume, "encryptformat", 
None):

Review Comment:
   Storage pool type comparisons mix case-insensitive (`type.lower() == ...`) 
and case-sensitive (`type == \"RBD\"`) checks in the same helper. This makes 
the test brittle to API casing changes and inconsistent with the rest of the 
file’s usage. Normalize once (e.g., `pool_type = volume_pool.type.lower()`) and 
compare using a single convention (`pool_type == \"rbd\"`).



##########
test/integration/smoke/test_snapshots.py:
##########
@@ -262,7 +262,7 @@ def test_02_list_snapshots_with_removed_data_store(self):
         volume_pool_response = list_storage_pools(self.apiclient,
                                                     id=vol_res[0].storageid)
         volume_pool = volume_pool_response[0]
-        if volume_pool.type.lower() != 'networkfilesystem':
+        if volume_pool.type not in ("NetworkFilesystem", "RBD"):

Review Comment:
   This check switched from case-insensitive (`type.lower()`) to case-sensitive 
matching. Elsewhere in the tests, pool types are often normalized via 
`.lower()`. For consistency and to avoid brittle behavior if the API returns 
different casing, normalize before comparing (e.g., compare against lowercased 
constants).



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