This is an automated email from the ASF dual-hosted git repository. pearl11594 pushed a commit to branch 4.19 in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/4.19 by this push: new a4263da8aea linstor: Use template's uuid if pool's downloadPath is null as resource-name (#11053) a4263da8aea is described below commit a4263da8aea93fd12ead37c61489b9e529c0cdbc Author: ghernadi <gabor.hern...@linbit.com> AuthorDate: Fri Jul 25 13:51:11 2025 +0200 linstor: Use template's uuid if pool's downloadPath is null as resource-name (#11053) Also added an integration test for templates from snapshots --- plugins/storage/volume/linstor/CHANGELOG.md | 6 ++ .../kvm/storage/LinstorStorageAdaptor.java | 2 +- .../driver/LinstorPrimaryDataStoreDriverImpl.java | 12 +++- .../plugins/linstor/test_linstor_volumes.py | 73 ++++++++++++++++++++-- 4 files changed, 86 insertions(+), 7 deletions(-) diff --git a/plugins/storage/volume/linstor/CHANGELOG.md b/plugins/storage/volume/linstor/CHANGELOG.md index 2abda3ebc50..a96b7c75b2b 100644 --- a/plugins/storage/volume/linstor/CHANGELOG.md +++ b/plugins/storage/volume/linstor/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to Linstor CloudStack plugin will be documented in this file The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2025-07-01] + +### Fixed + +- Regression in 4.19.3 and 4.21.0 with templates from snapshots + ## [2025-05-07] ### Added diff --git a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java index 9de2479b20b..4210008f1c0 100644 --- a/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java +++ b/plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java @@ -619,7 +619,7 @@ public class LinstorStorageAdaptor implements StorageAdaptor { try { templateProps.load(new FileInputStream(propFile.toFile())); String desc = templateProps.getProperty("description"); - if (desc.startsWith("SystemVM Template")) { + if (desc != null && desc.startsWith("SystemVM Template")) { return true; } } catch (IOException e) { diff --git a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java index 3b384831518..71e1b528506 100644 --- a/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java +++ b/plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java @@ -74,12 +74,14 @@ import com.cloud.storage.StorageManager; import com.cloud.storage.StoragePool; import com.cloud.storage.VMTemplateStoragePoolVO; import com.cloud.storage.VMTemplateStorageResourceAssoc; +import com.cloud.storage.VMTemplateVO; import com.cloud.storage.Volume; import com.cloud.storage.VolumeDetailVO; import com.cloud.storage.VolumeVO; import com.cloud.storage.dao.SnapshotDao; import com.cloud.storage.dao.SnapshotDetailsDao; import com.cloud.storage.dao.SnapshotDetailsVO; +import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VMTemplatePoolDao; import com.cloud.storage.dao.VolumeDao; import com.cloud.storage.dao.VolumeDetailsDao; @@ -131,6 +133,7 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver ConfigurationDao _configDao; @Inject private HostDao _hostDao; + @Inject private VMTemplateDao _vmTemplateDao; private long volumeStatsLastUpdate = 0L; private final Map<String, Pair<Long, Long>> volumeStats = new HashMap<>(); @@ -668,8 +671,15 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver storagePoolVO.getId(), csCloneId, null); if (tmplPoolRef != null) { - final String templateRscName = LinstorUtil.RSC_PREFIX + tmplPoolRef.getLocalDownloadPath(); + final String templateRscName; + if (tmplPoolRef.getLocalDownloadPath() == null) { + VMTemplateVO vmTemplateVO = _vmTemplateDao.findById(tmplPoolRef.getTemplateId()); + templateRscName = LinstorUtil.RSC_PREFIX + vmTemplateVO.getUuid(); + } else { + templateRscName = LinstorUtil.RSC_PREFIX + tmplPoolRef.getLocalDownloadPath(); + } final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getUuid(); + final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress()); try { diff --git a/test/integration/plugins/linstor/test_linstor_volumes.py b/test/integration/plugins/linstor/test_linstor_volumes.py index a2c7978d801..e0ba15a0499 100644 --- a/test/integration/plugins/linstor/test_linstor_volumes.py +++ b/test/integration/plugins/linstor/test_linstor_volumes.py @@ -953,9 +953,72 @@ class TestLinstorVolumes(cloudstackTestCase): snapshot.delete(self.apiClient) + @attr(tags=['basic'], required_hardware=False) + def test_10_create_template_from_snapshot(self): + """ + Create a template from a snapshot and start an instance from it + """ + self.virtual_machine.stop(self.apiClient) + + volume = list_volumes( + self.apiClient, + virtualmachineid = self.virtual_machine.id, + type = "ROOT", + listall = True, + ) + snapshot = Snapshot.create( + self.apiClient, + volume_id=volume[0].id, + account=self.account.name, + domainid=self.domain.id, + ) + self.cleanup.append(snapshot) + + self.assertIsNotNone(snapshot, "Could not create snapshot") + + services = { + "displaytext": "IntegrationTestTemplate", + "name": "int-test-template", + "ostypeid": self.template.ostypeid, + "ispublic": "true" + } + + custom_template = Template.create_from_snapshot( + self.apiClient, + snapshot, + services, + ) + self.cleanup.append(custom_template) + + # create VM from custom template + test_virtual_machine = VirtualMachine.create( + self.apiClient, + self.testdata[TestData.virtualMachine2], + accountid=self.account.name, + zoneid=self.zone.id, + serviceofferingid=self.compute_offering.id, + templateid=custom_template.id, + domainid=self.domain.id, + startvm=False, + mode='basic', + ) + self.cleanup.append(test_virtual_machine) + + TestLinstorVolumes._start_vm(test_virtual_machine) + + test_virtual_machine.stop(self.apiClient) + + test_virtual_machine.delete(self.apiClient, True) + self.cleanup.remove(test_virtual_machine) + + custom_template.delete(self.apiClient) + self.cleanup.remove(custom_template) + snapshot.delete(self.apiClient) + self.cleanup.remove(snapshot) + @attr(tags=['advanced', 'migration'], required_hardware=False) - def test_10_migrate_volume_to_same_instance_pool(self): + def test_11_migrate_volume_to_same_instance_pool(self): """Migrate volume to the same instance pool""" if not self.testdata[TestData.migrationTests]: @@ -1088,7 +1151,7 @@ class TestLinstorVolumes(cloudstackTestCase): test_virtual_machine.delete(self.apiClient, True) @attr(tags=['advanced', 'migration'], required_hardware=False) - def test_11_migrate_volume_to_distinct_instance_pool(self): + def test_12_migrate_volume_to_distinct_instance_pool(self): """Migrate volume to distinct instance pool""" if not self.testdata[TestData.migrationTests]: @@ -1221,7 +1284,7 @@ class TestLinstorVolumes(cloudstackTestCase): test_virtual_machine.delete(self.apiClient, True) @attr(tags=["basic"], required_hardware=False) - def test_12_create_vm_snapshots(self): + def test_13_create_vm_snapshots(self): """Test to create VM snapshots """ vm = TestLinstorVolumes._start_vm(self.virtual_machine) @@ -1251,7 +1314,7 @@ class TestLinstorVolumes(cloudstackTestCase): ) @attr(tags=["basic"], required_hardware=False) - def test_13_revert_vm_snapshots(self): + def test_14_revert_vm_snapshots(self): """Test to revert VM snapshots """ @@ -1313,7 +1376,7 @@ class TestLinstorVolumes(cloudstackTestCase): ) @attr(tags=["basic"], required_hardware=False) - def test_14_delete_vm_snapshots(self): + def test_15_delete_vm_snapshots(self): """Test to delete vm snapshots """