Skip to content

Commit 2460238

Browse files
Add HPE Storage Pool Allocator implementation & maxfree volume allocation algorithm
1 parent 2dbc86a commit 2460238

File tree

5 files changed

+218
-5
lines changed

5 files changed

+218
-5
lines changed

core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@
117117
class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry">
118118
<property name="orderConfigKey" value="storage.pool.allocators.order" />
119119
<property name="orderConfigDefault"
120-
value="LocalStorage,ClusterScopeStoragePoolAllocator,ZoneWideStoragePoolAllocator" />
120+
value="HPEStorage,LocalStorage,ClusterScopeStoragePoolAllocator,ZoneWideStoragePoolAllocator" />
121121
<property name="excludeKey" value="storage.pool.allocators.exclude" />
122122
<property name="excludeDefault" value="GCStorage" />
123123
</bean>

engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,11 @@ public interface VolumeOrchestrationService {
9090
"volume.allocation.algorithm",
9191
"Advanced",
9292
"random",
93-
"Order in which storage pool within a cluster will be considered for volume allocation. The value can be 'random', 'firstfit', 'userdispersing', 'userconcentratedpod_random', 'userconcentratedpod_firstfit', or 'firstfitleastconsumed'.",
93+
"Order in which storage pool within a cluster will be considered for volume allocation. The value can be 'random', 'firstfit', 'userdispersing', 'userconcentratedpod_random', 'userconcentratedpod_firstfit', 'firstfitleastconsumed', or 'maxfree'.",
9494
true,
9595
ConfigKey.Scope.Global, null, null, null, null, null,
9696
ConfigKey.Kind.Select,
97-
"random,firstfit,userdispersing,userconcentratedpod_random,userconcentratedpod_firstfit,firstfitleastconsumed");
97+
"random,firstfit,userdispersing,userconcentratedpod_random,userconcentratedpod_firstfit,firstfitleastconsumed,maxfree");
9898

9999
VolumeInfo moveVolume(VolumeInfo volume, long destPoolDcId, Long destPoolPodId, Long destPoolClusterId, HypervisorType dataDiskHyperType)
100100
throws ConcurrentOperationException, StorageUnavailableException;

engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,30 @@ protected List<StoragePool> reorderPoolsByNumberOfVolumes(DeploymentPlan plan, L
204204
return reorderedPools;
205205
}
206206

207+
/**
208+
* Reorders storage pools based on available free space in descending order.
209+
* Pools with the most available free space will be prioritized first.
210+
*
211+
* @param pools List of storage pools to be reordered
212+
* @return List of storage pools sorted by free space (highest to lowest)
213+
*/
214+
protected List<StoragePool> reorderPoolsByFreeSpace(List<StoragePool> pools) {
215+
if (CollectionUtils.isEmpty(pools)) {
216+
logger.debug("No storage pools provided for free space reordering, returning original list.");
217+
return pools;
218+
}
219+
220+
List<StoragePool> sortedPools = new ArrayList<>(pools);
221+
222+
sortedPools.sort((p1, p2) -> {
223+
long free1 = p1.getCapacityBytes() - p1.getUsedBytes();
224+
long free2 = p2.getCapacityBytes() - p2.getUsedBytes();
225+
return Long.compare(free2, free1);
226+
});
227+
228+
logger.debug("Storage pools reordered by free space (descending): {}", sortedPools);
229+
return sortedPools;
230+
}
207231
@Override
208232
public List<StoragePool> reorderPools(List<StoragePool> pools, VirtualMachineProfile vmProfile, DeploymentPlan plan, DiskProfile dskCh) {
209233
if (logger.isTraceEnabled()) {
@@ -238,9 +262,14 @@ public List<StoragePool> reorderPools(List<StoragePool> pools, VirtualMachinePro
238262
return pools;
239263
}
240264

265+
/**
266+
* Reorders storage pools based on the configured volume allocation algorithm.
267+
* Different algorithms provide different strategies for pool selection and ordering.
268+
*/
241269
List<StoragePool> reorderStoragePoolsBasedOnAlgorithm(List<StoragePool> pools, DeploymentPlan plan, Account account) {
242270
String volumeAllocationAlgorithm = VolumeOrchestrationService.VolumeAllocationAlgorithm.value();
243-
logger.debug("Using volume allocation algorithm {} to reorder pools.", volumeAllocationAlgorithm);
271+
logger.debug("Using volume allocation algorithm '{}' to reorder storage pools.", volumeAllocationAlgorithm);
272+
244273
if (volumeAllocationAlgorithm.equals("random") || volumeAllocationAlgorithm.equals("userconcentratedpod_random") || (account == null)) {
245274
reorderRandomPools(pools);
246275
} else if (StringUtils.equalsAny(volumeAllocationAlgorithm, "userdispersing", "firstfitleastconsumed")) {
@@ -253,6 +282,10 @@ List<StoragePool> reorderStoragePoolsBasedOnAlgorithm(List<StoragePool> pools, D
253282
} else {
254283
pools = reorderPoolsByCapacity(plan, pools);
255284
}
285+
} else if (volumeAllocationAlgorithm.equals("maxfree")) {
286+
// MaxFree algorithm: Prioritize pools with maximum available free space
287+
logger.debug("Applying maxfree algorithm - prioritizing storage pools with most available free space.");
288+
pools = reorderPoolsByFreeSpace(pools);
256289
}
257290
return pools;
258291
}
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
package org.apache.cloudstack.storage.allocator;
18+
19+
import java.util.ArrayList;
20+
import java.util.Arrays;
21+
import java.util.List;
22+
23+
import com.cloud.storage.VolumeApiServiceImpl;
24+
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
25+
import org.springframework.stereotype.Component;
26+
27+
import com.cloud.deploy.DeploymentPlan;
28+
import com.cloud.deploy.DeploymentPlanner.ExcludeList;
29+
import com.cloud.storage.ScopeType;
30+
import com.cloud.storage.StoragePool;
31+
import com.cloud.vm.DiskProfile;
32+
import com.cloud.vm.VirtualMachineProfile;
33+
34+
/**
35+
* HPE Storage Pool Allocator
36+
*
37+
* This allocator is specifically designed for HPE storage systems and filters
38+
* storage pools based on the storage provider name being "Primera". It extends the
39+
* AbstractStoragePoolAllocator to provide specialized allocation logic for
40+
* HPE storage infrastructure.
41+
*
42+
* Key features:
43+
* - Filters pools by HPE storage provider name
44+
* - Supports cluster-scoped storage allocation
45+
* - Handles storage tags for pool selection
46+
* - Implements proper fallback mechanism when no suitable pools are found
47+
*
48+
* @author CloudStack Development Team
49+
*/
50+
@Component
51+
public class HPEStoragePoolAllocator extends AbstractStoragePoolAllocator {
52+
53+
/**
54+
* Selects suitable storage pools for volume allocation based on HPE storage criteria.
55+
*
56+
* This method implements the core allocation logic for HPE storage systems by:
57+
* 1. Validating storage type requirements (local vs shared)
58+
* 2. Finding pools that match deployment plan criteria (datacenter, pod, cluster)
59+
* 3. Filtering pools by storage tags if specified
60+
* 4. Selecting only pools with "HPE" as storage provider name
61+
* 5. Applying additional filtering based on capacity, compatibility, etc.
62+
*
63+
* @param dskCh Disk profile containing volume requirements and specifications
64+
* @param vmProfile Virtual machine profile for which storage is being allocated
65+
* @param plan Deployment plan specifying datacenter, pod, and cluster constraints
66+
* @param avoid List of storage pools to exclude from allocation
67+
* @param returnUpTo Maximum number of storage pools to return
68+
* @param bypassStorageTypeCheck Flag to bypass local/shared storage type validation
69+
* @param keyword Additional keyword filter for pool selection
70+
* @return List of suitable HPE storage pools, or null if no suitable pools found
71+
*/
72+
@Override
73+
protected List<StoragePool> select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck, String keyword) {
74+
// Log entry into HPE storage allocator with detailed parameters for debugging
75+
logger.debug("Starting pool selection process.");
76+
77+
// Log the start of search using parent class logging method
78+
logStartOfSearch(dskCh, vmProfile, plan, returnUpTo, bypassStorageTypeCheck);
79+
80+
// Early return if local storage is required but storage type check is not bypassed
81+
// HPE allocator is designed for shared storage, so we skip local storage requests
82+
if (!bypassStorageTypeCheck && dskCh.useLocalStorage()) {
83+
logger.debug("Skipping allocation as local storage is required but HPE allocator handles shared storage only.");
84+
return null;
85+
}
86+
87+
// Initialize list to collect suitable storage pools
88+
List<StoragePool> suitablePools = new ArrayList<>();
89+
90+
// Extract deployment constraints from the plan
91+
long dcId = plan.getDataCenterId();
92+
Long podId = plan.getPodId();
93+
Long clusterId = plan.getClusterId();
94+
95+
// Zone-wide storage is not supported by this allocator (requires pod-level scope)
96+
if (podId == null) {
97+
logger.debug("Returning null as pod ID is null. This may indicate zone-wide storage which is not supported by HPE allocator.");
98+
return null;
99+
}
100+
101+
// Log search criteria based on whether storage tags are specified
102+
if (dskCh.getTags() != null && dskCh.getTags().length != 0) {
103+
logger.debug("Searching for HPE pools in datacenter [{}], pod [{}], cluster [{}] with storage tags [{}]. Disabled pools will be excluded.",
104+
dcId, podId, clusterId, Arrays.toString(dskCh.getTags()));
105+
} else {
106+
logger.debug("Searching for HPE pools in datacenter [{}], pod [{}], cluster [{}] without specific storage tags. Disabled pools will be excluded.",
107+
dcId, podId, clusterId);
108+
}
109+
110+
// Log disabled pools if trace logging is enabled
111+
if (logger.isTraceEnabled()) {
112+
logDisabledStoragePools(dcId, podId, clusterId, ScopeType.CLUSTER);
113+
}
114+
115+
// Find storage pools that match the deployment criteria and tags
116+
List<StoragePoolVO> pools = storagePoolDao.findPoolsByTags(dcId, podId, clusterId, ScopeType.CLUSTER, dskCh.getTags(), true, VolumeApiServiceImpl.storageTagRuleExecutionTimeout.value());
117+
pools.addAll(storagePoolJoinDao.findStoragePoolByScopeAndRuleTags(dcId, podId, clusterId, ScopeType.CLUSTER, List.of(dskCh.getTags())));
118+
119+
logger.debug("Found {} candidate pools matching deployment criteria and tags [{}]: {}",
120+
pools.size(), Arrays.toString(dskCh.getTags()), pools);
121+
122+
// Add remaining pools in cluster that didn't match tags to the avoid set
123+
// This ensures they won't be considered by subsequent allocators either
124+
List<StoragePoolVO> allPools = storagePoolDao.findPoolsByTags(dcId, podId, clusterId, ScopeType.CLUSTER, null, false, 0);
125+
allPools.removeAll(pools);
126+
for (StoragePoolVO pool : allPools) {
127+
logger.trace("Adding pool [{}] to avoid set as it did not match required storage tags.", pool);
128+
avoid.addPool(pool.getId());
129+
}
130+
131+
// Early return if no candidate pools were found
132+
if (pools.isEmpty()) {
133+
logger.debug("No storage pools available for HPE storage allocation in the specified scope.");
134+
return null; // Return null to allow other allocators to attempt allocation
135+
}
136+
137+
// Iterate through candidate pools and filter by HPE storage provider name
138+
for (StoragePoolVO pool : pools) {
139+
// Stop if we've reached the maximum number of pools to return
140+
if (suitablePools.size() == returnUpTo) {
141+
logger.debug("Reached maximum number of pools to return ({}), stopping search.", returnUpTo);
142+
break;
143+
}
144+
145+
// Filter by Primera storage provider name (exact case match)
146+
if ("Primera".equals(pool.getStorageProviderName())) {
147+
StoragePool storagePool = (StoragePool)dataStoreMgr.getPrimaryDataStore(pool.getId());
148+
149+
// Apply comprehensive filtering (capacity, compatibility, maintenance status, etc.)
150+
if (filter(avoid, storagePool, dskCh, plan)) {
151+
logger.debug("Found suitable HPE storage pool [{}] with provider [{}] for disk allocation [{}]. Adding to candidate list.",
152+
pool.getName(), pool.getStorageProviderName(), dskCh);
153+
suitablePools.add(storagePool);
154+
} else {
155+
logger.debug("HPE storage pool [{}] did not pass filtering checks for disk allocation [{}]. Adding to avoid set.",
156+
pool.getName(), dskCh);
157+
avoid.addPool(pool.getId());
158+
}
159+
} else {
160+
// Log pools that don't match HPE provider name for debugging purposes
161+
logger.debug("Storage pool [{}] with provider [{}] is not an HPE storage pool. Skipping.",
162+
pool.getName(), pool.getStorageProviderName());
163+
}
164+
}
165+
166+
// Final check: if no HPE pools were found after filtering, return null
167+
// This allows other allocators in the chain to attempt allocation
168+
if (suitablePools.isEmpty()) {
169+
logger.debug("No HPE storage pools found after filtering. All candidate pools had different storage providers. Returning null to allow other allocators to attempt allocation.");
170+
return null;
171+
}
172+
173+
// Log successful completion with final pool count
174+
logEndOfSearch(suitablePools);
175+
return suitablePools;
176+
}
177+
}

engine/storage/src/main/resources/META-INF/cloudstack/storage-allocator/spring-engine-storage-storage-allocator-context.xml

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,10 @@
2626
http://www.springframework.org/schema/context
2727
http://www.springframework.org/schema/context/spring-context.xsd"
2828
>
29-
29+
<bean id="HPEStoragePoolAllocator"
30+
class="org.apache.cloudstack.storage.allocator.HPEStoragePoolAllocator">
31+
<property name="name" value="HPEStorage" />
32+
</bean>
3033
<bean id="LocalStoragePoolAllocator"
3134
class="org.apache.cloudstack.storage.allocator.LocalStoragePoolAllocator">
3235
<property name="name" value="LocalStorage" />

0 commit comments

Comments
 (0)