You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by da...@apache.org on 2026/10/07 12:35:30 UTC
(cloudstack) branch fix-npe-external-template-no-store created (now ed9571df61e)
This is an automated email from the ASF dual-hosted git repository.
DaanHoogland pushed a change to branch fix-npe-external-template-no-store
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
at ed9571df61e Fix listTemplates NPE for External templates
This branch includes the following new commits:
new ed9571df61e Fix listTemplates NPE for External templates
The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
(cloudstack) 01/01: Fix listTemplates NPE for External templates
Posted by da...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
DaanHoogland pushed a commit to branch fix-npe-external-template-no-store
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
commit ed9571df61e0b31bd418338f334cd0dcfa82dec1
Author: Daan Hoogland <da...@apache.org>
AuthorDate: Wed Oct 7 14:33:39 2026 +0200
Fix listTemplates NPE for External templates
---
.../storage/datastore/db/TemplateDataStoreVO.java | 8 +++++++
.../cloud/api/query/dao/TemplateJoinDaoImpl.java | 14 ++++++++++--
.../api/query/dao/TemplateJoinDaoImplTest.java | 26 ++++++++++++++++++++++
3 files changed, 46 insertions(+), 2 deletions(-)
diff --git a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/TemplateDataStoreVO.java b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/TemplateDataStoreVO.java
index a6e7a5a4fea..3097378cb81 100644
--- a/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/TemplateDataStoreVO.java
+++ b/engine/schema/src/main/java/org/apache/cloudstack/storage/datastore/db/TemplateDataStoreVO.java
@@ -184,6 +184,14 @@ public class TemplateDataStoreVO implements StateObject<ObjectInDataStoreStateMa
return dataStoreId;
}
+ /**
+ * Baremetal and External templates are registered with a store-less entry (store_id NULL),
+ * for which {@link #getDataStoreId()} cannot be called.
+ */
+ public boolean hasDataStore() {
+ return dataStoreId != null;
+ }
+
public void setDataStoreId(long storeId) {
dataStoreId = storeId;
}
diff --git a/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java b/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java
index bd73fee5c59..27ad87e27cb 100644
--- a/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java
+++ b/server/src/main/java/com/cloud/api/query/dao/TemplateJoinDaoImpl.java
@@ -209,11 +209,21 @@ public class TemplateJoinDaoImpl extends GenericDaoBaseWithTagInformation<Templa
return templateStatus.getStatus();
}
+ /**
+ * Lists the not bypassed store entries of a template, leaving out store-less entries (store_id NULL)
+ * as created for Baremetal and External templates. These are returned when storeIds is empty.
+ */
+ protected List<TemplateDataStoreVO> listTemplatesInImageStores(long templateId, Long... storeIds) {
+ return _templateStoreDao.listByTemplateNotBypassed(templateId, storeIds).stream()
+ .filter(TemplateDataStoreVO::hasDataStore)
+ .collect(Collectors.toList());
+ }
+
@Override
public TemplateResponse newTemplateResponse(EnumSet<ApiConstants.DomainDetails> detailsView, ResponseView view, TemplateJoinVO template) {
List<ImageStoreVO> storesInZone = dataStoreDao.listStoresByZoneId(template.getDataCenterId());
Long[] storeIds = storesInZone.stream().map(ImageStoreVO::getId).toArray(Long[]::new);
- List<TemplateDataStoreVO> templatesInStore = _templateStoreDao.listByTemplateNotBypassed(template.getId(), storeIds);
+ List<TemplateDataStoreVO> templatesInStore = listTemplatesInImageStores(template.getId(), storeIds);
List<Long> dataStoreIdList = templatesInStore.stream().map(TemplateDataStoreVO::getDataStoreId).collect(Collectors.toList());
Map<Long, ImageStoreVO> imageStoreMap = dataStoreDao.listByIds(dataStoreIdList).stream().collect(Collectors.toMap(ImageStoreVO::getId, imageStore -> imageStore));
@@ -532,7 +542,7 @@ public class TemplateJoinDaoImpl extends GenericDaoBaseWithTagInformation<Templa
isoResponse.setStatus(templateStatus);
}
isoResponse.setUrl(iso.getUrl());
- List<TemplateDataStoreVO> isosInStore = _templateStoreDao.listByTemplateNotBypassed(iso.getId());
+ List<TemplateDataStoreVO> isosInStore = listTemplatesInImageStores(iso.getId());
List<Map<String, String>> downloadProgressDetails = new ArrayList<>();
HashMap<String, String> downloadDetailInImageStores = null;
for (TemplateDataStoreVO isoInStore : isosInStore) {
diff --git a/server/src/test/java/com/cloud/api/query/dao/TemplateJoinDaoImplTest.java b/server/src/test/java/com/cloud/api/query/dao/TemplateJoinDaoImplTest.java
index 94d6722a7c5..6b5e30e734f 100755
--- a/server/src/test/java/com/cloud/api/query/dao/TemplateJoinDaoImplTest.java
+++ b/server/src/test/java/com/cloud/api/query/dao/TemplateJoinDaoImplTest.java
@@ -27,6 +27,8 @@ import com.cloud.template.TemplateManager;
import com.cloud.user.Account;
import org.apache.cloudstack.api.response.TemplateResponse;
import org.apache.cloudstack.api.response.VnfTemplateResponse;
+import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
+import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
@@ -39,6 +41,7 @@ import org.springframework.test.util.ReflectionTestUtils;
import java.util.Arrays;
import java.util.Date;
+import java.util.List;
import java.util.Map;
@RunWith(MockitoJUnitRunner.class)
@@ -53,6 +56,9 @@ public class TemplateJoinDaoImplTest extends GenericDaoBaseWithTagInformationBas
@Mock
private VnfTemplateDetailsDao vnfTemplateDetailsDao;
+ @Mock
+ private TemplateDataStoreDao templateDataStoreDao;
+
private TemplateJoinVO template = new TemplateJoinVO();
private TemplateResponse templateResponse = new TemplateResponse();
@@ -150,4 +156,24 @@ public class TemplateJoinDaoImplTest extends GenericDaoBaseWithTagInformationBas
Assert.assertEquals(3, ((VnfTemplateResponse)response).getVnfDetails().size());
}
+
+ @Test
+ public void testListTemplatesInImageStoresSkipsStorelessEntry() {
+ TemplateDataStoreVO storeless = new TemplateDataStoreVO(null, templateId);
+ TemplateDataStoreVO inStore = new TemplateDataStoreVO(5L, templateId);
+ Mockito.doReturn(Arrays.asList(storeless, inStore)).when(templateDataStoreDao).listByTemplateNotBypassed(templateId);
+
+ List<TemplateDataStoreVO> result = _templateJoinDaoImpl.listTemplatesInImageStores(templateId);
+
+ Assert.assertEquals(1, result.size());
+ Assert.assertEquals(5L, result.get(0).getDataStoreId());
+ }
+
+ @Test
+ public void testListTemplatesInImageStoresOnlyStorelessEntry() {
+ TemplateDataStoreVO storeless = new TemplateDataStoreVO(null, templateId);
+ Mockito.doReturn(List.of(storeless)).when(templateDataStoreDao).listByTemplateNotBypassed(templateId);
+
+ Assert.assertTrue(_templateJoinDaoImpl.listTemplatesInImageStores(templateId).isEmpty());
+ }
}