diff --git a/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml b/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml index 01c568d78916..722e0f85ec48 100644 --- a/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml +++ b/core/src/main/resources/META-INF/cloudstack/core/spring-core-registry-core-context.xml @@ -117,7 +117,7 @@ class="org.apache.cloudstack.spring.lifecycle.registry.ExtensionRegistry"> + value="HPEStorage,LocalStorage,ClusterScopeStoragePoolAllocator,ZoneWideStoragePoolAllocator" /> diff --git a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java index 168822c21ebc..7199f7c46bc9 100644 --- a/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java +++ b/engine/api/src/main/java/org/apache/cloudstack/engine/orchestration/service/VolumeOrchestrationService.java @@ -90,11 +90,11 @@ public interface VolumeOrchestrationService { "volume.allocation.algorithm", "Advanced", "random", - "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'.", + "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'.", true, ConfigKey.Scope.Global, null, null, null, null, null, ConfigKey.Kind.Select, - "random,firstfit,userdispersing,userconcentratedpod_random,userconcentratedpod_firstfit,firstfitleastconsumed"); + "random,firstfit,userdispersing,userconcentratedpod_random,userconcentratedpod_firstfit,firstfitleastconsumed,maxfree"); VolumeInfo moveVolume(VolumeInfo volume, long destPoolDcId, Long destPoolPodId, Long destPoolClusterId, HypervisorType dataDiskHyperType) throws ConcurrentOperationException, StorageUnavailableException; diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java index e0e244d53d3c..117ca4d0a62d 100644 --- a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/AbstractStoragePoolAllocator.java @@ -204,6 +204,30 @@ protected List reorderPoolsByNumberOfVolumes(DeploymentPlan plan, L return reorderedPools; } + /** + * Reorders storage pools based on available free space in descending order. + * Pools with the most available free space will be prioritized first. + * + * @param pools List of storage pools to be reordered + * @return List of storage pools sorted by free space (highest to lowest) + */ + protected List reorderPoolsByFreeSpace(List pools) { + if (CollectionUtils.isEmpty(pools)) { + logger.debug("No storage pools provided for free space reordering, returning original list."); + return pools; + } + + List sortedPools = new ArrayList<>(pools); + + sortedPools.sort((p1, p2) -> { + long free1 = p1.getCapacityBytes() - p1.getUsedBytes(); + long free2 = p2.getCapacityBytes() - p2.getUsedBytes(); + return Long.compare(free2, free1); + }); + + logger.debug("Storage pools reordered by free space (descending): {}", sortedPools); + return sortedPools; + } @Override public List reorderPools(List pools, VirtualMachineProfile vmProfile, DeploymentPlan plan, DiskProfile dskCh) { if (logger.isTraceEnabled()) { @@ -238,9 +262,14 @@ public List reorderPools(List pools, VirtualMachinePro return pools; } + /** + * Reorders storage pools based on the configured volume allocation algorithm. + * Different algorithms provide different strategies for pool selection and ordering. + */ List reorderStoragePoolsBasedOnAlgorithm(List pools, DeploymentPlan plan, Account account) { String volumeAllocationAlgorithm = VolumeOrchestrationService.VolumeAllocationAlgorithm.value(); - logger.debug("Using volume allocation algorithm {} to reorder pools.", volumeAllocationAlgorithm); + logger.debug("Using volume allocation algorithm '{}' to reorder storage pools.", volumeAllocationAlgorithm); + if (volumeAllocationAlgorithm.equals("random") || volumeAllocationAlgorithm.equals("userconcentratedpod_random") || (account == null)) { reorderRandomPools(pools); } else if (StringUtils.equalsAny(volumeAllocationAlgorithm, "userdispersing", "firstfitleastconsumed")) { @@ -253,6 +282,10 @@ List reorderStoragePoolsBasedOnAlgorithm(List pools, D } else { pools = reorderPoolsByCapacity(plan, pools); } + } else if (volumeAllocationAlgorithm.equals("maxfree")) { + // MaxFree algorithm: Prioritize pools with maximum available free space + logger.debug("Applying maxfree algorithm - prioritizing storage pools with most available free space."); + pools = reorderPoolsByFreeSpace(pools); } return pools; } diff --git a/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/HPEStoragePoolAllocator.java b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/HPEStoragePoolAllocator.java new file mode 100644 index 000000000000..b216063c1982 --- /dev/null +++ b/engine/storage/src/main/java/org/apache/cloudstack/storage/allocator/HPEStoragePoolAllocator.java @@ -0,0 +1,177 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. +package org.apache.cloudstack.storage.allocator; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.cloud.storage.VolumeApiServiceImpl; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.springframework.stereotype.Component; + +import com.cloud.deploy.DeploymentPlan; +import com.cloud.deploy.DeploymentPlanner.ExcludeList; +import com.cloud.storage.ScopeType; +import com.cloud.storage.StoragePool; +import com.cloud.vm.DiskProfile; +import com.cloud.vm.VirtualMachineProfile; + +/** + * HPE Storage Pool Allocator + * + * This allocator is specifically designed for HPE storage systems and filters + * storage pools based on the storage provider name being "Primera". It extends the + * AbstractStoragePoolAllocator to provide specialized allocation logic for + * HPE storage infrastructure. + * + * Key features: + * - Filters pools by HPE storage provider name + * - Supports cluster-scoped storage allocation + * - Handles storage tags for pool selection + * - Implements proper fallback mechanism when no suitable pools are found + * + * @author CloudStack Development Team + */ +@Component +public class HPEStoragePoolAllocator extends AbstractStoragePoolAllocator { + + /** + * Selects suitable storage pools for volume allocation based on HPE storage criteria. + * + * This method implements the core allocation logic for HPE storage systems by: + * 1. Validating storage type requirements (local vs shared) + * 2. Finding pools that match deployment plan criteria (datacenter, pod, cluster) + * 3. Filtering pools by storage tags if specified + * 4. Selecting only pools with "HPE" as storage provider name + * 5. Applying additional filtering based on capacity, compatibility, etc. + * + * @param dskCh Disk profile containing volume requirements and specifications + * @param vmProfile Virtual machine profile for which storage is being allocated + * @param plan Deployment plan specifying datacenter, pod, and cluster constraints + * @param avoid List of storage pools to exclude from allocation + * @param returnUpTo Maximum number of storage pools to return + * @param bypassStorageTypeCheck Flag to bypass local/shared storage type validation + * @param keyword Additional keyword filter for pool selection + * @return List of suitable HPE storage pools, or null if no suitable pools found + */ + @Override + protected List select(DiskProfile dskCh, VirtualMachineProfile vmProfile, DeploymentPlan plan, ExcludeList avoid, int returnUpTo, boolean bypassStorageTypeCheck, String keyword) { + // Log entry into HPE storage allocator with detailed parameters for debugging + logger.debug("Starting pool selection process."); + + // Log the start of search using parent class logging method + logStartOfSearch(dskCh, vmProfile, plan, returnUpTo, bypassStorageTypeCheck); + + // Early return if local storage is required but storage type check is not bypassed + // HPE allocator is designed for shared storage, so we skip local storage requests + if (!bypassStorageTypeCheck && dskCh.useLocalStorage()) { + logger.debug("Skipping allocation as local storage is required but HPE allocator handles shared storage only."); + return null; + } + + // Initialize list to collect suitable storage pools + List suitablePools = new ArrayList<>(); + + // Extract deployment constraints from the plan + long dcId = plan.getDataCenterId(); + Long podId = plan.getPodId(); + Long clusterId = plan.getClusterId(); + + // Zone-wide storage is not supported by this allocator (requires pod-level scope) + if (podId == null) { + logger.debug("Returning null as pod ID is null. This may indicate zone-wide storage which is not supported by HPE allocator."); + return null; + } + + // Log search criteria based on whether storage tags are specified + if (dskCh.getTags() != null && dskCh.getTags().length != 0) { + logger.debug("Searching for HPE pools in datacenter [{}], pod [{}], cluster [{}] with storage tags [{}]. Disabled pools will be excluded.", + dcId, podId, clusterId, Arrays.toString(dskCh.getTags())); + } else { + logger.debug("Searching for HPE pools in datacenter [{}], pod [{}], cluster [{}] without specific storage tags. Disabled pools will be excluded.", + dcId, podId, clusterId); + } + + // Log disabled pools if trace logging is enabled + if (logger.isTraceEnabled()) { + logDisabledStoragePools(dcId, podId, clusterId, ScopeType.CLUSTER); + } + + // Find storage pools that match the deployment criteria and tags + List pools = storagePoolDao.findPoolsByTags(dcId, podId, clusterId, ScopeType.CLUSTER, dskCh.getTags(), true, VolumeApiServiceImpl.storageTagRuleExecutionTimeout.value()); + pools.addAll(storagePoolJoinDao.findStoragePoolByScopeAndRuleTags(dcId, podId, clusterId, ScopeType.CLUSTER, List.of(dskCh.getTags()))); + + logger.debug("Found {} candidate pools matching deployment criteria and tags [{}]: {}", + pools.size(), Arrays.toString(dskCh.getTags()), pools); + + // Add remaining pools in cluster that didn't match tags to the avoid set + // This ensures they won't be considered by subsequent allocators either + List allPools = storagePoolDao.findPoolsByTags(dcId, podId, clusterId, ScopeType.CLUSTER, null, false, 0); + allPools.removeAll(pools); + for (StoragePoolVO pool : allPools) { + logger.trace("Adding pool [{}] to avoid set as it did not match required storage tags.", pool); + avoid.addPool(pool.getId()); + } + + // Early return if no candidate pools were found + if (pools.isEmpty()) { + logger.debug("No storage pools available for HPE storage allocation in the specified scope."); + return null; // Return null to allow other allocators to attempt allocation + } + + // Iterate through candidate pools and filter by HPE storage provider name + for (StoragePoolVO pool : pools) { + // Stop if we've reached the maximum number of pools to return + if (suitablePools.size() == returnUpTo) { + logger.debug("Reached maximum number of pools to return ({}), stopping search.", returnUpTo); + break; + } + + // Filter by Primera storage provider name (exact case match) + if ("Primera".equals(pool.getStorageProviderName())) { + StoragePool storagePool = (StoragePool)dataStoreMgr.getPrimaryDataStore(pool.getId()); + + // Apply comprehensive filtering (capacity, compatibility, maintenance status, etc.) + if (filter(avoid, storagePool, dskCh, plan)) { + logger.debug("Found suitable HPE storage pool [{}] with provider [{}] for disk allocation [{}]. Adding to candidate list.", + pool.getName(), pool.getStorageProviderName(), dskCh); + suitablePools.add(storagePool); + } else { + logger.debug("HPE storage pool [{}] did not pass filtering checks for disk allocation [{}]. Adding to avoid set.", + pool.getName(), dskCh); + avoid.addPool(pool.getId()); + } + } else { + // Log pools that don't match HPE provider name for debugging purposes + logger.debug("Storage pool [{}] with provider [{}] is not an HPE storage pool. Skipping.", + pool.getName(), pool.getStorageProviderName()); + } + } + + // Final check: if no HPE pools were found after filtering, return null + // This allows other allocators in the chain to attempt allocation + if (suitablePools.isEmpty()) { + 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."); + return null; + } + + // Log successful completion with final pool count + logEndOfSearch(suitablePools); + return suitablePools; + } +} diff --git a/engine/storage/src/main/resources/META-INF/cloudstack/storage-allocator/spring-engine-storage-storage-allocator-context.xml b/engine/storage/src/main/resources/META-INF/cloudstack/storage-allocator/spring-engine-storage-storage-allocator-context.xml index e68d2c673092..bb7fff41d58d 100644 --- a/engine/storage/src/main/resources/META-INF/cloudstack/storage-allocator/spring-engine-storage-storage-allocator-context.xml +++ b/engine/storage/src/main/resources/META-INF/cloudstack/storage-allocator/spring-engine-storage-storage-allocator-context.xml @@ -26,7 +26,10 @@ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd" > - + + +