Skip to content

Commit a4263da

Browse files
authored
linstor: Use template's uuid if pool's downloadPath is null as resource-name (#11053)
Also added an integration test for templates from snapshots
1 parent 75a2b3c commit a4263da

File tree

4 files changed

+86
-7
lines changed

4 files changed

+86
-7
lines changed

plugins/storage/volume/linstor/CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@ All notable changes to Linstor CloudStack plugin will be documented in this file
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [2025-07-01]
9+
10+
### Fixed
11+
12+
- Regression in 4.19.3 and 4.21.0 with templates from snapshots
13+
814
## [2025-05-07]
915

1016
### Added

plugins/storage/volume/linstor/src/main/java/com/cloud/hypervisor/kvm/storage/LinstorStorageAdaptor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,7 @@ private static boolean isSystemTemplate(KVMPhysicalDisk disk) {
619619
try {
620620
templateProps.load(new FileInputStream(propFile.toFile()));
621621
String desc = templateProps.getProperty("description");
622-
if (desc.startsWith("SystemVM Template")) {
622+
if (desc != null && desc.startsWith("SystemVM Template")) {
623623
return true;
624624
}
625625
} catch (IOException e) {

plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,14 @@
7474
import com.cloud.storage.StoragePool;
7575
import com.cloud.storage.VMTemplateStoragePoolVO;
7676
import com.cloud.storage.VMTemplateStorageResourceAssoc;
77+
import com.cloud.storage.VMTemplateVO;
7778
import com.cloud.storage.Volume;
7879
import com.cloud.storage.VolumeDetailVO;
7980
import com.cloud.storage.VolumeVO;
8081
import com.cloud.storage.dao.SnapshotDao;
8182
import com.cloud.storage.dao.SnapshotDetailsDao;
8283
import com.cloud.storage.dao.SnapshotDetailsVO;
84+
import com.cloud.storage.dao.VMTemplateDao;
8385
import com.cloud.storage.dao.VMTemplatePoolDao;
8486
import com.cloud.storage.dao.VolumeDao;
8587
import com.cloud.storage.dao.VolumeDetailsDao;
@@ -131,6 +133,7 @@ public class LinstorPrimaryDataStoreDriverImpl implements PrimaryDataStoreDriver
131133
ConfigurationDao _configDao;
132134
@Inject
133135
private HostDao _hostDao;
136+
@Inject private VMTemplateDao _vmTemplateDao;
134137

135138
private long volumeStatsLastUpdate = 0L;
136139
private final Map<String, Pair<Long, Long>> volumeStats = new HashMap<>();
@@ -668,8 +671,15 @@ private String cloneResource(long csCloneId, VolumeInfo volumeInfo, StoragePoolV
668671
storagePoolVO.getId(), csCloneId, null);
669672

670673
if (tmplPoolRef != null) {
671-
final String templateRscName = LinstorUtil.RSC_PREFIX + tmplPoolRef.getLocalDownloadPath();
674+
final String templateRscName;
675+
if (tmplPoolRef.getLocalDownloadPath() == null) {
676+
VMTemplateVO vmTemplateVO = _vmTemplateDao.findById(tmplPoolRef.getTemplateId());
677+
templateRscName = LinstorUtil.RSC_PREFIX + vmTemplateVO.getUuid();
678+
} else {
679+
templateRscName = LinstorUtil.RSC_PREFIX + tmplPoolRef.getLocalDownloadPath();
680+
}
672681
final String rscName = LinstorUtil.RSC_PREFIX + volumeInfo.getUuid();
682+
673683
final DevelopersApi linstorApi = LinstorUtil.getLinstorAPI(storagePoolVO.getHostAddress());
674684

675685
try {

test/integration/plugins/linstor/test_linstor_volumes.py

Lines changed: 68 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -953,9 +953,72 @@ def test_09_create_snapshot(self):
953953

954954
snapshot.delete(self.apiClient)
955955

956+
@attr(tags=['basic'], required_hardware=False)
957+
def test_10_create_template_from_snapshot(self):
958+
"""
959+
Create a template from a snapshot and start an instance from it
960+
"""
961+
self.virtual_machine.stop(self.apiClient)
962+
963+
volume = list_volumes(
964+
self.apiClient,
965+
virtualmachineid = self.virtual_machine.id,
966+
type = "ROOT",
967+
listall = True,
968+
)
969+
snapshot = Snapshot.create(
970+
self.apiClient,
971+
volume_id=volume[0].id,
972+
account=self.account.name,
973+
domainid=self.domain.id,
974+
)
975+
self.cleanup.append(snapshot)
976+
977+
self.assertIsNotNone(snapshot, "Could not create snapshot")
978+
979+
services = {
980+
"displaytext": "IntegrationTestTemplate",
981+
"name": "int-test-template",
982+
"ostypeid": self.template.ostypeid,
983+
"ispublic": "true"
984+
}
985+
986+
custom_template = Template.create_from_snapshot(
987+
self.apiClient,
988+
snapshot,
989+
services,
990+
)
991+
self.cleanup.append(custom_template)
992+
993+
# create VM from custom template
994+
test_virtual_machine = VirtualMachine.create(
995+
self.apiClient,
996+
self.testdata[TestData.virtualMachine2],
997+
accountid=self.account.name,
998+
zoneid=self.zone.id,
999+
serviceofferingid=self.compute_offering.id,
1000+
templateid=custom_template.id,
1001+
domainid=self.domain.id,
1002+
startvm=False,
1003+
mode='basic',
1004+
)
1005+
self.cleanup.append(test_virtual_machine)
1006+
1007+
TestLinstorVolumes._start_vm(test_virtual_machine)
1008+
1009+
test_virtual_machine.stop(self.apiClient)
1010+
1011+
test_virtual_machine.delete(self.apiClient, True)
1012+
self.cleanup.remove(test_virtual_machine)
1013+
1014+
custom_template.delete(self.apiClient)
1015+
self.cleanup.remove(custom_template)
1016+
snapshot.delete(self.apiClient)
1017+
self.cleanup.remove(snapshot)
1018+
9561019

9571020
@attr(tags=['advanced', 'migration'], required_hardware=False)
958-
def test_10_migrate_volume_to_same_instance_pool(self):
1021+
def test_11_migrate_volume_to_same_instance_pool(self):
9591022
"""Migrate volume to the same instance pool"""
9601023

9611024
if not self.testdata[TestData.migrationTests]:
@@ -1088,7 +1151,7 @@ def test_10_migrate_volume_to_same_instance_pool(self):
10881151
test_virtual_machine.delete(self.apiClient, True)
10891152

10901153
@attr(tags=['advanced', 'migration'], required_hardware=False)
1091-
def test_11_migrate_volume_to_distinct_instance_pool(self):
1154+
def test_12_migrate_volume_to_distinct_instance_pool(self):
10921155
"""Migrate volume to distinct instance pool"""
10931156

10941157
if not self.testdata[TestData.migrationTests]:
@@ -1221,7 +1284,7 @@ def test_11_migrate_volume_to_distinct_instance_pool(self):
12211284
test_virtual_machine.delete(self.apiClient, True)
12221285

12231286
@attr(tags=["basic"], required_hardware=False)
1224-
def test_12_create_vm_snapshots(self):
1287+
def test_13_create_vm_snapshots(self):
12251288
"""Test to create VM snapshots
12261289
"""
12271290
vm = TestLinstorVolumes._start_vm(self.virtual_machine)
@@ -1251,7 +1314,7 @@ def test_12_create_vm_snapshots(self):
12511314
)
12521315

12531316
@attr(tags=["basic"], required_hardware=False)
1254-
def test_13_revert_vm_snapshots(self):
1317+
def test_14_revert_vm_snapshots(self):
12551318
"""Test to revert VM snapshots
12561319
"""
12571320

@@ -1313,7 +1376,7 @@ def test_13_revert_vm_snapshots(self):
13131376
)
13141377

13151378
@attr(tags=["basic"], required_hardware=False)
1316-
def test_14_delete_vm_snapshots(self):
1379+
def test_15_delete_vm_snapshots(self):
13171380
"""Test to delete vm snapshots
13181381
"""
13191382

0 commit comments

Comments
 (0)