From 1c448d32b57b2cacb1afe4866b9aece4e0b68cf6 Mon Sep 17 00:00:00 2001 From: Harikrishna Patnala Date: Thu, 18 Dec 2025 15:33:24 +0530 Subject: [PATCH] Allow copy of templates from secondary storages of other zone when adding a new secondary storage --- .../com/cloud/storage/StorageManager.java | 5 +- .../storage/image/TemplateServiceImpl.java | 101 +++++++++++++++-- .../image/TemplateServiceImplTest.java | 104 ++++++++++++++++++ .../com/cloud/storage/StorageManagerImpl.java | 2 +- 4 files changed, 200 insertions(+), 12 deletions(-) diff --git a/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java b/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java index de0cb34d63ee..21a0e1f15ddc 100644 --- a/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java +++ b/engine/components-api/src/main/java/com/cloud/storage/StorageManager.java @@ -220,8 +220,9 @@ public interface StorageManager extends StorageService { "storage.pool.host.connect.workers", "1", "Number of worker threads to be used to connect hosts to a primary storage", true); - ConfigKey COPY_PUBLIC_TEMPLATES_FROM_OTHER_STORAGES = new ConfigKey<>(Boolean.class, "copy.public.templates.from.other.storages", - "Storage", "true", "Allow SSVMs to try copying public templates from one secondary storage to another instead of downloading them from the source.", + ConfigKey COPY_TEMPLATES_FROM_OTHER_SECONDARY_STORAGES = new ConfigKey<>(Boolean.class, "copy.templates.from.other.secondary.storages", + "Storage", "true", "Allow templates to be copied from existing Secondary Storage servers (within the same zone or across zones) " + + "when adding a new Secondary Storage, instead of downloading them from the source URL.", true, ConfigKey.Scope.Zone, null); /** diff --git a/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/TemplateServiceImpl.java b/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/TemplateServiceImpl.java index fd723b8bf34f..c2cbd2e46c95 100644 --- a/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/TemplateServiceImpl.java +++ b/engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/TemplateServiceImpl.java @@ -31,6 +31,7 @@ import javax.inject.Inject; +import org.apache.cloudstack.context.CallContext; import org.apache.cloudstack.engine.orchestration.service.StorageOrchestrationService; import org.apache.cloudstack.engine.subsystem.api.storage.CopyCommandResult; import org.apache.cloudstack.engine.subsystem.api.storage.CreateCmdResult; @@ -615,28 +616,110 @@ protected void tryDownloadingTemplateToImageStore(VMTemplateVO tmplt, DataStore } protected boolean tryCopyingTemplateToImageStore(VMTemplateVO tmplt, DataStore destStore) { - Long zoneId = destStore.getScope().getScopeId(); - List storesInZone = _storeMgr.getImageStoresByZoneIds(zoneId); - for (DataStore sourceStore : storesInZone) { + Long destZoneId = destStore.getScope().getScopeId(); + + List storesInSameZone = _storeMgr.getImageStoresByZoneIds(destZoneId); + if (searchAndCopyWithinZone(tmplt, destStore, storesInSameZone)) { + return true; + } + + logger.debug("Template [{}] not found in any image store of zone [{}]. Checking other zones", + tmplt.getUniqueName(), destZoneId); + + return searchAndCopyAcrossZones(tmplt, destStore, destZoneId); + } + + private boolean searchAndCopyAcrossZones(VMTemplateVO tmplt, DataStore destStore, Long destZoneId) { + List allZoneIds = _dcDao.listAllIds(); + for (Long otherZoneId : allZoneIds) { + if (otherZoneId.equals(destZoneId)) { + continue; + } + + List storesInOtherZone = _storeMgr.getImageStoresByZoneIds(otherZoneId); + logger.debug("Checking zone [{}] for template [{}]...", otherZoneId, tmplt.getUniqueName()); + + if (storesInOtherZone == null || storesInOtherZone.isEmpty()) { + logger.debug("Zone [{}] has no image stores. Skipping.", otherZoneId); + continue; + } + + DataStore sourceStore = findTemplateInStores(tmplt, storesInOtherZone); + if (sourceStore == null) { + logger.debug("Template [{}] not found in any image store of zone [{}].", + tmplt.getUniqueName(), otherZoneId); + continue; + } + + logger.info("Template [{}] found in zone [{}]. Initiating cross-zone copy to zone [{}].", + tmplt.getUniqueName(), otherZoneId, destZoneId); + + return copyTemplateAcrossZones(sourceStore, destStore, tmplt); + } + + logger.debug("Template [{}] was not found in any zone. Cannot perform zone-to-zone copy.", + tmplt.getUniqueName()); + return false; + } + + private boolean searchAndCopyWithinZone(VMTemplateVO tmplt, DataStore destStore, List stores) { + for (DataStore sourceStore : stores) { Map existingTemplatesInSourceStore = listTemplate(sourceStore); - if (existingTemplatesInSourceStore == null || !existingTemplatesInSourceStore.containsKey(tmplt.getUniqueName())) { - logger.debug("Template [{}] does not exist on image store [{}]; searching on another one.", + if (existingTemplatesInSourceStore == null || + !existingTemplatesInSourceStore.containsKey(tmplt.getUniqueName())) { + logger.debug("Template [{}] does not exist on image store [{}]; searching another.", tmplt.getUniqueName(), sourceStore.getName()); continue; } + TemplateObject sourceTmpl = (TemplateObject) _templateFactory.getTemplate(tmplt.getId(), sourceStore); if (sourceTmpl.getInstallPath() == null) { - logger.warn("Can not copy template [{}] from image store [{}], as it returned a null install path.", tmplt.getUniqueName(), - sourceStore.getName()); + logger.warn("Cannot copy template [{}] from image store [{}]; install path is null.", + tmplt.getUniqueName(), sourceStore.getName()); continue; } + storageOrchestrator.orchestrateTemplateCopyToImageStore(sourceTmpl, destStore); return true; } - logger.debug("Can't copy template [{}] from another image store.", tmplt.getUniqueName()); return false; } + private DataStore findTemplateInStores(VMTemplateVO tmplt, List stores) { + for (DataStore store : stores) { + Map templates = listTemplate(store); + if (templates != null && templates.containsKey(tmplt.getUniqueName())) { + return store; + } + } + return null; + } + + private boolean copyTemplateAcrossZones(DataStore sourceStore, + DataStore destStore, + VMTemplateVO tmplt) { + Long dstZoneId = destStore.getScope().getScopeId(); + DataCenterVO dstZone = _dcDao.findById(dstZoneId); + + if (dstZone == null) { + logger.warn("Destination zone [{}] not found for template [{}]", + dstZoneId, tmplt.getUniqueName()); + return false; + } + + try { + Long userId = CallContext.current().getCallingUserId(); + return _tmpltMgr.copy(userId, tmplt, sourceStore, dstZone); + } catch (Exception e) { + logger.error("Failed to copy template [{}] from zone [{}] to zone [{}]", + tmplt.getUniqueName(), + sourceStore.getScope().getScopeId(), + dstZoneId, + e); + return false; + } + } + @Override public AsyncCallFuture copyTemplateToImageStore(DataObject source, DataStore destStore) { TemplateObject sourceTmpl = (TemplateObject) source; @@ -681,7 +764,7 @@ protected Void copyTemplateToImageStoreCallback(AsyncCallbackDispatcher