Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@
public interface TemplateManager {
static final String AllowPublicUserTemplatesCK = "allow.public.user.templates";
static final String TemplatePreloaderPoolSizeCK = "template.preloader.pool.size";
static final String PublicTemplateSecStorageCopyCK = "public.template.secstorage.copy";
Comment thread
Damans227 marked this conversation as resolved.
Outdated
static final String PrivateTemplateSecStorageCopyCK = "private.template.secstorage.copy";
Comment thread
Damans227 marked this conversation as resolved.
Outdated

static final ConfigKey<Boolean> AllowPublicUserTemplates = new ConfigKey<Boolean>("Advanced", Boolean.class, AllowPublicUserTemplatesCK, "true",
"If false, users will not be able to create public Templates.", true, ConfigKey.Scope.Account);
Expand All @@ -64,6 +66,18 @@ public interface TemplateManager {
true,
ConfigKey.Scope.Global);

ConfigKey<Integer> PublicTemplateSecStorageCopy = new ConfigKey<Integer>("Advanced", Integer.class,
PublicTemplateSecStorageCopyCK, "0",
"Maximum number of secondary storage pools to which a public template is copied. " +
"0 means copy to all secondary storage pools (default behavior).",
true, ConfigKey.Scope.Zone);

ConfigKey<Integer> PrivateTemplateSecStorageCopy = new ConfigKey<Integer>("Advanced", Integer.class,
PrivateTemplateSecStorageCopyCK, "1",
"Maximum number of secondary storage pools to which a private template is copied. " +
"Default is 1 to preserve existing behavior.",
true, ConfigKey.Scope.Zone);
Comment on lines +69 to +79
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The config key descriptions don’t mention that the limits are evaluated per-zone (and the keys themselves are zone-scoped). Consider clarifying the wording (e.g., “per zone”) so operators understand how replica limits are applied across multiple zones/image stores.

Copilot uses AI. Check for mistakes.

static final String VMWARE_TOOLS_ISO = "vmware-tools.iso";
static final String XS_TOOLS_ISO = "xs-tools.iso";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import java.util.stream.Collectors;

Expand Down Expand Up @@ -292,9 +292,12 @@ protected void createTemplateWithinZones(TemplateProfile profile, VMTemplateVO t

if (imageStore == null) {
List<DataStore> imageStores = getImageStoresThrowsExceptionIfNotFound(zoneId, profile);
standardImageStoreAllocation(imageStores, template);
standardImageStoreAllocation(imageStores, template, zoneId);
} else {
validateSecondaryStorageAndCreateTemplate(List.of(imageStore), template, null);
int replicaLimit = isPrivateTemplate(template)
? TemplateManager.PrivateTemplateSecStorageCopy.valueIn(zoneId)
: TemplateManager.PublicTemplateSecStorageCopy.valueIn(zoneId);
Comment thread
Damans227 marked this conversation as resolved.
Outdated
validateSecondaryStorageAndCreateTemplate(List.of(imageStore), template, new HashMap<>(), replicaLimit);
}
}
}
Expand All @@ -307,17 +310,19 @@ protected List<DataStore> getImageStoresThrowsExceptionIfNotFound(long zoneId, T
return imageStores;
}

protected void standardImageStoreAllocation(List<DataStore> imageStores, VMTemplateVO template) {
Set<Long> zoneSet = new HashSet<Long>();
protected void standardImageStoreAllocation(List<DataStore> imageStores, VMTemplateVO template, long zoneId) {
int replicaLimit = isPrivateTemplate(template)
Comment thread
Damans227 marked this conversation as resolved.
Outdated
? TemplateManager.PrivateTemplateSecStorageCopy.valueIn(zoneId)
: TemplateManager.PublicTemplateSecStorageCopy.valueIn(zoneId);
Collections.shuffle(imageStores);
validateSecondaryStorageAndCreateTemplate(imageStores, template, zoneSet);
validateSecondaryStorageAndCreateTemplate(imageStores, template, new HashMap<>(), replicaLimit);
}

protected void validateSecondaryStorageAndCreateTemplate(List<DataStore> imageStores, VMTemplateVO template, Set<Long> zoneSet) {
protected void validateSecondaryStorageAndCreateTemplate(List<DataStore> imageStores, VMTemplateVO template, Map<Long, Integer> zoneCopyCount, int replicaLimit) {
for (DataStore imageStore : imageStores) {
Long zoneId = imageStore.getScope().getScopeId();

if (!isZoneAndImageStoreAvailable(imageStore, zoneId, zoneSet, isPrivateTemplate(template))) {
if (!isZoneAndImageStoreAvailable(imageStore, zoneId, zoneCopyCount, replicaLimit)) {
continue;
}

Expand Down
26 changes: 11 additions & 15 deletions server/src/main/java/com/cloud/template/TemplateAdapterBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,9 @@
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import javax.inject.Inject;

Expand Down Expand Up @@ -169,7 +167,7 @@ protected DataStore verifyHeuristicRulesForZone(VMTemplateVO template, Long zone
return heuristicRuleHelper.getImageStoreIfThereIsHeuristicRule(zoneId, heuristicType, template);
}

protected boolean isZoneAndImageStoreAvailable(DataStore imageStore, Long zoneId, Set<Long> zoneSet, boolean isTemplatePrivate) {
protected boolean isZoneAndImageStoreAvailable(DataStore imageStore, Long zoneId, Map<Long, Integer> zoneCopyCount, int replicaLimit) {
if (zoneId == null) {
logger.warn(String.format("Zone ID is null, cannot allocate ISO/template in image store [%s].", imageStore));
return false;
Expand All @@ -191,19 +189,13 @@ protected boolean isZoneAndImageStoreAvailable(DataStore imageStore, Long zoneId
return false;
}

if (zoneSet == null) {
logger.info(String.format("Zone set is null; therefore, the ISO/template should be allocated in every secondary storage of zone [%s].", zone));
return true;
}

if (isTemplatePrivate && zoneSet.contains(zoneId)) {
logger.info(String.format("The template is private and it is already allocated in a secondary storage in zone [%s]; therefore, image store [%s] will be skipped.",
zone, imageStore));
int currentCount = zoneCopyCount.getOrDefault(zoneId, 0);
if (replicaLimit > 0 && currentCount >= replicaLimit) {
logger.info("Replica limit of {} reached for zone [{}]; skipping image store [{}].", replicaLimit, zone, imageStore);
Comment thread
DaanHoogland marked this conversation as resolved.
Outdated
return false;
}

logger.info(String.format("Private template will be allocated in image store [%s] in zone [%s].", imageStore, zone));
zoneSet.add(zoneId);
zoneCopyCount.put(zoneId, currentCount + 1);
return true;
}

Expand All @@ -212,12 +204,16 @@ protected boolean isZoneAndImageStoreAvailable(DataStore imageStore, Long zoneId
* {@link TemplateProfile#getZoneIdList()}.
*/
protected void postUploadAllocation(List<DataStore> imageStores, VMTemplateVO template, List<TemplateOrVolumePostUploadCommand> payloads) {
Comment on lines 208 to 212
Copy link

Copilot AI Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method JavaDoc no longer matches the behavior: allocation is now governed by replica-limit config keys (public/private can both be limited), not strictly by template visibility (private=random vs public=all). Please update the comment to reflect the new replica-limit semantics (and that the limits are zone-scoped).

Copilot uses AI. Check for mistakes.
Set<Long> zoneSet = new HashSet<>();
boolean isPrivate = isPrivateTemplate(template);
Map<Long, Integer> zoneCopyCount = new HashMap<>();
Collections.shuffle(imageStores);
for (DataStore imageStore : imageStores) {
Long zoneId_is = imageStore.getScope().getScopeId();
int replicaLimit = zoneId_is == null ? 0 : (isPrivate
? TemplateManager.PrivateTemplateSecStorageCopy.valueIn(zoneId_is)
: TemplateManager.PublicTemplateSecStorageCopy.valueIn(zoneId_is));

if (!isZoneAndImageStoreAvailable(imageStore, zoneId_is, zoneSet, isPrivateTemplate(template))) {
if (!isZoneAndImageStoreAvailable(imageStore, zoneId_is, zoneCopyCount, replicaLimit)) {
continue;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2493,7 +2493,9 @@ public ConfigKey<?>[] getConfigKeys() {
return new ConfigKey<?>[] {AllowPublicUserTemplates,
TemplatePreloaderPoolSize,
ValidateUrlIsResolvableBeforeRegisteringTemplate,
TemplateDeleteFromPrimaryStorage};
TemplateDeleteFromPrimaryStorage,
PublicTemplateSecStorageCopy,
PrivateTemplateSecStorageCopy};
}

public List<TemplateAdapter> getTemplateAdapters() {
Expand Down
Loading
Loading