Skip to content

Commit b2db897

Browse files
shwstppryadvr
authored andcommitted
server: fix for respecting secondary storage threshold limit (#3480)
Retrieval of an image store using ImageStoreProviderManager has been refactored by introducing three different methods, DataStore getRandomImageStore(List<DataStore> imageStores); To get an image store for reading purpose. Threshold capacity check will not be used here. DataStore getImageStoreWithFreeCapacity(List<DataStore> imageStores); To get an image store for reading purpose. Threshold capacity check will be used here and the store with max free space will be returned. If no store with filled storage less than the threshold is found, the NULL value will be returned. List<DataStore> listImageStoresWithFreeCapacity(List<DataStore> imageStores); To get a list of image stores for writing purpose which fulfills threshold capacity check. Correspondingly DataStoreManager methods have been refactored to return similar values for a given zone. Fixes #3287 - NULL value will be returned when secondary storage is needed for writing but there is not store with free space. Fixes #3041 - Rather than returning random secondary storage for writing, storage with max. free space will be returned. Fixes #3478 - For migration on VMware, all writable secondary storage will be mounted while preparation. Signed-off-by: Abhishek Kumar <abhishek.mrt22@gmail.com>
1 parent cf0649d commit b2db897

21 files changed

Lines changed: 262 additions & 97 deletions

File tree

engine/api/src/main/java/org/apache/cloudstack/engine/subsystem/api/storage/DataStoreManager.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,11 @@ public interface DataStoreManager {
3333

3434
List<DataStore> getImageStoresByScope(ZoneScope scope);
3535

36-
DataStore getImageStore(long zoneId);
36+
DataStore getRandomImageStore(long zoneId);
37+
38+
DataStore getImageStoreWithFreeCapacity(long zoneId);
39+
40+
List<DataStore> listImageStoresWithFreeCapacity(long zoneId);
3741

3842
List<DataStore> getImageCacheStores(Scope scope);
3943

engine/storage/cache/src/main/java/org/apache/cloudstack/storage/cache/allocator/StorageCacheRandomAllocator.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public DataStore getCacheStore(Scope scope) {
6262
return null;
6363
}
6464

65-
return imageStoreMgr.getImageStore(cacheStores);
65+
return imageStoreMgr.getImageStoreWithFreeCapacity(cacheStores);
6666
}
6767

6868
@Override
@@ -88,6 +88,6 @@ public DataStore getCacheStore(DataObject data, Scope scope) {
8888
}
8989
}
9090
}
91-
return imageStoreMgr.getImageStore(cacheStores);
91+
return imageStoreMgr.getImageStoreWithFreeCapacity(cacheStores);
9292
}
9393
}

engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/AncientDataMotionStrategy.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,8 +328,8 @@ protected Answer copyVolumeBetweenPools(DataObject srcData, DataObject destData)
328328
if (cacheStore == null) {
329329
// need to find a nfs or cifs image store, assuming that can't copy volume
330330
// directly to s3
331-
ImageStoreEntity imageStore = (ImageStoreEntity)dataStoreMgr.getImageStore(destScope.getScopeId());
332-
if (!imageStore.getProtocol().equalsIgnoreCase("nfs") && !imageStore.getProtocol().equalsIgnoreCase("cifs")) {
331+
ImageStoreEntity imageStore = (ImageStoreEntity)dataStoreMgr.getImageStoreWithFreeCapacity(destScope.getScopeId());
332+
if (imageStore == null || !imageStore.getProtocol().equalsIgnoreCase("nfs") && !imageStore.getProtocol().equalsIgnoreCase("cifs")) {
333333
s_logger.debug("can't find a nfs (or cifs) image store to satisfy the need for a staging store");
334334
return null;
335335
}

engine/storage/datamotion/src/main/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageDataMotionStrategy.java

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,18 +24,17 @@
2424

2525
import javax.inject.Inject;
2626

27-
import com.cloud.storage.ScopeType;
28-
import com.cloud.storage.Storage;
2927
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
28+
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
3029
import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
3130
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateDataFactory;
3231
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateInfo;
3332
import org.apache.cloudstack.engine.subsystem.api.storage.VolumeInfo;
34-
import org.apache.cloudstack.engine.subsystem.api.storage.ObjectInDataStoreStateMachine;
3533
import org.apache.cloudstack.storage.command.CopyCommand;
3634
import org.apache.cloudstack.storage.datastore.DataStoreManagerImpl;
3735
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
3836
import org.apache.cloudstack.storage.to.TemplateObjectTO;
37+
import org.apache.commons.collections.MapUtils;
3938
import org.apache.commons.lang.StringUtils;
4039
import org.apache.log4j.Logger;
4140

@@ -47,6 +46,8 @@
4746
import com.cloud.host.Host;
4847
import com.cloud.hypervisor.Hypervisor.HypervisorType;
4948
import com.cloud.storage.DataStoreRole;
49+
import com.cloud.storage.ScopeType;
50+
import com.cloud.storage.Storage;
5051
import com.cloud.storage.Storage.StoragePoolType;
5152
import com.cloud.storage.StorageManager;
5253
import com.cloud.storage.StoragePool;
@@ -56,7 +57,6 @@
5657
import com.cloud.storage.dao.VMTemplatePoolDao;
5758
import com.cloud.utils.exception.CloudRuntimeException;
5859
import com.cloud.vm.VirtualMachineManager;
59-
import org.apache.commons.collections.MapUtils;
6060

6161
/**
6262
* Extends {@link StorageSystemDataMotionStrategy}, allowing KVM hosts to migrate VMs with the ROOT volume on a non managed local storage pool.
@@ -197,19 +197,21 @@ protected void copyTemplateToTargetFilesystemStorageIfNeeded(VolumeInfo srcVolum
197197
Host destHost) {
198198
VMTemplateStoragePoolVO sourceVolumeTemplateStoragePoolVO = vmTemplatePoolDao.findByPoolTemplate(destStoragePool.getId(), srcVolumeInfo.getTemplateId());
199199
if (sourceVolumeTemplateStoragePoolVO == null && destStoragePool.getPoolType() == StoragePoolType.Filesystem) {
200-
DataStore sourceTemplateDataStore = dataStoreManagerImpl.getImageStore(srcVolumeInfo.getDataCenterId());
201-
TemplateInfo sourceTemplateInfo = templateDataFactory.getTemplate(srcVolumeInfo.getTemplateId(), sourceTemplateDataStore);
202-
TemplateObjectTO sourceTemplate = new TemplateObjectTO(sourceTemplateInfo);
200+
DataStore sourceTemplateDataStore = dataStoreManagerImpl.getRandomImageStore(srcVolumeInfo.getDataCenterId());
201+
if (sourceTemplateDataStore != null) {
202+
TemplateInfo sourceTemplateInfo = templateDataFactory.getTemplate(srcVolumeInfo.getTemplateId(), sourceTemplateDataStore);
203+
TemplateObjectTO sourceTemplate = new TemplateObjectTO(sourceTemplateInfo);
203204

204-
LOGGER.debug(String.format("Could not find template [id=%s, name=%s] on the storage pool [id=%s]; copying the template to the target storage pool.",
205-
srcVolumeInfo.getTemplateId(), sourceTemplateInfo.getName(), destDataStore.getId()));
205+
LOGGER.debug(String.format("Could not find template [id=%s, name=%s] on the storage pool [id=%s]; copying the template to the target storage pool.",
206+
srcVolumeInfo.getTemplateId(), sourceTemplateInfo.getName(), destDataStore.getId()));
206207

207-
TemplateInfo destTemplateInfo = templateDataFactory.getTemplate(srcVolumeInfo.getTemplateId(), destDataStore);
208-
final TemplateObjectTO destTemplate = new TemplateObjectTO(destTemplateInfo);
209-
Answer copyCommandAnswer = sendCopyCommand(destHost, sourceTemplate, destTemplate, destDataStore);
208+
TemplateInfo destTemplateInfo = templateDataFactory.getTemplate(srcVolumeInfo.getTemplateId(), destDataStore);
209+
final TemplateObjectTO destTemplate = new TemplateObjectTO(destTemplateInfo);
210+
Answer copyCommandAnswer = sendCopyCommand(destHost, sourceTemplate, destTemplate, destDataStore);
210211

211-
if (copyCommandAnswer != null && copyCommandAnswer.getResult()) {
212-
updateTemplateReferenceIfSuccessfulCopy(srcVolumeInfo, srcStoragePool, destTemplateInfo, destDataStore);
212+
if (copyCommandAnswer != null && copyCommandAnswer.getResult()) {
213+
updateTemplateReferenceIfSuccessfulCopy(srcVolumeInfo, srcStoragePool, destTemplateInfo, destDataStore);
214+
}
213215
}
214216
}
215217
}

engine/storage/datamotion/src/test/java/org/apache/cloudstack/storage/motion/KvmNonManagedStorageSystemDataMotionTest.java

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,12 @@
1818
*/
1919
package org.apache.cloudstack.storage.motion;
2020

21+
import static org.junit.Assert.assertEquals;
22+
import static org.mockito.Mockito.when;
23+
2124
import java.util.HashMap;
2225
import java.util.Map;
2326

24-
import com.cloud.host.Host;
25-
import com.cloud.hypervisor.Hypervisor;
26-
import com.cloud.storage.ScopeType;
27-
import com.cloud.storage.Storage;
2827
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
2928
import org.apache.cloudstack.engine.subsystem.api.storage.StrategyPriority;
3029
import org.apache.cloudstack.engine.subsystem.api.storage.TemplateDataFactory;
@@ -58,21 +57,22 @@
5857
import com.cloud.exception.AgentUnavailableException;
5958
import com.cloud.exception.CloudException;
6059
import com.cloud.exception.OperationTimedoutException;
60+
import com.cloud.host.Host;
6161
import com.cloud.host.HostVO;
62+
import com.cloud.hypervisor.Hypervisor;
6263
import com.cloud.hypervisor.Hypervisor.HypervisorType;
6364
import com.cloud.storage.DataStoreRole;
64-
import com.cloud.storage.StoragePool;
65-
import com.cloud.storage.VMTemplateStoragePoolVO;
65+
import com.cloud.storage.ScopeType;
66+
import com.cloud.storage.Storage;
6667
import com.cloud.storage.Storage.ImageFormat;
6768
import com.cloud.storage.Storage.StoragePoolType;
69+
import com.cloud.storage.StoragePool;
70+
import com.cloud.storage.VMTemplateStoragePoolVO;
6871
import com.cloud.storage.dao.DiskOfferingDao;
6972
import com.cloud.storage.dao.VMTemplatePoolDao;
7073
import com.cloud.utils.exception.CloudRuntimeException;
7174
import com.cloud.vm.VirtualMachineManager;
7275

73-
import static org.junit.Assert.assertEquals;
74-
import static org.mockito.Mockito.when;
75-
7676
@RunWith(MockitoJUnitRunner.class)
7777
public class KvmNonManagedStorageSystemDataMotionTest {
7878

@@ -353,7 +353,7 @@ private void configureAndTestcopyTemplateToTargetStorageIfNeeded(VMTemplateStora
353353
Mockito.when(sourceTemplateInfo.getHypervisorType()).thenReturn(HypervisorType.KVM);
354354

355355
Mockito.when(vmTemplatePoolDao.findByPoolTemplate(Mockito.anyLong(), Mockito.anyLong())).thenReturn(vmTemplateStoragePoolVO);
356-
Mockito.when(dataStoreManagerImpl.getImageStore(Mockito.anyLong())).thenReturn(sourceTemplateDataStore);
356+
Mockito.when(dataStoreManagerImpl.getRandomImageStore(Mockito.anyLong())).thenReturn(sourceTemplateDataStore);
357357
Mockito.when(templateDataFactory.getTemplate(Mockito.anyLong(), Mockito.eq(sourceTemplateDataStore))).thenReturn(sourceTemplateInfo);
358358
Mockito.when(templateDataFactory.getTemplate(Mockito.anyLong(), Mockito.eq(destDataStore))).thenReturn(sourceTemplateInfo);
359359
kvmNonManagedStorageDataMotionStrategy.copyTemplateToTargetFilesystemStorageIfNeeded(srcVolumeInfo, srcStoragePool, destDataStore, destStoragePool, destHost);
@@ -362,7 +362,7 @@ private void configureAndTestcopyTemplateToTargetStorageIfNeeded(VMTemplateStora
362362

363363
InOrder verifyInOrder = Mockito.inOrder(vmTemplatePoolDao, dataStoreManagerImpl, templateDataFactory, kvmNonManagedStorageDataMotionStrategy);
364364
verifyInOrder.verify(vmTemplatePoolDao, Mockito.times(1)).findByPoolTemplate(Mockito.anyLong(), Mockito.anyLong());
365-
verifyInOrder.verify(dataStoreManagerImpl, Mockito.times(times)).getImageStore(Mockito.anyLong());
365+
verifyInOrder.verify(dataStoreManagerImpl, Mockito.times(times)).getRandomImageStore(Mockito.anyLong());
366366
verifyInOrder.verify(templateDataFactory, Mockito.times(times)).getTemplate(Mockito.anyLong(), Mockito.eq(sourceTemplateDataStore));
367367
verifyInOrder.verify(templateDataFactory, Mockito.times(times)).getTemplate(Mockito.anyLong(), Mockito.eq(destDataStore));
368368
verifyInOrder.verify(kvmNonManagedStorageDataMotionStrategy, Mockito.times(times)).sendCopyCommand(Mockito.eq(destHost), Mockito.any(TemplateObjectTO.class),

engine/storage/image/src/main/java/org/apache/cloudstack/storage/image/manager/ImageStoreProviderManagerImpl.java

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,17 +20,14 @@
2020

2121
import java.util.ArrayList;
2222
import java.util.Collections;
23+
import java.util.Comparator;
2324
import java.util.HashMap;
24-
import java.util.Iterator;
2525
import java.util.List;
2626
import java.util.Map;
2727

2828
import javax.annotation.PostConstruct;
2929
import javax.inject.Inject;
3030

31-
import org.apache.log4j.Logger;
32-
import org.springframework.stereotype.Component;
33-
3431
import org.apache.cloudstack.engine.subsystem.api.storage.DataStore;
3532
import org.apache.cloudstack.engine.subsystem.api.storage.DataStoreProviderManager;
3633
import org.apache.cloudstack.engine.subsystem.api.storage.ImageStoreProvider;
@@ -42,6 +39,8 @@
4239
import org.apache.cloudstack.storage.image.datastore.ImageStoreEntity;
4340
import org.apache.cloudstack.storage.image.datastore.ImageStoreProviderManager;
4441
import org.apache.cloudstack.storage.image.store.ImageStoreImpl;
42+
import org.apache.log4j.Logger;
43+
import org.springframework.stereotype.Component;
4544

4645
import com.cloud.server.StatsCollector;
4746
import com.cloud.storage.ScopeType;
@@ -144,19 +143,56 @@ public List<DataStore> listImageCacheStores(Scope scope) {
144143
}
145144

146145
@Override
147-
public DataStore getImageStore(List<DataStore> imageStores) {
146+
public DataStore getRandomImageStore(List<DataStore> imageStores) {
147+
if (imageStores.size() > 1) {
148+
Collections.shuffle(imageStores);
149+
}
150+
return imageStores.get(0);
151+
}
152+
153+
@Override
154+
public DataStore getImageStoreWithFreeCapacity(List<DataStore> imageStores) {
148155
if (imageStores.size() > 1) {
149-
Collections.shuffle(imageStores); // Randomize image store list.
150-
Iterator<DataStore> i = imageStores.iterator();
151-
DataStore imageStore = null;
152-
while(i.hasNext()) {
153-
imageStore = i.next();
156+
imageStores.sort(new Comparator<DataStore>() { // Sort data stores based on free capacity
157+
@Override
158+
public int compare(DataStore store1, DataStore store2) {
159+
return Long.compare(_statsCollector.imageStoreCurrentFreeCapacity(store1),
160+
_statsCollector.imageStoreCurrentFreeCapacity(store2));
161+
}
162+
});
163+
for (DataStore imageStore : imageStores) {
154164
// Return image store if used percentage is less then threshold value i.e. 90%.
155165
if (_statsCollector.imageStoreHasEnoughCapacity(imageStore)) {
156166
return imageStore;
157167
}
158168
}
169+
} else if (imageStores.size() == 1) {
170+
if (_statsCollector.imageStoreHasEnoughCapacity(imageStores.get(0))) {
171+
return imageStores.get(0);
172+
}
159173
}
160-
return imageStores.get(0);
174+
175+
// No store with space found
176+
s_logger.error(String.format("Can't find an image storage in zone with less than %d usage",
177+
Math.round(_statsCollector.getImageStoreCapacityThreshold()*100)));
178+
return null;
179+
}
180+
181+
@Override
182+
public List<DataStore> listImageStoresWithFreeCapacity(List<DataStore> imageStores) {
183+
List<DataStore> stores = new ArrayList<>();
184+
for (DataStore imageStore : imageStores) {
185+
// Return image store if used percentage is less then threshold value i.e. 90%.
186+
if (_statsCollector.imageStoreHasEnoughCapacity(imageStore)) {
187+
stores.add(imageStore);
188+
}
189+
}
190+
191+
// No store with space found
192+
if (stores.isEmpty()) {
193+
s_logger.error(String.format("Can't find image storage in zone with less than %d usage",
194+
Math.round(_statsCollector.getImageStoreCapacityThreshold() * 100)));
195+
}
196+
return stores;
161197
}
162198
}

engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/snapshot/SnapshotServiceImpl.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ private DataStore findSnapshotImageStore(SnapshotInfo snapshot) {
240240
fullSnapshot = snapshotFullBackup;
241241
}
242242
if (fullSnapshot) {
243-
return dataStoreMgr.getImageStore(snapshot.getDataCenterId());
243+
return dataStoreMgr.getImageStoreWithFreeCapacity(snapshot.getDataCenterId());
244244
} else {
245245
SnapshotInfo parentSnapshot = snapshot.getParent();
246246
// Note that DataStore information in parentSnapshot is for primary
@@ -251,7 +251,7 @@ private DataStore findSnapshotImageStore(SnapshotInfo snapshot) {
251251
parentSnapshotOnBackupStore = _snapshotStoreDao.findBySnapshot(parentSnapshot.getId(), DataStoreRole.Image);
252252
}
253253
if (parentSnapshotOnBackupStore == null) {
254-
return dataStoreMgr.getImageStore(snapshot.getDataCenterId());
254+
return dataStoreMgr.getImageStoreWithFreeCapacity(snapshot.getDataCenterId());
255255
}
256256
return dataStoreMgr.getDataStore(parentSnapshotOnBackupStore.getDataStoreId(), parentSnapshotOnBackupStore.getRole());
257257
}

engine/storage/src/main/java/org/apache/cloudstack/storage/datastore/DataStoreManagerImpl.java

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,30 @@ public List<DataStore> getImageStoresByScope(ZoneScope scope) {
7373
}
7474

7575
@Override
76-
public DataStore getImageStore(long zoneId) {
76+
public DataStore getRandomImageStore(long zoneId) {
7777
List<DataStore> stores = getImageStoresByScope(new ZoneScope(zoneId));
7878
if (stores == null || stores.size() == 0) {
7979
return null;
8080
}
81-
return imageDataStoreMgr.getImageStore(stores);
81+
return imageDataStoreMgr.getRandomImageStore(stores);
82+
}
83+
84+
@Override
85+
public DataStore getImageStoreWithFreeCapacity(long zoneId) {
86+
List<DataStore> stores = getImageStoresByScope(new ZoneScope(zoneId));
87+
if (stores == null || stores.size() == 0) {
88+
return null;
89+
}
90+
return imageDataStoreMgr.getImageStoreWithFreeCapacity(stores);
91+
}
92+
93+
@Override
94+
public List<DataStore> listImageStoresWithFreeCapacity(long zoneId) {
95+
List<DataStore> stores = getImageStoresByScope(new ZoneScope(zoneId));
96+
if (stores == null || stores.size() == 0) {
97+
return null;
98+
}
99+
return imageDataStoreMgr.listImageStoresWithFreeCapacity(stores);
82100
}
83101

84102
@Override
@@ -110,7 +128,7 @@ public DataStore getImageCacheStore(long zoneId) {
110128
if (stores == null || stores.size() == 0) {
111129
return null;
112130
}
113-
return imageDataStoreMgr.getImageStore(stores);
131+
return imageDataStoreMgr.getImageStoreWithFreeCapacity(stores);
114132
}
115133

116134
@Override

engine/storage/src/main/java/org/apache/cloudstack/storage/image/datastore/ImageStoreProviderManager.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,38 @@ public interface ImageStoreProviderManager {
4242

4343
boolean registerDriver(String uuid, ImageStoreDriver driver);
4444

45-
DataStore getImageStore(List<DataStore> imageStores);
45+
/**
46+
* Return a random DataStore from the a list of DataStores.
47+
*
48+
* @param imageStores the list of image stores from which a random store
49+
* to be returned
50+
* @return random DataStore
51+
*/
52+
DataStore getRandomImageStore(List<DataStore> imageStores);
53+
54+
/**
55+
* Return a DataStore which has free capacity. Stores will be sorted
56+
* based on their free space and capacity check will be done based on
57+
* the predefined threshold value. If a store is full beyond the
58+
* threshold it won't be considered for response. First store in the
59+
* sorted list free capacity will be returned. When there is no store
60+
* with free capacity in the list a null value will be returned.
61+
*
62+
* @param imageStores the list of image stores from which stores with free
63+
* capacity stores to be returned
64+
* @return the DataStore which has free capacity
65+
*/
66+
DataStore getImageStoreWithFreeCapacity(List<DataStore> imageStores);
67+
68+
/**
69+
* Return a list of DataStore which have free capacity. Free capacity check
70+
* will be done based on the predefined threshold value. If a store is full
71+
* beyond the threshold it won't be considered for response. An empty list
72+
* will be returned when no store in the parameter list has free capacity.
73+
*
74+
* @param imageStores the list of image stores from which stores with free
75+
* capacity stores to be returned
76+
* @return the list of DataStore which have free capacity
77+
*/
78+
List<DataStore> listImageStoresWithFreeCapacity(List<DataStore> imageStores);
4679
}

plugins/hypervisors/hyperv/src/main/java/com/cloud/hypervisor/hyperv/manager/HypervManagerImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ public String prepareSecondaryStorageStore(long zoneId) {
137137

138138
private String getSecondaryStorageStoreUrl(long zoneId) {
139139
String secUrl = null;
140-
DataStore secStore = _dataStoreMgr.getImageStore(zoneId);
140+
DataStore secStore = _dataStoreMgr.getImageStoreWithFreeCapacity(zoneId);
141141
if (secStore != null) {
142142
secUrl = secStore.getUri();
143143
}

0 commit comments

Comments
 (0)