linstor: Use template's uuid if pool's downloadPath is null as resource-name (#11053)

Also added an integration test for templates from snapshots
This commit is contained in:
ghernadi 2025-07-25 13:51:11 +02:00 committed by GitHub
parent 75a2b3cc54
commit a4263da8ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 86 additions and 7 deletions

View File

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

View File

@ -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) {

View File

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

View File

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