Skip to content

Commit f1f6cc4

Browse files
ghernadirp-
authored andcommitted
linstor: Use template's uuid if pool's downloadPath is null as resource-name
Also added an integration test for templates from snapshots
1 parent 0d5a0ea commit f1f6cc4

File tree

4 files changed

+80
-7
lines changed

4 files changed

+80
-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: 62 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -953,9 +953,66 @@ 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+
976+
self.assertIsNotNone(snapshot, "Could not create snapshot")
977+
978+
services = {
979+
"displaytext": "IntegrationTestTemplate",
980+
"name": "int-test-template",
981+
"ostypeid": self.template.ostypeid,
982+
"ispublic": "true"
983+
}
984+
985+
custom_template = Template.create_from_snapshot(
986+
self.apiClient,
987+
snapshot,
988+
services,
989+
)
990+
991+
# create VM from custom template
992+
test_virtual_machine = VirtualMachine.create(
993+
self.apiClient,
994+
self.testdata[TestData.virtualMachine2],
995+
accountid=self.account.name,
996+
zoneid=self.zone.id,
997+
serviceofferingid=self.compute_offering.id,
998+
templateid=custom_template.id,
999+
domainid=self.domain.id,
1000+
startvm=False,
1001+
mode='basic',
1002+
)
1003+
1004+
TestLinstorVolumes._start_vm(test_virtual_machine)
1005+
1006+
test_virtual_machine.stop(self.apiClient)
1007+
1008+
test_virtual_machine.delete(self.apiClient, True)
1009+
1010+
custom_template.delete(self.apiClient)
1011+
snapshot.delete(self.apiClient)
1012+
9561013

9571014
@attr(tags=['advanced', 'migration'], required_hardware=False)
958-
def test_10_migrate_volume_to_same_instance_pool(self):
1015+
def test_11_migrate_volume_to_same_instance_pool(self):
9591016
"""Migrate volume to the same instance pool"""
9601017

9611018
if not self.testdata[TestData.migrationTests]:
@@ -1088,7 +1145,7 @@ def test_10_migrate_volume_to_same_instance_pool(self):
10881145
test_virtual_machine.delete(self.apiClient, True)
10891146

10901147
@attr(tags=['advanced', 'migration'], required_hardware=False)
1091-
def test_11_migrate_volume_to_distinct_instance_pool(self):
1148+
def test_12_migrate_volume_to_distinct_instance_pool(self):
10921149
"""Migrate volume to distinct instance pool"""
10931150

10941151
if not self.testdata[TestData.migrationTests]:
@@ -1221,7 +1278,7 @@ def test_11_migrate_volume_to_distinct_instance_pool(self):
12211278
test_virtual_machine.delete(self.apiClient, True)
12221279

12231280
@attr(tags=["basic"], required_hardware=False)
1224-
def test_12_create_vm_snapshots(self):
1281+
def test_13_create_vm_snapshots(self):
12251282
"""Test to create VM snapshots
12261283
"""
12271284
vm = TestLinstorVolumes._start_vm(self.virtual_machine)
@@ -1251,7 +1308,7 @@ def test_12_create_vm_snapshots(self):
12511308
)
12521309

12531310
@attr(tags=["basic"], required_hardware=False)
1254-
def test_13_revert_vm_snapshots(self):
1311+
def test_14_revert_vm_snapshots(self):
12551312
"""Test to revert VM snapshots
12561313
"""
12571314

@@ -1313,7 +1370,7 @@ def test_13_revert_vm_snapshots(self):
13131370
)
13141371

13151372
@attr(tags=["basic"], required_hardware=False)
1316-
def test_14_delete_vm_snapshots(self):
1373+
def test_15_delete_vm_snapshots(self):
13171374
"""Test to delete vm snapshots
13181375
"""
13191376

0 commit comments

Comments
 (0)