This is an automated email from the ASF dual-hosted git repository.
harikrishna pushed a commit to branch 4.20
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/4.20 by this push:
new e8200a0b749 Prioritize copying templates from other secondary storages
instead of downloading them (#10363)
e8200a0b749 is described below
commit e8200a0b749ed2a53ec32e66da2ef02ff97e7a6c
Author: Fabricio Duarte <[email protected]>
AuthorDate: Thu Dec 18 06:53:27 2025 -0300
Prioritize copying templates from other secondary storages instead of
downloading them (#10363)
* Prioritize copying templates from other secondary storages instead of
downloading them
* Treat some corner cases
---
.../service/StorageOrchestrationService.java | 6 +
.../subsystem/api/storage/TemplateService.java | 2 +
.../java/com/cloud/storage/StorageManager.java | 4 +
.../engine/orchestration/DataMigrationUtility.java | 90 ++++++++---
.../engine/orchestration/StorageOrchestrator.java | 154 ++++++++++++++----
.../storage/image/TemplateServiceImpl.java | 179 +++++++++++++++------
.../storage/image/TemplateServiceImplTest.java | 102 ++++++++++++
.../java/com/cloud/storage/StorageManagerImpl.java | 3 +-
8 files changed, 440 insertions(+), 100 deletions(-)
diff --git
a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/StorageOrchestrationService.java
b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/StorageOrchestrationService.java
index 481d0ebbc76..8be2015bfef 100644
---
a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/StorageOrchestrationService.java
+++
b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/StorageOrchestrationService.java
@@ -18,12 +18,18 @@
package org.apache.cloudstack.engine.orchestration.service;
import java.util.List;
+import java.util.concurrent.Future;
import org.apache.cloudstack.api.response.MigrationResponse;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
+import
org.apache.cloudstack.engine.subsystem.api.storage.TemplateService.TemplateApiResult;
import org.apache.cloudstack.storage.ImageStoreService.MigrationPolicy;
public interface StorageOrchestrationService {
MigrationResponse migrateData(Long srcDataStoreId, List<Long>
destDatastores, MigrationPolicy migrationPolicy);
MigrationResponse migrateResources(Long srcImgStoreId, Long
destImgStoreId, List<Long> templateIdList, List<Long> snapshotIdList);
+
+ Future<TemplateApiResult> orchestrateTemplateCopyToImageStore(TemplateInfo
source, DataStore destStore);
}
diff --git
a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/TemplateService.java
b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/TemplateService.java
index 115cf024617..a8861d5acc6 100644
---
a/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/TemplateService.java
+++
b/engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/TemplateService.java
@@ -78,4 +78,6 @@ public interface TemplateService {
AsyncCallFuture<TemplateApiResult>
createDatadiskTemplateAsync(TemplateInfo parentTemplate, TemplateInfo
dataDiskTemplate, String path, String diskId, long fileSize, boolean bootable);
List<DatadiskTO> getTemplateDatadisksOnImageStore(TemplateInfo
templateInfo, String configurationId);
+
+ AsyncCallFuture<TemplateApiResult> copyTemplateToImageStore(DataObject
source, DataStore destStore);
}
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 95e44bbb7b3..de0cb34d63e 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,6 +220,10 @@ 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<Boolean> 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.",
+ true, ConfigKey.Scope.Zone, null);
+
/**
* should we execute in sequence not involving any storages?
* @return tru if commands should execute in sequence
diff --git
a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/DataMigrationUtility.java
b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/DataMigrationUtility.java
index 9609ba7751d..5a8dc3038aa 100644
---
a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/DataMigrationUtility.java
+++
b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/DataMigrationUtility.java
@@ -22,10 +22,12 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import javax.inject.Inject;
@@ -206,12 +208,22 @@ public class DataMigrationUtility {
protected List<DataObject> getAllReadyTemplates(DataStore srcDataStore,
Map<DataObject, Pair<List<TemplateInfo>, Long>> childTemplates,
List<TemplateDataStoreVO> templates) {
List<TemplateInfo> files = new LinkedList<>();
+ Set<Long> idsForMigration = new HashSet<>();
+
for (TemplateDataStoreVO template : templates) {
- VMTemplateVO templateVO =
templateDao.findById(template.getTemplateId());
- if (shouldMigrateTemplate(template, templateVO)) {
-
files.add(templateFactory.getTemplate(template.getTemplateId(), srcDataStore));
+ long templateId = template.getTemplateId();
+ if (idsForMigration.contains(templateId)) {
+ logger.warn("Template store reference [{}] is duplicated; not
considering it for migration.", template);
+ continue;
+ }
+ VMTemplateVO templateVO = templateDao.findById(templateId);
+ if (!shouldMigrateTemplate(template, templateVO)) {
+ continue;
}
+ files.add(templateFactory.getTemplate(template.getTemplateId(),
srcDataStore));
+ idsForMigration.add(templateId);
}
+
for (TemplateInfo template: files) {
List<VMTemplateVO> children =
templateDao.listByParentTemplatetId(template.getId());
List<TemplateInfo> temps = new ArrayList<>();
@@ -221,6 +233,7 @@ public class DataMigrationUtility {
}
childTemplates.put(template, new Pair<>(temps,
getTotalChainSize(temps)));
}
+
return (List<DataObject>) (List<?>) files;
}
@@ -263,16 +276,37 @@ public class DataMigrationUtility {
*/
protected List<DataObject> getAllReadySnapshotsAndChains(DataStore
srcDataStore, Map<DataObject, Pair<List<SnapshotInfo>, Long>> snapshotChains,
List<SnapshotDataStoreVO> snapshots) {
List<SnapshotInfo> files = new LinkedList<>();
+ Set<Long> idsForMigration = new HashSet<>();
+
for (SnapshotDataStoreVO snapshot : snapshots) {
- SnapshotVO snapshotVO =
snapshotDao.findById(snapshot.getSnapshotId());
- if (snapshot.getState() ==
ObjectInDataStoreStateMachine.State.Ready &&
- snapshotVO != null && snapshotVO.getHypervisorType() !=
Hypervisor.HypervisorType.Simulator
- && snapshot.getParentSnapshotId() == 0 ) {
- SnapshotInfo snap =
snapshotFactory.getSnapshot(snapshotVO.getSnapshotId(),
snapshot.getDataStoreId(), snapshot.getRole());
- if (snap != null) {
- files.add(snap);
- }
+ long snapshotId = snapshot.getSnapshotId();
+ if (idsForMigration.contains(snapshotId)) {
+ logger.warn("Snapshot store reference [{}] is duplicated; not
considering it for migration.", snapshot);
+ continue;
+ }
+ if (snapshot.getState() !=
ObjectInDataStoreStateMachine.State.Ready) {
+ logger.warn("Not migrating snapshot [{}] because its state is
not ready.", snapshot);
+ continue;
+ }
+ SnapshotVO snapshotVO = snapshotDao.findById(snapshotId);
+ if (snapshotVO == null) {
+ logger.debug("Not migrating snapshot [{}] because we could not
find its database entry.", snapshot);
+ continue;
+ }
+ if (snapshotVO.getHypervisorType() ==
Hypervisor.HypervisorType.Simulator) {
+ logger.debug("Not migrating snapshot [{}] because its
hypervisor type is simulator.", snapshot);
+ continue;
}
+ if (snapshot.getParentSnapshotId() != 0) {
+ continue; // The child snapshot will be migrated in the for
loop below.
+ }
+ SnapshotInfo snap =
snapshotFactory.getSnapshot(snapshotVO.getSnapshotId(),
snapshot.getDataStoreId(), snapshot.getRole());
+ if (snap == null) {
+ logger.debug("Not migrating snapshot [{}] because we could not
get its information.", snapshot);
+ continue;
+ }
+ files.add(snap);
+ idsForMigration.add(snapshotId);
}
for (SnapshotInfo parent : files) {
@@ -285,7 +319,7 @@ public class DataMigrationUtility {
chain.addAll(children);
}
}
- snapshotChains.put(parent, new Pair<List<SnapshotInfo>,
Long>(chain, getTotalChainSize(chain)));
+ snapshotChains.put(parent, new Pair<>(chain,
getTotalChainSize(chain)));
}
return (List<DataObject>) (List<?>) files;
@@ -306,14 +340,31 @@ public class DataMigrationUtility {
protected List<DataObject> getAllReadyVolumes(DataStore srcDataStore,
List<VolumeDataStoreVO> volumes) {
List<DataObject> files = new LinkedList<>();
+ Set<Long> idsForMigration = new HashSet<>();
+
for (VolumeDataStoreVO volume : volumes) {
- if (volume.getState() ==
ObjectInDataStoreStateMachine.State.Ready) {
- VolumeInfo volumeInfo =
volumeFactory.getVolume(volume.getVolumeId(), srcDataStore);
- if (volumeInfo != null && volumeInfo.getHypervisorType() !=
Hypervisor.HypervisorType.Simulator) {
- files.add(volumeInfo);
- }
+ long volumeId = volume.getVolumeId();
+ if (idsForMigration.contains(volumeId)) {
+ logger.warn("Volume store reference [{}] is duplicated; not
considering it for migration.", volume);
+ continue;
}
+ if (volume.getState() !=
ObjectInDataStoreStateMachine.State.Ready) {
+ logger.debug("Not migrating volume [{}] because its state is
not ready.", volume);
+ continue;
+ }
+ VolumeInfo volumeInfo =
volumeFactory.getVolume(volume.getVolumeId(), srcDataStore);
+ if (volumeInfo == null) {
+ logger.debug("Not migrating volume [{}] because we could not
get its information.", volume);
+ continue;
+ }
+ if (volumeInfo.getHypervisorType() ==
Hypervisor.HypervisorType.Simulator) {
+ logger.debug("Not migrating volume [{}] because its hypervisor
type is simulator.", volume);
+ continue;
+ }
+ files.add(volumeInfo);
+ idsForMigration.add(volumeId);
}
+
return files;
}
@@ -325,10 +376,9 @@ public class DataMigrationUtility {
/** Returns the count of active SSVMs - SSVM with agents in connected
state, so as to dynamically increase the thread pool
* size when SSVMs scale
*/
- protected int activeSSVMCount(DataStore dataStore) {
- long datacenterId = dataStore.getScope().getScopeId();
+ protected int activeSSVMCount(Long zoneId) {
List<SecondaryStorageVmVO> ssvms =
- secStorageVmDao.getSecStorageVmListInStates(null,
datacenterId, VirtualMachine.State.Running, VirtualMachine.State.Migrating);
+ secStorageVmDao.getSecStorageVmListInStates(null, zoneId,
VirtualMachine.State.Running, VirtualMachine.State.Migrating);
int activeSSVMs = 0;
for (SecondaryStorageVmVO vm : ssvms) {
String name = "s-"+vm.getId()+"-VM";
diff --git
a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/StorageOrchestrator.java
b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/StorageOrchestrator.java
index 0773c20b6b9..37a1f8dc196 100644
---
a/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/StorageOrchestrator.java
+++
b/engine/orchestration/src/main/java/org/apache/cloudstack/engine/orchestration/StorageOrchestrator.java
@@ -46,6 +46,8 @@ import
org.apache.cloudstack.engine.subsystem.api.storage.SecondaryStorageServic
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotDataFactory;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
+import org.apache.cloudstack.engine.subsystem.api.storage.TemplateService;
+import
org.apache.cloudstack.engine.subsystem.api.storage.TemplateService.TemplateApiResult;
import org.apache.cloudstack.framework.async.AsyncCallFuture;
import org.apache.cloudstack.framework.config.ConfigKey;
import org.apache.cloudstack.framework.config.Configurable;
@@ -71,9 +73,12 @@ import com.cloud.storage.dao.SnapshotDao;
import com.cloud.utils.Pair;
import com.cloud.utils.component.ManagerBase;
import com.cloud.utils.exception.CloudRuntimeException;
+import org.apache.logging.log4j.ThreadContext;
public class StorageOrchestrator extends ManagerBase implements
StorageOrchestrationService, Configurable {
+ private static final String LOGCONTEXTID = "logcontextid";
+
@Inject
SnapshotDataStoreDao snapshotDataStoreDao;
@Inject
@@ -91,6 +96,8 @@ public class StorageOrchestrator extends ManagerBase
implements StorageOrchestra
@Inject
private SecondaryStorageService secStgSrv;
@Inject
+ TemplateService templateService;
+ @Inject
TemplateDataStoreDao templateDataStoreDao;
@Inject
VolumeDataStoreDao volumeDataStoreDao;
@@ -106,6 +113,9 @@ public class StorageOrchestrator extends ManagerBase
implements StorageOrchestra
Integer numConcurrentCopyTasksPerSSVM = 2;
+ private final Map<Long, ThreadPoolExecutor> zoneExecutorMap = new
HashMap<>();
+ private final Map<Long, Integer> zonePendingWorkCountMap = new HashMap<>();
+
@Override
public String getConfigComponentName() {
return StorageOrchestrationService.class.getName();
@@ -167,8 +177,6 @@ public class StorageOrchestrator extends ManagerBase
implements StorageOrchestra
double meanstddev = getStandardDeviation(storageCapacities);
double threshold = ImageStoreImbalanceThreshold.value();
MigrationResponse response = null;
- ThreadPoolExecutor executor = new
ThreadPoolExecutor(numConcurrentCopyTasksPerSSVM ,
numConcurrentCopyTasksPerSSVM, 30,
- TimeUnit.MINUTES, new
MigrateBlockingQueue<>(numConcurrentCopyTasksPerSSVM));
Date start = new Date();
if (meanstddev < threshold && migrationPolicy ==
MigrationPolicy.BALANCE) {
logger.debug("mean std deviation of the image stores is below
threshold, no migration required");
@@ -177,7 +185,7 @@ public class StorageOrchestrator extends ManagerBase
implements StorageOrchestra
}
int skipped = 0;
- List<Future<AsyncCallFuture<DataObjectResult>>> futures = new
ArrayList<>();
+ List<Future<DataObjectResult>> futures = new ArrayList<>();
while (true) {
DataObject chosenFileForMigration = null;
if (files.size() > 0) {
@@ -206,7 +214,7 @@ public class StorageOrchestrator extends ManagerBase
implements StorageOrchestra
}
if (shouldMigrate(chosenFileForMigration, srcDatastore.getId(),
destDatastoreId, storageCapacities, snapshotChains, childTemplates,
migrationPolicy)) {
- storageCapacities = migrateAway(chosenFileForMigration,
storageCapacities, snapshotChains, childTemplates, srcDatastore,
destDatastoreId, executor, futures);
+ storageCapacities = migrateAway(chosenFileForMigration,
storageCapacities, snapshotChains, childTemplates, srcDatastore,
destDatastoreId, futures);
} else {
if (migrationPolicy == MigrationPolicy.BALANCE) {
continue;
@@ -217,7 +225,7 @@ public class StorageOrchestrator extends ManagerBase
implements StorageOrchestra
}
}
Date end = new Date();
- handleSnapshotMigration(srcDataStoreId, start, end, migrationPolicy,
futures, storageCapacities, executor);
+ handleSnapshotMigration(srcDataStoreId, start, end, migrationPolicy,
futures, storageCapacities);
return handleResponse(futures, migrationPolicy, message, success);
}
@@ -250,9 +258,7 @@ public class StorageOrchestrator extends ManagerBase
implements StorageOrchestra
storageCapacities = getStorageCapacities(storageCapacities,
srcImgStoreId);
storageCapacities = getStorageCapacities(storageCapacities,
destImgStoreId);
- ThreadPoolExecutor executor = new
ThreadPoolExecutor(numConcurrentCopyTasksPerSSVM,
numConcurrentCopyTasksPerSSVM, 30,
- TimeUnit.MINUTES, new
MigrateBlockingQueue<>(numConcurrentCopyTasksPerSSVM));
- List<Future<AsyncCallFuture<DataObjectResult>>> futures = new
ArrayList<>();
+ List<Future<DataObjectResult>> futures = new ArrayList<>();
Date start = new Date();
while (true) {
@@ -272,7 +278,7 @@ public class StorageOrchestrator extends ManagerBase
implements StorageOrchestra
}
if (storageCapacityBelowThreshold(storageCapacities,
destImgStoreId)) {
- storageCapacities = migrateAway(chosenFileForMigration,
storageCapacities, snapshotChains, childTemplates, srcDatastore,
destImgStoreId, executor, futures);
+ storageCapacities = migrateAway(chosenFileForMigration,
storageCapacities, snapshotChains, childTemplates, srcDatastore,
destImgStoreId, futures);
} else {
message = "Migration failed. Destination store doesn't have
enough capacity for migration";
success = false;
@@ -289,7 +295,7 @@ public class StorageOrchestrator extends ManagerBase
implements StorageOrchestra
SnapshotInfo snapshotInfo =
snapshotFactory.getSnapshot(snap.getSnapshotId(), snap.getDataStoreId(),
DataStoreRole.Image);
SnapshotInfo parentSnapshot = snapshotInfo.getParent();
if (snapshotInfo.getDataStore().getId() == srcImgStoreId &&
parentSnapshot != null &&
migratedSnapshotIdList.contains(parentSnapshot.getSnapshotId())) {
- futures.add(executor.submit(new
MigrateDataTask(snapshotInfo, srcDatastore,
dataStoreManager.getDataStore(destImgStoreId, DataStoreRole.Image))));
+ futures.add(submit(srcDatastore.getScope().getScopeId(),
new MigrateDataTask(snapshotInfo, srcDatastore,
dataStoreManager.getDataStore(destImgStoreId, DataStoreRole.Image))));
}
});
}
@@ -297,6 +303,11 @@ public class StorageOrchestrator extends ManagerBase
implements StorageOrchestra
return handleResponse(futures, null, message, success);
}
+ @Override
+ public Future<TemplateApiResult>
orchestrateTemplateCopyToImageStore(TemplateInfo source, DataStore destStore) {
+ return submit(destStore.getScope().getScopeId(), new
CopyTemplateTask(source, destStore));
+ }
+
protected Pair<String, Boolean> migrateCompleted(Long destDatastoreId,
DataStore srcDatastore, List<DataObject> files, MigrationPolicy
migrationPolicy, int skipped) {
String message = "";
boolean success = true;
@@ -332,19 +343,10 @@ public class StorageOrchestrator extends ManagerBase
implements StorageOrchestra
Map<DataObject, Pair<List<TemplateInfo>, Long>> templateChains,
DataStore srcDatastore,
Long destDatastoreId,
- ThreadPoolExecutor executor,
- List<Future<AsyncCallFuture<DataObjectResult>>> futures) {
+ List<Future<DataObjectResult>> futures) {
Long fileSize = migrationHelper.getFileSize(chosenFileForMigration,
snapshotChains, templateChains);
-
storageCapacities = assumeMigrate(storageCapacities,
srcDatastore.getId(), destDatastoreId, fileSize);
- long activeSsvms = migrationHelper.activeSSVMCount(srcDatastore);
- long totalJobs = activeSsvms * numConcurrentCopyTasksPerSSVM;
- // Increase thread pool size with increase in number of SSVMs
- if ( totalJobs > executor.getCorePoolSize()) {
- executor.setMaximumPoolSize((int) (totalJobs));
- executor.setCorePoolSize((int) (totalJobs));
- }
MigrateDataTask task = new MigrateDataTask(chosenFileForMigration,
srcDatastore, dataStoreManager.getDataStore(destDatastoreId,
DataStoreRole.Image));
if (chosenFileForMigration instanceof SnapshotInfo ) {
@@ -353,19 +355,64 @@ public class StorageOrchestrator extends ManagerBase
implements StorageOrchestra
if (chosenFileForMigration instanceof TemplateInfo) {
task.setTemplateChain(templateChains);
}
- futures.add((executor.submit(task)));
+ futures.add(submit(srcDatastore.getScope().getScopeId(), task));
logger.debug("Migration of {}: {} is initiated.",
chosenFileForMigration.getType().name(), chosenFileForMigration.getUuid());
return storageCapacities;
}
+ protected <T> Future<T> submit(Long zoneId, Callable<T> task) {
+ ThreadPoolExecutor executor;
+ synchronized (this) {
+ if (!zoneExecutorMap.containsKey(zoneId)) {
+ zoneExecutorMap.put(zoneId, new
ThreadPoolExecutor(numConcurrentCopyTasksPerSSVM, numConcurrentCopyTasksPerSSVM,
+ 30, TimeUnit.MINUTES, new
MigrateBlockingQueue<>(numConcurrentCopyTasksPerSSVM)));
+ zonePendingWorkCountMap.put(zoneId, 0);
+ }
+ zonePendingWorkCountMap.merge(zoneId, 1, Integer::sum);
+ scaleExecutorIfNecessary(zoneId);
+ executor = zoneExecutorMap.get(zoneId);
+ }
+ return executor.submit(task);
+
+ }
+
+ protected void scaleExecutorIfNecessary(Long zoneId) {
+ long activeSsvms = migrationHelper.activeSSVMCount(zoneId);
+ long totalJobs = activeSsvms * numConcurrentCopyTasksPerSSVM;
+ ThreadPoolExecutor executor = zoneExecutorMap.get(zoneId);
+ if (totalJobs > executor.getCorePoolSize()) {
+ logger.debug("Scaling up executor of zone [{}] from [{}] to [{}]
threads.", zoneId, executor.getCorePoolSize(),
+ totalJobs);
+ executor.setMaximumPoolSize((int) (totalJobs));
+ executor.setCorePoolSize((int) (totalJobs));
+ }
+ }
+
+ protected synchronized void tryCleaningUpExecutor(Long zoneId) {
+ if (!zoneExecutorMap.containsKey(zoneId)) {
+ logger.debug("No executor exists for zone [{}].", zoneId);
+ return;
+ }
+
+ zonePendingWorkCountMap.merge(zoneId, -1, Integer::sum);
+ Integer pendingWorkCount = zonePendingWorkCountMap.get(zoneId);
+ if (pendingWorkCount > 0) {
+ logger.debug("Not cleaning executor of zone [{}] yet, as there is
[{}] pending work.", zoneId, pendingWorkCount);
+ return;
+ }
+ logger.debug("Cleaning executor of zone [{}].", zoneId);
+ ThreadPoolExecutor executor = zoneExecutorMap.get(zoneId);
+ zoneExecutorMap.remove(zoneId);
+ executor.shutdown();
+ }
- private MigrationResponse
handleResponse(List<Future<AsyncCallFuture<DataObjectResult>>> futures,
MigrationPolicy migrationPolicy, String message, boolean success) {
+ private MigrationResponse handleResponse(List<Future<DataObjectResult>>
futures, MigrationPolicy migrationPolicy, String message, boolean success) {
int successCount = 0;
- for (Future<AsyncCallFuture<DataObjectResult>> future : futures) {
+ for (Future<DataObjectResult> future : futures) {
try {
- AsyncCallFuture<DataObjectResult> res = future.get();
- if (res.get().isSuccess()) {
+ DataObjectResult res = future.get();
+ if (res.isSuccess()) {
successCount++;
}
} catch ( InterruptedException | ExecutionException e) {
@@ -379,7 +426,7 @@ public class StorageOrchestrator extends ManagerBase
implements StorageOrchestra
}
private void handleSnapshotMigration(Long srcDataStoreId, Date start, Date
end, MigrationPolicy policy,
-
List<Future<AsyncCallFuture<DataObjectResult>>> futures, Map<Long, Pair<Long,
Long>> storageCapacities, ThreadPoolExecutor executor) {
+ List<Future<DataObjectResult>>
futures, Map<Long, Pair<Long, Long>> storageCapacities) {
DataStore srcDatastore = dataStoreManager.getDataStore(srcDataStoreId,
DataStoreRole.Image);
List<SnapshotDataStoreVO> snaps =
snapshotDataStoreDao.findSnapshots(srcDataStoreId, start, end);
if (!snaps.isEmpty()) {
@@ -395,12 +442,12 @@ public class StorageOrchestrator extends ManagerBase
implements StorageOrchestra
storeId = dstores.get(1);
}
DataStore datastore =
dataStoreManager.getDataStore(storeId, DataStoreRole.Image);
- futures.add(executor.submit(new
MigrateDataTask(snapshotInfo, srcDatastore, datastore)));
+ futures.add(submit(srcDatastore.getScope().getScopeId(),
new MigrateDataTask(snapshotInfo, srcDatastore, datastore)));
}
if (parentSnapshot != null) {
DataStore parentDS =
dataStoreManager.getDataStore(parentSnapshot.getDataStore().getId(),
DataStoreRole.Image);
if (parentDS.getId() !=
snapshotInfo.getDataStore().getId()) {
- futures.add(executor.submit(new
MigrateDataTask(snapshotInfo, srcDatastore, parentDS)));
+
futures.add(submit(srcDatastore.getScope().getScopeId(), new
MigrateDataTask(snapshotInfo, srcDatastore, parentDS)));
}
}
}
@@ -527,16 +574,19 @@ public class StorageOrchestrator extends ManagerBase
implements StorageOrchestra
return standardDeviation.evaluate(metricValues, mean);
}
- private class MigrateDataTask implements
Callable<AsyncCallFuture<DataObjectResult>> {
+ private class MigrateDataTask implements Callable<DataObjectResult> {
private DataObject file;
private DataStore srcDataStore;
private DataStore destDataStore;
private Map<DataObject, Pair<List<SnapshotInfo>, Long>> snapshotChain;
private Map<DataObject, Pair<List<TemplateInfo>, Long>> templateChain;
+ private String logid;
+
public MigrateDataTask(DataObject file, DataStore srcDataStore,
DataStore destDataStore) {
this.file = file;
this.srcDataStore = srcDataStore;
this.destDataStore = destDataStore;
+ this.logid = ThreadContext.get(LOGCONTEXTID);
}
public void setSnapshotChains(Map<DataObject, Pair<List<SnapshotInfo>,
Long>> snapshotChain) {
@@ -557,8 +607,50 @@ public class StorageOrchestrator extends ManagerBase
implements StorageOrchestra
}
@Override
- public AsyncCallFuture<DataObjectResult> call() throws Exception {
- return secStgSrv.migrateData(file, srcDataStore, destDataStore,
snapshotChain, templateChain);
+ public DataObjectResult call() {
+ ThreadContext.put(LOGCONTEXTID, logid);
+ DataObjectResult result;
+ AsyncCallFuture<DataObjectResult> future =
secStgSrv.migrateData(file, srcDataStore, destDataStore, snapshotChain,
templateChain);
+ try {
+ result = future.get();
+ } catch (ExecutionException | InterruptedException e) {
+ logger.warn("Exception while migrating data to another
secondary storage: {}", e.toString());
+ result = new DataObjectResult(file);
+ result.setResult(e.toString());
+ }
+ tryCleaningUpExecutor(srcDataStore.getScope().getScopeId());
+ ThreadContext.clearAll();
+ return result;
+ }
+ }
+
+ private class CopyTemplateTask implements Callable<TemplateApiResult> {
+ private TemplateInfo sourceTmpl;
+ private DataStore destStore;
+ private String logid;
+
+ public CopyTemplateTask(TemplateInfo sourceTmpl, DataStore destStore) {
+ this.sourceTmpl = sourceTmpl;
+ this.destStore = destStore;
+ this.logid = ThreadContext.get(LOGCONTEXTID);
+ }
+
+ @Override
+ public TemplateApiResult call() {
+ ThreadContext.put(LOGCONTEXTID, logid);
+ TemplateApiResult result;
+ AsyncCallFuture<TemplateApiResult> future =
templateService.copyTemplateToImageStore(sourceTmpl, destStore);
+ try {
+ result = future.get();
+ } catch (ExecutionException | InterruptedException e) {
+ logger.warn("Exception while copying template [{}] from image
store [{}] to image store [{}]: {}",
+ sourceTmpl.getUniqueName(),
sourceTmpl.getDataStore().getName(), destStore.getName(), e.toString());
+ result = new TemplateApiResult(sourceTmpl);
+ result.setResult(e.getMessage());
+ }
+ tryCleaningUpExecutor(destStore.getScope().getScopeId());
+ ThreadContext.clearAll();
+ return result;
}
}
}
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 38e0d0d081c..fd723b8bf34 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 java.util.concurrent.ExecutionException;
import javax.inject.Inject;
+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;
import org.apache.cloudstack.engine.subsystem.api.storage.DataMotionService;
@@ -42,7 +43,6 @@ import
org.apache.cloudstack.engine.subsystem.api.storage.EndPointSelector;
import
org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
import
org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.Event;
import
org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine.State;
-import org.apache.cloudstack.engine.subsystem.api.storage.Scope;
import org.apache.cloudstack.engine.subsystem.api.storage.SnapshotInfo;
import org.apache.cloudstack.engine.subsystem.api.storage.StorageCacheManager;
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateDataFactory;
@@ -58,6 +58,7 @@ import
org.apache.cloudstack.framework.config.dao.ConfigurationDao;
import org.apache.cloudstack.framework.messagebus.MessageBus;
import org.apache.cloudstack.framework.messagebus.PublishScope;
import org.apache.cloudstack.storage.command.CommandResult;
+import org.apache.cloudstack.storage.command.CopyCmdAnswer;
import org.apache.cloudstack.storage.command.DeleteCommand;
import org.apache.cloudstack.storage.datastore.DataObjectManager;
import org.apache.cloudstack.storage.datastore.ObjectInDataStoreManager;
@@ -77,7 +78,6 @@ import com.cloud.agent.api.storage.ListTemplateCommand;
import com.cloud.agent.api.to.DatadiskTO;
import com.cloud.alert.AlertManager;
import com.cloud.configuration.Config;
-import com.cloud.configuration.Resource;
import com.cloud.configuration.Resource.ResourceType;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.dao.ClusterDao;
@@ -157,6 +157,8 @@ public class TemplateServiceImpl implements TemplateService
{
ImageStoreDetailsUtil imageStoreDetailsUtil;
@Inject
TemplateDataFactory imageFactory;
+ @Inject
+ StorageOrchestrationService storageOrchestrator;
class TemplateOpContext<T> extends AsyncRpcContext<T> {
final TemplateObject template;
@@ -320,7 +322,6 @@ public class TemplateServiceImpl implements TemplateService
{
if (syncLock.lock(3)) {
try {
Long zoneId = store.getScope().getScopeId();
-
Map<String, TemplateProp> templateInfos =
listTemplate(store);
if (templateInfos == null) {
return;
@@ -529,10 +530,6 @@ public class TemplateServiceImpl implements
TemplateService {
availHypers.add(HypervisorType.None); // bug 9809:
resume ISO
// download.
for (VMTemplateVO tmplt : toBeDownloaded) {
- if (tmplt.getUrl() == null) { // If url is null,
skip downloading
- logger.info("Skip downloading template {}
since no url is specified.", tmplt);
- continue;
- }
// if this is private template, skip sync to a new
image store
if (isSkipTemplateStoreDownload(tmplt, zoneId)) {
logger.info("Skip sync downloading private
template {} to a new image store", tmplt);
@@ -551,14 +548,10 @@ public class TemplateServiceImpl implements
TemplateService {
}
if
(availHypers.contains(tmplt.getHypervisorType())) {
- logger.info("Downloading template {} to image
store {}", tmplt, store);
- associateTemplateToZone(tmplt.getId(), zoneId);
- TemplateInfo tmpl =
_templateFactory.getTemplate(tmplt.getId(), store);
- TemplateOpContext<TemplateApiResult> context =
new TemplateOpContext<>(null,(TemplateObject)tmpl, null);
- AsyncCallbackDispatcher<TemplateServiceImpl,
TemplateApiResult> caller = AsyncCallbackDispatcher.create(this);
-
caller.setCallback(caller.getTarget().createTemplateAsyncCallBack(null, null));
- caller.setContext(context);
- createTemplateAsync(tmpl, store, caller);
+ boolean copied =
isCopyFromOtherStoragesEnabled(zoneId) && tryCopyingTemplateToImageStore(tmplt,
store);
+ if (!copied) {
+ tryDownloadingTemplateToImageStore(tmplt,
store);
+ }
} else {
logger.info("Skip downloading template {}
since current data center does not have hypervisor {}", tmplt,
tmplt.getHypervisorType());
}
@@ -605,6 +598,127 @@ public class TemplateServiceImpl implements
TemplateService {
}
+ protected void tryDownloadingTemplateToImageStore(VMTemplateVO tmplt,
DataStore destStore) {
+ if (tmplt.getUrl() == null) {
+ logger.info("Not downloading template [{}] to image store [{}], as
it has no URL.", tmplt.getUniqueName(),
+ destStore.getName());
+ return;
+ }
+ logger.info("Downloading template [{}] to image store [{}].",
tmplt.getUniqueName(), destStore.getName());
+ associateTemplateToZone(tmplt.getId(),
destStore.getScope().getScopeId());
+ TemplateInfo tmpl = _templateFactory.getTemplate(tmplt.getId(),
destStore);
+ TemplateOpContext<TemplateApiResult> context = new
TemplateOpContext<>(null,(TemplateObject)tmpl, null);
+ AsyncCallbackDispatcher<TemplateServiceImpl, TemplateApiResult> caller
= AsyncCallbackDispatcher.create(this);
+
caller.setCallback(caller.getTarget().createTemplateAsyncCallBack(null, null));
+ caller.setContext(context);
+ createTemplateAsync(tmpl, destStore, caller);
+ }
+
+ protected boolean tryCopyingTemplateToImageStore(VMTemplateVO tmplt,
DataStore destStore) {
+ Long zoneId = destStore.getScope().getScopeId();
+ List<DataStore> storesInZone =
_storeMgr.getImageStoresByZoneIds(zoneId);
+ for (DataStore sourceStore : storesInZone) {
+ Map<String, TemplateProp> existingTemplatesInSourceStore =
listTemplate(sourceStore);
+ if (existingTemplatesInSourceStore == null ||
!existingTemplatesInSourceStore.containsKey(tmplt.getUniqueName())) {
+ logger.debug("Template [{}] does not exist on image store
[{}]; searching on another one.",
+ 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());
+ continue;
+ }
+
storageOrchestrator.orchestrateTemplateCopyToImageStore(sourceTmpl, destStore);
+ return true;
+ }
+ logger.debug("Can't copy template [{}] from another image store.",
tmplt.getUniqueName());
+ return false;
+ }
+
+ @Override
+ public AsyncCallFuture<TemplateApiResult>
copyTemplateToImageStore(DataObject source, DataStore destStore) {
+ TemplateObject sourceTmpl = (TemplateObject) source;
+ logger.debug("Copying template [{}] from image store [{}] to [{}].",
sourceTmpl.getUniqueName(), sourceTmpl.getDataStore().getName(),
+ destStore.getName());
+ TemplateObject destTmpl = (TemplateObject)
destStore.create(sourceTmpl);
+ destTmpl.processEvent(Event.CreateOnlyRequested);
+
+ AsyncCallFuture<TemplateApiResult> future = new AsyncCallFuture<>();
+ TemplateOpContext<TemplateApiResult> context = new
TemplateOpContext<>(null, destTmpl, future);
+ AsyncCallbackDispatcher<TemplateServiceImpl, CopyCommandResult> caller
= AsyncCallbackDispatcher.create(this);
+
caller.setCallback(caller.getTarget().copyTemplateToImageStoreCallback(null,
null)).setContext(context);
+
+ if (source.getDataStore().getId() == destStore.getId()) {
+ logger.debug("Destination image store [{}] is the same as the
origin; returning success to normalize the metadata.");
+ CopyCmdAnswer answer = new CopyCmdAnswer(source.getTO());
+ CopyCommandResult result = new CopyCommandResult("", answer);
+ caller.complete(result);
+ return future;
+ }
+
+ _motionSrv.copyAsync(sourceTmpl, destTmpl, caller);
+ return future;
+ }
+
+ protected Void
copyTemplateToImageStoreCallback(AsyncCallbackDispatcher<TemplateServiceImpl,
CopyCommandResult> callback, TemplateOpContext<TemplateApiResult> context) {
+ TemplateInfo tmplt = context.getTemplate();
+ CopyCommandResult result = callback.getResult();
+ AsyncCallFuture<TemplateApiResult> future = context.getFuture();
+ TemplateApiResult res = new TemplateApiResult(tmplt);
+ if (result.isSuccess()) {
+ logger.info("Copied template [{}] to image store [{}].",
tmplt.getUniqueName(), tmplt.getDataStore().getName());
+ tmplt.processEvent(Event.OperationSuccessed, result.getAnswer());
+ publishTemplateCreation(tmplt);
+ } else {
+ logger.warn("Failed to copy template [{}] to image store [{}].",
tmplt.getUniqueName(), tmplt.getDataStore().getName());
+ res.setResult(result.getResult());
+ tmplt.processEvent(Event.OperationFailed);
+ }
+ future.complete(res);
+ return null;
+ }
+
+ protected boolean isCopyFromOtherStoragesEnabled(Long zoneId) {
+ return
StorageManager.COPY_PUBLIC_TEMPLATES_FROM_OTHER_STORAGES.valueIn(zoneId);
+ }
+
+ protected void publishTemplateCreation(TemplateInfo tmplt) {
+ VMTemplateVO tmpltVo = _templateDao.findById(tmplt.getId());
+
+ if (tmpltVo.isPublicTemplate()) {
+ _messageBus.publish(null,
TemplateManager.MESSAGE_REGISTER_PUBLIC_TEMPLATE_EVENT, PublishScope.LOCAL,
tmpltVo.getId());
+ }
+
+ Long size = tmplt.getSize();
+ if (size == null) {
+ return;
+ }
+
+ DataStore store = tmplt.getDataStore();
+ TemplateDataStoreVO tmpltStore =
_vmTemplateStoreDao.findByStoreTemplate(store.getId(), tmpltVo.getId());
+
+ long physicalSize = 0;
+ if (tmpltStore != null) {
+ physicalSize = tmpltStore.getPhysicalSize();
+ } else {
+ logger.warn("No entry found in template_store_ref for template
[{}] and image store [{}] at the end of registering template!",
+ tmpltVo.getUniqueName(), store.getName());
+ }
+
+ Long zoneId = store.getScope().getScopeId();
+ if (zoneId != null) {
+ String usageEvent = tmplt.getFormat() == ImageFormat.ISO ?
EventTypes.EVENT_ISO_CREATE : EventTypes.EVENT_TEMPLATE_CREATE;
+ UsageEventUtils.publishUsageEvent(usageEvent,
tmpltVo.getAccountId(), zoneId, tmpltVo.getId(), tmpltVo.getName(),
+ null, null, physicalSize, size,
VirtualMachineTemplate.class.getName(), tmpltVo.getUuid());
+ } else {
+ logger.warn("Zone-wide image store [{}] has a null scope ID.",
store);
+ }
+
+ _resourceLimitMgr.incrementResourceCount(tmpltVo.getAccountId(),
ResourceType.secondary_storage, size);
+ }
+
// persist entry in template_zone_ref table. zoneId can be empty for
// region-wide image store, in that case,
// we will associate the template to all the zones.
@@ -650,45 +764,14 @@ public class TemplateServiceImpl implements
TemplateService {
protected Void
createTemplateAsyncCallBack(AsyncCallbackDispatcher<TemplateServiceImpl,
TemplateApiResult> callback,
TemplateOpContext<TemplateApiResult> context) {
- TemplateInfo template = context.template;
TemplateApiResult result = callback.getResult();
if (result.isSuccess()) {
- VMTemplateVO tmplt = _templateDao.findById(template.getId());
- // need to grant permission for public templates
- if (tmplt.isPublicTemplate()) {
- _messageBus.publish(null,
TemplateManager.MESSAGE_REGISTER_PUBLIC_TEMPLATE_EVENT, PublishScope.LOCAL,
tmplt.getId());
- }
- long accountId = tmplt.getAccountId();
- if (template.getSize() != null) {
- // publish usage event
- String etype = EventTypes.EVENT_TEMPLATE_CREATE;
- if (tmplt.getFormat() == ImageFormat.ISO) {
- etype = EventTypes.EVENT_ISO_CREATE;
- }
- // get physical size from template_store_ref table
- long physicalSize = 0;
- DataStore ds = template.getDataStore();
- TemplateDataStoreVO tmpltStore =
_vmTemplateStoreDao.findByStoreTemplate(ds.getId(), template.getId());
- if (tmpltStore != null) {
- physicalSize = tmpltStore.getPhysicalSize();
- } else {
- logger.warn("No entry found in template_store_ref for
template: {} and image store: {} at the end of registering template!",
template, ds);
- }
- Scope dsScope = ds.getScope();
- if (dsScope.getScopeId() != null) {
- UsageEventUtils.publishUsageEvent(etype,
template.getAccountId(), dsScope.getScopeId(), template.getId(),
template.getName(), null, null,
- physicalSize, template.getSize(),
VirtualMachineTemplate.class.getName(), template.getUuid());
- } else {
- logger.warn("Zone scope image store {} has a null scope
id", ds);
- }
- _resourceLimitMgr.incrementResourceCount(accountId,
Resource.ResourceType.secondary_storage, template.getSize());
- }
+ publishTemplateCreation(context.template);
}
-
return null;
}
- private Map<String, TemplateProp> listTemplate(DataStore ssStore) {
+ protected Map<String, TemplateProp> listTemplate(DataStore ssStore) {
String nfsVersion =
imageStoreDetailsUtil.getNfsVersion(ssStore.getId());
ListTemplateCommand cmd = new ListTemplateCommand(ssStore.getTO(),
nfsVersion);
EndPoint ep = _epSelector.select(ssStore);
diff --git
a/engine/storage/image/src/test/java/org/apache/cloudstack/storage/image/TemplateServiceImplTest.java
b/engine/storage/image/src/test/java/org/apache/cloudstack/storage/image/TemplateServiceImplTest.java
index bc6f37b201a..276581e2e48 100644
---
a/engine/storage/image/src/test/java/org/apache/cloudstack/storage/image/TemplateServiceImplTest.java
+++
b/engine/storage/image/src/test/java/org/apache/cloudstack/storage/image/TemplateServiceImplTest.java
@@ -18,9 +18,17 @@
*/
package org.apache.cloudstack.storage.image;
+import com.cloud.storage.template.TemplateProp;
+import
org.apache.cloudstack.engine.orchestration.service.StorageOrchestrationService;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
+import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreManager;
+import org.apache.cloudstack.engine.subsystem.api.storage.Scope;
+import org.apache.cloudstack.framework.async.AsyncCallFuture;
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreDao;
import org.apache.cloudstack.storage.datastore.db.TemplateDataStoreVO;
+import org.apache.cloudstack.storage.image.store.TemplateObject;
import org.junit.Assert;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
@@ -33,6 +41,10 @@ import com.cloud.storage.DataStoreRole;
import com.cloud.storage.Storage;
import com.cloud.storage.VMTemplateVO;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
@RunWith(MockitoJUnitRunner.class)
public class TemplateServiceImplTest {
@@ -43,6 +55,49 @@ public class TemplateServiceImplTest {
@Mock
TemplateDataStoreDao templateDataStoreDao;
+ @Mock
+ TemplateDataFactoryImpl templateDataFactoryMock;
+
+ @Mock
+ DataStoreManager dataStoreManagerMock;
+
+ @Mock
+ VMTemplateVO tmpltMock;
+
+ @Mock
+ TemplateProp tmpltPropMock;
+
+ @Mock
+ TemplateObject templateInfoMock;
+
+ @Mock
+ DataStore sourceStoreMock;
+
+ @Mock
+ DataStore destStoreMock;
+
+ @Mock
+ Scope zoneScopeMock;
+
+ @Mock
+ StorageOrchestrationService storageOrchestrator;
+
+ Map<String, TemplateProp> templatesInSourceStore = new HashMap<>();
+
+ @Before
+ public void setUp() {
+ Long zoneId = 1L;
+ Mockito.doReturn(2L).when(tmpltMock).getId();
+ Mockito.doReturn("unique-name").when(tmpltMock).getUniqueName();
+ Mockito.doReturn(zoneId).when(zoneScopeMock).getScopeId();
+ Mockito.doReturn(zoneScopeMock).when(destStoreMock).getScope();
+ Mockito.doReturn(List.of(sourceStoreMock,
destStoreMock)).when(dataStoreManagerMock).getImageStoresByZoneIds(zoneId);
+
Mockito.doReturn(templatesInSourceStore).when(templateService).listTemplate(sourceStoreMock);
+
Mockito.doReturn(null).when(templateService).listTemplate(destStoreMock);
+
Mockito.doReturn("install-path").when(templateInfoMock).getInstallPath();
+
Mockito.doReturn(templateInfoMock).when(templateDataFactoryMock).getTemplate(2L,
sourceStoreMock);
+ }
+
@Test
public void testIsSkipTemplateStoreDownloadPublicTemplate() {
VMTemplateVO templateVO = Mockito.mock(VMTemplateVO.class);
@@ -81,4 +136,51 @@ public class TemplateServiceImplTest {
Mockito.when(templateDataStoreDao.findByTemplateZone(id, id,
DataStoreRole.Image)).thenReturn(Mockito.mock(TemplateDataStoreVO.class));
Assert.assertTrue(templateService.isSkipTemplateStoreDownload(templateVO, id));
}
+
+ @Test
+ public void
tryDownloadingTemplateToImageStoreTestDownloadsTemplateWhenUrlIsNotNull() {
+ Mockito.doReturn("url").when(tmpltMock).getUrl();
+
Mockito.doNothing().when(templateService).associateTemplateToZone(Mockito.anyLong(),
Mockito.any(Long.class));
+
+ templateService.tryDownloadingTemplateToImageStore(tmpltMock,
destStoreMock);
+
+ Mockito.verify(templateService).createTemplateAsync(Mockito.any(),
Mockito.any(), Mockito.any());
+ }
+
+ @Test
+ public void
tryDownloadingTemplateToImageStoreTestDoesNothingWhenUrlIsNull() {
+ templateService.tryDownloadingTemplateToImageStore(tmpltMock,
destStoreMock);
+
+ Mockito.verify(templateService,
Mockito.never()).createTemplateAsync(Mockito.any(), Mockito.any(),
Mockito.any());
+ }
+
+ @Test
+ public void
tryCopyingTemplateToImageStoreTestReturnsFalseWhenTemplateDoesNotExistOnAnotherImageStore()
{
+ boolean result =
templateService.tryCopyingTemplateToImageStore(tmpltMock, destStoreMock);
+
+ Assert.assertFalse(result);
+ Mockito.verify(storageOrchestrator,
Mockito.never()).orchestrateTemplateCopyToImageStore(Mockito.any(),
Mockito.any());
+ }
+
+ @Test
+ public void
tryCopyingTemplateToImageStoreTestReturnsFalseWhenInstallPathIsNull() {
+ templatesInSourceStore.put(tmpltMock.getUniqueName(), tmpltPropMock);
+ Mockito.doReturn(null).when(templateInfoMock).getInstallPath();
+
+ boolean result =
templateService.tryCopyingTemplateToImageStore(tmpltMock, destStoreMock);
+
+ Assert.assertFalse(result);
+ Mockito.verify(storageOrchestrator,
Mockito.never()).orchestrateTemplateCopyToImageStore(Mockito.any(),
Mockito.any());
+ }
+
+ @Test
+ public void
tryCopyingTemplateToImageStoreTestReturnsTrueWhenTemplateExistsInAnotherStorageAndTaskWasScheduled()
{
+ templatesInSourceStore.put(tmpltMock.getUniqueName(), tmpltPropMock);
+ Mockito.doReturn(new
AsyncCallFuture<>()).when(storageOrchestrator).orchestrateTemplateCopyToImageStore(Mockito.any(),
Mockito.any());
+
+ boolean result =
templateService.tryCopyingTemplateToImageStore(tmpltMock, destStoreMock);
+
+ Assert.assertTrue(result);
+
Mockito.verify(storageOrchestrator).orchestrateTemplateCopyToImageStore(Mockito.any(),
Mockito.any());
+ }
}
diff --git a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java
b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java
index 5a66ad502f2..19da1425dc0 100644
--- a/server/src/main/java/com/cloud/storage/StorageManagerImpl.java
+++ b/server/src/main/java/com/cloud/storage/StorageManagerImpl.java
@@ -4194,7 +4194,8 @@ public class StorageManagerImpl extends ManagerBase
implements StorageManager, C
VmwareAllowParallelExecution,
DataStoreDownloadFollowRedirects,
AllowVolumeReSizeBeyondAllocation,
- StoragePoolHostConnectWorkers
+ StoragePoolHostConnectWorkers,
+ COPY_PUBLIC_TEMPLATES_FROM_OTHER_STORAGES
};
}