From 250ea533441c56f643d0b6db96e8496f2f7374c8 Mon Sep 17 00:00:00 2001 From: Fabricio Duarte Date: Tue, 14 Apr 2026 16:57:42 -0300 Subject: [PATCH] Inject the template's download state in the secondary storage selectors --- .../heuristics/HeuristicRuleHelper.java | 35 +++++++++++++--- .../presetvariables/DownloadDetails.java | 42 +++++++++++++++++++ .../heuristics/presetvariables/Template.java | 23 ++++++++++ .../heuristics/HeuristicRuleHelperTest.java | 24 ++++++----- 4 files changed, 108 insertions(+), 16 deletions(-) create mode 100644 server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/DownloadDetails.java diff --git a/server/src/main/java/org/apache/cloudstack/storage/heuristics/HeuristicRuleHelper.java b/server/src/main/java/org/apache/cloudstack/storage/heuristics/HeuristicRuleHelper.java index beecf90d2b83..9dd3664a5381 100644 --- a/server/src/main/java/org/apache/cloudstack/storage/heuristics/HeuristicRuleHelper.java +++ b/server/src/main/java/org/apache/cloudstack/storage/heuristics/HeuristicRuleHelper.java @@ -35,8 +35,11 @@ import org.apache.cloudstack.secstorage.heuristics.HeuristicType; import org.apache.cloudstack.storage.datastore.db.ImageStoreDao; import org.apache.cloudstack.storage.datastore.db.ImageStoreVO; +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO; import org.apache.cloudstack.storage.heuristics.presetvariables.Account; import org.apache.cloudstack.storage.heuristics.presetvariables.Domain; +import org.apache.cloudstack.storage.heuristics.presetvariables.DownloadDetails; import org.apache.cloudstack.storage.heuristics.presetvariables.PresetVariables; import org.apache.cloudstack.storage.heuristics.presetvariables.SecondaryStorage; import org.apache.cloudstack.storage.heuristics.presetvariables.Snapshot; @@ -78,6 +81,9 @@ public class HeuristicRuleHelper { @Inject private DataCenterDao zoneDao; + @Inject + private TemplateDataStoreDao templateDataStoreDao; + /** * Returns the {@link DataStore} object if the zone, specified by the ID, has an active heuristic rule for the given {@link HeuristicType}. * It returns null otherwise. @@ -187,6 +193,23 @@ protected Template setTemplatePresetVariable(VMTemplateVO templateVO) { template.setName(templateVO.getName()); template.setFormat(templateVO.getFormat().toString()); template.setHypervisorType(templateVO.getHypervisorType().toString()); + template.setTemplateType(templateVO.getTemplateType().toString()); + template.setPublic(templateVO.isPublicTemplate()); + + List downloadDetails = new ArrayList<>(); + List templateDataStoreVOs = templateDataStoreDao.listByTemplate(templateVO.getId()); + + for (TemplateDataStoreVO templateDataStoreVO : templateDataStoreVOs) { + ImageStoreVO imageStore = imageStoreDao.findById(templateDataStoreVO.getDataStoreId()); + + DownloadDetails downloadDetail = new DownloadDetails(); + downloadDetail.setDataStoreId(imageStore.getUuid()); + downloadDetail.setDownloadState(templateDataStoreVO.getDownloadState()); + downloadDetails.add(downloadDetail); + } + + template.setDownloadDetails(downloadDetails); + return template; } @@ -248,13 +271,15 @@ protected Domain setDomainPresetVariable(long domainId) { * in the code scope. *
*
- * The JS script needs to return a valid UUID ({@link String}) of a secondary storage, otherwise a {@link CloudRuntimeException} is thrown. + * The JS script needs to either return the valid UUID ({@link String}) of a secondary storage or nothing. If a valid UUID is returned, + * this method returns the specific secondary storage; if nothing is returned, this method returns null to allow allocation in any + * available secondary storage; otherwise a {@link CloudRuntimeException} is thrown. * @param rule the {@link String} representing the JS script. * @param heuristicType used for building the preset variables accordingly to the {@link HeuristicType} specified. * @param obj can be from the following classes: {@link VMTemplateVO}, {@link SnapshotInfo} and {@link VolumeVO}. * They are used to retrieve attributes for injecting in the JS rule. * @param zoneId used for injecting the {@link SecondaryStorage} preset variables. - * @return the {@link DataStore} returned by the script. + * @return the {@link DataStore} returned by the script, or null. */ public DataStore interpretHeuristicRule(String rule, HeuristicType heuristicType, Object obj, long zoneId) { try (JsInterpreter jsInterpreter = new JsInterpreter(HEURISTICS_SCRIPT_TIMEOUT)) { @@ -262,14 +287,14 @@ public DataStore interpretHeuristicRule(String rule, HeuristicType heuristicType Object scriptReturn = jsInterpreter.executeScript(rule); if (!(scriptReturn instanceof String)) { - throw new CloudRuntimeException(String.format("Error while interpreting heuristic rule [%s], the rule did not return a String.", rule)); + logger.debug("Script did not return a string; allocating resource in any available secondary storage."); + return null; } DataStore dataStore = dataStoreManager.getImageStoreByUuid((String) scriptReturn); if (dataStore == null) { - throw new CloudRuntimeException(String.format("Unable to find a secondary storage with the UUID [%s] returned by the heuristic rule [%s]. Check if the rule is " + - "returning a valid UUID.", scriptReturn, rule)); + logger.debug("Script did not return a valid secondary storage; allocating resource in any available secondary storage."); } return dataStore; diff --git a/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/DownloadDetails.java b/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/DownloadDetails.java new file mode 100644 index 000000000000..25673ed0bdf8 --- /dev/null +++ b/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/DownloadDetails.java @@ -0,0 +1,42 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.storage.heuristics.presetvariables; + +import com.cloud.storage.VMTemplateStorageResourceAssoc; + +public class DownloadDetails extends GenericHeuristicPresetVariable { + + private String dataStoreId; + + private VMTemplateStorageResourceAssoc.Status downloadState; + + public String getDataStoreId() { + return dataStoreId; + } + + public void setDataStoreId(String dataStoreId) { + this.dataStoreId = dataStoreId; + } + + public VMTemplateStorageResourceAssoc.Status getDownloadState() { + return downloadState; + } + + public void setDownloadState(VMTemplateStorageResourceAssoc.Status downloadState) { + this.downloadState = downloadState; + } +} diff --git a/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/Template.java b/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/Template.java index c6df349010f5..b42de284f3f1 100644 --- a/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/Template.java +++ b/server/src/main/java/org/apache/cloudstack/storage/heuristics/presetvariables/Template.java @@ -16,6 +16,8 @@ // under the License. package org.apache.cloudstack.storage.heuristics.presetvariables; +import java.util.List; + public class Template extends GenericHeuristicPresetVariable { private String hypervisorType; @@ -24,6 +26,10 @@ public class Template extends GenericHeuristicPresetVariable { private String templateType; + private boolean isPublic; + + private List downloadDetails; + public String getHypervisorType() { return hypervisorType; } @@ -47,4 +53,21 @@ public String getTemplateType() { public void setTemplateType(String templateType) { this.templateType = templateType; } + + public boolean isPublic() { + return isPublic; + } + + public void setPublic(boolean isPublic) { + this.isPublic = isPublic; + } + + public List getDownloadDetails() { + return downloadDetails; + } + + public void setDownloadDetails(List downloadDetails) { + this.downloadDetails = downloadDetails; + } + } diff --git a/server/src/test/java/org/apache/cloudstack/storage/heuristics/HeuristicRuleHelperTest.java b/server/src/test/java/org/apache/cloudstack/storage/heuristics/HeuristicRuleHelperTest.java index 032e947fdce7..84c59691b224 100644 --- a/server/src/test/java/org/apache/cloudstack/storage/heuristics/HeuristicRuleHelperTest.java +++ b/server/src/test/java/org/apache/cloudstack/storage/heuristics/HeuristicRuleHelperTest.java @@ -27,6 +27,7 @@ import org.apache.cloudstack.secstorage.HeuristicVO; import org.apache.cloudstack.secstorage.dao.SecondaryStorageHeuristicDao; import org.apache.cloudstack.secstorage.heuristics.HeuristicType; +import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao; import org.apache.cloudstack.storage.heuristics.presetvariables.PresetVariables; import org.apache.cloudstack.utils.jsinterpreter.JsInterpreter; import org.apache.logging.log4j.Logger; @@ -64,6 +65,9 @@ public class HeuristicRuleHelperTest { @Mock DataStore dataStoreMock; + @Mock + TemplateDataStoreDao templateDataStoreDaoMock; + @Mock Logger loggerMock; @@ -76,6 +80,7 @@ public void setUp() { Mockito.doReturn("template-name").when(vmTemplateVOMock).getName(); Mockito.doReturn(Storage.ImageFormat.QCOW2).when(vmTemplateVOMock).getFormat(); Mockito.doReturn(Hypervisor.HypervisorType.KVM).when(vmTemplateVOMock).getHypervisorType(); + Mockito.doReturn(Storage.TemplateType.USER).when(vmTemplateVOMock).getTemplateType(); Mockito.doReturn("snapshot-name").when(snapshotInfoMock).getName(); Mockito.doReturn(1024L).when(snapshotInfoMock).getSize(); Mockito.doReturn(Hypervisor.HypervisorType.VMware).when(snapshotInfoMock).getHypervisorType(); @@ -166,31 +171,28 @@ public void buildPresetVariablesTestWithSnapshotHeuristicTypeShouldSetVolumeAndS } @Test - public void interpretHeuristicRuleTestHeuristicRuleDoesNotReturnAStringShouldThrowCloudRuntimeException() { + public void interpretHeuristicRuleTestHeuristicRuleDoesNotReturnAStringShouldReturnNull() { String heuristicRule = "1"; Mockito.doNothing().when(heuristicRuleHelperSpy).buildPresetVariables(Mockito.any(JsInterpreter.class), Mockito.any(HeuristicType.class), Mockito.anyLong(), Mockito.any()); - String expectedMessage = String.format("Error while interpreting heuristic rule [%s], the rule did not return a String.", heuristicRule); - CloudRuntimeException assertThrows = Assert.assertThrows(CloudRuntimeException.class, - () -> heuristicRuleHelperSpy.interpretHeuristicRule(heuristicRule, HeuristicType.TEMPLATE, volumeVOMock, 1L)); - Assert.assertEquals(expectedMessage, assertThrows.getMessage()); + DataStore result = heuristicRuleHelperSpy.interpretHeuristicRule(heuristicRule, HeuristicType.TEMPLATE, volumeVOMock, 1L); + + Assert.assertNull(result); } @Test - public void interpretHeuristicRuleTestHeuristicRuleReturnAStringWithInvalidUuidShouldThrowCloudRuntimeException() { + public void interpretHeuristicRuleTestHeuristicRuleReturnAStringWithInvalidUuidShouldReturnNull() { String heuristicRule = "'uuid'"; Mockito.doNothing().when(heuristicRuleHelperSpy).buildPresetVariables(Mockito.any(JsInterpreter.class), Mockito.any(HeuristicType.class), Mockito.anyLong(), Mockito.any()); Mockito.doReturn(null).when(dataStoreManagerMock).getImageStoreByUuid(Mockito.anyString()); - String expectedMessage = String.format("Unable to find a secondary storage with the UUID [%s] returned by the heuristic rule [%s]. Check if the rule is " + - "returning a valid UUID.", "uuid", heuristicRule); - CloudRuntimeException assertThrows = Assert.assertThrows(CloudRuntimeException.class, - () -> heuristicRuleHelperSpy.interpretHeuristicRule(heuristicRule, HeuristicType.TEMPLATE, volumeVOMock, 1L)); - Assert.assertEquals(expectedMessage, assertThrows.getMessage()); + DataStore result = heuristicRuleHelperSpy.interpretHeuristicRule(heuristicRule, HeuristicType.TEMPLATE, volumeVOMock, 1L); + + Assert.assertNull(result); } @Test