Merge branch '4.20' into 4.22

This commit is contained in:
Daan Hoogland 2025-12-22 10:27:57 +01:00
commit cee330e7c8
17 changed files with 503 additions and 142 deletions

View File

@ -211,7 +211,7 @@ public class CreateTemplateCmd extends BaseAsyncCreateCmd implements UserCmd {
}
public String getTemplateTag() {
return templateTag;
return StringUtils.isBlank(templateTag) ? null : templateTag;
}
public Map getDetails() {

View File

@ -170,7 +170,7 @@ public class GetUploadParamsForTemplateCmd extends AbstractGetUploadParamsCmd {
}
public String getTemplateTag() {
return templateTag;
return StringUtils.isBlank(templateTag) ? null : templateTag;
}
public boolean isDeployAsIs() {

View File

@ -279,7 +279,7 @@ public class RegisterTemplateCmd extends BaseCmd implements UserCmd {
}
public String getTemplateTag() {
return templateTag;
return StringUtils.isBlank(templateTag) ? null : templateTag;
}
public Map getDetails() {

3
debian/rules vendored
View File

@ -4,7 +4,6 @@ VERSION := $(shell grep '<version>' pom.xml | head -2 | tail -1 | cut -d'>' -f2
PACKAGE = $(shell dh_listpackages|head -n 1|cut -d '-' -f 1)
SYSCONFDIR = "/etc"
DESTDIR = "debian/tmp"
CMK_REL := $(shell wget -O - "https://api.github.com/repos/apache/cloudstack-cloudmonkey/releases" 2>/dev/null | jq -r '.[0].tag_name')
%:
dh $@ --with systemd
@ -90,7 +89,7 @@ override_dh_auto_install:
rm -rf $(DESTDIR)/usr/share/$(PACKAGE)-management/templates/systemvm/sha512sum.txt
# Bundle cmk in cloudstack-management
wget https://github.com/apache/cloudstack-cloudmonkey/releases/download/$(CMK_REL)/cmk.linux.x86-64 -O $(DESTDIR)/usr/bin/cmk
wget https://github.com/apache/cloudstack-cloudmonkey/releases/latest/download/cmk.linux.x86-64 -O $(DESTDIR)/usr/bin/cmk
chmod +x $(DESTDIR)/usr/bin/cmk
# nast hack for a couple of configuration files

View File

@ -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);
}

View File

@ -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);
}

View File

@ -161,22 +161,22 @@ public interface StorageManager extends StorageService {
ConfigKey.Scope.StoragePool,
null);
ConfigKey<Integer> PRIMARY_STORAGE_DOWNLOAD_WAIT = new ConfigKey<Integer>("Storage", Integer.class, "primary.storage.download.wait", "10800",
ConfigKey<Integer> PRIMARY_STORAGE_DOWNLOAD_WAIT = new ConfigKey<>("Storage", Integer.class, "primary.storage.download.wait", "10800",
"In second, timeout for download template to primary storage", false);
ConfigKey<Integer> SecStorageMaxMigrateSessions = new ConfigKey<Integer>("Advanced", Integer.class, "secstorage.max.migrate.sessions", "2",
ConfigKey<Integer> SecStorageMaxMigrateSessions = new ConfigKey<>("Advanced", Integer.class, "secstorage.max.migrate.sessions", "2",
"The max number of concurrent copy command execution sessions that an SSVM can handle", false, ConfigKey.Scope.Global);
ConfigKey<Boolean> SecStorageVMAutoScaleDown = new ConfigKey<Boolean>("Advanced", Boolean.class, "secstorage.vm.auto.scale.down", "false",
ConfigKey<Boolean> SecStorageVMAutoScaleDown = new ConfigKey<>("Advanced", Boolean.class, "secstorage.vm.auto.scale.down", "false",
"Setting this to 'true' will auto scale down SSVMs", true, ConfigKey.Scope.Global);
ConfigKey<Integer> MaxDataMigrationWaitTime = new ConfigKey<Integer>("Advanced", Integer.class, "max.data.migration.wait.time", "15",
ConfigKey<Integer> MaxDataMigrationWaitTime = new ConfigKey<>("Advanced", Integer.class, "max.data.migration.wait.time", "15",
"Maximum wait time (in minutes) for a data migration task before spawning a new SSVM", false, ConfigKey.Scope.Global);
ConfigKey<Boolean> DiskProvisioningStrictness = new ConfigKey<Boolean>("Storage", Boolean.class, "disk.provisioning.type.strictness", "false",
ConfigKey<Boolean> DiskProvisioningStrictness = new ConfigKey<>("Storage", Boolean.class, "disk.provisioning.type.strictness", "false",
"If set to true, the disk is created only when there is a suitable storage pool that supports the disk provisioning type specified by the service/disk offering. " +
"If set to false, the disk is created with a disk provisioning type supported by the pool. Default value is false, and this is currently supported for VMware only.",
true, ConfigKey.Scope.Zone);
ConfigKey<String> PreferredStoragePool = new ConfigKey<String>(String.class, "preferred.storage.pool", "Advanced", "",
ConfigKey<String> PreferredStoragePool = new ConfigKey<>(String.class, "preferred.storage.pool", "Advanced", "",
"The UUID of preferred storage pool for allocation.", true, ConfigKey.Scope.Account, null);
ConfigKey<Boolean> MountDisabledStoragePool = new ConfigKey<>(Boolean.class,
@ -203,7 +203,7 @@ public interface StorageManager extends StorageService {
true,
ConfigKey.Scope.Global,
null);
static final ConfigKey<Boolean> DataStoreDownloadFollowRedirects = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED,
ConfigKey<Boolean> DataStoreDownloadFollowRedirects = new ConfigKey<>(ConfigKey.CATEGORY_ADVANCED,
Boolean.class, "store.download.follow.redirects", "false",
"Whether HTTP redirect is followed during store downloads for objects such as template, volume etc.",
true, ConfigKey.Scope.Global);
@ -211,7 +211,7 @@ public interface StorageManager extends StorageService {
ConfigKey<Long> HEURISTICS_SCRIPT_TIMEOUT = new ConfigKey<>("Advanced", Long.class, "heuristics.script.timeout", "3000",
"The maximum runtime, in milliseconds, to execute the heuristic rule; if it is reached, a timeout will happen.", true);
ConfigKey<Boolean> AllowVolumeReSizeBeyondAllocation = new ConfigKey<Boolean>("Advanced", Boolean.class, "volume.resize.allowed.beyond.allocation", "false",
ConfigKey<Boolean> AllowVolumeReSizeBeyondAllocation = new ConfigKey<>("Advanced", Boolean.class, "volume.resize.allowed.beyond.allocation", "false",
"Determines whether volume size can exceed the pool capacity allocation disable threshold (pool.storage.allocated.capacity.disablethreshold) " +
"when resize a volume upto resize capacity disable threshold (pool.storage.allocated.resize.capacity.disablethreshold)",
true, List.of(ConfigKey.Scope.StoragePool, ConfigKey.Scope.Zone));
@ -228,6 +228,10 @@ public interface StorageManager extends StorageService {
ConfigKey.Scope.Global,
null);
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
@ -252,14 +256,14 @@ public interface StorageManager extends StorageService {
/**
* Returns a comma separated list of tags for the specified storage pool
* @param poolId
* @param poolId the id of the pool to get tags for
* @return comma separated list of tags
*/
String getStoragePoolTags(long poolId);
/**
* Returns a list of Strings with tags for the specified storage pool
* @param poolId
* @param poolId the id of the pool to get tags for
* @return comma separated list of tags
*/
List<String> getStoragePoolTagList(long poolId);
@ -276,7 +280,7 @@ public interface StorageManager extends StorageService {
Pair<Long, Answer> sendToPool(StoragePool pool, long[] hostIdsToTryFirst, List<Long> hostIdsToAvoid, Command cmd) throws StorageUnavailableException;
public Answer getVolumeStats(StoragePool pool, Command cmd);
Answer getVolumeStats(StoragePool pool, Command cmd);
boolean canPoolProvideStorageStats(StoragePool pool);

View File

@ -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";

View File

@ -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);
}
private MigrationResponse handleResponse(List<Future<AsyncCallFuture<DataObjectResult>>> futures, MigrationPolicy migrationPolicy, String message, boolean success) {
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<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;
}
}
}

View File

@ -62,8 +62,8 @@ function getChecksum() {
}
function createMetadataFile() {
local fileData=$(cat $SOURCEFILE)
echo -e "["default"]\nversion = $VERSION.${securityversion}\n" >> $METADATAFILE
local fileData=$(cat "$SOURCEFILE")
echo -e "["default"]\nversion = $VERSION.${securityversion}\n" >> "$METADATAFILE"
for template in "${templates[@]}"
do
section="${template%%:*}"
@ -76,7 +76,7 @@ function createMetadataFile() {
templatename="systemvm-${sectionHv%.*}-${VERSION}-${arch}"
checksum=$(getChecksum "$fileData" "$VERSION-${arch}-$hvName")
filename=$(echo ${downloadurl##*'/'})
echo -e "["$section"]\ntemplatename = $templatename\nchecksum = $checksum\ndownloadurl = $downloadurl\nfilename = $filename\narch = $arch\nguestos = $guestos\n" >> $METADATAFILE
echo -e "["$section"]\ntemplatename = $templatename\nchecksum = $checksum\ndownloadurl = $downloadurl\nfilename = $filename\narch = $arch\nguestos = $guestos\n" >> "$METADATAFILE"
done
}
@ -91,8 +91,8 @@ templates=( "kvm-x86_64:https://download.cloudstack.org/systemvm/${CS_VERSION}/s
"ovm3:https://download.cloudstack.org/systemvm/$CS_VERSION/systemvmtemplate-$VERSION-x86_64-ovm.raw.bz2" )
PARENTPATH="$( cd -- "$(dirname "$0")" >/dev/null 2>&1 ; pwd -P )/dist/systemvm-templates/"
mkdir -p $PARENTPATH
METADATAFILE=${PARENTPATH}"metadata.ini"
echo > $METADATAFILE
SOURCEFILE=${PARENTPATH}'sha512sum.txt'
mkdir -p "$PARENTPATH"
METADATAFILE="${PARENTPATH}metadata.ini"
echo > "$METADATAFILE"
SOURCEFILE="${PARENTPATH}sha512sum.txt"
createMetadataFile

View File

@ -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;
@ -321,7 +323,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;
@ -531,10 +532,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);
@ -553,14 +550,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());
}
@ -607,6 +600,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.
@ -652,45 +766,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);

View File

@ -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());
}
}

View File

@ -271,8 +271,7 @@ install -D client/target/utilities/bin/cloud-setup-baremetal ${RPM_BUILD_ROOT}%{
install -D client/target/utilities/bin/cloud-sysvmadm ${RPM_BUILD_ROOT}%{_bindir}/%{name}-sysvmadm
install -D client/target/utilities/bin/cloud-update-xenserver-licenses ${RPM_BUILD_ROOT}%{_bindir}/%{name}-update-xenserver-licenses
# Bundle cmk in cloudstack-management
CMK_REL=$(wget -O - "https://api.github.com/repos/apache/cloudstack-cloudmonkey/releases" 2>/dev/null | jq -r '.[0].tag_name')
wget https://github.com/apache/cloudstack-cloudmonkey/releases/download/$CMK_REL/cmk.linux.x86-64 -O ${RPM_BUILD_ROOT}%{_bindir}/cmk
wget https://github.com/apache/cloudstack-cloudmonkey/releases/latest/download/cmk.linux.x86-64 -O ${RPM_BUILD_ROOT}%{_bindir}/cmk
chmod +x ${RPM_BUILD_ROOT}%{_bindir}/cmk
cp -r client/target/utilities/scripts/db/* ${RPM_BUILD_ROOT}%{_datadir}/%{name}-management/setup

View File

@ -21,7 +21,6 @@ import java.util.List;
import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.api.response.ListResponse;
import org.apache.cloudstack.api.response.SystemVmResponse;
import org.apache.cloudstack.api.response.VolumeResponse;
import org.apache.cloudstack.response.VolumeMetricsStatsResponse;
@ -37,7 +36,7 @@ public class ListVolumesUsageHistoryCmd extends BaseResourceUsageHistoryCmd {
@Parameter(name = ApiConstants.ID, type = CommandType.UUID, entityType = VolumeResponse.class, description = "the ID of the volume.")
private Long id;
@Parameter(name=ApiConstants.IDS, type=CommandType.LIST, collectionType=CommandType.UUID, entityType= SystemVmResponse.class, description="the IDs of the volumes, mutually exclusive with id.")
@Parameter(name = ApiConstants.IDS, type = CommandType.LIST, collectionType = CommandType.UUID, entityType = VolumeResponse.class, description = "the IDs of the volumes, mutually exclusive with id.")
private List<Long> ids;
@Parameter(name = ApiConstants.NAME, type = CommandType.STRING, description = "name of the volume (a substring match is made against the parameter value returning the data for all matching Volumes).")

View File

@ -248,6 +248,30 @@ public class MetricsServiceImpl extends MutualExclusiveIdsManagerBase implements
return createVolumeMetricsStatsResponse(volumeList, volumeStatsList);
}
/**
* Outputs the parameters that should be used for access control in the query of a resource to
* {@code permittedAccounts} and {@code domainIdRecursiveListProject}.
* @param isIdProvided indicates whether any ID was provided to the command
*/
private void buildBaseACLSearchParametersForMetrics(boolean isIdProvided, List<Long> permittedAccounts, Ternary<Long, Boolean,
Project.ListProjectResourcesCriteria> domainIdRecursiveListProject) {
Account caller = CallContext.current().getCallingAccount();
Account.Type callerType = caller.getType();
boolean recursive = AccountTypesWithRecursiveUsageAccess.contains(callerType);
domainIdRecursiveListProject.second(recursive);
// If no ID was provided, then the listing will skip project resources (null); otherwise, project resources should
// be listed as well (any long allows this)
Long id = isIdProvided ? 1L : null;
// Allow users to also list metrics of resources owned by projects they belong to (-1L), and admins to list all
// metrics belonging to their domains recursively (null)
Long projectId = isIdProvided && callerType == Account.Type.NORMAL ? -1L : null;
accountMgr.buildACLSearchParameters(caller, id, null, projectId, permittedAccounts, domainIdRecursiveListProject, true, false);
}
/**
* Searches VMs based on {@code ListVMsUsageHistoryCmd} parameters.
*
@ -255,18 +279,18 @@ public class MetricsServiceImpl extends MutualExclusiveIdsManagerBase implements
* @return the list of VMs.
*/
protected Pair<List<UserVmVO>, Integer> searchForUserVmsInternal(ListVMsUsageHistoryCmd cmd) {
final Long id = cmd.getId();
Account caller = CallContext.current().getCallingAccount();
List<Long> ids = getIdsListFromCmd(cmd.getId(), cmd.getIds());
boolean isIdProvided = CollectionUtils.isNotEmpty(ids);
List<Long> permittedAccounts = new ArrayList<>();
boolean recursive = AccountTypesWithRecursiveUsageAccess.contains(caller.getType());
Ternary<Long, Boolean, Project.ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<>(null, recursive, null);
accountMgr.buildACLSearchParameters(caller, id, null, null, permittedAccounts, domainIdRecursiveListProject, true, false);
Ternary<Long, Boolean, Project.ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<>(null, null, null);
buildBaseACLSearchParametersForMetrics(isIdProvided, permittedAccounts, domainIdRecursiveListProject);
Long domainId = domainIdRecursiveListProject.first();
Boolean isRecursive = domainIdRecursiveListProject.second();
Project.ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
Filter searchFilter = new Filter(UserVmVO.class, "id", true, cmd.getStartIndex(), cmd.getPageSizeVal());
List<Long> ids = getIdsListFromCmd(cmd.getId(), cmd.getIds());
String name = cmd.getName();
String keyword = cmd.getKeyword();
@ -361,18 +385,18 @@ public class MetricsServiceImpl extends MutualExclusiveIdsManagerBase implements
* @return the list of VMs.
*/
protected Pair<List<VolumeVO>, Integer> searchForVolumesInternal(ListVolumesUsageHistoryCmd cmd) {
final Long id = cmd.getId();
Account caller = CallContext.current().getCallingAccount();
List<Long> ids = getIdsListFromCmd(cmd.getId(), cmd.getIds());
boolean isIdProvided = CollectionUtils.isNotEmpty(ids);
List<Long> permittedAccounts = new ArrayList<>();
boolean recursive = AccountTypesWithRecursiveUsageAccess.contains(caller.getType());
Ternary<Long, Boolean, Project.ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<>(null, recursive, null);
accountMgr.buildACLSearchParameters(caller, id, null, null, permittedAccounts, domainIdRecursiveListProject, true, false);
Ternary<Long, Boolean, Project.ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<>(null, null, null);
buildBaseACLSearchParametersForMetrics(isIdProvided, permittedAccounts, domainIdRecursiveListProject);
Long domainId = domainIdRecursiveListProject.first();
Boolean isRecursive = domainIdRecursiveListProject.second();
Project.ListProjectResourcesCriteria listProjectResourcesCriteria = domainIdRecursiveListProject.third();
Filter searchFilter = new Filter(VolumeVO.class, "id", true, cmd.getStartIndex(), cmd.getPageSizeVal());
List<Long> ids = getIdsListFromCmd(cmd.getId(), cmd.getIds());
String name = cmd.getName();
String keyword = cmd.getKeyword();

View File

@ -736,7 +736,7 @@ public class ConsoleProxyManagerImpl extends ManagerBase implements ConsoleProxy
logger.debug("Unable to allocate proxy {} with {} in {} due to [{}]. Retrying with another template", proxy, template, dc, e.getMessage(), e);
continue;
}
throw new CloudRuntimeException("Failed to allocate proxy [%s] in zone [%s] with available templates", e);
throw new CloudRuntimeException(String.format("Failed to allocate proxy [%s] in zone [%s] with available templates", proxy, dc), e);
}
}

View File

@ -3354,7 +3354,7 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C
throw new InvalidParameterValueException(String.format("host: %s is not a secondary storage", secHost));
}
URI uri = null;
URI uri;
try {
uri = new URI(UriUtils.encodeURIComponent(newUrl));
if (uri.getScheme() == null) {
@ -3377,7 +3377,7 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C
String oldUrl = secHost.getStorageUrl();
URI oldUri = null;
URI oldUri;
try {
oldUri = new URI(UriUtils.encodeURIComponent(oldUrl));
if (!oldUri.getScheme().equalsIgnoreCase(uri.getScheme())) {
@ -4602,7 +4602,8 @@ public class StorageManagerImpl extends ManagerBase implements StorageManager, C
DataStoreDownloadFollowRedirects,
AllowVolumeReSizeBeyondAllocation,
StoragePoolHostConnectWorkers,
ObjectStorageCapacityThreshold
ObjectStorageCapacityThreshold,
COPY_PUBLIC_TEMPLATES_FROM_OTHER_STORAGES
};
}