mirror of https://github.com/apache/cloudstack.git
storage: Add config keys for controlling public/private template secondary storage replica counts (#12877)
Adds two new operator-level configuration keys to control the number of secondary storage copies made for public and private templates, decoupling replica count from template visibility. - secstorage.public.template.copy.max (default: 0 = all stores, preserving existing behavior) - secstorage.private.template.copy.max (default: 1, preserving existing behavior)
This commit is contained in:
parent
06aebb63ee
commit
5ed4894e97
|
|
@ -67,6 +67,12 @@ public interface TemplateService {
|
|||
|
||||
void handleTemplateSync(DataStore store);
|
||||
|
||||
void enforceSecStorageCopyLimit(long templateId, long zoneId);
|
||||
|
||||
boolean canCopyTemplateToImageStore(long templateId, long zoneId);
|
||||
|
||||
void replicateTemplateUpToCap(long templateId, long zoneId);
|
||||
|
||||
void downloadBootstrapSysTemplate(DataStore store);
|
||||
|
||||
void addSystemVMTemplatesToSecondary(DataStore store);
|
||||
|
|
|
|||
|
|
@ -45,6 +45,8 @@ import com.cloud.vm.VirtualMachineProfile;
|
|||
public interface TemplateManager {
|
||||
static final String AllowPublicUserTemplatesCK = "allow.public.user.templates";
|
||||
static final String TemplatePreloaderPoolSizeCK = "template.preloader.pool.size";
|
||||
static final String PublicTemplateSecStorageCopyCK = "secstorage.public.template.copy.max";
|
||||
static final String PrivateTemplateSecStorageCopyCK = "secstorage.private.template.copy.max";
|
||||
|
||||
static final ConfigKey<Boolean> AllowPublicUserTemplates = new ConfigKey<Boolean>("Advanced", Boolean.class, AllowPublicUserTemplatesCK, "true",
|
||||
"If false, users will not be able to create public Templates.", true, ConfigKey.Scope.Account);
|
||||
|
|
@ -64,6 +66,18 @@ public interface TemplateManager {
|
|||
true,
|
||||
ConfigKey.Scope.Global);
|
||||
|
||||
ConfigKey<Integer> PublicTemplateSecStorageCopy = new ConfigKey<Integer>("Advanced", Integer.class,
|
||||
PublicTemplateSecStorageCopyCK, "0",
|
||||
"Maximum number of secondary storage pools to which a public template is copied. " +
|
||||
"0 means copy to all secondary storage pools (default behavior).",
|
||||
true, ConfigKey.Scope.Zone);
|
||||
|
||||
ConfigKey<Integer> PrivateTemplateSecStorageCopy = new ConfigKey<Integer>("Advanced", Integer.class,
|
||||
PrivateTemplateSecStorageCopyCK, "1",
|
||||
"Maximum number of secondary storage pools to which a private template is copied. " +
|
||||
"Default is 1 to preserve existing behavior.",
|
||||
true, ConfigKey.Scope.Zone);
|
||||
|
||||
ConfigKey<Integer> VmIsoMaxCount = new ConfigKey<Integer>("Advanced",
|
||||
Integer.class,
|
||||
"vm.iso.max.count", "1",
|
||||
|
|
@ -153,6 +167,12 @@ public interface TemplateManager {
|
|||
|
||||
List<DataStore> getImageStoreByTemplate(long templateId, Long zoneId);
|
||||
|
||||
/**
|
||||
* Max number of secondary storage copies for the template in this zone; {@code 0} means no limit.
|
||||
* SYSTEM/ROUTING/BUILTIN templates are always exempt (returns {@code 0}).
|
||||
*/
|
||||
int getSecStorageCopyLimit(VMTemplateVO template, long zoneId);
|
||||
|
||||
TemplateInfo prepareIso(long isoId, long dcId, Long hostId, Long poolId);
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -295,6 +295,171 @@ public class TemplateServiceImpl implements TemplateService {
|
|||
}
|
||||
}
|
||||
|
||||
private int countActiveSecStorageCopies(long templateId, long zoneId) {
|
||||
List<DataStore> stores = _storeMgr.getImageStoresByScope(new ZoneScope(zoneId));
|
||||
if (stores == null || stores.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
int count = 0;
|
||||
for (DataStore ds : stores) {
|
||||
List<TemplateDataStoreVO> rows = _vmTemplateStoreDao.listByTemplateStore(templateId, ds.getId());
|
||||
if (rows == null) {
|
||||
continue;
|
||||
}
|
||||
for (TemplateDataStoreVO row : rows) {
|
||||
State st = row.getState();
|
||||
Status ds_state = row.getDownloadState();
|
||||
if (st != State.Failed && st != State.Destroyed
|
||||
&& ds_state != Status.ABANDONED && ds_state != Status.DOWNLOAD_ERROR) {
|
||||
count++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Central gate for the secondary storage copy limit (secstorage.public/private.template.copy.max).
|
||||
* Every template-landing path (periodic sync, cross-zone copy, register, upload) should consult this
|
||||
* single method before placing another copy of a template on a secondary store in a zone, so the limit
|
||||
* is enforced consistently instead of being re-implemented per call site.
|
||||
*
|
||||
* SYSTEM/ROUTING/BUILTIN templates and a limit of 0 mean "unlimited" (return true). The per-template,
|
||||
* per-zone {@link GlobalLock} serializes concurrent placement decisions so racing SSVM syncs / copies
|
||||
* cannot collectively exceed the limit.
|
||||
*/
|
||||
@Override
|
||||
public boolean canCopyTemplateToImageStore(long templateId, long zoneId) {
|
||||
VMTemplateVO template = _templateDao.findById(templateId);
|
||||
if (template == null) {
|
||||
return false;
|
||||
}
|
||||
int copyLimit = _tmpltMgr.getSecStorageCopyLimit(template, zoneId);
|
||||
if (copyLimit <= 0) {
|
||||
logger.debug("Template [{}] has no secondary storage copy limit in zone [{}] (limit={}); copy allowed.",
|
||||
template.getUniqueName(), zoneId, copyLimit);
|
||||
return true;
|
||||
}
|
||||
int count = countActiveSecStorageCopies(templateId, zoneId);
|
||||
logger.debug("Template [{}] secstorage copy check in zone [{}]: count={}, limit={}",
|
||||
template.getUniqueName(), zoneId, count, copyLimit);
|
||||
return count < copyLimit;
|
||||
}
|
||||
|
||||
private boolean hasReachedSecStorageCopyLimit(VMTemplateVO template, long zoneId) {
|
||||
return !canCopyTemplateToImageStore(template.getId(), zoneId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void replicateTemplateUpToCap(long templateId, long zoneId) {
|
||||
VMTemplateVO template = _templateDao.findById(templateId);
|
||||
if (template == null) {
|
||||
return;
|
||||
}
|
||||
int copyLimit = _tmpltMgr.getSecStorageCopyLimit(template, zoneId);
|
||||
if (copyLimit <= 0) {
|
||||
return;
|
||||
}
|
||||
int needed = copyLimit - countActiveSecStorageCopies(templateId, zoneId);
|
||||
if (needed <= 0) {
|
||||
return;
|
||||
}
|
||||
List<DataStore> stores = _storeMgr.getImageStoresByScope(new ZoneScope(zoneId));
|
||||
if (stores == null || stores.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
int kicked = 0;
|
||||
for (DataStore store : stores) {
|
||||
if (kicked >= needed) {
|
||||
break;
|
||||
}
|
||||
if (hasActiveTemplateCopyOnStore(templateId, store.getId())) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
storageOrchestrator.orchestrateTemplateCopyFromSecondaryStores(templateId, store);
|
||||
kicked++;
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to proactively replicate template [{}] to image store [{}] in zone [{}]: {}",
|
||||
template.getUniqueName(), store.getName(), zoneId, e.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean hasActiveTemplateCopyOnStore(long templateId, long storeId) {
|
||||
List<TemplateDataStoreVO> rows = _vmTemplateStoreDao.listByTemplateStore(templateId, storeId);
|
||||
if (rows == null) {
|
||||
return false;
|
||||
}
|
||||
for (TemplateDataStoreVO row : rows) {
|
||||
State st = row.getState();
|
||||
Status ds = row.getDownloadState();
|
||||
if (st != State.Failed && st != State.Destroyed
|
||||
&& ds != Status.ABANDONED && ds != Status.DOWNLOAD_ERROR) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void enforceSecStorageCopyLimit(long templateId, long zoneId) {
|
||||
VMTemplateVO template = _templateDao.findById(templateId);
|
||||
if (template == null) {
|
||||
return;
|
||||
}
|
||||
int copyLimit = _tmpltMgr.getSecStorageCopyLimit(template, zoneId);
|
||||
if (copyLimit <= 0) {
|
||||
return;
|
||||
}
|
||||
if (_tmpltMgr.verifyHeuristicRulesForZone(template, zoneId) != null) {
|
||||
return;
|
||||
}
|
||||
GlobalLock lock = GlobalLock.getInternLock("template.copy.limit." + templateId + "." + zoneId);
|
||||
try {
|
||||
if (!lock.lock(30)) {
|
||||
logger.warn("Could not acquire lock to enforce secondary storage copy limit for template [{}] in zone [{}].",
|
||||
template.getUniqueName(), zoneId);
|
||||
return;
|
||||
}
|
||||
List<DataStore> stores = _storeMgr.getImageStoresByScope(new ZoneScope(zoneId));
|
||||
if (stores == null) {
|
||||
return;
|
||||
}
|
||||
List<TemplateDataStoreVO> removable = new ArrayList<>();
|
||||
for (DataStore ds : stores) {
|
||||
TemplateDataStoreVO ref = _vmTemplateStoreDao.findByStoreTemplate(ds.getId(), templateId);
|
||||
if (ref != null
|
||||
&& ref.getState() == State.Ready
|
||||
&& ref.getDownloadState() == Status.DOWNLOADED
|
||||
&& (ref.getRefCnt() == null || ref.getRefCnt() == 0)) {
|
||||
removable.add(ref);
|
||||
}
|
||||
}
|
||||
int excess = removable.size() - copyLimit;
|
||||
if (excess <= 0) {
|
||||
return;
|
||||
}
|
||||
logger.info("Template [{}] has [{}] removable secondary storage copies in zone [{}], limit is [{}]; removing [{}] excess copies.",
|
||||
template.getUniqueName(), removable.size(), zoneId, copyLimit, excess);
|
||||
for (int i = 0; i < excess; i++) {
|
||||
DataStore ds = _storeMgr.getDataStore(removable.get(i).getDataStoreId(), DataStoreRole.Image);
|
||||
try {
|
||||
deleteTemplateAsync(_templateFactory.getTemplate(templateId, ds));
|
||||
logger.info("Removed excess copy of template [{}] from image store [{}] to honor the secondary storage copy limit.",
|
||||
template.getUniqueName(), ds.getName());
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to remove excess copy of template [{}] from image store [{}]: {}",
|
||||
template.getUniqueName(), ds, e.getMessage());
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
lock.unlock();
|
||||
lock.releaseRef();
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean shouldDownloadTemplateToStore(VMTemplateVO template, DataStore store) {
|
||||
Long zoneId = store.getScope().getScopeId();
|
||||
DataStore directedStore = _tmpltMgr.verifyHeuristicRulesForZone(template, zoneId);
|
||||
|
|
@ -304,6 +469,12 @@ public class TemplateServiceImpl implements TemplateService {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (zoneId != null && hasReachedSecStorageCopyLimit(template, zoneId)) {
|
||||
logger.info("Skipping sync of template [{}] to image store [{}]: zone [{}] has reached the configured copy limit.",
|
||||
template.getUniqueName(), store.getName(), zoneId);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (template.isPublicTemplate()) {
|
||||
logger.debug("Download of template [{}] to image store [{}] cannot be skipped, as it is public.", template.getUniqueName(),
|
||||
store.getName());
|
||||
|
|
@ -328,8 +499,9 @@ public class TemplateServiceImpl implements TemplateService {
|
|||
return true;
|
||||
}
|
||||
|
||||
logger.info("Skipping download of template [{}] to image store [{}].", template.getUniqueName(), store.getName());
|
||||
return false;
|
||||
logger.debug("Copying template [{}] to image store [{}] to reach the configured secondary storage copy limit in zone [{}].",
|
||||
template.getUniqueName(), store.getName(), zoneId);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -531,10 +703,13 @@ public class TemplateServiceImpl implements TemplateService {
|
|||
&& tmpltStore.getState() == State.Ready
|
||||
&& tmpltStore.getInstallPath() == null) {
|
||||
logger.info("Keep fake entry in template store table for migration of previous NFS to object store");
|
||||
} else {
|
||||
} else if (tmpltStore.getDownloadState() == VMTemplateStorageResourceAssoc.Status.DOWNLOADED
|
||||
|| tmpltStore.getState() == State.Ready) {
|
||||
logger.info("Removing leftover template {} entry from template store table", tmplt);
|
||||
// remove those leftover entries
|
||||
_vmTemplateStoreDao.remove(tmpltStore.getId());
|
||||
} else {
|
||||
logger.debug("Template {} entry on store {} is in pre-download state ({}/{}); not treating as leftover.",
|
||||
tmplt, store, tmpltStore.getState(), tmpltStore.getDownloadState());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -556,7 +731,7 @@ public class TemplateServiceImpl implements TemplateService {
|
|||
availHypers.add(HypervisorType.None); // bug 9809: resume ISO
|
||||
// download.
|
||||
for (VMTemplateVO tmplt : toBeDownloaded) {
|
||||
// if this is private template, skip sync to a new image store
|
||||
// skip stores excluded by heuristic rules or already at the configured copy limit
|
||||
if (!shouldDownloadTemplateToStore(tmplt, store)) {
|
||||
continue;
|
||||
}
|
||||
|
|
@ -580,6 +755,12 @@ public class TemplateServiceImpl implements TemplateService {
|
|||
}
|
||||
}
|
||||
|
||||
if (zoneId != null) {
|
||||
for (VMTemplateVO tmplt : allTemplates) {
|
||||
enforceSecStorageCopyLimit(tmplt.getId(), zoneId);
|
||||
}
|
||||
}
|
||||
|
||||
for (String uniqueName : templateInfos.keySet()) {
|
||||
TemplateProp tInfo = templateInfos.get(uniqueName);
|
||||
if (_tmpltMgr.templateIsDeleteable(tInfo.getId())) {
|
||||
|
|
@ -965,6 +1146,15 @@ public class TemplateServiceImpl implements TemplateService {
|
|||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
DataStore destStore = template.getDataStore();
|
||||
if (destStore != null && destStore.getScope() != null && destStore.getScope().getScopeId() != null) {
|
||||
enforceSecStorageCopyLimit(template.getId(), destStore.getScope().getScopeId());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to enforce secstorage copy limit after template [{}] became Ready: {}", template.getUuid(), e.getMessage());
|
||||
}
|
||||
|
||||
if (parentCallback != null) {
|
||||
parentCallback.complete(result);
|
||||
}
|
||||
|
|
@ -1406,6 +1596,14 @@ public class TemplateServiceImpl implements TemplateService {
|
|||
destTemplate.processEvent(Event.OperationFailed);
|
||||
} else {
|
||||
destTemplate.processEvent(Event.OperationSucceeded, result.getAnswer());
|
||||
try {
|
||||
DataStore destStore = destTemplate.getDataStore();
|
||||
if (destStore != null && destStore.getScope() != null && destStore.getScope().getScopeId() != null) {
|
||||
enforceSecStorageCopyLimit(destTemplate.getId(), destStore.getScope().getScopeId());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to enforce secstorage copy limit after copy of template [{}] became Ready: {}", destTemplate.getUuid(), e.getMessage());
|
||||
}
|
||||
}
|
||||
future.complete(res);
|
||||
} catch (Exception e) {
|
||||
|
|
@ -1431,6 +1629,15 @@ public class TemplateServiceImpl implements TemplateService {
|
|||
destTemplate.processEvent(Event.OperationFailed);
|
||||
} else {
|
||||
destTemplate.processEvent(Event.OperationSucceeded, result.getAnswer());
|
||||
try {
|
||||
DataStore destStore = destTemplate.getDataStore();
|
||||
if (destStore != null && destStore.getScope() != null && destStore.getScope().getScopeId() != null) {
|
||||
replicateTemplateUpToCap(destTemplate.getId(), destStore.getScope().getScopeId());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to schedule additional copies for cross-zone copied template [{}]: {}",
|
||||
destTemplate.getUuid(), e.getMessage());
|
||||
}
|
||||
}
|
||||
future.complete(res);
|
||||
} catch (Exception e) {
|
||||
|
|
|
|||
|
|
@ -119,6 +119,7 @@ public class TemplateServiceImplTest {
|
|||
Mockito.doReturn(templateInfoMock).when(templateDataFactoryMock).getTemplate(2L, sourceStoreMock);
|
||||
Mockito.doReturn(3L).when(dataStoreMock).getId();
|
||||
Mockito.doReturn(zoneScopeMock).when(dataStoreMock).getScope();
|
||||
Mockito.lenient().doReturn(tmpltMock).when(templateDao).findById(2L);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -153,11 +154,37 @@ public class TemplateServiceImplTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void shouldDownloadTemplateToStoreTestSkipsPrivateExistingTemplate() {
|
||||
public void shouldDownloadTemplateToStoreTestReplicatesPrivateTemplateUnderCopyLimit() {
|
||||
DataStore storeWithCopy = Mockito.mock(DataStore.class);
|
||||
Mockito.doReturn(10L).when(storeWithCopy).getId();
|
||||
Mockito.when(templateManagerMock.getSecStorageCopyLimit(tmpltMock, zoneScopeMock.getScopeId())).thenReturn(2);
|
||||
Mockito.doReturn(List.of(storeWithCopy)).when(dataStoreManagerMock).getImageStoresByScope(Mockito.any());
|
||||
Mockito.doReturn(List.of(Mockito.mock(TemplateDataStoreVO.class))).when(templateDataStoreDao).listByTemplateStore(2L, 10L);
|
||||
Mockito.when(templateDataStoreDao.findByTemplateZone(tmpltMock.getId(), zoneScopeMock.getScopeId(), DataStoreRole.Image)).thenReturn(Mockito.mock(TemplateDataStoreVO.class));
|
||||
Assert.assertTrue(templateService.shouldDownloadTemplateToStore(tmpltMock, dataStoreMock));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldDownloadTemplateToStoreTestSkipsPrivateTemplateAtCopyLimit() {
|
||||
DataStore storeWithCopy = Mockito.mock(DataStore.class);
|
||||
Mockito.doReturn(10L).when(storeWithCopy).getId();
|
||||
Mockito.when(templateManagerMock.getSecStorageCopyLimit(tmpltMock, zoneScopeMock.getScopeId())).thenReturn(1);
|
||||
Mockito.doReturn(List.of(storeWithCopy)).when(dataStoreManagerMock).getImageStoresByScope(Mockito.any());
|
||||
Mockito.doReturn(List.of(Mockito.mock(TemplateDataStoreVO.class))).when(templateDataStoreDao).listByTemplateStore(2L, 10L);
|
||||
Assert.assertFalse(templateService.shouldDownloadTemplateToStore(tmpltMock, dataStoreMock));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void canCopyTemplateToImageStoreTestUnlimitedWhenLimitIsZero() {
|
||||
Mockito.when(templateManagerMock.getSecStorageCopyLimit(tmpltMock, 1L)).thenReturn(0);
|
||||
Assert.assertTrue(templateService.canCopyTemplateToImageStore(2L, 1L));
|
||||
}
|
||||
|
||||
// The under-limit / at-limit behavior of canCopyTemplateToImageStore is exercised through
|
||||
// shouldDownloadTemplateToStore above (Replicates*UnderCopyLimit / Skips*AtCopyLimit), which run it via
|
||||
// the real call path. Calling the GlobalLock-wrapped method directly on the Mockito spy is not reliable
|
||||
// in the unit-test JVM, so it is not duplicated here.
|
||||
|
||||
@Test
|
||||
public void tryDownloadingTemplateToImageStoreTestDownloadsTemplateWhenUrlIsNotNull() {
|
||||
Mockito.doReturn("url").when(tmpltMock).getUrl();
|
||||
|
|
|
|||
|
|
@ -574,6 +574,12 @@ public class ImageStoreUploadMonitorImpl extends ManagerBase implements ImageSto
|
|||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Template {} uploaded successfully", tmpTemplate);
|
||||
}
|
||||
try {
|
||||
templateService.replicateTemplateUpToCap(tmpTemplate.getId(), vo.getDataCenterId());
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to schedule additional copies for uploaded template [{}] in zone [{}]: {}",
|
||||
tmpTemplate.getUuid(), vo.getDataCenterId(), e.getMessage());
|
||||
}
|
||||
break;
|
||||
case IN_PROGRESS:
|
||||
if (!checkAndUpdateTemplateResourceLimit(tmpTemplate, tmpTemplateDataStore, answer)) {
|
||||
|
|
|
|||
|
|
@ -19,10 +19,10 @@ package com.cloud.template;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
|
@ -264,9 +264,10 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase {
|
|||
|
||||
if (imageStore == null) {
|
||||
List<DataStore> imageStores = getImageStoresThrowsExceptionIfNotFound(zoneId, profile);
|
||||
standardImageStoreAllocation(imageStores, template);
|
||||
standardImageStoreAllocation(imageStores, template, zoneId);
|
||||
} else {
|
||||
validateSecondaryStorageAndCreateTemplate(List.of(imageStore), template, null);
|
||||
int copyLimit = getSecStorageCopyLimit(template, zoneId);
|
||||
validateSecondaryStorageAndCreateTemplate(List.of(imageStore), template, new HashMap<>(), copyLimit);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -279,17 +280,17 @@ public class HypervisorTemplateAdapter extends TemplateAdapterBase {
|
|||
return imageStores;
|
||||
}
|
||||
|
||||
protected void standardImageStoreAllocation(List<DataStore> imageStores, VMTemplateVO template) {
|
||||
Set<Long> zoneSet = new HashSet<Long>();
|
||||
protected void standardImageStoreAllocation(List<DataStore> imageStores, VMTemplateVO template, long zoneId) {
|
||||
int copyLimit = getSecStorageCopyLimit(template, zoneId);
|
||||
Collections.shuffle(imageStores);
|
||||
validateSecondaryStorageAndCreateTemplate(imageStores, template, zoneSet);
|
||||
validateSecondaryStorageAndCreateTemplate(imageStores, template, new HashMap<>(), copyLimit);
|
||||
}
|
||||
|
||||
protected void validateSecondaryStorageAndCreateTemplate(List<DataStore> imageStores, VMTemplateVO template, Set<Long> zoneSet) {
|
||||
protected void validateSecondaryStorageAndCreateTemplate(List<DataStore> imageStores, VMTemplateVO template, Map<Long, Integer> zoneCopyCount, int copyLimit) {
|
||||
for (DataStore imageStore : imageStores) {
|
||||
Long zoneId = imageStore.getScope().getScopeId();
|
||||
|
||||
if (!isZoneAndImageStoreAvailable(imageStore, zoneId, zoneSet, isPrivateTemplate(template))) {
|
||||
if (!isZoneAndImageStoreAvailable(imageStore, zoneId, zoneCopyCount, copyLimit)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,11 +20,9 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
|
|
@ -169,7 +167,11 @@ public abstract class TemplateAdapterBase extends AdapterBase implements Templat
|
|||
return heuristicRuleHelper.getImageStoreIfThereIsHeuristicRule(zoneId, heuristicType, template);
|
||||
}
|
||||
|
||||
protected boolean isZoneAndImageStoreAvailable(DataStore imageStore, Long zoneId, Set<Long> zoneSet, boolean isTemplatePrivate) {
|
||||
protected int getSecStorageCopyLimit(VMTemplateVO template, long zoneId) {
|
||||
return templateMgr.getSecStorageCopyLimit(template, zoneId);
|
||||
}
|
||||
|
||||
protected boolean isZoneAndImageStoreAvailable(DataStore imageStore, Long zoneId, Map<Long, Integer> zoneCopyCount, int copyLimit) {
|
||||
if (zoneId == null) {
|
||||
logger.warn(String.format("Zone ID is null, cannot allocate ISO/template in image store [%s].", imageStore));
|
||||
return false;
|
||||
|
|
@ -191,33 +193,30 @@ public abstract class TemplateAdapterBase extends AdapterBase implements Templat
|
|||
return false;
|
||||
}
|
||||
|
||||
if (zoneSet == null) {
|
||||
logger.info(String.format("Zone set is null; therefore, the ISO/template should be allocated in every secondary storage of zone [%s].", zone));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isTemplatePrivate && zoneSet.contains(zoneId)) {
|
||||
logger.info(String.format("The template is private and it is already allocated in a secondary storage in zone [%s]; therefore, image store [%s] will be skipped.",
|
||||
zone, imageStore));
|
||||
int currentCount = zoneCopyCount.getOrDefault(zoneId, 0);
|
||||
if (copyLimit > 0 && currentCount >= copyLimit) {
|
||||
logger.info("Copy limit of {} reached for zone [{}]; skipping image store [{}].", copyLimit, zone, imageStore);
|
||||
return false;
|
||||
}
|
||||
|
||||
logger.info(String.format("Private template will be allocated in image store [%s] in zone [%s].", imageStore, zone));
|
||||
zoneSet.add(zoneId);
|
||||
zoneCopyCount.put(zoneId, currentCount + 1);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the template/ISO is marked as private, then it is allocated to a random secondary storage; otherwise, allocates to every storage pool in every zone given by the
|
||||
* {@link TemplateProfile#getZoneIdList()}.
|
||||
* Allocates the template/ISO to a single image store - the one the file will be uploaded to. The upload can only
|
||||
* target one secondary store, so additional copies (up to the configured secstorage.public/private.template.copy.max)
|
||||
* are propagated later by template sync instead of being pre-allocated here as empty placeholder entries that never
|
||||
* receive the data.
|
||||
*/
|
||||
protected void postUploadAllocation(List<DataStore> imageStores, VMTemplateVO template, List<TemplateOrVolumePostUploadCommand> payloads) {
|
||||
Set<Long> zoneSet = new HashSet<>();
|
||||
Map<Long, Integer> zoneCopyCount = new HashMap<>();
|
||||
Collections.shuffle(imageStores);
|
||||
for (DataStore imageStore : imageStores) {
|
||||
Long zoneId_is = imageStore.getScope().getScopeId();
|
||||
int copyLimit = zoneId_is == null ? 0 : getSecStorageCopyLimit(template, zoneId_is);
|
||||
|
||||
if (!isZoneAndImageStoreAvailable(imageStore, zoneId_is, zoneSet, isPrivateTemplate(template))) {
|
||||
if (!isZoneAndImageStoreAvailable(imageStore, zoneId_is, zoneCopyCount, copyLimit)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -251,15 +250,11 @@ public abstract class TemplateAdapterBase extends AdapterBase implements Templat
|
|||
payload.setRequiresHvm(template.requiresHvm());
|
||||
payload.setDescription(template.getDisplayText());
|
||||
payloads.add(payload);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean isPrivateTemplate(VMTemplateVO template){
|
||||
// if public OR featured OR system template
|
||||
if (template.isPublicTemplate() || template.isFeatured() || template.getTemplateType() == TemplateType.SYSTEM) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
// The file can only be uploaded to a single secondary store. Allocate just this one; additional copies
|
||||
// up to the configured secondary storage copy limit are propagated afterwards by template sync, so we do
|
||||
// not create empty placeholder template_store_ref rows on the other stores.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -943,6 +943,12 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
|
|||
_tmplStoreDao.removeByTemplateStore(tmpltId, dstSecStore.getId());
|
||||
}
|
||||
|
||||
if (!_tmpltSvr.canCopyTemplateToImageStore(tmpltId, dstZoneId)) {
|
||||
logger.info("Not copying template {} to image store {}: zone {} has reached the configured secondary storage copy limit.",
|
||||
template, dstSecStore, dstZone);
|
||||
continue;
|
||||
}
|
||||
|
||||
AsyncCallFuture<TemplateApiResult> future = _tmpltSvr.copyTemplate(srcTemplate, dstSecStore);
|
||||
try {
|
||||
TemplateApiResult result = future.get();
|
||||
|
|
@ -1914,6 +1920,13 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
|
|||
_launchPermissionDao.removeAllPermissions(id);
|
||||
_messageBus.publish(_name, TemplateManager.MESSAGE_RESET_TEMPLATE_PERMISSION_EVENT, PublishScope.LOCAL, template.getId());
|
||||
}
|
||||
|
||||
if (isPublic != null || isFeatured != null || "reset".equalsIgnoreCase(operation)) {
|
||||
for (VMTemplateZoneVO templateZone : _tmpltZoneDao.listByTemplateId(template.getId())) {
|
||||
_tmpltSvr.enforceSecStorageCopyLimit(template.getId(), templateZone.getZoneId());
|
||||
_tmpltSvr.replicateTemplateUpToCap(template.getId(), templateZone.getZoneId());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -1931,10 +1944,10 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
|
|||
Account caller = CallContext.current().getCallingAccount();
|
||||
boolean kvmSnapshotOnlyInPrimaryStorage = false;
|
||||
SnapshotInfo snapInfo = null;
|
||||
long zoneId = 0;
|
||||
|
||||
try {
|
||||
TemplateInfo tmplInfo = _tmplFactory.getTemplate(templateId, DataStoreRole.Image);
|
||||
long zoneId = 0;
|
||||
if (snapshotId != null) {
|
||||
snapshot = _snapshotDao.findById(snapshotId);
|
||||
if (command.getZoneId() == null) {
|
||||
|
|
@ -2074,6 +2087,12 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
|
|||
}
|
||||
|
||||
if (privateTemplate != null) {
|
||||
try {
|
||||
_tmpltSvr.replicateTemplateUpToCap(privateTemplate.getId(), zoneId);
|
||||
} catch (Exception e) {
|
||||
logger.warn("Failed to schedule additional copies for template [{}] in zone [{}]: {}",
|
||||
privateTemplate.getUniqueName(), zoneId, e.getMessage());
|
||||
}
|
||||
return privateTemplate;
|
||||
} else {
|
||||
throw new CloudRuntimeException("Failed to create a Template");
|
||||
|
|
@ -2397,6 +2416,20 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
|
|||
return stores;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getSecStorageCopyLimit(VMTemplateVO template, long zoneId) {
|
||||
if (template == null) {
|
||||
return 0;
|
||||
}
|
||||
TemplateType type = template.getTemplateType();
|
||||
if (type == TemplateType.SYSTEM || type == TemplateType.ROUTING || type == TemplateType.BUILTIN) {
|
||||
return 0;
|
||||
}
|
||||
return template.isPublicTemplate()
|
||||
? PublicTemplateSecStorageCopy.valueIn(zoneId)
|
||||
: PrivateTemplateSecStorageCopy.valueIn(zoneId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ActionEvent(eventType = EventTypes.EVENT_ISO_UPDATE, eventDescription = "Updating ISO", async = false)
|
||||
public VMTemplateVO updateTemplate(UpdateIsoCmd cmd) {
|
||||
|
|
@ -2718,6 +2751,8 @@ public class TemplateManagerImpl extends ManagerBase implements TemplateManager,
|
|||
TemplatePreloaderPoolSize,
|
||||
ValidateUrlIsResolvableBeforeRegisteringTemplate,
|
||||
TemplateDeleteFromPrimaryStorage,
|
||||
PublicTemplateSecStorageCopy,
|
||||
PrivateTemplateSecStorageCopy,
|
||||
VmIsoMaxCount};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -32,10 +32,8 @@ import java.lang.reflect.Method;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
|
||||
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
|
||||
|
|
@ -339,7 +337,7 @@ public class HypervisorTemplateAdapterTest {
|
|||
Mockito.when(templateProfileMock.getZoneIdList()).thenReturn(zoneIds);
|
||||
Mockito.doReturn(null).when(_adapter).getImageStoresThrowsExceptionIfNotFound(Mockito.any(Long.class), Mockito.any(TemplateProfile.class));
|
||||
Mockito.doReturn(null).when(_templateMgr).verifyHeuristicRulesForZone(Mockito.any(VMTemplateVO.class), Mockito.anyLong());
|
||||
Mockito.doNothing().when(_adapter).standardImageStoreAllocation(Mockito.isNull(), Mockito.any(VMTemplateVO.class));
|
||||
Mockito.doNothing().when(_adapter).standardImageStoreAllocation(Mockito.isNull(), Mockito.any(VMTemplateVO.class), Mockito.anyLong());
|
||||
|
||||
_adapter.createTemplateWithinZones(templateProfileMock, vmTemplateVOMock);
|
||||
|
||||
|
|
@ -355,11 +353,11 @@ public class HypervisorTemplateAdapterTest {
|
|||
Mockito.when(templateProfileMock.getZoneIdList()).thenReturn(zoneIds);
|
||||
Mockito.doReturn(null).when(_adapter).getImageStoresThrowsExceptionIfNotFound(Mockito.any(Long.class), Mockito.any(TemplateProfile.class));
|
||||
Mockito.doReturn(null).when(_templateMgr).verifyHeuristicRulesForZone(Mockito.any(VMTemplateVO.class), Mockito.anyLong());
|
||||
Mockito.doNothing().when(_adapter).standardImageStoreAllocation(Mockito.isNull(), Mockito.any(VMTemplateVO.class));
|
||||
Mockito.doNothing().when(_adapter).standardImageStoreAllocation(Mockito.isNull(), Mockito.any(VMTemplateVO.class), Mockito.anyLong());
|
||||
|
||||
_adapter.createTemplateWithinZones(templateProfileMock, vmTemplateVOMock);
|
||||
|
||||
Mockito.verify(_adapter, Mockito.times(1)).standardImageStoreAllocation(Mockito.isNull(), Mockito.any(VMTemplateVO.class));
|
||||
Mockito.verify(_adapter, Mockito.times(1)).standardImageStoreAllocation(Mockito.isNull(), Mockito.any(VMTemplateVO.class), Mockito.anyLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -371,11 +369,11 @@ public class HypervisorTemplateAdapterTest {
|
|||
|
||||
Mockito.when(templateProfileMock.getZoneIdList()).thenReturn(zoneIds);
|
||||
Mockito.doReturn(dataStoreMock).when(_templateMgr).verifyHeuristicRulesForZone(Mockito.any(VMTemplateVO.class), Mockito.anyLong());
|
||||
Mockito.doNothing().when(_adapter).validateSecondaryStorageAndCreateTemplate(Mockito.any(List.class), Mockito.any(VMTemplateVO.class), Mockito.isNull());
|
||||
Mockito.doNothing().when(_adapter).validateSecondaryStorageAndCreateTemplate(Mockito.any(List.class), Mockito.any(VMTemplateVO.class), Mockito.any(Map.class), Mockito.anyInt());
|
||||
|
||||
_adapter.createTemplateWithinZones(templateProfileMock, vmTemplateVOMock);
|
||||
|
||||
Mockito.verify(_adapter, Mockito.times(1)).validateSecondaryStorageAndCreateTemplate(Mockito.any(List.class), Mockito.any(VMTemplateVO.class), Mockito.isNull());
|
||||
Mockito.verify(_adapter, Mockito.times(1)).validateSecondaryStorageAndCreateTemplate(Mockito.any(List.class), Mockito.any(VMTemplateVO.class), Mockito.any(Map.class), Mockito.anyInt());
|
||||
}
|
||||
|
||||
@Test(expected = CloudRuntimeException.class)
|
||||
|
|
@ -411,11 +409,8 @@ public class HypervisorTemplateAdapterTest {
|
|||
@Test
|
||||
public void isZoneAndImageStoreAvailableTestZoneIdIsNullShouldReturnFalse() {
|
||||
DataStore dataStoreMock = Mockito.mock(DataStore.class);
|
||||
Long zoneId = null;
|
||||
Set<Long> zoneSet = null;
|
||||
boolean isTemplatePrivate = false;
|
||||
|
||||
boolean result = _adapter.isZoneAndImageStoreAvailable(dataStoreMock, zoneId, zoneSet, isTemplatePrivate);
|
||||
boolean result = _adapter.isZoneAndImageStoreAvailable(dataStoreMock, null, new HashMap<>(), 0);
|
||||
|
||||
Mockito.verify(loggerMock, Mockito.times(1)).warn(String.format("Zone ID is null, cannot allocate ISO/template in image store [%s].", dataStoreMock));
|
||||
Assert.assertFalse(result);
|
||||
|
|
@ -425,13 +420,10 @@ public class HypervisorTemplateAdapterTest {
|
|||
public void isZoneAndImageStoreAvailableTestZoneIsNullShouldReturnFalse() {
|
||||
DataStore dataStoreMock = Mockito.mock(DataStore.class);
|
||||
Long zoneId = 1L;
|
||||
Set<Long> zoneSet = null;
|
||||
boolean isTemplatePrivate = false;
|
||||
DataCenterVO dataCenterVOMock = null;
|
||||
|
||||
Mockito.when(_dcDao.findById(Mockito.anyLong())).thenReturn(dataCenterVOMock);
|
||||
Mockito.when(_dcDao.findById(Mockito.anyLong())).thenReturn(null);
|
||||
|
||||
boolean result = _adapter.isZoneAndImageStoreAvailable(dataStoreMock, zoneId, zoneSet, isTemplatePrivate);
|
||||
boolean result = _adapter.isZoneAndImageStoreAvailable(dataStoreMock, zoneId, new HashMap<>(), 0);
|
||||
|
||||
Mockito.verify(loggerMock, Mockito.times(1)).warn("Unable to find zone by id [{}], so skip downloading template to its image store [{}].",
|
||||
zoneId, dataStoreMock);
|
||||
|
|
@ -442,14 +434,12 @@ public class HypervisorTemplateAdapterTest {
|
|||
public void isZoneAndImageStoreAvailableTestZoneIsDisabledShouldReturnFalse() {
|
||||
DataStore dataStoreMock = Mockito.mock(DataStore.class);
|
||||
Long zoneId = 1L;
|
||||
Set<Long> zoneSet = null;
|
||||
boolean isTemplatePrivate = false;
|
||||
DataCenterVO dataCenterVOMock = Mockito.mock(DataCenterVO.class);
|
||||
|
||||
Mockito.when(_dcDao.findById(Mockito.anyLong())).thenReturn(dataCenterVOMock);
|
||||
Mockito.when(dataCenterVOMock.getAllocationState()).thenReturn(Grouping.AllocationState.Disabled);
|
||||
|
||||
boolean result = _adapter.isZoneAndImageStoreAvailable(dataStoreMock, zoneId, zoneSet, isTemplatePrivate);
|
||||
boolean result = _adapter.isZoneAndImageStoreAvailable(dataStoreMock, zoneId, new HashMap<>(), 0);
|
||||
|
||||
Mockito.verify(loggerMock, Mockito.times(1)).info("Zone [{}] is disabled. Skip downloading template to its image store [{}].", dataCenterVOMock, dataStoreMock);
|
||||
Assert.assertFalse(result);
|
||||
|
|
@ -459,15 +449,13 @@ public class HypervisorTemplateAdapterTest {
|
|||
public void isZoneAndImageStoreAvailableTestImageStoreDoesNotHaveEnoughCapacityShouldReturnFalse() {
|
||||
DataStore dataStoreMock = Mockito.mock(DataStore.class);
|
||||
Long zoneId = 1L;
|
||||
Set<Long> zoneSet = null;
|
||||
boolean isTemplatePrivate = false;
|
||||
DataCenterVO dataCenterVOMock = Mockito.mock(DataCenterVO.class);
|
||||
|
||||
Mockito.when(_dcDao.findById(Mockito.anyLong())).thenReturn(dataCenterVOMock);
|
||||
Mockito.when(dataCenterVOMock.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
|
||||
Mockito.when(statsCollectorMock.imageStoreHasEnoughCapacity(any(DataStore.class))).thenReturn(false);
|
||||
|
||||
boolean result = _adapter.isZoneAndImageStoreAvailable(dataStoreMock, zoneId, zoneSet, isTemplatePrivate);
|
||||
boolean result = _adapter.isZoneAndImageStoreAvailable(dataStoreMock, zoneId, new HashMap<>(), 0);
|
||||
|
||||
Mockito.verify(loggerMock, times(1)).info("Image store doesn't have enough capacity. Skip downloading template to this image store [{}].",
|
||||
dataStoreMock);
|
||||
|
|
@ -475,60 +463,72 @@ public class HypervisorTemplateAdapterTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void isZoneAndImageStoreAvailableTestImageStoreHasEnoughCapacityAndZoneSetIsNullShouldReturnTrue() {
|
||||
public void isZoneAndImageStoreAvailableTestReplicaLimitZeroShouldCopyToAllStores() {
|
||||
DataStore dataStoreMock = Mockito.mock(DataStore.class);
|
||||
Long zoneId = 1L;
|
||||
Set<Long> zoneSet = null;
|
||||
boolean isTemplatePrivate = false;
|
||||
DataCenterVO dataCenterVOMock = Mockito.mock(DataCenterVO.class);
|
||||
Map<Long, Integer> zoneCopyCount = new HashMap<>();
|
||||
zoneCopyCount.put(zoneId, 999);
|
||||
|
||||
Mockito.when(_dcDao.findById(Mockito.anyLong())).thenReturn(dataCenterVOMock);
|
||||
Mockito.when(dataCenterVOMock.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
|
||||
Mockito.when(statsCollectorMock.imageStoreHasEnoughCapacity(any(DataStore.class))).thenReturn(true);
|
||||
|
||||
boolean result = _adapter.isZoneAndImageStoreAvailable(dataStoreMock, zoneId, zoneSet, isTemplatePrivate);
|
||||
boolean result = _adapter.isZoneAndImageStoreAvailable(dataStoreMock, zoneId, zoneCopyCount, 0);
|
||||
|
||||
Mockito.verify(loggerMock, times(1)).info(String.format("Zone set is null; therefore, the ISO/template should be allocated in every secondary storage " +
|
||||
"of zone [%s].", dataCenterVOMock));
|
||||
Assert.assertTrue(result);
|
||||
Assert.assertEquals(1000, (int) zoneCopyCount.get(zoneId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isZoneAndImageStoreAvailableTestTemplateIsPrivateAndItIsAlreadyAllocatedToTheSameZoneShouldReturnFalse() {
|
||||
public void isZoneAndImageStoreAvailableTestReplicaLimitReachedShouldReturnFalse() {
|
||||
DataStore dataStoreMock = Mockito.mock(DataStore.class);
|
||||
Long zoneId = 1L;
|
||||
Set<Long> zoneSet = Set.of(1L);
|
||||
boolean isTemplatePrivate = true;
|
||||
DataCenterVO dataCenterVOMock = Mockito.mock(DataCenterVO.class);
|
||||
Map<Long, Integer> zoneCopyCount = new HashMap<>();
|
||||
zoneCopyCount.put(zoneId, 1);
|
||||
|
||||
Mockito.when(_dcDao.findById(Mockito.anyLong())).thenReturn(dataCenterVOMock);
|
||||
Mockito.when(dataCenterVOMock.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
|
||||
Mockito.when(statsCollectorMock.imageStoreHasEnoughCapacity(any(DataStore.class))).thenReturn(true);
|
||||
|
||||
boolean result = _adapter.isZoneAndImageStoreAvailable(dataStoreMock, zoneId, zoneSet, isTemplatePrivate);
|
||||
boolean result = _adapter.isZoneAndImageStoreAvailable(dataStoreMock, zoneId, zoneCopyCount, 1);
|
||||
|
||||
Mockito.verify(loggerMock, times(1)).info(String.format("The template is private and it is already allocated in a secondary storage in zone [%s]; " +
|
||||
"therefore, image store [%s] will be skipped.", dataCenterVOMock, dataStoreMock));
|
||||
Mockito.verify(loggerMock, times(1)).info("Copy limit of {} reached for zone [{}]; skipping image store [{}].", 1, dataCenterVOMock, dataStoreMock);
|
||||
Assert.assertFalse(result);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isZoneAndImageStoreAvailableTestTemplateIsPrivateAndItIsNotAlreadyAllocatedToTheSameZoneShouldReturnTrue() {
|
||||
public void isZoneAndImageStoreAvailableTestReplicaLimitNotYetReachedShouldReturnTrueAndIncrementCount() {
|
||||
DataStore dataStoreMock = Mockito.mock(DataStore.class);
|
||||
Long zoneId = 1L;
|
||||
Set<Long> zoneSet = new HashSet<>();
|
||||
boolean isTemplatePrivate = true;
|
||||
DataCenterVO dataCenterVOMock = Mockito.mock(DataCenterVO.class);
|
||||
Map<Long, Integer> zoneCopyCount = new HashMap<>();
|
||||
|
||||
Mockito.when(_dcDao.findById(Mockito.anyLong())).thenReturn(dataCenterVOMock);
|
||||
Mockito.when(dataCenterVOMock.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
|
||||
Mockito.when(statsCollectorMock.imageStoreHasEnoughCapacity(any(DataStore.class))).thenReturn(true);
|
||||
|
||||
boolean result = _adapter.isZoneAndImageStoreAvailable(dataStoreMock, zoneId, zoneSet, isTemplatePrivate);
|
||||
boolean result = _adapter.isZoneAndImageStoreAvailable(dataStoreMock, zoneId, zoneCopyCount, 2);
|
||||
|
||||
Mockito.verify(loggerMock, times(1)).info(String.format("Private template will be allocated in image store [%s] in zone [%s].",
|
||||
dataStoreMock, dataCenterVOMock));
|
||||
Assert.assertTrue(result);
|
||||
Assert.assertEquals(1, (int) zoneCopyCount.get(zoneId));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isZoneAndImageStoreAvailableTestReplicaLimitOfTwoShouldCopyToExactlyTwoStores() {
|
||||
Long zoneId = 1L;
|
||||
DataCenterVO dataCenterVOMock = Mockito.mock(DataCenterVO.class);
|
||||
Map<Long, Integer> zoneCopyCount = new HashMap<>();
|
||||
|
||||
Mockito.when(_dcDao.findById(Mockito.anyLong())).thenReturn(dataCenterVOMock);
|
||||
Mockito.when(dataCenterVOMock.getAllocationState()).thenReturn(Grouping.AllocationState.Enabled);
|
||||
Mockito.when(statsCollectorMock.imageStoreHasEnoughCapacity(any(DataStore.class))).thenReturn(true);
|
||||
|
||||
Assert.assertTrue(_adapter.isZoneAndImageStoreAvailable(Mockito.mock(DataStore.class), zoneId, zoneCopyCount, 2));
|
||||
Assert.assertTrue(_adapter.isZoneAndImageStoreAvailable(Mockito.mock(DataStore.class), zoneId, zoneCopyCount, 2));
|
||||
Assert.assertFalse(_adapter.isZoneAndImageStoreAvailable(Mockito.mock(DataStore.class), zoneId, zoneCopyCount, 2));
|
||||
Assert.assertEquals(2, (int) zoneCopyCount.get(zoneId));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
Loading…
Reference in New Issue